feat: basic browsing (#1)

This commit is contained in:
Cody Tseng 2024-10-31 17:53:03 +08:00 committed by GitHub
parent 824e2ea9d5
commit 9b0251240c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
104 changed files with 5624 additions and 122 deletions

View file

@ -0,0 +1,125 @@
import {
embedded,
embeddedHashtagRenderer,
embeddedNormalUrlRenderer,
embeddedNostrNpubRenderer,
embeddedNostrProfileRenderer
} from '@renderer/embedded'
import { isNsfwEvent } from '@renderer/lib/event'
import { cn } from '@renderer/lib/utils'
import { Event } from 'nostr-tools'
import { memo } from 'react'
import { EmbeddedNote } from '../Embedded'
import ImageGallery from '../ImageGallery'
import VideoPlayer from '../VideoPlayer'
const Content = memo(
({
event,
className,
size = 'normal'
}: {
event: Event
className?: string
size?: 'normal' | 'small'
}) => {
const { content, images, videos, embeddedNotes } = preprocess(event.content)
const isNsfw = isNsfwEvent(event)
const nodes = embedded(content, [
embeddedNormalUrlRenderer,
embeddedHashtagRenderer,
embeddedNostrNpubRenderer,
embeddedNostrProfileRenderer
])
// Add images
if (images.length) {
nodes.push(
<ImageGallery
className="mt-2 w-fit"
key="images"
images={images}
isNsfw={isNsfw}
size={size}
/>
)
}
// Add videos
if (videos.length) {
videos.forEach((src, index) => {
nodes.push(
<VideoPlayer
className="mt-2"
key={`video-${index}`}
src={src}
isNsfw={isNsfw}
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} />)
})
}
return (
<div className={cn('text-sm text-wrap break-words whitespace-pre-wrap', className)}>
{nodes}
</div>
)
}
)
Content.displayName = 'Content'
export default Content
function preprocess(content: string) {
const urlRegex = /(https?:\/\/[^\s"']+)/g
const urls = content.match(urlRegex) || []
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)
}
})
const embeddedNotes: string[] = []
const embeddedNoteRegex = /(nostr:note1[a-z0-9]{58}|nostr:nevent1[a-z0-9]+)/g
;(c.match(embeddedNoteRegex) || []).forEach((note) => {
c = c.replace(note, '').trim()
embeddedNotes.push(note)
})
return { content: c, images, videos, embeddedNotes }
}
function isImage(url: string) {
try {
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', 'webp', 'heic', 'svg']
return imageExtensions.some((ext) => new URL(url).pathname.toLowerCase().endsWith(ext))
} catch {
return false
}
}
function isVideo(url: string) {
try {
const videoExtensions = ['.mp4', '.webm', '.ogg']
return videoExtensions.some((ext) => new URL(url).pathname.toLowerCase().endsWith(ext))
} catch {
return false
}
}