Sort by inbox position to match phone after link

This commit is contained in:
Josh Perez 2020-03-09 17:43:09 -07:00 committed by Scott Nonnenberg
parent 1f5cb9e8af
commit 4830213a12
25 changed files with 707 additions and 1029 deletions

View file

@ -285,6 +285,10 @@
}
}
},
"messageHistoryUnsynced": {
"message": "For your security, conversation history isn't transferred to new linked devices.",
"description": "Shown in the conversation history when a user links a new device to explain what is not supported."
},
"youMarkedAsVerified": {
"message": "You marked your Safety Number with $name$ as verified",
"description": "Shown in the conversation history when the user marks a contact as verified.",
@ -1530,6 +1534,10 @@
"message": "Start new conversation…",
"description": "Label underneath number a user enters that is not an existing contact"
},
"notSupportedSMS": {
"message": "SMS/MMS messages are not supported.",
"description": "Label underneath number informing user that SMS is not supported on desktop"
},
"newPhoneNumber": {
"message": "Enter a phone number to add a contact.",
"description": "Placeholder for adding a new number to a contact"

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><title>info-outline-24</title><path d="M12,2.5A9.5,9.5,0,1,1,2.5,12,9.511,9.511,0,0,1,12,2.5M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,7.5A1.5,1.5,0,0,0,13.5,7a1.5,1.5,0,1,0-2.56,1.06A1.435,1.435,0,0,0,12,8.5Zm1,8V10H9.5v1.5h2v5H9V18h6V16.5Z"/></svg>

After

Width:  |  Height:  |  Size: 334 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><title>info-solid-24</title><path d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1ZM10.94,5.939A1.5,1.5,0,0,1,13.5,7a1.5,1.5,0,0,1-2.56,1.06,1.5,1.5,0,0,1,0-2.121ZM15,18H9V16.5h2.5v-5h-2V10H13v6.5h2Z"/></svg>

After

Width:  |  Height:  |  Size: 286 B

View file

@ -1975,6 +1975,7 @@
name: details.name,
color: details.color,
active_at: activeAt,
inbox_position: details.inboxPosition,
});
// Update the conversation avatar only if new avatar exists and hash differs
@ -2030,6 +2031,14 @@
verifiedEvent.viaContactSync = true;
await onVerified(verifiedEvent);
}
const { appView } = window.owsDesktopApp;
if (appView && appView.installView && appView.installView.didLink) {
window.log.info(
'onContactReceived: Adding the message history disclaimer on link'
);
await conversation.addMessageHistoryDisclaimer();
}
} catch (error) {
window.log.error('onContactReceived error:', Errors.toLogFormat(error));
}
@ -2063,6 +2072,7 @@
members,
color: details.color,
type: 'group',
inbox_position: details.inboxPosition,
};
if (details.active) {
@ -2103,6 +2113,13 @@
window.Signal.Data.updateConversation(id, conversation.attributes);
const { appView } = window.owsDesktopApp;
if (appView && appView.installView && appView.installView.didLink) {
window.log.info(
'onGroupReceived: Adding the message history disclaimer on link'
);
await conversation.addMessageHistoryDisclaimer();
}
const { expireTimer } = details;
const isValidExpireTimer = typeof expireTimer === 'number';
if (!isValidExpireTimer) {

View file

@ -385,6 +385,7 @@
const draftText = this.get('draft');
const shouldShowDraft =
this.hasDraft() && draftTimestamp && draftTimestamp >= timestamp;
const inboxPosition = this.get('inbox_position');
const result = {
id: this.id,
@ -400,6 +401,7 @@
name: this.getName(),
profileName: this.getProfileName(),
timestamp,
inboxPosition,
title: this.getTitle(),
unreadCount: this.get('unreadCount') || 0,
@ -1662,6 +1664,38 @@
return message;
},
async addMessageHistoryDisclaimer() {
const timestamp = Date.now();
const model = new Whisper.Message({
type: 'message-history-unsynced',
// Even though this isn't reflected to the user, we want to place the last seen
// indicator above it. We set it to 'unread' to trigger that placement.
unread: 1,
conversationId: this.id,
// No type; 'incoming' messages are specially treated by conversation.markRead()
sent_at: timestamp,
received_at: timestamp,
});
if (this.isPrivate()) {
model.set({ destination: this.id });
}
if (model.isOutgoing()) {
model.set({ recipients: this.getRecipients() });
}
const id = await window.Signal.Data.saveMessage(model.attributes, {
Message: Whisper.Message,
});
model.set({ id });
const message = MessageController.register(id, model);
this.addSingleMessage(message);
return message;
},
isSearchable() {
return !this.get('left');
},

View file

@ -134,6 +134,7 @@
!this.isUnsupportedMessage() &&
!this.isExpirationTimerUpdate() &&
!this.isKeyChange() &&
!this.isMessageHistoryUnsynced() &&
!this.isVerifiedChange() &&
!this.isGroupUpdate() &&
!this.isEndSession()
@ -147,6 +148,11 @@
type: 'unsupportedMessage',
data: this.getPropsForUnsupportedMessage(),
};
} else if (this.isMessageHistoryUnsynced()) {
return {
type: 'linkNotification',
data: null,
};
} else if (this.isExpirationTimerUpdate()) {
return {
type: 'timerNotification',
@ -343,6 +349,9 @@
isVerifiedChange() {
return this.get('type') === 'verified-change';
},
isMessageHistoryUnsynced() {
return this.get('type') === 'message-history-unsynced';
},
isGroupUpdate() {
return !!this.get('group_update');
},

View file

@ -32,6 +32,7 @@
initialize(options = {}) {
window.readyForUpdates();
this.didLink = false;
this.selectStep(Steps.SCAN_QR_CODE);
this.connect();
this.on('disconnected', this.reconnect);
@ -179,7 +180,10 @@
this.selectStep(Steps.PROGRESS_BAR);
const finish = () => resolve(name);
const finish = () => {
this.didLink = true;
return resolve(name);
};
// Delete all data from database unless we're in the middle
// of a re-link, or we are finishing a light import. Without this,

View file

@ -377,15 +377,16 @@ message ContactDetails {
optional uint32 length = 2;
}
optional string number = 1;
optional string uuid = 9;
optional string name = 2;
optional Avatar avatar = 3;
optional string color = 4;
optional Verified verified = 5;
optional bytes profileKey = 6;
optional bool blocked = 7;
optional uint32 expireTimer = 8;
optional string number = 1;
optional string uuid = 9;
optional string name = 2;
optional Avatar avatar = 3;
optional string color = 4;
optional Verified verified = 5;
optional bytes profileKey = 6;
optional bool blocked = 7;
optional uint32 expireTimer = 8;
optional uint32 inboxPosition = 10;
}
message GroupDetails {
@ -399,13 +400,14 @@ message GroupDetails {
optional string e164 = 2;
}
optional bytes id = 1;
optional string name = 2;
repeated string membersE164 = 3;
repeated Member members = 9;
optional Avatar avatar = 4;
optional bool active = 5 [default = true];
optional uint32 expireTimer = 6;
optional string color = 7;
optional bool blocked = 8;
optional bytes id = 1;
optional string name = 2;
repeated string membersE164 = 3;
repeated Member members = 9;
optional Avatar avatar = 4;
optional bool active = 5 [default = true];
optional uint32 expireTimer = 6;
optional string color = 7;
optional bool blocked = 8;
optional uint32 inboxPosition = 10;
}

View file

@ -2335,6 +2335,36 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
}
}
.module-message-unsynced {
padding-bottom: 24px;
text-align: center;
@include light-theme {
color: $color-gray-60;
}
@include dark-theme {
color: $color-gray-25;
}
}
.module-message-unsynced__icon {
height: 24px;
margin-bottom: 4px;
margin-left: auto;
margin-right: auto;
width: 24px;
@include light-theme {
@include color-svg(
'../images/icons/v2/info-outline-24.svg',
$color-gray-60
);
}
@include dark-theme {
@include color-svg('../images/icons/v2/info-solid-24.svg', $color-gray-25);
}
}
// Module: Verification Notification
.module-verification-notification {
@ -4729,6 +4759,19 @@ button.module-image__border-overlay:focus {
}
}
.module-search-results__sms-not-supported {
font-size: 14px;
padding-top: 12px;
text-align: center;
@include light-theme {
color: $color-gray-60;
}
@include dark-theme {
color: $color-gray-25;
}
}
.module-search-results__no-results {
margin-top: 27px;
padding-left: 1em;

View file

@ -1,983 +0,0 @@
#### With all result types
```jsx
const items = [
{
type: 'conversations-header',
data: undefined,
},
{
type: 'conversation',
data: {
name: 'Everyone 🌆',
conversationType: 'group',
phoneNumber: '(202) 555-0011',
avatarPath: util.landscapeGreenObjectUrl,
lastUpdated: Date.now() - 5 * 60 * 1000,
lastMessage: {
text: 'The rabbit hopped silently in the night.',
status: 'sent',
},
},
},
{
type: 'conversation',
data: {
name: 'Everyone Else 🔥',
conversationType: 'direct',
phoneNumber: '(202) 555-0012',
avatarPath: util.landscapePurpleObjectUrl,
lastUpdated: Date.now() - 5 * 60 * 1000,
lastMessage: {
text: "What's going on?",
status: 'sent',
},
},
},
{
type: 'contacts-header',
data: undefined,
},
{
type: 'contact',
data: {
name: 'The one Everyone',
conversationType: 'direct',
phoneNumber: '(202) 555-0013',
avatarPath: util.gifObjectUrl,
},
},
{
type: 'contact',
data: {
name: 'No likey everyone',
conversationType: 'direct',
phoneNumber: '(202) 555-0014',
color: 'red',
},
},
{
type: 'messages-header',
data: undefined,
},
];
const messages = [
{
from: {
isMe: true,
avatarPath: util.gifObjectUrl,
},
to: {
name: 'Mr. Fire 🔥',
phoneNumber: '(202) 555-0015',
},
id: '1-guid-guid-guid-guid-guid',
conversationId: '(202) 555-0015',
receivedAt: Date.now() - 5 * 60 * 1000,
snippet: '<<left>>Everyone<<right>>! Get in!',
conversationOpenInternal: () => console.log('onClick'),
},
{
from: {
name: 'Jon ❄️',
phoneNumber: '(202) 555-0016',
color: 'green',
},
to: {
isMe: true,
},
id: '2-guid-guid-guid-guid-guid',
conversationId: '(202) 555-0016',
snippet: 'Why is <<left>>everyone<<right>> so frustrated?',
receivedAt: Date.now() - 20 * 60 * 1000,
conversationOpenInternal: () => console.log('onClick'),
},
{
from: {
name: 'Someone',
phoneNumber: '(202) 555-0011',
color: 'green',
avatarPath: util.pngObjectUrl,
},
to: {
name: "Y'all 🌆",
},
id: '3-guid-guid-guid-guid-guid',
conversationId: 'EveryoneGroupID',
snippet: 'Hello, <<left>>everyone<<right>>! Woohooo!',
receivedAt: Date.now() - 24 * 60 * 1000,
conversationOpenInternal: () => console.log('onClick'),
},
{
from: {
isMe: true,
avatarPath: util.gifObjectUrl,
},
to: {
name: "Y'all 🌆",
},
id: '4-guid-guid-guid-guid-guid',
conversationId: 'EveryoneGroupID',
snippet: 'Well, <<left>>everyone<<right>>, happy new year!',
receivedAt: Date.now() - 24 * 60 * 1000,
conversationOpenInternal: () => console.log('onClick'),
},
];
const messageLookup = util._.fromPairs(
util._.map(messages, message => [message.id, message])
);
messages.forEach(message => {
items.push({
type: 'message',
data: message.id,
});
});
<util.LeftPaneContext
theme={util.theme}
gutterStyle={{ height: '500px', display: 'flex', flexDirection: 'row' }}
>
<SearchResults
items={items}
i18n={util.i18n}
openConversationInternal={(...args) =>
console.log('openConversationInternal', args)
}
startNewConversation={(...args) =>
console.log('startNewConversation', args)
}
onStartNewConversation={(...args) =>
console.log('onStartNewConversation', args)
}
renderMessageSearchResult={id => (
<MessageSearchResult
{...messageLookup[id]}
i18n={util.i18n}
openConversationInternal={(...args) =>
console.log('openConversationInternal', args)
}
/>
)}
/>
</util.LeftPaneContext>;
```
#### With 'start new conversation'
```jsx
const items = [
{
type: 'start-new-conversation',
data: undefined,
},
{
type: 'conversations-header',
data: undefined,
},
{
type: 'conversation',
data: {
name: 'Everyone 🌆',
conversationType: 'group',
phoneNumber: '(202) 555-0011',
avatarPath: util.landscapeGreenObjectUrl,
lastUpdated: Date.now() - 5 * 60 * 1000,
lastMessage: {
text: 'The rabbit hopped silently in the night.',
status: 'sent',
},
},
},
{
type: 'conversation',
data: {
name: 'Everyone Else 🔥',
conversationType: 'direct',
phoneNumber: '(202) 555-0012',
avatarPath: util.landscapePurpleObjectUrl,
lastUpdated: Date.now() - 5 * 60 * 1000,
lastMessage: {
text: "What's going on?",
status: 'sent',
},
},
},
{
type: 'contacts-header',
data: undefined,
},
{
type: 'contact',
data: {
name: 'The one Everyone',
conversationType: 'direct',
phoneNumber: '(202) 555-0013',
avatarPath: util.gifObjectUrl,
},
},
{
type: 'contact',
data: {
name: 'No likey everyone',
conversationType: 'direct',
phoneNumber: '(202) 555-0014',
color: 'red',
},
},
{
type: 'messages-header',
data: undefined,
},
];
const messages = [
{
from: {
isMe: true,
avatarPath: util.gifObjectUrl,
},
to: {
name: 'Mr. Fire 🔥',
phoneNumber: '(202) 555-0015',
},
id: '1-guid-guid-guid-guid-guid',
conversationId: '(202) 555-0015',
receivedAt: Date.now() - 5 * 60 * 1000,
snippet: '<<left>>Everyone<<right>>! Get in!',
conversationOpenInternal: () => console.log('onClick'),
},
{
from: {
name: 'Jon ❄️',
phoneNumber: '(202) 555-0016',
color: 'green',
},
to: {
isMe: true,
},
id: '2-guid-guid-guid-guid-guid',
conversationId: '(202) 555-0016',
snippet: 'Why is <<left>>everyone<<right>> so frustrated?',
receivedAt: Date.now() - 20 * 60 * 1000,
conversationOpenInternal: () => console.log('onClick'),
},
{
from: {
name: 'Someone',
phoneNumber: '(202) 555-0011',
color: 'green',
avatarPath: util.pngObjectUrl,
},
to: {
name: "Y'all 🌆",
},
id: '3-guid-guid-guid-guid-guid',
conversationId: 'EveryoneGroupID',
snippet: 'Hello, <<left>>everyone<<right>>! Woohooo!',
receivedAt: Date.now() - 24 * 60 * 1000,
conversationOpenInternal: () => console.log('onClick'),
},
{
from: {
isMe: true,
avatarPath: util.gifObjectUrl,
},
to: {
name: "Y'all 🌆",
},
id: '4-guid-guid-guid-guid-guid',
conversationId: 'EveryoneGroupID',
snippet: 'Well, <<left>>everyone<<right>>, happy new year!',
receivedAt: Date.now() - 24 * 60 * 1000,
conversationOpenInternal: () => console.log('onClick'),
},
];
const messageLookup = util._.fromPairs(
util._.map(messages, message => [message.id, message])
);
messages.forEach(message => {
items.push({
type: 'message',
data: message.id,
});
});
<util.LeftPaneContext
theme={util.theme}
gutterStyle={{ height: '500px', display: 'flex', flexDirection: 'row' }}
>
<SearchResults
items={items}
i18n={util.i18n}
searchTerm="(202) 555-0015"
openConversationInternal={(...args) =>
console.log('openConversationInternal', args)
}
startNewConversation={(...args) =>
console.log('startNewConversation', args)
}
onStartNewConversation={(...args) =>
console.log('onStartNewConversation', args)
}
renderMessageSearchResult={id => (
<MessageSearchResult
{...messageLookup[id]}
i18n={util.i18n}
openConversationInternal={(...args) =>
console.log('openConversationInternal', args)
}
/>
)}
/>
</util.LeftPaneContext>;
```
#### With no conversations
```jsx
const items = [
{
type: 'contacts-header',
data: undefined,
},
{
type: 'contact',
data: {
name: 'The one Everyone',
conversationType: 'direct',
phoneNumber: '(202) 555-0013',
avatarPath: util.gifObjectUrl,
},
},
{
type: 'contact',
data: {
name: 'No likey everyone',
conversationType: 'direct',
phoneNumber: '(202) 555-0014',
color: 'red',
},
},
{
type: 'messages-header',
data: undefined,
},
];
const messages = [
{
from: {
isMe: true,
avatarPath: util.gifObjectUrl,
},
to: {
name: 'Mr. Fire 🔥',
phoneNumber: '(202) 555-0015',
},
id: '1-guid-guid-guid-guid-guid',
conversationId: '(202) 555-0015',
receivedAt: Date.now() - 5 * 60 * 1000,
snippet: '<<left>>Everyone<<right>>! Get in!',
conversationOpenInternal: () => console.log('onClick'),
},
{
from: {
name: 'Jon ❄️',
phoneNumber: '(202) 555-0016',
color: 'green',
},
to: {
isMe: true,
},
id: '2-guid-guid-guid-guid-guid',
conversationId: '(202) 555-0016',
snippet: 'Why is <<left>>everyone<<right>> so frustrated?',
receivedAt: Date.now() - 20 * 60 * 1000,
conversationOpenInternal: () => console.log('onClick'),
},
{
from: {
name: 'Someone',
phoneNumber: '(202) 555-0011',
color: 'green',
avatarPath: util.pngObjectUrl,
},
to: {
name: "Y'all 🌆",
},
id: '3-guid-guid-guid-guid-guid',
conversationId: 'EveryoneGroupID',
snippet: 'Hello, <<left>>everyone<<right>>! Woohooo!',
receivedAt: Date.now() - 24 * 60 * 1000,
conversationOpenInternal: () => console.log('onClick'),
},
{
from: {
isMe: true,
avatarPath: util.gifObjectUrl,
},
to: {
name: "Y'all 🌆",
},
id: '4-guid-guid-guid-guid-guid',
conversationId: 'EveryoneGroupID',
snippet: 'Well, <<left>>everyone<<right>>, happy new year!',
receivedAt: Date.now() - 24 * 60 * 1000,
conversationOpenInternal: () => console.log('onClick'),
},
];
const messageLookup = util._.fromPairs(
util._.map(messages, message => [message.id, message])
);
messages.forEach(message => {
items.push({
type: 'message',
data: message.id,
});
});
<util.LeftPaneContext
theme={util.theme}
gutterStyle={{ height: '500px', display: 'flex', flexDirection: 'row' }}
>
<SearchResults
items={items}
i18n={util.i18n}
openConversationInternal={(...args) =>
console.log('openConversationInternal', args)
}
startNewConversation={(...args) =>
console.log('startNewConversation', args)
}
onStartNewConversation={(...args) =>
console.log('onStartNewConversation', args)
}
renderMessageSearchResult={id => (
<MessageSearchResult
{...messageLookup[id]}
i18n={util.i18n}
openConversationInternal={(...args) =>
console.log('openConversationInternal', args)
}
/>
)}
/>
</util.LeftPaneContext>;
```
#### With no contacts
```jsx
const items = [
{
type: 'conversations-header',
data: undefined,
},
{
type: 'conversation',
data: {
name: 'Everyone 🌆',
conversationType: 'group',
phoneNumber: '(202) 555-0011',
avatarPath: util.landscapeGreenObjectUrl,
lastUpdated: Date.now() - 5 * 60 * 1000,
lastMessage: {
text: 'The rabbit hopped silently in the night.',
status: 'sent',
},
},
},
{
type: 'conversation',
data: {
name: 'Everyone Else 🔥',
conversationType: 'direct',
phoneNumber: '(202) 555-0012',
avatarPath: util.landscapePurpleObjectUrl,
lastUpdated: Date.now() - 5 * 60 * 1000,
lastMessage: {
text: "What's going on?",
status: 'sent',
},
},
},
{
type: 'messages-header',
data: undefined,
},
];
const messages = [
{
from: {
isMe: true,
avatarPath: util.gifObjectUrl,
},
to: {
name: 'Mr. Fire 🔥',
phoneNumber: '(202) 555-0015',
},
id: '1-guid-guid-guid-guid-guid',
conversationId: '(202) 555-0015',
receivedAt: Date.now() - 5 * 60 * 1000,
snippet: '<<left>>Everyone<<right>>! Get in!',
conversationOpenInternal: () => console.log('onClick'),
},
{
from: {
name: 'Jon ❄️',
phoneNumber: '(202) 555-0016',
color: 'green',
},
to: {
isMe: true,
},
id: '2-guid-guid-guid-guid-guid',
conversationId: '(202) 555-0016',
snippet: 'Why is <<left>>everyone<<right>> so frustrated?',
receivedAt: Date.now() - 20 * 60 * 1000,
conversationOpenInternal: () => console.log('onClick'),
},
{
from: {
name: 'Someone',
phoneNumber: '(202) 555-0011',
color: 'green',
avatarPath: util.pngObjectUrl,
},
to: {
name: "Y'all 🌆",
},
id: '3-guid-guid-guid-guid-guid',
conversationId: 'EveryoneGroupID',
snippet: 'Hello, <<left>>everyone<<right>>! Woohooo!',
receivedAt: Date.now() - 24 * 60 * 1000,
conversationOpenInternal: () => console.log('onClick'),
},
{
from: {
isMe: true,
avatarPath: util.gifObjectUrl,
},
to: {
name: "Y'all 🌆",
},
id: '4-guid-guid-guid-guid-guid',
conversationId: 'EveryoneGroupID',
snippet: 'Well, <<left>>everyone<<right>>, happy new year!',
receivedAt: Date.now() - 24 * 60 * 1000,
conversationOpenInternal: () => console.log('onClick'),
},
];
const messageLookup = util._.fromPairs(
util._.map(messages, message => [message.id, message])
);
messages.forEach(message => {
items.push({
type: 'message',
data: message.id,
});
});
<util.LeftPaneContext
theme={util.theme}
gutterStyle={{ height: '500px', display: 'flex', flexDirection: 'row' }}
>
<SearchResults
items={items}
i18n={util.i18n}
openConversationInternal={(...args) =>
console.log('openConversationInternal', args)
}
startNewConversation={(...args) =>
console.log('startNewConversation', args)
}
onStartNewConversation={(...args) =>
console.log('onStartNewConversation', args)
}
renderMessageSearchResult={id => (
<MessageSearchResult
{...messageLookup[id]}
i18n={util.i18n}
openConversationInternal={(...args) =>
console.log('openConversationInternal', args)
}
/>
)}
/>
</util.LeftPaneContext>;
```
#### With no messages
```jsx
const items = [
{
type: 'conversations-header',
data: undefined,
},
{
type: 'conversation',
data: {
name: 'Everyone 🌆',
conversationType: 'group',
phoneNumber: '(202) 555-0011',
avatarPath: util.landscapeGreenObjectUrl,
lastUpdated: Date.now() - 5 * 60 * 1000,
lastMessage: {
text: 'The rabbit hopped silently in the night.',
status: 'sent',
},
},
},
{
type: 'conversation',
data: {
name: 'Everyone Else 🔥',
conversationType: 'direct',
phoneNumber: '(202) 555-0012',
avatarPath: util.landscapePurpleObjectUrl,
lastUpdated: Date.now() - 5 * 60 * 1000,
lastMessage: {
text: "What's going on?",
status: 'sent',
},
},
},
{
type: 'contacts-header',
data: undefined,
},
{
type: 'contact',
data: {
name: 'The one Everyone',
conversationType: 'direct',
phoneNumber: '(202) 555-0013',
avatarPath: util.gifObjectUrl,
},
},
{
type: 'contact',
data: {
name: 'No likey everyone',
conversationType: 'direct',
phoneNumber: '(202) 555-0014',
color: 'red',
},
},
];
<util.LeftPaneContext
theme={util.theme}
gutterStyle={{ height: '500px', display: 'flex', flexDirection: 'row' }}
>
<SearchResults
items={items}
i18n={util.i18n}
openConversationInternal={(...args) =>
console.log('openConversationInternal', args)
}
startNewConversation={(...args) =>
console.log('startNewConversation', args)
}
onStartNewConversation={(...args) =>
console.log('onStartNewConversation', args)
}
/>
</util.LeftPaneContext>;
```
#### With no results at all
```jsx
<util.LeftPaneContext
theme={util.theme}
gutterStyle={{ height: '500px', display: 'flex', flexDirection: 'row' }}
>
<SearchResults
items={[]}
noResults={true}
searchTerm="something"
i18n={util.i18n}
openConversationInternal={(...args) =>
console.log('openConversationInternal', args)
}
startNewConversation={(...args) =>
console.log('startNewConversation', args)
}
onStartNewConversation={(...args) =>
console.log('onStartNewConversation', args)
}
renderMessageSearchResult={id => (
<MessageSearchResult
{...messageLookup[id]}
i18n={util.i18n}
openConversationInternal={(...args) =>
console.log('openConversationInternal', args)
}
/>
)}
/>
</util.LeftPaneContext>
```
#### With no results at all, searching in conversation
```jsx
<util.LeftPaneContext
theme={util.theme}
gutterStyle={{ height: '500px', display: 'flex', flexDirection: 'row' }}
>
<SearchResults
items={[]}
noResults={true}
searchTerm="something"
searchInConversationName="Everyone 🔥"
i18n={util.i18n}
openConversationInternal={(...args) =>
console.log('openConversationInternal', args)
}
startNewConversation={(...args) =>
console.log('startNewConversation', args)
}
onStartNewConversation={(...args) =>
console.log('onStartNewConversation', args)
}
renderMessageSearchResult={id => (
<MessageSearchResult
{...messageLookup[id]}
i18n={util.i18n}
openConversationInternal={(...args) =>
console.log('openConversationInternal', args)
}
/>
)}
/>
</util.LeftPaneContext>
```
#### Searching in conversation but no search term
```jsx
<util.LeftPaneContext
theme={util.theme}
gutterStyle={{ height: '500px', display: 'flex', flexDirection: 'row' }}
>
<SearchResults
items={[]}
noResults={true}
searchTerm=""
searchInConversationName="Everyone 🔥"
i18n={util.i18n}
openConversationInternal={(...args) =>
console.log('openConversationInternal', args)
}
startNewConversation={(...args) =>
console.log('startNewConversation', args)
}
onStartNewConversation={(...args) =>
console.log('onStartNewConversation', args)
}
renderMessageSearchResult={id => (
<MessageSearchResult
{...messageLookup[id]}
i18n={util.i18n}
openConversationInternal={(...args) =>
console.log('openConversationInternal', args)
}
/>
)}
/>
</util.LeftPaneContext>
```
#### With a lot of results
```jsx
const items = [
{
type: 'conversations-header',
data: undefined,
},
{
type: 'conversation',
data: {
name: 'Everyone 🌆',
conversationType: 'group',
phoneNumber: '(202) 555-0011',
avatarPath: util.landscapeGreenObjectUrl,
lastUpdated: Date.now() - 5 * 60 * 1000,
lastMessage: {
text: 'The rabbit hopped silently in the night.',
status: 'sent',
},
},
},
{
type: 'conversation',
data: {
name: 'Everyone Else 🔥',
conversationType: 'direct',
phoneNumber: '(202) 555-0012',
avatarPath: util.landscapePurpleObjectUrl,
lastUpdated: Date.now() - 5 * 60 * 1000,
lastMessage: {
text: "What's going on?",
status: 'sent',
},
},
},
{
type: 'contacts-header',
data: undefined,
},
{
type: 'contact',
data: {
name: 'The one Everyone',
conversationType: 'direct',
phoneNumber: '(202) 555-0013',
avatarPath: util.gifObjectUrl,
},
},
{
type: 'contact',
data: {
name: 'No likey everyone',
conversationType: 'direct',
phoneNumber: '(202) 555-0014',
color: 'red',
},
},
{
type: 'messages-header',
data: undefined,
},
];
const messages = [];
for (let i = 0; i < 100; i += 1) {
messages.push({
from: {
name: 'Mr. Fire 🔥',
phoneNumber: '(202) 555-0015',
avatarPath: util.landscapeGreenObjectUrl,
},
to: {
isMe: true,
},
id: `${i}-guid-guid-guid-guid-guid`,
conversationId: '(202) 555-0015',
receivedAt: Date.now() - 5 * 60 * 1000,
snippet: `${i} <<left>>Everyone<<right>>! Get in!`,
conversationOpenInternal: data => console.log('onClick', data),
});
}
const messageLookup = util._.fromPairs(
util._.map(messages, message => [message.id, message])
);
messages.forEach(message => {
items.push({
type: 'message',
data: message.id,
});
});
<util.LeftPaneContext
gutterStyle={{ height: '500px', display: 'flex', flexDirection: 'row' }}
theme={util.theme}
>
<SearchResults
items={items}
i18n={util.i18n}
openConversationInternal={(...args) =>
console.log('openConversationInternal', args)
}
startNewConversation={(...args) =>
console.log('startNewConversation', args)
}
onStartNewConversation={(...args) =>
console.log('onStartNewConversation', args)
}
renderMessageSearchResult={id => (
<MessageSearchResult
{...messageLookup[id]}
i18n={util.i18n}
openConversationInternal={(...args) =>
console.log('openConversationInternal', args)
}
/>
)}
/>
</util.LeftPaneContext>;
```
#### With just messages and no header
```jsx
const items = [];
const messages = [];
for (let i = 0; i < 10; i += 1) {
messages.push({
from: {
name: 'Mr. Fire 🔥',
phoneNumber: '(202) 555-0015',
avatarPath: util.landscapeGreenObjectUrl,
},
to: {
isMe: true,
},
id: `${i}-guid-guid-guid-guid-guid`,
conversationId: '(202) 555-0015',
receivedAt: Date.now() - 5 * 60 * 1000,
snippet: `${i} <<left>>Everyone<<right>>! Get in!`,
conversationOpenInternal: data => console.log('onClick', data),
});
}
const messageLookup = util._.fromPairs(
util._.map(messages, message => [message.id, message])
);
messages.forEach(message => {
items.push({
type: 'message',
data: message.id,
});
});
<util.LeftPaneContext
gutterStyle={{ height: '500px', display: 'flex', flexDirection: 'row' }}
theme={util.theme}
>
<SearchResults
items={items}
i18n={util.i18n}
openConversationInternal={(...args) =>
console.log('openConversationInternal', args)
}
startNewConversation={(...args) =>
console.log('startNewConversation', args)
}
onStartNewConversation={(...args) =>
console.log('onStartNewConversation', args)
}
renderMessageSearchResult={id => (
<MessageSearchResult
{...messageLookup[id]}
i18n={util.i18n}
openConversationInternal={(...args) =>
console.log('openConversationInternal', args)
}
/>
)}
/>
</util.LeftPaneContext>;
```

View file

@ -0,0 +1,423 @@
import * as React from 'react';
import { SearchResults } from './SearchResults';
import {
MessageSearchResult,
PropsDataType as MessageSearchResultPropsType,
} from './MessageSearchResult';
// @ts-ignore
import { setup as setupI18n } from '../../js/modules/i18n';
// @ts-ignore
import enMessages from '../../_locales/en/messages.json';
import { storiesOf } from '@storybook/react';
//import { boolean, select } from '@storybook/addon-knobs';
import { action } from '@storybook/addon-actions';
// @ts-ignore
import gif from '../../fixtures/giphy-GVNvOUpeYmI7e.gif';
// @ts-ignore
import png from '../../fixtures/freepngs-2cd43b_bed7d1327e88454487397574d87b64dc_mv2.png';
// @ts-ignore
import landscapeGreen from '../../fixtures/1000x50-green.jpeg';
// @ts-ignore
import landscapePurple from '../../fixtures/200x50-purple.png';
const i18n = setupI18n('en', enMessages);
function makeObjectUrl(data: ArrayBuffer, contentType: string): string {
const blob = new Blob([data], {
type: contentType,
});
return URL.createObjectURL(blob);
}
// 320x240
const gifObjectUrl = makeObjectUrl(gif, 'image/gif');
// 800×1200
const pngObjectUrl = makeObjectUrl(png, 'image/png');
const landscapeGreenObjectUrl = makeObjectUrl(landscapeGreen, 'image/jpeg');
const landscapePurpleObjectUrl = makeObjectUrl(landscapePurple, 'image/png');
const messageLookup: Map<string, MessageSearchResultPropsType> = new Map();
const CONTACT = 'contact' as 'contact';
const CONTACTS_HEADER = 'contacts-header' as 'contacts-header';
const CONVERSATION = 'conversation' as 'conversation';
const CONVERSATIONS_HEADER = 'conversations-header' as 'conversations-header';
const DIRECT = 'direct' as 'direct';
const GROUP = 'group' as 'group';
const MESSAGE = 'message' as 'message';
const MESSAGES_HEADER = 'messages-header' as 'messages-header';
const SENT = 'sent' as 'sent';
const START_NEW_CONVERSATION = 'start-new-conversation' as 'start-new-conversation';
const SMS_MMS_NOT_SUPPORTED = 'sms-mms-not-supported-text' as 'sms-mms-not-supported-text';
// tslint:disable-next-line no-backbone-get-set-outside-model
messageLookup.set('1-guid-guid-guid-guid-guid', {
id: '1-guid-guid-guid-guid-guid',
conversationId: '(202) 555-0015',
sentAt: Date.now() - 5 * 60 * 1000,
snippet: '<<left>>Everyone<<right>>! Get in!',
from: {
phoneNumber: '(202) 555-0020',
isMe: true,
avatarPath: gifObjectUrl,
},
to: {
phoneNumber: '(202) 555-0015',
name: 'Mr. Fire 🔥',
},
});
// tslint:disable-next-line no-backbone-get-set-outside-model
messageLookup.set('2-guid-guid-guid-guid-guid', {
id: '2-guid-guid-guid-guid-guid',
conversationId: '(202) 555-0016',
sentAt: Date.now() - 20 * 60 * 1000,
snippet: 'Why is <<left>>everyone<<right>> so frustrated?',
from: {
phoneNumber: '(202) 555-0016',
name: 'Jon ❄️',
color: 'green',
},
to: {
phoneNumber: '(202) 555-0020',
isMe: true,
},
});
// tslint:disable-next-line no-backbone-get-set-outside-model
messageLookup.set('3-guid-guid-guid-guid-guid', {
id: '3-guid-guid-guid-guid-guid',
conversationId: 'EveryoneGroupID',
sentAt: Date.now() - 24 * 60 * 1000,
snippet: 'Hello, <<left>>everyone<<right>>! Woohooo!',
from: {
phoneNumber: '(202) 555-0011',
name: 'Someone',
color: 'green',
avatarPath: pngObjectUrl,
},
to: {
phoneNumber: '(202) 555-0016',
name: "Y'all 🌆",
},
});
// tslint:disable-next-line no-backbone-get-set-outside-model
messageLookup.set('4-guid-guid-guid-guid-guid', {
id: '4-guid-guid-guid-guid-guid',
conversationId: 'EveryoneGroupID',
sentAt: Date.now() - 24 * 60 * 1000,
snippet: 'Well, <<left>>everyone<<right>>, happy new year!',
from: {
phoneNumber: '(202) 555-0020',
isMe: true,
avatarPath: gifObjectUrl,
},
to: {
phoneNumber: '(202) 555-0016',
name: "Y'all 🌆",
},
});
const defaultProps = {
discussionsLoading: false,
items: [],
i18n,
messagesLoading: false,
noResults: false,
openConversationInternal: action('open-conversation-internal'),
regionCode: 'US',
renderMessageSearchResult(id: string): JSX.Element {
const messageProps = messageLookup.get(id) as MessageSearchResultPropsType;
return (
<MessageSearchResult
{...messageProps}
i18n={i18n}
openConversationInternal={action(
'MessageSearchResult-open-conversation-internal'
)}
/>
);
},
searchConversationName: undefined,
searchTerm: '1234567890',
selectedConversationId: undefined,
selectedMessageId: undefined,
startNewConversation: action('start-new-conversation'),
};
const conversations = [
{
type: CONVERSATION,
data: {
id: '+12025550011',
phoneNumber: '(202) 555-0011',
name: 'Everyone 🌆',
type: GROUP,
avatarPath: landscapeGreenObjectUrl,
isMe: false,
lastUpdated: Date.now() - 5 * 60 * 1000,
unreadCount: 0,
isSelected: false,
lastMessage: {
text: 'The rabbit hopped silently in the night.',
status: SENT,
},
},
},
{
type: CONVERSATION,
data: {
id: '+12025550012',
phoneNumber: '(202) 555-0012',
name: 'Everyone Else 🔥',
type: DIRECT,
avatarPath: landscapePurpleObjectUrl,
isMe: false,
lastUpdated: Date.now() - 5 * 60 * 1000,
unreadCount: 0,
isSelected: false,
lastMessage: {
text: "What's going on?",
status: SENT,
},
},
},
];
const contacts = [
{
type: CONTACT,
data: {
id: '+12025550013',
phoneNumber: '(202) 555-0013',
name: 'The one Everyone',
type: DIRECT,
avatarPath: gifObjectUrl,
isMe: false,
lastUpdated: Date.now() - 10 * 60 * 1000,
unreadCount: 0,
isSelected: false,
},
},
{
type: CONTACT,
data: {
id: '+12025550014',
phoneNumber: '(202) 555-0014',
name: 'No likey everyone',
type: DIRECT,
color: 'red',
isMe: false,
lastUpdated: Date.now() - 11 * 60 * 1000,
unreadCount: 0,
isSelected: false,
},
},
];
const messages = [
{
type: MESSAGE,
data: '1-guid-guid-guid-guid-guid',
},
{
type: MESSAGE,
data: '2-guid-guid-guid-guid-guid',
},
{
type: MESSAGE,
data: '3-guid-guid-guid-guid-guid',
},
{
type: MESSAGE,
data: '4-guid-guid-guid-guid-guid',
},
];
const messagesMany = Array.from(Array(100), (_, i) => messages[i % 4]);
const permutations = [
{
title: 'SMS/MMS Not Supported Text',
props: {
items: [
{
type: START_NEW_CONVERSATION,
data: undefined,
},
{
type: SMS_MMS_NOT_SUPPORTED,
data: undefined,
},
],
},
},
{
title: 'All Result Types',
props: {
items: [
{
type: CONVERSATIONS_HEADER,
data: undefined,
},
...conversations,
{
type: CONTACTS_HEADER,
data: undefined,
},
...contacts,
{
type: MESSAGES_HEADER,
data: undefined,
},
...messages,
],
},
},
{
title: 'Start new Conversation',
props: {
items: [
{
type: START_NEW_CONVERSATION,
data: undefined,
},
{
type: CONVERSATIONS_HEADER,
data: undefined,
},
...conversations,
{
type: CONTACTS_HEADER,
data: undefined,
},
...contacts,
{
type: MESSAGES_HEADER,
data: undefined,
},
...messages,
],
},
},
{
title: 'No Conversations',
props: {
items: [
{
type: CONTACTS_HEADER,
data: undefined,
},
...contacts,
{
type: MESSAGES_HEADER,
data: undefined,
},
...messages,
],
},
},
{
title: 'No Contacts',
props: {
items: [
{
type: CONVERSATIONS_HEADER,
data: undefined,
},
...conversations,
{
type: MESSAGES_HEADER,
data: undefined,
},
...messages,
],
},
},
{
title: 'No Messages',
props: {
items: [
{
type: CONVERSATIONS_HEADER,
data: undefined,
},
...conversations,
{
type: CONTACTS_HEADER,
data: undefined,
},
...contacts,
],
},
},
{
title: 'No Results',
props: {
noResults: true,
},
},
{
title: 'No Results, Searching in Conversation',
props: {
noResults: true,
searchInConversationName: 'Everyone 🔥',
searchTerm: 'something',
},
},
{
title: 'Searching in Conversation no search term',
props: {
noResults: true,
searchInConversationName: 'Everyone 🔥',
searchTerm: '',
},
},
{
title: 'Lots of results',
props: {
items: [
{
type: CONVERSATIONS_HEADER,
data: undefined,
},
...conversations,
{
type: CONTACTS_HEADER,
data: undefined,
},
...contacts,
{
type: MESSAGES_HEADER,
data: undefined,
},
...messagesMany,
],
},
},
{
title: 'Messages, no header',
props: {
items: messages,
},
},
];
storiesOf('Components/SearchResults', module).add('Iterations', () => {
return permutations.map(({ props, title }) => (
<>
<h3>{title}</h3>
<div className="module-left-pane">
<SearchResults {...defaultProps} {...props} />
</div>
<hr />
</>
));
});

View file

@ -35,6 +35,10 @@ type StartNewConversationType = {
type: 'start-new-conversation';
data: undefined;
};
type NotSupportedSMS = {
type: 'sms-mms-not-supported-text';
data: undefined;
};
type ConversationHeaderType = {
type: 'conversations-header';
data: undefined;
@ -66,6 +70,7 @@ type SpinnerType = {
export type SearchResultRowType =
| StartNewConversationType
| NotSupportedSMS
| ConversationHeaderType
| ContactsHeaderType
| MessagesHeaderType
@ -368,6 +373,12 @@ export class SearchResults extends React.Component<PropsType, StateType> {
onClick={this.handleStartNewConversation}
/>
);
} else if (row.type === 'sms-mms-not-supported-text') {
return (
<div className="module-search-results__sms-not-supported">
{i18n('notSupportedSMS')}
</div>
);
} else if (row.type === 'conversations-header') {
return (
<div

View file

@ -167,6 +167,10 @@ window.itemLookup = {
'Hello there from the new world! And this is multiple lines of text. Lines and lines and lines.',
},
},
'id-15': {
type: 'linkNotification',
data: null,
},
};
window.actions = {

View file

@ -32,6 +32,10 @@ import {
} from './GroupNotification';
import { ResetSessionNotification } from './ResetSessionNotification';
type LinkNotificationType = {
type: 'linkNotification';
data: null;
};
type MessageType = {
type: 'message';
data: MessageProps;
@ -61,12 +65,13 @@ type ResetSessionNotificationType = {
data: null;
};
export type TimelineItemType =
| LinkNotificationType
| MessageType
| UnsupportedMessageType
| TimerNotificationType
| SafetyNumberNotificationType
| VerificationNotificationType
| ResetSessionNotificationType
| SafetyNumberNotificationType
| TimerNotificationType
| UnsupportedMessageType
| VerificationNotificationType
| GroupNotificationType;
type PropsLocalType = {
@ -112,6 +117,13 @@ export class TimelineItem extends React.PureComponent<PropsType> {
notification = (
<UnsupportedMessage {...this.props} {...item.data} i18n={i18n} />
);
} else if (item.type === 'linkNotification') {
notification = (
<div className="module-message-unsynced">
<div className="module-message-unsynced__icon" />
{i18n('messageHistoryUnsynced')}
</div>
);
} else if (item.type === 'timerNotification') {
notification = (
<TimerNotification {...this.props} {...item.data} i18n={i18n} />

View file

@ -5,6 +5,7 @@ type TextSecureType = {
user: {
getNumber: () => string;
};
get: (item: string) => any;
};
messaging: {
sendStickerPackSync: (

View file

@ -27,6 +27,7 @@ export type ConversationType = {
isArchived: boolean;
activeAt?: number;
timestamp: number;
inboxPosition: number;
lastMessage?: {
status: 'error' | 'sending' | 'sent' | 'delivered' | 'read';
text: string;
@ -56,7 +57,13 @@ export type MessageType = {
id: string;
conversationId: string;
source: string;
type: 'incoming' | 'outgoing' | 'group' | 'keychange' | 'verified-change';
type:
| 'incoming'
| 'outgoing'
| 'group'
| 'keychange'
| 'verified-change'
| 'message-history-unsynced';
quote?: { author: string };
received_at: number;
hasSignalAccount?: boolean;

View file

@ -117,6 +117,21 @@ export const _getConversationComparator = (
return rightTimestamp - leftTimestamp;
}
if (
typeof left.inboxPosition === 'number' &&
typeof right.inboxPosition === 'number'
) {
return right.inboxPosition > left.inboxPosition ? -1 : 1;
}
if (typeof left.inboxPosition === 'number' && right.inboxPosition == null) {
return -1;
}
if (typeof right.inboxPosition === 'number' && left.inboxPosition == null) {
return 1;
}
const leftTitle = getConversationTitle(left, {
i18n,
ourRegionCode,

View file

@ -1,6 +1,7 @@
import memoizee from 'memoizee';
import { createSelector } from 'reselect';
import { getSearchResultsProps } from '../../shims/Whisper';
import { instance } from '../../util/libphonenumberInstance';
import { StateType } from '../reducer';
@ -20,7 +21,7 @@ import {
} from '../../components/SearchResults';
import { PropsDataType as MessageSearchResultPropsDataType } from '../../components/MessageSearchResult';
import { getRegionCode, getUserNumber } from './user';
import { getRegionCode, getUserAgent, getUserNumber } from './user';
import {
GetConversationByIdType,
getConversationLookup,
@ -72,6 +73,7 @@ export const getSearchResults = createSelector(
[
getSearch,
getRegionCode,
getUserAgent,
getConversationLookup,
getSelectedConversation,
getSelectedMessage,
@ -79,6 +81,7 @@ export const getSearchResults = createSelector(
(
state: SearchStateType,
regionCode: string,
userAgent: string,
lookup: ConversationLookupType,
selectedConversationId?: string,
selectedMessageId?: string
@ -114,6 +117,17 @@ export const getSearchResults = createSelector(
type: 'start-new-conversation',
data: undefined,
});
const isIOS = userAgent === 'OWI';
const parsedNumber = instance.parse(state.query, regionCode);
const isValidNumber = instance.isValidNumber(parsedNumber);
if (!isIOS && isValidNumber) {
items.push({
type: 'sms-mms-not-supported-text',
data: undefined,
});
}
}
if (haveConversations) {

View file

@ -4,9 +4,12 @@ import { LocalizerType } from '../../types/Util';
import { StateType } from '../reducer';
import { UserStateType } from '../ducks/user';
import { ItemsStateType } from '../ducks/items';
export const getUser = (state: StateType): UserStateType => state.user;
export const getItems = (state: StateType): ItemsStateType => state.items;
export const getUserNumber = createSelector(
getUser,
(state: UserStateType): string => state.ourNumber
@ -27,6 +30,11 @@ export const getUserUuid = createSelector(
(state: UserStateType): string => state.ourUuid
);
export const getUserAgent = createSelector(
getItems,
(state: ItemsStateType): string => state.userAgent
);
export const getIntl = createSelector(
getUser,
(state: UserStateType): LocalizerType => state.i18n

View file

@ -17,6 +17,7 @@ describe('state/selectors/conversations', () => {
activeAt: Date.now(),
name: 'No timestamp',
timestamp: 0,
inboxPosition: 0,
phoneNumber: 'notused',
isArchived: false,
@ -36,6 +37,7 @@ describe('state/selectors/conversations', () => {
activeAt: Date.now(),
name: 'B',
timestamp: 20,
inboxPosition: 21,
phoneNumber: 'notused',
isArchived: false,
@ -55,6 +57,7 @@ describe('state/selectors/conversations', () => {
activeAt: Date.now(),
name: 'C',
timestamp: 20,
inboxPosition: 22,
phoneNumber: 'notused',
isArchived: false,
@ -74,6 +77,7 @@ describe('state/selectors/conversations', () => {
activeAt: Date.now(),
name: 'Á',
timestamp: 20,
inboxPosition: 20,
phoneNumber: 'notused',
isArchived: false,
@ -93,6 +97,7 @@ describe('state/selectors/conversations', () => {
activeAt: Date.now(),
name: 'First!',
timestamp: 30,
inboxPosition: 30,
phoneNumber: 'notused',
isArchived: false,

View file

@ -3,6 +3,7 @@ import { assert } from 'chai';
import * as Conversation from '../../types/Conversation';
import {
IncomingMessage,
MessageHistoryUnsyncedMessage,
OutgoingMessage,
VerifiedChangeMessage,
} from '../../types/Message';
@ -44,6 +45,30 @@ describe('Conversation', () => {
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,
timestamp: 555,
};
const actual = Conversation.createLastMessageUpdate(input);
assert.deepEqual(actual, expected);
});
});
context('for verified change message', () => {
it('should skip update', () => {
const input = {

View file

@ -26,13 +26,16 @@ export const createLastMessageUpdate = ({
}
const { type, expirationTimerUpdate } = lastMessage;
const isMessageHistoryUnsynced = type === 'message-history-unsynced';
const isVerifiedChangeMessage = type === 'verified-change';
const isExpireTimerUpdateFromSync = Boolean(
expirationTimerUpdate && expirationTimerUpdate.fromSync
);
const shouldUpdateTimestamp = Boolean(
!isVerifiedChangeMessage && !isExpireTimerUpdateFromSync
!isMessageHistoryUnsynced &&
!isVerifiedChangeMessage &&
!isExpireTimerUpdateFromSync
);
const newTimestamp = shouldUpdateTimestamp
? lastMessage.sent_at

View file

@ -2,7 +2,10 @@ import { Attachment } from './Attachment';
import { ContactType } from './Contact';
import { IndexableBoolean, IndexablePresence } from './IndexedDB';
export type Message = UserMessage | VerifiedChangeMessage;
export type Message =
| UserMessage
| VerifiedChangeMessage
| MessageHistoryUnsyncedMessage;
export type UserMessage = IncomingMessage | OutgoingMessage;
export type IncomingMessage = Readonly<
@ -65,6 +68,14 @@ export type VerifiedChangeMessage = Readonly<
ExpirationTimerUpdate
>;
export type MessageHistoryUnsyncedMessage = Readonly<
{
type: 'message-history-unsynced';
} & SharedMessageProperties &
MessageSchemaVersion5 &
ExpirationTimerUpdate
>;
type SharedMessageProperties = Readonly<{
conversationId: string;
sent_at: number;

View file

@ -16,6 +16,9 @@ export const initializeAttachmentMetadata = async (
if (message.type === 'verified-change') {
return message;
}
if (message.type === 'message-history-unsynced') {
return message;
}
if (message.messageTimer || message.isViewOnce) {
return message;
}

View file

@ -570,7 +570,7 @@
"rule": "jQuery-$(",
"path": "js/views/install_view.js",
"line": " this.$('#qr img').remove();",
"lineNumber": 135,
"lineNumber": 136,
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
@ -579,7 +579,7 @@
"rule": "jQuery-$(",
"path": "js/views/install_view.js",
"line": " this.$('#qr .container').show();",
"lineNumber": 137,
"lineNumber": 138,
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
@ -588,7 +588,7 @@
"rule": "jQuery-$(",
"path": "js/views/install_view.js",
"line": " if ($('#qr').length === 0) {",
"lineNumber": 141,
"lineNumber": 142,
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
@ -597,25 +597,25 @@
"rule": "jQuery-$(",
"path": "js/views/install_view.js",
"line": " this.$('#qr .container').hide();",
"lineNumber": 146,
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-$(",
"path": "js/views/install_view.js",
"line": " this.qr = new QRCode(this.$('#qr')[0]).makeCode(url);",
"lineNumber": 147,
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-$(",
"path": "js/views/install_view.js",
"line": " this.qr = new QRCode(this.$('#qr')[0]).makeCode(url);",
"lineNumber": 148,
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
},
{
"rule": "jQuery-$(",
"path": "js/views/install_view.js",
"line": " this.$('#qr').addClass('ready');",
"lineNumber": 149,
"lineNumber": 150,
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
@ -624,7 +624,7 @@
"rule": "jQuery-$(",
"path": "js/views/install_view.js",
"line": " this.$(DEVICE_NAME_SELECTOR).val(deviceName || window.getHostName());",
"lineNumber": 154,
"lineNumber": 155,
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
@ -633,7 +633,7 @@
"rule": "jQuery-$(",
"path": "js/views/install_view.js",
"line": " this.$('#link-phone').submit();",
"lineNumber": 159,
"lineNumber": 160,
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
@ -642,7 +642,7 @@
"rule": "jQuery-$(",
"path": "js/views/install_view.js",
"line": " this.$('#link-phone').submit(e => {",
"lineNumber": 169,
"lineNumber": 170,
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
@ -651,7 +651,7 @@
"rule": "jQuery-$(",
"path": "js/views/install_view.js",
"line": " let name = this.$(DEVICE_NAME_SELECTOR).val();",
"lineNumber": 173,
"lineNumber": 174,
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
@ -660,7 +660,7 @@
"rule": "jQuery-$(",
"path": "js/views/install_view.js",
"line": " this.$(DEVICE_NAME_SELECTOR).focus();",
"lineNumber": 176,
"lineNumber": 177,
"reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input"
@ -11812,7 +11812,7 @@
"rule": "jQuery-wrap(",
"path": "ts/shims/textsecure.ts",
"line": " wrap(",
"lineNumber": 63,
"lineNumber": 64,
"reasonCategory": "falseMatch",
"updated": "2020-02-07T19:52:28.522Z"
}