Delete For Everyone Send
Co-authored-by: Chris Svenningsen <chris@carbonfive.com>
This commit is contained in:
parent
693deaebe8
commit
866217a724
14 changed files with 276 additions and 10 deletions
|
@ -943,7 +943,12 @@
|
|||
"message": "Delete"
|
||||
},
|
||||
"deleteWarning": {
|
||||
"message": "Are you sure? Clicking 'delete' will permanently remove this message from this device only."
|
||||
"message": "Clicking 'delete' will permanently remove this message from your devices only.",
|
||||
"description": "Text shown in the confirmation dialog for deleting a message locally"
|
||||
},
|
||||
"deleteForEveryoneWarning": {
|
||||
"message": "This message will be permanently deleted for everyone in the conversation. Members will be able to see that you deleted a message.",
|
||||
"description": "Text shown in the confirmation dialog for deleting a message for everyone"
|
||||
},
|
||||
"deleteThisMessage": {
|
||||
"message": "Delete this message"
|
||||
|
@ -1014,9 +1019,13 @@
|
|||
"description": "Shown on the drop-down menu for an individual message, but only if it is an outgoing message that failed to send"
|
||||
},
|
||||
"deleteMessage": {
|
||||
"message": "Delete Message",
|
||||
"message": "Delete message for me",
|
||||
"description": "Shown on the drop-down menu for an individual message, deletes single message"
|
||||
},
|
||||
"deleteMessageForEveryone": {
|
||||
"message": "Delete message for everyone",
|
||||
"description": "Shown on the drop-down menu for an individual message, deletes single message for everyone"
|
||||
},
|
||||
"deleteMessages": {
|
||||
"message": "Delete messages",
|
||||
"description": "Menu item for deleting messages, title case."
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
return [];
|
||||
},
|
||||
async onDelete(del) {
|
||||
onDelete(del) {
|
||||
try {
|
||||
// The contact the delete message came from
|
||||
const fromContact = ConversationController.get(del.get('fromId'));
|
||||
|
|
|
@ -45,12 +45,14 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
|||
authorTitle: text('authorTitle', overrideProps.authorTitle || ''),
|
||||
bodyRanges: overrideProps.bodyRanges,
|
||||
canReply: true,
|
||||
canDeleteForEveryone: overrideProps.canDeleteForEveryone || false,
|
||||
clearSelectedMessage: action('clearSelectedMessage'),
|
||||
collapseMetadata: overrideProps.collapseMetadata,
|
||||
conversationId: text('conversationId', overrideProps.conversationId || ''),
|
||||
conversationType: overrideProps.conversationType || 'direct',
|
||||
deletedForEveryone: overrideProps.deletedForEveryone,
|
||||
deleteMessage: action('deleteMessage'),
|
||||
deleteMessageForEveryone: action('deleteMessageForEveryone'),
|
||||
disableMenu: overrideProps.disableMenu,
|
||||
disableScroll: overrideProps.disableScroll,
|
||||
direction: overrideProps.direction || 'incoming',
|
||||
|
@ -324,6 +326,16 @@ story.add('Deleted', () => {
|
|||
return renderBothDirections(props);
|
||||
});
|
||||
|
||||
story.add('Can delete for everyone', () => {
|
||||
const props = createProps({
|
||||
status: 'read',
|
||||
text: 'I hope you get this.',
|
||||
canDeleteForEveryone: true,
|
||||
});
|
||||
|
||||
return <Message {...props} direction="outgoing" />;
|
||||
});
|
||||
|
||||
story.add('Error', () => {
|
||||
const props = createProps({
|
||||
status: 'error',
|
||||
|
|
|
@ -53,6 +53,7 @@ interface Trigger {
|
|||
const MINIMUM_LINK_PREVIEW_IMAGE_WIDTH = 200;
|
||||
const STICKER_SIZE = 200;
|
||||
const SELECTED_TIMEOUT = 1000;
|
||||
const THREE_HOURS = 3 * 60 * 60 * 1000;
|
||||
|
||||
interface LinkPreviewType {
|
||||
title: string;
|
||||
|
@ -134,6 +135,7 @@ export type PropsData = {
|
|||
deletedForEveryone?: boolean;
|
||||
|
||||
canReply: boolean;
|
||||
canDeleteForEveryone: boolean;
|
||||
bodyRanges?: BodyRangesType;
|
||||
};
|
||||
|
||||
|
@ -154,6 +156,7 @@ export type PropsActions = {
|
|||
replyToMessage: (id: string) => void;
|
||||
retrySend: (id: string) => void;
|
||||
deleteMessage: (id: string) => void;
|
||||
deleteMessageForEveryone: (id: string) => void;
|
||||
showMessageDetail: (id: string) => void;
|
||||
|
||||
openConversation: (conversationId: string, messageId?: string) => void;
|
||||
|
@ -200,6 +203,7 @@ interface State {
|
|||
isWide: boolean;
|
||||
|
||||
containerWidth: number;
|
||||
canDeleteForEveryone: boolean;
|
||||
}
|
||||
|
||||
const EXPIRATION_CHECK_MINIMUM = 2000;
|
||||
|
@ -226,9 +230,13 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
|
||||
public selectedTimeout: NodeJS.Timeout | undefined;
|
||||
|
||||
public deleteForEveryoneTimeout: NodeJS.Timeout | undefined;
|
||||
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
const { canDeleteForEveryone } = props;
|
||||
|
||||
this.wideMl = window.matchMedia('(min-width: 926px)');
|
||||
this.wideMl.addEventListener('change', this.handleWideMlChange);
|
||||
|
||||
|
@ -246,6 +254,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
isWide: this.wideMl.matches,
|
||||
|
||||
containerWidth: 0,
|
||||
canDeleteForEveryone,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -322,6 +331,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
|
||||
public componentDidMount(): void {
|
||||
this.startSelectedTimer();
|
||||
this.startDeleteForEveryoneTimer();
|
||||
|
||||
const { isSelected } = this.props;
|
||||
if (isSelected) {
|
||||
|
@ -353,6 +363,9 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
if (this.expiredTimeout) {
|
||||
clearTimeout(this.expiredTimeout);
|
||||
}
|
||||
if (this.deleteForEveryoneTimeout) {
|
||||
clearTimeout(this.deleteForEveryoneTimeout);
|
||||
}
|
||||
this.toggleReactionViewer(true);
|
||||
this.toggleReactionPicker(true);
|
||||
|
||||
|
@ -360,7 +373,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
public componentDidUpdate(prevProps: Props): void {
|
||||
const { isSelected } = this.props;
|
||||
const { canDeleteForEveryone, isSelected } = this.props;
|
||||
|
||||
this.startSelectedTimer();
|
||||
|
||||
|
@ -369,6 +382,10 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
this.checkExpired();
|
||||
|
||||
if (canDeleteForEveryone !== prevProps.canDeleteForEveryone) {
|
||||
this.startDeleteForEveryoneTimer();
|
||||
}
|
||||
}
|
||||
|
||||
public startSelectedTimer(): void {
|
||||
|
@ -388,6 +405,29 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
public startDeleteForEveryoneTimer(): void {
|
||||
if (this.deleteForEveryoneTimeout) {
|
||||
clearTimeout(this.deleteForEveryoneTimeout);
|
||||
}
|
||||
|
||||
const { canDeleteForEveryone } = this.props;
|
||||
|
||||
if (!canDeleteForEveryone) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { timestamp } = this.props;
|
||||
const timeToDeletion = timestamp - Date.now() + THREE_HOURS;
|
||||
|
||||
if (timeToDeletion <= 0) {
|
||||
this.setState({ canDeleteForEveryone: false });
|
||||
} else {
|
||||
this.deleteForEveryoneTimeout = setTimeout(() => {
|
||||
this.setState({ canDeleteForEveryone: false });
|
||||
}, timeToDeletion);
|
||||
}
|
||||
}
|
||||
|
||||
public checkExpired(): void {
|
||||
const now = Date.now();
|
||||
const { isExpired, expirationTimestamp, expirationLength } = this.props;
|
||||
|
@ -1285,6 +1325,7 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
attachments,
|
||||
canReply,
|
||||
deleteMessage,
|
||||
deleteMessageForEveryone,
|
||||
direction,
|
||||
i18n,
|
||||
id,
|
||||
|
@ -1296,6 +1337,8 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
status,
|
||||
} = this.props;
|
||||
|
||||
const { canDeleteForEveryone } = this.state;
|
||||
|
||||
const showRetry = status === 'error' && direction === 'outgoing';
|
||||
const multipleAttachments = attachments && attachments.length > 1;
|
||||
|
||||
|
@ -1386,6 +1429,21 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
>
|
||||
{i18n('deleteMessage')}
|
||||
</MenuItem>
|
||||
{canDeleteForEveryone ? (
|
||||
<MenuItem
|
||||
attributes={{
|
||||
className: 'module-message__context__delete-message-for-everyone',
|
||||
}}
|
||||
onClick={(event: React.MouseEvent) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
deleteMessageForEveryone(id);
|
||||
}}
|
||||
>
|
||||
{i18n('deleteMessageForEveryone')}
|
||||
</MenuItem>
|
||||
) : null}
|
||||
</ContextMenu>
|
||||
);
|
||||
|
||||
|
|
|
@ -16,10 +16,12 @@ const story = storiesOf('Components/Conversation/MessageDetail', module);
|
|||
const defaultMessage: MessageProps = {
|
||||
authorTitle: 'Max',
|
||||
canReply: true,
|
||||
canDeleteForEveryone: true,
|
||||
clearSelectedMessage: () => null,
|
||||
conversationId: 'my-convo',
|
||||
conversationType: 'direct',
|
||||
deleteMessage: action('deleteMessage'),
|
||||
deleteMessageForEveryone: action('deleteMessageForEveryone'),
|
||||
direction: 'incoming',
|
||||
displayTapToViewMessage: () => null,
|
||||
downloadAttachment: () => null,
|
||||
|
|
|
@ -19,10 +19,12 @@ const story = storiesOf('Components/Conversation/Quote', module);
|
|||
const defaultMessageProps: MessagesProps = {
|
||||
authorTitle: 'Person X',
|
||||
canReply: true,
|
||||
canDeleteForEveryone: true,
|
||||
clearSelectedMessage: () => null,
|
||||
conversationId: 'conversationId',
|
||||
conversationType: 'direct', // override
|
||||
deleteMessage: () => null,
|
||||
deleteMessageForEveryone: () => null,
|
||||
direction: 'incoming',
|
||||
displayTapToViewMessage: () => null,
|
||||
downloadAttachment: () => null,
|
||||
|
|
|
@ -223,6 +223,7 @@ const actions = () => ({
|
|||
replyToMessage: action('replyToMessage'),
|
||||
retrySend: action('retrySend'),
|
||||
deleteMessage: action('deleteMessage'),
|
||||
deleteMessageForEveryone: action('deleteMessageForEveryone'),
|
||||
showMessageDetail: action('showMessageDetail'),
|
||||
openConversation: action('openConversation'),
|
||||
showContactDetail: action('showContactDetail'),
|
||||
|
|
|
@ -40,6 +40,7 @@ const getDefaultProps = () => ({
|
|||
replyToMessage: action('replyToMessage'),
|
||||
retrySend: action('retrySend'),
|
||||
deleteMessage: action('deleteMessage'),
|
||||
deleteMessageForEveryone: action('deleteMessageForEveryone'),
|
||||
showMessageDetail: action('showMessageDetail'),
|
||||
openConversation: action('openConversation'),
|
||||
showContactDetail: action('showContactDetail'),
|
||||
|
|
1
ts/model-types.d.ts
vendored
1
ts/model-types.d.ts
vendored
|
@ -50,6 +50,7 @@ export type MessageAttributesType = {
|
|||
dataMessage: ArrayBuffer | null;
|
||||
decrypted_at: number;
|
||||
deletedForEveryone: boolean;
|
||||
deletedForEveryoneTimestamp?: number;
|
||||
delivered: number;
|
||||
delivered_to: Array<string | null>;
|
||||
errors: Array<CustomError> | null;
|
||||
|
|
|
@ -63,6 +63,8 @@ const COLORS = [
|
|||
'ultramarine',
|
||||
];
|
||||
|
||||
const THREE_HOURS = 3 * 60 * 60 * 1000;
|
||||
|
||||
interface CustomError extends Error {
|
||||
identifier?: string;
|
||||
number?: string;
|
||||
|
@ -1811,6 +1813,106 @@ export class ConversationModel extends window.Backbone.Model<
|
|||
window.reduxActions.stickers.useSticker(packId, stickerId);
|
||||
}
|
||||
|
||||
async sendDeleteForEveryoneMessage(targetTimestamp: number): Promise<void> {
|
||||
const timestamp = Date.now();
|
||||
|
||||
if (timestamp - targetTimestamp > THREE_HOURS) {
|
||||
throw new Error('Cannot send DOE for a message older than three hours');
|
||||
}
|
||||
|
||||
const deleteModel = window.Whisper.Deletes.add({
|
||||
targetSentTimestamp: targetTimestamp,
|
||||
fromId: window.ConversationController.getOurConversationId(),
|
||||
});
|
||||
|
||||
window.Whisper.Deletes.onDelete(deleteModel);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const destination = this.getSendTarget()!;
|
||||
const recipients = this.getRecipients();
|
||||
|
||||
let profileKey: ArrayBuffer | undefined;
|
||||
if (this.get('profileSharing')) {
|
||||
profileKey = window.storage.get('profileKey');
|
||||
}
|
||||
|
||||
return this.queueJob(async () => {
|
||||
window.log.info(
|
||||
'Sending deleteForEveryone to conversation',
|
||||
this.idForLogging(),
|
||||
'with timestamp',
|
||||
timestamp
|
||||
);
|
||||
|
||||
const attributes = ({
|
||||
id: window.getGuid(),
|
||||
type: 'outgoing',
|
||||
conversationId: this.get('id'),
|
||||
sent_at: timestamp,
|
||||
received_at: timestamp,
|
||||
recipients,
|
||||
deletedForEveryoneTimestamp: targetTimestamp,
|
||||
// TODO: DESKTOP-722
|
||||
} as unknown) as typeof window.Whisper.MessageAttributesType;
|
||||
|
||||
if (this.isPrivate()) {
|
||||
attributes.destination = destination;
|
||||
}
|
||||
|
||||
// We are only creating this model so we can use its sync message
|
||||
// sending functionality. It will not be saved to the datbase.
|
||||
const message = new window.Whisper.Message(attributes);
|
||||
|
||||
// We're offline!
|
||||
if (!window.textsecure.messaging) {
|
||||
throw new Error('Cannot send DOE while offline!');
|
||||
}
|
||||
|
||||
const options = this.getSendOptions();
|
||||
|
||||
const promise = (() => {
|
||||
if (this.isPrivate()) {
|
||||
return window.textsecure.messaging.sendMessageToIdentifier(
|
||||
destination,
|
||||
undefined, // body
|
||||
[], // attachments
|
||||
undefined, // quote
|
||||
[], // preview
|
||||
undefined, // sticker
|
||||
undefined, // reaction
|
||||
targetTimestamp,
|
||||
timestamp,
|
||||
undefined, // expireTimer
|
||||
profileKey,
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
return window.textsecure.messaging.sendMessageToGroup(
|
||||
{
|
||||
groupV1: this.getGroupV1Info(),
|
||||
groupV2: this.getGroupV2Info(),
|
||||
deletedForEveryoneTimestamp: targetTimestamp,
|
||||
timestamp,
|
||||
profileKey,
|
||||
},
|
||||
options
|
||||
);
|
||||
})();
|
||||
|
||||
return message.send(this.wrapSend(promise));
|
||||
}).catch(error => {
|
||||
window.log.error(
|
||||
'Error sending deleteForEveryone',
|
||||
deleteModel,
|
||||
targetTimestamp,
|
||||
error
|
||||
);
|
||||
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
async sendReactionMessage(
|
||||
reaction: { emoji: string; remove: boolean },
|
||||
target: {
|
||||
|
@ -1882,6 +1984,7 @@ export class ConversationModel extends window.Backbone.Model<
|
|||
[], // preview
|
||||
undefined, // sticker
|
||||
outgoingReaction,
|
||||
undefined, // deletedForEveryoneTimestamp
|
||||
timestamp,
|
||||
expireTimer,
|
||||
profileKey
|
||||
|
@ -1901,6 +2004,7 @@ export class ConversationModel extends window.Backbone.Model<
|
|||
[], // preview
|
||||
undefined, // sticker
|
||||
outgoingReaction,
|
||||
undefined, // deletedForEveryoneTimestamp
|
||||
timestamp,
|
||||
expireTimer,
|
||||
profileKey,
|
||||
|
@ -2072,6 +2176,7 @@ export class ConversationModel extends window.Backbone.Model<
|
|||
preview,
|
||||
sticker,
|
||||
null, // reaction
|
||||
undefined, // deletedForEveryoneTimestamp
|
||||
now,
|
||||
expireTimer,
|
||||
profileKey
|
||||
|
@ -2108,6 +2213,7 @@ export class ConversationModel extends window.Backbone.Model<
|
|||
preview,
|
||||
sticker,
|
||||
null, // reaction
|
||||
undefined, // deletedForEveryoneTimestamp
|
||||
now,
|
||||
expireTimer,
|
||||
profileKey,
|
||||
|
@ -2553,6 +2659,7 @@ export class ConversationModel extends window.Backbone.Model<
|
|||
[], // preview
|
||||
undefined, // sticker
|
||||
undefined, // reaction
|
||||
undefined, // deletedForEveryoneTimestamp
|
||||
message.get('sent_at'),
|
||||
expireTimer,
|
||||
profileKey,
|
||||
|
|
|
@ -63,6 +63,8 @@ const PLACEHOLDER_CONTACT = {
|
|||
title: window.i18n('unknownContact'),
|
||||
};
|
||||
|
||||
const THREE_HOURS = 3 * 60 * 60 * 1000;
|
||||
|
||||
window.AccountCache = Object.create(null);
|
||||
window.AccountJobs = Object.create(null);
|
||||
|
||||
|
@ -373,6 +375,9 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
deleteMessage: (messageId: string) => {
|
||||
this.trigger('delete', messageId);
|
||||
},
|
||||
deleteMessageForEveryone: (messageId: string) => {
|
||||
this.trigger('delete-for-everyone', messageId);
|
||||
},
|
||||
showVisualAttachment: (options: unknown) => {
|
||||
this.trigger('show-visual-attachment', options);
|
||||
},
|
||||
|
@ -730,6 +735,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
status: this.getMessagePropStatus(),
|
||||
contact: this.getPropsForEmbeddedContact(),
|
||||
canReply: this.canReply(),
|
||||
canDeleteForEveryone: this.canDeleteForEveryone(),
|
||||
authorTitle: contact.title,
|
||||
authorColor,
|
||||
authorName: contact.name,
|
||||
|
@ -1897,6 +1903,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
previewWithData,
|
||||
stickerWithData,
|
||||
null,
|
||||
this.get('deletedForEveryoneTimestamp'),
|
||||
this.get('sent_at'),
|
||||
this.get('expireTimer'),
|
||||
profileKey
|
||||
|
@ -1966,6 +1973,25 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
);
|
||||
}
|
||||
|
||||
canDeleteForEveryone(): boolean {
|
||||
// is someone else's message
|
||||
if (this.isIncoming()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// has already been deleted for everyone
|
||||
if (this.get('deletedForEveryone')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// is too old to delete
|
||||
if (Date.now() - this.get('sent_at') > THREE_HOURS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
canReply(): boolean {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const isAccepted = this.getConversation()!.getAccepted();
|
||||
|
@ -2051,6 +2077,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
|||
previewWithData,
|
||||
stickerWithData,
|
||||
null,
|
||||
this.get('deletedForEveryoneTimestamp'),
|
||||
this.get('sent_at'),
|
||||
this.get('expireTimer'),
|
||||
profileKey,
|
||||
|
|
|
@ -118,6 +118,7 @@ type MessageOptionsType = {
|
|||
recipients: Array<string>;
|
||||
sticker?: any;
|
||||
reaction?: any;
|
||||
deletedForEveryoneTimestamp?: number;
|
||||
timestamp: number;
|
||||
};
|
||||
|
||||
|
@ -157,6 +158,8 @@ class Message {
|
|||
|
||||
attachmentPointers?: Array<any>;
|
||||
|
||||
deletedForEveryoneTimestamp?: number;
|
||||
|
||||
constructor(options: MessageOptionsType) {
|
||||
this.attachments = options.attachments || [];
|
||||
this.body = options.body;
|
||||
|
@ -172,6 +175,7 @@ class Message {
|
|||
this.sticker = options.sticker;
|
||||
this.reaction = options.reaction;
|
||||
this.timestamp = options.timestamp;
|
||||
this.deletedForEveryoneTimestamp = options.deletedForEveryoneTimestamp;
|
||||
|
||||
if (!(this.recipients instanceof Array)) {
|
||||
throw new Error('Invalid recipient list');
|
||||
|
@ -330,6 +334,11 @@ class Message {
|
|||
if (this.profileKey) {
|
||||
proto.profileKey = this.profileKey;
|
||||
}
|
||||
if (this.deletedForEveryoneTimestamp) {
|
||||
proto.delete = {
|
||||
targetSentTimestamp: this.deletedForEveryoneTimestamp,
|
||||
};
|
||||
}
|
||||
|
||||
this.dataMessage = proto;
|
||||
return proto;
|
||||
|
@ -1412,6 +1421,7 @@ export default class MessageSender {
|
|||
preview: Array<PreviewType>,
|
||||
sticker: unknown,
|
||||
reaction: unknown,
|
||||
deletedForEveryoneTimestamp: number | undefined,
|
||||
timestamp: number,
|
||||
expireTimer: number | undefined,
|
||||
profileKey?: ArrayBuffer,
|
||||
|
@ -1427,6 +1437,7 @@ export default class MessageSender {
|
|||
preview,
|
||||
sticker,
|
||||
reaction,
|
||||
deletedForEveryoneTimestamp,
|
||||
expireTimer,
|
||||
profileKey,
|
||||
flags,
|
||||
|
@ -1457,6 +1468,7 @@ export default class MessageSender {
|
|||
preview: Array<PreviewType> | undefined,
|
||||
sticker: unknown,
|
||||
reaction: unknown,
|
||||
deletedForEveryoneTimestamp: number | undefined,
|
||||
timestamp: number,
|
||||
expireTimer: number | undefined,
|
||||
profileKey?: ArrayBuffer,
|
||||
|
@ -1472,6 +1484,7 @@ export default class MessageSender {
|
|||
preview,
|
||||
sticker,
|
||||
reaction,
|
||||
deletedForEveryoneTimestamp,
|
||||
expireTimer,
|
||||
profileKey,
|
||||
},
|
||||
|
@ -1575,6 +1588,7 @@ export default class MessageSender {
|
|||
quote,
|
||||
reaction,
|
||||
sticker,
|
||||
deletedForEveryoneTimestamp,
|
||||
timestamp,
|
||||
}: {
|
||||
attachments?: Array<AttachmentType>;
|
||||
|
@ -1587,6 +1601,7 @@ export default class MessageSender {
|
|||
quote?: any;
|
||||
reaction?: any;
|
||||
sticker?: any;
|
||||
deletedForEveryoneTimestamp?: number;
|
||||
timestamp: number;
|
||||
},
|
||||
options?: SendOptionsType
|
||||
|
@ -1625,6 +1640,7 @@ export default class MessageSender {
|
|||
reaction,
|
||||
expireTimer,
|
||||
profileKey,
|
||||
deletedForEveryoneTimestamp,
|
||||
groupV2,
|
||||
group: groupV1
|
||||
? {
|
||||
|
|
|
@ -13057,7 +13057,7 @@
|
|||
"rule": "React-createRef",
|
||||
"path": "ts/components/conversation/Message.js",
|
||||
"line": " this.audioRef = react_1.default.createRef();",
|
||||
"lineNumber": 58,
|
||||
"lineNumber": 59,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-08-28T16:12:19.904Z"
|
||||
},
|
||||
|
@ -13065,7 +13065,7 @@
|
|||
"rule": "React-createRef",
|
||||
"path": "ts/components/conversation/Message.js",
|
||||
"line": " this.focusRef = react_1.default.createRef();",
|
||||
"lineNumber": 59,
|
||||
"lineNumber": 60,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-09-11T17:24:56.124Z",
|
||||
"reasonDetail": "Used for managing focus only"
|
||||
|
@ -13074,7 +13074,7 @@
|
|||
"rule": "React-createRef",
|
||||
"path": "ts/components/conversation/Message.js",
|
||||
"line": " this.reactionsContainerRef = react_1.default.createRef();",
|
||||
"lineNumber": 60,
|
||||
"lineNumber": 61,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-08-28T16:12:19.904Z",
|
||||
"reasonDetail": "Used for detecting clicks outside reaction viewer"
|
||||
|
@ -13083,7 +13083,7 @@
|
|||
"rule": "React-createRef",
|
||||
"path": "ts/components/conversation/Message.tsx",
|
||||
"line": " public audioRef: React.RefObject<HTMLAudioElement> = React.createRef();",
|
||||
"lineNumber": 211,
|
||||
"lineNumber": 215,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-09-08T20:19:01.913Z"
|
||||
},
|
||||
|
@ -13091,7 +13091,7 @@
|
|||
"rule": "React-createRef",
|
||||
"path": "ts/components/conversation/Message.tsx",
|
||||
"line": " public focusRef: React.RefObject<HTMLDivElement> = React.createRef();",
|
||||
"lineNumber": 213,
|
||||
"lineNumber": 217,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-09-08T20:19:01.913Z"
|
||||
},
|
||||
|
@ -13099,7 +13099,7 @@
|
|||
"rule": "React-createRef",
|
||||
"path": "ts/components/conversation/Message.tsx",
|
||||
"line": " > = React.createRef();",
|
||||
"lineNumber": 217,
|
||||
"lineNumber": 221,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2020-08-28T19:36:40.817Z"
|
||||
},
|
||||
|
|
|
@ -308,6 +308,11 @@ Whisper.ConversationView = Whisper.View.extend({
|
|||
);
|
||||
this.listenTo(this.model.messageCollection, 'force-send', this.forceSend);
|
||||
this.listenTo(this.model.messageCollection, 'delete', this.deleteMessage);
|
||||
this.listenTo(
|
||||
this.model.messageCollection,
|
||||
'delete-for-everyone',
|
||||
this.deleteMessageForEveryone
|
||||
);
|
||||
this.listenTo(
|
||||
this.model.messageCollection,
|
||||
'show-visual-attachment',
|
||||
|
@ -618,6 +623,9 @@ Whisper.ConversationView = Whisper.View.extend({
|
|||
const deleteMessage = (messageId: any) => {
|
||||
this.deleteMessage(messageId);
|
||||
};
|
||||
const deleteMessageForEveryone = (messageId: string) => {
|
||||
this.deleteMessageForEveryone(messageId);
|
||||
};
|
||||
const showMessageDetail = (messageId: any) => {
|
||||
this.showMessageDetail(messageId);
|
||||
};
|
||||
|
@ -796,6 +804,7 @@ Whisper.ConversationView = Whisper.View.extend({
|
|||
id,
|
||||
|
||||
deleteMessage,
|
||||
deleteMessageForEveryone,
|
||||
displayTapToViewMessage,
|
||||
downloadAttachment,
|
||||
downloadNewVersion,
|
||||
|
@ -2330,6 +2339,27 @@ Whisper.ConversationView = Whisper.View.extend({
|
|||
dialog.focusCancel();
|
||||
},
|
||||
|
||||
deleteMessageForEveryone(messageId: string) {
|
||||
const message = this.model.messageCollection.get(messageId);
|
||||
if (!message) {
|
||||
throw new Error(
|
||||
`deleteMessageForEveryone: Did not find message for id ${messageId}`
|
||||
);
|
||||
}
|
||||
|
||||
const dialog = new Whisper.ConfirmationDialogView({
|
||||
message: window.i18n('deleteForEveryoneWarning'),
|
||||
okText: window.i18n('delete'),
|
||||
resolve: async () => {
|
||||
await this.model.sendDeleteForEveryoneMessage(message.get('sent_at'));
|
||||
this.resetPanel();
|
||||
},
|
||||
});
|
||||
|
||||
this.$el.prepend(dialog.el);
|
||||
dialog.focusCancel();
|
||||
},
|
||||
|
||||
showStickerPackPreview(packId: any, packKey: any) {
|
||||
window.Signal.Stickers.downloadEphemeralPack(packId, packKey);
|
||||
|
||||
|
|
Loading…
Reference in a new issue