diff --git a/src/SlashCommands.js b/src/SlashCommands.js index 20b8ba76da..414dd60121 100644 --- a/src/SlashCommands.js +++ b/src/SlashCommands.js @@ -81,6 +81,8 @@ class Command { } run(roomId, args) { + // if it has no runFn then its an ignored/nop command (autocomplete only) e.g `/me` + if (!this.runFn) return; return this.runFn.bind(this)(roomId, args); } @@ -918,12 +920,12 @@ export function processCommandInput(roomId, input) { input = input.replace(/\s+$/, ''); if (input[0] !== '/') return null; // not a command - const bits = input.match(/^(\S+?)( +((.|\n)*))?$/); + const bits = input.match(/^(\S+?)(?: +((.|\n)*))?$/); let cmd; let args; if (bits) { cmd = bits[1].substring(1).toLowerCase(); - args = bits[3]; + args = bits[2]; } else { cmd = input; } @@ -932,11 +934,8 @@ export function processCommandInput(roomId, input) { cmd = aliases[cmd]; } if (CommandMap[cmd]) { - // if it has no runFn then its an ignored/nop command (autocomplete only) e.g `/me` - if (!CommandMap[cmd].runFn) return null; - return CommandMap[cmd].run(roomId, args); - } else { - return reject(_t('Unrecognised command:') + ' ' + input); } + return null; + // return reject(_t('Unrecognised command:') + ' ' + input); } diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js index 8de105d84d..9f3a407402 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -43,6 +43,9 @@ import ContentMessages from '../../../ContentMessages'; import {Key} from "../../../Keyboard"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; +const SEND_ANYWAY = Symbol("send-anyway"); +const UNKNOWN_CMD = Symbol("unknown-cmd"); + function addReplyToMessageContent(content, repliedToEvent, permalinkCreator) { const replyContent = ReplyThread.makeReplyMixIn(repliedToEvent); Object.assign(content, replyContent); @@ -194,7 +197,12 @@ export default class SendMessageComposer extends React.Component { return false; } - async _runSlashCommand() { + /** + * Parses and executes current input as a Slash Command + * @returns {Promise} UNKNOWN_CMD if the command is not known, + * SEND_ANYWAY if the input should be sent as message instead + */ + async _tryRunSlashCommand() { const commandText = this.model.parts.reduce((text, part) => { // use mxid to textify user pills in a command if (part.type === "user-pill") { @@ -236,16 +244,38 @@ export default class SendMessageComposer extends React.Component { } else { console.log("Command success."); } + } else { + const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); + // unknown command, ask the user if they meant to send it as a message + const {finished} = Modal.createTrackedDialog("Unknown command", "", QuestionDialog, { + title: _t("Unknown Command"), + description: _t("Unrecognised command: ") + commandText, + button: _t('Send as message'), + danger: true, + }); + const [sendAnyway] = await finished; + return sendAnyway ? SEND_ANYWAY : UNKNOWN_CMD; } } - _sendMessage() { + async _sendMessage() { if (this.model.isEmpty) { return; } + + let shouldSend = true; + if (!containsEmote(this.model) && this._isSlashCommand()) { - this._runSlashCommand(); - } else { + const resp = await this._tryRunSlashCommand(); + if (resp === UNKNOWN_CMD) { + // unknown command, bail to let the user modify it + return; + } + + shouldSend = resp === SEND_ANYWAY; + } + + if (shouldSend) { const isReply = !!RoomViewStore.getQuotingEvent(); const {roomId} = this.props.room; const content = createMessageContent(this.model, this.props.permalinkCreator); @@ -259,6 +289,7 @@ export default class SendMessageComposer extends React.Component { }); } } + this.sendHistoryManager.save(this.model); // clear composer this.model.reset([]); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index f0eab6b12d..314731a910 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -200,7 +200,6 @@ "Sends the given message coloured as a rainbow": "Sends the given message coloured as a rainbow", "Sends the given emote coloured as a rainbow": "Sends the given emote coloured as a rainbow", "Displays list of commands with usages and descriptions": "Displays list of commands with usages and descriptions", - "Unrecognised command:": "Unrecognised command:", "Reason": "Reason", "%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s accepted the invitation for %(displayName)s.", "%(targetName)s accepted an invitation.": "%(targetName)s accepted an invitation.", @@ -1077,6 +1076,9 @@ "Server error": "Server error", "Command error": "Command error", "Server unavailable, overloaded, or something else went wrong.": "Server unavailable, overloaded, or something else went wrong.", + "Unknown Command": "Unknown Command", + "Unrecognised command: ": "Unrecognised command: ", + "Send as message": "Send as message", "Failed to connect to integration manager": "Failed to connect to integration manager", "You don't currently have any stickerpacks enabled": "You don't currently have any stickerpacks enabled", "Add some now": "Add some now",