| 
									
										
										
										
											2021-02-23 14:34:28 -06:00
										 |  |  | // Copyright 2021 Signal Messenger, LLC
 | 
					
						
							|  |  |  | // SPDX-License-Identifier: AGPL-3.0-only
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-11 09:23:21 -07:00
										 |  |  | import React, { ReactNode, FunctionComponent } from 'react'; | 
					
						
							| 
									
										
										
										
											2021-02-23 14:34:28 -06:00
										 |  |  | import classNames from 'classnames'; | 
					
						
							|  |  |  | import { isBoolean, isNumber } from 'lodash'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import { Avatar, AvatarSize } from '../Avatar'; | 
					
						
							|  |  |  | import { Timestamp } from '../conversation/Timestamp'; | 
					
						
							|  |  |  | import { isConversationUnread } from '../../util/isConversationUnread'; | 
					
						
							|  |  |  | import { cleanId } from '../_util'; | 
					
						
							|  |  |  | import { LocalizerType } from '../../types/Util'; | 
					
						
							| 
									
										
										
										
											2021-04-30 14:40:25 -05:00
										 |  |  | import { ConversationType } from '../../state/ducks/conversations'; | 
					
						
							| 
									
										
										
										
											2021-02-23 14:34:28 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | const BASE_CLASS_NAME = | 
					
						
							|  |  |  |   'module-conversation-list__item--contact-or-conversation'; | 
					
						
							|  |  |  | const CONTENT_CLASS_NAME = `${BASE_CLASS_NAME}__content`; | 
					
						
							|  |  |  | const HEADER_CLASS_NAME = `${CONTENT_CLASS_NAME}__header`; | 
					
						
							| 
									
										
										
										
											2021-10-14 10:48:48 -05:00
										 |  |  | export const HEADER_NAME_CLASS_NAME = `${HEADER_CLASS_NAME}__name`; | 
					
						
							|  |  |  | export const HEADER_CONTACT_NAME_CLASS_NAME = `${HEADER_NAME_CLASS_NAME}__contact-name`; | 
					
						
							| 
									
										
										
										
											2021-02-23 14:34:28 -06:00
										 |  |  | export const DATE_CLASS_NAME = `${HEADER_CLASS_NAME}__date`; | 
					
						
							|  |  |  | const TIMESTAMP_CLASS_NAME = `${DATE_CLASS_NAME}__timestamp`; | 
					
						
							| 
									
										
										
										
											2021-03-12 16:04:56 -06:00
										 |  |  | const MESSAGE_CLASS_NAME = `${CONTENT_CLASS_NAME}__message`; | 
					
						
							| 
									
										
										
										
											2021-02-23 14:34:28 -06:00
										 |  |  | export const MESSAGE_TEXT_CLASS_NAME = `${MESSAGE_CLASS_NAME}__text`; | 
					
						
							| 
									
										
										
										
											2021-03-03 14:09:58 -06:00
										 |  |  | const CHECKBOX_CLASS_NAME = `${BASE_CLASS_NAME}__checkbox`; | 
					
						
							| 
									
										
										
										
											2021-02-23 14:34:28 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | type PropsType = { | 
					
						
							| 
									
										
										
										
											2021-03-03 14:09:58 -06:00
										 |  |  |   checked?: boolean; | 
					
						
							| 
									
										
										
										
											2021-02-23 14:34:28 -06:00
										 |  |  |   conversationType: 'group' | 'direct'; | 
					
						
							| 
									
										
										
										
											2021-03-03 14:09:58 -06:00
										 |  |  |   disabled?: boolean; | 
					
						
							| 
									
										
										
										
											2021-02-23 14:34:28 -06:00
										 |  |  |   headerDate?: number; | 
					
						
							|  |  |  |   headerName: ReactNode; | 
					
						
							|  |  |  |   id?: string; | 
					
						
							| 
									
										
										
										
											2021-04-30 14:40:25 -05:00
										 |  |  |   i18n: LocalizerType; | 
					
						
							| 
									
										
										
										
											2021-02-23 14:34:28 -06:00
										 |  |  |   isNoteToSelf?: boolean; | 
					
						
							|  |  |  |   isSelected: boolean; | 
					
						
							|  |  |  |   markedUnread?: boolean; | 
					
						
							|  |  |  |   messageId?: string; | 
					
						
							|  |  |  |   messageStatusIcon?: ReactNode; | 
					
						
							|  |  |  |   messageText?: ReactNode; | 
					
						
							| 
									
										
										
										
											2021-10-12 18:59:08 -05:00
										 |  |  |   messageTextIsAlwaysFullSize?: boolean; | 
					
						
							| 
									
										
										
										
											2021-03-03 14:09:58 -06:00
										 |  |  |   onClick?: () => void; | 
					
						
							| 
									
										
										
										
											2021-02-23 14:34:28 -06:00
										 |  |  |   unreadCount?: number; | 
					
						
							| 
									
										
										
										
											2021-04-30 14:40:25 -05:00
										 |  |  | } & Pick< | 
					
						
							|  |  |  |   ConversationType, | 
					
						
							|  |  |  |   | 'acceptedMessageRequest' | 
					
						
							|  |  |  |   | 'avatarPath' | 
					
						
							|  |  |  |   | 'color' | 
					
						
							|  |  |  |   | 'isMe' | 
					
						
							|  |  |  |   | 'markedUnread' | 
					
						
							|  |  |  |   | 'name' | 
					
						
							|  |  |  |   | 'phoneNumber' | 
					
						
							|  |  |  |   | 'profileName' | 
					
						
							|  |  |  |   | 'sharedGroupNames' | 
					
						
							|  |  |  |   | 'title' | 
					
						
							|  |  |  |   | 'unblurredAvatarPath' | 
					
						
							|  |  |  | >; | 
					
						
							| 
									
										
										
										
											2021-02-23 14:34:28 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | export const BaseConversationListItem: FunctionComponent<PropsType> = React.memo( | 
					
						
							| 
									
										
										
										
											2021-08-11 12:29:07 -07:00
										 |  |  |   function BaseConversationListItem({ | 
					
						
							| 
									
										
										
										
											2021-04-30 14:40:25 -05:00
										 |  |  |     acceptedMessageRequest, | 
					
						
							| 
									
										
										
										
											2021-02-23 14:34:28 -06:00
										 |  |  |     avatarPath, | 
					
						
							| 
									
										
										
										
											2021-03-03 14:09:58 -06:00
										 |  |  |     checked, | 
					
						
							| 
									
										
										
										
											2021-02-23 14:34:28 -06:00
										 |  |  |     color, | 
					
						
							|  |  |  |     conversationType, | 
					
						
							| 
									
										
										
										
											2021-03-03 14:09:58 -06:00
										 |  |  |     disabled, | 
					
						
							| 
									
										
										
										
											2021-02-23 14:34:28 -06:00
										 |  |  |     headerDate, | 
					
						
							|  |  |  |     headerName, | 
					
						
							|  |  |  |     i18n, | 
					
						
							|  |  |  |     id, | 
					
						
							|  |  |  |     isMe, | 
					
						
							|  |  |  |     isNoteToSelf, | 
					
						
							|  |  |  |     isSelected, | 
					
						
							|  |  |  |     markedUnread, | 
					
						
							|  |  |  |     messageStatusIcon, | 
					
						
							|  |  |  |     messageText, | 
					
						
							| 
									
										
										
										
											2021-10-12 18:59:08 -05:00
										 |  |  |     messageTextIsAlwaysFullSize, | 
					
						
							| 
									
										
										
										
											2021-02-23 14:34:28 -06:00
										 |  |  |     name, | 
					
						
							|  |  |  |     onClick, | 
					
						
							|  |  |  |     phoneNumber, | 
					
						
							|  |  |  |     profileName, | 
					
						
							| 
									
										
										
										
											2021-04-30 14:40:25 -05:00
										 |  |  |     sharedGroupNames, | 
					
						
							| 
									
										
										
										
											2021-02-23 14:34:28 -06:00
										 |  |  |     title, | 
					
						
							| 
									
										
										
										
											2021-04-30 14:40:25 -05:00
										 |  |  |     unblurredAvatarPath, | 
					
						
							| 
									
										
										
										
											2021-02-23 14:34:28 -06:00
										 |  |  |     unreadCount, | 
					
						
							| 
									
										
										
										
											2021-08-11 12:29:07 -07:00
										 |  |  |   }) { | 
					
						
							| 
									
										
										
										
											2021-09-17 19:48:57 -04:00
										 |  |  |     const identifier = id ? cleanId(id) : undefined; | 
					
						
							| 
									
										
										
										
											2021-02-23 14:34:28 -06:00
										 |  |  |     const isUnread = isConversationUnread({ markedUnread, unreadCount }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const isAvatarNoteToSelf = isBoolean(isNoteToSelf) | 
					
						
							|  |  |  |       ? isNoteToSelf | 
					
						
							|  |  |  |       : Boolean(isMe); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-03 14:09:58 -06:00
										 |  |  |     const isCheckbox = isBoolean(checked); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     let checkboxNode: ReactNode; | 
					
						
							|  |  |  |     if (isCheckbox) { | 
					
						
							|  |  |  |       let ariaLabel: string; | 
					
						
							|  |  |  |       if (disabled) { | 
					
						
							| 
									
										
										
										
											2021-09-17 19:48:57 -04:00
										 |  |  |         ariaLabel = i18n('cannotSelectContact', [title]); | 
					
						
							| 
									
										
										
										
											2021-03-03 14:09:58 -06:00
										 |  |  |       } else if (checked) { | 
					
						
							| 
									
										
										
										
											2021-09-17 19:48:57 -04:00
										 |  |  |         ariaLabel = i18n('deselectContact', [title]); | 
					
						
							| 
									
										
										
										
											2021-03-03 14:09:58 -06:00
										 |  |  |       } else { | 
					
						
							| 
									
										
										
										
											2021-09-17 19:48:57 -04:00
										 |  |  |         ariaLabel = i18n('selectContact', [title]); | 
					
						
							| 
									
										
										
										
											2021-03-03 14:09:58 -06:00
										 |  |  |       } | 
					
						
							|  |  |  |       checkboxNode = ( | 
					
						
							|  |  |  |         <input | 
					
						
							|  |  |  |           aria-label={ariaLabel} | 
					
						
							|  |  |  |           checked={checked} | 
					
						
							|  |  |  |           className={CHECKBOX_CLASS_NAME} | 
					
						
							|  |  |  |           disabled={disabled} | 
					
						
							| 
									
										
										
										
											2021-09-17 19:48:57 -04:00
										 |  |  |           id={identifier} | 
					
						
							| 
									
										
										
										
											2021-03-03 14:09:58 -06:00
										 |  |  |           onChange={onClick} | 
					
						
							| 
									
										
										
										
											2021-03-11 15:29:31 -06:00
										 |  |  |           onKeyDown={event => { | 
					
						
							|  |  |  |             if (onClick && !disabled && event.key === 'Enter') { | 
					
						
							|  |  |  |               onClick(); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           }} | 
					
						
							| 
									
										
										
										
											2021-03-03 14:09:58 -06:00
										 |  |  |           type="checkbox" | 
					
						
							|  |  |  |         /> | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const contents = ( | 
					
						
							|  |  |  |       <> | 
					
						
							| 
									
										
										
										
											2021-10-12 18:59:08 -05:00
										 |  |  |         <Avatar | 
					
						
							|  |  |  |           acceptedMessageRequest={acceptedMessageRequest} | 
					
						
							|  |  |  |           avatarPath={avatarPath} | 
					
						
							|  |  |  |           color={color} | 
					
						
							|  |  |  |           conversationType={conversationType} | 
					
						
							|  |  |  |           noteToSelf={isAvatarNoteToSelf} | 
					
						
							|  |  |  |           i18n={i18n} | 
					
						
							|  |  |  |           isMe={isMe} | 
					
						
							|  |  |  |           name={name} | 
					
						
							|  |  |  |           phoneNumber={phoneNumber} | 
					
						
							|  |  |  |           profileName={profileName} | 
					
						
							|  |  |  |           title={title} | 
					
						
							|  |  |  |           sharedGroupNames={sharedGroupNames} | 
					
						
							|  |  |  |           size={AvatarSize.FORTY_EIGHT} | 
					
						
							|  |  |  |           unblurredAvatarPath={unblurredAvatarPath} | 
					
						
							|  |  |  |         /> | 
					
						
							| 
									
										
										
										
											2021-03-03 14:09:58 -06:00
										 |  |  |         <div | 
					
						
							|  |  |  |           className={classNames( | 
					
						
							|  |  |  |             CONTENT_CLASS_NAME, | 
					
						
							|  |  |  |             disabled && `${CONTENT_CLASS_NAME}--disabled` | 
					
						
							|  |  |  |           )} | 
					
						
							|  |  |  |         > | 
					
						
							| 
									
										
										
										
											2021-02-23 14:34:28 -06:00
										 |  |  |           <div className={HEADER_CLASS_NAME}> | 
					
						
							|  |  |  |             <div className={`${HEADER_CLASS_NAME}__name`}>{headerName}</div> | 
					
						
							|  |  |  |             {isNumber(headerDate) && ( | 
					
						
							| 
									
										
										
										
											2021-10-12 18:59:08 -05:00
										 |  |  |               <div className={DATE_CLASS_NAME}> | 
					
						
							| 
									
										
										
										
											2021-02-23 14:34:28 -06:00
										 |  |  |                 <Timestamp | 
					
						
							|  |  |  |                   timestamp={headerDate} | 
					
						
							|  |  |  |                   extended={false} | 
					
						
							|  |  |  |                   module={TIMESTAMP_CLASS_NAME} | 
					
						
							|  |  |  |                   i18n={i18n} | 
					
						
							|  |  |  |                 /> | 
					
						
							|  |  |  |               </div> | 
					
						
							|  |  |  |             )} | 
					
						
							|  |  |  |           </div> | 
					
						
							| 
									
										
										
										
											2021-10-12 18:59:08 -05:00
										 |  |  |           {messageText || isUnread ? ( | 
					
						
							| 
									
										
										
										
											2021-02-23 14:34:28 -06:00
										 |  |  |             <div className={MESSAGE_CLASS_NAME}> | 
					
						
							| 
									
										
										
										
											2021-10-12 18:59:08 -05:00
										 |  |  |               {Boolean(messageText) && ( | 
					
						
							|  |  |  |                 <div | 
					
						
							|  |  |  |                   dir="auto" | 
					
						
							|  |  |  |                   className={classNames( | 
					
						
							|  |  |  |                     MESSAGE_TEXT_CLASS_NAME, | 
					
						
							|  |  |  |                     messageTextIsAlwaysFullSize && | 
					
						
							|  |  |  |                       `${MESSAGE_TEXT_CLASS_NAME}--always-full-size` | 
					
						
							|  |  |  |                   )} | 
					
						
							|  |  |  |                 > | 
					
						
							|  |  |  |                   {messageText} | 
					
						
							|  |  |  |                 </div> | 
					
						
							|  |  |  |               )} | 
					
						
							| 
									
										
										
										
											2021-02-23 14:34:28 -06:00
										 |  |  |               {messageStatusIcon} | 
					
						
							| 
									
										
										
										
											2021-10-14 15:21:10 -05:00
										 |  |  |               {isUnread && <UnreadCount count={unreadCount} />} | 
					
						
							| 
									
										
										
										
											2021-02-23 14:34:28 -06:00
										 |  |  |             </div> | 
					
						
							|  |  |  |           ) : null} | 
					
						
							|  |  |  |         </div> | 
					
						
							| 
									
										
										
										
											2021-03-03 14:09:58 -06:00
										 |  |  |         {checkboxNode} | 
					
						
							|  |  |  |       </> | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const commonClassNames = classNames(BASE_CLASS_NAME, { | 
					
						
							|  |  |  |       [`${BASE_CLASS_NAME}--is-selected`]: isSelected, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (isCheckbox) { | 
					
						
							|  |  |  |       return ( | 
					
						
							|  |  |  |         <label | 
					
						
							|  |  |  |           className={classNames( | 
					
						
							|  |  |  |             commonClassNames, | 
					
						
							|  |  |  |             `${BASE_CLASS_NAME}--is-checkbox`, | 
					
						
							|  |  |  |             { [`${BASE_CLASS_NAME}--is-checkbox--disabled`]: disabled } | 
					
						
							|  |  |  |           )} | 
					
						
							| 
									
										
										
										
											2021-09-17 19:48:57 -04:00
										 |  |  |           data-id={identifier} | 
					
						
							|  |  |  |           htmlFor={identifier} | 
					
						
							| 
									
										
										
										
											2021-03-03 14:09:58 -06:00
										 |  |  |           // `onClick` is will double-fire if we're enabled. We want it to fire when we're
 | 
					
						
							|  |  |  |           //   disabled so we can show any "can't add contact" modals, etc. This won't
 | 
					
						
							|  |  |  |           //   work for keyboard users, though, because labels are not tabbable.
 | 
					
						
							|  |  |  |           {...(disabled ? { onClick } : {})} | 
					
						
							|  |  |  |         > | 
					
						
							|  |  |  |           {contents} | 
					
						
							|  |  |  |         </label> | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (onClick) { | 
					
						
							|  |  |  |       return ( | 
					
						
							|  |  |  |         <button | 
					
						
							| 
									
										
										
										
											2021-10-07 19:52:48 -04:00
										 |  |  |           aria-label={i18n('BaseConversationListItem__aria-label', { title })} | 
					
						
							| 
									
										
										
										
											2021-03-03 14:09:58 -06:00
										 |  |  |           className={classNames( | 
					
						
							|  |  |  |             commonClassNames, | 
					
						
							|  |  |  |             `${BASE_CLASS_NAME}--is-button` | 
					
						
							|  |  |  |           )} | 
					
						
							| 
									
										
										
										
											2021-09-17 19:48:57 -04:00
										 |  |  |           data-id={identifier} | 
					
						
							| 
									
										
										
										
											2021-03-03 14:09:58 -06:00
										 |  |  |           disabled={disabled} | 
					
						
							|  |  |  |           onClick={onClick} | 
					
						
							|  |  |  |           type="button" | 
					
						
							|  |  |  |         > | 
					
						
							|  |  |  |           {contents} | 
					
						
							|  |  |  |         </button> | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return ( | 
					
						
							| 
									
										
										
										
											2021-09-17 19:48:57 -04:00
										 |  |  |       <div className={commonClassNames} data-id={identifier}> | 
					
						
							| 
									
										
										
										
											2021-03-03 14:09:58 -06:00
										 |  |  |         {contents} | 
					
						
							|  |  |  |       </div> | 
					
						
							| 
									
										
										
										
											2021-02-23 14:34:28 -06:00
										 |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | ); | 
					
						
							| 
									
										
										
										
											2021-10-05 18:46:51 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-14 15:21:10 -05:00
										 |  |  | function UnreadCount({ count = 0 }: Readonly<{ count?: number }>) { | 
					
						
							|  |  |  |   return ( | 
					
						
							|  |  |  |     <div | 
					
						
							|  |  |  |       className={classNames( | 
					
						
							|  |  |  |         `${BASE_CLASS_NAME}__unread-count`, | 
					
						
							|  |  |  |         count > 99 && `${BASE_CLASS_NAME}__unread-count--big` | 
					
						
							|  |  |  |       )} | 
					
						
							|  |  |  |     > | 
					
						
							|  |  |  |       {Boolean(count) && Math.min(count, 99)} | 
					
						
							|  |  |  |     </div> | 
					
						
							|  |  |  |   ); | 
					
						
							| 
									
										
										
										
											2021-10-05 18:46:51 -05:00
										 |  |  | } |