From 15e573116753d5f4808b46f5adc5b7e0ce61f910 Mon Sep 17 00:00:00 2001 From: Szymon Szlachtowicz <38212223+Szymx95@users.noreply.github.com> Date: Tue, 14 Dec 2021 00:55:09 +0100 Subject: [PATCH] Refactor chat messages (#156) --- .../src/components/ActivityCenter.tsx | 22 +- .../src/components/Chat/ChatBody.tsx | 4 +- .../src/components/Chat/ChatMessages.tsx | 470 ------------------ .../src/components/Form/ContactMenu.tsx | 2 +- .../src/components/Messages/MessageQuote.tsx | 81 +++ .../src/components/Messages/MessagesList.tsx | 95 ++++ .../src/components/Messages/Styles.tsx | 117 +++++ .../src/components/Messages/UiMessage.tsx | 197 ++++++++ 8 files changed, 504 insertions(+), 484 deletions(-) delete mode 100644 packages/react-chat/src/components/Chat/ChatMessages.tsx create mode 100644 packages/react-chat/src/components/Messages/MessageQuote.tsx create mode 100644 packages/react-chat/src/components/Messages/MessagesList.tsx create mode 100644 packages/react-chat/src/components/Messages/Styles.tsx create mode 100644 packages/react-chat/src/components/Messages/UiMessage.tsx diff --git a/packages/react-chat/src/components/ActivityCenter.tsx b/packages/react-chat/src/components/ActivityCenter.tsx index ba2be3cd..2a133056 100644 --- a/packages/react-chat/src/components/ActivityCenter.tsx +++ b/packages/react-chat/src/components/ActivityCenter.tsx @@ -11,17 +11,6 @@ import { equalDate } from "../utils/equalDate"; import { DownloadButton } from "./Buttons/DownloadButton"; import { buttonStyles } from "./Buttons/buttonStyle"; import { Mention } from "./Chat/ChatMessageContent"; -import { - ContentWrapper, - DateSeparator, - MessageHeaderWrapper, - MessageOuterWrapper, - MessageText, - TimeWrapper, - UserAddress, - UserName, - UserNameWrapper, -} from "./Chat/ChatMessages"; import { Logo } from "./CommunityIdentity"; import { ContactMenu } from "./Form/ContactMenu"; import { Tooltip } from "./Form/Tooltip"; @@ -37,6 +26,17 @@ import { ReplyIcon } from "./Icons/ReplyActivityIcon"; import { ShowIcon } from "./Icons/ShowIcon"; import { UntrustworthIcon } from "./Icons/UntrustworthIcon"; import { UserIcon } from "./Icons/UserIcon"; +import { + ContentWrapper, + DateSeparator, + MessageHeaderWrapper, + MessageOuterWrapper, + MessageText, + TimeWrapper, + UserAddress, + UserName, + UserNameWrapper, +} from "./Messages/Styles"; import { ProfileModalName } from "./Modals/ProfileModal"; import { textMediumStyles, textSmallStyles } from "./Text"; diff --git a/packages/react-chat/src/components/Chat/ChatBody.tsx b/packages/react-chat/src/components/Chat/ChatBody.tsx index d3749ca4..e958d782 100644 --- a/packages/react-chat/src/components/Chat/ChatBody.tsx +++ b/packages/react-chat/src/components/Chat/ChatBody.tsx @@ -4,13 +4,13 @@ import styled from "styled-components"; import { useMessengerContext } from "../../contexts/messengerProvider"; import { useNarrow } from "../../contexts/narrowProvider"; import { Reply } from "../../hooks/useReply"; +import { MessagesList } from "../Messages/MessagesList"; import { NarrowChannels } from "../NarrowMode/NarrowChannels"; import { NarrowMembers } from "../NarrowMode/NarrowMembers"; import { LoadingSkeleton } from "../Skeleton/LoadingSkeleton"; import { ChatCreation } from "./ChatCreation"; import { ChatInput } from "./ChatInput"; -import { ChatMessages } from "./ChatMessages"; import { ChatTopbar } from "./ChatTopbar"; export enum ChatBodyState { @@ -71,7 +71,7 @@ export function ChatBody({ onClick, showMembers }: ChatBodyProps) { {showState === ChatBodyState.Chat && ( <> {messenger && communityData ? ( - + ) : ( )} diff --git a/packages/react-chat/src/components/Chat/ChatMessages.tsx b/packages/react-chat/src/components/Chat/ChatMessages.tsx deleted file mode 100644 index 16c69614..00000000 --- a/packages/react-chat/src/components/Chat/ChatMessages.tsx +++ /dev/null @@ -1,470 +0,0 @@ -import React, { - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from "react"; -import { utils } from "status-communities/dist/cjs"; -import styled from "styled-components"; - -import { useActivities } from "../../contexts/activityProvider"; -import { useIdentity } from "../../contexts/identityProvider"; -import { useMessengerContext } from "../../contexts/messengerProvider"; -import { useModal } from "../../contexts/modalProvider"; -import { useChatScrollHandle } from "../../hooks/useChatScrollHandle"; -import { Reply } from "../../hooks/useReply"; -import { ChannelData } from "../../models/ChannelData"; -import { ChatMessage } from "../../models/ChatMessage"; -import { equalDate } from "../../utils"; -import { EmptyChannel } from "../Channels/EmptyChannel"; -import { ContactMenu } from "../Form/ContactMenu"; -import { Icon } from "../Icons/Icon"; -import { LoadingIcon } from "../Icons/LoadingIcon"; -import { QuoteSvg } from "../Icons/QuoteIcon"; -import { ReactionSvg } from "../Icons/ReactionIcon"; -import { ReplySvg } from "../Icons/ReplyIcon"; -import { UntrustworthIcon } from "../Icons/UntrustworthIcon"; -import { UserIcon } from "../Icons/UserIcon"; -import { LinkModal, LinkModalName } from "../Modals/LinkModal"; -import { PictureModal, PictureModalName } from "../Modals/PictureModal"; -import { textMediumStyles, textSmallStyles } from "../Text"; - -import { ReplyOn, ReplyTo } from "./ChatInput"; -import { ChatMessageContent } from "./ChatMessageContent"; - -const today = new Date(); - -function calcHeight(quote: ChatMessage) { - if (quote.image && quote.content) { - return 88; - } else if (quote.image && !quote.content) { - return 68; - } else { - return 25; - } -} - -type MessageQuoteProps = { - quote: ChatMessage | undefined; -}; - -function MessageQuote({ quote }: MessageQuoteProps) { - const quoteClick = useCallback(() => { - if (quote) { - const quoteDiv = document.getElementById(quote.id); - if (quoteDiv) { - quoteDiv.scrollIntoView({ - behavior: "smooth", - block: "center", - inline: "center", - }); - quoteDiv.style.background = "lightblue"; - quoteDiv.style.transition = "background-color 1000ms linear"; - window.setTimeout(() => { - quoteDiv.style.background = ""; - window.setTimeout(() => { - quoteDiv.style.transition = ""; - }, 1000); - }, 1000); - } - } - }, [quote]); - - if (quote) { - return ( - - - - {" "} - {quote.sender} - - {quote.content} - {quote.image && } - - ); - } - return null; -} - -type ChatUiMessageProps = { - idx: number; - message: ChatMessage; - channel: ChannelData; - prevMessage: ChatMessage; - setImage: (img: string) => void; - setLink: (link: string) => void; - setReply: (val: Reply | undefined) => void; - quote?: ChatMessage; -}; - -function ChatUiMessage({ - message, - channel, - idx, - prevMessage, - setImage, - setLink, - setReply, - quote, -}: ChatUiMessageProps) { - const { contacts } = useMessengerContext(); - const { setActivities } = useActivities(); - const identity = useIdentity(); - - const contact = useMemo( - () => contacts[message.sender], - [message.sender, contacts] - ); - const [showMenu, setShowMenu] = useState(false); - const [mentioned, setMentioned] = useState(false); - - useEffect(() => { - if (mentioned) - setActivities((prev) => [ - ...prev, - { - id: message.date.getTime().toString() + message.content, - type: "mention", - date: message.date, - user: message.sender, - message: message, - channel: channel, - }, - ]); - if (quote && quote.sender === utils.bufToHex(identity.publicKey)) - setActivities((prev) => [ - ...prev, - { - id: message.date.getTime().toString() + message.content, - type: "reply", - date: message.date, - user: message.sender, - message: message, - channel: channel, - quote: quote, - }, - ]); - }, [mentioned, message, quote]); - - return ( - - {(idx === 0 || !equalDate(prevMessage.date, message.date)) && ( - - {equalDate(message.date, today) - ? "Today" - : message.date.toLocaleDateString()} - - )} - - - - { - setShowMenu((e) => !e); - }} - > - {showMenu && ( - - )} - - - - - - - {" "} - {contact.customName ?? message.sender.slice(0, 10)} - - {contact.customName && ( - - {message.sender.slice(0, 5)}...{message.sender.slice(-3)} - - )} - {contact.isUntrustworthy && } - - {message.date.toLocaleString()} - - - - - - - - - - - - setReply({ - sender: message.sender, - content: message.content, - image: message.image, - id: message.id, - }) - } - > - - - - - - ); -} - -interface ChatMessagesProps { - setReply: (val: Reply | undefined) => void; -} - -export function ChatMessages({ setReply }: ChatMessagesProps) { - const { messages, activeChannel, contacts } = useMessengerContext(); - const ref = useRef(null); - const loadingMessages = useChatScrollHandle(messages, ref, activeChannel); - - const shownMessages = useMemo( - () => - messages.filter( - (message) => !contacts?.[message.sender]?.blocked ?? true - ), - [contacts, messages, messages.length] - ); - - const [image, setImage] = useState(""); - const [link, setLink] = useState(""); - - const { setModal: setPictureModal, isVisible: showPictureModal } = - useModal(PictureModalName); - const { setModal: setLinkModal, isVisible: showLinkModal } = - useModal(LinkModalName); - - useEffect(() => (!image ? undefined : setPictureModal(true)), [image]); - useEffect(() => (!link ? undefined : setLinkModal(true)), [link]); - - useEffect( - () => (!showPictureModal ? setImage("") : undefined), - [showPictureModal] - ); - useEffect(() => (!showLinkModal ? setLink("") : undefined), [showLinkModal]); - - return ( - - - - - {loadingMessages && ( - - - - )} - {shownMessages.map((message, idx) => ( - msg.id == message?.responseTo)} - /> - ))} - - ); -} - -const MessagesWrapper = styled.div` - display: flex; - flex-direction: column; - height: calc(100% - 44px); - overflow: auto; - padding: 8px 0; - - &::-webkit-scrollbar { - width: 0; - } -`; - -export const MessageWrapper = styled.div` - width: 100%; - display: flex; - flex-direction: column; - padding: 8px 16px; - border-left: 2px solid ${({ theme }) => theme.bodyBackgroundColor}; - position: relative; - - &:hover { - background: ${({ theme }) => theme.inputColor}; - border-color: ${({ theme }) => theme.inputColor}; - } - - &:hover > div { - visibility: visible; - } - - &.mention { - background: ${({ theme }) => theme.mentionBg}; - border-color: ${({ theme }) => theme.mentionColor}; - } - - &.mention:hover { - background: ${({ theme }) => theme.mentionBgHover}; - border-color: ${({ theme }) => theme.mentionColor}; - } -`; - -export const MessageOuterWrapper = styled.div` - width: 100%; - display: flex; - flex-direction: column; - position: relative; -`; - -const UserMessageWrapper = styled.div` - width: 100%; - display: flex; -`; - -export const DateSeparator = styled.div` - width: 100%; - display: flex; - flex: 1; - height: 100%; - text-align: center; - justify-content: center; - align-items: center; - font-family: "Inter"; - font-style: normal; - font-weight: 500; - color: #939ba1; - margin-top: 16px; - margin-bottom: 16px; - - ${textSmallStyles} -`; - -export const ContentWrapper = styled.div` - display: flex; - flex-direction: column; - margin-left: 8px; -`; - -export const MessageHeaderWrapper = styled.div` - display: flex; - align-items: center; -`; - -export const UserNameWrapper = styled.div` - display: flex; - align-items: center; -`; - -export const UserName = styled.p` - font-weight: 500; - color: ${({ theme }) => theme.tertiary}; - margin-right: 4px; - - ${textMediumStyles} -`; - -export const UserAddress = styled.p` - font-size: 10px; - line-height: 14px; - letter-spacing: 0.2px; - color: ${({ theme }) => theme.secondary}; - position: relative; - padding-right: 8px; - - &:after { - content: ""; - position: absolute; - right: 0; - top: 50%; - transform: translateY(-50%); - width: 4px; - height: 4px; - border-radius: 50%; - background: ${({ theme }) => theme.secondary}; - } -`; - -export const TimeWrapper = styled.div` - font-size: 10px; - line-height: 14px; - letter-spacing: 0.2px; - text-transform: uppercase; - color: ${({ theme }) => theme.secondary}; - margin-left: 4px; -`; - -export const MessageText = styled.div` - overflow-wrap: anywhere; - width: 100%; - white-space: pre-wrap; - color: ${({ theme }) => theme.primary}; -`; - -const LoadingWrapper = styled.div` - display: flex; - align-self: center; - align-items: center; - justify-content: center; - background: ${({ theme }) => theme.bodyBackgroundColor}; - position: relative; -`; - -const Reactions = styled.div` - display: flex; - position: absolute; - right: 20px; - top: -18px; - box-shadow: 0px 4px 12px rgba(0, 34, 51, 0.08); - border-radius: 8px; - background: ${({ theme }) => theme.bodyBackgroundColor}; - visibility: hidden; -`; - -const ReactionBtn = styled.button` - width: 32px; - height: 32px; - display: flex; - justify-content: center; - align-items: center; - border-radius: 8px; - align-self: center; - - &:hover { - background: ${({ theme }) => theme.buttonBgHover}; - } - - &:hover > svg { - fill: ${({ theme }) => theme.tertiary}; - } -`; - -const QuoteWrapper = styled.div` - display: flex; - flex-direction: column; - padding-left: 48px; - position: relative; -`; - -const QuoteSender = styled(ReplyTo)` - color: ${({ theme }) => theme.secondary}; -`; - -const Quote = styled(ReplyOn)` - color: ${({ theme }) => theme.secondary}; -`; - -const QuoteImage = styled.img` - width: 56px; - height: 56px; - border-radius: 4px; - margin-top: 4px; -`; diff --git a/packages/react-chat/src/components/Form/ContactMenu.tsx b/packages/react-chat/src/components/Form/ContactMenu.tsx index 8d36f5c2..af541a4b 100644 --- a/packages/react-chat/src/components/Form/ContactMenu.tsx +++ b/packages/react-chat/src/components/Form/ContactMenu.tsx @@ -5,7 +5,6 @@ import styled from "styled-components"; import { useIdentity } from "../../contexts/identityProvider"; import { useModal } from "../../contexts/modalProvider"; import { useManageContact } from "../../hooks/useManageContact"; -import { UserAddress } from "../Chat/ChatMessages"; import { AddContactSvg } from "../Icons/AddContactIcon"; import { BlockSvg } from "../Icons/BlockIcon"; import { ChatSvg } from "../Icons/ChatIcon"; @@ -14,6 +13,7 @@ import { ProfileSvg } from "../Icons/ProfileIcon"; import { UntrustworthIcon } from "../Icons/UntrustworthIcon"; import { UserIcon } from "../Icons/UserIcon"; import { WarningSvg } from "../Icons/WarningIcon"; +import { UserAddress } from "../Messages/Styles"; import { ProfileModalName } from "../Modals/ProfileModal"; import { textMediumStyles } from "../Text"; diff --git a/packages/react-chat/src/components/Messages/MessageQuote.tsx b/packages/react-chat/src/components/Messages/MessageQuote.tsx new file mode 100644 index 00000000..54dbaa6c --- /dev/null +++ b/packages/react-chat/src/components/Messages/MessageQuote.tsx @@ -0,0 +1,81 @@ +import React, { useCallback } from "react"; +import styled from "styled-components"; + +import { ChatMessage } from "../../models/ChatMessage"; +import { ReplyOn, ReplyTo } from "../Chat/ChatInput"; +import { QuoteSvg } from "../Icons/QuoteIcon"; +import { UserIcon } from "../Icons/UserIcon"; + +function calcHeight(quote: ChatMessage) { + if (quote.image && quote.content) { + return 88; + } else if (quote.image && !quote.content) { + return 68; + } else { + return 25; + } +} + +type MessageQuoteProps = { + quote: ChatMessage | undefined; +}; + +export function MessageQuote({ quote }: MessageQuoteProps) { + const quoteClick = useCallback(() => { + if (quote) { + const quoteDiv = document.getElementById(quote.id); + if (quoteDiv) { + quoteDiv.scrollIntoView({ + behavior: "smooth", + block: "center", + inline: "center", + }); + quoteDiv.style.background = "lightblue"; + quoteDiv.style.transition = "background-color 1000ms linear"; + window.setTimeout(() => { + quoteDiv.style.background = ""; + window.setTimeout(() => { + quoteDiv.style.transition = ""; + }, 1000); + }, 1000); + } + } + }, [quote]); + + if (quote) { + return ( + + + + {" "} + {quote.sender} + + {quote.content} + {quote.image && } + + ); + } + return null; +} + +const QuoteWrapper = styled.div` + display: flex; + flex-direction: column; + padding-left: 48px; + position: relative; +`; + +const QuoteSender = styled(ReplyTo)` + color: ${({ theme }) => theme.secondary}; +`; + +const Quote = styled(ReplyOn)` + color: ${({ theme }) => theme.secondary}; +`; + +const QuoteImage = styled.img` + width: 56px; + height: 56px; + border-radius: 4px; + margin-top: 4px; +`; diff --git a/packages/react-chat/src/components/Messages/MessagesList.tsx b/packages/react-chat/src/components/Messages/MessagesList.tsx new file mode 100644 index 00000000..8fce8819 --- /dev/null +++ b/packages/react-chat/src/components/Messages/MessagesList.tsx @@ -0,0 +1,95 @@ +import React, { useEffect, useMemo, useRef, useState } from "react"; +import styled from "styled-components"; + +import { useMessengerContext } from "../../contexts/messengerProvider"; +import { useModal } from "../../contexts/modalProvider"; +import { useChatScrollHandle } from "../../hooks/useChatScrollHandle"; +import { Reply } from "../../hooks/useReply"; +import { EmptyChannel } from "../Channels/EmptyChannel"; +import { LoadingIcon } from "../Icons/LoadingIcon"; +import { LinkModal, LinkModalName } from "../Modals/LinkModal"; +import { PictureModal, PictureModalName } from "../Modals/PictureModal"; + +import { UiMessage } from "./UiMessage"; + +interface MessagesListProps { + setReply: (val: Reply | undefined) => void; +} + +export function MessagesList({ setReply }: MessagesListProps) { + const { messages, activeChannel, contacts } = useMessengerContext(); + const ref = useRef(null); + const loadingMessages = useChatScrollHandle(messages, ref, activeChannel); + + const shownMessages = useMemo( + () => + messages.filter( + (message) => !contacts?.[message.sender]?.blocked ?? true + ), + [contacts, messages, messages.length] + ); + + const [image, setImage] = useState(""); + const [link, setLink] = useState(""); + + const { setModal: setPictureModal, isVisible: showPictureModal } = + useModal(PictureModalName); + const { setModal: setLinkModal, isVisible: showLinkModal } = + useModal(LinkModalName); + + useEffect(() => (!image ? undefined : setPictureModal(true)), [image]); + useEffect(() => (!link ? undefined : setLinkModal(true)), [link]); + + useEffect( + () => (!showPictureModal ? setImage("") : undefined), + [showPictureModal] + ); + useEffect(() => (!showLinkModal ? setLink("") : undefined), [showLinkModal]); + + return ( + + + + + {loadingMessages && ( + + + + )} + {shownMessages.map((message, idx) => ( + msg.id == message?.responseTo)} + /> + ))} + + ); +} + +const LoadingWrapper = styled.div` + display: flex; + align-self: center; + align-items: center; + justify-content: center; + background: ${({ theme }) => theme.bodyBackgroundColor}; + position: relative; +`; + +const MessagesWrapper = styled.div` + display: flex; + flex-direction: column; + height: calc(100% - 44px); + overflow: auto; + padding: 8px 0; + + &::-webkit-scrollbar { + width: 0; + } +`; diff --git a/packages/react-chat/src/components/Messages/Styles.tsx b/packages/react-chat/src/components/Messages/Styles.tsx new file mode 100644 index 00000000..f2d24b9a --- /dev/null +++ b/packages/react-chat/src/components/Messages/Styles.tsx @@ -0,0 +1,117 @@ +import styled from "styled-components"; + +import { textMediumStyles, textSmallStyles } from "../Text"; + +export const MessageWrapper = styled.div` + width: 100%; + display: flex; + flex-direction: column; + padding: 8px 16px; + border-left: 2px solid ${({ theme }) => theme.bodyBackgroundColor}; + position: relative; + + &:hover { + background: ${({ theme }) => theme.inputColor}; + border-color: ${({ theme }) => theme.inputColor}; + } + + &:hover > div { + visibility: visible; + } + + &.mention { + background: ${({ theme }) => theme.mentionBg}; + border-color: ${({ theme }) => theme.mentionColor}; + } + + &.mention:hover { + background: ${({ theme }) => theme.mentionBgHover}; + border-color: ${({ theme }) => theme.mentionColor}; + } +`; + +export const MessageOuterWrapper = styled.div` + width: 100%; + display: flex; + flex-direction: column; + position: relative; +`; + +export const DateSeparator = styled.div` + width: 100%; + display: flex; + flex: 1; + height: 100%; + text-align: center; + justify-content: center; + align-items: center; + font-family: "Inter"; + font-style: normal; + font-weight: 500; + color: #939ba1; + margin-top: 16px; + margin-bottom: 16px; + + ${textSmallStyles} +`; + +export const ContentWrapper = styled.div` + display: flex; + flex-direction: column; + margin-left: 8px; +`; + +export const MessageHeaderWrapper = styled.div` + display: flex; + align-items: center; +`; + +export const UserNameWrapper = styled.div` + display: flex; + align-items: center; +`; + +export const UserName = styled.p` + font-weight: 500; + color: ${({ theme }) => theme.tertiary}; + margin-right: 4px; + + ${textMediumStyles} +`; + +export const UserAddress = styled.p` + font-size: 10px; + line-height: 14px; + letter-spacing: 0.2px; + color: ${({ theme }) => theme.secondary}; + position: relative; + padding-right: 8px; + + &:after { + content: ""; + position: absolute; + right: 0; + top: 50%; + transform: translateY(-50%); + width: 4px; + height: 4px; + border-radius: 50%; + background: ${({ theme }) => theme.secondary}; + } +`; + +export const TimeWrapper = styled.div` + font-size: 10px; + line-height: 14px; + letter-spacing: 0.2px; + text-transform: uppercase; + color: ${({ theme }) => theme.secondary}; + margin-left: 4px; +`; + +export const MessageText = styled.div` + overflow-wrap: anywhere; + width: 100%; + white-space: pre-wrap; + color: ${({ theme }) => theme.primary}; +`; diff --git a/packages/react-chat/src/components/Messages/UiMessage.tsx b/packages/react-chat/src/components/Messages/UiMessage.tsx new file mode 100644 index 00000000..da0e947d --- /dev/null +++ b/packages/react-chat/src/components/Messages/UiMessage.tsx @@ -0,0 +1,197 @@ +import React, { useEffect, useMemo, useState } from "react"; +import { utils } from "status-communities/dist/cjs"; +import styled from "styled-components"; + +import { useActivities } from "../../contexts/activityProvider"; +import { useIdentity } from "../../contexts/identityProvider"; +import { useMessengerContext } from "../../contexts/messengerProvider"; +import { Reply } from "../../hooks/useReply"; +import { ChannelData } from "../../models/ChannelData"; +import { ChatMessage } from "../../models/ChatMessage"; +import { equalDate } from "../../utils"; +import { ChatMessageContent } from "../Chat/ChatMessageContent"; +import { ContactMenu } from "../Form/ContactMenu"; +import { Icon } from "../Icons/Icon"; +import { ReactionSvg } from "../Icons/ReactionIcon"; +import { ReplySvg } from "../Icons/ReplyIcon"; +import { UntrustworthIcon } from "../Icons/UntrustworthIcon"; +import { UserIcon } from "../Icons/UserIcon"; + +import { MessageQuote } from "./MessageQuote"; +import { + ContentWrapper, + DateSeparator, + MessageHeaderWrapper, + MessageOuterWrapper, + MessageText, + MessageWrapper, + TimeWrapper, + UserAddress, + UserName, + UserNameWrapper, +} from "./Styles"; + +type UiMessageProps = { + idx: number; + message: ChatMessage; + channel: ChannelData; + prevMessage: ChatMessage; + setImage: (img: string) => void; + setLink: (link: string) => void; + setReply: (val: Reply | undefined) => void; + quote?: ChatMessage; +}; + +export function UiMessage({ + message, + channel, + idx, + prevMessage, + setImage, + setLink, + setReply, + quote, +}: UiMessageProps) { + const today = new Date(); + const { contacts } = useMessengerContext(); + const { setActivities } = useActivities(); + const identity = useIdentity(); + + const contact = useMemo( + () => contacts[message.sender], + [message.sender, contacts] + ); + const [showMenu, setShowMenu] = useState(false); + const [mentioned, setMentioned] = useState(false); + + useEffect(() => { + if (mentioned) + setActivities((prev) => [ + ...prev, + { + id: message.date.getTime().toString() + message.content, + type: "mention", + date: message.date, + user: message.sender, + message: message, + channel: channel, + }, + ]); + if (quote && quote.sender === utils.bufToHex(identity.publicKey)) + setActivities((prev) => [ + ...prev, + { + id: message.date.getTime().toString() + message.content, + type: "reply", + date: message.date, + user: message.sender, + message: message, + channel: channel, + quote: quote, + }, + ]); + }, [mentioned, message, quote]); + + return ( + + {(idx === 0 || !equalDate(prevMessage.date, message.date)) && ( + + {equalDate(message.date, today) + ? "Today" + : message.date.toLocaleDateString()} + + )} + + + + { + setShowMenu((e) => !e); + }} + > + {showMenu && ( + + )} + + + + + + + {" "} + {contact.customName ?? message.sender.slice(0, 10)} + + {contact.customName && ( + + {message.sender.slice(0, 5)}...{message.sender.slice(-3)} + + )} + {contact.isUntrustworthy && } + + {message.date.toLocaleString()} + + + + + + + + + + + + setReply({ + sender: message.sender, + content: message.content, + image: message.image, + id: message.id, + }) + } + > + + + + + + ); +} + +const UserMessageWrapper = styled.div` + width: 100%; + display: flex; +`; + +const Reactions = styled.div` + display: flex; + position: absolute; + right: 20px; + top: -18px; + box-shadow: 0px 4px 12px rgba(0, 34, 51, 0.08); + border-radius: 8px; + background: ${({ theme }) => theme.bodyBackgroundColor}; + visibility: hidden; +`; + +const ReactionBtn = styled.button` + width: 32px; + height: 32px; + display: flex; + justify-content: center; + align-items: center; + border-radius: 8px; + align-self: center; + + &:hover { + background: ${({ theme }) => theme.buttonBgHover}; + } + + &:hover > svg { + fill: ${({ theme }) => theme.tertiary}; + } +`;