fix(site-builder): published Text/Heading keep plain-text newlines as <br> like the canvas

Canvas renders plain text via textToInlineHtml (newline -> <br>); toHtml used escapeHtml,
so line breaks typed in the editor vanished on publish (TTM about page: ~100px shorter).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
shadowdaoandClaude Sonnet 5.5 committed 2026-11-01 09:16:26 -08:00
1 parent 7d77201e5a
commit d75f9d90ca
5 files changed
+20 -8

No files matched your search

@@ -154,3 +154,10 @@ describe('Heading.toHtml inline rich text (#46)', () => {
test('Heading default colour is the text token', () => {
expect((Heading as any).craft.props.style.color).toBe('var(--wsb-text)');
});
describe('Heading.toHtml newlines (canvas parity)', () => {
test('plain text newlines become <br>, like the editor canvas', () => {
const { html } = (Heading as any).toHtml({ text: 'a\nb', level: 'h2' }, '');
expect(html).toContain('a<br>b');
});
});
+2 -3
View File
@@ -1,8 +1,7 @@
import React, { CSSProperties } from 'react';
import { useNode, UserComponent } from '@craftjs/core';
import { cssPropsToString } from '../../utils/style-helpers';
import { escapeHtml } from '../../utils/escape';
import { resolveInlineHtml } from '../../utils/inline-html';
import { resolveInlineHtml, textToInlineHtml } from '../../utils/inline-html';
import { tokenRef } from '../../utils/tokens';
import { useInlineEditable } from './useInlineEditable';
@@ -102,7 +101,7 @@ Heading.craft = {
(Heading as any).toHtml = (props: HeadingProps, _childrenHtml: string) => {
const tag = sanitizeHeadingLevel(props.level);
const safeText = resolveInlineHtml(props.text, props.html) ?? escapeHtml(props.text || '');
const safeText = resolveInlineHtml(props.text, props.html) ?? textToInlineHtml(props.text || '');
const styleStr = cssPropsToString(props.style);
return { html: `<${tag}${styleStr ? ` style="${styleStr}"` : ''}>${safeText}</${tag}>` };
};
@@ -117,3 +117,10 @@ describe('TextBlock.toHtml design tokens (#44)', () => {
for (const hex of ["#3f3f46"]) expect(lower, `old ${hex}`).not.toContain(hex);
});
});
describe('TextBlock.toHtml newlines (canvas parity)', () => {
test('plain text newlines become <br>, like the editor canvas (textToInlineHtml)', () => {
const { html } = toHtml({ text: 'a\n\nb <i>' }, '');
expect(html).toBe('<p>a<br><br>b &lt;i&gt;</p>');
});
});
+2 -3
View File
@@ -2,8 +2,7 @@ import React, { CSSProperties } from 'react';
import { tokenRef } from '../../utils/tokens';
import { useNode, UserComponent } from '@craftjs/core';
import { cssPropsToString } from '../../utils/style-helpers';
import { escapeHtml } from '../../utils/escape';
import { resolveInlineHtml } from '../../utils/inline-html';
import { resolveInlineHtml, textToInlineHtml } from '../../utils/inline-html';
import { useInlineEditable } from './useInlineEditable';
interface TextBlockProps {
@@ -94,6 +93,6 @@ TextBlock.craft = {
(TextBlock as any).toHtml = (props: TextBlockProps, _childrenHtml: string) => {
const styleStr = cssPropsToString(props.style);
const escapedText = resolveInlineHtml(props.text, props.html) ?? escapeHtml(props.text || '');
const escapedText = resolveInlineHtml(props.text, props.html) ?? textToInlineHtml(props.text || '');
return { html: `<p${styleStr ? ` style="${styleStr}"` : ''}>${escapedText}</p>` };
};
+2 -2
View File
@@ -205,10 +205,10 @@ describe('responsiveCssForNode', () => {
import { dropBlankStyle as _dropBlankStyle } from './responsive-style';
describe('dropBlankStyle (canvas == exporter: blanks are not declarations)', () => {
it('drops "", null, undefined; keeps 0 and real values', () => {
test('drops "", null, undefined; keeps 0 and real values', () => {
expect(_dropBlankStyle({ padding: '20px', paddingTop: '', a: null, b: undefined, opacity: 0, zIndex: '0' } as any)).toEqual({ padding: '20px', opacity: 0, zIndex: '0' });
});
it('returns the same object when nothing is blank, and passes non-objects through', () => {
test('returns the same object when nothing is blank, and passes non-objects through', () => {
const s = { color: 'red' };
expect(_dropBlankStyle(s)).toBe(s);
expect(_dropBlankStyle(undefined)).toBeUndefined();