diff --git a/src/components/BookmarkViews/CardView.tsx b/src/components/BookmarkViews/CardView.tsx index 09695a72..438e9ecc 100644 --- a/src/components/BookmarkViews/CardView.tsx +++ b/src/components/BookmarkViews/CardView.tsx @@ -5,7 +5,7 @@ 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 ContentWithResolvedProfiles from '../ContentWithResolvedProfiles' +import RichContent from '../RichContent' import { classifyUrl } from '../../utils/helpers' import { useImageCache } from '../../hooks/useImageCache' import { getPreviewImage, fetchOgImage } from '../../utils/imagePreview' @@ -147,19 +147,15 @@ export const CardView: React.FC = ({ )} {isArticle && articleSummary ? ( -
- -
+ ) : bookmark.parsedContent ? (
{shouldTruncate && bookmark.content - ? + ? : renderParsedContent(bookmark.parsedContent)}
) : bookmark.content && ( -
- -
+ )} {contentLength > 210 && ( diff --git a/src/components/BookmarkViews/CompactView.tsx b/src/components/BookmarkViews/CompactView.tsx index 74c78a4f..6be3d76f 100644 --- a/src/components/BookmarkViews/CompactView.tsx +++ b/src/components/BookmarkViews/CompactView.tsx @@ -3,7 +3,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { IconDefinition } from '@fortawesome/fontawesome-svg-core' import { IndividualBookmark } from '../../types/bookmarks' import { formatDateCompact } from '../../utils/bookmarkUtils' -import ContentWithResolvedProfiles from '../ContentWithResolvedProfiles' +import RichContent from '../RichContent' interface CompactViewProps { bookmark: IndividualBookmark @@ -66,7 +66,7 @@ export const CompactView: React.FC = ({ {displayText && (
- 60 ? '…' : '')} /> + 60 ? '…' : '')} className="" />
)} {formatDateCompact(bookmark.created_at)} diff --git a/src/components/BookmarkViews/LargeView.tsx b/src/components/BookmarkViews/LargeView.tsx index 51158833..9f0d1571 100644 --- a/src/components/BookmarkViews/LargeView.tsx +++ b/src/components/BookmarkViews/LargeView.tsx @@ -4,7 +4,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { IconDefinition } from '@fortawesome/fontawesome-svg-core' import { IndividualBookmark } from '../../types/bookmarks' import { formatDate } from '../../utils/bookmarkUtils' -import ContentWithResolvedProfiles from '../ContentWithResolvedProfiles' +import RichContent from '../RichContent' import { IconGetter } from './shared' import { useImageCache } from '../../hooks/useImageCache' import { getEventUrl } from '../../config/nostrGateways' @@ -95,13 +95,9 @@ export const LargeView: React.FC = ({
{isArticle && articleSummary ? ( -
- -
+ ) : bookmark.content && ( -
- -
+ )} {/* Reading progress indicator for articles - shown only if there's progress */} diff --git a/src/components/RichContent.tsx b/src/components/RichContent.tsx new file mode 100644 index 00000000..7bd55d12 --- /dev/null +++ b/src/components/RichContent.tsx @@ -0,0 +1,77 @@ +import React from 'react' +import NostrMentionLink from './NostrMentionLink' + +interface RichContentProps { + content: string + className?: string +} + +/** + * Component to render text content with: + * - Clickable links + * - Resolved nostr mentions (npub, nprofile, note, nevent, naddr) + * - Plain text + * + * Handles both nostr:npub1... and plain npub1... formats + */ +const RichContent: React.FC = ({ + content, + className = 'bookmark-content' +}) => { + // Pattern to match: + // 1. nostr: URIs (nostr:npub1..., nostr:note1..., etc.) + // 2. Plain nostr identifiers (npub1..., nprofile1..., note1..., etc.) + // 3. http(s) URLs + const pattern = /(nostr:[a-z0-9]+|npub1[a-z0-9]+|nprofile1[a-z0-9]+|note1[a-z0-9]+|nevent1[a-z0-9]+|naddr1[a-z0-9]+|https?:\/\/[^\s]+)/gi + + const parts = content.split(pattern) + + return ( +
+ {parts.map((part, index) => { + // Handle nostr: URIs + if (part.startsWith('nostr:')) { + return ( + + ) + } + + // Handle plain nostr identifiers (add nostr: prefix) + if ( + part.match(/^(npub1|nprofile1|note1|nevent1|naddr1)[a-z0-9]+$/i) + ) { + return ( + + ) + } + + // Handle http(s) URLs + if (part.match(/^https?:\/\//)) { + return ( + + {part} + + ) + } + + // Plain text + return {part} + })} +
+ ) +} + +export default RichContent + diff --git a/src/utils/bookmarkUtils.tsx b/src/utils/bookmarkUtils.tsx index dc671a14..66a6f21d 100644 --- a/src/utils/bookmarkUtils.tsx +++ b/src/utils/bookmarkUtils.tsx @@ -2,7 +2,7 @@ import React from 'react' import { formatDistanceToNow, differenceInSeconds, differenceInMinutes, differenceInHours, differenceInDays, differenceInMonths, differenceInYears } from 'date-fns' import { ParsedContent, ParsedNode, IndividualBookmark } from '../types/bookmarks' import ResolvedMention from '../components/ResolvedMention' -// Note: ContentWithResolvedProfiles is imported by components directly to keep this file component-only for fast refresh +// Note: RichContent is imported by components directly to keep this file component-only for fast refresh export const formatDate = (timestamp: number) => { const date = new Date(timestamp * 1000)