diff --git a/app/containers/MessageBox/index.tsx b/app/containers/MessageBox/index.tsx index 647c0e614ca..597fbadf8e9 100644 --- a/app/containers/MessageBox/index.tsx +++ b/app/containers/MessageBox/index.tsx @@ -51,7 +51,7 @@ import { IMessage } from '../../definitions/IMessage'; import { forceJpgExtension } from './forceJpgExtension'; import { IBaseScreen, IPreviewItem, IUser, TSubscriptionModel, TThreadModel } from '../../definitions'; import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types'; -import { getPermalinkMessage, hasPermission, search, sendFileMessage } from '../../lib/methods'; +import { getPermalinkMessage, hasPermission, search, sendFileMessage, mentionsSearch } from '../../lib/methods'; import { Services } from '../../lib/services'; import { TSupportedThemes } from '../../theme'; @@ -571,7 +571,8 @@ class MessageBox extends Component { }; getUsers = debounce(async (keyword: any) => { - let res = await search({ text: keyword, filterRooms: false, filterUsers: true }); + const { rid, roomType } = this.props; + let res = await mentionsSearch({ text: keyword, rid, roomType, filterRooms: false, filterUsers: true }); res = [...this.getFixedMentions(keyword), ...res]; this.setState({ mentions: res, mentionLoading: false }); }, 300); diff --git a/app/definitions/ISubscription.ts b/app/definitions/ISubscription.ts index e2e6f2739a7..a0bee7bc2c9 100644 --- a/app/definitions/ISubscription.ts +++ b/app/definitions/ISubscription.ts @@ -18,6 +18,14 @@ export enum SubscriptionType { E2E = 'e2e', // FIXME: this is not a type of subscription THREAD = 'thread' // FIXME: this is not a type of subscription } +export enum ReverseSubscriptionType { + 'p' = SubscriptionType.GROUP, + 'd' = SubscriptionType.DIRECT, + 'c' = SubscriptionType.CHANNEL, + 'l' = SubscriptionType.OMNICHANNEL, + 'e2e' = SubscriptionType.E2E, + 't' = SubscriptionType.THREAD +} export interface IVisitor { _id?: string; diff --git a/app/lib/methods/search.ts b/app/lib/methods/search.ts index d8f0f7d1a5b..739727d881c 100644 --- a/app/lib/methods/search.ts +++ b/app/lib/methods/search.ts @@ -2,13 +2,18 @@ import { Q } from '@nozbe/watermelondb'; import { sanitizeLikeString } from '../database/utils'; import database from '../database/index'; -import { spotlight } from '../services/restApi'; -import { ISearch, ISearchLocal, SubscriptionType } from '../../definitions'; +import { spotlight, getRoomMembers } from '../services/restApi'; +import { ISearch, ISearchLocal, SubscriptionType, ReverseSubscriptionType } from '../../definitions'; import { isGroupChat } from './helpers'; let debounce: null | ((reason: string) => void) = null; -export const localSearch = async ({ text = '', filterUsers = true, filterRooms = true }): Promise<(ISearch | ISearchLocal)[]> => { +export const localSearch = async ({ + text = '', + filterUsers = true, + filterRooms = true, + spliceIndex = 7 +}): Promise<(ISearch | ISearchLocal)[]> => { const searchText = text.trim(); const db = database.active; const likeString = sanitizeLikeString(searchText); @@ -19,15 +24,12 @@ export const localSearch = async ({ text = '', filterUsers = true, filterRooms = Q.experimentalSortBy('room_updated_at', Q.desc) ) .fetch(); - if (filterUsers && !filterRooms) { subscriptions = subscriptions.filter(item => item.t === 'd' && !isGroupChat(item)); } else if (!filterUsers && filterRooms) { subscriptions = subscriptions.filter(item => item.t !== 'd' || isGroupChat(item)); } - - const sliceSubscriptions = subscriptions.slice(0, 7); - + const sliceSubscriptions = subscriptions.slice(0, spliceIndex === -1 ? subscriptions.length : spliceIndex); const search = sliceSubscriptions.map(sub => ({ rid: sub.rid, name: sub.name, @@ -38,7 +40,6 @@ export const localSearch = async ({ text = '', filterUsers = true, filterRooms = lastMessage: sub.lastMessage, ...(sub.teamId && { teamId: sub.teamId }) })) as (ISearch | ISearchLocal)[]; - return search; }; @@ -96,3 +97,88 @@ export const search = async ({ text = '', filterUsers = true, filterRooms = true return data; } }; + +export const mentionsSearch = async ({ + text = '', + rid, + roomType, + filterUsers = true, + filterRooms = true +}: { + text: string; + rid: string; + roomType: string; + filterUsers: boolean; + filterRooms: boolean; +}): Promise<(ISearch | ISearchLocal)[]> => { + const searchText = text.trim(); + + if (debounce) { + debounce('cancel'); + } + + const localSearchData = await localSearch({ text, filterUsers, filterRooms, spliceIndex: -1 }); + const usernames = localSearchData.map(sub => sub.name); + + const data = localSearchData as (ISearch | ISearchLocal)[]; + try { + if (localSearchData.length < 7) { + const { users, rooms } = (await Promise.race([ + spotlight(searchText, usernames, { users: filterUsers, rooms: filterRooms }), + new Promise((resolve, reject) => (debounce = reject)) + ])) as { users: ISearch[]; rooms: ISearch[] }; + if (filterUsers) { + users + .filter((item1, index) => users.findIndex(item2 => item2._id === item1._id) === index) // Remove duplicated data from response + .filter(user => !data.some(sub => user.username === sub.name)) // Make sure to remove users already on local database + .forEach(user => { + data.push({ + ...user, + rid: user.username, + name: user.username, + t: SubscriptionType.DIRECT, + search: true + }); + }); + } + if (filterRooms) { + rooms.forEach(room => { + // Check if it exists on local database + const index = data.findIndex(item => item.rid === room._id); + if (index === -1) { + data.push({ + ...room, + rid: room._id, + search: true + }); + } + }); + } + } + const roomTypeSub = (ReverseSubscriptionType)[roomType]; + const regex = new RegExp(`^${searchText}`); + const roomInfo = await getRoomMembers({ + rid, + roomType: roomTypeSub, + type: 'all', + filter: false, + skip: 0, + allUsers: true, + limit: 7 + }); + let setData; + const roomUsers = roomInfo.map((x: any) => x.username); + let tempData = data.filter(user => user.name.match(regex)); + tempData = tempData.filter(user => roomUsers.includes(user.name)); + // TODO: add the conditions to check whether external mentions are allowed + setData = new Set([...tempData, ...data.filter(user => user.name.match(regex))]); + setData = new Set([...setData, ...data.filter(user => roomUsers.includes(user.name))]); + // TODO: add the conditions to check whether external mentions are allowed + setData = new Set([...setData, ...data]); + debounce = null; + return [...setData].splice(0, 7); + } catch (e) { + console.warn(e); + return data; + } +};