From 20c806be2d2dba387c2ab42df115195836bdcf8f Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 19 Jan 2021 15:24:15 +1100 Subject: [PATCH] fix being able to remove messages from anyone as a moderator --- js/models/messages.d.ts | 71 +++++++++++++++++- js/models/messages.js | 9 +-- ts/components/conversation/Message.tsx | 73 +------------------ ts/components/conversation/MessageDetail.tsx | 5 +- .../conversation/SessionConversation.tsx | 2 +- .../conversation/SessionMessagesList.tsx | 23 +++++- 6 files changed, 99 insertions(+), 84 deletions(-) diff --git a/js/models/messages.d.ts b/js/models/messages.d.ts index e3837a8ad..8ce9695b8 100644 --- a/js/models/messages.d.ts +++ b/js/models/messages.d.ts @@ -1,3 +1,4 @@ +import { LocalizerType } from '../../ts/types/Util'; import { ConversationModel } from './conversations'; type MessageModelType = 'incoming' | 'outgoing'; @@ -45,6 +46,74 @@ interface MessageAttributes { status: MessageDeliveryStatus; } +export interface MessageRegularProps { + disableMenu?: boolean; + isDeletable: boolean; + isAdmin?: boolean; + weAreAdmin?: boolean; + text?: string; + bodyPending?: boolean; + id: string; + collapseMetadata?: boolean; + direction: 'incoming' | 'outgoing'; + timestamp: number; + serverTimestamp?: number; + status?: 'sending' | 'sent' | 'delivered' | 'read' | 'error' | 'pow'; + // What if changed this over to a single contact like quote, and put the events on it? + contact?: Contact & { + hasSignalAccount: boolean; + onSendMessage?: () => void; + onClick?: () => void; + }; + authorName?: string; + authorProfileName?: string; + /** Note: this should be formatted for display */ + authorPhoneNumber: string; + conversationType: 'group' | 'direct'; + attachments?: Array; + quote?: { + text: string; + attachment?: QuotedAttachmentType; + isFromMe: boolean; + authorPhoneNumber: string; + authorProfileName?: string; + authorName?: string; + messageId?: string; + onClick: (data: any) => void; + referencedMessageNotFound: boolean; + }; + previews: Array; + authorAvatarPath?: string; + isExpired: boolean; + expirationLength?: number; + expirationTimestamp?: number; + convoId: string; + isPublic?: boolean; + isRss?: boolean; + selected: boolean; + isKickedFromGroup: boolean; + // whether or not to show check boxes + multiSelectMode: boolean; + firstMessageOfSeries: boolean; + isUnread: boolean; + isQuotedMessageToAnimate?: boolean; + + onClickAttachment?: (attachment: AttachmentType) => void; + onClickLinkPreview?: (url: string) => void; + onCopyText?: () => void; + onSelectMessage: (messageId: string) => void; + onReply?: (messagId: number) => void; + onRetrySend?: () => void; + onDownload?: (attachment: AttachmentType) => void; + onDeleteMessage: (messageId: string) => void; + onCopyPubKey?: () => void; + onBanUser?: () => void; + onShowDetail: () => void; + onShowUserDetails: (userPubKey: string) => void; + markRead: (readAt: number) => Promise; + theme: DefaultTheme; +} + export interface MessageModel extends Backbone.Model { idForLogging: () => string; isGroupUpdate: () => boolean; @@ -62,7 +131,7 @@ export interface MessageModel extends Backbone.Model { handleMessageSentSuccess: (sentMessage: any, wrappedEnvelope: any) => any; handleMessageSentFailure: (sentMessage: any, error: any) => any; - propsForMessage?: any; + propsForMessage?: MessageRegularProps; propsForTimerNotification?: any; propsForResetSessionNotification?: any; propsForGroupInvitation?: any; diff --git a/js/models/messages.js b/js/models/messages.js index 32e9b77fe..4d580ccda 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -572,6 +572,7 @@ const convoId = conversation ? conversation.id : undefined; const isGroup = !!conversation && !conversation.isPrivate(); + const isPublic = !!this.get('isPublic'); const attachments = this.get('attachments') || []; @@ -599,15 +600,11 @@ isUnread: this.isUnread(), expirationLength, expirationTimestamp, - isPublic: !!this.get('isPublic'), + isPublic, isRss: !!this.get('isRss'), isKickedFromGroup: conversation && conversation.get('isKickedFromGroup'), - isDeletable: - !this.get('isPublic') || - isAdmin || - phoneNumber === textsecure.storage.user.getNumber(), - isAdmin, + isAdmin, // if the sender is an admin (not us) onCopyText: () => this.copyText(), onCopyPubKey: () => this.copyPubKey(), diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx index e4f3d1720..1a530dd57 100644 --- a/ts/components/conversation/Message.tsx +++ b/ts/components/conversation/Message.tsx @@ -38,6 +38,7 @@ import uuid from 'uuid'; import { InView } from 'react-intersection-observer'; import { DefaultTheme, withTheme } from 'styled-components'; import { MessageMetadata } from './message/MessageMetadata'; +import { MessageRegularProps } from '../../../js/models/messages'; // Same as MIN_WIDTH in ImageGrid.tsx const MINIMUM_LINK_PREVIEW_IMAGE_WIDTH = 200; @@ -49,74 +50,6 @@ interface LinkPreviewType { image?: AttachmentType; } -export interface Props { - disableMenu?: boolean; - isDeletable: boolean; - isAdmin?: boolean; - weAreAdmin?: boolean; - text?: string; - bodyPending?: boolean; - id: string; - collapseMetadata?: boolean; - direction: 'incoming' | 'outgoing'; - timestamp: number; - serverTimestamp?: number; - status?: 'sending' | 'sent' | 'delivered' | 'read' | 'error' | 'pow'; - // What if changed this over to a single contact like quote, and put the events on it? - contact?: Contact & { - hasSignalAccount: boolean; - onSendMessage?: () => void; - onClick?: () => void; - }; - authorName?: string; - authorProfileName?: string; - /** Note: this should be formatted for display */ - authorPhoneNumber: string; - conversationType: 'group' | 'direct'; - attachments?: Array; - quote?: { - text: string; - attachment?: QuotedAttachmentType; - isFromMe: boolean; - authorPhoneNumber: string; - authorProfileName?: string; - authorName?: string; - messageId?: string; - onClick: (data: any) => void; - referencedMessageNotFound: boolean; - }; - previews: Array; - authorAvatarPath?: string; - isExpired: boolean; - expirationLength?: number; - expirationTimestamp?: number; - convoId: string; - isPublic?: boolean; - isRss?: boolean; - selected: boolean; - isKickedFromGroup: boolean; - // whether or not to show check boxes - multiSelectMode: boolean; - firstMessageOfSeries: boolean; - isUnread: boolean; - isQuotedMessageToAnimate?: boolean; - - onClickAttachment?: (attachment: AttachmentType) => void; - onClickLinkPreview?: (url: string) => void; - onCopyText?: () => void; - onSelectMessage: (messageId: string) => void; - onReply?: (messagId: number) => void; - onRetrySend?: () => void; - onDownload?: (attachment: AttachmentType) => void; - onDeleteMessage: (messageId: string) => void; - onCopyPubKey?: () => void; - onBanUser?: () => void; - onShowDetail: () => void; - onShowUserDetails: (userPubKey: string) => void; - markRead: (readAt: number) => Promise; - theme: DefaultTheme; -} - interface State { expiring: boolean; expired: boolean; @@ -126,14 +59,14 @@ interface State { const EXPIRATION_CHECK_MINIMUM = 2000; const EXPIRED_DELAY = 600; -class MessageInner extends React.PureComponent { +class MessageInner extends React.PureComponent { public handleImageErrorBound: () => void; public expirationCheckInterval: any; public expiredTimeout: any; public ctxMenuID: string; - public constructor(props: Props) { + public constructor(props: MessageRegularProps) { super(props); this.handleImageErrorBound = this.handleImageError.bind(this); diff --git a/ts/components/conversation/MessageDetail.tsx b/ts/components/conversation/MessageDetail.tsx index 992564997..ba8770bf0 100644 --- a/ts/components/conversation/MessageDetail.tsx +++ b/ts/components/conversation/MessageDetail.tsx @@ -4,7 +4,8 @@ import moment from 'moment'; import { Avatar } from '../Avatar'; import { ContactName } from './ContactName'; -import { Message, Props as MessageProps } from './Message'; +import { Message } from './Message'; +import { MessageRegularProps } from '../../../js/models/messages'; interface Contact { status: string; @@ -26,7 +27,7 @@ interface Props { sentAt: number; receivedAt: number; - message: MessageProps; + message: MessageRegularProps; errors: Array; contacts: Array; diff --git a/ts/components/session/conversation/SessionConversation.tsx b/ts/components/session/conversation/SessionConversation.tsx index c274406e2..250034839 100644 --- a/ts/components/session/conversation/SessionConversation.tsx +++ b/ts/components/session/conversation/SessionConversation.tsx @@ -294,8 +294,8 @@ export class SessionConversation extends React.Component { const showMessageDetails = !!messageDetailShowProps; const isPublic = selectedConversation.isPublic || false; - const isPrivate = selectedConversation.type === 'direct'; + const isPrivate = selectedConversation.type === 'direct'; return (
{this.renderHeader()}
diff --git a/ts/components/session/conversation/SessionMessagesList.tsx b/ts/components/session/conversation/SessionMessagesList.tsx index c096dc7b9..5aa98f776 100644 --- a/ts/components/session/conversation/SessionMessagesList.tsx +++ b/ts/components/session/conversation/SessionMessagesList.tsx @@ -12,7 +12,10 @@ import { AttachmentType } from '../../../types/Attachment'; import { GroupNotification } from '../../conversation/GroupNotification'; import { GroupInvitation } from '../../conversation/GroupInvitation'; import { ConversationType } from '../../../state/ducks/conversations'; -import { MessageModel } from '../../../../js/models/messages'; +import { + MessageModel, + MessageRegularProps, +} from '../../../../js/models/messages'; import { SessionLastSeenIndicator } from './SessionLastSeedIndicator'; import { VerificationNotification } from '../../conversation/VerificationNotification'; import { ToastUtils } from '../../../session/utils'; @@ -296,12 +299,25 @@ export class SessionMessagesList extends React.Component { ); } + if (!messageProps) { + return; + } if (messageProps.conversationType === 'group') { messageProps.weAreAdmin = conversation.groupAdmins?.includes( ourPrimary ); } + // a message is deletable if + // either we sent it, + // or the convo is not a public one (in this case, we will only be able to delete for us) + // or the convo is public and we are an admin + const isDeletable = + messageProps.authorPhoneNumber === this.props.ourPrimary || + !conversation.isPublic || + (conversation.isPublic && !!messageProps.weAreAdmin); + + messageProps.isDeletable = isDeletable; // firstMessageOfSeries tells us to render the avatar only for the first message // in a series of messages from the same user @@ -322,7 +338,7 @@ export class SessionMessagesList extends React.Component { } private renderMessage( - messageProps: any, + messageProps: MessageRegularProps, firstMessageOfSeries: boolean, multiSelectMode: boolean, message: MessageModel @@ -331,7 +347,6 @@ export class SessionMessagesList extends React.Component { !!messageProps?.id && this.props.selectedMessages.includes(messageProps.id); - messageProps.i18n = window.i18n; messageProps.selected = selected; messageProps.firstMessageOfSeries = firstMessageOfSeries; @@ -344,7 +359,7 @@ export class SessionMessagesList extends React.Component { void this.props.showMessageDetails(messageDetailsProps); }; - messageProps.onClickAttachment = (attachment: any) => { + messageProps.onClickAttachment = (attachment: AttachmentType) => { this.props.onClickAttachment(attachment, messageProps); }; messageProps.onDownload = (attachment: AttachmentType) => {