Force markdown parse on replies and update quote to use markdown parser.

This commit is contained in:
David Langley 2021-11-22 17:02:12 +00:00
parent 289339f2db
commit 2b3de840f1
5 changed files with 73 additions and 25 deletions

View File

@ -56,6 +56,16 @@ interface SendService {
*/
fun sendFormattedTextMessage(text: String, formattedText: String, msgType: String = MessageType.MSGTYPE_TEXT): Cancelable
/**
* Method to quote an events content.
* @param quotedEvent The event to which we will quote it's content.
* @param text the text message to send
* @param msgType the message type: MessageType.MSGTYPE_TEXT (default) or MessageType.MSGTYPE_EMOTE
* @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present
* @return a [Cancelable]
*/
fun sendQuotedTextMessage(quotedEvent: TimelineEvent, text: String, autoMarkdown: Boolean = false): Cancelable
/**
* Method to send a media asynchronously.
* @param attachment the media to send

View File

@ -97,6 +97,12 @@ internal class DefaultSendService @AssistedInject constructor(
.let { sendEvent(it) }
}
override fun sendQuotedTextMessage(quotedEvent: TimelineEvent, text: String, autoMarkdown: Boolean): Cancelable {
return localEchoEventFactory.createQuotedTextEvent(roomId, quotedEvent, text, autoMarkdown)
.also { createLocalEcho(it) }
.let { sendEvent(it) }
}
override fun sendPoll(question: String, options: List<String>): Cancelable {
return localEchoEventFactory.createPollEvent(roomId, question, options)
.also { createLocalEcho(it) }

View File

@ -20,6 +20,7 @@ import android.content.Context
import android.graphics.Bitmap
import android.media.MediaMetadataRetriever
import androidx.exifinterface.media.ExifInterface
import okhttp3.internal.format
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.events.model.Event
@ -88,9 +89,9 @@ internal class LocalEchoEventFactory @Inject constructor(
return createMessageEvent(roomId, content)
}
private fun createTextContent(text: CharSequence, autoMarkdown: Boolean): TextContent {
private fun createTextContent(text: CharSequence, autoMarkdown: Boolean, forceMarkdownParse: Boolean = false): TextContent {
if (autoMarkdown) {
return markdownParser.parse(text)
return markdownParser.parse(text, force = forceMarkdownParse)
} else {
// Try to detect pills
textPillsUtils.processSpecialSpansToHtml(text)?.let {
@ -175,13 +176,17 @@ internal class LocalEchoEventFactory @Inject constructor(
val userLink = originalEvent.root.senderId?.let { permalinkFactory.createPermalink(it, false) } ?: ""
val body = bodyForReply(originalEvent.getLastMessageContent(), originalEvent.isReply())
// As we always supply formatted body for replies we should force the MarkdownParser to produce html.
val newBodyFormatted = createTextContent(newBodyText, newBodyAutoMarkdown, forceMarkdownParse = true).takeFormatted()
// Body of the original message may not have formatted version, so may also have to convert to html.
val bodyFormatted = body.formattedText ?: createTextContent(body.text, newBodyAutoMarkdown, forceMarkdownParse = true).takeFormatted()
val replyFormatted = REPLY_PATTERN.format(
permalink,
userLink,
originalEvent.senderInfo.disambiguatedDisplayName,
// Remove inner mx_reply tags if any
body.takeFormatted().replace(MX_REPLY_REGEX, ""),
createTextContent(newBodyText, newBodyAutoMarkdown).takeFormatted()
bodyFormatted.replace(MX_REPLY_REGEX, ""),
newBodyFormatted
)
//
// > <@alice:example.org> This is the original body
@ -361,13 +366,18 @@ internal class LocalEchoEventFactory @Inject constructor(
val userLink = permalinkFactory.createPermalink(userId, false) ?: return null
val body = bodyForReply(eventReplied.getLastMessageContent(), eventReplied.isReply())
// As we always supply formatted body for replies we should force the MarkdownParser to produce html.
val replyTextFormatted = createTextContent(replyText, autoMarkdown, forceMarkdownParse = true).takeFormatted()
// Body of the original message may not have formatted version, so may also have to convert to html.
val bodyFormatted = body.formattedText ?: createTextContent(body.text, autoMarkdown, forceMarkdownParse = true).takeFormatted()
val replyFormatted = REPLY_PATTERN.format(
permalink,
userLink,
userId,
// Remove inner mx_reply tags if any
body.takeFormatted().replace(MX_REPLY_REGEX, ""),
createTextContent(replyText, autoMarkdown).takeFormatted()
bodyFormatted.replace(MX_REPLY_REGEX, ""),
replyTextFormatted
)
//
// > <@alice:example.org> This is the original body
@ -467,6 +477,38 @@ internal class LocalEchoEventFactory @Inject constructor(
localEchoRepository.createLocalEcho(event)
}
fun createQuotedTextEvent(
roomId: String,
quotedEvent: TimelineEvent,
text: String,
autoMarkdown: Boolean
): Event {
val messageContent = quotedEvent.getLastMessageContent()
val textMsg = messageContent?.body
val quoteText = legacyRiotQuoteText(textMsg, text)
return createFormattedTextEvent(roomId, createTextContent(quoteText, autoMarkdown), MessageType.MSGTYPE_TEXT)
}
private fun legacyRiotQuoteText(quotedText: String?, myText: String): String {
val messageParagraphs = quotedText?.split("\n\n".toRegex())?.dropLastWhile { it.isEmpty() }?.toTypedArray()
return buildString {
if (messageParagraphs != null) {
for (i in messageParagraphs.indices) {
if (messageParagraphs[i].isNotBlank()) {
append("> ")
append(messageParagraphs[i])
}
if (i != messageParagraphs.lastIndex) {
append("\n\n")
}
}
}
append("\n\n")
append(myText)
}
}
companion object {
// <mx-reply>
// <blockquote>

View File

@ -34,11 +34,17 @@ internal class MarkdownParser @Inject constructor(
private val mdSpecialChars = "[`_\\-*>.\\[\\]#~]".toRegex()
fun parse(text: CharSequence): TextContent {
/**
* Parses some input text and produces html.
* @param text An input CharSequence to be parsed.
* @param force Skips the check for detecting if the input contains markdown and always converts to html.
* @return TextContent containing the plain text and the formatted html if generated.
*/
fun parse(text: CharSequence, force: Boolean = false): TextContent {
val source = textPillsUtils.processSpecialSpansToMarkdown(text) ?: text.toString()
// If no special char are detected, just return plain text
if (source.contains(mdSpecialChars).not()) {
if (!force && source.contains(mdSpecialChars).not()) {
return TextContent(source)
}

View File

@ -395,23 +395,7 @@ class TextComposerViewModel @AssistedInject constructor(
popDraft()
}
is SendMode.QUOTE -> {
val messageContent = state.sendMode.timelineEvent.getLastMessageContent()
val textMsg = messageContent?.body
val finalText = legacyRiotQuoteText(textMsg, action.text.toString())
// TODO check for pills?
// TODO Refactor this, just temporary for quotes
val parser = Parser.builder().build()
val document = parser.parse(finalText)
val renderer = HtmlRenderer.builder().build()
val htmlText = renderer.render(document)
if (finalText == htmlText) {
room.sendTextMessage(finalText)
} else {
room.sendFormattedTextMessage(finalText, htmlText)
}
room.sendQuotedTextMessage(state.sendMode.timelineEvent, action.text.toString(), action.autoMarkdown)
_viewEvents.post(TextComposerViewEvents.MessageSent)
popDraft()
}