diff --git a/_locales/en/messages.json b/_locales/en/messages.json index c70e06a07..556d299bb 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -187,6 +187,27 @@ } } }, + "archivedConversations": { + "message": "Archived Conversations", + "description": + "Shown in place of the search box when showing archived conversation list" + }, + "archiveHelperText": { + "message": + "These conversations are archived and will only appear in the Inbox if new messages are received.", + "description": + "Shown at the top of the archived converations list in the left pane" + }, + "archiveConversation": { + "message": "Archive Conversation", + "description": + "Shown in menu for conversation, and moves conversation out of main conversation list" + }, + "moveConversationToInbox": { + "message": "Move Converstion to Inbox", + "description": + "Undoes Archive Conversation action, and moves archived conversation back to the main conversation list" + }, "chooseDirectory": { "message": "Choose folder", "description": "Button to allow the user to find a folder on disk" diff --git a/js/models/conversations.js b/js/models/conversations.js index fcca64564..ada59edc0 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -312,6 +312,7 @@ const result = { id: this.id, + isArchived: this.get('isArchived'), activeAt: this.get('active_at'), avatarPath: this.getAvatarPath(), color, @@ -889,6 +890,7 @@ lastMessageStatus: 'sending', active_at: now, timestamp: now, + isArchived: false, }); await window.Signal.Data.updateConversation(this.id, this.attributes, { Conversation: Whisper.Conversation, @@ -1170,6 +1172,13 @@ } }, + async setArchived(isArchived) { + this.set({ isArchived }); + await window.Signal.Data.updateConversation(this.id, this.attributes, { + Conversation: Whisper.Conversation, + }); + }, + async updateExpirationTimer( providedExpireTimer, providedSource, diff --git a/js/models/messages.js b/js/models/messages.js index cdd662681..53cea9e3d 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -1645,10 +1645,10 @@ c.onReadMessage(message); } } else { - conversation.set( - 'unreadCount', - conversation.get('unreadCount') + 1 - ); + conversation.set({ + unreadCount: conversation.get('unreadCount') + 1, + isArchived: false, + }); } } diff --git a/js/notifications.js b/js/notifications.js index 6aaff5257..2860e8d2c 100644 --- a/js/notifications.js +++ b/js/notifications.js @@ -1,7 +1,6 @@ /* global Signal:false */ /* global Backbone: false */ -/* global ConversationController: false */ /* global drawAttention: false */ /* global i18n: false */ /* global isFocused: false */ diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index 963b2105c..e2cee8295 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -185,9 +185,12 @@ profileName: this.model.getProfileName(), color: this.model.getColor(), avatarPath: this.model.getAvatarPath(), + isVerified: this.model.isVerified(), isMe: this.model.isMe(), isGroup: !this.model.isPrivate(), + isArchived: this.model.get('isArchived'), + expirationSettingName, showBackButton: Boolean(this.panels && this.panels.length), timerOptions: Whisper.ExpirationTimerOptions.map(item => ({ @@ -217,6 +220,14 @@ this.resetPanel(); this.updateHeader(); }, + + onArchive: () => { + this.unload(); + this.model.setArchived(true); + }, + onMoveToInbox: () => { + this.model.setArchived(false); + }, }; }; this.titleView = new Whisper.ReactWrapperView({ diff --git a/js/views/inbox_view.js b/js/views/inbox_view.js index 9891be3ef..0370e156b 100644 --- a/js/views/inbox_view.js +++ b/js/views/inbox_view.js @@ -220,7 +220,7 @@ window.location.reload(); }, async openConversation(id, messageId) { - const conversation = await window.ConversationController.getOrCreateAndWait( + const conversation = await ConversationController.getOrCreateAndWait( id, 'private' ); diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss index 048fb1c59..61e607d76 100644 --- a/stylesheets/_modules.scss +++ b/stylesheets/_modules.scss @@ -2986,15 +2986,74 @@ flex-grow: 0; } +.module-left-pane__archive-header { + height: 48px; + width: 100%; + + display: inline-flex; + flex-direction: row; + align-items: center; + + border-bottom: 1px solid $color-gray-15; +} + +.module-left-pane__to-inbox-button { + margin-left: 2px; + + width: 35px; + height: 35px; + + cursor: pointer; + @include color-svg('../images/back.svg', $color-gray-60); +} + +.module-left-pane__archive-header-text { + color: $color-gray-90; + font-size: 16px; + font-weight: 300px; +} + .module-left-pane__list { flex-grow: 1; flex-shrink: 1; } +.module-left-pane__archive-helper-text { + padding: 1em; + font-size: 12px; + color: $color-gray-60; + background-color: $color-gray-05; +} + .module-left-pane__virtual-list { outline: none; } +.module-left-pane__archived-button { + font-size: 14px; + height: 64px; + line-height: 64px; + text-align: center; + font-weight: 300; + color: $color-gray-60; + + cursor: pointer; + &:hover { + background-color: $color-gray-05; + } +} + +.module-left-pane__archived-button__archived-count { + font-size: 12px; + font-weight: 300; + color: $color-gray-60; + background-color: $color-gray-05; + padding: 6px; + padding-top: 1px; + padding-bottom: 1px; + border-radius: 10px; +} + // Module: Start New Conversation .module-start-new-conversation { diff --git a/stylesheets/_theme_dark.scss b/stylesheets/_theme_dark.scss index 007eb6e66..b4285a828 100644 --- a/stylesheets/_theme_dark.scss +++ b/stylesheets/_theme_dark.scss @@ -1346,7 +1346,7 @@ body.dark-theme { } .module-main-header__search__cancel-icon { - @include color-svg('../images/x.svg', $color-gray-25); + @include color-svg('../images/x-16.svg', $color-gray-25); } // Module: Image @@ -1382,7 +1382,7 @@ body.dark-theme { } .module-attachments__close-button { - @include color-svg('../images/x.svg', $color-gray-45); + @include color-svg('../images/x-16.svg', $color-gray-45); } // Module: Staged Generic Attachment @@ -1482,7 +1482,7 @@ body.dark-theme { } .module-caption-editor__close-button { - @include color-svg('../images/x.svg', $color-white); + @include color-svg('../images/x-16.svg', $color-white); } .module-caption-editor__media-container { @@ -1553,6 +1553,35 @@ body.dark-theme { border-right: 1px solid $color-gray-75; } + .module-left-pane__archive-header { + border-bottom: 1px solid $color-gray-75; + } + + .module-left-pane__to-inbox-button { + background-color: $color-gray-25; + } + + .module-left-pane__archive-header-text { + color: $color-gray-05; + } + + .module-left-pane__archive-helper-text { + color: $color-gray-25; + background-color: $color-gray-75; + } + + .module-left-pane__archived-button { + color: $color-gray-25; + &:hover { + background-color: $color-gray-75; + } + } + + .module-left-pane__archived-button__archived-count { + color: $color-gray-25; + background-color: $color-gray-75; + } + // Module: Start New Conversation .module-start-new-conversation { diff --git a/ts/components/LeftPane.md b/ts/components/LeftPane.md index a9fd7f3d8..11fb6c978 100644 --- a/ts/components/LeftPane.md +++ b/ts/components/LeftPane.md @@ -129,8 +129,14 @@ window.searchResults.messages = [ console.log('openConversation', result)} - openMessage={result => console.log('onClickMessage', result)} + startNewConversation={(query, options) => + console.log('startNewConversation', query, options) + } + openConversationInternal={(id, messageId) => + console.log('openConversation', id, messageId) + } + showArchivedConversations={() => console.log('showArchivedConversations')} + showInbox={() => console.log('showInbox')} renderMainHeader={() => ( console.log('openConversation', result)} - openMessage={result => console.log('onClickMessage', result)} + archivedConversations={[]} + startNewConversation={(query, options) => + console.log('startNewConversation', query, options) + } + openConversationInternal={(id, messageId) => + console.log('openConversation', id, messageId) + } + showArchivedConversations={() => console.log('showArchivedConversations')} + showInbox={() => console.log('showInbox')} + renderMainHeader={() => ( + console.log('search', result)} + updateSearch={result => console.log('updateSearch', result)} + clearSearch={result => console.log('clearSearch', result)} + i18n={util.i18n} + /> + )} + i18n={util.i18n} + /> + +``` + +#### Showing inbox, with some archived + +```jsx + + + console.log('startNewConversation', query, options) + } + openConversationInternal={(id, messageId) => + console.log('openConversation', id, messageId) + } + showArchivedConversations={() => console.log('showArchivedConversations')} + showInbox={() => console.log('showInbox')} + renderMainHeader={() => ( + console.log('search', result)} + updateSearch={result => console.log('updateSearch', result)} + clearSearch={result => console.log('clearSearch', result)} + i18n={util.i18n} + /> + )} + i18n={util.i18n} + /> + +``` + +#### Showing archived conversations + +```jsx + + + console.log('startNewConversation', query, options) + } + openConversationInternal={(id, messageId) => + console.log('openConversation', id, messageId) + } + showArchivedConversations={() => console.log('showArchivedConversations')} + showInbox={() => console.log('showInbox')} renderMainHeader={() => ( ; + archivedConversations?: Array; searchResults?: SearchResultsProps; + showArchived?: boolean; + i18n: LocalizerType; // Action Creators - startNewConversation: () => void; + startNewConversation: ( + query: string, + options: { regionCode: string } + ) => void; openConversationInternal: (id: string, messageId?: string) => void; + showArchivedConversations: () => void; + showInbox: () => void; // Render Props renderMainHeader: () => JSX.Element; } // from https://github.com/bvaughn/react-virtualized/blob/fb3484ed5dcc41bffae8eab029126c0fb8f7abc0/source/List/types.js#L5 -type RowRendererParams = { +type RowRendererParamsType = { index: number; isScrolling: boolean; isVisible: boolean; @@ -35,12 +43,51 @@ type RowRendererParams = { }; export class LeftPane extends React.Component { - public renderRow = ({ index, key, style }: RowRendererParams) => { - const { conversations, i18n, openConversationInternal } = this.props; - if (!conversations) { - return null; + public listRef: React.RefObject = React.createRef(); + + public scrollToTop() { + if (this.listRef && this.listRef.current) { + const { current } = this.listRef; + current.scrollToRow(0); } - const conversation = conversations[index]; + } + + public componentDidUpdate(prevProps: Props) { + const { showArchived, searchResults } = this.props; + + const isNotShowingSearchResults = !searchResults; + const hasArchiveViewChanged = showArchived !== prevProps.showArchived; + + if (isNotShowingSearchResults && hasArchiveViewChanged) { + this.scrollToTop(); + } + } + + public renderRow = ({ + index, + key, + style, + }: RowRendererParamsType): JSX.Element => { + const { + archivedConversations, + conversations, + i18n, + openConversationInternal, + showArchived, + } = this.props; + if (!conversations || !archivedConversations) { + throw new Error( + 'renderRow: Tried to render without conversations or archivedConversations' + ); + } + + if (!showArchived && index === conversations.length) { + return this.renderArchivedButton({ key, style }); + } + + const conversation = showArchived + ? archivedConversations[index] + : conversations[index]; return ( { ); }; - public renderList() { + public renderArchivedButton({ + key, + style, + }: { + key: string; + style: Object; + }): JSX.Element { const { + archivedConversations, + i18n, + showArchivedConversations, + } = this.props; + + if (!archivedConversations || !archivedConversations.length) { + throw new Error( + 'renderArchivedButton: Tried to render without archivedConversations' + ); + } + + return ( +
+ {i18n('archivedConversations')}{' '} + + {archivedConversations.length} + +
+ ); + } + + public renderList(): JSX.Element { + const { + archivedConversations, i18n, conversations, openConversationInternal, startNewConversation, searchResults, + showArchived, } = this.props; if (searchResults) { @@ -73,22 +157,35 @@ export class LeftPane extends React.Component { ); } - if (!conversations || !conversations.length) { - return null; + if (!conversations || !archivedConversations) { + throw new Error( + 'render: must provided conversations and archivedConverstions if no search results are provided' + ); } + // That extra 1 element added to the list is the 'archived converastions' button + const length = showArchived + ? archivedConversations.length + : conversations.length + (archivedConversations.length ? 1 : 0); + // Note: conversations is not a known prop for List, but it is required to ensure that // it re-renders when our conversation data changes. Otherwise it would just render // on startup and scroll. return (
+ {showArchived ? ( +
+ {i18n('archiveHelperText')} +
+ ) : null} {({ height, width }) => ( { ); } - public render() { - const { renderMainHeader } = this.props; + public renderArchivedHeader(): JSX.Element { + const { i18n, showInbox } = this.props; + + return ( +
+
+
+ {i18n('archivedConversations')} +
+
+ ); + } + + public render(): JSX.Element { + const { renderMainHeader, showArchived } = this.props; return (
-
{renderMainHeader()}
+
+ {showArchived ? this.renderArchivedHeader() : renderMainHeader()} +
{this.renderList()}
); diff --git a/ts/components/SearchResults.md b/ts/components/SearchResults.md index 8d066bb12..e1c8e1ae5 100644 --- a/ts/components/SearchResults.md +++ b/ts/components/SearchResults.md @@ -113,7 +113,9 @@ window.searchResults.messages = [ i18n={util.i18n} onClickMessage={id => console.log('onClickMessage', id)} onClickConversation={id => console.log('onClickConversation', id)} - onStartNewConversation={() => console.log('onStartNewConversation')} + onStartNewConversation={(query, options) => + console.log('onStartNewConversation', query, options) + } /> ; ``` @@ -131,7 +133,9 @@ window.searchResults.messages = [ i18n={util.i18n} onClickMessage={id => console.log('onClickMessage', id)} onClickConversation={id => console.log('onClickConversation', id)} - onStartNewConversation={() => console.log('onStartNewConversation')} + onStartNewConversation={(query, options) => + console.log('onStartNewConversation', query, options) + } /> ``` @@ -147,7 +151,9 @@ window.searchResults.messages = [ i18n={util.i18n} onClickMessage={id => console.log('onClickMessage', id)} onClickConversation={id => console.log('onClickConversation', id)} - onStartNewConversation={() => console.log('onStartNewConversation')} + onStartNewConversation={(query, options) => + console.log('onStartNewConversation', query, options) + } /> ``` @@ -163,7 +169,9 @@ window.searchResults.messages = [ i18n={util.i18n} onClickMessage={id => console.log('onClickMessage', id)} onClickConversation={id => console.log('onClickConversation', id)} - onStartNewConversation={() => console.log('onStartNewConversation')} + onStartNewConversation={(query, options) => + console.log('onStartNewConversation', query, options) + } /> ``` diff --git a/ts/components/SearchResults.tsx b/ts/components/SearchResults.tsx index 4a03f2b96..5314c108a 100644 --- a/ts/components/SearchResults.tsx +++ b/ts/components/SearchResults.tsx @@ -16,6 +16,7 @@ export type PropsData = { conversations: Array; hideMessagesHeader: boolean; messages: Array; + regionCode: string; searchTerm: string; showStartNewConversation: boolean; }; @@ -23,12 +24,21 @@ export type PropsData = { type PropsHousekeeping = { i18n: LocalizerType; openConversation: (id: string, messageId?: string) => void; - startNewConversation: (id: string) => void; + startNewConversation: ( + query: string, + options: { regionCode: string } + ) => void; }; type Props = PropsData & PropsHousekeeping; export class SearchResults extends React.Component { + public handleStartNewConversation = () => { + const { regionCode, searchTerm, startNewConversation } = this.props; + + startNewConversation(searchTerm, { regionCode }); + }; + public render() { const { conversations, @@ -37,7 +47,6 @@ export class SearchResults extends React.Component { i18n, messages, openConversation, - startNewConversation, searchTerm, showStartNewConversation, } = this.props; @@ -62,7 +71,7 @@ export class SearchResults extends React.Component { ) : null} {haveConversations ? ( diff --git a/ts/components/StartNewConversation.tsx b/ts/components/StartNewConversation.tsx index c547434cf..40b11b008 100644 --- a/ts/components/StartNewConversation.tsx +++ b/ts/components/StartNewConversation.tsx @@ -7,7 +7,7 @@ import { LocalizerType } from '../types/Util'; export interface Props { phoneNumber: string; i18n: LocalizerType; - onClick: (id: string) => void; + onClick: () => void; } export class StartNewConversation extends React.PureComponent { @@ -18,9 +18,7 @@ export class StartNewConversation extends React.PureComponent {
{ - onClick(phoneNumber); - }} + onClick={onClick} > ; @@ -39,6 +41,11 @@ interface Props { onShowAllMedia: () => void; onShowGroupMembers: () => void; onGoBack: () => void; + + onArchive: () => void; + onMoveToInbox: () => void; + + i18n: LocalizerType; } export class ConversationHeader extends React.Component { @@ -184,12 +191,15 @@ export class ConversationHeader extends React.Component { i18n, isMe, isGroup, + isArchived, onDeleteMessages, onResetSession, onSetDisappearingMessages, onShowAllMedia, onShowGroupMembers, onShowSafetyNumber, + onArchive, + onMoveToInbox, timerOptions, } = this.props; @@ -223,6 +233,13 @@ export class ConversationHeader extends React.Component { {!isGroup ? ( {i18n('resetSession')} ) : null} + {isArchived ? ( + + {i18n('moveConversationToInbox')} + + ) : ( + {i18n('archiveConversation')} + )} {i18n('deleteMessages')} ); diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index 3271b202e..2cbccfae5 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -34,6 +34,7 @@ export type MessageType = { export type ConversationType = { id: string; name?: string; + isArchived: boolean; activeAt?: number; timestamp: number; lastMessage?: { @@ -55,6 +56,7 @@ export type ConversationLookupType = { export type ConversationsStateType = { conversationLookup: ConversationLookupType; selectedConversation?: string; + showArchived: boolean; }; // Actions @@ -97,6 +99,14 @@ export type SelectedConversationChangedActionType = { messageId?: string; }; }; +type ShowInboxActionType = { + type: 'SHOW_INBOX'; + payload: null; +}; +type ShowArchivedConversationsActionType = { + type: 'SHOW_ARCHIVED_CONVERSATIONS'; + payload: null; +}; export type ConversationActionType = | ConversationAddedActionType @@ -104,7 +114,11 @@ export type ConversationActionType = | ConversationRemovedActionType | RemoveAllConversationsActionType | MessageExpiredActionType - | SelectedConversationChangedActionType; + | SelectedConversationChangedActionType + | MessageExpiredActionType + | SelectedConversationChangedActionType + | ShowInboxActionType + | ShowArchivedConversationsActionType; // Action Creators @@ -116,6 +130,8 @@ export const actions = { messageExpired, openConversationInternal, openConversationExternal, + showInbox, + showArchivedConversations, }; function conversationAdded( @@ -156,6 +172,7 @@ function removeAllConversations(): RemoveAllConversationsActionType { payload: null, }; } + function messageExpired( id: string, conversationId: string @@ -196,11 +213,25 @@ function openConversationExternal( }; } +function showInbox() { + return { + type: 'SHOW_INBOX', + payload: null, + }; +} +function showArchivedConversations() { + return { + type: 'SHOW_ARCHIVED_CONVERSATIONS', + payload: null, + }; +} + // Reducer function getEmptyState(): ConversationsStateType { return { conversationLookup: {}, + showArchived: false, }; } @@ -225,27 +256,38 @@ export function reducer( }, }; } - if (action.type === 'SELECTED_CONVERSATION_CHANGED') { - const { payload } = action; - const { id } = payload; - - return { - ...state, - selectedConversation: id, - }; - } if (action.type === 'CONVERSATION_CHANGED') { const { payload } = action; const { id, data } = payload; const { conversationLookup } = state; + let showArchived = state.showArchived; + let selectedConversation = state.selectedConversation; + + const existing = conversationLookup[id]; // In the change case we only modify the lookup if we already had that conversation - if (!conversationLookup[id]) { + if (!existing) { return state; } + if (selectedConversation === id) { + // Archived -> Inbox: we go back to the normal inbox view + if (existing.isArchived && !data.isArchived) { + showArchived = false; + } + // Inbox -> Archived: no conversation is selected + // Note: With today's stacked converastions architecture, this can result in weird + // behavior - no selected conversation in the left pane, but a conversation show + // in the right pane. + if (!existing.isArchived && data.isArchived) { + selectedConversation = undefined; + } + } + return { ...state, + selectedConversation, + showArchived, conversationLookup: { ...conversationLookup, [id]: data, @@ -268,6 +310,27 @@ export function reducer( if (action.type === 'MESSAGE_EXPIRED') { // noop - for now this is only important for search } + if (action.type === 'SELECTED_CONVERSATION_CHANGED') { + const { payload } = action; + const { id } = payload; + + return { + ...state, + selectedConversation: id, + }; + } + if (action.type === 'SHOW_INBOX') { + return { + ...state, + showArchived: false, + }; + } + if (action.type === 'SHOW_ARCHIVED_CONVERSATIONS') { + return { + ...state, + showArchived: true, + }; + } return state; } diff --git a/ts/state/selectors/conversations.ts b/ts/state/selectors/conversations.ts index 69efbeaab..2d255f7da 100644 --- a/ts/state/selectors/conversations.ts +++ b/ts/state/selectors/conversations.ts @@ -1,4 +1,3 @@ -import { compact } from 'lodash'; import { createSelector } from 'reselect'; import { format } from '../../types/PhoneNumber'; @@ -29,6 +28,13 @@ export const getSelectedConversation = createSelector( } ); +export const getShowArchived = createSelector( + getConversations, + (state: ConversationsStateType): boolean => { + return Boolean(state.showArchived); + } +); + function getConversationTitle( conversation: ConversationType, options: { i18n: LocalizerType; ourRegionCode: string } @@ -83,37 +89,49 @@ export const getConversationComparator = createSelector( _getConversationComparator ); -export const _getLeftPaneList = ( +export const _getLeftPaneLists = ( lookup: ConversationLookupType, comparator: (left: ConversationType, right: ConversationType) => number, selectedConversation?: string -): Array => { +): { + conversations: Array; + archivedConversations: Array; +} => { const values = Object.values(lookup); - const filtered = compact( - values.map(conversation => { - if (!conversation.activeAt) { - return null; - } + const sorted = values.sort(comparator); - if (selectedConversation === conversation.id) { - return { - ...conversation, - isSelected: true, - }; - } + const conversations: Array = []; + const archivedConversations: Array = []; - return conversation; - }) - ); + const max = sorted.length; + for (let i = 0; i < max; i += 1) { + let conversation = sorted[i]; + if (!conversation.activeAt) { + continue; + } - return filtered.sort(comparator); + if (selectedConversation === conversation.id) { + conversation = { + ...conversation, + isSelected: true, + }; + } + + if (conversation.isArchived) { + archivedConversations.push(conversation); + } else { + conversations.push(conversation); + } + } + + return { conversations, archivedConversations }; }; -export const getLeftPaneList = createSelector( +export const getLeftPaneLists = createSelector( getConversationLookup, getConversationComparator, getSelectedConversation, - _getLeftPaneList + _getLeftPaneLists ); export const getMe = createSelector( diff --git a/ts/state/selectors/search.ts b/ts/state/selectors/search.ts index cfbff804a..f638006b8 100644 --- a/ts/state/selectors/search.ts +++ b/ts/state/selectors/search.ts @@ -2,14 +2,16 @@ import { compact } from 'lodash'; import { createSelector } from 'reselect'; import { StateType } from '../reducer'; -import { SearchStateType } from '../ducks/search'; +import { SearchStateType } from '../ducks/search'; import { getConversationLookup, getSelectedConversation, } from './conversations'; import { ConversationLookupType } from '../ducks/conversations'; +import { getRegionCode } from './user'; + export const getSearch = (state: StateType): SearchStateType => state.search; export const getQuery = createSelector( @@ -34,12 +36,14 @@ export const isSearching = createSelector( export const getSearchResults = createSelector( [ getSearch, + getRegionCode, getConversationLookup, getSelectedConversation, getSelectedMessage, ], ( state: SearchStateType, + regionCode: string, lookup: ConversationLookupType, selectedConversation?: string, selectedMessage?: string @@ -84,6 +88,7 @@ export const getSearchResults = createSelector( return message; }), + regionCode: regionCode, searchTerm: state.query, showStartNewConversation: Boolean( state.normalizedPhoneNumber && !lookup[state.normalizedPhoneNumber] diff --git a/ts/state/smart/LeftPane.tsx b/ts/state/smart/LeftPane.tsx index dc33dfc72..6ee0c9718 100644 --- a/ts/state/smart/LeftPane.tsx +++ b/ts/state/smart/LeftPane.tsx @@ -4,9 +4,9 @@ import { mapDispatchToProps } from '../actions'; import { LeftPane } from '../../components/LeftPane'; import { StateType } from '../reducer'; -import { getQuery, getSearchResults, isSearching } from '../selectors/search'; +import { getSearchResults, isSearching } from '../selectors/search'; import { getIntl } from '../selectors/user'; -import { getLeftPaneList, getMe } from '../selectors/conversations'; +import { getLeftPaneLists, getShowArchived } from '../selectors/conversations'; import { SmartMainHeader } from './MainHeader'; @@ -17,12 +17,14 @@ const FilteredSmartMainHeader = SmartMainHeader as any; const mapStateToProps = (state: StateType) => { const showSearch = isSearching(state); + const lists = showSearch ? undefined : getLeftPaneLists(state); + const searchResults = showSearch ? getSearchResults(state) : undefined; + return { + ...lists, + searchResults, + showArchived: getShowArchived(state), i18n: getIntl(state), - me: getMe(state), - query: getQuery(state), - conversations: showSearch ? undefined : getLeftPaneList(state), - searchResults: showSearch ? getSearchResults(state) : undefined, renderMainHeader: () => , }; }; diff --git a/ts/test/state/selectors/conversations_test.ts b/ts/test/state/selectors/conversations_test.ts index 167fa77d7..c56f50f2a 100644 --- a/ts/test/state/selectors/conversations_test.ts +++ b/ts/test/state/selectors/conversations_test.ts @@ -3,7 +3,7 @@ import { assert } from 'chai'; import { ConversationLookupType } from '../../../state/ducks/conversations'; import { _getConversationComparator, - _getLeftPaneList, + _getLeftPaneLists, } from '../../../state/selectors/conversations'; describe('state/selectors/conversations', () => { @@ -11,13 +11,14 @@ describe('state/selectors/conversations', () => { it('sorts conversations based on timestamp then by intl-friendly title', () => { const i18n = (key: string) => key; const regionCode = 'US'; - const conversations: ConversationLookupType = { + const data: ConversationLookupType = { id1: { id: 'id1', activeAt: Date.now(), name: 'No timestamp', timestamp: 0, phoneNumber: 'notused', + isArchived: false, type: 'direct', isMe: false, @@ -32,6 +33,7 @@ describe('state/selectors/conversations', () => { name: 'B', timestamp: 20, phoneNumber: 'notused', + isArchived: false, type: 'direct', isMe: false, @@ -46,6 +48,7 @@ describe('state/selectors/conversations', () => { name: 'C', timestamp: 20, phoneNumber: 'notused', + isArchived: false, type: 'direct', isMe: false, @@ -60,6 +63,7 @@ describe('state/selectors/conversations', () => { name: 'Á', timestamp: 20, phoneNumber: 'notused', + isArchived: false, type: 'direct', isMe: false, @@ -74,6 +78,7 @@ describe('state/selectors/conversations', () => { name: 'First!', timestamp: 30, phoneNumber: 'notused', + isArchived: false, type: 'direct', isMe: false, @@ -84,13 +89,13 @@ describe('state/selectors/conversations', () => { }, }; const comparator = _getConversationComparator(i18n, regionCode); - const list = _getLeftPaneList(conversations, comparator); + const { conversations } = _getLeftPaneLists(data, comparator); - assert.strictEqual(list[0].name, 'First!'); - assert.strictEqual(list[1].name, 'Á'); - assert.strictEqual(list[2].name, 'B'); - assert.strictEqual(list[3].name, 'C'); - assert.strictEqual(list[4].name, 'No timestamp'); + assert.strictEqual(conversations[0].name, 'First!'); + assert.strictEqual(conversations[1].name, 'Á'); + assert.strictEqual(conversations[2].name, 'B'); + assert.strictEqual(conversations[3].name, 'C'); + assert.strictEqual(conversations[4].name, 'No timestamp'); }); }); }); diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json index 14d77829e..6b1aebb9d 100644 --- a/ts/util/lint/exceptions.json +++ b/ts/util/lint/exceptions.json @@ -164,7 +164,7 @@ "rule": "jQuery-load(", "path": "js/conversation_controller.js", "line": " async load() {", - "lineNumber": 179, + "lineNumber": 177, "reasonCategory": "falseMatch", "updated": "2018-10-02T21:00:44.007Z" }, @@ -172,7 +172,7 @@ "rule": "jQuery-load(", "path": "js/conversation_controller.js", "line": " this._initialPromise = load();", - "lineNumber": 214, + "lineNumber": 212, "reasonCategory": "falseMatch", "updated": "2018-10-02T21:00:44.007Z" }, @@ -562,7 +562,7 @@ "rule": "jQuery-append(", "path": "js/views/inbox_view.js", "line": " .append(this.networkStatusView.render().el);", - "lineNumber": 89, + "lineNumber": 88, "reasonCategory": "usageTrusted", "updated": "2018-09-19T18:13:29.628Z", "reasonDetail": "Interacting with already-existing DOM nodes" @@ -571,7 +571,7 @@ "rule": "jQuery-prependTo(", "path": "js/views/inbox_view.js", "line": " banner.$el.prependTo(this.$el);", - "lineNumber": 93, + "lineNumber": 92, "reasonCategory": "usageTrusted", "updated": "2018-09-19T18:13:29.628Z", "reasonDetail": "Interacting with already-existing DOM nodes" @@ -580,7 +580,7 @@ "rule": "jQuery-$(", "path": "js/views/inbox_view.js", "line": " this.$('.left-pane-placeholder').append(this.leftPaneView.el);", - "lineNumber": 164, + "lineNumber": 166, "reasonCategory": "usageTrusted", "updated": "2019-03-08T23:49:08.796Z", "reasonDetail": "Protected from arbitrary input" @@ -589,7 +589,7 @@ "rule": "jQuery-append(", "path": "js/views/inbox_view.js", "line": " this.$('.left-pane-placeholder').append(this.leftPaneView.el);", - "lineNumber": 164, + "lineNumber": 166, "reasonCategory": "usageTrusted", "updated": "2019-03-08T23:49:08.796Z", "reasonDetail": "Protected from arbitrary input" @@ -598,7 +598,7 @@ "rule": "jQuery-$(", "path": "js/views/inbox_view.js", "line": " if (e && this.$(e.target).closest('.placeholder').length) {", - "lineNumber": 205, + "lineNumber": 207, "reasonCategory": "usageTrusted", "updated": "2019-03-08T23:49:08.796Z", "reasonDetail": "Protected from arbitrary input" @@ -607,7 +607,7 @@ "rule": "jQuery-$(", "path": "js/views/inbox_view.js", "line": " this.$('#header, .gutter').addClass('inactive');", - "lineNumber": 209, + "lineNumber": 211, "reasonCategory": "usageTrusted", "updated": "2019-03-08T23:49:08.796Z", "reasonDetail": "Protected from arbitrary input" @@ -616,25 +616,25 @@ "rule": "jQuery-$(", "path": "js/views/inbox_view.js", "line": " this.$('.conversation-stack').addClass('inactive');", - "lineNumber": 213, - "reasonCategory": "usageTrusted", - "updated": "2019-03-08T23:49:08.796Z", - "reasonDetail": "Protected from arbitrary input" - }, - { - "rule": "jQuery-$(", - "path": "js/views/inbox_view.js", - "line": " this.$('.conversation:first .menu').trigger('close');", "lineNumber": 215, "reasonCategory": "usageTrusted", "updated": "2019-03-08T23:49:08.796Z", "reasonDetail": "Protected from arbitrary input" }, + { + "rule": "jQuery-$(", + "path": "js/views/inbox_view.js", + "line": " this.$('.conversation:first .menu').trigger('close');", + "lineNumber": 217, + "reasonCategory": "usageTrusted", + "updated": "2019-03-08T23:49:08.796Z", + "reasonDetail": "Protected from arbitrary input" + }, { "rule": "jQuery-$(", "path": "js/views/inbox_view.js", "line": " if (e && this.$(e.target).closest('.capture-audio').length > 0) {", - "lineNumber": 230, + "lineNumber": 236, "reasonCategory": "usageTrusted", "updated": "2019-03-08T23:49:08.796Z", "reasonDetail": "Protected from arbitrary input" @@ -643,7 +643,7 @@ "rule": "jQuery-$(", "path": "js/views/inbox_view.js", "line": " this.$('.conversation:first .recorder').trigger('close');", - "lineNumber": 233, + "lineNumber": 239, "reasonCategory": "usageTrusted", "updated": "2019-03-08T23:49:08.796Z", "reasonDetail": "Protected from arbitrary input" @@ -5464,6 +5464,24 @@ "updated": "2019-03-09T00:08:44.242Z", "reasonDetail": "Used only to set focus" }, + { + "rule": "React-createRef", + "path": "ts/components/LeftPane.js", + "line": " this.listRef = react_1.default.createRef();", + "lineNumber": 13, + "reasonCategory": "usageTrusted", + "updated": "2019-03-12T23:33:50.889Z", + "reasonDetail": "Used only to scroll to top on archive/inbox switch" + }, + { + "rule": "React-createRef", + "path": "ts/components/LeftPane.tsx", + "line": " public listRef: React.RefObject = React.createRef();", + "lineNumber": 46, + "reasonCategory": "usageTrusted", + "updated": "2019-03-12T23:33:50.889Z", + "reasonDetail": "Used only to scroll to top on archive/inbox switch" + }, { "rule": "React-createRef", "path": "ts/components/Lightbox.js", @@ -5513,9 +5531,9 @@ "rule": "React-createRef", "path": "ts/components/conversation/ConversationHeader.tsx", "line": " this.menuTriggerRef = React.createRef();", - "lineNumber": 51, + "lineNumber": 58, "reasonCategory": "usageTrusted", "updated": "2019-03-09T00:08:44.242Z", "reasonDetail": "Used only to trigger menu display" } -] +] \ No newline at end of file