refactor: integrate long-form article rendering into existing reader view

- Create articleService to fetch articles by naddr
- Update Bookmarks component to detect naddr in URL params
- Articles now render in the existing ContentPanel with highlight support
- Remove standalone Article component
- Articles work seamlessly within the existing three-pane layout
- Support for article metadata (title, image, published date, summary)
This commit is contained in:
Gigi
2025-10-05 08:12:55 +01:00
parent 9b0c59b1ae
commit edd4e20e22
5 changed files with 147 additions and 179 deletions

View File

@@ -0,0 +1,102 @@
import { RelayPool, completeOnEose } from 'applesauce-relay'
import { lastValueFrom, takeUntil, timer, toArray } from 'rxjs'
import { nip19 } from 'nostr-tools'
import { AddressPointer } from 'nostr-tools/nip19'
import { NostrEvent } from 'nostr-tools'
import {
getArticleTitle,
getArticleImage,
getArticlePublished,
getArticleSummary
} from 'applesauce-core/helpers'
export interface ArticleContent {
title: string
markdown: string
image?: string
published?: number
summary?: string
author: string
event: NostrEvent
}
/**
* Fetches a Nostr long-form article (NIP-23) by naddr
*/
export async function fetchArticleByNaddr(
relayPool: RelayPool,
naddr: string
): Promise<ArticleContent> {
try {
// Decode the naddr
const decoded = nip19.decode(naddr)
if (decoded.type !== 'naddr') {
throw new Error('Invalid naddr format')
}
const pointer = decoded.data as AddressPointer
// Define relays to query
const relays = pointer.relays && pointer.relays.length > 0
? pointer.relays
: [
'wss://relay.damus.io',
'wss://nos.lol',
'wss://relay.nostr.band',
'wss://relay.primal.net'
]
// Fetch the article event
const filter = {
kinds: [pointer.kind],
authors: [pointer.pubkey],
'#d': [pointer.identifier]
}
// Use applesauce relay pool pattern
const events = await lastValueFrom(
relayPool
.req(relays, filter)
.pipe(completeOnEose(), takeUntil(timer(10000)), toArray())
)
if (events.length === 0) {
throw new Error('Article not found')
}
// Sort by created_at and take the most recent
events.sort((a, b) => b.created_at - a.created_at)
const article = events[0]
const title = getArticleTitle(article) || 'Untitled Article'
const image = getArticleImage(article)
const published = getArticlePublished(article)
const summary = getArticleSummary(article)
return {
title,
markdown: article.content,
image,
published,
summary,
author: article.pubkey,
event: article
}
} catch (err) {
console.error('Failed to fetch article:', err)
throw err
}
}
/**
* Checks if a string is a valid naddr
*/
export function isNaddr(str: string): boolean {
try {
const decoded = nip19.decode(str)
return decoded.type === 'naddr'
} catch {
return false
}
}