import React, { useRef, useState } from 'react' import { useNavigate } from 'react-router-dom' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faChevronLeft, faBookmark, faList, faThLarge, faImage, faRotate, faHeart, faPlus } from '@fortawesome/free-solid-svg-icons' import { formatDistanceToNow } from 'date-fns' import { RelayPool } from 'applesauce-relay' import { Bookmark, IndividualBookmark } from '../types/bookmarks' import { BookmarkItem } from './BookmarkItem' import SidebarHeader from './SidebarHeader' import IconButton from './IconButton' import CompactButton from './CompactButton' import { ViewMode } from './Bookmarks' import { usePullToRefresh } from 'use-pull-to-refresh' import RefreshIndicator from './RefreshIndicator' import { BookmarkSkeleton } from './Skeletons' import { groupIndividualBookmarks, hasContent, getBookmarkSets, getBookmarksWithoutSet } from '../utils/bookmarkUtils' import { UserSettings } from '../services/settingsService' import AddBookmarkModal from './AddBookmarkModal' import { createWebBookmark } from '../services/webBookmarkService' import { RELAYS } from '../config/relays' import { Hooks } from 'applesauce-react' interface BookmarkListProps { bookmarks: Bookmark[] onSelectUrl?: (url: string, bookmark?: { id: string; kind: number; tags: string[][]; pubkey: string }) => void isCollapsed: boolean onToggleCollapse: () => void onLogout: () => void viewMode: ViewMode onViewModeChange: (mode: ViewMode) => void selectedUrl?: string onOpenSettings: () => void onRefresh?: () => void isRefreshing?: boolean lastFetchTime?: number | null loading?: boolean relayPool: RelayPool | null isMobile?: boolean settings?: UserSettings } export const BookmarkList: React.FC = ({ bookmarks, onSelectUrl, isCollapsed, onToggleCollapse, onLogout, viewMode, onViewModeChange, selectedUrl, onOpenSettings, onRefresh, isRefreshing, lastFetchTime, loading = false, relayPool, isMobile = false, settings }) => { const navigate = useNavigate() const bookmarksListRef = useRef(null) const friendsColor = settings?.highlightColorFriends || '#f97316' const [showAddModal, setShowAddModal] = useState(false) const activeAccount = Hooks.useActiveAccount() const handleSaveBookmark = async (url: string, title?: string, description?: string, tags?: string[]) => { if (!activeAccount || !relayPool) { throw new Error('Please login to create bookmarks') } await createWebBookmark(url, title, description, tags, activeAccount, relayPool, RELAYS) } // Pull-to-refresh for bookmarks const { isRefreshing: isPulling, pullPosition } = usePullToRefresh({ onRefresh: () => { if (onRefresh) { onRefresh() } }, maximumPullLength: 240, refreshThreshold: 80, isDisabled: !onRefresh }) // Merge and flatten all individual bookmarks from all lists const allIndividualBookmarks = bookmarks.flatMap(b => b.individualBookmarks || []) .filter(hasContent) // Separate bookmarks with setName (kind 30003) from regular bookmarks const bookmarksWithoutSet = getBookmarksWithoutSet(allIndividualBookmarks) const bookmarkSets = getBookmarkSets(allIndividualBookmarks) // Group non-set bookmarks as before const groups = groupIndividualBookmarks(bookmarksWithoutSet) const sections: Array<{ key: string; title: string; items: IndividualBookmark[] }> = [ { key: 'private', title: 'Private bookmarks', items: groups.privateItems }, { key: 'public', title: 'Public bookmarks', items: groups.publicItems }, { key: 'web', title: 'Web bookmarks', items: groups.web }, { key: 'amethyst', title: 'Old Bookmarks (Legacy)', items: groups.amethyst } ] // Add bookmark sets as additional sections bookmarkSets.forEach(set => { sections.push({ key: `set-${set.name}`, title: set.title || set.name, items: set.bookmarks }) }) if (isCollapsed) { // Check if the selected URL is in bookmarks const isBookmarked = selectedUrl && bookmarks.some(bookmark => { const bookmarkUrl = bookmark.url return bookmarkUrl === selectedUrl || selectedUrl.includes(bookmarkUrl) || bookmarkUrl.includes(selectedUrl) }) return (
) } return (
{allIndividualBookmarks.length === 0 ? ( loading ? (
{Array.from({ length: viewMode === 'large' ? 4 : viewMode === 'cards' ? 6 : 8 }).map((_, i) => ( ))}
) : (

No bookmarks found.

Add bookmarks using your nostr client to see them here.

If you aren't on nostr yet, start here: nstart.me

) ) : (
{sections.filter(s => s.items.length > 0).map(section => (

{section.title}

{section.key === 'web' && activeAccount && ( setShowAddModal(true)} title="Add web bookmark" ariaLabel="Add web bookmark" className="bookmark-section-action" /> )}
{section.items.map((individualBookmark, index) => ( ))}
))}
)}
navigate('/support')} title="Support Boris" ariaLabel="Support" variant="ghost" style={{ color: friendsColor }} />
{onRefresh && ( )} onViewModeChange('compact')} title="Compact list view" ariaLabel="Compact list view" variant={viewMode === 'compact' ? 'primary' : 'ghost'} /> onViewModeChange('cards')} title="Cards view" ariaLabel="Cards view" variant={viewMode === 'cards' ? 'primary' : 'ghost'} /> onViewModeChange('large')} title="Large preview view" ariaLabel="Large preview view" variant={viewMode === 'large' ? 'primary' : 'ghost'} />
{showAddModal && ( setShowAddModal(false)} onSave={handleSaveBookmark} /> )}
) }