diff --git a/src/matrix/room/timeline/entries/reply.js b/src/matrix/room/timeline/entries/reply.js index 6039a0e092..92b573137b 100644 --- a/src/matrix/room/timeline/entries/reply.js +++ b/src/matrix/room/timeline/entries/reply.js @@ -37,6 +37,41 @@ function fallbackPrefix(msgtype) { return msgtype === "m.emote" ? "* " : ""; } +function _parsePlainBody(plainBody) { + // Strip any existing reply fallback and return an array of lines. + + const bodyLines = plainBody.trim().split("\n"); + + return bodyLines + .map((elem, index, array) => { + if (index > 0 && array[index-1][0] !== '>') { + // stop stripping the fallback at the first line of non-fallback text + return elem; + } else if (elem[0] === '>' && elem[1] === ' ') { + return null; + } else { + return elem; + } + }) + .filter((elem) => elem !== null) + // Join, trim, and split to remove any line breaks that were left between the + // fallback and the actual message body. Don't use trim() because that would + // also remove any other whitespace at the beginning of the message that the + // user added intentionally. + .join('\n') + .replace(/^\n+|\n+$/g, '') + .split('\n') +} + +function _parseFormattedBody(formattedBody) { + // Strip any existing reply fallback and return a HTML string again. + + // This is greedy and definitely not the most efficient way to do it. + // However, this function is only called when sending a reply (so: not too + // often) and it should make sure that all instances of are gone. + return formattedBody.replace(/[\s\S]*<\/mx-reply>/gi, ''); +} + function _createReplyContent(targetId, msgtype, body, formattedBody) { return { msgtype, @@ -48,28 +83,40 @@ function _createReplyContent(targetId, msgtype, body, formattedBody) { "event_id": targetId } } + // TODO include user mentions }; } export function createReplyContent(entry, msgtype, body, permaLink) { - // TODO check for absense of sender / body / msgtype / etc? + // NOTE We assume sender, body, and msgtype are never invalid because they + // are required fields. const nonTextual = fallbackForNonTextualMessage(entry.content.msgtype); const prefix = fallbackPrefix(entry.content.msgtype); const sender = entry.sender; - const name = entry.displayName || sender; - - const formattedBody = nonTextual || entry.content.formatted_body || - (entry.content.body && htmlEscape(entry.content.body)) || ""; - const formattedFallback = `
In reply to ${prefix}` + - `${name}
` + - `${formattedBody}
`; + const repliedToId = entry.id; + // TODO collect user mentions (sender and any previous mentions) + // Generate new plain body with plain reply fallback const plainBody = nonTextual || entry.content.body || ""; - const bodyLines = plainBody.split("\n"); + const bodyLines = _parsePlainBody(plainBody); bodyLines[0] = `> ${prefix}<${sender}> ${bodyLines[0]}` const plainFallback = bodyLines.join("\n> "); - const newBody = plainFallback + '\n\n' + body; - const newFormattedBody = formattedFallback + htmlEscape(body); + + // Generate new formatted body with formatted reply fallback + const formattedBody = nonTextual || entry.content.formatted_body || + (entry.content.body && htmlEscape(entry.content.body)) || ""; + const cleanedFormattedBody = _parseFormattedBody(formattedBody); + const formattedFallback = + `` + + `
` + + `In reply to` + + `${prefix}${sender}` + + `
` + + `${cleanedFormattedBody}` + + `
` + + `
`; + const newFormattedBody = formattedFallback + htmlEscape(body).replaceAll('\n', '
'); + return _createReplyContent(entry.id, msgtype, newBody, newFormattedBody); }