v4: what comes into a live list goes where its date puts it — a dump edited into a category stayed out of the index's feed of it until a reload, and a dump or a playlist made public went on top of the others' lists as if just posted. The index's feed now takes a dump edited into its scope, and orders the dumps that arrive by date, as the server pages them; a created playlist goes by date in the profile's and /playlists' lists, a new one first as before. Both use withByDate (utils/feed/pagedList.ts), which leaves an item older than every one loaded to a later page. The search runs again after a reconnection, for every page of dumps loaded, with its users and playlists; it shares refetchPages with usePagedList's resync. injectDump's doc said a private dump is never broadcast, while its poster gets it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
khannurien
2026-10-10 07:43:28 +00:00
parent 308a8299f3
commit 7c00db2b84
11 changed files with 278 additions and 61 deletions

View File

@@ -103,3 +103,46 @@ export async function readFirstPage<T>(
export function withFirst<T extends { id: string }>(items: T[], item: T): T[] {
return items.some((i) => i.id === item.id) ? items : [item, ...items];
}
/**
* Returns `items`, newest first as the API pages them, with `item` where its
* `createdAt` puts it: before the first item no newer than it. An item older
* than every one loaded is left to a later page, and `items` returned as they
* are, as they are when `item` is already there. The list sync hooks put a
* new or newly visible dump or playlist in a list with it.
*/
export function withByDate<T extends { id: string; createdAt: Date }>(
items: T[],
item: T,
): T[] {
if (items.some((i) => i.id === item.id)) return items;
if (items.length === 0) return [item];
const time = item.createdAt.getTime();
const idx = items.findIndex((i) => i.createdAt.getTime() <= time);
if (idx === -1) return items;
return [...items.slice(0, idx), item, ...items.slice(idx)];
}
/**
* Fetches pages 1 to `pages` of a list again, and resolves to their items,
* each once, and whether more follow. An item added between two pages'
* fetches shifts the later page by one, which would list an item twice. A
* live list calls it after a reconnection, through `usePagedList`'s `resync`
* or, for the search's dumps, directly; it rejects when a page fails.
*/
export async function refetchPages<T extends { id: string }>(
pages: number,
fetchPage: (page: number) => Promise<PaginatedData<T>>,
): Promise<PaginatedData<T>> {
const results = await Promise.all(
Array.from({ length: pages }, (_, i) => fetchPage(i + 1)),
);
const byId = new Map<string, T>();
for (const item of results.flatMap((r) => r.items)) {
if (!byId.has(item.id)) byId.set(item.id, item);
}
return {
items: [...byId.values()],
hasMore: results[results.length - 1].hasMore,
};
}