Room mentioning in the composer (#3868)

* refactored the suggestion item structure

to scale with the room pill

* Implemented a way for the rooms

to appear in the suggestions view for the RTE, however I need to add the pills to the composer and the compatibility with the plain text composer

* small code correction

* fix

* fixed a bug where the suggestion wasn't returning

the right suggestion type and the suggestion text properly

* implementation done!

also updated some tests, but we need more of them

* updated toolbar view model tests

* updated tests

* updated preview tests

* renamed the Avatars case for the suggestions
This commit is contained in:
Mauro
2025-03-06 11:32:37 +01:00
committed by GitHub
parent 2d77a1b10b
commit 5f59e867b5
43 changed files with 564 additions and 180 deletions
@@ -378,22 +378,23 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
shouldMakeAnotherPass = false
attributedString.enumerateAttribute(.link, in: .init(location: 0, length: attributedString.length), options: []) { value, range, stop in
guard let value else { return }
shouldMakeAnotherPass = true
// Remove the attribute so it doesn't get inherited by the new string
attributedString.removeAttribute(.link, range: range)
guard let userID = attributedString.attribute(.MatrixUserID, at: range.location, effectiveRange: nil) as? String else {
if let userID = attributedString.attribute(.MatrixUserID, at: range.location, effectiveRange: nil) as? String {
let displayName = attributedString.attribute(.MatrixUserDisplayName, at: range.location, effectiveRange: nil)
attributedString.replaceCharacters(in: range, with: "[\(displayName ?? userID)](\(value))")
userIDs.insert(userID)
stop.pointee = true
} else if let roomAlias = attributedString.attribute(.MatrixRoomAlias, at: range.location, effectiveRange: nil) as? String {
let displayName = attributedString.attribute(.MatrixRoomDisplayName, at: range.location, effectiveRange: nil)
attributedString.replaceCharacters(in: range, with: "[\(displayName ?? roomAlias)](\(value))")
stop.pointee = true
} else {
return
}
let displayName = attributedString.attribute(.MatrixUserDisplayName, at: range.location, effectiveRange: nil)
attributedString.replaceCharacters(in: range, with: "[\(displayName ?? userID)](\(value))")
userIDs.insert(userID)
stop.pointee = true
}
} while shouldMakeAnotherPass
@@ -473,23 +474,24 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
}
private func handleSuggestion(_ suggestion: SuggestionItem) {
switch suggestion {
case let .user(item):
guard let url = try? URL(string: matrixToUserPermalink(userId: item.id)) else {
switch suggestion.suggestionType {
case let .user(user):
guard let url = try? URL(string: matrixToUserPermalink(userId: user.id)) else {
MXLog.error("Could not build user permalink")
return
}
if context.composerFormattingEnabled {
wysiwygViewModel.setMention(url: url.absoluteString, name: item.id, mentionType: .user)
wysiwygViewModel.setMention(url: url.absoluteString, name: user.id, mentionType: .user)
} else {
let attributedString = NSMutableAttributedString(attributedString: state.bindings.plainComposerText)
mentionBuilder.handleUserMention(for: attributedString, in: suggestion.range, url: url, userID: item.id, userDisplayName: item.displayName)
mentionBuilder.handleUserMention(for: attributedString, in: suggestion.range, url: url, userID: user.id, userDisplayName: user.displayName)
state.bindings.plainComposerText = attributedString
let newSelectedRange = NSRange(location: state.bindings.selectedRange.location - item.rawSuggestionText.count, length: 0)
let newSelectedRange = NSRange(location: state.bindings.selectedRange.location - suggestion.rawSuggestionText.count, length: 0)
state.bindings.selectedRange = newSelectedRange
}
case let .allUsers(item):
case .allUsers:
if context.composerFormattingEnabled {
wysiwygViewModel.setAtRoomMention()
} else {
@@ -497,7 +499,23 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
mentionBuilder.handleAllUsersMention(for: attributedString, in: suggestion.range)
state.bindings.plainComposerText = attributedString
let newSelectedRange = NSRange(location: state.bindings.selectedRange.location - item.rawSuggestionText.count, length: 0)
let newSelectedRange = NSRange(location: state.bindings.selectedRange.location - suggestion.rawSuggestionText.count, length: 0)
state.bindings.selectedRange = newSelectedRange
}
case let .room(room):
guard let url = try? URL(string: matrixToRoomAliasPermalink(roomAlias: room.canonicalAlias)) else {
MXLog.error("Could not build alias permalink")
return
}
if context.composerFormattingEnabled {
wysiwygViewModel.setMention(url: url.absoluteString, name: room.name, mentionType: .room)
} else {
let attributedString = NSMutableAttributedString(attributedString: state.bindings.plainComposerText)
mentionBuilder.handleRoomAliasMention(for: attributedString, in: suggestion.range, url: url, roomAlias: room.canonicalAlias, roomDisplayName: room.name)
state.bindings.plainComposerText = attributedString
let newSelectedRange = NSRange(location: state.bindings.selectedRange.location - suggestion.rawSuggestionText.count, length: 0)
state.bindings.selectedRange = newSelectedRange
}
}