Load attachment data for quotedMessages, processMessage on add

Not ideal that it loads it twice, but AttachmentView is so selfish with
its blob and objectUrl!
This commit is contained in:
Scott Nonnenberg 2018-04-12 18:12:40 -07:00
parent d91f40177e
commit 32925ed026
No known key found for this signature in database
GPG key ID: 5F82280C35134661
3 changed files with 80 additions and 28 deletions

View file

@ -111,6 +111,11 @@
return this.id === this.ourNumber; return this.id === this.ourNumber;
}, },
addSingleMessage(message) {
this.messageCollection.add(message, { merge: true });
this.processQuotes(this.messageCollection);
},
onMessageError() { onMessageError() {
this.updateVerified(); this.updateVerified();
}, },
@ -1030,11 +1035,13 @@
makeKey(author, id) { makeKey(author, id) {
return `${author}-${id}`; return `${author}-${id}`;
}, },
doMessagesMatch(left, right) { doesMessageMatch(id, author, message) {
if (left.get('source') !== right.get('source')) { const messageAuthor = message.getContact().id;
if (author !== messageAuthor) {
return false; return false;
} }
if (left.get('sent_at') !== right.get('sent_at')) { if (id !== message.get('sent_at')) {
return false; return false;
} }
return true; return true;
@ -1061,7 +1068,19 @@
makeMessagesLookup(messages) { makeMessagesLookup(messages) {
return messages.reduce((acc, message) => { return messages.reduce((acc, message) => {
const { source, sent_at: sentAt } = message.attributes; const { source, sent_at: sentAt } = message.attributes;
const key = this.makeKey(source, sentAt);
// Checking for notification messages without a sender
if (!source && message.isIncoming()) {
return acc;
}
const contact = message.getContact();
if (!contact) {
return acc;
}
const author = contact.id;
const key = this.makeKey(author, sentAt);
acc[key] = message; acc[key] = message;
@ -1070,7 +1089,7 @@
}, },
async loadQuotedMessageFromDatabase(message) { async loadQuotedMessageFromDatabase(message) {
const { quote } = message.attributes; const { quote } = message.attributes;
const { attachments, id } = quote; const { attachments, id, author } = quote;
const first = attachments[0]; const first = attachments[0];
// Maybe in the future we could try to pull the thumbnail from a video ourselves, // Maybe in the future we could try to pull the thumbnail from a video ourselves,
@ -1081,7 +1100,7 @@
const collection = new Whisper.MessageCollection(); const collection = new Whisper.MessageCollection();
await collection.fetchSentAt(id); await collection.fetchSentAt(id);
const queryMessage = collection.find(m => this.doMessagesMatch(message, m)); const queryMessage = collection.find(m => this.doesMessageMatch(id, author, m));
if (!queryMessage) { if (!queryMessage) {
return false; return false;
@ -1097,8 +1116,6 @@
// Note: it would be nice to take the full-size image and downsample it into // Note: it would be nice to take the full-size image and downsample it into
// a true thumbnail here. // a true thumbnail here.
// Note: if the attachment is a video, then this object URL won't make any sense
// when we try to use it in an img tag.
queryMessage.updateImageUrl(); queryMessage.updateImageUrl();
// We need to differentiate between messages we load from database and those already // We need to differentiate between messages we load from database and those already
@ -1110,6 +1127,36 @@
this.forceRender(message); this.forceRender(message);
return true; return true;
}, },
async loadQuotedMessage(message, quotedMessage) {
// eslint-disable-next-line no-param-reassign
message.quotedMessage = quotedMessage;
const { quote } = message.attributes;
const { attachments } = quote;
const first = attachments[0];
// Maybe in the future we could try to pull thumbnails video ourselves,
// but for now we will rely on incoming thumbnails only.
console.log({ first, contentType: first ? first.contentType : null });
if (!first || !MIME.isImage(first.contentType)) {
return;
}
const quotedAttachments = quotedMessage.get('attachments') || [];
console.log({ quotedMessage, quotedAttachments });
if (quotedAttachments.length === 0) {
return;
}
const queryFirst = quotedAttachments[0];
// eslint-disable-next-line no-param-reassign
quotedMessage.attributes.attachments[0] = await loadAttachmentData(queryFirst);
// Note: it would be nice to take the full-size image and downsample it into
// a true thumbnail here.
quotedMessage.updateImageUrl();
console.log({ quotedMessage });
},
async loadQuoteThumbnail(message) { async loadQuoteThumbnail(message) {
const { quote } = message.attributes; const { quote } = message.attributes;
const { attachments } = quote; const { attachments } = quote;
@ -1133,7 +1180,6 @@
this.forceRender(message); this.forceRender(message);
return true; return true;
}, },
async processQuotes(messages) { async processQuotes(messages) {
const lookup = this.makeMessagesLookup(messages); const lookup = this.makeMessagesLookup(messages);
@ -1143,11 +1189,6 @@
return; return;
} }
const { attachments } = quote;
if (!this.needData(attachments)) {
return;
}
// If we already have a quoted message, then we exit early. If we don't have it, // If we already have a quoted message, then we exit early. If we don't have it,
// then we'll continue to look again for an in-memory message to use. Why? This // then we'll continue to look again for an in-memory message to use. Why? This
// will enable us to scroll to it when the user clicks. // will enable us to scroll to it when the user clicks.
@ -1162,11 +1203,18 @@
if (quotedMessage) { if (quotedMessage) {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
message.quotedMessage = quotedMessage; await this.loadQuotedMessage(message, quotedMessage);
this.forceRender(message); this.forceRender(message);
return; return;
} }
// We only go further if we need more data for this message. It's always important
// to grab the quoted message to allow for navigating to it by clicking.
const { attachments } = quote;
if (!this.needData(attachments)) {
return;
}
// We've don't want to go to the database or load thumbnails a second time. // We've don't want to go to the database or load thumbnails a second time.
if (message.quoteIsProcessed) { if (message.quoteIsProcessed) {
return; return;

View file

@ -530,11 +530,10 @@
} }
}, },
scrollToMessage: function(providedOptions) { scrollToMessage: function(options = {}) {
const options = providedOptions || {};
const { id } = options; const { id } = options;
if (id) { if (!id) {
return; return;
} }
@ -543,7 +542,7 @@
return; return;
} }
el.scrollIntoView(); el[0].scrollIntoView();
}, },
scrollToBottom: function() { scrollToBottom: function() {
@ -686,7 +685,7 @@
// This is debounced, so it won't hit the database too often. // This is debounced, so it won't hit the database too often.
this.lazyUpdateVerified(); this.lazyUpdateVerified();
this.model.messageCollection.add(message, {merge: true}); this.model.addSingleMessage(message);
message.setToExpire(); message.setToExpire();
if (message.isOutgoing()) { if (message.isOutgoing()) {

View file

@ -194,7 +194,7 @@
this.listenTo(this.model, 'change:delivered', this.renderDelivered); this.listenTo(this.model, 'change:delivered', this.renderDelivered);
this.listenTo(this.model, 'change:read_by', this.renderRead); this.listenTo(this.model, 'change:read_by', this.renderRead);
this.listenTo(this.model, 'change:expirationStartTimestamp', this.renderExpiring); this.listenTo(this.model, 'change:expirationStartTimestamp', this.renderExpiring);
this.listenTo(this.model, 'change', this.renderSent); this.listenTo(this.model, 'change', this.onChange);
this.listenTo(this.model, 'change:flags change:group_update', this.renderControl); this.listenTo(this.model, 'change:flags change:group_update', this.renderControl);
this.listenTo(this.model, 'destroy', this.onDestroy); this.listenTo(this.model, 'destroy', this.onDestroy);
this.listenTo(this.model, 'unload', this.onUnload); this.listenTo(this.model, 'unload', this.onUnload);
@ -274,6 +274,10 @@
} }
this.onUnload(); this.onUnload();
}, },
onChange() {
this.renderRead();
this.renderQuote();
},
select(e) { select(e) {
this.$el.trigger('select', { message: this.model }); this.$el.trigger('select', { message: this.model });
e.stopPropagation(); e.stopPropagation();
@ -379,17 +383,19 @@
return null; return null;
}, },
renderQuote() { renderQuote() {
const VOICE_FLAG = textsecure.protobuf.AttachmentPointer.Flags.VOICE_MESSAGE;
const objectUrl = this.getQuoteObjectUrl();
const quote = this.model.get('quote'); const quote = this.model.get('quote');
if (!quote) { if (!quote) {
return; return;
} }
const VOICE_FLAG = textsecure.protobuf.AttachmentPointer.Flags.VOICE_MESSAGE;
const objectUrl = this.getQuoteObjectUrl();
function processAttachment(attachment) { function processAttachment(attachment) {
const thumbnail = !attachment.thumbnail const thumbnail = !objectUrl
? null ? null
: Object.assign({}, attachment.thumbnail, { : Object.assign({}, attachment.thumbnail || {}, {
objectUrl, objectUrl,
}); });
@ -411,7 +417,7 @@
const isIncoming = this.model.isIncoming(); const isIncoming = this.model.isIncoming();
const props = { const props = {
attachments: quote.attachments && quote.attachments.map(processAttachment), attachments: (quote.attachments || []).map(processAttachment),
authorColor, authorColor,
authorProfileName, authorProfileName,
authorTitle, authorTitle,
@ -420,14 +426,13 @@
onClick: () => { onClick: () => {
const { quotedMessage } = this.model; const { quotedMessage } = this.model;
if (quotedMessage) { if (quotedMessage) {
this.trigger('scroll-to-message', { id: quotedMessage.id }); this.model.trigger('scroll-to-message', { id: quotedMessage.id });
} }
}, },
text: quote.text, text: quote.text,
}; };
if (this.replyView) { if (this.replyView) {
this.replyView.remove();
this.replyView = null; this.replyView = null;
} else if (contact) { } else if (contact) {
this.listenTo(contact, 'change:color', this.renderQuote); this.listenTo(contact, 'change:color', this.renderQuote);