135 lines
4 KiB
TypeScript
135 lines
4 KiB
TypeScript
import {
|
|
EmbeddedEmojiParser,
|
|
EmbeddedEventParser,
|
|
EmbeddedHashtagParser,
|
|
EmbeddedImageParser,
|
|
EmbeddedMentionParser,
|
|
EmbeddedNormalUrlParser,
|
|
EmbeddedVideoParser,
|
|
EmbeddedWebsocketUrlParser,
|
|
parseContent
|
|
} from '@/lib/content-parser'
|
|
import { extractEmojiInfosFromTags, isNsfwEvent } from '@/lib/event'
|
|
import { extractImageInfoFromTag } from '@/lib/tag'
|
|
import { cn } from '@/lib/utils'
|
|
import { TImageInfo } from '@/types'
|
|
import { Event } from 'nostr-tools'
|
|
import { memo } from 'react'
|
|
import {
|
|
EmbeddedHashtag,
|
|
EmbeddedMention,
|
|
EmbeddedNormalUrl,
|
|
EmbeddedNote,
|
|
EmbeddedWebsocketUrl
|
|
} from '../Embedded'
|
|
import Emoji from '../Emoji'
|
|
import ImageGallery from '../ImageGallery'
|
|
import VideoPlayer from '../VideoPlayer'
|
|
import WebPreview from '../WebPreview'
|
|
|
|
const Content = memo(
|
|
({
|
|
event,
|
|
className,
|
|
size = 'normal'
|
|
}: {
|
|
event: Event
|
|
className?: string
|
|
size?: 'normal' | 'small'
|
|
}) => {
|
|
const nodes = parseContent(event.content, [
|
|
EmbeddedImageParser,
|
|
EmbeddedVideoParser,
|
|
EmbeddedNormalUrlParser,
|
|
EmbeddedWebsocketUrlParser,
|
|
EmbeddedEventParser,
|
|
EmbeddedMentionParser,
|
|
EmbeddedHashtagParser,
|
|
EmbeddedEmojiParser
|
|
])
|
|
|
|
const imageInfos = event.tags
|
|
.map((tag) => extractImageInfoFromTag(tag))
|
|
.filter(Boolean) as TImageInfo[]
|
|
|
|
const emojiInfos = extractEmojiInfosFromTags(event.tags)
|
|
|
|
const lastNormalUrlNode = nodes.findLast((node) => node.type === 'url')
|
|
const lastNormalUrl =
|
|
typeof lastNormalUrlNode?.data === 'string' ? lastNormalUrlNode.data : undefined
|
|
|
|
return (
|
|
<div className={cn('text-wrap break-words whitespace-pre-wrap', className)}>
|
|
{nodes.map((node, index) => {
|
|
if (node.type === 'text') {
|
|
return node.data
|
|
}
|
|
if (node.type === 'image' || node.type === 'images') {
|
|
const imageUrls = Array.isArray(node.data) ? node.data : [node.data]
|
|
const images = imageUrls.map(
|
|
(url) => imageInfos.find((image) => image.url === url) ?? { url }
|
|
)
|
|
return (
|
|
<ImageGallery
|
|
className={`${size === 'small' ? 'mt-1' : 'mt-2'}`}
|
|
key={index}
|
|
images={images}
|
|
isNsfw={isNsfwEvent(event)}
|
|
size={size}
|
|
/>
|
|
)
|
|
}
|
|
if (node.type === 'video') {
|
|
return (
|
|
<VideoPlayer
|
|
className={size === 'small' ? 'mt-1' : 'mt-2'}
|
|
key={index}
|
|
src={node.data}
|
|
isNsfw={isNsfwEvent(event)}
|
|
size={size}
|
|
/>
|
|
)
|
|
}
|
|
if (node.type === 'url') {
|
|
return <EmbeddedNormalUrl url={node.data} key={index} />
|
|
}
|
|
if (node.type === 'websocket-url') {
|
|
return <EmbeddedWebsocketUrl url={node.data} key={index} />
|
|
}
|
|
if (node.type === 'event') {
|
|
const id = node.data.split(':')[1]
|
|
return (
|
|
<EmbeddedNote
|
|
key={index}
|
|
noteId={id}
|
|
className={size === 'small' ? 'mt-1' : 'mt-2'}
|
|
/>
|
|
)
|
|
}
|
|
if (node.type === 'mention') {
|
|
return <EmbeddedMention key={index} userId={node.data.split(':')[1]} />
|
|
}
|
|
if (node.type === 'hashtag') {
|
|
return <EmbeddedHashtag hashtag={node.data} key={index} />
|
|
}
|
|
if (node.type === 'emoji') {
|
|
const shortcode = node.data.split(':')[1]
|
|
const emoji = emojiInfos.find((e) => e.shortcode === shortcode)
|
|
if (!emoji) return node.data
|
|
return <Emoji emoji={emoji} key={index} className="size-4" />
|
|
}
|
|
return null
|
|
})}
|
|
{lastNormalUrl && (
|
|
<WebPreview
|
|
className={size === 'small' ? 'mt-1' : 'mt-2'}
|
|
url={lastNormalUrl}
|
|
size={size}
|
|
/>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
)
|
|
Content.displayName = 'Content'
|
|
export default Content
|