feat(highlights): add citation attribution to highlight items

- Create HighlightCitation component to show source attribution
- For nostr-native content: display as '— Author, Article Title'
- For web URLs: display hostname as '— domain.com'
- Automatically resolves article titles from event references
- Resolves author names from profile data
- Add styling for citation line below highlight text
- Keep code DRY by reusing existing articleTitleResolver service
This commit is contained in:
Gigi
2025-10-14 11:01:02 +02:00
parent d25a9b1735
commit 29213ceb1c
3 changed files with 96 additions and 0 deletions

View File

@@ -0,0 +1,87 @@
import React, { useState, useEffect } from 'react'
import { RelayPool } from 'applesauce-relay'
import { useEventModel } from 'applesauce-react/hooks'
import { Models } from 'applesauce-core'
import { nip19 } from 'nostr-tools'
import { fetchArticleTitle } from '../services/articleTitleResolver'
interface HighlightCitationProps {
eventReference?: string
urlReference?: string
authorPubkey?: string
relayPool?: RelayPool | null
}
export const HighlightCitation: React.FC<HighlightCitationProps> = ({
eventReference,
urlReference,
authorPubkey,
relayPool
}) => {
const [articleTitle, setArticleTitle] = useState<string>()
const authorProfile = useEventModel(Models.ProfileModel, authorPubkey ? [authorPubkey] : null)
useEffect(() => {
if (!eventReference || !relayPool) {
return
}
const loadTitle = async () => {
try {
// Convert eventReference to naddr if needed
let naddr: string
if (eventReference.includes(':')) {
const parts = eventReference.split(':')
const kind = parseInt(parts[0])
const pubkey = parts[1]
const identifier = parts[2] || ''
naddr = nip19.naddrEncode({
kind,
pubkey,
identifier
})
} else {
naddr = eventReference
}
const title = await fetchArticleTitle(relayPool, naddr)
if (title) {
setArticleTitle(title)
}
} catch (error) {
console.error('Failed to load article title:', error)
}
}
loadTitle()
}, [eventReference, relayPool])
const authorName = authorProfile?.name || authorProfile?.display_name
// For nostr-native content with article reference
if (eventReference && (authorName || articleTitle)) {
return (
<div className="highlight-citation">
{authorName || 'Unknown'}{articleTitle ? `, ${articleTitle}` : ''}
</div>
)
}
// For web URLs
if (urlReference) {
try {
const url = new URL(urlReference)
return (
<div className="highlight-citation">
{url.hostname}
</div>
)
} catch {
return null
}
}
return null
}

View File

@@ -15,6 +15,7 @@ import { createDeletionRequest } from '../services/deletionService'
import ConfirmDialog from './ConfirmDialog'
import { getNostrUrl } from '../config/nostrGateways'
import CompactButton from './CompactButton'
import { HighlightCitation } from './HighlightCitation'
interface HighlightWithLevel extends Highlight {
level?: 'mine' | 'friends' | 'nostrverse'
@@ -338,6 +339,13 @@ export const HighlightItem: React.FC<HighlightItemProps> = ({
{highlight.content}
</blockquote>
<HighlightCitation
eventReference={highlight.eventReference}
urlReference={highlight.urlReference}
authorPubkey={highlight.author}
relayPool={relayPool}
/>
{highlight.comment && (
<div className="highlight-comment">
{highlight.comment}

View File

@@ -128,6 +128,7 @@
.highlight-content { flex: 1; display: flex; flex-direction: column; gap: 0.5rem; padding: 2.25rem 0.75rem 2.5rem; }
.highlight-text { margin: 0; padding: 0 0 0 1.25rem; font-style: italic; color: var(--color-text); line-height: 1.6; border-left: none; font-size: 0.95rem; }
.highlight-citation { margin-left: 1.25rem; font-size: 0.8rem; color: var(--color-text-secondary); font-style: normal; padding-top: 0.25rem; }
.highlight-comment { margin-top: 0.5rem; margin-left: 1.25rem; padding: 0.75rem; border-left: 3px solid; border-radius: 4px; font-size: 0.875rem; color: var(--color-text); line-height: 1.5; }
/* Level-colored comments */