Restore intentional mentions in the markdown/plain text editor (#3193)
* Restore intentional mentions in the markdown/plain text editor --------- Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
parent
5493d6b741
commit
2ff5fa67fc
31 changed files with 717 additions and 288 deletions
|
|
@ -16,13 +16,16 @@
|
|||
|
||||
package io.element.android.libraries.matrix.api.core
|
||||
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
|
||||
|
||||
/**
|
||||
* This class contains pattern to match the different Matrix ids
|
||||
* Ref: https://matrix.org/docs/spec/appendices#identifier-grammar
|
||||
*/
|
||||
object MatrixPatterns {
|
||||
// Note: TLD is not mandatory (localhost, IP address...)
|
||||
private const val DOMAIN_REGEX = ":[A-Z0-9.-]+(:[0-9]{2,5})?"
|
||||
private const val DOMAIN_REGEX = ":[A-Za-z0-9.-]+(:[0-9]{2,5})?"
|
||||
|
||||
// regex pattern to find matrix user ids in a string.
|
||||
// See https://matrix.org/docs/spec/appendices#historical-user-ids
|
||||
|
|
@ -109,4 +112,56 @@ object MatrixPatterns {
|
|||
* @return true if the string is a valid thread id.
|
||||
*/
|
||||
fun isThreadId(str: String?) = isEventId(str)
|
||||
|
||||
/**
|
||||
* Finds existing ids or aliases in a [CharSequence].
|
||||
* Note not all cases are implemented.
|
||||
*/
|
||||
fun findPatterns(text: CharSequence, permalinkParser: PermalinkParser): List<MatrixPatternResult> {
|
||||
val rawTextMatches = "\\S+?$DOMAIN_REGEX".toRegex(RegexOption.IGNORE_CASE).findAll(text)
|
||||
val urlMatches = "\\[\\S+?\\]\\((\\S+?)\\)".toRegex(RegexOption.IGNORE_CASE).findAll(text)
|
||||
val atRoomMatches = Regex("@room").findAll(text)
|
||||
return buildList {
|
||||
for (match in rawTextMatches) {
|
||||
// Match existing id and alias patterns in the text
|
||||
val type = when {
|
||||
isUserId(match.value) -> MatrixPatternType.USER_ID
|
||||
isRoomId(match.value) -> MatrixPatternType.ROOM_ID
|
||||
isRoomAlias(match.value) -> MatrixPatternType.ROOM_ALIAS
|
||||
isEventId(match.value) -> MatrixPatternType.EVENT_ID
|
||||
else -> null
|
||||
}
|
||||
if (type != null) {
|
||||
add(MatrixPatternResult(type, match.value, match.range.first, match.range.last + 1))
|
||||
}
|
||||
}
|
||||
for (match in urlMatches) {
|
||||
// Extract the link and check if it's a valid permalink
|
||||
val urlMatch = match.groupValues[1]
|
||||
when (val permalink = permalinkParser.parse(urlMatch)) {
|
||||
is PermalinkData.UserLink -> {
|
||||
add(MatrixPatternResult(MatrixPatternType.USER_ID, permalink.userId.toString(), match.range.first, match.range.last + 1))
|
||||
}
|
||||
is PermalinkData.RoomLink -> {
|
||||
add(MatrixPatternResult(MatrixPatternType.ROOM_ALIAS, permalink.roomIdOrAlias.identifier, match.range.first, match.range.last + 1))
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
for (match in atRoomMatches) {
|
||||
// Special case for `@room` mentions
|
||||
add(MatrixPatternResult(MatrixPatternType.AT_ROOM, match.value, match.range.first, match.range.last + 1))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class MatrixPatternType {
|
||||
USER_ID,
|
||||
ROOM_ID,
|
||||
ROOM_ALIAS,
|
||||
EVENT_ID,
|
||||
AT_ROOM
|
||||
}
|
||||
|
||||
data class MatrixPatternResult(val type: MatrixPatternType, val value: String, val start: Int, val end: Int)
|
||||
|
|
|
|||
|
|
@ -16,12 +16,15 @@
|
|||
|
||||
package io.element.android.libraries.matrix.api.permalink
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
|
||||
interface PermalinkBuilder {
|
||||
fun permalinkForUser(userId: UserId): Result<String>
|
||||
fun permalinkForRoomAlias(roomAlias: RoomAlias): Result<String>
|
||||
}
|
||||
|
||||
sealed class PermalinkBuilderError : Throwable() {
|
||||
data object InvalidUserId : PermalinkBuilderError()
|
||||
data object InvalidRoomAlias : PermalinkBuilderError()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Copyright (c) 2024 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
|
||||
*
|
||||
* https://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 io.element.android.libraries.matrix.api.core
|
||||
|
||||
import android.net.Uri
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
|
||||
import org.junit.Test
|
||||
|
||||
class MatrixPatternsTest {
|
||||
@Test
|
||||
fun `findPatterns - returns raw user ids`() {
|
||||
val text = "A @user:server.com and @user2:server.com"
|
||||
val patterns = MatrixPatterns.findPatterns(text, aPermalinkParser())
|
||||
assertThat(patterns).containsExactly(
|
||||
MatrixPatternResult(MatrixPatternType.USER_ID, "@user:server.com", 2, 18),
|
||||
MatrixPatternResult(MatrixPatternType.USER_ID, "@user2:server.com", 23, 40)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `findPatterns - returns raw room ids`() {
|
||||
val text = "A !room:server.com and !room2:server.com"
|
||||
val patterns = MatrixPatterns.findPatterns(text, aPermalinkParser())
|
||||
assertThat(patterns).containsExactly(
|
||||
MatrixPatternResult(MatrixPatternType.ROOM_ID, "!room:server.com", 2, 18),
|
||||
MatrixPatternResult(MatrixPatternType.ROOM_ID, "!room2:server.com", 23, 40)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `findPatterns - returns raw room aliases`() {
|
||||
val text = "A #room:server.com and #room2:server.com"
|
||||
val patterns = MatrixPatterns.findPatterns(text, aPermalinkParser())
|
||||
assertThat(patterns).containsExactly(
|
||||
MatrixPatternResult(MatrixPatternType.ROOM_ALIAS, "#room:server.com", 2, 18),
|
||||
MatrixPatternResult(MatrixPatternType.ROOM_ALIAS, "#room2:server.com", 23, 40)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `findPatterns - returns raw room event ids`() {
|
||||
val text = "A \$event:server.com and \$event2:server.com"
|
||||
val patterns = MatrixPatterns.findPatterns(text, aPermalinkParser())
|
||||
assertThat(patterns).containsExactly(
|
||||
MatrixPatternResult(MatrixPatternType.EVENT_ID, "\$event:server.com", 2, 19),
|
||||
MatrixPatternResult(MatrixPatternType.EVENT_ID, "\$event2:server.com", 24, 42)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `findPatterns - returns @room mention`() {
|
||||
val text = "A @room mention"
|
||||
val patterns = MatrixPatterns.findPatterns(text, aPermalinkParser())
|
||||
assertThat(patterns).containsExactly(MatrixPatternResult(MatrixPatternType.AT_ROOM, "@room", 2, 7))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `findPatterns - returns user ids in permalinks`() {
|
||||
val text = "A [User](https://matrix.to/#/@user:server.com)"
|
||||
val permalinkParser = aPermalinkParser { _ ->
|
||||
PermalinkData.UserLink(UserId("@user:server.com"))
|
||||
}
|
||||
val patterns = MatrixPatterns.findPatterns(text, permalinkParser)
|
||||
assertThat(patterns).containsExactly(MatrixPatternResult(MatrixPatternType.USER_ID, "@user:server.com", 2, 46))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `findPatterns - returns room aliases in permalinks`() {
|
||||
val text = "A [Room](https://matrix.to/#/#room:server.com)"
|
||||
val permalinkParser = aPermalinkParser { _ ->
|
||||
PermalinkData.RoomLink(RoomIdOrAlias.Alias(RoomAlias("#room:server.com")))
|
||||
}
|
||||
val patterns = MatrixPatterns.findPatterns(text, permalinkParser)
|
||||
assertThat(patterns).containsExactly(MatrixPatternResult(MatrixPatternType.ROOM_ALIAS, "#room:server.com", 2, 46))
|
||||
}
|
||||
|
||||
private fun aPermalinkParser(block: (String) -> PermalinkData = { PermalinkData.FallbackLink(Uri.EMPTY) }) = object : PermalinkParser {
|
||||
override fun parse(uriString: String): PermalinkData {
|
||||
return block(uriString)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue