mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 01:20:12 +00:00
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:
+77
-42
@@ -9,7 +9,10 @@ import Combine
|
||||
import Foundation
|
||||
|
||||
private enum SuggestionTriggerRegex {
|
||||
static let at = /@\w+/
|
||||
static let atOrHash = /[@#]\w*/
|
||||
|
||||
static let at: Character = "@"
|
||||
static let hash: Character = "#"
|
||||
}
|
||||
|
||||
final class CompletionSuggestionService: CompletionSuggestionServiceProtocol {
|
||||
@@ -19,12 +22,13 @@ final class CompletionSuggestionService: CompletionSuggestionServiceProtocol {
|
||||
|
||||
private let suggestionTriggerSubject = CurrentValueSubject<SuggestionTrigger?, Never>(nil)
|
||||
|
||||
init(roomProxy: JoinedRoomProxyProtocol) {
|
||||
init(roomProxy: JoinedRoomProxyProtocol,
|
||||
roomListPublisher: AnyPublisher<[RoomSummary], Never>) {
|
||||
self.roomProxy = roomProxy
|
||||
|
||||
suggestionsPublisher = suggestionTriggerSubject
|
||||
.combineLatest(roomProxy.membersPublisher)
|
||||
.map { [weak self, ownUserID = roomProxy.ownUserID] suggestionTrigger, members -> [SuggestionItem] in
|
||||
.combineLatest(roomProxy.membersPublisher, roomListPublisher)
|
||||
.map { [weak self, ownUserID = roomProxy.ownUserID] suggestionTrigger, members, roomSummaries -> [SuggestionItem] in
|
||||
guard let self,
|
||||
let suggestionTrigger else {
|
||||
return []
|
||||
@@ -32,32 +36,9 @@ final class CompletionSuggestionService: CompletionSuggestionServiceProtocol {
|
||||
|
||||
switch suggestionTrigger.type {
|
||||
case .user:
|
||||
var membersSuggestion = members
|
||||
.compactMap { member -> SuggestionItem? in
|
||||
guard member.userID != ownUserID,
|
||||
member.membership == .join,
|
||||
Self.shouldIncludeMember(userID: member.userID, displayName: member.displayName, searchText: suggestionTrigger.text) else {
|
||||
return nil
|
||||
}
|
||||
return SuggestionItem.user(item: .init(id: member.userID,
|
||||
displayName: member.displayName,
|
||||
avatarURL: member.avatarURL,
|
||||
range: suggestionTrigger.range,
|
||||
rawSuggestionText: suggestionTrigger.text))
|
||||
}
|
||||
|
||||
if self.canMentionAllUsers,
|
||||
!self.roomProxy.isDirectOneToOneRoom,
|
||||
Self.shouldIncludeMember(userID: PillConstants.atRoom, displayName: PillConstants.everyone, searchText: suggestionTrigger.text) {
|
||||
membersSuggestion
|
||||
.insert(SuggestionItem.allUsers(item: .init(id: PillConstants.atRoom,
|
||||
displayName: PillConstants.everyone,
|
||||
avatarURL: self.roomProxy.infoPublisher.value.avatarURL,
|
||||
range: suggestionTrigger.range,
|
||||
rawSuggestionText: suggestionTrigger.text)), at: 0)
|
||||
}
|
||||
|
||||
return membersSuggestion
|
||||
return membersSuggestions(suggestionTrigger: suggestionTrigger, members: members, ownUserID: ownUserID)
|
||||
case .room:
|
||||
return roomSuggestions(suggestionTrigger: suggestionTrigger, roomSummaries: roomSummaries)
|
||||
}
|
||||
}
|
||||
// We only debounce if the suggestion is nil
|
||||
@@ -85,25 +66,71 @@ final class CompletionSuggestionService: CompletionSuggestionServiceProtocol {
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func detectTriggerInText(_ text: String, selectedRange: NSRange) -> SuggestionTrigger? {
|
||||
let matches = text.matches(of: SuggestionTriggerRegex.at)
|
||||
let match = matches
|
||||
.first { matchResult in
|
||||
let lowerBound = matchResult.range.lowerBound.utf16Offset(in: matchResult.base)
|
||||
let upperBound = matchResult.range.upperBound.utf16Offset(in: matchResult.base)
|
||||
return selectedRange.location >= lowerBound
|
||||
&& selectedRange.location <= upperBound
|
||||
&& selectedRange.length <= upperBound - lowerBound
|
||||
private func membersSuggestions(suggestionTrigger: SuggestionTrigger,
|
||||
members: [RoomMemberProxyProtocol],
|
||||
ownUserID: String) -> [SuggestionItem] {
|
||||
var membersSuggestion = members
|
||||
.compactMap { member -> SuggestionItem? in
|
||||
guard member.userID != ownUserID,
|
||||
member.membership == .join,
|
||||
Self.shouldIncludeMember(userID: member.userID, displayName: member.displayName, searchText: suggestionTrigger.text) else {
|
||||
return nil
|
||||
}
|
||||
return .init(suggestionType: .user(.init(id: member.userID, displayName: member.displayName, avatarURL: member.avatarURL)), range: suggestionTrigger.range, rawSuggestionText: suggestionTrigger.text)
|
||||
}
|
||||
|
||||
if canMentionAllUsers,
|
||||
!roomProxy.isDirectOneToOneRoom,
|
||||
Self.shouldIncludeMember(userID: PillConstants.atRoom, displayName: PillConstants.everyone, searchText: suggestionTrigger.text) {
|
||||
membersSuggestion
|
||||
.insert(SuggestionItem(suggestionType: .allUsers(roomProxy.details.avatar), range: suggestionTrigger.range, rawSuggestionText: suggestionTrigger.text), at: 0)
|
||||
}
|
||||
|
||||
return membersSuggestion
|
||||
}
|
||||
|
||||
private func roomSuggestions(suggestionTrigger: SuggestionTrigger,
|
||||
roomSummaries: [RoomSummary]) -> [SuggestionItem] {
|
||||
roomSummaries
|
||||
.compactMap { roomSummary -> SuggestionItem? in
|
||||
guard let canonicalAlias = roomSummary.canonicalAlias,
|
||||
Self.shouldIncludeRoom(roomName: roomSummary.name, roomAlias: canonicalAlias, searchText: suggestionTrigger.text) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return .init(suggestionType: .room(.init(id: roomSummary.id,
|
||||
canonicalAlias: canonicalAlias,
|
||||
name: roomSummary.name,
|
||||
avatar: roomSummary.avatar)),
|
||||
range: suggestionTrigger.range, rawSuggestionText: suggestionTrigger.text)
|
||||
}
|
||||
}
|
||||
|
||||
private func detectTriggerInText(_ text: String, selectedRange: NSRange) -> SuggestionTrigger? {
|
||||
let matches = text.matches(of: SuggestionTriggerRegex.atOrHash)
|
||||
let match = matches.first { matchResult in
|
||||
let lowerBound = matchResult.range.lowerBound.utf16Offset(in: matchResult.base)
|
||||
let upperBound = matchResult.range.upperBound.utf16Offset(in: matchResult.base)
|
||||
return selectedRange.location >= lowerBound
|
||||
&& selectedRange.location <= upperBound
|
||||
&& selectedRange.length <= upperBound - lowerBound
|
||||
}
|
||||
|
||||
guard let match else {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
var suggestionText = String(text[match.range])
|
||||
suggestionText.removeFirst()
|
||||
let firstChar = suggestionText.removeFirst()
|
||||
|
||||
return .init(type: .user, text: suggestionText, range: NSRange(match.range, in: text))
|
||||
switch firstChar {
|
||||
case SuggestionTriggerRegex.at:
|
||||
return .init(type: .user, text: suggestionText, range: NSRange(match.range, in: text))
|
||||
case SuggestionTriggerRegex.hash:
|
||||
return .init(type: .room, text: suggestionText, range: NSRange(match.range, in: text))
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private static func shouldIncludeMember(userID: String, displayName: String?, searchText: String) -> Bool {
|
||||
@@ -122,4 +149,12 @@ final class CompletionSuggestionService: CompletionSuggestionServiceProtocol {
|
||||
|
||||
return containedInUserID || containedInDisplayName
|
||||
}
|
||||
|
||||
private static func shouldIncludeRoom(roomName: String, roomAlias: String, searchText: String) -> Bool {
|
||||
// If the search text is empty give back all the results
|
||||
guard !searchText.isEmpty else {
|
||||
return true
|
||||
}
|
||||
return roomName.localizedStandardContains(searchText.lowercased()) || roomAlias.localizedStandardContains(searchText.lowercased())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user