From ca46feb80fdea5912f46fe40ad272871fd0894b2 Mon Sep 17 00:00:00 2001 From: Gigi Date: Sun, 5 Oct 2025 09:12:01 +0100 Subject: [PATCH] fix: fetch highlights by article reference instead of author - Add fetchHighlightsForArticle() to query highlights by article coordinate - Use #a tag filter to find highlights that reference the article - Query well-known relays for highlights even without authentication - Extract article's d-tag and construct coordinate (kind:pubkey:identifier) - Keep original fetchHighlights() for fetching user's own highlights - Add detailed logging for debugging highlight fetching This fixes the issue where no highlights were shown because we were querying for highlights created BY the article author rather than highlights created ABOUT the article. --- .cursor/rules/highlights-nip-and-docs.mdc | 3 + dist/index.html | 2 +- src/components/Bookmarks.tsx | 11 ++- src/services/highlightService.ts | 83 ++++++++++++++++++++++- 4 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 .cursor/rules/highlights-nip-and-docs.mdc diff --git a/.cursor/rules/highlights-nip-and-docs.mdc b/.cursor/rules/highlights-nip-and-docs.mdc new file mode 100644 index 00000000..3dca9096 --- /dev/null +++ b/.cursor/rules/highlights-nip-and-docs.mdc @@ -0,0 +1,3 @@ +--- +alwaysApply: true +--- diff --git a/dist/index.html b/dist/index.html index d944c05f..8f71a20a 100644 --- a/dist/index.html +++ b/dist/index.html @@ -5,7 +5,7 @@ Boris - Nostr Bookmarks - + diff --git a/src/components/Bookmarks.tsx b/src/components/Bookmarks.tsx index 6f1cbb13..c4384bb2 100644 --- a/src/components/Bookmarks.tsx +++ b/src/components/Bookmarks.tsx @@ -8,7 +8,7 @@ import { Bookmark } from '../types/bookmarks' import { Highlight } from '../types/highlights' import { BookmarkList } from './BookmarkList' import { fetchBookmarks } from '../services/bookmarkService' -import { fetchHighlights } from '../services/highlightService' +import { fetchHighlights, fetchHighlightsForArticle } from '../services/highlightService' import ContentPanel from './ContentPanel' import { HighlightsPanel } from './HighlightsPanel' import { fetchReadableContent, ReadableContent } from '../services/readerService' @@ -68,10 +68,15 @@ const Bookmarks: React.FC = ({ relayPool, onLogout }) => { url: `nostr:${naddr}` }) - // Fetch highlights for this article (using the article author's pubkey) + // Fetch highlights for this article using its address coordinate + // Extract the d-tag identifier from the article event + const dTag = article.event.tags.find(t => t[0] === 'd')?.[1] || '' + const articleCoordinate = `${article.event.kind}:${article.author}:${dTag}` + try { setHighlightsLoading(true) - const fetchedHighlights = await fetchHighlights(relayPool, article.author) + const fetchedHighlights = await fetchHighlightsForArticle(relayPool, articleCoordinate) + console.log(`📌 Found ${fetchedHighlights.length} highlights for article ${articleCoordinate}`) setHighlights(fetchedHighlights) } catch (err) { console.error('Failed to fetch highlights:', err) diff --git a/src/services/highlightService.ts b/src/services/highlightService.ts index c3bcc8d6..b745118d 100644 --- a/src/services/highlightService.ts +++ b/src/services/highlightService.ts @@ -29,6 +29,85 @@ function dedupeHighlights(events: NostrEvent[]): NostrEvent[] { return Array.from(byId.values()) } +/** + * Fetches highlights for a specific article by its address coordinate + * @param relayPool - The relay pool to query + * @param articleCoordinate - The article's address in format "kind:pubkey:identifier" (e.g., "30023:abc...def:my-article") + */ +export const fetchHighlightsForArticle = async ( + relayPool: RelayPool, + articleCoordinate: string +): Promise => { + try { + // Use well-known relays for highlights even if user isn't logged in + const highlightRelays = [ + 'wss://relay.damus.io', + 'wss://nos.lol', + 'wss://relay.nostr.band', + 'wss://relay.snort.social', + 'wss://purplepag.es' + ] + + console.log('🔍 Fetching highlights (kind 9802) for article:', articleCoordinate) + console.log('🔍 From relays:', highlightRelays) + + // Query for highlights that reference this article via the 'a' tag + const rawEvents = await lastValueFrom( + relayPool + .req(highlightRelays, { kinds: [9802], '#a': [articleCoordinate] }) + .pipe(completeOnEose(), takeUntil(timer(10000)), toArray()) + ) + + console.log('📊 Raw highlight events fetched:', rawEvents.length) + + // Deduplicate events by ID + const uniqueEvents = dedupeHighlights(rawEvents) + console.log('📊 Unique highlight events after deduplication:', uniqueEvents.length) + + const highlights: Highlight[] = uniqueEvents.map((event: NostrEvent) => { + // Use applesauce helpers to extract highlight data + const highlightText = getHighlightText(event) + const context = getHighlightContext(event) + const comment = getHighlightComment(event) + const sourceEventPointer = getHighlightSourceEventPointer(event) + const sourceAddressPointer = getHighlightSourceAddressPointer(event) + const sourceUrl = getHighlightSourceUrl(event) + const attributions = getHighlightAttributions(event) + + // Get author from attributions + const author = attributions.find(a => a.role === 'author')?.pubkey + + // Get event reference (prefer event pointer, fallback to address pointer) + const eventReference = sourceEventPointer?.id || + (sourceAddressPointer ? `${sourceAddressPointer.kind}:${sourceAddressPointer.pubkey}:${sourceAddressPointer.identifier}` : undefined) + + return { + id: event.id, + pubkey: event.pubkey, + created_at: event.created_at, + content: highlightText, + tags: event.tags, + eventReference, + urlReference: sourceUrl, + author, + context, + comment + } + }) + + // Sort by creation time (newest first) + return highlights.sort((a, b) => b.created_at - a.created_at) + } catch (error) { + console.error('Failed to fetch highlights for article:', error) + return [] + } +} + +/** + * Fetches highlights created by a specific user + * @param relayPool - The relay pool to query + * @param pubkey - The user's public key + */ export const fetchHighlights = async ( relayPool: RelayPool, pubkey: string @@ -36,7 +115,7 @@ export const fetchHighlights = async ( try { const relayUrls = Array.from(relayPool.relays.values()).map(relay => relay.url) - console.log('🔍 Fetching highlights (kind 9802) from relays:', relayUrls) + console.log('🔍 Fetching highlights (kind 9802) by author:', pubkey) const rawEvents = await lastValueFrom( relayPool @@ -84,7 +163,7 @@ export const fetchHighlights = async ( // Sort by creation time (newest first) return highlights.sort((a, b) => b.created_at - a.created_at) } catch (error) { - console.error('Failed to fetch highlights:', error) + console.error('Failed to fetch highlights by author:', error) return [] } }