Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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 @@ -611,7 +611,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