feat: enable highlight creation from external URLs

- Update createHighlight service to accept both NostrEvent and URL string as source
- Modify Bookmarks component to support highlighting on /r/* paths
- Add fetchHighlightsForUrl import for refreshing URL-based highlights
- Extract context from reader content (markdown/html) for external URLs
- Automatically use 'r' tag for external URLs via HighlightBlueprint
This commit is contained in:
Gigi
2025-10-06 19:43:27 +01:00
parent 107d6757bd
commit 06c3c1ff20
3 changed files with 56 additions and 20 deletions

View File

@@ -7,7 +7,7 @@ import { Bookmark } from '../types/bookmarks'
import { Highlight } from '../types/highlights'
import { BookmarkList } from './BookmarkList'
import { fetchBookmarks } from '../services/bookmarkService'
import { fetchHighlights, fetchHighlightsForArticle } from '../services/highlightService'
import { fetchHighlights, fetchHighlightsForArticle, fetchHighlightsForUrl } from '../services/highlightService'
import { fetchContacts } from '../services/contactService'
import ContentPanel from './ContentPanel'
import { HighlightsPanel } from './HighlightsPanel'
@@ -225,15 +225,23 @@ const Bookmarks: React.FC<BookmarksProps> = ({ relayPool, onLogout }) => {
const handleHighlightCreated = async () => {
// Refresh highlights after creating a new one
if (!relayPool || !currentArticleCoordinate) return
if (!relayPool) return
try {
const newHighlights = await fetchHighlightsForArticle(
relayPool,
currentArticleCoordinate,
currentArticleEventId
)
setHighlights(newHighlights)
// Refresh based on what we're currently viewing
if (currentArticleCoordinate) {
// Viewing a nostr article - fetch by article coordinate
const newHighlights = await fetchHighlightsForArticle(
relayPool,
currentArticleCoordinate,
currentArticleEventId
)
setHighlights(newHighlights)
} else if (selectedUrl && !selectedUrl.startsWith('nostr:')) {
// Viewing an external URL - fetch by URL
const newHighlights = await fetchHighlightsForUrl(relayPool, selectedUrl)
setHighlights(newHighlights)
}
} catch (err) {
console.error('Failed to refresh highlights:', err)
}
@@ -248,17 +256,32 @@ const Bookmarks: React.FC<BookmarksProps> = ({ relayPool, onLogout }) => {
}, [])
const handleCreateHighlight = useCallback(async (text: string) => {
if (!activeAccount || !relayPool || !currentArticle) {
if (!activeAccount || !relayPool) {
console.error('Missing requirements for highlight creation')
return
}
// Need either a nostr article or an external URL
if (!currentArticle && !selectedUrl) {
console.error('No source available for highlight creation')
return
}
try {
// Determine the source: prefer currentArticle (for nostr content), fallback to selectedUrl (for external URLs)
const source = currentArticle || selectedUrl!
// For context extraction, use article content or reader content
const contentForContext = currentArticle
? currentArticle.content
: readerContent?.markdown || readerContent?.html
await createHighlight(
text,
currentArticle,
source,
activeAccount,
relayPool
relayPool,
contentForContext
)
console.log('✅ Highlight created successfully!')
@@ -269,7 +292,7 @@ const Bookmarks: React.FC<BookmarksProps> = ({ relayPool, onLogout }) => {
} catch (error) {
console.error('Failed to create highlight:', error)
}
}, [activeAccount, relayPool, currentArticle, handleHighlightCreated])
}, [activeAccount, relayPool, currentArticle, selectedUrl, readerContent, handleHighlightCreated])
return (
<>

View File

@@ -8,32 +8,45 @@ import { RELAYS } from '../config/relays'
/**
* Creates and publishes a highlight event (NIP-84)
* Supports both nostr-native articles and external URLs
*/
export async function createHighlight(
selectedText: string,
article: NostrEvent | null,
source: NostrEvent | string,
account: IAccount,
relayPool: RelayPool,
contentForContext?: string,
comment?: string
): Promise<void> {
if (!selectedText || !article) {
if (!selectedText || !source) {
throw new Error('Missing required data to create highlight')
}
// Create EventFactory with the account as signer
const factory = new EventFactory({ signer: account })
// Parse article coordinate to get address pointer
const addressPointer = parseArticleCoordinate(article)
let blueprintSource: NostrEvent | AddressPointer | string
let context: string | undefined
// Extract context (previous and next sentences from the same paragraph)
const context = extractContext(selectedText, article.content)
// Handle NostrEvent (article) source
if (typeof source === 'object' && 'kind' in source) {
blueprintSource = parseArticleCoordinate(source)
context = extractContext(selectedText, source.content)
}
// Handle URL string source
else {
blueprintSource = source
// Try to extract context from provided content if available
if (contentForContext) {
context = extractContext(selectedText, contentForContext)
}
}
// Create highlight event using the blueprint
const highlightEvent = await factory.create(
HighlightBlueprint,
selectedText,
addressPointer,
blueprintSource,
context ? { comment, context } : comment ? { comment } : undefined
)