diff --git a/ts/components/SessionSearchInput.tsx b/ts/components/SessionSearchInput.tsx index 3d1a21f54..1836e85d0 100644 --- a/ts/components/SessionSearchInput.tsx +++ b/ts/components/SessionSearchInput.tsx @@ -1,12 +1,11 @@ import { Dispatch } from '@reduxjs/toolkit'; -import { debounce } from 'lodash'; +import { debounce, isEmpty } from 'lodash'; import { useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import styled from 'styled-components'; import { clearSearch, search, updateSearchTerm } from '../state/ducks/search'; import { getConversationsCount } from '../state/selectors/conversations'; import { getLeftOverlayMode } from '../state/selectors/section'; -import { cleanSearchTerm } from '../util/cleanSearchTerm'; import { SessionIconButton } from './icon'; const StyledSearchInput = styled.div` @@ -55,7 +54,7 @@ const doTheSearch = (dispatch: Dispatch, cleanedTerm: string) => { const debouncedSearch = debounce(doTheSearch, 50); function updateSearch(dispatch: Dispatch, searchTerm: string) { - if (!searchTerm) { + if (isEmpty(searchTerm)) { dispatch(clearSearch()); return; } @@ -63,15 +62,6 @@ function updateSearch(dispatch: Dispatch, searchTerm: string) { // this updates our current state and text field. dispatch(updateSearchTerm(searchTerm)); - if (searchTerm.length < 2) { - return; - } - // this effectively trigger a search - const cleanedTerm = cleanSearchTerm(searchTerm); - if (!cleanedTerm) { - return; - } - debouncedSearch(dispatch, searchTerm); } export const SessionSearchInput = () => { diff --git a/ts/state/ducks/search.ts b/ts/state/ducks/search.ts index 359e9a9e4..2c73d8c1a 100644 --- a/ts/state/ducks/search.ts +++ b/ts/state/ducks/search.ts @@ -191,6 +191,7 @@ function getAdvancedSearchOptionsFromQuery(query: string): AdvancedSearchOptions async function queryMessages(query: string): Promise> { try { const trimmedQuery = query.trim(); + // we clean the search term to avoid special characters since the query is referenced in the SQL query directly const normalized = cleanSearchTerm(trimmedQuery); // 200 on a large database is already pretty slow const limit = Math.min((trimmedQuery.length || 2) * 50, 200); @@ -203,7 +204,9 @@ async function queryMessages(query: string): Promise> async function queryConversationsAndContacts(providedQuery: string, options: SearchOptions) { const { ourNumber, noteToSelf, savedMessages, filter } = options; + // we don't need to use cleanSearchTerm here because the query is wrapped as a wild card and is not referenced in the SQL query directly const query = providedQuery.replace(/[+-.()]*/g, ''); + const contactsOnly = filter === 'contacts'; const conversationsOnly = filter === 'conversations'; @@ -239,9 +242,14 @@ async function queryConversationsAndContacts(providedQuery: string, options: Sea } } - const queryLowered = providedQuery.toLowerCase(); + const queryLowered = query.toLowerCase(); // Inject synthetic Note to Self entry if query matches localized 'Note to Self' - if (noteToSelf.includes(queryLowered) || savedMessages.includes(queryLowered)) { + if ( + noteToSelf.includes(query) || + noteToSelf.includes(queryLowered) || + savedMessages.includes(query) || + savedMessages.includes(queryLowered) + ) { // Ensure that we don't have duplicates in our results contacts = contacts.filter(id => id !== ourNumber); conversations = conversations.filter(id => id !== ourNumber); diff --git a/ts/state/selectors/search.ts b/ts/state/selectors/search.ts index b9cfa0b70..84e554200 100644 --- a/ts/state/selectors/search.ts +++ b/ts/state/selectors/search.ts @@ -75,23 +75,15 @@ export type SearchResultsMergedListItem = export const getSearchResultsList = createSelector([getSearchResults], searchState => { const { contactsAndConversations, messages } = searchState; + window.log.debug( + `WIP: [getSearchResultsList] contactsAndConversations ${JSON.stringify(contactsAndConversations)}` + ); const builtList: Array = []; if (contactsAndConversations.length) { const us = UserUtils.getOurPubKeyStrFromCache(); let usIndex: number = -1; - for (let i = 0; i < contactsAndConversations.length; i++) { - if (contactsAndConversations[i].id === us) { - usIndex = i; - break; - } - } - - if (usIndex !== -1) { - contactsAndConversations.splice(usIndex, 1); - } - const idsAndDisplayNames = contactsAndConversations.map(m => ({ contactConvoId: m.id, displayName: m.nickname || m.displayNameInProfile, @@ -104,14 +96,22 @@ export const getSearchResultsList = createSelector([getSearchResults], searchSta if (idsWithDisplayNames.length) { // add a break wherever needed let currentChar = ''; - - idsWithDisplayNames.forEach(m => { - if (m.displayName && m.displayName[0].toLowerCase() !== currentChar) { + for (let i = 0; i < idsWithDisplayNames.length; i++) { + const m = idsWithDisplayNames[i]; + if (m.contactConvoId === us) { + usIndex = i; + continue; + } + if ( + idsWithDisplayNames.length > 1 && + m.displayName && + m.displayName[0].toLowerCase() !== currentChar + ) { currentChar = m.displayName[0].toLowerCase(); builtList.push(currentChar.toUpperCase()); } builtList.push(m); - }); + } if (usIndex !== -1) { builtList.unshift({ contactConvoId: us, displayName: window.i18n('noteToSelf') }); diff --git a/ts/util/cleanSearchTerm.ts b/ts/util/cleanSearchTerm.ts index 32e7f5dd3..f78fb5b16 100644 --- a/ts/util/cleanSearchTerm.ts +++ b/ts/util/cleanSearchTerm.ts @@ -3,7 +3,6 @@ export function cleanSearchTerm(searchTerm: string) { const withoutSpecialCharacters = lowercase.replace(/([!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~])/g, ' '); const whiteSpaceNormalized = withoutSpecialCharacters.replace(/\s+/g, ' '); const byToken = whiteSpaceNormalized.split(' '); - // be aware that a user typing Note To Self will have an issue when the `not` part of it is typed as the not word is reserved const withoutSpecialTokens = byToken.filter( token => token &&