mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
Add permalink detection and custom attributed string attributes (#536)
* Pass full attributed string to TimelineItems and let the UI level handle blockquote coalescing * Add permalink detection and attribute embedding within the attributed string
This commit is contained in:
@@ -72,6 +72,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
|
||||
let mutableAttributedString = NSMutableAttributedString(attributedString: attributedString)
|
||||
removeDefaultForegroundColor(mutableAttributedString)
|
||||
addLinks(mutableAttributedString)
|
||||
detectPermalinks(mutableAttributedString)
|
||||
removeLinkColors(mutableAttributedString)
|
||||
replaceMarkedBlockquotes(mutableAttributedString)
|
||||
replaceMarkedCodeBlocks(mutableAttributedString)
|
||||
@@ -80,28 +81,6 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
|
||||
return try? AttributedString(mutableAttributedString, including: \.elementX)
|
||||
}
|
||||
|
||||
func blockquoteCoalescedComponentsFrom(_ attributedString: AttributedString?) -> [AttributedStringBuilderComponent]? {
|
||||
guard let attributedString else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return attributedString.runs[\.blockquote].map { value, range in
|
||||
var attributedString = AttributedString(attributedString[range])
|
||||
|
||||
// Remove trailing new lines if any
|
||||
if attributedString.characters.last?.isNewline ?? false,
|
||||
let range = attributedString.range(of: "\n", options: .backwards, locale: nil) {
|
||||
attributedString.removeSubrange(range)
|
||||
}
|
||||
|
||||
let isBlockquote = value != nil
|
||||
/// This is a temporary workaround until replies are retrieved from the SDK.
|
||||
let isReply = isBlockquote && attributedString.characters.starts(with: "In reply to @")
|
||||
|
||||
return AttributedStringBuilderComponent(attributedString: attributedString, isBlockquote: isBlockquote, isReply: isReply)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func replaceMarkedBlockquotes(_ attributedString: NSMutableAttributedString) {
|
||||
@@ -115,7 +94,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
|
||||
return
|
||||
}
|
||||
|
||||
attributedString.addAttribute(.MXBlockquote, value: true, range: range)
|
||||
attributedString.addAttribute(.MatrixBlockquote, value: true, range: range)
|
||||
}
|
||||
|
||||
attributedString.enumerateAttribute(.backgroundColor, in: .init(location: 0, length: attributedString.length), options: []) { value, range, _ in
|
||||
@@ -125,7 +104,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
|
||||
}
|
||||
|
||||
attributedString.removeAttribute(.backgroundColor, range: range)
|
||||
attributedString.addAttribute(.MXBlockquote, value: true, range: range)
|
||||
attributedString.addAttribute(.MatrixBlockquote, value: true, range: range)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,6 +167,27 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
private func detectPermalinks(_ attributedString: NSMutableAttributedString) {
|
||||
attributedString.enumerateAttribute(.link, in: .init(location: 0, length: attributedString.length), options: []) { value, range, _ in
|
||||
if value != nil {
|
||||
if let url = value as? URL {
|
||||
switch PermalinkBuilder.detectPermalink(in: url) {
|
||||
case .userIdentifier(let identifier):
|
||||
attributedString.addAttributes([.MatrixUserID: identifier], range: range)
|
||||
case .roomIdentifier(let identifier):
|
||||
attributedString.addAttributes([.MatrixRoomID: identifier], range: range)
|
||||
case .roomAlias(let alias):
|
||||
attributedString.addAttributes([.MatrixRoomAlias: alias], range: range)
|
||||
case .event(let roomIdentifier, let eventIdentifier):
|
||||
attributedString.addAttributes([.MatrixEventID: EventIDAttributeValue(roomID: roomIdentifier, eventID: eventIdentifier)], range: range)
|
||||
case .none:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func removeDefaultForegroundColor(_ attributedString: NSMutableAttributedString) {
|
||||
attributedString.enumerateAttribute(.foregroundColor, in: .init(location: 0, length: attributedString.length), options: []) { value, range, _ in
|
||||
if value as? UIColor == UIColor.black {
|
||||
@@ -241,5 +241,9 @@ extension UIColor {
|
||||
|
||||
extension NSAttributedString.Key {
|
||||
static let DTTextBlocks: NSAttributedString.Key = .init(rawValue: DTTextBlocksAttribute)
|
||||
static let MXBlockquote: NSAttributedString.Key = .init(rawValue: BlockquoteAttribute.name)
|
||||
static let MatrixBlockquote: NSAttributedString.Key = .init(rawValue: BlockquoteAttribute.name)
|
||||
static let MatrixUserID: NSAttributedString.Key = .init(rawValue: UserIDAttribute.name)
|
||||
static let MatrixRoomID: NSAttributedString.Key = .init(rawValue: RoomIDAttribute.name)
|
||||
static let MatrixRoomAlias: NSAttributedString.Key = .init(rawValue: RoomAliasAttribute.name)
|
||||
static let MatrixEventID: NSAttributedString.Key = .init(rawValue: EventIDAttribute.name)
|
||||
}
|
||||
|
||||
@@ -26,6 +26,4 @@ protocol AttributedStringBuilderProtocol {
|
||||
func fromPlain(_ string: String?) -> AttributedString?
|
||||
|
||||
func fromHTML(_ htmlString: String?) -> AttributedString?
|
||||
|
||||
func blockquoteCoalescedComponentsFrom(_ attributedString: AttributedString?) -> [AttributedStringBuilderComponent]?
|
||||
}
|
||||
|
||||
@@ -21,10 +21,40 @@ enum BlockquoteAttribute: AttributedStringKey {
|
||||
public static var name = "MXBlockquoteAttribute"
|
||||
}
|
||||
|
||||
enum UserIDAttribute: AttributedStringKey {
|
||||
typealias Value = String
|
||||
public static var name = "MXUserIDAttribute"
|
||||
}
|
||||
|
||||
enum RoomIDAttribute: AttributedStringKey {
|
||||
typealias Value = String
|
||||
public static var name = "MXRoomIDAttribute"
|
||||
}
|
||||
|
||||
enum RoomAliasAttribute: AttributedStringKey {
|
||||
typealias Value = String
|
||||
public static var name = "MXRoomAliasAttribute"
|
||||
}
|
||||
|
||||
struct EventIDAttributeValue: Hashable {
|
||||
let roomID: String
|
||||
let eventID: String
|
||||
}
|
||||
|
||||
enum EventIDAttribute: AttributedStringKey {
|
||||
typealias Value = EventIDAttributeValue
|
||||
public static var name = "MXEventIDAttribute"
|
||||
}
|
||||
|
||||
extension AttributeScopes {
|
||||
struct ElementXAttributes: AttributeScope {
|
||||
let blockquote: BlockquoteAttribute
|
||||
|
||||
let userID: UserIDAttribute
|
||||
let roomID: RoomIDAttribute
|
||||
let roomAlias: RoomAliasAttribute
|
||||
let eventID: EventIDAttribute
|
||||
|
||||
let swiftUI: SwiftUIAttributes
|
||||
let uiKit: UIKitAttributes
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user