| 
									
										
										
										
											2023-01-03 11:55:46 -08:00
										 |  |  | // Copyright 2018 Signal Messenger, LLC
 | 
					
						
							| 
									
										
										
										
											2020-10-30 15:34:04 -05:00
										 |  |  | // SPDX-License-Identifier: AGPL-3.0-only
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-26 14:15:33 -05:00
										 |  |  | import type { ReactChild, ReactNode } from 'react'; | 
					
						
							|  |  |  | import React from 'react'; | 
					
						
							| 
									
										
										
										
											2018-07-09 14:29:13 -07:00
										 |  |  | import classNames from 'classnames'; | 
					
						
							| 
									
										
										
										
											2021-06-17 10:15:10 -07:00
										 |  |  | import { noop } from 'lodash'; | 
					
						
							| 
									
										
										
										
											2018-07-09 14:29:13 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-20 14:56:50 -05:00
										 |  |  | import { Avatar, AvatarSize } from '../Avatar'; | 
					
						
							| 
									
										
										
										
											2018-07-09 14:29:13 -07:00
										 |  |  | import { ContactName } from './ContactName'; | 
					
						
							| 
									
										
										
										
											2022-07-25 14:55:44 -04:00
										 |  |  | import { ContextMenu } from '../ContextMenu'; | 
					
						
							| 
									
										
										
										
											2022-01-26 17:05:26 -06:00
										 |  |  | import { Time } from '../Time'; | 
					
						
							| 
									
										
										
										
											2021-10-26 14:15:33 -05:00
										 |  |  | import type { | 
					
						
							| 
									
										
										
										
											2021-03-24 17:06:12 -05:00
										 |  |  |   Props as MessagePropsType, | 
					
						
							|  |  |  |   PropsData as MessagePropsDataType, | 
					
						
							|  |  |  | } from './Message'; | 
					
						
							| 
									
										
										
										
											2021-10-26 14:15:33 -05:00
										 |  |  | import { Message } from './Message'; | 
					
						
							| 
									
										
										
										
											2021-11-15 16:53:42 -06:00
										 |  |  | import type { LocalizerType, ThemeType } from '../../types/Util'; | 
					
						
							| 
									
										
										
										
											2021-10-26 14:15:33 -05:00
										 |  |  | import type { ConversationType } from '../../state/ducks/conversations'; | 
					
						
							| 
									
										
										
										
											2021-11-16 09:53:41 -06:00
										 |  |  | import type { PreferredBadgeSelectorType } from '../../state/selectors/badges'; | 
					
						
							| 
									
										
										
										
											2021-07-20 14:56:50 -05:00
										 |  |  | import { groupBy } from '../../util/mapUtil'; | 
					
						
							| 
									
										
										
										
											2021-10-26 14:15:33 -05:00
										 |  |  | import type { ContactNameColorType } from '../../types/Colors'; | 
					
						
							| 
									
										
										
										
											2021-07-19 17:44:49 -05:00
										 |  |  | import { SendStatus } from '../../messages/MessageSendState'; | 
					
						
							| 
									
										
										
										
											2021-10-12 18:59:08 -05:00
										 |  |  | import { WidthBreakpoint } from '../_util'; | 
					
						
							| 
									
										
										
										
											2021-09-17 14:27:53 -04:00
										 |  |  | import * as log from '../../logging/log'; | 
					
						
							| 
									
										
										
										
											2022-01-26 17:05:26 -06:00
										 |  |  | import { formatDateTimeLong } from '../../util/timestamp'; | 
					
						
							| 
									
										
										
										
											2022-11-16 12:18:02 -08:00
										 |  |  | import { DurationInSeconds } from '../../util/durations'; | 
					
						
							| 
									
										
										
										
											2022-09-09 12:35:00 -06:00
										 |  |  | import { format as formatRelativeTime } from '../../util/expirationTimer'; | 
					
						
							| 
									
										
										
										
											2018-07-09 14:29:13 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-07 17:21:10 -05:00
										 |  |  | export type Contact = Pick< | 
					
						
							|  |  |  |   ConversationType, | 
					
						
							|  |  |  |   | 'acceptedMessageRequest' | 
					
						
							|  |  |  |   | 'avatarPath' | 
					
						
							| 
									
										
										
										
											2021-11-16 09:53:41 -06:00
										 |  |  |   | 'badges' | 
					
						
							| 
									
										
										
										
											2021-05-07 17:21:10 -05:00
										 |  |  |   | 'color' | 
					
						
							| 
									
										
										
										
											2021-06-15 17:44:14 -07:00
										 |  |  |   | 'id' | 
					
						
							| 
									
										
										
										
											2021-05-07 17:21:10 -05:00
										 |  |  |   | 'isMe' | 
					
						
							|  |  |  |   | 'phoneNumber' | 
					
						
							|  |  |  |   | 'profileName' | 
					
						
							|  |  |  |   | 'sharedGroupNames' | 
					
						
							|  |  |  |   | 'title' | 
					
						
							|  |  |  |   | 'unblurredAvatarPath' | 
					
						
							|  |  |  | > & { | 
					
						
							| 
									
										
										
										
											2021-07-20 14:56:50 -05:00
										 |  |  |   status?: SendStatus; | 
					
						
							| 
									
										
										
										
											2021-10-12 19:40:42 -04:00
										 |  |  |   statusTimestamp?: number; | 
					
						
							| 
									
										
										
										
											2020-07-23 18:35:32 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-09 14:29:13 -07:00
										 |  |  |   isOutgoingKeyError: boolean; | 
					
						
							| 
									
										
										
										
											2018-10-17 18:01:21 -07:00
										 |  |  |   isUnidentifiedDelivery: boolean; | 
					
						
							| 
									
										
										
										
											2018-07-09 14:29:13 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |   errors?: Array<Error>; | 
					
						
							| 
									
										
										
										
											2021-01-14 12:07:05 -06:00
										 |  |  | }; | 
					
						
							| 
									
										
										
										
											2018-07-09 14:29:13 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-30 14:32:56 -07:00
										 |  |  | export type PropsData = { | 
					
						
							| 
									
										
										
										
											2021-07-20 14:56:50 -05:00
										 |  |  |   // An undefined status means they were the sender and it's an incoming message. If
 | 
					
						
							|  |  |  |   //   `undefined` is a status, there should be no other items in the array; if there are
 | 
					
						
							|  |  |  |   //   any defined statuses, `undefined` shouldn't be present.
 | 
					
						
							|  |  |  |   contacts: ReadonlyArray<Contact>; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-07 12:50:18 -04:00
										 |  |  |   contactNameColor?: ContactNameColorType; | 
					
						
							| 
									
										
										
										
											2021-03-24 17:06:12 -05:00
										 |  |  |   errors: Array<Error>; | 
					
						
							| 
									
										
										
										
											2022-11-04 07:22:07 -06:00
										 |  |  |   message: Omit< | 
					
						
							|  |  |  |     MessagePropsDataType, | 
					
						
							|  |  |  |     'renderingContext' | 'menu' | 'contextMenu' | 'showMenu' | 
					
						
							|  |  |  |   >; | 
					
						
							| 
									
										
										
										
											2021-03-24 17:06:12 -05:00
										 |  |  |   receivedAt: number; | 
					
						
							|  |  |  |   sentAt: number; | 
					
						
							| 
									
										
										
										
											2018-07-09 14:29:13 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-14 13:49:58 -08:00
										 |  |  |   i18n: LocalizerType; | 
					
						
							| 
									
										
										
										
											2021-11-15 16:53:42 -06:00
										 |  |  |   theme: ThemeType; | 
					
						
							| 
									
										
										
										
											2021-11-16 09:53:41 -06:00
										 |  |  |   getPreferredBadge: PreferredBadgeSelectorType; | 
					
						
							| 
									
										
										
										
											2022-12-20 19:25:10 -08:00
										 |  |  | } & Pick<MessagePropsType, 'getPreferredBadge' | 'interactionMode'>; | 
					
						
							| 
									
										
										
										
											2021-08-30 14:32:56 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-20 19:25:10 -08:00
										 |  |  | export type PropsSmartActions = Pick<MessagePropsType, 'renderAudioAttachment'>; | 
					
						
							| 
									
										
										
										
											2018-07-09 14:29:13 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-30 14:32:56 -07:00
										 |  |  | export type PropsReduxActions = Pick< | 
					
						
							|  |  |  |   MessagePropsType, | 
					
						
							| 
									
										
										
										
											2022-12-09 21:02:22 -05:00
										 |  |  |   | 'checkForAccount' | 
					
						
							| 
									
										
										
										
											2021-08-30 14:32:56 -07:00
										 |  |  |   | 'clearSelectedMessage' | 
					
						
							|  |  |  |   | 'doubleCheckMissingQuoteReference' | 
					
						
							| 
									
										
										
										
											2022-12-19 17:04:47 -08:00
										 |  |  |   | 'kickOffAttachmentDownload' | 
					
						
							|  |  |  |   | 'markAttachmentAsCorrupted' | 
					
						
							|  |  |  |   | 'openGiftBadge' | 
					
						
							| 
									
										
										
										
											2022-12-14 20:10:09 -05:00
										 |  |  |   | 'pushPanelForConversation' | 
					
						
							| 
									
										
										
										
											2022-12-14 13:12:04 -05:00
										 |  |  |   | 'saveAttachment' | 
					
						
							| 
									
										
										
										
											2022-12-09 01:08:55 -05:00
										 |  |  |   | 'showContactModal' | 
					
						
							| 
									
										
										
										
											2022-12-14 11:05:32 -08:00
										 |  |  |   | 'showConversation' | 
					
						
							| 
									
										
										
										
											2022-12-19 17:04:47 -08:00
										 |  |  |   | 'showExpiredIncomingTapToViewToast' | 
					
						
							|  |  |  |   | 'showExpiredOutgoingTapToViewToast' | 
					
						
							| 
									
										
										
										
											2022-12-09 21:02:22 -05:00
										 |  |  |   | 'showLightbox' | 
					
						
							|  |  |  |   | 'showLightboxForViewOnceMedia' | 
					
						
							| 
									
										
										
										
											2022-12-20 19:25:10 -08:00
										 |  |  |   | 'startConversation' | 
					
						
							| 
									
										
										
										
											2022-07-06 15:06:20 -04:00
										 |  |  |   | 'viewStory' | 
					
						
							| 
									
										
										
										
											2022-12-09 00:53:19 -05:00
										 |  |  | > & { | 
					
						
							|  |  |  |   toggleSafetyNumberModal: (contactId: string) => void; | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2021-08-30 14:32:56 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-20 19:25:10 -08:00
										 |  |  | export type Props = PropsData & PropsSmartActions & PropsReduxActions; | 
					
						
							| 
									
										
										
										
											2021-08-30 14:32:56 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-20 14:56:50 -05:00
										 |  |  | const contactSortCollator = new Intl.Collator(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-14 12:51:27 -07:00
										 |  |  | const _keyForError = (error: Error): string => { | 
					
						
							|  |  |  |   return `${error.name}-${error.message}`; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-08 14:11:11 -05:00
										 |  |  | export class MessageDetail extends React.Component<Props> { | 
					
						
							| 
									
										
										
										
											2022-03-03 14:23:10 -06:00
										 |  |  |   private readonly focusRef = React.createRef<HTMLDivElement>(); | 
					
						
							| 
									
										
										
										
											2021-08-20 14:36:27 -05:00
										 |  |  |   private readonly messageContainerRef = React.createRef<HTMLDivElement>(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-12 17:44:20 -06:00
										 |  |  |   public override componentDidMount(): void { | 
					
						
							| 
									
										
										
										
											2019-11-07 13:36:16 -08:00
										 |  |  |     // When this component is created, it's initially not part of the DOM, and then it's
 | 
					
						
							|  |  |  |     //   added off-screen and animated in. This ensures that the focus takes.
 | 
					
						
							|  |  |  |     setTimeout(() => { | 
					
						
							|  |  |  |       if (this.focusRef.current) { | 
					
						
							|  |  |  |         this.focusRef.current.focus(); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-14 12:51:27 -07:00
										 |  |  |   public renderAvatar(contact: Contact): JSX.Element { | 
					
						
							| 
									
										
										
										
											2021-11-16 09:53:41 -06:00
										 |  |  |     const { getPreferredBadge, i18n, theme } = this.props; | 
					
						
							| 
									
										
										
										
											2020-07-23 18:35:32 -07:00
										 |  |  |     const { | 
					
						
							| 
									
										
										
										
											2021-05-04 18:19:36 -05:00
										 |  |  |       acceptedMessageRequest, | 
					
						
							| 
									
										
										
										
											2020-07-23 18:35:32 -07:00
										 |  |  |       avatarPath, | 
					
						
							| 
									
										
										
										
											2021-11-16 09:53:41 -06:00
										 |  |  |       badges, | 
					
						
							| 
									
										
										
										
											2020-07-23 18:35:32 -07:00
										 |  |  |       color, | 
					
						
							| 
									
										
										
										
											2021-05-07 17:21:10 -05:00
										 |  |  |       isMe, | 
					
						
							| 
									
										
										
										
											2021-05-04 18:19:36 -05:00
										 |  |  |       phoneNumber, | 
					
						
							| 
									
										
										
										
											2020-07-23 18:35:32 -07:00
										 |  |  |       profileName, | 
					
						
							| 
									
										
										
										
											2021-05-04 18:19:36 -05:00
										 |  |  |       sharedGroupNames, | 
					
						
							| 
									
										
										
										
											2020-07-23 18:35:32 -07:00
										 |  |  |       title, | 
					
						
							| 
									
										
										
										
											2021-05-04 18:19:36 -05:00
										 |  |  |       unblurredAvatarPath, | 
					
						
							| 
									
										
										
										
											2020-07-23 18:35:32 -07:00
										 |  |  |     } = contact; | 
					
						
							| 
									
										
										
										
											2018-07-09 14:29:13 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     return ( | 
					
						
							| 
									
										
										
										
											2018-09-26 17:23:17 -07:00
										 |  |  |       <Avatar | 
					
						
							| 
									
										
										
										
											2021-05-04 18:19:36 -05:00
										 |  |  |         acceptedMessageRequest={acceptedMessageRequest} | 
					
						
							| 
									
										
										
										
											2018-09-26 17:23:17 -07:00
										 |  |  |         avatarPath={avatarPath} | 
					
						
							| 
									
										
										
										
											2021-11-16 09:53:41 -06:00
										 |  |  |         badge={getPreferredBadge(badges)} | 
					
						
							| 
									
										
										
										
											2018-09-26 17:23:17 -07:00
										 |  |  |         color={color} | 
					
						
							|  |  |  |         conversationType="direct" | 
					
						
							|  |  |  |         i18n={i18n} | 
					
						
							| 
									
										
										
										
											2021-05-07 17:21:10 -05:00
										 |  |  |         isMe={isMe} | 
					
						
							| 
									
										
										
										
											2018-09-26 17:23:17 -07:00
										 |  |  |         phoneNumber={phoneNumber} | 
					
						
							|  |  |  |         profileName={profileName} | 
					
						
							| 
									
										
										
										
											2021-11-16 09:53:41 -06:00
										 |  |  |         theme={theme} | 
					
						
							| 
									
										
										
										
											2020-07-23 18:35:32 -07:00
										 |  |  |         title={title} | 
					
						
							| 
									
										
										
										
											2021-05-04 18:19:36 -05:00
										 |  |  |         sharedGroupNames={sharedGroupNames} | 
					
						
							| 
									
										
										
										
											2022-12-09 13:37:45 -07:00
										 |  |  |         size={AvatarSize.THIRTY_TWO} | 
					
						
							| 
									
										
										
										
											2021-05-04 18:19:36 -05:00
										 |  |  |         unblurredAvatarPath={unblurredAvatarPath} | 
					
						
							| 
									
										
										
										
											2018-07-09 14:29:13 -07:00
										 |  |  |       /> | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-14 12:51:27 -07:00
										 |  |  |   public renderContact(contact: Contact): JSX.Element { | 
					
						
							| 
									
										
										
										
											2022-12-09 00:53:19 -05:00
										 |  |  |     const { i18n, toggleSafetyNumberModal } = this.props; | 
					
						
							| 
									
										
										
										
											2018-07-09 14:29:13 -07:00
										 |  |  |     const errors = contact.errors || []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const errorComponent = contact.isOutgoingKeyError ? ( | 
					
						
							|  |  |  |       <div className="module-message-detail__contact__error-buttons"> | 
					
						
							|  |  |  |         <button | 
					
						
							| 
									
										
										
										
											2020-09-14 12:51:27 -07:00
										 |  |  |           type="button" | 
					
						
							| 
									
										
										
										
											2018-07-09 14:29:13 -07:00
										 |  |  |           className="module-message-detail__contact__show-safety-number" | 
					
						
							| 
									
										
										
										
											2022-12-09 00:53:19 -05:00
										 |  |  |           onClick={() => toggleSafetyNumberModal(contact.id)} | 
					
						
							| 
									
										
										
										
											2018-07-09 14:29:13 -07:00
										 |  |  |         > | 
					
						
							|  |  |  |           {i18n('showSafetyNumber')} | 
					
						
							|  |  |  |         </button> | 
					
						
							|  |  |  |       </div> | 
					
						
							|  |  |  |     ) : null; | 
					
						
							| 
									
										
										
										
											2018-10-17 18:01:21 -07:00
										 |  |  |     const unidentifiedDeliveryComponent = contact.isUnidentifiedDelivery ? ( | 
					
						
							|  |  |  |       <div className="module-message-detail__contact__unidentified-delivery-icon" /> | 
					
						
							|  |  |  |     ) : null; | 
					
						
							| 
									
										
										
										
											2018-07-09 14:29:13 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     return ( | 
					
						
							| 
									
										
										
										
											2021-07-20 14:56:50 -05:00
										 |  |  |       <div key={contact.id} className="module-message-detail__contact"> | 
					
						
							| 
									
										
										
										
											2018-07-09 14:29:13 -07:00
										 |  |  |         {this.renderAvatar(contact)} | 
					
						
							|  |  |  |         <div className="module-message-detail__contact__text"> | 
					
						
							|  |  |  |           <div className="module-message-detail__contact__name"> | 
					
						
							| 
									
										
										
										
											2021-09-16 11:15:43 -05:00
										 |  |  |             <ContactName title={contact.title} /> | 
					
						
							| 
									
										
										
										
											2018-07-09 14:29:13 -07:00
										 |  |  |           </div> | 
					
						
							| 
									
										
										
										
											2020-09-14 12:51:27 -07:00
										 |  |  |           {errors.map(error => ( | 
					
						
							|  |  |  |             <div | 
					
						
							|  |  |  |               key={_keyForError(error)} | 
					
						
							|  |  |  |               className="module-message-detail__contact__error" | 
					
						
							|  |  |  |             > | 
					
						
							| 
									
										
										
										
											2018-07-09 14:29:13 -07:00
										 |  |  |               {error.message} | 
					
						
							|  |  |  |             </div> | 
					
						
							|  |  |  |           ))} | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  |         {errorComponent} | 
					
						
							| 
									
										
										
										
											2018-10-17 18:01:21 -07:00
										 |  |  |         {unidentifiedDeliveryComponent} | 
					
						
							| 
									
										
										
										
											2021-10-12 19:40:42 -04:00
										 |  |  |         {contact.statusTimestamp && ( | 
					
						
							| 
									
										
										
										
											2022-01-26 17:05:26 -06:00
										 |  |  |           <Time | 
					
						
							|  |  |  |             className="module-message-detail__status-timestamp" | 
					
						
							| 
									
										
										
										
											2021-10-12 19:40:42 -04:00
										 |  |  |             timestamp={contact.statusTimestamp} | 
					
						
							| 
									
										
										
										
											2022-01-26 17:05:26 -06:00
										 |  |  |           > | 
					
						
							|  |  |  |             {formatDateTimeLong(i18n, contact.statusTimestamp)} | 
					
						
							|  |  |  |           </Time> | 
					
						
							| 
									
										
										
										
											2021-10-12 19:40:42 -04:00
										 |  |  |         )} | 
					
						
							| 
									
										
										
										
											2018-07-09 14:29:13 -07:00
										 |  |  |       </div> | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-20 14:56:50 -05:00
										 |  |  |   private renderContactGroup( | 
					
						
							|  |  |  |     sendStatus: undefined | SendStatus, | 
					
						
							|  |  |  |     contacts: undefined | ReadonlyArray<Contact> | 
					
						
							|  |  |  |   ): ReactNode { | 
					
						
							|  |  |  |     const { i18n } = this.props; | 
					
						
							| 
									
										
										
										
											2018-07-09 14:29:13 -07:00
										 |  |  |     if (!contacts || !contacts.length) { | 
					
						
							|  |  |  |       return null; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-20 14:56:50 -05:00
										 |  |  |     const i18nKey = | 
					
						
							|  |  |  |       sendStatus === undefined ? 'from' : `MessageDetailsHeader--${sendStatus}`; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const sortedContacts = [...contacts].sort((a, b) => | 
					
						
							|  |  |  |       contactSortCollator.compare(a.title, b.title) | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return ( | 
					
						
							|  |  |  |       <div key={i18nKey} className="module-message-detail__contact-group"> | 
					
						
							|  |  |  |         <div | 
					
						
							|  |  |  |           className={classNames( | 
					
						
							|  |  |  |             'module-message-detail__contact-group__header', | 
					
						
							|  |  |  |             sendStatus && | 
					
						
							|  |  |  |               `module-message-detail__contact-group__header--${sendStatus}` | 
					
						
							|  |  |  |           )} | 
					
						
							|  |  |  |         > | 
					
						
							| 
									
										
										
										
											2023-01-05 14:43:33 -08:00
										 |  |  |           {/* eslint-disable-next-line local-rules/valid-i18n-keys */} | 
					
						
							| 
									
										
										
										
											2021-07-20 14:56:50 -05:00
										 |  |  |           {i18n(i18nKey)} | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  |         {sortedContacts.map(contact => this.renderContact(contact))} | 
					
						
							|  |  |  |       </div> | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private renderContacts(): ReactChild { | 
					
						
							|  |  |  |     // This assumes that the list either contains one sender (a status of `undefined`) or
 | 
					
						
							|  |  |  |     //   1+ contacts with `SendStatus`es, but it doesn't check that assumption.
 | 
					
						
							|  |  |  |     const { contacts } = this.props; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const contactsBySendStatus = groupBy(contacts, contact => contact.status); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-09 14:29:13 -07:00
										 |  |  |     return ( | 
					
						
							|  |  |  |       <div className="module-message-detail__contact-container"> | 
					
						
							| 
									
										
										
										
											2021-07-20 14:56:50 -05:00
										 |  |  |         {[ | 
					
						
							|  |  |  |           undefined, | 
					
						
							|  |  |  |           SendStatus.Failed, | 
					
						
							|  |  |  |           SendStatus.Viewed, | 
					
						
							|  |  |  |           SendStatus.Read, | 
					
						
							|  |  |  |           SendStatus.Delivered, | 
					
						
							|  |  |  |           SendStatus.Sent, | 
					
						
							|  |  |  |           SendStatus.Pending, | 
					
						
							|  |  |  |         ].map(sendStatus => | 
					
						
							|  |  |  |           this.renderContactGroup( | 
					
						
							|  |  |  |             sendStatus, | 
					
						
							|  |  |  |             contactsBySendStatus.get(sendStatus) | 
					
						
							|  |  |  |           ) | 
					
						
							|  |  |  |         )} | 
					
						
							| 
									
										
										
										
											2018-07-09 14:29:13 -07:00
										 |  |  |       </div> | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-12 17:44:20 -06:00
										 |  |  |   public override render(): JSX.Element { | 
					
						
							| 
									
										
										
										
											2021-03-24 17:06:12 -05:00
										 |  |  |     const { | 
					
						
							|  |  |  |       errors, | 
					
						
							|  |  |  |       message, | 
					
						
							|  |  |  |       receivedAt, | 
					
						
							|  |  |  |       sentAt, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-17 10:15:10 -07:00
										 |  |  |       checkForAccount, | 
					
						
							| 
									
										
										
										
											2021-03-24 17:06:12 -05:00
										 |  |  |       clearSelectedMessage, | 
					
						
							| 
									
										
										
										
											2021-06-07 12:50:18 -04:00
										 |  |  |       contactNameColor, | 
					
						
							| 
									
										
										
										
											2022-12-09 21:02:22 -05:00
										 |  |  |       showLightboxForViewOnceMedia, | 
					
						
							| 
									
										
										
										
											2021-06-17 10:15:10 -07:00
										 |  |  |       doubleCheckMissingQuoteReference, | 
					
						
							| 
									
										
										
										
											2021-11-17 15:11:46 -06:00
										 |  |  |       getPreferredBadge, | 
					
						
							| 
									
										
										
										
											2021-03-24 17:06:12 -05:00
										 |  |  |       i18n, | 
					
						
							|  |  |  |       interactionMode, | 
					
						
							|  |  |  |       kickOffAttachmentDownload, | 
					
						
							|  |  |  |       markAttachmentAsCorrupted, | 
					
						
							| 
									
										
										
										
											2022-05-11 13:59:58 -07:00
										 |  |  |       openGiftBadge, | 
					
						
							| 
									
										
										
										
											2022-12-14 20:10:09 -05:00
										 |  |  |       pushPanelForConversation, | 
					
						
							| 
									
										
										
										
											2021-03-24 17:06:12 -05:00
										 |  |  |       renderAudioAttachment, | 
					
						
							| 
									
										
										
										
											2022-12-14 13:12:04 -05:00
										 |  |  |       saveAttachment, | 
					
						
							| 
									
										
										
										
											2021-03-24 17:06:12 -05:00
										 |  |  |       showContactModal, | 
					
						
							| 
									
										
										
										
											2022-12-14 11:05:32 -08:00
										 |  |  |       showConversation, | 
					
						
							| 
									
										
										
										
											2021-03-24 17:06:12 -05:00
										 |  |  |       showExpiredIncomingTapToViewToast, | 
					
						
							|  |  |  |       showExpiredOutgoingTapToViewToast, | 
					
						
							| 
									
										
										
										
											2022-12-09 21:02:22 -05:00
										 |  |  |       showLightbox, | 
					
						
							| 
									
										
										
										
											2022-04-11 17:26:09 -07:00
										 |  |  |       startConversation, | 
					
						
							| 
									
										
										
										
											2021-11-15 16:53:42 -06:00
										 |  |  |       theme, | 
					
						
							| 
									
										
										
										
											2022-07-06 15:06:20 -04:00
										 |  |  |       viewStory, | 
					
						
							| 
									
										
										
										
											2021-03-24 17:06:12 -05:00
										 |  |  |     } = this.props; | 
					
						
							| 
									
										
										
										
											2018-07-09 14:29:13 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-20 19:25:10 -08:00
										 |  |  |     const timeRemaining = message.expirationTimestamp | 
					
						
							|  |  |  |       ? DurationInSeconds.fromMillis(message.expirationTimestamp - Date.now()) | 
					
						
							| 
									
										
										
										
											2022-09-09 12:35:00 -06:00
										 |  |  |       : undefined; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-09 14:29:13 -07:00
										 |  |  |     return ( | 
					
						
							| 
									
										
										
										
											2020-09-14 12:51:27 -07:00
										 |  |  |       // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
 | 
					
						
							| 
									
										
										
										
											2019-11-07 13:36:16 -08:00
										 |  |  |       <div className="module-message-detail" tabIndex={0} ref={this.focusRef}> | 
					
						
							| 
									
										
										
										
											2021-08-20 14:36:27 -05:00
										 |  |  |         <div | 
					
						
							|  |  |  |           className="module-message-detail__message-container" | 
					
						
							|  |  |  |           ref={this.messageContainerRef} | 
					
						
							|  |  |  |         > | 
					
						
							| 
									
										
										
										
											2021-06-29 12:58:29 -07:00
										 |  |  |           <Message | 
					
						
							|  |  |  |             {...message} | 
					
						
							|  |  |  |             renderingContext="conversation/MessageDetail" | 
					
						
							|  |  |  |             checkForAccount={checkForAccount} | 
					
						
							|  |  |  |             clearSelectedMessage={clearSelectedMessage} | 
					
						
							|  |  |  |             contactNameColor={contactNameColor} | 
					
						
							| 
									
										
										
										
											2021-08-20 14:36:27 -05:00
										 |  |  |             containerElementRef={this.messageContainerRef} | 
					
						
							| 
									
										
										
										
											2021-10-12 18:59:08 -05:00
										 |  |  |             containerWidthBreakpoint={WidthBreakpoint.Wide} | 
					
						
							| 
									
										
										
										
											2022-12-19 14:33:55 -08:00
										 |  |  |             renderMenu={undefined} | 
					
						
							| 
									
										
										
										
											2021-06-29 12:58:29 -07:00
										 |  |  |             disableScroll | 
					
						
							| 
									
										
										
										
											2021-11-11 15:45:47 -08:00
										 |  |  |             displayLimit={Number.MAX_SAFE_INTEGER} | 
					
						
							| 
									
										
										
										
											2022-12-09 21:02:22 -05:00
										 |  |  |             showLightboxForViewOnceMedia={showLightboxForViewOnceMedia} | 
					
						
							| 
									
										
										
										
											2021-06-29 12:58:29 -07:00
										 |  |  |             doubleCheckMissingQuoteReference={doubleCheckMissingQuoteReference} | 
					
						
							| 
									
										
										
										
											2021-11-17 15:11:46 -06:00
										 |  |  |             getPreferredBadge={getPreferredBadge} | 
					
						
							| 
									
										
										
										
											2021-06-29 12:58:29 -07:00
										 |  |  |             i18n={i18n} | 
					
						
							|  |  |  |             interactionMode={interactionMode} | 
					
						
							|  |  |  |             kickOffAttachmentDownload={kickOffAttachmentDownload} | 
					
						
							|  |  |  |             markAttachmentAsCorrupted={markAttachmentAsCorrupted} | 
					
						
							| 
									
										
										
										
											2021-11-11 15:45:47 -08:00
										 |  |  |             messageExpanded={noop} | 
					
						
							| 
									
										
										
										
											2022-12-14 11:05:32 -08:00
										 |  |  |             showConversation={showConversation} | 
					
						
							| 
									
										
										
										
											2022-05-11 13:59:58 -07:00
										 |  |  |             openGiftBadge={openGiftBadge} | 
					
						
							| 
									
										
										
										
											2022-12-14 20:10:09 -05:00
										 |  |  |             pushPanelForConversation={pushPanelForConversation} | 
					
						
							| 
									
										
										
										
											2021-06-29 12:58:29 -07:00
										 |  |  |             renderAudioAttachment={renderAudioAttachment} | 
					
						
							| 
									
										
										
										
											2022-12-14 13:12:04 -05:00
										 |  |  |             saveAttachment={saveAttachment} | 
					
						
							| 
									
										
										
										
											2022-03-28 15:55:12 -07:00
										 |  |  |             shouldCollapseAbove={false} | 
					
						
							|  |  |  |             shouldCollapseBelow={false} | 
					
						
							|  |  |  |             shouldHideMetadata={false} | 
					
						
							| 
									
										
										
										
											2021-06-29 12:58:29 -07:00
										 |  |  |             scrollToQuotedMessage={() => { | 
					
						
							| 
									
										
										
										
											2021-09-17 14:27:53 -04:00
										 |  |  |               log.warn('MessageDetail: scrollToQuotedMessage called!'); | 
					
						
							| 
									
										
										
										
											2021-06-29 12:58:29 -07:00
										 |  |  |             }} | 
					
						
							|  |  |  |             showContactModal={showContactModal} | 
					
						
							|  |  |  |             showExpiredIncomingTapToViewToast={ | 
					
						
							|  |  |  |               showExpiredIncomingTapToViewToast | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             showExpiredOutgoingTapToViewToast={ | 
					
						
							|  |  |  |               showExpiredOutgoingTapToViewToast | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-12-09 21:02:22 -05:00
										 |  |  |             showLightbox={showLightbox} | 
					
						
							| 
									
										
										
										
											2022-04-11 17:26:09 -07:00
										 |  |  |             startConversation={startConversation} | 
					
						
							| 
									
										
										
										
											2021-11-15 16:53:42 -06:00
										 |  |  |             theme={theme} | 
					
						
							| 
									
										
										
										
											2022-07-06 15:06:20 -04:00
										 |  |  |             viewStory={viewStory} | 
					
						
							| 
									
										
										
										
											2021-06-29 12:58:29 -07:00
										 |  |  |           /> | 
					
						
							| 
									
										
										
										
											2018-07-09 14:29:13 -07:00
										 |  |  |         </div> | 
					
						
							|  |  |  |         <table className="module-message-detail__info"> | 
					
						
							|  |  |  |           <tbody> | 
					
						
							| 
									
										
										
										
											2020-09-14 12:51:27 -07:00
										 |  |  |             {(errors || []).map(error => ( | 
					
						
							|  |  |  |               <tr key={_keyForError(error)}> | 
					
						
							| 
									
										
										
										
											2018-07-09 14:29:13 -07:00
										 |  |  |                 <td className="module-message-detail__label"> | 
					
						
							|  |  |  |                   {i18n('error')} | 
					
						
							|  |  |  |                 </td> | 
					
						
							|  |  |  |                 <td> | 
					
						
							|  |  |  |                   {' '} | 
					
						
							|  |  |  |                   <span className="error-message">{error.message}</span>{' '} | 
					
						
							|  |  |  |                 </td> | 
					
						
							|  |  |  |               </tr> | 
					
						
							|  |  |  |             ))} | 
					
						
							|  |  |  |             <tr> | 
					
						
							|  |  |  |               <td className="module-message-detail__label">{i18n('sent')}</td> | 
					
						
							|  |  |  |               <td> | 
					
						
							| 
									
										
										
										
											2022-07-25 14:55:44 -04:00
										 |  |  |                 <ContextMenu | 
					
						
							|  |  |  |                   i18n={i18n} | 
					
						
							|  |  |  |                   menuOptions={[ | 
					
						
							|  |  |  |                     { | 
					
						
							|  |  |  |                       icon: 'StoryDetailsModal__copy-icon', | 
					
						
							|  |  |  |                       label: i18n('StoryDetailsModal__copy-timestamp'), | 
					
						
							|  |  |  |                       onClick: () => { | 
					
						
							| 
									
										
										
										
											2022-12-21 10:41:48 -08:00
										 |  |  |                         void window.navigator.clipboard.writeText( | 
					
						
							|  |  |  |                           String(sentAt) | 
					
						
							|  |  |  |                         ); | 
					
						
							| 
									
										
										
										
											2022-07-25 14:55:44 -04:00
										 |  |  |                       }, | 
					
						
							|  |  |  |                     }, | 
					
						
							|  |  |  |                   ]} | 
					
						
							|  |  |  |                 > | 
					
						
							|  |  |  |                   <> | 
					
						
							|  |  |  |                     <Time timestamp={sentAt}> | 
					
						
							|  |  |  |                       {formatDateTimeLong(i18n, sentAt)} | 
					
						
							|  |  |  |                     </Time>{' '} | 
					
						
							|  |  |  |                     <span className="module-message-detail__unix-timestamp"> | 
					
						
							|  |  |  |                       ({sentAt}) | 
					
						
							|  |  |  |                     </span> | 
					
						
							|  |  |  |                   </> | 
					
						
							|  |  |  |                 </ContextMenu> | 
					
						
							| 
									
										
										
										
											2018-07-09 14:29:13 -07:00
										 |  |  |               </td> | 
					
						
							|  |  |  |             </tr> | 
					
						
							| 
									
										
										
										
											2022-01-21 11:51:40 -06:00
										 |  |  |             {receivedAt && message.direction === 'incoming' ? ( | 
					
						
							| 
									
										
										
										
											2018-07-09 14:29:13 -07:00
										 |  |  |               <tr> | 
					
						
							|  |  |  |                 <td className="module-message-detail__label"> | 
					
						
							|  |  |  |                   {i18n('received')} | 
					
						
							|  |  |  |                 </td> | 
					
						
							|  |  |  |                 <td> | 
					
						
							| 
									
										
										
										
											2022-01-26 17:05:26 -06:00
										 |  |  |                   <Time timestamp={receivedAt}> | 
					
						
							|  |  |  |                     {formatDateTimeLong(i18n, receivedAt)} | 
					
						
							|  |  |  |                   </Time>{' '} | 
					
						
							| 
									
										
										
										
											2018-07-09 14:29:13 -07:00
										 |  |  |                   <span className="module-message-detail__unix-timestamp"> | 
					
						
							|  |  |  |                     ({receivedAt}) | 
					
						
							|  |  |  |                   </span> | 
					
						
							|  |  |  |                 </td> | 
					
						
							|  |  |  |               </tr> | 
					
						
							|  |  |  |             ) : null} | 
					
						
							| 
									
										
										
										
											2022-09-09 12:35:00 -06:00
										 |  |  |             {timeRemaining && timeRemaining > 0 && ( | 
					
						
							|  |  |  |               <tr> | 
					
						
							|  |  |  |                 <td className="module-message-detail__label"> | 
					
						
							|  |  |  |                   {i18n('MessageDetail--disappears-in')} | 
					
						
							|  |  |  |                 </td> | 
					
						
							|  |  |  |                 <td> | 
					
						
							| 
									
										
										
										
											2022-11-16 12:18:02 -08:00
										 |  |  |                   {formatRelativeTime(i18n, timeRemaining, { | 
					
						
							| 
									
										
										
										
											2022-09-09 12:35:00 -06:00
										 |  |  |                     largest: 2, | 
					
						
							|  |  |  |                   })} | 
					
						
							|  |  |  |                 </td> | 
					
						
							|  |  |  |               </tr> | 
					
						
							|  |  |  |             )} | 
					
						
							| 
									
										
										
										
											2018-07-09 14:29:13 -07:00
										 |  |  |           </tbody> | 
					
						
							|  |  |  |         </table> | 
					
						
							|  |  |  |         {this.renderContacts()} | 
					
						
							|  |  |  |       </div> | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |