import React, { useState } from 'react' import { Link } from 'react-router-dom' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons' import { IconDefinition } from '@fortawesome/fontawesome-svg-core' import { IndividualBookmark } from '../../types/bookmarks' import { formatDate, renderParsedContent } from '../../utils/bookmarkUtils' import RichContent from '../RichContent' import { classifyUrl } from '../../utils/helpers' import { useImageCache } from '../../hooks/useImageCache' import { getPreviewImage, fetchOgImage } from '../../utils/imagePreview' import { getEventUrl } from '../../config/nostrGateways' interface CardViewProps { bookmark: IndividualBookmark index: number hasUrls: boolean extractedUrls: string[] onSelectUrl?: (url: string, bookmark?: { id: string; kind: number; tags: string[][]; pubkey: string }) => void authorNpub: string eventNevent?: string getAuthorDisplayName: () => string handleReadNow: (e: React.MouseEvent) => void articleImage?: string articleSummary?: string contentTypeIcon: IconDefinition readingProgress?: number } export const CardView: React.FC = ({ bookmark, index, hasUrls, extractedUrls, onSelectUrl, authorNpub, eventNevent, getAuthorDisplayName, handleReadNow, articleImage, articleSummary, contentTypeIcon, readingProgress }) => { const firstUrl = hasUrls ? extractedUrls[0] : null const firstUrlClassificationType = firstUrl ? classifyUrl(firstUrl)?.type : null const instantPreview = firstUrl ? getPreviewImage(firstUrl, firstUrlClassificationType || '') : null const [ogImage, setOgImage] = useState(null) const [expanded, setExpanded] = useState(false) const [urlsExpanded, setUrlsExpanded] = useState(false) const contentLength = (bookmark.content || '').length const shouldTruncate = !expanded && contentLength > 210 const isArticle = bookmark.kind === 30023 // Calculate progress color (matching BlogPostCard logic) let progressColor = '#6366f1' // Default blue (reading) if (readingProgress && readingProgress >= 0.95) { progressColor = '#10b981' // Green (completed) } else if (readingProgress && readingProgress > 0 && readingProgress <= 0.10) { progressColor = 'var(--color-text)' // Neutral text color (started) } // Determine which image to use (article image, instant preview, or OG image) const previewImage = articleImage || instantPreview || ogImage const cachedImage = useImageCache(previewImage || undefined) // Fetch OG image if we don't have any other image React.useEffect(() => { if (firstUrl && !articleImage && !instantPreview && !ogImage) { fetchOgImage(firstUrl).then(setOgImage) } }, [firstUrl, articleImage, instantPreview, ogImage]) const triggerOpen = () => handleReadNow({ preventDefault: () => {} } as React.MouseEvent) const handleKeyDown: React.KeyboardEventHandler = (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault() triggerOpen() } } return (
{cachedImage && (
handleReadNow({ preventDefault: () => {} } as React.MouseEvent)} /> )}
{eventNevent ? ( e.stopPropagation()} > {formatDate(bookmark.created_at ?? bookmark.listUpdatedAt)} ) : ( {formatDate(bookmark.created_at ?? bookmark.listUpdatedAt)} )}
{extractedUrls.length > 0 && (
{(urlsExpanded ? extractedUrls : extractedUrls.slice(0, 1)).map((url, urlIndex) => { return ( ) })} {extractedUrls.length > 1 && ( )}
)} {isArticle && articleSummary ? ( ) : bookmark.parsedContent ? (
{shouldTruncate && bookmark.content ? : renderParsedContent(bookmark.parsedContent)}
) : bookmark.content && ( )} {contentLength > 210 && ( )} {/* Reading progress indicator for articles */} {isArticle && readingProgress !== undefined && readingProgress > 0 && (
)}
e.stopPropagation()} > {getAuthorDisplayName()}
{/* CTA removed */}
) }