Skip to content

Commit 457e4ae

Browse files
fix: Preserve whitespace in rich text nodes on parse (#455)
* Preserve whitespace in rich text nodes on parse * Preserve whitespace when parsing plain text from DOM * Amend existing test * preserveWhitespace: "full", because we do not want to misrepresent newlines * Move page doc helpers out of library code, and revert preserveWhitespace We do not use 'preserveWhitespace: full' here to allow us to format HTML in fixtures without breaking tests - see in ./cypress/helpers/editor.ts
1 parent 5c67ea4 commit 457e4ae

File tree

6 files changed

+36
-23
lines changed

6 files changed

+36
-23
lines changed

demo/helpers.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { DOMParser, DOMSerializer, Node } from "prosemirror-model";
12
import type { EditorState, Transaction } from "prosemirror-state";
23
import { Plugin } from "prosemirror-state";
34
import type { DemoSetMedia } from "../src/elements/demo-image/DemoImageElement";
@@ -218,3 +219,16 @@ export const getImageFromMediaPayload = (
218219
mediaId: mediaPayload.mediaId,
219220
};
220221
};
222+
223+
export const docToHtml = (serializer: DOMSerializer, doc: Node) => {
224+
const dom = serializer.serializeFragment(doc.content);
225+
const e = document.createElement("div");
226+
e.appendChild(dom);
227+
return e.innerHTML;
228+
};
229+
230+
export const htmlToDoc = (parser: DOMParser, html: string) => {
231+
const dom = document.createElement("div");
232+
dom.innerHTML = html;
233+
return parser.parse(dom, { preserveWhitespace: true });
234+
};

demo/index.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,7 @@ import { richlinkElement } from "../src/elements/rich-link/RichlinkForm";
3232
import { createStandardElement } from "../src/elements/standard/StandardForm";
3333
import { tableElement } from "../src/elements/table/TableForm";
3434
import { createTweetElement } from "../src/elements/tweet/TweetForm";
35-
import {
36-
createParsers,
37-
docToHtml,
38-
htmlToDoc,
39-
} from "../src/plugin/helpers/prosemirror";
35+
import { createParsers } from "../src/plugin/helpers/prosemirror";
4036
import {
4137
testDecorationPlugin,
4238
testInnerEditorEventPropagationPlugin,
@@ -45,7 +41,9 @@ import {
4541
import { CollabServer, EditorConnection } from "./collab/CollabServer";
4642
import { createSelectionCollabPlugin } from "./collab/SelectionPlugin";
4743
import {
44+
docToHtml,
4845
getImageFromMediaPayload,
46+
htmlToDoc,
4947
onCropCartoon,
5048
onCropImage,
5149
onDemoCropImage,

src/plugin/__tests__/element.spec.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -949,6 +949,21 @@ describe("buildElementPlugin", () => {
949949
expect(node?.textContent).toBe("");
950950
});
951951

952+
it("should retain whitespace in rich text", () => {
953+
const { getNodeFromElementData, view } = createEditorWithElements({
954+
testElement,
955+
});
956+
957+
const fieldText = "<p> </p>";
958+
959+
const node = getNodeFromElementData(
960+
{ elementName: "testElement", values: { field1: fieldText } },
961+
view.state.schema
962+
);
963+
964+
expect(node?.textContent).toBe(" ");
965+
});
966+
952967
it("should retain HTML chars in plain text", () => {
953968
const { getNodeFromElementData, view } = createEditorWithElements({
954969
testElement,

src/plugin/__tests__/nodeSpec.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ describe("nodeSpec generation", () => {
147147
expect(field1NodeSpec).toHaveProperty("content", "text*");
148148
expect(field1NodeSpec?.parseDOM?.[0]).toMatchObject({
149149
tag: "div",
150-
preserveWhitespace: false,
150+
preserveWhitespace: "full",
151151
});
152152
});
153153
});

src/plugin/helpers/prosemirror.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -324,19 +324,6 @@ const createParsers = <S extends Schema>(schema: S) => {
324324
return { parser, serializer };
325325
};
326326

327-
const docToHtml = (serializer: DOMSerializer, doc: Node) => {
328-
const dom = serializer.serializeFragment(doc.content);
329-
const e = document.createElement("div");
330-
e.appendChild(dom);
331-
return e.innerHTML;
332-
};
333-
334-
const htmlToDoc = (parser: DOMParser, html: string) => {
335-
const dom = document.createElement("div");
336-
dom.innerHTML = html;
337-
return parser.parse(dom);
338-
};
339-
340327
// Select all text within a node, as opposed to AllSelection
341328
const selectAllText = (
342329
state: EditorState,
@@ -386,7 +373,5 @@ export {
386373
defaultPredicate,
387374
createUpdateDecorations,
388375
createParsers,
389-
docToHtml,
390-
htmlToDoc,
391376
selectAllText,
392377
};

src/plugin/nodeSpec.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export const getNodeSpecForField = (
112112
{
113113
tag: "div",
114114
getAttrs: createGetAttrsForTextNode(nodeName),
115-
preserveWhitespace: field.isCode ? "full" : false,
115+
preserveWhitespace: "full",
116116
},
117117
],
118118
code: field.isCode,
@@ -518,9 +518,10 @@ const createContentNodeFromRichText = <S extends Schema>(
518518
const parser = DOMParser.fromSchema(schema);
519519
const element = document.createElement("div");
520520
element.innerHTML = fieldValue;
521+
521522
return parser.parse(element, {
522523
topNode,
523-
preserveWhitespace: false,
524+
preserveWhitespace: "full",
524525
});
525526
};
526527

0 commit comments

Comments
 (0)