Support tapping Matrix URIs in messages. (#2828)
This commit is contained in:
@@ -149,7 +149,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
|
||||
// Event identifiers and room aliases and identifiers detected in plain text are techincally incomplete
|
||||
// without via parameters and we won't bother detecting them
|
||||
|
||||
var matches: [TextParsingMatch] = MatrixEntityRegex.userIdentifierRegex.matches(in: string, options: []).compactMap { match in
|
||||
var matches: [TextParsingMatch] = MatrixEntityRegex.userIdentifierRegex.matches(in: string).compactMap { match in
|
||||
guard let matchRange = Range(match.range, in: string) else {
|
||||
return nil
|
||||
}
|
||||
@@ -159,7 +159,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
|
||||
return TextParsingMatch(type: .userID(identifier: identifier), range: match.range)
|
||||
}
|
||||
|
||||
matches.append(contentsOf: MatrixEntityRegex.roomAliasRegex.matches(in: string, options: []).compactMap { match in
|
||||
matches.append(contentsOf: MatrixEntityRegex.roomAliasRegex.matches(in: string).compactMap { match in
|
||||
guard let matchRange = Range(match.range, in: string) else {
|
||||
return nil
|
||||
}
|
||||
@@ -169,7 +169,17 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
|
||||
return TextParsingMatch(type: .roomAlias(alias: alias), range: match.range)
|
||||
})
|
||||
|
||||
matches.append(contentsOf: MatrixEntityRegex.linkRegex.matches(in: string, options: []).compactMap { match in
|
||||
matches.append(contentsOf: MatrixEntityRegex.uriRegex.matches(in: string).compactMap { match in
|
||||
guard let matchRange = Range(match.range, in: string) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let uri = String(string[matchRange])
|
||||
|
||||
return TextParsingMatch(type: .matrixURI(uri: uri), range: match.range)
|
||||
})
|
||||
|
||||
matches.append(contentsOf: MatrixEntityRegex.linkRegex.matches(in: string).compactMap { match in
|
||||
guard let matchRange = Range(match.range, in: string) else {
|
||||
return nil
|
||||
}
|
||||
@@ -183,7 +193,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
|
||||
return TextParsingMatch(type: .link(urlString: link), range: match.range)
|
||||
})
|
||||
|
||||
matches.append(contentsOf: MatrixEntityRegex.allUsersRegex.matches(in: attributedString.string, options: []).map { match in
|
||||
matches.append(contentsOf: MatrixEntityRegex.allUsersRegex.matches(in: attributedString.string).map { match in
|
||||
TextParsingMatch(type: .atRoom, range: match.range)
|
||||
})
|
||||
|
||||
@@ -217,6 +227,10 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
|
||||
if let url = try? matrixToRoomAliasPermalink(roomAlias: alias) {
|
||||
attributedString.addAttribute(.link, value: url, range: match.range)
|
||||
}
|
||||
case .matrixURI(let uri):
|
||||
if let url = URL(string: uri) {
|
||||
attributedString.addAttribute(.link, value: url, range: match.range)
|
||||
}
|
||||
case .userID, .link:
|
||||
if let url = match.link {
|
||||
attributedString.addAttribute(.link, value: url, range: match.range)
|
||||
@@ -357,6 +371,7 @@ private struct TextParsingMatch {
|
||||
enum MatchType {
|
||||
case userID(identifier: String)
|
||||
case roomAlias(alias: String)
|
||||
case matrixURI(uri: String)
|
||||
case link(urlString: String)
|
||||
case atRoom
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ enum MatrixEntityRegex: String {
|
||||
case homeserver
|
||||
case userID
|
||||
case roomAlias
|
||||
case uri
|
||||
case allUsers
|
||||
|
||||
var rawValue: String {
|
||||
@@ -32,6 +33,8 @@ enum MatrixEntityRegex: String {
|
||||
return "@[\\x21-\\x39\\x3B-\\x7F]+:" + MatrixEntityRegex.homeserver.rawValue
|
||||
case .roomAlias:
|
||||
return "#[A-Z0-9._%#@=+-]+:" + MatrixEntityRegex.homeserver.rawValue
|
||||
case .uri:
|
||||
return "matrix:(r|u|roomid)\\/[A-Z0-9\\-._~:/?#\\[\\]@!$&'()*+,;=%]*(?:\\?[A-Z0-9\\-._~:/?#\\[\\]@!$&'()*+,;=%]*)?"
|
||||
case .allUsers:
|
||||
return PillConstants.atRoom
|
||||
}
|
||||
@@ -41,6 +44,7 @@ enum MatrixEntityRegex: String {
|
||||
static var homeserverRegex = try! NSRegularExpression(pattern: MatrixEntityRegex.homeserver.rawValue, options: .caseInsensitive)
|
||||
static var userIdentifierRegex = try! NSRegularExpression(pattern: MatrixEntityRegex.userID.rawValue, options: .caseInsensitive)
|
||||
static var roomAliasRegex = try! NSRegularExpression(pattern: MatrixEntityRegex.roomAlias.rawValue, options: .caseInsensitive)
|
||||
static var uriRegex = try! NSRegularExpression(pattern: MatrixEntityRegex.uri.rawValue, options: .caseInsensitive)
|
||||
static var allUsersRegex = try! NSRegularExpression(pattern: MatrixEntityRegex.allUsers.rawValue)
|
||||
static var linkRegex = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
|
||||
// swiftlint:enable force_try
|
||||
@@ -69,6 +73,14 @@ enum MatrixEntityRegex: String {
|
||||
return match.range.length == alias.count
|
||||
}
|
||||
|
||||
static func isMatrixURI(_ uri: String) -> Bool {
|
||||
guard let match = uriRegex.firstMatch(in: uri) else {
|
||||
return false
|
||||
}
|
||||
|
||||
return match.range.length == uri.count
|
||||
}
|
||||
|
||||
static func containsMatrixAllUsers(_ string: String) -> Bool {
|
||||
guard allUsersRegex.firstMatch(in: string) != nil else {
|
||||
return false
|
||||
|
||||
@@ -188,6 +188,12 @@ class AttributedStringBuilderTests: XCTestCase {
|
||||
checkLinkIn(attributedString: attributedStringBuilder.fromPlain(string), expectedLink: string, expectedRuns: 1)
|
||||
}
|
||||
|
||||
func testMatrixURI() {
|
||||
let string = "matrix:roomid/hello:matrix.org/e/world?via=matrix.org"
|
||||
checkLinkIn(attributedString: attributedStringBuilder.fromHTML(string), expectedLink: string, expectedRuns: 1)
|
||||
checkLinkIn(attributedString: attributedStringBuilder.fromPlain(string), expectedLink: string, expectedRuns: 1)
|
||||
}
|
||||
|
||||
func testUserIDLink() {
|
||||
let userID = "@user:matrix.org"
|
||||
let string = "The user is \(userID)."
|
||||
|
||||
@@ -39,6 +39,38 @@ class MatrixEntityRegexTests: XCTestCase {
|
||||
XCTAssertFalse(MatrixEntityRegex.isMatrixRoomAlias("#element-ios.matrix.org"))
|
||||
}
|
||||
|
||||
func testMatrixURI() {
|
||||
// Users
|
||||
XCTAssertTrue(MatrixEntityRegex.isMatrixURI("matrix:u/alice:example.org"))
|
||||
XCTAssertTrue(MatrixEntityRegex.isMatrixURI("matrix:u/alice:example.org?action=chat"))
|
||||
|
||||
// Room ID
|
||||
XCTAssertTrue(MatrixEntityRegex.isMatrixURI("matrix:roomid/somewhere:example.org"))
|
||||
XCTAssertTrue(MatrixEntityRegex.isMatrixURI("matrix:roomid/my-room:example.com?via=elsewhere.ca"))
|
||||
XCTAssertTrue(MatrixEntityRegex.isMatrixURI("matrix:roomid/123_room:chat.myserver.net?via=elsewhere.ca&via=other.org"))
|
||||
|
||||
// Room Alias
|
||||
XCTAssertTrue(MatrixEntityRegex.isMatrixURI("matrix:r/general:matrix.org"))
|
||||
XCTAssertTrue(MatrixEntityRegex.isMatrixURI("matrix:r/123_room:chat.myserver.net"))
|
||||
|
||||
// Event
|
||||
XCTAssertTrue(MatrixEntityRegex.isMatrixURI("matrix:roomid/somewhere:example.org/e/event"))
|
||||
XCTAssertTrue(MatrixEntityRegex.isMatrixURI("matrix:roomid/my-room:example.com/e/message?via=elsewhere.ca"))
|
||||
XCTAssertTrue(MatrixEntityRegex.isMatrixURI("matrix:roomid/123_room:chat.myserver.net/e/1234?via=elsewhere.ca&via=other.org"))
|
||||
|
||||
// Inline
|
||||
let string = "Hello matrix:u/alice:example.org how are you?"
|
||||
XCTAssertFalse(MatrixEntityRegex.isMatrixURI("Hello matrix:u/alice:example.org how are you?"))
|
||||
XCTAssertEqual(MatrixEntityRegex.uriRegex.matches(in: string).count, 1)
|
||||
|
||||
// Invalid
|
||||
XCTAssertFalse(MatrixEntityRegex.isMatrixURI("matrix://@alice:example.org"))
|
||||
XCTAssertFalse(MatrixEntityRegex.isMatrixURI("matrix://!somewhere:example.org"))
|
||||
XCTAssertFalse(MatrixEntityRegex.isMatrixURI("matrix://#general:matrix.org"))
|
||||
XCTAssertFalse(MatrixEntityRegex.isMatrixURI("matrix:event/somewhere:example.org/e/event"))
|
||||
XCTAssertFalse(MatrixEntityRegex.isMatrixURI("matrix:e/somewhere:example.org/e/event"))
|
||||
}
|
||||
|
||||
func testAllUsers() {
|
||||
XCTAssertTrue(MatrixEntityRegex.containsMatrixAllUsers("@room"))
|
||||
XCTAssertTrue(MatrixEntityRegex.containsMatrixAllUsers("a@rooma"))
|
||||
|
||||
1
changelog.d/2802.bugfix
Normal file
1
changelog.d/2802.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
Render matrix URIs as links so they can be tapped.
|
||||
Reference in New Issue
Block a user