|
|
|
|
@ -27,128 +27,99 @@ import ImageGallery from '../ImageGallery'
@@ -27,128 +27,99 @@ 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 Content = memo(({ event, className }: { event: Event; className?: string }) => { |
|
|
|
|
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 allImages = nodes |
|
|
|
|
.map((node) => { |
|
|
|
|
if (node.type === 'image') { |
|
|
|
|
const imageInfo = imageInfos.find((image) => image.url === node.data) |
|
|
|
|
return imageInfo ?? { url: node.data } |
|
|
|
|
} |
|
|
|
|
if (node.type === 'images') { |
|
|
|
|
const urls = Array.isArray(node.data) ? node.data : [node.data] |
|
|
|
|
return urls.map((url) => { |
|
|
|
|
const imageInfo = imageInfos.find((image) => image.url === url) |
|
|
|
|
return imageInfo ?? { url } |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
return null |
|
|
|
|
}) |
|
|
|
|
.filter(Boolean) |
|
|
|
|
.flat() as TImageInfo[] |
|
|
|
|
let imageIndex = 0 |
|
|
|
|
const imageInfos = event.tags |
|
|
|
|
.map((tag) => extractImageInfoFromTag(tag)) |
|
|
|
|
.filter(Boolean) as TImageInfo[] |
|
|
|
|
const allImages = nodes |
|
|
|
|
.map((node) => { |
|
|
|
|
if (node.type === 'image') { |
|
|
|
|
const imageInfo = imageInfos.find((image) => image.url === node.data) |
|
|
|
|
return imageInfo ?? { url: node.data } |
|
|
|
|
} |
|
|
|
|
if (node.type === 'images') { |
|
|
|
|
const urls = Array.isArray(node.data) ? node.data : [node.data] |
|
|
|
|
return urls.map((url) => { |
|
|
|
|
const imageInfo = imageInfos.find((image) => image.url === url) |
|
|
|
|
return imageInfo ?? { url } |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
return null |
|
|
|
|
}) |
|
|
|
|
.filter(Boolean) |
|
|
|
|
.flat() as TImageInfo[] |
|
|
|
|
let imageIndex = 0 |
|
|
|
|
|
|
|
|
|
const emojiInfos = extractEmojiInfosFromTags(event.tags) |
|
|
|
|
const emojiInfos = extractEmojiInfosFromTags(event.tags) |
|
|
|
|
|
|
|
|
|
const lastNormalUrlNode = nodes.findLast((node) => node.type === 'url') |
|
|
|
|
const lastNormalUrl = |
|
|
|
|
typeof lastNormalUrlNode?.data === 'string' ? lastNormalUrlNode.data : undefined |
|
|
|
|
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 start = imageIndex |
|
|
|
|
const end = imageIndex + (Array.isArray(node.data) ? node.data.length : 1) |
|
|
|
|
imageIndex = end |
|
|
|
|
return ( |
|
|
|
|
<ImageGallery |
|
|
|
|
className={`${size === 'small' ? 'mt-1' : 'mt-2'}`} |
|
|
|
|
key={index} |
|
|
|
|
images={allImages} |
|
|
|
|
isNsfw={isNsfwEvent(event)} |
|
|
|
|
size={size} |
|
|
|
|
start={start} |
|
|
|
|
end={end} |
|
|
|
|
/> |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
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> |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
) |
|
|
|
|
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 start = imageIndex |
|
|
|
|
const end = imageIndex + (Array.isArray(node.data) ? node.data.length : 1) |
|
|
|
|
imageIndex = end |
|
|
|
|
return ( |
|
|
|
|
<ImageGallery |
|
|
|
|
className="mt-2" |
|
|
|
|
key={index} |
|
|
|
|
images={allImages} |
|
|
|
|
isNsfw={isNsfwEvent(event)} |
|
|
|
|
start={start} |
|
|
|
|
end={end} |
|
|
|
|
/> |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
if (node.type === 'video') { |
|
|
|
|
return ( |
|
|
|
|
<VideoPlayer className="mt-2" key={index} src={node.data} isNsfw={isNsfwEvent(event)} /> |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
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="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="mt-2" url={lastNormalUrl} />} |
|
|
|
|
</div> |
|
|
|
|
) |
|
|
|
|
}) |
|
|
|
|
Content.displayName = 'Content' |
|
|
|
|
export default Content |
|
|
|
|
|