From 2458085eed462c5481a168f31281cef9d9e486c1 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Wed, 5 Apr 2023 09:02:26 -0400 Subject: [PATCH] fix: Draft comment on text gets into a strange state when unfocused (#5153) --- server/utils/ProsemirrorHelper.test.ts | 184 +++++++++++++++++++++++++ shared/utils/ProsemirrorHelper.ts | 48 +++++-- 2 files changed, 219 insertions(+), 13 deletions(-) create mode 100644 server/utils/ProsemirrorHelper.test.ts diff --git a/server/utils/ProsemirrorHelper.test.ts b/server/utils/ProsemirrorHelper.test.ts new file mode 100644 index 000000000..0068d04de --- /dev/null +++ b/server/utils/ProsemirrorHelper.test.ts @@ -0,0 +1,184 @@ +import { Node } from "prosemirror-model"; +import ProsemirrorHelper from "@shared/utils/ProsemirrorHelper"; +import { schema } from "@server/editor"; + +// Note: The test is here rather than shared to access the schema +describe("#ProsemirrorHelper", () => { + describe("#trim", () => { + it("Does not remove single paragraph", () => { + const doc = Node.fromJSON(schema, { + type: "doc", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: " ", + }, + ], + }, + ], + }); + + expect(ProsemirrorHelper.trim(doc).toJSON()).toEqual({ + type: "doc", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: " ", + }, + ], + }, + ], + }); + }); + + it("Removes empty first paragraph", () => { + const doc = Node.fromJSON(schema, { + type: "doc", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: " ", + }, + ], + }, + { + type: "paragraph", + content: [ + { + type: "text", + text: "one", + }, + ], + }, + ], + }); + + expect(ProsemirrorHelper.trim(doc).toJSON()).toEqual({ + type: "doc", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "one", + }, + ], + }, + ], + }); + }); + + it("Removes empty last paragraph", () => { + const doc = Node.fromJSON(schema, { + type: "doc", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "nice", + }, + ], + }, + { + type: "paragraph", + content: [ + { + type: "text", + text: " ", + }, + ], + }, + ], + }); + + expect(ProsemirrorHelper.trim(doc).toJSON()).toEqual({ + type: "doc", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "nice", + }, + ], + }, + ], + }); + }); + + it("Removes multiple empty paragraphs", () => { + const doc = Node.fromJSON(schema, { + type: "doc", + content: [ + { + type: "paragraph", + }, + { + type: "paragraph", + content: [ + { + type: "text", + text: " ", + }, + ], + }, + { + type: "paragraph", + content: [ + { + type: "text", + text: "nice", + }, + ], + }, + { + type: "paragraph", + content: [ + { + type: "text", + text: " ", + }, + ], + }, + { + type: "paragraph", + content: [ + { + type: "text", + text: " ", + }, + ], + }, + ], + }); + + expect(ProsemirrorHelper.trim(doc).toJSON()).toEqual({ + type: "doc", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "nice", + }, + ], + }, + ], + }); + }); + }); +}); diff --git a/shared/utils/ProsemirrorHelper.ts b/shared/utils/ProsemirrorHelper.ts index b288178f4..4b1069b11 100644 --- a/shared/utils/ProsemirrorHelper.ts +++ b/shared/utils/ProsemirrorHelper.ts @@ -49,20 +49,42 @@ export default class ProsemirrorHelper { * @returns True if the editor is empty */ static trim(doc: Node) { - const first = doc.firstChild; - const last = doc.lastChild; - const firstIsEmpty = - first && - ProsemirrorHelper.toPlainText(first, doc.type.schema).trim() === ""; - const lastIsEmpty = - last && - ProsemirrorHelper.toPlainText(last, doc.type.schema).trim() === ""; - const firstIsLast = first === last; + const { schema } = doc.type; + let index = 0, + start = 0, + end = doc.nodeSize - 2, + isEmpty; - return doc.cut( - firstIsEmpty ? first.nodeSize : 0, - lastIsEmpty && !firstIsLast ? doc.nodeSize - last.nodeSize : undefined - ); + if (doc.childCount <= 1) { + return doc; + } + + isEmpty = true; + while (isEmpty) { + const node = doc.maybeChild(index++); + if (!node) { + break; + } + isEmpty = ProsemirrorHelper.toPlainText(node, schema).trim() === ""; + if (isEmpty) { + start += node.nodeSize; + } + } + + index = doc.childCount - 1; + isEmpty = true; + while (isEmpty) { + const node = doc.maybeChild(index--); + if (!node) { + break; + } + isEmpty = ProsemirrorHelper.toPlainText(node, schema).trim() === ""; + if (isEmpty) { + end -= node.nodeSize; + } + } + + return doc.cut(start, end); } /**