Handle /me in rte (#10558)
* add /me handling * use typeguards to avoid fighting TS * improve clarity and use of typeguards * add createMessageContent tests * remove completed TODO * improve comments * remove duplication and renaming of argument
This commit is contained in:
parent
7b5d1802b1
commit
4d5744008e
3 changed files with 45 additions and 18 deletions
|
@ -21,6 +21,8 @@ import SettingsStore from "../../../../../settings/SettingsStore";
|
||||||
import { RoomPermalinkCreator } from "../../../../../utils/permalinks/Permalinks";
|
import { RoomPermalinkCreator } from "../../../../../utils/permalinks/Permalinks";
|
||||||
import { addReplyToMessageContent } from "../../../../../utils/Reply";
|
import { addReplyToMessageContent } from "../../../../../utils/Reply";
|
||||||
|
|
||||||
|
export const EMOTE_PREFIX = "/me ";
|
||||||
|
|
||||||
// Merges favouring the given relation
|
// Merges favouring the given relation
|
||||||
function attachRelation(content: IContent, relation?: IEventRelation): void {
|
function attachRelation(content: IContent, relation?: IEventRelation): void {
|
||||||
if (relation) {
|
if (relation) {
|
||||||
|
@ -61,6 +63,8 @@ interface CreateMessageContentParams {
|
||||||
editedEvent?: MatrixEvent;
|
editedEvent?: MatrixEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isMatrixEvent = (e: MatrixEvent | undefined): e is MatrixEvent => e instanceof MatrixEvent;
|
||||||
|
|
||||||
export async function createMessageContent(
|
export async function createMessageContent(
|
||||||
message: string,
|
message: string,
|
||||||
isHTML: boolean,
|
isHTML: boolean,
|
||||||
|
@ -72,22 +76,22 @@ export async function createMessageContent(
|
||||||
editedEvent,
|
editedEvent,
|
||||||
}: CreateMessageContentParams,
|
}: CreateMessageContentParams,
|
||||||
): Promise<IContent> {
|
): Promise<IContent> {
|
||||||
// TODO emote ?
|
const isEditing = isMatrixEvent(editedEvent);
|
||||||
|
const isReply = isEditing ? Boolean(editedEvent.replyEventId) : isMatrixEvent(replyToEvent);
|
||||||
const isEditing = Boolean(editedEvent);
|
|
||||||
const isReply = isEditing ? Boolean(editedEvent?.replyEventId) : Boolean(replyToEvent);
|
|
||||||
const isReplyAndEditing = isEditing && isReply;
|
const isReplyAndEditing = isEditing && isReply;
|
||||||
|
|
||||||
/*const isEmote = containsEmote(model);
|
const isEmote = message.startsWith(EMOTE_PREFIX);
|
||||||
if (isEmote) {
|
if (isEmote) {
|
||||||
model = stripEmoteCommand(model);
|
// if we are dealing with an emote we want to remove the prefix so that `/me` does not
|
||||||
|
// appear after the `* <userName>` text in the timeline
|
||||||
|
message = message.slice(EMOTE_PREFIX.length);
|
||||||
}
|
}
|
||||||
if (startsWith(model, "//")) {
|
if (message.startsWith("//")) {
|
||||||
model = stripPrefix(model, "/");
|
// if user wants to enter a single slash at the start of a message, this
|
||||||
|
// is how they have to do it (due to it clashing with commands), so here we
|
||||||
|
// remove the first character to make sure //word displays as /word
|
||||||
|
message = message.slice(1);
|
||||||
}
|
}
|
||||||
model = unescapeMessage(model);*/
|
|
||||||
|
|
||||||
// const body = textSerialize(model);
|
|
||||||
|
|
||||||
// if we're editing rich text, the message content is pure html
|
// if we're editing rich text, the message content is pure html
|
||||||
// BUT if we're not, the message content will be plain text
|
// BUT if we're not, the message content will be plain text
|
||||||
|
@ -96,8 +100,7 @@ export async function createMessageContent(
|
||||||
const formattedBodyPrefix = (isReplyAndEditing && getHtmlReplyFallback(editedEvent)) || "";
|
const formattedBodyPrefix = (isReplyAndEditing && getHtmlReplyFallback(editedEvent)) || "";
|
||||||
|
|
||||||
const content: IContent = {
|
const content: IContent = {
|
||||||
// TODO emote
|
msgtype: isEmote ? MsgType.Emote : MsgType.Text,
|
||||||
msgtype: MsgType.Text,
|
|
||||||
body: isEditing ? `${bodyPrefix} * ${body}` : body,
|
body: isEditing ? `${bodyPrefix} * ${body}` : body,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ import dis from "../../../../../dispatcher/dispatcher";
|
||||||
import { createRedactEventDialog } from "../../../dialogs/ConfirmRedactDialog";
|
import { createRedactEventDialog } from "../../../dialogs/ConfirmRedactDialog";
|
||||||
import { endEditing, cancelPreviousPendingEdit } from "./editing";
|
import { endEditing, cancelPreviousPendingEdit } from "./editing";
|
||||||
import EditorStateTransfer from "../../../../../utils/EditorStateTransfer";
|
import EditorStateTransfer from "../../../../../utils/EditorStateTransfer";
|
||||||
import { createMessageContent } from "./createMessageContent";
|
import { createMessageContent, EMOTE_PREFIX } from "./createMessageContent";
|
||||||
import { isContentModified } from "./isContentModified";
|
import { isContentModified } from "./isContentModified";
|
||||||
import { CommandCategories, getCommand } from "../../../../../SlashCommands";
|
import { CommandCategories, getCommand } from "../../../../../SlashCommands";
|
||||||
import { runSlashCommand, shouldSendAnyway } from "../../../../../editor/commands";
|
import { runSlashCommand, shouldSendAnyway } from "../../../../../editor/commands";
|
||||||
|
@ -78,11 +78,11 @@ export async function sendMessage(
|
||||||
|
|
||||||
let content: IContent | null = null;
|
let content: IContent | null = null;
|
||||||
|
|
||||||
// Functionality here approximates what can be found in SendMessageComposer.sendMessage()
|
// Slash command handling here approximates what can be found in SendMessageComposer.sendMessage()
|
||||||
if (message.startsWith("/") && !message.startsWith("//")) {
|
// but note that the /me and // special cases are handled by the call to createMessageContent
|
||||||
|
if (message.startsWith("/") && !message.startsWith("//") && !message.startsWith(EMOTE_PREFIX)) {
|
||||||
const { cmd, args } = getCommand(message);
|
const { cmd, args } = getCommand(message);
|
||||||
if (cmd) {
|
if (cmd) {
|
||||||
// TODO handle /me special case separately, see end of SlashCommands.Commands
|
|
||||||
const threadId = relation?.rel_type === THREAD_RELATION_TYPE.name ? relation?.event_id : null;
|
const threadId = relation?.rel_type === THREAD_RELATION_TYPE.name ? relation?.event_id : null;
|
||||||
let commandSuccessful: boolean;
|
let commandSuccessful: boolean;
|
||||||
[content, commandSuccessful] = await runSlashCommand(cmd, args, roomId, threadId ?? null);
|
[content, commandSuccessful] = await runSlashCommand(cmd, args, roomId, threadId ?? null);
|
||||||
|
|
|
@ -13,10 +13,14 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
import { MsgType } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import { mkEvent } from "../../../../../test-utils";
|
import { mkEvent } from "../../../../../test-utils";
|
||||||
import { RoomPermalinkCreator } from "../../../../../../src/utils/permalinks/Permalinks";
|
import { RoomPermalinkCreator } from "../../../../../../src/utils/permalinks/Permalinks";
|
||||||
import { createMessageContent } from "../../../../../../src/components/views/rooms/wysiwyg_composer/utils/createMessageContent";
|
import {
|
||||||
|
createMessageContent,
|
||||||
|
EMOTE_PREFIX,
|
||||||
|
} from "../../../../../../src/components/views/rooms/wysiwyg_composer/utils/createMessageContent";
|
||||||
|
|
||||||
describe("createMessageContent", () => {
|
describe("createMessageContent", () => {
|
||||||
const permalinkCreator = {
|
const permalinkCreator = {
|
||||||
|
@ -130,4 +134,24 @@ describe("createMessageContent", () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Should strip the /me prefix from a message", async () => {
|
||||||
|
const textBody = "some body text";
|
||||||
|
const content = await createMessageContent(EMOTE_PREFIX + textBody, true, { permalinkCreator });
|
||||||
|
|
||||||
|
expect(content).toMatchObject({ body: textBody, formatted_body: textBody });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should strip single / from message prefixed with //", async () => {
|
||||||
|
const content = await createMessageContent("//twoSlashes", true, { permalinkCreator });
|
||||||
|
|
||||||
|
expect(content).toMatchObject({ body: "/twoSlashes", formatted_body: "/twoSlashes" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should set the content type to MsgType.Emote when /me prefix is used", async () => {
|
||||||
|
const textBody = "some body text";
|
||||||
|
const content = await createMessageContent(EMOTE_PREFIX + textBody, true, { permalinkCreator });
|
||||||
|
|
||||||
|
expect(content).toMatchObject({ msgtype: MsgType.Emote });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue