feat: merge and flatten bookmarks from multiple lists

- Extract all individual bookmarks from all bookmark lists
- Display them in a single flat list (already sorted by date in service)
- Remove wrapper metadata like 'N bookmarks in this list'
- Show all bookmarks together, newest first
- Implements NIP-51 and NIP-B0 bookmark list merging
This commit is contained in:
Gigi
2025-10-07 05:06:00 +01:00
parent b37aac0a33
commit 0124de8318

View File

@@ -3,7 +3,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faChevronLeft, faBookmark, faSpinner, faList, faThLarge, faImage } from '@fortawesome/free-solid-svg-icons'
import { Bookmark } from '../types/bookmarks'
import { BookmarkItem } from './BookmarkItem'
import { formatDate, renderParsedContent } from '../utils/bookmarkUtils'
import SidebarHeader from './SidebarHeader'
import IconButton from './IconButton'
import { ViewMode } from './Bookmarks'
@@ -37,6 +36,11 @@ export const BookmarkList: React.FC<BookmarkListProps> = ({
isRefreshing,
loading = false
}) => {
// Merge and flatten all individual bookmarks from all lists
// They're already sorted by date in the service layer
const allIndividualBookmarks = bookmarks.flatMap(b => b.individualBookmarks || [])
.filter(ib => ib.content || ib.kind === 30023 || ib.kind === 39701)
if (isCollapsed) {
// Check if the selected URL is in bookmarks
const isBookmarked = selectedUrl && bookmarks.some(bookmark => {
@@ -73,73 +77,24 @@ export const BookmarkList: React.FC<BookmarkListProps> = ({
<div className="loading">
<FontAwesomeIcon icon={faSpinner} spin />
</div>
) : bookmarks.length === 0 ? (
) : allIndividualBookmarks.length === 0 ? (
<div className="empty-state">
<p>No bookmarks found.</p>
<p>Add bookmarks using your nostr client to see them here.</p>
</div>
) : (
<div className="bookmarks-list">
{bookmarks.map((bookmark, index) => (
<div key={`${bookmark.id}-${index}`} className="bookmark-item">
{bookmark.bookmarkCount && (
<p className="bookmark-count">
{bookmark.bookmarkCount} bookmarks in{' '}
<a
href={`https://search.dergigi.com/e/${bookmark.id}`}
target="_blank"
rel="noopener noreferrer"
className="event-link"
>
this list
</a>
:
</p>
)}
{bookmark.urlReferences && bookmark.urlReferences.length > 0 && (
<div className="bookmark-urls">
<h4>URLs:</h4>
{bookmark.urlReferences.map((url, index) => (
<a key={index} href={url} target="_blank" rel="noopener noreferrer" className="bookmark-url">
{url}
</a>
))}
</div>
)}
{bookmark.individualBookmarks && bookmark.individualBookmarks.length > 0 && (
<div className="individual-bookmarks">
<div className={`bookmarks-grid bookmarks-${viewMode}`}>
{bookmark.individualBookmarks
.filter(ib => ib.content || ib.kind === 30023 || ib.kind === 39701)
.map((individualBookmark, index) =>
<BookmarkItem
key={index}
bookmark={individualBookmark}
index={index}
onSelectUrl={onSelectUrl}
viewMode={viewMode}
/>
)}
</div>
</div>
)}
{bookmark.eventReferences && bookmark.eventReferences.length > 0 && bookmark.individualBookmarks?.length === 0 && (
<div className="bookmark-events">
<h4>Event References ({bookmark.eventReferences.length}):</h4>
<div className="event-ids">
{bookmark.eventReferences.slice(0, 3).map((eventId, index) => (
<span key={index} className="event-id">
{eventId.slice(0, 8)}...{eventId.slice(-8)}
</span>
))}
{bookmark.eventReferences.length > 3 && (
<span className="more-events">... and {bookmark.eventReferences.length - 3} more</span>
)}
</div>
</div>
)}
</div>
))}
<div className={`bookmarks-grid bookmarks-${viewMode}`}>
{allIndividualBookmarks.map((individualBookmark, index) =>
<BookmarkItem
key={`${individualBookmark.id}-${index}`}
bookmark={individualBookmark}
index={index}
onSelectUrl={onSelectUrl}
viewMode={viewMode}
/>
)}
</div>
</div>
)}
<div className="view-mode-controls">