New React component: ConversationListItem, installed in left pane
When collecting a conversation's last message, we grab that message's status as well (if outgoing) and show it.
This commit is contained in:
parent
7e2d7b5e60
commit
675e34fc8d
17 changed files with 713 additions and 303 deletions
|
@ -78,6 +78,18 @@
|
|||
window.getInboxCollection = () => inboxCollection;
|
||||
|
||||
window.ConversationController = {
|
||||
markAsSelected(toSelect) {
|
||||
conversations.each(conversation => {
|
||||
const current = conversation.isSelected || false;
|
||||
const newValue = conversation.id === toSelect.id;
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
conversation.isSelected = newValue;
|
||||
if (current !== newValue) {
|
||||
conversation.trigger('change');
|
||||
}
|
||||
});
|
||||
},
|
||||
get(id) {
|
||||
if (!this._initialFetchComplete) {
|
||||
throw new Error(
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
Errors,
|
||||
Message,
|
||||
VisualAttachment,
|
||||
PhoneNumber,
|
||||
} = window.Signal.Types;
|
||||
const { upgradeMessageSchema, loadAttachmentData } = window.Signal.Migrations;
|
||||
|
||||
|
@ -110,6 +111,17 @@
|
|||
this.messageCollection.on('change:errors', this.handleMessageError, this);
|
||||
this.messageCollection.on('send-error', this.onMessageError, this);
|
||||
|
||||
const debouncedUpdateLastMessage = _.debounce(
|
||||
this.updateLastMessage.bind(this),
|
||||
1000
|
||||
);
|
||||
this.listenTo(
|
||||
this.messageCollection,
|
||||
'add remove',
|
||||
debouncedUpdateLastMessage
|
||||
);
|
||||
this.listenTo(this.model, 'newmessage', debouncedUpdateLastMessage);
|
||||
|
||||
this.on('change:avatar', this.updateAvatarUrl);
|
||||
this.on('change:profileAvatar', this.updateAvatarUrl);
|
||||
this.on('change:profileKey', this.onChangeProfileKey);
|
||||
|
@ -119,6 +131,7 @@
|
|||
this.on('newmessage', this.addSingleMessage);
|
||||
this.on('delivered', this.updateMessage);
|
||||
this.on('read', this.updateMessage);
|
||||
this.on('sent', this.updateLastMessage);
|
||||
this.on('expired', this.onExpired);
|
||||
this.listenTo(
|
||||
this.messageCollection,
|
||||
|
@ -158,6 +171,7 @@
|
|||
// Used to update existing messages when updated from out-of-band db access,
|
||||
// like read and delivery receipts.
|
||||
updateMessage(message) {
|
||||
this.updateLastMessage();
|
||||
this.messageCollection.add(message, { merge: true });
|
||||
},
|
||||
|
||||
|
@ -168,6 +182,43 @@
|
|||
return model;
|
||||
},
|
||||
|
||||
format() {
|
||||
const { format } = PhoneNumber;
|
||||
const regionCode = storage.get('regionCode');
|
||||
|
||||
const avatar = this.getAvatar();
|
||||
const color = this.getColor();
|
||||
|
||||
return {
|
||||
phoneNumber: format(this.id, {
|
||||
ourRegionCode: regionCode,
|
||||
}),
|
||||
color,
|
||||
avatarPath: avatar ? avatar.url : null,
|
||||
name: this.getName(),
|
||||
profileName: this.getProfileName(),
|
||||
title: this.getTitle(),
|
||||
};
|
||||
},
|
||||
getPropsForListItem() {
|
||||
const result = {
|
||||
...this.format(),
|
||||
|
||||
lastUpdated: this.get('timestamp'),
|
||||
hasUnread: Boolean(this.get('unreadCount')),
|
||||
isSelected: this.isSelected,
|
||||
|
||||
lastMessage: {
|
||||
status: this.get('lastMessageStatus'),
|
||||
text: this.get('lastMessage'),
|
||||
},
|
||||
|
||||
onClick: () => this.trigger('select', this),
|
||||
};
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
onMessageError() {
|
||||
this.updateVerified();
|
||||
},
|
||||
|
@ -850,6 +901,7 @@
|
|||
active_at: now,
|
||||
timestamp: now,
|
||||
lastMessage: message.getNotificationText(),
|
||||
lastMessageStatus: 'sending',
|
||||
});
|
||||
|
||||
const conversationType = this.get('type');
|
||||
|
@ -889,10 +941,14 @@
|
|||
const lastMessage = collection.at(0);
|
||||
|
||||
const lastMessageJSON = lastMessage ? lastMessage.toJSON() : null;
|
||||
const lastMessageStatus = lastMessage
|
||||
? lastMessage.getMessagePropStatus()
|
||||
: null;
|
||||
const lastMessageUpdate = Conversation.createLastMessageUpdate({
|
||||
currentLastMessageText: this.get('lastMessage') || null,
|
||||
currentTimestamp: this.get('timestamp') || null,
|
||||
lastMessage: lastMessageJSON,
|
||||
lastMessageStatus,
|
||||
lastMessageNotificationText: lastMessage
|
||||
? lastMessage.getNotificationText()
|
||||
: null,
|
||||
|
|
|
@ -422,6 +422,10 @@
|
|||
};
|
||||
},
|
||||
getMessagePropStatus() {
|
||||
if (!this.isOutgoing()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.hasErrors()) {
|
||||
return 'error';
|
||||
}
|
||||
|
@ -763,6 +767,7 @@
|
|||
sent: true,
|
||||
expirationStartTimestamp: now,
|
||||
});
|
||||
this.trigger('sent', this);
|
||||
this.sendSyncMessage();
|
||||
})
|
||||
.catch(result => {
|
||||
|
|
|
@ -19,6 +19,9 @@ const { ContactName } = require('../../ts/components/conversation/ContactName');
|
|||
const {
|
||||
ConversationHeader,
|
||||
} = require('../../ts/components/conversation/ConversationHeader');
|
||||
const {
|
||||
ConversationListItem,
|
||||
} = require('../../ts/components/ConversationListItem');
|
||||
const {
|
||||
EmbeddedContact,
|
||||
} = require('../../ts/components/conversation/EmbeddedContact');
|
||||
|
@ -151,6 +154,7 @@ exports.setup = (options = {}) => {
|
|||
ContactListItem,
|
||||
ContactName,
|
||||
ConversationHeader,
|
||||
ConversationListItem,
|
||||
EmbeddedContact,
|
||||
Emojify,
|
||||
GroupNotification,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* global Whisper, _, extension, Backbone, Mustache */
|
||||
/* global Whisper, Signal, Backbone */
|
||||
|
||||
// eslint-disable-next-line func-names
|
||||
(function() {
|
||||
|
@ -13,118 +13,38 @@
|
|||
return `conversation-list-item contact ${this.model.cid}`;
|
||||
},
|
||||
templateName: 'conversation-preview',
|
||||
events: {
|
||||
click: 'select',
|
||||
},
|
||||
initialize() {
|
||||
// auto update
|
||||
this.listenTo(
|
||||
this.model,
|
||||
'change',
|
||||
_.debounce(this.render.bind(this), 1000)
|
||||
);
|
||||
this.listenTo(this.model, 'destroy', this.remove); // auto update
|
||||
this.listenTo(this.model, 'opened', this.markSelected); // auto update
|
||||
|
||||
const updateLastMessage = _.debounce(
|
||||
this.model.updateLastMessage.bind(this.model),
|
||||
1000
|
||||
);
|
||||
this.listenTo(
|
||||
this.model.messageCollection,
|
||||
'add remove',
|
||||
updateLastMessage
|
||||
);
|
||||
this.listenTo(this.model, 'newmessage', updateLastMessage);
|
||||
|
||||
extension.windows.onClosed(() => {
|
||||
this.stopListening();
|
||||
});
|
||||
this.timeStampView = new Whisper.TimestampView({ brief: true });
|
||||
this.listenTo(this.model, 'destroy', this.remove);
|
||||
this.model.updateLastMessage();
|
||||
},
|
||||
|
||||
markSelected() {
|
||||
this.$el
|
||||
.addClass('selected')
|
||||
.siblings('.selected')
|
||||
.removeClass('selected');
|
||||
},
|
||||
|
||||
select() {
|
||||
this.markSelected();
|
||||
this.$el.trigger('select', this.model);
|
||||
},
|
||||
|
||||
remove() {
|
||||
if (this.nameView) {
|
||||
this.nameView.remove();
|
||||
this.nameView = null;
|
||||
}
|
||||
if (this.bodyView) {
|
||||
this.bodyView.remove();
|
||||
this.bodyView = null;
|
||||
if (this.childView) {
|
||||
this.childView.remove();
|
||||
this.childView = null;
|
||||
}
|
||||
Backbone.View.prototype.remove.call(this);
|
||||
},
|
||||
|
||||
render() {
|
||||
const lastMessage = this.model.get('lastMessage');
|
||||
|
||||
this.$el.html(
|
||||
Mustache.render(
|
||||
_.result(this, 'template', ''),
|
||||
{
|
||||
last_message: Boolean(lastMessage),
|
||||
last_message_timestamp: this.model.get('timestamp'),
|
||||
number: this.model.getNumber(),
|
||||
avatar: this.model.getAvatar(),
|
||||
unreadCount: this.model.get('unreadCount'),
|
||||
},
|
||||
this.render_partials()
|
||||
)
|
||||
);
|
||||
this.timeStampView.setElement(this.$('.last-timestamp'));
|
||||
this.timeStampView.update();
|
||||
|
||||
if (this.nameView) {
|
||||
this.nameView.remove();
|
||||
this.nameView = null;
|
||||
if (this.childView) {
|
||||
this.childView.remove();
|
||||
this.childView = null;
|
||||
}
|
||||
this.nameView = new Whisper.ReactWrapperView({
|
||||
className: 'name-wrapper',
|
||||
Component: window.Signal.Components.ContactName,
|
||||
props: {
|
||||
phoneNumber: this.model.getNumber(),
|
||||
name: this.model.getName(),
|
||||
profileName: this.model.getProfileName(),
|
||||
},
|
||||
|
||||
const props = this.model.getPropsForListItem();
|
||||
this.childView = new Whisper.ReactWrapperView({
|
||||
className: 'list-item-wrapper',
|
||||
Component: Signal.Components.ConversationListItem,
|
||||
props,
|
||||
});
|
||||
this.$('.name').append(this.nameView.el);
|
||||
|
||||
if (lastMessage) {
|
||||
if (this.bodyView) {
|
||||
this.bodyView.remove();
|
||||
this.bodyView = null;
|
||||
}
|
||||
this.bodyView = new Whisper.ReactWrapperView({
|
||||
className: 'body-wrapper',
|
||||
Component: window.Signal.Components.MessageBody,
|
||||
props: {
|
||||
text: lastMessage,
|
||||
disableJumbomoji: true,
|
||||
disableLinks: true,
|
||||
},
|
||||
});
|
||||
this.$('.last-message').append(this.bodyView.el);
|
||||
}
|
||||
const update = () =>
|
||||
this.childView.update(this.model.getPropsForListItem());
|
||||
|
||||
const unread = this.model.get('unreadCount');
|
||||
if (unread > 0) {
|
||||
this.$el.addClass('unread');
|
||||
} else {
|
||||
this.$el.removeClass('unread');
|
||||
}
|
||||
this.listenTo(this.model, 'change', update);
|
||||
|
||||
this.$el.append(this.childView.el);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
|
|
@ -107,11 +107,12 @@
|
|||
|
||||
const inboxCollection = getInboxCollection();
|
||||
|
||||
inboxCollection.on('messageError', () => {
|
||||
this.listenTo(inboxCollection, 'messageError', () => {
|
||||
if (this.networkStatusView) {
|
||||
this.networkStatusView.render();
|
||||
}
|
||||
});
|
||||
this.listenTo(inboxCollection, 'select', this.openConversation);
|
||||
|
||||
this.inboxListView = new Whisper.ConversationListView({
|
||||
el: this.$('.inbox'),
|
||||
|
@ -144,11 +145,7 @@
|
|||
this.searchView.$el.show();
|
||||
this.inboxListView.$el.hide();
|
||||
});
|
||||
this.listenTo(
|
||||
this.searchView,
|
||||
'open',
|
||||
this.openConversation.bind(this, null)
|
||||
);
|
||||
this.listenTo(this.searchView, 'open', this.openConversation);
|
||||
|
||||
this.networkStatusView = new Whisper.NetworkStatusView();
|
||||
this.$el
|
||||
|
@ -175,7 +172,6 @@
|
|||
click: 'onClick',
|
||||
'click #header': 'focusHeader',
|
||||
'click .conversation': 'focusConversation',
|
||||
'select .gutter .conversation-list-item': 'openConversation',
|
||||
'input input.search': 'filterContacts',
|
||||
},
|
||||
startConnectionListener() {
|
||||
|
@ -250,7 +246,9 @@
|
|||
input.removeClass('active');
|
||||
}
|
||||
},
|
||||
openConversation(e, conversation) {
|
||||
openConversation(conversation) {
|
||||
ConversationController.markAsSelected(conversation);
|
||||
|
||||
this.searchView.hideHints();
|
||||
if (conversation) {
|
||||
this.conversation_stack.open(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue