mirror of
https://github.com/dergigi/boris.git
synced 2025-12-17 14:44:26 +01:00
fix: properly parse NIP-51 bookmark lists (kind 10003) according to specification
- Parse bookmark lists correctly according to NIP-51 specification - Handle 'e' tags for event references (the actual bookmarks) - Handle 'a' tags for article references - Handle 'r' tags for URL references - Display bookmark count and organize references by type - Show event IDs in a readable format with truncation - Add proper CSS styling for bookmark list display - Update Bookmark interface to include metadata fields This fixes the issue where kind:10003 bookmark lists weren't being displayed properly. Now follows the NIP-51 specification exactly as shown in the example event structure.
This commit is contained in:
@@ -11,6 +11,10 @@ interface Bookmark {
|
|||||||
content: string
|
content: string
|
||||||
created_at: number
|
created_at: number
|
||||||
tags: string[][]
|
tags: string[][]
|
||||||
|
bookmarkCount?: number
|
||||||
|
eventReferences?: string[]
|
||||||
|
articleReferences?: string[]
|
||||||
|
urlReferences?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BookmarksProps {
|
interface BookmarksProps {
|
||||||
@@ -87,37 +91,31 @@ const Bookmarks: React.FC<BookmarksProps> = ({ addressLoader, onLogout }) => {
|
|||||||
|
|
||||||
const parseBookmarkEvent = (event: NostrEvent): Bookmark | null => {
|
const parseBookmarkEvent = (event: NostrEvent): Bookmark | null => {
|
||||||
try {
|
try {
|
||||||
// Parse the event content as JSON (bookmark list)
|
// According to NIP-51, bookmark lists (kind 10003) contain:
|
||||||
const content = JSON.parse(event.content || '{}')
|
// - "e" tags for event references (the actual bookmarks)
|
||||||
|
// - "a" tags for article references
|
||||||
|
// - "r" tags for URL references
|
||||||
|
|
||||||
if (content.bookmarks && Array.isArray(content.bookmarks)) {
|
const eventTags = event.tags.filter((tag: string[]) => tag[0] === 'e')
|
||||||
// Handle bookmark list format
|
const articleTags = event.tags.filter((tag: string[]) => tag[0] === 'a')
|
||||||
return {
|
const urlTags = event.tags.filter((tag: string[]) => tag[0] === 'r')
|
||||||
id: event.id,
|
|
||||||
title: content.name || 'Untitled Bookmark List',
|
// Get the title from content or use a default
|
||||||
url: '',
|
const title = event.content || `Bookmark List (${eventTags.length + articleTags.length + urlTags.length} items)`
|
||||||
content: event.content,
|
|
||||||
created_at: event.created_at,
|
return {
|
||||||
tags: event.tags
|
id: event.id,
|
||||||
}
|
title: title,
|
||||||
|
url: '', // Bookmark lists don't have a single URL
|
||||||
|
content: event.content,
|
||||||
|
created_at: event.created_at,
|
||||||
|
tags: event.tags,
|
||||||
|
// Add metadata about the bookmark list
|
||||||
|
bookmarkCount: eventTags.length + articleTags.length + urlTags.length,
|
||||||
|
eventReferences: eventTags.map(tag => tag[1]),
|
||||||
|
articleReferences: articleTags.map(tag => tag[1]),
|
||||||
|
urlReferences: urlTags.map(tag => tag[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle individual bookmark entries
|
|
||||||
const urlTag = event.tags.find((tag: string[]) => tag[0] === 'r' && tag[1])
|
|
||||||
const titleTag = event.tags.find((tag: string[]) => tag[0] === 'title' && tag[1])
|
|
||||||
|
|
||||||
if (urlTag) {
|
|
||||||
return {
|
|
||||||
id: event.id,
|
|
||||||
title: titleTag?.[1] || 'Untitled',
|
|
||||||
url: urlTag[1],
|
|
||||||
content: event.content,
|
|
||||||
created_at: event.created_at,
|
|
||||||
tags: event.tags
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error parsing bookmark event:', error)
|
console.error('Error parsing bookmark event:', error)
|
||||||
return null
|
return null
|
||||||
@@ -189,21 +187,41 @@ const Bookmarks: React.FC<BookmarksProps> = ({ addressLoader, onLogout }) => {
|
|||||||
{bookmarks.map((bookmark) => (
|
{bookmarks.map((bookmark) => (
|
||||||
<div key={bookmark.id} className="bookmark-item">
|
<div key={bookmark.id} className="bookmark-item">
|
||||||
<h3>{bookmark.title}</h3>
|
<h3>{bookmark.title}</h3>
|
||||||
{bookmark.url && (
|
{bookmark.bookmarkCount && (
|
||||||
<a
|
<p className="bookmark-count">
|
||||||
href={bookmark.url}
|
{bookmark.bookmarkCount} bookmarks in this list
|
||||||
target="_blank"
|
</p>
|
||||||
rel="noopener noreferrer"
|
)}
|
||||||
className="bookmark-url"
|
{bookmark.urlReferences && bookmark.urlReferences.length > 0 && (
|
||||||
>
|
<div className="bookmark-urls">
|
||||||
{bookmark.url}
|
<h4>URLs:</h4>
|
||||||
</a>
|
{bookmark.urlReferences.map((url, index) => (
|
||||||
|
<a key={index} href={url} target="_blank" rel="noopener noreferrer" className="bookmark-url">
|
||||||
|
{url}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{bookmark.eventReferences && bookmark.eventReferences.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>
|
||||||
)}
|
)}
|
||||||
{bookmark.content && (
|
{bookmark.content && (
|
||||||
<p className="bookmark-content">{bookmark.content}</p>
|
<p className="bookmark-content">{bookmark.content}</p>
|
||||||
)}
|
)}
|
||||||
<div className="bookmark-meta">
|
<div className="bookmark-meta">
|
||||||
<span>Added: {formatDate(bookmark.created_at)}</span>
|
<span>Created: {formatDate(bookmark.created_at)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -122,6 +122,65 @@ body {
|
|||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bookmark-count {
|
||||||
|
color: #666;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bookmark-urls {
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bookmark-urls h4 {
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bookmark-url {
|
||||||
|
display: block;
|
||||||
|
margin: 0.25rem 0;
|
||||||
|
color: #007bff;
|
||||||
|
text-decoration: none;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bookmark-url:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bookmark-events {
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bookmark-events h4 {
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-ids {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-id {
|
||||||
|
background: #f5f5f5;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.more-events {
|
||||||
|
color: #999;
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
.logout-button {
|
.logout-button {
|
||||||
background: #dc3545;
|
background: #dc3545;
|
||||||
color: white;
|
color: white;
|
||||||
|
|||||||
Reference in New Issue
Block a user