const TheadsContainer = styled.div` width: 100%; background-color: #0e0e10; padding-left: 1.125rem; padding-right: 1.125rem; pading-top: 2rem; padding-bottom: 1rem; color: #fff; @media (max-width: 1024px) { padding-bottom: 1rem; padding-left: 0rem; padding-right: 0rem; margin-bottom: 10px; } `; const CloseButton = styled.div` color: #fff; :hover { color: #5765f2; } z-index: 40; relative: absolute; margin-top: 10px; top: 10px; right: 1rem; cursor: pointer; `; const ReplyMessageContainer = styled.div` padding-top: 1rem; font-size: 18px; font-weight: 700; padding-bottom: 1rem; border-bottom: 1px solid #777583; @media (max-width: 1024px) { font-size: 14px; font-weight: 400; padding-left: 18px; padding-right: 18px; } `; 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; `; 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 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 ImageContainer = styled.img` border-radius: 4px; cursor: pointer; max-width: 400px; max-height: 400px; @media (max-width: 1024px) { max-width: 300px; max-height: 300px; } `; const ReplyTextContainer = styled.div` max-width: 400px; position: relative; word-wrap: break-word; display: flex; flex-direction: column; row-gap: 20px; font-size: 16px; font-style: normal; font-weight: 400; line-height: 100%; @media (max-width: 1024px) { font-size: 14px; max-width: 320px; } `; const Header = styled.div` display: flex; justify-content: space-between; `; const MessageUrls = styled.a` cursor: pointer; text-decoration: none; color: #ffdd1d; cursor: pointer; :hover { color: #ffdd1d; text-decoration: underline; } :visited { color: #d0fc42; } `; console.log("THREAD message", props.message); const text = props.message.text; const markdownParser = (text) => { const toHTML = text .replace(/^##### (.*$)/gim, "$1") // h5 tag .replace(/^#### (.*$)/gim, "$1") // h4 tag .replace(/^### (.*$)/gim, "$1") // h3 tag .replace(/^## (.*$)/gim, "$1") // h2 tag .replace(/^# (.*$)/gim, "$1") // h1 tag .replace(/\*\*(.*)\*\*/gim, "<b>$1</b>") // bold text .replace(/\*(.*)\*/gim, "<i>$1</i>") // italic text .replace(/^- (.*)$/gim, "<li>$1</li>"); // list item return toHTML.trim(); // using trim method to remove whitespace }; let finalText = ""; let ipfsImage = null; let ipfsFile = null; let ipfsFileName = null; if (text) { let normalText = ""; if (text.split("$?$").length > 1) { normalText = text.split("$?$")[0]; } else if (text.split("@?$@").length > 1) { normalText = text.split("@?$@")[0]; } else { normalText = text; } ipfsImage = text.split("$?$").length > 1 ? text.split("$?$")[1] : null; ipfsFile = text.split("@?$@").length > 1 ? text.split("@?$@")[1].split("?fileName=")[0] : null; if (ipfsFile) { ipfsFileName = text.split("@?$@")[1].split("?fileName=")[1]; } const mentionRegex = /^@(([a-z\d]+[-_])*[a-z\d]+\.)*([a-z\d]+[-_])*[a-z\d]+$/; const urlRegex = /(https?:\/\/[^\s]+)/g; const replacedMentionsText = normalText.replace( mentionRegex, (_match, username) => { return `<span class="mention" style="${ username === context.accountId ? "color: #FEAE37; background-color: #83522F" : "color: #fff; background-color: #343C93" }">@${username}</span>`; } ); const replacedUrlText = replacedMentionsText.replace( urlRegex, (_match, url) => { return `<a href=${url} class="url-link">${url}</a>`; } ); finalText = ` <style> .url-link { cursor: pointer; text-decoration: none; color: #ffdd1d; cursor: pointer; } .url-link:hover { color: #ffdd1d; text-decoration: underline; } .url-link:visited { color: #d0fc42; } .text-container { color: #fff; font-family: Helvetica Neue; font-size: 16px; font-style: normal; font-weight: 400; line-height: 150%; } </style> <script> const handleMessage = (text) => { document.getElementById("text-container").innerHTML = text; }; window.iFrameResizer = { onMessage: handleMessage } </script> <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.6/iframeResizer.contentWindow.js"></script> <div class="text-container"> ${markdownParser(replacedUrlText)} </div> `; } return ( <TheadsContainer> <ReplyMessageContainer> <Header> <div>Thread</div> <CloseButton onClick={() => props.setOpenThreadId(undefined)}> <i className="bi bi-x-lg"></i> </CloseButton> </Header> <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> <ReplyTextContainer> <iframe iframeResizer srcDoc={finalText} class="styled-iframe" className="w-100" /> {ipfsImage && ( <ImageContainer src={ipfsImage} alt="uploaded" style={{ maxHeight: "400px", maxWidth: "400px" }} onClick={() => { if (ipfsImage) { props.setImage(ipfsImage); } }} /> )} {ipfsFile && ( <div className="d-flex gap-1"> <i class="bi bi-file-earmark-arrow-down-fill text-light"></i> <MessageUrls href={ipfsFile} target="_blank"> {ipfsFileName} </MessageUrls> </div> )} </ReplyTextContainer> </ReplyMessageContainer> {props.message.thread.length > 0 && props.message.thread.map((message) => ( <Widget src={`${props.componentOwnerId}/widget/Calimero.Curb.Chat.UserMessage`} props={{ componentOwnerId: props.componentOwnerId, message, addMessageReaction: props.addMessageReaction, isThread: true, setImage: (src) => props.setImage(src), }} key={message.id} /> ))} </TheadsContainer> );