Trim whitespace at the end of messages (#2169)

Trim whitespace at the end of messages
This commit is contained in:
Jorge Martin Espinosa 2024-01-05 12:40:10 +01:00 committed by GitHub
parent 4776fc5452
commit e04780fbf6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 42 additions and 29 deletions

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

@ -0,0 +1 @@
Trim whitespace at the end of messages to ensure we render the right content.

View file

@ -72,7 +72,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
suspend fun create(content: MessageContent, senderDisplayName: String, eventId: EventId?): TimelineItemEventContent {
return when (val messageType = content.type) {
is EmoteMessageType -> {
val emoteBody = "* $senderDisplayName ${messageType.body}"
val emoteBody = "* $senderDisplayName ${messageType.body.trimEnd()}"
TimelineItemEmoteContent(
body = emoteBody,
htmlDocument = messageType.formatted?.toHtmlDocument(prefix = "* $senderDisplayName"),
@ -83,7 +83,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
is ImageMessageType -> {
val aspectRatio = aspectRatioOf(messageType.info?.width, messageType.info?.height)
TimelineItemImageContent(
body = messageType.body,
body = messageType.body.trimEnd(),
mediaSource = messageType.source,
thumbnailSource = messageType.info?.thumbnailSource,
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
@ -98,7 +98,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
is StickerMessageType -> {
val aspectRatio = aspectRatioOf(messageType.info?.width, messageType.info?.height)
TimelineItemStickerContent(
body = messageType.body,
body = messageType.body.trimEnd(),
mediaSource = messageType.source,
thumbnailSource = messageType.info?.thumbnailSource,
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
@ -113,16 +113,17 @@ class TimelineItemContentMessageFactory @Inject constructor(
is LocationMessageType -> {
val location = Location.fromGeoUri(messageType.geoUri)
if (location == null) {
val body = messageType.body.trimEnd()
TimelineItemTextContent(
body = messageType.body,
body = body,
htmlDocument = null,
plainText = messageType.body,
plainText = body,
formattedBody = null,
isEdited = content.isEdited,
)
} else {
TimelineItemLocationContent(
body = messageType.body,
body = messageType.body.trimEnd(),
location = location,
description = messageType.description
)
@ -131,7 +132,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
is VideoMessageType -> {
val aspectRatio = aspectRatioOf(messageType.info?.width, messageType.info?.height)
TimelineItemVideoContent(
body = messageType.body,
body = messageType.body.trimEnd(),
thumbnailSource = messageType.info?.thumbnailSource,
videoSource = messageType.source,
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
@ -146,7 +147,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
}
is AudioMessageType -> {
TimelineItemAudioContent(
body = messageType.body,
body = messageType.body.trimEnd(),
mediaSource = messageType.source,
duration = messageType.info?.duration ?: Duration.ZERO,
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
@ -159,7 +160,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
true -> {
TimelineItemVoiceContent(
eventId = eventId,
body = messageType.body,
body = messageType.body.trimEnd(),
mediaSource = messageType.source,
duration = messageType.info?.duration ?: Duration.ZERO,
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
@ -168,7 +169,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
}
false -> {
TimelineItemAudioContent(
body = messageType.body,
body = messageType.body.trimEnd(),
mediaSource = messageType.source,
duration = messageType.info?.duration ?: Duration.ZERO,
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
@ -181,7 +182,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
is FileMessageType -> {
val fileExtension = fileExtensionExtractor.extractFromName(messageType.body)
TimelineItemFileContent(
body = messageType.body,
body = messageType.body.trimEnd(),
thumbnailSource = messageType.info?.thumbnailSource,
fileSource = messageType.source,
mimeType = messageType.info?.mimetype ?: MimeTypes.fromFileExtension(fileExtension),
@ -189,26 +190,33 @@ class TimelineItemContentMessageFactory @Inject constructor(
fileExtension = fileExtension
)
}
is NoticeMessageType -> TimelineItemNoticeContent(
body = messageType.body,
htmlDocument = messageType.formatted?.toHtmlDocument(),
formattedBody = parseHtml(messageType.formatted) ?: messageType.body.withLinks(),
isEdited = content.isEdited,
)
is TextMessageType -> {
TimelineItemTextContent(
body = messageType.body,
is NoticeMessageType -> {
val body = messageType.body.trimEnd()
TimelineItemNoticeContent(
body = body,
htmlDocument = messageType.formatted?.toHtmlDocument(),
formattedBody = parseHtml(messageType.formatted) ?: messageType.body.withLinks(),
formattedBody = parseHtml(messageType.formatted) ?:body.withLinks(),
isEdited = content.isEdited,
)
}
is TextMessageType -> {
val body = messageType.body.trimEnd()
TimelineItemTextContent(
body = body,
htmlDocument = messageType.formatted?.toHtmlDocument(),
formattedBody = parseHtml(messageType.formatted) ?: body.withLinks(),
isEdited = content.isEdited,
)
}
is OtherMessageType -> {
val body = messageType.body.trimEnd()
TimelineItemTextContent(
body = body,
htmlDocument = null,
formattedBody = body.withLinks(),
isEdited = content.isEdited,
)
}
is OtherMessageType -> TimelineItemTextContent(
body = messageType.body,
htmlDocument = null,
formattedBody = messageType.body.withLinks(),
isEdited = content.isEdited,
)
}
}
@ -225,7 +233,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
private fun parseHtml(formattedBody: FormattedBody?, prefix: String? = null): CharSequence? {
if (formattedBody == null || formattedBody.format != MessageFormat.HTML) return null
val result = htmlConverterProvider.provide()
.fromHtmlToSpans(formattedBody.body)
.fromHtmlToSpans(formattedBody.body.trimEnd())
.withFixedURLSpans()
return if (prefix != null) {
buildSpannedString {

View file

@ -32,7 +32,11 @@ import org.jsoup.nodes.Document
* @param prefix if not null, the prefix will be inserted at the beginning of the message.
*/
fun FormattedBody.toHtmlDocument(prefix: String? = null): Document? {
return takeIf { it.format == MessageFormat.HTML }?.body?.let { formattedBody ->
return takeIf { it.format == MessageFormat.HTML }?.body
// Trim whitespace at the end to avoid having wrong rendering of the message.
// We don't trim the start in case it's used as indentation.
?.trimEnd()
?.let { formattedBody ->
val dom = if (prefix != null) {
Jsoup.parse("$prefix $formattedBody")
} else {