Refactor: Prepare Message function props for conversation scope

This commit is contained in:
Scott Nonnenberg 2019-03-15 15:18:00 -07:00
parent 7e58594038
commit d342b23cbc
13 changed files with 300 additions and 256 deletions

View file

@ -162,17 +162,8 @@
isUnread() { isUnread() {
return !!this.get('unread'); return !!this.get('unread');
}, },
// Important to allow for this.unset('unread'), save to db, then fetch()
// to propagate. We don't want the unset key in the db so our unread index
// stays small.
merge(model) { merge(model) {
const attributes = model.attributes || model; const attributes = model.attributes || model;
const { unread } = attributes;
if (typeof unread === 'undefined') {
this.unset('unread');
}
this.set(attributes); this.set(attributes);
}, },
getNameForNumber(number) { getNameForNumber(number) {
@ -311,13 +302,12 @@
const conversation = this.getConversation(); const conversation = this.getConversation();
const isGroup = conversation && !conversation.isPrivate(); const isGroup = conversation && !conversation.isPrivate();
const phoneNumber = this.get('key_changed'); const phoneNumber = this.get('key_changed');
const onVerify = () => const showIdentity = id => this.trigger('show-identity', id);
this.trigger('show-identity', this.findContact(phoneNumber));
return { return {
isGroup, isGroup,
contact: this.findAndFormatContact(phoneNumber), contact: this.findAndFormatContact(phoneNumber),
onVerify, showIdentity,
}; };
}, },
getPropsForVerificationNotification() { getPropsForVerificationNotification() {
@ -339,21 +329,17 @@
return ConversationController.get(phoneNumber); return ConversationController.get(phoneNumber);
}, },
findAndFormatContact(phoneNumber) { findAndFormatContact(phoneNumber) {
const contactModel = this.findContact(phoneNumber);
if (contactModel) {
return contactModel.getProps();
}
const { format } = PhoneNumber; const { format } = PhoneNumber;
const regionCode = storage.get('regionCode'); const regionCode = storage.get('regionCode');
const contactModel = this.findContact(phoneNumber);
const color = contactModel ? contactModel.getColor() : null;
return { return {
phoneNumber: format(phoneNumber, { phoneNumber: format(phoneNumber, {
ourRegionCode: regionCode, ourRegionCode: regionCode,
}), }),
color,
avatarPath: contactModel ? contactModel.getAvatarPath() : null,
name: contactModel ? contactModel.getName() : null,
profileName: contactModel ? contactModel.getProfileName() : null,
title: contactModel ? contactModel.getTitle() : null,
}; };
}, },
getPropsForGroupNotification() { getPropsForGroupNotification() {
@ -460,7 +446,7 @@
snippet: this.get('snippet'), snippet: this.get('snippet'),
}; };
}, },
getPropsForMessage(options) { getPropsForMessage() {
const phoneNumber = this.getSource(); const phoneNumber = this.getSource();
const contact = this.findAndFormatContact(phoneNumber); const contact = this.findAndFormatContact(phoneNumber);
const contactModel = this.findContact(phoneNumber); const contactModel = this.findContact(phoneNumber);
@ -479,9 +465,7 @@
const conversation = this.getConversation(); const conversation = this.getConversation();
const isGroup = conversation && !conversation.isPrivate(); const isGroup = conversation && !conversation.isPrivate();
const attachments = this.get('attachments') || []; const attachments = this.get('attachments') || [];
const firstAttachment = attachments[0];
return { return {
text: this.createNonBreakingLastSeparator(this.get('body')), text: this.createNonBreakingLastSeparator(this.get('body')),
@ -500,28 +484,30 @@
.filter(attachment => !attachment.error) .filter(attachment => !attachment.error)
.map(attachment => this.getPropsForAttachment(attachment)), .map(attachment => this.getPropsForAttachment(attachment)),
previews: this.getPropsForPreview(), previews: this.getPropsForPreview(),
quote: this.getPropsForQuote(options), quote: this.getPropsForQuote(),
authorAvatarPath, authorAvatarPath,
isExpired: this.hasExpired, isExpired: this.hasExpired,
expirationLength, expirationLength,
expirationTimestamp, expirationTimestamp,
onReply: () => this.trigger('reply', this),
onRetrySend: () => this.retrySend(),
onShowDetail: () => this.trigger('show-message-detail', this),
onDelete: () => this.trigger('delete', this),
onClickLinkPreview: url => this.trigger('navigate-to', url),
onClickAttachment: attachment =>
this.trigger('show-lightbox', {
attachment,
message: this,
}),
onDownload: isDangerous => replyToMessage: id => this.trigger('reply', id),
this.trigger('download', { retrySend: id => this.trigger('retry', id),
attachment: firstAttachment, deleteMessage: id => this.trigger('delete', id),
message: this, showMessageDetail: id => this.trigger('show-message-detail', id),
isDangerous,
}), openConversation: conversationId =>
this.trigger('open-conversation', conversationId),
showContactDetail: contactOptions =>
this.trigger('show-contact-detail', contactOptions),
showVisualAttachment: lightboxOptions =>
this.trigger('show-lightbox', lightboxOptions),
downloadAttachment: downloadOptions =>
this.trigger('download', downloadOptions),
openLink: url => this.trigger('navigate-to', url),
scrollToMessage: scrollOptions =>
this.trigger('scroll-to-message', scrollOptions),
}; };
}, },
createNonBreakingLastSeparator(text) { createNonBreakingLastSeparator(text) {
@ -551,20 +537,6 @@
const contact = contacts[0]; const contact = contacts[0];
const firstNumber = const firstNumber =
contact.number && contact.number[0] && contact.number[0].value; contact.number && contact.number[0] && contact.number[0].value;
const onSendMessage = firstNumber
? () => {
this.trigger('open-conversation', firstNumber);
}
: null;
const onClick = async () => {
// First let's be sure that the signal account check is complete.
await window.checkForSignalAccount(firstNumber);
this.trigger('show-contact-detail', {
contact,
hasSignalAccount: window.hasSignalAccount(firstNumber),
});
};
// Would be nice to do this before render, on initial load of message // Would be nice to do this before render, on initial load of message
if (!window.isSignalAccountCheckComplete(firstNumber)) { if (!window.isSignalAccountCheckComplete(firstNumber)) {
@ -576,9 +548,9 @@
return contactSelector(contact, { return contactSelector(contact, {
regionCode, regionCode,
getAbsoluteAttachmentPath, getAbsoluteAttachmentPath,
onSendMessage, signalAccount: window.hasSignalAccount(firstNumber)
onClick, ? firstNumber
hasSignalAccount: window.hasSignalAccount(firstNumber), : null,
}); });
}, },
processQuoteAttachment(attachment) { processQuoteAttachment(attachment) {
@ -610,8 +582,7 @@
image: preview.image ? this.getPropsForAttachment(preview.image) : null, image: preview.image ? this.getPropsForAttachment(preview.image) : null,
})); }));
}, },
getPropsForQuote(options = {}) { getPropsForQuote() {
const { noClick } = options;
const quote = this.get('quote'); const quote = this.get('quote');
if (!quote) { if (!quote) {
return null; return null;
@ -620,7 +591,7 @@
const { format } = PhoneNumber; const { format } = PhoneNumber;
const regionCode = storage.get('regionCode'); const regionCode = storage.get('regionCode');
const { author, id, referencedMessageNotFound } = quote; const { author, id: sentAt, referencedMessageNotFound } = quote;
const contact = author && ConversationController.get(author); const contact = author && ConversationController.get(author);
const authorColor = contact ? contact.getColor() : 'grey'; const authorColor = contact ? contact.getColor() : 'grey';
@ -630,16 +601,6 @@
const authorProfileName = contact ? contact.getProfileName() : null; const authorProfileName = contact ? contact.getProfileName() : null;
const authorName = contact ? contact.getName() : null; const authorName = contact ? contact.getName() : null;
const isFromMe = contact ? contact.id === this.OUR_NUMBER : false; const isFromMe = contact ? contact.id === this.OUR_NUMBER : false;
const onClick = noClick
? null
: () => {
this.trigger('scroll-to-message', {
author,
id,
referencedMessageNotFound,
});
};
const firstAttachment = quote.attachments && quote.attachments[0]; const firstAttachment = quote.attachments && quote.attachments[0];
return { return {
@ -648,11 +609,12 @@
? this.processQuoteAttachment(firstAttachment) ? this.processQuoteAttachment(firstAttachment)
: null, : null,
isFromMe, isFromMe,
sentAt,
authorId: author,
authorPhoneNumber, authorPhoneNumber,
authorProfileName, authorProfileName,
authorName, authorName,
authorColor, authorColor,
onClick,
referencedMessageNotFound, referencedMessageNotFound,
}; };
}, },
@ -740,6 +702,7 @@
return { return {
...this.findAndFormatContact(id), ...this.findAndFormatContact(id),
status: this.getStatus(id), status: this.getStatus(id),
errors: errorsForContact, errors: errorsForContact,
isOutgoingKeyError, isOutgoingKeyError,
@ -765,8 +728,9 @@
sentAt: this.get('sent_at'), sentAt: this.get('sent_at'),
receivedAt: this.get('received_at'), receivedAt: this.get('received_at'),
message: { message: {
...this.getPropsForMessage({ noClick: true }), ...this.getPropsForMessage(),
disableMenu: true, disableMenu: true,
disableScroll: true,
// To ensure that group avatar doesn't show up // To ensure that group avatar doesn't show up
conversationType: 'direct', conversationType: 'direct',
}, },

View file

@ -114,6 +114,7 @@
'reply', 'reply',
this.setQuoteMessage this.setQuoteMessage
); );
this.listenTo(this.model.messageCollection, 'retry', this.retrySend);
this.listenTo( this.listenTo(
this.model.messageCollection, this.model.messageCollection,
'show-contact-detail', 'show-contact-detail',
@ -705,8 +706,16 @@
} }
}, },
async retrySend(messageId) {
const message = this.model.messageCollection.get(messageId);
if (!message) {
throw new Error(`retrySend: Did not find message for id ${messageId}`);
}
await message.retrySend();
},
async scrollToMessage(options = {}) { async scrollToMessage(options = {}) {
const { author, id, referencedMessageNotFound } = options; const { author, sentAt, referencedMessageNotFound } = options;
// For simplicity's sake, we show the 'not found' toast no matter what if we were // 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. // not able to find the referenced message when the quote was received.
@ -724,7 +733,7 @@
if (!messageAuthor || author !== messageAuthor.id) { if (!messageAuthor || author !== messageAuthor.id) {
return false; return false;
} }
if (id !== item.get('sent_at')) { if (sentAt !== item.get('sent_at')) {
return false; return false;
} }
@ -734,13 +743,16 @@
// If there's no message already in memory, we won't be scrolling. So we'll gather // 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. // some more information then show an informative toast to the user.
if (!targetMessage) { if (!targetMessage) {
const collection = await window.Signal.Data.getMessagesBySentAt(id, { const collection = await window.Signal.Data.getMessagesBySentAt(
MessageCollection: Whisper.MessageCollection, sentAt,
}); {
MessageCollection: Whisper.MessageCollection,
}
);
const found = Boolean( const found = Boolean(
collection.find(item => { collection.find(item => {
const messageAuthor = item.getContact(); const messageAuthor = item.getContact();
return messageAuthor && author === messageAuthor.id; return messageAuthor && author === messageAuthor.id;
}) })
); );
@ -765,7 +777,7 @@
toast.render(); toast.render();
window.log.info( window.log.info(
`Error: had target message ${id} in messageCollection, but it was not in DOM` `Error: had target message ${targetMessage.idForLogging()} in messageCollection, but it was not in DOM`
); );
return; return;
} }
@ -1202,23 +1214,25 @@
dialog.focusCancel(); dialog.focusCancel();
}, },
showSafetyNumber(providedModel) { showSafetyNumber(id) {
let model = providedModel; let conversation;
if (!model && this.model.isPrivate()) { if (!id && this.model.isPrivate()) {
// eslint-disable-next-line prefer-destructuring // eslint-disable-next-line prefer-destructuring
model = this.model; conversation = this.model;
} else {
conversation = ConversationController.get(id);
} }
if (model) { if (conversation) {
const view = new Whisper.KeyVerificationPanelView({ const view = new Whisper.KeyVerificationPanelView({
model, model: conversation,
}); });
this.listenBack(view); this.listenBack(view);
this.updateHeader(); this.updateHeader();
} }
}, },
downloadAttachment({ attachment, message, isDangerous }) { downloadAttachment({ attachment, timestamp, isDangerous }) {
if (isDangerous) { if (isDangerous) {
const toast = new Whisper.DangerousFileTypeToast(); const toast = new Whisper.DangerousFileTypeToast();
toast.$el.appendTo(this.$el); toast.$el.appendTo(this.$el);
@ -1230,11 +1244,18 @@
attachment, attachment,
document, document,
getAbsolutePath: getAbsoluteAttachmentPath, getAbsolutePath: getAbsoluteAttachmentPath,
timestamp: message.get('sent_at'), timestamp,
}); });
}, },
deleteMessage(message) { deleteMessage(messageId) {
const message = this.model.messageCollection.get(messageId);
if (!message) {
throw new Error(
`deleteMessage: Did not find message for id ${messageId}`
);
}
const dialog = new Whisper.ConfirmationDialogView({ const dialog = new Whisper.ConfirmationDialogView({
message: i18n('deleteWarning'), message: i18n('deleteWarning'),
okText: i18n('delete'), okText: i18n('delete'),
@ -1253,7 +1274,13 @@
dialog.focusCancel(); dialog.focusCancel();
}, },
showLightbox({ attachment, message }) { showLightbox({ attachment, messageId }) {
const message = this.model.messageCollection.get(messageId);
if (!message) {
throw new Error(
`showLightbox: did not find message for id ${messageId}`
);
}
const { contentType, path } = attachment; const { contentType, path } = attachment;
if ( if (
@ -1333,7 +1360,14 @@
Signal.Backbone.Views.Lightbox.show(this.lightboxGalleryView.el); Signal.Backbone.Views.Lightbox.show(this.lightboxGalleryView.el);
}, },
showMessageDetail(message) { showMessageDetail(messageId) {
const message = this.model.messageCollection.get(messageId);
if (!message) {
throw new Error(
`showMessageDetail: Did not find message for id ${messageId}`
);
}
const onClose = () => { const onClose = () => {
this.stopListening(message, 'change', update); this.stopListening(message, 'change', update);
this.resetPanel(); this.resetPanel();
@ -1358,24 +1392,16 @@
view.render(); view.render();
}, },
showContactDetail({ contact, hasSignalAccount }) { showContactDetail({ contact, signalAccount }) {
const regionCode = storage.get('regionCode');
const { contactSelector } = Signal.Types.Contact;
const view = new Whisper.ReactWrapperView({ const view = new Whisper.ReactWrapperView({
Component: Signal.Components.ContactDetail, Component: Signal.Components.ContactDetail,
className: 'contact-detail-pane panel', className: 'contact-detail-pane panel',
props: { props: {
contact: contactSelector(contact, { contact,
regionCode, signalAccount,
getAbsoluteAttachmentPath,
}),
hasSignalAccount,
onSendMessage: () => { onSendMessage: () => {
const number = if (signalAccount) {
contact.number && contact.number[0] && contact.number[0].value; this.openConversation(signalAccount);
if (number) {
this.openConversation(number);
} }
}, },
}, },
@ -1592,20 +1618,25 @@
this.focusMessageField(); this.focusMessageField();
}, },
async setQuoteMessage(message) { async setQuoteMessage(messageId) {
this.quote = null; this.quote = null;
this.quotedMessage = message; this.quotedMessage = null;
if (this.quoteHolder) { if (this.quoteHolder) {
this.quoteHolder.unload(); this.quoteHolder.unload();
this.quoteHolder = null; this.quoteHolder = null;
} }
const message = this.model.messageCollection.get(messageId);
if (message) { if (message) {
const quote = await this.model.makeQuote(this.quotedMessage); this.quotedMessage = message;
this.quote = quote;
this.focusMessageFieldAndClearDisabled(); if (message) {
const quote = await this.model.makeQuote(this.quotedMessage);
this.quote = quote;
this.focusMessageFieldAndClearDisabled();
}
} }
this.renderQuotedMessage(); this.renderQuotedMessage();

View file

@ -2,7 +2,7 @@ import React from 'react';
import { import {
AddressType, AddressType,
Contact, ContactFormType,
ContactType, ContactType,
Email, Email,
Phone, Phone,
@ -19,7 +19,7 @@ import {
import { LocalizerType } from '../../types/Util'; import { LocalizerType } from '../../types/Util';
interface Props { interface Props {
contact: Contact; contact: ContactType;
hasSignalAccount: boolean; hasSignalAccount: boolean;
i18n: LocalizerType; i18n: LocalizerType;
onSendMessage: () => void; onSendMessage: () => void;
@ -27,13 +27,13 @@ interface Props {
function getLabelForEmail(method: Email, i18n: LocalizerType): string { function getLabelForEmail(method: Email, i18n: LocalizerType): string {
switch (method.type) { switch (method.type) {
case ContactType.CUSTOM: case ContactFormType.CUSTOM:
return method.label || i18n('email'); return method.label || i18n('email');
case ContactType.HOME: case ContactFormType.HOME:
return i18n('home'); return i18n('home');
case ContactType.MOBILE: case ContactFormType.MOBILE:
return i18n('mobile'); return i18n('mobile');
case ContactType.WORK: case ContactFormType.WORK:
return i18n('work'); return i18n('work');
default: default:
throw missingCaseError(method.type); throw missingCaseError(method.type);
@ -42,13 +42,13 @@ function getLabelForEmail(method: Email, i18n: LocalizerType): string {
function getLabelForPhone(method: Phone, i18n: LocalizerType): string { function getLabelForPhone(method: Phone, i18n: LocalizerType): string {
switch (method.type) { switch (method.type) {
case ContactType.CUSTOM: case ContactFormType.CUSTOM:
return method.label || i18n('phone'); return method.label || i18n('phone');
case ContactType.HOME: case ContactFormType.HOME:
return i18n('home'); return i18n('home');
case ContactType.MOBILE: case ContactFormType.MOBILE:
return i18n('mobile'); return i18n('mobile');
case ContactType.WORK: case ContactFormType.WORK:
return i18n('work'); return i18n('work');
default: default:
throw missingCaseError(method.type); throw missingCaseError(method.type);

View file

@ -20,7 +20,7 @@ const contact = {
}, },
onClick: () => console.log('onClick'), onClick: () => console.log('onClick'),
onSendMessage: () => console.log('onSendMessage'), onSendMessage: () => console.log('onSendMessage'),
hasSignalAccount: true, signalAccount: '+12025550000',
}; };
<util.ConversationContext theme={util.theme} ios={util.ios}> <util.ConversationContext theme={util.theme} ios={util.ios}>
<li> <li>
@ -86,7 +86,7 @@ const contact = {
}, },
onClick: () => console.log('onClick'), onClick: () => console.log('onClick'),
onSendMessage: () => console.log('onSendMessage'), onSendMessage: () => console.log('onSendMessage'),
hasSignalAccount: true, signalAccount: '+12025550000',
}; };
<util.ConversationContext theme={util.theme} ios={util.ios}> <util.ConversationContext theme={util.theme} ios={util.ios}>
<li> <li>
@ -129,7 +129,6 @@ const contact = {
path: util.gifObjectUrl, path: util.gifObjectUrl,
}, },
}, },
hasSignalAccount: true,
}; };
<util.ConversationContext theme={util.theme} ios={util.ios}> <util.ConversationContext theme={util.theme} ios={util.ios}>
<li> <li>
@ -170,7 +169,7 @@ const contact = {
path: util.gifObjectUrl, path: util.gifObjectUrl,
}, },
}, },
hasSignalAccount: true, signalAccount: '+12025550000',
}; };
<util.ConversationContext theme={util.theme} type="group" ios={util.ios}> <util.ConversationContext theme={util.theme} type="group" ios={util.ios}>
<li> <li>
@ -230,7 +229,6 @@ const contact = {
path: util.gifObjectUrl, path: util.gifObjectUrl,
}, },
}, },
hasSignalAccount: false,
}; };
<util.ConversationContext theme={util.theme} ios={util.ios}> <util.ConversationContext theme={util.theme} ios={util.ios}>
<li> <li>
@ -292,7 +290,6 @@ const contact = {
path: util.gifObjectUrl, path: util.gifObjectUrl,
}, },
}, },
hasSignalAccount: false,
}; };
<util.ConversationContext theme={util.theme} ios={util.ios}> <util.ConversationContext theme={util.theme} ios={util.ios}>
<li> <li>
@ -356,7 +353,7 @@ const contact = {
path: util.gifObjectUrl, path: util.gifObjectUrl,
}, },
}, },
hasSignalAccount: false, signalAccount: '+12025551000',
}; };
<util.ConversationContext theme={util.theme} ios={util.ios}> <util.ConversationContext theme={util.theme} ios={util.ios}>
<li> <li>
@ -415,7 +412,6 @@ const contact = {
type: 1, type: 1,
}, },
], ],
hasSignalAccount: true,
}; };
<util.ConversationContext theme={util.theme} ios={util.ios}> <util.ConversationContext theme={util.theme} ios={util.ios}>
<li> <li>
@ -527,7 +523,7 @@ const contactWithAccount = {
path: util.gifObjectUrl, path: util.gifObjectUrl,
}, },
}, },
hasSignalAccount: true, signalAccount: '+12025550000',
}; };
const contactWithoutAccount = { const contactWithoutAccount = {
name: { name: {
@ -544,7 +540,6 @@ const contactWithoutAccount = {
path: util.gifObjectUrl, path: util.gifObjectUrl,
}, },
}, },
hasSignalAccount: false,
}; };
<util.ConversationContext theme={util.theme} ios={util.ios}> <util.ConversationContext theme={util.theme} ios={util.ios}>
<li> <li>

View file

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { Contact } from '../../types/Contact'; import { ContactType } from '../../types/Contact';
import { LocalizerType } from '../../types/Util'; import { LocalizerType } from '../../types/Util';
import { import {
@ -11,8 +11,7 @@ import {
} from './_contactUtil'; } from './_contactUtil';
interface Props { interface Props {
contact: Contact; contact: ContactType;
hasSignalAccount: boolean;
i18n: LocalizerType; i18n: LocalizerType;
isIncoming: boolean; isIncoming: boolean;
withContentAbove: boolean; withContentAbove: boolean;

View file

@ -24,7 +24,7 @@ interface Props {
i18n: LocalizerType; i18n: LocalizerType;
onError: () => void; onError: () => void;
onClickAttachment?: (attachment: AttachmentType) => void; onClick?: (attachment: AttachmentType) => void;
} }
export class ImageGrid extends React.Component<Props> { export class ImageGrid extends React.Component<Props> {
@ -35,7 +35,7 @@ export class ImageGrid extends React.Component<Props> {
bottomOverlay, bottomOverlay,
i18n, i18n,
onError, onError,
onClickAttachment, onClick,
withContentAbove, withContentAbove,
withContentBelow, withContentBelow,
} = this.props; } = this.props;
@ -76,7 +76,7 @@ export class ImageGrid extends React.Component<Props> {
height={height} height={height}
width={width} width={width}
url={getUrl(attachments[0])} url={getUrl(attachments[0])}
onClick={onClickAttachment} onClick={onClick}
onError={onError} onError={onError}
/> />
</div> </div>
@ -97,7 +97,7 @@ export class ImageGrid extends React.Component<Props> {
height={149} height={149}
width={149} width={149}
url={getThumbnailUrl(attachments[0])} url={getThumbnailUrl(attachments[0])}
onClick={onClickAttachment} onClick={onClick}
onError={onError} onError={onError}
/> />
<Image <Image
@ -111,7 +111,7 @@ export class ImageGrid extends React.Component<Props> {
width={149} width={149}
attachment={attachments[1]} attachment={attachments[1]}
url={getThumbnailUrl(attachments[1])} url={getThumbnailUrl(attachments[1])}
onClick={onClickAttachment} onClick={onClick}
onError={onError} onError={onError}
/> />
</div> </div>
@ -132,7 +132,7 @@ export class ImageGrid extends React.Component<Props> {
height={200} height={200}
width={199} width={199}
url={getUrl(attachments[0])} url={getUrl(attachments[0])}
onClick={onClickAttachment} onClick={onClick}
onError={onError} onError={onError}
/> />
<div className="module-image-grid__column"> <div className="module-image-grid__column">
@ -145,7 +145,7 @@ export class ImageGrid extends React.Component<Props> {
attachment={attachments[1]} attachment={attachments[1]}
playIconOverlay={isVideoAttachment(attachments[1])} playIconOverlay={isVideoAttachment(attachments[1])}
url={getThumbnailUrl(attachments[1])} url={getThumbnailUrl(attachments[1])}
onClick={onClickAttachment} onClick={onClick}
onError={onError} onError={onError}
/> />
<Image <Image
@ -158,7 +158,7 @@ export class ImageGrid extends React.Component<Props> {
attachment={attachments[2]} attachment={attachments[2]}
playIconOverlay={isVideoAttachment(attachments[2])} playIconOverlay={isVideoAttachment(attachments[2])}
url={getThumbnailUrl(attachments[2])} url={getThumbnailUrl(attachments[2])}
onClick={onClickAttachment} onClick={onClick}
onError={onError} onError={onError}
/> />
</div> </div>
@ -180,7 +180,7 @@ export class ImageGrid extends React.Component<Props> {
height={149} height={149}
width={149} width={149}
url={getThumbnailUrl(attachments[0])} url={getThumbnailUrl(attachments[0])}
onClick={onClickAttachment} onClick={onClick}
onError={onError} onError={onError}
/> />
<Image <Image
@ -192,7 +192,7 @@ export class ImageGrid extends React.Component<Props> {
width={149} width={149}
attachment={attachments[1]} attachment={attachments[1]}
url={getThumbnailUrl(attachments[1])} url={getThumbnailUrl(attachments[1])}
onClick={onClickAttachment} onClick={onClick}
onError={onError} onError={onError}
/> />
</div> </div>
@ -207,7 +207,7 @@ export class ImageGrid extends React.Component<Props> {
width={149} width={149}
attachment={attachments[2]} attachment={attachments[2]}
url={getThumbnailUrl(attachments[2])} url={getThumbnailUrl(attachments[2])}
onClick={onClickAttachment} onClick={onClick}
onError={onError} onError={onError}
/> />
<Image <Image
@ -220,7 +220,7 @@ export class ImageGrid extends React.Component<Props> {
width={149} width={149}
attachment={attachments[3]} attachment={attachments[3]}
url={getThumbnailUrl(attachments[3])} url={getThumbnailUrl(attachments[3])}
onClick={onClickAttachment} onClick={onClick}
onError={onError} onError={onError}
/> />
</div> </div>
@ -247,7 +247,7 @@ export class ImageGrid extends React.Component<Props> {
height={149} height={149}
width={149} width={149}
url={getThumbnailUrl(attachments[0])} url={getThumbnailUrl(attachments[0])}
onClick={onClickAttachment} onClick={onClick}
onError={onError} onError={onError}
/> />
<Image <Image
@ -259,7 +259,7 @@ export class ImageGrid extends React.Component<Props> {
width={149} width={149}
attachment={attachments[1]} attachment={attachments[1]}
url={getThumbnailUrl(attachments[1])} url={getThumbnailUrl(attachments[1])}
onClick={onClickAttachment} onClick={onClick}
onError={onError} onError={onError}
/> />
</div> </div>
@ -274,7 +274,7 @@ export class ImageGrid extends React.Component<Props> {
width={99} width={99}
attachment={attachments[2]} attachment={attachments[2]}
url={getThumbnailUrl(attachments[2])} url={getThumbnailUrl(attachments[2])}
onClick={onClickAttachment} onClick={onClick}
onError={onError} onError={onError}
/> />
<Image <Image
@ -286,7 +286,7 @@ export class ImageGrid extends React.Component<Props> {
width={98} width={98}
attachment={attachments[3]} attachment={attachments[3]}
url={getThumbnailUrl(attachments[3])} url={getThumbnailUrl(attachments[3])}
onClick={onClickAttachment} onClick={onClick}
onError={onError} onError={onError}
/> />
<Image <Image
@ -301,7 +301,7 @@ export class ImageGrid extends React.Component<Props> {
overlayText={moreMessagesOverlayText} overlayText={moreMessagesOverlayText}
attachment={attachments[4]} attachment={attachments[4]}
url={getThumbnailUrl(attachments[4])} url={getThumbnailUrl(attachments[4])}
onClick={onClickAttachment} onClick={onClick}
onError={onError} onError={onError}
/> />
</div> </div>

View file

@ -25,7 +25,7 @@ import {
isVideo, isVideo,
} from '../../../ts/types/Attachment'; } from '../../../ts/types/Attachment';
import { AttachmentType } from '../../types/Attachment'; import { AttachmentType } from '../../types/Attachment';
import { Contact } from '../../types/Contact'; import { ContactType } from '../../types/Contact';
import { getIncrement } from '../../util/timer'; import { getIncrement } from '../../util/timer';
import { isFileDangerous } from '../../util/isFileDangerous'; import { isFileDangerous } from '../../util/isFileDangerous';
@ -46,22 +46,14 @@ interface LinkPreviewType {
image?: AttachmentType; image?: AttachmentType;
} }
export interface Props { type PropsData = {
disableMenu?: boolean; id: string;
text?: string; text?: string;
textPending?: boolean; textPending?: boolean;
id?: string;
collapseMetadata?: boolean;
direction: 'incoming' | 'outgoing'; direction: 'incoming' | 'outgoing';
timestamp: number; timestamp: number;
status?: 'sending' | 'sent' | 'delivered' | 'read' | 'error'; status?: 'sending' | 'sent' | 'delivered' | 'read' | 'error';
// What if changed this over to a single contact like quote, and put the events on it? contact?: ContactType;
contact?: Contact & {
hasSignalAccount: boolean;
onSendMessage?: () => void;
onClick?: () => void;
};
i18n: LocalizerType;
authorName?: string; authorName?: string;
authorProfileName?: string; authorProfileName?: string;
/** Note: this should be formatted for display */ /** Note: this should be formatted for display */
@ -73,11 +65,12 @@ export interface Props {
text: string; text: string;
attachment?: QuotedAttachmentType; attachment?: QuotedAttachmentType;
isFromMe: boolean; isFromMe: boolean;
sentAt: number;
authorId: string;
authorPhoneNumber: string; authorPhoneNumber: string;
authorProfileName?: string; authorProfileName?: string;
authorName?: string; authorName?: string;
authorColor?: ColorType; authorColor?: ColorType;
onClick?: () => void;
referencedMessageNotFound: boolean; referencedMessageNotFound: boolean;
}; };
previews: Array<LinkPreviewType>; previews: Array<LinkPreviewType>;
@ -85,14 +78,48 @@ export interface Props {
isExpired: boolean; isExpired: boolean;
expirationLength?: number; expirationLength?: number;
expirationTimestamp?: number; expirationTimestamp?: number;
onClickAttachment?: (attachment: AttachmentType) => void; };
onClickLinkPreview?: (url: string) => void;
onReply?: () => void; type PropsHousekeeping = {
onRetrySend?: () => void; i18n: LocalizerType;
onDownload?: (isDangerous: boolean) => void; disableMenu?: boolean;
onDelete?: () => void; disableScroll?: boolean;
onShowDetail: () => void; collapseMetadata?: boolean;
} };
export type PropsActions = {
replyToMessage: (id: string) => void;
retrySend: (id: string) => void;
deleteMessage: (id: string) => void;
showMessageDetail: (id: string) => void;
openConversation: (conversationId: string, messageId?: string) => void;
showContactDetail: (
options: { contact: ContactType; signalAccount?: string }
) => void;
showVisualAttachment: (
options: { attachment: AttachmentType; messageId: string }
) => void;
downloadAttachment: (
options: {
attachment: AttachmentType;
timestamp: number;
isDangerous: boolean;
}
) => void;
openLink: (url: string) => void;
scrollToMessage: (
options: {
author: string;
sentAt: number;
referencedMessageNotFound: boolean;
}
) => void;
};
export type Props = PropsData & PropsHousekeeping & PropsActions;
interface State { interface State {
expiring: boolean; expiring: boolean;
@ -301,6 +328,7 @@ export class Message extends React.PureComponent<Props, State> {
// tslint:disable-next-line max-func-body-length cyclomatic-complexity // tslint:disable-next-line max-func-body-length cyclomatic-complexity
public renderAttachment() { public renderAttachment() {
const { const {
id,
attachments, attachments,
text, text,
collapseMetadata, collapseMetadata,
@ -308,7 +336,7 @@ export class Message extends React.PureComponent<Props, State> {
direction, direction,
i18n, i18n,
quote, quote,
onClickAttachment, showVisualAttachment,
} = this.props; } = this.props;
const { imageBroken } = this.state; const { imageBroken } = this.state;
@ -349,7 +377,9 @@ export class Message extends React.PureComponent<Props, State> {
bottomOverlay={!collapseMetadata} bottomOverlay={!collapseMetadata}
i18n={i18n} i18n={i18n}
onError={this.handleImageErrorBound} onError={this.handleImageErrorBound}
onClickAttachment={onClickAttachment} onClick={attachment => {
showVisualAttachment({ attachment, messageId: id });
}}
/> />
</div> </div>
); );
@ -438,7 +468,7 @@ export class Message extends React.PureComponent<Props, State> {
conversationType, conversationType,
direction, direction,
i18n, i18n,
onClickLinkPreview, openLink,
previews, previews,
quote, quote,
} = this.props; } = this.props;
@ -475,9 +505,7 @@ export class Message extends React.PureComponent<Props, State> {
: null : null
)} )}
onClick={() => { onClick={() => {
if (onClickLinkPreview) { openLink(first.url);
onClickLinkPreview(first.url);
}
}} }}
> >
{first.image && previewHasImage && isFullSizeImage ? ( {first.image && previewHasImage && isFullSizeImage ? (
@ -537,8 +565,10 @@ export class Message extends React.PureComponent<Props, State> {
conversationType, conversationType,
authorColor, authorColor,
direction, direction,
disableScroll,
i18n, i18n,
quote, quote,
scrollToMessage,
} = this.props; } = this.props;
if (!quote) { if (!quote) {
@ -550,10 +580,21 @@ export class Message extends React.PureComponent<Props, State> {
const quoteColor = const quoteColor =
direction === 'incoming' ? authorColor : quote.authorColor; direction === 'incoming' ? authorColor : quote.authorColor;
const { referencedMessageNotFound } = quote;
const clickHandler = disableScroll
? undefined
: () => {
scrollToMessage({
author: quote.authorId,
sentAt: quote.sentAt,
referencedMessageNotFound,
});
};
return ( return (
<Quote <Quote
i18n={i18n} i18n={i18n}
onClick={quote.onClick} onClick={clickHandler}
text={quote.text} text={quote.text}
attachment={quote.attachment} attachment={quote.attachment}
isIncoming={direction === 'incoming'} isIncoming={direction === 'incoming'}
@ -561,7 +602,7 @@ export class Message extends React.PureComponent<Props, State> {
authorProfileName={quote.authorProfileName} authorProfileName={quote.authorProfileName}
authorName={quote.authorName} authorName={quote.authorName}
authorColor={quoteColor} authorColor={quoteColor}
referencedMessageNotFound={quote.referencedMessageNotFound} referencedMessageNotFound={referencedMessageNotFound}
isFromMe={quote.isFromMe} isFromMe={quote.isFromMe}
withContentAbove={withContentAbove} withContentAbove={withContentAbove}
/> />
@ -575,6 +616,7 @@ export class Message extends React.PureComponent<Props, State> {
conversationType, conversationType,
direction, direction,
i18n, i18n,
showContactDetail,
text, text,
} = this.props; } = this.props;
if (!contact) { if (!contact) {
@ -589,10 +631,11 @@ export class Message extends React.PureComponent<Props, State> {
return ( return (
<EmbeddedContact <EmbeddedContact
contact={contact} contact={contact}
hasSignalAccount={contact.hasSignalAccount}
isIncoming={direction === 'incoming'} isIncoming={direction === 'incoming'}
i18n={i18n} i18n={i18n}
onClick={contact.onClick} onClick={() => {
showContactDetail({ contact, signalAccount: contact.signalAccount });
}}
withContentAbove={withContentAbove} withContentAbove={withContentAbove}
withContentBelow={withContentBelow} withContentBelow={withContentBelow}
/> />
@ -600,15 +643,19 @@ export class Message extends React.PureComponent<Props, State> {
} }
public renderSendMessageButton() { public renderSendMessageButton() {
const { contact, i18n } = this.props; const { contact, openConversation, i18n } = this.props;
if (!contact || !contact.hasSignalAccount) { if (!contact || !contact.signalAccount) {
return null; return null;
} }
return ( return (
<div <div
role="button" role="button"
onClick={contact.onSendMessage} onClick={() => {
if (contact.signalAccount) {
openConversation(contact.signalAccount);
}
}}
className="module-message__send-message-button" className="module-message__send-message-button"
> >
{i18n('sendMessageToContact')} {i18n('sendMessageToContact')}
@ -718,8 +765,10 @@ export class Message extends React.PureComponent<Props, State> {
attachments, attachments,
direction, direction,
disableMenu, disableMenu,
onDownload, downloadAttachment,
onReply, id,
replyToMessage,
timestamp,
} = this.props; } = this.props;
if (!isCorrectSide || disableMenu) { if (!isCorrectSide || disableMenu) {
@ -736,9 +785,11 @@ export class Message extends React.PureComponent<Props, State> {
!multipleAttachments && firstAttachment && !firstAttachment.pending ? ( !multipleAttachments && firstAttachment && !firstAttachment.pending ? (
<div <div
onClick={() => { onClick={() => {
if (onDownload) { downloadAttachment({
onDownload(isDangerous); isDangerous,
} attachment: firstAttachment,
timestamp,
});
}} }}
role="button" role="button"
className={classNames( className={classNames(
@ -750,7 +801,9 @@ export class Message extends React.PureComponent<Props, State> {
const replyButton = ( const replyButton = (
<div <div
onClick={onReply} onClick={() => {
replyToMessage(id);
}}
role="button" role="button"
className={classNames( className={classNames(
'module-message__buttons__reply', 'module-message__buttons__reply',
@ -793,13 +846,15 @@ export class Message extends React.PureComponent<Props, State> {
const { const {
attachments, attachments,
direction, direction,
status, downloadAttachment,
onDelete,
onDownload,
onReply,
onRetrySend,
onShowDetail,
i18n, i18n,
id,
deleteMessage,
showMessageDetail,
replyToMessage,
retrySend,
status,
timestamp,
} = this.props; } = this.props;
const showRetry = status === 'error' && direction === 'outgoing'; const showRetry = status === 'error' && direction === 'outgoing';
@ -816,9 +871,11 @@ export class Message extends React.PureComponent<Props, State> {
className: 'module-message__context__download', className: 'module-message__context__download',
}} }}
onClick={() => { onClick={() => {
if (onDownload) { downloadAttachment({
onDownload(isDangerous); attachment: attachments[0],
} timestamp,
isDangerous,
});
}} }}
> >
{i18n('downloadAttachment')} {i18n('downloadAttachment')}
@ -828,7 +885,9 @@ export class Message extends React.PureComponent<Props, State> {
attributes={{ attributes={{
className: 'module-message__context__reply', className: 'module-message__context__reply',
}} }}
onClick={onReply} onClick={() => {
replyToMessage(id);
}}
> >
{i18n('replyToMessage')} {i18n('replyToMessage')}
</MenuItem> </MenuItem>
@ -836,7 +895,9 @@ export class Message extends React.PureComponent<Props, State> {
attributes={{ attributes={{
className: 'module-message__context__more-info', className: 'module-message__context__more-info',
}} }}
onClick={onShowDetail} onClick={() => {
showMessageDetail(id);
}}
> >
{i18n('moreInfo')} {i18n('moreInfo')}
</MenuItem> </MenuItem>
@ -845,7 +906,9 @@ export class Message extends React.PureComponent<Props, State> {
attributes={{ attributes={{
className: 'module-message__context__retry-send', className: 'module-message__context__retry-send',
}} }}
onClick={onRetrySend} onClick={() => {
retrySend(id);
}}
> >
{i18n('retrySend')} {i18n('retrySend')}
</MenuItem> </MenuItem>
@ -854,7 +917,9 @@ export class Message extends React.PureComponent<Props, State> {
attributes={{ attributes={{
className: 'module-message__context__delete-message', className: 'module-message__context__delete-message',
}} }}
onClick={onDelete} onClick={() => {
deleteMessage(id);
}}
> >
{i18n('deleteMessage')} {i18n('deleteMessage')}
</MenuItem> </MenuItem>

View file

@ -59,7 +59,9 @@ export class MessageDetail extends React.Component<Props> {
return ( return (
<div className="module-message-detail__delete-button-container"> <div className="module-message-detail__delete-button-container">
<button <button
onClick={message.onDelete} onClick={() => {
message.deleteMessage(message.id);
}}
className="module-message-detail__delete-button" className="module-message-detail__delete-button"
> >
{i18n('deleteThisMessage')} {i18n('deleteThisMessage')}

View file

@ -5,22 +5,31 @@ import { ContactName } from './ContactName';
import { Intl } from '../Intl'; import { Intl } from '../Intl';
import { LocalizerType } from '../../types/Util'; import { LocalizerType } from '../../types/Util';
interface Contact { interface ContactType {
id: string;
phoneNumber: string; phoneNumber: string;
profileName?: string; profileName?: string;
name?: string; name?: string;
} }
interface Props { type PropsData = {
isGroup: boolean; isGroup: boolean;
contact: Contact; contact: ContactType;
};
type PropsHousekeeping = {
i18n: LocalizerType; i18n: LocalizerType;
onVerify: () => void; };
}
export type PropsActions = {
showIdentity: (id: string) => void;
};
type Props = PropsData & PropsHousekeeping & PropsActions;
export class SafetyNumberNotification extends React.Component<Props> { export class SafetyNumberNotification extends React.Component<Props> {
public render() { public render() {
const { contact, isGroup, i18n, onVerify } = this.props; const { contact, isGroup, i18n, showIdentity } = this.props;
const changeKey = isGroup const changeKey = isGroup
? 'safetyNumberChangedGroup' ? 'safetyNumberChangedGroup'
: 'safetyNumberChanged'; : 'safetyNumberChanged';
@ -50,7 +59,9 @@ export class SafetyNumberNotification extends React.Component<Props> {
</div> </div>
<div <div
role="button" role="button"
onClick={onVerify} onClick={() => {
showIdentity(contact.id);
}}
className="module-verification-notification__button" className="module-verification-notification__button"
> >
{i18n('verifyNewNumber')} {i18n('verifyNewNumber')}

View file

@ -5,7 +5,7 @@ import { Avatar } from '../Avatar';
import { Spinner } from '../Spinner'; import { Spinner } from '../Spinner';
import { LocalizerType } from '../../types/Util'; import { LocalizerType } from '../../types/Util';
import { Contact, getName } from '../../types/Contact'; import { ContactType, getName } from '../../types/Contact';
// This file starts with _ to keep it from showing up in the StyleGuide. // This file starts with _ to keep it from showing up in the StyleGuide.
@ -15,7 +15,7 @@ export function renderAvatar({
size, size,
direction, direction,
}: { }: {
contact: Contact; contact: ContactType;
i18n: LocalizerType; i18n: LocalizerType;
size: number; size: number;
direction?: string; direction?: string;
@ -52,7 +52,7 @@ export function renderName({
isIncoming, isIncoming,
module, module,
}: { }: {
contact: Contact; contact: ContactType;
isIncoming: boolean; isIncoming: boolean;
module: string; module: string;
}) { }) {
@ -73,7 +73,7 @@ export function renderContactShorthand({
isIncoming, isIncoming,
module, module,
}: { }: {
contact: Contact; contact: ContactType;
isIncoming: boolean; isIncoming: boolean;
module: string; module: string;
}) { }) {

View file

@ -63,10 +63,8 @@ describe('Contact', () => {
}); });
describe('contactSelector', () => { describe('contactSelector', () => {
const regionCode = '1'; const regionCode = '1';
const hasSignalAccount = true; const signalAccount = '+1202555000';
const getAbsoluteAttachmentPath = (path: string) => `absolute:${path}`; const getAbsoluteAttachmentPath = (path: string) => `absolute:${path}`;
const onSendMessage = () => null;
const onClick = () => null;
it('eliminates avatar if it has had an attachment download error', () => { it('eliminates avatar if it has had an attachment download error', () => {
const contact = { const contact = {
@ -91,17 +89,13 @@ describe('Contact', () => {
}, },
organization: 'Somewhere, Inc.', organization: 'Somewhere, Inc.',
avatar: undefined, avatar: undefined,
hasSignalAccount, signalAccount,
onSendMessage,
onClick,
number: undefined, number: undefined,
}; };
const actual = contactSelector(contact, { const actual = contactSelector(contact, {
regionCode, regionCode,
hasSignalAccount, signalAccount,
getAbsoluteAttachmentPath, getAbsoluteAttachmentPath,
onSendMessage,
onClick,
}); });
assert.deepEqual(actual, expected); assert.deepEqual(actual, expected);
}); });
@ -135,17 +129,13 @@ describe('Contact', () => {
path: undefined, path: undefined,
}, },
}, },
hasSignalAccount, signalAccount,
onSendMessage,
onClick,
number: undefined, number: undefined,
}; };
const actual = contactSelector(contact, { const actual = contactSelector(contact, {
regionCode, regionCode,
hasSignalAccount, signalAccount,
getAbsoluteAttachmentPath, getAbsoluteAttachmentPath,
onSendMessage,
onClick,
}); });
assert.deepEqual(actual, expected); assert.deepEqual(actual, expected);
}); });
@ -178,17 +168,13 @@ describe('Contact', () => {
path: 'absolute:somewhere', path: 'absolute:somewhere',
}, },
}, },
hasSignalAccount, signalAccount,
onSendMessage,
onClick,
number: undefined, number: undefined,
}; };
const actual = contactSelector(contact, { const actual = contactSelector(contact, {
regionCode, regionCode,
hasSignalAccount, signalAccount,
getAbsoluteAttachmentPath, getAbsoluteAttachmentPath,
onSendMessage,
onClick,
}); });
assert.deepEqual(actual, expected); assert.deepEqual(actual, expected);
}); });

View file

@ -2,13 +2,14 @@
import Attachments from '../../app/attachments'; import Attachments from '../../app/attachments';
import { format as formatPhoneNumber } from '../types/PhoneNumber'; import { format as formatPhoneNumber } from '../types/PhoneNumber';
export interface Contact { export interface ContactType {
name?: Name; name?: Name;
number?: Array<Phone>; number?: Array<Phone>;
email?: Array<Email>; email?: Array<Email>;
address?: Array<PostalAddress>; address?: Array<PostalAddress>;
avatar?: Avatar; avatar?: Avatar;
organization?: string; organization?: string;
signalAccount?: string;
} }
interface Name { interface Name {
@ -20,7 +21,7 @@ interface Name {
displayName?: string; displayName?: string;
} }
export enum ContactType { export enum ContactFormType {
HOME = 1, HOME = 1,
MOBILE = 2, MOBILE = 2,
WORK = 3, WORK = 3,
@ -35,13 +36,13 @@ export enum AddressType {
export interface Phone { export interface Phone {
value: string; value: string;
type: ContactType; type: ContactFormType;
label?: string; label?: string;
} }
export interface Email { export interface Email {
value: string; value: string;
type: ContactType; type: ContactFormType;
label?: string; label?: string;
} }
@ -69,22 +70,14 @@ interface Attachment {
} }
export function contactSelector( export function contactSelector(
contact: Contact, contact: ContactType,
options: { options: {
regionCode: string; regionCode: string;
hasSignalAccount: boolean; signalAccount?: string;
getAbsoluteAttachmentPath: (path: string) => string; getAbsoluteAttachmentPath: (path: string) => string;
onSendMessage: () => void;
onClick: () => void;
} }
) { ) {
const { const { getAbsoluteAttachmentPath, signalAccount, regionCode } = options;
getAbsoluteAttachmentPath,
hasSignalAccount,
onClick,
onSendMessage,
regionCode,
} = options;
let { avatar } = contact; let { avatar } = contact;
if (avatar && avatar.avatar) { if (avatar && avatar.avatar) {
@ -105,9 +98,7 @@ export function contactSelector(
return { return {
...contact, ...contact,
hasSignalAccount, signalAccount,
onSendMessage,
onClick,
avatar, avatar,
number: number:
contact.number && contact.number &&
@ -120,7 +111,7 @@ export function contactSelector(
}; };
} }
export function getName(contact: Contact): string | undefined { export function getName(contact: ContactType): string | undefined {
const { name, organization } = contact; const { name, organization } = contact;
const displayName = (name && name.displayName) || undefined; const displayName = (name && name.displayName) || undefined;
const givenName = (name && name.givenName) || undefined; const givenName = (name && name.givenName) || undefined;

View file

@ -1,5 +1,5 @@
import { Attachment } from './Attachment'; import { Attachment } from './Attachment';
import { Contact } from './Contact'; import { ContactType } from './Contact';
import { IndexableBoolean, IndexablePresence } from './IndexedDB'; import { IndexableBoolean, IndexablePresence } from './IndexedDB';
export type Message = UserMessage | VerifiedChangeMessage; export type Message = UserMessage | VerifiedChangeMessage;
@ -87,7 +87,7 @@ type MessageSchemaVersion5 = Partial<
type MessageSchemaVersion6 = Partial< type MessageSchemaVersion6 = Partial<
Readonly<{ Readonly<{
contact: Array<Contact>; contact: Array<ContactType>;
}> }>
>; >;