mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
Converting detected plain text mentions into permalinks (#1963)
* it works but a test fail because URL(string) fails * done! * let
This commit is contained in:
@@ -176,31 +176,29 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
|
|||||||
private func addLinksAndMentions(_ attributedString: NSMutableAttributedString) {
|
private func addLinksAndMentions(_ attributedString: NSMutableAttributedString) {
|
||||||
let string = attributedString.string
|
let string = attributedString.string
|
||||||
|
|
||||||
var matches = MatrixEntityRegex.userIdentifierRegex.matches(in: string, options: [])
|
var matches = MatrixEntityRegex.userIdentifierRegex.matches(in: string, options: []).map { TypedMatch(match: $0, type: .permalink(type: .userID)) }
|
||||||
matches.append(contentsOf: MatrixEntityRegex.roomIdentifierRegex.matches(in: string, options: []))
|
matches.append(contentsOf: MatrixEntityRegex.roomIdentifierRegex.matches(in: string, options: []).map { TypedMatch(match: $0, type: .permalink(type: .roomID)) })
|
||||||
|
|
||||||
// As of right now we do not handle event id links in any way so there is no need to add them as links
|
// As of right now we do not handle event id links in any way so there is no need to add them as links
|
||||||
// matches.append(contentsOf: MatrixEntityRegex.eventIdentifierRegex.matches(in: string, options: []))
|
// matches.append(contentsOf: MatrixEntityRegex.eventIdentifierRegex.matches(in: string, options: []))
|
||||||
matches.append(contentsOf: MatrixEntityRegex.roomAliasRegex.matches(in: string, options: []))
|
|
||||||
|
|
||||||
let linkMatches = MatrixEntityRegex.linkRegex.matches(in: string, options: [])
|
matches.append(contentsOf: MatrixEntityRegex.roomAliasRegex.matches(in: string, options: []).map { TypedMatch(match: $0, type: .permalink(type: .roomAlias)) })
|
||||||
matches.append(contentsOf: linkMatches)
|
|
||||||
|
|
||||||
let allUserMentionsMatches = MatrixEntityRegex.allUsersRegex.matches(in: attributedString.string, options: [])
|
matches.append(contentsOf: MatrixEntityRegex.linkRegex.matches(in: string, options: []).map { TypedMatch(match: $0, type: .link) })
|
||||||
matches.append(contentsOf: allUserMentionsMatches)
|
|
||||||
|
|
||||||
let allUsersMentionsCount = allUserMentionsMatches.count
|
matches.append(contentsOf: MatrixEntityRegex.allUsersRegex.matches(in: attributedString.string, options: []).map { TypedMatch(match: $0, type: .atRoom) })
|
||||||
|
|
||||||
guard matches.count > 0 else {
|
guard matches.count > 0 else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Sort the links by length so the longest one always takes priority
|
// Sort the links by length so the longest one always takes priority
|
||||||
matches.sorted { $0.range.length > $1.range.length }.enumerated().forEach { offset, match in
|
matches.sorted { $0.match.range.length > $1.match.range.length }.forEach { [attributedString] typedMatch in
|
||||||
guard let matchRange = Range(match.range, in: string) else {
|
guard let matchRange = Range(typedMatch.match.range, in: string) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasLink = false
|
var hasLink = false
|
||||||
attributedString.enumerateAttribute(.link, in: match.range, options: []) { value, _, stop in
|
attributedString.enumerateAttribute(.link, in: typedMatch.match.range, options: []) { value, _, stop in
|
||||||
if value != nil {
|
if value != nil {
|
||||||
hasLink = true
|
hasLink = true
|
||||||
stop.pointee = true
|
stop.pointee = true
|
||||||
@@ -211,17 +209,24 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if offset > matches.count - allUsersMentionsCount - 1 {
|
switch typedMatch.type {
|
||||||
attributedString.addAttribute(.MatrixAllUsersMention, value: true, range: match.range)
|
case .atRoom:
|
||||||
} else {
|
attributedString.addAttribute(.MatrixAllUsersMention, value: true, range: typedMatch.match.range)
|
||||||
|
case let .permalink(type):
|
||||||
|
let identifier = String(string[matchRange])
|
||||||
|
|
||||||
|
if let url = type.getPermalinkFrom(identifier: identifier, baseURL: permalinkBaseURL) {
|
||||||
|
attributedString.addAttribute(.link, value: url, range: typedMatch.match.range)
|
||||||
|
}
|
||||||
|
case .link:
|
||||||
var link = String(string[matchRange])
|
var link = String(string[matchRange])
|
||||||
|
|
||||||
if linkMatches.contains(match), !link.contains("://") {
|
if !link.contains("://") {
|
||||||
link.insert(contentsOf: "https://", at: link.startIndex)
|
link.insert(contentsOf: "https://", at: link.startIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let url = URL(string: link) {
|
if let url = URL(string: link) {
|
||||||
attributedString.addAttribute(.link, value: url, range: match.range)
|
attributedString.addAttribute(.link, value: url, range: typedMatch.match.range)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -320,3 +325,31 @@ protocol MentionBuilderProtocol {
|
|||||||
func handleUserMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, userID: String)
|
func handleUserMention(for attributedString: NSMutableAttributedString, in range: NSRange, url: URL, userID: String)
|
||||||
func handleAllUsersMention(for attributedString: NSMutableAttributedString, in range: NSRange)
|
func handleAllUsersMention(for attributedString: NSMutableAttributedString, in range: NSRange)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct TypedMatch {
|
||||||
|
enum MatchType {
|
||||||
|
case permalink(type: MentionType)
|
||||||
|
case link
|
||||||
|
case atRoom
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MentionType {
|
||||||
|
case roomAlias
|
||||||
|
case roomID
|
||||||
|
case userID
|
||||||
|
|
||||||
|
func getPermalinkFrom(identifier: String, baseURL: URL) -> URL? {
|
||||||
|
switch self {
|
||||||
|
case .roomAlias:
|
||||||
|
return try? PermalinkBuilder.permalinkTo(roomAlias: identifier, baseURL: baseURL)
|
||||||
|
case .roomID:
|
||||||
|
return try? PermalinkBuilder.permalinkTo(roomIdentifier: identifier, baseURL: baseURL)
|
||||||
|
case .userID:
|
||||||
|
return try? PermalinkBuilder.permalinkTo(userIdentifier: identifier, baseURL: baseURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let match: NSTextCheckingResult
|
||||||
|
let type: MatchType
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,8 +18,9 @@
|
|||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
class AttributedStringBuilderTests: XCTestCase {
|
class AttributedStringBuilderTests: XCTestCase {
|
||||||
let attributedStringBuilder = AttributedStringBuilder(permalinkBaseURL: ServiceLocator.shared.settings.permalinkBaseURL, mentionBuilder: MentionBuilder(mentionsEnabled: true))
|
private let permalinkBaseURL = ServiceLocator.shared.settings.permalinkBaseURL
|
||||||
let maxHeaderPointSize = ceil(UIFont.preferredFont(forTextStyle: .body).pointSize * 1.2)
|
private lazy var attributedStringBuilder = AttributedStringBuilder(permalinkBaseURL: permalinkBaseURL, mentionBuilder: MentionBuilder(mentionsEnabled: true))
|
||||||
|
private let maxHeaderPointSize = ceil(UIFont.preferredFont(forTextStyle: .body).pointSize * 1.2)
|
||||||
|
|
||||||
func testRenderHTMLStringWithHeaders() {
|
func testRenderHTMLStringWithHeaders() {
|
||||||
let h1HTMLString = "<h1>Large Heading</h1>"
|
let h1HTMLString = "<h1>Large Heading</h1>"
|
||||||
@@ -191,22 +192,25 @@ class AttributedStringBuilderTests: XCTestCase {
|
|||||||
func testUserIdLink() {
|
func testUserIdLink() {
|
||||||
let userId = "@user:matrix.org"
|
let userId = "@user:matrix.org"
|
||||||
let string = "The user is \(userId)."
|
let string = "The user is \(userId)."
|
||||||
checkLinkIn(attributedString: attributedStringBuilder.fromHTML(string), expectedLink: userId, expectedRuns: 3)
|
let expectedLink = "\(permalinkBaseURL)/#/\(userId)"
|
||||||
checkLinkIn(attributedString: attributedStringBuilder.fromPlain(string), expectedLink: userId, expectedRuns: 3)
|
checkLinkIn(attributedString: attributedStringBuilder.fromHTML(string), expectedLink: expectedLink, expectedRuns: 3)
|
||||||
|
checkLinkIn(attributedString: attributedStringBuilder.fromPlain(string), expectedLink: expectedLink, expectedRuns: 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRoomAliasLink() {
|
func testRoomAliasLink() {
|
||||||
let roomAlias = "#matrix:matrix.org"
|
let roomAlias = "#matrix:matrix.org"
|
||||||
let string = "The room alias is \(roomAlias)."
|
let string = "The room alias is \(roomAlias)."
|
||||||
checkLinkIn(attributedString: attributedStringBuilder.fromHTML(string), expectedLink: roomAlias, expectedRuns: 3)
|
let expectedLink = "https://matrix.to/#/%23matrix%3Amatrix.org"
|
||||||
checkLinkIn(attributedString: attributedStringBuilder.fromPlain(string), expectedLink: roomAlias, expectedRuns: 3)
|
checkLinkIn(attributedString: attributedStringBuilder.fromHTML(string), expectedLink: expectedLink, expectedRuns: 3)
|
||||||
|
checkLinkIn(attributedString: attributedStringBuilder.fromPlain(string), expectedLink: expectedLink, expectedRuns: 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRoomIdLink() {
|
func testRoomIdLink() {
|
||||||
let roomId = "!roomidentifier:matrix.org"
|
let roomId = "!roomidentifier:matrix.org"
|
||||||
let string = "The room is \(roomId)."
|
let string = "The room is \(roomId)."
|
||||||
checkLinkIn(attributedString: attributedStringBuilder.fromHTML(string), expectedLink: roomId, expectedRuns: 3)
|
let expectedLink = "https://matrix.to/#/!roomidentifier%3Amatrix.org"
|
||||||
checkLinkIn(attributedString: attributedStringBuilder.fromPlain(string), expectedLink: roomId, expectedRuns: 3)
|
checkLinkIn(attributedString: attributedStringBuilder.fromHTML(string), expectedLink: expectedLink, expectedRuns: 3)
|
||||||
|
checkLinkIn(attributedString: attributedStringBuilder.fromPlain(string), expectedLink: expectedLink, expectedRuns: 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
// As of right now we do not handle event id links in any way so there is no need to add them as links
|
// As of right now we do not handle event id links in any way so there is no need to add them as links
|
||||||
|
|||||||
Reference in New Issue
Block a user