Message Requests improvements
This commit is contained in:
parent
b63291507a
commit
81cb7730a5
21 changed files with 302 additions and 263 deletions
|
@ -1764,6 +1764,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"ConversationListItem--message-request": {
|
||||||
|
"message": "Message Request",
|
||||||
|
"description": "Preview shown for conversation if the user has not yet accepted an incoming message request"
|
||||||
|
},
|
||||||
"ConversationListItem--draft-prefix": {
|
"ConversationListItem--draft-prefix": {
|
||||||
"message": "Draft:",
|
"message": "Draft:",
|
||||||
"description": "Prefix shown in italic in conversation view when a draft is saved"
|
"description": "Prefix shown in italic in conversation view when a draft is saved"
|
||||||
|
|
|
@ -1885,6 +1885,11 @@
|
||||||
logger: window.log,
|
logger: window.log,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Force a re-fetch here when we've processed our queue. Without this, we won't try
|
||||||
|
// again for two hours after our first attempt. Which might have been while we were
|
||||||
|
// offline or didn't have credentials.
|
||||||
|
window.Signal.RemoteConfig.refreshRemoteConfig();
|
||||||
|
|
||||||
let interval = setInterval(() => {
|
let interval = setInterval(() => {
|
||||||
const view = window.owsDesktopApp.appView;
|
const view = window.owsDesktopApp.appView;
|
||||||
if (view) {
|
if (view) {
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
const { Util } = window.Signal;
|
const { Util } = window.Signal;
|
||||||
const { Conversation, Contact, Message } = window.Signal.Types;
|
const { Contact, Message } = window.Signal.Types;
|
||||||
const {
|
const {
|
||||||
deleteAttachmentData,
|
deleteAttachmentData,
|
||||||
doesAttachmentExist,
|
doesAttachmentExist,
|
||||||
|
@ -134,12 +134,6 @@
|
||||||
this.updateLastMessage.bind(this),
|
this.updateLastMessage.bind(this),
|
||||||
200
|
200
|
||||||
);
|
);
|
||||||
this.throttledUpdateSharedGroups =
|
|
||||||
this.throttledUpdateSharedGroups ||
|
|
||||||
_.throttle(
|
|
||||||
this.updateSharedGroups.bind(this),
|
|
||||||
1000 * 60 * 5 // five minutes
|
|
||||||
);
|
|
||||||
|
|
||||||
this.listenTo(
|
this.listenTo(
|
||||||
this.messageCollection,
|
this.messageCollection,
|
||||||
|
@ -175,10 +169,11 @@
|
||||||
this.typingPauseTimer = null;
|
this.typingPauseTimer = null;
|
||||||
|
|
||||||
// Keep props ready
|
// Keep props ready
|
||||||
this.generateProps = () => {
|
const generateProps = () => {
|
||||||
this.cachedProps = this.getProps();
|
this.cachedProps = this.getProps();
|
||||||
};
|
};
|
||||||
this.on('change', this.generateProps);
|
this.on('change', generateProps);
|
||||||
|
generateProps();
|
||||||
},
|
},
|
||||||
|
|
||||||
isMe() {
|
isMe() {
|
||||||
|
@ -451,8 +446,6 @@
|
||||||
getProps() {
|
getProps() {
|
||||||
const color = this.getColor();
|
const color = this.getColor();
|
||||||
|
|
||||||
this.throttledUpdateSharedGroups();
|
|
||||||
|
|
||||||
const typingValues = _.values(this.contactTypingTimers || {});
|
const typingValues = _.values(this.contactTypingTimers || {});
|
||||||
const typingMostRecent = _.first(_.sortBy(typingValues, 'timestamp'));
|
const typingMostRecent = _.first(_.sortBy(typingValues, 'timestamp'));
|
||||||
const typingContact = typingMostRecent
|
const typingContact = typingMostRecent
|
||||||
|
@ -575,15 +568,16 @@
|
||||||
async handleReadAndDownloadAttachments() {
|
async handleReadAndDownloadAttachments() {
|
||||||
let messages;
|
let messages;
|
||||||
do {
|
do {
|
||||||
|
const first = messages ? messages.first() : null;
|
||||||
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
messages = await window.Signal.Data.getOlderMessagesByConversation(
|
messages = await window.Signal.Data.getOlderMessagesByConversation(
|
||||||
this.get('id'),
|
this.get('id'),
|
||||||
{
|
{
|
||||||
MessageCollection: Whisper.MessageCollection,
|
MessageCollection: Whisper.MessageCollection,
|
||||||
limit: 100,
|
limit: 100,
|
||||||
receivedAt: messages
|
receivedAt: first ? first.get('received_at') : null,
|
||||||
? messages.first().get('received_at')
|
messageId: first ? first.id : null,
|
||||||
: undefined,
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -959,13 +953,18 @@
|
||||||
(this.get('messageCountBeforeMessageRequests') || 0) > 0;
|
(this.get('messageCountBeforeMessageRequests') || 0) > 0;
|
||||||
const hasNoMessages = (this.get('messageCount') || 0) === 0;
|
const hasNoMessages = (this.get('messageCount') || 0) === 0;
|
||||||
|
|
||||||
|
const isEmptyPrivateConvo = hasNoMessages && this.isPrivate();
|
||||||
|
const isEmptyWhitelistedGroup =
|
||||||
|
hasNoMessages && !this.isPrivate() && this.get('profileSharing');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
isFromOrAddedByTrustedContact ||
|
isFromOrAddedByTrustedContact ||
|
||||||
hasSentMessages ||
|
hasSentMessages ||
|
||||||
hasMessagesBeforeMessageRequests ||
|
hasMessagesBeforeMessageRequests ||
|
||||||
// an empty conversation is the scenario where we need to rely on
|
// an empty group is the scenario where we need to rely on
|
||||||
// whether the profile has already been shared or not
|
// whether the profile has already been shared or not
|
||||||
(hasNoMessages && this.get('profileSharing'))
|
isEmptyPrivateConvo ||
|
||||||
|
isEmptyWhitelistedGroup
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1868,37 +1867,40 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const messages = await window.Signal.Data.getOlderMessagesByConversation(
|
const [previewMessage, activityMessage] = await Promise.all([
|
||||||
this.id,
|
window.Signal.Data.getLastConversationPreview(this.id, {
|
||||||
{ limit: 1, MessageCollection: Whisper.MessageCollection }
|
Message: Whisper.Message,
|
||||||
);
|
}),
|
||||||
|
window.Signal.Data.getLastConversationActivity(this.id, {
|
||||||
|
Message: Whisper.Message,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// This is the less-restrictive of these two fetches; if it's falsey, both will be
|
||||||
|
if (!previewMessage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const lastMessageModel = messages.at(0);
|
|
||||||
if (
|
if (
|
||||||
this.hasDraft() &&
|
this.hasDraft() &&
|
||||||
this.get('draftTimestamp') &&
|
this.get('draftTimestamp') &&
|
||||||
(!lastMessageModel ||
|
previewMessage.get('sent_at') < this.get('draftTimestamp')
|
||||||
lastMessageModel.get('sent_at') < this.get('draftTimestamp'))
|
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastMessageJSON = lastMessageModel
|
const currentTimestamp = this.get('timestamp') || null;
|
||||||
? lastMessageModel.toJSON()
|
const timestamp = activityMessage
|
||||||
: null;
|
? activityMessage.sent_at || currentTimestamp
|
||||||
const lastMessageStatusModel = lastMessageModel
|
: currentTimestamp;
|
||||||
? lastMessageModel.getMessagePropStatus()
|
|
||||||
: null;
|
this.set({
|
||||||
const lastMessageUpdate = Conversation.createLastMessageUpdate({
|
lastMessage: previewMessage.getNotificationText() || '',
|
||||||
currentTimestamp: this.get('timestamp') || null,
|
lastMessageStatus: previewMessage.getMessagePropStatus() || null,
|
||||||
lastMessage: lastMessageJSON,
|
timestamp,
|
||||||
lastMessageStatus: lastMessageStatusModel,
|
lastMessageDeletedForEveryone: previewMessage.deletedForEveryone,
|
||||||
lastMessageNotificationText: lastMessageModel
|
|
||||||
? lastMessageModel.getNotificationText()
|
|
||||||
: null,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.set(lastMessageUpdate);
|
|
||||||
window.Signal.Data.updateConversation(this.attributes);
|
window.Signal.Data.updateConversation(this.attributes);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
/* global crypto, window */
|
/* global crypto, window */
|
||||||
|
|
||||||
const { isFunction, isNumber } = require('lodash');
|
const { isFunction, isNumber } = require('lodash');
|
||||||
const { createLastMessageUpdate } = require('../../../ts/types/Conversation');
|
|
||||||
const {
|
const {
|
||||||
arrayBufferToBase64,
|
arrayBufferToBase64,
|
||||||
base64ToArrayBuffer,
|
base64ToArrayBuffer,
|
||||||
|
@ -161,7 +160,7 @@ module.exports = {
|
||||||
arrayBufferToBase64,
|
arrayBufferToBase64,
|
||||||
base64ToArrayBuffer,
|
base64ToArrayBuffer,
|
||||||
computeHash,
|
computeHash,
|
||||||
createLastMessageUpdate,
|
|
||||||
deleteExternalFiles,
|
deleteExternalFiles,
|
||||||
maybeUpdateAvatar,
|
maybeUpdateAvatar,
|
||||||
maybeUpdateProfileAvatar,
|
maybeUpdateProfileAvatar,
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
(function() {
|
(function() {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const FIVE_MINUTES = 1000 * 60 * 5;
|
||||||
|
|
||||||
window.Whisper = window.Whisper || {};
|
window.Whisper = window.Whisper || {};
|
||||||
const { Message, MIME, VisualAttachment } = window.Signal.Types;
|
const { Message, MIME, VisualAttachment } = window.Signal.Types;
|
||||||
const {
|
const {
|
||||||
|
@ -304,9 +306,12 @@
|
||||||
);
|
);
|
||||||
this.model.throttledGetProfiles =
|
this.model.throttledGetProfiles =
|
||||||
this.model.throttledGetProfiles ||
|
this.model.throttledGetProfiles ||
|
||||||
|
_.throttle(this.model.getProfiles.bind(this.model), FIVE_MINUTES);
|
||||||
|
this.model.throttledUpdateSharedGroups =
|
||||||
|
this.model.throttledUpdateSharedGroups ||
|
||||||
_.throttle(
|
_.throttle(
|
||||||
this.model.getProfiles.bind(this.model),
|
this.model.updateSharedGroups.bind(this.model),
|
||||||
1000 * 60 * 5 // five minutes
|
FIVE_MINUTES
|
||||||
);
|
);
|
||||||
this.debouncedMaybeGrabLinkPreview = _.debounce(
|
this.debouncedMaybeGrabLinkPreview = _.debounce(
|
||||||
this.maybeGrabLinkPreview.bind(this),
|
this.maybeGrabLinkPreview.bind(this),
|
||||||
|
@ -720,6 +725,7 @@
|
||||||
showVisualAttachment,
|
showVisualAttachment,
|
||||||
showExpiredIncomingTapToViewToast,
|
showExpiredIncomingTapToViewToast,
|
||||||
showExpiredOutgoingTapToViewToast,
|
showExpiredOutgoingTapToViewToast,
|
||||||
|
updateSharedGroups: this.model.throttledUpdateSharedGroups,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -3750,6 +3750,17 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.module-conversation-list-item__message-request {
|
||||||
|
@include font-body-2-bold;
|
||||||
|
|
||||||
|
@include light-theme {
|
||||||
|
color: $color-gray-60;
|
||||||
|
}
|
||||||
|
@include dark-theme {
|
||||||
|
color: $color-gray-25;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.module-conversation-list-item__message__text {
|
.module-conversation-list-item__message__text {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
flex-shrink: 1;
|
flex-shrink: 1;
|
||||||
|
|
|
@ -636,8 +636,6 @@ export class ConversationController {
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
this._conversations.map(async conversation => {
|
this._conversations.map(async conversation => {
|
||||||
conversation.generateProps();
|
|
||||||
|
|
||||||
if (!conversation.get('lastMessage')) {
|
if (!conversation.get('lastMessage')) {
|
||||||
await conversation.updateLastMessage();
|
await conversation.updateLastMessage();
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ export function onChange(key: ConfigKeyType, fn: ConfigListenerType) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const refreshRemoteConfig = async () => {
|
export const refreshRemoteConfig = async () => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const server = getServer();
|
const server = getServer();
|
||||||
const newConfig = await server.getConfig();
|
const newConfig = await server.getConfig();
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
<util.LeftPaneContext theme={util.theme}>
|
<util.LeftPaneContext theme={util.theme}>
|
||||||
<ConversationListItem
|
<ConversationListItem
|
||||||
id="conversationId1"
|
id="conversationId1"
|
||||||
|
isAccepted
|
||||||
|
title="Someone 🔥 Somewhere"
|
||||||
name="Someone 🔥 Somewhere"
|
name="Someone 🔥 Somewhere"
|
||||||
type={'direct'}
|
type={'direct'}
|
||||||
phoneNumber="(202) 555-0011"
|
phoneNumber="(202) 555-0011"
|
||||||
|
@ -25,8 +27,10 @@
|
||||||
<util.LeftPaneContext theme={util.theme}>
|
<util.LeftPaneContext theme={util.theme}>
|
||||||
<ConversationListItem
|
<ConversationListItem
|
||||||
id="conversationId1"
|
id="conversationId1"
|
||||||
|
isAccepted
|
||||||
phoneNumber="(202) 555-0011"
|
phoneNumber="(202) 555-0011"
|
||||||
type={'direct'}
|
type={'direct'}
|
||||||
|
title="Mr. Fire🔥"
|
||||||
name="Mr. Fire🔥"
|
name="Mr. Fire🔥"
|
||||||
color="green"
|
color="green"
|
||||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||||
|
@ -46,9 +50,11 @@
|
||||||
<util.LeftPaneContext theme={util.theme}>
|
<util.LeftPaneContext theme={util.theme}>
|
||||||
<ConversationListItem
|
<ConversationListItem
|
||||||
id="conversationId1"
|
id="conversationId1"
|
||||||
|
isAccepted
|
||||||
isMe={true}
|
isMe={true}
|
||||||
phoneNumber="(202) 555-0011"
|
phoneNumber="(202) 555-0011"
|
||||||
type={'direct'}
|
type={'direct'}
|
||||||
|
title="Mr. Fire🔥"
|
||||||
name="Mr. Fire🔥"
|
name="Mr. Fire🔥"
|
||||||
color="green"
|
color="green"
|
||||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||||
|
@ -69,8 +75,10 @@
|
||||||
<div>
|
<div>
|
||||||
<ConversationListItem
|
<ConversationListItem
|
||||||
id="conversationId1"
|
id="conversationId1"
|
||||||
|
isAccepted
|
||||||
phoneNumber="(202) 555-0011"
|
phoneNumber="(202) 555-0011"
|
||||||
type={'direct'}
|
type={'direct'}
|
||||||
|
title="Mr. Fire🔥"
|
||||||
name="Mr. Fire🔥"
|
name="Mr. Fire🔥"
|
||||||
color="green"
|
color="green"
|
||||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||||
|
@ -83,8 +91,10 @@
|
||||||
/>
|
/>
|
||||||
<ConversationListItem
|
<ConversationListItem
|
||||||
id="conversationId2"
|
id="conversationId2"
|
||||||
|
isAccepted
|
||||||
phoneNumber="(202) 555-0011"
|
phoneNumber="(202) 555-0011"
|
||||||
type={'direct'}
|
type={'direct'}
|
||||||
|
title="Mr. Fire🔥"
|
||||||
name="Mr. Fire🔥"
|
name="Mr. Fire🔥"
|
||||||
color="green"
|
color="green"
|
||||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||||
|
@ -97,8 +107,10 @@
|
||||||
/>
|
/>
|
||||||
<ConversationListItem
|
<ConversationListItem
|
||||||
id="conversationId3"
|
id="conversationId3"
|
||||||
|
isAccepted
|
||||||
phoneNumber="(202) 555-0011"
|
phoneNumber="(202) 555-0011"
|
||||||
type={'direct'}
|
type={'direct'}
|
||||||
|
title="Mr. Fire🔥"
|
||||||
name="Mr. Fire🔥"
|
name="Mr. Fire🔥"
|
||||||
color="green"
|
color="green"
|
||||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||||
|
@ -111,8 +123,10 @@
|
||||||
/>
|
/>
|
||||||
<ConversationListItem
|
<ConversationListItem
|
||||||
id="conversationId4"
|
id="conversationId4"
|
||||||
|
isAccepted
|
||||||
phoneNumber="(202) 555-0011"
|
phoneNumber="(202) 555-0011"
|
||||||
type={'direct'}
|
type={'direct'}
|
||||||
|
title="Mr. Fire🔥"
|
||||||
name="Mr. Fire🔥"
|
name="Mr. Fire🔥"
|
||||||
color="green"
|
color="green"
|
||||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||||
|
@ -125,8 +139,10 @@
|
||||||
/>
|
/>
|
||||||
<ConversationListItem
|
<ConversationListItem
|
||||||
id="conversationId5"
|
id="conversationId5"
|
||||||
|
isAccepted
|
||||||
phoneNumber="(202) 555-0011"
|
phoneNumber="(202) 555-0011"
|
||||||
type={'direct'}
|
type={'direct'}
|
||||||
|
title="Mr. Fire🔥"
|
||||||
name="Mr. Fire🔥"
|
name="Mr. Fire🔥"
|
||||||
color="green"
|
color="green"
|
||||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||||
|
@ -148,7 +164,9 @@
|
||||||
<div>
|
<div>
|
||||||
<ConversationListItem
|
<ConversationListItem
|
||||||
id="conversationId1"
|
id="conversationId1"
|
||||||
|
isAccepted
|
||||||
phoneNumber="(202) 555-0011"
|
phoneNumber="(202) 555-0011"
|
||||||
|
title="(202) 555-0011"
|
||||||
type={'direct'}
|
type={'direct'}
|
||||||
unreadCount={4}
|
unreadCount={4}
|
||||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||||
|
@ -162,7 +180,51 @@
|
||||||
<div>
|
<div>
|
||||||
<ConversationListItem
|
<ConversationListItem
|
||||||
id="conversationId2"
|
id="conversationId2"
|
||||||
|
isAccepted
|
||||||
phoneNumber="(202) 555-0011"
|
phoneNumber="(202) 555-0011"
|
||||||
|
title="(202) 555-0011"
|
||||||
|
type={'direct'}
|
||||||
|
unreadCount={4}
|
||||||
|
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||||
|
typingContact={{
|
||||||
|
name: 'Someone Here',
|
||||||
|
}}
|
||||||
|
lastMessage={{
|
||||||
|
status: 'read',
|
||||||
|
}}
|
||||||
|
onClick={result => console.log('onClick', result)}
|
||||||
|
i18n={util.i18n}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</util.LeftPaneContext>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Message Request
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
<util.LeftPaneContext theme={util.theme}>
|
||||||
|
<div>
|
||||||
|
<ConversationListItem
|
||||||
|
id="conversationId1"
|
||||||
|
isAccepted={false}
|
||||||
|
phoneNumber="(202) 555-0011"
|
||||||
|
title="(202) 555-0011"
|
||||||
|
type={'direct'}
|
||||||
|
unreadCount={4}
|
||||||
|
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||||
|
typingContact={{
|
||||||
|
name: 'Someone Here',
|
||||||
|
}}
|
||||||
|
onClick={result => console.log('onClick', result)}
|
||||||
|
i18n={util.i18n}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<ConversationListItem
|
||||||
|
id="conversationId2"
|
||||||
|
isAccepted={false}
|
||||||
|
phoneNumber="(202) 555-0011"
|
||||||
|
title="(202) 555-0011"
|
||||||
type={'direct'}
|
type={'direct'}
|
||||||
unreadCount={4}
|
unreadCount={4}
|
||||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||||
|
@ -188,7 +250,9 @@
|
||||||
<div>
|
<div>
|
||||||
<ConversationListItem
|
<ConversationListItem
|
||||||
id="conversationId1"
|
id="conversationId1"
|
||||||
|
isAccepted
|
||||||
phoneNumber="(202) 555-0011"
|
phoneNumber="(202) 555-0011"
|
||||||
|
title="(202) 555-0011"
|
||||||
type={'direct'}
|
type={'direct'}
|
||||||
unreadCount={4}
|
unreadCount={4}
|
||||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||||
|
@ -200,7 +264,9 @@
|
||||||
/>
|
/>
|
||||||
<ConversationListItem
|
<ConversationListItem
|
||||||
id="conversationId2"
|
id="conversationId2"
|
||||||
|
isAccepted
|
||||||
phoneNumber="(202) 555-0011"
|
phoneNumber="(202) 555-0011"
|
||||||
|
title="(202) 555-0011"
|
||||||
type={'direct'}
|
type={'direct'}
|
||||||
unreadCount={10}
|
unreadCount={10}
|
||||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||||
|
@ -212,7 +278,9 @@
|
||||||
/>
|
/>
|
||||||
<ConversationListItem
|
<ConversationListItem
|
||||||
id="conversationId3"
|
id="conversationId3"
|
||||||
|
isAccepted
|
||||||
phoneNumber="(202) 555-0011"
|
phoneNumber="(202) 555-0011"
|
||||||
|
title="(202) 555-0011"
|
||||||
type={'direct'}
|
type={'direct'}
|
||||||
unreadCount={250}
|
unreadCount={250}
|
||||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||||
|
@ -232,7 +300,9 @@
|
||||||
<util.LeftPaneContext theme={util.theme}>
|
<util.LeftPaneContext theme={util.theme}>
|
||||||
<ConversationListItem
|
<ConversationListItem
|
||||||
id="conversationId1"
|
id="conversationId1"
|
||||||
|
isAccepted
|
||||||
phoneNumber="(202) 555-0011"
|
phoneNumber="(202) 555-0011"
|
||||||
|
title="(202) 555-0011"
|
||||||
type={'direct'}
|
type={'direct'}
|
||||||
isSelected={true}
|
isSelected={true}
|
||||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||||
|
@ -254,7 +324,9 @@ We don't want Jumbomoji or links.
|
||||||
<div>
|
<div>
|
||||||
<ConversationListItem
|
<ConversationListItem
|
||||||
id="conversationId1"
|
id="conversationId1"
|
||||||
|
isAccepted
|
||||||
phoneNumber="(202) 555-0011"
|
phoneNumber="(202) 555-0011"
|
||||||
|
title="(202) 555-0011"
|
||||||
type={'direct'}
|
type={'direct'}
|
||||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||||
lastMessage={{
|
lastMessage={{
|
||||||
|
@ -265,7 +337,9 @@ We don't want Jumbomoji or links.
|
||||||
/>
|
/>
|
||||||
<ConversationListItem
|
<ConversationListItem
|
||||||
id="conversationId2"
|
id="conversationId2"
|
||||||
|
isAccepted
|
||||||
phoneNumber="(202) 555-0011"
|
phoneNumber="(202) 555-0011"
|
||||||
|
title="(202) 555-0011"
|
||||||
type={'direct'}
|
type={'direct'}
|
||||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||||
lastMessage={{
|
lastMessage={{
|
||||||
|
@ -287,7 +361,9 @@ We only show one line.
|
||||||
<div>
|
<div>
|
||||||
<ConversationListItem
|
<ConversationListItem
|
||||||
id="conversationId1"
|
id="conversationId1"
|
||||||
|
isAccepted
|
||||||
phoneNumber="(202) 555-0011"
|
phoneNumber="(202) 555-0011"
|
||||||
|
title="(202) 555-0011"
|
||||||
type={'direct'}
|
type={'direct'}
|
||||||
name="Long contact name. Esquire. The third. And stuff. And more! And more!"
|
name="Long contact name. Esquire. The third. And stuff. And more! And more!"
|
||||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||||
|
@ -299,7 +375,9 @@ We only show one line.
|
||||||
/>
|
/>
|
||||||
<ConversationListItem
|
<ConversationListItem
|
||||||
id="conversationId2"
|
id="conversationId2"
|
||||||
|
isAccepted
|
||||||
phoneNumber="(202) 555-0011"
|
phoneNumber="(202) 555-0011"
|
||||||
|
title="(202) 555-0011"
|
||||||
type={'direct'}
|
type={'direct'}
|
||||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||||
lastMessage={{
|
lastMessage={{
|
||||||
|
@ -311,7 +389,9 @@ We only show one line.
|
||||||
/>
|
/>
|
||||||
<ConversationListItem
|
<ConversationListItem
|
||||||
id="conversationId3"
|
id="conversationId3"
|
||||||
|
isAccepted
|
||||||
phoneNumber="(202) 555-0011"
|
phoneNumber="(202) 555-0011"
|
||||||
|
title="(202) 555-0011"
|
||||||
type={'direct'}
|
type={'direct'}
|
||||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||||
lastMessage={{
|
lastMessage={{
|
||||||
|
@ -325,7 +405,9 @@ We only show one line.
|
||||||
|
|
||||||
<ConversationListItem
|
<ConversationListItem
|
||||||
id="conversationId4"
|
id="conversationId4"
|
||||||
|
isAccepted
|
||||||
phoneNumber="(202) 555-0011"
|
phoneNumber="(202) 555-0011"
|
||||||
|
title="(202) 555-0011"
|
||||||
type={'direct'}
|
type={'direct'}
|
||||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||||
unreadCount={8}
|
unreadCount={8}
|
||||||
|
@ -338,7 +420,9 @@ We only show one line.
|
||||||
/>
|
/>
|
||||||
<ConversationListItem
|
<ConversationListItem
|
||||||
id="conversationId5"
|
id="conversationId5"
|
||||||
|
isAccepted
|
||||||
phoneNumber="(202) 555-0011"
|
phoneNumber="(202) 555-0011"
|
||||||
|
title="(202) 555-0011"
|
||||||
type={'direct'}
|
type={'direct'}
|
||||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||||
lastMessage={{
|
lastMessage={{
|
||||||
|
@ -350,7 +434,9 @@ We only show one line.
|
||||||
/>
|
/>
|
||||||
<ConversationListItem
|
<ConversationListItem
|
||||||
id="conversationId6"
|
id="conversationId6"
|
||||||
|
isAccepted
|
||||||
phoneNumber="(202) 555-0011"
|
phoneNumber="(202) 555-0011"
|
||||||
|
title="(202) 555-0011"
|
||||||
type={'direct'}
|
type={'direct'}
|
||||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||||
lastMessage={{
|
lastMessage={{
|
||||||
|
@ -374,7 +460,9 @@ On platforms that show scrollbars all the time, this is true all the time.
|
||||||
<div style={{ width: '280px' }}>
|
<div style={{ width: '280px' }}>
|
||||||
<ConversationListItem
|
<ConversationListItem
|
||||||
id="conversationId1"
|
id="conversationId1"
|
||||||
|
isAccepted
|
||||||
phoneNumber="(202) 555-0011"
|
phoneNumber="(202) 555-0011"
|
||||||
|
title="(202) 555-0011"
|
||||||
type={'direct'}
|
type={'direct'}
|
||||||
name="Long contact name. Esquire. The third. And stuff. And more! And more!"
|
name="Long contact name. Esquire. The third. And stuff. And more! And more!"
|
||||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||||
|
@ -386,7 +474,9 @@ On platforms that show scrollbars all the time, this is true all the time.
|
||||||
/>
|
/>
|
||||||
<ConversationListItem
|
<ConversationListItem
|
||||||
id="conversationId2"
|
id="conversationId2"
|
||||||
|
isAccepted
|
||||||
phoneNumber="(202) 555-0011"
|
phoneNumber="(202) 555-0011"
|
||||||
|
title="(202) 555-0011"
|
||||||
type={'direct'}
|
type={'direct'}
|
||||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||||
lastMessage={{
|
lastMessage={{
|
||||||
|
@ -407,7 +497,9 @@ On platforms that show scrollbars all the time, this is true all the time.
|
||||||
<div>
|
<div>
|
||||||
<ConversationListItem
|
<ConversationListItem
|
||||||
id="conversationId1"
|
id="conversationId1"
|
||||||
|
isAccepted
|
||||||
phoneNumber="(202) 555-0011"
|
phoneNumber="(202) 555-0011"
|
||||||
|
title="(202) 555-0011"
|
||||||
type={'direct'}
|
type={'direct'}
|
||||||
lastUpdated={Date.now() - 5 * 60 * 60 * 1000}
|
lastUpdated={Date.now() - 5 * 60 * 60 * 1000}
|
||||||
lastMessage={{
|
lastMessage={{
|
||||||
|
@ -418,7 +510,9 @@ On platforms that show scrollbars all the time, this is true all the time.
|
||||||
/>
|
/>
|
||||||
<ConversationListItem
|
<ConversationListItem
|
||||||
id="conversationId2"
|
id="conversationId2"
|
||||||
|
isAccepted
|
||||||
phoneNumber="(202) 555-0011"
|
phoneNumber="(202) 555-0011"
|
||||||
|
title="(202) 555-0011"
|
||||||
type={'direct'}
|
type={'direct'}
|
||||||
lastUpdated={Date.now() - 24 * 60 * 60 * 1000}
|
lastUpdated={Date.now() - 24 * 60 * 60 * 1000}
|
||||||
lastMessage={{
|
lastMessage={{
|
||||||
|
@ -429,7 +523,9 @@ On platforms that show scrollbars all the time, this is true all the time.
|
||||||
/>
|
/>
|
||||||
<ConversationListItem
|
<ConversationListItem
|
||||||
id="conversationId3"
|
id="conversationId3"
|
||||||
|
isAccepted
|
||||||
phoneNumber="(202) 555-0011"
|
phoneNumber="(202) 555-0011"
|
||||||
|
title="(202) 555-0011"
|
||||||
type={'direct'}
|
type={'direct'}
|
||||||
lastUpdated={Date.now() - 7 * 24 * 60 * 60 * 1000}
|
lastUpdated={Date.now() - 7 * 24 * 60 * 60 * 1000}
|
||||||
lastMessage={{
|
lastMessage={{
|
||||||
|
@ -440,7 +536,9 @@ On platforms that show scrollbars all the time, this is true all the time.
|
||||||
/>
|
/>
|
||||||
<ConversationListItem
|
<ConversationListItem
|
||||||
id="conversationId4"
|
id="conversationId4"
|
||||||
|
isAccepted
|
||||||
phoneNumber="(202) 555-0011"
|
phoneNumber="(202) 555-0011"
|
||||||
|
title="(202) 555-0011"
|
||||||
type={'direct'}
|
type={'direct'}
|
||||||
lastUpdated={Date.now() - 365 * 24 * 60 * 60 * 1000}
|
lastUpdated={Date.now() - 365 * 24 * 60 * 60 * 1000}
|
||||||
lastMessage={{
|
lastMessage={{
|
||||||
|
@ -460,7 +558,9 @@ On platforms that show scrollbars all the time, this is true all the time.
|
||||||
<div>
|
<div>
|
||||||
<ConversationListItem
|
<ConversationListItem
|
||||||
id="conversationId1"
|
id="conversationId1"
|
||||||
|
isAccepted
|
||||||
name="John"
|
name="John"
|
||||||
|
title="John"
|
||||||
type={'direct'}
|
type={'direct'}
|
||||||
lastUpdated={null}
|
lastUpdated={null}
|
||||||
lastMessage={{
|
lastMessage={{
|
||||||
|
@ -471,7 +571,9 @@ On platforms that show scrollbars all the time, this is true all the time.
|
||||||
/>
|
/>
|
||||||
<ConversationListItem
|
<ConversationListItem
|
||||||
id="conversationId2"
|
id="conversationId2"
|
||||||
|
isAccepted
|
||||||
name="Missing message"
|
name="Missing message"
|
||||||
|
title="Missing message"
|
||||||
type={'direct'}
|
type={'direct'}
|
||||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||||
lastMessage={{
|
lastMessage={{
|
||||||
|
@ -482,7 +584,9 @@ On platforms that show scrollbars all the time, this is true all the time.
|
||||||
/>
|
/>
|
||||||
<ConversationListItem
|
<ConversationListItem
|
||||||
id="conversationId3"
|
id="conversationId3"
|
||||||
|
isAccepted
|
||||||
phoneNumber="(202) 555-0011"
|
phoneNumber="(202) 555-0011"
|
||||||
|
title="(202) 555-0011"
|
||||||
type={'direct'}
|
type={'direct'}
|
||||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||||
lastMessage={{
|
lastMessage={{
|
||||||
|
|
|
@ -26,6 +26,7 @@ export type PropsData = {
|
||||||
unreadCount?: number;
|
unreadCount?: number;
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
|
|
||||||
|
isAccepted?: boolean;
|
||||||
draftPreview?: string;
|
draftPreview?: string;
|
||||||
shouldShowDraft?: boolean;
|
shouldShowDraft?: boolean;
|
||||||
|
|
||||||
|
@ -152,6 +153,7 @@ export class ConversationListItem extends React.PureComponent<Props> {
|
||||||
const {
|
const {
|
||||||
draftPreview,
|
draftPreview,
|
||||||
i18n,
|
i18n,
|
||||||
|
isAccepted,
|
||||||
lastMessage,
|
lastMessage,
|
||||||
shouldShowDraft,
|
shouldShowDraft,
|
||||||
typingContact,
|
typingContact,
|
||||||
|
@ -187,7 +189,11 @@ export class ConversationListItem extends React.PureComponent<Props> {
|
||||||
: null
|
: null
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{typingContact ? (
|
{!isAccepted ? (
|
||||||
|
<span className="module-conversation-list-item__message-request">
|
||||||
|
{i18n('ConversationListItem--message-request')}
|
||||||
|
</span>
|
||||||
|
) : typingContact ? (
|
||||||
<TypingAnimation i18n={i18n} />
|
<TypingAnimation i18n={i18n} />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -13,6 +13,7 @@ export type Props = {
|
||||||
membersCount?: number;
|
membersCount?: number;
|
||||||
phoneNumber?: string;
|
phoneNumber?: string;
|
||||||
onHeightChange?: () => unknown;
|
onHeightChange?: () => unknown;
|
||||||
|
updateSharedGroups?: () => unknown;
|
||||||
} & Omit<AvatarProps, 'onClick' | 'size' | 'noteToSelf'>;
|
} & Omit<AvatarProps, 'onClick' | 'size' | 'noteToSelf'>;
|
||||||
|
|
||||||
const renderMembershipRow = ({
|
const renderMembershipRow = ({
|
||||||
|
@ -113,6 +114,7 @@ export const ConversationHero = ({
|
||||||
profileName,
|
profileName,
|
||||||
title,
|
title,
|
||||||
onHeightChange,
|
onHeightChange,
|
||||||
|
updateSharedGroups,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const firstRenderRef = React.useRef(true);
|
const firstRenderRef = React.useRef(true);
|
||||||
|
|
||||||
|
@ -121,6 +123,11 @@ export const ConversationHero = ({
|
||||||
// component may have changed. The cleanup function notifies listeners of
|
// component may have changed. The cleanup function notifies listeners of
|
||||||
// any potential height changes.
|
// any potential height changes.
|
||||||
return () => {
|
return () => {
|
||||||
|
// Kick off the expensive hydration of the current sharedGroupNames
|
||||||
|
if (updateSharedGroups) {
|
||||||
|
updateSharedGroups();
|
||||||
|
}
|
||||||
|
|
||||||
if (onHeightChange && !firstRenderRef.current) {
|
if (onHeightChange && !firstRenderRef.current) {
|
||||||
onHeightChange();
|
onHeightChange();
|
||||||
} else {
|
} else {
|
||||||
|
@ -135,7 +142,7 @@ export const ConversationHero = ({
|
||||||
`mc-${membersCount}`,
|
`mc-${membersCount}`,
|
||||||
`n-${name}`,
|
`n-${name}`,
|
||||||
`pn-${profileName}`,
|
`pn-${profileName}`,
|
||||||
...sharedGroupNames.map(g => `g-${g}`),
|
sharedGroupNames.map(g => `g-${g}`).join(' '),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const phoneNumberOnly = Boolean(
|
const phoneNumberOnly = Boolean(
|
||||||
|
|
|
@ -50,7 +50,11 @@ type PropsHousekeepingType = {
|
||||||
actions: Object
|
actions: Object
|
||||||
) => JSX.Element;
|
) => JSX.Element;
|
||||||
renderLastSeenIndicator: (id: string) => JSX.Element;
|
renderLastSeenIndicator: (id: string) => JSX.Element;
|
||||||
renderHeroRow: (id: string, resizeHeroRow: () => unknown) => JSX.Element;
|
renderHeroRow: (
|
||||||
|
id: string,
|
||||||
|
resizeHeroRow: () => unknown,
|
||||||
|
updateSharedGroups: () => unknown
|
||||||
|
) => JSX.Element;
|
||||||
renderLoadingRow: (id: string) => JSX.Element;
|
renderLoadingRow: (id: string) => JSX.Element;
|
||||||
renderTypingBubble: (id: string) => JSX.Element;
|
renderTypingBubble: (id: string) => JSX.Element;
|
||||||
};
|
};
|
||||||
|
@ -70,6 +74,7 @@ type PropsActionsType = {
|
||||||
markMessageRead: (messageId: string) => unknown;
|
markMessageRead: (messageId: string) => unknown;
|
||||||
selectMessage: (messageId: string, conversationId: string) => unknown;
|
selectMessage: (messageId: string, conversationId: string) => unknown;
|
||||||
clearSelectedMessage: () => unknown;
|
clearSelectedMessage: () => unknown;
|
||||||
|
updateSharedGroups: () => unknown;
|
||||||
} & MessageActionsType &
|
} & MessageActionsType &
|
||||||
SafetyNumberActionsType;
|
SafetyNumberActionsType;
|
||||||
|
|
||||||
|
@ -510,6 +515,7 @@ export class Timeline extends React.PureComponent<Props, State> {
|
||||||
renderLoadingRow,
|
renderLoadingRow,
|
||||||
renderLastSeenIndicator,
|
renderLastSeenIndicator,
|
||||||
renderTypingBubble,
|
renderTypingBubble,
|
||||||
|
updateSharedGroups,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const styleWithWidth = {
|
const styleWithWidth = {
|
||||||
|
@ -524,7 +530,7 @@ export class Timeline extends React.PureComponent<Props, State> {
|
||||||
if (haveOldest && row === 0) {
|
if (haveOldest && row === 0) {
|
||||||
rowContents = (
|
rowContents = (
|
||||||
<div data-row={row} style={styleWithWidth} role="row">
|
<div data-row={row} style={styleWithWidth} role="row">
|
||||||
{renderHeroRow(id, this.resizeHeroRow)}
|
{renderHeroRow(id, this.resizeHeroRow, updateSharedGroups)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (!haveOldest && row === 0) {
|
} else if (!haveOldest && row === 0) {
|
||||||
|
|
1
ts/model-types.d.ts
vendored
1
ts/model-types.d.ts
vendored
|
@ -85,7 +85,6 @@ declare class ConversationModelType extends Backbone.Model<
|
||||||
cleanup(): Promise<void>;
|
cleanup(): Promise<void>;
|
||||||
disableProfileSharing(): void;
|
disableProfileSharing(): void;
|
||||||
dropProfileKey(): Promise<void>;
|
dropProfileKey(): Promise<void>;
|
||||||
generateProps(): void;
|
|
||||||
getAccepted(): boolean;
|
getAccepted(): boolean;
|
||||||
getAvatarPath(): string | undefined;
|
getAvatarPath(): string | undefined;
|
||||||
getColor(): ColorType | undefined;
|
getColor(): ColorType | undefined;
|
||||||
|
|
|
@ -156,6 +156,8 @@ const dataInterface: ClientInterface = {
|
||||||
getTapToViewMessagesNeedingErase,
|
getTapToViewMessagesNeedingErase,
|
||||||
getOlderMessagesByConversation,
|
getOlderMessagesByConversation,
|
||||||
getNewerMessagesByConversation,
|
getNewerMessagesByConversation,
|
||||||
|
getLastConversationActivity,
|
||||||
|
getLastConversationPreview,
|
||||||
getMessageMetricsForConversation,
|
getMessageMetricsForConversation,
|
||||||
migrateConversationMessages,
|
migrateConversationMessages,
|
||||||
|
|
||||||
|
@ -1022,6 +1024,32 @@ async function getNewerMessagesByConversation(
|
||||||
|
|
||||||
return new MessageCollection(handleMessageJSON(messages));
|
return new MessageCollection(handleMessageJSON(messages));
|
||||||
}
|
}
|
||||||
|
async function getLastConversationActivity(
|
||||||
|
conversationId: string,
|
||||||
|
options: {
|
||||||
|
Message: typeof MessageModelType;
|
||||||
|
}
|
||||||
|
): Promise<MessageModelType | undefined> {
|
||||||
|
const { Message } = options;
|
||||||
|
const result = await channels.getLastConversationActivity(conversationId);
|
||||||
|
if (result) {
|
||||||
|
return new Message(result);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
async function getLastConversationPreview(
|
||||||
|
conversationId: string,
|
||||||
|
options: {
|
||||||
|
Message: typeof MessageModelType;
|
||||||
|
}
|
||||||
|
): Promise<MessageModelType | undefined> {
|
||||||
|
const { Message } = options;
|
||||||
|
const result = await channels.getLastConversationPreview(conversationId);
|
||||||
|
if (result) {
|
||||||
|
return new Message(result);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
async function getMessageMetricsForConversation(conversationId: string) {
|
async function getMessageMetricsForConversation(conversationId: string) {
|
||||||
const result = await channels.getMessageMetricsForConversation(
|
const result = await channels.getMessageMetricsForConversation(
|
||||||
conversationId
|
conversationId
|
||||||
|
|
|
@ -210,6 +210,12 @@ export type ServerInterface = DataInterface & {
|
||||||
conversationId: string,
|
conversationId: string,
|
||||||
options?: { limit?: number; receivedAt?: number }
|
options?: { limit?: number; receivedAt?: number }
|
||||||
) => Promise<Array<MessageTypeUnhydrated>>;
|
) => Promise<Array<MessageTypeUnhydrated>>;
|
||||||
|
getLastConversationActivity: (
|
||||||
|
conversationId: string
|
||||||
|
) => Promise<MessageType | undefined>;
|
||||||
|
getLastConversationPreview: (
|
||||||
|
conversationId: string
|
||||||
|
) => Promise<MessageType | undefined>;
|
||||||
getNextExpiringMessage: () => Promise<MessageType>;
|
getNextExpiringMessage: () => Promise<MessageType>;
|
||||||
getNextTapToViewMessageToAgeOut: () => Promise<MessageType>;
|
getNextTapToViewMessageToAgeOut: () => Promise<MessageType>;
|
||||||
getOutgoingWithoutExpiresAt: () => Promise<Array<MessageType>>;
|
getOutgoingWithoutExpiresAt: () => Promise<Array<MessageType>>;
|
||||||
|
@ -308,6 +314,18 @@ export type ClientInterface = DataInterface & {
|
||||||
MessageCollection: typeof MessageModelCollectionType;
|
MessageCollection: typeof MessageModelCollectionType;
|
||||||
}
|
}
|
||||||
) => Promise<MessageModelCollectionType>;
|
) => Promise<MessageModelCollectionType>;
|
||||||
|
getLastConversationActivity: (
|
||||||
|
conversationId: string,
|
||||||
|
options: {
|
||||||
|
Message: typeof MessageModelType;
|
||||||
|
}
|
||||||
|
) => Promise<MessageModelType | undefined>;
|
||||||
|
getLastConversationPreview: (
|
||||||
|
conversationId: string,
|
||||||
|
options: {
|
||||||
|
Message: typeof MessageModelType;
|
||||||
|
}
|
||||||
|
) => Promise<MessageModelType | undefined>;
|
||||||
getNextExpiringMessage: ({
|
getNextExpiringMessage: ({
|
||||||
Message,
|
Message,
|
||||||
}: {
|
}: {
|
||||||
|
|
|
@ -132,6 +132,8 @@ const dataInterface: ServerInterface = {
|
||||||
getOlderMessagesByConversation,
|
getOlderMessagesByConversation,
|
||||||
getNewerMessagesByConversation,
|
getNewerMessagesByConversation,
|
||||||
getMessageMetricsForConversation,
|
getMessageMetricsForConversation,
|
||||||
|
getLastConversationActivity,
|
||||||
|
getLastConversationPreview,
|
||||||
migrateConversationMessages,
|
migrateConversationMessages,
|
||||||
|
|
||||||
getUnprocessedCount,
|
getUnprocessedCount,
|
||||||
|
@ -2749,6 +2751,50 @@ async function getNewestMessageForConversation(conversationId: string) {
|
||||||
|
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getLastConversationActivity(
|
||||||
|
conversationId: string
|
||||||
|
): Promise<MessageType | null> {
|
||||||
|
const db = getInstance();
|
||||||
|
const row = await db.get(
|
||||||
|
`SELECT * FROM messages WHERE
|
||||||
|
conversationId = $conversationId AND
|
||||||
|
type NOT IN ('profile-change', 'verified-change', 'message-history-unsynced') AND
|
||||||
|
json_extract(json, '$.expirationTimerUpdate.fromSync') != true
|
||||||
|
ORDER BY received_at DESC
|
||||||
|
LIMIT 1;`,
|
||||||
|
{
|
||||||
|
$conversationId: conversationId,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!row) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonToObject(row.json);
|
||||||
|
}
|
||||||
|
async function getLastConversationPreview(
|
||||||
|
conversationId: string
|
||||||
|
): Promise<MessageType | null> {
|
||||||
|
const db = getInstance();
|
||||||
|
const row = await db.get(
|
||||||
|
`SELECT * FROM messages WHERE
|
||||||
|
conversationId = $conversationId AND
|
||||||
|
type NOT IN ('profile-change', 'verified-change', 'message-history-unsynced')
|
||||||
|
ORDER BY received_at DESC
|
||||||
|
LIMIT 1;`,
|
||||||
|
{
|
||||||
|
$conversationId: conversationId,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!row) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonToObject(row.json);
|
||||||
|
}
|
||||||
async function getOldestUnreadMessageForConversation(conversationId: string) {
|
async function getOldestUnreadMessageForConversation(conversationId: string) {
|
||||||
const db = getInstance();
|
const db = getInstance();
|
||||||
const row = await db.get(
|
const row = await db.get(
|
||||||
|
|
|
@ -68,8 +68,18 @@ function renderEmojiPicker({
|
||||||
function renderLastSeenIndicator(id: string): JSX.Element {
|
function renderLastSeenIndicator(id: string): JSX.Element {
|
||||||
return <FilteredSmartLastSeenIndicator id={id} />;
|
return <FilteredSmartLastSeenIndicator id={id} />;
|
||||||
}
|
}
|
||||||
function renderHeroRow(id: string, onHeightChange: () => unknown): JSX.Element {
|
function renderHeroRow(
|
||||||
return <FilteredSmartHeroRow id={id} onHeightChange={onHeightChange} />;
|
id: string,
|
||||||
|
onHeightChange: () => unknown,
|
||||||
|
updateSharedGroups: () => unknown
|
||||||
|
): JSX.Element {
|
||||||
|
return (
|
||||||
|
<FilteredSmartHeroRow
|
||||||
|
id={id}
|
||||||
|
onHeightChange={onHeightChange}
|
||||||
|
updateSharedGroups={updateSharedGroups}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
function renderLoadingRow(id: string): JSX.Element {
|
function renderLoadingRow(id: string): JSX.Element {
|
||||||
return <FilteredSmartTimelineLoadingRow id={id} />;
|
return <FilteredSmartTimelineLoadingRow id={id} />;
|
||||||
|
|
|
@ -1,152 +0,0 @@
|
||||||
import { assert } from 'chai';
|
|
||||||
|
|
||||||
import * as Conversation from '../../types/Conversation';
|
|
||||||
import {
|
|
||||||
IncomingMessage,
|
|
||||||
MessageHistoryUnsyncedMessage,
|
|
||||||
OutgoingMessage,
|
|
||||||
ProfileChangeNotificationMessage,
|
|
||||||
VerifiedChangeMessage,
|
|
||||||
} from '../../types/Message';
|
|
||||||
|
|
||||||
describe('Conversation', () => {
|
|
||||||
describe('createLastMessageUpdate', () => {
|
|
||||||
it('should reset last message if conversation has no messages', () => {
|
|
||||||
const input = {};
|
|
||||||
const expected = {
|
|
||||||
lastMessage: '',
|
|
||||||
lastMessageStatus: null,
|
|
||||||
timestamp: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
const actual = Conversation.createLastMessageUpdate(input);
|
|
||||||
assert.deepEqual(actual, expected);
|
|
||||||
});
|
|
||||||
|
|
||||||
context('for regular message', () => {
|
|
||||||
it('should update last message text and timestamp', () => {
|
|
||||||
const input = {
|
|
||||||
currentTimestamp: 555,
|
|
||||||
lastMessageStatus: 'read',
|
|
||||||
lastMessage: {
|
|
||||||
type: 'outgoing',
|
|
||||||
conversationId: 'foo',
|
|
||||||
sent_at: 666,
|
|
||||||
timestamp: 666,
|
|
||||||
} as OutgoingMessage,
|
|
||||||
lastMessageNotificationText: 'New outgoing message',
|
|
||||||
};
|
|
||||||
const expected = {
|
|
||||||
lastMessage: 'New outgoing message',
|
|
||||||
lastMessageStatus: 'read',
|
|
||||||
lastMessageDeletedForEveryone: undefined,
|
|
||||||
timestamp: 666,
|
|
||||||
};
|
|
||||||
|
|
||||||
const actual = Conversation.createLastMessageUpdate(input);
|
|
||||||
assert.deepEqual(actual, expected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
context('for message history unsynced message', () => {
|
|
||||||
it('should skip update', () => {
|
|
||||||
const input = {
|
|
||||||
currentTimestamp: 555,
|
|
||||||
lastMessage: {
|
|
||||||
type: 'message-history-unsynced',
|
|
||||||
conversationId: 'foo',
|
|
||||||
sent_at: 666,
|
|
||||||
timestamp: 666,
|
|
||||||
} as MessageHistoryUnsyncedMessage,
|
|
||||||
lastMessageNotificationText: 'xoxoxoxo',
|
|
||||||
};
|
|
||||||
const expected = {
|
|
||||||
lastMessage: 'xoxoxoxo',
|
|
||||||
lastMessageStatus: null,
|
|
||||||
lastMessageDeletedForEveryone: undefined,
|
|
||||||
timestamp: 555,
|
|
||||||
};
|
|
||||||
|
|
||||||
const actual = Conversation.createLastMessageUpdate(input);
|
|
||||||
assert.deepEqual(actual, expected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
context('for verified change message', () => {
|
|
||||||
it('should skip update', () => {
|
|
||||||
const input = {
|
|
||||||
currentTimestamp: 555,
|
|
||||||
lastMessage: {
|
|
||||||
type: 'verified-change',
|
|
||||||
conversationId: 'foo',
|
|
||||||
sent_at: 666,
|
|
||||||
timestamp: 666,
|
|
||||||
} as VerifiedChangeMessage,
|
|
||||||
lastMessageNotificationText: 'Verified Changed',
|
|
||||||
};
|
|
||||||
const expected = {
|
|
||||||
lastMessage: '',
|
|
||||||
lastMessageStatus: null,
|
|
||||||
lastMessageDeletedForEveryone: undefined,
|
|
||||||
timestamp: 555,
|
|
||||||
};
|
|
||||||
|
|
||||||
const actual = Conversation.createLastMessageUpdate(input);
|
|
||||||
assert.deepEqual(actual, expected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
context('for expire timer update from sync', () => {
|
|
||||||
it('should update message but not timestamp (to prevent bump to top)', () => {
|
|
||||||
const input = {
|
|
||||||
currentTimestamp: 555,
|
|
||||||
lastMessage: {
|
|
||||||
type: 'incoming',
|
|
||||||
conversationId: 'foo',
|
|
||||||
sent_at: 666,
|
|
||||||
timestamp: 666,
|
|
||||||
expirationTimerUpdate: {
|
|
||||||
expireTimer: 111,
|
|
||||||
fromSync: true,
|
|
||||||
source: '+12223334455',
|
|
||||||
},
|
|
||||||
} as IncomingMessage,
|
|
||||||
lastMessageNotificationText: 'Last message before expired',
|
|
||||||
};
|
|
||||||
const expected = {
|
|
||||||
lastMessage: 'Last message before expired',
|
|
||||||
lastMessageStatus: null,
|
|
||||||
lastMessageDeletedForEveryone: undefined,
|
|
||||||
timestamp: 555,
|
|
||||||
};
|
|
||||||
|
|
||||||
const actual = Conversation.createLastMessageUpdate(input);
|
|
||||||
assert.deepEqual(actual, expected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
context('for profile change message', () => {
|
|
||||||
it('should update message but not timestamp (to prevent bump to top)', () => {
|
|
||||||
const input = {
|
|
||||||
currentTimestamp: 555,
|
|
||||||
lastMessage: {
|
|
||||||
type: 'profile-change',
|
|
||||||
conversationId: 'foo',
|
|
||||||
sent_at: 666,
|
|
||||||
timestamp: 666,
|
|
||||||
} as ProfileChangeNotificationMessage,
|
|
||||||
lastMessageNotificationText: 'John changed their profile name',
|
|
||||||
};
|
|
||||||
const expected = {
|
|
||||||
lastMessage: 'John changed their profile name',
|
|
||||||
lastMessageStatus: null,
|
|
||||||
lastMessageDeletedForEveryone: undefined,
|
|
||||||
timestamp: 555,
|
|
||||||
};
|
|
||||||
|
|
||||||
const actual = Conversation.createLastMessageUpdate(input);
|
|
||||||
assert.deepEqual(actual, expected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1401,7 +1401,7 @@ class MessageReceiverInner extends EventTarget {
|
||||||
}
|
}
|
||||||
const to = sentMessage.message.group
|
const to = sentMessage.message.group
|
||||||
? `group(${sentMessage.message.group.id.toBinary()})`
|
? `group(${sentMessage.message.group.id.toBinary()})`
|
||||||
: sentMessage.destination;
|
: sentMessage.destination || sentMessage.destinationUuid;
|
||||||
|
|
||||||
window.log.info(
|
window.log.info(
|
||||||
'sent message to',
|
'sent message to',
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
import { Message } from './Message';
|
|
||||||
|
|
||||||
interface ConversationLastMessageUpdate {
|
|
||||||
lastMessage: string;
|
|
||||||
lastMessageStatus: string | null;
|
|
||||||
timestamp: number | null;
|
|
||||||
lastMessageDeletedForEveryone?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createLastMessageUpdate = ({
|
|
||||||
currentTimestamp,
|
|
||||||
lastMessage,
|
|
||||||
lastMessageStatus,
|
|
||||||
lastMessageNotificationText,
|
|
||||||
}: {
|
|
||||||
currentTimestamp?: number;
|
|
||||||
lastMessage?: Message;
|
|
||||||
lastMessageStatus?: string;
|
|
||||||
lastMessageNotificationText?: string;
|
|
||||||
}): ConversationLastMessageUpdate => {
|
|
||||||
if (!lastMessage) {
|
|
||||||
return {
|
|
||||||
lastMessage: '',
|
|
||||||
lastMessageStatus: null,
|
|
||||||
timestamp: null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const { type, expirationTimerUpdate, deletedForEveryone } = lastMessage;
|
|
||||||
const isMessageHistoryUnsynced = type === 'message-history-unsynced';
|
|
||||||
const isProfileChangedMessage = type === 'profile-change';
|
|
||||||
const isVerifiedChangeMessage = type === 'verified-change';
|
|
||||||
const isExpireTimerUpdateFromSync = Boolean(
|
|
||||||
expirationTimerUpdate && expirationTimerUpdate.fromSync
|
|
||||||
);
|
|
||||||
|
|
||||||
const shouldUpdateTimestamp = Boolean(
|
|
||||||
!isMessageHistoryUnsynced &&
|
|
||||||
!isProfileChangedMessage &&
|
|
||||||
!isVerifiedChangeMessage &&
|
|
||||||
!isExpireTimerUpdateFromSync
|
|
||||||
);
|
|
||||||
const newTimestamp = shouldUpdateTimestamp
|
|
||||||
? lastMessage.sent_at
|
|
||||||
: currentTimestamp;
|
|
||||||
|
|
||||||
const shouldUpdateLastMessageText = !isVerifiedChangeMessage;
|
|
||||||
const newLastMessageText = shouldUpdateLastMessageText
|
|
||||||
? lastMessageNotificationText
|
|
||||||
: '';
|
|
||||||
|
|
||||||
return {
|
|
||||||
lastMessage: deletedForEveryone ? '' : newLastMessageText || '',
|
|
||||||
lastMessageStatus: lastMessageStatus || null,
|
|
||||||
timestamp: newTimestamp || null,
|
|
||||||
lastMessageDeletedForEveryone: deletedForEveryone,
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -207,7 +207,7 @@
|
||||||
"rule": "jQuery-wrap(",
|
"rule": "jQuery-wrap(",
|
||||||
"path": "js/models/conversations.js",
|
"path": "js/models/conversations.js",
|
||||||
"line": " await wrap(",
|
"line": " await wrap(",
|
||||||
"lineNumber": 671,
|
"lineNumber": 665,
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2020-06-09T20:26:46.515Z"
|
"updated": "2020-06-09T20:26:46.515Z"
|
||||||
},
|
},
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue