diff --git a/src/Markdown.ts b/src/Markdown.ts index 05efdcfd56..709c34f31b 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 tags 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 6bc45a5657..8391183a65 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -36,20 +36,20 @@ export function mdSerialize(model: EditorModel): string { case Type.PillCandidate: case Type.AtRoomPill: return html + part.text; - case Type.RoomPill: + case Type.RoomPill: { + const url = makeGenericPermalink(part.resourceId); + // Escape square brackets and backslashes // Here we use the resourceId for compatibility with non-rich text clients // See https://github.com/vector-im/element-web/issues/16660 - return ( - html + - `[${part.resourceId.replace(/[[\\\]]/g, (c) => "\\" + c)}](${makeGenericPermalink( - part.resourceId, - )})` - ); - case Type.UserPill: - return ( - html + - `[${part.text.replace(/[[\\\]]/g, (c) => "\\" + c)}](${makeGenericPermalink(part.resourceId)})` - ); + const title = part.resourceId.replace(/[[\\\]]/g, (c) => "\\" + c); + return html + `[${title}](${url})`; + } + case Type.UserPill: { + const url = makeGenericPermalink(part.resourceId); + // Escape square brackets and backslashes; convert newlines to HTML + const title = part.text.replace(/[[\\\]]/g, (c) => "\\" + c).replace(/\n/g, "
"); + return html + `[${title}](${url})`; + } } }, ""); } diff --git a/test/editor/deserialize-test.ts b/test/editor/deserialize-test.ts index 908b83a651..275f34ca8f 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", 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 ce4815658f..b78ae308c8 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);