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:
Jorge Martin Espinosa 2024-07-15 18:27:59 +02:00 committed by GitHub
parent 5493d6b741
commit 2ff5fa67fc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 717 additions and 288 deletions

View file

@ -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)

View file

@ -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()
}