Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 7ac12d1

Browse files
authored
Merge pull request #5865 from jaiwanth-v/edit-slashcommands
Add slash commands support to edit message composer
2 parents 392cde7 + 12657f0 commit 7ac12d1

File tree

2 files changed

+126
-15
lines changed

2 files changed

+126
-15
lines changed

src/components/views/rooms/EditMessageComposer.js

Lines changed: 119 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,26 @@ limitations under the License.
1616
*/
1717
import React from 'react';
1818
import * as sdk from '../../../index';
19-
import {_t} from '../../../languageHandler';
19+
import {_t, _td} from '../../../languageHandler';
2020
import PropTypes from 'prop-types';
2121
import dis from '../../../dispatcher/dispatcher';
2222
import EditorModel from '../../../editor/model';
2323
import {getCaretOffsetAndText} from '../../../editor/dom';
2424
import {htmlSerializeIfNeeded, textSerialize, containsEmote, stripEmoteCommand} from '../../../editor/serialize';
2525
import {findEditableEvent} from '../../../utils/EventUtils';
2626
import {parseEvent} from '../../../editor/deserialize';
27-
import {PartCreator} from '../../../editor/parts';
27+
import {CommandPartCreator} from '../../../editor/parts';
2828
import EditorStateTransfer from '../../../utils/EditorStateTransfer';
2929
import classNames from 'classnames';
3030
import {EventStatus} from 'matrix-js-sdk/src/models/event';
3131
import BasicMessageComposer from "./BasicMessageComposer";
3232
import MatrixClientContext from "../../../contexts/MatrixClientContext";
33+
import {CommandCategories, getCommand} from '../../../SlashCommands';
3334
import {Action} from "../../../dispatcher/actions";
3435
import CountlyAnalytics from "../../../CountlyAnalytics";
3536
import {getKeyBindingsManager, MessageComposerAction} from '../../../KeyBindingsManager';
3637
import {replaceableComponent} from "../../../utils/replaceableComponent";
38+
import Modal from '../../../Modal';
3739

3840
function _isReply(mxEvent) {
3941
const relatesTo = mxEvent.getContent()["m.relates_to"];
@@ -178,6 +180,22 @@ export default class EditMessageComposer extends React.Component {
178180
dis.fire(Action.FocusComposer);
179181
}
180182

183+
_isSlashCommand() {
184+
const parts = this.model.parts;
185+
const firstPart = parts[0];
186+
if (firstPart) {
187+
if (firstPart.type === "command" && firstPart.text.startsWith("/") && !firstPart.text.startsWith("//")) {
188+
return true;
189+
}
190+
191+
if (firstPart.text.startsWith("/") && !firstPart.text.startsWith("//")
192+
&& (firstPart.type === "plain" || firstPart.type === "pill-candidate")) {
193+
return true;
194+
}
195+
}
196+
return false;
197+
}
198+
181199
_isContentModified(newContent) {
182200
// if nothing has changed then bail
183201
const oldContent = this.props.editState.getEvent().getContent();
@@ -190,19 +208,112 @@ export default class EditMessageComposer extends React.Component {
190208
return true;
191209
}
192210

193-
_sendEdit = () => {
211+
_getSlashCommand() {
212+
const commandText = this.model.parts.reduce((text, part) => {
213+
// use mxid to textify user pills in a command
214+
if (part.type === "user-pill") {
215+
return text + part.resourceId;
216+
}
217+
return text + part.text;
218+
}, "");
219+
const {cmd, args} = getCommand(commandText);
220+
return [cmd, args, commandText];
221+
}
222+
223+
async _runSlashCommand(cmd, args, roomId) {
224+
const result = cmd.run(roomId, args);
225+
let messageContent;
226+
let error = result.error;
227+
if (result.promise) {
228+
try {
229+
if (cmd.category === CommandCategories.messages) {
230+
messageContent = await result.promise;
231+
} else {
232+
await result.promise;
233+
}
234+
} catch (err) {
235+
error = err;
236+
}
237+
}
238+
if (error) {
239+
console.error("Command failure: %s", error);
240+
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
241+
// assume the error is a server error when the command is async
242+
const isServerError = !!result.promise;
243+
const title = isServerError ? _td("Server error") : _td("Command error");
244+
245+
let errText;
246+
if (typeof error === 'string') {
247+
errText = error;
248+
} else if (error.message) {
249+
errText = error.message;
250+
} else {
251+
errText = _t("Server unavailable, overloaded, or something else went wrong.");
252+
}
253+
254+
Modal.createTrackedDialog(title, '', ErrorDialog, {
255+
title: _t(title),
256+
description: errText,
257+
});
258+
} else {
259+
console.log("Command success.");
260+
if (messageContent) return messageContent;
261+
}
262+
}
263+
264+
_sendEdit = async () => {
194265
const startTime = CountlyAnalytics.getTimestamp();
195266
const editedEvent = this.props.editState.getEvent();
196267
const editContent = createEditContent(this.model, editedEvent);
197268
const newContent = editContent["m.new_content"];
269+
let shouldSend = true;
198270

199271
// If content is modified then send an updated event into the room
200272
if (this._isContentModified(newContent)) {
201273
const roomId = editedEvent.getRoomId();
202-
this._cancelPreviousPendingEdit();
203-
const prom = this.context.sendMessage(roomId, editContent);
204-
dis.dispatch({action: "message_sent"});
205-
CountlyAnalytics.instance.trackSendMessage(startTime, prom, roomId, true, false, editContent);
274+
if (!containsEmote(this.model) && this._isSlashCommand()) {
275+
const [cmd, args, commandText] = this._getSlashCommand();
276+
if (cmd) {
277+
if (cmd.category === CommandCategories.messages) {
278+
editContent["m.new_content"] = await this._runSlashCommand(cmd, args, roomId);
279+
} else {
280+
this._runSlashCommand(cmd, args, roomId);
281+
shouldSend = false;
282+
}
283+
} else {
284+
// ask the user if their unknown command should be sent as a message
285+
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
286+
const {finished} = Modal.createTrackedDialog("Unknown command", "", QuestionDialog, {
287+
title: _t("Unknown Command"),
288+
description: <div>
289+
<p>
290+
{ _t("Unrecognised command: %(commandText)s", {commandText}) }
291+
</p>
292+
<p>
293+
{ _t("You can use <code>/help</code> to list available commands. " +
294+
"Did you mean to send this as a message?", {}, {
295+
code: t => <code>{ t }</code>,
296+
}) }
297+
</p>
298+
<p>
299+
{ _t("Hint: Begin your message with <code>//</code> to start it with a slash.", {}, {
300+
code: t => <code>{ t }</code>,
301+
}) }
302+
</p>
303+
</div>,
304+
button: _t('Send as message'),
305+
});
306+
const [sendAnyway] = await finished;
307+
// if !sendAnyway bail to let the user edit the composer and try again
308+
if (!sendAnyway) return;
309+
}
310+
}
311+
if (shouldSend) {
312+
this._cancelPreviousPendingEdit();
313+
const prom = this.context.sendMessage(roomId, editContent);
314+
dis.dispatch({action: "message_sent"});
315+
CountlyAnalytics.instance.trackSendMessage(startTime, prom, roomId, true, false, editContent);
316+
}
206317
}
207318

208319
// close the event editing and focus composer
@@ -240,7 +351,7 @@ export default class EditMessageComposer extends React.Component {
240351
_createEditorModel() {
241352
const {editState} = this.props;
242353
const room = this._getRoom();
243-
const partCreator = new PartCreator(room, this.context);
354+
const partCreator = new CommandPartCreator(room, this.context);
244355
let parts;
245356
if (editState.hasEditorState()) {
246357
// if restoring state from a previous editor,

src/i18n/strings/en_EN.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1441,6 +1441,13 @@
14411441
"Someone is using an unknown session": "Someone is using an unknown session",
14421442
"This room is end-to-end encrypted": "This room is end-to-end encrypted",
14431443
"Everyone in this room is verified": "Everyone in this room is verified",
1444+
"Server error": "Server error",
1445+
"Server unavailable, overloaded, or something else went wrong.": "Server unavailable, overloaded, or something else went wrong.",
1446+
"Unknown Command": "Unknown Command",
1447+
"Unrecognised command: %(commandText)s": "Unrecognised command: %(commandText)s",
1448+
"You can use <code>/help</code> to list available commands. Did you mean to send this as a message?": "You can use <code>/help</code> to list available commands. Did you mean to send this as a message?",
1449+
"Hint: Begin your message with <code>//</code> to start it with a slash.": "Hint: Begin your message with <code>//</code> to start it with a slash.",
1450+
"Send as message": "Send as message",
14441451
"Edit message": "Edit message",
14451452
"Mod": "Mod",
14461453
"This event could not be displayed": "This event could not be displayed",
@@ -1631,13 +1638,6 @@
16311638
"This Room": "This Room",
16321639
"All Rooms": "All Rooms",
16331640
"Search…": "Search…",
1634-
"Server error": "Server error",
1635-
"Server unavailable, overloaded, or something else went wrong.": "Server unavailable, overloaded, or something else went wrong.",
1636-
"Unknown Command": "Unknown Command",
1637-
"Unrecognised command: %(commandText)s": "Unrecognised command: %(commandText)s",
1638-
"You can use <code>/help</code> to list available commands. Did you mean to send this as a message?": "You can use <code>/help</code> to list available commands. Did you mean to send this as a message?",
1639-
"Hint: Begin your message with <code>//</code> to start it with a slash.": "Hint: Begin your message with <code>//</code> to start it with a slash.",
1640-
"Send as message": "Send as message",
16411641
"Failed to connect to integration manager": "Failed to connect to integration manager",
16421642
"You don't currently have any stickerpacks enabled": "You don't currently have any stickerpacks enabled",
16431643
"Add some now": "Add some now",

0 commit comments

Comments
 (0)