v4: a new link between two dumps shows among their related dumps at once — it showed on the next load of the page: the backlink service recorded the link without telling anyone, and the dump page fetched its related dumps once, the socket then only editing or removing the ones it had. relinkBacklinks now sends dump_related_changed when a note or a comment gains or loses a link, naming the dump and those at the other end, and the page showing one of them fetches /related again. A link shows to those who may see both its ends, so each socket is told only of the dumps its user may see, and not at all without one at each end. A private dump that turns public sends it too, for the links it already had, which are new to everyone but its poster. The page also fetches its related dumps again on a reconnection, as it does its comments and playlists.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -77,6 +77,16 @@ export interface DumpEvent {
|
||||
dump: Dump;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keeps the related dumps on a dump's page live. `WSProvider` emits it as a
|
||||
* `related` event on each `dump_related_changed`, and the `Dump` page fetches
|
||||
* its related dumps again when `dumpIds` names its dump.
|
||||
*/
|
||||
export interface RelatedEvent {
|
||||
/** The dumps this user may see whose related dumps changed. */
|
||||
dumpIds: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Keeps a user's profile, and their name on what they posted, up to date.
|
||||
* `WSProvider` emits it as a `user` event on each `user_updated`, which the
|
||||
@@ -96,6 +106,7 @@ export interface UserEvent {
|
||||
export interface WSEvents {
|
||||
vote: VoteEvent;
|
||||
dump: DumpEvent;
|
||||
related: RelatedEvent;
|
||||
playlist: PlaylistEvent;
|
||||
comment: CommentEvent;
|
||||
user: UserEvent;
|
||||
|
||||
@@ -538,6 +538,10 @@ export function WSProvider({ children }: WSProviderProps): React.JSX.Element {
|
||||
break;
|
||||
}
|
||||
|
||||
case "dump_related_changed":
|
||||
events.emit("related", { dumpIds: msg.dumpIds });
|
||||
break;
|
||||
|
||||
case "vote_ack": {
|
||||
const { dumpId, action, voteCount } = msg;
|
||||
// With more clicks in flight, the last ack tells how it ended.
|
||||
|
||||
@@ -67,8 +67,7 @@ type DumpState =
|
||||
* `DumpEdit` or a title edit here just saved it. Reached by a former name or
|
||||
* slug, the URL takes the current ones. Its comments, related dumps and
|
||||
* playlists come from their own endpoints once it is loaded, and the socket's
|
||||
* events keep them current; a reconnection fetches the dump and its comments
|
||||
* again. A dump deleted, or made private by its poster, while someone else
|
||||
* events keep them current; a reconnection fetches them and the dump again. A dump deleted, or made private by its poster, while someone else
|
||||
* views it gives way to a notice. The poster and moderators can edit the
|
||||
* title in place, and get a link to `DumpEdit`.
|
||||
*/
|
||||
@@ -246,6 +245,13 @@ export function Dump(): React.JSX.Element {
|
||||
});
|
||||
|
||||
useDumpListSync(relatedDumps, setRelatedDumps);
|
||||
// Counts the events that say this dump gained or lost a related dump.
|
||||
const [relatedVersion, setRelatedVersion] = useState(0);
|
||||
useWSEvent("related", ({ dumpIds }) => {
|
||||
if (loadedDumpId && dumpIds.includes(loadedDumpId)) {
|
||||
setRelatedVersion((n) => n + 1);
|
||||
}
|
||||
});
|
||||
|
||||
// Counts the playlist events, each of which may change the playlists
|
||||
// holding the dump.
|
||||
@@ -285,7 +291,9 @@ export function Dump(): React.JSX.Element {
|
||||
})
|
||||
.catch(() => {});
|
||||
return () => controller.abort();
|
||||
}, [loadedDumpId, authFetch]);
|
||||
// The list sync above edits and removes its dumps, but can't tell which
|
||||
// one a new link adds: that, like a reconnection, fetches it again.
|
||||
}, [loadedDumpId, authFetch, relatedVersion, connectionEpoch]);
|
||||
|
||||
// The server leaves out the private playlists of others.
|
||||
useEffect(() => {
|
||||
|
||||
@@ -55,6 +55,7 @@ function setup({ signedIn = true } = {}) {
|
||||
events: {
|
||||
vote: useEvents("vote"),
|
||||
dump: useEvents("dump"),
|
||||
related: useEvents("related"),
|
||||
playlist: useEvents("playlist"),
|
||||
comment: useEvents("comment"),
|
||||
user: useEvents("user"),
|
||||
@@ -458,6 +459,13 @@ describe("dumps, playlists, comments, users", () => {
|
||||
expect(ws().recentDumps).toEqual([]);
|
||||
});
|
||||
|
||||
test("changed links are passed on as a related event", () => {
|
||||
const { socket, events } = setup();
|
||||
socket.welcome();
|
||||
socket.receive({ type: "dump_related_changed", dumpIds: ["a", "b"] });
|
||||
expect(events().related).toEqual([{ dumpIds: ["a", "b"] }]);
|
||||
});
|
||||
|
||||
test("injectDump adds my own new dump once", () => {
|
||||
const { ws } = setup();
|
||||
const d = makeDump({ id: "mine" });
|
||||
|
||||
@@ -304,6 +304,25 @@ describe("Dump page", () => {
|
||||
.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("a new link to or from this dump shows among its related dumps", async () => {
|
||||
dumpRoutes();
|
||||
const { socket } = openDump();
|
||||
await screen.findByRole("heading", { name: "A find" });
|
||||
expect(screen.queryByRole("heading", { name: "Related" })).toBeNull();
|
||||
route(
|
||||
"GET /api/dumps/:id/related",
|
||||
() => [wire(makeDump({ id: "d2", slug: "linked", title: "Linked" }))],
|
||||
);
|
||||
// Links between other dumps leave this page alone.
|
||||
socket.receive({ type: "dump_related_changed", dumpIds: ["d2", "d3"] });
|
||||
socket.receive({ type: "dump_related_changed", dumpIds: ["d2", "d1"] });
|
||||
expect(await screen.findByRole("link", { name: "Linked" }))
|
||||
.toBeInTheDocument();
|
||||
expect(screen.getByRole("heading", { name: "Related" }))
|
||||
.toBeInTheDocument();
|
||||
expect(calls("GET /api/dumps/:id/related")).toHaveLength(2);
|
||||
});
|
||||
|
||||
test("a dump deleted, or made private, while on screen gives way to a notice", async () => {
|
||||
dumpRoutes();
|
||||
const { socket } = openDump(other);
|
||||
|
||||
Reference in New Issue
Block a user