diff --git a/CHANGES.md b/CHANGES.md index 5f83727cd7..50279bcf0e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,6 +15,7 @@ Bugfix 🐛: - Scroll breadcrumbs to top when opened - Render default room name when it starts with an emoji (#477) - Do not display " (IRC)" in display names https://github.com/vector-im/riot-android/issues/444 + - Fix rendering issue with HTML formatted body Translations 🗣: - diff --git a/vector/build.gradle b/vector/build.gradle index 8a2df7c120..de15a67fbd 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -293,6 +293,7 @@ dependencies { implementation 'me.gujun.android:span:1.7' implementation "io.noties.markwon:core:$markwon_version" implementation "io.noties.markwon:html:$markwon_version" + implementation 'com.googlecode.htmlcompressor:htmlcompressor:1.4' implementation 'me.saket:better-link-movement-method:2.2.0' implementation 'com.google.android:flexbox:1.1.1' implementation "androidx.autofill:autofill:$autofill_version" diff --git a/vector/src/main/assets/open_source_licenses.html b/vector/src/main/assets/open_source_licenses.html index 3cd500138a..2b08270c89 100755 --- a/vector/src/main/assets/open_source_licenses.html +++ b/vector/src/main/assets/open_source_licenses.html @@ -359,6 +359,11 @@ SOFTWARE.
Copyright 2018 Kumar Bibek +
  • + htmlcompressor +
    + Copyright 2017 Sergiy Kovalchuk +
  •  Apache License
    diff --git a/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt b/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt
    index c8c5d697f6..b78e291506 100644
    --- a/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt
    +++ b/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt
    @@ -38,6 +38,7 @@ import im.vector.riotx.features.home.AvatarRenderer
     import im.vector.riotx.features.home.HomeRoomListDataSource
     import im.vector.riotx.features.home.group.SelectedGroupDataSource
     import im.vector.riotx.features.html.EventHtmlRenderer
    +import im.vector.riotx.features.html.VectorHtmlCompressor
     import im.vector.riotx.features.navigation.Navigator
     import im.vector.riotx.features.notifications.*
     import im.vector.riotx.features.rageshake.BugReporter
    @@ -87,6 +88,8 @@ interface VectorComponent {
     
         fun eventHtmlRenderer(): EventHtmlRenderer
     
    +    fun vectorHtmlCompressor(): VectorHtmlCompressor
    +
         fun navigator(): Navigator
     
         fun errorFormatter(): ErrorFormatter
    diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
    index 102412948b..3c7e4624fe 100644
    --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
    +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
    @@ -41,6 +41,7 @@ import im.vector.riotx.core.resources.StringProvider
     import im.vector.riotx.features.home.room.detail.timeline.format.NoticeEventFormatter
     import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
     import im.vector.riotx.features.html.EventHtmlRenderer
    +import im.vector.riotx.features.html.VectorHtmlCompressor
     import java.text.SimpleDateFormat
     import java.util.*
     
    @@ -82,6 +83,7 @@ data class MessageActionState(
     class MessageActionsViewModel @AssistedInject constructor(@Assisted
                                                               initialState: MessageActionState,
                                                               private val eventHtmlRenderer: Lazy,
    +                                                          private val htmlCompressor: VectorHtmlCompressor,
                                                               private val session: Session,
                                                               private val noticeEventFormatter: NoticeEventFormatter,
                                                               private val stringProvider: StringProvider
    @@ -170,8 +172,12 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
                 EventType.MESSAGE     -> {
                     val messageContent: MessageContent? = timelineEvent()?.getLastMessageContent()
                     if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
    -                    eventHtmlRenderer.get().render(messageContent.formattedBody
    -                            ?: messageContent.body)
    +                    val html = messageContent.formattedBody
    +                            ?.takeIf { it.isNotBlank() }
    +                            ?.let { htmlCompressor.compress(it) }
    +                            ?: messageContent.body
    +
    +                    eventHtmlRenderer.get().render(html)
                     } else {
                         messageContent?.body
                     }
    diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt
    index 9c96f17022..30f4e94cfb 100644
    --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt
    +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt
    @@ -46,6 +46,7 @@ import im.vector.riotx.features.home.room.detail.timeline.tools.createLinkMoveme
     import im.vector.riotx.features.home.room.detail.timeline.tools.linkify
     import im.vector.riotx.features.html.CodeVisitor
     import im.vector.riotx.features.html.EventHtmlRenderer
    +import im.vector.riotx.features.html.VectorHtmlCompressor
     import im.vector.riotx.features.media.ImageContentRenderer
     import im.vector.riotx.features.media.VideoContentRenderer
     import me.gujun.android.span.span
    @@ -57,6 +58,7 @@ class MessageItemFactory @Inject constructor(
             private val dimensionConverter: DimensionConverter,
             private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
             private val htmlRenderer: Lazy,
    +        private val htmlCompressor: VectorHtmlCompressor,
             private val stringProvider: StringProvider,
             private val imageContentRenderer: ImageContentRenderer,
             private val messageInformationDataFactory: MessageInformationDataFactory,
    @@ -227,6 +229,7 @@ class MessageItemFactory @Inject constructor(
                                             attributes: AbsMessageItem.Attributes): VectorEpoxyModel<*>? {
             val isFormatted = messageContent.formattedBody.isNullOrBlank().not()
             return if (isFormatted) {
    +            // First detect if the message contains some code block(s) or inline code
                 val localFormattedBody = htmlRenderer.get().parse(messageContent.body) as Document
                 val codeVisitor = CodeVisitor()
                 codeVisitor.visit(localFormattedBody)
    @@ -240,7 +243,8 @@ class MessageItemFactory @Inject constructor(
                         buildMessageTextItem(codeFormatted, false, informationData, highlight, callback, attributes)
                     }
                     CodeVisitor.Kind.NONE   -> {
    -                    val formattedBody = htmlRenderer.get().render(messageContent.formattedBody!!)
    +                    val compressed = htmlCompressor.compress(messageContent.formattedBody!!)
    +                    val formattedBody = htmlRenderer.get().render(compressed)
                         buildMessageTextItem(formattedBody, true, informationData, highlight, callback, attributes)
                     }
                 }
    diff --git a/vector/src/main/java/im/vector/riotx/features/html/VectorHtmlCompressor.kt b/vector/src/main/java/im/vector/riotx/features/html/VectorHtmlCompressor.kt
    new file mode 100644
    index 0000000000..9f3cf96a7e
    --- /dev/null
    +++ b/vector/src/main/java/im/vector/riotx/features/html/VectorHtmlCompressor.kt
    @@ -0,0 +1,40 @@
    +/*
    + * Copyright 2019 New Vector Ltd
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +package im.vector.riotx.features.html
    +
    +import com.googlecode.htmlcompressor.compressor.Compressor
    +import com.googlecode.htmlcompressor.compressor.HtmlCompressor
    +import javax.inject.Inject
    +import javax.inject.Singleton
    +
    +@Singleton
    +class VectorHtmlCompressor @Inject constructor() {
    +
    +    // All default options are suitable so far
    +    private val htmlCompressor: Compressor = HtmlCompressor()
    +
    +    fun compress(html: String): String {
    +        var result = htmlCompressor.compress(html)
    +
    +        // Trim space after 
    and

    , unfortunately the method setRemoveSurroundingSpaces() from the doc does not exist + result = result.replace("
    ", "
    ") + result = result.replace("
    ", "
    ") + result = result.replace("

    ", "

    ") + + return result + } +}