You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

141 lines
3.5 KiB

import { isNsfwEvent, isPictureEvent } from '@/lib/event'
import { extractImetaUrlFromTag } from '@/lib/tag'
import { isImage, isVideo } from '@/lib/url'
import { cn } from '@/lib/utils'
import { Event } from 'nostr-tools'
import { memo } from 'react'
import {
embedded,
embeddedHashtagRenderer,
embeddedNormalUrlRenderer,
embeddedNostrNpubRenderer,
embeddedNostrProfileRenderer,
EmbeddedNote,
embeddedWebsocketUrlRenderer
} from '../Embedded'
import ImageGallery from '../ImageGallery'
import VideoPlayer from '../VideoPlayer'
import WebPreview from '../WebPreview'
import { URL_REGEX } from '@/constants'
const Content = memo(
({
event,
className,
size = 'normal'
}: {
event: Event
className?: string
size?: 'normal' | 'small'
}) => {
const { content, images, videos, embeddedNotes, lastNonMediaUrl } = preprocess(event)
const isNsfw = isNsfwEvent(event)
const nodes = embedded(content, [
embeddedNormalUrlRenderer,
embeddedWebsocketUrlRenderer,
embeddedHashtagRenderer,
embeddedNostrNpubRenderer,
embeddedNostrProfileRenderer
])
// Add images
if (images.length) {
nodes.push(
<ImageGallery
className={`${size === 'small' ? 'mt-1' : 'mt-2'}`}
key={`image-gallery-${event.id}`}
images={images}
isNsfw={isNsfw}
size={size}
/>
)
}
// Add videos
if (videos.length) {
videos.forEach((src, index) => {
nodes.push(
<VideoPlayer
className={size === 'small' ? 'mt-1' : 'mt-2'}
key={`video-${index}-${src}`}
src={src}
isNsfw={isNsfw}
size={size}
/>
)
})
}
// Add website preview
if (lastNonMediaUrl) {
nodes.push(
<WebPreview
className={size === 'small' ? 'mt-1' : 'mt-2'}
key={`web-preview-${event.id}`}
url={lastNonMediaUrl}
size={size}
/>
)
}
// Add embedded notes
if (embeddedNotes.length) {
embeddedNotes.forEach((note, index) => {
const id = note.split(':')[1]
nodes.push(
<EmbeddedNote
key={`embedded-event-${index}`}
noteId={id}
className={size === 'small' ? 'mt-1' : 'mt-2'}
/>
)
})
}
return <div className={cn('text-wrap break-words whitespace-pre-wrap', className)}>{nodes}</div>
}
)
Content.displayName = 'Content'
export default Content
function preprocess(event: Event) {
const content = event.content
const urls = content.match(URL_REGEX) || []
let lastNonMediaUrl: string | undefined
let c = content
const images: string[] = []
const videos: string[] = []
urls.forEach((url) => {
if (isImage(url)) {
c = c.replace(url, '').trim()
images.push(url)
} else if (isVideo(url)) {
c = c.replace(url, '').trim()
videos.push(url)
} else {
lastNonMediaUrl = url
}
})
if (isPictureEvent(event)) {
event.tags.forEach((tag) => {
const imageUrl = extractImetaUrlFromTag(tag)
if (imageUrl) {
images.push(imageUrl)
}
})
}
const embeddedNotes: string[] = []
const embeddedNoteRegex = /nostr:(note1[a-z0-9]{58}|nevent1[a-z0-9]+|naddr1[a-z0-9]+)/g
;(c.match(embeddedNoteRegex) || []).forEach((note) => {
c = c.replace(note, '').trim()
embeddedNotes.push(note)
})
c = c.replace(/\n{3,}/g, '\n\n').trim()
return { content: c, images, videos, embeddedNotes, lastNonMediaUrl }
}