diff --git a/Sources/WysiwygComposer/Components/ComposerModelWrapper.swift b/Sources/WysiwygComposer/Components/ComposerModelWrapper.swift index b132b03..039582f 100644 --- a/Sources/WysiwygComposer/Components/ComposerModelWrapper.swift +++ b/Sources/WysiwygComposer/Components/ComposerModelWrapper.swift @@ -21,7 +21,9 @@ protocol ComposerModelWrapperProtocol { func setContentFromHtml(html: String) -> ComposerUpdate func setContentFromMarkdown(markdown: String) -> ComposerUpdate func getContentAsHtml() -> String + func getContentAsMessageHtml() -> String func getContentAsMarkdown() -> String + func getContentAsMessageMarkdown() -> String func getContentAsPlainText() -> String func clear() -> ComposerUpdate func select(startUtf16Codeunit: UInt32, endUtf16Codeunit: UInt32) -> ComposerUpdate @@ -78,10 +80,18 @@ final class ComposerModelWrapper: ComposerModelWrapperProtocol { model.getContentAsHtml() } + func getContentAsMessageHtml() -> String { + model.getContentAsMessageHtml() + } + func getContentAsMarkdown() -> String { model.getContentAsMarkdown() } + func getContentAsMessageMarkdown() -> String { + model.getContentAsMessageMarkdown() + } + func getContentAsPlainText() -> String { model.getContentAsPlainText() } @@ -129,6 +139,14 @@ final class ComposerModelWrapper: ComposerModelWrapperProtocol { func insertMentionAtSuggestion(url: String, text: String, suggestion: SuggestionPattern, attributes: [Attribute]) -> ComposerUpdate { execute { try $0.insertMentionAtSuggestion(url: url, text: text, suggestion: suggestion, attributes: attributes) } } + + func insertAtRoomMention() -> ComposerUpdate { + execute { try $0.insertAtRoomMention() } + } + + func insertAtRoomMentionAtSuggestion(_ suggestion: SuggestionPattern) -> ComposerUpdate { + execute { try $0.insertAtRoomMentionAtSuggestion(suggestion: suggestion) } + } func removeLinks() -> ComposerUpdate { execute { try $0.removeLinks() } diff --git a/Sources/WysiwygComposer/Components/WysiwygComposerView/WysiwygComposerViewModel.swift b/Sources/WysiwygComposer/Components/WysiwygComposerView/WysiwygComposerViewModel.swift index 8fb7b65..486ff80 100644 --- a/Sources/WysiwygComposer/Components/WysiwygComposerView/WysiwygComposerViewModel.swift +++ b/Sources/WysiwygComposer/Components/WysiwygComposerView/WysiwygComposerViewModel.swift @@ -108,8 +108,8 @@ public class WysiwygComposerViewModel: WysiwygComposerViewModelProtocol, Observa if plainTextMode { _ = model.setContentFromMarkdown(markdown: computeMarkdownContent()) } - return WysiwygComposerContent(markdown: model.getContentAsMarkdown(), - html: model.getContentAsHtml()) + return WysiwygComposerContent(markdown: model.getContentAsMessageMarkdown(), + html: model.getContentAsMessageHtml()) } // MARK: - Private @@ -251,6 +251,18 @@ public extension WysiwygComposerViewModel { applyUpdate(update) hasPendingFormats = true } + + /// Sets the @room mention at the suggestion position + func setAtRoomMention() { + let update: ComposerUpdate + if let suggestionPattern, suggestionPattern.key == .at { + update = model.insertAtRoomMentionAtSuggestion(suggestionPattern) + } else { + update = model.insertAtRoomMention() + } + applyUpdate(update) + hasPendingFormats = true + } /// Set a command with `Slash` pattern. /// diff --git a/Sources/WysiwygComposer/Components/WysiwygComposerView/WysiwygTextView.swift b/Sources/WysiwygComposer/Components/WysiwygComposerView/WysiwygTextView.swift index 29f2c6c..c6a532a 100644 --- a/Sources/WysiwygComposer/Components/WysiwygComposerView/WysiwygTextView.swift +++ b/Sources/WysiwygComposer/Components/WysiwygComposerView/WysiwygTextView.swift @@ -41,7 +41,7 @@ protocol WysiwygTextViewDelegate: AnyObject { } /// A markdown protocol used to provide additional context to the text view when displaying mentions through the text attachment provider -public protocol MentionDisplayHelper {} +public protocol MentionDisplayHelper { } public class WysiwygTextView: UITextView { /// Internal delegate for the text view. diff --git a/Tests/WysiwygComposerTests/Components/WysiwygComposerView/WysiwygComposerViewModelTests+Suggestions.swift b/Tests/WysiwygComposerTests/Components/WysiwygComposerView/WysiwygComposerViewModelTests+Suggestions.swift index b323ec3..60266cf 100644 --- a/Tests/WysiwygComposerTests/Components/WysiwygComposerView/WysiwygComposerViewModelTests+Suggestions.swift +++ b/Tests/WysiwygComposerTests/Components/WysiwygComposerView/WysiwygComposerViewModelTests+Suggestions.swift @@ -54,7 +54,18 @@ extension WysiwygComposerViewModelTests { XCTAssertEqual( viewModel.content.html, """ - Alice\u{00A0} + Alice\u{00A0} + """ + ) + } + + func testAtRoomSuggestionCanBeUsed() { + _ = viewModel.replaceText(range: .zero, replacementText: "@ro") + viewModel.setAtRoomMention() + XCTAssertEqual( + viewModel.content.html, + """ + @room\u{00A0} """ ) } @@ -68,10 +79,25 @@ extension WysiwygComposerViewModelTests { XCTAssertEqual( viewModel.content.html, """ - TextAlice\u{00A0} + TextAlice\u{00A0} """ ) } + + func testAtRoomMentionWithNoSuggestion() { + _ = viewModel.replaceText(range: .zero, replacementText: "Text") + viewModel.select(range: .init(location: 0, length: 4)) + viewModel.setAtRoomMention() + // Text is not removed, and the + // mention is added after the text + XCTAssertEqual( + viewModel.content.html, + """ + Text@room\u{00A0} + """ + ) + } + func testAtMentionWithNoSuggestionAtLeading() { _ = viewModel.replaceText(range: .zero, replacementText: "Text") @@ -81,7 +107,20 @@ extension WysiwygComposerViewModelTests { XCTAssertEqual( viewModel.content.html, """ - AliceText + AliceText + """ + ) + } + + func testAtRoomMentionWithNoSuggestionAtLeading() { + _ = viewModel.replaceText(range: .zero, replacementText: "Text") + viewModel.select(range: .init(location: 0, length: 0)) + viewModel.setAtRoomMention() + // Text is not removed, and the mention is added before the text + XCTAssertEqual( + viewModel.content.html, + """ + @roomText """ ) } @@ -92,7 +131,7 @@ extension WysiwygComposerViewModelTests { XCTAssertEqual( viewModel.content.html, """ - Room 1\u{00A0} + #room1:matrix.org\u{00A0} """ ) } @@ -104,7 +143,7 @@ extension WysiwygComposerViewModelTests { XCTAssertEqual( viewModel.content.html, """ - TextRoom 1\u{00A0} + Text#room1:matrix.org\u{00A0} """ ) } @@ -116,7 +155,7 @@ extension WysiwygComposerViewModelTests { XCTAssertEqual( viewModel.content.html, """ - Room 1Text + #room1:matrix.orgText """ ) } diff --git a/Tests/WysiwygComposerTests/WysiwygComposerTests+Suggestions.swift b/Tests/WysiwygComposerTests/WysiwygComposerTests+Suggestions.swift index ab85e43..818df42 100644 --- a/Tests/WysiwygComposerTests/WysiwygComposerTests+Suggestions.swift +++ b/Tests/WysiwygComposerTests/WysiwygComposerTests+Suggestions.swift @@ -75,6 +75,40 @@ extension WysiwygComposerTests { ) .assertSelection(start: 8, end: 8) } + + func testSuggestionForAtRoomPattern() { + let model = ComposerModelWrapper() + let update = model.replaceText(newText: "@roo") + + guard case .suggestion(suggestionPattern: let suggestionPattern) = update.menuAction() else { + XCTFail("No user suggestion found") + return + } + + model + .action { + $0.insertAtRoomMentionAtSuggestion(suggestionPattern) + } + .assertHtml("@room ") + .assertSelection(start: 2, end: 2) + } + + func testForNonLeadingSuggestionForAtRoomPattern() { + let model = ComposerModelWrapper() + let update = model.replaceText(newText: "Hello @roo") + + guard case .suggestion(suggestionPattern: let suggestionPattern) = update.menuAction() else { + XCTFail("No user suggestion found") + return + } + + model + .action { + $0.insertAtRoomMentionAtSuggestion(suggestionPattern) + } + .assertHtml("Hello @room ") + .assertSelection(start: 8, end: 8) + } func testSuggestionForHashPattern() { let model = ComposerModelWrapper()