Fix a bunch of issues related to edition and reply #5969
This commit is contained in:
parent
c46b3148e4
commit
b72039e735
|
@ -0,0 +1 @@
|
||||||
|
Fix some issues related to edition and reply of events.
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue