import React, { useMemo, useState } from 'react'; import { ChatMessage, formatShortTime, getMessageTimeDiff, shouldShowMessageTime, SYSTEM_USERID, t, useCachedUserInfo, MessageHelper, useAsync, getCachedUserInfo, } from 'tailchat-shared'; import { Avatar } from '@/components/Avatar'; import { useRenderPluginMessageInterpreter } from './useRenderPluginMessageInterpreter'; import { getMessageRender } from '@/plugin/common'; import { Icon } from '@iconify/react'; import { Divider, Dropdown } from 'antd'; import { UserName } from '@/components/UserName'; import './item.less'; import clsx from 'clsx'; import { useChatMessageItemAction } from './useChatMessageItemAction'; import { useChatMessageReaction } from './useChatMessageReaction'; import { DevContainer } from '@/components/DevContainer'; import { TcPopover } from '@/components/TcPopover'; import { useMessageReactions } from './useMessageReactions'; /** * 消息引用 */ const MessageQuote: React.FC<{ payload: ChatMessage }> = React.memo( ({ payload }) => { const quote = useMemo( () => new MessageHelper(payload).hasReply(), [payload] ); if (quote === false) { return null; } return (
{t('回复')} :{' '} {getMessageRender(quote.content)}
); } ); MessageQuote.displayName = 'MessageQuote'; const MessageActionIcon: React.FC<{ icon: string }> = (props) => (
); /** * 普通消息 */ const NormalMessage: React.FC = React.memo((props) => { const { showAvatar, payload } = props; const userInfo = useCachedUserInfo(payload.author ?? ''); const [isActionBtnActive, setIsActionBtnActive] = useState(false); const reactions = useMessageReactions(payload); const emojiAction = useChatMessageReaction(payload); const moreActions = useChatMessageItemAction(payload); return (
{/* 头像 */}
{showAvatar ? ( ) : (
{formatShortTime(payload.createdAt)}
)}
{/* 主体 */}
{showAvatar && (
{userInfo.nickname}
{formatShortTime(payload.createdAt)}
)}
{getMessageRender(payload.content)} {/* 解释器按钮 */} {useRenderPluginMessageInterpreter(payload.content)}
{reactions}
{/* 操作 */}
); }); NormalMessage.displayName = 'NormalMessage'; /** * 系统消息 */ const SystemMessage: React.FC = React.memo( ({ payload }) => { return (
{payload.content}
); } ); SystemMessage.displayName = 'SystemMessage'; /** * 带userId => nickname异步解析的SystemMessage 组件 */ const SystemMessageWithNickname: React.FC< ChatMessageItemProps & { userIds: string[]; overwritePayload: (nicknameList: string[]) => ChatMessage; } > = React.memo((props) => { const { value: nicknameList = [] } = useAsync(() => { return Promise.all( props.userIds.map((userId) => getCachedUserInfo(userId).then((u) => u.nickname) ) ); }, [props.userIds.join(',')]); return ( ); }); SystemMessageWithNickname.displayName = 'SystemMessageWithNickname'; interface ChatMessageItemProps { showAvatar: boolean; payload: ChatMessage; } const ChatMessageItem: React.FC = React.memo((props) => { const payload = props.payload; if (payload.author === SYSTEM_USERID) { // 系统消息 return ; } else if (payload.hasRecall === true) { // 撤回消息 return ( ({ ...payload, content: t('{{nickname}} 撤回了一条消息', { nickname: nickname[0] ?? '', }), })} /> ); } // 普通消息 return ; }); ChatMessageItem.displayName = 'ChatMessageItem'; function findMessageIndexWithId( messages: ChatMessage[], messageId: string ): number { return messages.findIndex((m) => m._id === messageId); } /** * 构造聊天项 */ export function buildMessageItemRow( messages: ChatMessage[], messageId: string ) { const index = findMessageIndexWithId(messages, messageId); // TODO: 这里是因为mattermost的动态列表传的id因此只能这边再用id找回,可以看看是否可以优化 if (index === -1) { return
; } const message = messages[index]; let showDate = true; let showAvatar = true; const messageCreatedAt = new Date(message.createdAt ?? ''); if (index > 0) { // 当不是第一条数据时 // 进行时间合并 const prevMessage = messages[index - 1]; if ( !shouldShowMessageTime( new Date(prevMessage.createdAt ?? ''), messageCreatedAt ) ) { showDate = false; } // 进行头像合并(在同一时间块下 且发送者为同一人) if (showDate === false) { showAvatar = prevMessage.author !== message.author; } } return (
{showDate && ( {getMessageTimeDiff(messageCreatedAt)} )}
); }