Skip to content

Commit d0e091c

Browse files
committed
fix: remove tokenization payload from user suggestion on select
1 parent 2f30be1 commit d0e091c

File tree

4 files changed

+87
-71
lines changed

4 files changed

+87
-71
lines changed

src/messageComposer/middleware/textComposer/TextComposerMiddlewareExecutor.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { createTextComposerPreValidationMiddleware } from './validation';
44
import { MiddlewareExecutor } from '../../../middleware';
55
import { withCancellation } from '../../../utils/concurrency';
66
import type {
7+
TextComposerMiddleware,
78
TextComposerMiddlewareExecutorOptions,
89
TextComposerMiddlewareValue,
910
} from './types';
@@ -16,7 +17,7 @@ export class TextComposerMiddlewareExecutor extends MiddlewareExecutor<TextCompo
1617
createTextComposerPreValidationMiddleware(composer),
1718
createMentionsMiddleware(composer.channel),
1819
createCommandsMiddleware(composer.channel),
19-
]);
20+
] as TextComposerMiddleware[]);
2021
}
2122
async execute(
2223
eventName: string,

src/messageComposer/middleware/textComposer/mentions.ts

Lines changed: 76 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { TokenizationPayload } from './textMiddlewareUtils';
12
import {
23
getTokenizedSuggestionDisplayName,
34
getTriggerCharWithToken,
@@ -23,7 +24,7 @@ import type { Channel } from '../../../channel';
2324
import { MAX_CHANNEL_MEMBER_COUNT_IN_CHANNEL_QUERY } from '../../../constants';
2425
import type { TextComposerSuggestion } from '../../types';
2526

26-
export type UserSuggestion = TextComposerSuggestion<UserResponse>;
27+
export type UserSuggestion = TextComposerSuggestion<UserResponse & TokenizationPayload>;
2728

2829
// todo: the map is too small - Slavic letters with diacritics are missing for example
2930
export const accentsMap: { [key: string]: string } = {
@@ -152,57 +153,53 @@ export class MentionsSearchSource extends BaseSearchSource<UserSuggestion> {
152153

153154
searchMembersLocally = (searchQuery: string) => {
154155
const { textComposerText } = this.config;
155-
if (!textComposerText) return { items: [] };
156+
if (!textComposerText) return [];
156157

157-
return {
158-
items: this.getMembersAndWatchers()
159-
.filter((user) => {
160-
if (user.id === this.client.userID) return false;
161-
if (!searchQuery) return true;
162-
163-
const updatedId = this.transliterate(removeDiacritics(user.id)).toLowerCase();
164-
const updatedName = this.transliterate(
165-
removeDiacritics(user.name),
166-
).toLowerCase();
167-
const updatedQuery = this.transliterate(
168-
removeDiacritics(searchQuery),
169-
).toLowerCase();
170-
171-
const maxDistance = 3;
172-
const lastDigits = textComposerText.slice(-(maxDistance + 1)).includes('@');
173-
174-
if (updatedName) {
175-
const levenshtein = calculateLevenshtein(updatedQuery, updatedName);
176-
if (
177-
updatedName.includes(updatedQuery) ||
178-
(levenshtein <= maxDistance && lastDigits)
179-
) {
180-
return true;
181-
}
158+
return this.getMembersAndWatchers()
159+
.filter((user) => {
160+
if (user.id === this.client.userID) return false;
161+
if (!searchQuery) return true;
162+
163+
const updatedId = this.transliterate(removeDiacritics(user.id)).toLowerCase();
164+
const updatedName = this.transliterate(removeDiacritics(user.name)).toLowerCase();
165+
const updatedQuery = this.transliterate(
166+
removeDiacritics(searchQuery),
167+
).toLowerCase();
168+
169+
const maxDistance = 3;
170+
const lastDigits = textComposerText.slice(-(maxDistance + 1)).includes('@');
171+
172+
if (updatedName) {
173+
const levenshtein = calculateLevenshtein(updatedQuery, updatedName);
174+
if (
175+
updatedName.includes(updatedQuery) ||
176+
(levenshtein <= maxDistance && lastDigits)
177+
) {
178+
return true;
182179
}
180+
}
183181

184-
const levenshtein = calculateLevenshtein(updatedQuery, updatedId);
182+
const levenshtein = calculateLevenshtein(updatedQuery, updatedId);
185183

186-
return (
187-
updatedId.includes(updatedQuery) || (levenshtein <= maxDistance && lastDigits)
188-
);
189-
})
190-
.sort((a, b) => {
191-
if (!this.memberSort) return (a.name || '').localeCompare(b.name || '');
184+
return (
185+
updatedId.includes(updatedQuery) || (levenshtein <= maxDistance && lastDigits)
186+
);
187+
})
188+
.sort((a, b) => {
189+
if (!this.memberSort) return (a.name || '').localeCompare(b.name || '');
192190

193-
// Apply each sort criteria in order
194-
for (const [field, direction] of Object.entries(this.memberSort)) {
195-
const aValue = a[field as keyof UserResponse];
196-
const bValue = b[field as keyof UserResponse];
191+
// Apply each sort criteria in order
192+
for (const [field, direction] of Object.entries(this.memberSort)) {
193+
const aValue = a[field as keyof UserResponse];
194+
const bValue = b[field as keyof UserResponse];
197195

198-
if (aValue === bValue) continue;
199-
return direction === 1
200-
? String(aValue || '').localeCompare(String(bValue || ''))
201-
: String(bValue || '').localeCompare(String(aValue || ''));
202-
}
203-
return 0;
204-
}),
205-
};
196+
if (aValue === bValue) continue;
197+
return direction === 1
198+
? String(aValue || '').localeCompare(String(bValue || ''))
199+
: String(bValue || '').localeCompare(String(aValue || ''));
200+
}
201+
return 0;
202+
});
206203
};
207204

208205
prepareQueryUsersParams = (searchQuery: string) => ({
@@ -240,32 +237,44 @@ export class MentionsSearchSource extends BaseSearchSource<UserSuggestion> {
240237
queryUsers = async (searchQuery: string) => {
241238
const { filters, sort, options } = this.prepareQueryUsersParams(searchQuery);
242239
const { users } = await this.client.queryUsers(filters, sort, options);
243-
return { items: users };
240+
return users;
244241
};
245242

246243
queryMembers = async (searchQuery: string) => {
247244
const { filters, sort, options } = this.prepareQueryMembersParams(searchQuery);
248245
const response = await this.channel.queryMembers(filters, sort, options);
249246

250-
return { items: response.members.map((member) => member.user) as UserResponse[] };
247+
return response.members.map((member) => member.user) as UserResponse[];
251248
};
252249

253250
async query(searchQuery: string) {
254-
if (this.config.mentionAllAppUsers) {
255-
return await this.queryUsers(searchQuery);
256-
}
257-
251+
let users: UserResponse[];
258252
const shouldSearchLocally =
259253
this.allMembersLoadedWithInitialChannelQuery || !searchQuery;
260254

261-
if (shouldSearchLocally) {
262-
return this.searchMembersLocally(searchQuery);
255+
if (this.config.mentionAllAppUsers) {
256+
users = await this.queryUsers(searchQuery);
257+
} else if (shouldSearchLocally) {
258+
users = this.searchMembersLocally(searchQuery);
259+
} else {
260+
users = await this.queryMembers(searchQuery);
263261
}
264262

265-
return await this.queryMembers(searchQuery);
263+
return {
264+
items: users.map(
265+
(user) =>
266+
({
267+
...user,
268+
...getTokenizedSuggestionDisplayName({
269+
displayName: user.name || user.id,
270+
searchToken: this.searchQuery,
271+
}),
272+
}) as UserSuggestion,
273+
),
274+
};
266275
}
267276

268-
filterMutes = (data: UserResponse[]) => {
277+
filterMutes = (data: UserSuggestion[]) => {
269278
const { textComposerText } = this.config;
270279
if (!textComposerText) return [];
271280

@@ -285,19 +294,19 @@ export class MentionsSearchSource extends BaseSearchSource<UserSuggestion> {
285294
);
286295
};
287296

288-
filterQueryResults(items: UserResponse[]) {
289-
return this.filterMutes(items).map((item) => ({
290-
...item,
291-
...getTokenizedSuggestionDisplayName({
292-
displayName: item.name || item.id,
293-
searchToken: this.searchQuery,
294-
}),
295-
}));
297+
filterQueryResults(items: UserSuggestion[]) {
298+
return this.filterMutes(items);
296299
}
297300
}
298301

299302
const DEFAULT_OPTIONS: TextComposerMiddlewareOptions = { minChars: 1, trigger: '@' };
300303

304+
const userSuggestionToUserResponse = (suggestion: UserSuggestion): UserResponse => {
305+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
306+
const { tokenizedDisplayName, ...userResponse } = suggestion;
307+
return userResponse;
308+
};
309+
301310
/**
302311
* TextComposer middleware for mentions
303312
* Usage:
@@ -397,7 +406,9 @@ export const createMentionsMiddleware = (
397406
text: state.text,
398407
trigger: finalOptions.trigger,
399408
}),
400-
mentionedUsers: state.mentionedUsers.concat(selectedSuggestion),
409+
mentionedUsers: state.mentionedUsers.concat(
410+
userSuggestionToUserResponse(selectedSuggestion),
411+
),
401412
suggestions: undefined, // Clear suggestions after selection
402413
},
403414
});

src/messageComposer/middleware/textComposer/textMiddlewareUtils.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,13 +114,17 @@ export function escapeRegExp(text: string) {
114114
return text.replace(/[-[\]{}()*+?.,/\\^$|#]/g, '\\$&');
115115
}
116116

117+
export type TokenizationPayload = {
118+
tokenizedDisplayName: { token: string; parts: string[] };
119+
};
120+
117121
export const getTokenizedSuggestionDisplayName = ({
118122
displayName,
119123
searchToken,
120124
}: {
121125
displayName: string;
122126
searchToken: string;
123-
}): { tokenizedDisplayName: { token: string; parts: string[] } } => ({
127+
}): TokenizationPayload => ({
124128
tokenizedDisplayName: {
125129
token: searchToken,
126130
parts: searchToken

test/unit/MessageComposer/middleware/textComposer/MentionsSearchSource.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -254,8 +254,8 @@ describe('MentionsSearchSource', () => {
254254
channel.state.members = mockMembers;
255255

256256
const result = source.searchMembersLocally('valid');
257-
expect(result.items).toHaveLength(1);
258-
expect(result.items[0].name).toBe('Valid Name');
257+
expect(result).toHaveLength(1);
258+
expect(result[0].name).toBe('Valid Name');
259259
});
260260

261261
it('should handle errors in API queries', async () => {
@@ -289,7 +289,7 @@ describe('MentionsSearchSource', () => {
289289
};
290290

291291
const result = source.searchMembersLocally('joh');
292-
expect(result.items).toHaveLength(2); // Should match John and Johnny
293-
expect(result.items.map((i) => i.name)).toEqual(['John', 'Johnny']);
292+
expect(result).toHaveLength(2); // Should match John and Johnny
293+
expect(result.map((i) => i.name)).toEqual(['John', 'Johnny']);
294294
});
295295
});

0 commit comments

Comments
 (0)