appview-less bluesky client
24
fork

Configure Feed

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

at main 131 lines 3.4 kB view raw
1<script lang="ts"> 2 import { type State as PostComposerState } from './PostComposer.svelte'; 3 import { AtpClient } from '$lib/at/client.svelte'; 4 import { accounts } from '$lib/accounts'; 5 import { SvelteSet } from 'svelte/reactivity'; 6 import { 7 fetchFollowingTimeline, 8 allPosts, 9 followingFeed, 10 accountPreferences, 11 fetchInteractionsToFollowingTimelineEnd, 12 follows, 13 followingCursors, 14 initialDone 15 } from '$lib/state.svelte'; 16 import { buildThreadsFiltered } from '$lib/thread'; 17 import type { Did } from '@atcute/lexicons/syntax'; 18 import GenericTimelineView from './GenericTimelineView.svelte'; 19 20 interface Props { 21 client?: AtpClient | null; 22 postComposerState: PostComposerState; 23 class?: string; 24 targetDid?: Did; 25 } 26 27 let { 28 client = null, 29 postComposerState = $bindable(), 30 class: className = '', 31 targetDid = undefined 32 }: Props = $props(); 33 34 let viewOwnPosts = $state(true); 35 let displayCount = $state(10); 36 37 const userDid = $derived(targetDid ?? client?.user?.did); 38 39 const currentPrefs = $derived(userDid ? accountPreferences.get(userDid) : null); 40 const mutes = $derived(new Set(currentPrefs?.mutes ?? [])); 41 42 const followedDids = $derived.by(() => { 43 if (!userDid) return new Set<Did>(); 44 const map = follows.get(userDid); 45 if (!map) return new Set<Did>(); 46 return new Set(map.keys()); 47 }); 48 49 const threads = $derived( 50 userDid 51 ? buildThreadsFiltered( 52 userDid, 53 followingFeed.get(userDid) ?? new SvelteSet(), 54 allPosts, 55 mutes, 56 $accounts, 57 { viewOwnPosts, filterReplies: true, filterRootsToDids: followedDids }, 58 displayCount 59 ) 60 : [] 61 ); 62 63 const isComplete = $derived.by(() => { 64 if (!userDid) return false; 65 const cursors = followingCursors.get(userDid); 66 const subjects = follows.get(userDid); 67 68 // if no cursors yet, we haven't started 69 if (!cursors) return false; 70 71 // Check self 72 if (cursors.get(userDid) !== null) return false; 73 74 // Check follows 75 if (subjects) { 76 for (const subject of subjects.keys()) { 77 // if checking logic in state.svelte.ts: 78 // undefined means "not fetched", null means "exhausted" 79 // if any cursor is undefined or string, it's not complete. 80 if (cursors.get(subject) !== null) return false; 81 } 82 } 83 84 return true; 85 }); 86 87 let fetchingInteractions = $state(false); 88 let scheduledFetchInteractions = $state(false); 89 90 const loadMore = async () => { 91 if (!client || !userDid) return; 92 93 await fetchFollowingTimeline(client, userDid); 94 95 if (client.user && userDid) { 96 if (!fetchingInteractions) { 97 scheduledFetchInteractions = false; 98 fetchingInteractions = true; 99 await fetchInteractionsToFollowingTimelineEnd(client, userDid); 100 fetchingInteractions = false; 101 } else { 102 scheduledFetchInteractions = true; 103 } 104 } 105 }; 106 107 $effect(() => { 108 if (client && scheduledFetchInteractions && userDid) { 109 if (!fetchingInteractions) { 110 scheduledFetchInteractions = false; 111 fetchingInteractions = true; 112 fetchInteractionsToFollowingTimelineEnd(client, userDid).finally( 113 () => (fetchingInteractions = false) 114 ); 115 } 116 } 117 }); 118</script> 119 120<GenericTimelineView 121 {client} 122 {threads} 123 timelineId={`following:${userDid}`} 124 bind:postComposerState 125 bind:displayCount 126 class={className} 127 isLoggedIn={!!(userDid || $accounts.length > 0)} 128 canLoad={!!(client && userDid && initialDone.has(userDid))} 129 onLoadMore={loadMore} 130 {isComplete} 131/>