Fix a bunch of issues related to edition and reply #5969

This commit is contained in:
ganfra 2023-12-20 16:53:54 +01:00 committed by Jorge Martín
parent c46b3148e4
commit b72039e735
9 changed files with 56 additions and 43 deletions

1
changelog.d/5969.bugfix Normal file
View file

@ -0,0 +1 @@
Fix some issues related to edition and reply of events.

View file

@ -117,7 +117,7 @@ interface RelationService {
fun editReply(
replyToEdit: TimelineEvent,
originalTimelineEvent: TimelineEvent,
newBodyText: String,
newBodyText: CharSequence,
newFormattedBodyText: String? = null,
compatibilityBodyText: String = "* $newBodyText"
): Cancelable

View file

@ -37,13 +37,13 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocati
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody
import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent
import org.matrix.android.sdk.api.session.room.model.message.MessageFormat
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
import org.matrix.android.sdk.api.util.ContentUtils
import org.matrix.android.sdk.api.util.ContentUtils.ensureCorrectFormattedBodyInTextReply
import org.matrix.android.sdk.api.util.ContentUtils.extractUsefulTextFromReply
/**
@ -160,37 +160,17 @@ fun TimelineEvent.getLastMessageContent(): MessageContent? {
fun TimelineEvent.getLastEditNewContent(): Content? {
val lastContent = annotations?.editSummary?.latestEdit?.getClearContent()?.toModel<MessageContent>()?.newContent
return if (isReply()) {
val previousFormattedBody = root.getClearContent().toModel<MessageTextContent>()?.formattedBody
if (previousFormattedBody?.isNotEmpty() == true) {
val lastMessageContent = lastContent.toModel<MessageTextContent>()
lastMessageContent?.let { ensureCorrectFormattedBodyInTextReply(it, previousFormattedBody) }?.toContent() ?: lastContent
} else {
lastContent
}
} else {
lastContent
}
}
private const val MX_REPLY_END_TAG = "</mx-reply>"
/**
* Not every client sends a formatted body in the last edited event since this is not required in the
* [Matrix specification](https://spec.matrix.org/v1.4/client-server-api/#applying-mnew_content).
* We must ensure there is one so that it is still considered as a reply when rendering the message.
*/
private fun ensureCorrectFormattedBodyInTextReply(messageTextContent: MessageTextContent, previousFormattedBody: String): MessageTextContent {
return when {
messageTextContent.formattedBody.isNullOrEmpty() && previousFormattedBody.contains(MX_REPLY_END_TAG) -> {
// take previous formatted body with the new body content
val newFormattedBody = previousFormattedBody.replaceAfterLast(MX_REPLY_END_TAG, messageTextContent.body)
messageTextContent.copy(
formattedBody = newFormattedBody,
format = MessageFormat.FORMAT_MATRIX_HTML,
)
isReply() -> {
val originalFormattedBody = root.getClearContent().toModel<MessageTextContent>()?.formattedBody
val lastMessageContent = lastContent.toModel<MessageTextContent>()
if (lastMessageContent != null && originalFormattedBody?.isNotEmpty() == true) {
ensureCorrectFormattedBodyInTextReply(lastMessageContent, originalFormattedBody).toContent()
} else {
lastContent
}
}
else -> messageTextContent
else -> lastContent
}
}

View file

@ -15,6 +15,8 @@
*/
package org.matrix.android.sdk.api.util
import org.matrix.android.sdk.api.session.room.model.message.MessageFormat
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
import org.matrix.android.sdk.internal.util.unescapeHtml
object ContentUtils {
@ -38,15 +40,36 @@ object ContentUtils {
}
fun extractUsefulTextFromHtmlReply(repliedBody: String): String {
if (repliedBody.startsWith("<mx-reply>")) {
val closingTagIndex = repliedBody.lastIndexOf("</mx-reply>")
if (repliedBody.startsWith(MX_REPLY_START_TAG)) {
val closingTagIndex = repliedBody.lastIndexOf(MX_REPLY_END_TAG)
if (closingTagIndex != -1) {
return repliedBody.substring(closingTagIndex + "</mx-reply>".length).trim()
return repliedBody.substring(closingTagIndex + MX_REPLY_END_TAG.length).trim()
}
}
return repliedBody
}
/**
* Not every client sends a formatted body in the last edited event since this is not required in the
* [Matrix specification](https://spec.matrix.org/v1.4/client-server-api/#applying-mnew_content).
* We must ensure there is one so that it is still considered as a reply when rendering the message.
*/
fun ensureCorrectFormattedBodyInTextReply(messageTextContent: MessageTextContent, originalFormattedBody: String): MessageTextContent {
return when {
messageTextContent.formattedBody != null &&
!messageTextContent.formattedBody.contains(MX_REPLY_END_TAG) &&
originalFormattedBody.contains(MX_REPLY_END_TAG) -> {
// take previous formatted body with the new body content
val newFormattedBody = originalFormattedBody.replaceAfterLast(MX_REPLY_END_TAG, messageTextContent.body)
messageTextContent.copy(
formattedBody = newFormattedBody,
format = MessageFormat.FORMAT_MATRIX_HTML,
)
}
else -> messageTextContent
}
}
@Suppress("RegExpRedundantEscape")
fun formatSpoilerTextFromHtml(formattedBody: String): String {
// var reason = "",
@ -57,4 +80,6 @@ object ContentUtils {
}
private const val SPOILER_CHAR = ""
private const val MX_REPLY_START_TAG = "<mx-reply>"
private const val MX_REPLY_END_TAG = "</mx-reply>"
}

View file

@ -115,7 +115,7 @@ internal class DefaultRelationService @AssistedInject constructor(
override fun editReply(
replyToEdit: TimelineEvent,
originalTimelineEvent: TimelineEvent,
newBodyText: String,
newBodyText: CharSequence,
newFormattedBodyText: String?,
compatibilityBodyText: String
): Cancelable {

View file

@ -106,7 +106,7 @@ internal class EventEditor @Inject constructor(
fun editReply(
replyToEdit: TimelineEvent,
originalTimelineEvent: TimelineEvent,
newBodyText: String,
newBodyText: CharSequence,
newBodyFormattedText: String?,
compatibilityBodyText: String
): Cancelable {

View file

@ -312,7 +312,7 @@ internal class LocalEchoEventFactory @Inject constructor(
roomId: String,
eventReplaced: TimelineEvent,
originalEvent: TimelineEvent,
newBodyText: String,
newBodyText: CharSequence,
autoMarkdown: Boolean,
msgType: String,
compatibilityText: String,
@ -336,7 +336,7 @@ internal class LocalEchoEventFactory @Inject constructor(
//
// > <@alice:example.org> This is the original body
//
val replyFallback = buildReplyFallback(body, originalEvent.root.senderId ?: "", newBodyText)
val replyFallback = buildReplyFallback(body, originalEvent.root.senderId ?: "", newBodyText.toString())
return createMessageEvent(
roomId,

View file

@ -178,7 +178,8 @@ class MessageComposerViewModel @AssistedInject constructor(
private fun handleEnterEditMode(room: Room, action: MessageComposerAction.EnterEditMode) {
room.getTimelineEvent(action.eventId)?.let { timelineEvent ->
val formatted = vectorPreferences.isRichTextEditorEnabled()
setState { copy(sendMode = SendMode.Edit(timelineEvent, timelineEvent.getTextEditableContent(formatted))) }
val editableContent = timelineEvent.getTextEditableContent(formatted)
setState { copy(sendMode = SendMode.Edit(timelineEvent, editableContent)) }
}
}
@ -578,7 +579,7 @@ class MessageComposerViewModel @AssistedInject constructor(
if (inReplyTo != null) {
// TODO check if same content?
room.getTimelineEvent(inReplyTo)?.let {
room.relationService().editReply(state.sendMode.timelineEvent, it, action.text.toString(), action.formattedText)
room.relationService().editReply(state.sendMode.timelineEvent, it, action.text, action.formattedText)
}
} else {
val messageContent = state.sendMode.timelineEvent.getVectorLastMessageContent()
@ -624,14 +625,14 @@ class MessageComposerViewModel @AssistedInject constructor(
state.rootThreadEventId?.let {
room.relationService().replyInThread(
rootThreadEventId = it,
replyInThreadText = action.text.toString(),
replyInThreadText = action.text,
autoMarkdown = action.autoMarkdown,
formattedText = action.formattedText,
eventReplied = timelineEvent
)
} ?: room.relationService().replyToMessage(
eventReplied = timelineEvent,
replyText = action.text.toString(),
replyText = action.text,
replyFormattedText = action.formattedText,
autoMarkdown = action.autoMarkdown,
showInThread = showInThread,

View file

@ -48,6 +48,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollConte
import org.matrix.android.sdk.api.session.room.model.message.MessageFormat
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
import org.matrix.android.sdk.api.util.ContentUtils
import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.toMatrixItem
import javax.inject.Inject
@ -188,7 +189,12 @@ class PlainTextComposerLayout @JvmOverloads constructor(
var formattedBody: CharSequence? = null
if (messageContent is MessageTextContent && messageContent.format == MessageFormat.FORMAT_MATRIX_HTML) {
val parser = Parser.builder().build()
val document = parser.parse(messageContent.formattedBody ?: messageContent.body)
val bodyToParse = messageContent.formattedBody?.let {
ContentUtils.extractUsefulTextFromHtmlReply(it)
} ?: ContentUtils.extractUsefulTextFromReply(messageContent.body)
val document = parser.parse(bodyToParse)
formattedBody = eventHtmlRenderer.render(document, pillsPostProcessor)
}
views.composerRelatedMessageContent.text = (formattedBody ?: nonFormattedBody)