Remove legacy mx-reply from toPlainText formatted event contents (#6683)

This commit is contained in:
Jorge Martin Espinosa 2026-04-29 12:54:03 +02:00 committed by GitHub
parent 1eeabb1e64
commit d215354e64
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 57 additions and 3 deletions

View file

@ -12,8 +12,10 @@ import io.element.android.libraries.matrix.api.permalink.PermalinkData
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody
import io.element.android.libraries.matrix.api.timeline.item.event.MessageFormat
import io.element.android.wysiwyg.utils.HtmlToDomParser
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.nodes.Document.OutputSettings
import org.jsoup.safety.Safelist
/**
* Converts the HTML string [FormattedBody.body] to a [Document] by parsing it.
@ -34,9 +36,9 @@ fun FormattedBody.toHtmlDocument(
?.trimEnd()
?.let { formattedBody ->
val dom = if (prefix != null) {
HtmlToDomParser.document("$prefix $formattedBody")
CustomHtmlToDomParser.document("$prefix $formattedBody")
} else {
HtmlToDomParser.document(formattedBody)
CustomHtmlToDomParser.document(formattedBody)
}
// Prepend `@` to mentions
@ -60,3 +62,35 @@ private fun fixMentions(
}
}
}
/** Custom Html to DOM parser, based on the one included in the rich text editor library. */
private object CustomHtmlToDomParser {
fun document(html: String): Document {
val outputSettings = OutputSettings().prettyPrint(false).indentAmount(0)
val cleanHtml = Jsoup.clean(html, "", safeList, outputSettings)
return Jsoup.parse(cleanHtml)
}
private val safeList = Safelist()
.addTags(
"a",
"b",
"strong",
"i",
"em",
"u",
"del",
"code",
"ul",
"ol",
"li",
"pre",
"blockquote",
"p",
"br",
// Add custom `mx-reply` tag, even if it's just to remove its contents from the plain text version of the message
"mx-reply"
)
.addAttributes("a", "href", "data-mention-type", "contenteditable")
.addAttributes("ol", "start")
}

View file

@ -65,6 +65,8 @@ fun Document.toPlainText(): String {
return visitor.build()
}
private const val FALLBACK_REPLY_NODE_TAG = "mx-reply"
private class PlainTextNodeVisitor : NodeVisitor {
private val builder = StringBuilder()
@ -92,6 +94,9 @@ private class PlainTextNodeVisitor : NodeVisitor {
} else {
builder.append("")
}
} else if (node is Element && node.tagName() == FALLBACK_REPLY_NODE_TAG) {
// Remove the fallback reply node and its contents so they aren't added to the plain text message
node.remove()
} else if (node is Element && node.isBlock && builder.lastOrNull() != '\n') {
builder.append("\n")
}

View file

@ -136,4 +136,19 @@ class ToPlainTextTest {
)
assertThat(messageType.toPlainText(permalinkParser = FakePermalinkParser())).isEqualTo("This is the fallback text")
}
@Test
fun `TextMessageType toPlainText - ignores mx-reply element`() {
val messageType = TextMessageType(
body = "This is the fallback text",
formatted = FormattedBody(
format = MessageFormat.HTML,
body = """
<mx-reply>In reply to...</mx-reply>
This is the message content.
""".trimIndent()
)
)
assertThat(messageType.toPlainText(permalinkParser = FakePermalinkParser())).isEqualTo("This is the message content.")
}
}