Skip to content

Commit d7677c7

Browse files
Johennesrichvdh
andauthored
Handle newlines in user pills (matrix-org#11166)
* Handle newlines in user pills Fixes: element-hq/element-web#10994 * Fix typo in comment Co-authored-by: Richard van der Hoff <[email protected]> * Refactor link generation for better readability * Use `<br>` instead of `<br/>` * Fix copy/paste error --------- Co-authored-by: Richard van der Hoff <[email protected]>
1 parent 3f20675 commit d7677c7

File tree

4 files changed

+29
-16
lines changed

4 files changed

+29
-16
lines changed

src/Markdown.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { logger } from "matrix-js-sdk/src/logger";
2222

2323
import { linkify } from "./linkify-matrix";
2424

25-
const ALLOWED_HTML_TAGS = ["sub", "sup", "del", "u"];
25+
const ALLOWED_HTML_TAGS = ["sub", "sup", "del", "u", "br", "br/"];
2626

2727
// These types of node are definitely text
2828
const TEXT_NODES = ["text", "softbreak", "linebreak", "paragraph", "document"];
@@ -36,8 +36,8 @@ function isAllowedHtmlTag(node: commonmark.Node): boolean {
3636
return true;
3737
}
3838

39-
// Regex won't work for tags with attrs, but we only
40-
// allow <del> anyway.
39+
// Regex won't work for tags with attrs, but the tags we allow
40+
// shouldn't really have any anyway.
4141
const matches = /^<\/?(.*)>$/.exec(node.literal);
4242
if (matches && matches.length == 2) {
4343
const tag = matches[1];

src/editor/serialize.ts

+12-12
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,20 @@ export function mdSerialize(model: EditorModel): string {
3636
case Type.PillCandidate:
3737
case Type.AtRoomPill:
3838
return html + part.text;
39-
case Type.RoomPill:
39+
case Type.RoomPill: {
40+
const url = makeGenericPermalink(part.resourceId);
41+
// Escape square brackets and backslashes
4042
// Here we use the resourceId for compatibility with non-rich text clients
4143
// See https://github.com/vector-im/element-web/issues/16660
42-
return (
43-
html +
44-
`[${part.resourceId.replace(/[[\\\]]/g, (c) => "\\" + c)}](${makeGenericPermalink(
45-
part.resourceId,
46-
)})`
47-
);
48-
case Type.UserPill:
49-
return (
50-
html +
51-
`[${part.text.replace(/[[\\\]]/g, (c) => "\\" + c)}](${makeGenericPermalink(part.resourceId)})`
52-
);
44+
const title = part.resourceId.replace(/[[\\\]]/g, (c) => "\\" + c);
45+
return html + `[${title}](${url})`;
46+
}
47+
case Type.UserPill: {
48+
const url = makeGenericPermalink(part.resourceId);
49+
// Escape square brackets and backslashes; convert newlines to HTML
50+
const title = part.text.replace(/[[\\\]]/g, (c) => "\\" + c).replace(/\n/g, "<br>");
51+
return html + `[${title}](${url})`;
52+
}
5353
}
5454
}, "");
5555
}

test/editor/deserialize-test.ts

+8
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,14 @@ describe("editor/deserialize", function () {
183183
expect(parts[1]).toStrictEqual({ type: "user-pill", text: "Alice]", resourceId: "@alice:hs.tld" });
184184
expect(parts[2]).toStrictEqual({ type: "plain", text: "!" });
185185
});
186+
it("user pill with displayname containing linebreak", function () {
187+
const html = 'Hi <a href="https://matrix.to/#/@alice:hs.tld">Alice<br>123</a>!';
188+
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));
189+
expect(parts.length).toBe(3);
190+
expect(parts[0]).toStrictEqual({ type: "plain", text: "Hi " });
191+
expect(parts[1]).toStrictEqual({ type: "user-pill", text: "Alice123", resourceId: "@alice:hs.tld" });
192+
expect(parts[2]).toStrictEqual({ type: "plain", text: "!" });
193+
});
186194
it("room pill", function () {
187195
const html = 'Try <a href="https://matrix.to/#/#room:hs.tld">#room:hs.tld</a>?';
188196
const parts = normalize(parseEvent(htmlMessage(html), createPartCreator()));

test/editor/serialize-test.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ describe("editor/serialize", function () {
6363
const html = htmlSerializeIfNeeded(model, {});
6464
expect(html).toBe('<a href="https://matrix.to/#/@user:server">Displayname]</a>');
6565
});
66+
it("displaynames containing a newline work", function () {
67+
const pc = createPartCreator();
68+
const model = new EditorModel([pc.userPill("Display\nname", "@user:server")], pc);
69+
const html = htmlSerializeIfNeeded(model, {});
70+
expect(html).toBe('<a href="https://matrix.to/#/@user:server">Display<br>name</a>');
71+
});
6672
it("escaped markdown should not retain backslashes", function () {
6773
const pc = createPartCreator();
6874
const model = new EditorModel([pc.plain("\\*hello\\* world")], pc);
@@ -96,7 +102,6 @@ describe("editor/serialize", function () {
96102
const html = htmlSerializeIfNeeded(model, { useMarkdown: false });
97103
expect(html).toBe("\\*hello\\* world &lt; hey world!");
98104
});
99-
100105
it("plaintext remains plaintext even when forcing html", function () {
101106
const pc = createPartCreator();
102107
const model = new EditorModel([pc.plain("hello world")], pc);

0 commit comments

Comments
 (0)