2023-01-03 19:55:46 +00:00
|
|
|
// Copyright 2019 Signal Messenger, LLC
|
2020-10-30 20:34:04 +00:00
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
2019-08-09 00:46:49 +00:00
|
|
|
import memoizee from 'memoizee';
|
2019-01-14 21:49:58 +00:00
|
|
|
import { createSelector } from 'reselect';
|
2021-02-23 20:34:28 +00:00
|
|
|
|
|
|
|
import { deconstructLookup } from '../../util/deconstructLookup';
|
2019-01-14 21:49:58 +00:00
|
|
|
|
2021-10-26 19:15:33 +00:00
|
|
|
import type { StateType } from '../reducer';
|
2019-01-14 21:49:58 +00:00
|
|
|
|
2021-10-26 19:15:33 +00:00
|
|
|
import type {
|
2019-08-09 00:46:49 +00:00
|
|
|
MessageSearchResultLookupType,
|
|
|
|
MessageSearchResultType,
|
|
|
|
SearchStateType,
|
|
|
|
} from '../ducks/search';
|
2021-10-26 19:15:33 +00:00
|
|
|
import type {
|
2019-08-09 00:46:49 +00:00
|
|
|
ConversationLookupType,
|
|
|
|
ConversationType,
|
|
|
|
} from '../ducks/conversations';
|
|
|
|
|
2021-10-26 19:15:33 +00:00
|
|
|
import type { LeftPaneSearchPropsType } from '../../components/leftPane/LeftPaneSearchHelper';
|
|
|
|
import type { PropsDataType as MessageSearchResultPropsDataType } from '../../components/conversationList/MessageSearchResult';
|
2019-08-09 00:46:49 +00:00
|
|
|
|
2021-11-01 18:43:02 +00:00
|
|
|
import { getIntl, getUserConversationId } from './user';
|
2021-10-26 19:15:33 +00:00
|
|
|
import type { GetConversationByIdType } from './conversations';
|
2019-08-09 00:46:49 +00:00
|
|
|
import {
|
2019-01-14 21:49:58 +00:00
|
|
|
getConversationLookup,
|
2019-08-09 00:46:49 +00:00
|
|
|
getConversationSelector,
|
2019-01-14 21:49:58 +00:00
|
|
|
} from './conversations';
|
2019-03-12 00:20:16 +00:00
|
|
|
|
2023-04-10 16:31:45 +00:00
|
|
|
import type { HydratedBodyRangeType } from '../../types/BodyRange';
|
|
|
|
import { BodyRange } from '../../types/BodyRange';
|
2021-09-17 18:27:53 +00:00
|
|
|
import * as log from '../../logging/log';
|
2021-11-01 18:43:02 +00:00
|
|
|
import { getOwn } from '../../util/getOwn';
|
2023-04-10 16:31:45 +00:00
|
|
|
import { missingCaseError } from '../../util';
|
2021-03-05 17:57:09 +00:00
|
|
|
|
2019-01-14 21:49:58 +00:00
|
|
|
export const getSearch = (state: StateType): SearchStateType => state.search;
|
|
|
|
|
|
|
|
export const getQuery = createSelector(
|
|
|
|
getSearch,
|
|
|
|
(state: SearchStateType): string => state.query
|
|
|
|
);
|
|
|
|
|
|
|
|
export const getSelectedMessage = createSelector(
|
|
|
|
getSearch,
|
2023-03-20 22:23:53 +00:00
|
|
|
(state: SearchStateType): string | undefined => state.targetedMessage
|
2019-01-14 21:49:58 +00:00
|
|
|
);
|
|
|
|
|
2021-11-01 18:43:02 +00:00
|
|
|
const getSearchConversationId = createSelector(
|
2019-08-09 23:12:29 +00:00
|
|
|
getSearch,
|
|
|
|
(state: SearchStateType): string | undefined => state.searchConversationId
|
|
|
|
);
|
|
|
|
|
2021-10-12 23:59:08 +00:00
|
|
|
export const getIsSearchingInAConversation = createSelector(
|
|
|
|
getSearchConversationId,
|
|
|
|
Boolean
|
|
|
|
);
|
|
|
|
|
2023-02-22 19:21:59 +00:00
|
|
|
export const getIsSearchingGlobally = createSelector(
|
2023-02-22 20:40:35 +00:00
|
|
|
getSearch,
|
|
|
|
(state: SearchStateType): boolean => Boolean(state.globalSearch)
|
2023-02-22 19:21:59 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
export const getIsSearching = createSelector(
|
|
|
|
getIsSearchingInAConversation,
|
|
|
|
getIsSearchingGlobally,
|
|
|
|
(isSearchingInAConversation, isSearchingGlobally): boolean =>
|
|
|
|
isSearchingInAConversation || isSearchingGlobally
|
|
|
|
);
|
|
|
|
|
2021-11-01 18:43:02 +00:00
|
|
|
export const getSearchConversation = createSelector(
|
|
|
|
getSearchConversationId,
|
|
|
|
getConversationLookup,
|
|
|
|
(searchConversationId, conversationLookup): undefined | ConversationType =>
|
|
|
|
searchConversationId
|
|
|
|
? getOwn(conversationLookup, searchConversationId)
|
|
|
|
: undefined
|
|
|
|
);
|
|
|
|
|
2019-08-09 23:12:29 +00:00
|
|
|
export const getSearchConversationName = createSelector(
|
2021-11-01 18:43:02 +00:00
|
|
|
getSearchConversation,
|
|
|
|
getIntl,
|
|
|
|
(conversation, i18n): undefined | string => {
|
|
|
|
if (!conversation) {
|
|
|
|
return undefined;
|
|
|
|
}
|
2023-03-30 00:03:25 +00:00
|
|
|
return conversation.isMe ? i18n('icu:noteToSelf') : conversation.title;
|
2021-11-01 18:43:02 +00:00
|
|
|
}
|
2019-08-09 23:12:29 +00:00
|
|
|
);
|
|
|
|
|
2019-11-07 21:36:16 +00:00
|
|
|
export const getStartSearchCounter = createSelector(
|
|
|
|
getSearch,
|
|
|
|
(state: SearchStateType): number => state.startSearchCounter
|
|
|
|
);
|
|
|
|
|
2019-01-14 21:49:58 +00:00
|
|
|
export const isSearching = createSelector(
|
2021-03-18 20:36:47 +00:00
|
|
|
getQuery,
|
2021-04-14 10:52:37 +00:00
|
|
|
(query: string): boolean => query.trim().length > 0
|
2019-01-14 21:49:58 +00:00
|
|
|
);
|
|
|
|
|
2019-08-09 00:46:49 +00:00
|
|
|
export const getMessageSearchResultLookup = createSelector(
|
|
|
|
getSearch,
|
|
|
|
(state: SearchStateType) => state.messageLookup
|
|
|
|
);
|
2021-02-23 20:34:28 +00:00
|
|
|
|
2019-01-14 21:49:58 +00:00
|
|
|
export const getSearchResults = createSelector(
|
2021-11-01 18:43:02 +00:00
|
|
|
[getSearch, getSearchConversationName, getConversationLookup],
|
2019-01-14 21:49:58 +00:00
|
|
|
(
|
|
|
|
state: SearchStateType,
|
2021-11-01 18:43:02 +00:00
|
|
|
searchConversationName,
|
2021-02-23 20:34:28 +00:00
|
|
|
conversationLookup: ConversationLookupType
|
2022-01-27 22:12:26 +00:00
|
|
|
): Pick<
|
|
|
|
LeftPaneSearchPropsType,
|
|
|
|
| 'conversationResults'
|
|
|
|
| 'contactResults'
|
|
|
|
| 'messageResults'
|
|
|
|
| 'searchConversationName'
|
|
|
|
| 'searchTerm'
|
|
|
|
> => {
|
2019-08-09 23:12:29 +00:00
|
|
|
const {
|
2021-02-23 20:34:28 +00:00
|
|
|
contactIds,
|
|
|
|
conversationIds,
|
2019-09-04 14:46:28 +00:00
|
|
|
discussionsLoading,
|
2019-08-09 23:12:29 +00:00
|
|
|
messageIds,
|
2021-02-23 20:34:28 +00:00
|
|
|
messageLookup,
|
2019-09-04 14:46:28 +00:00
|
|
|
messagesLoading,
|
2019-08-09 23:12:29 +00:00
|
|
|
} = state;
|
2019-08-09 00:46:49 +00:00
|
|
|
|
2021-02-23 20:34:28 +00:00
|
|
|
return {
|
|
|
|
conversationResults: discussionsLoading
|
|
|
|
? { isLoading: true }
|
|
|
|
: {
|
|
|
|
isLoading: false,
|
|
|
|
results: deconstructLookup(conversationLookup, conversationIds),
|
2019-08-09 00:46:49 +00:00
|
|
|
},
|
2021-02-23 20:34:28 +00:00
|
|
|
contactResults: discussionsLoading
|
|
|
|
? { isLoading: true }
|
|
|
|
: {
|
|
|
|
isLoading: false,
|
|
|
|
results: deconstructLookup(conversationLookup, contactIds),
|
|
|
|
},
|
|
|
|
messageResults: messagesLoading
|
|
|
|
? { isLoading: true }
|
|
|
|
: {
|
|
|
|
isLoading: false,
|
|
|
|
results: deconstructLookup(messageLookup, messageIds),
|
2019-08-09 00:46:49 +00:00
|
|
|
},
|
2019-08-09 23:12:29 +00:00
|
|
|
searchConversationName,
|
2019-01-14 21:49:58 +00:00
|
|
|
searchTerm: state.query,
|
2019-08-09 00:46:49 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
// A little optimization to reset our selector cache whenever high-level application data
|
|
|
|
// changes: regionCode and userNumber.
|
|
|
|
type CachedMessageSearchResultSelectorType = (
|
|
|
|
message: MessageSearchResultType,
|
2021-01-06 15:41:43 +00:00
|
|
|
from: ConversationType,
|
|
|
|
to: ConversationType,
|
2019-08-09 23:12:29 +00:00
|
|
|
searchConversationId?: string,
|
2023-03-20 22:23:53 +00:00
|
|
|
targetedMessageId?: string
|
2019-08-09 00:46:49 +00:00
|
|
|
) => MessageSearchResultPropsDataType;
|
2021-03-05 17:57:09 +00:00
|
|
|
|
2019-08-09 00:46:49 +00:00
|
|
|
export const getCachedSelectorForMessageSearchResult = createSelector(
|
2021-01-06 15:41:43 +00:00
|
|
|
getUserConversationId,
|
2021-03-05 17:57:09 +00:00
|
|
|
getConversationSelector,
|
|
|
|
(
|
|
|
|
_,
|
|
|
|
conversationSelector: GetConversationByIdType
|
|
|
|
): CachedMessageSearchResultSelectorType => {
|
2019-08-09 00:46:49 +00:00
|
|
|
// Note: memoizee will check all parameters provided, and only run our selector
|
|
|
|
// if any of them have changed.
|
2021-03-05 17:57:09 +00:00
|
|
|
return memoizee(
|
|
|
|
(
|
|
|
|
message: MessageSearchResultType,
|
|
|
|
from: ConversationType,
|
|
|
|
to: ConversationType,
|
|
|
|
searchConversationId?: string,
|
2023-03-20 22:23:53 +00:00
|
|
|
targetedMessageId?: string
|
2021-03-05 17:57:09 +00:00
|
|
|
) => {
|
|
|
|
const bodyRanges = message.bodyRanges || [];
|
|
|
|
return {
|
|
|
|
from,
|
|
|
|
to,
|
|
|
|
|
|
|
|
id: message.id,
|
|
|
|
conversationId: message.conversationId,
|
|
|
|
sentAt: message.sent_at,
|
2021-06-17 17:15:10 +00:00
|
|
|
snippet: message.snippet || '',
|
2023-04-10 16:31:45 +00:00
|
|
|
bodyRanges: bodyRanges.map((range): HydratedBodyRangeType => {
|
|
|
|
// Hydrate user information on mention
|
|
|
|
if (BodyRange.isMention(range)) {
|
|
|
|
const conversation = conversationSelector(range.mentionUuid);
|
2022-11-10 04:59:36 +00:00
|
|
|
|
|
|
|
return {
|
2023-04-10 16:31:45 +00:00
|
|
|
...range,
|
2022-11-10 04:59:36 +00:00
|
|
|
conversationID: conversation.id,
|
|
|
|
replacementText: conversation.title,
|
|
|
|
};
|
|
|
|
}
|
2023-04-10 16:31:45 +00:00
|
|
|
|
|
|
|
if (BodyRange.isFormatting(range)) {
|
|
|
|
return range;
|
|
|
|
}
|
|
|
|
|
|
|
|
throw missingCaseError(range);
|
|
|
|
}),
|
2021-06-17 17:15:10 +00:00
|
|
|
body: message.body || '',
|
2021-03-05 17:57:09 +00:00
|
|
|
|
|
|
|
isSelected: Boolean(
|
2023-03-20 22:23:53 +00:00
|
|
|
targetedMessageId && message.id === targetedMessageId
|
2021-03-05 17:57:09 +00:00
|
|
|
),
|
|
|
|
isSearchingInConversation: Boolean(searchConversationId),
|
|
|
|
};
|
|
|
|
},
|
|
|
|
{ max: 500 }
|
|
|
|
);
|
2019-08-09 00:46:49 +00:00
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
type GetMessageSearchResultByIdType = (
|
|
|
|
id: string
|
|
|
|
) => MessageSearchResultPropsDataType | undefined;
|
2021-03-05 17:57:09 +00:00
|
|
|
|
2019-08-09 00:46:49 +00:00
|
|
|
export const getMessageSearchResultSelector = createSelector(
|
|
|
|
getCachedSelectorForMessageSearchResult,
|
|
|
|
getMessageSearchResultLookup,
|
|
|
|
getSelectedMessage,
|
|
|
|
getConversationSelector,
|
2019-08-09 23:12:29 +00:00
|
|
|
getSearchConversationId,
|
2021-01-06 15:41:43 +00:00
|
|
|
getUserConversationId,
|
2019-08-09 00:46:49 +00:00
|
|
|
(
|
|
|
|
messageSearchResultSelector: CachedMessageSearchResultSelectorType,
|
|
|
|
messageSearchResultLookup: MessageSearchResultLookupType,
|
2023-03-20 22:23:53 +00:00
|
|
|
targetedMessageId: string | undefined,
|
2019-08-09 00:46:49 +00:00
|
|
|
conversationSelector: GetConversationByIdType,
|
2019-08-09 23:12:29 +00:00
|
|
|
searchConversationId: string | undefined,
|
2022-02-23 18:48:40 +00:00
|
|
|
ourConversationId: string | undefined
|
2019-08-09 00:46:49 +00:00
|
|
|
): GetMessageSearchResultByIdType => {
|
|
|
|
return (id: string) => {
|
|
|
|
const message = messageSearchResultLookup[id];
|
|
|
|
if (!message) {
|
2021-09-17 18:27:53 +00:00
|
|
|
log.warn(
|
2021-01-06 15:41:43 +00:00
|
|
|
`getMessageSearchResultSelector: messageSearchResultLookup was missing id ${id}`
|
|
|
|
);
|
2020-09-14 21:56:35 +00:00
|
|
|
return undefined;
|
2019-08-09 00:46:49 +00:00
|
|
|
}
|
|
|
|
|
2021-01-06 15:41:43 +00:00
|
|
|
const { conversationId, source, sourceUuid, type } = message;
|
|
|
|
let from: ConversationType;
|
|
|
|
let to: ConversationType;
|
2019-08-09 00:46:49 +00:00
|
|
|
|
|
|
|
if (type === 'incoming') {
|
2021-01-06 15:41:43 +00:00
|
|
|
from = conversationSelector(sourceUuid || source);
|
|
|
|
to = conversationSelector(conversationId);
|
2021-04-08 00:27:27 +00:00
|
|
|
if (from === to) {
|
|
|
|
to = conversationSelector(ourConversationId);
|
|
|
|
}
|
2019-08-09 00:46:49 +00:00
|
|
|
} else if (type === 'outgoing') {
|
2021-01-06 15:41:43 +00:00
|
|
|
from = conversationSelector(ourConversationId);
|
|
|
|
to = conversationSelector(conversationId);
|
|
|
|
} else {
|
2021-09-17 18:27:53 +00:00
|
|
|
log.warn(`getMessageSearchResultSelector: Got unexpected type ${type}`);
|
2021-01-06 15:41:43 +00:00
|
|
|
return undefined;
|
2019-08-09 00:46:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return messageSearchResultSelector(
|
|
|
|
message,
|
2021-01-06 15:41:43 +00:00
|
|
|
from,
|
|
|
|
to,
|
2019-08-09 23:12:29 +00:00
|
|
|
searchConversationId,
|
2023-03-20 22:23:53 +00:00
|
|
|
targetedMessageId
|
2019-08-09 00:46:49 +00:00
|
|
|
);
|
2019-01-14 21:49:58 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
);
|