Files
jitsi-meet/react/features/chat/components/web/ChatMessageGroup.tsx
Hristo Terezov a62fa3f833 feat(chat): Display file uploads as inline chat messages
Integrates file sharing into the chat interface so uploaded files appear as messages in the chat timeline alongside text
messages.

Changes:
- Created FileMessage component for inline file display in chat
- Extracted FileItem component for reusable file UI across chat and file sharing tab
- Show "A file was deleted" placeholder instead of removing message when file deleted
- Hide message menu (3-dot) when no actions are available for file messages
- Add button backgrounds in chat context to hide text on hover
- Fix timing: local participant only sees file message after upload completes (progress: 100%)

Technical implementation:
- Added fileMetadata field to IMessage interface
- Added isDeleted flag to IFileMetadata for soft-delete state
- Middleware dispatches addMessage when files uploaded (ADD_FILE action)
- Middleware uses editMessage when files deleted to preserve chat history
- Minimal state retention (only isDeleted flag) for deleted files

This provides a unified messaging experience where file sharing is part of the conversation flow.
2025-10-14 08:45:51 -05:00

86 lines
2.2 KiB
TypeScript

import clsx from 'clsx';
import React from 'react';
import { makeStyles } from 'tss-react/mui';
import Avatar from '../../../base/avatar/components/Avatar';
import { IMessage } from '../../types';
import ChatMessage from './ChatMessage';
interface IProps {
/**
* Additional CSS classes to apply to the root element.
*/
className: string;
/**
* The messages to display as a group.
*/
messages: Array<IMessage>;
}
const useStyles = makeStyles()(theme => {
return {
messageGroup: {
display: 'flex',
flexDirection: 'column',
maxWidth: '100%',
'&.remote, &.file': {
maxWidth: 'calc(100% - 40px)' // 100% - avatar and margin
}
},
groupContainer: {
display: 'flex',
'&.local': {
justifyContent: 'flex-end',
'& .avatar': {
display: 'none'
}
}
},
avatar: {
margin: `${theme.spacing(1)} ${theme.spacing(2)} ${theme.spacing(3)} 0`,
position: 'sticky',
flexShrink: 0,
top: 0
}
};
});
const ChatMessageGroup = ({ className = '', messages }: IProps) => {
const { classes } = useStyles();
const messagesLength = messages.length;
if (!messagesLength) {
return null;
}
return (
<div className = { clsx(classes.groupContainer, className) }>
<Avatar
className = { clsx(classes.avatar, 'avatar') }
participantId = { messages[0].participantId }
size = { 32 } />
<div className = { `${classes.messageGroup} chat-message-group ${className}` }>
{messages.map((message, i) => (
<ChatMessage
className = { className }
key = { i }
message = { message }
showDisplayName = { i === 0 }
showTimestamp = { i === messages.length - 1 } />
))}
</div>
</div>
);
};
export default ChatMessageGroup;