When linkifying HTML messages, give priority to explicit link tags (#2879)

* When linkifying HTML messages, give priority to explicit link tags
This commit is contained in:
Jorge Martin Espinosa 2024-05-20 13:09:37 +02:00 committed by GitHub
parent 616370f31e
commit 4919cd69b1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 39 additions and 4 deletions

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

@ -0,0 +1 @@
Make sure explicit links in messages take priority over links found by linkification (urls, emails, phone numbers, etc.)

View file

@ -268,12 +268,16 @@ class TimelineItemContentMessageFactory @Inject constructor(
}
// Find and set as URLSpans any links present in the text
LinkifyCompat.addLinks(this, Linkify.WEB_URLS or Linkify.PHONE_NUMBERS or Linkify.EMAIL_ADDRESSES)
// Restore old spans if they don't conflict with the new ones
// Restore old spans, remove new ones if there is a conflict
for ((urlSpan, location) in oldURLSpans) {
val (start, end) = location
if (getSpans<URLSpan>(start, end).isEmpty()) {
setSpan(urlSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
val addedSpans = getSpans<URLSpan>(start, end).orEmpty()
if (addedSpans.isNotEmpty()) {
for (span in addedSpans) {
removeSpan(span)
}
}
setSpan(urlSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
return this
}

View file

@ -16,7 +16,9 @@
package io.element.android.features.messages.impl.timeline.factories.event
import android.net.Uri
import android.text.SpannableString
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.style.URLSpan
import androidx.core.text.buildSpannedString
@ -46,6 +48,7 @@ import io.element.android.libraries.matrix.api.media.ImageInfo
import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.api.media.ThumbnailInfo
import io.element.android.libraries.matrix.api.media.VideoInfo
import io.element.android.libraries.matrix.api.permalink.PermalinkData
import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.FileMessageType
@ -75,6 +78,7 @@ import org.robolectric.RobolectricTestRunner
import kotlin.time.Duration
import kotlin.time.Duration.Companion.minutes
@Suppress("LargeClass")
@RunWith(RobolectricTestRunner::class)
class TimelineItemContentMessageFactoryTest {
@Test
@ -641,6 +645,31 @@ class TimelineItemContentMessageFactoryTest {
assertThat((result as TimelineItemEmoteContent).formattedBody).isEqualTo(SpannableString("* Bob formatted"))
}
@Test
fun `a message with existing URLSpans keeps it after linkification`() = runTest {
val expectedSpanned = SpannableStringBuilder().apply {
append("Test ")
inSpans(URLSpan("https://www.example.org")) {
append("me@matrix.org")
}
}
val sut = createTimelineItemContentMessageFactory(
htmlConverterTransform = { expectedSpanned },
permalinkParser = FakePermalinkParser { PermalinkData.FallbackLink(Uri.EMPTY) }
)
val result = sut.create(
content = createMessageContent(
type = TextMessageType(
body = "Test [me@matrix.org](https://www.example.org)",
formatted = FormattedBody(MessageFormat.HTML, "Test <a href=\"https://www.example.org\">me@matrix.org</a>")
)
),
senderDisambiguatedDisplayName = "Bob",
eventId = AN_EVENT_ID,
)
assertThat((result as TimelineItemTextContent).formattedBody).isEqualTo(expectedSpanned)
}
private fun createMessageContent(
body: String = "Body",
inReplyTo: InReplyTo? = null,
@ -660,12 +689,13 @@ class TimelineItemContentMessageFactoryTest {
private fun createTimelineItemContentMessageFactory(
featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
htmlConverterTransform: (String) -> CharSequence = { it },
permalinkParser: FakePermalinkParser = FakePermalinkParser(),
) = TimelineItemContentMessageFactory(
fileSizeFormatter = FakeFileSizeFormatter(),
fileExtensionExtractor = FileExtensionExtractorWithoutValidation(),
featureFlagService = featureFlagService,
htmlConverterProvider = FakeHtmlConverterProvider(htmlConverterTransform),
permalinkParser = FakePermalinkParser(),
permalinkParser = permalinkParser,
)
private fun createStickerContent(