v3: every doc opens with the role, then the mechanism, then the details — the docs were written bottom-up: what a thing does first ("Declares the three contexts WSProvider fills…"), details that only make sense once its purpose is known next, and the reason it exists last ("…so that a feed doesn't re-render"), so a reader had nothing to hang the first sentences on. The README's rule caused it, since a module doc "opened with what the module does"; it now opens with the module's role in the app, and the value-side phrasing of a type ("Returned to Y by X") comes once the role is said, with WSContext as the before and after. WSContext.ts is rewritten that way throughout, and so are the docs elsewhere that buried their purpose in a trailing "so that" (the migrations, og, image-proxy, public-address, useFading, useFeedCache, the drafts), opened on a generic verb that hid it ("Holds", "Declares", "Loads", "Defines", "Types": the contexts and providers, the shared types, the models, the hooks), or were label fragments ("The page of a dump…", "A row of tabs."). A function whose verb is its action ("Stores session"), a props or result type, and a protocol message ("Sent by X when Y") already say their role and are left as they are. Only comments changed, and the catalogs only move their references; deno fmt, deno lint and doc:lint are clean.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* The modal that posts a dump from a link or a file, then offers to add it to
|
||||
* the poster's playlists. Every "new dump" button opens it.
|
||||
* Renders the modal that posts a dump from a link or a file, then offers to add
|
||||
* it to the poster's playlists. Every "new dump" button opens it.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* The panels of the dump creation modal, and the indicator showing which one
|
||||
* is open.
|
||||
* Shows which panel of the dump creation modal is open, and declares the panels
|
||||
* in the order they are walked.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* The title field of the dump creation modal, read-only until the poster
|
||||
* chooses to edit it.
|
||||
* Renders the title field of the dump creation modal, read-only until the
|
||||
* poster chooses to edit it.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/**
|
||||
* The tab bar of the index page, with the category switcher before the tabs.
|
||||
* Renders the tab bar of the index page, with the category switcher before the
|
||||
* tabs.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* A row of tabs.
|
||||
* Renders the row of tabs of the index, profile and search pages.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/**
|
||||
* Reads which Bandcamp player this deployment uses, for `usePlayRichContent`.
|
||||
* Lets a deployment choose between Bandcamp's embed and the site's own player.
|
||||
* It reads the choice for `usePlayRichContent`.
|
||||
*
|
||||
* The server writes `GERBEUR_BANDCAMP_PLAYER` into index.html's
|
||||
* `bandcamp-player` `<meta>` tag (`api/lib/index-html.ts`, and the Vite config
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Declares the session's React context. `AuthProvider` fills it and `useAuth`
|
||||
* reads it; nothing else should touch it.
|
||||
* Shares the session with every component. `AuthProvider` fills this context
|
||||
* and `useAuth` reads it; nothing else should touch it.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Holds the session for the whole app and keeps it in step with the other tabs
|
||||
* Keeps the user signed in across the whole app, in step with the other tabs
|
||||
* and with the server. `App` mounts it near the root.
|
||||
*
|
||||
* @module
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Declares the categories' React context. `CategoriesProvider` fills it and
|
||||
* `useCategories` reads it.
|
||||
* Shares the site's categories with every component. `CategoriesProvider` fills
|
||||
* this context and `useCategories` reads it.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Loads the site's categories once for the whole app, and again when the
|
||||
* category manager changes them. `App` mounts it.
|
||||
* Spares every page fetching the site's categories: it loads them once for the
|
||||
* whole app, and again when the category manager changes them. `App` mounts it.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/**
|
||||
* Declares the context through which the header's chat button and the floating
|
||||
* chat button open the one chat modal, which `ChatProvider` owns. Read it with
|
||||
* `useChat`.
|
||||
* Lets the header's chat button and the floating one open the same chat modal,
|
||||
* which `ChatProvider` owns. Read it with `useChat`.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* Provides `ChatContext` and renders the chat modal while it is open. `App`
|
||||
* mounts it inside the router, around every page.
|
||||
* Owns the one chat modal: it renders it while it is open, and provides
|
||||
* `ChatContext` to the buttons that open it. `App` mounts it inside the router,
|
||||
* around every page.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/**
|
||||
* Defines the player's two contexts, one for the queue and the controls and one
|
||||
* for the playhead alone. PlayerProvider provides both around the whole app;
|
||||
* GlobalPlayer draws the player, and the cards and previews that start playback
|
||||
* read and drive it.
|
||||
* Shares the player docked at the bottom of every page with the whole app. The
|
||||
* cards and previews that start playback read and drive it, and GlobalPlayer
|
||||
* draws it. It is two contexts, one for the queue and the controls and one for
|
||||
* the playhead alone, which changes several times a second. PlayerProvider
|
||||
* provides both around the whole app.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Holds the player's queue, playback state and controls, saves them across
|
||||
* reloads and keeps streams' signed URLs fresh. App mounts it around the whole
|
||||
* app, and GlobalPlayer draws what it holds.
|
||||
* Keeps playback going from page to page and across reloads. It holds the
|
||||
* player's queue, playback state and controls, saves them as they change and
|
||||
* keeps streams' signed URLs fresh. App mounts it around the whole app, and
|
||||
* GlobalPlayer draws what it holds.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/**
|
||||
* Declares the context that holds the users and playlists the signed-in user
|
||||
* follows, for the Follow buttons. `FollowProvider` provides it and
|
||||
* `useFollows` reads it.
|
||||
* Tells the Follow buttons what the signed-in user follows. `FollowProvider`
|
||||
* provides this context and `useFollows` reads it.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
@@ -42,7 +41,10 @@ export const defaultFollowValue: FollowContextValue = {
|
||||
isLoaded: false,
|
||||
};
|
||||
|
||||
/** Holds the signed-in user's follows. Read it with `useFollows`. */
|
||||
/**
|
||||
* Tells the Follow buttons what the signed-in user follows. Read it with
|
||||
* `useFollows`.
|
||||
*/
|
||||
export const FollowContext: React.Context<FollowContextValue> = createContext<
|
||||
FollowContextValue
|
||||
>(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* Provides `FollowContext`, from `GET /api/follows/status`. `App` mounts it
|
||||
* around every page.
|
||||
* Keeps what the signed-in user follows for the Follow buttons, loaded from
|
||||
* `GET /api/follows/status` and again when another tab changes it. `App` mounts
|
||||
* it around every page.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Declares the theme's React context. `ThemeProvider` fills it and `useTheme`
|
||||
* reads it.
|
||||
* Shares the chosen theme with every component. `ThemeProvider` fills this
|
||||
* context and `useTheme` reads it.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* Holds the chosen style and color scheme, stores them in localStorage and
|
||||
* applies them to the document. `App` mounts it outermost.
|
||||
* Lets each browser pick the site's style and color scheme. It holds the
|
||||
* choice, stores it in localStorage and applies it to the document. `App`
|
||||
* mounts it outermost.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* Declares the context through which `Avatar` learns of avatars changed since
|
||||
* the page loaded. `WSProvider` fills it from `user_updated` messages.
|
||||
* Shows a user's new avatar everywhere as soon as they change it, without a
|
||||
* reload. `WSProvider` fills this context from `user_updated` messages, and
|
||||
* `Avatar` reads it.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
@@ -21,8 +22,9 @@ export interface AvatarOverride {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds an {@link AvatarOverride} by user id. It is apart from `WSContext`, so
|
||||
* the many avatars of a page don't re-render on every vote or comment.
|
||||
* Shows changed avatars without a reload, as an {@link AvatarOverride} by user
|
||||
* id. It is apart from `WSContext`, so the many avatars of a page don't
|
||||
* re-render on every vote or comment.
|
||||
*/
|
||||
export const AvatarOverrideContext: React.Context<
|
||||
Record<string, AvatarOverride>
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
/**
|
||||
* Declares the three contexts `WSProvider` fills from the WebSocket, and the
|
||||
* types they carry. They are split by how often each part changes, so that a
|
||||
* feed doesn't re-render with every chat message or presence change.
|
||||
* Live updates from the server reach the pages through the three contexts
|
||||
* declared here: votes and likes, dumps, playlists, comments, profiles and
|
||||
* notifications in one, the chat in another, who is online in the third. They
|
||||
* are split so that a feed doesn't re-render with every chat message or
|
||||
* presence change. `WSProvider` fills them from its WebSocket, and components
|
||||
* read them with `useWS`, `useWSChat` and `useWSPresence`.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
@@ -16,8 +19,9 @@ import type { Playlist } from "../../../shared/playlist.ts";
|
||||
import type { PublicUser } from "../../../shared/user.ts";
|
||||
|
||||
/**
|
||||
* Set as `lastVoteEvent` by `WSProvider` on a `votes_update`. The profile
|
||||
* pages read it to add or drop a dump from a user's upvoted list.
|
||||
* Keeps a user's upvoted list live, on their profile and on `UserUpvoted`.
|
||||
* `WSProvider` sets it as `lastVoteEvent` on each `votes_update`, and
|
||||
* `useUpvotedList` adds or drops the dump.
|
||||
*/
|
||||
export interface VoteEvent {
|
||||
dumpId: string;
|
||||
@@ -26,9 +30,11 @@ export interface VoteEvent {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set as `lastPlaylistEvent` by `WSProvider` on each `playlist_*` message.
|
||||
* The pages that show playlists apply it, some through `usePlaylistListSync`.
|
||||
* Which fields are set depends on `type`.
|
||||
* Keeps the playlists on screen up to date, in lists, on a playlist's page and
|
||||
* on a dump's page. `WSProvider` sets it as `lastPlaylistEvent` on each
|
||||
* `playlist_*` message; lists apply it through `usePlaylistListSync`, a
|
||||
* playlist's page through `usePlaylist`. Which fields are set depends on
|
||||
* `type`.
|
||||
*/
|
||||
export interface PlaylistEvent {
|
||||
type: "created" | "updated" | "deleted" | "dumps_updated";
|
||||
@@ -47,9 +53,9 @@ export interface PlaylistEvent {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set as `lastCommentEvent` by `WSProvider` on each `comment_*` message. The
|
||||
* `Dump` page applies it to its thread, and `useDumpListSync` to its comment
|
||||
* counts.
|
||||
* Keeps comment threads and comment counts live. `WSProvider` sets it as
|
||||
* `lastCommentEvent` on each `comment_*` message; the `Dump` page applies it
|
||||
* to its thread, and `useDumpListSync` to the counts on dump cards.
|
||||
*/
|
||||
export interface CommentEvent {
|
||||
type: "created" | "deleted" | "updated";
|
||||
@@ -61,16 +67,18 @@ export interface CommentEvent {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set as `lastUserEvent` by `WSProvider` on a `user_updated`, which the
|
||||
* server sends on any change to a profile. `useProfile` applies it.
|
||||
* Keeps a profile page live while its user edits their profile. `WSProvider`
|
||||
* sets it as `lastUserEvent` on each `user_updated`, which the server sends on
|
||||
* any change to a profile, and `useProfile` applies it.
|
||||
*/
|
||||
export interface UserEvent {
|
||||
user: PublicUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Chosen in `ChatModal` for the reply bar, and kept on the pending message so
|
||||
* it shows the reply before the server's echo. Only `id` goes to the server.
|
||||
* Quotes the message a chat message replies to. `ChatModal` shows it in the
|
||||
* reply bar once the user picks it, and it stays on the pending message so
|
||||
* the quote shows before the server's echo. Only `id` goes to the server.
|
||||
*/
|
||||
export interface ChatReplyTarget {
|
||||
id: string;
|
||||
@@ -79,9 +87,9 @@ export interface ChatReplyTarget {
|
||||
}
|
||||
|
||||
/**
|
||||
* Held by `WSProvider` for a chat message this client sent and the server
|
||||
* hasn't echoed back yet. `ChatModal` shows it, as `PendingItem`, after the
|
||||
* messages the server has.
|
||||
* Shows a chat message the moment the user sends it, and lets them retry it if
|
||||
* it fails. `WSProvider` holds it until the server echoes the message back, and
|
||||
* `ChatModal` shows it, as `PendingItem`, after the messages the server has.
|
||||
*/
|
||||
export interface PendingChatMessage {
|
||||
/**
|
||||
@@ -102,8 +110,8 @@ export interface PendingChatMessage {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set as `chatError` by `WSProvider` when the server refuses a chat edit or
|
||||
* delete. `ChatModal` shows it for a few seconds.
|
||||
* Tells the user the server refused their chat edit or delete. `WSProvider`
|
||||
* sets it as `chatError`, and `ChatModal` shows it for a few seconds.
|
||||
*/
|
||||
export interface ChatError {
|
||||
/** Tells apart two errors with the same message. */
|
||||
@@ -112,60 +120,75 @@ export interface ChatError {
|
||||
}
|
||||
|
||||
/**
|
||||
* Provided by `WSProvider` through {@link WSContext}, read with `useWS`. The
|
||||
* `last*Event` fields hold only the latest event of their kind: a consumer
|
||||
* reacts in an effect when one changes, and an event that another replaces
|
||||
* within the same render is missed. After a reconnect, `connectionEpoch` tells
|
||||
* pages to fetch what they missed.
|
||||
* Carries everything live but the chat and presence, for pages to lay over
|
||||
* what they fetched. `WSProvider` provides it through {@link WSContext}, and
|
||||
* components read it with `useWS`. Each `last*Event` field holds only the
|
||||
* latest event of its kind: a consumer reacts in an effect when one changes,
|
||||
* and an event that another replaces within the same render is missed. After a
|
||||
* reconnect, `connectionEpoch` tells pages to fetch what they missed.
|
||||
*/
|
||||
export interface WSContextValue {
|
||||
wsStatus: "connecting" | "connected" | "disconnected";
|
||||
/** Set while the socket is down, for the banner that says so. */
|
||||
wsErrorMessage: string | null;
|
||||
/**
|
||||
* Counts known to the socket, by dump id: pushed by the server, or changed
|
||||
* optimistically. A missing dump shows its fetched `voteCount`.
|
||||
* Shows vote counts live. It holds, by dump id, the counts the server pushed
|
||||
* or this tab changed optimistically; a dump missing from it shows its
|
||||
* fetched `voteCount`.
|
||||
*/
|
||||
voteCounts: Record<string, number>;
|
||||
/** Reset from the server's `welcome` on every connection. */
|
||||
/**
|
||||
* Marks the dumps this user upvoted, for their vote buttons. The server's
|
||||
* `welcome` resets it on every connection.
|
||||
*/
|
||||
myVotes: Set<string>;
|
||||
/** As `voteCounts`, for comments. */
|
||||
likeCounts: Record<string, number>;
|
||||
/** As `myVotes`, for comments. */
|
||||
myLikes: Set<string>;
|
||||
/**
|
||||
* Dumps that arrived this session, newest first and at most 100: the ones
|
||||
* the server announced, which are public, and the ones this user posted,
|
||||
* which may be private. `Index` puts them above the feed it fetched.
|
||||
* Shows new dumps on the index as they are posted, above the feed `Index`
|
||||
* fetched. It holds, newest first and at most 100, the ones the server
|
||||
* announced this session, which are public, and the ones this user posted,
|
||||
* which may be private.
|
||||
*/
|
||||
recentDumps: Dump[];
|
||||
/**
|
||||
* It holds the dumps deleted this session, or made private by someone else,
|
||||
* for the lists to filter out. A dump made public again leaves it.
|
||||
* Takes deleted dumps off every list at once. It holds the dumps deleted this
|
||||
* session, or made private by someone else; a dump made public again leaves
|
||||
* it.
|
||||
*/
|
||||
deletedDumpIds: Set<string>;
|
||||
lastVoteEvent: VoteEvent | null;
|
||||
/**
|
||||
* It holds the dump as its latest `dump_created` or `dump_updated` has it,
|
||||
* for the lists to update it in place, or put it back where it was.
|
||||
* Keeps a dump up to date in every list that shows it. It holds the dump as
|
||||
* its latest `dump_created` or `dump_updated` has it, and a list updates it
|
||||
* in place, or puts it back where it was.
|
||||
*/
|
||||
lastDumpEvent: Dump | null;
|
||||
lastPlaylistEvent: PlaylistEvent | null;
|
||||
deletedPlaylistIds: Set<string>;
|
||||
lastCommentEvent: CommentEvent | null;
|
||||
lastUserEvent: UserEvent | null;
|
||||
/** Read from the `welcome`, then counted up by each new notification. */
|
||||
/**
|
||||
* Drives the bell's badge. It comes from the `welcome`, then each new
|
||||
* notification counts it up.
|
||||
*/
|
||||
unreadNotificationCount: number;
|
||||
/** Read by the bell, which rings, and by the notifications page. */
|
||||
/**
|
||||
* Makes the bell ring, and puts the notification on the notifications page
|
||||
* as it arrives.
|
||||
*/
|
||||
lastNotification: Notification | null;
|
||||
/**
|
||||
* Incremented on each reconnect, never on the first connection, so pages
|
||||
* fetch again what they missed while the socket was down.
|
||||
* Tells pages to fetch again what they missed while the socket was down. It
|
||||
* goes up on each reconnect, never on the first connection.
|
||||
*/
|
||||
connectionEpoch: number;
|
||||
/**
|
||||
* Incremented when the user follows or unfollows something in another tab
|
||||
* (or this one), so `FollowProvider` fetches the follow status again.
|
||||
* Keeps the Follow buttons right when the user follows or unfollows in
|
||||
* another tab. It goes up on every follow change, in any tab, and
|
||||
* `FollowProvider` fetches the follow status again.
|
||||
*/
|
||||
followsVersion: number;
|
||||
/**
|
||||
@@ -182,8 +205,9 @@ export interface WSContextValue {
|
||||
/** Withdraws a like, as {@link WSContextValue.castVote} casts a vote. */
|
||||
removeCommentLike: (commentId: string, shownCount?: number) => void;
|
||||
/**
|
||||
* Adds a dump this user just posted to `recentDumps`, without waiting for
|
||||
* its broadcast, which a private dump never gets. `DumpCreateModal` calls it.
|
||||
* Shows a dump this user just posted on the index without waiting for its
|
||||
* broadcast, which a private dump never gets. `DumpCreateModal` calls it,
|
||||
* and it adds the dump to `recentDumps`.
|
||||
*/
|
||||
injectDump: (dump: Dump) => void;
|
||||
/**
|
||||
@@ -194,36 +218,43 @@ export interface WSContextValue {
|
||||
}
|
||||
|
||||
/**
|
||||
* Provided by `WSProvider` through {@link WSChatContext}, read with
|
||||
* `useWSChat`. The socket sends chat messages only to signed-in users, so for
|
||||
* a visitor it stays empty.
|
||||
* Carries the chat's live side, which `ChatModal` lays over the history it
|
||||
* fetches and the chat buttons show as a badge. `WSProvider` provides it
|
||||
* through {@link WSChatContext}, and components read it with `useWSChat`. The
|
||||
* socket sends chat only to signed-in users, so for a visitor it stays empty.
|
||||
*/
|
||||
export interface WSChatContextValue {
|
||||
/**
|
||||
* The messages received this session, at most 200, for `ChatModal` to merge
|
||||
* with the history it fetches. A reconnect empties it while the chat is
|
||||
* closed, since `ChatModal` fetches everything again on open.
|
||||
* Shows chat messages as they arrive. It holds the ones received this
|
||||
* session, at most 200, which `ChatModal` merges with the history it
|
||||
* fetches. A reconnect empties it while the chat is closed, since `ChatModal`
|
||||
* fetches everything again on open.
|
||||
*/
|
||||
chatMessages: ChatMessage[];
|
||||
/**
|
||||
* Messages deleted this session, which `ChatModal` filters out of its
|
||||
* fetched history.
|
||||
* Takes the messages deleted this session out of the history `ChatModal`
|
||||
* fetched.
|
||||
*/
|
||||
deletedChatIds: Set<string>;
|
||||
/**
|
||||
* Messages from others after the read marker. The server keeps the marker,
|
||||
* so the count agrees across tabs and devices.
|
||||
* Drives the chat buttons' unread badge. It counts the messages from others
|
||||
* after the read marker, which the server keeps, so the count agrees across
|
||||
* tabs and devices.
|
||||
*/
|
||||
unreadChatCount: number;
|
||||
/**
|
||||
* The time of the newest message the user has seen, which the server keeps.
|
||||
* Null until the `welcome`, and for a visitor.
|
||||
* Places the "new" divider when the chat opens. It holds the time of the
|
||||
* newest message the user has seen, which the server keeps, and is null
|
||||
* until the `welcome`, and for a visitor.
|
||||
*/
|
||||
chatReadAt: Date | null;
|
||||
pendingChatMessages: PendingChatMessage[];
|
||||
/** Only for edits and deletes. A failed send shows on its pending message. */
|
||||
chatError: ChatError | null;
|
||||
/** Includes the user when they are typing: `ChatModal` filters them out. */
|
||||
/**
|
||||
* Shows who is typing. It includes the user themselves, whom `ChatModal`
|
||||
* filters out.
|
||||
*/
|
||||
typingUsers: OnlineUser[];
|
||||
/** Trims `body` and sends it as a pending message. Ignores a blank body. */
|
||||
sendChatMessage: (body: string, replyTo?: ChatReplyTarget) => void;
|
||||
@@ -244,10 +275,10 @@ export interface WSChatContextValue {
|
||||
*/
|
||||
markChatRead: (at: Date) => void;
|
||||
/**
|
||||
* Replaces the live buffer's messages between `from` and `to` with
|
||||
* `messages`, which `ChatModal` fetched after a reconnect. A message missing
|
||||
* from them was deleted while the socket was down. A failed send found among
|
||||
* them did reach the server, and leaves the pending list.
|
||||
* Repairs the live buffer after a reconnect. It replaces its messages between
|
||||
* `from` and `to` with `messages`, which `ChatModal` fetched. A message
|
||||
* missing from them was deleted while the socket was down. A failed send
|
||||
* found among them did reach the server, and leaves the pending list.
|
||||
*/
|
||||
reconcileChat: (messages: ChatMessage[], from: Date, to: Date) => void;
|
||||
/**
|
||||
@@ -257,23 +288,27 @@ export interface WSChatContextValue {
|
||||
*/
|
||||
setChatTyping: (typing: boolean) => void;
|
||||
/**
|
||||
* Tells the server whether the user is reading the chat: open, in a visible
|
||||
* tab. The server sends no notification for a mention the user reads live,
|
||||
* and this client doesn't count the message unread.
|
||||
* Spares the user a notification for a mention they read live. It tells the
|
||||
* server whether the chat is open in a visible tab; while it is, the server
|
||||
* sends no notification for a mention, and this client doesn't count the
|
||||
* message unread.
|
||||
*/
|
||||
setChatOpen: (open: boolean) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provided by `WSProvider` through {@link WSPresenceContext}, read with
|
||||
* `useWSPresence`.
|
||||
* Carries who is online, for `PresenceRow`. `WSProvider` provides it through
|
||||
* {@link WSPresenceContext}, and components read it with `useWSPresence`.
|
||||
*/
|
||||
export interface WSPresenceContextValue {
|
||||
/** The signed-in users with a socket open, each once. */
|
||||
onlineUsers: OnlineUser[];
|
||||
}
|
||||
|
||||
/** Given to a consumer outside `WSProvider`, as if it were still connecting. */
|
||||
/**
|
||||
* Lets a component render outside `WSProvider`, as if the socket were
|
||||
* connecting.
|
||||
*/
|
||||
export const defaultWSValue: WSContextValue = {
|
||||
wsStatus: "connecting",
|
||||
wsErrorMessage: null,
|
||||
@@ -301,7 +336,7 @@ export const defaultWSValue: WSContextValue = {
|
||||
clearUnreadNotifications: () => {},
|
||||
};
|
||||
|
||||
/** Given to a consumer outside `WSProvider`: an empty chat. */
|
||||
/** Lets a component render outside `WSProvider`, with an empty chat. */
|
||||
export const defaultWSChatValue: WSChatContextValue = {
|
||||
chatMessages: [],
|
||||
deletedChatIds: new Set(),
|
||||
@@ -322,25 +357,25 @@ export const defaultWSChatValue: WSChatContextValue = {
|
||||
setChatOpen: () => {},
|
||||
};
|
||||
|
||||
/** Given to a consumer outside `WSProvider`: nobody is online. */
|
||||
/** Lets a component render outside `WSProvider`, with nobody online. */
|
||||
export const defaultWSPresenceValue: WSPresenceContextValue = {
|
||||
onlineUsers: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* Holds the connection, the votes and likes, and the latest live event of
|
||||
* each kind. Read it with `useWS`.
|
||||
* Carries the live state pages lay over what they fetched: the connection, the
|
||||
* votes and likes, and the latest event of each kind. Read it with `useWS`.
|
||||
*/
|
||||
export const WSContext: React.Context<WSContextValue> = createContext<
|
||||
WSContextValue
|
||||
>(defaultWSValue);
|
||||
/** Holds the chat's live stream and read state. Read it with `useWSChat`. */
|
||||
/** Carries the chat's live stream and read state. Read it with `useWSChat`. */
|
||||
export const WSChatContext: React.Context<WSChatContextValue> = createContext<
|
||||
WSChatContextValue
|
||||
>(
|
||||
defaultWSChatValue,
|
||||
);
|
||||
/** Holds who is online. Read it with `useWSPresence`. */
|
||||
/** Carries who is online. Read it with `useWSPresence`. */
|
||||
export const WSPresenceContext: React.Context<WSPresenceContextValue> =
|
||||
createContext<
|
||||
WSPresenceContextValue
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/**
|
||||
* Holds the app's one WebSocket to `/ws` and turns its messages into the state
|
||||
* of `WSContext`, `WSChatContext`, `WSPresenceContext` and
|
||||
* `AvatarOverrideContext`. `App` mounts it around every page. The protocol is
|
||||
* `shared/ws.ts`, and the server's side is `api/routes/ws.ts`.
|
||||
* Brings the server's live updates into the app. It holds the one WebSocket to
|
||||
* `/ws` and turns its messages into the state of `WSContext`, `WSChatContext`,
|
||||
* `WSPresenceContext` and `AvatarOverrideContext`. `App` mounts it around every
|
||||
* page. The protocol is `shared/ws.ts`, and the server's side is
|
||||
* `api/routes/ws.ts`.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
@@ -94,9 +95,9 @@ const CHAT_READ_THROTTLE = 1_000;
|
||||
const CHAT_ECHO_WINDOW = 60_000;
|
||||
|
||||
/**
|
||||
* Held by {@link useOptimisticToggles} for an item whose toggles the server
|
||||
* hasn't all acknowledged yet. `count` and `on` are as before the first of
|
||||
* them, for a failure to restore.
|
||||
* Lets a failed toggle restore what was shown before the first click the server
|
||||
* hasn't acknowledged. {@link useOptimisticToggles} holds one for each such
|
||||
* item; `count` and `on` are as they were before that click.
|
||||
*/
|
||||
interface PendingToggle {
|
||||
timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Reads `ChatContext`, for the buttons that open the chat.
|
||||
* Lets a button open the chat, by reading `ChatContext`.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Loads a user's dumps or upvoted dumps, a page at a time, for the profile
|
||||
* subpages that list them in full.
|
||||
* Feeds the profile subpages that list all of a user's dumps, or all they
|
||||
* upvoted, loading them a page at a time.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Keeps the state of `TextEditor`'s emoji picker, which opens on a typed
|
||||
* `:query` or from the editor's button. `TextEditor` renders the picker and
|
||||
* handles its keys.
|
||||
* Lets a user insert an emoji by typing `:query` or from the editor's button.
|
||||
* It keeps the state of `TextEditor`'s emoji picker, and `TextEditor` renders
|
||||
* the picker and handles its keys.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Keeps the state of `TextEditor`'s `@mention` suggestions, which it fetches
|
||||
* from `GET /api/users/search`. `TextEditor` renders them in a
|
||||
* `MentionDropdown`.
|
||||
* Suggests users to @mention as the user types in `TextEditor`. It keeps the
|
||||
* state of the suggestions, fetched from `GET /api/users/search`, and
|
||||
* `TextEditor` renders them in a `MentionDropdown`.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/**
|
||||
* Stores, in this browser, which feed tab the index opens on.
|
||||
* Lets a user choose which feed tab the index opens on, remembered in this
|
||||
* browser.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Times the fade-out of items leaving a list, so that a removal made by
|
||||
* mistake can be undone before the item is gone.
|
||||
* Lets a removal made by mistake be undone before the item leaves the list, by
|
||||
* timing the fade-out of items leaving a list.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* Keeps a feed's loaded pages and scroll position in sessionStorage, so that
|
||||
* going back to a feed page shows it as it was left instead of refetching it.
|
||||
* Shows a feed as it was left when the user comes back to it, instead of
|
||||
* fetching it again. It keeps the feed's loaded pages and scroll position in
|
||||
* sessionStorage.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Loads a list a page at a time. Every list the API pages goes through
|
||||
* {@link useLoadMore} for its next pages, and a list that loads on its own
|
||||
* through {@link usePagedList}, from its first page on.
|
||||
* Gives every paged list of the app the same loading. Every list the API pages
|
||||
* goes through {@link useLoadMore} for its next pages, and a list that loads on
|
||||
* its own through {@link usePagedList}, from its first page on.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/**
|
||||
* react-hook-form, with the shared lifecycle of a form that posts to the API.
|
||||
* Gives every form that posts to the API the same submit, which shows a failed
|
||||
* request as the form's root error. It is react-hook-form's `useForm` plus that
|
||||
* `submit`.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
@@ -15,7 +17,7 @@ import {
|
||||
|
||||
import { friendlyFetchError } from "../../utils/shared/api.ts";
|
||||
|
||||
/** What {@link useApiForm} returns: the form, and `submit`. */
|
||||
/** Returned by {@link useApiForm}, the form with `submit` added. */
|
||||
export interface UseApiFormResult<T extends FieldValues>
|
||||
extends UseFormReturn<T> {
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* Loads a playlist with its dumps for its page, keeps them live, and makes the
|
||||
* changes its owner or a moderator makes there.
|
||||
* Everything a playlist's page shows and changes goes through this hook. It
|
||||
* loads the playlist with its dumps, keeps them live, and makes the changes its
|
||||
* owner or a moderator makes there.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* Loads which of the signed-in user's playlists hold a dump, and adds the dump
|
||||
* to them or takes it out.
|
||||
* Lets the user add a dump to their playlists or take it out, in
|
||||
* `AddToPlaylistModal` and the dump form's last step. It loads which of their
|
||||
* playlists hold the dump.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* Loads a profile's user with the first page of their dumps, upvotes and
|
||||
* playlists, and keeps them live.
|
||||
* Everything a profile page shows comes through this hook. It loads the user
|
||||
* with the first page of their dumps, upvotes and playlists, and keeps them
|
||||
* live.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/**
|
||||
* Loads one thing from the API, once per key, when first asked to.
|
||||
* Lets a component load one thing from the API the first time it's needed, and
|
||||
* keep it for as long as its key holds, as the profile's lazy tabs do.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/**
|
||||
* Reads `FollowContext`, for the Follow buttons.
|
||||
* Tells a Follow button what the user follows, and follows or unfollows, by
|
||||
* reading `FollowContext`.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Reads the live state that `WSProvider` keeps from the WebSocket. Each hook
|
||||
* reads one of its three contexts, so that a component re-renders only with
|
||||
* the part it uses. Outside `WSProvider`, they return inert defaults.
|
||||
* Gives components the server's live updates. Each hook reads one of the three
|
||||
* contexts `WSProvider` fills, so that a component re-renders only with the
|
||||
* part it uses. Outside `WSProvider`, they return inert defaults.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -200,7 +200,7 @@ msgstr "Already dumped by {0} {1}"
|
||||
msgid "Already have an account? <0>Log in</0>"
|
||||
msgstr "Already have an account? <0>Log in</0>"
|
||||
|
||||
#: src/utils/shared/api.ts:34
|
||||
#: src/utils/shared/api.ts:35
|
||||
msgid "An unexpected error occurred."
|
||||
msgstr "An unexpected error occurred."
|
||||
|
||||
@@ -228,8 +228,8 @@ msgstr "Auto"
|
||||
msgid "Back to login"
|
||||
msgstr "Back to login"
|
||||
|
||||
#: src/contexts/ws/WSProvider.tsx:394
|
||||
#: src/contexts/ws/WSProvider.tsx:760
|
||||
#: src/contexts/ws/WSProvider.tsx:395
|
||||
#: src/contexts/ws/WSProvider.tsx:761
|
||||
msgid "Can't connect to the live updates server. Upvotes and notifications may not sync until it reconnects."
|
||||
msgstr "Can't connect to the live updates server. Upvotes and notifications may not sync until it reconnects."
|
||||
|
||||
@@ -341,7 +341,7 @@ msgstr "Color scheme"
|
||||
msgid "Confirm new password"
|
||||
msgstr "Confirm new password"
|
||||
|
||||
#: src/contexts/ws/WSProvider.tsx:753
|
||||
#: src/contexts/ws/WSProvider.tsx:754
|
||||
msgid "Connection lost — this message may not have been sent"
|
||||
msgstr "Connection lost — this message may not have been sent"
|
||||
|
||||
@@ -357,7 +357,7 @@ msgstr "Copy"
|
||||
msgid "Could not change password"
|
||||
msgstr "Could not change password"
|
||||
|
||||
#: src/utils/shared/api.ts:32
|
||||
#: src/utils/shared/api.ts:33
|
||||
msgid "Could not connect to the server. Check your connection and try again."
|
||||
msgstr "Could not connect to the server. Check your connection and try again."
|
||||
|
||||
@@ -699,7 +699,7 @@ msgstr "Follow some public playlists to see their dumps here."
|
||||
msgid "Follow some users to see their dumps here."
|
||||
msgstr "Follow some users to see their dumps here."
|
||||
|
||||
#: src/components/feed/FeedTabBar.tsx:48
|
||||
#: src/components/feed/FeedTabBar.tsx:49
|
||||
#: src/components/profile/ProfileSettingsTab.tsx:44
|
||||
#: src/pages/UserPublicProfile.tsx:191
|
||||
msgid "Followed"
|
||||
@@ -745,7 +745,7 @@ msgstr "Go home"
|
||||
msgid "Go to login"
|
||||
msgstr "Go to login"
|
||||
|
||||
#: src/components/feed/FeedTabBar.tsx:44
|
||||
#: src/components/feed/FeedTabBar.tsx:45
|
||||
#: src/components/profile/ProfileSettingsTab.tsx:41
|
||||
msgid "Hot"
|
||||
msgstr "Hot"
|
||||
@@ -789,7 +789,7 @@ msgstr "invited by"
|
||||
msgid "Invitees"
|
||||
msgstr "Invitees"
|
||||
|
||||
#: src/components/feed/FeedTabBar.tsx:46
|
||||
#: src/components/feed/FeedTabBar.tsx:47
|
||||
#: src/components/profile/ProfileSettingsTab.tsx:43
|
||||
msgid "Journal"
|
||||
msgstr "Journal"
|
||||
@@ -814,7 +814,7 @@ msgstr "Link"
|
||||
msgid "Link or file"
|
||||
msgstr "Link or file"
|
||||
|
||||
#: src/contexts/ws/WSProvider.tsx:759
|
||||
#: src/contexts/ws/WSProvider.tsx:760
|
||||
msgid "Live updates are temporarily disconnected. Trying to reconnect…"
|
||||
msgstr "Live updates are temporarily disconnected. Trying to reconnect…"
|
||||
|
||||
@@ -919,7 +919,7 @@ msgid "new"
|
||||
msgstr "new"
|
||||
|
||||
#: src/components/chat/ChatModal.tsx:669
|
||||
#: src/components/feed/FeedTabBar.tsx:45
|
||||
#: src/components/feed/FeedTabBar.tsx:46
|
||||
#: src/components/profile/ProfileSettingsTab.tsx:42
|
||||
msgid "New"
|
||||
msgstr "New"
|
||||
@@ -993,7 +993,7 @@ msgstr "No playlists match \"{q}\"."
|
||||
msgid "No playlists yet."
|
||||
msgstr "No playlists yet."
|
||||
|
||||
#: src/contexts/ws/WSProvider.tsx:928
|
||||
#: src/contexts/ws/WSProvider.tsx:929
|
||||
msgid "No reply from the server — this message may not have been sent"
|
||||
msgstr "No reply from the server — this message may not have been sent"
|
||||
|
||||
@@ -1351,7 +1351,7 @@ msgstr "Some images couldn't be uploaded."
|
||||
|
||||
#: src/components/form/FormError.tsx:31
|
||||
#: src/components/layout/PageError.tsx:29
|
||||
#: src/contexts/ws/WSProvider.tsx:728
|
||||
#: src/contexts/ws/WSProvider.tsx:729
|
||||
#: src/pages/PlaylistDetail.tsx:119
|
||||
msgid "Something went wrong"
|
||||
msgstr "Something went wrong"
|
||||
@@ -1380,7 +1380,7 @@ msgstr "That file is over {limit}."
|
||||
msgid "the chat"
|
||||
msgstr "the chat"
|
||||
|
||||
#: src/hooks/playlist/usePlaylist.ts:160
|
||||
#: src/hooks/playlist/usePlaylist.ts:161
|
||||
msgid "The dump couldn't be removed. Try again."
|
||||
msgstr "The dump couldn't be removed. Try again."
|
||||
|
||||
@@ -1388,7 +1388,7 @@ msgstr "The dump couldn't be removed. Try again."
|
||||
msgid "The image couldn't be uploaded."
|
||||
msgstr "The image couldn't be uploaded."
|
||||
|
||||
#: src/hooks/playlist/usePlaylist.ts:306
|
||||
#: src/hooks/playlist/usePlaylist.ts:307
|
||||
msgid "The new order couldn't be saved. Try again."
|
||||
msgstr "The new order couldn't be saved. Try again."
|
||||
|
||||
@@ -1448,7 +1448,7 @@ msgstr "Type a message…"
|
||||
msgid "Undo"
|
||||
msgstr "Undo"
|
||||
|
||||
#: src/utils/shared/api.ts:21
|
||||
#: src/utils/shared/api.ts:22
|
||||
msgid "Unexpected response format"
|
||||
msgstr "Unexpected response format"
|
||||
|
||||
@@ -1580,7 +1580,7 @@ msgstr "You can post it anyway."
|
||||
msgid "You'll be notified when someone follows your playlists, upvotes your dumps, or posts new content."
|
||||
msgstr "You'll be notified when someone follows your playlists, upvotes your dumps, or posts new content."
|
||||
|
||||
#: src/contexts/ws/WSProvider.tsx:909
|
||||
#: src/contexts/ws/WSProvider.tsx:910
|
||||
msgid "You're offline — not sent"
|
||||
msgstr "You're offline — not sent"
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@ msgstr "Déjà recommandé par {0} {1}"
|
||||
msgid "Already have an account? <0>Log in</0>"
|
||||
msgstr "Vous avez déjà un compte ? <0>Se connecter</0>"
|
||||
|
||||
#: src/utils/shared/api.ts:34
|
||||
#: src/utils/shared/api.ts:35
|
||||
msgid "An unexpected error occurred."
|
||||
msgstr "Une erreur inattendue s'est produite."
|
||||
|
||||
@@ -228,8 +228,8 @@ msgstr "Auto"
|
||||
msgid "Back to login"
|
||||
msgstr "Retour à la connexion"
|
||||
|
||||
#: src/contexts/ws/WSProvider.tsx:394
|
||||
#: src/contexts/ws/WSProvider.tsx:760
|
||||
#: src/contexts/ws/WSProvider.tsx:395
|
||||
#: src/contexts/ws/WSProvider.tsx:761
|
||||
msgid "Can't connect to the live updates server. Upvotes and notifications may not sync until it reconnects."
|
||||
msgstr "Impossible de se connecter au serveur de mises à jour en direct. Les votes et les notifications pourraient ne pas se synchroniser avant la reconnexion."
|
||||
|
||||
@@ -341,7 +341,7 @@ msgstr "Thème de couleur"
|
||||
msgid "Confirm new password"
|
||||
msgstr "Confirmer le nouveau mot de passe"
|
||||
|
||||
#: src/contexts/ws/WSProvider.tsx:753
|
||||
#: src/contexts/ws/WSProvider.tsx:754
|
||||
msgid "Connection lost — this message may not have been sent"
|
||||
msgstr "Connexion perdue — ce message n'a peut-être pas été envoyé"
|
||||
|
||||
@@ -357,7 +357,7 @@ msgstr "Copier"
|
||||
msgid "Could not change password"
|
||||
msgstr "Impossible de changer le mot de passe"
|
||||
|
||||
#: src/utils/shared/api.ts:32
|
||||
#: src/utils/shared/api.ts:33
|
||||
msgid "Could not connect to the server. Check your connection and try again."
|
||||
msgstr "Impossible de joindre le serveur. Vérifiez votre connexion et réessayez."
|
||||
|
||||
@@ -699,7 +699,7 @@ msgstr "Suivez des collections publiques pour voir leurs recos ici."
|
||||
msgid "Follow some users to see their dumps here."
|
||||
msgstr "Suivez des utilisateurs pour voir leurs recos ici."
|
||||
|
||||
#: src/components/feed/FeedTabBar.tsx:48
|
||||
#: src/components/feed/FeedTabBar.tsx:49
|
||||
#: src/components/profile/ProfileSettingsTab.tsx:44
|
||||
#: src/pages/UserPublicProfile.tsx:191
|
||||
msgid "Followed"
|
||||
@@ -745,7 +745,7 @@ msgstr "Accueil"
|
||||
msgid "Go to login"
|
||||
msgstr "Aller à la connexion"
|
||||
|
||||
#: src/components/feed/FeedTabBar.tsx:44
|
||||
#: src/components/feed/FeedTabBar.tsx:45
|
||||
#: src/components/profile/ProfileSettingsTab.tsx:41
|
||||
msgid "Hot"
|
||||
msgstr "Tendances"
|
||||
@@ -789,7 +789,7 @@ msgstr "invité par"
|
||||
msgid "Invitees"
|
||||
msgstr "Invités"
|
||||
|
||||
#: src/components/feed/FeedTabBar.tsx:46
|
||||
#: src/components/feed/FeedTabBar.tsx:47
|
||||
#: src/components/profile/ProfileSettingsTab.tsx:43
|
||||
msgid "Journal"
|
||||
msgstr "Journal"
|
||||
@@ -814,7 +814,7 @@ msgstr "Lien"
|
||||
msgid "Link or file"
|
||||
msgstr "Lien ou fichier"
|
||||
|
||||
#: src/contexts/ws/WSProvider.tsx:759
|
||||
#: src/contexts/ws/WSProvider.tsx:760
|
||||
msgid "Live updates are temporarily disconnected. Trying to reconnect…"
|
||||
msgstr "Les mises à jour en direct sont temporairement interrompues. Tentative de reconnexion…"
|
||||
|
||||
@@ -919,7 +919,7 @@ msgid "new"
|
||||
msgstr "nouveau"
|
||||
|
||||
#: src/components/chat/ChatModal.tsx:669
|
||||
#: src/components/feed/FeedTabBar.tsx:45
|
||||
#: src/components/feed/FeedTabBar.tsx:46
|
||||
#: src/components/profile/ProfileSettingsTab.tsx:42
|
||||
msgid "New"
|
||||
msgstr "Nouveau"
|
||||
@@ -993,7 +993,7 @@ msgstr "Aucune collection ne correspond à « {q} »."
|
||||
msgid "No playlists yet."
|
||||
msgstr "Pas encore de collections."
|
||||
|
||||
#: src/contexts/ws/WSProvider.tsx:928
|
||||
#: src/contexts/ws/WSProvider.tsx:929
|
||||
msgid "No reply from the server — this message may not have been sent"
|
||||
msgstr "Pas de réponse du serveur — ce message n'a peut-être pas été envoyé"
|
||||
|
||||
@@ -1351,7 +1351,7 @@ msgstr "Certaines images n'ont pas pu être envoyées."
|
||||
|
||||
#: src/components/form/FormError.tsx:31
|
||||
#: src/components/layout/PageError.tsx:29
|
||||
#: src/contexts/ws/WSProvider.tsx:728
|
||||
#: src/contexts/ws/WSProvider.tsx:729
|
||||
#: src/pages/PlaylistDetail.tsx:119
|
||||
msgid "Something went wrong"
|
||||
msgstr "Une erreur est survenue"
|
||||
@@ -1380,7 +1380,7 @@ msgstr "Ce fichier dépasse {limit}."
|
||||
msgid "the chat"
|
||||
msgstr "la tribune"
|
||||
|
||||
#: src/hooks/playlist/usePlaylist.ts:160
|
||||
#: src/hooks/playlist/usePlaylist.ts:161
|
||||
msgid "The dump couldn't be removed. Try again."
|
||||
msgstr "Le dump n'a pas pu être retiré. Réessayez."
|
||||
|
||||
@@ -1388,7 +1388,7 @@ msgstr "Le dump n'a pas pu être retiré. Réessayez."
|
||||
msgid "The image couldn't be uploaded."
|
||||
msgstr "L'image n'a pas pu être envoyée."
|
||||
|
||||
#: src/hooks/playlist/usePlaylist.ts:306
|
||||
#: src/hooks/playlist/usePlaylist.ts:307
|
||||
msgid "The new order couldn't be saved. Try again."
|
||||
msgstr "Le nouvel ordre n'a pas pu être enregistré. Réessayez."
|
||||
|
||||
@@ -1448,7 +1448,7 @@ msgstr "Écrivez un message…"
|
||||
msgid "Undo"
|
||||
msgstr "Annuler"
|
||||
|
||||
#: src/utils/shared/api.ts:21
|
||||
#: src/utils/shared/api.ts:22
|
||||
msgid "Unexpected response format"
|
||||
msgstr "Format de réponse inattendu"
|
||||
|
||||
@@ -1580,7 +1580,7 @@ msgstr "Vous pouvez la publier quand même."
|
||||
msgid "You'll be notified when someone follows your playlists, upvotes your dumps, or posts new content."
|
||||
msgstr "Vous serez notifié lorsque quelqu'un suit vos collections, vote pour vos recos ou publie du nouveau contenu."
|
||||
|
||||
#: src/contexts/ws/WSProvider.tsx:909
|
||||
#: src/contexts/ws/WSProvider.tsx:910
|
||||
msgid "You're offline — not sent"
|
||||
msgstr "Hors ligne — message non envoyé"
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* The page of a dump, with its preview, comments, related dumps and the
|
||||
* Renders a dump's page, with its preview, comments, related dumps and the
|
||||
* playlists holding it.
|
||||
*
|
||||
* @module
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* The page where a dump's poster, or a moderator, edits or deletes it.
|
||||
* Renders the page where a dump's poster, or a moderator, edits or deletes it.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* The page that shows the results of a search, in a tab for dumps, one for
|
||||
* users and one for playlists.
|
||||
* Renders the results of a search, in a tab for dumps, one for users and one
|
||||
* for playlists.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* The page listing all of a user's dumps, which the "dumps" section of their
|
||||
* profile links to.
|
||||
* Renders the page listing all of a user's dumps, which the "dumps" section of
|
||||
* their profile links to.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* The page listing all the dumps a user upvoted, which the "upvoted" section of
|
||||
* their profile links to.
|
||||
* Renders the page listing all the dumps a user upvoted, which the "upvoted"
|
||||
* section of their profile links to.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Reads and writes the session in localStorage, where every tab shares one
|
||||
* entry. `AuthProvider` and `useAuth` are its only users.
|
||||
* Keeps the user signed in across reloads and tabs, in one localStorage entry
|
||||
* every tab shares. `AuthProvider` and `useAuth` are its only users.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Keeps the chat composer's unsent message in sessionStorage, per user, so
|
||||
* closing the chat modal or reloading the tab doesn't lose it. `ChatModal`
|
||||
* reads it on open and writes it on every change.
|
||||
* Keeps the chat composer's unsent message when the chat modal closes or the
|
||||
* tab reloads. It lives in sessionStorage, per user; `ChatModal` reads it on
|
||||
* open and writes it on every change.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Reads the Markdown of chat messages, to find mentions and to flatten it into
|
||||
* reply previews.
|
||||
* Finds the mentions in a chat message, and flattens its Markdown into reply
|
||||
* previews.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Keeps the half-written dump of the dump creation modal in localStorage, so
|
||||
* that closing the modal by mistake, with Escape or a click outside, loses
|
||||
* nothing but an attached file, which can't be stored.
|
||||
* Gives back the half-written dump when the dump creation modal is closed by
|
||||
* mistake, with Escape or a click outside. It is kept in localStorage, all but
|
||||
* an attached file, which can't be stored.
|
||||
*
|
||||
* There is one draft, not one per tab: whoever closed the modal by mistake
|
||||
* wants back what they were just writing, wherever they reopen it.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Stores the emoji picker's skin tone in localStorage, so that it outlasts the
|
||||
* picker and the page. `EmojiPickerPopup` reads and writes it.
|
||||
* Remembers the emoji picker's skin tone across pickers and pages. It lives in
|
||||
* localStorage, and `EmojiPickerPopup` reads and writes it.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Reads a text as the editor's autocompletes and the chat see it: whether the
|
||||
* caret is inside code, and whether a message is only emoji.
|
||||
* Tells the editor's autocompletes when the caret is inside code, where they
|
||||
* stay closed, and the chat when a message is only emoji, which it shows large.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Declares a list the API serves a page at a time, and reads its pages. Every
|
||||
* such list in the app is a {@link PagedList}, whose next page `useLoadMore`
|
||||
* appends.
|
||||
* Makes every list the API serves a page at a time work the same way. Each is a
|
||||
* {@link PagedList}, whose next page `useLoadMore` appends, and this module
|
||||
* reads its pages.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* Reads the API's JSON envelope, and turns the errors a request can end in into
|
||||
* messages. Every component and hook that calls the API goes through it.
|
||||
* Makes every call to the API succeed or fail the same way. It reads the JSON
|
||||
* envelope, and turns the errors a request can end in into messages. Every
|
||||
* component and hook that calls the API goes through it.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Declares the state of something a component loads from the API, which
|
||||
* `ListState` renders while it isn't loaded.
|
||||
* Gives every component that loads from the API the same states, so that
|
||||
* `ListState` renders loading, error and empty alike everywhere.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
|
||||
const STORAGE_KEY = "sounds";
|
||||
/**
|
||||
* Holds, in localStorage, the id of the last event that chimed, so that a user
|
||||
* with the site open in several tabs hears one chime per event, not one per
|
||||
* tab.
|
||||
* Makes a user with the site open in several tabs hear one chime per event, not
|
||||
* one per tab. The key holds, in localStorage, the id of the last event that
|
||||
* chimed.
|
||||
*/
|
||||
const CLAIM_KEY = "sounds-last-claim";
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user