Skip to content

Commit 93640e5

Browse files
ichthyosaurusb100dian
authored andcommitted
Refactor reply formatting to follow the 1.12 spec
Due to invalid formatting, replies to replies became garbled, causing display issues in some clients. Hydrogen itself managed to display the replies correctly but other clients and bridges struggled because they were actually using the fallbacks. Current spec: https://spec.matrix.org/v1.12/client-server-api/#fallbacks-for-rich-replies Reply fallbacks are actively being removed in the upcoming spec but that doesn't mean that Hydrogen should keep the old bugged code in place. Upcoming MSCs: - matrix-org/matrix-spec-proposals#2781 - matrix-org/matrix-spec-proposals#3676 - spec: matrix-org/matrix-spec#1994 Signed-off-by: Mirian Margiani <[email protected]>
1 parent 72d80fa commit 93640e5

File tree

1 file changed

+58
-11
lines changed
  • src/matrix/room/timeline/entries

1 file changed

+58
-11
lines changed

src/matrix/room/timeline/entries/reply.js

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,41 @@ function fallbackPrefix(msgtype) {
3737
return msgtype === "m.emote" ? "* " : "";
3838
}
3939

40+
function _parsePlainBody(plainBody) {
41+
// Strip any existing reply fallback and return an array of lines.
42+
43+
const bodyLines = plainBody.trim().split("\n");
44+
45+
return bodyLines
46+
.map((elem, index, array) => {
47+
if (index > 0 && array[index-1][0] !== '>') {
48+
// stop stripping the fallback at the first line of non-fallback text
49+
return elem;
50+
} else if (elem[0] === '>' && elem[1] === ' ') {
51+
return null;
52+
} else {
53+
return elem;
54+
}
55+
})
56+
.filter((elem) => elem !== null)
57+
// Join, trim, and split to remove any line breaks that were left between the
58+
// fallback and the actual message body. Don't use trim() because that would
59+
// also remove any other whitespace at the beginning of the message that the
60+
// user added intentionally.
61+
.join('\n')
62+
.replace(/^\n+|\n+$/g, '')
63+
.split('\n')
64+
}
65+
66+
function _parseFormattedBody(formattedBody) {
67+
// Strip any existing reply fallback and return a HTML string again.
68+
69+
// This is greedy and definitely not the most efficient way to do it.
70+
// However, this function is only called when sending a reply (so: not too
71+
// often) and it should make sure that all instances of <mx-reply> are gone.
72+
return formattedBody.replace(/<mx-reply>[\s\S]*<\/mx-reply>/gi, '');
73+
}
74+
4075
function _createReplyContent(targetId, msgtype, body, formattedBody) {
4176
return {
4277
msgtype,
@@ -48,28 +83,40 @@ function _createReplyContent(targetId, msgtype, body, formattedBody) {
4883
"event_id": targetId
4984
}
5085
}
86+
// TODO include user mentions
5187
};
5288
}
5389

5490
export function createReplyContent(entry, msgtype, body, permaLink) {
55-
// TODO check for absense of sender / body / msgtype / etc?
91+
// NOTE We assume sender, body, and msgtype are never invalid because they
92+
// are required fields.
5693
const nonTextual = fallbackForNonTextualMessage(entry.content.msgtype);
5794
const prefix = fallbackPrefix(entry.content.msgtype);
5895
const sender = entry.sender;
59-
const name = entry.displayName || sender;
60-
61-
const formattedBody = nonTextual || entry.content.formatted_body ||
62-
(entry.content.body && htmlEscape(entry.content.body)) || "";
63-
const formattedFallback = `<mx-reply><blockquote>In reply to ${prefix}` +
64-
`<a href="https://matrix.to/#/${sender}">${name}</a><br />` +
65-
`${formattedBody}</blockquote></mx-reply>`;
96+
const repliedToId = entry.id;
97+
// TODO collect user mentions (sender and any previous mentions)
6698

99+
// Generate new plain body with plain reply fallback
67100
const plainBody = nonTextual || entry.content.body || "";
68-
const bodyLines = plainBody.split("\n");
101+
const bodyLines = _parsePlainBody(plainBody);
69102
bodyLines[0] = `> ${prefix}<${sender}> ${bodyLines[0]}`
70103
const plainFallback = bodyLines.join("\n> ");
71-
72104
const newBody = plainFallback + '\n\n' + body;
73-
const newFormattedBody = formattedFallback + htmlEscape(body);
105+
106+
// Generate new formatted body with formatted reply fallback
107+
const formattedBody = nonTextual || entry.content.formatted_body ||
108+
(entry.content.body && htmlEscape(entry.content.body)) || "";
109+
const cleanedFormattedBody = _parseFormattedBody(formattedBody);
110+
const formattedFallback =
111+
`<mx-reply>` +
112+
`<blockquote>` +
113+
`<a href="${permaLink}">In reply to</a>` +
114+
`${prefix}<a href="https://matrix.to/#/${sender}">${sender}</a>` +
115+
`<br />` +
116+
`${cleanedFormattedBody}` +
117+
`</blockquote>` +
118+
`</mx-reply>`;
119+
const newFormattedBody = formattedFallback + htmlEscape(body).replaceAll('\n', '<br/>');
120+
74121
return _createReplyContent(entry.id, msgtype, newBody, newFormattedBody);
75122
}

0 commit comments

Comments
 (0)