mirror of
https://github.com/dergigi/boris.git
synced 2026-01-09 09:54:34 +01:00
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:
87
src/components/HighlightCitation.tsx
Normal file
87
src/components/HighlightCitation.tsx
Normal 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
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user