Skip to content
5 changes: 3 additions & 2 deletions app/containers/MessageBox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -571,7 +571,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
};

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);
Expand Down
8 changes: 8 additions & 0 deletions app/definitions/ISubscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Comment on lines +21 to +28
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed?

Copy link
Contributor Author

@samay-kothari samay-kothari Apr 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think later in the comment you have mentioned the usage of roomTypeToApiType, but here we need an enumeration that maps the string literals 'p', 'd', etc, to Subscription type of the room, since the function getRoomMembers requires it in this data type only.
The roomType gives us data of type RoomType and I wasn't able to find a way to map RoomType to SubscriptionType.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An alternative to using this could be add an async call to getRoom function from lib/methods/getRoom and then get the roomSubscriptionType from there.
Right now I am receiving the roomType and rid as a string through props.


export interface IVisitor {
_id?: string;
Expand Down
102 changes: 94 additions & 8 deletions app/lib/methods/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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,
Expand All @@ -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;
};

Expand Down Expand Up @@ -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 = (<any>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;
}
};