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 = "
Start
Continue
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 = "- Oak\n
- Spruce\n
- 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()));