Send Reactions

This commit is contained in:
Ken Powers 2020-01-23 18:57:37 -05:00 committed by Scott Nonnenberg
parent 3b050116fc
commit 153503efc5
16 changed files with 683 additions and 41 deletions

View file

@ -857,6 +857,11 @@
if (reactionViewer) {
return;
}
const reactionPicker = document.querySelector('module-reaction-picker');
if (reactionPicker) {
return;
}
}
// Close Backbone-based confirmation dialog
@ -2223,6 +2228,7 @@
targetTimestamp: reaction.targetTimestamp.toNumber(),
timestamp: Date.now(),
fromId: ourNumber,
fromSync: true,
});
// Note: We do not wait for completion here
Whisper.Reactions.onReaction(reactionModel);

View file

@ -952,6 +952,138 @@
window.reduxActions.stickers.useSticker(packId, stickerId);
},
/**
* Sends a reaction message
* @param {object} reaction - The reaction to send
* @param {string} reaction.emoji - The emoji to react with
* @param {boolean} [reaction.remove] - Set to `true` if we are removing a
* reaction with the given emoji
* @param {object} target - The target of the reaction
* @param {string} target.targetAuthorE164 - The E164 address of the target
* message's author
* @param {number} target.targetTimestamp - The sent_at timestamp of the
* target message
*/
async sendReactionMessage(reaction, target) {
if (!window.ENABLE_REACTION_SEND) {
return null;
}
const timestamp = Date.now();
const outgoingReaction = { ...reaction, ...target };
const reactionModel = Whisper.Reactions.add({
...outgoingReaction,
fromId: this.ourNumber || textsecure.storage.user.getNumber(),
timestamp,
fromSync: true,
});
Whisper.Reactions.onReaction(reactionModel);
const destination = this.id;
const recipients = this.getRecipients();
let profileKey;
if (this.get('profileSharing')) {
profileKey = storage.get('profileKey');
}
return this.queueJob(async () => {
window.log.info(
'Sending reaction to conversation',
this.idForLogging(),
'with timestamp',
timestamp
);
// Here we move attachments to disk
const attributes = {
id: window.getGuid(),
type: 'outgoing',
conversationId: destination,
sent_at: timestamp,
received_at: timestamp,
recipients,
reaction: outgoingReaction,
};
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 Whisper.Message(attributes);
// We're offline!
if (!textsecure.messaging) {
throw new Error('Cannot send reaction while offline!');
}
// Special-case the self-send case - we send only a sync message
if (this.isMe()) {
const dataMessage = await textsecure.messaging.getMessageProto(
destination,
null,
null,
null,
null,
null,
outgoingReaction,
timestamp,
null,
profileKey
);
return message.sendSyncMessageOnly(dataMessage);
}
const options = this.getSendOptions();
const groupNumbers = this.getRecipients();
const promise = (() => {
if (this.isPrivate()) {
return textsecure.messaging.sendMessageToNumber(
destination,
null,
null,
null,
null,
null,
outgoingReaction,
timestamp,
null,
profileKey,
options
);
}
return textsecure.messaging.sendMessageToGroup(
destination,
groupNumbers,
null,
null,
null,
null,
null,
outgoingReaction,
timestamp,
null,
profileKey,
options
);
})();
return message.send(this.wrapSend(promise));
}).catch(error => {
window.log.error('Error sending reaction', reaction, target, error);
const reverseReaction = reactionModel.clone();
reverseReaction.set('remove', !reverseReaction.get('remove'));
Whisper.Reactions.onReaction(reverseReaction);
throw error;
});
},
sendMessage(body, attachments, quote, preview, sticker) {
this.clearTypingTimers();
@ -1055,6 +1187,7 @@
quote,
preview,
sticker,
null,
now,
expireTimer,
profileKey
@ -1076,6 +1209,7 @@
quote,
preview,
sticker,
null,
now,
expireTimer,
profileKey,
@ -1090,6 +1224,7 @@
quote,
preview,
sticker,
null,
now,
expireTimer,
profileKey,
@ -1401,6 +1536,7 @@
null,
[],
null,
null,
message.get('sent_at'),
expireTimer,
profileKey,

View file

@ -295,6 +295,9 @@
openLink: url => {
this.trigger('navigate-to', url);
},
reactWith: emoji => {
this.trigger('react-with', emoji);
},
},
errors,
contacts: sortedContacts,
@ -515,6 +518,12 @@
};
});
const selectedReaction = (
(this.get('reactions') || []).find(
re => re.fromId === this.OUR_NUMBER
) || {}
).emoji;
return {
text: this.createNonBreakingLastSeparator(this.get('body')),
textPending: this.get('bodyPending'),
@ -538,6 +547,7 @@
expirationLength,
expirationTimestamp,
reactions,
selectedReaction,
isTapToView,
isTapToViewExpired: isTapToView && this.get('isErased'),
@ -1234,6 +1244,7 @@
quoteWithData,
previewWithData,
stickerWithData,
null,
this.get('sent_at'),
this.get('expireTimer'),
profileKey
@ -1253,6 +1264,7 @@
quoteWithData,
previewWithData,
stickerWithData,
null,
this.get('sent_at'),
this.get('expireTimer'),
profileKey,
@ -1326,6 +1338,7 @@
quoteWithData,
previewWithData,
stickerWithData,
null,
this.get('sent_at'),
this.get('expireTimer'),
profileKey
@ -2229,7 +2242,7 @@
);
// Only notify for reactions to our own messages
if (conversation && this.isOutgoing()) {
if (conversation && this.isOutgoing() && !reaction.get('fromSync')) {
conversation.notify(this, reaction);
}
}

View file

@ -142,6 +142,38 @@
return { toastMessage: i18n('attachmentSaved') };
},
});
Whisper.ReactionFailedToast = Whisper.ToastView.extend({
className: 'toast toast-clickable',
initialize() {
this.timeout = 4000;
if (window.getInteractionMode() === 'keyboard') {
setTimeout(() => {
this.$el.focus();
}, 1);
}
},
events: {
click: 'onClick',
keydown: 'onKeydown',
},
onClick() {
this.close();
},
onKeydown(event) {
if (event.key !== 'Enter' && event.key !== ' ') {
return;
}
event.preventDefault();
event.stopPropagation();
this.close();
},
render_attributes() {
return { toastMessage: i18n('Reactions--error') };
},
});
const MAX_MESSAGE_BODY_LENGTH = 64 * 1024;
Whisper.MessageBodyTooLongToast = Whisper.ToastView.extend({
@ -438,6 +470,9 @@
setupTimeline() {
const { id } = this.model;
const reactToMessage = (messageId, reaction) => {
this.sendReactionMessage(messageId, reaction);
};
const replyToMessage = messageId => {
this.setQuoteMessage(messageId);
};
@ -636,6 +671,7 @@
markMessageRead,
openConversation,
openLink,
reactToMessage,
replyToMessage,
retrySend,
scrollToQuotedMessage,
@ -2418,6 +2454,24 @@
});
},
async sendReactionMessage(messageId, reaction) {
const messageModel = messageId
? await getMessageById(messageId, {
Message: Whisper.Message,
})
: null;
try {
await this.model.sendReactionMessage(reaction, {
targetAuthorE164: messageModel.getSource(),
targetTimestamp: messageModel.get('sent_at'),
});
} catch (error) {
window.log.error('Error sending reaction', error, messageId, reaction);
this.showToast(Whisper.ReactionFailedToast);
}
},
async sendStickerMessage(options = {}) {
try {
const contacts = await this.getUntrustedContacts(options);