Send html in plain text mode (#2246)

This commit is contained in:
Mauro
2023-12-15 10:04:51 +01:00
committed by GitHub
parent af562eabf7
commit 2f88d39789
19 changed files with 54 additions and 64 deletions
+1 -1
View File
@@ -6598,7 +6598,7 @@
repositoryURL = "https://github.com/matrix-org/matrix-wysiwyg-composer-swift"; repositoryURL = "https://github.com/matrix-org/matrix-wysiwyg-composer-swift";
requirement = { requirement = {
kind = exactVersion; kind = exactVersion;
version = 2.19.0; version = 2.22.0;
}; };
}; };
96495DD8554E2F39D3954354 /* XCRemoteSwiftPackageReference "posthog-ios" */ = { 96495DD8554E2F39D3954354 /* XCRemoteSwiftPackageReference "posthog-ios" */ = {
@@ -139,8 +139,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/matrix-org/matrix-wysiwyg-composer-swift", "location" : "https://github.com/matrix-org/matrix-wysiwyg-composer-swift",
"state" : { "state" : {
"revision" : "0aa1308c43451fd077e332f72d6a32135f258834", "revision" : "5d3bdd55f8c68c216f91191a501c0d7225057f92",
"version" : "2.19.0" "version" : "2.22.0"
} }
}, },
{ {
@@ -267,9 +267,8 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate,
return return
} }
let roomProxy = await userSession.clientProxy.roomForIdentifier(roomID) let roomProxy = await userSession.clientProxy.roomForIdentifier(roomID)
switch await roomProxy?.timeline.sendMessage(replyText, let messageContent = messageEventContentFromMarkdown(md: replyText)
html: nil, switch await roomProxy?.timeline.sendMessageEventContent(messageContent) {
intentionalMentions: .empty) {
case .success: case .success:
break break
default: default:
@@ -2628,12 +2628,12 @@ class TimelineProxyMock: TimelineProxyProtocol {
var editMessageHtmlOriginalIntentionalMentionsCalled: Bool { var editMessageHtmlOriginalIntentionalMentionsCalled: Bool {
return editMessageHtmlOriginalIntentionalMentionsCallsCount > 0 return editMessageHtmlOriginalIntentionalMentionsCallsCount > 0
} }
var editMessageHtmlOriginalIntentionalMentionsReceivedArguments: (message: String, html: String?, eventID: String, intentionalMentions: IntentionalMentions)? var editMessageHtmlOriginalIntentionalMentionsReceivedArguments: (message: String, html: String, eventID: String, intentionalMentions: IntentionalMentions)?
var editMessageHtmlOriginalIntentionalMentionsReceivedInvocations: [(message: String, html: String?, eventID: String, intentionalMentions: IntentionalMentions)] = [] var editMessageHtmlOriginalIntentionalMentionsReceivedInvocations: [(message: String, html: String, eventID: String, intentionalMentions: IntentionalMentions)] = []
var editMessageHtmlOriginalIntentionalMentionsReturnValue: Result<Void, TimelineProxyError>! var editMessageHtmlOriginalIntentionalMentionsReturnValue: Result<Void, TimelineProxyError>!
var editMessageHtmlOriginalIntentionalMentionsClosure: ((String, String?, String, IntentionalMentions) async -> Result<Void, TimelineProxyError>)? var editMessageHtmlOriginalIntentionalMentionsClosure: ((String, String, String, IntentionalMentions) async -> Result<Void, TimelineProxyError>)?
func editMessage(_ message: String, html: String?, original eventID: String, intentionalMentions: IntentionalMentions) async -> Result<Void, TimelineProxyError> { func editMessage(_ message: String, html: String, original eventID: String, intentionalMentions: IntentionalMentions) async -> Result<Void, TimelineProxyError> {
editMessageHtmlOriginalIntentionalMentionsCallsCount += 1 editMessageHtmlOriginalIntentionalMentionsCallsCount += 1
editMessageHtmlOriginalIntentionalMentionsReceivedArguments = (message: message, html: html, eventID: eventID, intentionalMentions: intentionalMentions) editMessageHtmlOriginalIntentionalMentionsReceivedArguments = (message: message, html: html, eventID: eventID, intentionalMentions: intentionalMentions)
editMessageHtmlOriginalIntentionalMentionsReceivedInvocations.append((message: message, html: html, eventID: eventID, intentionalMentions: intentionalMentions)) editMessageHtmlOriginalIntentionalMentionsReceivedInvocations.append((message: message, html: html, eventID: eventID, intentionalMentions: intentionalMentions))
@@ -2887,12 +2887,12 @@ class TimelineProxyMock: TimelineProxyProtocol {
var sendMessageHtmlInReplyToIntentionalMentionsCalled: Bool { var sendMessageHtmlInReplyToIntentionalMentionsCalled: Bool {
return sendMessageHtmlInReplyToIntentionalMentionsCallsCount > 0 return sendMessageHtmlInReplyToIntentionalMentionsCallsCount > 0
} }
var sendMessageHtmlInReplyToIntentionalMentionsReceivedArguments: (message: String, html: String?, eventID: String?, intentionalMentions: IntentionalMentions)? var sendMessageHtmlInReplyToIntentionalMentionsReceivedArguments: (message: String, html: String, eventID: String?, intentionalMentions: IntentionalMentions)?
var sendMessageHtmlInReplyToIntentionalMentionsReceivedInvocations: [(message: String, html: String?, eventID: String?, intentionalMentions: IntentionalMentions)] = [] var sendMessageHtmlInReplyToIntentionalMentionsReceivedInvocations: [(message: String, html: String, eventID: String?, intentionalMentions: IntentionalMentions)] = []
var sendMessageHtmlInReplyToIntentionalMentionsReturnValue: Result<Void, TimelineProxyError>! var sendMessageHtmlInReplyToIntentionalMentionsReturnValue: Result<Void, TimelineProxyError>!
var sendMessageHtmlInReplyToIntentionalMentionsClosure: ((String, String?, String?, IntentionalMentions) async -> Result<Void, TimelineProxyError>)? var sendMessageHtmlInReplyToIntentionalMentionsClosure: ((String, String, String?, IntentionalMentions) async -> Result<Void, TimelineProxyError>)?
func sendMessage(_ message: String, html: String?, inReplyTo eventID: String?, intentionalMentions: IntentionalMentions) async -> Result<Void, TimelineProxyError> { func sendMessage(_ message: String, html: String, inReplyTo eventID: String?, intentionalMentions: IntentionalMentions) async -> Result<Void, TimelineProxyError> {
sendMessageHtmlInReplyToIntentionalMentionsCallsCount += 1 sendMessageHtmlInReplyToIntentionalMentionsCallsCount += 1
sendMessageHtmlInReplyToIntentionalMentionsReceivedArguments = (message: message, html: html, eventID: eventID, intentionalMentions: intentionalMentions) sendMessageHtmlInReplyToIntentionalMentionsReceivedArguments = (message: message, html: html, eventID: eventID, intentionalMentions: intentionalMentions)
sendMessageHtmlInReplyToIntentionalMentionsReceivedInvocations.append((message: message, html: html, eventID: eventID, intentionalMentions: intentionalMentions)) sendMessageHtmlInReplyToIntentionalMentionsReceivedInvocations.append((message: message, html: html, eventID: eventID, intentionalMentions: intentionalMentions))
@@ -93,8 +93,6 @@ class CallScreenViewModel: CallScreenViewModelType, CallScreenViewModelProtocol
switch action { switch action {
case .callEnded: case .callEnded:
actionsSubject.send(.dismiss) actionsSubject.send(.dismiss)
default:
break
} }
} }
.store(in: &cancellables) .store(in: &cancellables)
@@ -31,7 +31,7 @@ enum ComposerToolbarVoiceMessageAction {
} }
enum ComposerToolbarViewModelAction { enum ComposerToolbarViewModelAction {
case sendMessage(plain: String, html: String?, mode: RoomScreenComposerMode, intentionalMentions: IntentionalMentions) case sendMessage(plain: String, html: String, mode: RoomScreenComposerMode, intentionalMentions: IntentionalMentions)
case attach(ComposerAttachmentType) case attach(ComposerAttachmentType)
case handlePasteOrDrop(provider: NSItemProvider) case handlePasteOrDrop(provider: NSItemProvider)
@@ -87,6 +87,10 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
} }
.store(in: &cancellables) .store(in: &cancellables)
appSettings.$richTextEditorEnabled
.map { !$0 }
.assign(to: &wysiwygViewModel.$plainTextMode)
completionSuggestionService.suggestionsPublisher completionSuggestionService.suggestionsPublisher
.weakAssign(to: \.state.suggestions, on: self) .weakAssign(to: \.state.suggestions, on: self)
.store(in: &cancellables) .store(in: &cancellables)
@@ -107,9 +111,8 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
case .previewVoiceMessage: case .previewVoiceMessage:
actionsSubject.send(.voiceMessage(.send)) actionsSubject.send(.voiceMessage(.send))
default: default:
let sendHTML = ServiceLocator.shared.settings.richTextEditorEnabled
actionsSubject.send(.sendMessage(plain: wysiwygViewModel.content.markdown, actionsSubject.send(.sendMessage(plain: wysiwygViewModel.content.markdown,
html: sendHTML ? wysiwygViewModel.content.html : nil, html: wysiwygViewModel.content.html,
mode: state.composerMode, mode: state.composerMode,
intentionalMentions: wysiwygViewModel intentionalMentions: wysiwygViewModel
.getMentionsState() .getMentionsState()
@@ -154,15 +157,13 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
set(text: "") set(text: "")
} }
} }
func handleKeyCommand(_ keyCommand: WysiwygKeyCommand) -> Bool { var keyCommands: [WysiwygKeyCommand] {
switch keyCommand { [
case .enter: .enter { [weak self] in
process(viewAction: .sendMessage) self?.process(viewAction: .sendMessage)
return true }
case .shiftEnter: ]
return false
}
} }
// MARK: - Private // MARK: - Private
@@ -24,7 +24,7 @@ struct ComposerToolbar: View {
// Needs to be observable or the placeholder and the dictation state are not managed correctly. // Needs to be observable or the placeholder and the dictation state are not managed correctly.
@ObservedObject var wysiwygViewModel: WysiwygComposerViewModel @ObservedObject var wysiwygViewModel: WysiwygComposerViewModel
let keyCommandHandler: KeyCommandHandler let keyCommands: [WysiwygKeyCommand]
@FocusState private var composerFocused: Bool @FocusState private var composerFocused: Bool
@State private var frame: CGRect = .zero @State private var frame: CGRect = .zero
@@ -201,7 +201,7 @@ struct ComposerToolbar: View {
placeholderColor: .compound.textSecondary, placeholderColor: .compound.textSecondary,
viewModel: wysiwygViewModel, viewModel: wysiwygViewModel,
itemProviderHelper: ItemProviderHelper(), itemProviderHelper: ItemProviderHelper(),
keyCommandHandler: keyCommandHandler) { provider in keyCommands: keyCommands) { provider in
context.send(viewAction: .handlePasteOrDrop(provider: provider)) context.send(viewAction: .handlePasteOrDrop(provider: provider))
} }
} }
@@ -306,7 +306,7 @@ struct ComposerToolbar_Previews: PreviewProvider, TestablePreview {
// The mock functon can't be used in this context because it does not hold a reference to the view model, losing the combine subscriptions // The mock functon can't be used in this context because it does not hold a reference to the view model, losing the combine subscriptions
ComposerToolbar(context: composerViewModel.context, ComposerToolbar(context: composerViewModel.context,
wysiwygViewModel: wysiwygViewModel, wysiwygViewModel: wysiwygViewModel,
keyCommandHandler: { _ in false }) keyCommands: [])
} }
.previewDisplayName("With Suggestions") .previewDisplayName("With Suggestions")
@@ -336,7 +336,7 @@ extension ComposerToolbar {
} }
return ComposerToolbar(context: composerViewModel.context, return ComposerToolbar(context: composerViewModel.context,
wysiwygViewModel: wysiwygViewModel, wysiwygViewModel: wysiwygViewModel,
keyCommandHandler: { _ in false }) keyCommands: [])
} }
static func textWithVoiceMessage(focused: Bool = true) -> ComposerToolbar { static func textWithVoiceMessage(focused: Bool = true) -> ComposerToolbar {
@@ -352,7 +352,7 @@ extension ComposerToolbar {
} }
return ComposerToolbar(context: composerViewModel.context, return ComposerToolbar(context: composerViewModel.context,
wysiwygViewModel: wysiwygViewModel, wysiwygViewModel: wysiwygViewModel,
keyCommandHandler: { _ in false }) keyCommands: [])
} }
static func voiceMessageRecordingMock() -> ComposerToolbar { static func voiceMessageRecordingMock() -> ComposerToolbar {
@@ -368,7 +368,7 @@ extension ComposerToolbar {
} }
return ComposerToolbar(context: composerViewModel.context, return ComposerToolbar(context: composerViewModel.context,
wysiwygViewModel: wysiwygViewModel, wysiwygViewModel: wysiwygViewModel,
keyCommandHandler: { _ in false }) keyCommands: [])
} }
static func voiceMessagePreviewMock(uploading: Bool) -> ComposerToolbar { static func voiceMessagePreviewMock(uploading: Bool) -> ComposerToolbar {
@@ -385,6 +385,6 @@ extension ComposerToolbar {
} }
return ComposerToolbar(context: composerViewModel.context, return ComposerToolbar(context: composerViewModel.context,
wysiwygViewModel: wysiwygViewModel, wysiwygViewModel: wysiwygViewModel,
keyCommandHandler: { _ in false }) keyCommands: [])
} }
} }
@@ -212,7 +212,7 @@ struct MessageComposer_Previews: PreviewProvider, TestablePreview {
let composerView = WysiwygComposerView(placeholder: L10n.richTextEditorComposerPlaceholder, let composerView = WysiwygComposerView(placeholder: L10n.richTextEditorComposerPlaceholder,
viewModel: viewModel, viewModel: viewModel,
itemProviderHelper: nil, itemProviderHelper: nil,
keyCommandHandler: nil, keyCommands: nil,
pasteHandler: nil) pasteHandler: nil)
return MessageComposer(composerView: composerView, return MessageComposer(composerView: composerView,
@@ -138,7 +138,7 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
func toPresentable() -> AnyView { func toPresentable() -> AnyView {
let composerToolbar = ComposerToolbar(context: composerViewModel.context, let composerToolbar = ComposerToolbar(context: composerViewModel.context,
wysiwygViewModel: wysiwygViewModel, wysiwygViewModel: wysiwygViewModel,
keyCommandHandler: composerViewModel.handleKeyCommand) keyCommands: composerViewModel.keyCommands)
return AnyView(RoomScreen(context: viewModel.context, composerToolbar: composerToolbar)) return AnyView(RoomScreen(context: viewModel.context, composerToolbar: composerToolbar))
} }
@@ -461,7 +461,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
state.showLoading = false state.showLoading = false
} }
private func sendCurrentMessage(_ message: String, html: String?, mode: RoomScreenComposerMode, intentionalMentions: IntentionalMentions) async { private func sendCurrentMessage(_ message: String, html: String, mode: RoomScreenComposerMode, intentionalMentions: IntentionalMentions) async {
guard !message.isEmpty else { guard !message.isEmpty else {
fatalError("This message should never be empty") fatalError("This message should never be empty")
} }
@@ -65,14 +65,14 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
func processItemDisappearance(_ itemID: TimelineItemIdentifier) async { } func processItemDisappearance(_ itemID: TimelineItemIdentifier) async { }
func sendMessage(_ message: String, func sendMessage(_ message: String,
html: String?, html: String,
inReplyTo itemID: TimelineItemIdentifier?, inReplyTo itemID: TimelineItemIdentifier?,
intentionalMentions: IntentionalMentions) async { } intentionalMentions: IntentionalMentions) async { }
func toggleReaction(_ reaction: String, to itemID: TimelineItemIdentifier) async { } func toggleReaction(_ reaction: String, to itemID: TimelineItemIdentifier) async { }
func editMessage(_ newMessage: String, func editMessage(_ newMessage: String,
html: String?, html: String,
original itemID: TimelineItemIdentifier, original itemID: TimelineItemIdentifier,
intentionalMentions: IntentionalMentions) async { } intentionalMentions: IntentionalMentions) async { }
@@ -107,7 +107,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
func processItemDisappearance(_ itemID: TimelineItemIdentifier) { } func processItemDisappearance(_ itemID: TimelineItemIdentifier) { }
func sendMessage(_ message: String, func sendMessage(_ message: String,
html: String?, html: String,
inReplyTo itemID: TimelineItemIdentifier?, inReplyTo itemID: TimelineItemIdentifier?,
intentionalMentions: IntentionalMentions) async { intentionalMentions: IntentionalMentions) async {
var inReplyTo: String? var inReplyTo: String?
@@ -148,7 +148,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
} }
func editMessage(_ newMessage: String, func editMessage(_ newMessage: String,
html: String?, html: String,
original itemID: TimelineItemIdentifier, original itemID: TimelineItemIdentifier,
intentionalMentions: IntentionalMentions) async { intentionalMentions: IntentionalMentions) async {
MXLog.info("Edit message in \(roomID)") MXLog.info("Edit message in \(roomID)")
@@ -50,12 +50,12 @@ protocol RoomTimelineControllerProtocol {
func sendReadReceipt(for itemID: TimelineItemIdentifier) async -> Result<Void, RoomTimelineControllerError> func sendReadReceipt(for itemID: TimelineItemIdentifier) async -> Result<Void, RoomTimelineControllerError>
func sendMessage(_ message: String, func sendMessage(_ message: String,
html: String?, html: String,
inReplyTo itemID: TimelineItemIdentifier?, inReplyTo itemID: TimelineItemIdentifier?,
intentionalMentions: IntentionalMentions) async intentionalMentions: IntentionalMentions) async
func editMessage(_ newMessage: String, func editMessage(_ newMessage: String,
html: String?, html: String,
original itemID: TimelineItemIdentifier, original itemID: TimelineItemIdentifier,
intentionalMentions: IntentionalMentions) async intentionalMentions: IntentionalMentions) async
@@ -74,7 +74,7 @@ protocol RoomTimelineControllerProtocol {
extension RoomTimelineControllerProtocol { extension RoomTimelineControllerProtocol {
func sendMessage(_ message: String, func sendMessage(_ message: String,
html: String?, html: String,
intentionalMentions: IntentionalMentions) async { intentionalMentions: IntentionalMentions) async {
await sendMessage(message, await sendMessage(message,
html: html, html: html,
@@ -93,7 +93,7 @@ final class TimelineProxy: TimelineProxyProtocol {
} }
func editMessage(_ message: String, func editMessage(_ message: String,
html: String?, html: String,
original eventID: String, original eventID: String,
intentionalMentions: IntentionalMentions) async -> Result<Void, TimelineProxyError> { intentionalMentions: IntentionalMentions) async -> Result<Void, TimelineProxyError> {
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
@@ -320,7 +320,7 @@ final class TimelineProxy: TimelineProxyProtocol {
} }
func sendMessage(_ message: String, func sendMessage(_ message: String,
html: String?, html: String,
inReplyTo eventID: String? = nil, inReplyTo eventID: String? = nil,
intentionalMentions: IntentionalMentions) async -> Result<Void, TimelineProxyError> { intentionalMentions: IntentionalMentions) async -> Result<Void, TimelineProxyError> {
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
@@ -454,7 +454,7 @@ final class TimelineProxy: TimelineProxyProtocol {
// MARK: - Private // MARK: - Private
private func buildMessageContentFor(_ message: String, private func buildMessageContentFor(_ message: String,
html: String?, html: String,
intentionalMentions: Mentions) -> RoomMessageEventContentWithoutRelation { intentionalMentions: Mentions) -> RoomMessageEventContentWithoutRelation {
let emoteSlashCommand = "/me " let emoteSlashCommand = "/me "
let isEmote: Bool = message.starts(with: emoteSlashCommand) let isEmote: Bool = message.starts(with: emoteSlashCommand)
@@ -462,18 +462,10 @@ final class TimelineProxy: TimelineProxyProtocol {
let content: RoomMessageEventContentWithoutRelation let content: RoomMessageEventContentWithoutRelation
if isEmote { if isEmote {
let emoteMessage = String(message.dropFirst(emoteSlashCommand.count)) let emoteMessage = String(message.dropFirst(emoteSlashCommand.count))
let emoteHtml = String(html.dropFirst(emoteSlashCommand.count))
var emoteHtml: String?
if let html {
emoteHtml = String(html.dropFirst(emoteSlashCommand.count))
}
content = buildEmoteMessageContentFor(emoteMessage, html: emoteHtml) content = buildEmoteMessageContentFor(emoteMessage, html: emoteHtml)
} else { } else {
if let html { content = messageEventContentFromHtml(body: message, htmlBody: html)
content = messageEventContentFromHtml(body: message, htmlBody: html)
} else {
content = messageEventContentFromMarkdown(md: message)
}
} }
return content.withMentions(mentions: intentionalMentions) return content.withMentions(mentions: intentionalMentions)
} }
@@ -49,7 +49,7 @@ protocol TimelineProxyProtocol {
func cancelSend(transactionID: String) async func cancelSend(transactionID: String) async
func editMessage(_ message: String, func editMessage(_ message: String,
html: String?, html: String,
original eventID: String, original eventID: String,
intentionalMentions: IntentionalMentions) async -> Result<Void, TimelineProxyError> intentionalMentions: IntentionalMentions) async -> Result<Void, TimelineProxyError>
@@ -103,7 +103,7 @@ protocol TimelineProxyProtocol {
func sendMessageEventContent(_ messageContent: RoomMessageEventContentWithoutRelation) async -> Result<Void, TimelineProxyError> func sendMessageEventContent(_ messageContent: RoomMessageEventContentWithoutRelation) async -> Result<Void, TimelineProxyError>
func sendMessage(_ message: String, func sendMessage(_ message: String,
html: String?, html: String,
inReplyTo eventID: String?, inReplyTo eventID: String?,
intentionalMentions: IntentionalMentions) async -> Result<Void, TimelineProxyError> intentionalMentions: IntentionalMentions) async -> Result<Void, TimelineProxyError>
@@ -124,7 +124,7 @@ protocol TimelineProxyProtocol {
extension TimelineProxyProtocol { extension TimelineProxyProtocol {
func sendMessage(_ message: String, func sendMessage(_ message: String,
html: String?, html: String,
intentionalMentions: IntentionalMentions) async -> Result<Void, TimelineProxyError> { intentionalMentions: IntentionalMentions) async -> Result<Void, TimelineProxyError> {
await sendMessage(message, await sendMessage(message,
html: html, html: html,
@@ -81,8 +81,7 @@ class ComposerToolbarViewModelTests: XCTestCase {
} }
func testHandleKeyCommand() { func testHandleKeyCommand() {
XCTAssertTrue(viewModel.handleKeyCommand(.enter)) XCTAssertTrue(viewModel.keyCommands.count == 1)
XCTAssertFalse(viewModel.handleKeyCommand(.shiftEnter))
} }
func testComposerFocusAfterEnablingRTE() { func testComposerFocusAfterEnablingRTE() {
+1
View File
@@ -0,0 +1 @@
Fix for plain text mode, using the RTE markdown to html conversion properly.
+1 -1
View File
@@ -64,7 +64,7 @@ packages:
branch: 0.0.1 branch: 0.0.1
WysiwygComposer: WysiwygComposer:
url: https://github.com/matrix-org/matrix-wysiwyg-composer-swift url: https://github.com/matrix-org/matrix-wysiwyg-composer-swift
exactVersion: 2.19.0 exactVersion: 2.22.0
# path: ../matrix-wysiwyg/platforms/ios/lib/WysiwygComposer # path: ../matrix-wysiwyg/platforms/ios/lib/WysiwygComposer
# External dependencies # External dependencies