import React, { useState, useEffect, useMemo } from 'react' import { useParams } from 'react-router-dom' import { Hooks } from 'applesauce-react' import { useEventStore } from 'applesauce-react/hooks' import { RelayPool } from 'applesauce-relay' import { Bookmark } from '../types/bookmarks' import { Highlight } from '../types/highlights' import { BookmarkList } from './BookmarkList' import { fetchBookmarks } from '../services/bookmarkService' import { fetchHighlights, fetchHighlightsForArticle } from '../services/highlightService' import { fetchContacts } from '../services/contactService' import ContentPanel from './ContentPanel' import { HighlightsPanel } from './HighlightsPanel' import { ReadableContent } from '../services/readerService' import Settings from './Settings' import Toast from './Toast' import { useSettings } from '../hooks/useSettings' import { useArticleLoader } from '../hooks/useArticleLoader' import { loadContent, BookmarkReference } from '../utils/contentLoader' import { HighlightVisibility } from './HighlightsPanel' export type ViewMode = 'compact' | 'cards' | 'large' interface BookmarksProps { relayPool: RelayPool | null onLogout: () => void } const Bookmarks: React.FC = ({ relayPool, onLogout }) => { const { naddr } = useParams<{ naddr?: string }>() const [bookmarks, setBookmarks] = useState([]) const [highlights, setHighlights] = useState([]) const [highlightsLoading, setHighlightsLoading] = useState(true) const [selectedUrl, setSelectedUrl] = useState(undefined) const [readerLoading, setReaderLoading] = useState(false) const [readerContent, setReaderContent] = useState(undefined) const [isCollapsed, setIsCollapsed] = useState(true) // Start collapsed const [isHighlightsCollapsed, setIsHighlightsCollapsed] = useState(true) // Start collapsed const [viewMode, setViewMode] = useState('compact') const [showUnderlines, setShowUnderlines] = useState(true) const [selectedHighlightId, setSelectedHighlightId] = useState(undefined) const [showSettings, setShowSettings] = useState(false) const [currentArticleCoordinate, setCurrentArticleCoordinate] = useState(undefined) const [currentArticleEventId, setCurrentArticleEventId] = useState(undefined) const [highlightVisibility, setHighlightVisibility] = useState({ nostrverse: true, friends: true, mine: true }) const [followedPubkeys, setFollowedPubkeys] = useState>(new Set()) const activeAccount = Hooks.useActiveAccount() const accountManager = Hooks.useAccountManager() const eventStore = useEventStore() const { settings, saveSettings, toastMessage, toastType, clearToast } = useSettings({ relayPool, eventStore, pubkey: activeAccount?.pubkey, accountManager }) // Load article if naddr is in URL useArticleLoader({ naddr, relayPool, setSelectedUrl, setReaderContent, setReaderLoading, setIsCollapsed, setIsHighlightsCollapsed, setHighlights, setHighlightsLoading, setCurrentArticleCoordinate, setCurrentArticleEventId }) // Load initial data on login useEffect(() => { if (!relayPool || !activeAccount) return handleFetchBookmarks() // Avoid overwriting article-specific highlights during initial article load // If an article is being viewed (naddr present), let useArticleLoader own the first highlights set if (!naddr) { handleFetchHighlights() } handleFetchContacts() }, [relayPool, activeAccount?.pubkey]) const handleFetchContacts = async () => { if (!relayPool || !activeAccount) return const contacts = await fetchContacts(relayPool, activeAccount.pubkey) setFollowedPubkeys(contacts) } // Apply UI settings useEffect(() => { if (settings.defaultViewMode) setViewMode(settings.defaultViewMode) if (settings.showUnderlines !== undefined) setShowUnderlines(settings.showUnderlines) if (settings.sidebarCollapsed !== undefined) setIsCollapsed(settings.sidebarCollapsed) if (settings.highlightsCollapsed !== undefined) setIsHighlightsCollapsed(settings.highlightsCollapsed) }, [settings]) const handleFetchBookmarks = async () => { if (!relayPool || !activeAccount) return const fullAccount = accountManager.getActive() await fetchBookmarks(relayPool, fullAccount || activeAccount, setBookmarks) } const handleFetchHighlights = async () => { if (!relayPool) return setHighlightsLoading(true) try { // If we're viewing an article, fetch highlights for that article if (currentArticleCoordinate) { const fetchedHighlights = await fetchHighlightsForArticle( relayPool, currentArticleCoordinate, currentArticleEventId ) console.log(`🔄 Refreshed ${fetchedHighlights.length} highlights for article`) setHighlights(fetchedHighlights) } // Otherwise, if logged in, fetch user's own highlights else if (activeAccount) { const fetchedHighlights = await fetchHighlights(relayPool, activeAccount.pubkey) setHighlights(fetchedHighlights) } } catch (err) { console.error('Failed to fetch highlights:', err) } finally { setHighlightsLoading(false) } } // Classify highlights with levels based on user context const classifiedHighlights = useMemo(() => { return highlights.map(h => { let level: 'mine' | 'friends' | 'nostrverse' = 'nostrverse' if (h.pubkey === activeAccount?.pubkey) { level = 'mine' } else if (followedPubkeys.has(h.pubkey)) { level = 'friends' } return { ...h, level } }) }, [highlights, activeAccount?.pubkey, followedPubkeys]) const handleSelectUrl = async (url: string, bookmark?: BookmarkReference) => { if (!relayPool) return setSelectedUrl(url) setReaderLoading(true) setReaderContent(undefined) setShowSettings(false) if (settings.collapseOnArticleOpen !== false) setIsCollapsed(true) try { const content = await loadContent(url, relayPool, bookmark) setReaderContent(content) } catch (err) { console.warn('Failed to fetch content:', err) } finally { setReaderLoading(false) } } return ( <>
setIsCollapsed(!isCollapsed)} onLogout={onLogout} viewMode={viewMode} onViewModeChange={setViewMode} selectedUrl={selectedUrl} onOpenSettings={() => { setShowSettings(true) setIsCollapsed(true) setIsHighlightsCollapsed(true) }} />
{showSettings ? ( setShowSettings(false)} /> ) : ( { setSelectedHighlightId(id) if (isHighlightsCollapsed) setIsHighlightsCollapsed(false) }} selectedHighlightId={selectedHighlightId} /> )}
setIsHighlightsCollapsed(!isHighlightsCollapsed)} onSelectUrl={handleSelectUrl} selectedUrl={selectedUrl} onToggleUnderlines={setShowUnderlines} selectedHighlightId={selectedHighlightId} onRefresh={handleFetchHighlights} onHighlightClick={setSelectedHighlightId} currentUserPubkey={activeAccount?.pubkey} highlightVisibility={highlightVisibility} onHighlightVisibilityChange={setHighlightVisibility} followedPubkeys={followedPubkeys} />
{toastMessage && ( )} ) } export default Bookmarks