Files
gerbeur/shared/notification.ts
khannurien 134d1a0b97 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>
2026-10-09 12:54:09 +00:00

162 lines
4.5 KiB
TypeScript

/**
* Notifications tell a user when someone follows them or their playlists,
* upvotes, comments, likes or mentions them, or posts where they follow. This
* module types the ones `api/services/notification-service.ts` makes, and the
* data each kind carries. The client's `/notifications` page lists them and
* turns each into a sentence and a link.
*
* @module
*/
/**
* Says which of the `…Data` interfaces a notification's `data` is. Each value
* has one `notify…` trigger in `notification-service.ts`.
*/
export type NotificationType =
| "playlist_followed"
| "user_followed"
| "user_dump_posted"
| "playlist_dump_added"
| "dump_upvoted"
| "user_mentioned"
| "dump_commented"
| "comment_liked";
/**
* Where a notification's dump is now, for its link (`dumpPath`). The server
* joins it each time it sends the notification, so a renamed dump or poster
* still links right; it is absent once the dump is deleted.
*/
export interface DumpRef {
dumpSlug?: string;
dumpOwnerUsername?: string;
}
/** Where a notification's playlist is now, joined as {@link DumpRef} is. */
export interface PlaylistRef {
playlistSlug?: string;
playlistOwnerUsername?: string;
}
/**
* Carried by a `playlist_followed` notification, sent to a playlist's owner.
*/
export interface PlaylistFollowedData extends PlaylistRef {
followerId: string;
followerUsername: string;
playlistId: string;
playlistTitle: string;
}
/** Carried by a `user_followed` notification, sent to the user followed. */
export interface UserFollowedData {
followerId: string;
followerUsername: string;
}
/**
* Carried by a `user_dump_posted` notification, sent to the followers of a
* user who posted a public dump.
*/
export interface UserDumpPostedData extends DumpRef {
dumperId: string;
dumperUsername: string;
dumpId: string;
dumpTitle: string;
}
/**
* Carried by a `playlist_dump_added` notification, sent to the followers of a
* public playlist when a public dump is added to it.
*/
export interface PlaylistDumpAddedData extends DumpRef, PlaylistRef {
dumpId: string;
dumpTitle: string;
playlistId: string;
playlistTitle: string;
}
/** Carried by a `dump_upvoted` notification, sent to the dump's poster. */
export interface DumpUpvotedData extends DumpRef {
voterId: string;
voterUsername: string;
dumpId: string;
dumpTitle: string;
}
/**
* Carried by a `user_mentioned` notification, sent to a user @mentioned in a
* comment, a dump, a playlist's description or a chat message, or to every
* user by a chat message's `@everyone`. Its
* {@link DumpRef} is the dump's, or the comment's dump's; its
* {@link PlaylistRef} the playlist's.
*/
export interface UserMentionedData extends DumpRef, PlaylistRef {
mentionerId: string;
mentionerUsername: string;
contextType: "comment" | "dump" | "playlist" | "chat";
/** The id of the comment, dump, playlist or chat message. */
contextId: string;
/**
* The title of the dump or playlist, or of the dump a comment is under;
* empty for the chat.
*/
contextTitle: string;
/**
* Set only for a comment. It names the dump the comment is under, which the
* notification's link opens.
*/
dumpId?: string;
/**
* Set when the user was reached by `@everyone` rather than by name. A
* message naming them as well notifies them once, by name.
*/
everyone?: true;
}
/** Carried by a `dump_commented` notification, sent to the dump's poster. */
export interface DumpCommentedData extends DumpRef {
commenterId: string;
commenterUsername: string;
commentId: string;
dumpId: string;
dumpTitle: string;
}
/** Carried by a `comment_liked` notification, sent to the comment's author. */
export interface CommentLikedData extends DumpRef {
likerId: string;
likerUsername: string;
commentId: string;
dumpId: string;
dumpTitle: string;
}
/**
* Stored when the notification is made, so a title changed later still shows
* the old one. The `…Username` fields and the refs are joined when it is sent,
* so they follow a rename. The notification's `type` says which member it is.
*/
export type NotificationData =
| PlaylistFollowedData
| UserFollowedData
| UserDumpPostedData
| PlaylistDumpAddedData
| DumpUpvotedData
| UserMentionedData
| DumpCommentedData
| CommentLikedData;
/**
* Pushed to its user's sockets in a `notification_created` message when it is
* made, and returned by `GET /api/me/notifications`.
*/
export interface Notification {
id: string;
userId: string;
type: NotificationType;
data: NotificationData;
read: boolean;
createdAt: Date;
}