RocketChat/Rocket.Chat

View on GitHub
apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts

Summary

Maintainability
B
4 hrs
Test Coverage
import type { IMessage } from '@rocket.chat/core-typings';
import { isE2EEMessage, isRoomFederated } from '@rocket.chat/core-typings';
import { Meteor } from 'meteor/meteor';
import moment from 'moment';

import { getPermaLink } from '../../../../client/lib/getPermaLink';
import { imperativeModal } from '../../../../client/lib/imperativeModal';
import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator';
import { dispatchToastMessage } from '../../../../client/lib/toast';
import { messageArgs } from '../../../../client/lib/utils/messageArgs';
import { router } from '../../../../client/providers/RouterProvider';
import ForwardMessageModal from '../../../../client/views/room/modals/ForwardMessageModal/ForwardMessageModal';
import ReactionListModal from '../../../../client/views/room/modals/ReactionListModal';
import ReportMessageModal from '../../../../client/views/room/modals/ReportMessageModal';
import { hasAtLeastOnePermission, hasPermission } from '../../../authorization/client';
import { ChatRoom, Subscriptions } from '../../../models/client';
import { t } from '../../../utils/lib/i18n';
import { MessageAction } from './MessageAction';

const getMainMessageText = (message: IMessage): IMessage => {
    const newMessage = { ...message };
    newMessage.msg = newMessage.msg || newMessage.attachments?.[0]?.description || newMessage.attachments?.[0]?.title || '';
    newMessage.md = newMessage.md || newMessage.attachments?.[0]?.descriptionMd || undefined;
    return { ...newMessage };
};

Meteor.startup(async () => {
    MessageAction.addButton({
        id: 'reply-directly',
        icon: 'reply-directly',
        label: 'Reply_in_direct_message',
        context: ['message', 'message-mobile', 'threads', 'federated'],
        role: 'link',
        type: 'communication',
        action(_, props) {
            const { message = messageArgs(this).msg } = props;
            roomCoordinator.openRouteLink(
                'd',
                { name: message.u.username },
                {
                    ...router.getSearchParameters(),
                    reply: message._id,
                },
            );
        },
        condition({ subscription, room, message, user }) {
            if (subscription == null) {
                return false;
            }
            if (room.t === 'd' || room.t === 'l') {
                return false;
            }

            // Check if we already have a DM started with the message user (not ourselves) or we can start one
            if (!!user && user._id !== message.u._id && !hasPermission('create-d')) {
                const dmRoom = ChatRoom.findOne({ _id: [user._id, message.u._id].sort().join('') });
                if (!dmRoom || !Subscriptions.findOne({ 'rid': dmRoom._id, 'u._id': user._id })) {
                    return false;
                }
            }

            return true;
        },
        order: 0,
        group: 'menu',
        disabled({ message }) {
            return isE2EEMessage(message);
        },
    });

    MessageAction.addButton({
        id: 'forward-message',
        icon: 'arrow-forward',
        label: 'Forward_message',
        context: ['message', 'message-mobile', 'threads'],
        type: 'communication',
        async action(_, props) {
            const { message = messageArgs(this).msg } = props;
            const permalink = await getPermaLink(message._id);
            imperativeModal.open({
                component: ForwardMessageModal,
                props: {
                    message,
                    permalink,
                    onClose: (): void => {
                        imperativeModal.close();
                    },
                },
            });
        },
        order: 0,
        group: 'message',
        disabled({ message }) {
            return isE2EEMessage(message);
        },
    });

    MessageAction.addButton({
        id: 'quote-message',
        icon: 'quote',
        label: 'Quote',
        context: ['message', 'message-mobile', 'threads', 'federated'],
        async action(_, props) {
            const { message = messageArgs(this).msg, chat, autoTranslateOptions } = props;

            if (message && autoTranslateOptions?.autoTranslateEnabled && autoTranslateOptions.showAutoTranslate(message)) {
                message.msg =
                    message.translations && autoTranslateOptions.autoTranslateLanguage
                        ? message.translations[autoTranslateOptions.autoTranslateLanguage]
                        : message.msg;
            }

            await chat?.composer?.quoteMessage(message);
        },
        condition({ subscription }) {
            if (subscription == null) {
                return false;
            }

            return true;
        },
        order: -2,
        group: 'message',
    });

    MessageAction.addButton({
        id: 'permalink',
        icon: 'permalink',
        label: 'Copy_link',
        // classes: 'clipboard',
        context: ['message', 'message-mobile', 'threads', 'federated', 'videoconf', 'videoconf-threads'],
        type: 'duplication',
        async action(_, props) {
            try {
                const { message = messageArgs(this).msg } = props;
                const permalink = await getPermaLink(message._id);
                await navigator.clipboard.writeText(permalink);
                dispatchToastMessage({ type: 'success', message: t('Copied') });
            } catch (e) {
                dispatchToastMessage({ type: 'error', message: e });
            }
        },
        condition({ subscription }) {
            return !!subscription;
        },
        order: 5,
        group: 'menu',
        disabled({ message }) {
            return isE2EEMessage(message);
        },
    });

    MessageAction.addButton({
        id: 'copy',
        icon: 'copy',
        label: 'Copy_text',
        // classes: 'clipboard',
        context: ['message', 'message-mobile', 'threads', 'federated'],
        type: 'duplication',
        async action(_, props) {
            const { message = messageArgs(this).msg } = props;
            const msgText = getMainMessageText(message).msg;
            await navigator.clipboard.writeText(msgText);
            dispatchToastMessage({ type: 'success', message: t('Copied') });
        },
        condition({ subscription }) {
            return !!subscription;
        },
        order: 6,
        group: 'menu',
    });

    MessageAction.addButton({
        id: 'edit-message',
        icon: 'edit',
        label: 'Edit',
        context: ['message', 'message-mobile', 'threads', 'federated'],
        type: 'management',
        async action(_, props) {
            const { message = messageArgs(this).msg, chat } = props;
            await chat?.messageEditing.editMessage(message);
        },
        condition({ message, subscription, settings, room, user }) {
            if (subscription == null) {
                return false;
            }
            if (isRoomFederated(room)) {
                return message.u._id === user?._id;
            }
            const canEditMessage = hasAtLeastOnePermission('edit-message', message.rid);
            const isEditAllowed = settings.Message_AllowEditing;
            const editOwn = message.u && message.u._id === user?._id;
            if (!(canEditMessage || (isEditAllowed && editOwn))) {
                return false;
            }
            const blockEditInMinutes = settings.Message_AllowEditing_BlockEditInMinutes as number;
            const bypassBlockTimeLimit = hasPermission('bypass-time-limit-edit-and-delete', message.rid);

            if (!bypassBlockTimeLimit && blockEditInMinutes) {
                let msgTs;
                if (message.ts != null) {
                    msgTs = moment(message.ts);
                }
                let currentTsDiff;
                if (msgTs != null) {
                    currentTsDiff = moment().diff(msgTs, 'minutes');
                }
                return (!!currentTsDiff || currentTsDiff === 0) && currentTsDiff < blockEditInMinutes;
            }
            return true;
        },
        order: 8,
        group: 'menu',
    });

    MessageAction.addButton({
        id: 'delete-message',
        icon: 'trash',
        label: 'Delete',
        context: ['message', 'message-mobile', 'threads', 'federated', 'videoconf', 'videoconf-threads'],
        color: 'alert',
        type: 'management',
        async action(this: unknown, _, { message = messageArgs(this).msg, chat }) {
            await chat?.flows.requestMessageDeletion(message);
        },
        condition({ message, subscription, room, chat, user }) {
            if (!subscription) {
                return false;
            }
            if (isRoomFederated(room)) {
                return message.u._id === user?._id;
            }
            const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t);
            if (isLivechatRoom) {
                return false;
            }

            return chat?.data.canDeleteMessage(message) ?? false;
        },
        order: 10,
        group: 'menu',
    });

    MessageAction.addButton({
        id: 'report-message',
        icon: 'report',
        label: 'Report',
        context: ['message', 'message-mobile', 'threads', 'federated', 'videoconf', 'videoconf-threads'],
        color: 'alert',
        type: 'management',
        action(this: unknown, _, { message = messageArgs(this).msg }) {
            imperativeModal.open({
                component: ReportMessageModal,
                props: {
                    message: getMainMessageText(message),
                    onClose: imperativeModal.close,
                },
            });
        },
        condition({ subscription, room, message, user }) {
            const isLivechatRoom = roomCoordinator.isLivechatRoom(room.t);
            if (isLivechatRoom || message.u._id === user?._id) {
                return false;
            }

            return Boolean(subscription);
        },
        order: 9,
        group: 'menu',
    });

    MessageAction.addButton({
        id: 'reaction-list',
        icon: 'emoji',
        label: 'Reactions',
        context: ['message', 'message-mobile', 'threads', 'videoconf', 'videoconf-threads'],
        type: 'interaction',
        action(this: unknown, _, { message: { reactions = {} } = messageArgs(this).msg }) {
            imperativeModal.open({
                component: ReactionListModal,
                props: { reactions, onClose: imperativeModal.close },
            });
        },
        condition({ message: { reactions } }) {
            return !!reactions;
        },
        order: 9,
        group: 'menu',
    });
});