): ReactChild {
+ getPreRowsNode({
+ i18n,
+ }: Readonly<{ i18n: LocalizerType }>): ReactChild | null {
+ if (this.searchHelper) {
+ return this.searchHelper.getPreRowsNode({ i18n });
+ }
+
return (
{i18n('archiveHelperText')}
@@ -64,10 +113,16 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper,
selectedConversationId: undefined | string,
- _selectedMessageId: unknown
+ selectedMessageId: unknown
): undefined | { conversationId: string } {
+ if (this.searchHelper) {
+ return this.searchHelper.getConversationAndMessageInDirection(
+ toFind,
+ selectedConversationId,
+ selectedMessageId
+ );
+ }
+
return getConversationInDirection(
this.archivedConversations,
toFind,
@@ -110,7 +182,51 @@ export class LeftPaneArchiveHelper extends LeftPaneHelper): boolean {
+ const hasSearchingChanged =
+ 'conversationResults' in old !== Boolean(this.searchHelper);
+ if (hasSearchingChanged) {
+ return true;
+ }
+
+ if ('conversationResults' in old && this.searchHelper) {
+ return this.searchHelper.shouldRecomputeRowHeights(old);
+ }
+
return false;
}
+
+ onKeyDown(
+ event: KeyboardEvent,
+ {
+ searchInConversation,
+ selectedConversationId,
+ }: Readonly<{
+ searchInConversation: (conversationId: string) => unknown;
+ selectedConversationId: undefined | string;
+ }>
+ ): void {
+ if (!selectedConversationId) {
+ return;
+ }
+
+ const { ctrlKey, metaKey, shiftKey, key } = event;
+ const commandKey = window.platform === 'darwin' && metaKey;
+ const controlKey = window.platform !== 'darwin' && ctrlKey;
+ const commandOrCtrl = commandKey || controlKey;
+ const commandAndCtrl = commandKey && ctrlKey;
+
+ if (
+ commandOrCtrl &&
+ !commandAndCtrl &&
+ shiftKey &&
+ key.toLowerCase() === 'f' &&
+ this.archivedConversations.some(({ id }) => id === selectedConversationId)
+ ) {
+ searchInConversation(selectedConversationId);
+
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
}
diff --git a/ts/components/leftPane/LeftPaneHelper.tsx b/ts/components/leftPane/LeftPaneHelper.tsx
index 321d9cd2da..82daccd5bd 100644
--- a/ts/components/leftPane/LeftPaneHelper.tsx
+++ b/ts/components/leftPane/LeftPaneHelper.tsx
@@ -26,10 +26,12 @@ export type ToFindType = {
export abstract class LeftPaneHelper {
getHeaderContents(
_: Readonly<{
+ clearSearch: () => void;
i18n: LocalizerType;
showInbox: () => void;
startComposing: () => void;
showChooseGroupMembers: () => void;
+ updateSearchTerm: (query: string) => void;
}>
): null | ReactChild {
return null;
@@ -97,6 +99,17 @@ export abstract class LeftPaneHelper {
return true;
}
+ onKeyDown(
+ _event: KeyboardEvent,
+ _options: Readonly<{
+ searchInConversation: (conversationId: string) => unknown;
+ selectedConversationId: undefined | string;
+ startSearch: () => unknown;
+ }>
+ ): void {
+ return undefined;
+ }
+
abstract getConversationAndMessageAtIndex(
conversationIndex: number
): undefined | { conversationId: string; messageId?: string };
diff --git a/ts/components/leftPane/LeftPaneInboxHelper.tsx b/ts/components/leftPane/LeftPaneInboxHelper.tsx
index 712bf792e1..c129d0aef4 100644
--- a/ts/components/leftPane/LeftPaneInboxHelper.tsx
+++ b/ts/components/leftPane/LeftPaneInboxHelper.tsx
@@ -13,6 +13,7 @@ import type { Row } from '../ConversationList';
import { RowType } from '../ConversationList';
import type { PropsData as ConversationListItemPropsType } from '../conversationList/ConversationListItem';
import type { LocalizerType } from '../../types/Util';
+import { handleKeydownForSearch } from './handleKeydownForSearch';
export type LeftPaneInboxPropsType = {
conversations: ReadonlyArray;
@@ -229,6 +230,17 @@ export class LeftPaneInboxHelper extends LeftPaneHelper
);
}
+ onKeyDown(
+ event: KeyboardEvent,
+ options: Readonly<{
+ searchInConversation: (conversationId: string) => unknown;
+ selectedConversationId: undefined | string;
+ startSearch: () => unknown;
+ }>
+ ): void {
+ handleKeydownForSearch(event, options);
+ }
+
private hasPinnedAndNonpinned(): boolean {
return Boolean(
this.pinnedConversations.length && this.conversations.length
diff --git a/ts/components/leftPane/LeftPaneSearchHelper.tsx b/ts/components/leftPane/LeftPaneSearchHelper.tsx
index 3d54b7b7aa..356583517d 100644
--- a/ts/components/leftPane/LeftPaneSearchHelper.tsx
+++ b/ts/components/leftPane/LeftPaneSearchHelper.tsx
@@ -10,6 +10,7 @@ import type { LocalizerType } from '../../types/Util';
import type { Row } from '../ConversationList';
import { RowType } from '../ConversationList';
import type { PropsData as ConversationListItemPropsType } from '../conversationList/ConversationListItem';
+import { handleKeydownForSearch } from './handleKeydownForSearch';
import { Intl } from '../Intl';
import { Emojify } from '../conversation/Emojify';
@@ -42,6 +43,8 @@ const searchResultKeys: Array<
'conversationResults' | 'contactResults' | 'messageResults'
> = ['conversationResults', 'contactResults', 'messageResults'];
+/* eslint-disable class-methods-use-this */
+
export class LeftPaneSearchHelper extends LeftPaneHelper {
private readonly conversationResults: MaybeLoadedSearchResultsType;
@@ -270,6 +273,17 @@ export class LeftPaneSearchHelper extends LeftPaneHelper unknown;
+ selectedConversationId: undefined | string;
+ startSearch: () => unknown;
+ }>
+ ): void {
+ handleKeydownForSearch(event, options);
+ }
+
private allResults() {
return [this.conversationResults, this.contactResults, this.messageResults];
}
diff --git a/ts/components/leftPane/handleKeydownForSearch.ts b/ts/components/leftPane/handleKeydownForSearch.ts
new file mode 100644
index 0000000000..2591f44d53
--- /dev/null
+++ b/ts/components/leftPane/handleKeydownForSearch.ts
@@ -0,0 +1,33 @@
+// Copyright 2021 Signal Messenger, LLC
+// SPDX-License-Identifier: AGPL-3.0-only
+
+export function handleKeydownForSearch(
+ event: Readonly,
+ {
+ searchInConversation,
+ selectedConversationId,
+ startSearch,
+ }: Readonly<{
+ searchInConversation: (conversationId: string) => unknown;
+ selectedConversationId: undefined | string;
+ startSearch: () => unknown;
+ }>
+): void {
+ const { ctrlKey, metaKey, shiftKey, key } = event;
+ const commandKey = window.platform === 'darwin' && metaKey;
+ const controlKey = window.platform !== 'darwin' && ctrlKey;
+ const commandOrCtrl = commandKey || controlKey;
+ const commandAndCtrl = commandKey && ctrlKey;
+
+ if (commandOrCtrl && !commandAndCtrl && key.toLowerCase() === 'f') {
+ if (!shiftKey) {
+ startSearch();
+ event.preventDefault();
+ event.stopPropagation();
+ } else if (selectedConversationId) {
+ searchInConversation(selectedConversationId);
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+}
diff --git a/ts/state/ducks/search.ts b/ts/state/ducks/search.ts
index aa57f6b0c9..f4f786e0d0 100644
--- a/ts/state/ducks/search.ts
+++ b/ts/state/ducks/search.ts
@@ -1,9 +1,10 @@
-// Copyright 2019-2020 Signal Messenger, LLC
+// Copyright 2019-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
-import { omit, reject } from 'lodash';
+import type { ThunkAction, ThunkDispatch } from 'redux-thunk';
+import { debounce, omit, reject } from 'lodash';
-import { normalize } from '../../types/PhoneNumber';
+import type { StateType as RootStateType } from '../reducer';
import { cleanSearchTerm } from '../../util/cleanSearchTerm';
import type {
ClientSearchResultMessageType,
@@ -21,6 +22,8 @@ import type {
SelectedConversationChangedActionType,
ShowArchivedConversationsActionType,
} from './conversations';
+import { getQuery, getSearchConversation } from '../selectors/search';
+import { getIntl, getUserConversationId } from '../selectors/user';
const {
searchConversations: dataSearchConversations,
@@ -41,11 +44,9 @@ export type MessageSearchResultLookupType = {
export type SearchStateType = {
startSearchCounter: number;
searchConversationId?: string;
- searchConversationName?: string;
contactIds: Array;
conversationIds: Array;
query: string;
- normalizedPhoneNumber?: string;
messageIds: Array;
// We do store message data to pass through the selector
messageLookup: MessageSearchResultLookupType;
@@ -57,33 +58,20 @@ export type SearchStateType = {
// Actions
-type SearchResultsBaseType = {
- query: string;
- normalizedPhoneNumber?: string;
-};
-type SearchMessagesResultsPayloadType = SearchResultsBaseType & {
- messages: Array;
-};
-type SearchDiscussionsResultsPayloadType = SearchResultsBaseType & {
- conversationIds: Array;
- contactIds: Array;
-};
-type SearchMessagesResultsKickoffActionType = {
- type: 'SEARCH_MESSAGES_RESULTS';
- payload: Promise;
-};
-type SearchDiscussionsResultsKickoffActionType = {
- type: 'SEARCH_DISCUSSIONS_RESULTS';
- payload: Promise;
-};
-
type SearchMessagesResultsFulfilledActionType = {
type: 'SEARCH_MESSAGES_RESULTS_FULFILLED';
- payload: SearchMessagesResultsPayloadType;
+ payload: {
+ messages: Array;
+ query: string;
+ };
};
type SearchDiscussionsResultsFulfilledActionType = {
type: 'SEARCH_DISCUSSIONS_RESULTS_FULFILLED';
- payload: SearchDiscussionsResultsPayloadType;
+ payload: {
+ conversationIds: Array;
+ contactIds: Array;
+ query: string;
+ };
};
type UpdateSearchTermActionType = {
type: 'SEARCH_UPDATE';
@@ -105,15 +93,10 @@ type ClearConversationSearchActionType = {
};
type SearchInConversationActionType = {
type: 'SEARCH_IN_CONVERSATION';
- payload: {
- searchConversationId: string;
- searchConversationName: string;
- };
+ payload: { searchConversationId: string };
};
export type SearchActionType =
- | SearchMessagesResultsKickoffActionType
- | SearchDiscussionsResultsKickoffActionType
| SearchMessagesResultsFulfilledActionType
| SearchDiscussionsResultsFulfilledActionType
| UpdateSearchTermActionType
@@ -130,8 +113,6 @@ export type SearchActionType =
// Action Creators
export const actions = {
- searchMessages,
- searchDiscussions,
startSearch,
clearSearch,
clearConversationSearch,
@@ -139,72 +120,6 @@ export const actions = {
updateSearchTerm,
};
-function searchMessages(
- query: string,
- options: {
- regionCode: string;
- }
-): SearchMessagesResultsKickoffActionType {
- return {
- type: 'SEARCH_MESSAGES_RESULTS',
- payload: doSearchMessages(query, options),
- };
-}
-
-function searchDiscussions(
- query: string,
- options: {
- ourConversationId: string;
- noteToSelf: string;
- }
-): SearchDiscussionsResultsKickoffActionType {
- return {
- type: 'SEARCH_DISCUSSIONS_RESULTS',
- payload: doSearchDiscussions(query, options),
- };
-}
-
-async function doSearchMessages(
- query: string,
- options: {
- searchConversationId?: string;
- regionCode: string;
- }
-): Promise {
- const { regionCode, searchConversationId } = options;
- const normalizedPhoneNumber = normalize(query, { regionCode });
-
- const messages = await queryMessages(query, searchConversationId);
-
- return {
- messages,
- normalizedPhoneNumber,
- query,
- };
-}
-
-async function doSearchDiscussions(
- query: string,
- options: {
- ourConversationId: string;
- noteToSelf: string;
- }
-): Promise {
- const { ourConversationId, noteToSelf } = options;
- const { conversationIds, contactIds } = await queryConversationsAndContacts(
- query,
- {
- ourConversationId,
- noteToSelf,
- }
- );
-
- return {
- conversationIds,
- contactIds,
- query,
- };
-}
function startSearch(): StartSearchActionType {
return {
type: 'SEARCH_START',
@@ -224,27 +139,92 @@ function clearConversationSearch(): ClearConversationSearchActionType {
};
}
function searchInConversation(
- searchConversationId: string,
- searchConversationName: string
+ searchConversationId: string
): SearchInConversationActionType {
return {
type: 'SEARCH_IN_CONVERSATION',
- payload: {
- searchConversationId,
- searchConversationName,
- },
+ payload: { searchConversationId },
};
}
-function updateSearchTerm(query: string): UpdateSearchTermActionType {
- return {
- type: 'SEARCH_UPDATE',
- payload: {
- query,
- },
+function updateSearchTerm(
+ query: string
+): ThunkAction {
+ return (dispatch, getState) => {
+ dispatch({
+ type: 'SEARCH_UPDATE',
+ payload: { query },
+ });
+
+ const state = getState();
+
+ doSearch({
+ dispatch,
+ noteToSelf: getIntl(state)('noteToSelf').toLowerCase(),
+ ourConversationId: getUserConversationId(state),
+ query: getQuery(state),
+ searchConversationId: getSearchConversation(state)?.id,
+ });
};
}
+const doSearch = debounce(
+ ({
+ dispatch,
+ noteToSelf,
+ ourConversationId,
+ query,
+ searchConversationId,
+ }: Readonly<{
+ dispatch: ThunkDispatch<
+ RootStateType,
+ unknown,
+ | SearchMessagesResultsFulfilledActionType
+ | SearchDiscussionsResultsFulfilledActionType
+ >;
+ noteToSelf: string;
+ ourConversationId: string;
+ query: string;
+ searchConversationId: undefined | string;
+ }>) => {
+ if (!query) {
+ return;
+ }
+
+ (async () => {
+ dispatch({
+ type: 'SEARCH_MESSAGES_RESULTS_FULFILLED',
+ payload: {
+ messages: await queryMessages(query, searchConversationId),
+ query,
+ },
+ });
+ })();
+
+ if (!searchConversationId) {
+ (async () => {
+ const {
+ conversationIds,
+ contactIds,
+ } = await queryConversationsAndContacts(query, {
+ ourConversationId,
+ noteToSelf,
+ });
+
+ dispatch({
+ type: 'SEARCH_DISCUSSIONS_RESULTS_FULFILLED',
+ payload: {
+ conversationIds,
+ contactIds,
+ query,
+ },
+ });
+ })();
+ }
+ },
+ 200
+);
+
async function queryMessages(
query: string,
searchConversationId?: string
@@ -342,7 +322,6 @@ export function reducer(
return {
...state,
searchConversationId: undefined,
- searchConversationName: undefined,
startSearchCounter: state.startSearchCounter + 1,
};
}
@@ -376,7 +355,7 @@ export function reducer(
if (action.type === 'SEARCH_IN_CONVERSATION') {
const { payload } = action;
- const { searchConversationId, searchConversationName } = payload;
+ const { searchConversationId } = payload;
if (searchConversationId === state.searchConversationId) {
return {
@@ -388,23 +367,21 @@ export function reducer(
return {
...getEmptyState(),
searchConversationId,
- searchConversationName,
startSearchCounter: state.startSearchCounter + 1,
};
}
if (action.type === 'CLEAR_CONVERSATION_SEARCH') {
- const { searchConversationId, searchConversationName } = state;
+ const { searchConversationId } = state;
return {
...getEmptyState(),
searchConversationId,
- searchConversationName,
};
}
if (action.type === 'SEARCH_MESSAGES_RESULTS_FULFILLED') {
const { payload } = action;
- const { messages, normalizedPhoneNumber, query } = payload;
+ const { messages, query } = payload;
// Reject if the associated query is not the most recent user-provided query
if (state.query !== query) {
@@ -415,7 +392,6 @@ export function reducer(
return {
...state,
- normalizedPhoneNumber,
query,
messageIds,
messageLookup: makeLookup(messages, 'id'),
@@ -425,7 +401,12 @@ export function reducer(
if (action.type === 'SEARCH_DISCUSSIONS_RESULTS_FULFILLED') {
const { payload } = action;
- const { contactIds, conversationIds } = payload;
+ const { contactIds, conversationIds, query } = payload;
+
+ // Reject if the associated query is not the most recent user-provided query
+ if (state.query !== query) {
+ return state;
+ }
return {
...state,
diff --git a/ts/state/selectors/search.ts b/ts/state/selectors/search.ts
index 5eec356c5c..f226b8cadc 100644
--- a/ts/state/selectors/search.ts
+++ b/ts/state/selectors/search.ts
@@ -21,7 +21,7 @@ import type {
import type { LeftPaneSearchPropsType } from '../../components/leftPane/LeftPaneSearchHelper';
import type { PropsDataType as MessageSearchResultPropsDataType } from '../../components/conversationList/MessageSearchResult';
-import { getUserConversationId } from './user';
+import { getIntl, getUserConversationId } from './user';
import type { GetConversationByIdType } from './conversations';
import {
getConversationLookup,
@@ -30,6 +30,7 @@ import {
import type { BodyRangeType } from '../../types/Util';
import * as log from '../../logging/log';
+import { getOwn } from '../../util/getOwn';
export const getSearch = (state: StateType): SearchStateType => state.search;
@@ -43,7 +44,7 @@ export const getSelectedMessage = createSelector(
(state: SearchStateType): string | undefined => state.selectedMessage
);
-export const getSearchConversationId = createSelector(
+const getSearchConversationId = createSelector(
getSearch,
(state: SearchStateType): string | undefined => state.searchConversationId
);
@@ -53,9 +54,24 @@ export const getIsSearchingInAConversation = createSelector(
Boolean
);
+export const getSearchConversation = createSelector(
+ getSearchConversationId,
+ getConversationLookup,
+ (searchConversationId, conversationLookup): undefined | ConversationType =>
+ searchConversationId
+ ? getOwn(conversationLookup, searchConversationId)
+ : undefined
+);
+
export const getSearchConversationName = createSelector(
- getSearch,
- (state: SearchStateType): string | undefined => state.searchConversationName
+ getSearchConversation,
+ getIntl,
+ (conversation, i18n): undefined | string => {
+ if (!conversation) {
+ return undefined;
+ }
+ return conversation.isMe ? i18n('noteToSelf') : conversation.title;
+ }
);
export const getStartSearchCounter = createSelector(
@@ -74,9 +90,10 @@ export const getMessageSearchResultLookup = createSelector(
);
export const getSearchResults = createSelector(
- [getSearch, getConversationLookup],
+ [getSearch, getSearchConversationName, getConversationLookup],
(
state: SearchStateType,
+ searchConversationName,
conversationLookup: ConversationLookupType
): Omit => {
const {
@@ -86,7 +103,6 @@ export const getSearchResults = createSelector(
messageIds,
messageLookup,
messagesLoading,
- searchConversationName,
} = state;
return {
diff --git a/ts/state/smart/LeftPane.tsx b/ts/state/smart/LeftPane.tsx
index 1ff3d5283f..e1836efb8c 100644
--- a/ts/state/smart/LeftPane.tsx
+++ b/ts/state/smart/LeftPane.tsx
@@ -13,6 +13,8 @@ import { missingCaseError } from '../../util/missingCaseError';
import { ComposerStep, OneTimeModalState } from '../ducks/conversations';
import {
getIsSearchingInAConversation,
+ getQuery,
+ getSearchConversation,
getSearchResults,
getStartSearchCounter,
isSearching,
@@ -91,9 +93,14 @@ const getModeSpecificProps = (
case undefined:
if (getShowArchived(state)) {
const { archivedConversations } = getLeftPaneLists(state);
+ const searchConversation = getSearchConversation(state);
+ const searchTerm = getQuery(state);
return {
mode: LeftPaneMode.Archive,
archivedConversations,
+ searchConversation,
+ searchTerm,
+ ...(searchConversation && searchTerm ? getSearchResults(state) : {}),
};
}
if (isSearching(state)) {
diff --git a/ts/state/smart/MainHeader.tsx b/ts/state/smart/MainHeader.tsx
index ff3d3abc72..aabffb2201 100644
--- a/ts/state/smart/MainHeader.tsx
+++ b/ts/state/smart/MainHeader.tsx
@@ -1,4 +1,4 @@
-// Copyright 2019-2020 Signal Messenger, LLC
+// Copyright 2019-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { connect } from 'react-redux';
@@ -9,8 +9,7 @@ import type { StateType } from '../reducer';
import {
getQuery,
- getSearchConversationId,
- getSearchConversationName,
+ getSearchConversation,
getStartSearchCounter,
} from '../selectors/search';
import {
@@ -27,8 +26,7 @@ const mapStateToProps = (state: StateType) => {
disabled: state.network.challengeStatus !== 'idle',
hasPendingUpdate: Boolean(state.updates.didSnooze),
searchTerm: getQuery(state),
- searchConversationId: getSearchConversationId(state),
- searchConversationName: getSearchConversationName(state),
+ searchConversation: getSearchConversation(state),
selectedConversation: getSelectedConversation(state),
startSearchCounter: getStartSearchCounter(state),
regionCode: getRegionCode(state),
diff --git a/ts/test-node/components/leftPane/LeftPaneArchiveHelper_test.ts b/ts/test-node/components/leftPane/LeftPaneArchiveHelper_test.ts
index 762fba1a2b..b2252a1a57 100644
--- a/ts/test-node/components/leftPane/LeftPaneArchiveHelper_test.ts
+++ b/ts/test-node/components/leftPane/LeftPaneArchiveHelper_test.ts
@@ -7,14 +7,41 @@ import { v4 as uuid } from 'uuid';
import { RowType } from '../../../components/ConversationList';
import { FindDirection } from '../../../components/leftPane/LeftPaneHelper';
import { getDefaultConversation } from '../../../test-both/helpers/getDefaultConversation';
+import { LeftPaneSearchHelper } from '../../../components/leftPane/LeftPaneSearchHelper';
import { LeftPaneArchiveHelper } from '../../../components/leftPane/LeftPaneArchiveHelper';
describe('LeftPaneArchiveHelper', () => {
+ let sandbox: sinon.SinonSandbox;
+
+ const defaults = {
+ archivedConversations: [],
+ searchConversation: undefined,
+ searchTerm: '',
+ };
+
+ const searchingDefaults = {
+ ...defaults,
+ searchConversation: getDefaultConversation(),
+ conversationResults: { isLoading: false, results: [] },
+ contactResults: { isLoading: false, results: [] },
+ messageResults: { isLoading: false, results: [] },
+ searchTerm: 'foo',
+ primarySendsSms: false,
+ };
+
+ beforeEach(() => {
+ sandbox = sinon.createSandbox();
+ });
+
+ afterEach(() => {
+ sandbox.restore();
+ });
+
describe('getBackAction', () => {
it('returns the "show inbox" action', () => {
const showInbox = sinon.fake();
- const helper = new LeftPaneArchiveHelper({ archivedConversations: [] });
+ const helper = new LeftPaneArchiveHelper(defaults);
assert.strictEqual(helper.getBackAction({ showInbox }), showInbox);
});
@@ -22,12 +49,10 @@ describe('LeftPaneArchiveHelper', () => {
describe('getRowCount', () => {
it('returns the number of archived conversations', () => {
- assert.strictEqual(
- new LeftPaneArchiveHelper({ archivedConversations: [] }).getRowCount(),
- 0
- );
+ assert.strictEqual(new LeftPaneArchiveHelper(defaults).getRowCount(), 0);
assert.strictEqual(
new LeftPaneArchiveHelper({
+ ...defaults,
archivedConversations: [
getDefaultConversation(),
getDefaultConversation(),
@@ -36,11 +61,20 @@ describe('LeftPaneArchiveHelper', () => {
2
);
});
+
+ it('defers to the search helper if searching', () => {
+ sandbox.stub(LeftPaneSearchHelper.prototype, 'getRowCount').returns(123);
+ assert.strictEqual(
+ new LeftPaneArchiveHelper(searchingDefaults).getRowCount(),
+ 123
+ );
+ });
});
describe('getRowIndexToScrollTo', () => {
it('returns undefined if no conversation is selected', () => {
const helper = new LeftPaneArchiveHelper({
+ ...defaults,
archivedConversations: [
getDefaultConversation(),
getDefaultConversation(),
@@ -52,6 +86,7 @@ describe('LeftPaneArchiveHelper', () => {
it('returns undefined if the selected conversation is not pinned or non-pinned', () => {
const helper = new LeftPaneArchiveHelper({
+ ...defaults,
archivedConversations: [
getDefaultConversation(),
getDefaultConversation(),
@@ -66,7 +101,10 @@ describe('LeftPaneArchiveHelper', () => {
getDefaultConversation(),
getDefaultConversation(),
];
- const helper = new LeftPaneArchiveHelper({ archivedConversations });
+ const helper = new LeftPaneArchiveHelper({
+ ...defaults,
+ archivedConversations,
+ });
assert.strictEqual(
helper.getRowIndexToScrollTo(archivedConversations[0].id),
@@ -77,6 +115,23 @@ describe('LeftPaneArchiveHelper', () => {
1
);
});
+
+ it('defers to the search helper if searching', () => {
+ sandbox
+ .stub(LeftPaneSearchHelper.prototype, 'getRowIndexToScrollTo')
+ .returns(123);
+
+ const archivedConversations = [
+ getDefaultConversation(),
+ getDefaultConversation(),
+ ];
+ const helper = new LeftPaneArchiveHelper(searchingDefaults);
+
+ assert.strictEqual(
+ helper.getRowIndexToScrollTo(archivedConversations[0].id),
+ 123
+ );
+ });
});
describe('getRow', () => {
@@ -85,7 +140,10 @@ describe('LeftPaneArchiveHelper', () => {
getDefaultConversation(),
getDefaultConversation(),
];
- const helper = new LeftPaneArchiveHelper({ archivedConversations });
+ const helper = new LeftPaneArchiveHelper({
+ ...defaults,
+ archivedConversations,
+ });
assert.deepEqual(helper.getRow(0), {
type: RowType.Conversation,
@@ -96,6 +154,18 @@ describe('LeftPaneArchiveHelper', () => {
conversation: archivedConversations[1],
});
});
+
+ it('defers to the search helper if searching', () => {
+ sandbox
+ .stub(LeftPaneSearchHelper.prototype, 'getRow')
+ .returns({ type: RowType.SearchResultsLoadingFakeHeader });
+
+ const helper = new LeftPaneArchiveHelper(searchingDefaults);
+
+ assert.deepEqual(helper.getRow(0), {
+ type: RowType.SearchResultsLoadingFakeHeader,
+ });
+ });
});
describe('getConversationAndMessageAtIndex', () => {
@@ -104,7 +174,10 @@ describe('LeftPaneArchiveHelper', () => {
getDefaultConversation(),
getDefaultConversation(),
];
- const helper = new LeftPaneArchiveHelper({ archivedConversations });
+ const helper = new LeftPaneArchiveHelper({
+ ...defaults,
+ archivedConversations,
+ });
assert.strictEqual(
helper.getConversationAndMessageAtIndex(0)?.conversationId,
@@ -121,7 +194,10 @@ describe('LeftPaneArchiveHelper', () => {
getDefaultConversation(),
getDefaultConversation(),
];
- const helper = new LeftPaneArchiveHelper({ archivedConversations });
+ const helper = new LeftPaneArchiveHelper({
+ ...defaults,
+ archivedConversations,
+ });
assert.strictEqual(
helper.getConversationAndMessageAtIndex(2)?.conversationId,
@@ -141,12 +217,27 @@ describe('LeftPaneArchiveHelper', () => {
});
it('returns undefined if there are no archived conversations', () => {
- const helper = new LeftPaneArchiveHelper({ archivedConversations: [] });
+ const helper = new LeftPaneArchiveHelper(defaults);
assert.isUndefined(helper.getConversationAndMessageAtIndex(0));
assert.isUndefined(helper.getConversationAndMessageAtIndex(1));
assert.isUndefined(helper.getConversationAndMessageAtIndex(-1));
});
+
+ it('defers to the search helper if searching', () => {
+ sandbox
+ .stub(
+ LeftPaneSearchHelper.prototype,
+ 'getConversationAndMessageAtIndex'
+ )
+ .returns({ conversationId: 'abc123' });
+
+ const helper = new LeftPaneArchiveHelper(searchingDefaults);
+
+ assert.deepEqual(helper.getConversationAndMessageAtIndex(999), {
+ conversationId: 'abc123',
+ });
+ });
});
describe('getConversationAndMessageInDirection', () => {
@@ -155,7 +246,10 @@ describe('LeftPaneArchiveHelper', () => {
getDefaultConversation(),
getDefaultConversation(),
];
- const helper = new LeftPaneArchiveHelper({ archivedConversations });
+ const helper = new LeftPaneArchiveHelper({
+ ...defaults,
+ archivedConversations,
+ });
assert.deepEqual(
helper.getConversationAndMessageInDirection(
@@ -168,11 +262,37 @@ describe('LeftPaneArchiveHelper', () => {
});
// Additional tests are found with `getConversationInDirection`.
+
+ it('defers to the search helper if searching', () => {
+ sandbox
+ .stub(
+ LeftPaneSearchHelper.prototype,
+ 'getConversationAndMessageInDirection'
+ )
+ .returns({ conversationId: 'abc123' });
+
+ const helper = new LeftPaneArchiveHelper(searchingDefaults);
+
+ assert.deepEqual(
+ helper.getConversationAndMessageInDirection(
+ {
+ direction: FindDirection.Down,
+ unreadOnly: false,
+ },
+ getDefaultConversation().id,
+ undefined
+ ),
+ {
+ conversationId: 'abc123',
+ }
+ );
+ });
});
describe('shouldRecomputeRowHeights', () => {
- it('always returns false because row heights are constant', () => {
+ it('returns false when not searching because row heights are constant', () => {
const helper = new LeftPaneArchiveHelper({
+ ...defaults,
archivedConversations: [
getDefaultConversation(),
getDefaultConversation(),
@@ -181,11 +301,13 @@ describe('LeftPaneArchiveHelper', () => {
assert.isFalse(
helper.shouldRecomputeRowHeights({
+ ...defaults,
archivedConversations: [getDefaultConversation()],
})
);
assert.isFalse(
helper.shouldRecomputeRowHeights({
+ ...defaults,
archivedConversations: [
getDefaultConversation(),
getDefaultConversation(),
@@ -193,5 +315,27 @@ describe('LeftPaneArchiveHelper', () => {
})
);
});
+
+ it('returns true when going from searching → not searching', () => {
+ const helper = new LeftPaneArchiveHelper(defaults);
+
+ assert.isTrue(helper.shouldRecomputeRowHeights(searchingDefaults));
+ });
+
+ it('returns true when going from not searching → searching', () => {
+ const helper = new LeftPaneArchiveHelper(searchingDefaults);
+
+ assert.isTrue(helper.shouldRecomputeRowHeights(defaults));
+ });
+
+ it('defers to the search helper if searching', () => {
+ sandbox
+ .stub(LeftPaneSearchHelper.prototype, 'shouldRecomputeRowHeights')
+ .returns(true);
+
+ const helper = new LeftPaneArchiveHelper(searchingDefaults);
+
+ assert.isTrue(helper.shouldRecomputeRowHeights(searchingDefaults));
+ });
});
});
diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json
index 4ddc8f756b..e60c43356e 100644
--- a/ts/util/lint/exceptions.json
+++ b/ts/util/lint/exceptions.json
@@ -12660,6 +12660,14 @@
"reasonCategory": "falseMatch",
"updated": "2020-07-21T18:34:59.251Z"
},
+ {
+ "rule": "React-useRef",
+ "path": "ts/components/LeftPaneSearchInput.tsx",
+ "line": " const inputRef = useRef(null);",
+ "reasonCategory": "usageTrusted",
+ "updated": "2021-10-29T22:48:58.354Z",
+ "reasonDetail": "Only used to focus the input."
+ },
{
"rule": "React-useRef",
"path": "ts/components/Lightbox.tsx",
diff --git a/ts/views/conversation_view.ts b/ts/views/conversation_view.ts
index 16421bc5fc..349399df33 100644
--- a/ts/views/conversation_view.ts
+++ b/ts/views/conversation_view.ts
@@ -39,7 +39,6 @@ import type { GroupNameCollisionsWithIdsByTitle } from '../util/groupMemberNameC
import {
isDirectConversation,
isGroupV1,
- isMe,
} from '../util/whatTypeOfConversation';
import { findAndFormatContact } from '../util/findAndFormatContact';
import * as Bytes from '../Bytes';
@@ -383,10 +382,7 @@ export class ConversationView extends window.Backbone.View {
onDeleteMessages: () => this.destroyMessages(),
onSearchInConversation: () => {
const { searchInConversation } = window.reduxActions.search;
- const name = isMe(this.model.attributes)
- ? window.i18n('noteToSelf')
- : this.model.getTitle();
- searchInConversation(this.model.id, name);
+ searchInConversation(this.model.id);
},
onSetMuteNotifications: this.setMuteExpiration.bind(this),
onSetPin: this.setPin.bind(this),
diff --git a/ts/views/inbox_view.ts b/ts/views/inbox_view.ts
index 83bf5e0e09..dde91c42b5 100644
--- a/ts/views/inbox_view.ts
+++ b/ts/views/inbox_view.ts
@@ -220,7 +220,7 @@ Whisper.InboxView = Whisper.View.extend({
view.remove();
const searchInput = document.querySelector(
- '.module-main-header__search__input'
+ '.LeftPaneSearchInput__input'
) as HTMLElement;
searchInput?.focus?.();
}