Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion app/containers/MessageBox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -658,7 +658,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
};

getUsers = debounce(async (keyword: any) => {
let res = await search({ text: keyword, filterRooms: false, filterUsers: true });
const { rid } = this.props;
let res = await search({ text: keyword, filterRooms: false, filterUsers: true, rid });
res = [...this.getFixedMentions(keyword), ...res];
this.setState({ mentions: res, mentionLoading: false });
}, 300);
Expand Down
3 changes: 3 additions & 0 deletions app/lib/constants/defaultSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,5 +226,8 @@ export const defaultSettings = {
},
Accounts_AllowDeleteOwnAccount: {
type: 'valueAsBoolean'
},
Number_of_users_autocomplete_suggestions: {
type: 'valueAsNumber'
}
} as const;
54 changes: 44 additions & 10 deletions app/lib/methods/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ import { Q } from '@nozbe/watermelondb';

import { sanitizeLikeString } from '../database/utils';
import database from '../database/index';
import { store as reduxStore } from '../store/auxStore';
import { spotlight } from '../services/restApi';
import { ISearch, ISearchLocal, SubscriptionType } from '../../definitions';
import { ISearch, ISearchLocal, IUserMessage, SubscriptionType } from '../../definitions';
import { isGroupChat } from './helpers';

export type TSearch = ISearchLocal | IUserMessage | ISearch;

let debounce: null | ((reason: string) => void) = null;

export const localSearch = async ({ text = '', filterUsers = true, filterRooms = true }): Promise<ISearchLocal[]> => {
export const localSearchSubscription = async ({ text = '', filterUsers = true, filterRooms = true }): Promise<ISearchLocal[]> => {
const searchText = text.trim();
const db = database.active;
const likeString = sanitizeLikeString(searchText);
Expand Down Expand Up @@ -41,29 +44,60 @@ export const localSearch = async ({ text = '', filterUsers = true, filterRooms =
return search;
};

export const search = async ({ text = '', filterUsers = true, filterRooms = true }): Promise<(ISearch | ISearchLocal)[]> => {
export const localSearchUsersMessageByRid = async ({ text = '', rid = '' }): Promise<IUserMessage[]> => {
const userId = reduxStore.getState().login.user.id;
const numberOfSuggestions = reduxStore.getState().settings.Number_of_users_autocomplete_suggestions as number;
const searchText = text.trim();
const db = database.active;
const likeString = sanitizeLikeString(searchText);
const messages = await db
.get('messages')
.query(
Q.and(Q.where('rid', rid), Q.where('u', Q.notLike(`%${userId}%`)), Q.where('t', null)),
Q.experimentalSortBy('ts', Q.desc),
Q.experimentalTake(50)
)
.fetch();

const regExp = new RegExp(`${likeString}`, 'i');
const users = messages.map(message => message.u);

const usersFromLocal = users
.filter((item1, index) => users.findIndex(item2 => item2._id === item1._id) === index) // Remove duplicated data from response
.filter(user => user?.name?.match(regExp) || user?.username?.match(regExp))
.slice(0, text ? 2 : numberOfSuggestions);

return usersFromLocal;
};

export const search = async ({ text = '', filterUsers = true, filterRooms = true, rid = '' }): Promise<TSearch[]> => {
const searchText = text.trim();

if (debounce) {
debounce('cancel');
}

const localSearchData = await localSearch({ text, filterUsers, filterRooms });
const usernames = localSearchData.map(sub => sub.name);
let localSearchData = [];
if (rid) {
localSearchData = await localSearchUsersMessageByRid({ text, rid });
} else {
localSearchData = await localSearchSubscription({ text, filterUsers, filterRooms });
}
const usernames = localSearchData.map(sub => sub.name as string);

const data = localSearchData as (ISearch | ISearchLocal)[];
const data: TSearch[] = localSearchData;

try {
if (localSearchData.length < 7) {
if (searchText && localSearchData.length < 7) {
const { users, rooms } = (await Promise.race([
spotlight(searchText, usernames, { users: filterUsers, rooms: filterRooms }),
spotlight(searchText, usernames, { users: filterUsers, rooms: filterRooms }, rid),
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
.filter(user => !data.some(sub => 'username' in sub && user.username === sub.username)) // Make sure to remove users already on local database
.forEach(user => {
data.push({
...user,
Expand All @@ -77,7 +111,7 @@ export const search = async ({ text = '', filterUsers = true, filterRooms = true
if (filterRooms) {
rooms.forEach(room => {
// Check if it exists on local database
const index = data.findIndex(item => item.rid === room._id);
const index = data.findIndex(item => 'rid' in item && item.rid === room._id);
if (index === -1) {
data.push({
...room,
Expand Down
11 changes: 9 additions & 2 deletions app/lib/services/restApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,16 @@ export const forgotPassword = (email: string) =>
export const sendConfirmationEmail = (email: string): Promise<{ message: string; success: boolean }> =>
sdk.methodCallWrapper('sendConfirmationEmail', email);

export const spotlight = (search: string, usernames: string[], type: { users: boolean; rooms: boolean }): Promise<ISpotlight> =>
export const spotlight = (
search: string,
usernames: string[],
type: { users: boolean; rooms: boolean },
rid?: string
): Promise<ISpotlight> =>
// RC 0.51.0
sdk.methodCallWrapper('spotlight', search, usernames, type);
rid
? sdk.methodCallWrapper('spotlight', search, usernames, type, rid)
: sdk.methodCallWrapper('spotlight', search, usernames, type);

export const createDirectMessage = (username: string) =>
// RC 0.59.0
Expand Down
4 changes: 2 additions & 2 deletions app/views/CreateDiscussionView/SelectChannel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import I18n from '../../i18n';
import { getAvatarURL } from '../../lib/methods/helpers/getAvatarUrl';
import { ICreateDiscussionViewSelectChannel } from './interfaces';
import styles from './styles';
import { localSearch } from '../../lib/methods';
import { localSearchSubscription } from '../../lib/methods';
import { getRoomAvatar, getRoomTitle } from '../../lib/methods/helpers';
import { useTheme } from '../../theme';

Expand All @@ -25,7 +25,7 @@ const SelectChannel = ({

const getChannels = async (keyword = '') => {
try {
const res = (await localSearch({ text: keyword, filterUsers: false })) as ISearchLocal[];
const res = (await localSearchSubscription({ text: keyword, filterUsers: false })) as ISearchLocal[];
setChannels(res);
return res.map(channel => ({
value: channel,
Expand Down
7 changes: 2 additions & 5 deletions app/views/SelectedUsersView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import * as List from '../../containers/List';
import { sendLoadingEvent } from '../../containers/Loading';
import SafeAreaView from '../../containers/SafeAreaView';
import StatusBar from '../../containers/StatusBar';
import { ISearch, ISearchLocal } from '../../definitions';
import I18n from '../../i18n';
import database from '../../lib/database';
import UserItem from '../../containers/UserItem';
Expand All @@ -23,19 +22,17 @@ import { ChatsStackParamList } from '../../stacks/types';
import { useTheme } from '../../theme';
import { showErrorAlert } from '../../lib/methods/helpers/info';
import log, { events, logEvent } from '../../lib/methods/helpers/log';
import { search as searchMethod } from '../../lib/methods';
import { search as searchMethod, TSearch } from '../../lib/methods';
import { isGroupChat as isGroupChatMethod } from '../../lib/methods/helpers';
import { useAppSelector } from '../../lib/hooks';
import Header from './Header';

type TRoute = RouteProp<ChatsStackParamList, 'SelectedUsersView'>;
type TNavigation = StackNavigationProp<ChatsStackParamList, 'SelectedUsersView'>;

type TSearchItem = ISearch | ISearchLocal;

const SelectedUsersView = () => {
const [chats, setChats] = useState<ISelectedUser[]>([]);
const [search, setSearch] = useState<TSearchItem[]>([]);
const [search, setSearch] = useState<TSearch[]>([]);

const { maxUsers, showButton, title, buttonText, nextAction } = useRoute<TRoute>().params;
const navigation = useNavigation<TNavigation>();
Expand Down