Something went wrong. Try again.
A Bsky-like frontend using the atprotocol natively.
Something went wrong. Try again.
3.4 kB · 110 lines
TSX
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111import { useState, useCallback, useEffect, useRef } from 'react';import type { BookmarkContextValue } from './bookmarkContext';import { BookmarkContext } from './bookmarkContext';import { atprotoClient } from '../api/client';import type { PostView } from '../api/types';
async function fetchAllBookmarks() { const uris = new Set<string>(); const posts = new Map<string, PostView>(); let cursor: string | undefined; do { const resp = await atprotoClient.getBookmarks(100, cursor); for (const bm of resp.bookmarks) { uris.add(bm.subject.uri); const item = bm.item as PostView | { $type: string; uri: string; notFound?: true; blocked?: boolean }; if (item && '$type' in item && (item as { $type: string }).$type === 'app.bsky.feed.defs#postView') { posts.set(bm.subject.uri, item as unknown as PostView); } } cursor = resp.cursor; } while (cursor); return { uris, posts };}
export default function BookmarkProvider({ children }: { children: React.ReactNode }) { const [bookmarkedUris, setBookmarkedUris] = useState<Set<string>>(new Set()); const [bookmarkPosts, setBookmarkPosts] = useState<Map<string, PostView>>(new Map()); const [loaded, setLoaded] = useState(false); const mountedRef = useRef(true);
const loadBookmarks = useCallback(async () => { try { const { uris, posts } = await fetchAllBookmarks(); if (mountedRef.current) { setBookmarkedUris(uris); setBookmarkPosts(posts); setLoaded(true); } } catch (err) { console.error('Failed to load bookmarks:', err); if (mountedRef.current) setLoaded(true); } }, []);
useEffect(() => { mountedRef.current = true; let cancelled = false; (async () => { try { const { uris, posts } = await fetchAllBookmarks(); if (!cancelled) { setBookmarkedUris(uris); setBookmarkPosts(posts); setLoaded(true); } } catch (err) { console.error('Failed to load bookmarks:', err); if (!cancelled) setLoaded(true); } })(); return () => { cancelled = true; mountedRef.current = false; }; }, []);
const addBookmark = useCallback(async (postUri: string, postCid: string) => { try { await atprotoClient.createBookmark(postUri, postCid); setBookmarkedUris((prev) => new Set(prev).add(postUri)); } catch (err) { console.error('Failed to add bookmark:', err); } }, []);
const removeBookmark = useCallback(async (postUri: string) => { try { await atprotoClient.deleteBookmark(postUri); setBookmarkedUris((prev) => { const next = new Set(prev); next.delete(postUri); return next; }); setBookmarkPosts((prev) => { const next = new Map(prev); next.delete(postUri); return next; }); } catch (err) { console.error('Failed to remove bookmark:', err); } }, []);
const isBookmarked = useCallback((postUri: string) => { return bookmarkedUris.has(postUri); }, [bookmarkedUris]);
const value: BookmarkContextValue = { bookmarkedUris, bookmarkPosts, loaded, addBookmark, removeBookmark, isBookmarked, reload: loadBookmarks, };
return ( <BookmarkContext.Provider value={value}> {children} </BookmarkContext.Provider> );}