diff --git a/dist/index.html b/dist/index.html index dc5b4fa9..6849bf82 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 b0d4666f..e2ac0845 100644 --- a/src/components/Bookmarks.tsx +++ b/src/components/Bookmarks.tsx @@ -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, fetchHighlightsForUrl } from '../services/highlightService' +import { fetchHighlights, fetchHighlightsForArticle } from '../services/highlightService' import { fetchContacts } from '../services/contactService' import ContentPanel from './ContentPanel' import { HighlightsPanel } from './HighlightsPanel' @@ -20,7 +20,7 @@ import { useExternalUrlLoader } from '../hooks/useExternalUrlLoader' import { loadContent, BookmarkReference } from '../utils/contentLoader' import { HighlightVisibility } from './HighlightsPanel' import { HighlightButton, HighlightButtonRef } from './HighlightButton' -import { createHighlight } from '../services/highlightCreationService' +import { createHighlight, eventToHighlight } from '../services/highlightCreationService' import { useRef, useCallback } from 'react' import { NostrEvent } from 'nostr-tools' export type ViewMode = 'compact' | 'cards' | 'large' @@ -223,30 +223,6 @@ const Bookmarks: React.FC = ({ relayPool, onLogout }) => { } } - const handleHighlightCreated = async () => { - // Refresh highlights after creating a new one - if (!relayPool) return - - try { - // 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) - } - } - const handleTextSelection = useCallback((text: string) => { highlightButtonRef.current?.updateSelection(text) }, []) @@ -276,7 +252,8 @@ const Bookmarks: React.FC = ({ relayPool, onLogout }) => { ? currentArticle.content : readerContent?.markdown || readerContent?.html - await createHighlight( + // Create and publish the highlight + const signedEvent = await createHighlight( text, source, activeAccount, @@ -287,12 +264,13 @@ const Bookmarks: React.FC = ({ relayPool, onLogout }) => { console.log('✅ Highlight created successfully!') highlightButtonRef.current?.clearSelection() - // Trigger refresh of highlights - handleHighlightCreated() + // Immediately add the highlight to the UI (optimistic update) + const newHighlight = eventToHighlight(signedEvent) + setHighlights(prev => [newHighlight, ...prev]) } catch (error) { console.error('Failed to create highlight:', error) } - }, [activeAccount, relayPool, currentArticle, selectedUrl, readerContent, handleHighlightCreated]) + }, [activeAccount, relayPool, currentArticle, selectedUrl, readerContent]) return ( <> diff --git a/src/services/highlightCreationService.ts b/src/services/highlightCreationService.ts index 2afd0a5a..279c974a 100644 --- a/src/services/highlightCreationService.ts +++ b/src/services/highlightCreationService.ts @@ -5,10 +5,21 @@ import { IAccount } from 'applesauce-accounts' import { AddressPointer } from 'nostr-tools/nip19' import { NostrEvent } from 'nostr-tools' import { RELAYS } from '../config/relays' +import { Highlight } from '../types/highlights' +import { + getHighlightText, + getHighlightContext, + getHighlightComment, + getHighlightSourceEventPointer, + getHighlightSourceAddressPointer, + getHighlightSourceUrl, + getHighlightAttributions +} from 'applesauce-core/helpers' /** * Creates and publishes a highlight event (NIP-84) * Supports both nostr-native articles and external URLs + * Returns the signed event for immediate UI updates */ export async function createHighlight( selectedText: string, @@ -17,7 +28,7 @@ export async function createHighlight( relayPool: RelayPool, contentForContext?: string, comment?: string -): Promise { +): Promise { if (!selectedText || !source) { throw new Error('Missing required data to create highlight') } @@ -65,6 +76,9 @@ export async function createHighlight( await relayPool.publish(RELAYS, signedEvent) console.log('✅ Highlight published to', RELAYS.length, 'relays (including local):', signedEvent) + + // Return the signed event for immediate UI updates + return signedEvent } /** @@ -159,3 +173,33 @@ function extractContext(selectedText: string, articleContent: string): string | return contextParts.length > 1 ? contextParts.join(' ') : undefined } +/** + * Converts a NostrEvent to a Highlight object for immediate UI display + */ +export function eventToHighlight(event: NostrEvent): Highlight { + 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) + + const author = attributions.find(a => a.role === 'author')?.pubkey + 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 + } +} +