Use <Message /> for group story replies
This commit is contained in:
parent
dca848389c
commit
5dc42122a8
8 changed files with 169 additions and 78 deletions
|
@ -34,7 +34,6 @@
|
||||||
@include color-svg('../images/icons/v2/x-24.svg', $color-gray-15);
|
@include color-svg('../images/icons/v2/x-24.svg', $color-gray-15);
|
||||||
}
|
}
|
||||||
right: 28px;
|
right: 28px;
|
||||||
top: 0;
|
|
||||||
z-index: $z-index-above-above-base;
|
z-index: $z-index-above-above-base;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
&__replies {
|
&__replies {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
margin: 0 -16px;
|
||||||
max-height: 75vh;
|
max-height: 75vh;
|
||||||
overflow-y: overlay;
|
overflow-y: overlay;
|
||||||
|
|
||||||
|
@ -127,7 +128,7 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 12px 0;
|
padding: 12px 16px;
|
||||||
|
|
||||||
&--container {
|
&--container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -10,6 +10,7 @@ import enMessages from '../../_locales/en/messages.json';
|
||||||
import { IMAGE_JPEG } from '../types/MIME';
|
import { IMAGE_JPEG } from '../types/MIME';
|
||||||
import { SendStatus } from '../messages/MessageSendState';
|
import { SendStatus } from '../messages/MessageSendState';
|
||||||
import { StoryViewsNRepliesModal } from './StoryViewsNRepliesModal';
|
import { StoryViewsNRepliesModal } from './StoryViewsNRepliesModal';
|
||||||
|
import { UUID } from '../types/UUID';
|
||||||
import { fakeAttachment } from '../test-both/helpers/fakeAttachment';
|
import { fakeAttachment } from '../test-both/helpers/fakeAttachment';
|
||||||
import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation';
|
import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation';
|
||||||
import { setupI18n } from '../util/setupI18n';
|
import { setupI18n } from '../util/setupI18n';
|
||||||
|
@ -69,6 +70,9 @@ function getViewsAndReplies() {
|
||||||
const p3 = getDefaultConversation();
|
const p3 = getDefaultConversation();
|
||||||
const p4 = getDefaultConversation();
|
const p4 = getDefaultConversation();
|
||||||
const p5 = getDefaultConversation();
|
const p5 = getDefaultConversation();
|
||||||
|
const p6 = getDefaultConversation({
|
||||||
|
isMe: true,
|
||||||
|
});
|
||||||
|
|
||||||
const views = [
|
const views = [
|
||||||
{
|
{
|
||||||
|
@ -100,20 +104,52 @@ function getViewsAndReplies() {
|
||||||
|
|
||||||
const replies = [
|
const replies = [
|
||||||
{
|
{
|
||||||
...p2,
|
author: p2,
|
||||||
body: 'So cute ❤️',
|
body: 'So cute ❤️',
|
||||||
|
conversationId: p2.id,
|
||||||
|
id: UUID.generate().toString(),
|
||||||
timestamp: Date.now() - 24 * durations.MINUTE,
|
timestamp: Date.now() - 24 * durations.MINUTE,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...p3,
|
author: p3,
|
||||||
body: "That's awesome",
|
body: "That's awesome",
|
||||||
|
conversationId: p3.id,
|
||||||
|
id: UUID.generate().toString(),
|
||||||
timestamp: Date.now() - 13 * durations.MINUTE,
|
timestamp: Date.now() - 13 * durations.MINUTE,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...p4,
|
author: p3,
|
||||||
|
body: 'Very awesome',
|
||||||
|
conversationId: p3.id,
|
||||||
|
id: UUID.generate().toString(),
|
||||||
|
timestamp: Date.now() - 13 * durations.MINUTE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
author: p3,
|
||||||
|
body: 'Did I mention how awesome this is?',
|
||||||
|
conversationId: p3.id,
|
||||||
|
id: UUID.generate().toString(),
|
||||||
|
timestamp: Date.now() - 12 * durations.MINUTE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
author: p4,
|
||||||
|
conversationId: p4.id,
|
||||||
|
id: UUID.generate().toString(),
|
||||||
reactionEmoji: '❤️',
|
reactionEmoji: '❤️',
|
||||||
timestamp: Date.now() - 5 * durations.MINUTE,
|
timestamp: Date.now() - 5 * durations.MINUTE,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
author: p6,
|
||||||
|
body: 'Thanks everyone!',
|
||||||
|
conversationId: p6.id,
|
||||||
|
id: UUID.generate().toString(),
|
||||||
|
sendStateByConversationId: {
|
||||||
|
[p1.id]: {
|
||||||
|
status: SendStatus.Pending,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
timestamp: Date.now(),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { CompositionInput } from './CompositionInput';
|
||||||
import { ContactName } from './conversation/ContactName';
|
import { ContactName } from './conversation/ContactName';
|
||||||
import { EmojiButton } from './emoji/EmojiButton';
|
import { EmojiButton } from './emoji/EmojiButton';
|
||||||
import { Emojify } from './conversation/Emojify';
|
import { Emojify } from './conversation/Emojify';
|
||||||
import { MessageBody } from './conversation/MessageBody';
|
import { Message, TextDirection } from './conversation/Message';
|
||||||
import { MessageTimestamp } from './conversation/MessageTimestamp';
|
import { MessageTimestamp } from './conversation/MessageTimestamp';
|
||||||
import { Modal } from './Modal';
|
import { Modal } from './Modal';
|
||||||
import { Quote } from './conversation/Quote';
|
import { Quote } from './conversation/Quote';
|
||||||
|
@ -24,8 +24,58 @@ import { ReactionPicker } from './conversation/ReactionPicker';
|
||||||
import { Tabs } from './Tabs';
|
import { Tabs } from './Tabs';
|
||||||
import { Theme } from '../util/theme';
|
import { Theme } from '../util/theme';
|
||||||
import { ThemeType } from '../types/Util';
|
import { ThemeType } from '../types/Util';
|
||||||
|
import { WidthBreakpoint } from './_util';
|
||||||
import { getAvatarColor } from '../types/Colors';
|
import { getAvatarColor } from '../types/Colors';
|
||||||
import { getStoryReplyText } from '../util/getStoryReplyText';
|
import { getStoryReplyText } from '../util/getStoryReplyText';
|
||||||
|
import { shouldNeverBeCalled } from '../util/shouldNeverBeCalled';
|
||||||
|
|
||||||
|
// Menu is disabled so these actions are inaccessible. We also don't support
|
||||||
|
// link previews, tap to view messages, attachments, or gifts. Just regular
|
||||||
|
// text messages and reactions.
|
||||||
|
const MESSAGE_DEFAULT_PROPS = {
|
||||||
|
canDeleteForEveryone: false,
|
||||||
|
canDownload: false,
|
||||||
|
canReact: false,
|
||||||
|
canReply: false,
|
||||||
|
canRetry: false,
|
||||||
|
canRetryDeleteForEveryone: false,
|
||||||
|
checkForAccount: shouldNeverBeCalled,
|
||||||
|
clearSelectedMessage: shouldNeverBeCalled,
|
||||||
|
containerWidthBreakpoint: WidthBreakpoint.Medium,
|
||||||
|
deleteMessage: shouldNeverBeCalled,
|
||||||
|
deleteMessageForEveryone: shouldNeverBeCalled,
|
||||||
|
displayTapToViewMessage: shouldNeverBeCalled,
|
||||||
|
doubleCheckMissingQuoteReference: shouldNeverBeCalled,
|
||||||
|
downloadAttachment: shouldNeverBeCalled,
|
||||||
|
isBlocked: false,
|
||||||
|
isMessageRequestAccepted: true,
|
||||||
|
kickOffAttachmentDownload: shouldNeverBeCalled,
|
||||||
|
markAttachmentAsCorrupted: shouldNeverBeCalled,
|
||||||
|
markViewed: shouldNeverBeCalled,
|
||||||
|
messageExpanded: shouldNeverBeCalled,
|
||||||
|
openConversation: shouldNeverBeCalled,
|
||||||
|
openGiftBadge: shouldNeverBeCalled,
|
||||||
|
openLink: shouldNeverBeCalled,
|
||||||
|
previews: [],
|
||||||
|
reactToMessage: shouldNeverBeCalled,
|
||||||
|
renderAudioAttachment: () => <div />,
|
||||||
|
renderEmojiPicker: () => <div />,
|
||||||
|
renderReactionPicker: () => <div />,
|
||||||
|
replyToMessage: shouldNeverBeCalled,
|
||||||
|
retryDeleteForEveryone: shouldNeverBeCalled,
|
||||||
|
retrySend: shouldNeverBeCalled,
|
||||||
|
scrollToQuotedMessage: shouldNeverBeCalled,
|
||||||
|
showContactDetail: shouldNeverBeCalled,
|
||||||
|
showContactModal: shouldNeverBeCalled,
|
||||||
|
showExpiredIncomingTapToViewToast: shouldNeverBeCalled,
|
||||||
|
showExpiredOutgoingTapToViewToast: shouldNeverBeCalled,
|
||||||
|
showForwardMessageModal: shouldNeverBeCalled,
|
||||||
|
showMessageDetail: shouldNeverBeCalled,
|
||||||
|
showVisualAttachment: shouldNeverBeCalled,
|
||||||
|
startConversation: shouldNeverBeCalled,
|
||||||
|
theme: ThemeType.dark,
|
||||||
|
viewStory: shouldNeverBeCalled,
|
||||||
|
};
|
||||||
|
|
||||||
enum Tab {
|
enum Tab {
|
||||||
Replies = 'Replies',
|
Replies = 'Replies',
|
||||||
|
@ -79,6 +129,7 @@ export const StoryViewsNRepliesModal = ({
|
||||||
storyPreviewAttachment,
|
storyPreviewAttachment,
|
||||||
views,
|
views,
|
||||||
}: PropsType): JSX.Element | null => {
|
}: PropsType): JSX.Element | null => {
|
||||||
|
const containerElementRef = useRef<HTMLDivElement | null>(null);
|
||||||
const inputApiRef = useRef<InputApi | undefined>();
|
const inputApiRef = useRef<InputApi | undefined>();
|
||||||
const [bottom, setBottom] = useState<HTMLDivElement | null>(null);
|
const [bottom, setBottom] = useState<HTMLDivElement | null>(null);
|
||||||
const [messageBodyText, setMessageBodyText] = useState('');
|
const [messageBodyText, setMessageBodyText] = useState('');
|
||||||
|
@ -211,30 +262,34 @@ export const StoryViewsNRepliesModal = ({
|
||||||
|
|
||||||
if (replies.length) {
|
if (replies.length) {
|
||||||
repliesElement = (
|
repliesElement = (
|
||||||
<div className="StoryViewsNRepliesModal__replies">
|
<div
|
||||||
{replies.map(reply =>
|
className="StoryViewsNRepliesModal__replies"
|
||||||
|
ref={containerElementRef}
|
||||||
|
>
|
||||||
|
{replies.map((reply, index) =>
|
||||||
reply.reactionEmoji ? (
|
reply.reactionEmoji ? (
|
||||||
<div className="StoryViewsNRepliesModal__reaction" key={reply.id}>
|
<div className="StoryViewsNRepliesModal__reaction" key={reply.id}>
|
||||||
<div className="StoryViewsNRepliesModal__reaction--container">
|
<div className="StoryViewsNRepliesModal__reaction--container">
|
||||||
<Avatar
|
<Avatar
|
||||||
acceptedMessageRequest={reply.acceptedMessageRequest}
|
acceptedMessageRequest={reply.author.acceptedMessageRequest}
|
||||||
avatarPath={reply.avatarPath}
|
avatarPath={reply.author.avatarPath}
|
||||||
badge={undefined}
|
badge={getPreferredBadge(reply.author.badges)}
|
||||||
color={getAvatarColor(reply.color)}
|
color={getAvatarColor(reply.author.color)}
|
||||||
conversationType="direct"
|
conversationType="direct"
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={Boolean(reply.isMe)}
|
isMe={Boolean(reply.author.isMe)}
|
||||||
name={reply.name}
|
name={reply.author.name}
|
||||||
profileName={reply.profileName}
|
profileName={reply.author.profileName}
|
||||||
sharedGroupNames={reply.sharedGroupNames || []}
|
sharedGroupNames={reply.author.sharedGroupNames || []}
|
||||||
size={AvatarSize.TWENTY_EIGHT}
|
size={AvatarSize.TWENTY_EIGHT}
|
||||||
title={reply.title}
|
theme={ThemeType.dark}
|
||||||
|
title={reply.author.title}
|
||||||
/>
|
/>
|
||||||
<div className="StoryViewsNRepliesModal__reaction--body">
|
<div className="StoryViewsNRepliesModal__reaction--body">
|
||||||
<div className="StoryViewsNRepliesModal__reply--title">
|
<div className="StoryViewsNRepliesModal__reply--title">
|
||||||
<ContactName
|
<ContactName
|
||||||
contactNameColor={reply.contactNameColor}
|
contactNameColor={reply.contactNameColor}
|
||||||
title={reply.title}
|
title={reply.author.title}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{i18n('StoryViewsNRepliesModal__reacted')}
|
{i18n('StoryViewsNRepliesModal__reacted')}
|
||||||
|
@ -249,54 +304,34 @@ export const StoryViewsNRepliesModal = ({
|
||||||
<Emojify text={reply.reactionEmoji} />
|
<Emojify text={reply.reactionEmoji} />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="StoryViewsNRepliesModal__reply" key={reply.id}>
|
<div key={reply.id}>
|
||||||
<Avatar
|
<Message
|
||||||
acceptedMessageRequest={reply.acceptedMessageRequest}
|
{...MESSAGE_DEFAULT_PROPS}
|
||||||
avatarPath={reply.avatarPath}
|
author={reply.author}
|
||||||
badge={undefined}
|
containerElementRef={containerElementRef}
|
||||||
color={getAvatarColor(reply.color)}
|
conversationColor="ultramarine"
|
||||||
conversationType="direct"
|
conversationId={reply.conversationId}
|
||||||
|
conversationTitle={reply.author.title}
|
||||||
|
conversationType="group"
|
||||||
|
direction="incoming"
|
||||||
|
disableMenu
|
||||||
|
getPreferredBadge={getPreferredBadge}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
isMe={Boolean(reply.isMe)}
|
id={reply.id}
|
||||||
name={reply.name}
|
interactionMode="mouse"
|
||||||
profileName={reply.profileName}
|
readStatus={reply.readStatus}
|
||||||
sharedGroupNames={reply.sharedGroupNames || []}
|
renderingContext="StoryViewsNRepliesModal"
|
||||||
size={AvatarSize.TWENTY_EIGHT}
|
shouldCollapseAbove={
|
||||||
title={reply.title}
|
reply.conversationId === replies[index - 1]?.conversationId
|
||||||
|
}
|
||||||
|
shouldCollapseBelow={
|
||||||
|
reply.conversationId === replies[index + 1]?.conversationId
|
||||||
|
}
|
||||||
|
shouldHideMetadata={false}
|
||||||
|
text={reply.body}
|
||||||
|
textDirection={TextDirection.Default}
|
||||||
|
timestamp={reply.timestamp}
|
||||||
/>
|
/>
|
||||||
<div
|
|
||||||
className={classNames(
|
|
||||||
'StoryViewsNRepliesModal__message-bubble',
|
|
||||||
{
|
|
||||||
'StoryViewsNRepliesModal__message-bubble--doe': Boolean(
|
|
||||||
reply.deletedForEveryone
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="StoryViewsNRepliesModal__reply--title">
|
|
||||||
<ContactName
|
|
||||||
contactNameColor={reply.contactNameColor}
|
|
||||||
title={reply.title}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<MessageBody
|
|
||||||
disableJumbomoji
|
|
||||||
i18n={i18n}
|
|
||||||
text={
|
|
||||||
reply.deletedForEveryone
|
|
||||||
? i18n('message--deletedForEveryone')
|
|
||||||
: String(reply.body)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<MessageTimestamp
|
|
||||||
i18n={i18n}
|
|
||||||
module="StoryViewsNRepliesModal__reply--timestamp"
|
|
||||||
timestamp={reply.timestamp}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -263,7 +263,7 @@ export type PropsData = {
|
||||||
isTapToViewExpired?: boolean;
|
isTapToViewExpired?: boolean;
|
||||||
isTapToViewError?: boolean;
|
isTapToViewError?: boolean;
|
||||||
|
|
||||||
readStatus: ReadStatus;
|
readStatus?: ReadStatus;
|
||||||
|
|
||||||
expirationLength?: number;
|
expirationLength?: number;
|
||||||
expirationTimestamp?: number;
|
expirationTimestamp?: number;
|
||||||
|
|
|
@ -80,8 +80,10 @@ function getAvatarData(
|
||||||
ConversationType,
|
ConversationType,
|
||||||
| 'acceptedMessageRequest'
|
| 'acceptedMessageRequest'
|
||||||
| 'avatarPath'
|
| 'avatarPath'
|
||||||
|
| 'badges'
|
||||||
| 'color'
|
| 'color'
|
||||||
| 'isMe'
|
| 'isMe'
|
||||||
|
| 'id'
|
||||||
| 'name'
|
| 'name'
|
||||||
| 'profileName'
|
| 'profileName'
|
||||||
| 'sharedGroupNames'
|
| 'sharedGroupNames'
|
||||||
|
@ -90,8 +92,10 @@ function getAvatarData(
|
||||||
return pick(conversation, [
|
return pick(conversation, [
|
||||||
'acceptedMessageRequest',
|
'acceptedMessageRequest',
|
||||||
'avatarPath',
|
'avatarPath',
|
||||||
|
'badges',
|
||||||
'color',
|
'color',
|
||||||
'isMe',
|
'isMe',
|
||||||
|
'id',
|
||||||
'name',
|
'name',
|
||||||
'profileName',
|
'profileName',
|
||||||
'sharedGroupNames',
|
'sharedGroupNames',
|
||||||
|
@ -212,11 +216,12 @@ export const getStoryReplies = createSelector(
|
||||||
const conversation = conversationSelector(reaction.fromId);
|
const conversation = conversationSelector(reaction.fromId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...getAvatarData(conversation),
|
author: getAvatarData(conversation),
|
||||||
contactNameColor: contactNameColorSelector(
|
contactNameColor: contactNameColorSelector(
|
||||||
foundStory.conversationId,
|
foundStory.conversationId,
|
||||||
conversation.id
|
conversation.id
|
||||||
),
|
),
|
||||||
|
conversationId: reaction.fromId,
|
||||||
id: getReactionUniqueId(reaction),
|
id: getReactionUniqueId(reaction),
|
||||||
reactionEmoji: reaction.emoji,
|
reactionEmoji: reaction.emoji,
|
||||||
timestamp: reaction.timestamp,
|
timestamp: reaction.timestamp,
|
||||||
|
@ -231,12 +236,14 @@ export const getStoryReplies = createSelector(
|
||||||
: conversationSelector(reply.sourceUuid || reply.source);
|
: conversationSelector(reply.sourceUuid || reply.source);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...getAvatarData(conversation),
|
author: getAvatarData(conversation),
|
||||||
...pick(reply, ['body', 'deletedForEveryone', 'id', 'timestamp']),
|
...pick(reply, ['body', 'deletedForEveryone', 'id', 'timestamp']),
|
||||||
contactNameColor: contactNameColorSelector(
|
contactNameColor: contactNameColorSelector(
|
||||||
reply.conversationId,
|
reply.conversationId,
|
||||||
conversation.id
|
conversation.id
|
||||||
),
|
),
|
||||||
|
conversationId: conversation.id,
|
||||||
|
readStatus: reply.readStatus,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -5,25 +5,31 @@ import type { AttachmentType } from './Attachment';
|
||||||
import type { ContactNameColorType } from './Colors';
|
import type { ContactNameColorType } from './Colors';
|
||||||
import type { ConversationType } from '../state/ducks/conversations';
|
import type { ConversationType } from '../state/ducks/conversations';
|
||||||
import type { LocalizerType } from './Util';
|
import type { LocalizerType } from './Util';
|
||||||
|
import type { ReadStatus } from '../messages/MessageReadStatus';
|
||||||
import type { SendStatus } from '../messages/MessageSendState';
|
import type { SendStatus } from '../messages/MessageSendState';
|
||||||
import type { StoryDistributionListDataType } from '../state/ducks/storyDistributionLists';
|
import type { StoryDistributionListDataType } from '../state/ducks/storyDistributionLists';
|
||||||
|
|
||||||
export type ReplyType = Pick<
|
export type ReplyType = {
|
||||||
ConversationType,
|
author: Pick<
|
||||||
| 'acceptedMessageRequest'
|
ConversationType,
|
||||||
| 'avatarPath'
|
| 'acceptedMessageRequest'
|
||||||
| 'color'
|
| 'avatarPath'
|
||||||
| 'isMe'
|
| 'badges'
|
||||||
| 'name'
|
| 'color'
|
||||||
| 'profileName'
|
| 'id'
|
||||||
| 'sharedGroupNames'
|
| 'isMe'
|
||||||
| 'title'
|
| 'name'
|
||||||
> & {
|
| 'profileName'
|
||||||
|
| 'sharedGroupNames'
|
||||||
|
| 'title'
|
||||||
|
>;
|
||||||
body?: string;
|
body?: string;
|
||||||
contactNameColor?: ContactNameColorType;
|
contactNameColor?: ContactNameColorType;
|
||||||
|
conversationId: string;
|
||||||
deletedForEveryone?: boolean;
|
deletedForEveryone?: boolean;
|
||||||
id: string;
|
id: string;
|
||||||
reactionEmoji?: string;
|
reactionEmoji?: string;
|
||||||
|
readStatus?: ReadStatus;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -9231,6 +9231,13 @@
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2022-02-15T17:57:06.507Z"
|
"updated": "2022-02-15T17:57:06.507Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"rule": "React-useRef",
|
||||||
|
"path": "ts/components/StoryViewsNRepliesModal.tsx",
|
||||||
|
"line": " const containerElementRef = useRef<HTMLDivElement | null>(null);",
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2022-08-04T00:52:01.080Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"rule": "React-useRef",
|
"rule": "React-useRef",
|
||||||
"path": "ts/components/TextAttachment.tsx",
|
"path": "ts/components/TextAttachment.tsx",
|
||||||
|
|
Loading…
Reference in a new issue