diff --git a/packages/rich-text-html-renderer/src/__test__/index.test.ts b/packages/rich-text-html-renderer/src/__test__/index.test.ts index a25db3d4..ea70f06e 100644 --- a/packages/rich-text-html-renderer/src/__test__/index.test.ts +++ b/packages/rich-text-html-renderer/src/__test__/index.test.ts @@ -332,4 +332,85 @@ describe('documentToHtmlString', () => { it('does not crash with undefined documents', () => { expect(documentToHtmlString(undefined as Document)).toEqual(''); }); + + it('preserves whitespace with preserveWhitespace option', () => { + const document: Document = { + nodeType: BLOCKS.DOCUMENT, + data: {}, + content: [ + { + nodeType: BLOCKS.PARAGRAPH, + data: {}, + content: [ + { + nodeType: 'text', + value: 'hello world', + marks: [], + data: {}, + }, + ], + }, + ], + }; + const options: Options = { + preserveWhitespace: true, + }; + const expected = '
hello world
'; + + expect(documentToHtmlString(document, options)).toEqual(expected); + }); + + it('preserves line breaks with preserveWhitespace option', () => { + const document: Document = { + nodeType: BLOCKS.DOCUMENT, + data: {}, + content: [ + { + nodeType: BLOCKS.PARAGRAPH, + data: {}, + content: [ + { + nodeType: 'text', + value: 'hello\nworld', + marks: [], + data: {}, + }, + ], + }, + ], + }; + const options: Options = { + preserveWhitespace: true, + }; + const expected = 'hello
world
hello
world
- + - type: + type: asset-hyperlink - id: + id: 9mpxT4zsRi6Iwukey8KeM - +
, ] `; @@ -49,14 +49,14 @@ Array [ exports[`documentToReactComponents renders embedded entry 1`] = ` Array [- + - type: + type: embedded-entry-inline - id: + id: 9mpxT4zsRi6Iwukey8KeM - +
, ] `; @@ -72,14 +72,14 @@ Array [ exports[`documentToReactComponents renders entry hyperlink 1`] = ` Array [- + - type: + type: entry-hyperlink - id: + id: 9mpxT4zsRi6Iwukey8KeM - +
, ] `; @@ -91,7 +91,7 @@ Array [ ,- +
, ] `; @@ -99,7 +99,7 @@ Array [ exports[`documentToReactComponents renders hyperlink 1`] = ` Array [
- Some text
+ Some text
@@ -244,7 +244,7 @@ Array [
,
-
+
-
+
- +
, ] `; @@ -421,3 +421,21 @@ exports[`nodeToReactComponent renders valid nodes 1`] = ` hello world `; + +exports[`preserveWhitespace preserves new lines 1`] = ` +Array [ +
+ hello
+
+ world
+
+ hello world +
, +] +`; diff --git a/packages/rich-text-react-renderer/src/__test__/index.test.tsx b/packages/rich-text-react-renderer/src/__test__/index.test.tsx index 10dfa9de..37e98ca1 100644 --- a/packages/rich-text-react-renderer/src/__test__/index.test.tsx +++ b/packages/rich-text-react-renderer/src/__test__/index.test.tsx @@ -404,3 +404,55 @@ describe('nodeListToReactComponents', () => { expect(renderedNodes).toMatchSnapshot(); }); }); + +describe.only('preserveWhitespace', () => { + it('preserves spaces between words', () => { + const options: Options = { + preserveWhitespace: true, + }; + const document: Document = { + nodeType: BLOCKS.DOCUMENT, + data: {}, + content: [ + { + nodeType: BLOCKS.PARAGRAPH, + data: {}, + content: [ + { + nodeType: 'text', + value: 'hello world', + marks: [], + data: {}, + }, + ], + }, + ], + }; + expect(documentToReactComponents(document, options)).toMatchSnapshot(); + }); + + it('preserves new lines', () => { + const options: Options = { + preserveWhitespace: true, + }; + const document: Document = { + nodeType: BLOCKS.DOCUMENT, + data: {}, + content: [ + { + nodeType: BLOCKS.PARAGRAPH, + data: {}, + content: [ + { + nodeType: 'text', + value: 'hello\nworld', + marks: [], + data: {}, + }, + ], + }, + ], + }; + expect(documentToReactComponents(document, options)).toMatchSnapshot(); + }); +}); diff --git a/packages/rich-text-react-renderer/src/index.tsx b/packages/rich-text-react-renderer/src/index.tsx index c6fd10c3..56723722 100644 --- a/packages/rich-text-react-renderer/src/index.tsx +++ b/packages/rich-text-react-renderer/src/index.tsx @@ -80,6 +80,10 @@ export interface Options { * Text renderer */ renderText?: RenderText; + /** + * Keep line breaks and multiple spaces + */ + preserveWhitespace?: boolean; } /** @@ -103,5 +107,6 @@ export function documentToReactComponents( ...options.renderMark, }, renderText: options.renderText, + preserveWhitespace: options.preserveWhitespace, }); } diff --git a/packages/rich-text-react-renderer/src/util/nodeListToReactComponents.tsx b/packages/rich-text-react-renderer/src/util/nodeListToReactComponents.tsx index b01875d4..800a79f5 100644 --- a/packages/rich-text-react-renderer/src/util/nodeListToReactComponents.tsx +++ b/packages/rich-text-react-renderer/src/util/nodeListToReactComponents.tsx @@ -10,17 +10,33 @@ export function nodeListToReactComponents(nodes: CommonNode[], options: Options) } export function nodeToReactComponent(node: CommonNode, options: Options): ReactNode { - const { renderNode, renderMark, renderText } = options; + const { renderNode, renderMark, renderText, preserveWhitespace } = options; + if (helpers.isText(node)) { - return node.marks.reduce( - (value: ReactNode, mark: Mark): ReactNode => { - if (!renderMark[mark.type]) { - return value; + let nodeValue: ReactNode = renderText ? renderText(node.value) : node.value; + + if (preserveWhitespace) { + // Preserve multiple spaces. + nodeValue = (nodeValue as string).replace(/ {2,}/g, (match) => ' '.repeat(match.length)); + + // Preserve line breaks. + let lines = (nodeValue as string).split('\n'); + let jsxLines: (string | JSX.Element)[] = []; + lines.forEach((line, index) => { + jsxLines.push(line); + if (index !== lines.length - 1) { + jsxLines.push(