const { componentOwnerId, threadReply, selectedChat, sendMessage } = props; State.init({ img: null, }); const Container = styled.div` position: absolute; bottom: 16px; margin-left: 2.5rem; margin-right: 2.5rem; background-color: #1D1D21; padding-left: 16px; padding-right: 16px; padding-top: 12px; padding-bottom: 12px; display: flex; align-items: end; @media (min-width: 1025px) { width: calc(100% - 382px); gap: 8px; border-radius: 4px; } @media (max-width: 1024px) { position: fixed; left: 0; right: 0; bottom: 0px; border-top-left-radius: 4px; border-top-right-radius: 4px; gap: 4px; margin: 0px; padding-left: 8px; padding-right: 8px; padding-bottom: 12px; padding-top: 12px; } `; const EmojiPopupContainer = styled.div` position: absolute; bottom: 70px; right: 2.5rem; `; const UploadPopupContainer = styled.div` position: absolute; bottom: 70px; left: 2.5rem; @media (max-width: 1024px) { left: 30px; } `; const UploadContainer = styled.div` background-color: #0e0e10; border-radius: 0.375rem; width: fit-content; padding: 1rem; `; const [showEmojiPopup, setShowEmojiPopup] = useState(false); const [showUpload, setShowUpload] = useState(false); const [message, setMessage] = useState(""); const [showMarkdown, setShowMarkdown] = useState(false); const [uploadedFile, setUploadedFile] = useState(null); const [emojiSelectorOpen, setEmojiSelectorOpen] = useState(false); const updateShowMarkdown = useCallback(() => { if (message) { setShowMarkdown(!showMarkdown); } }, [message, showMarkdown]); const setImage = useCallback( (img) => { State.update({ img: img }); }, [state.img] ); const updateShowUpload = useCallback((state) => { setShowUpload(state); }, []); const handleMessageChange = useCallback((mesage) => { setMessage(mesage); }, []); const resetImage = useCallback(() => State.update({ img: null }), []); const resetFile = useCallback(() => setUploadedFile(null), []); const resetMessage = useCallback(() => setMessage(""), []); const markdownParser = (text) => { const toHTML = text.replace( /(\b(https?:\/\/[^\s<]+\/?)\b)|^(#####|####|###|##|#) (.*)$|(@everyone)|(@here)|(@[a-zA-Z0-9_.-]+(?<![-.0-9])(?:\.testnet|\.near))|<p><br><\/p>\s*/gim, (match, url, url2, heading, text, everyoneMention, hereMention, validMention) => { if (url || url2) { return `<a href="${url || url2}" class="url-link" target="_blank">${url || url2}</a>`; } else if (heading) { return text; } else if (everyoneMention) { return `<span class='mention-everyone'>@everyone</span>`; } else if (hereMention) { return `<span class='mention-here'>@here</span>`; } else if (validMention) { return `<span class='mention mention-user-${validMention.replace("@", "").replace(/\./g, '\\.').replace(/_/g, '\\_')}'>${validMention}</span>`; } else { return ''; } } ); return toHTML; } const handleSendMessage = useCallback(() => { if(markdownParser(message) !== "") { sendMessage(markdownParser(message), state.img, uploadedFile); } resetImage(); resetFile(); updateShowUpload(false); setEmojiSelectorOpen(false); }, [message, state.img, uploadedFile]); const [selectedEmoji, setSelectedEmoji] = useState(""); const IconSvg = styled.svg` margin-bottom: 8px; :hover { fill: #fff; } cursor: pointer; ` const Wrapper = styled.div` flex: 1; display: flex; align-items: end; `; const FullWidthWrapper = styled.div` width: 100%; display: flex; flex-direction: column; `; const IconUpload = ({onClick}) => ( <IconSvg onClick={onClick} xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="#686672" className="bi bi-plus-circle" viewBox="0 0 16 16"> <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/> <path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"/> </IconSvg> ); const EmojiContainer = styled.div` border-radius: 2px; margin-bottom: 4px; height: 26px; width: 26px; display: flex; justify-content: center; align-items: center; padding: 2px; cursor: pointer; .hidden-svg { visibility: hidden; position: absolute; z-index: -10; } .visible-svg { visibility: visible; } @media (max-width: 1024px) { display: none; } `; const IconEmoji = () => { const [hovered, setHovered] = useState(false); return ( <EmojiContainer onMouseEnter={() => setHovered(true)} onMouseLeave={() => setHovered(false)} > <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="#686672" className={`bi bi-emoji-wink ${hovered ? 'hidden-svg' : 'visible-svg'}`} viewBox="0 0 16 16" > <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/> <path d="M4.285 9.567a.5.5 0 0 1 .683.183A3.498 3.498 0 0 0 8 11.5a3.498 3.498 0 0 0 3.032-1.75.5.5 0 1 1 .866.5A4.498 4.498 0 0 1 8 12.5a4.498 4.498 0 0 1-3.898-2.25.5.5 0 0 1 .183-.683zM7 6.5C7 7.328 6.552 8 6 8s-1-.672-1-1.5S5.448 5 6 5s1 .672 1 1.5zm1.757-.437a.5.5 0 0 1 .68.194.934.934 0 0 0 .813.493c.339 0 .645-.19.813-.493a.5.5 0 1 1 .874.486A1.934 1.934 0 0 1 10.25 7.75c-.73 0-1.356-.412-1.687-1.007a.5.5 0 0 1 .194-.68z"/> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="#FFDD1D" className={`bi bi-emoji-wink-fill ${hovered ? 'visible-svg' : 'hidden-svg'}`} viewBox="0 0 16 16" > <path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0zM7 6.5C7 5.672 6.552 5 6 5s-1 .672-1 1.5S5.448 8 6 8s1-.672 1-1.5zM4.285 9.567a.5.5 0 0 0-.183.683A4.498 4.498 0 0 0 8 12.5a4.5 4.5 0 0 0 3.898-2.25.5.5 0 1 0-.866-.5A3.498 3.498 0 0 1 8 11.5a3.498 3.498 0 0 1-3.032-1.75.5.5 0 0 0-.683-.183zm5.152-3.31a.5.5 0 0 0-.874.486c.33.595.958 1.007 1.687 1.007.73 0 1.356-.412 1.687-1.007a.5.5 0 0 0-.874-.486.934.934 0 0 1-.813.493.934.934 0 0 1-.813-.493z"/> </svg> </EmojiContainer> ); }; const IconSendSvg = styled.svg` margin-bottom: 8px; :hover { fill: #4E95FF; } cursor: pointer; ` const IconSend = ({onClick, isActive}) => ( <IconSendSvg onClick={onClick} xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill={`${isActive ? "#4E95FF": "#686672"}`} class="bi bi-send-fill" viewBox="0 0 16 16"> <path d="M15.964.686a.5.5 0 0 0-.65-.65L.767 5.855H.766l-.452.18a.5.5 0 0 0-.082.887l.41.26.001.002 4.995 3.178 3.178 4.995.002.002.26.41a.5.5 0 0 0 .886-.083l6-15Zm-1.833 1.89L6.637 10.07l-.215-.338a.5.5 0 0 0-.154-.154l-.338-.215 7.494-7.494 1.178-.471-.47 1.178Z"/> </IconSendSvg> ); const Placeholder = styled.div` position: absolute; z-index: 10; bottom: 16px; left: 52px; color: #686672; font-size: 16px; font-style: normal; font-weight: 400; line-height: 150%; pointer-events: none; @media (max-width: 1024px) { font-size: 14px; font-style: normal; font-weight: 400; line-height: 150%; bottom: 16px; left: 40px; } `; const PlaceholderMarkdown = styled.div` position: absolute; z-index: 10; bottom: 12px; right: 80px; color: #686672; font-size: 12px; font-style: normal; font-weight: 400; line-height: 150%; pointer-events: none; @media (max-width: 1024px) { display: none; } `; return ( <> <Container> <IconUpload onClick={() => { setEmojiSelectorOpen(false); updateShowUpload(!showUpload); }}/> <Wrapper> <FullWidthWrapper> <MarkdownEditor setValue={setMessage} value={message} onChange={handleMessageChange} selectedEmoji={selectedEmoji} resetSelectedEmoji={() => setSelectedEmoji("")} handleMessageSent={handleSendMessage} /> </FullWidthWrapper> {(!message || message === "<p><br></p>") && <Placeholder> {threadReply ? `Reply in thread` : `Type message in ${selectedChat}`} </Placeholder>} {(!message || message === "<p><br></p>") && <PlaceholderMarkdown> <b>Markdown</b> is supported </PlaceholderMarkdown> } </Wrapper> <div onClick={() => setEmojiSelectorOpen(!emojiSelectorOpen)} > <IconEmoji/> </div> <IconSend onClick={() => { if(message && message !== "<p><br></p>") { handleSendMessage(); handleMessageChange(""); } }} isActive={message && message !== "<p><br></p>"}/> {emojiSelectorOpen && ( <EmojiPopupContainer> <Widget src={`${componentOwnerId}/widget/Calimero.Curb.EmojiSelector.EmojiSelector`} props={{ OnEmojiSelected: (emoji) => setSelectedEmoji(emoji), }} /> </EmojiPopupContainer> )} {showUpload && ( <UploadPopupContainer> <UploadContainer> {!uploadedFile && ( <Widget src={`${componentOwnerId}/widget/Calimero.Curb.Chat.UploadImage`} props={{ imageUp: state.img, uploadComponent: ( <IpfsImageUpload image={state.img} className="btn btn-secondary" /> ), resetImage: resetImage, }} /> )} {!state.img && ( <Widget src={`${componentOwnerId}/widget/Calimero.Curb.Chat.FileUpload`} props={{ uploadedFile: uploadedFile, setUploadedFile: setUploadedFile, resetFile: resetFile, }} /> )} </UploadContainer> </UploadPopupContainer> )} </Container> </> );