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 Cleo from the suggestions:
- The text becomes "Thanks @Cleo ", and Cleo is added to the composer's mentions.
- The user sends the message. It goes out with Cleo among its mentioned users, and Cleo is notified.
Had the user deleted "@Cleo" before sending, Cleo 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 | @Cleo 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. An
@with nothing typed after it always lists the loaded members and watchers, whatever the channel's size. @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. The members and watchers are then never searched locally. |
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 some accents, such as acute accents and the umlaut on u. Server queries don't use it. |
suggestionFactoryMappers |
Functions that build each kind of suggestion from a user, role, group, channel or here. |
textComposerText |
The composer's text. The mentions middleware sets it before each search. When it contains /unmute, only muted users are offered. A search source you call yourself, outside the middleware, needs it too. |
trigger |
The mentions middleware's trigger, which the search source uses when it matches names locally. Pass the same one to both. |
In this sample, transliterate removes accents the built-in step keeps, such as the carons in Czech names, and the search covers every user of the app. 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 {
createMentionsMiddleware,
MentionsSearchSource,
StreamChat,
} from "stream-chat";
import type { TextComposerMiddleware } from "stream-chat";
const client = StreamChat.getInstance("YOUR_API_KEY");
const removeAccents = (text: string) =>
text.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
client.config.setSetupFunction("messageComposer", ({ composer }) => {
const { channel, textComposer } = composer;
const searchSource = new MentionsSearchSource(channel, {
mentionAllAppUsers: true,
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 { MentionsSearchSource } from "stream-chat";
import type { MentionSuggestion } from "stream-chat";
class WithoutBotsSearchSource extends MentionsSearchSource {
filterQueryResults(items: MentionSuggestion[]) {
return super
.filterQueryResults(items)
.filter((item) => item.mentionType !== "user" || item.role !== "bot");
}
}Suggestions explains the minChars and trigger options of the mentions middleware. The SDK's AutoCompleteSuggestionItem renders each suggestion, and Custom suggestions shows how to replace it.
Pitfalls
- A search source per composer. Create the search source inside the setup function, as the example does, so composers don't share results.