appview-less bluesky client
27
fork

Configure Feed

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

check for new posts, show 'load new posts'

dawn 527024a1 5ec6fc4a

+52 -4
+29 -1
src/components/TimelineView.svelte
··· 17 17 feedTimelines, 18 18 feedCursors, 19 19 fetchFeed, 20 - resetFeed 20 + resetFeed, 21 + checkForNewPosts 21 22 } from '$lib/state.svelte'; 22 23 import Icon from '@iconify/svelte'; 23 24 import { buildThreads, filterThreads, type ThreadPost } from '$lib/thread'; ··· 54 55 const mutes = $derived(currentPrefs?.mutes ?? []); 55 56 56 57 let feedServiceDid = $state<string | null>(null); 58 + let newPostsAvailable = $state(false); 57 59 58 60 $effect(() => { 59 61 if (selectedFeed) { ··· 63 65 } else { 64 66 feedServiceDid = null; 65 67 } 68 + 69 + newPostsAvailable = false; 70 + if (!client || !selectedFeed) return; 71 + 72 + const check = async () => { 73 + if (!client || !selectedFeed || !feedServiceDid) return; 74 + newPostsAvailable = await checkForNewPosts(client, selectedFeed, feedServiceDid); 75 + }; 76 + 77 + check(); 78 + const interval = setInterval(check, 15000); 79 + 80 + return () => clearInterval(interval); 66 81 }); 67 82 68 83 export const clearFeed = () => { 69 84 if (!selectedFeed || !userDid) return; 85 + scrollContainer?.scrollTo({ top: 0, behavior: 'smooth' }); 86 + newPostsAvailable = false; 70 87 resetFeed(userDid, selectedFeed); 71 88 loaderState.reset(); 72 89 loadMore(); ··· 268 285 class="min-h-full p-2 [scrollbar-color:var(--nucleus-accent)_transparent] {className}" 269 286 bind:this={scrollContainer} 270 287 > 288 + {#if newPostsAvailable} 289 + <div class="sticky top-2 z-20 mb-4 flex w-full justify-center"> 290 + <button 291 + class="flex action-button items-center gap-2 bg-(--nucleus-bg) hover:scale-115! enabled:hover:bg-(--nucleus-bg)!" 292 + onclick={clearFeed} 293 + > 294 + <Icon icon="heroicons:arrow-up-16-solid" width="20" /> 295 + load new posts 296 + </button> 297 + </div> 298 + {/if} 271 299 {#if targetDid || $accounts.length > 0} 272 300 <InfiniteLoader {loaderState} triggerLoad={loadMore} loopDetectionTimeout={0}> 273 301 {#if selectedFeed}
+3 -2
src/lib/at/feeds.ts
··· 63 63 client: AtpClient, 64 64 feedUri: string, 65 65 feedServiceDid: string, 66 - cursor?: string 66 + cursor?: string, 67 + limit: number = 25 67 68 ): Promise<FeedSkeleton | null> { 68 69 const auth = client.user; 69 70 if (!auth) return null; 70 71 71 72 const cursorParam = cursor ? `&cursor=${encodeURIComponent(cursor)}` : ''; 72 - const url = `/xrpc/app.bsky.feed.getFeedSkeleton?feed=${encodeURIComponent(feedUri)}${cursorParam}`; 73 + const url = `/xrpc/app.bsky.feed.getFeedSkeleton?feed=${encodeURIComponent(feedUri)}&limit=${limit}${cursorParam}`; 73 74 74 75 try { 75 76 const response = await auth.atcute.handler(url, {
+20 -1
src/lib/state.svelte.ts
··· 606 606 if (cursor?.end) return; 607 607 608 608 const skeleton = await import('./at/feeds').then((m) => 609 - m.fetchFeedSkeleton(client, feedUri, feedServiceDid, cursor?.value) 609 + m.fetchFeedSkeleton(client, feedUri, feedServiceDid, cursor?.value, limit) 610 610 ); 611 611 if (!skeleton) throw `failed to fetch feed skeleton for ${feedUri}`; 612 612 ··· 643 643 ); 644 644 645 645 return newCursor; 646 + }; 647 + 648 + export const checkForNewPosts = async (client: AtpClient, feedUri: string, feedServiceDid: string) => { 649 + const userDid = client.user?.did; 650 + if (!userDid) return false; 651 + 652 + const currentFeed = feedTimelines.get(userDid)?.get(feedUri); 653 + if (!currentFeed || currentFeed.length === 0) return false; 654 + 655 + const skeleton = await import('./at/feeds').then((m) => 656 + m.fetchFeedSkeleton(client, feedUri, feedServiceDid, undefined, 1) 657 + ); 658 + 659 + if (skeleton && skeleton.feed.length > 0) { 660 + const latestPost = skeleton.feed[0].post; 661 + return latestPost !== currentFeed[0]; 662 + } 663 + 664 + return false; 646 665 }; 647 666 648 667 export const resetFeed = (did: Did, feedUri: string) => {