const Message = styled.div` width: 100%; display: flex; padding-bottom: 1rem; justify-content: flex-start; ${({ isTemporal }) => isTemporal && "opacity: 0.5;"} `; const SenderInfoContainer = styled.div` width: 100%; display: flex; align-items: center; column-gap: 0.5rem; display: flex; justify-content: flex-start; `; const ProfileIconContainerMsg = styled.div` display: flex; justify-content: center; align-items: center; width: 32px; height: 32px; border-radius: 50%; ${({ id }) => id && `background-color: #111;`} text-align: center; /* Body/Small */ font-family: Helvetica Neue; font-size: 14px; font-style: normal; font-weight: 400; line-height: 150%; /* 21px */ margin-left: -8px; `; const NameContainerSender = styled.div` display: flex; justify-content: start; align-items: center; width: 100%; color: #6c757d; font-size: 14px; font-style: normal; font-weight: 400; line-height: 100%; `; const TimeText = styled.p` color: #adb5bd; font-family: Helvetica Neue; font-size: 14px; font-style: normal; font-weight: 400; line-height: 100%; padding-top: 1rem; `; const ReactionsContainer = styled.div` display: none; position: absolute; z-index: 30; top: -2rem; left: 4rem; column-gap: 0.5rem; font-size: 1.5rem; line-height: 1.75rem; cursor: pointer; background: #0e0e10; border-radius: 4px; padding-left: 2px; padding-right: 2px; padding-top: 2px; padding-bottom: 0px; `; const MessageText = styled.div` max-width: 400px; position: relative; word-wrap: break-word; display: flex; flex-direction: column; padding: 1rem; color: #fff; font-family: Helvetica Neue; font-size: 16px; font-style: normal; font-weight: 400; line-height: 150%; border-radius: 0px 8px 8px 8px; ${({ ownMessage, clickable }) => ownMessage ? `background-color: #5765F2; ${ clickable && ":hover { background-color: #545CCD; cursor: pointer; }" }` : `background-color: #1E1F28; ${ clickable && ":hover { background-color: #3C3E52; cursor: pointer; }" }`} ${ReactionsContainer} { display: none; } /* When hovering over MessageText, display the ReactionsContainer */ &:hover ${ReactionsContainer} { display: flex; } `; const EmojiContainer = styled.p` border-radius: 4px; :hover { background-color: #5765f2; } text-align: center; display: flex; justify-content: center; align-items: center; `; const ReactedContainer = styled.div` display: flex; position: absolute; bottom: -0.75rem; right: 0.5rem; column-gap: 0.5rem; font-size: 1rem; line-height: 1rem; cursor: pointer; border-radius: 4px; padding-left: 2px; padding-right: 2px; padding-top: 2px; padding-bottom: 2px; `; const EmojiReactedContainer = styled.div` position: relative; `; const EmojiCountContainer = styled.div` font-size: 0.75rem; line-height: 1rem; position: absolute; bottom: -0.7rem; left: 0.5rem; background-color: #1d1d21; width: 1rem; height: 1rem; border-radius: 50%; text-align: center; `; const ImageContainer = styled.img` border-radius: 4px; `; const reactionsArray = [ { emoji: "😠", title: "Angry Face", }, { emoji: "❤️", title: "Red Heart", }, { emoji: "😀", title: "Grinning Face", }, { emoji: "😂", title: "Face with Tears of Joy", }, { emoji: "👍", title: "Thumbs Up", }, { emoji: "👎", title: "Thumbs Down", }, { emoji: "✅", title: "Check Mark Button", }, ]; const formatTimeAgo = (seconds) => { const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); const days = Math.floor(hours / 24); const weeks = Math.floor(days / 7); const months = Math.floor(weeks / 4); if (months > 0) { return `${months} month${months > 1 ? "s" : ""} ago`; } else if (weeks > 0) { return `${weeks} week${weeks > 1 ? "s" : ""} ago`; } else if (days > 0) { return `${days} day${days > 1 ? "s" : ""} ago`; } else if (hours > 0) { return `${hours} hour${hours > 1 ? "s" : ""} ago`; } else if (minutes > 0) { return `${minutes} minute${minutes > 1 ? "s" : ""} ago`; } else { return `just now`; } }; const parseReactions = (reactions) => { if (!reactions) { return []; } return Object.keys(reactions).map((reaction) => { const accountsForReaction = Array.isArray(reactions[reaction]) ? reactions[reaction] : []; return { reaction, accounts: accountsForReaction, }; }); }; const [messageReactions, setMessageReactions] = useState( parseReactions(props.message.reactions) ); useEffect(() => { const newReactions = parseReactions(props.message.reactions); if (newReactions !== messageReactions) { setMessageReactions(newReactions); } }, [props.message.reactions]); const handleNewReaction = (reaction) => { const emoji = reaction.emoji; let newMessageReactions = [...messageReactions]; const existingReactionIndex = newMessageReactions.findIndex( (r) => r.reaction === emoji ); if (existingReactionIndex !== -1) { const existingReaction = newMessageReactions[existingReactionIndex]; const hasUserReacted = existingReaction.accounts.includes( context.accountId ); if (hasUserReacted) { existingReaction.accounts = existingReaction.accounts.filter( (account) => account !== context.accountId ); if (!existingReaction.accounts.length) { newMessageReactions.splice(existingReactionIndex, 1); } } else { existingReaction.accounts.push(context.accountId); } } else { newMessageReactions.push({ reaction: emoji, accounts: [context.accountId], }); } setMessageReactions(newMessageReactions); props.addMessageReaction({ message_id: props.message.id, reaction: emoji, }); }; const text = props.message.text; const isTemporal = props.message.isTemporal; const overLayContainer = styled.div` z-index: 40; display: flex; text-align: center; padding-left: 0.5rem; padding-right: 0.5rem; padding-top: 0.25rem; padding-bottom: 0.25rem; background-color: #0e0e10; border: 1px solid #777583; border-radius: 0.375rem; font-size: 16px; padding-left: 24px; margin-top: 0.725rem; color: #fff; `; const MessageUrls = styled.a` cursor: pointer; text-decoration: none; color: #ffdd1d; cursor: pointer; :hover { color: #ffdd1d; text-decoration: underline; } :visited { color: #d0fc42; } `; const storageKey = "lastReportedMessage"; const oldReportedId = Storage.privateGet(storageKey); function extractTextAndUrls(inputText) { const urlRegex = /(https?:\/\/[^\s]+)/g; const urls = inputText.match(urlRegex) || []; const noUrlText = inputText.replace(urlRegex, ""); return { noUrlText, urls }; } const normalText = text.split("$?$")[0]; const ipfsImage = text.split("$?$").length > 1 ? text.split("$?$")[1] : null; const { noUrlText, urls } = extractTextAndUrls(normalText); const overlay = <overLayContainer>Reply in thread</overLayContainer>; return ( text && ( <> <Message ownMessage={props.message.sender === context.accountId} isTemporal={!!props.message.isTemporal} > <div> <SenderInfoContainer ownMessage={props.message.sender === context.accountId} > <div> <ProfileIconContainerMsg> <Widget src={`${props.componentOwnerId}/widget/Calimero.Curb.ProfileIcon.UserProfileIcon`} props={{ accountId: props.message.sender, showStatus: false, componentOwnerId: props.componentOwnerId, }} /> </ProfileIconContainerMsg> </div> <div> <NameContainerSender>{props.message.sender}</NameContainerSender> </div> <TimeText> {formatTimeAgo((Date.now() - props.message.timestamp) / 1000)} </TimeText> </SenderInfoContainer> <MessageText ownMessage={props.message.sender === context.accountId} clickable={noUrlText.split("$?$").length > 1} > {noUrlText.split("$?$")[0]} <ReactionsContainer ownMessage={props.message.sender === context.accountId} > {reactionsArray && ( <> {reactionsArray?.map((reaction, id) => ( <EmojiContainer onClick={() => handleNewReaction(reaction)} key={id} > {reaction.emoji} </EmojiContainer> ))} {!props.isThread && ( <EmojiContainer onClick={() => props.setThread(props.message.id)} > <OverlayTrigger show={state.show} trigger={["hover", "focus"]} delay={{ show: 250, hide: 300 }} placement="auto" overlay={overlay} > <i class="bi bi-reply"></i> </OverlayTrigger> </EmojiContainer> )} </> )} </ReactionsContainer> {ipfsImage && ( <ImageContainer src={ipfsImage} alt="uploaded" style={{ maxHeight: "400px", maxWidth: "400px" }} onClick={() => { if (ipfsImage) { props.setImage(ipfsImage); } }} /> )} {messageReactions.length > 0 && ( <ReactedContainer> {messageReactions?.map((reaction, id) => ( <> {reaction.accounts.length > 0 && ( <EmojiReactedContainer key={id}> {reaction.reaction} <EmojiCountContainer> {reaction.accounts.length.toString()} </EmojiCountContainer> </EmojiReactedContainer> )} </> ))} </ReactedContainer> )} {urls.length > 0 && urls.map((url) => { return ( <MessageUrls href={url} target="_blank"> {url} </MessageUrls> ); })} </MessageText> {props.message.thread.length > 0 && ( <Widget src={`${props.componentOwnerId}/widget/Calimero.Curb.Chat.ReplyContainerButton`} props={{ ownMessage: props.message.sender === context.accountId, replyCount: props.message.thread.length, onClick: () => { props.setThread(props.message.id); }, time: formatTimeAgo( (Date.now() - props.message.thread[props.message.thread.length - 1] .timestamp) / 1000 ), }} /> )} </div> </Message> {oldReportedId === props.message.id && props.lastMessageId !== oldReportedId && props.message.sender !== context.accountId && ( <Widget src={`${props.componentOwnerId}/widget/Calimero.Curb.Chat.UnreadBadge`} /> )} </> ) );