diff --git a/src/app/molecules/message/Message.jsx b/src/app/molecules/message/Message.jsx index b17cb33..fa5d608 100644 --- a/src/app/molecules/message/Message.jsx +++ b/src/app/molecules/message/Message.jsx @@ -1,5 +1,5 @@ /* 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'; @@ -52,25 +52,35 @@ function PlaceholderMessage() { ); } -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 ( -
-
- {twemojify(name)} - {twemojify(userId)} -
-
- {time} -
+
+
); -} +}); + +const MessageHeader = React.memo(({ + userId, username, time, +}) => ( +
+
+ {twemojify(username)} + {twemojify(userId)} +
+
+ {time} +
+
+)); MessageHeader.propTypes = { userId: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - color: PropTypes.string.isRequired, + username: PropTypes.string.isRequired, time: PropTypes.string.isRequired, }; @@ -93,7 +103,7 @@ MessageReply.propTypes = { body: PropTypes.string.isRequired, }; -function MessageReplyWrapper({ roomTimeline, eventId }) { +const MessageReplyWrapper = React.memo(({ roomTimeline, eventId }) => { const [reply, setReply] = useState(null); const isMountedRef = useRef(true); @@ -141,19 +151,19 @@ function MessageReplyWrapper({ roomTimeline, eventId }) { {reply !== null && }
); -} +}); 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
{body}
; @@ -176,7 +186,7 @@ function MessageBody({ { isEdited && (edited)} ); -} +}); MessageBody.defaultProps = { isCustomHTML: false, isEdited: false, @@ -379,17 +389,6 @@ MessageReactionGroup.propTypes = { mEvent: PropTypes.shape({}).isRequired, }; -function MessageOptions({ children }) { - return ( -
- {children} -
- ); -} -MessageOptions.propTypes = { - children: PropTypes.node.isRequired, -}; - function isMedia(mE) { return ( mE.getContent()?.msgtype === 'm.file' @@ -400,6 +399,86 @@ function isMedia(mE) { ); } +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 ( +
+ pickEmoji(e, roomId, eventId, roomTimeline)} + src={EmojiAddIC} + size="extra-small" + tooltip="Add reaction" + /> + reply()} + src={ReplyArrowIC} + size="extra-small" + tooltip="Reply" + /> + {(senderId === mx.getUserId() && !isMedia(mEvent)) && ( + edit(true)} + src={PencilIC} + size="extra-small" + tooltip="Edit" + /> + )} + ( + <> + Options + openReadReceipts(roomId, roomTimeline.getEventReaders(eventId))} + > + Read receipts + + {(canIRedact || senderId === mx.getUserId()) && ( + <> + + { + if (window.confirm('Are you sure you want to delete this event')) { + redactEvent(roomId, eventId); + } + }} + > + Delete + + + )} + + )} + render={(toggleMenu) => ( + + )} + /> +
+ ); +}); +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(); @@ -481,14 +560,10 @@ function getEditedBody(editedMEvent) { } 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'); @@ -496,17 +571,12 @@ function Message({ 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'); @@ -524,18 +594,23 @@ function Message({ body = parseReply(body)?.body ?? body; } + const edit = useCallback(() => { + setIsEditing(true); + }, []); + const reply = useCallback(() => { + replyTo(senderId, eventId, body); + }, [body]); + return (
-
- {!isBodyOnly && ( - - )} -
+ { + isBodyOnly + ?
+ : + }
{!isBodyOnly && ( - + )} {isReply && ( )} {!isEditing && ( - - pickEmoji(e, roomId, eventId, roomTimeline)} - src={EmojiAddIC} - size="extra-small" - tooltip="Add reaction" - /> - replyTo(senderId, eventId, body)} - src={ReplyArrowIC} - size="extra-small" - tooltip="Reply" - /> - {(senderId === mx.getUserId() && !isMedia(mEvent)) && ( - setIsEditing(true)} - src={PencilIC} - size="extra-small" - tooltip="Edit" - /> - )} - ( - <> - Options - openReadReceipts(roomId, roomTimeline.getEventReaders(eventId))} - > - Read receipts - - {(canIRedact || senderId === mx.getUserId()) && ( - <> - - { - if (window.confirm('Are you sure you want to delete this event')) { - redactEvent(roomId, eventId); - } - }} - > - Delete - - - )} - - )} - render={(toggleMenu) => ( - - )} - /> - + )}