appview-less bluesky client
27
fork

Configure Feed

Select the types of activity you want to include in your feed.

on other user profiles, only fetch thread chains upwards, not downwards

dawn 4bfdd92b 991339b7

+46 -29
+1
src/components/ProfileView.svelte
··· 146 146 </div> 147 147 148 148 <TimelineView 149 + hydrateOptions={{ downwards: 'none' }} 149 150 showReplies={false} 150 151 {client} 151 152 targetDid={did}
+3 -1
src/components/TimelineView.svelte
··· 63 63 loaderState.status = 'LOADING'; 64 64 65 65 try { 66 - await fetchTimeline(client, did, 7, showReplies); 66 + await fetchTimeline(client, did, 7, showReplies, { 67 + downwards: userDid === did ? 'sameAuthor' : 'none' 68 + }); 67 69 // only fetch interactions if logged in (because if not who is the interactor) 68 70 if (client.user && userDid) { 69 71 if (!fetchingInteractions) {
+32 -25
src/lib/at/fetch.ts
··· 59 59 } 60 60 }; 61 61 62 + export type HydrateOptions = { 63 + downwards: 'sameAuthor' | 'none'; 64 + }; 65 + 62 66 export const hydratePosts = async ( 63 67 client: AtpClient, 64 68 repo: Did, 65 69 data: PostWithBacklinks[], 66 - cacheFn: (did: Did, rkey: RecordKey) => Ok<PostWithUri> | undefined 70 + cacheFn: (did: Did, rkey: RecordKey) => Ok<PostWithUri> | undefined, 71 + options?: Partial<HydrateOptions> 67 72 ): Promise<Result<Map<ResourceUri, PostWithUri>, string>> => { 68 73 let posts: Map<ResourceUri, PostWithUri> = new Map(); 69 74 try { ··· 115 120 }; 116 121 await Promise.all(posts.values().map(fetchUpwardsChain)); 117 122 118 - try { 119 - const fetchDownwardsChain = async (post: PostWithUri) => { 120 - const { repo: postRepo } = expect(parseCanonicalResourceUri(post.uri)); 121 - if (repo === postRepo) return; 123 + if (options?.downwards !== 'none') { 124 + try { 125 + const fetchDownwardsChain = async (post: PostWithUri) => { 126 + const { repo: postRepo } = expect(parseCanonicalResourceUri(post.uri)); 127 + if (repo === postRepo) return; 122 128 123 - // get chains that are the same author until we exhaust them 124 - const backlinks = await client.getBacklinks(post.uri, replySource); 125 - if (!backlinks.ok) return; 129 + // get chains that are the same author until we exhaust them 130 + const backlinks = await client.getBacklinks(post.uri, replySource); 131 + if (!backlinks.ok) return; 126 132 127 - const promises = []; 128 - for (const reply of backlinks.value.records) { 129 - if (reply.did !== postRepo) continue; 130 - // if we already have this reply, then we already fetched this chain / are fetching it 131 - if (posts.has(toCanonicalUri(reply))) continue; 132 - const record = 133 - cacheFn(reply.did, reply.rkey) ?? 134 - (await client.getRecord(AppBskyFeedPost.mainSchema, reply.did, reply.rkey)); 135 - if (!record.ok) break; // TODO: this doesnt handle deleted posts in between 136 - posts.set(record.value.uri, record.value); 137 - promises.push(fetchDownwardsChain(record.value)); 138 - } 133 + const promises = []; 134 + for (const reply of backlinks.value.records) { 135 + if (reply.did !== postRepo) continue; 136 + // if we already have this reply, then we already fetched this chain / are fetching it 137 + if (posts.has(toCanonicalUri(reply))) continue; 138 + const record = 139 + cacheFn(reply.did, reply.rkey) ?? 140 + (await client.getRecord(AppBskyFeedPost.mainSchema, reply.did, reply.rkey)); 141 + if (!record.ok) break; // TODO: this doesnt handle deleted posts in between 142 + posts.set(record.value.uri, record.value); 143 + promises.push(fetchDownwardsChain(record.value)); 144 + } 139 145 140 - await Promise.all(promises); 141 - }; 142 - await Promise.all(posts.values().map(fetchDownwardsChain)); 143 - } catch (error) { 144 - return err(`cant fetch post reply chain: ${error}`); 146 + await Promise.all(promises); 147 + }; 148 + await Promise.all(posts.values().map(fetchDownwardsChain)); 149 + } catch (error) { 150 + return err(`cant fetch post reply chain: ${error}`); 151 + } 145 152 } 146 153 147 154 return ok(posts);
+10 -3
src/lib/state.svelte.ts
··· 7 7 } from './at/client.svelte'; 8 8 import { SvelteMap, SvelteDate, SvelteSet } from 'svelte/reactivity'; 9 9 import type { Did, Handle, Nsid, RecordKey, ResourceUri } from '@atcute/lexicons'; 10 - import { fetchPosts, hydratePosts, type PostWithUri } from './at/fetch'; 10 + import { fetchPosts, hydratePosts, type HydrateOptions, type PostWithUri } from './at/fetch'; 11 11 import { parseCanonicalResourceUri, type AtprotoDid } from '@atcute/lexicons/syntax'; 12 12 import { 13 13 AppBskyActorProfile, ··· 479 479 client: AtpClient, 480 480 subject: Did, 481 481 limit: number = 6, 482 - withBacklinks: boolean = true 482 + withBacklinks: boolean = true, 483 + hydrateOptions?: Partial<HydrateOptions> 483 484 ) => { 484 485 const cursor = postCursors.get(subject); 485 486 if (cursor && cursor.end) return; ··· 490 491 // if the cursor is undefined, we've reached the end of the timeline 491 492 const newCursor = { value: accPosts.value.cursor, end: !accPosts.value.cursor }; 492 493 postCursors.set(subject, newCursor); 493 - const hydrated = await hydratePosts(client, subject, accPosts.value.posts, hydrateCacheFn); 494 + const hydrated = await hydratePosts( 495 + client, 496 + subject, 497 + accPosts.value.posts, 498 + hydrateCacheFn, 499 + hydrateOptions 500 + ); 494 501 if (!hydrated.ok) throw `cant hydrate posts ${subject}: ${hydrated.error}`; 495 502 496 503 addPosts(hydrated.value.values());