v4: a moderator's or an admin's @everyone in the chat notifies every user — there was no way to reach everyone at once. The new chat:everyone permission, held by moderators and admins, lets a chat message saying @everyone notify every user but its author and those reading the chat, through notifyEveryone, after notifyMentions, so a user also named is told once, by name; the notification is a user_mentioned with everyone set, which the notifications page and the digest word as "mentioned everyone in the chat". chat_messages.mentions_everyone, sent as ChatMessage.mentionsEveryone, is set from the author's role each time the body is written, so a user's @everyone reaches no one, a moderator's edit of a user's message can't make it reach everyone, and a demoted author's next edit loses it. Only an edit that adds @everyone notifies, so fixing an announcement's typo reaches no one new. The client highlights and chimes by the flag, never by the text; Markdown shows @everyone as a mention only in a flagged message, and never links it to a profile. The chat composer offers @everyone first to those who may say it. No account may be named everyone, in any case: migration 0023 adds the column, flags the moderators' and admins' past @everyone messages, renames a user named everyone to everyone_N and drops an alias everyone. 265 backend tests and 681 frontend tests pass, and deno check, tsc, eslint, doc:lint and i18n:compile --strict are clean; checked in screenshots of the chat at 1280 and 390px, with a moderator's and a user's @everyone and the suggestions open.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -16,7 +16,7 @@ import {
|
||||
import { API_URL } from "../../config/api.ts";
|
||||
import { insideCode } from "../../utils/editor/text.ts";
|
||||
import type { UserSuggestion } from "../../../shared/user.ts";
|
||||
import { MENTION_RE } from "../../../shared/mention.ts";
|
||||
import { EVERYONE, MENTION_RE } from "../../../shared/mention.ts";
|
||||
|
||||
interface MentionState {
|
||||
open: boolean;
|
||||
@@ -37,6 +37,16 @@ const CLOSED: MentionState = {
|
||||
selectedIndex: 0,
|
||||
};
|
||||
|
||||
/**
|
||||
* Offered first, to those who may say it, when the query starts `everyone`.
|
||||
* No user has the name, so `MentionDropdown` tells it apart by it.
|
||||
*/
|
||||
const EVERYONE_SUGGESTION: UserSuggestion = {
|
||||
id: EVERYONE,
|
||||
username: EVERYONE,
|
||||
avatarMime: null,
|
||||
};
|
||||
|
||||
/**
|
||||
* Matches the `@query` ending at the caret by the rule of shared/mention.ts,
|
||||
* which the server notifies by, so that what is offered would notify.
|
||||
@@ -100,10 +110,12 @@ export interface UseMentionAutocompleteResult {
|
||||
* Returns the suggestions' state and the functions that drive them. A pick
|
||||
* goes through `replace`, `TextEditor`'s function that puts the text over
|
||||
* `[start, end)` of the textarea's value, followed by a space when `word` is
|
||||
* set.
|
||||
* set. With `everyone`, which the chat's composer sets for a role with
|
||||
* `chat:everyone`, `@everyone` is offered too.
|
||||
*/
|
||||
export function useMentionAutocomplete(
|
||||
replace: (start: number, end: number, text: string, word: boolean) => void,
|
||||
everyone = false,
|
||||
): UseMentionAutocompleteResult {
|
||||
const [state, setStateRaw] = useState<MentionState>(CLOSED);
|
||||
// Mirrored synchronously, so that the key handler and a late response both
|
||||
@@ -165,13 +177,15 @@ export function useMentionAutocomplete(
|
||||
const current = stateRef.current;
|
||||
// Only the search for what is typed now may open or close the list.
|
||||
if (current.query !== mention.query) return;
|
||||
if (body.success && body.data.length > 0) {
|
||||
setState({
|
||||
...current,
|
||||
open: true,
|
||||
results: body.data,
|
||||
selectedIndex: 0,
|
||||
});
|
||||
const results: UserSuggestion[] = [
|
||||
...(everyone &&
|
||||
EVERYONE.startsWith(mention.query.toLowerCase())
|
||||
? [EVERYONE_SUGGESTION]
|
||||
: []),
|
||||
...(body.success ? body.data : []),
|
||||
];
|
||||
if (results.length > 0) {
|
||||
setState({ ...current, open: true, results, selectedIndex: 0 });
|
||||
} else {
|
||||
setState({ ...current, open: false, results: [] });
|
||||
}
|
||||
@@ -180,7 +194,7 @@ export function useMentionAutocomplete(
|
||||
}
|
||||
}, 150);
|
||||
},
|
||||
[cancelLookup, close, setState],
|
||||
[cancelLookup, close, everyone, setState],
|
||||
);
|
||||
|
||||
const select = useCallback(
|
||||
|
||||
Reference in New Issue
Block a user