feat: centralize bookmark loading with streaming and auto-decrypt

Implemented centralized bookmark loading system:
- Bookmarks loaded in App.tsx with streaming + auto-decrypt pattern
- Load triggers: login, app mount, manual refresh only
- No redundant fetching on route changes

Changes:
1. bookmarkService.ts: Refactored fetchBookmarks for streaming
   - Events stream with onEvent callback
   - Auto-decrypt encrypted content (NIP-04/NIP-44) as events arrive
   - Progressive UI updates during loading

2. App.tsx: Added centralized bookmark state
   - bookmarks and bookmarksLoading state in AppRoutes
   - loadBookmarks function with streaming support
   - Load on mount if account exists (app reopen)
   - Load when activeAccount changes (login)
   - handleRefreshBookmarks for manual refresh
   - Pass props to all Bookmarks components

3. Bookmarks.tsx: Accept bookmarks as props
   - Receive bookmarks, bookmarksLoading, onRefreshBookmarks
   - Pass onRefreshBookmarks to useBookmarksData

4. useBookmarksData.ts: Simplified to accept bookmarks as props
   - Removed bookmark fetching logic
   - Removed handleFetchBookmarks function
   - Accept onRefreshBookmarks callback
   - Use onRefreshBookmarks in handleRefreshAll

5. Me.tsx: Removed fallback bookmark loading
   - Removed fetchBookmarks import and calls
   - Use bookmarks directly from props (centralized source)

Benefits:
- Single source of truth for bookmarks
- No duplicate fetching across components
- Streaming + auto-decrypt for better UX
- Simpler, more maintainable code
- DRY principle: one place for bookmark loading
This commit is contained in:
Gigi
2025-10-17 22:06:33 +02:00
parent d1ffc8c3f9
commit c2223e6b08
5 changed files with 325 additions and 280 deletions

View File

@@ -13,6 +13,7 @@ import { useHighlightCreation } from '../hooks/useHighlightCreation'
import { useBookmarksUI } from '../hooks/useBookmarksUI'
import { useRelayStatus } from '../hooks/useRelayStatus'
import { useOfflineSync } from '../hooks/useOfflineSync'
import { Bookmark } from '../types/bookmarks'
import ThreePaneLayout from './ThreePaneLayout'
import Explore from './Explore'
import Me from './Me'
@@ -24,9 +25,18 @@ export type ViewMode = 'compact' | 'cards' | 'large'
interface BookmarksProps {
relayPool: RelayPool | null
onLogout: () => void
bookmarks: Bookmark[]
bookmarksLoading: boolean
onRefreshBookmarks: () => Promise<void>
}
const Bookmarks: React.FC<BookmarksProps> = ({ relayPool, onLogout }) => {
const Bookmarks: React.FC<BookmarksProps> = ({
relayPool,
onLogout,
bookmarks,
bookmarksLoading,
onRefreshBookmarks
}) => {
const { naddr, npub } = useParams<{ naddr?: string; npub?: string }>()
const location = useLocation()
const navigate = useNavigate()
@@ -152,8 +162,6 @@ const Bookmarks: React.FC<BookmarksProps> = ({ relayPool, onLogout }) => {
}, [navigationState, setIsHighlightsCollapsed, setSelectedHighlightId, navigate, location.pathname])
const {
bookmarks,
bookmarksLoading,
highlights,
setHighlights,
highlightsLoading,
@@ -166,12 +174,12 @@ const Bookmarks: React.FC<BookmarksProps> = ({ relayPool, onLogout }) => {
} = useBookmarksData({
relayPool,
activeAccount,
accountManager,
naddr,
externalUrl,
currentArticleCoordinate,
currentArticleEventId,
settings
settings,
onRefreshBookmarks
})
const {

View File

@@ -9,7 +9,6 @@ import { useNavigate, useParams } from 'react-router-dom'
import { Highlight } from '../types/highlights'
import { HighlightItem } from './HighlightItem'
import { fetchHighlights } from '../services/highlightService'
import { fetchBookmarks } from '../services/bookmarkService'
import { fetchAllReads, ReadItem } from '../services/readsService'
import { fetchLinks } from '../services/linksService'
import { BlogPostPreview, fetchBlogPostsFromAuthors } from '../services/exploreService'
@@ -142,14 +141,7 @@ const Me: React.FC<MeProps> = ({ relayPool, activeTab: propActiveTab, pubkey: pr
try {
if (!hasBeenLoaded) setLoading(true)
try {
await fetchBookmarks(relayPool, activeAccount, (newBookmarks) => {
setBookmarks(newBookmarks)
})
} catch (err) {
console.warn('Failed to load bookmarks:', err)
setBookmarks([])
}
// Bookmarks come from centralized loading in App.tsx
setLoadedTabs(prev => new Set(prev).add('reading-list'))
} catch (err) {
console.error('Failed to load reading list:', err)
@@ -166,22 +158,8 @@ const Me: React.FC<MeProps> = ({ relayPool, activeTab: propActiveTab, pubkey: pr
try {
if (!hasBeenLoaded) setLoading(true)
// Ensure bookmarks are loaded
let fetchedBookmarks: Bookmark[] = bookmarks
if (bookmarks.length === 0) {
try {
await fetchBookmarks(relayPool, activeAccount, (newBookmarks) => {
fetchedBookmarks = newBookmarks
setBookmarks(newBookmarks)
})
} catch (err) {
console.warn('Failed to load bookmarks:', err)
fetchedBookmarks = []
}
}
// Derive reads from bookmarks immediately
const initialReads = deriveReadsFromBookmarks(fetchedBookmarks)
// Derive reads from bookmarks immediately (bookmarks come from centralized loading in App.tsx)
const initialReads = deriveReadsFromBookmarks(bookmarks)
const initialMap = new Map(initialReads.map(item => [item.id, item]))
setReadsMap(initialMap)
setReads(initialReads)
@@ -190,7 +168,7 @@ const Me: React.FC<MeProps> = ({ relayPool, activeTab: propActiveTab, pubkey: pr
// Background enrichment: merge reading progress and mark-as-read
// Only update items that are already in our map
fetchAllReads(relayPool, viewingPubkey, fetchedBookmarks, (item) => {
fetchAllReads(relayPool, viewingPubkey, bookmarks, (item) => {
console.log('📈 [Reads] Enrichment item received:', {
id: item.id.slice(0, 20) + '...',
progress: item.readingProgress,
@@ -230,22 +208,8 @@ const Me: React.FC<MeProps> = ({ relayPool, activeTab: propActiveTab, pubkey: pr
try {
if (!hasBeenLoaded) setLoading(true)
// Ensure bookmarks are loaded
let fetchedBookmarks: Bookmark[] = bookmarks
if (bookmarks.length === 0) {
try {
await fetchBookmarks(relayPool, activeAccount, (newBookmarks) => {
fetchedBookmarks = newBookmarks
setBookmarks(newBookmarks)
})
} catch (err) {
console.warn('Failed to load bookmarks:', err)
fetchedBookmarks = []
}
}
// Derive links from bookmarks immediately
const initialLinks = deriveLinksFromBookmarks(fetchedBookmarks)
// Derive links from bookmarks immediately (bookmarks come from centralized loading in App.tsx)
const initialLinks = deriveLinksFromBookmarks(bookmarks)
const initialMap = new Map(initialLinks.map(item => [item.id, item]))
setLinksMap(initialMap)
setLinks(initialLinks)