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( fun editReply(
replyToEdit: TimelineEvent, replyToEdit: TimelineEvent,
originalTimelineEvent: TimelineEvent, originalTimelineEvent: TimelineEvent,
newBodyText: String, newBodyText: CharSequence,
newFormattedBodyText: String? = null, newFormattedBodyText: String? = null,
compatibilityBodyText: String = "* $newBodyText" compatibilityBodyText: String = "* $newBodyText"
): Cancelable ): 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.MessageContent
import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody 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.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.MessagePollContent
import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent 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.message.MessageTextContent
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent 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.session.room.sender.SenderInfo
import org.matrix.android.sdk.api.util.ContentUtils 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 import org.matrix.android.sdk.api.util.ContentUtils.extractUsefulTextFromReply
/** /**
@ -160,37 +160,17 @@ fun TimelineEvent.getLastMessageContent(): MessageContent? {
fun TimelineEvent.getLastEditNewContent(): Content? { fun TimelineEvent.getLastEditNewContent(): Content? {
val lastContent = annotations?.editSummary?.latestEdit?.getClearContent()?.toModel<MessageContent>()?.newContent 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 { return when {
messageTextContent.formattedBody.isNullOrEmpty() && previousFormattedBody.contains(MX_REPLY_END_TAG) -> { isReply() -> {
// take previous formatted body with the new body content val originalFormattedBody = root.getClearContent().toModel<MessageTextContent>()?.formattedBody
val newFormattedBody = previousFormattedBody.replaceAfterLast(MX_REPLY_END_TAG, messageTextContent.body) val lastMessageContent = lastContent.toModel<MessageTextContent>()
messageTextContent.copy( if (lastMessageContent != null && originalFormattedBody?.isNotEmpty() == true) {
formattedBody = newFormattedBody, ensureCorrectFormattedBodyInTextReply(lastMessageContent, originalFormattedBody).toContent()
format = MessageFormat.FORMAT_MATRIX_HTML, } else {
) lastContent
}
} }
else -> messageTextContent else -> lastContent
} }
} }

View File

@ -15,6 +15,8 @@
*/ */
package org.matrix.android.sdk.api.util 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 import org.matrix.android.sdk.internal.util.unescapeHtml
object ContentUtils { object ContentUtils {
@ -38,15 +40,36 @@ object ContentUtils {
} }
fun extractUsefulTextFromHtmlReply(repliedBody: String): String { fun extractUsefulTextFromHtmlReply(repliedBody: String): String {
if (repliedBody.startsWith("<mx-reply>")) { if (repliedBody.startsWith(MX_REPLY_START_TAG)) {
val closingTagIndex = repliedBody.lastIndexOf("</mx-reply>") val closingTagIndex = repliedBody.lastIndexOf(MX_REPLY_END_TAG)
if (closingTagIndex != -1) { if (closingTagIndex != -1) {
return repliedBody.substring(closingTagIndex + "</mx-reply>".length).trim() return repliedBody.substring(closingTagIndex + MX_REPLY_END_TAG.length).trim()
} }
} }
return repliedBody 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") @Suppress("RegExpRedundantEscape")
fun formatSpoilerTextFromHtml(formattedBody: String): String { fun formatSpoilerTextFromHtml(formattedBody: String): String {
// var reason = "", // var reason = "",
@ -57,4 +80,6 @@ object ContentUtils {
} }
private const val SPOILER_CHAR = "" 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( override fun editReply(
replyToEdit: TimelineEvent, replyToEdit: TimelineEvent,
originalTimelineEvent: TimelineEvent, originalTimelineEvent: TimelineEvent,
newBodyText: String, newBodyText: CharSequence,
newFormattedBodyText: String?, newFormattedBodyText: String?,
compatibilityBodyText: String compatibilityBodyText: String
): Cancelable { ): Cancelable {

View File

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

View File

@ -312,7 +312,7 @@ internal class LocalEchoEventFactory @Inject constructor(
roomId: String, roomId: String,
eventReplaced: TimelineEvent, eventReplaced: TimelineEvent,
originalEvent: TimelineEvent, originalEvent: TimelineEvent,
newBodyText: String, newBodyText: CharSequence,
autoMarkdown: Boolean, autoMarkdown: Boolean,
msgType: String, msgType: String,
compatibilityText: String, compatibilityText: String,
@ -336,7 +336,7 @@ internal class LocalEchoEventFactory @Inject constructor(
// //
// > <@alice:example.org> This is the original body // > <@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( return createMessageEvent(
roomId, roomId,

View File

@ -178,7 +178,8 @@ class MessageComposerViewModel @AssistedInject constructor(
private fun handleEnterEditMode(room: Room, action: MessageComposerAction.EnterEditMode) { private fun handleEnterEditMode(room: Room, action: MessageComposerAction.EnterEditMode) {
room.getTimelineEvent(action.eventId)?.let { timelineEvent -> room.getTimelineEvent(action.eventId)?.let { timelineEvent ->
val formatted = vectorPreferences.isRichTextEditorEnabled() 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) { if (inReplyTo != null) {
// TODO check if same content? // TODO check if same content?
room.getTimelineEvent(inReplyTo)?.let { 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 { } else {
val messageContent = state.sendMode.timelineEvent.getVectorLastMessageContent() val messageContent = state.sendMode.timelineEvent.getVectorLastMessageContent()
@ -624,14 +625,14 @@ class MessageComposerViewModel @AssistedInject constructor(
state.rootThreadEventId?.let { state.rootThreadEventId?.let {
room.relationService().replyInThread( room.relationService().replyInThread(
rootThreadEventId = it, rootThreadEventId = it,
replyInThreadText = action.text.toString(), replyInThreadText = action.text,
autoMarkdown = action.autoMarkdown, autoMarkdown = action.autoMarkdown,
formattedText = action.formattedText, formattedText = action.formattedText,
eventReplied = timelineEvent eventReplied = timelineEvent
) )
} ?: room.relationService().replyToMessage( } ?: room.relationService().replyToMessage(
eventReplied = timelineEvent, eventReplied = timelineEvent,
replyText = action.text.toString(), replyText = action.text,
replyFormattedText = action.formattedText, replyFormattedText = action.formattedText,
autoMarkdown = action.autoMarkdown, autoMarkdown = action.autoMarkdown,
showInThread = showInThread, 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.MessageFormat
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent 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.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.MatrixItem
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
import javax.inject.Inject import javax.inject.Inject
@ -188,7 +189,12 @@ class PlainTextComposerLayout @JvmOverloads constructor(
var formattedBody: CharSequence? = null var formattedBody: CharSequence? = null
if (messageContent is MessageTextContent && messageContent.format == MessageFormat.FORMAT_MATRIX_HTML) { if (messageContent is MessageTextContent && messageContent.format == MessageFormat.FORMAT_MATRIX_HTML) {
val parser = Parser.builder().build() 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) formattedBody = eventHtmlRenderer.render(document, pillsPostProcessor)
} }
views.composerRelatedMessageContent.text = (formattedBody ?: nonFormattedBody) views.composerRelatedMessageContent.text = (formattedBody ?: nonFormattedBody)