Use <Message /> for group story replies

This commit is contained in:
Josh Perez 2022-08-04 21:29:44 -04:00 committed by GitHub
parent dca848389c
commit 5dc42122a8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 169 additions and 78 deletions

View file

@ -34,7 +34,6 @@
@include color-svg('../images/icons/v2/x-24.svg', $color-gray-15);
}
right: 28px;
top: 0;
z-index: $z-index-above-above-base;
}

View file

@ -13,6 +13,7 @@
&__replies {
flex: 1;
margin: 0 -16px;
max-height: 75vh;
overflow-y: overlay;
@ -127,7 +128,7 @@
align-items: center;
display: flex;
justify-content: space-between;
padding: 12px 0;
padding: 12px 16px;
&--container {
display: flex;

View file

@ -10,6 +10,7 @@ import enMessages from '../../_locales/en/messages.json';
import { IMAGE_JPEG } from '../types/MIME';
import { SendStatus } from '../messages/MessageSendState';
import { StoryViewsNRepliesModal } from './StoryViewsNRepliesModal';
import { UUID } from '../types/UUID';
import { fakeAttachment } from '../test-both/helpers/fakeAttachment';
import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation';
import { setupI18n } from '../util/setupI18n';
@ -69,6 +70,9 @@ function getViewsAndReplies() {
const p3 = getDefaultConversation();
const p4 = getDefaultConversation();
const p5 = getDefaultConversation();
const p6 = getDefaultConversation({
isMe: true,
});
const views = [
{
@ -100,20 +104,52 @@ function getViewsAndReplies() {
const replies = [
{
...p2,
author: p2,
body: 'So cute ❤️',
conversationId: p2.id,
id: UUID.generate().toString(),
timestamp: Date.now() - 24 * durations.MINUTE,
},
{
...p3,
author: p3,
body: "That's awesome",
conversationId: p3.id,
id: UUID.generate().toString(),
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: '❤️',
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 {

View file

@ -16,7 +16,7 @@ import { CompositionInput } from './CompositionInput';
import { ContactName } from './conversation/ContactName';
import { EmojiButton } from './emoji/EmojiButton';
import { Emojify } from './conversation/Emojify';
import { MessageBody } from './conversation/MessageBody';
import { Message, TextDirection } from './conversation/Message';
import { MessageTimestamp } from './conversation/MessageTimestamp';
import { Modal } from './Modal';
import { Quote } from './conversation/Quote';
@ -24,8 +24,58 @@ import { ReactionPicker } from './conversation/ReactionPicker';
import { Tabs } from './Tabs';
import { Theme } from '../util/theme';
import { ThemeType } from '../types/Util';
import { WidthBreakpoint } from './_util';
import { getAvatarColor } from '../types/Colors';
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 {
Replies = 'Replies',
@ -79,6 +129,7 @@ export const StoryViewsNRepliesModal = ({
storyPreviewAttachment,
views,
}: PropsType): JSX.Element | null => {
const containerElementRef = useRef<HTMLDivElement | null>(null);
const inputApiRef = useRef<InputApi | undefined>();
const [bottom, setBottom] = useState<HTMLDivElement | null>(null);
const [messageBodyText, setMessageBodyText] = useState('');
@ -211,30 +262,34 @@ export const StoryViewsNRepliesModal = ({
if (replies.length) {
repliesElement = (
<div className="StoryViewsNRepliesModal__replies">
{replies.map(reply =>
<div
className="StoryViewsNRepliesModal__replies"
ref={containerElementRef}
>
{replies.map((reply, index) =>
reply.reactionEmoji ? (
<div className="StoryViewsNRepliesModal__reaction" key={reply.id}>
<div className="StoryViewsNRepliesModal__reaction--container">
<Avatar
acceptedMessageRequest={reply.acceptedMessageRequest}
avatarPath={reply.avatarPath}
badge={undefined}
color={getAvatarColor(reply.color)}
acceptedMessageRequest={reply.author.acceptedMessageRequest}
avatarPath={reply.author.avatarPath}
badge={getPreferredBadge(reply.author.badges)}
color={getAvatarColor(reply.author.color)}
conversationType="direct"
i18n={i18n}
isMe={Boolean(reply.isMe)}
name={reply.name}
profileName={reply.profileName}
sharedGroupNames={reply.sharedGroupNames || []}
isMe={Boolean(reply.author.isMe)}
name={reply.author.name}
profileName={reply.author.profileName}
sharedGroupNames={reply.author.sharedGroupNames || []}
size={AvatarSize.TWENTY_EIGHT}
title={reply.title}
theme={ThemeType.dark}
title={reply.author.title}
/>
<div className="StoryViewsNRepliesModal__reaction--body">
<div className="StoryViewsNRepliesModal__reply--title">
<ContactName
contactNameColor={reply.contactNameColor}
title={reply.title}
title={reply.author.title}
/>
</div>
{i18n('StoryViewsNRepliesModal__reacted')}
@ -249,54 +304,34 @@ export const StoryViewsNRepliesModal = ({
<Emojify text={reply.reactionEmoji} />
</div>
) : (
<div className="StoryViewsNRepliesModal__reply" key={reply.id}>
<Avatar
acceptedMessageRequest={reply.acceptedMessageRequest}
avatarPath={reply.avatarPath}
badge={undefined}
color={getAvatarColor(reply.color)}
conversationType="direct"
<div key={reply.id}>
<Message
{...MESSAGE_DEFAULT_PROPS}
author={reply.author}
containerElementRef={containerElementRef}
conversationColor="ultramarine"
conversationId={reply.conversationId}
conversationTitle={reply.author.title}
conversationType="group"
direction="incoming"
disableMenu
getPreferredBadge={getPreferredBadge}
i18n={i18n}
isMe={Boolean(reply.isMe)}
name={reply.name}
profileName={reply.profileName}
sharedGroupNames={reply.sharedGroupNames || []}
size={AvatarSize.TWENTY_EIGHT}
title={reply.title}
id={reply.id}
interactionMode="mouse"
readStatus={reply.readStatus}
renderingContext="StoryViewsNRepliesModal"
shouldCollapseAbove={
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>
)
)}

View file

@ -263,7 +263,7 @@ export type PropsData = {
isTapToViewExpired?: boolean;
isTapToViewError?: boolean;
readStatus: ReadStatus;
readStatus?: ReadStatus;
expirationLength?: number;
expirationTimestamp?: number;

View file

@ -80,8 +80,10 @@ function getAvatarData(
ConversationType,
| 'acceptedMessageRequest'
| 'avatarPath'
| 'badges'
| 'color'
| 'isMe'
| 'id'
| 'name'
| 'profileName'
| 'sharedGroupNames'
@ -90,8 +92,10 @@ function getAvatarData(
return pick(conversation, [
'acceptedMessageRequest',
'avatarPath',
'badges',
'color',
'isMe',
'id',
'name',
'profileName',
'sharedGroupNames',
@ -212,11 +216,12 @@ export const getStoryReplies = createSelector(
const conversation = conversationSelector(reaction.fromId);
return {
...getAvatarData(conversation),
author: getAvatarData(conversation),
contactNameColor: contactNameColorSelector(
foundStory.conversationId,
conversation.id
),
conversationId: reaction.fromId,
id: getReactionUniqueId(reaction),
reactionEmoji: reaction.emoji,
timestamp: reaction.timestamp,
@ -231,12 +236,14 @@ export const getStoryReplies = createSelector(
: conversationSelector(reply.sourceUuid || reply.source);
return {
...getAvatarData(conversation),
author: getAvatarData(conversation),
...pick(reply, ['body', 'deletedForEveryone', 'id', 'timestamp']),
contactNameColor: contactNameColorSelector(
reply.conversationId,
conversation.id
),
conversationId: conversation.id,
readStatus: reply.readStatus,
};
});

View file

@ -5,25 +5,31 @@ import type { AttachmentType } from './Attachment';
import type { ContactNameColorType } from './Colors';
import type { ConversationType } from '../state/ducks/conversations';
import type { LocalizerType } from './Util';
import type { ReadStatus } from '../messages/MessageReadStatus';
import type { SendStatus } from '../messages/MessageSendState';
import type { StoryDistributionListDataType } from '../state/ducks/storyDistributionLists';
export type ReplyType = Pick<
ConversationType,
| 'acceptedMessageRequest'
| 'avatarPath'
| 'color'
| 'isMe'
| 'name'
| 'profileName'
| 'sharedGroupNames'
| 'title'
> & {
export type ReplyType = {
author: Pick<
ConversationType,
| 'acceptedMessageRequest'
| 'avatarPath'
| 'badges'
| 'color'
| 'id'
| 'isMe'
| 'name'
| 'profileName'
| 'sharedGroupNames'
| 'title'
>;
body?: string;
contactNameColor?: ContactNameColorType;
conversationId: string;
deletedForEveryone?: boolean;
id: string;
reactionEmoji?: string;
readStatus?: ReadStatus;
timestamp: number;
};

View file

@ -9231,6 +9231,13 @@
"reasonCategory": "usageTrusted",
"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",
"path": "ts/components/TextAttachment.tsx",