feat: add support for publishing highlights
This commit is contained in:
parent
c4881e3435
commit
079a2f90ef
29 changed files with 578 additions and 171 deletions
|
|
@ -15,7 +15,7 @@ import { cn } from '@/lib/utils'
|
|||
import mediaUpload from '@/services/media-upload.service'
|
||||
import { TImetaInfo } from '@/types'
|
||||
import { Event } from 'nostr-tools'
|
||||
import { useMemo } from 'react'
|
||||
import { useMemo, useRef, useState } from 'react'
|
||||
import {
|
||||
EmbeddedHashtag,
|
||||
EmbeddedLNInvoice,
|
||||
|
|
@ -25,8 +25,10 @@ import {
|
|||
} from '../Embedded'
|
||||
import Emoji from '../Emoji'
|
||||
import ExternalLink from '../ExternalLink'
|
||||
import HighlightButton from '../HighlightButton'
|
||||
import ImageGallery from '../ImageGallery'
|
||||
import MediaPlayer from '../MediaPlayer'
|
||||
import PostEditor from '../PostEditor'
|
||||
import WebPreview from '../WebPreview'
|
||||
import XEmbeddedPost from '../XEmbeddedPost'
|
||||
import YoutubeEmbeddedPlayer from '../YoutubeEmbeddedPlayer'
|
||||
|
|
@ -35,13 +37,18 @@ export default function Content({
|
|||
event,
|
||||
content,
|
||||
className,
|
||||
mustLoadMedia
|
||||
mustLoadMedia,
|
||||
enableHighlight = false
|
||||
}: {
|
||||
event?: Event
|
||||
content?: string
|
||||
className?: string
|
||||
mustLoadMedia?: boolean
|
||||
enableHighlight?: boolean
|
||||
}) {
|
||||
const contentRef = useRef<HTMLDivElement>(null)
|
||||
const [showHighlightEditor, setShowHighlightEditor] = useState(false)
|
||||
const [selectedText, setSelectedText] = useState('')
|
||||
const translatedEvent = useTranslatedEvent(event?.id)
|
||||
const { nodes, allImages, lastNormalUrl, emojiInfos } = useMemo(() => {
|
||||
const _content = translatedEvent?.content ?? event?.content ?? content
|
||||
|
|
@ -95,81 +102,99 @@ export default function Content({
|
|||
return null
|
||||
}
|
||||
|
||||
const handleHighlight = (text: string) => {
|
||||
setSelectedText(text)
|
||||
setShowHighlightEditor(true)
|
||||
}
|
||||
|
||||
let imageIndex = 0
|
||||
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}
|
||||
start={start}
|
||||
end={end}
|
||||
mustLoad={mustLoadMedia}
|
||||
/>
|
||||
)
|
||||
}
|
||||
if (node.type === 'media') {
|
||||
return (
|
||||
<MediaPlayer className="mt-2" key={index} src={node.data} mustLoad={mustLoadMedia} />
|
||||
)
|
||||
}
|
||||
if (node.type === 'url') {
|
||||
return <ExternalLink url={node.data} key={index} />
|
||||
}
|
||||
if (node.type === 'invoice') {
|
||||
return <EmbeddedLNInvoice invoice={node.data} key={index} className="mt-2" />
|
||||
}
|
||||
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 classNames={{ img: 'mb-1' }} emoji={emoji} key={index} />
|
||||
}
|
||||
if (node.type === 'youtube') {
|
||||
return (
|
||||
<YoutubeEmbeddedPlayer
|
||||
key={index}
|
||||
url={node.data}
|
||||
className="mt-2"
|
||||
mustLoad={mustLoadMedia}
|
||||
/>
|
||||
)
|
||||
}
|
||||
if (node.type === 'x-post') {
|
||||
return (
|
||||
<XEmbeddedPost
|
||||
key={index}
|
||||
url={node.data}
|
||||
className="mt-2"
|
||||
mustLoad={mustLoadMedia}
|
||||
/>
|
||||
)
|
||||
}
|
||||
return null
|
||||
})}
|
||||
{lastNormalUrl && <WebPreview className="mt-2" url={lastNormalUrl} />}
|
||||
</div>
|
||||
<>
|
||||
<div ref={contentRef} 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}
|
||||
start={start}
|
||||
end={end}
|
||||
mustLoad={mustLoadMedia}
|
||||
/>
|
||||
)
|
||||
}
|
||||
if (node.type === 'media') {
|
||||
return (
|
||||
<MediaPlayer className="mt-2" key={index} src={node.data} mustLoad={mustLoadMedia} />
|
||||
)
|
||||
}
|
||||
if (node.type === 'url') {
|
||||
return <ExternalLink url={node.data} key={index} />
|
||||
}
|
||||
if (node.type === 'invoice') {
|
||||
return <EmbeddedLNInvoice invoice={node.data} key={index} className="mt-2" />
|
||||
}
|
||||
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 classNames={{ img: 'mb-1' }} emoji={emoji} key={index} />
|
||||
}
|
||||
if (node.type === 'youtube') {
|
||||
return (
|
||||
<YoutubeEmbeddedPlayer
|
||||
key={index}
|
||||
url={node.data}
|
||||
className="mt-2"
|
||||
mustLoad={mustLoadMedia}
|
||||
/>
|
||||
)
|
||||
}
|
||||
if (node.type === 'x-post') {
|
||||
return (
|
||||
<XEmbeddedPost
|
||||
key={index}
|
||||
url={node.data}
|
||||
className="mt-2"
|
||||
mustLoad={mustLoadMedia}
|
||||
/>
|
||||
)
|
||||
}
|
||||
return null
|
||||
})}
|
||||
{lastNormalUrl && <WebPreview className="mt-2" url={lastNormalUrl} />}
|
||||
</div>
|
||||
{enableHighlight && (
|
||||
<HighlightButton onHighlight={handleHighlight} containerRef={contentRef} />
|
||||
)}
|
||||
{enableHighlight && (
|
||||
<PostEditor
|
||||
highlightedText={selectedText}
|
||||
parentStuff={event}
|
||||
open={showHighlightEditor}
|
||||
setOpen={setShowHighlightEditor}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue