diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index 6597d0058..387c300bf 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -2252,12 +2252,18 @@ const existing = this.model.get('quotedMessageId'); if (existing !== messageId) { - const timestamp = messageId ? Date.now() : null; this.model.set({ quotedMessageId: messageId, - draftTimestamp: timestamp, - timestamp, }); + + if (messageId) { + const timestamp = Date.now(); + this.model.set({ + draftTimestamp: timestamp, + timestamp, + }); + } + await this.saveModel(); } diff --git a/ts/components/conversation/Timeline.tsx b/ts/components/conversation/Timeline.tsx index e1e9c0391..c0211a3e2 100644 --- a/ts/components/conversation/Timeline.tsx +++ b/ts/components/conversation/Timeline.tsx @@ -494,6 +494,10 @@ export class Timeline extends React.PureComponent { renderTypingBubble, } = this.props; + const styleWithWidth = { + ...style, + width: `${this.mostRecentWidth}px`, + }; const row = index; const oldestUnreadRow = this.getLastSeenIndicatorRow(); const typingBubbleRow = this.getTypingBubbleRow(); @@ -501,13 +505,13 @@ export class Timeline extends React.PureComponent { if (!haveOldest && row === 0) { rowContents = ( -
+
{renderLoadingRow(id)}
); } else if (oldestUnreadRow === row) { rowContents = ( -
+
{renderLastSeenIndicator(id)}
); @@ -516,7 +520,7 @@ export class Timeline extends React.PureComponent {
{renderTypingBubble(id)}
@@ -534,7 +538,7 @@ export class Timeline extends React.PureComponent { id={messageId} data-row={row} className="module-timeline__message-container" - style={style} + style={styleWithWidth} > {renderItem(messageId, this.props)}
@@ -715,6 +719,7 @@ export class Timeline extends React.PureComponent { this.updateWithVisibleRows(forceFocus); }; + // tslint:disable-next-line cyclomatic-complexity public componentDidUpdate(prevProps: Props) { const { id, @@ -763,35 +768,48 @@ export class Timeline extends React.PureComponent { const oldFirstIndex = 0; const oldFirstId = prevProps.items[oldFirstIndex]; - const newIndex = items.findIndex(item => item === oldFirstId); - if (newIndex < 0) { + const newFirstIndex = items.findIndex(item => item === oldFirstId); + if (newFirstIndex < 0) { this.resizeAll(); return; } - const newRow = this.fromItemIndexToRow(newIndex); + const newRow = this.fromItemIndexToRow(newFirstIndex); + const delta = newFirstIndex - oldFirstIndex; + if (delta > 0) { + // We're loading more new messages at the top; we want to stay at the top + this.resizeAll(); + this.setState({ oneTimeScrollRow: newRow }); + + return; + } + } + + // We continue on after our atTop check; because if we're not loading new messages + // we still have to check for all the other situations which might require a + // resize. + + const oldLastIndex = prevProps.items.length - 1; + const oldLastId = prevProps.items[oldLastIndex]; + + const newLastIndex = items.findIndex(item => item === oldLastId); + if (newLastIndex < 0) { this.resizeAll(); - this.setState({ oneTimeScrollRow: newRow }); - } else { - const oldLastIndex = prevProps.items.length - 1; - const oldLastId = prevProps.items[oldLastIndex]; - const newIndex = items.findIndex(item => item === oldLastId); - if (newIndex < 0) { - this.resizeAll(); + return; + } - return; - } + const indexDelta = newLastIndex - oldLastIndex; - const indexDelta = newIndex - oldLastIndex; - - // If we've just added to the end of the list, then the index of the last id's - // index won't have changed, and we can rely on List's detection that items is - // different for the necessary re-render. - if (indexDelta !== 0) { - this.resizeAll(); - } + // If we've just added to the end of the list, then the index of the last id's + // index won't have changed, and we can rely on List's detection that items is + // different for the necessary re-render. + if (indexDelta !== 0) { + this.resizeAll(); + } else if (typingContact && prevProps.typingContact) { + // The last row will be off, because it was previously the typing indicator + this.resizeAll(); } } else if (messageHeightChanges) { this.resizeAll(); diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index bf367875f..898eadba5 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -857,6 +857,30 @@ export function reducer( totalUnread, } = existingConversation.metrics; + if (messages.length < 1) { + return state; + } + + const lookup = fromPairs( + existingConversation.messageIds.map(id => [id, messagesLookup[id]]) + ); + messages.forEach(message => { + lookup[message.id] = message; + }); + + const sorted = orderBy(values(lookup), ['received_at'], ['ASC']); + const messageIds = sorted.map(message => message.id); + + const first = sorted[0]; + const last = sorted[sorted.length - 1]; + + if (!newest) { + newest = pick(first, ['id', 'received_at']); + } + if (!oldest) { + oldest = pick(last, ['id', 'received_at']); + } + const existingTotal = existingConversation.messageIds.length; if (isNewMessage && existingTotal > 0) { const lastMessageId = existingConversation.messageIds[existingTotal - 1]; @@ -869,27 +893,6 @@ export function reducer( } } - const newIds = messages.map(message => message.id); - const newChanges = intersection(newIds, existingConversation.messageIds); - const heightChangeMessageIds = uniq([ - ...newChanges, - ...existingConversation.heightChangeMessageIds, - ]); - - const lookup = fromPairs( - existingConversation.messageIds.map(id => [id, messagesLookup[id]]) - ); - - messages.forEach(message => { - lookup[message.id] = message; - }); - - const sorted = orderBy(values(lookup), ['received_at'], ['ASC']); - const messageIds = sorted.map(message => message.id); - - const first = sorted[0]; - const last = sorted.length > 0 ? sorted[sorted.length - 1] : null; - if (first && oldest && first.received_at < oldest.received_at) { oldest = pick(first, ['id', 'received_at']); } @@ -897,6 +900,7 @@ export function reducer( newest = pick(last, ['id', 'received_at']); } + const newIds = messages.map(message => message.id); const newMessageIds = difference(newIds, existingConversation.messageIds); const { isNearBottom } = existingConversation; @@ -924,6 +928,12 @@ export function reducer( totalUnread = (totalUnread || 0) + newUnread; } + const changedIds = intersection(newIds, existingConversation.messageIds); + const heightChangeMessageIds = uniq([ + ...changedIds, + ...existingConversation.heightChangeMessageIds, + ]); + return { ...state, messagesLookup: { diff --git a/ts/state/selectors/conversations.ts b/ts/state/selectors/conversations.ts index 26904d304..bed506eb7 100644 --- a/ts/state/selectors/conversations.ts +++ b/ts/state/selectors/conversations.ts @@ -1,4 +1,5 @@ import memoizee from 'memoizee'; +import { isNumber } from 'lodash'; import { createSelector } from 'reselect'; import { format } from '../../types/PhoneNumber'; @@ -388,7 +389,7 @@ export function _conversationMessagesSelector( items, messageHeightChanges, oldestUnreadIndex: - oldestUnreadIndex && oldestUnreadIndex >= 0 + isNumber(oldestUnreadIndex) && oldestUnreadIndex >= 0 ? oldestUnreadIndex : undefined, resetCounter,