Send Reactions
This commit is contained in:
parent
3b050116fc
commit
153503efc5
16 changed files with 683 additions and 41 deletions
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue