From b5e902e1f2f8f3a00fea93adeb7985db3ba13b23 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 21 Jan 2020 15:55:21 +0000
Subject: [PATCH 1/9] Fix escaping commands using double-slash //, e.g //plain
sends `/plain`
---
src/components/views/rooms/SendMessageComposer.js | 9 +++++++--
src/editor/serialize.js | 12 ++++++++++--
2 files changed, 17 insertions(+), 4 deletions(-)
diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js
index c11d940331..c4ae2929af 100644
--- a/src/components/views/rooms/SendMessageComposer.js
+++ b/src/components/views/rooms/SendMessageComposer.js
@@ -24,6 +24,8 @@ import {
containsEmote,
stripEmoteCommand,
unescapeMessage,
+ startsWith,
+ stripPrefix,
} from '../../../editor/serialize';
import {CommandPartCreator} from '../../../editor/parts';
import BasicMessageComposer from "./BasicMessageComposer";
@@ -61,6 +63,9 @@ function createMessageContent(model, permalinkCreator) {
if (isEmote) {
model = stripEmoteCommand(model);
}
+ if (startsWith(model, "//")) {
+ model = stripPrefix(model, "/");
+ }
model = unescapeMessage(model);
const repliedToEvent = RoomViewStore.getQuotingEvent();
@@ -175,13 +180,13 @@ export default class SendMessageComposer extends React.Component {
const parts = this.model.parts;
const firstPart = parts[0];
if (firstPart) {
- if (firstPart.type === "command") {
+ if (firstPart.type === "command" && !firstPart.text.startsWith("//")) {
return true;
}
// be extra resilient when somehow the AutocompleteWrapperModel or
// CommandPartCreator fails to insert a command part, so we don't send
// a command as a message
- if (firstPart.text.startsWith("/") && (firstPart.type === "plain" || firstPart.type === "pill-candidate")) {
+ if (firstPart.text.startsWith("/") && !firstPart.text.startsWith("//") && (firstPart.type === "plain" || firstPart.type === "pill-candidate")) {
return true;
}
}
diff --git a/src/editor/serialize.js b/src/editor/serialize.js
index a55eed97da..ba380f2809 100644
--- a/src/editor/serialize.js
+++ b/src/editor/serialize.js
@@ -61,18 +61,26 @@ export function textSerialize(model) {
}
export function containsEmote(model) {
+ return startsWith(model, "/me ");
+}
+
+export function startsWith(model, prefix) {
const firstPart = model.parts[0];
// part type will be "plain" while editing,
// and "command" while composing a message.
return firstPart &&
(firstPart.type === "plain" || firstPart.type === "command") &&
- firstPart.text.startsWith("/me ");
+ firstPart.text.startsWith(prefix);
}
export function stripEmoteCommand(model) {
// trim "/me "
+ return stripPrefix(model, "/me ");
+}
+
+export function stripPrefix(model, prefix) {
model = model.clone();
- model.removeText({index: 0, offset: 0}, 4);
+ model.removeText({index: 0, offset: 0}, prefix.length);
return model;
}
From 060938379a7183f7959aa4af436eef775ce6c9d3 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 21 Jan 2020 15:58:51 +0000
Subject: [PATCH 2/9] Fix changes after typing / at pos=0 allowing to cancel
command
---
src/components/views/rooms/SendMessageComposer.js | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js
index c4ae2929af..8de105d84d 100644
--- a/src/components/views/rooms/SendMessageComposer.js
+++ b/src/components/views/rooms/SendMessageComposer.js
@@ -180,13 +180,14 @@ export default class SendMessageComposer extends React.Component {
const parts = this.model.parts;
const firstPart = parts[0];
if (firstPart) {
- if (firstPart.type === "command" && !firstPart.text.startsWith("//")) {
+ if (firstPart.type === "command" && firstPart.text.startsWith("/") && !firstPart.text.startsWith("//")) {
return true;
}
// be extra resilient when somehow the AutocompleteWrapperModel or
// CommandPartCreator fails to insert a command part, so we don't send
// a command as a message
- if (firstPart.text.startsWith("/") && !firstPart.text.startsWith("//") && (firstPart.type === "plain" || firstPart.type === "pill-candidate")) {
+ if (firstPart.text.startsWith("/") && firstPart.text.startsWith("//") && !firstPart.text.startsWith("//")
+ && (firstPart.type === "plain" || firstPart.type === "pill-candidate")) {
return true;
}
}
From b34fe45518fbfff23f92a860a7af653fc383180b Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 21 Jan 2020 16:50:04 +0000
Subject: [PATCH 3/9] First attempt. Has a lag issue due to the async-clear :(
---
src/SlashCommands.js | 13 +++----
.../views/rooms/SendMessageComposer.js | 39 +++++++++++++++++--
src/i18n/strings/en_EN.json | 4 +-
3 files changed, 44 insertions(+), 12 deletions(-)
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",
From 9f7df33bc30acaceaa7d1f9d100fbf2de153d8d3 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 21 Jan 2020 16:57:07 +0000
Subject: [PATCH 4/9] re-arrange to split the async task into two and only wait
on the user-blocking one
---
src/SlashCommands.js | 10 +-
.../views/rooms/SendMessageComposer.js | 100 +++++++++---------
2 files changed, 52 insertions(+), 58 deletions(-)
diff --git a/src/SlashCommands.js b/src/SlashCommands.js
index 414dd60121..2eb34576ac 100644
--- a/src/SlashCommands.js
+++ b/src/SlashCommands.js
@@ -907,14 +907,14 @@ const aliases = {
/**
- * Process the given text for /commands and perform them.
+ * Process the given text for /commands and return a bound method to perform them.
* @param {string} roomId The room in which the command was performed.
* @param {string} input The raw text input by the user.
- * @return {Object|null} An object with the property 'error' if there was an error
+ * @return {null|function(): Object} Function returning an object with the property 'error' if there was an error
* processing the command, or 'promise' if a request was sent out.
* Returns null if the input didn't match a command.
*/
-export function processCommandInput(roomId, input) {
+export function getCommand(roomId, input) {
// trim any trailing whitespace, as it can confuse the parser for
// IRC-style commands
input = input.replace(/\s+$/, '');
@@ -934,8 +934,6 @@ export function processCommandInput(roomId, input) {
cmd = aliases[cmd];
}
if (CommandMap[cmd]) {
- return CommandMap[cmd].run(roomId, args);
+ return () => CommandMap[cmd].run(roomId, args);
}
- 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 9f3a407402..994c28f531 100644
--- a/src/components/views/rooms/SendMessageComposer.js
+++ b/src/components/views/rooms/SendMessageComposer.js
@@ -35,7 +35,7 @@ import ReplyThread from "../elements/ReplyThread";
import {parseEvent} from '../../../editor/deserialize';
import {findEditableEvent} from '../../../utils/EventUtils';
import SendHistoryManager from "../../../SendHistoryManager";
-import {processCommandInput} from '../../../SlashCommands';
+import {getCommand} from '../../../SlashCommands';
import * as sdk from '../../../index';
import Modal from '../../../Modal';
import {_t, _td} from '../../../languageHandler';
@@ -197,12 +197,7 @@ export default class SendMessageComposer extends React.Component {
return false;
}
- /**
- * 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() {
+ _getSlashCommand() {
const commandText = this.model.parts.reduce((text, part) => {
// use mxid to textify user pills in a command
if (part.type === "user-pill") {
@@ -210,51 +205,41 @@ export default class SendMessageComposer extends React.Component {
}
return text + part.text;
}, "");
- const cmd = processCommandInput(this.props.room.roomId, commandText);
+ return [getCommand(this.props.room.roomId, commandText), commandText];
+ }
- if (cmd) {
- let error = cmd.error;
- if (cmd.promise) {
- try {
- await cmd.promise;
- } catch (err) {
- error = err;
- }
+ async _runSlashCommand(fn) {
+ const cmd = fn();
+ let error = cmd.error;
+ if (cmd.promise) {
+ try {
+ await cmd.promise;
+ } catch (err) {
+ error = err;
}
- if (error) {
- console.error("Command failure: %s", error);
- const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
- // assume the error is a server error when the command is async
- const isServerError = !!cmd.promise;
- const title = isServerError ? _td("Server error") : _td("Command error");
+ }
+ if (error) {
+ console.error("Command failure: %s", error);
+ const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+ // assume the error is a server error when the command is async
+ const isServerError = !!cmd.promise;
+ const title = isServerError ? _td("Server error") : _td("Command error");
- let errText;
- if (typeof error === 'string') {
- errText = error;
- } else if (error.message) {
- errText = error.message;
- } else {
- errText = _t("Server unavailable, overloaded, or something else went wrong.");
- }
-
- Modal.createTrackedDialog(title, '', ErrorDialog, {
- title: _t(title),
- description: errText,
- });
+ let errText;
+ if (typeof error === 'string') {
+ errText = error;
+ } else if (error.message) {
+ errText = error.message;
} else {
- console.log("Command success.");
+ errText = _t("Server unavailable, overloaded, or something else went wrong.");
}
- } 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,
+
+ Modal.createTrackedDialog(title, '', ErrorDialog, {
+ title: _t(title),
+ description: errText,
});
- const [sendAnyway] = await finished;
- return sendAnyway ? SEND_ANYWAY : UNKNOWN_CMD;
+ } else {
+ console.log("Command success.");
}
}
@@ -266,13 +251,24 @@ export default class SendMessageComposer extends React.Component {
let shouldSend = true;
if (!containsEmote(this.model) && this._isSlashCommand()) {
- const resp = await this._tryRunSlashCommand();
- if (resp === UNKNOWN_CMD) {
- // unknown command, bail to let the user modify it
- return;
+ const [cmd, commandText] = this._getSlashCommand();
+ if (cmd) {
+ shouldSend = false;
+ this._runSlashCommand(cmd);
+ } else {
+ // ask the user if their unknown command should be sent as a message instead
+ 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;
+ // if !sendAnyway bail to let the user edit the composer and try again
+ if (!sendAnyway) return;
}
-
- shouldSend = resp === SEND_ANYWAY;
}
if (shouldSend) {
From a8df058ea6f7e55e2b8f1bbddc171a46777febe8 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 21 Jan 2020 17:54:27 +0000
Subject: [PATCH 5/9] tidy up, improve wording on modal
---
.../views/rooms/SendMessageComposer.js | 23 +++++++++++++------
src/i18n/strings/en_EN.json | 4 +++-
2 files changed, 19 insertions(+), 8 deletions(-)
diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js
index 994c28f531..7870699fec 100644
--- a/src/components/views/rooms/SendMessageComposer.js
+++ b/src/components/views/rooms/SendMessageComposer.js
@@ -43,9 +43,6 @@ 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);
@@ -256,14 +253,26 @@ export default class SendMessageComposer extends React.Component {
shouldSend = false;
this._runSlashCommand(cmd);
} else {
- // ask the user if their unknown command should be sent as a message instead
+ // ask the user if their unknown command should be sent as a message
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,
+ description:
+
+ { _t("Unrecognised command: %(commandText)s", {commandText}) }
+
+
+ { _t("You can use /help
to list available commands. Did you mean to send this as a message?", {}, {
+ code: t => { t }
,
+ }) }
+
+
+ { _t("Protip: Begin your message with //
to start it with a slash.", {}, {
+ code: t => { t }
,
+ }) }
+
+
,
button: _t('Send as message'),
- danger: true,
});
const [sendAnyway] = await finished;
// if !sendAnyway bail to let the user edit the composer and try again
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 314731a910..da4111aec8 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -1077,7 +1077,9 @@
"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: ",
+ "Unrecognised command: %(commandText)s": "Unrecognised command: %(commandText)s",
+ "You can use /help
to list available commands. Did you mean to send this as a message?": "You can use /help
to list available commands. Did you mean to send this as a message?",
+ "Protip: Begin your message with //
to start it with a slash.": "Protip: Begin your message with //
to start it with a slash.",
"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",
From e455aa474d652c1f66228ea4e2e2f8b69f2a796b Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 21 Jan 2020 17:58:53 +0000
Subject: [PATCH 6/9] improve copy further
---
src/components/views/rooms/SendMessageComposer.js | 2 +-
src/i18n/strings/en_EN.json | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js
index 7870699fec..4402a034f6 100644
--- a/src/components/views/rooms/SendMessageComposer.js
+++ b/src/components/views/rooms/SendMessageComposer.js
@@ -267,7 +267,7 @@ export default class SendMessageComposer extends React.Component {
}) }
- { _t("Protip: Begin your message with //
to start it with a slash.", {}, {
+ { _t("Hint: Begin your message with //
to start it with a slash.", {}, {
code: t => { t }
,
}) }
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index da4111aec8..a3c56e5973 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -1079,7 +1079,7 @@
"Unknown Command": "Unknown Command",
"Unrecognised command: %(commandText)s": "Unrecognised command: %(commandText)s",
"You can use /help
to list available commands. Did you mean to send this as a message?": "You can use /help
to list available commands. Did you mean to send this as a message?",
- "Protip: Begin your message with //
to start it with a slash.": "Protip: Begin your message with //
to start it with a slash.",
+ "Hint: Begin your message with //
to start it with a slash.": "Hint: Begin your message with //
to start it with a slash.",
"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",
From 7b26067397e5bc1870c725968778bde83c6b0b34 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 21 Jan 2020 18:03:01 +0000
Subject: [PATCH 7/9] delint
---
src/components/views/rooms/SendMessageComposer.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js
index 4402a034f6..c4970c4570 100644
--- a/src/components/views/rooms/SendMessageComposer.js
+++ b/src/components/views/rooms/SendMessageComposer.js
@@ -262,7 +262,8 @@ export default class SendMessageComposer extends React.Component {
{ _t("Unrecognised command: %(commandText)s", {commandText}) }
- { _t("You can use /help
to list available commands. Did you mean to send this as a message?", {}, {
+ { _t("You can use /help
to list available commands. " +
+ "Did you mean to send this as a message?", {}, {
code: t => { t }
,
}) }
From e1e53f567f93ab044f24bc195d40fa4046acd2fb Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 22 Jan 2020 11:56:27 +0000
Subject: [PATCH 8/9] add more tests
---
.../views/rooms/SendMessageComposer.js | 3 +-
.../views/rooms/SendMessageComposer-test.js | 83 +++++++++++++++++++
test/editor/mock.js | 10 +++
test/editor/model-test.js | 12 +--
4 files changed, 96 insertions(+), 12 deletions(-)
create mode 100644 test/components/views/rooms/SendMessageComposer-test.js
diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js
index c4970c4570..a857e40f55 100644
--- a/src/components/views/rooms/SendMessageComposer.js
+++ b/src/components/views/rooms/SendMessageComposer.js
@@ -58,7 +58,8 @@ function addReplyToMessageContent(content, repliedToEvent, permalinkCreator) {
}
}
-function createMessageContent(model, permalinkCreator) {
+// exported for tests
+export function createMessageContent(model, permalinkCreator) {
const isEmote = containsEmote(model);
if (isEmote) {
model = stripEmoteCommand(model);
diff --git a/test/components/views/rooms/SendMessageComposer-test.js b/test/components/views/rooms/SendMessageComposer-test.js
new file mode 100644
index 0000000000..d5a143a1fb
--- /dev/null
+++ b/test/components/views/rooms/SendMessageComposer-test.js
@@ -0,0 +1,83 @@
+/*
+Copyright 2020 The Matrix.org Foundation C.I.C.
+
+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 RoomViewStore from "../../../../src/stores/RoomViewStore";
+import {createMessageContent} from "../../../../src/components/views/rooms/SendMessageComposer";
+import EditorModel from "../../../../src/editor/model";
+import {createPartCreator, createRenderer} from "../../../editor/mock";
+
+jest.mock("../../../../src/stores/RoomViewStore");
+
+describe('', () => {
+ describe("createMessageContent", () => {
+ RoomViewStore.getQuotingEvent.mockReturnValue(false);
+ const permalinkCreator = jest.fn();
+
+ it("sends plaintext messages correctly", () => {
+ const model = new EditorModel([], createPartCreator(), createRenderer());
+ model.update("hello world", "insertText", {offset: 11, atNodeEnd: true});
+
+ const content = createMessageContent(model, permalinkCreator);
+
+ expect(content).toEqual({
+ body: "hello world",
+ msgtype: "m.text",
+ });
+ });
+
+ it("sends markdown messages correctly", () => {
+ const model = new EditorModel([], createPartCreator(), createRenderer());
+ model.update("hello *world*", "insertText", {offset: 13, atNodeEnd: true});
+
+ const content = createMessageContent(model, permalinkCreator);
+
+ expect(content).toEqual({
+ body: "hello *world*",
+ msgtype: "m.text",
+ format: "org.matrix.custom.html",
+ formatted_body: "hello world",
+ });
+ });
+
+ it("strips /me from messages and marks them as m.emote accordingly", () => {
+ const model = new EditorModel([], createPartCreator(), createRenderer());
+ model.update("/me blinks __quickly__", "insertText", {offset: 22, atNodeEnd: true});
+
+ const content = createMessageContent(model, permalinkCreator);
+
+ expect(content).toEqual({
+ body: "blinks __quickly__",
+ msgtype: "m.emote",
+ format: "org.matrix.custom.html",
+ formatted_body: "blinks quickly",
+ });
+ });
+
+ it("allows sending double-slash escaped slash commands correctly", () => {
+ const model = new EditorModel([], createPartCreator(), createRenderer());
+ model.update("//dev/null is my favourite place", "insertText", {offset: 32, atNodeEnd: true});
+
+ const content = createMessageContent(model, permalinkCreator);
+
+ expect(content).toEqual({
+ body: "/dev/null is my favourite place",
+ msgtype: "m.text",
+ });
+ });
+ });
+});
+
+
diff --git a/test/editor/mock.js b/test/editor/mock.js
index bb1a51d14b..6de65cf23d 100644
--- a/test/editor/mock.js
+++ b/test/editor/mock.js
@@ -67,3 +67,13 @@ export function createPartCreator(completions = []) {
};
return new PartCreator(new MockRoom(), new MockClient(), autoCompleteCreator);
}
+
+export function createRenderer() {
+ const render = (c) => {
+ render.caret = c;
+ render.count += 1;
+ };
+ render.count = 0;
+ render.caret = null;
+ return render;
+}
diff --git a/test/editor/model-test.js b/test/editor/model-test.js
index 826dde3d68..2a3584d508 100644
--- a/test/editor/model-test.js
+++ b/test/editor/model-test.js
@@ -15,17 +15,7 @@ limitations under the License.
*/
import EditorModel from "../../src/editor/model";
-import {createPartCreator} from "./mock";
-
-function createRenderer() {
- const render = (c) => {
- render.caret = c;
- render.count += 1;
- };
- render.count = 0;
- render.caret = null;
- return render;
-}
+import {createPartCreator, createRenderer} from "./mock";
describe('editor/model', function() {
describe('plain text manipulation', function() {
From 516dd25797f4ad5c8fb53a6471ef65cf212879a7 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 22 Jan 2020 14:24:10 +0000
Subject: [PATCH 9/9] fix typo in fallback codepath
---
src/components/views/rooms/SendMessageComposer.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js
index a857e40f55..6a60037036 100644
--- a/src/components/views/rooms/SendMessageComposer.js
+++ b/src/components/views/rooms/SendMessageComposer.js
@@ -187,7 +187,7 @@ export default class SendMessageComposer extends React.Component {
// be extra resilient when somehow the AutocompleteWrapperModel or
// CommandPartCreator fails to insert a command part, so we don't send
// a command as a message
- if (firstPart.text.startsWith("/") && firstPart.text.startsWith("//") && !firstPart.text.startsWith("//")
+ if (firstPart.text.startsWith("/") && !firstPart.text.startsWith("//")
&& (firstPart.type === "plain" || firstPart.type === "pill-candidate")) {
return true;
}