Trim whitespace at the end of messages (#2169)
Trim whitespace at the end of messages
This commit is contained in:
parent
936127c682
commit
0e3f0ac6b5
3 changed files with 42 additions and 29 deletions
1
changelog.d/2099.bugfix
Normal file
1
changelog.d/2099.bugfix
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Trim whitespace at the end of messages to ensure we render the right content.
|
||||||
|
|
@ -72,7 +72,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
||||||
suspend fun create(content: MessageContent, senderDisplayName: String, eventId: EventId?): TimelineItemEventContent {
|
suspend fun create(content: MessageContent, senderDisplayName: String, eventId: EventId?): TimelineItemEventContent {
|
||||||
return when (val messageType = content.type) {
|
return when (val messageType = content.type) {
|
||||||
is EmoteMessageType -> {
|
is EmoteMessageType -> {
|
||||||
val emoteBody = "* $senderDisplayName ${messageType.body}"
|
val emoteBody = "* $senderDisplayName ${messageType.body.trimEnd()}"
|
||||||
TimelineItemEmoteContent(
|
TimelineItemEmoteContent(
|
||||||
body = emoteBody,
|
body = emoteBody,
|
||||||
htmlDocument = messageType.formatted?.toHtmlDocument(prefix = "* $senderDisplayName"),
|
htmlDocument = messageType.formatted?.toHtmlDocument(prefix = "* $senderDisplayName"),
|
||||||
|
|
@ -83,7 +83,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
||||||
is ImageMessageType -> {
|
is ImageMessageType -> {
|
||||||
val aspectRatio = aspectRatioOf(messageType.info?.width, messageType.info?.height)
|
val aspectRatio = aspectRatioOf(messageType.info?.width, messageType.info?.height)
|
||||||
TimelineItemImageContent(
|
TimelineItemImageContent(
|
||||||
body = messageType.body,
|
body = messageType.body.trimEnd(),
|
||||||
mediaSource = messageType.source,
|
mediaSource = messageType.source,
|
||||||
thumbnailSource = messageType.info?.thumbnailSource,
|
thumbnailSource = messageType.info?.thumbnailSource,
|
||||||
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
|
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
|
||||||
|
|
@ -98,7 +98,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
||||||
is StickerMessageType -> {
|
is StickerMessageType -> {
|
||||||
val aspectRatio = aspectRatioOf(messageType.info?.width, messageType.info?.height)
|
val aspectRatio = aspectRatioOf(messageType.info?.width, messageType.info?.height)
|
||||||
TimelineItemStickerContent(
|
TimelineItemStickerContent(
|
||||||
body = messageType.body,
|
body = messageType.body.trimEnd(),
|
||||||
mediaSource = messageType.source,
|
mediaSource = messageType.source,
|
||||||
thumbnailSource = messageType.info?.thumbnailSource,
|
thumbnailSource = messageType.info?.thumbnailSource,
|
||||||
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
|
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
|
||||||
|
|
@ -113,16 +113,17 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
||||||
is LocationMessageType -> {
|
is LocationMessageType -> {
|
||||||
val location = Location.fromGeoUri(messageType.geoUri)
|
val location = Location.fromGeoUri(messageType.geoUri)
|
||||||
if (location == null) {
|
if (location == null) {
|
||||||
|
val body = messageType.body.trimEnd()
|
||||||
TimelineItemTextContent(
|
TimelineItemTextContent(
|
||||||
body = messageType.body,
|
body = body,
|
||||||
htmlDocument = null,
|
htmlDocument = null,
|
||||||
plainText = messageType.body,
|
plainText = body,
|
||||||
formattedBody = null,
|
formattedBody = null,
|
||||||
isEdited = content.isEdited,
|
isEdited = content.isEdited,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
TimelineItemLocationContent(
|
TimelineItemLocationContent(
|
||||||
body = messageType.body,
|
body = messageType.body.trimEnd(),
|
||||||
location = location,
|
location = location,
|
||||||
description = messageType.description
|
description = messageType.description
|
||||||
)
|
)
|
||||||
|
|
@ -131,7 +132,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
||||||
is VideoMessageType -> {
|
is VideoMessageType -> {
|
||||||
val aspectRatio = aspectRatioOf(messageType.info?.width, messageType.info?.height)
|
val aspectRatio = aspectRatioOf(messageType.info?.width, messageType.info?.height)
|
||||||
TimelineItemVideoContent(
|
TimelineItemVideoContent(
|
||||||
body = messageType.body,
|
body = messageType.body.trimEnd(),
|
||||||
thumbnailSource = messageType.info?.thumbnailSource,
|
thumbnailSource = messageType.info?.thumbnailSource,
|
||||||
videoSource = messageType.source,
|
videoSource = messageType.source,
|
||||||
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
|
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
|
||||||
|
|
@ -146,7 +147,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
||||||
}
|
}
|
||||||
is AudioMessageType -> {
|
is AudioMessageType -> {
|
||||||
TimelineItemAudioContent(
|
TimelineItemAudioContent(
|
||||||
body = messageType.body,
|
body = messageType.body.trimEnd(),
|
||||||
mediaSource = messageType.source,
|
mediaSource = messageType.source,
|
||||||
duration = messageType.info?.duration ?: Duration.ZERO,
|
duration = messageType.info?.duration ?: Duration.ZERO,
|
||||||
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
|
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
|
||||||
|
|
@ -159,7 +160,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
||||||
true -> {
|
true -> {
|
||||||
TimelineItemVoiceContent(
|
TimelineItemVoiceContent(
|
||||||
eventId = eventId,
|
eventId = eventId,
|
||||||
body = messageType.body,
|
body = messageType.body.trimEnd(),
|
||||||
mediaSource = messageType.source,
|
mediaSource = messageType.source,
|
||||||
duration = messageType.info?.duration ?: Duration.ZERO,
|
duration = messageType.info?.duration ?: Duration.ZERO,
|
||||||
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
|
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
|
||||||
|
|
@ -168,7 +169,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
||||||
}
|
}
|
||||||
false -> {
|
false -> {
|
||||||
TimelineItemAudioContent(
|
TimelineItemAudioContent(
|
||||||
body = messageType.body,
|
body = messageType.body.trimEnd(),
|
||||||
mediaSource = messageType.source,
|
mediaSource = messageType.source,
|
||||||
duration = messageType.info?.duration ?: Duration.ZERO,
|
duration = messageType.info?.duration ?: Duration.ZERO,
|
||||||
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
|
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
|
||||||
|
|
@ -181,7 +182,7 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
||||||
is FileMessageType -> {
|
is FileMessageType -> {
|
||||||
val fileExtension = fileExtensionExtractor.extractFromName(messageType.body)
|
val fileExtension = fileExtensionExtractor.extractFromName(messageType.body)
|
||||||
TimelineItemFileContent(
|
TimelineItemFileContent(
|
||||||
body = messageType.body,
|
body = messageType.body.trimEnd(),
|
||||||
thumbnailSource = messageType.info?.thumbnailSource,
|
thumbnailSource = messageType.info?.thumbnailSource,
|
||||||
fileSource = messageType.source,
|
fileSource = messageType.source,
|
||||||
mimeType = messageType.info?.mimetype ?: MimeTypes.fromFileExtension(fileExtension),
|
mimeType = messageType.info?.mimetype ?: MimeTypes.fromFileExtension(fileExtension),
|
||||||
|
|
@ -189,26 +190,33 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
||||||
fileExtension = fileExtension
|
fileExtension = fileExtension
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is NoticeMessageType -> TimelineItemNoticeContent(
|
is NoticeMessageType -> {
|
||||||
body = messageType.body,
|
val body = messageType.body.trimEnd()
|
||||||
htmlDocument = messageType.formatted?.toHtmlDocument(),
|
TimelineItemNoticeContent(
|
||||||
formattedBody = parseHtml(messageType.formatted) ?: messageType.body.withLinks(),
|
body = body,
|
||||||
isEdited = content.isEdited,
|
|
||||||
)
|
|
||||||
is TextMessageType -> {
|
|
||||||
TimelineItemTextContent(
|
|
||||||
body = messageType.body,
|
|
||||||
htmlDocument = messageType.formatted?.toHtmlDocument(),
|
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,
|
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? {
|
private fun parseHtml(formattedBody: FormattedBody?, prefix: String? = null): CharSequence? {
|
||||||
if (formattedBody == null || formattedBody.format != MessageFormat.HTML) return null
|
if (formattedBody == null || formattedBody.format != MessageFormat.HTML) return null
|
||||||
val result = htmlConverterProvider.provide()
|
val result = htmlConverterProvider.provide()
|
||||||
.fromHtmlToSpans(formattedBody.body)
|
.fromHtmlToSpans(formattedBody.body.trimEnd())
|
||||||
.withFixedURLSpans()
|
.withFixedURLSpans()
|
||||||
return if (prefix != null) {
|
return if (prefix != null) {
|
||||||
buildSpannedString {
|
buildSpannedString {
|
||||||
|
|
|
||||||
|
|
@ -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.
|
* @param prefix if not null, the prefix will be inserted at the beginning of the message.
|
||||||
*/
|
*/
|
||||||
fun FormattedBody.toHtmlDocument(prefix: String? = null): Document? {
|
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) {
|
val dom = if (prefix != null) {
|
||||||
Jsoup.parse("$prefix $formattedBody")
|
Jsoup.parse("$prefix $formattedBody")
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue