336 lines
14 KiB
TypeScript
336 lines
14 KiB
TypeScript
import { formatDate } from 'date-fns'
|
|
import { useBodyScrollDisable, useNDKContext, useReplies } from 'hooks'
|
|
import { nip19 } from 'nostr-tools'
|
|
import { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react'
|
|
import {
|
|
Link,
|
|
useLoaderData,
|
|
useLocation,
|
|
useNavigate,
|
|
useParams
|
|
} from 'react-router-dom'
|
|
import { getBlogPageRoute, getModPageRoute, getProfilePageRoute } from 'routes'
|
|
import { CommentEvent, UserProfile } from 'types'
|
|
import { CommentsLoaderResult } from 'types/comments'
|
|
import { adjustTextareaHeight, handleCommentSubmit, hexToNpub } from 'utils'
|
|
import { Reactions } from './Reactions'
|
|
import { Zap } from './Zap'
|
|
import { NDKKind } from '@nostr-dev-kit/ndk'
|
|
import { Comment } from './Comment'
|
|
import { useComments } from 'hooks/useComments'
|
|
import { CommentContent } from './CommentContent'
|
|
import { Dots } from 'components/Spinner'
|
|
|
|
export const CommentsPopup = () => {
|
|
const { naddr } = useParams()
|
|
const location = useLocation()
|
|
const { ndk } = useNDKContext()
|
|
useBodyScrollDisable(true)
|
|
const isMod = location.pathname.includes('/mod/')
|
|
const isBlog = location.pathname.includes('/blog/')
|
|
const baseUrl = naddr
|
|
? isMod
|
|
? getModPageRoute(naddr)
|
|
: isBlog
|
|
? getBlogPageRoute(naddr)
|
|
: undefined
|
|
: undefined
|
|
|
|
const { event } = useLoaderData() as CommentsLoaderResult
|
|
const {
|
|
size,
|
|
parent: replyEvent,
|
|
isComplete,
|
|
root: rootEvent
|
|
} = useReplies(event.tagValue('e'))
|
|
const isRoot = event.tagValue('a') === event.tagValue('A')
|
|
const [profile, setProfile] = useState<UserProfile>()
|
|
const { commentEvents, setCommentEvents } = useComments(
|
|
event.author.pubkey,
|
|
undefined,
|
|
event.id
|
|
)
|
|
useEffect(() => {
|
|
event.author.fetchProfile().then((res) => setProfile(res))
|
|
}, [event.author])
|
|
const profileRoute = useMemo(
|
|
() =>
|
|
getProfilePageRoute(
|
|
nip19.nprofileEncode({
|
|
pubkey: event.pubkey
|
|
})
|
|
),
|
|
[event.pubkey]
|
|
)
|
|
|
|
const navigate = useNavigate()
|
|
|
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
|
const [replyText, setReplyText] = useState('')
|
|
|
|
const handleChange = useCallback((e: ChangeEvent<HTMLTextAreaElement>) => {
|
|
const value = e.currentTarget.value
|
|
setReplyText(value)
|
|
adjustTextareaHeight(e.currentTarget)
|
|
}, [])
|
|
|
|
const [visible, setVisible] = useState<CommentEvent[]>([])
|
|
const discoveredCount = commentEvents.length - visible.length
|
|
const [isLoading, setIsLoading] = useState(true)
|
|
useEffect(() => {
|
|
// Initial loading to indicate comments fetching (stop after 5 seconds)
|
|
const t = window.setTimeout(() => setIsLoading(false), 5000)
|
|
return () => {
|
|
window.clearTimeout(t)
|
|
}
|
|
}, [])
|
|
useEffect(() => {
|
|
if (isLoading) {
|
|
setVisible(commentEvents)
|
|
}
|
|
}, [commentEvents, isLoading])
|
|
const handleDiscoveredClick = () => {
|
|
setVisible(commentEvents)
|
|
}
|
|
const handleSubmit = handleCommentSubmit(
|
|
event,
|
|
setCommentEvents,
|
|
setVisible,
|
|
ndk
|
|
)
|
|
|
|
const handleComment = async () => {
|
|
setIsSubmitting(true)
|
|
const submitted = await handleSubmit(replyText)
|
|
if (submitted) setReplyText('')
|
|
setIsSubmitting(false)
|
|
}
|
|
|
|
return (
|
|
<div className='popUpMain'>
|
|
<div className='ContainerMain'>
|
|
<div className='popUpMainCardWrapper'>
|
|
<div className='popUpMainCard'>
|
|
<div className='popUpMainCardTop'>
|
|
<div className='popUpMainCardTopInfo'>
|
|
<h3>Comment replies</h3>
|
|
</div>
|
|
|
|
<div
|
|
className='popUpMainCardTopClose'
|
|
onClick={() => navigate('..')}
|
|
>
|
|
<svg
|
|
xmlns='http://www.w3.org/2000/svg'
|
|
viewBox='-96 0 512 512'
|
|
width='1em'
|
|
height='1em'
|
|
fill='currentColor'
|
|
style={{ zIndex: 1 }}
|
|
>
|
|
<path d='M310.6 361.4c12.5 12.5 12.5 32.75 0 45.25C304.4 412.9 296.2 416 288 416s-16.38-3.125-22.62-9.375L160 301.3L54.63 406.6C48.38 412.9 40.19 416 32 416S15.63 412.9 9.375 406.6c-12.5-12.5-12.5-32.75 0-45.25l105.4-105.4L9.375 150.6c-12.5-12.5-12.5-32.75 0-45.25s32.75-12.5 45.25 0L160 210.8l105.4-105.4c12.5-12.5 32.75-12.5 45.25 0s12.5 32.75 0 45.25l-105.4 105.4L310.6 361.4z'></path>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div className='popUpMainCardBottom'>
|
|
<div className='pUMCB_PrimeComment'>
|
|
<div className='IBMSMSMBSSCL_Comment'>
|
|
<div className='IBMSMSMBSSCL_CommentTopOther'>
|
|
<div className='IBMSMSMBSSCL_CTO'>
|
|
{replyEvent && (
|
|
<Link
|
|
style={{
|
|
...(!isComplete ? { pointerEvents: 'none' } : {})
|
|
}}
|
|
className='IBMSMSMBSSCL_CTOLink'
|
|
to={baseUrl + replyEvent.encode()}
|
|
>
|
|
<svg
|
|
xmlns='http://www.w3.org/2000/svg'
|
|
viewBox='-32 0 512 512'
|
|
width='1em'
|
|
height='1em'
|
|
fill='currentColor'
|
|
className='IBMSMSMBSSCL_CTOLinkIcon'
|
|
>
|
|
<path d='M447.1 256C447.1 273.7 433.7 288 416 288H109.3l105.4 105.4c12.5 12.5 12.5 32.75 0 45.25C208.4 444.9 200.2 448 192 448s-16.38-3.125-22.62-9.375l-160-160c-12.5-12.5-12.5-32.75 0-45.25l160-160c12.5-12.5 32.75-12.5 45.25 0s12.5 32.75 0 45.25L109.3 224H416C433.7 224 447.1 238.3 447.1 256z'></path>
|
|
</svg>
|
|
</Link>
|
|
)}
|
|
<p className='IBMSMSMBSSCL_CTOText'>
|
|
Reply Depth: <span>{size}</span>
|
|
{!isComplete && <Dots />}
|
|
</p>
|
|
</div>
|
|
{!isRoot && rootEvent && (
|
|
<Link
|
|
style={{
|
|
...(!isComplete ? { pointerEvents: 'none' } : {})
|
|
}}
|
|
className='btn btnMain IBMSMSMBSSCL_CTOBtn'
|
|
type='button'
|
|
to={baseUrl + rootEvent.encode()}
|
|
>
|
|
Main Post {!isComplete && <Dots />}
|
|
</Link>
|
|
)}
|
|
</div>
|
|
<div className='IBMSMSMBSSCL_CommentTop'>
|
|
<div className='IBMSMSMBSSCL_CommentTopPPWrapper'>
|
|
<Link
|
|
className='IBMSMSMBSSCL_CommentTopPP'
|
|
to={profileRoute}
|
|
style={{
|
|
background: `url('${
|
|
profile?.image || ''
|
|
}') center / cover no-repeat`
|
|
}}
|
|
/>
|
|
</div>
|
|
<div className='IBMSMSMBSSCL_CommentTopDetailsWrapper'>
|
|
<div className='IBMSMSMBSSCL_CommentTopDetails'>
|
|
<Link
|
|
className='IBMSMSMBSSCL_CTD_Name'
|
|
to={profileRoute}
|
|
>
|
|
{profile?.displayName || profile?.name || ''}{' '}
|
|
</Link>
|
|
<Link
|
|
className='IBMSMSMBSSCL_CTD_Address'
|
|
to={profileRoute}
|
|
>
|
|
{hexToNpub(event.pubkey)}
|
|
</Link>
|
|
</div>
|
|
{event.created_at && (
|
|
<div className='IBMSMSMBSSCL_CommentActionsDetails'>
|
|
<a className='IBMSMSMBSSCL_CADTime'>
|
|
{formatDate(event.created_at * 1000, 'hh:mm aa')}{' '}
|
|
</a>
|
|
<a className='IBMSMSMBSSCL_CADDate'>
|
|
{formatDate(event.created_at * 1000, 'dd/MM/yyyy')}
|
|
</a>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className='IBMSMSMBSSCL_CommentBottom'>
|
|
<CommentContent content={event.content} />
|
|
</div>
|
|
<div className='IBMSMSMBSSCL_CommentActions'>
|
|
<div className='IBMSMSMBSSCL_CommentActionsInside'>
|
|
<Reactions {...event.rawEvent()} />
|
|
|
|
{/* <div
|
|
className='IBMSMSMBSSCL_CAElement IBMSMSMBSSCL_CAERepost'
|
|
style={{ cursor: 'not-allowed' }}
|
|
>
|
|
<svg
|
|
xmlns='http://www.w3.org/2000/svg'
|
|
viewBox='0 -64 640 640'
|
|
width='1em'
|
|
height='1em'
|
|
fill='currentColor'
|
|
className='IBMSMSMBSSCL_CAElementIcon'
|
|
>
|
|
<path d='M614.2 334.8C610.5 325.8 601.7 319.1 592 319.1H544V176C544 131.9 508.1 96 464 96h-128c-17.67 0-32 14.31-32 32s14.33 32 32 32h128C472.8 160 480 167.2 480 176v143.1h-48c-9.703 0-18.45 5.844-22.17 14.82s-1.656 19.29 5.203 26.16l80 80.02C499.7 445.7 505.9 448 512 448s12.28-2.344 16.97-7.031l80-80.02C615.8 354.1 617.9 343.8 614.2 334.8zM304 352h-128C167.2 352 160 344.8 160 336V192h48c9.703 0 18.45-5.844 22.17-14.82s1.656-19.29-5.203-26.16l-80-80.02C140.3 66.34 134.1 64 128 64S115.7 66.34 111 71.03l-80 80.02C24.17 157.9 22.11 168.2 25.83 177.2S38.3 192 48 192H96V336C96 380.1 131.9 416 176 416h128c17.67 0 32-14.31 32-32S321.7 352 304 352z'></path>
|
|
</svg>
|
|
<p className='IBMSMSMBSSCL_CAElementText'>0</p>
|
|
<div className='IBMSMSMBSSCL_CAElementLoadWrapper'>
|
|
<div className='IBMSMSMBSSCL_CAElementLoad'></div>
|
|
</div>
|
|
</div> */}
|
|
|
|
{typeof profile?.lud16 !== 'undefined' &&
|
|
profile.lud16 !== '' && <Zap {...event.rawEvent()} />}
|
|
|
|
{event.kind === NDKKind.GenericReply && (
|
|
<>
|
|
<span className='IBMSMSMBSSCL_CAElement IBMSMSMBSSCL_CAEReplies'>
|
|
<svg
|
|
xmlns='http://www.w3.org/2000/svg'
|
|
viewBox='0 0 512 512'
|
|
width='1em'
|
|
height='1em'
|
|
fill='currentColor'
|
|
className='IBMSMSMBSSCL_CAElementIcon'
|
|
>
|
|
<path d='M256 32C114.6 32 .0272 125.1 .0272 240c0 49.63 21.35 94.98 56.97 130.7c-12.5 50.37-54.27 95.27-54.77 95.77c-2.25 2.25-2.875 5.734-1.5 8.734C1.979 478.2 4.75 480 8 480c66.25 0 115.1-31.76 140.6-51.39C181.2 440.9 217.6 448 256 448c141.4 0 255.1-93.13 255.1-208S397.4 32 256 32z'></path>
|
|
</svg>
|
|
<p className='IBMSMSMBSSCL_CAElementText'>
|
|
{commentEvents.length}
|
|
</p>
|
|
<p className='IBMSMSMBSSCL_CAElementText'>
|
|
Replies
|
|
</p>
|
|
</span>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className='pUMCB_CommentToPrime'>
|
|
<div className='IBMSMSMBSSCC_Top'>
|
|
<textarea
|
|
className='IBMSMSMBSSCC_Top_Box postSocialTextarea'
|
|
placeholder='Got something to say?'
|
|
value={replyText}
|
|
onChange={handleChange}
|
|
style={{ height: '0px' }}
|
|
></textarea>
|
|
</div>
|
|
<div className='IBMSMSMBSSCC_Bottom'>
|
|
{/* <a className='IBMSMSMBSSCC_BottomButton'>Quote-Repost</a> */}
|
|
<button
|
|
onClick={handleComment}
|
|
disabled={isSubmitting}
|
|
className='IBMSMSMBSSCC_BottomButton'
|
|
>
|
|
{isSubmitting ? 'Replying...' : 'Reply'}
|
|
<div className='IBMSMSMBSSCL_CAElementLoadWrapper'>
|
|
<div className='IBMSMSMBSSCL_CAElementLoad'></div>
|
|
</div>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
{commentEvents.length > 0 && (
|
|
<>
|
|
<h3 className='IBMSMSMBSSCL_CommentNoteRepliesTitle'>
|
|
Replies
|
|
<button
|
|
type='button'
|
|
className='btnMain IBMSMSMBSSCL_CommentNoteRepliesTitleBtn'
|
|
onClick={
|
|
discoveredCount ? handleDiscoveredClick : undefined
|
|
}
|
|
>
|
|
<span>
|
|
{isLoading ? (
|
|
<>
|
|
Discovering replies
|
|
<Dots />
|
|
</>
|
|
) : discoveredCount ? (
|
|
<>Load {discoveredCount} discovered replies</>
|
|
) : (
|
|
<>No new replies</>
|
|
)}
|
|
</span>
|
|
</button>
|
|
</h3>
|
|
<div className='pUMCB_RepliesToPrime'>
|
|
{commentEvents.map((reply) => (
|
|
<Comment key={reply.event.id} comment={reply} />
|
|
))}
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|