Add support for slash commands (under Feature Flag) (#6482)
* Add support for slash commands * Update screenshots * Rename module `slash` to `slashcommands` * Rename `SlashCommand` to `SlashCommandService` * Introduce MsgType in order to send text message with a different msgtype value. * Format file and add parameter names, add default values and cleanup * Add isSupported parameter to filter out unsupported yet commands. * Slash commands: disable suggestions if the feature is disabled. * Fix sending shrug command. * Add missing test on SuggestionsProcessor * Add tests on MessageComposerPresenter about slash command. * Fix import ordering * Add missing tests on CommandExecutor * Add missing tests in MarkdownTextEditorStateTest * Slash commands: Improve code when sending message with prefix. * Slash commands: Add support for /unflip --------- Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
parent
3634b5593c
commit
4ad495d36c
65 changed files with 3038 additions and 86 deletions
|
|
@ -14,6 +14,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
|||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.slashcommands.api.SlashCommandSuggestion
|
||||
|
||||
@Immutable
|
||||
sealed interface ResolvedSuggestion {
|
||||
|
|
@ -32,4 +33,8 @@ sealed interface ResolvedSuggestion {
|
|||
size = size,
|
||||
)
|
||||
}
|
||||
|
||||
data class Command(
|
||||
val command: SlashCommandSuggestion,
|
||||
) : ResolvedSuggestion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,21 +61,29 @@ class MarkdownTextEditorState(
|
|||
}
|
||||
is ResolvedSuggestion.Member -> {
|
||||
val currentText = SpannableStringBuilder(text.value())
|
||||
val mentionSpan = mentionSpanProvider.createUserMentionSpan(resolvedSuggestion.roomMember.userId)
|
||||
val userId = resolvedSuggestion.roomMember.userId
|
||||
val mentionSpan = mentionSpanProvider.createUserMentionSpan(userId)
|
||||
currentText.replace(suggestion.start, suggestion.end, "@ ")
|
||||
val end = suggestion.start + 1
|
||||
currentText.setSpan(mentionSpan, suggestion.start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
this.text.update(currentText, true)
|
||||
this.selection = IntRange(end + 1, end + 1)
|
||||
text.update(currentText, true)
|
||||
selection = IntRange(end + 1, end + 1)
|
||||
}
|
||||
is ResolvedSuggestion.Alias -> {
|
||||
val currentText = SpannableStringBuilder(text.value())
|
||||
val mentionSpan = mentionSpanProvider.createRoomMentionSpan(resolvedSuggestion.roomAlias.toRoomIdOrAlias())
|
||||
val roomAlias = resolvedSuggestion.roomAlias
|
||||
val mentionSpan = mentionSpanProvider.createRoomMentionSpan(roomAlias.toRoomIdOrAlias())
|
||||
currentText.replace(suggestion.start, suggestion.end, "# ")
|
||||
val end = suggestion.start + 1
|
||||
currentText.setSpan(mentionSpan, suggestion.start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
this.text.update(currentText, true)
|
||||
this.selection = IntRange(end + 1, end + 1)
|
||||
text.update(currentText, true)
|
||||
selection = IntRange(end + 1, end + 1)
|
||||
}
|
||||
is ResolvedSuggestion.Command -> {
|
||||
// Just insert the command text
|
||||
text.update("${resolvedSuggestion.command.command} ", true)
|
||||
val length = resolvedSuggestion.command.command.length + 1
|
||||
selection = IntRange(length, length)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID
|
|||
import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder
|
||||
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
|
||||
import io.element.android.libraries.matrix.test.room.aRoomMember
|
||||
import io.element.android.libraries.slashcommands.api.SlashCommandSuggestion
|
||||
import io.element.android.libraries.textcomposer.impl.mentions.aMentionSpanProvider
|
||||
import io.element.android.libraries.textcomposer.mentions.MentionSpan
|
||||
import io.element.android.libraries.textcomposer.mentions.MentionType
|
||||
|
|
@ -42,6 +43,7 @@ class MarkdownTextEditorStateTest {
|
|||
val mentionSpanProvider = aMentionSpanProvider()
|
||||
state.insertSuggestion(suggestion, mentionSpanProvider)
|
||||
assertThat(state.getMentions()).isEmpty()
|
||||
assertThat(state.text.value().toString()).isEqualTo("Hello @")
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -53,6 +55,7 @@ class MarkdownTextEditorStateTest {
|
|||
val permalinkParser = FakePermalinkParser(result = { PermalinkData.RoomLink(A_ROOM_ALIAS.toRoomIdOrAlias()) })
|
||||
val mentionSpanProvider = aMentionSpanProvider(permalinkParser = permalinkParser)
|
||||
state.insertSuggestion(suggestion, mentionSpanProvider)
|
||||
assertThat(state.text.value().toString()).isEqualTo("Hello # ")
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -64,6 +67,19 @@ class MarkdownTextEditorStateTest {
|
|||
val permalinkParser = FakePermalinkParser(result = { PermalinkData.RoomLink(A_ROOM_ALIAS.toRoomIdOrAlias()) })
|
||||
val mentionSpanProvider = aMentionSpanProvider(permalinkParser = permalinkParser)
|
||||
state.insertSuggestion(suggestion, mentionSpanProvider)
|
||||
assertThat(state.text.value().toString()).isEqualTo("Hello # ")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `insertSuggestion - command`() {
|
||||
val state = aMarkdownTextEditorState(initialText = "/rai", initialFocus = true).apply {
|
||||
currentSuggestion = Suggestion(start = 0, end = 3, type = SuggestionType.Command, text = "/rainbow")
|
||||
}
|
||||
val suggestion = aSlashCommandSuggestion()
|
||||
val permalinkParser = FakePermalinkParser(result = { PermalinkData.RoomLink(A_ROOM_ALIAS.toRoomIdOrAlias()) })
|
||||
val mentionSpanProvider = aMentionSpanProvider(permalinkParser = permalinkParser)
|
||||
state.insertSuggestion(suggestion, mentionSpanProvider)
|
||||
assertThat(state.text.value().toString()).isEqualTo("/rainbow ")
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -74,6 +90,7 @@ class MarkdownTextEditorStateTest {
|
|||
val mentionSpanProvider = aMentionSpanProvider()
|
||||
state.insertSuggestion(mention, mentionSpanProvider)
|
||||
assertThat(state.getMentions()).isEmpty()
|
||||
assertThat(state.text.value().toString()).isEqualTo("Hello @")
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -91,6 +108,7 @@ class MarkdownTextEditorStateTest {
|
|||
val mentions = state.getMentions()
|
||||
assertThat(mentions).isNotEmpty()
|
||||
assertThat((mentions.firstOrNull() as? IntentionalMention.User)?.userId).isEqualTo(member.userId)
|
||||
assertThat(state.text.value().toString()).isEqualTo("Hello @ ")
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -107,15 +125,14 @@ class MarkdownTextEditorStateTest {
|
|||
val mentions = state.getMentions()
|
||||
assertThat(mentions).isNotEmpty()
|
||||
assertThat(mentions.firstOrNull()).isInstanceOf(IntentionalMention.Room::class.java)
|
||||
assertThat(state.text.value().toString()).isEqualTo("Hello @ ")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getMessageMarkdown - when there are no MentionSpans returns the same text`() {
|
||||
val text = "No mentions here"
|
||||
val state = aMarkdownTextEditorState(initialText = text, initialFocus = true)
|
||||
|
||||
val markdown = state.getMessageMarkdown(FakePermalinkBuilder())
|
||||
|
||||
assertThat(markdown).isEqualTo(text)
|
||||
}
|
||||
|
||||
|
|
@ -128,19 +145,17 @@ class MarkdownTextEditorStateTest {
|
|||
)
|
||||
val state = aMarkdownTextEditorState(initialText = text, initialFocus = true)
|
||||
state.text.update(aMarkdownTextWithMentions(), needsDisplaying = false)
|
||||
|
||||
val markdown = state.getMessageMarkdown(permalinkBuilder = permalinkBuilder)
|
||||
|
||||
assertThat(markdown).isEqualTo(
|
||||
"Hello [@alice:matrix.org](https://matrix.to/#/@alice:matrix.org) and everyone in @room" +
|
||||
" and a room [#room:domain.org](https://matrix.to/#/#room:domain.org)"
|
||||
)
|
||||
assertThat(state.text.value().toString()).isEqualTo("Hello @ and everyone in @ and a room #room:domain.org")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getMentions - when there are no MentionSpans returns empty list of mentions`() {
|
||||
val state = aMarkdownTextEditorState(initialText = "Hello @", initialFocus = true)
|
||||
|
||||
assertThat(state.getMentions()).isEmpty()
|
||||
}
|
||||
|
||||
|
|
@ -148,9 +163,7 @@ class MarkdownTextEditorStateTest {
|
|||
fun `getMentions - when there are MentionSpans returns a list of mentions`() {
|
||||
val state = aMarkdownTextEditorState(initialText = "Hello @", initialFocus = true)
|
||||
state.text.update(aMarkdownTextWithMentions(), needsDisplaying = false)
|
||||
|
||||
val mentions = state.getMentions()
|
||||
|
||||
assertThat(mentions).isNotEmpty()
|
||||
assertThat((mentions.firstOrNull() as? IntentionalMention.User)?.userId?.value).isEqualTo("@alice:matrix.org")
|
||||
assertThat(mentions.lastOrNull()).isInstanceOf(IntentionalMention.Room::class.java)
|
||||
|
|
@ -184,4 +197,14 @@ class MarkdownTextEditorStateTest {
|
|||
roomAvatarUrl = null
|
||||
)
|
||||
}
|
||||
|
||||
private fun aSlashCommandSuggestion(): ResolvedSuggestion.Command {
|
||||
return ResolvedSuggestion.Command(
|
||||
command = SlashCommandSuggestion(
|
||||
command = "/rainbow",
|
||||
parameters = "param",
|
||||
description = "Make the text colorful 🌈",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue