diff --git a/src/Markdown.ts b/src/Markdown.ts index 05efdcfd56d..3fa6e07218e 100644 --- a/src/Markdown.ts +++ b/src/Markdown.ts @@ -22,7 +22,7 @@ import { logger } from "matrix-js-sdk/src/logger"; import { linkify } from "./linkify-matrix"; -const ALLOWED_HTML_TAGS = ["sub", "sup", "del", "u"]; +const ALLOWED_HTML_TAGS = ["sub", "sup", "del", "u", "br", "br/"]; // These types of node are definitely text const TEXT_NODES = ["text", "softbreak", "linebreak", "paragraph", "document"]; @@ -36,8 +36,8 @@ function isAllowedHtmlTag(node: commonmark.Node): boolean { return true; } - // Regex won't work for tags with attrs, but we only - // allow anyway. + // Regex won't work for tags with attrs, but the oens we allow + // shouldn't really have any anyway. const matches = /^<\/?(.*)>$/.exec(node.literal); if (matches && matches.length == 2) { const tag = matches[1]; diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index 6bc45a5657d..00882f27941 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -48,7 +48,9 @@ export function mdSerialize(model: EditorModel): string { case Type.UserPill: return ( html + - `[${part.text.replace(/[[\\\]]/g, (c) => "\\" + c)}](${makeGenericPermalink(part.resourceId)})` + `[${part.text.replace(/[[\\\]]/g, (c) => "\\" + c).replace(/\n/g, "
")}](${makeGenericPermalink( + part.resourceId, + )})` ); } }, ""); diff --git a/test/editor/deserialize-test.ts b/test/editor/deserialize-test.ts index 908b83a6512..a7c4595eb4e 100644 --- a/test/editor/deserialize-test.ts +++ b/test/editor/deserialize-test.ts @@ -183,6 +183,14 @@ describe("editor/deserialize", function () { expect(parts[1]).toStrictEqual({ type: "user-pill", text: "Alice]", resourceId: "@alice:hs.tld" }); expect(parts[2]).toStrictEqual({ type: "plain", text: "!" }); }); + it("user pill with displayname containing linebreak square bracket", function () { + const html = 'Hi Alice
123
!'; + const parts = normalize(parseEvent(htmlMessage(html), createPartCreator())); + expect(parts.length).toBe(3); + expect(parts[0]).toStrictEqual({ type: "plain", text: "Hi " }); + expect(parts[1]).toStrictEqual({ type: "user-pill", text: "Alice123", resourceId: "@alice:hs.tld" }); + expect(parts[2]).toStrictEqual({ type: "plain", text: "!" }); + }); it("room pill", function () { const html = 'Try #room:hs.tld?'; const parts = normalize(parseEvent(htmlMessage(html), createPartCreator())); diff --git a/test/editor/serialize-test.ts b/test/editor/serialize-test.ts index ce4815658f7..b78ae308c85 100644 --- a/test/editor/serialize-test.ts +++ b/test/editor/serialize-test.ts @@ -63,6 +63,12 @@ describe("editor/serialize", function () { const html = htmlSerializeIfNeeded(model, {}); expect(html).toBe('Displayname]'); }); + it("displaynames containing a newline work", function () { + const pc = createPartCreator(); + const model = new EditorModel([pc.userPill("Display\nname", "@user:server")], pc); + const html = htmlSerializeIfNeeded(model, {}); + expect(html).toBe('Display
name
'); + }); it("escaped markdown should not retain backslashes", function () { const pc = createPartCreator(); const model = new EditorModel([pc.plain("\\*hello\\* world")], pc); @@ -96,7 +102,6 @@ describe("editor/serialize", function () { const html = htmlSerializeIfNeeded(model, { useMarkdown: false }); expect(html).toBe("\\*hello\\* world < hey world!"); }); - it("plaintext remains plaintext even when forcing html", function () { const pc = createPartCreator(); const model = new EditorModel([pc.plain("hello world")], pc);