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 }; } let ipfsImage = null; let noUrlText = null; let urls = null; if ( text ) { const normalText = text.split( "$?$" )[ 0 ]; ipfsImage = text.split( "$?$" ).length > 1 ? text.split( "$?$" )[ 1 ] : null; const extractedTextAndUrl = extractTextAndUrls( normalText ); noUrlText = extractedTextAndUrl.noUrlText; urls = extractedTextAndUrl.urls; } 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` } /> ) } </> ) );