appview-less bluesky client
27
fork

Configure Feed

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

limit how much we show in ui in timelines and reset it on navigation

dawn 2a633c66 3579176e

+50 -22
+26 -14
src/components/FeedTimelineView.svelte
··· 46 46 selectedFeed; 47 47 feedServiceDid = null; 48 48 newPostsAvailable = false; 49 + displayCount = 15; 49 50 loaderState.reset(); 50 51 fetchFeedGenerator(client ?? viewClient, selectedFeed).then((meta) => { 51 52 feedServiceDid = meta?.did ?? null; ··· 78 79 if (!userDid) return; 79 80 scrollContainer?.scrollTo({ top: 0, behavior: 'smooth' }); 80 81 newPostsAvailable = false; 82 + displayCount = 15; 81 83 resetFeed(userDid, selectedFeed); 82 84 loaderState.reset(); 83 85 loadMore(); 84 86 }; 87 + 88 + let displayCount = $state(15); 85 89 86 90 const feedPosts = $derived.by(() => { 87 91 if (!userDid) return []; ··· 93 97 }) 94 98 .filter((p): p is NonNullable<typeof p> => p !== undefined); 95 99 }); 100 + 101 + const renderedPosts = $derived(feedPosts.slice(0, displayCount)); 96 102 97 103 const loadMore = async () => { 98 104 if (loading || !client || !userDid || !feedServiceDid) return; ··· 101 107 loaderState.status = 'LOADING'; 102 108 103 109 try { 104 - const result = await fetchFeed(client, selectedFeed, feedServiceDid); 110 + displayCount += 10; 111 + const bufferSize = feedPosts.length - displayCount; 112 + const cursor = feedCursors.get(userDid)?.get(selectedFeed); 105 113 106 - if (client.user && userDid) { 107 - if (!fetchingInteractions) { 108 - scheduledFetchInteractions = false; 109 - fetchingInteractions = true; 110 - await fetchInteractionsToFeedTimelineEnd(client, userDid, selectedFeed); 111 - fetchingInteractions = false; 112 - } else { 113 - scheduledFetchInteractions = true; 114 + if (bufferSize < 5 && !cursor?.end) { 115 + const result = await fetchFeed(client, selectedFeed, feedServiceDid); 116 + if (client.user && userDid) { 117 + if (!fetchingInteractions) { 118 + scheduledFetchInteractions = false; 119 + fetchingInteractions = true; 120 + await fetchInteractionsToFeedTimelineEnd(client, userDid, selectedFeed); 121 + fetchingInteractions = false; 122 + } else { 123 + scheduledFetchInteractions = true; 124 + } 114 125 } 126 + console.log('feed loaded', result?.end); 127 + if (result?.end) loaderState.complete(); 128 + } else { 129 + if (cursor?.end && displayCount >= feedPosts.length) loaderState.complete(); 115 130 } 116 - 117 131 loaderState.loaded(); 118 - console.log('feed loaded', result?.end); 119 - if (result?.end) loaderState.complete(); 120 132 } catch (error) { 121 133 loadError = `${error}`; 122 134 loaderState.error(); ··· 135 147 </script> 136 148 137 149 {#snippet feedPostsView()} 138 - {#each feedPosts as post, i (post.uri)} 150 + {#each renderedPosts as post, i (post.uri)} 139 151 {@const uriParts = post.uri.split('/')} 140 152 {@const postDid = uriParts[2] as Did} 141 153 {@const postRkey = uriParts[4] as RecordKey} ··· 155 167 }} 156 168 /> 157 169 </div> 158 - {#if i < feedPosts.length - 1} 170 + {#if i < renderedPosts.length - 1} 159 171 <div 160 172 class="mx-8 mt-3 mb-4 h-px bg-linear-to-r from-(--nucleus-accent)/30 to-(--nucleus-accent2)/30" 161 173 ></div>
+1
src/components/FollowingTimelineView.svelte
··· 119 119 <GenericTimelineView 120 120 {client} 121 121 {threads} 122 + timelineId={`following:${userDid}`} 122 123 bind:postComposerState 123 124 class={className} 124 125 isLoggedIn={!!(userDid || $accounts.length > 0)}
+18 -4
src/components/GenericTimelineView.svelte
··· 18 18 interface Props { 19 19 client?: AtpClient | null; 20 20 threads: Thread[]; 21 + timelineId?: string; 21 22 postComposerState: PostComposerState; 22 23 class?: string; 23 24 isLoggedIn?: boolean; // Controls rendering of the list vs NotLoggedIn ··· 29 30 let { 30 31 client = null, 31 32 threads, 33 + timelineId = undefined, 32 34 postComposerState = $bindable(), 33 35 class: className = '', 34 36 isLoggedIn = false, ··· 50 52 return threads.filter((t) => t.newestTime <= boundaryTime!); 51 53 }); 52 54 55 + let displayCount = $state(15); 56 + $effect(() => { 57 + timelineId; 58 + displayCount = 15; 59 + }); 60 + 61 + const renderedThreads = $derived(visibleThreads.slice(0, displayCount)); 62 + 53 63 $effect(() => { 54 64 if (threads.length > 0) { 55 65 if (isAtTop) boundaryTime = threads[0].newestTime; ··· 77 87 loadError = ''; 78 88 79 89 try { 80 - await onLoadMore(); 90 + displayCount += 10; 91 + const bufferSize = visibleThreads.length - displayCount; 92 + 93 + if (bufferSize < 5 && !isComplete) await onLoadMore(); 94 + 81 95 loaderState.loaded(); 82 - if (isComplete) loaderState.complete(); 96 + if (isComplete && displayCount >= visibleThreads.length) loaderState.complete(); 83 97 } catch (error) { 84 98 loadError = `${error}`; 85 99 loaderState.error(); ··· 116 130 {/snippet} 117 131 118 132 {#snippet threadsView()} 119 - {#each visibleThreads as thread, i (thread.rootUri)} 133 + {#each renderedThreads as thread, i (thread.rootUri)} 120 134 <div class="flex w-full shrink-0 {reverseChronological ? 'flex-col' : 'flex-col-reverse'}"> 121 135 {#if thread.branchParentPost} 122 136 {@render replyPost(thread.branchParentPost)} ··· 165 179 {/if} 166 180 {/each} 167 181 </div> 168 - {#if i < visibleThreads.length - 1} 182 + {#if i < renderedThreads.length - 1} 169 183 <div 170 184 class="mx-8 mt-3 mb-4 h-px bg-linear-to-r from-(--nucleus-accent)/30 to-(--nucleus-accent2)/30" 171 185 ></div>
+1
src/components/ReplyTimelineView.svelte
··· 85 85 <GenericTimelineView 86 86 {client} 87 87 {threads} 88 + timelineId={`replies:${did}`} 88 89 bind:postComposerState 89 90 class={className} 90 91 isLoggedIn={!!(did || $accounts.length > 0)}
+4 -4
src/lib/state.svelte.ts
··· 919 919 const feed = followingFeed.get(userDid); 920 920 if (!feed || feed.size === 0) return; 921 921 922 - let minTimestamp = Date.now() * 1000; 922 + let minTimestamp = Date.now(); 923 923 let found = false; 924 924 925 925 for (const uri of feed) { 926 926 const post = getPostFromUri(uri); 927 927 if (post) { 928 - const ts = new Date(post.record.createdAt).getTime() * 1000; 928 + const ts = new Date(post.record.createdAt).getTime(); 929 929 if (ts < minTimestamp) minTimestamp = ts; 930 930 found = true; 931 931 } ··· 948 948 const posts = userFeedTimelines.get(feedUri); 949 949 if (!posts || posts.length === 0) return; 950 950 951 - let minTimestamp = Date.now() * 1000; 951 + let minTimestamp = Date.now(); 952 952 let found = false; 953 953 954 954 for (const uri of posts) { 955 955 const post = getPostFromUri(uri); 956 956 if (post) { 957 - const ts = new Date(post.record.createdAt).getTime() * 1000; 957 + const ts = new Date(post.record.createdAt).getTime(); 958 958 if (ts < minTimestamp) minTimestamp = ts; 959 959 found = true; 960 960 }