From a3b5778da17be4630e30844aae1da687c8c0a188 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Fri, 6 Jun 2025 06:32:41 +0200 Subject: [PATCH] Add type --- src/components/Chat/MessageItem.tsx | 932 ++++++++++++++-------------- 1 file changed, 477 insertions(+), 455 deletions(-) diff --git a/src/components/Chat/MessageItem.tsx b/src/components/Chat/MessageItem.tsx index 65d6f80..c129408 100644 --- a/src/components/Chat/MessageItem.tsx +++ b/src/components/Chat/MessageItem.tsx @@ -98,533 +98,555 @@ const UserBadge = memo(({ userInfo }) => { ); }); -export const MessageItem = memo( - ({ - handleReaction, - isLast, - isPrivate, - isShowingAsReply, - isTemp, - isUpdating, - lastSignature, - message, - myAddress, - onEdit, - onReply, - onSeen, - reactions, - reply, - replyIndex, - scrollToItem, - }) => { - const { getIndividualUserInfo } = useContext(QORTAL_APP_CONTEXT); - const [anchorEl, setAnchorEl] = useState(null); - const [selectedReaction, setSelectedReaction] = useState(null); - const [userInfo, setUserInfo] = useState(null); +type MessageItemProps = { + handleReaction: (reaction: string, messageId: string) => void; + isLast: boolean; + isPrivate: boolean; + isShowingAsReply?: boolean; + isTemp: boolean; + isUpdating: boolean; + lastSignature: any; + message: any; + myAddress: any; + onEdit: (messageId: string) => void; + onReply: (messageId: string) => void; + onSeen: () => void; + reactions: any; // could be null, or type it more strictly + reply: any; // same here + replyIndex: number; + scrollToItem: (index: number) => void; +}; - useEffect(() => { - const getInfo = async () => { - if (!message?.sender) return; - try { - const res = await getIndividualUserInfo(message?.sender); - if (!res) return null; - setUserInfo(res); - } catch (error) { - // - } - }; +export const MessageItemComponent = ({ + handleReaction, + isLast, + isPrivate, + isShowingAsReply, + isTemp, + isUpdating, + lastSignature, + message, + myAddress, + onEdit, + onReply, + onSeen, + reactions, + reply, + replyIndex, + scrollToItem, +}: MessageItemProps) => { + const { getIndividualUserInfo } = useContext(QORTAL_APP_CONTEXT); + const [anchorEl, setAnchorEl] = useState(null); + const [selectedReaction, setSelectedReaction] = useState(null); + const [userInfo, setUserInfo] = useState(null); - getInfo(); - }, [message?.sender, getIndividualUserInfo]); - - const htmlText = useMemo(() => { - if (message?.messageText) { - const isHtml = isHtmlString(message?.messageText); - if (isHtml) return message?.messageText; - return generateHTML(message?.messageText, [ - StarterKit, - Underline, - Highlight, - Mention, - TextStyle, - ]); + useEffect(() => { + const getInfo = async () => { + if (!message?.sender) return; + try { + const res = await getIndividualUserInfo(message?.sender); + if (!res) return null; + setUserInfo(res); + } catch (error) { + // } - }, [message?.editTimestamp]); + }; - const htmlReply = useMemo(() => { - if (reply?.messageText) { - const isHtml = isHtmlString(reply?.messageText); - if (isHtml) return reply?.messageText; - return generateHTML(reply?.messageText, [ - StarterKit, - Underline, - Highlight, - Mention, - TextStyle, - ]); - } - }, [reply?.editTimestamp]); + getInfo(); + }, [message?.sender, getIndividualUserInfo]); - const userAvatarUrl = useMemo(() => { - return message?.senderName - ? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${ - message?.senderName - }/qortal_avatar?async=true` - : ''; - }, []); + const htmlText = useMemo(() => { + if (message?.messageText) { + const isHtml = isHtmlString(message?.messageText); + if (isHtml) return message?.messageText; + return generateHTML(message?.messageText, [ + StarterKit, + Underline, + Highlight, + Mention, + TextStyle, + ]); + } + }, [message?.editTimestamp]); - const onSeenFunc = useCallback(() => { - onSeen(message.id); - }, [message?.id]); + const htmlReply = useMemo(() => { + if (reply?.messageText) { + const isHtml = isHtmlString(reply?.messageText); + if (isHtml) return reply?.messageText; + return generateHTML(reply?.messageText, [ + StarterKit, + Underline, + Highlight, + Mention, + TextStyle, + ]); + } + }, [reply?.editTimestamp]); - const theme = useTheme(); - const { t } = useTranslation([ - 'auth', - 'core', - 'group', - 'question', - 'tutorial', - ]); + const userAvatarUrl = useMemo(() => { + return message?.senderName + ? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${ + message?.senderName + }/qortal_avatar?async=true` + : ''; + }, []); - return ( - <> - {message?.divide && ( -
- {t('core:message.generic.unread_messages', { - postProcess: 'capitalizeFirstChar', - })} -
- )} + const onSeenFunc = useCallback(() => { + onSeen(message.id); + }, [message?.id]); - + {message?.divide && ( +
+ {t('core:message.generic.unread_messages', { + postProcess: 'capitalizeFirstChar', + })} +
+ )} + + +
-
+ ) : ( + + + + {message?.senderName?.charAt(0)} + + + + + )} + + - {isShowingAsReply ? ( - - ) : ( + + + + {message?.senderName || message?.sender} + + + - - { + onEdit(message); + }} + > + + + )} + + {!isShowingAsReply && ( + { + onReply(message); }} - alt={message?.senderName} - src={userAvatarUrl} > - {message?.senderName?.charAt(0)} - - - + + + )} + + {!isShowingAsReply && handleReaction && ( + { + if ( + reactions && + reactions[val] && + reactions[val]?.find( + (item) => item?.sender === myAddress + ) + ) { + handleReaction(val, message, false); + } else { + handleReaction(val, message, true); + } + }} + /> + )} + + + {reply && ( + <> + + + { + scrollToItem(replyIndex); + }} + > + + + + + {t('core:message.generic.replied_to', { + person: reply?.senderName || reply?.senderAddress, + postProcess: 'capitalizeFirstChar', + })} + + + {reply?.messageText && ( + + )} + + {reply?.decryptedData?.type === 'notification' ? ( + + ) : ( + + )} + + + + )} + + {htmlText && } + + {message?.decryptedData?.type === 'notification' ? ( + + ) : ( + + )} + {message?.images && messageHasImage(message) && ( + )} - - - {message?.senderName || message?.sender} - - - - - {message?.sender === myAddress && - (!message?.isNotEncrypted || isPrivate === false) && ( + {reactions && + Object.keys(reactions).map((reaction) => { + const numberOfReactions = reactions[reaction]?.length; + if (numberOfReactions === 0) return null; + return ( { - onEdit(message); + key={reaction} + sx={{ + background: theme.palette.background.surface, + borderRadius: '7px', + height: '30px', + minWidth: '45px', + }} + onClick={(event) => { + event.stopPropagation(); // Prevent event bubbling + setAnchorEl(event.currentTarget); + setSelectedReaction(reaction); }} > - +
+ {reaction} +
{' '} + {numberOfReactions > 1 && ( + + {numberOfReactions} + + )}
- )} + ); + })} +
- {!isShowingAsReply && ( - { - onReply(message); + {selectedReaction && ( + { + setAnchorEl(null); + setSelectedReaction(null); + }} + anchorOrigin={{ + vertical: 'top', + horizontal: 'center', + }} + transformOrigin={{ + vertical: 'bottom', + horizontal: 'center', + }} + slotProps={{ + paper: { + style: { + backgroundColor: theme.palette.background.default, + color: theme.palette.text.primary, + }, + }, + }} + > + + + {t('core:message.generic.people_reaction', { + reaction: selectedReaction, + postProcess: 'capitalizeFirstChar', + })} + + + - - - )} + {reactions[selectedReaction]?.map((reactionItem) => ( + + + + ))} + - {!isShowingAsReply && handleReaction && ( - { +
- - )} - - {htmlText && } - - {message?.decryptedData?.type === 'notification' ? ( - - ) : ( - - )} - {message?.images && messageHasImage(message) && ( - + )} - - {reactions && - Object.keys(reactions).map((reaction) => { - const numberOfReactions = reactions[reaction]?.length; - if (numberOfReactions === 0) return null; - return ( - { - event.stopPropagation(); // Prevent event bubbling - setAnchorEl(event.currentTarget); - setSelectedReaction(reaction); - }} - > -
- {reaction} -
{' '} - {numberOfReactions > 1 && ( - - {numberOfReactions} - - )} -
- ); - })} -
- - {selectedReaction && ( - { - setAnchorEl(null); - setSelectedReaction(null); + {message?.isNotEncrypted && isPrivate && ( + - - - {t('core:message.generic.people_reaction', { - reaction: selectedReaction, - postProcess: 'capitalizeFirstChar', - })} - - - - {reactions[selectedReaction]?.map((reactionItem) => ( - - - - ))} - - - - - + /> )} - - {message?.isNotEncrypted && isPrivate && ( - - )} - - {isUpdating ? ( - - {message?.status === 'failed-permanent' - ? t('core:message.error.update_failed', { - postProcess: 'capitalizeFirstChar', - }) - : t('core:message.generic.updating', { - postProcess: 'capitalizeFirstChar', - })} - - ) : isTemp ? ( - - {message?.status === 'failed-permanent' - ? t('core:message.error.send_failed', { - postProcess: 'capitalizeFirstChar', - }) - : t('core:message.generic.sending', { - postProcess: 'capitalizeFirstChar', - })} - - ) : ( - <> - {message?.isEdit && ( - - {t('core:message.generic.edited', { - postProcess: 'capitalizeFirstChar', - })} - - )} - + {isUpdating ? ( + + {message?.status === 'failed-permanent' + ? t('core:message.error.update_failed', { + postProcess: 'capitalizeFirstChar', + }) + : t('core:message.generic.updating', { + postProcess: 'capitalizeFirstChar', + })} + + ) : isTemp ? ( + + {message?.status === 'failed-permanent' + ? t('core:message.error.send_failed', { + postProcess: 'capitalizeFirstChar', + }) + : t('core:message.generic.sending', { + postProcess: 'capitalizeFirstChar', + })} + + ) : ( + <> + {message?.isEdit && ( - {formatTimestamp(message.timestamp)} + {t('core:message.generic.edited', { + postProcess: 'capitalizeFirstChar', + })} - - )} - + )} + + + {formatTimestamp(message.timestamp)} + + + )}
-
- - - ); - } -); + +
+
+ + ); +}; + +const MemoizedMessageItem = memo(MessageItemComponent); +MemoizedMessageItem.displayName = 'MessageItem'; // It ensures React DevTools shows MessageItem as the name (instead of "Anonymous" or "Memo") + +export const MessageItem = MemoizedMessageItem; export const ReplyPreview = ({ message, isEdit = false }) => { const theme = useTheme();