diff --git a/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved index 44f6bd53c..751383169 100644 --- a/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -50,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/matrix-org/matrix-wysiwyg-composer-swift", "state" : { - "revision" : "addf90f3e2a6ab46bd2b2febe117d9cddb646e7d", - "version" : "1.1.1" + "revision" : "efa0b75e383a8f8a8269b871cbdee3d9a3a99060", + "version" : "1.2.0" } }, { diff --git a/Riot/Modules/Pills/PillAttachmentViewProvider.swift b/Riot/Modules/Pills/PillAttachmentViewProvider.swift index ba03ef61a..07806eddc 100644 --- a/Riot/Modules/Pills/PillAttachmentViewProvider.swift +++ b/Riot/Modules/Pills/PillAttachmentViewProvider.swift @@ -25,13 +25,18 @@ import UIKit avatarLeading: 2.0, avatarSideLength: 16.0, itemSpacing: 4) - private weak var messageTextView: MXKMessageTextView? + private weak var pillViewFlusher: PillViewFlusher? // MARK: - Override override init(textAttachment: NSTextAttachment, parentView: UIView?, textLayoutManager: NSTextLayoutManager?, location: NSTextLocation) { super.init(textAttachment: textAttachment, parentView: parentView, textLayoutManager: textLayoutManager, location: location) - self.messageTextView = parentView?.superview as? MXKMessageTextView + // Try to register a flusher for the pills. + if let pillViewFlusher = parentView?.superview as? PillViewFlusher { + self.pillViewFlusher = pillViewFlusher + } else { + MXLog.debug("[PillAttachmentViewProvider]: no handler found, pills will not be flushed properly") + } } override func loadView() { @@ -55,6 +60,6 @@ import UIKit mediaManager: mainSession?.mediaManager, andPillData: pillData) view = pillView - messageTextView?.registerPillView(pillView) + pillViewFlusher?.registerPillView(pillView) } } diff --git a/Riot/Modules/Pills/PillViewFlusher.swift b/Riot/Modules/Pills/PillViewFlusher.swift new file mode 100644 index 000000000..44a4d7cbf --- /dev/null +++ b/Riot/Modules/Pills/PillViewFlusher.swift @@ -0,0 +1,39 @@ +// +// Copyright 2023 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import UIKit +import WysiwygComposer + +/// Defines behaviour for an object that is able to manage views created +/// by a `NSTextAttachmentViewProvider`. This can be implemented +/// by an `UITextView` that would keep track of views in order to +/// (internally) clear them when required (e.g. when setting a new attributed text). +/// +/// Note: It is necessary to clear views manually due to a bug in iOS. See `MXKMessageTextView`. +@available(iOS 15.0, *) +protocol PillViewFlusher: AnyObject { + /// Register a pill view that has been added through `NSTextAttachmentViewProvider`. + /// Should be called within the `loadView` function in order to clear the pills properly on text updates. + /// + /// - Parameter pillView: View to register. + func registerPillView(_ pillView: UIView) +} + +@available(iOS 15.0, *) +extension MXKMessageTextView: PillViewFlusher { } + +@available(iOS 15.0, *) +extension WysiwygTextView: PillViewFlusher { } diff --git a/Riot/Modules/Pills/PillsFormatter.swift b/Riot/Modules/Pills/PillsFormatter.swift index a9df99fd4..334a39e73 100644 --- a/Riot/Modules/Pills/PillsFormatter.swift +++ b/Riot/Modules/Pills/PillsFormatter.swift @@ -74,6 +74,48 @@ class PillsFormatter: NSObject { return newAttr } + /// Insert text attachments for pills inside given attributed string containing markdown. + /// + /// - Parameters: + /// - markdownString: An attributed string with markdown formatting + /// - roomState: The current room state + /// - Returns: A new attributed string with pills. + static func insertPills(in markdownString: NSAttributedString, roomState: MXRoomState) -> NSAttributedString { + // Create a regexp that detects markdown links. + let pattern = "\\[([^\\]]+)\\]\\(([^\\)\"\\s]+)(?:\\s+\"(.*)\")?\\)" + guard let regExp = try? NSRegularExpression(pattern: pattern) else { return markdownString } + + let matches = regExp.matches(in: markdownString.string, + range: .init(location: 0, length: markdownString.length)) + + // If we have some matches, replace permalinks by a pill version. + let mutable = NSMutableAttributedString(attributedString: markdownString) + for match in matches.reversed() { + // Range at 2 is the URL, no need to care about the other parts because + // we are retrieving the most recent display name from the room state. + let urlRange = match.range(at: 2) + var url = markdownString.attributedSubstring(from: urlRange).string + + // Note: a valid markdown link can be written with + // enclosing <..>, remove them for userId detection. + if url.first == "<" && url.last == ">" { + url = String(url[url.index(after: url.startIndex)...url.index(url.endIndex, offsetBy: -2)]) + } + + // If we find a user matching the link, replace the + // entire range of the match with a mention pill. + if let userId = userIdFromPermalink(url), + let roomMember = roomMember(withUserId: userId, + roomState: roomState, + andLatestRoomState: nil) { + let attachmentString = mentionPill(withRoomMember: roomMember, isHighlighted: false, font: UIFont.systemFont(ofSize: 14)) + mutable.replaceCharacters(in: match.range, with: attachmentString) + } + } + + return mutable + } + /// Creates a string with all pills of given attributed string replaced by display names. /// /// - Parameters: @@ -160,7 +202,6 @@ class PillsFormatter: NSObject { } } } - } // MARK: - Private Methods @@ -175,4 +216,30 @@ extension PillsFormatter { } return string } + + /// Extract user id from given permalink + /// - Parameter permalink: the permalink + /// - Returns: userId, if any + static func userIdFromPermalink(_ permalink: String) -> String? { + let baseUrl: String + if let clientBaseUrl = BuildSettings.clientPermalinkBaseUrl { + baseUrl = String(format: "%@/#/user/", clientBaseUrl) + } else { + baseUrl = String(format: "%@/#/", kMXMatrixDotToUrl) + } + return permalink.starts(with: baseUrl) ? String(permalink.dropFirst(baseUrl.count)) : nil + } + + /// Retrieve the latest available `MXRoomMember` from given data. + /// + /// - Parameters: + /// - userId: the id of the user + /// - roomState: room state for message + /// - latestRoomState: latest room state of the room containing this message + /// - Returns: the room member, if available + static func roomMember(withUserId userId: String, + roomState: MXRoomState, + andLatestRoomState latestRoomState: MXRoomState?) -> MXRoomMember? { + return latestRoomState?.members.member(withUserId: userId) ?? roomState.members.member(withUserId: userId) + } } diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 2e641916a..17ab27fc4 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -5149,6 +5149,11 @@ static CGSize kThreadListBarButtonItemImageSize; [self.userSuggestionCoordinator processTextMessage:toolbarView.textMessage]; } +- (void)didDetectTextPattern:(SuggestionPatternWrapper *)suggestionPattern +{ + [self.userSuggestionCoordinator processSuggestionPattern:suggestionPattern]; +} + - (void)roomInputToolbarViewDidOpenActionMenu:(RoomInputToolbarView*)toolbarView { // Consider opening the action menu as beginning to type and share encryption keys if requested. diff --git a/Riot/Modules/Room/RoomViewController.swift b/Riot/Modules/Room/RoomViewController.swift index a177281f3..1da92a731 100644 --- a/Riot/Modules/Room/RoomViewController.swift +++ b/Riot/Modules/Room/RoomViewController.swift @@ -20,40 +20,42 @@ import WysiwygComposer extension RoomViewController { // MARK: - Override open override func mention(_ roomMember: MXRoomMember) { - guard let inputToolbar = inputToolbar else { - return - } - - let newAttributedString = NSMutableAttributedString(attributedString: inputToolbar.attributedTextMessage) - - if inputToolbar.attributedTextMessage.length > 0 { - if #available(iOS 15.0, *) { - newAttributedString.append(PillsFormatter.mentionPill(withRoomMember: roomMember, - isHighlighted: false, - font: inputToolbar.textDefaultFont)) - } else { - newAttributedString.appendString(roomMember.displayname.count > 0 ? roomMember.displayname : roomMember.userId) - } - newAttributedString.appendString(" ") - } else if roomMember.userId == self.mainSession.myUser.userId { - newAttributedString.appendString("/me ") + if let wysiwygInputToolbar, wysiwygInputToolbar.textFormattingEnabled { + wysiwygInputToolbar.mention(roomMember) + wysiwygInputToolbar.becomeFirstResponder() } else { - if #available(iOS 15.0, *) { - newAttributedString.append(PillsFormatter.mentionPill(withRoomMember: roomMember, - isHighlighted: false, - font: inputToolbar.textDefaultFont)) - } else { - newAttributedString.appendString(roomMember.displayname.count > 0 ? roomMember.displayname : roomMember.userId) - } - newAttributedString.appendString(": ") - } + guard let attributedText = inputToolbarView.attributedTextMessage else { return } + let newAttributedString = NSMutableAttributedString(attributedString: attributedText) - inputToolbar.attributedTextMessage = newAttributedString - inputToolbar.becomeFirstResponder() + if attributedText.length > 0 { + if #available(iOS 15.0, *) { + newAttributedString.append(PillsFormatter.mentionPill(withRoomMember: roomMember, + isHighlighted: false, + font: UIFont.systemFont(ofSize: 14))) + } else { + newAttributedString.appendString(roomMember.displayname.count > 0 ? roomMember.displayname : roomMember.userId) + } + newAttributedString.appendString(" ") + } else if roomMember.userId == self.mainSession.myUser.userId { + newAttributedString.appendString("/me ") + } else { + if #available(iOS 15.0, *) { + newAttributedString.append(PillsFormatter.mentionPill(withRoomMember: roomMember, + isHighlighted: false, + font: UIFont.systemFont(ofSize: 14))) + } else { + newAttributedString.appendString(roomMember.displayname.count > 0 ? roomMember.displayname : roomMember.userId) + } + newAttributedString.appendString(": ") + } + + inputToolbarView.attributedTextMessage = newAttributedString + inputToolbarView.becomeFirstResponder() + } } - /// Send the formatted text message and its raw counterpat to the room + /// Send the formatted text message and its raw counterpart to the room /// /// - Parameter rawTextMsg: the raw text message /// - Parameter htmlMsg: the html text message @@ -153,7 +155,22 @@ extension RoomViewController { @objc func togglePlainTextMode() { RiotSettings.shared.enableWysiwygTextFormatting.toggle() - wysiwygInputToolbar?.textFormattingEnabled.toggle() + + guard let wysiwygInputToolbar else { return } + + // Switching from plain -> RTE, replace Pills by valid markdown links for parsing. + if !wysiwygInputToolbar.textFormattingEnabled, #available(iOS 15.0, *), + let attributedText = wysiwygInputToolbar.attributedTextMessage { + wysiwygInputToolbar.attributedTextMessage = NSAttributedString(string: PillsFormatter.stringByReplacingPills(in: attributedText, mode: .markdown)) + } + + wysiwygInputToolbar.textFormattingEnabled.toggle() + + // Switching from RTE -> plain, replace markdown links with Pills. + if !wysiwygInputToolbar.textFormattingEnabled, #available(iOS 15.0, *), + let attributedText = wysiwygInputToolbar.attributedTextMessage { + wysiwygInputToolbar.attributedTextMessage = PillsFormatter.insertPills(in: attributedText, roomState: self.roomDataSource.roomState) + } } @objc func didChangeMaximisedState(_ isMaximised: Bool) { @@ -251,6 +268,21 @@ extension RoomViewController { composerLinkActionBridgePresenter = presenter presenter.present(from: self, animated: true) } + + @objc func didRequestAttachmentStringForLink(_ link: String, andDisplayName: String) -> NSAttributedString? { + guard #available(iOS 15.0, *), + let userId = PillsFormatter.userIdFromPermalink(link), + let roomState = self.roomDataSource.roomState, + let member = PillsFormatter.roomMember(withUserId: userId, + roomState: roomState, + andLatestRoomState: nil) else { + return nil + } + + return PillsFormatter.mentionPill(withRoomMember: member, + isHighlighted: false, + font: UIFont.systemFont(ofSize: 14)) + } @objc func showWaitingOtherParticipantHeader() { let controller = VectorHostingController(rootView: RoomWaitingForMembers()) diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h index af84b462d..ee1a032e0 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h @@ -21,6 +21,7 @@ @class RoomActionsBar; @class RoomInputToolbarView; @class LinkActionWrapper; +@class SuggestionPatternWrapper; /** Destination of the message in the composer @@ -80,6 +81,10 @@ typedef NS_ENUM(NSUInteger, RoomInputToolbarViewSendMode) - (void)didSendLinkAction: (LinkActionWrapper *)linkAction; +- (void)didDetectTextPattern: (SuggestionPatternWrapper *)suggestionPattern; + +- (nullable NSAttributedString *)didRequestAttachmentStringForLink: (NSString *)link andDisplayName: (NSString *)displayName; + @end /** diff --git a/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift b/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift index db6cc8193..aefef2459 100644 --- a/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift +++ b/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift @@ -43,8 +43,9 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp private var heightConstraint: NSLayoutConstraint! private var voiceMessageBottomConstraint: NSLayoutConstraint? private var hostingViewController: VectorHostingController! - private var wysiwygViewModel = WysiwygComposerViewModel( - parserStyle: WysiwygInputToolbarView.parserStyle + private lazy var wysiwygViewModel = WysiwygComposerViewModel( + parserStyle: WysiwygInputToolbarView.parserStyle, + permalinkReplacer: self ) /// Compute current HTML parser style for composer. private static var parserStyle: HTMLParserStyle { @@ -85,6 +86,19 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp override var isFocused: Bool { viewModel.isFocused } + + override var attributedTextMessage: NSAttributedString? { + // Note: this is only interactive in plain text mode. If RTE is enabled, + // APIs from the composer view model should be used. + get { + guard !self.textFormattingEnabled else { return nil } + return self.wysiwygViewModel.textView.attributedText + } + set { + guard !self.textFormattingEnabled else { return } + self.wysiwygViewModel.textView.attributedText = newValue + } + } var isMaximised: Bool { wysiwygViewModel.maximised @@ -217,6 +231,11 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp override func dismissKeyboard() { self.viewModel.dismissKeyboard() } + + @discardableResult + override func becomeFirstResponder() -> Bool { + self.wysiwygViewModel.textView.becomeFirstResponder() + } override func dismissValidationView(_ validationView: MXKImageView!) { super.dismissValidationView(validationView) @@ -239,6 +258,12 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp } wysiwygViewModel.applyLinkOperation(linkOperation) } + + func mention(_ member: MXRoomMember) { + self.wysiwygViewModel.setMention(link: MXTools.permalinkToUser(withUserId: member.userId), + name: member.displayname, + key: .at) + } // MARK: - Private @@ -291,6 +316,8 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp setVoiceMessageToolbarIsHidden(!isEmpty) case let .linkTapped(linkAction): toolbarViewDelegate?.didSendLinkAction(LinkActionWrapper(linkAction)) + case let .suggestion(pattern): + toolbarViewDelegate?.didDetectTextPattern(SuggestionPatternWrapper(pattern)) } } @@ -412,6 +439,12 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp } } +extension WysiwygInputToolbarView: PermalinkReplacer { + func replacementForLink(_ link: String, text: String) -> NSAttributedString? { + return toolbarViewDelegate?.didRequestAttachmentString(forLink: link, andDisplayName: text) + } +} + // MARK: - LegacySendModeAdapter fileprivate extension ComposerSendMode { diff --git a/RiotSwiftUI/Modules/Room/Composer/Model/ComposerModels.swift b/RiotSwiftUI/Modules/Room/Composer/Model/ComposerModels.swift index 98d7febf6..c96453667 100644 --- a/RiotSwiftUI/Modules/Room/Composer/Model/ComposerModels.swift +++ b/RiotSwiftUI/Modules/Room/Composer/Model/ComposerModels.swift @@ -229,12 +229,14 @@ enum ComposerViewAction: Equatable { case contentDidChange(isEmpty: Bool) case linkTapped(linkAction: LinkAction) case storeSelection(selection: NSRange) + case suggestion(pattern: SuggestionPattern?) } enum ComposerViewModelResult: Equatable { case cancel case contentDidChange(isEmpty: Bool) case linkTapped(LinkAction: LinkAction) + case suggestion(pattern: SuggestionPattern?) } final class LinkActionWrapper: NSObject { @@ -245,3 +247,12 @@ final class LinkActionWrapper: NSObject { super.init() } } + +final class SuggestionPatternWrapper: NSObject { + let suggestionPattern: SuggestionPattern? + + init(_ suggestionPattern: SuggestionPattern?) { + self.suggestionPattern = suggestionPattern + super.init() + } +} diff --git a/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift b/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift index 1413912c2..fb6ed8851 100644 --- a/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift +++ b/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift @@ -248,6 +248,9 @@ struct Composer: View { wysiwygViewModel.maximised = false } } + .onChange(of: wysiwygViewModel.suggestionPattern) { newValue in + sendMentionPattern(pattern: newValue) + } } private func storeCurrentSelection() { @@ -258,6 +261,10 @@ struct Composer: View { let linkAction = wysiwygViewModel.getLinkAction() viewModel.send(viewAction: .linkTapped(linkAction: linkAction)) } + + private func sendMentionPattern(pattern: SuggestionPattern?) { + viewModel.send(viewAction: .suggestion(pattern: pattern)) + } } private extension WysiwygComposerViewModel { diff --git a/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModel.swift b/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModel.swift index a78018f60..6448b9de3 100644 --- a/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModel.swift +++ b/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModel.swift @@ -90,6 +90,8 @@ final class ComposerViewModel: ComposerViewModelType, ComposerViewModelProtocol callback?(.linkTapped(LinkAction: linkAction)) case let .storeSelection(selection): selectionToRestore = selection + case let .suggestion(pattern: pattern): + callback?(.suggestion(pattern: pattern)) } } diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinator.swift b/RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinator.swift index c6d86a655..59b25ef86 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinator.swift +++ b/RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinator.swift @@ -18,6 +18,7 @@ import Combine import Foundation import SwiftUI import UIKit +import WysiwygComposer protocol UserSuggestionCoordinatorDelegate: AnyObject { func userSuggestionCoordinator(_ coordinator: UserSuggestionCoordinator, didRequestMentionForMember member: MXRoomMember, textTrigger: String?) @@ -92,6 +93,10 @@ final class UserSuggestionCoordinator: Coordinator, Presentable { userSuggestionService.processTextMessage(textMessage) } + func processSuggestionPattern(_ suggestionPattern: SuggestionPattern?) { + userSuggestionService.processSuggestionPattern(suggestionPattern) + } + // MARK: - Public func start() { } diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinatorBridge.swift b/RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinatorBridge.swift index c5b68eeee..4605547eb 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinatorBridge.swift +++ b/RiotSwiftUI/Modules/Room/UserSuggestion/Coordinator/UserSuggestionCoordinatorBridge.swift @@ -44,6 +44,10 @@ final class UserSuggestionCoordinatorBridge: NSObject { func processTextMessage(_ textMessage: String) { userSuggestionCoordinator.processTextMessage(textMessage) } + + func processSuggestionPattern(_ suggestionPatternWrapper: SuggestionPatternWrapper) { + userSuggestionCoordinator.processSuggestionPattern(suggestionPatternWrapper.suggestionPattern) + } func toPresentable() -> UIViewController? { userSuggestionCoordinator.toPresentable() diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/Service/UserSuggestionService.swift b/RiotSwiftUI/Modules/Room/UserSuggestion/Service/UserSuggestionService.swift index bf8fa00a5..0f161ee38 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/Service/UserSuggestionService.swift +++ b/RiotSwiftUI/Modules/Room/UserSuggestion/Service/UserSuggestionService.swift @@ -16,6 +16,7 @@ import Combine import Foundation +import WysiwygComposer struct RoomMembersProviderMember { var userId: String @@ -85,6 +86,16 @@ class UserSuggestionService: UserSuggestionServiceProtocol { currentTextTriggerSubject.send(lastComponent) } + + func processSuggestionPattern(_ suggestionPattern: SuggestionPattern?) { + guard let suggestionPattern, suggestionPattern.key == .at else { + items.send([]) + currentTextTriggerSubject.send(nil) + return + } + + currentTextTriggerSubject.send("@" + suggestionPattern.text) + } // MARK: - Private diff --git a/RiotSwiftUI/Modules/Room/UserSuggestion/Service/UserSuggestionServiceProtocol.swift b/RiotSwiftUI/Modules/Room/UserSuggestion/Service/UserSuggestionServiceProtocol.swift index 81edb0df9..43006dbed 100644 --- a/RiotSwiftUI/Modules/Room/UserSuggestion/Service/UserSuggestionServiceProtocol.swift +++ b/RiotSwiftUI/Modules/Room/UserSuggestion/Service/UserSuggestionServiceProtocol.swift @@ -16,6 +16,7 @@ import Combine import Foundation +import WysiwygComposer protocol UserSuggestionItemProtocol: Avatarable { var userId: String { get } @@ -29,6 +30,7 @@ protocol UserSuggestionServiceProtocol { var currentTextTrigger: String? { get } func processTextMessage(_ textMessage: String?) + func processSuggestionPattern(_ suggestionPattern: SuggestionPattern?) } // MARK: Avatarable diff --git a/project.yml b/project.yml index acc69ccdc..3df4c94ff 100644 --- a/project.yml +++ b/project.yml @@ -56,7 +56,7 @@ packages: branch: 0.0.1 WysiwygComposer: url: https://github.com/matrix-org/matrix-wysiwyg-composer-swift - version: 1.1.1 + version: 1.2.0 DeviceKit: url: https://github.com/devicekit/DeviceKit majorVersion: 4.7.0