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.
This commit is contained in:
Gigi
2025-10-05 09:12:01 +01:00
parent 82ab07e606
commit ca46feb80f
4 changed files with 93 additions and 6 deletions

View File

@@ -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<BookmarksProps> = ({ 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)

View File

@@ -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<Highlight[]> => {
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 []
}
}