diff --git a/app/components/autocomplete/at_mention/at_mention.tsx b/app/components/autocomplete/at_mention/at_mention.tsx index 8e0d4ede67..160d5c4455 100644 --- a/app/components/autocomplete/at_mention/at_mention.tsx +++ b/app/components/autocomplete/at_mention/at_mention.tsx @@ -17,6 +17,7 @@ import {useTheme} from '@context/theme'; import DatabaseManager from '@database/manager'; import {t} from '@i18n'; import {queryAllUsers} from '@queries/servers/user'; +import {hasTrailingSpaces} from '@utils/helpers'; import {makeStyleSheetFromTheme} from '@utils/theme'; import type GroupModel from '@typings/database/models/servers/group'; @@ -85,13 +86,16 @@ const keyExtractor = (item: UserProfile) => { return item.id; }; -const filterLocalResults = (users: UserModel[], term: string) => { - return users.filter((u) => - u.username.toLowerCase().startsWith(term) || - u.nickname.toLowerCase().startsWith(term) || - u.firstName.toLowerCase().startsWith(term) || - u.lastName.toLowerCase().startsWith(term), - ); +const filterResults = (users: Array, term: string) => { + return users.filter((u) => { + const firstName = ('firstName' in u ? u.firstName : u.first_name).toLowerCase(); + const lastName = ('lastName' in u ? u.lastName : u.last_name).toLowerCase(); + const fullName = `${firstName} ${lastName}`; + return u.username.toLowerCase().includes(term) || + u.nickname.toLowerCase().includes(term) || + fullName.includes(term) || + u.email.toLowerCase().includes(term); + }); }; const makeSections = (teamMembers: Array, usersInChannel: Array, usersOutOfChannel: Array, groups: GroupModel[], showSpecialMentions: boolean, isLocal = false, isSearch = false) => { @@ -198,8 +202,7 @@ const getStyleFromTheme = makeStyleSheetFromTheme((theme) => { }; }); -const emptyProfileList: UserProfile[] = []; -const emptyModelList: UserModel[] = []; +const emptyUserlList: Array = []; const emptySectionList: UserMentionSections = []; const emptyGroupList: GroupModel[] = []; @@ -232,15 +235,15 @@ const AtMention = ({ const style = getStyleFromTheme(theme); const [sections, setSections] = useState(emptySectionList); - const [usersInChannel, setUsersInChannel] = useState(emptyProfileList); - const [usersOutOfChannel, setUsersOutOfChannel] = useState(emptyProfileList); + const [usersInChannel, setUsersInChannel] = useState>(emptyUserlList); + const [usersOutOfChannel, setUsersOutOfChannel] = useState>(emptyUserlList); const [groups, setGroups] = useState(emptyGroupList); const [loading, setLoading] = useState(false); const [noResultsTerm, setNoResultsTerm] = useState(null); const [localCursorPosition, setLocalCursorPosition] = useState(cursorPosition); // To avoid errors due to delay between value changes and cursor position changes. const [useLocal, setUseLocal] = useState(true); const [localUsers, setLocalUsers] = useState(); - const [filteredLocalUsers, setFilteredLocalUsers] = useState(emptyModelList); + const [filteredLocalUsers, setFilteredLocalUsers] = useState(emptyUserlList); const runSearch = useMemo(() => debounce(async (sUrl: string, term: string, cId?: string) => { setLoading(true); @@ -253,11 +256,19 @@ const AtMention = ({ fallbackUsers = await getAllUsers(sUrl); setLocalUsers(fallbackUsers); } - const filteredUsers = filterLocalResults(fallbackUsers, term); - setFilteredLocalUsers(filteredUsers.length ? filteredUsers : emptyModelList); + const filteredUsers = filterResults(fallbackUsers, term); + setFilteredLocalUsers(filteredUsers.length ? filteredUsers : emptyUserlList); } else if (receivedUsers) { - setUsersInChannel(receivedUsers.users.length ? receivedUsers.users : emptyProfileList); - setUsersOutOfChannel(receivedUsers.out_of_channel?.length ? receivedUsers.out_of_channel : emptyProfileList); + if (hasTrailingSpaces(term)) { + const filteredReceivedUsers = filterResults(receivedUsers.users, term); + const filteredReceivedOutOfChannelUsers = filterResults(receivedUsers.out_of_channel || [], term); + + setUsersInChannel(filteredReceivedUsers.length ? filteredReceivedUsers : emptyUserlList); + setUsersOutOfChannel(filteredReceivedOutOfChannelUsers.length ? filteredReceivedOutOfChannelUsers : emptyUserlList); + } else { + setUsersInChannel(receivedUsers.users.length ? receivedUsers.users : emptyUserlList); + setUsersOutOfChannel(receivedUsers.out_of_channel?.length ? receivedUsers.out_of_channel : emptyUserlList); + } } setLoading(false); @@ -270,9 +281,9 @@ const AtMention = ({ const matchTerm = getMatchTermForAtMention(value.substring(0, localCursorPosition), isSearch); const resetState = () => { - setUsersInChannel(emptyProfileList); - setUsersOutOfChannel(emptyProfileList); - setFilteredLocalUsers(emptyModelList); + setUsersInChannel(emptyUserlList); + setUsersOutOfChannel(emptyUserlList); + setFilteredLocalUsers(emptyUserlList); setSections(emptySectionList); runSearch.cancel(); }; @@ -425,6 +436,10 @@ const AtMention = ({ if (!loading && !nSections && noResultsTerm == null) { setNoResultsTerm(matchTerm); } + + if (nSections && noResultsTerm) { + setNoResultsTerm(null); + } setSections(nSections ? newSections : emptySectionList); onShowingChange(Boolean(nSections)); }, [!useLocal && usersInChannel, !useLocal && usersOutOfChannel, teamMembers, groups, loading, channelId, useLocal && filteredLocalUsers]); diff --git a/app/components/autocomplete/channel_mention/channel_mention.tsx b/app/components/autocomplete/channel_mention/channel_mention.tsx index 561ef7b55f..c8991bb338 100644 --- a/app/components/autocomplete/channel_mention/channel_mention.tsx +++ b/app/components/autocomplete/channel_mention/channel_mention.tsx @@ -17,6 +17,7 @@ import useDidUpdate from '@hooks/did_update'; import {t} from '@i18n'; import {queryAllChannelsForTeam} from '@queries/servers/channel'; import {getCurrentTeamId} from '@queries/servers/system'; +import {hasTrailingSpaces} from '@utils/helpers'; import {makeStyleSheetFromTheme} from '@utils/theme'; import type ChannelModel from '@typings/database/models/servers/channel'; @@ -148,11 +149,12 @@ const makeSections = (channels: Array, myMembers: MyChan return newSections; }; -const filterLocalResults = (channels: ChannelModel[], term: string) => { - return channels.filter((c) => - c.name.toLowerCase().startsWith(term) || - c.displayName.toLowerCase().startsWith(term), - ); +const filterResults = (channels: Array, term: string) => { + return channels.filter((c) => { + const displayName = ('displayName' in c ? c.displayName : c.display_name).toLowerCase(); + return c.name.toLowerCase().includes(term) || + displayName.includes(term); + }); }; type Props = { @@ -186,8 +188,7 @@ const getAllChannels = async (serverUrl: string) => { }; const emptySections: Array> = []; -const emptyChannels: Channel[] = []; -const emptyModelList: ChannelModel[] = []; +const emptyChannels: Array = []; const ChannelMention = ({ cursorPosition, @@ -204,13 +205,13 @@ const ChannelMention = ({ const style = getStyleFromTheme(theme); const [sections, setSections] = useState>>(emptySections); - const [channels, setChannels] = useState(emptyChannels); + const [channels, setChannels] = useState>(emptyChannels); const [loading, setLoading] = useState(false); const [noResultsTerm, setNoResultsTerm] = useState(null); const [localCursorPosition, setLocalCursorPosition] = useState(cursorPosition); // To avoid errors due to delay between value changes and cursor position changes. const [useLocal, setUseLocal] = useState(true); const [localChannels, setlocalChannels] = useState(); - const [filteredLocalChannels, setFilteredLocalChannels] = useState(emptyModelList); + const [filteredLocalChannels, setFilteredLocalChannels] = useState(emptyChannels); const listStyle = useMemo(() => [style.listView, {maxHeight: maxListHeight}] @@ -227,17 +228,21 @@ const ChannelMention = ({ fallbackChannels = await getAllChannels(sUrl); setlocalChannels(fallbackChannels); } - const filteredChannels = filterLocalResults(fallbackChannels, term); - setFilteredLocalChannels(filteredChannels.length ? filteredChannels : emptyModelList); + const filteredChannels = filterResults(fallbackChannels, term); + setFilteredLocalChannels(filteredChannels.length ? filteredChannels : emptyChannels); } else if (receivedChannels) { - setChannels(receivedChannels.length ? receivedChannels : emptyChannels); + let channelsToStore: Array = receivedChannels; + if (hasTrailingSpaces(term)) { + channelsToStore = filterResults(receivedChannels, term); + } + setChannels(channelsToStore.length ? channelsToStore : emptyChannels); } setLoading(false); }, 200), []); const matchTerm = getMatchTermForChannelMention(value.substring(0, localCursorPosition), isSearch); const resetState = () => { - setFilteredLocalChannels(emptyModelList); + setFilteredLocalChannels(emptyChannels); setChannels(emptyChannels); setSections(emptySections); runSearch.cancel(); diff --git a/app/utils/helpers.ts b/app/utils/helpers.ts index 1a95d4c7df..f3cf904263 100644 --- a/app/utils/helpers.ts +++ b/app/utils/helpers.ts @@ -146,3 +146,7 @@ export const pluckUnique = (key: string) => (array: Array<{[key: string]: unknow export function bottomSheetSnapPoint(itemsCount: number, itemHeight: number, bottomInset = 0) { return ((itemsCount + Platform.select({android: 1, default: 0})) * itemHeight) + (bottomInset * 2.5); } + +export function hasTrailingSpaces(term: string) { + return term.length !== term.trimEnd().length; +}