diff --git a/src/editor/deserialize.ts b/src/editor/deserialize.ts index b95634ca42..d2ab8da702 100644 --- a/src/editor/deserialize.ts +++ b/src/editor/deserialize.ts @@ -242,7 +242,19 @@ function parseHtmlMessage(html: string, partCreator: PartCreator, isQuotedMessag } if (n.nodeType === Node.TEXT_NODE) { - newParts.push(...parseAtRoomMentions(n.nodeValue, partCreator)); + let { nodeValue } = n; + + // Sometimes commonmark adds a newline at the end of the list item text + if (n.parentNode.nodeName === "LI") { + nodeValue = nodeValue.trimEnd(); + } + newParts.push(...parseAtRoomMentions(nodeValue, partCreator)); + + const grandParent = n.parentNode.parentNode; + const isTight = n.parentNode.nodeName !== "P" || grandParent?.nodeName !== "LI"; + if (!isTight) { + newParts.push(partCreator.newline()); + } } else if (n.nodeType === Node.ELEMENT_NODE) { const parseResult = parseElement(n, partCreator, lastNode, state); if (parseResult) { diff --git a/test/editor/deserialize-test.js b/test/editor/deserialize-test.js index 2de6375b82..5526ff3df4 100644 --- a/test/editor/deserialize-test.js +++ b/test/editor/deserialize-test.js @@ -237,6 +237,18 @@ describe('editor/deserialize', function() { expect(parts[3]).toStrictEqual({ type: "newline", text: "\n" }); expect(parts[4]).toStrictEqual({ type: "plain", text: "3. Finish" }); }); + it('non tight lists', () => { + const html = "
  1. Start

  2. Continue

  3. Finish

"; + const parts = normalize(parseEvent(htmlMessage(html), createPartCreator())); + expect(parts.length).toBe(8); + expect(parts[0]).toStrictEqual({ type: "plain", text: "1. Start" }); + expect(parts[1]).toStrictEqual({ type: "newline", text: "\n" }); + expect(parts[2]).toStrictEqual({ type: "newline", text: "\n" }); + expect(parts[3]).toStrictEqual({ type: "plain", text: "2. Continue" }); + expect(parts[4]).toStrictEqual({ type: "newline", text: "\n" }); + expect(parts[5]).toStrictEqual({ type: "newline", text: "\n" }); + expect(parts[6]).toStrictEqual({ type: "plain", text: "3. Finish" }); + }); it('nested unordered lists', () => { const html = ""; const parts = normalize(parseEvent(htmlMessage(html), createPartCreator())); @@ -257,6 +269,16 @@ describe('editor/deserialize', function() { expect(parts[3]).toStrictEqual({ type: "newline", text: "\n" }); expect(parts[4]).toStrictEqual({ type: "plain", text: `${FOUR_SPACES.repeat(2)}1. Birch` }); }); + it('nested tight lists', () => { + const html = "
  1. Oak\n
    1. Spruce\n
      1. Birch
"; + const parts = normalize(parseEvent(htmlMessage(html), createPartCreator())); + expect(parts.length).toBe(5); + expect(parts[0]).toStrictEqual({ type: "plain", text: "1. Oak" }); + expect(parts[1]).toStrictEqual({ type: "newline", text: "\n" }); + expect(parts[2]).toStrictEqual({ type: "plain", text: `${FOUR_SPACES}1. Spruce` }); + expect(parts[3]).toStrictEqual({ type: "newline", text: "\n" }); + expect(parts[4]).toStrictEqual({ type: "plain", text: `${FOUR_SPACES.repeat(2)}1. Birch` }); + }); it('mx-reply is stripped', function() { const html = "foobar"; const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));