Browse Source

made kind 1 highlights more stylish

imwald
Silberengel 4 months ago
parent
commit
f54ad9aa1e
  1. 224
      src/components/Note/Highlight/index.tsx
  2. 1
      src/i18n/locales/en.ts

224
src/components/Note/Highlight/index.tsx

@ -1,8 +1,17 @@ @@ -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 { @@ -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 (
<div
className="flex items-center gap-2 p-3 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50/50 dark:bg-gray-800/50"
>
<span className="text-sm text-muted-foreground">
A{' '}
<button
onClick={handleNoteClick}
className="text-primary hover:text-primary/80 hover:underline font-medium cursor-pointer"
>
note
</button>
{' '}from:
</span>
<UserAvatar userId={authorPubkey} size="small" />
<Username userId={authorPubkey} className="text-sm font-medium" />
</div>
)
}
export default function Highlight({
event,
className
@ -47,6 +102,11 @@ export default function Highlight({ @@ -47,6 +102,11 @@ export default function Highlight({
event: Event
className?: string
}) {
// State for storing the referenced event's author
const [referencedEventAuthor, setReferencedEventAuthor] = useState<string | null>(null)
const [sourceEventId, setSourceEventId] = useState<string | null>(null)
const [sourceBech32, setSourceBech32] = useState<string | null>(null)
try {
// Extract the source (e-tag, a-tag, or r-tag) with improved priority handling
@ -86,6 +146,9 @@ export default function Highlight({ @@ -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({ @@ -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({ @@ -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({ @@ -186,7 +373,20 @@ export default function Highlight({
{/* Source preview card */}
{source && (
<div className="mt-3">
<HighlightSourcePreview source={source} className="w-full" />
{/* 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 ? (
<HighlightAuthorCard
authorPubkey={referencedEventAuthor}
eventId={sourceBech32 || undefined}
/>
) : (
// For sources with special cards, URLs with OpenGraph, or while loading, show full preview
<HighlightSourcePreview source={source} className="w-full" />
)}
</div>
)}
</div>

1
src/i18n/locales/en.ts

@ -374,6 +374,7 @@ export default { @@ -374,6 +374,7 @@ export default {
Posts: 'Posts',
Articles: 'Articles',
Highlights: 'Highlights',
'A note from': 'A note from',
Polls: 'Polls',
'Voice Posts': 'Voice Posts',
'Photo Posts': 'Photo Posts',

Loading…
Cancel
Save