diff --git a/js/models/messages.js b/js/models/messages.js index 6e20813bd..96d078d16 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -603,10 +603,6 @@ isRss: !!this.get('isRss'), isKickedFromGroup: conversation && conversation.get('isKickedFromGroup'), - senderIsModerator: - !!this.get('isPublic') && - conversation && - conversation.isModerator(phoneNumber), isDeletable: !this.get('isPublic') || isModerator || diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx index f28f0017c..04df5e6a1 100644 --- a/ts/components/conversation/Message.tsx +++ b/ts/components/conversation/Message.tsx @@ -4,10 +4,8 @@ import classNames from 'classnames'; import { Avatar } from '../Avatar'; import { Spinner } from '../Spinner'; import { MessageBody } from './MessageBody'; -import { ExpireTimer } from './ExpireTimer'; import { ImageGrid } from './ImageGrid'; import { Image } from './Image'; -import { Timestamp } from './Timestamp'; import { ContactName } from './ContactName'; import { Quote, QuotedAttachmentType } from './Quote'; import { EmbeddedContact } from './EmbeddedContact'; @@ -38,10 +36,8 @@ import _ from 'lodash'; import { animation, contextMenu, Item, Menu } from 'react-contexify'; import uuid from 'uuid'; import { InView } from 'react-intersection-observer'; -import { MetadataBadges } from './message/MetadataBadge'; -import { MetadataSpacer } from './message/MetadataUtilComponent'; import { DefaultTheme, withTheme } from 'styled-components'; -import { OutgoingMessageStatus } from './message/OutgoingMessageStatus'; +import { MessageMetadata } from './message/MessageMetadata'; // Same as MIN_WIDTH in ImageGrid.tsx const MINIMUM_LINK_PREVIEW_IMAGE_WIDTH = 200; @@ -55,7 +51,6 @@ interface LinkPreviewType { export interface Props { disableMenu?: boolean; - senderIsModerator?: boolean; isDeletable: boolean; isModerator?: boolean; text?: string; @@ -212,96 +207,6 @@ class MessageInner extends React.PureComponent { }); } - public renderMetadataBadges(withImageNoCaption: boolean) { - const { direction, isPublic, senderIsModerator, id } = this.props; - - return ( - - ); - } - - public renderMetadata() { - const { - collapseMetadata, - direction, - expirationLength, - expirationTimestamp, - status, - text, - bodyPending, - timestamp, - serverTimestamp, - } = this.props; - - if (collapseMetadata) { - return null; - } - const isOutgoing = direction === 'outgoing'; - - const isShowingImage = this.isShowingImage(); - const withImageNoCaption = Boolean(!text && isShowingImage); - const showError = status === 'error' && isOutgoing; - - const showStatus = Boolean(!bodyPending && status?.length && isOutgoing); - return ( -
- {showError ? ( - - {window.i18n('sendFailed')} - - ) : ( - - )} - {this.renderMetadataBadges(withImageNoCaption)} - {expirationLength && expirationTimestamp ? ( - - ) : null} - - {bodyPending ? : null} - - {showStatus ? ( - - ) : null} -
- ); - } - // tslint:disable-next-line max-func-body-length cyclomatic-complexity public renderAttachment() { const { @@ -686,7 +591,7 @@ class MessageInner extends React.PureComponent { authorPhoneNumber, authorProfileName, collapseMetadata, - senderIsModerator, + isModerator, conversationType, direction, onShowUserDetails, @@ -717,7 +622,7 @@ class MessageInner extends React.PureComponent { }} pubkey={authorPhoneNumber} /> - {senderIsModerator && ( + {isModerator && (
@@ -910,7 +815,7 @@ class MessageInner extends React.PureComponent { return; } - public isShowingImage() { + public isShowingImage(): boolean { const { attachments, previews } = this.props; const { imageBroken } = this.state; @@ -921,10 +826,10 @@ class MessageInner extends React.PureComponent { if (attachments && attachments.length) { const displayImage = canDisplayImage(attachments); - return ( + return Boolean( displayImage && - ((isImage(attachments) && hasImage(attachments)) || - (isVideo(attachments) && hasVideoScreenshot(attachments))) + ((isImage(attachments) && hasImage(attachments)) || + (isVideo(attachments) && hasVideoScreenshot(attachments))) ); } @@ -1078,7 +983,10 @@ class MessageInner extends React.PureComponent { {this.renderPreview()} {this.renderEmbeddedContact()} {this.renderText()} - {this.renderMetadata()} +
{this.renderError(!isIncoming)} {this.renderContextMenu()} diff --git a/ts/components/conversation/message/MessageMetadata.tsx b/ts/components/conversation/message/MessageMetadata.tsx new file mode 100644 index 000000000..5555bb60c --- /dev/null +++ b/ts/components/conversation/message/MessageMetadata.tsx @@ -0,0 +1,116 @@ +import React from 'react'; +import classNames from 'classnames'; + +import { MetadataSpacer } from './MetadataUtilComponent'; +import { OutgoingMessageStatus } from './OutgoingMessageStatus'; +import { Spinner } from '../../Spinner'; +import { MetadataBadges } from './MetadataBadge'; +import { Timestamp } from '../Timestamp'; +import { ExpireTimer } from '../ExpireTimer'; +import { DefaultTheme } from 'styled-components'; + +type Props = { + disableMenu?: boolean; + isModerator?: boolean; + isDeletable: boolean; + text?: string; + bodyPending?: boolean; + id: string; + collapseMetadata?: boolean; + direction: 'incoming' | 'outgoing'; + timestamp: number; + serverTimestamp?: number; + status?: 'sending' | 'sent' | 'delivered' | 'read' | 'error' | 'pow'; + expirationLength?: number; + expirationTimestamp?: number; + isPublic?: boolean; + isShowingImage: boolean; + theme: DefaultTheme; +}; + +export const MessageMetadata = (props: Props) => { + const { + id, + collapseMetadata, + direction, + expirationLength, + expirationTimestamp, + status, + text, + bodyPending, + timestamp, + serverTimestamp, + isShowingImage, + isPublic, + isModerator, + theme, + } = props; + + if (collapseMetadata) { + return null; + } + const isOutgoing = direction === 'outgoing'; + + const withImageNoCaption = Boolean(!text && isShowingImage); + const showError = status === 'error' && isOutgoing; + + const showStatus = Boolean(!bodyPending && status?.length && isOutgoing); + return ( +
+ {showError ? ( + + {window.i18n('sendFailed')} + + ) : ( + + )} + + + {expirationLength && expirationTimestamp ? ( + + ) : null} + + {bodyPending ? : null} + + {showStatus ? ( + + ) : null} +
+ ); +}; diff --git a/ts/components/conversation/message/MetadataBadge.tsx b/ts/components/conversation/message/MetadataBadge.tsx index 261e666a7..ec18f0cf8 100644 --- a/ts/components/conversation/message/MetadataBadge.tsx +++ b/ts/components/conversation/message/MetadataBadge.tsx @@ -50,21 +50,15 @@ type BadgesProps = { id: string; direction: string; isPublic?: boolean; - senderIsModerator?: boolean; + isModerator?: boolean; withImageNoCaption: boolean; }; export const MetadataBadges = (props: BadgesProps): JSX.Element => { - const { - id, - direction, - isPublic, - senderIsModerator, - withImageNoCaption, - } = props; + const { id, direction, isPublic, isModerator, withImageNoCaption } = props; const badges = [ (isPublic && 'Public') || null, - (senderIsModerator && 'Mod') || null, + (isModerator && 'Mod') || null, ].filter(nonNullish); if (!badges || badges.length === 0) { diff --git a/ts/types/Attachment.ts b/ts/types/Attachment.ts index b0d2902af..bda7d3f94 100644 --- a/ts/types/Attachment.ts +++ b/ts/types/Attachment.ts @@ -124,30 +124,30 @@ export function isImage(attachments?: Array) { ); } -export function isImageAttachment(attachment: AttachmentType) { - return ( +export function isImageAttachment(attachment: AttachmentType): boolean { + return Boolean( attachment && - attachment.contentType && - isImageTypeSupported(attachment.contentType) + attachment.contentType && + isImageTypeSupported(attachment.contentType) ); } -export function hasImage(attachments?: Array) { - return ( +export function hasImage(attachments?: Array): boolean { + return Boolean( attachments && - attachments[0] && - (attachments[0].url || attachments[0].pending) + attachments[0] && + (attachments[0].url || attachments[0].pending) ); } -export function isVideo(attachments?: Array) { - return attachments && isVideoAttachment(attachments[0]); +export function isVideo(attachments?: Array): boolean { + return Boolean(attachments && isVideoAttachment(attachments[0])); } export function isVideoAttachment(attachment?: AttachmentType): boolean { - return ( + return Boolean( !!attachment && - !!attachment.contentType && - isVideoTypeSupported(attachment.contentType) + !!attachment.contentType && + isVideoTypeSupported(attachment.contentType) ); }