diff --git a/src/components/Note/Highlight/index.tsx b/src/components/Note/Highlight/index.tsx
index 68bd1f5..cdcdeb7 100644
--- a/src/components/Note/Highlight/index.tsx
+++ b/src/components/Note/Highlight/index.tsx
@@ -1,8 +1,17 @@
-import { Event } from 'nostr-tools'
+import { Event, kinds } from 'nostr-tools'
import { Highlighter } from 'lucide-react'
import { nip19 } from 'nostr-tools'
import logger from '@/lib/logger'
import HighlightSourcePreview from '@/components/UniversalContent/HighlightSourcePreview'
+import UserAvatar from '@/components/UserAvatar'
+import Username from '@/components/Username'
+import { useTranslation } from 'react-i18next'
+import { useSmartNoteNavigation } from '@/PageManager'
+import { toNote } from '@/lib/link'
+import { useFetchEvent } from '@/hooks'
+import { useEffect, useState, useMemo } from 'react'
+import client from '@/services/client.service'
+import { ExtendedKind } from '@/constants'
/**
* Check if a string is a URL or Nostr address
@@ -40,6 +49,52 @@ function isUrlOrNostrAddress(value: string | undefined): boolean {
return false
}
+/**
+ * Simple author card for highlights with Nostr sources (e-tags, r-tags)
+ * Shows just "A note from: [user badge]" instead of the full embedded note
+ * The word "note" is a hyperlink to the referenced event
+ */
+function HighlightAuthorCard({
+ authorPubkey,
+ eventId,
+ onClick
+}: {
+ authorPubkey: string
+ eventId?: string
+ onClick?: () => void
+}) {
+ const { t } = useTranslation()
+ const { navigateToNote } = useSmartNoteNavigation()
+
+ const handleNoteClick = (e: React.MouseEvent) => {
+ e.stopPropagation()
+ if (onClick) {
+ onClick()
+ } else if (eventId) {
+ navigateToNote(toNote(eventId))
+ }
+ }
+
+ return (
+
+
+ A{' '}
+
+ {' '}from:
+
+
+
+
+ )
+}
+
export default function Highlight({
event,
className
@@ -47,6 +102,11 @@ export default function Highlight({
event: Event
className?: string
}) {
+ // State for storing the referenced event's author
+ const [referencedEventAuthor, setReferencedEventAuthor] = useState(null)
+ const [sourceEventId, setSourceEventId] = useState(null)
+ const [sourceBech32, setSourceBech32] = useState(null)
+
try {
// Extract the source (e-tag, a-tag, or r-tag) with improved priority handling
@@ -86,6 +146,9 @@ export default function Highlight({
}
// Process the selected source tag
+ // We'll fetch the referenced event to get the author pubkey
+ let tempSourceEventId: string | null = null // Event ID or bech32 for fetching the event
+ let tempSourceBech32: string | null = null // Bech32 ID for navigation
if (sourceTag) {
if (sourceTag[0] === 'e' && sourceTag[1]) {
source = {
@@ -93,26 +156,80 @@ export default function Highlight({
value: sourceTag[1],
bech32: nip19.noteEncode(sourceTag[1])
}
+ tempSourceEventId = sourceTag[1] // Store event ID for fetching
+ tempSourceBech32 = nip19.noteEncode(sourceTag[1]) // Store bech32 for navigation
} else if (sourceTag[0] === 'a' && sourceTag[1]) {
const [kind, pubkey, identifier] = sourceTag[1].split(':')
const relay = sourceTag[2]
+ const bech32 = nip19.naddrEncode({
+ kind: parseInt(kind),
+ pubkey,
+ identifier: identifier || '',
+ relays: relay ? [relay] : []
+ })
source = {
type: 'addressable' as const,
value: sourceTag[1],
- bech32: nip19.naddrEncode({
- kind: parseInt(kind),
- pubkey,
- identifier: identifier || '',
- relays: relay ? [relay] : []
- })
+ bech32
}
+ tempSourceEventId = bech32 // Store bech32 for fetching the event
+ tempSourceBech32 = bech32 // Store bech32 for navigation
} else if (sourceTag[0] === 'r') {
// Check if the r-tag value is a URL or Nostr address
if (sourceTag[1] && isUrlOrNostrAddress(sourceTag[1])) {
- source = {
- type: 'url' as const,
- value: sourceTag[1],
- bech32: sourceTag[1]
+ // Try to decode as Nostr address to extract author
+ try {
+ const decoded = nip19.decode(sourceTag[1])
+ if (decoded.type === 'naddr') {
+ // For naddr, we have the pubkey directly
+ nostrSourceAuthor = decoded.data.pubkey
+ source = {
+ type: 'url' as const,
+ value: sourceTag[1],
+ bech32: sourceTag[1]
+ }
+ } else if (decoded.type === 'nevent') {
+ // For nevent, we can fetch the event to get the author
+ tempSourceEventId = sourceTag[1] // Store bech32 for fetching
+ tempSourceBech32 = sourceTag[1] // Store bech32 for navigation
+ source = {
+ type: 'url' as const,
+ value: sourceTag[1],
+ bech32: sourceTag[1]
+ }
+ } else if (decoded.type === 'note') {
+ // For note, we can fetch the event to get the author
+ tempSourceEventId = sourceTag[1] // Store bech32 for fetching
+ tempSourceBech32 = sourceTag[1] // Store bech32 for navigation
+ source = {
+ type: 'url' as const,
+ value: sourceTag[1],
+ bech32: sourceTag[1]
+ }
+ } else if (decoded.type === 'naddr') {
+ // For naddr, we have the pubkey directly, but also fetch the event for consistency
+ tempSourceEventId = sourceTag[1] // Store bech32 for fetching
+ tempSourceBech32 = sourceTag[1] // Store bech32 for navigation
+ source = {
+ type: 'url' as const,
+ value: sourceTag[1],
+ bech32: sourceTag[1]
+ }
+ } else {
+ // Other Nostr types or URL
+ source = {
+ type: 'url' as const,
+ value: sourceTag[1],
+ bech32: sourceTag[1]
+ }
+ }
+ } catch {
+ // Not a valid Nostr address, treat as regular URL
+ source = {
+ type: 'url' as const,
+ value: sourceTag[1],
+ bech32: sourceTag[1]
+ }
}
} else if (sourceTag[1]) {
// It's plain text, store it as a quote source
@@ -120,6 +237,76 @@ export default function Highlight({
}
}
}
+
+ // Update state for fetching the referenced event
+ useEffect(() => {
+ if (tempSourceEventId) {
+ setSourceEventId(tempSourceEventId)
+ setSourceBech32(tempSourceBech32)
+ }
+ }, [tempSourceEventId, tempSourceBech32])
+
+ // Fetch the referenced event to get the author pubkey and check if it has a special card
+ const { event: referencedEvent } = useFetchEvent(sourceEventId || undefined)
+
+ // Determine if the referenced event has a special card that should be used instead of simple author card
+ const hasSpecialCard = useMemo(() => {
+ // For r-tags that are regular URLs (http/https), they have OpenGraph cards - always use those
+ if (sourceTag && sourceTag[0] === 'r' && sourceTag[1]) {
+ if (sourceTag[1].startsWith('http://') || sourceTag[1].startsWith('https://')) {
+ return true // URLs have OpenGraph cards - use full preview
+ }
+ }
+
+ if (!referencedEvent) {
+ // For a-tags, check the kind from the tag itself (before event is loaded)
+ if (sourceTag && sourceTag[0] === 'a' && sourceTag[1]) {
+ const [kindStr] = sourceTag[1].split(':')
+ const kind = parseInt(kindStr)
+ // Longform articles (30023) have their own preview card
+ if (kind === kinds.LongFormArticle) {
+ return true
+ }
+ }
+ return false // Don't know yet - wait for event to load
+ }
+
+ // Events with special preview cards that should always use full preview
+ const specialCardKinds = [
+ kinds.LongFormArticle, // 30023 - has LongFormArticlePreview
+ ExtendedKind.POLL, // Has PollPreview
+ ExtendedKind.DISCUSSION, // Has DiscussionNote
+ ExtendedKind.VIDEO, // Has VideoNotePreview
+ ExtendedKind.SHORT_VIDEO, // Has VideoNotePreview
+ ExtendedKind.PICTURE, // Has PictureNotePreview
+ ExtendedKind.PUBLICATION, // Has PublicationCard
+ ExtendedKind.WIKI_ARTICLE, // Has special card
+ ExtendedKind.WIKI_ARTICLE_MARKDOWN, // Has special card
+ ExtendedKind.VOICE, // Has special card
+ ExtendedKind.VOICE_COMMENT, // Has special card
+ ]
+
+ return specialCardKinds.includes(referencedEvent.kind)
+ }, [referencedEvent, sourceTag])
+
+ // Update the author when we get the referenced event
+ useEffect(() => {
+ if (referencedEvent) {
+ setReferencedEventAuthor(referencedEvent.pubkey)
+ }
+ }, [referencedEvent])
+
+ // For a-tags, we can also extract the pubkey directly from the tag (for immediate display)
+ useEffect(() => {
+ if (sourceTag && sourceTag[0] === 'a' && sourceTag[1] && !referencedEventAuthor && !hasSpecialCard) {
+ const [kindStr, pubkey] = sourceTag[1].split(':')
+ const kind = parseInt(kindStr)
+ // Only set author for a-tags that don't have special cards
+ if (pubkey && /^[0-9a-f]{64}$/i.test(pubkey) && kind !== kinds.LongFormArticle) {
+ setReferencedEventAuthor(pubkey)
+ }
+ }
+ }, [sourceTag, referencedEventAuthor, hasSpecialCard])
// Extract the context (the main quote/full text being highlighted from)
const contextTag = event.tags.find(tag => tag[0] === 'context')
@@ -186,7 +373,20 @@ export default function Highlight({
{/* Source preview card */}
{source && (
-
+ {/* Only show simple author card if:
+ 1. We have the author pubkey
+ 2. The referenced event doesn't have a special card (like LongFormArticle preview)
+ 3. For r-tags: only if it's a Nostr address, not a regular URL (URLs have OpenGraph cards)
+ */}
+ {referencedEventAuthor && !hasSpecialCard ? (
+
+ ) : (
+ // For sources with special cards, URLs with OpenGraph, or while loading, show full preview
+
+ )}