Mentions
A mention points a message at people: a user, everyone in the channel, everyone online, a role or a user group. The user types @, picks from the suggestions, and the message goes out with the mention, so the server can notify the people it names.
Example
The user types "Thanks @jo" and picks John from the suggestions:
- The text becomes "Thanks @John ", and John is added to the composer's mentions.
- The user sends the message. It goes out with John among its mentioned users, and John is notified.
Had the user deleted "@John" before sending, John wouldn't be mentioned: only mentions still present in the text are sent.
Kinds of mentions
The mentions middleware opens the suggestions after @. A picked mention is inserted as @ and its name, and kept in textComposer.mentions (see Text). When the message is sent, every mention still present in the text goes with it.
| Mention | Inserted as | Offered when the user has | Sent as |
|---|---|---|---|
| A user | @Ann Lee |
always | mentioned_users |
| Everyone | @channel |
notify-channel |
mentioned_channel: true |
| Everyone online | @here |
notify-here |
mentioned_here: true |
| A role | @admin |
notify-role |
mentioned_roles |
| A user group | @Design team |
notify-group |
mentioned_group_ids |
The capabilities come from channel.data.own_capabilities and are read at each query, so the list follows a change of the user's permissions.
Where the suggestions come from
- Users. When the channel's query loaded all its members (fewer than 100), the members and watchers are searched locally, without a request, and the current user is left out. Larger channels query the members by name.
@channeland@hereappear on the first page, when they start with what was typed.- Roles are searched with
client.searchRoles(), and user groups withclient.searchUserGroups(), among the groups of the channel's team when it has one. Both start once something is typed after@. Roles appear on the first page only.
Muted users aren't offered. After /unmute, only muted users are. If the role or group search fails, the other suggestions still appear.
Customizing mention search
MentionsSearchSource loads the mention suggestions. To change what is searched, give the mentions middleware a search source of your own, created with these options:
| Option | What it changes |
|---|---|
mentionAllAppUsers |
Searches every user of the app with client.queryUsers(), not only the channel's members. |
userFilters |
Filters for the user query, added to the default search by id and name. |
userSort |
The sort of the user query, in place of the default. |
memberFilters |
The filters of the member query, in place of the default. |
memberSort |
The sort of the member query, in place of the default. Only its first entry is used. |
searchOptions |
Extra options for the user and member queries. |
pageSize |
How many suggestions a page holds. |
shortQueryMaxLength |
The longest query that counts as short: 2 characters by default. |
shortQueryDebounceMs |
How long a short query waits before it's searched: 500 ms by default. |
longQueryDebounceMs |
How long a longer query waits: 300 ms by default. |
transliterate |
A function that normalizes names and queries before the local member search compares them. It runs on top of the built-in step, which already ignores common accents such as é and ü. Server queries don't use it. |
suggestionFactoryMappers |
Functions that build each kind of suggestion from a user, role, group, channel or here. |
In this sample, transliterate removes accents the built-in step keeps, such as č and ž. Replace the middleware in the composer setup function. MiddlewareExecutor.replace() puts the new middleware in the place of the built-in one, because both have the same id:
import type { TextComposerMiddleware } from "stream-chat";
import { createMentionsMiddleware, MentionsSearchSource } from "stream-chat";
const removeAccents = (text: string) =>
text.normalize("NFD").replace(/[̀-ͯ]/g, "");
client.config.setSetupFunction("messageComposer", ({ composer }) => {
const { channel, textComposer } = composer;
const searchSource = new MentionsSearchSource(channel, {
pageSize: 20,
transliterate: removeAccents,
});
textComposer.middlewareExecutor.replace([
createMentionsMiddleware(channel, {
searchSource,
}) as TextComposerMiddleware,
]);
return () =>
textComposer.middlewareExecutor.replace([
createMentionsMiddleware(channel) as TextComposerMiddleware,
]);
});For more control, extend MentionsSearchSource and override one of its steps:
| Override | What it does |
|---|---|
query(searchQuery) |
Loads a page and returns { items }. |
filterQueryResults(items) |
Filters a page before it's shown. The default leaves out muted users. |
prepareQueryUsersParams(searchQuery, offset) |
Returns the filters, sort and options of the user query. It's an arrow-function property, not a method, so override it with a property of the same name. |
prepareQueryMembersParams(searchQuery, offset) |
Returns the filters, sort and options of the member query. It's an arrow-function property too. |
getStateBeforeFirstQuery(searchQuery) |
Sets the state a new search starts from. The default keeps the previous items until new ones arrive, so the list doesn't flicker. |
import type { MentionSuggestion } from "stream-chat";
import { MentionsSearchSource } from "stream-chat";
class WithoutBotsSearchSource extends MentionsSearchSource {
filterQueryResults(items: MentionSuggestion[]) {
return super
.filterQueryResults(items)
.filter((item) => item.mentionType !== "user" || item.role !== "bot");
}
}Pitfalls
- A search source per composer. Create the search source inside the setup function, as the example does, so composers don't share results.