on other user profiles, only fetch thread chains upwards, not downwards
144 unmodified linesExpand all1451461471481491501511521537 unmodified lines144 unmodified linesExpand all <ProfileInfo {client} {did} {profile} /> </div>
<TimelineView hydrateOptions={{ downwards: 'none' }} showReplies={false} {client} targetDid={did} bind:postComposerState7 unmodified lines61 unmodified lines626364656666676869707172162 unmodified linesExpand all61 unmodified lines loading = true; loaderState.status = 'LOADING';
try { await fetchTimeline(client, did, 7, showReplies); await fetchTimeline(client, did, 7, showReplies, { downwards: userDid === did ? 'sameAuthor' : 'none' }); // only fetch interactions if logged in (because if not who is the interactor) if (client.user && userDid) { if (!fetchingInteractions) { scheduledFetchInteractions = false;162 unmodified linesExpand all5 unmodified lines6789101011121314463 unmodified linesExpand all4784794804814824824834844854864872 unmodified lines490491492493493494495496497498499500501502503504149 unmodified linesExpand all5 unmodified lines type NotificationsStreamEvent} from './at/client.svelte';import { SvelteMap, SvelteDate, SvelteSet } from 'svelte/reactivity';import type { Did, Handle, Nsid, RecordKey, ResourceUri } from '@atcute/lexicons';import { fetchPosts, hydratePosts, type PostWithUri } from './at/fetch';import { fetchPosts, hydratePosts, type HydrateOptions, type PostWithUri } from './at/fetch';import { parseCanonicalResourceUri, type AtprotoDid } from '@atcute/lexicons/syntax';import { AppBskyActorProfile, AppBskyFeedPost,463 unmodified linesExpand allexport const fetchTimeline = async ( client: AtpClient, subject: Did, limit: number = 6, withBacklinks: boolean = true withBacklinks: boolean = true, hydrateOptions?: Partial<HydrateOptions>) => { const cursor = postCursors.get(subject); if (cursor && cursor.end) return;
2 unmodified lines
// if the cursor is undefined, we've reached the end of the timeline const newCursor = { value: accPosts.value.cursor, end: !accPosts.value.cursor }; postCursors.set(subject, newCursor); const hydrated = await hydratePosts(client, subject, accPosts.value.posts, hydrateCacheFn); const hydrated = await hydratePosts( client, subject, accPosts.value.posts, hydrateCacheFn, hydrateOptions ); if (!hydrated.ok) throw `cant hydrate posts ${subject}: ${hydrated.error}`;
addPosts(hydrated.value.values()); addTimeline(subject, hydrated.value.keys());149 unmodified linesExpand all57 unmodified lines5859606162636465666768696670717273747543 unmodified lines11912012112212311811912012112412512612712812312412512913013113212712812913013113213313413513613713813313413513613713813914014114214314414514014114214314414614714814915015115215315415557 unmodified lines return err(`cant fetch posts backlinks: ${error}`); }};
export type HydrateOptions = { downwards: 'sameAuthor' | 'none';};
export const hydratePosts = async ( client: AtpClient, repo: Did, data: PostWithBacklinks[], cacheFn: (did: Did, rkey: RecordKey) => Ok<PostWithUri> | undefined cacheFn: (did: Did, rkey: RecordKey) => Ok<PostWithUri> | undefined, options?: Partial<HydrateOptions>): Promise<Result<Map<ResourceUri, PostWithUri>, string>> => { let posts: Map<ResourceUri, PostWithUri> = new Map(); try { const allPosts = await Promise.all(43 unmodified lines } }; await Promise.all(posts.values().map(fetchUpwardsChain));
if (options?.downwards !== 'none') { try { const fetchDownwardsChain = async (post: PostWithUri) => { const { repo: postRepo } = expect(parseCanonicalResourceUri(post.uri)); if (repo === postRepo) return; try { const fetchDownwardsChain = async (post: PostWithUri) => { const { repo: postRepo } = expect(parseCanonicalResourceUri(post.uri)); if (repo === postRepo) return;
// get chains that are the same author until we exhaust them const backlinks = await client.getBacklinks(post.uri, replySource); if (!backlinks.ok) return; // get chains that are the same author until we exhaust them const backlinks = await client.getBacklinks(post.uri, replySource); if (!backlinks.ok) return;
const promises = []; for (const reply of backlinks.value.records) { if (reply.did !== postRepo) continue; // if we already have this reply, then we already fetched this chain / are fetching it if (posts.has(toCanonicalUri(reply))) continue; const record = cacheFn(reply.did, reply.rkey) ?? (await client.getRecord(AppBskyFeedPost.mainSchema, reply.did, reply.rkey)); if (!record.ok) break; // TODO: this doesnt handle deleted posts in between posts.set(record.value.uri, record.value); promises.push(fetchDownwardsChain(record.value)); } const promises = []; for (const reply of backlinks.value.records) { if (reply.did !== postRepo) continue; // if we already have this reply, then we already fetched this chain / are fetching it if (posts.has(toCanonicalUri(reply))) continue; const record = cacheFn(reply.did, reply.rkey) ?? (await client.getRecord(AppBskyFeedPost.mainSchema, reply.did, reply.rkey)); if (!record.ok) break; // TODO: this doesnt handle deleted posts in between posts.set(record.value.uri, record.value); promises.push(fetchDownwardsChain(record.value)); }
await Promise.all(promises); }; await Promise.all(posts.values().map(fetchDownwardsChain)); } catch (error) { return err(`cant fetch post reply chain: ${error}`); await Promise.all(promises); }; await Promise.all(posts.values().map(fetchDownwardsChain)); } catch (error) { return err(`cant fetch post reply chain: ${error}`); } }
return ok(posts);};