Reply (#146)
This commit is contained in:
parent
3d48c1fe98
commit
6338c79af0
|
@ -3,6 +3,7 @@ import styled from "styled-components";
|
|||
|
||||
import { useMessengerContext } from "../../contexts/messengerProvider";
|
||||
import { useNarrow } from "../../contexts/narrowProvider";
|
||||
import { Reply } from "../../hooks/useReply";
|
||||
import { Channel } from "../Channels/Channel";
|
||||
import { Community } from "../Community";
|
||||
import { ChannelMenu } from "../Form/ChannelMenu";
|
||||
|
@ -53,6 +54,8 @@ export function ChatBody({ onClick, showMembers }: ChatBodyProps) {
|
|||
}
|
||||
}, [narrow]);
|
||||
|
||||
const [reply, setReply] = useState<Reply | undefined>(undefined);
|
||||
|
||||
return (
|
||||
<ChatBodyWrapper className={className}>
|
||||
{editGroup && communityData ? (
|
||||
|
@ -118,11 +121,11 @@ export function ChatBody({ onClick, showMembers }: ChatBodyProps) {
|
|||
{showState === ChatBodyState.Chat && (
|
||||
<>
|
||||
{messenger && communityData ? (
|
||||
<ChatMessages />
|
||||
<ChatMessages setReply={setReply} />
|
||||
) : (
|
||||
<LoadingSkeleton />
|
||||
)}
|
||||
<ChatInput />
|
||||
<ChatInput reply={reply} setReply={setReply} />
|
||||
</>
|
||||
)}
|
||||
|
||||
|
@ -142,7 +145,7 @@ export function ChatBody({ onClick, showMembers }: ChatBodyProps) {
|
|||
) : (
|
||||
<>
|
||||
<LoadingSkeleton />
|
||||
<ChatInput />
|
||||
<ChatInput reply={reply} setReply={setReply} />
|
||||
</>
|
||||
)}
|
||||
</ChatBodyWrapper>
|
||||
|
|
|
@ -11,18 +11,26 @@ import styled, { useTheme } from "styled-components";
|
|||
import { useMessengerContext } from "../../contexts/messengerProvider";
|
||||
import { useModal } from "../../contexts/modalProvider";
|
||||
import { useLow } from "../../contexts/narrowProvider";
|
||||
import { Reply } from "../../hooks/useReply";
|
||||
import { lightTheme, Theme } from "../../styles/themes";
|
||||
import { uintToImgUrl } from "../../utils/uintToImgUrl";
|
||||
import { ClearSvg } from "../Icons/ClearIcon";
|
||||
import { EmojiIcon } from "../Icons/EmojiIcon";
|
||||
import { GifIcon } from "../Icons/GifIcon";
|
||||
import { PictureIcon } from "../Icons/PictureIcon";
|
||||
import { ReplySvg } from "../Icons/ReplyIcon";
|
||||
import { StickerIcon } from "../Icons/StickerIcon";
|
||||
import "emoji-mart/css/emoji-mart.css";
|
||||
import { SizeLimitModal, SizeLimitModalName } from "../Modals/SizeLimitModal";
|
||||
import { SearchBlock } from "../SearchBlock";
|
||||
import { textMediumStyles } from "../Text";
|
||||
import { textMediumStyles, textSmallStyles } from "../Text";
|
||||
|
||||
export function ChatInput() {
|
||||
interface ChatInputProps {
|
||||
reply: Reply | undefined;
|
||||
setReply: (val: Reply | undefined) => void;
|
||||
}
|
||||
|
||||
export function ChatInput({ reply, setReply }: ChatInputProps) {
|
||||
const { sendMessage } = useMessengerContext();
|
||||
const theme = useTheme() as Theme;
|
||||
const [content, setContent] = useState("");
|
||||
|
@ -107,6 +115,7 @@ export function ChatInput() {
|
|||
inputRef.current.innerHTML = "";
|
||||
}
|
||||
setContent("");
|
||||
setReply(undefined);
|
||||
}
|
||||
},
|
||||
[content, imageUint]
|
||||
|
@ -239,46 +248,65 @@ export function ChatInput() {
|
|||
}}
|
||||
/>
|
||||
</AddPictureInputWrapper>
|
||||
<Row style={{ height: `${inputHeight + (image ? 73 : 0)}px` }}>
|
||||
<InputWrapper>
|
||||
{image && (
|
||||
<ImagePreview src={image} onClick={() => setImageUint(undefined)} />
|
||||
)}
|
||||
<Input
|
||||
contentEditable
|
||||
onInput={onInputChange}
|
||||
onKeyDown={onInputKeyPress}
|
||||
onKeyUp={handleCursorChange}
|
||||
ref={inputRef}
|
||||
onClick={handleCursorChange}
|
||||
dangerouslySetInnerHTML={{ __html: clearComponent }}
|
||||
/>
|
||||
{query && (
|
||||
<SearchBlock
|
||||
query={query}
|
||||
dsicludeList={[]}
|
||||
onClick={addMention}
|
||||
onBotttom
|
||||
<InputArea>
|
||||
{reply && (
|
||||
<ReplyWrapper>
|
||||
<ReplyTo>
|
||||
{" "}
|
||||
<ReplySvg width={18} height={18} className="input" />{" "}
|
||||
{reply.sender}
|
||||
</ReplyTo>
|
||||
<ReplyOn>{reply.content}</ReplyOn>
|
||||
<CloseButton onClick={() => setReply(undefined)}>
|
||||
{" "}
|
||||
<ClearSvg width={20} height={20} className="input" />
|
||||
</CloseButton>
|
||||
</ReplyWrapper>
|
||||
)}
|
||||
<Row style={{ height: `${inputHeight + (image ? 73 : 0)}px` }}>
|
||||
<InputWrapper>
|
||||
{image && (
|
||||
<ImagePreview
|
||||
src={image}
|
||||
onClick={() => setImageUint(undefined)}
|
||||
/>
|
||||
)}
|
||||
<Input
|
||||
contentEditable
|
||||
onInput={onInputChange}
|
||||
onKeyDown={onInputKeyPress}
|
||||
onKeyUp={handleCursorChange}
|
||||
ref={inputRef}
|
||||
onClick={handleCursorChange}
|
||||
dangerouslySetInnerHTML={{ __html: clearComponent }}
|
||||
/>
|
||||
)}
|
||||
</InputWrapper>
|
||||
<InputButtons>
|
||||
<ChatButton
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setShowEmoji(!showEmoji);
|
||||
}}
|
||||
>
|
||||
<EmojiIcon isActive={showEmoji} />
|
||||
</ChatButton>
|
||||
<ChatButton>
|
||||
<StickerIcon />
|
||||
</ChatButton>
|
||||
<ChatButton>
|
||||
<GifIcon />
|
||||
</ChatButton>
|
||||
</InputButtons>
|
||||
</Row>
|
||||
{query && (
|
||||
<SearchBlock
|
||||
query={query}
|
||||
dsicludeList={[]}
|
||||
onClick={addMention}
|
||||
onBotttom
|
||||
/>
|
||||
)}
|
||||
</InputWrapper>
|
||||
<InputButtons>
|
||||
<ChatButton
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setShowEmoji(!showEmoji);
|
||||
}}
|
||||
>
|
||||
<EmojiIcon isActive={showEmoji} />
|
||||
</ChatButton>
|
||||
<ChatButton>
|
||||
<StickerIcon />
|
||||
</ChatButton>
|
||||
<ChatButton>
|
||||
<GifIcon />
|
||||
</ChatButton>
|
||||
</InputButtons>
|
||||
</Row>
|
||||
</InputArea>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
@ -297,6 +325,17 @@ const View = styled.div`
|
|||
position: relative;
|
||||
`;
|
||||
|
||||
const InputArea = styled.div`
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
max-height: 438px;
|
||||
padding: 2px;
|
||||
background: ${({ theme }) => theme.inputColor};
|
||||
border-radius: 16px 16px 4px 16px;
|
||||
`;
|
||||
|
||||
const Row = styled.div`
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
@ -393,3 +432,35 @@ const ChatButton = styled.button`
|
|||
width: 32px;
|
||||
height: 32px;
|
||||
`;
|
||||
|
||||
const CloseButton = styled(ChatButton)`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
`;
|
||||
const ReplyWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
max-height: 96px;
|
||||
padding: 6px 12px;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
color: ${({ theme }) => theme.primary};
|
||||
border-radius: 14px 14px 4px 14px;
|
||||
position: relative;
|
||||
|
||||
${textSmallStyles};
|
||||
`;
|
||||
|
||||
export const ReplyTo = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 500;
|
||||
`;
|
||||
|
||||
export const ReplyOn = styled.div`
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
`;
|
||||
|
|
|
@ -4,17 +4,22 @@ 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 { ChatMessage } from "../../models/ChatMessage";
|
||||
import { equalDate } from "../../utils";
|
||||
import { EmptyChannel } from "../Channels/EmptyChannel";
|
||||
import { ContactMenu } from "../Form/ContactMenu";
|
||||
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();
|
||||
|
@ -25,6 +30,7 @@ type ChatUiMessageProps = {
|
|||
prevMessage: ChatMessage;
|
||||
setImage: (img: string) => void;
|
||||
setLink: (link: string) => void;
|
||||
setReply: (val: Reply | undefined) => void;
|
||||
};
|
||||
|
||||
function ChatUiMessage({
|
||||
|
@ -33,6 +39,7 @@ function ChatUiMessage({
|
|||
prevMessage,
|
||||
setImage,
|
||||
setLink,
|
||||
setReply,
|
||||
}: ChatUiMessageProps) {
|
||||
const { contacts } = useMessengerContext();
|
||||
const contact = useMemo(
|
||||
|
@ -51,49 +58,78 @@ function ChatUiMessage({
|
|||
: message.date.toLocaleDateString()}
|
||||
</DateSeparator>
|
||||
)}
|
||||
<MessageWrapper className={`${mentioned && "mention"}`}>
|
||||
<Icon
|
||||
onClick={() => {
|
||||
setShowMenu((e) => !e);
|
||||
}}
|
||||
>
|
||||
{showMenu && (
|
||||
<ContactMenu id={message.sender} setShowMenu={setShowMenu} />
|
||||
)}
|
||||
<UserIcon />
|
||||
</Icon>
|
||||
|
||||
<ContentWrapper>
|
||||
<MessageHeaderWrapper>
|
||||
<UserNameWrapper>
|
||||
<UserName>
|
||||
{" "}
|
||||
{contact.customName ?? message.sender.slice(0, 10)}
|
||||
</UserName>
|
||||
{contact.customName && (
|
||||
<UserAddress>
|
||||
{message.sender.slice(0, 5)}...{message.sender.slice(-3)}
|
||||
</UserAddress>
|
||||
)}
|
||||
{contact.isUntrustworthy && <UntrustworthIcon />}
|
||||
</UserNameWrapper>
|
||||
<TimeWrapper>{message.date.toLocaleString()}</TimeWrapper>
|
||||
</MessageHeaderWrapper>
|
||||
<MessageText>
|
||||
<ChatMessageContent
|
||||
message={message}
|
||||
setImage={setImage}
|
||||
setLinkOpen={setLink}
|
||||
setMentioned={setMentioned}
|
||||
/>
|
||||
</MessageText>
|
||||
</ContentWrapper>
|
||||
<MessageWrapper className={`${mentioned && "mention"}`}>
|
||||
{message.quote && (
|
||||
<QuoteWrapper>
|
||||
<QuoteSvg width={22} height={25} />
|
||||
<QuoteAuthor>
|
||||
{" "}
|
||||
<UserIcon memberView={true} /> {message.quote.author}
|
||||
</QuoteAuthor>
|
||||
<Quote>{message.quote.content} </Quote>
|
||||
</QuoteWrapper>
|
||||
)}
|
||||
<UserMessageWrapper>
|
||||
<Icon
|
||||
onClick={() => {
|
||||
setShowMenu((e) => !e);
|
||||
}}
|
||||
>
|
||||
{showMenu && (
|
||||
<ContactMenu id={message.sender} setShowMenu={setShowMenu} />
|
||||
)}
|
||||
<UserIcon />
|
||||
</Icon>
|
||||
|
||||
<ContentWrapper>
|
||||
<MessageHeaderWrapper>
|
||||
<UserNameWrapper>
|
||||
<UserName>
|
||||
{" "}
|
||||
{contact.customName ?? message.sender.slice(0, 10)}
|
||||
</UserName>
|
||||
{contact.customName && (
|
||||
<UserAddress>
|
||||
{message.sender.slice(0, 5)}...{message.sender.slice(-3)}
|
||||
</UserAddress>
|
||||
)}
|
||||
{contact.isUntrustworthy && <UntrustworthIcon />}
|
||||
</UserNameWrapper>
|
||||
<TimeWrapper>{message.date.toLocaleString()}</TimeWrapper>
|
||||
</MessageHeaderWrapper>
|
||||
<MessageText>
|
||||
<ChatMessageContent
|
||||
message={message}
|
||||
setImage={setImage}
|
||||
setLinkOpen={setLink}
|
||||
setMentioned={setMentioned}
|
||||
/>
|
||||
</MessageText>
|
||||
</ContentWrapper>
|
||||
</UserMessageWrapper>
|
||||
<Reactions>
|
||||
<ReactionBtn>
|
||||
<ReactionSvg />
|
||||
</ReactionBtn>
|
||||
<ReactionBtn
|
||||
onClick={() =>
|
||||
setReply({ sender: message.sender, content: message.content })
|
||||
}
|
||||
>
|
||||
<ReplySvg width={22} height={22} />
|
||||
</ReactionBtn>
|
||||
</Reactions>
|
||||
</MessageWrapper>
|
||||
</MessageOuterWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export function ChatMessages() {
|
||||
interface ChatMessagesProps {
|
||||
setReply: (val: Reply | undefined) => void;
|
||||
}
|
||||
|
||||
export function ChatMessages({ setReply }: ChatMessagesProps) {
|
||||
const { messages, activeChannel, contacts } = useMessengerContext();
|
||||
const ref = useRef<HTMLHeadingElement>(null);
|
||||
const loadingMessages = useChatScrollHandle(messages, ref, activeChannel);
|
||||
|
@ -141,6 +177,7 @@ export function ChatMessages() {
|
|||
prevMessage={shownMessages[idx - 1]}
|
||||
setLink={setLink}
|
||||
setImage={setImage}
|
||||
setReply={setReply}
|
||||
/>
|
||||
))}
|
||||
</MessagesWrapper>
|
||||
|
@ -162,14 +199,20 @@ const MessagesWrapper = styled.div`
|
|||
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};
|
||||
|
@ -185,6 +228,12 @@ const MessageOuterWrapper = styled.div`
|
|||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const UserMessageWrapper = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const DateSeparator = styled.div`
|
||||
|
@ -288,3 +337,47 @@ const LoadingWrapper = styled.div`
|
|||
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 QuoteAuthor = styled(ReplyTo)`
|
||||
color: ${({ theme }) => theme.secondary};
|
||||
`;
|
||||
|
||||
const Quote = styled(ReplyOn)`
|
||||
color: ${({ theme }) => theme.secondary};
|
||||
`;
|
||||
|
|
|
@ -39,6 +39,10 @@ const Icon = styled(ClearSvg)`
|
|||
fill: ${({ theme }) => theme.secondary};
|
||||
}
|
||||
|
||||
&.input {
|
||||
fill: ${({ theme }) => theme.primary};
|
||||
}
|
||||
|
||||
&.profile > path {
|
||||
fill: ${({ theme }) => theme.bodyBackgroundColor};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
type QuoteProps = {
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
|
||||
export function QuoteSvg({ width, height }: QuoteProps) {
|
||||
return (
|
||||
<Icon
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox={`0 0 ${width} ${height}`}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M1.47619 69L1.05641 9.05601C1.02532 4.61595 4.61605 1 9.05622 1H21"
|
||||
strokeOpacity="0.4"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</Icon>
|
||||
);
|
||||
}
|
||||
|
||||
const Icon = styled.svg`
|
||||
& > path {
|
||||
stroke: ${({ theme }) => theme.secondary};
|
||||
}
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
`;
|
|
@ -0,0 +1,51 @@
|
|||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
type ReactionProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function ReactionSvg({ className }: ReactionProps) {
|
||||
return (
|
||||
<Icon
|
||||
width="22"
|
||||
height="22"
|
||||
viewBox="0 0 22 22"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<g clipPath="url(#clip0_1136_291537)">
|
||||
<path d="M18.3767 0C18.7564 0 19.0642 0.307804 19.0642 0.6875V2.42917C19.0642 2.73292 19.3104 2.97917 19.6142 2.97917H21.3132C21.6929 2.97917 22.0007 3.28697 22.0007 3.66667C22.0007 4.04636 21.6929 4.35417 21.3132 4.35417H19.6142C19.3104 4.35417 19.0642 4.60041 19.0642 4.90417V6.64583C19.0642 7.02553 18.7564 7.33333 18.3767 7.33333C17.997 7.33333 17.6892 7.02553 17.6892 6.64583V4.90417C17.6892 4.60041 17.443 4.35417 17.1392 4.35417H15.3526C14.9729 4.35417 14.6651 4.04636 14.6651 3.66667C14.6651 3.28697 14.9729 2.97917 15.3526 2.97917H17.1392C17.443 2.97917 17.6892 2.73292 17.6892 2.42917V0.6875C17.6892 0.307804 17.997 0 18.3767 0Z" />
|
||||
<path d="M13.092 2.7009C13.1869 2.40694 13.0511 2.05817 12.7478 1.99964C12.1822 1.89049 11.5981 1.83333 11.0007 1.83333C5.93804 1.83333 1.83398 5.93739 1.83398 11C1.83398 16.0626 5.93804 20.1667 11.0007 20.1667C16.0633 20.1667 20.1673 16.0626 20.1673 11C20.1673 10.4778 20.1237 9.96581 20.0398 9.46746C19.9864 9.1504 19.6165 9.01041 19.3135 9.11795C19.2552 9.13865 19.1959 9.15727 19.1357 9.17371C18.8607 9.24877 18.6561 9.51421 18.6998 9.79585C18.7607 10.1883 18.7923 10.5905 18.7923 11C18.7923 15.3032 15.3039 18.7917 11.0007 18.7917C6.69743 18.7917 3.20898 15.3032 3.20898 11C3.20898 6.69678 6.69743 3.20833 11.0007 3.20833C11.4768 3.20833 11.9429 3.25104 12.3955 3.33283C12.6896 3.38598 12.9716 3.16641 13.0424 2.87609C13.0569 2.81684 13.0735 2.75842 13.092 2.7009Z" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M5.40397 12.2118C5.65446 11.9242 6.04735 11.7708 6.45517 11.835C7.27533 11.9641 8.82139 12.1429 11.0937 12.1429C13.366 12.1429 14.912 11.9641 15.7322 11.835C16.14 11.7708 16.5329 11.9242 16.7834 12.2118C17.0426 12.5093 17.1437 12.9507 16.9535 13.3727C16.3725 14.6619 14.7535 17.1846 11.0937 17.1846C7.43386 17.1846 5.81485 14.6619 5.23388 13.3727C5.04369 12.9507 5.14481 12.5093 5.40397 12.2118ZM7.62413 13.3648C7.24863 13.3289 6.9887 13.7257 7.22435 14.0203C7.94812 14.925 9.15018 15.8096 11.0937 15.8096C13.0372 15.8096 14.2392 14.925 14.963 14.0203C15.1987 13.7257 14.9387 13.3289 14.5632 13.3648C13.6754 13.4498 12.5189 13.5179 11.0937 13.5179C9.66846 13.5179 8.51192 13.4498 7.62413 13.3648Z"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M6.50421 6.45233C6.76214 6.14281 7.22214 6.101 7.53165 6.35892L10.0328 8.44324C10.2688 8.63987 10.3563 8.96322 10.2518 9.25201C10.1472 9.5408 9.87295 9.73318 9.56581 9.73318C8.40917 9.73318 6.72438 10.1077 5.78393 10.6957C5.44231 10.9093 4.99222 10.8055 4.77862 10.4639C4.56503 10.1223 4.66881 9.67217 5.01043 9.45857C5.65306 9.05678 6.49574 8.75255 7.3457 8.55308C7.5168 8.51292 7.57278 8.29242 7.43776 8.1799L6.59761 7.47977C6.2881 7.22185 6.24628 6.76184 6.50421 6.45233Z"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M15.4971 6.45233C15.2392 6.14281 14.7792 6.101 14.4696 6.35892L11.9685 8.44324C11.7325 8.63987 11.645 8.96322 11.7496 9.25201C11.8541 9.5408 12.1284 9.73318 12.4355 9.73318C13.5921 9.73318 15.2769 10.1077 16.2174 10.6957C16.559 10.9093 17.0091 10.8055 17.2227 10.4639C17.4363 10.1223 17.3325 9.67217 16.9909 9.45857C16.3482 9.05678 15.5056 8.75255 14.6556 8.55308C14.4845 8.51292 14.4285 8.29242 14.5635 8.1799L15.4037 7.47977C15.7132 7.22185 15.755 6.76184 15.4971 6.45233Z"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1136_291537">
|
||||
<rect width="22" height="22" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</Icon>
|
||||
);
|
||||
}
|
||||
|
||||
const Icon = styled.svg`
|
||||
fill: ${({ theme }) => theme.secondary};
|
||||
|
||||
&:hover {
|
||||
fill: ${({ theme }) => theme.tertiary};
|
||||
}
|
||||
`;
|
|
@ -0,0 +1,30 @@
|
|||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
type ReplyProps = {
|
||||
width: number;
|
||||
height: number;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function ReplySvg({ width, height, className }: ReplyProps) {
|
||||
return (
|
||||
<Icon
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox="0 0 22 22"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
>
|
||||
<path d="M14.2368 11.4305C13.9683 11.162 13.533 11.162 13.2645 11.4305C12.996 11.699 12.996 12.1343 13.2645 12.4028L14.9751 14.1134C15.2639 14.4021 15.0594 14.8958 14.651 14.8958L11.0006 14.8958C7.83652 14.8958 5.27148 12.3308 5.27148 9.16667C5.27148 6.00254 7.83652 3.4375 11.0007 3.4375C11.3803 3.4375 11.6882 3.1297 11.6882 2.75C11.6882 2.3703 11.3803 2.0625 11.0007 2.0625C7.07713 2.0625 3.89648 5.24315 3.89648 9.16667C3.89648 13.0902 7.07713 16.2708 11.0006 16.2708H14.651C15.0594 16.2708 15.2639 16.7645 14.9751 17.0533L13.2645 18.7639C12.996 19.0324 12.996 19.4677 13.2645 19.7361C13.533 20.0046 13.9683 20.0046 14.2368 19.7361L17.9035 16.0695C18.1719 15.801 18.1719 15.3657 17.9035 15.0972L14.2368 11.4305Z" />
|
||||
</Icon>
|
||||
);
|
||||
}
|
||||
|
||||
const Icon = styled.svg`
|
||||
fill: ${({ theme }) => theme.secondary};
|
||||
|
||||
&.input {
|
||||
fill: ${({ theme }) => theme.primary};
|
||||
}
|
||||
`;
|
|
@ -0,0 +1,4 @@
|
|||
export type Reply = {
|
||||
sender: string;
|
||||
content: string;
|
||||
};
|
|
@ -7,12 +7,23 @@ export class ChatMessage {
|
|||
date: Date;
|
||||
sender: string;
|
||||
image?: string;
|
||||
quote?: {
|
||||
author: string;
|
||||
content: string;
|
||||
};
|
||||
|
||||
constructor(content: string, date: Date, sender: string, image?: string) {
|
||||
constructor(
|
||||
content: string,
|
||||
date: Date,
|
||||
sender: string,
|
||||
image?: string,
|
||||
quote?: { author: string; content: string }
|
||||
) {
|
||||
this.content = content;
|
||||
this.date = date;
|
||||
this.sender = sender;
|
||||
this.image = image;
|
||||
this.quote = quote;
|
||||
}
|
||||
|
||||
public static fromMetadataMessage(
|
||||
|
|
Loading…
Reference in New Issue