Copy quoted message contents into quote on receipt
Also: - visually distinguish any reference we couldn't verify on receipt - show toast on quote click if we can't scroll to message - toast visuals redesigned to match rest of app
This commit is contained in:
parent
a247ffe5cf
commit
fedfbed304
15 changed files with 468 additions and 336 deletions
|
@ -695,19 +695,20 @@
|
||||||
"description":
|
"description":
|
||||||
"When rendering an address, used to provide context to a post office box"
|
"When rendering an address, used to provide context to a post office box"
|
||||||
},
|
},
|
||||||
"replyToMessage": {
|
"originalMessageNotFound": {
|
||||||
"message": "Reply to Message",
|
"message": "Original message not found",
|
||||||
"description":
|
"description":
|
||||||
"Shown in triple-dot menu next to message to allow user to start crafting a message with a quotation"
|
"Shown in quote if reference message was not found as message was initially downloaded and processed"
|
||||||
},
|
},
|
||||||
"replyingToYourself": {
|
"originalMessageNotAvailable": {
|
||||||
"message": "Replying to Yourself",
|
"message": "Original message no longer available",
|
||||||
"description": "Shown in iOS theme when you quote yourself"
|
|
||||||
},
|
|
||||||
"replyingToYou": {
|
|
||||||
"message": "Replying to You",
|
|
||||||
"description":
|
"description":
|
||||||
"Shown in iOS theme when someone else quotes a message from you"
|
"Shown in toast if user clicks on quote that references message no longer in database"
|
||||||
|
},
|
||||||
|
"messageFoundButNotLoaded": {
|
||||||
|
"message": "Original message found, but not loaded. Scroll up to load it.",
|
||||||
|
"description":
|
||||||
|
"Shown in toast if user clicks on quote references messages not loaded in view, but in database"
|
||||||
},
|
},
|
||||||
"you": {
|
"you": {
|
||||||
"message": "You",
|
"message": "You",
|
||||||
|
|
12
images/broken-link.svg
Normal file
12
images/broken-link.svg
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 51.2 (57519) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>Link/broken-link-16</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="Link/broken-link-16" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<path d="M9.49509379,9.33333333 L12.6666667,9.33333333 C13.4030463,9.33333333 14,8.73637967 14,8 C14,7.26362033 13.4030463,6.66666667 12.6666667,6.66666667 L12.1617605,6.66666667 L13.3945435,5.43388365 C14.5135095,5.75065459 15.3333333,6.77958724 15.3333333,8 C15.3333333,9.47275933 14.139426,10.6666667 12.6666667,10.6666667 L8.66666667,10.6666667 C8.5081988,10.6666667 8.35295937,10.6528441 8.20208831,10.6263388 L9.49509379,9.33333333 Z" id="Combined-Shape" fill="#000000" fill-rule="nonzero"></path>
|
||||||
|
<path d="M6.50490621,6.66666667 L3.33333333,6.66666667 C2.59695367,6.66666667 2,7.26362033 2,8 C2,8.73637967 2.59695367,9.33333333 3.33333333,9.33333333 L3.83823954,9.33333333 L2.60545653,10.5661163 C1.48649049,10.2493454 0.666666667,9.22041276 0.666666667,8 C0.666666667,6.52724067 1.860574,5.33333333 3.33333333,5.33333333 L7.33333333,5.33333333 C7.4918012,5.33333333 7.64704063,5.34715592 7.79791169,5.37366118 L6.50490621,6.66666667 Z" id="Combined-Shape" fill="#000000" fill-rule="nonzero"></path>
|
||||||
|
<rect id="Rectangle-8" fill="#000000" fill-rule="nonzero" transform="translate(8.000000, 8.000000) rotate(45.000000) translate(-8.000000, -8.000000) " x="7.33333333" y="1.33333333" width="1.33333333" height="13.3333333"></rect>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
|
@ -973,7 +973,9 @@
|
||||||
return event.confirm();
|
return event.confirm();
|
||||||
}
|
}
|
||||||
|
|
||||||
const upgradedMessage = await upgradeMessageSchema(data.message);
|
const withQuoteReference = await copyFromQuotedMessage(data.message);
|
||||||
|
const upgradedMessage = await upgradeMessageSchema(withQuoteReference);
|
||||||
|
|
||||||
await ConversationController.getOrCreateAndWait(
|
await ConversationController.getOrCreateAndWait(
|
||||||
messageDescriptor.id,
|
messageDescriptor.id,
|
||||||
messageDescriptor.type
|
messageDescriptor.type
|
||||||
|
@ -984,6 +986,80 @@
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function copyFromQuotedMessage(message) {
|
||||||
|
const { quote } = message;
|
||||||
|
if (!quote) {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { attachments, id, author } = quote;
|
||||||
|
const firstAttachment = attachments[0];
|
||||||
|
|
||||||
|
const collection = await window.Signal.Data.getMessagesBySentAt(id, {
|
||||||
|
MessageCollection: Whisper.MessageCollection,
|
||||||
|
});
|
||||||
|
const queryMessage = collection.find(item => {
|
||||||
|
const messageAuthor = item.getContact();
|
||||||
|
|
||||||
|
return messageAuthor && author === messageAuthor.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!queryMessage) {
|
||||||
|
quote.referencedMessageNotFound = true;
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
quote.text = queryMessage.get('body');
|
||||||
|
if (firstAttachment) {
|
||||||
|
firstAttachment.thumbnail = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!firstAttachment ||
|
||||||
|
(!window.Signal.Util.GoogleChrome.isImageTypeSupported(
|
||||||
|
firstAttachment.contentType
|
||||||
|
) &&
|
||||||
|
!window.Signal.Util.GoogleChrome.isVideoTypeSupported(
|
||||||
|
firstAttachment.contentType
|
||||||
|
))
|
||||||
|
) {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (queryMessage.get('schemaVersion') < Message.CURRENT_SCHEMA_VERSION) {
|
||||||
|
const upgradedMessage = await upgradeMessageSchema(
|
||||||
|
queryMessage.attributes
|
||||||
|
);
|
||||||
|
queryMessage.set(upgradedMessage);
|
||||||
|
await window.Signal.Data.saveMessage(upgradedMessage, {
|
||||||
|
Message: Whisper.Message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
window.log.error(
|
||||||
|
'Problem upgrading message quoted message from database',
|
||||||
|
Errors.toLogFormat(error)
|
||||||
|
);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryAttachments = queryMessage.get('attachments') || [];
|
||||||
|
|
||||||
|
if (queryAttachments.length === 0) {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryFirst = queryAttachments[0];
|
||||||
|
const { thumbnail } = queryFirst;
|
||||||
|
|
||||||
|
if (thumbnail && thumbnail.path) {
|
||||||
|
firstAttachment.thumbnail = thumbnail;
|
||||||
|
}
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
// Received:
|
// Received:
|
||||||
async function handleMessageReceivedProfileUpdate({
|
async function handleMessageReceivedProfileUpdate({
|
||||||
data,
|
data,
|
||||||
|
|
|
@ -19,7 +19,6 @@
|
||||||
window.Whisper = window.Whisper || {};
|
window.Whisper = window.Whisper || {};
|
||||||
|
|
||||||
const { Util } = window.Signal;
|
const { Util } = window.Signal;
|
||||||
const { GoogleChrome } = Util;
|
|
||||||
const {
|
const {
|
||||||
Conversation,
|
Conversation,
|
||||||
Contact,
|
Contact,
|
||||||
|
@ -189,7 +188,6 @@
|
||||||
addSingleMessage(message) {
|
addSingleMessage(message) {
|
||||||
const model = this.messageCollection.add(message, { merge: true });
|
const model = this.messageCollection.add(message, { merge: true });
|
||||||
model.setToExpire();
|
model.setToExpire();
|
||||||
this.processQuotes(this.messageCollection);
|
|
||||||
return model;
|
return model;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1272,244 +1270,6 @@
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
makeKey(author, id) {
|
|
||||||
return `${author}-${id}`;
|
|
||||||
},
|
|
||||||
doesMessageMatch(id, author, message) {
|
|
||||||
const messageAuthor = message.getContact().id;
|
|
||||||
|
|
||||||
if (author !== messageAuthor) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (id !== message.get('sent_at')) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
needData(attachments) {
|
|
||||||
if (!attachments || attachments.length === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const first = attachments[0];
|
|
||||||
const { thumbnail, contentType } = first;
|
|
||||||
|
|
||||||
return (
|
|
||||||
thumbnail ||
|
|
||||||
GoogleChrome.isImageTypeSupported(contentType) ||
|
|
||||||
GoogleChrome.isVideoTypeSupported(contentType)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
forceRender(message) {
|
|
||||||
message.trigger('change', message);
|
|
||||||
},
|
|
||||||
makeMessagesLookup(messages) {
|
|
||||||
return messages.reduce((acc, message) => {
|
|
||||||
const { source, sent_at: sentAt } = message.attributes;
|
|
||||||
|
|
||||||
// Checking for notification messages (safety number change, timer change)
|
|
||||||
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;
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
},
|
|
||||||
async loadQuotedMessageFromDatabase(message) {
|
|
||||||
const { quote } = message.attributes;
|
|
||||||
const { attachments, id, author } = quote;
|
|
||||||
const first = attachments[0];
|
|
||||||
|
|
||||||
if (!first || message.quoteThumbnail) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!GoogleChrome.isImageTypeSupported(first.contentType) &&
|
|
||||||
!GoogleChrome.isVideoTypeSupported(first.contentType)
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const collection = await window.Signal.Data.getMessagesBySentAt(id, {
|
|
||||||
MessageCollection: Whisper.MessageCollection,
|
|
||||||
});
|
|
||||||
const queryMessage = collection.find(m =>
|
|
||||||
this.doesMessageMatch(id, author, m)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!queryMessage) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (
|
|
||||||
queryMessage.get('schemaVersion') < Message.CURRENT_SCHEMA_VERSION
|
|
||||||
) {
|
|
||||||
const upgradedMessage = await upgradeMessageSchema(
|
|
||||||
queryMessage.attributes
|
|
||||||
);
|
|
||||||
queryMessage.set(upgradedMessage);
|
|
||||||
await window.Signal.Data.saveMessage(upgradedMessage, {
|
|
||||||
Message: Whisper.Message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
window.log.error(
|
|
||||||
'Problem upgrading message quoted message from database',
|
|
||||||
Errors.toLogFormat(error)
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const queryAttachments = queryMessage.attachments || [];
|
|
||||||
if (queryAttachments.length === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const queryFirst = queryAttachments[0];
|
|
||||||
const { thumbnail } = queryFirst;
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
message.quoteThumbnail = {
|
|
||||||
...thumbnail,
|
|
||||||
objectUrl: getAbsoluteAttachmentPath(thumbnail.path),
|
|
||||||
};
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
loadQuotedMessage(message, quotedMessage) {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
message.quotedMessage = quotedMessage;
|
|
||||||
|
|
||||||
const { quote } = message.attributes;
|
|
||||||
const { attachments } = quote;
|
|
||||||
const first = attachments[0];
|
|
||||||
|
|
||||||
if (!first || message.quoteThumbnail) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!GoogleChrome.isImageTypeSupported(first.contentType) &&
|
|
||||||
!GoogleChrome.isVideoTypeSupported(first.contentType)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const quotedAttachments = quotedMessage.get('attachments') || [];
|
|
||||||
if (quotedAttachments.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const queryFirst = quotedAttachments[0];
|
|
||||||
const { thumbnail } = queryFirst;
|
|
||||||
|
|
||||||
if (!thumbnail) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
message.quoteThumbnail = {
|
|
||||||
...thumbnail,
|
|
||||||
objectUrl: getAbsoluteAttachmentPath(thumbnail.path),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
loadQuoteThumbnail(message) {
|
|
||||||
const { quote } = message.attributes;
|
|
||||||
const { attachments } = quote;
|
|
||||||
const first = attachments[0];
|
|
||||||
|
|
||||||
if (!first || message.quoteThumbnail) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { thumbnail } = first;
|
|
||||||
|
|
||||||
if (!thumbnail) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// If we update this data in place, there's the risk that this data could be
|
|
||||||
// saved back to the database
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
message.quoteThumbnail = {
|
|
||||||
...thumbnail,
|
|
||||||
objectUrl: getAbsoluteAttachmentPath(thumbnail.path),
|
|
||||||
};
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
async processQuotes(messages) {
|
|
||||||
const lookup = this.makeMessagesLookup(messages);
|
|
||||||
|
|
||||||
const promises = messages.map(async message => {
|
|
||||||
const { quote } = message.attributes;
|
|
||||||
if (!quote) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// will enable us to scroll to it when the user clicks.
|
|
||||||
if (message.quotedMessage) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. Load provided thumbnail
|
|
||||||
const gotThumbnail = this.loadQuoteThumbnail(message, quote);
|
|
||||||
|
|
||||||
// 2. Check to see if we've already loaded the target message into memory
|
|
||||||
const { author, id } = quote;
|
|
||||||
const key = this.makeKey(author, id);
|
|
||||||
const quotedMessage = lookup[key];
|
|
||||||
|
|
||||||
if (quotedMessage) {
|
|
||||||
this.loadQuotedMessage(message, quotedMessage);
|
|
||||||
this.forceRender(message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Even if we got the thumbnail locall, we wanted to populate the referenced
|
|
||||||
// message so a click can navigate to it.
|
|
||||||
if (gotThumbnail) {
|
|
||||||
this.forceRender(message);
|
|
||||||
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.
|
|
||||||
if (message.quoteIsProcessed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
message.quoteIsProcessed = true;
|
|
||||||
|
|
||||||
// 3. As a last resort, go to the database to generate a thumbnail on-demand
|
|
||||||
const loaded = await this.loadQuotedMessageFromDatabase(message, id);
|
|
||||||
if (loaded) {
|
|
||||||
this.forceRender(message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return Promise.all(promises);
|
|
||||||
},
|
|
||||||
|
|
||||||
async upgradeMessages(messages) {
|
async upgradeMessages(messages) {
|
||||||
for (let max = messages.length, i = 0; i < max; i += 1) {
|
for (let max = messages.length, i = 0; i < max; i += 1) {
|
||||||
const message = messages.at(i);
|
const message = messages.at(i);
|
||||||
|
@ -1558,10 +1318,6 @@
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We kick this process off, but don't wait for it. If async updates happen on a
|
|
||||||
// given Message, 'change' will be triggered
|
|
||||||
this.processQuotes(this.messageCollection);
|
|
||||||
|
|
||||||
this.inProgressFetch = null;
|
this.inProgressFetch = null;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -233,41 +233,7 @@
|
||||||
this.quotedMessage = null;
|
this.quotedMessage = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getQuoteObjectUrl() {
|
|
||||||
const thumbnail = this.quoteThumbnail;
|
|
||||||
if (!thumbnail || !thumbnail.objectUrl) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return thumbnail.objectUrl;
|
|
||||||
},
|
|
||||||
getQuoteContact() {
|
|
||||||
const quote = this.get('quote');
|
|
||||||
if (!quote) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const { author } = quote;
|
|
||||||
if (!author) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ConversationController.get(author);
|
|
||||||
},
|
|
||||||
processQuoteAttachment(attachment, externalObjectUrl) {
|
|
||||||
const { thumbnail } = attachment;
|
|
||||||
const objectUrl = (thumbnail && thumbnail.objectUrl) || externalObjectUrl;
|
|
||||||
|
|
||||||
const thumbnailWithObjectUrl = !objectUrl
|
|
||||||
? null
|
|
||||||
: Object.assign({}, attachment.thumbnail || {}, {
|
|
||||||
objectUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
return Object.assign({}, attachment, {
|
|
||||||
isVoiceMessage: Signal.Types.Attachment.isVoiceMessage(attachment),
|
|
||||||
thumbnail: thumbnailWithObjectUrl,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getPropsForTimerNotification() {
|
getPropsForTimerNotification() {
|
||||||
const { expireTimer, fromSync, source } = this.get(
|
const { expireTimer, fromSync, source } = this.get(
|
||||||
'expirationTimerUpdate'
|
'expirationTimerUpdate'
|
||||||
|
@ -535,15 +501,34 @@
|
||||||
hasSignalAccount: window.hasSignalAccount(firstNumber),
|
hasSignalAccount: window.hasSignalAccount(firstNumber),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
processQuoteAttachment(attachment) {
|
||||||
|
const { thumbnail } = attachment;
|
||||||
|
const path =
|
||||||
|
thumbnail &&
|
||||||
|
thumbnail.path &&
|
||||||
|
getAbsoluteAttachmentPath(thumbnail.path);
|
||||||
|
const objectUrl = thumbnail && thumbnail.objectUrl;
|
||||||
|
|
||||||
|
const thumbnailWithObjectUrl =
|
||||||
|
!path && !objectUrl
|
||||||
|
? null
|
||||||
|
: Object.assign({}, attachment.thumbnail || {}, {
|
||||||
|
objectUrl: path || objectUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
return Object.assign({}, attachment, {
|
||||||
|
isVoiceMessage: Signal.Types.Attachment.isVoiceMessage(attachment),
|
||||||
|
thumbnail: thumbnailWithObjectUrl,
|
||||||
|
});
|
||||||
|
},
|
||||||
getPropsForQuote() {
|
getPropsForQuote() {
|
||||||
const quote = this.get('quote');
|
const quote = this.get('quote');
|
||||||
if (!quote) {
|
if (!quote) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const objectUrl = this.getQuoteObjectUrl();
|
const { author, id, referencedMessageNotFound } = quote;
|
||||||
const { author } = quote;
|
const contact = author && ConversationController.get(author);
|
||||||
const contact = this.getQuoteContact();
|
|
||||||
|
|
||||||
const authorPhoneNumber = author;
|
const authorPhoneNumber = author;
|
||||||
const authorProfileName = contact ? contact.getProfileName() : null;
|
const authorProfileName = contact ? contact.getProfileName() : null;
|
||||||
|
@ -551,10 +536,11 @@
|
||||||
const authorColor = contact ? contact.getColor() : 'grey';
|
const authorColor = contact ? contact.getColor() : 'grey';
|
||||||
const isFromMe = contact ? contact.id === this.OUR_NUMBER : false;
|
const isFromMe = contact ? contact.id === this.OUR_NUMBER : false;
|
||||||
const onClick = () => {
|
const onClick = () => {
|
||||||
const { quotedMessage } = this;
|
this.trigger('scroll-to-message', {
|
||||||
if (quotedMessage) {
|
author,
|
||||||
this.trigger('scroll-to-message', { id: quotedMessage.id });
|
id,
|
||||||
}
|
referencedMessageNotFound,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const firstAttachment = quote.attachments && quote.attachments[0];
|
const firstAttachment = quote.attachments && quote.attachments[0];
|
||||||
|
@ -562,14 +548,15 @@
|
||||||
return {
|
return {
|
||||||
text: this.createNonBreakingLastSeparator(quote.text),
|
text: this.createNonBreakingLastSeparator(quote.text),
|
||||||
attachment: firstAttachment
|
attachment: firstAttachment
|
||||||
? this.processQuoteAttachment(firstAttachment, objectUrl)
|
? this.processQuoteAttachment(firstAttachment)
|
||||||
: null,
|
: null,
|
||||||
isFromMe,
|
isFromMe,
|
||||||
authorPhoneNumber,
|
authorPhoneNumber,
|
||||||
authorProfileName,
|
authorProfileName,
|
||||||
authorName,
|
authorName,
|
||||||
authorColor,
|
authorColor,
|
||||||
onClick: this.quotedMessage ? onClick : null,
|
onClick,
|
||||||
|
referencedMessageNotFound,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
getPropsForAttachment(attachment) {
|
getPropsForAttachment(attachment) {
|
||||||
|
@ -799,6 +786,19 @@
|
||||||
|
|
||||||
return ConversationController.getOrCreate(source, 'private');
|
return ConversationController.getOrCreate(source, 'private');
|
||||||
},
|
},
|
||||||
|
getQuoteContact() {
|
||||||
|
const quote = this.get('quote');
|
||||||
|
if (!quote) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const { author } = quote;
|
||||||
|
if (!author) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ConversationController.get(author);
|
||||||
|
},
|
||||||
|
|
||||||
getSource() {
|
getSource() {
|
||||||
if (this.isIncoming()) {
|
if (this.isIncoming()) {
|
||||||
return this.get('source');
|
return this.get('source');
|
||||||
|
|
|
@ -206,7 +206,7 @@ exports._mapQuotedAttachments = upgradeAttachment => async (
|
||||||
return attachment;
|
return attachment;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!thumbnail.data) {
|
if (!thumbnail.data && !thumbnail.path) {
|
||||||
logger.warn('Quoted attachment did not have thumbnail data; removing it');
|
logger.warn('Quoted attachment did not have thumbnail data; removing it');
|
||||||
return omit(attachment, ['thumbnail']);
|
return omit(attachment, ['thumbnail']);
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,21 @@
|
||||||
return { toastMessage: i18n('youLeftTheGroup') };
|
return { toastMessage: i18n('youLeftTheGroup') };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
Whisper.OriginalNotFoundToast = Whisper.ToastView.extend({
|
||||||
|
render_attributes() {
|
||||||
|
return { toastMessage: i18n('originalMessageNotFound') };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
Whisper.OriginalNoLongerAvailableToast = Whisper.ToastView.extend({
|
||||||
|
render_attributes() {
|
||||||
|
return { toastMessage: i18n('originalMessageNotAvailable') };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
Whisper.FoundButNotLoadedToast = Whisper.ToastView.extend({
|
||||||
|
render_attributes() {
|
||||||
|
return { toastMessage: i18n('messageFoundButNotLoaded') };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
Whisper.ConversationLoadingScreen = Whisper.View.extend({
|
Whisper.ConversationLoadingScreen = Whisper.View.extend({
|
||||||
templateName: 'conversation-loading-screen',
|
templateName: 'conversation-loading-screen',
|
||||||
|
@ -566,15 +581,66 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
scrollToMessage(options = {}) {
|
async scrollToMessage(options = {}) {
|
||||||
const { id } = options;
|
const { author, id, referencedMessageNotFound } = options;
|
||||||
|
|
||||||
if (!id) {
|
// For simplicity's sake, we show the 'not found' toast no matter what if we were
|
||||||
|
// not able to find the referenced message when the quote was received.
|
||||||
|
if (referencedMessageNotFound) {
|
||||||
|
const toast = new Whisper.OriginalNotFoundToast();
|
||||||
|
toast.$el.appendTo(this.$el);
|
||||||
|
toast.render();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const el = this.$(`#${id}`);
|
// Look for message in memory first, which would tell us if we could scroll to it
|
||||||
|
const targetMessage = this.model.messageCollection.find(item => {
|
||||||
|
const messageAuthor = item.getContact().id;
|
||||||
|
|
||||||
|
if (author !== messageAuthor) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (id !== item.get('sent_at')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// If there's no message already in memory, we won't be scrolling. So we'll gather
|
||||||
|
// some more information then show an informative toast to the user.
|
||||||
|
if (!targetMessage) {
|
||||||
|
const collection = await window.Signal.Data.getMessagesBySentAt(id, {
|
||||||
|
MessageCollection: Whisper.MessageCollection,
|
||||||
|
});
|
||||||
|
const messageFromDatabase = collection.find(item => {
|
||||||
|
const messageAuthor = item.getContact();
|
||||||
|
|
||||||
|
return messageAuthor && author === messageAuthor.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (messageFromDatabase) {
|
||||||
|
const toast = new Whisper.FoundButNotLoadedToast();
|
||||||
|
toast.$el.appendTo(this.$el);
|
||||||
|
toast.render();
|
||||||
|
} else {
|
||||||
|
const toast = new Whisper.OriginalNoLongerAvailableToast();
|
||||||
|
toast.$el.appendTo(this.$el);
|
||||||
|
toast.render();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const databaseId = targetMessage.id;
|
||||||
|
const el = this.$(`#${databaseId}`);
|
||||||
if (!el || el.length === 0) {
|
if (!el || el.length === 0) {
|
||||||
|
const toast = new Whisper.OriginalNoLongerAvailableToast();
|
||||||
|
toast.$el.appendTo(this.$el);
|
||||||
|
toast.render();
|
||||||
|
|
||||||
|
window.log.info(
|
||||||
|
`Error: had target message ${id} in messageCollection, but it was not in DOM`
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1375,7 +1441,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toast) {
|
if (toast) {
|
||||||
toast.$el.insertAfter(this.$el);
|
toast.$el.appendTo(this.$el);
|
||||||
toast.render();
|
toast.render();
|
||||||
this.focusMessageFieldAndClearDisabled();
|
this.focusMessageFieldAndClearDisabled();
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -299,15 +299,25 @@
|
||||||
|
|
||||||
.toast {
|
.toast {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
left: 50%;
|
||||||
margin: 0 2em 3em;
|
transform: translate(-50%, 0);
|
||||||
padding: 0.5em 1.5em;
|
bottom: 62px;
|
||||||
background: rgba(0, 0, 0, 0.75);
|
|
||||||
color: $color-white;
|
text-align: center;
|
||||||
box-shadow: 0 0 5px 0 black;
|
padding-left: 16px;
|
||||||
border-radius: $border-radius;
|
padding-right: 16px;
|
||||||
font-size: $font-size-small;
|
padding-top: 8px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 0;
|
||||||
|
|
||||||
|
background-color: $color-light-60;
|
||||||
|
color: $color-white;
|
||||||
|
box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.12), 0 0 0 0.5px rgba(0, 0, 0, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirmation-dialog {
|
.confirmation-dialog {
|
||||||
|
|
|
@ -206,6 +206,10 @@
|
||||||
color: $color-light-90;
|
color: $color-light-90;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.module-quote__reference-warning--incoming {
|
||||||
|
background-color: $color-signal-blue-mix;
|
||||||
|
}
|
||||||
|
|
||||||
// When you're composing a new quote
|
// When you're composing a new quote
|
||||||
.bottom-bar {
|
.bottom-bar {
|
||||||
.module-quote {
|
.module-quote {
|
||||||
|
@ -351,6 +355,30 @@
|
||||||
color: $color-white;
|
color: $color-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.module-quote__reference-warning {
|
||||||
|
background-color: $color-white-04;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-quote__reference-warning--incoming {
|
||||||
|
background-color: $color-signal-blue-050;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-quote__reference-warning__text {
|
||||||
|
color: $color-light-90;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-quote__reference-warning__text--incoming {
|
||||||
|
color: $color-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-quote__reference-warning__icon {
|
||||||
|
@include color-svg('../images/broken-link.svg', $color-light-60);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-quote__reference-warning__icon--incoming {
|
||||||
|
@include color-svg('../images/broken-link.svg', $color-white-085);
|
||||||
|
}
|
||||||
|
|
||||||
// When you're composing a new quote
|
// When you're composing a new quote
|
||||||
.bottom-bar {
|
.bottom-bar {
|
||||||
.module-quote__primary__author {
|
.module-quote__primary__author {
|
||||||
|
|
|
@ -697,17 +697,23 @@
|
||||||
|
|
||||||
// Module: Quoted Reply
|
// Module: Quoted Reply
|
||||||
|
|
||||||
|
.module-quote-container {
|
||||||
|
margin-left: -6px;
|
||||||
|
margin-right: -6px;
|
||||||
|
margin-top: -4px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-quote-container--with-content-above {
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
.module-quote {
|
.module-quote {
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border-top-left-radius: 10px;
|
border-top-left-radius: 10px;
|
||||||
border-top-right-radius: 10px;
|
border-top-right-radius: 10px;
|
||||||
|
|
||||||
margin-left: -6px;
|
|
||||||
margin-right: -6px;
|
|
||||||
margin-top: -4px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -723,11 +729,15 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-quote--with-content-above {
|
.module-quote--with-content-above {
|
||||||
margin-top: 3px;
|
|
||||||
border-top-left-radius: 4px;
|
border-top-left-radius: 4px;
|
||||||
border-top-right-radius: 4px;
|
border-top-right-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.module-quote--with-reference-warning {
|
||||||
|
border-bottom-left-radius: 0px;
|
||||||
|
border-bottom-right-radius: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
.module-quote--incoming {
|
.module-quote--incoming {
|
||||||
background-color: $color-white-075;
|
background-color: $color-white-075;
|
||||||
border-left-color: $color-white;
|
border-left-color: $color-white;
|
||||||
|
@ -973,6 +983,32 @@
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.module-quote__reference-warning {
|
||||||
|
height: 26px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
background-color: $color-white-085;
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
padding-left: 8px;
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-quote__reference-warning__icon {
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
@include color-svg('../images/broken-link.svg', $color-light-60);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-quote__reference-warning__text {
|
||||||
|
margin-left: 6px;
|
||||||
|
color: $color-light-90;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
// Module: Embedded Contact
|
// Module: Embedded Contact
|
||||||
|
|
||||||
.module-embedded-contact {
|
.module-embedded-contact {
|
||||||
|
|
|
@ -62,9 +62,10 @@ body.dark-theme {
|
||||||
}
|
}
|
||||||
|
|
||||||
.toast {
|
.toast {
|
||||||
background: rgba(0, 0, 0, 0.75);
|
background-color: $color-light-60;
|
||||||
color: $color-white;
|
color: $color-white;
|
||||||
box-shadow: 0 0 5px 0 black;
|
box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.12),
|
||||||
|
0 0 0 0.5px rgba(0, 0, 0, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
.confirmation-dialog {
|
.confirmation-dialog {
|
||||||
|
@ -1015,6 +1016,18 @@ body.dark-theme {
|
||||||
color: $color-dark-05;
|
color: $color-dark-05;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.module-quote__reference-warning {
|
||||||
|
background-color: $color-black-06;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-quote__reference-warning__icon {
|
||||||
|
@include color-svg('../images/broken-link.svg', $color-dark-30);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-quote__reference-warning__text {
|
||||||
|
color: $color-dark-05;
|
||||||
|
}
|
||||||
|
|
||||||
// Module: Embedded Contact
|
// Module: Embedded Contact
|
||||||
|
|
||||||
.module-embedded-contact__image-container__default-avatar {
|
.module-embedded-contact__image-container__default-avatar {
|
||||||
|
|
|
@ -32,12 +32,15 @@ $color-core-green: #4caf50;
|
||||||
$color-core-red: #f44336;
|
$color-core-red: #f44336;
|
||||||
|
|
||||||
$color-signal-blue-025: rgba($color-signal-blue, 0.25);
|
$color-signal-blue-025: rgba($color-signal-blue, 0.25);
|
||||||
|
$color-signal-blue-050: rgba($color-signal-blue, 0.5);
|
||||||
|
|
||||||
$color-white: #ffffff;
|
$color-white: #ffffff;
|
||||||
$color-white-02: rgba($color-white, 0.2);
|
$color-white-02: rgba($color-white, 0.2);
|
||||||
|
$color-white-04: rgba($color-white, 0.4);
|
||||||
$color-white-06: rgba($color-white, 0.6);
|
$color-white-06: rgba($color-white, 0.6);
|
||||||
$color-white-07: rgba($color-white, 0.7);
|
$color-white-07: rgba($color-white, 0.7);
|
||||||
$color-white-075: rgba($color-white, 0.75);
|
$color-white-075: rgba($color-white, 0.75);
|
||||||
|
$color-white-085: rgba($color-white, 0.85);
|
||||||
$color-light-02: #f9fafa;
|
$color-light-02: #f9fafa;
|
||||||
$color-light-10: #eeefef;
|
$color-light-10: #eeefef;
|
||||||
$color-light-35: #a4a6a9;
|
$color-light-35: #a4a6a9;
|
||||||
|
@ -58,6 +61,9 @@ $color-black-016-no-tranparency: #d9d9d9;
|
||||||
$color-black-012: rgba($color-black, 0.12);
|
$color-black-012: rgba($color-black, 0.12);
|
||||||
$color-black-02: rgba($color-black, 0.2);
|
$color-black-02: rgba($color-black, 0.2);
|
||||||
$color-black-04: rgba($color-black, 0.4);
|
$color-black-04: rgba($color-black, 0.4);
|
||||||
|
$color-black-06: rgba($color-black, 0.6);
|
||||||
|
|
||||||
|
$color-signal-blue-mix: mix($color-signal-blue-025, $color-white-04);
|
||||||
|
|
||||||
$color-conversation-grey: #757575;
|
$color-conversation-grey: #757575;
|
||||||
$color-conversation-blue: #1976d2;
|
$color-conversation-blue: #1976d2;
|
||||||
|
|
|
@ -78,6 +78,7 @@ export interface Props {
|
||||||
authorName?: string;
|
authorName?: string;
|
||||||
authorColor: Color;
|
authorColor: Color;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
|
referencedMessageNotFound: boolean;
|
||||||
};
|
};
|
||||||
authorAvatarPath?: string;
|
authorAvatarPath?: string;
|
||||||
expirationLength?: number;
|
expirationLength?: number;
|
||||||
|
@ -572,6 +573,7 @@ export class Message extends React.Component<Props, State> {
|
||||||
authorProfileName={quote.authorProfileName}
|
authorProfileName={quote.authorProfileName}
|
||||||
authorName={quote.authorName}
|
authorName={quote.authorName}
|
||||||
authorColor={quote.authorColor}
|
authorColor={quote.authorColor}
|
||||||
|
referencedMessageNotFound={quote.referencedMessageNotFound}
|
||||||
isFromMe={quote.isFromMe}
|
isFromMe={quote.isFromMe}
|
||||||
withContentAbove={withContentAbove}
|
withContentAbove={withContentAbove}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -225,6 +225,83 @@
|
||||||
</util.ConversationContext>
|
</util.ConversationContext>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Referenced message not found
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
<util.ConversationContext theme={util.theme} ios={util.ios}>
|
||||||
|
<li>
|
||||||
|
<Message
|
||||||
|
direction="incoming"
|
||||||
|
timestamp={Date.now()}
|
||||||
|
authorColor="green"
|
||||||
|
text="About six"
|
||||||
|
i18n={util.i18n}
|
||||||
|
quote={{
|
||||||
|
authorColor: 'red',
|
||||||
|
text: 'How many ferrets do you have?',
|
||||||
|
authorPhoneNumber: '(202) 555-0011',
|
||||||
|
isFromMe: true,
|
||||||
|
referencedMessageNotFound: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Message
|
||||||
|
direction="outgoing"
|
||||||
|
timestamp={Date.now()}
|
||||||
|
status="sending"
|
||||||
|
authorColor="green"
|
||||||
|
text="About six"
|
||||||
|
i18n={util.i18n}
|
||||||
|
quote={{
|
||||||
|
authorColor: 'red',
|
||||||
|
text: 'How many ferrets do you have?',
|
||||||
|
authorPhoneNumber: '(202) 555-0011',
|
||||||
|
isFromMe: true,
|
||||||
|
referencedMessageNotFound: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Message
|
||||||
|
direction="incoming"
|
||||||
|
timestamp={Date.now()}
|
||||||
|
conversationType="group"
|
||||||
|
authorName="Mr. 🔥Fire🔥"
|
||||||
|
authorColor="green"
|
||||||
|
text="About six"
|
||||||
|
i18n={util.i18n}
|
||||||
|
quote={{
|
||||||
|
authorColor: 'red',
|
||||||
|
text: 'How many ferrets do you have?',
|
||||||
|
authorPhoneNumber: '(202) 555-0011',
|
||||||
|
referencedMessageNotFound: true,
|
||||||
|
}}
|
||||||
|
authorAvatarPath={util.gifObjectUrl}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Message
|
||||||
|
direction="outgoing"
|
||||||
|
timestamp={Date.now()}
|
||||||
|
conversationType="group"
|
||||||
|
authorName="Mr. 🔥Fire🔥"
|
||||||
|
status="sending"
|
||||||
|
authorColor="green"
|
||||||
|
text="About six"
|
||||||
|
i18n={util.i18n}
|
||||||
|
quote={{
|
||||||
|
authorColor: 'red',
|
||||||
|
text: 'How many ferrets do you have?',
|
||||||
|
authorPhoneNumber: '(202) 555-0011',
|
||||||
|
referencedMessageNotFound: true,
|
||||||
|
}}
|
||||||
|
authorAvatarPath={util.gifObjectUrl}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
</util.ConversationContext>
|
||||||
|
```
|
||||||
|
|
||||||
#### Long names and context
|
#### Long names and context
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
|
|
|
@ -23,6 +23,7 @@ interface Props {
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
text: string;
|
text: string;
|
||||||
|
referencedMessageNotFound: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QuotedAttachment {
|
export interface QuotedAttachment {
|
||||||
|
@ -281,12 +282,49 @@ export class Quote extends React.Component<Props> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public renderReferenceWarning() {
|
||||||
|
const { i18n, isIncoming, referencedMessageNotFound } = this.props;
|
||||||
|
|
||||||
|
if (!referencedMessageNotFound) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'module-quote__reference-warning',
|
||||||
|
isIncoming ? 'module-quote__reference-warning--incoming' : null
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'module-quote__reference-warning__icon',
|
||||||
|
isIncoming
|
||||||
|
? 'module-quote__reference-warning__icon--incoming'
|
||||||
|
: null
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'module-quote__reference-warning__text',
|
||||||
|
isIncoming
|
||||||
|
? 'module-quote__reference-warning__text--incoming'
|
||||||
|
: null
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{i18n('originalMessageNotFound')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const {
|
const {
|
||||||
authorColor,
|
authorColor,
|
||||||
isFromMe,
|
isFromMe,
|
||||||
isIncoming,
|
isIncoming,
|
||||||
onClick,
|
onClick,
|
||||||
|
referencedMessageNotFound,
|
||||||
withContentAbove,
|
withContentAbove,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
@ -296,26 +334,37 @@ export class Quote extends React.Component<Props> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
onClick={onClick}
|
|
||||||
role="button"
|
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'module-quote',
|
'module-quote-container',
|
||||||
isIncoming ? 'module-quote--incoming' : 'module-quote--outgoing',
|
withContentAbove ? 'module-quote-container--with-content-above' : null
|
||||||
!isIncoming && !isFromMe
|
|
||||||
? `module-quote--outgoing-${authorColor}`
|
|
||||||
: null,
|
|
||||||
!isIncoming && isFromMe ? 'module-quote--outgoing-you' : null,
|
|
||||||
!onClick ? 'module-quote--no-click' : null,
|
|
||||||
withContentAbove ? 'module-quote--with-content-above' : null
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="module-quote__primary">
|
<div
|
||||||
{this.renderAuthor()}
|
onClick={onClick}
|
||||||
{this.renderGenericFile()}
|
role="button"
|
||||||
{this.renderText()}
|
className={classNames(
|
||||||
|
'module-quote',
|
||||||
|
isIncoming ? 'module-quote--incoming' : 'module-quote--outgoing',
|
||||||
|
!isIncoming && !isFromMe
|
||||||
|
? `module-quote--outgoing-${authorColor}`
|
||||||
|
: null,
|
||||||
|
!isIncoming && isFromMe ? 'module-quote--outgoing-you' : null,
|
||||||
|
!onClick ? 'module-quote--no-click' : null,
|
||||||
|
withContentAbove ? 'module-quote--with-content-above' : null,
|
||||||
|
referencedMessageNotFound
|
||||||
|
? 'module-quote--with-reference-warning'
|
||||||
|
: null
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="module-quote__primary">
|
||||||
|
{this.renderAuthor()}
|
||||||
|
{this.renderGenericFile()}
|
||||||
|
{this.renderText()}
|
||||||
|
</div>
|
||||||
|
{this.renderIconContainer()}
|
||||||
|
{this.renderClose()}
|
||||||
</div>
|
</div>
|
||||||
{this.renderIconContainer()}
|
{this.renderReferenceWarning()}
|
||||||
{this.renderClose()}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue