Make it so a space after autocomplete channels and users closes the autocomplete (#6589)

This commit is contained in:
Daniel Espino García
2022-08-23 09:56:04 +02:00
committed by GitHub
parent 1035a3c2e0
commit f874279d87
3 changed files with 56 additions and 32 deletions

View File

@@ -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<UserModel | UserProfile>, 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<UserProfile | UserModel>, usersInChannel: Array<UserProfile | UserModel>, usersOutOfChannel: Array<UserProfile | UserModel>, 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<UserModel | UserProfile> = [];
const emptySectionList: UserMentionSections = [];
const emptyGroupList: GroupModel[] = [];
@@ -232,15 +235,15 @@ const AtMention = ({
const style = getStyleFromTheme(theme);
const [sections, setSections] = useState<UserMentionSections>(emptySectionList);
const [usersInChannel, setUsersInChannel] = useState<UserProfile[]>(emptyProfileList);
const [usersOutOfChannel, setUsersOutOfChannel] = useState<UserProfile[]>(emptyProfileList);
const [usersInChannel, setUsersInChannel] = useState<Array<UserProfile | UserModel>>(emptyUserlList);
const [usersOutOfChannel, setUsersOutOfChannel] = useState<Array<UserProfile | UserModel>>(emptyUserlList);
const [groups, setGroups] = useState<GroupModel[]>(emptyGroupList);
const [loading, setLoading] = useState(false);
const [noResultsTerm, setNoResultsTerm] = useState<string|null>(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<UserModel[]>();
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]);

View File

@@ -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<Channel | ChannelModel>, 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<Channel | ChannelModel>, 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<SectionListData<Channel>> = [];
const emptyChannels: Channel[] = [];
const emptyModelList: ChannelModel[] = [];
const emptyChannels: Array<Channel | ChannelModel> = [];
const ChannelMention = ({
cursorPosition,
@@ -204,13 +205,13 @@ const ChannelMention = ({
const style = getStyleFromTheme(theme);
const [sections, setSections] = useState<Array<SectionListData<(Channel | ChannelModel)>>>(emptySections);
const [channels, setChannels] = useState<Channel[]>(emptyChannels);
const [channels, setChannels] = useState<Array<ChannelModel | Channel>>(emptyChannels);
const [loading, setLoading] = useState(false);
const [noResultsTerm, setNoResultsTerm] = useState<string|null>(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<ChannelModel[]>();
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<Channel | ChannelModel> = 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();

View File

@@ -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;
}