RocketChat/Rocket.Chat

View on GitHub
packages/livechat/src/components/Messages/Message/index.js

Summary

Maintainability
A
3 hrs
Test Coverage
import { format, formatDistance } from 'date-fns';
import isToday from 'date-fns/isToday';
import { memo } from 'preact/compat';
import { withTranslation } from 'react-i18next';

import { getAttachmentUrl } from '../../../helpers/baseUrl';
import { normalizeTransferHistoryMessage } from '../../../helpers/normalizeTransferHistoryMessage';
import { resolveDate } from '../../../helpers/resolveDate';
import { default as AudioAttachment } from '../AudioAttachment';
import { FileAttachment } from '../FileAttachment';
import { ImageAttachment } from '../ImageAttachment';
import { MessageAvatars } from '../MessageAvatars';
import MessageBlocks from '../MessageBlocks';
import { MessageBubble } from '../MessageBubble';
import { MessageContainer } from '../MessageContainer';
import { MessageContent } from '../MessageContent';
import { MessageText } from '../MessageText';
import MessageTime from '../MessageTime';
import VideoAttachment from '../VideoAttachment';
import {
    MESSAGE_TYPE_ROOM_NAME_CHANGED,
    MESSAGE_TYPE_USER_ADDED,
    MESSAGE_TYPE_USER_REMOVED,
    MESSAGE_TYPE_USER_JOINED,
    MESSAGE_TYPE_USER_LEFT,
    MESSAGE_TYPE_WELCOME,
    MESSAGE_TYPE_LIVECHAT_CLOSED,
    MESSAGE_TYPE_LIVECHAT_STARTED,
    MESSAGE_TYPE_LIVECHAT_TRANSFER_HISTORY,
    MESSAGE_WEBRTC_CALL,
} from '../constants';

const renderContent = ({ text, system, quoted, me, blocks, attachments, attachmentResolver, mid, rid }) =>
    [
        ...(attachments || []).map(
            (attachment) =>
                (attachment.audio_url && <AudioAttachment quoted={quoted} url={attachmentResolver(attachment.audio_url)} />) ||
                (attachment.video_url && <VideoAttachment quoted={quoted} url={attachmentResolver(attachment.video_url)} />) ||
                (attachment.image_url && <ImageAttachment quoted={quoted} url={attachmentResolver(attachment.image_url)} />) ||
                (attachment.title_link && (
                    <FileAttachment quoted={quoted} url={attachmentResolver(attachment.title_link)} title={attachment.title} />
                )) ||
                ((attachment.message_link || attachment.tmid) &&
                    renderContent({
                        text: attachment.text,
                        quoted: true,
                        attachments: attachment.attachments,
                        attachmentResolver,
                    })),
        ),
        text && (
            <MessageBubble inverse={me} quoted={quoted} system={system}>
                <MessageText text={text} system={system} />
            </MessageBubble>
        ),
        blocks && <MessageBlocks blocks={blocks} mid={mid} rid={rid} />,
    ].filter(Boolean);

const resolveWebRTCEndCallMessage = ({ webRtcCallEndTs, ts, t }) => {
    const callEndTime = resolveDate(webRtcCallEndTs);
    const callStartTime = resolveDate(ts);
    const callDuration = formatDistance(callEndTime, callStartTime);
    const time = format(callEndTime, isToday(callEndTime) ? 'HH:mm' : 'dddd HH:mm');
    return t('call_end_time', { time, callDuration });
};

const getSystemMessageText = ({ type, conversationFinishedMessage, transferData, u, webRtcCallEndTs, ts }, t) =>
    (type === MESSAGE_TYPE_ROOM_NAME_CHANGED && t('room_name_changed')) ||
    (type === MESSAGE_TYPE_USER_ADDED && t('user_added_by')) ||
    (type === MESSAGE_TYPE_USER_REMOVED && t('user_removed_by')) ||
    (type === MESSAGE_TYPE_USER_JOINED && t('user_joined')) ||
    (type === MESSAGE_TYPE_USER_LEFT && t('user_left')) ||
    (type === MESSAGE_TYPE_WELCOME && t('welcome')) ||
    (type === MESSAGE_TYPE_LIVECHAT_CLOSED && (conversationFinishedMessage || t('conversation_finished'))) ||
    (type === MESSAGE_TYPE_LIVECHAT_STARTED && t('chat_started')) ||
    (type === MESSAGE_TYPE_LIVECHAT_TRANSFER_HISTORY && normalizeTransferHistoryMessage(transferData, u, t)) ||
    (type === MESSAGE_WEBRTC_CALL && webRtcCallEndTs && ts && resolveWebRTCEndCallMessage({ webRtcCallEndTs, ts, t }));

const getMessageUsernames = (compact, message) => {
    if (compact || !message.u) {
        return [];
    }

    const {
        alias,
        u: { username, name },
    } = message;
    if (alias && name) {
        return [name];
    }

    return [username];
};

const Message = ({
    avatarResolver,
    attachmentResolver = getAttachmentUrl,
    use,
    me,
    compact,
    className,
    style = {},
    t,
    hideAvatar,
    ...message
}) => (
    <MessageContainer id={message._id} compact={compact} reverse={me} use={use} className={className} style={style} system={!!message.type}>
        {!message.type && !hideAvatar && <MessageAvatars avatarResolver={avatarResolver} usernames={getMessageUsernames(compact, message)} />}
        <MessageContent reverse={me}>
            {renderContent({
                text: message.type ? getSystemMessageText(message, t) : message.msg,
                system: !!message.type,
                me,
                attachments: message.attachments,
                blocks: message.blocks,
                mid: message._id,
                rid: message.rid,
                attachmentResolver,
            })}
        </MessageContent>

        {!compact && !message.type && <MessageTime normal={!me} inverse={me} ts={message.ts} />}
    </MessageContainer>
);

export default withTranslation()(memo(Message));