check for new posts, show 'load new posts'
15 unmodified lines161718192020212223242528 unmodified lines5455565758596061621 unmodified line646566676869707172737475767778798081828384858687888990193 unmodified linesExpand all28428528628728828929029129229329429529629729829930030130236 unmodified lines15 unmodified lines accountPreferences, feedTimelines, feedCursors, fetchFeed, resetFeed resetFeed, checkForNewPosts } from '$lib/state.svelte'; import Icon from '@iconify/svelte'; import { buildThreads, filterThreads, type ThreadPost } from '$lib/thread'; import type { Did, RecordKey } from '@atcute/lexicons/syntax';28 unmodified lines const currentPrefs = $derived(userDid ? accountPreferences.get(userDid) : null); const mutes = $derived(currentPrefs?.mutes ?? []);
let feedServiceDid = $state<string | null>(null); let newPostsAvailable = $state(false);
$effect(() => { if (selectedFeed) { fetchFeedGenerator(client ?? viewClient, selectedFeed).then((meta) => {1 unmodified line }); } else { feedServiceDid = null; }
newPostsAvailable = false; if (!client || !selectedFeed) return;
const check = async () => { if (!client || !selectedFeed || !feedServiceDid) return; newPostsAvailable = await checkForNewPosts(client, selectedFeed, feedServiceDid); };
check(); const interval = setInterval(check, 15000);
return () => clearInterval(interval); });
export const clearFeed = () => { if (!selectedFeed || !userDid) return; scrollContainer?.scrollTo({ top: 0, behavior: 'smooth' }); newPostsAvailable = false; resetFeed(userDid, selectedFeed); loaderState.reset(); loadMore(); };193 unmodified linesExpand all<div class="min-h-full p-2 [scrollbar-color:var(--nucleus-accent)_transparent] {className}" bind:this={scrollContainer}> {#if newPostsAvailable} <div class="sticky top-2 z-20 mb-4 flex w-full justify-center"> <button class="flex action-button items-center gap-2 bg-(--nucleus-bg) hover:scale-115! enabled:hover:bg-(--nucleus-bg)!" onclick={clearFeed} > <Icon icon="heroicons:arrow-up-16-solid" width="20" /> load new posts </button> </div> {/if} {#if targetDid || $accounts.length > 0} <InfiniteLoader {loaderState} triggerLoad={loadMore} loopDetectionTimeout={0}> {#if selectedFeed} {@render feedPostsView()}36 unmodified lines604 unmodified linesExpand all60560660760860960961061161261330 unmodified lines644645646647648649650651652653654655656657658659660661662663664665666667668669670191 unmodified linesExpand all604 unmodified linesExpand all const cursor = userFeedCursors.get(feedUri); if (cursor?.end) return;
const skeleton = await import('./at/feeds').then((m) => m.fetchFeedSkeleton(client, feedUri, feedServiceDid, cursor?.value) m.fetchFeedSkeleton(client, feedUri, feedServiceDid, cursor?.value, limit) ); if (!skeleton) throw `failed to fetch feed skeleton for ${feedUri}`;
const newCursor = { value: skeleton.cursor, end: !skeleton.cursor };30 unmodified lines
return newCursor;};
export const checkForNewPosts = async (client: AtpClient, feedUri: string, feedServiceDid: string) => { const userDid = client.user?.did; if (!userDid) return false;
const currentFeed = feedTimelines.get(userDid)?.get(feedUri); if (!currentFeed || currentFeed.length === 0) return false;
const skeleton = await import('./at/feeds').then((m) => m.fetchFeedSkeleton(client, feedUri, feedServiceDid, undefined, 1) );
if (skeleton && skeleton.feed.length > 0) { const latestPost = skeleton.feed[0].post; return latestPost !== currentFeed[0]; }
return false;};
export const resetFeed = (did: Did, feedUri: string) => { feedTimelines.get(did)?.delete(feedUri); feedCursors.get(did)?.delete(feedUri);};191 unmodified linesExpand all61 unmodified lines62636465666667686970717272737475767712 unmodified lines61 unmodified linesexport async function fetchFeedSkeleton( client: AtpClient, feedUri: string, feedServiceDid: string, cursor?: string cursor?: string, limit: number = 25): Promise<FeedSkeleton | null> { const auth = client.user; if (!auth) return null;
const cursorParam = cursor ? `&cursor=${encodeURIComponent(cursor)}` : ''; const url = `/xrpc/app.bsky.feed.getFeedSkeleton?feed=${encodeURIComponent(feedUri)}${cursorParam}`; const url = `/xrpc/app.bsky.feed.getFeedSkeleton?feed=${encodeURIComponent(feedUri)}&limit=${limit}${cursorParam}`;
try { const response = await auth.atcute.handler(url, { method: 'GET',12 unmodified lines