/* eslint-disable react/prop-types */
-import React, { useState, useEffect, useRef } from 'react';
+import React, { useState, useEffect, useCallback, useRef } from 'react';
import PropTypes from 'prop-types';
import './Message.scss';
);
}
-function MessageHeader({
- userId, name, color, time,
-}) {
+const MessageAvatar = React.memo(({
+ roomId, mEvent, userId, username,
+}) => {
+ const avatarSrc = mEvent.sender.getAvatarUrl(initMatrix.matrixClient.baseUrl, 36, 36, 'crop');
return (
- <div className="message__header">
- <div style={{ color }} className="message__profile">
- <Text variant="b1">{twemojify(name)}</Text>
- <Text variant="b1">{twemojify(userId)}</Text>
- </div>
- <div className="message__time">
- <Text variant="b3">{time}</Text>
- </div>
+ <div className="message__avatar-container">
+ <button type="button" onClick={() => openProfileViewer(userId, roomId)}>
+ <Avatar imageSrc={avatarSrc} text={username} bgColor={colorMXID(userId)} size="small" />
+ </button>
</div>
);
-}
+});
+
+const MessageHeader = React.memo(({
+ userId, username, time,
+}) => (
+ <div className="message__header">
+ <div style={{ color: colorMXID(userId) }} className="message__profile">
+ <Text variant="b1">{twemojify(username)}</Text>
+ <Text variant="b1">{twemojify(userId)}</Text>
+ </div>
+ <div className="message__time">
+ <Text variant="b3">{time}</Text>
+ </div>
+ </div>
+));
MessageHeader.propTypes = {
userId: PropTypes.string.isRequired,
- name: PropTypes.string.isRequired,
- color: PropTypes.string.isRequired,
+ username: PropTypes.string.isRequired,
time: PropTypes.string.isRequired,
};
body: PropTypes.string.isRequired,
};
-function MessageReplyWrapper({ roomTimeline, eventId }) {
+const MessageReplyWrapper = React.memo(({ roomTimeline, eventId }) => {
const [reply, setReply] = useState(null);
const isMountedRef = useRef(true);
{reply !== null && <MessageReply name={reply.to} color={reply.color} body={reply.body} />}
</div>
);
-}
+});
MessageReplyWrapper.propTypes = {
roomTimeline: PropTypes.shape({}).isRequired,
eventId: PropTypes.string.isRequired,
};
-function MessageBody({
+const MessageBody = React.memo(({
senderName,
body,
isCustomHTML,
isEdited,
msgType,
-}) {
+}) => {
// if body is not string it is a React element.
if (typeof body !== 'string') return <div className="message__body">{body}</div>;
{ isEdited && <Text className="message__body-edited" variant="b3">(edited)</Text>}
</div>
);
-}
+});
MessageBody.defaultProps = {
isCustomHTML: false,
isEdited: false,
mEvent: PropTypes.shape({}).isRequired,
};
-function MessageOptions({ children }) {
- return (
- <div className="message__options">
- {children}
- </div>
- );
-}
-MessageOptions.propTypes = {
- children: PropTypes.node.isRequired,
-};
-
function isMedia(mE) {
return (
mE.getContent()?.msgtype === 'm.file'
);
}
+const MessageOptions = React.memo(({
+ roomTimeline, mEvent, edit, reply,
+}) => {
+ const { roomId, room } = roomTimeline;
+ const mx = initMatrix.matrixClient;
+ const eventId = mEvent.getId();
+ const senderId = mEvent.getSender();
+
+ const myPowerlevel = room.getMember(mx.getUserId())?.powerLevel;
+ const canIRedact = room.currentState.hasSufficientPowerLevelFor('redact', myPowerlevel);
+
+ return (
+ <div className="message__options">
+ <IconButton
+ onClick={(e) => pickEmoji(e, roomId, eventId, roomTimeline)}
+ src={EmojiAddIC}
+ size="extra-small"
+ tooltip="Add reaction"
+ />
+ <IconButton
+ onClick={() => reply()}
+ src={ReplyArrowIC}
+ size="extra-small"
+ tooltip="Reply"
+ />
+ {(senderId === mx.getUserId() && !isMedia(mEvent)) && (
+ <IconButton
+ onClick={() => edit(true)}
+ src={PencilIC}
+ size="extra-small"
+ tooltip="Edit"
+ />
+ )}
+ <ContextMenu
+ content={() => (
+ <>
+ <MenuHeader>Options</MenuHeader>
+ <MenuItem
+ iconSrc={TickMarkIC}
+ onClick={() => openReadReceipts(roomId, roomTimeline.getEventReaders(eventId))}
+ >
+ Read receipts
+ </MenuItem>
+ {(canIRedact || senderId === mx.getUserId()) && (
+ <>
+ <MenuBorder />
+ <MenuItem
+ variant="danger"
+ iconSrc={BinIC}
+ onClick={() => {
+ if (window.confirm('Are you sure you want to delete this event')) {
+ redactEvent(roomId, eventId);
+ }
+ }}
+ >
+ Delete
+ </MenuItem>
+ </>
+ )}
+ </>
+ )}
+ render={(toggleMenu) => (
+ <IconButton
+ onClick={toggleMenu}
+ src={VerticalMenuIC}
+ size="extra-small"
+ tooltip="Options"
+ />
+ )}
+ />
+ </div>
+ );
+});
+MessageOptions.propTypes = {
+ roomTimeline: PropTypes.shape({}).isRequired,
+ mEvent: PropTypes.shape({}).isRequired,
+ edit: PropTypes.func.isRequired,
+ reply: PropTypes.func.isRequired,
+};
+
function genMediaContent(mE) {
const mx = initMatrix.matrixClient;
const mContent = mE.getContent();
}
function Message({
- mEvent, isBodyOnly, roomTimeline, focus, time
+ mEvent, isBodyOnly, roomTimeline, focus, time,
}) {
const [isEditing, setIsEditing] = useState(false);
-
- const mx = initMatrix.matrixClient;
- const {
- room, roomId, editedTimeline, reactionTimeline,
- } = roomTimeline;
+ const { roomId, editedTimeline, reactionTimeline } = roomTimeline;
const className = ['message', (isBodyOnly ? 'message--body-only' : 'message--full')];
if (focus) className.push('message--focus');
const eventId = mEvent.getId();
const msgType = content?.msgtype;
const senderId = mEvent.getSender();
- const mxidColor = colorMXID(senderId);
let { body } = content;
- const avatarSrc = mEvent.sender.getAvatarUrl(initMatrix.matrixClient.baseUrl, 36, 36, 'crop');
const username = getUsernameOfRoomMember(mEvent.sender);
if (typeof body === 'undefined') return null;
if (msgType === 'm.emote') className.push('message--type-emote');
- const myPowerlevel = room.getMember(mx.getUserId())?.powerLevel;
- const canIRedact = room.currentState.hasSufficientPowerLevelFor('redact', myPowerlevel);
-
let isCustomHTML = content.format === 'org.matrix.custom.html';
const isEdited = editedTimeline.has(eventId);
const haveReactions = reactionTimeline.has(eventId) || !!mEvent.getServerAggregatedRelation('m.annotation');
body = parseReply(body)?.body ?? body;
}
+ const edit = useCallback(() => {
+ setIsEditing(true);
+ }, []);
+ const reply = useCallback(() => {
+ replyTo(senderId, eventId, body);
+ }, [body]);
+
return (
<div className={className.join(' ')}>
- <div className="message__avatar-container">
- {!isBodyOnly && (
- <button type="button" onClick={() => openProfileViewer(senderId, roomId)}>
- <Avatar imageSrc={avatarSrc} text={username} bgColor={mxidColor} size="small" />
- </button>
- )}
- </div>
+ {
+ isBodyOnly
+ ? <div className="message__avatar-container" />
+ : <MessageAvatar roomId={roomId} mEvent={mEvent} userId={senderId} username={username} />
+ }
<div className="message__main-container">
{!isBodyOnly && (
- <MessageHeader userId={senderId} name={username} color={mxidColor} time={time} />
+ <MessageHeader userId={senderId} username={username} time={time} />
)}
{isReply && (
<MessageReplyWrapper
<MessageReactionGroup roomTimeline={roomTimeline} mEvent={mEvent} />
)}
{!isEditing && (
- <MessageOptions>
- <IconButton
- onClick={(e) => pickEmoji(e, roomId, eventId, roomTimeline)}
- src={EmojiAddIC}
- size="extra-small"
- tooltip="Add reaction"
- />
- <IconButton
- onClick={() => replyTo(senderId, eventId, body)}
- src={ReplyArrowIC}
- size="extra-small"
- tooltip="Reply"
- />
- {(senderId === mx.getUserId() && !isMedia(mEvent)) && (
- <IconButton
- onClick={() => setIsEditing(true)}
- src={PencilIC}
- size="extra-small"
- tooltip="Edit"
- />
- )}
- <ContextMenu
- content={() => (
- <>
- <MenuHeader>Options</MenuHeader>
- <MenuItem
- iconSrc={TickMarkIC}
- onClick={() => openReadReceipts(roomId, roomTimeline.getEventReaders(eventId))}
- >
- Read receipts
- </MenuItem>
- {(canIRedact || senderId === mx.getUserId()) && (
- <>
- <MenuBorder />
- <MenuItem
- variant="danger"
- iconSrc={BinIC}
- onClick={() => {
- if (window.confirm('Are you sure you want to delete this event')) {
- redactEvent(roomId, eventId);
- }
- }}
- >
- Delete
- </MenuItem>
- </>
- )}
- </>
- )}
- render={(toggleMenu) => (
- <IconButton
- onClick={toggleMenu}
- src={VerticalMenuIC}
- size="extra-small"
- tooltip="Options"
- />
- )}
- />
- </MessageOptions>
+ <MessageOptions
+ roomTimeline={roomTimeline}
+ mEvent={mEvent}
+ edit={edit}
+ reply={reply}
+ />
)}
</div>
</div>