diff --git a/app/components/autocomplete/at_mention/at_mention.tsx b/app/components/autocomplete/at_mention/at_mention.tsx index 697c68b8a..541ec87bf 100644 --- a/app/components/autocomplete/at_mention/at_mention.tsx +++ b/app/components/autocomplete/at_mention/at_mention.tsx @@ -15,7 +15,7 @@ import {AT_MENTION_REGEX, AT_MENTION_SEARCH_REGEX} from '@constants/autocomplete import {useServerUrl} from '@context/server'; import DatabaseManager from '@database/manager'; import {t} from '@i18n'; -import {queryAllUsers} from '@queries/servers/user'; +import {queryAllUsers, getUsersFromDMSorted} from '@queries/servers/user'; import {hasTrailingSpaces} from '@utils/helpers'; import type GroupModel from '@typings/database/models/servers/group'; @@ -23,7 +23,6 @@ import type UserModel from '@typings/database/models/servers/user'; const SECTION_KEY_TEAM_MEMBERS = 'teamMembers'; const SECTION_KEY_IN_CHANNEL = 'inChannel'; -const SECTION_KEY_OUT_OF_CHANNEL = 'outChannel'; const SECTION_KEY_SPECIAL = 'special'; const SECTION_KEY_GROUPS = 'groups'; @@ -96,7 +95,7 @@ const filterResults = (users: Array, term: string) => { }); }; -const makeSections = (teamMembers: Array, usersInChannel: Array, usersOutOfChannel: Array, groups: GroupModel[], showSpecialMentions: boolean, isLocal = false, isSearch = false) => { +const makeSections = (teamMembers: Array, users: Array, groups: GroupModel[], showSpecialMentions: boolean, isLocal = false, isSearch = false) => { const newSections: UserMentionSections = []; if (isSearch) { @@ -136,11 +135,11 @@ const makeSections = (teamMembers: Array, usersInChanne }); } } else { - if (usersInChannel.length) { + if (users.length) { newSections.push({ - id: t('suggestion.mention.members'), - defaultMessage: 'Channel Members', - data: usersInChannel, + id: t('suggestion.mention.users'), + defaultMessage: 'Users', + data: users, key: SECTION_KEY_IN_CHANNEL, }); } @@ -162,15 +161,6 @@ const makeSections = (teamMembers: Array, usersInChanne key: SECTION_KEY_SPECIAL, }); } - - if (usersOutOfChannel.length) { - newSections.push({ - id: t('suggestion.mention.nonmembers'), - defaultMessage: 'Not in Channel', - data: usersOutOfChannel, - key: SECTION_KEY_OUT_OF_CHANNEL, - }); - } } return newSections; }; @@ -248,8 +238,7 @@ const AtMention = ({ const serverUrl = useServerUrl(); const [sections, setSections] = useState(emptySectionList); - const [usersInChannel, setUsersInChannel] = useState>(emptyUserlList); - const [usersOutOfChannel, setUsersOutOfChannel] = useState>(emptyUserlList); + const [users, setUsers] = useState>(emptyUserlList); const [groups, setGroups] = useState(emptyGroupList); const [loading, setLoading] = useState(false); const [noResultsTerm, setNoResultsTerm] = useState(null); @@ -289,30 +278,43 @@ const AtMention = ({ const filteredUsers = filterResults(fallbackUsers, term); setFilteredLocalUsers(filteredUsers.length ? filteredUsers : emptyUserlList); } else if (receivedUsers) { - 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); - } + sortRecievedUsers(sUrl, term, receivedUsers?.users, receivedUsers?.out_of_channel); } setLoading(false); }, 200), []); - const teamMembers = useMemo( - () => [...usersInChannel, ...usersOutOfChannel], - [usersInChannel, usersOutOfChannel], - ); + async function sortRecievedUsers(sUrl: string, term: string, receivedUsers: UserProfile[], outOfChannel: UserProfile[] | undefined) { + const database = DatabaseManager.serverDatabases[sUrl]?.database; + if (!database) { + return; + } + const memberIds = receivedUsers.map((e) => e.id); + const sortedMembers: Array = await getUsersFromDMSorted(database, memberIds); + const sortedMembersId = new Set(sortedMembers.map((e) => e.id)); + + const membersNoDm = receivedUsers.filter((u) => !sortedMembersId.has(u.id)); + sortedMembers.push(...membersNoDm); + + if (outOfChannel?.length) { + const outChannelMemberIds = outOfChannel.map((e) => e.id); + const outSortedMembers = await getUsersFromDMSorted(database, outChannelMemberIds); + sortedMembers.push(...outSortedMembers); + } + + if (hasTrailingSpaces(term)) { + const filteredReceivedUsers = filterResults(sortedMembers, term); + const slicedArray = filteredReceivedUsers.slice(0, 20); + setUsers(slicedArray.length ? slicedArray : emptyUserlList); + } else { + const slicedArray = sortedMembers.slice(0, 20); + setUsers(slicedArray.length ? slicedArray : emptyUserlList); + } + } const matchTerm = getMatchTermForAtMention(value.substring(0, localCursorPosition), isSearch); const resetState = () => { - setUsersInChannel(emptyUserlList); - setUsersOutOfChannel(emptyUserlList); + setUsers(emptyUserlList); setGroups(emptyGroupList); setFilteredLocalUsers(emptyUserlList); setSections(emptySectionList); @@ -430,12 +432,12 @@ const AtMention = ({ return; } const showSpecialMentions = useChannelMentions && matchTerm != null && checkSpecialMentions(matchTerm); - const buildMemberSection = isSearch || (!channelId && teamMembers.length > 0); + const buildMemberSection = isSearch || (!channelId && users.length > 0); let newSections; if (useLocal) { - newSections = makeSections(filteredLocalUsers, [], [], groups, showSpecialMentions, true, buildMemberSection); + newSections = makeSections(filteredLocalUsers, [], groups, showSpecialMentions, true, buildMemberSection); } else { - newSections = makeSections(teamMembers, usersInChannel, usersOutOfChannel, groups, showSpecialMentions, buildMemberSection); + newSections = makeSections(users, users, groups, showSpecialMentions, buildMemberSection); } const nSections = newSections.length; @@ -448,7 +450,7 @@ const AtMention = ({ } setSections(nSections ? newSections : emptySectionList); onShowingChange(Boolean(nSections)); - }, [!useLocal && usersInChannel, !useLocal && usersOutOfChannel, teamMembers, groups, loading, channelId, useLocal && filteredLocalUsers]); + }, [!useLocal && users, users, groups, loading, channelId, useLocal && filteredLocalUsers]); if (sections.length === 0 || noResultsTerm != null) { // If we are not in an active state or the mention has been completed return null so nothing is rendered diff --git a/app/queries/servers/user.ts b/app/queries/servers/user.ts index 0839e1ee0..bfb5ac2ef 100644 --- a/app/queries/servers/user.ts +++ b/app/queries/servers/user.ts @@ -5,6 +5,7 @@ import {Database, Q} from '@nozbe/watermelondb'; import {combineLatest, of as of$} from 'rxjs'; import {distinctUntilChanged, switchMap} from 'rxjs/operators'; +import {General} from '@constants'; import {MM_TABLES} from '@constants/database'; import {getTeammateNameDisplaySetting} from '@helpers/api/preference'; import {sanitizeLikeString} from '@helpers/database'; @@ -17,7 +18,7 @@ import type ChannelMembershipModel from '@typings/database/models/servers/channe import type TeamMembershipModel from '@typings/database/models/servers/team_membership'; import type UserModel from '@typings/database/models/servers/user'; -const {SERVER: {CHANNEL_MEMBERSHIP, USER, TEAM_MEMBERSHIP}} = MM_TABLES; +const {SERVER: {CHANNEL_MEMBERSHIP, USER, TEAM_MEMBERSHIP, CHANNEL, MY_CHANNEL}} = MM_TABLES; export const getUserById = async (database: Database, userId: string) => { try { const userRecord = (await database.get(USER).find(userId)); @@ -135,3 +136,17 @@ export const observeDeactivatedUsers = (database: Database) => { }), ); }; + +export const getUsersFromDMSorted = async (database: Database, memberIds: string[]) => { + try { + return database.get(USER).query( + Q.unsafeSqlQuery(`SELECT DISTINCT u.* FROM User u + INNER JOIN ${CHANNEL_MEMBERSHIP} cm ON cm.user_id=u.id + INNER JOIN ${CHANNEL} c ON c.id=cm.id AND c.type='${General.DM_CHANNEL}' + INNER JOIN ${MY_CHANNEL} my + WHERE cm.user_id IN (${`'${memberIds.join("','")}'`}) + ORDER BY my.last_viewed_at DESC`)).fetch(); + } catch (error) { + return []; + } +}; diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json index 86d7a3d27..32563dce9 100644 --- a/assets/base/i18n/en.json +++ b/assets/base/i18n/en.json @@ -1056,6 +1056,7 @@ "suggestion.mention.here": "Notifies everyone online in this channel", "suggestion.mention.members": "Channel Members", "suggestion.mention.morechannels": "Other Channels", + "suggestion.mention.users": "Users", "suggestion.mention.nonmembers": "Not in Channel", "suggestion.mention.special": "Special Mentions", "suggestion.search.direct": "Direct Messages",