import { useState, useEffect, useMemo, useCallback } from "react"; import { useSearchParams } from "react-router-dom"; import AnnotationCard, { HighlightCard } from "../components/AnnotationCard"; import BookmarkCard from "../components/BookmarkCard"; import CollectionItemCard from "../components/CollectionItemCard"; import AnnotationSkeleton from "../components/AnnotationSkeleton"; import IOSInstallBanner from "../components/IOSInstallBanner"; import { getAnnotationFeed, deleteHighlight } from "../api/client"; import { AlertIcon, InboxIcon } from "../components/Icons"; import { useAuth } from "../context/AuthContext"; import { X, ArrowUp } from "lucide-react"; import AddToCollectionModal from "../components/AddToCollectionModal"; export default function Feed() { const [searchParams, setSearchParams] = useSearchParams(); const tagFilter = searchParams.get("tag"); const [filter, setFilter] = useState(() => { return localStorage.getItem("feedFilter") || "all"; }); const [feedType, setFeedType] = useState(() => { return localStorage.getItem("feedType") || "all"; }); const [annotations, setAnnotations] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [hasMore, setHasMore] = useState(true); const [loadingMore, setLoadingMore] = useState(false); useEffect(() => { localStorage.setItem("feedFilter", filter); }, [filter]); useEffect(() => { localStorage.setItem("feedType", feedType); }, [feedType]); const [collectionModalState, setCollectionModalState] = useState({ isOpen: false, uri: null, }); const { user } = useAuth(); const fetchFeed = useCallback( async (isLoadMore = false) => { try { if (isLoadMore) { setLoadingMore(true); } else { setLoading(true); } let creatorDid = ""; if (feedType === "my-feed") { if (user?.did) { creatorDid = user.did; } else { setAnnotations([]); setLoading(false); setLoadingMore(false); return; } } const motivationMap = { commenting: "commenting", highlighting: "highlighting", bookmarking: "bookmarking", }; const motivation = motivationMap[filter] || ""; const limit = 50; const offset = isLoadMore ? annotations.length : 0; const data = await getAnnotationFeed( limit, offset, tagFilter || "", creatorDid, feedType, motivation, ); const newItems = data.items || []; if (newItems.length < limit) { setHasMore(false); } else { setHasMore(true); } if (isLoadMore) { setAnnotations((prev) => [...prev, ...newItems]); } else { setAnnotations(newItems); } } catch (err) { setError(err.message); } finally { setLoading(false); setLoadingMore(false); } }, [tagFilter, feedType, filter, user, annotations.length], ); useEffect(() => { fetchFeed(false); }, [fetchFeed]); const deduplicatedAnnotations = useMemo(() => { const inCollectionUris = new Set(); for (const item of annotations) { if (item.type === "CollectionItem") { const inner = item.annotation || item.highlight || item.bookmark; if (inner) { if (inner.uri) inCollectionUris.add(inner.uri.trim()); if (inner.id) inCollectionUris.add(inner.id.trim()); } } } const result = []; for (const item of annotations) { if (item.type !== "CollectionItem") { const itemUri = (item.uri || "").trim(); const itemId = (item.id || "").trim(); if ( (itemUri && inCollectionUris.has(itemUri)) || (itemId && inCollectionUris.has(itemId)) ) { continue; } } result.push(item); } return result; }, [annotations]); const filteredAnnotations = feedType === "all" || feedType === "popular" || feedType === "semble" || feedType === "margin" || feedType === "my-feed" ? filter === "all" ? deduplicatedAnnotations : deduplicatedAnnotations.filter((a) => { if (a.type === "CollectionItem") { if (filter === "commenting") return !!a.annotation; if (filter === "highlighting") return !!a.highlight; if (filter === "bookmarking") return !!a.bookmark; } if (filter === "commenting") return a.motivation === "commenting" || a.type === "Annotation"; if (filter === "highlighting") return a.motivation === "highlighting" || a.type === "Highlight"; if (filter === "bookmarking") return a.motivation === "bookmarking" || a.type === "Bookmark"; return a.motivation === filter; }) : deduplicatedAnnotations; return (
See what people are annotating and bookmarking
{error}
{filter === "all" ? "Be the first to annotate something!" : `No ${filter} items found.`}