|
|
|
@ -1231,210 +1231,6 @@
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
deleteSelectedMessages() {
|
|
|
|
|
const ourPubkey = textsecure.storage.user.getNumber();
|
|
|
|
|
const selected = Array.from(this.model.selectedMessages);
|
|
|
|
|
const isModerator = this.model.isModerator(ourPubkey);
|
|
|
|
|
const isAllOurs = selected.every(
|
|
|
|
|
message =>
|
|
|
|
|
message.propsForMessage.authorPhoneNumber === message.OUR_NUMBER
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!isAllOurs && !isModerator) {
|
|
|
|
|
window.pushToast({
|
|
|
|
|
title: i18n('messageDeletionForbidden'),
|
|
|
|
|
type: 'error',
|
|
|
|
|
id: 'messageDeletionForbidden',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.deleteMessages(selected, () => {
|
|
|
|
|
this.resetMessageSelection();
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
deleteMessages(messages, onSuccess) {
|
|
|
|
|
const multiple = messages.length > 1;
|
|
|
|
|
const isPublic = this.model.isPublic();
|
|
|
|
|
|
|
|
|
|
// In future, we may be able to unsend private messages also
|
|
|
|
|
// isServerDeletable also defined in ConversationHeader.tsx for
|
|
|
|
|
// future reference
|
|
|
|
|
const isServerDeletable = isPublic;
|
|
|
|
|
|
|
|
|
|
const warningMessage = (() => {
|
|
|
|
|
if (isPublic) {
|
|
|
|
|
return multiple
|
|
|
|
|
? i18n('deleteMultiplePublicWarning')
|
|
|
|
|
: i18n('deletePublicWarning');
|
|
|
|
|
}
|
|
|
|
|
return multiple ? i18n('deleteMultipleWarning') : i18n('deleteWarning');
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
const doDelete = async () => {
|
|
|
|
|
let toDeleteLocally;
|
|
|
|
|
|
|
|
|
|
if (isPublic) {
|
|
|
|
|
toDeleteLocally = await this.model.deletePublicMessages(messages);
|
|
|
|
|
if (toDeleteLocally.length === 0) {
|
|
|
|
|
// Message failed to delete from server, show error?
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
messages.forEach(m => this.model.messageCollection.remove(m.id));
|
|
|
|
|
toDeleteLocally = messages;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await Promise.all(
|
|
|
|
|
toDeleteLocally.map(async m => {
|
|
|
|
|
await window.Signal.Data.removeMessage(m.id, {
|
|
|
|
|
Message: Whisper.Message,
|
|
|
|
|
});
|
|
|
|
|
m.trigger('unload');
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
this.resetPanel();
|
|
|
|
|
this.updateHeader();
|
|
|
|
|
|
|
|
|
|
if (onSuccess) {
|
|
|
|
|
onSuccess();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Only show a warning when at least one messages was successfully
|
|
|
|
|
// saved in on the server
|
|
|
|
|
if (!messages.some(m => !m.hasErrors())) {
|
|
|
|
|
doDelete();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If removable from server, we "Unsend" - otherwise "Delete"
|
|
|
|
|
let title;
|
|
|
|
|
if (isPublic) {
|
|
|
|
|
title = multiple
|
|
|
|
|
? i18n('deleteMessagesForEveryone')
|
|
|
|
|
: i18n('deleteMessageForEveryone');
|
|
|
|
|
} else {
|
|
|
|
|
title = multiple ? i18n('deleteMessages') : i18n('deleteMessage');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const okText = isServerDeletable
|
|
|
|
|
? i18n('deleteForEveryone')
|
|
|
|
|
: i18n('delete');
|
|
|
|
|
|
|
|
|
|
window.confirmationDialog({
|
|
|
|
|
title,
|
|
|
|
|
message: warningMessage,
|
|
|
|
|
okText,
|
|
|
|
|
okTheme: 'danger',
|
|
|
|
|
resolve: doDelete,
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
deleteMessage(message) {
|
|
|
|
|
this.deleteMessages([message]);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
showChannelLightbox({ media, attachment, message }) {
|
|
|
|
|
const selectedIndex = media.findIndex(
|
|
|
|
|
mediaMessage => mediaMessage.attachment.path === attachment.path
|
|
|
|
|
);
|
|
|
|
|
this.lightboxGalleryView = new Whisper.ReactWrapperView({
|
|
|
|
|
className: 'lightbox-wrapper',
|
|
|
|
|
Component: Signal.Components.LightboxGallery,
|
|
|
|
|
props: {
|
|
|
|
|
media,
|
|
|
|
|
onSave: () => this.downloadAttachment({ attachment, message }),
|
|
|
|
|
selectedIndex,
|
|
|
|
|
},
|
|
|
|
|
onClose: () => Signal.Backbone.Views.Lightbox.hide(),
|
|
|
|
|
});
|
|
|
|
|
Signal.Backbone.Views.Lightbox.show(this.lightboxGalleryView.el);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
showLightbox({ attachment, message }) {
|
|
|
|
|
const { contentType, path } = attachment;
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
!Signal.Util.GoogleChrome.isImageTypeSupported(contentType) &&
|
|
|
|
|
!Signal.Util.GoogleChrome.isVideoTypeSupported(contentType)
|
|
|
|
|
) {
|
|
|
|
|
this.downloadAttachment({ attachment, message });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const attachments = message.get('attachments') || [];
|
|
|
|
|
|
|
|
|
|
const media = attachments
|
|
|
|
|
.filter(item => item.thumbnail && !item.pending && !item.error)
|
|
|
|
|
.map((item, index) => ({
|
|
|
|
|
objectURL: getAbsoluteAttachmentPath(item.path),
|
|
|
|
|
path: item.path,
|
|
|
|
|
contentType: item.contentType,
|
|
|
|
|
index,
|
|
|
|
|
message,
|
|
|
|
|
attachment: item,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
if (media.length === 1) {
|
|
|
|
|
const props = {
|
|
|
|
|
objectURL: getAbsoluteAttachmentPath(path),
|
|
|
|
|
contentType,
|
|
|
|
|
caption: attachment.caption,
|
|
|
|
|
onSave: () => this.downloadAttachment({ attachment, message }),
|
|
|
|
|
};
|
|
|
|
|
this.lightboxView = new Whisper.ReactWrapperView({
|
|
|
|
|
className: 'lightbox-wrapper',
|
|
|
|
|
Component: Signal.Components.Lightbox,
|
|
|
|
|
props,
|
|
|
|
|
onClose: () => {
|
|
|
|
|
Signal.Backbone.Views.Lightbox.hide();
|
|
|
|
|
this.stopListening(message);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
this.listenTo(message, 'expired', () => this.lightboxView.remove());
|
|
|
|
|
Signal.Backbone.Views.Lightbox.show(this.lightboxView.el);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const selectedIndex = _.findIndex(
|
|
|
|
|
media,
|
|
|
|
|
item => attachment.path === item.path
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const onSave = async (options = {}) => {
|
|
|
|
|
Signal.Types.Attachment.save({
|
|
|
|
|
attachment: options.attachment,
|
|
|
|
|
document,
|
|
|
|
|
index: options.index + 1,
|
|
|
|
|
getAbsolutePath: getAbsoluteAttachmentPath,
|
|
|
|
|
timestamp: options.message.get('sent_at'),
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const props = {
|
|
|
|
|
media,
|
|
|
|
|
selectedIndex: selectedIndex >= 0 ? selectedIndex : 0,
|
|
|
|
|
onSave,
|
|
|
|
|
};
|
|
|
|
|
this.lightboxGalleryView = new Whisper.ReactWrapperView({
|
|
|
|
|
className: 'lightbox-wrapper',
|
|
|
|
|
Component: Signal.Components.LightboxGallery,
|
|
|
|
|
props,
|
|
|
|
|
onClose: () => {
|
|
|
|
|
Signal.Backbone.Views.Lightbox.hide();
|
|
|
|
|
this.stopListening(message);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
this.listenTo(message, 'expired', () =>
|
|
|
|
|
this.lightboxGalleryView.remove()
|
|
|
|
|
);
|
|
|
|
|
Signal.Backbone.Views.Lightbox.show(this.lightboxGalleryView.el);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async showMessageDetail(message) {
|
|
|
|
|
const onClose = () => {
|
|
|
|
|
this.stopListening(message, 'change', update);
|
|
|
|
|