feat(reply): publish new reply with ndkevent, fetch kind 1 and 1111
This commit is contained in:
parent
612524741b
commit
97b44a55f2
@ -1,4 +1,4 @@
|
||||
import { NDKEvent } from '@nostr-dev-kit/ndk'
|
||||
import { NDKEvent, NDKKind, NostrEvent } from '@nostr-dev-kit/ndk'
|
||||
import { Dots, Spinner } from 'components/Spinner'
|
||||
import { ZapPopUp } from 'components/Zap'
|
||||
import { formatDate } from 'date-fns'
|
||||
@ -10,7 +10,7 @@ import {
|
||||
useReactions
|
||||
} from 'hooks'
|
||||
import { useComments } from 'hooks/useComments'
|
||||
import { Event, kinds, nip19, UnsignedEvent } from 'nostr-tools'
|
||||
import { nip19 } from 'nostr-tools'
|
||||
import React, {
|
||||
Dispatch,
|
||||
SetStateAction,
|
||||
@ -18,16 +18,18 @@ import React, {
|
||||
useMemo,
|
||||
useState
|
||||
} from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Link, useLoaderData } from 'react-router-dom'
|
||||
import { toast } from 'react-toastify'
|
||||
import { getProfilePageRoute } from 'routes'
|
||||
import {
|
||||
Addressable,
|
||||
BlogPageLoaderResult,
|
||||
CommentEvent,
|
||||
CommentEventStatus,
|
||||
ModPageLoaderResult,
|
||||
UserProfile
|
||||
} from 'types/index.ts'
|
||||
import { abbreviateNumber, hexToNpub, log, LogType, now } from 'utils'
|
||||
import { abbreviateNumber, hexToNpub, log, LogType } from 'utils'
|
||||
|
||||
enum SortByEnum {
|
||||
Latest = 'Latest',
|
||||
@ -50,11 +52,14 @@ type Props = {
|
||||
}
|
||||
|
||||
export const Comments = ({ addressable, setCommentCount }: Props) => {
|
||||
const { ndk, publish } = useNDKContext()
|
||||
const { ndk } = useNDKContext()
|
||||
const { commentEvents, setCommentEvents } = useComments(
|
||||
addressable.author,
|
||||
addressable.aTag
|
||||
)
|
||||
const { event } = useLoaderData() as
|
||||
| ModPageLoaderResult
|
||||
| BlogPageLoaderResult
|
||||
const [filterOptions, setFilterOptions] = useState<FilterOptions>({
|
||||
sort: SortByEnum.Latest,
|
||||
author: AuthorFilterEnum.All_Comments
|
||||
@ -73,120 +78,73 @@ export const Comments = ({ addressable, setCommentCount }: Props) => {
|
||||
setCommentCount(commentEvents.length)
|
||||
}, [commentEvents, setCommentCount])
|
||||
|
||||
const userState = useAppSelector((state) => state.user)
|
||||
|
||||
const handleSubmit = async (content: string): Promise<boolean> => {
|
||||
if (content === '') return false
|
||||
|
||||
let pubkey: string | undefined
|
||||
// NDKEvent required
|
||||
if (!event) return false
|
||||
|
||||
if (userState.auth && userState.user?.pubkey) {
|
||||
pubkey = userState.user.pubkey as string
|
||||
} else {
|
||||
try {
|
||||
pubkey = (await window.nostr?.getPublicKey()) as string
|
||||
} catch (error) {
|
||||
log(true, LogType.Error, `Could not get pubkey`, error)
|
||||
}
|
||||
}
|
||||
try {
|
||||
const reply = event.reply()
|
||||
reply.content = content
|
||||
|
||||
if (!pubkey) {
|
||||
toast.error('Could not get user pubkey')
|
||||
return false
|
||||
}
|
||||
|
||||
const unsignedEvent: UnsignedEvent = {
|
||||
content: content,
|
||||
pubkey: pubkey,
|
||||
kind: kinds.ShortTextNote,
|
||||
created_at: now(),
|
||||
tags: [
|
||||
['e', addressable.id],
|
||||
['a', addressable.aTag],
|
||||
['p', addressable.author]
|
||||
]
|
||||
}
|
||||
|
||||
const signedEvent = await window.nostr
|
||||
?.signEvent(unsignedEvent)
|
||||
.then((event) => event as Event)
|
||||
.catch((err) => {
|
||||
toast.error('Failed to sign the event!')
|
||||
log(true, LogType.Error, 'Failed to sign the event!', err)
|
||||
return null
|
||||
})
|
||||
|
||||
if (!signedEvent) return false
|
||||
|
||||
setCommentEvents((prev) => [
|
||||
{
|
||||
...signedEvent,
|
||||
status: CommentEventStatus.Publishing
|
||||
},
|
||||
...prev
|
||||
])
|
||||
|
||||
const ndkEvent = new NDKEvent(ndk, signedEvent)
|
||||
publish(ndkEvent)
|
||||
.then((publishedOnRelays) => {
|
||||
if (publishedOnRelays.length === 0) {
|
||||
setCommentEvents((prev) =>
|
||||
prev.map((event) => {
|
||||
if (event.id === signedEvent.id) {
|
||||
return {
|
||||
...event,
|
||||
status: CommentEventStatus.Failed
|
||||
}
|
||||
setCommentEvents((prev) => [
|
||||
{
|
||||
event: new NDKEvent(ndk, reply),
|
||||
status: CommentEventStatus.Publishing
|
||||
},
|
||||
...prev
|
||||
])
|
||||
const relaySet = await reply.publish()
|
||||
if (relaySet.size) {
|
||||
setCommentEvents((prev) =>
|
||||
prev.map((ce) => {
|
||||
if (ce.event.id === reply.id) {
|
||||
return {
|
||||
event: ce.event,
|
||||
status: CommentEventStatus.Published
|
||||
}
|
||||
|
||||
return event
|
||||
})
|
||||
)
|
||||
} else {
|
||||
setCommentEvents((prev) =>
|
||||
prev.map((event) => {
|
||||
if (event.id === signedEvent.id) {
|
||||
return {
|
||||
...event,
|
||||
status: CommentEventStatus.Published
|
||||
}
|
||||
}
|
||||
|
||||
return event
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
return ce
|
||||
})
|
||||
)
|
||||
// when an event is successfully published remove the status from it after 15 seconds
|
||||
setTimeout(() => {
|
||||
setCommentEvents((prev) =>
|
||||
prev.map((event) => {
|
||||
if (event.id === signedEvent.id) {
|
||||
delete event.status
|
||||
prev.map((ce) => {
|
||||
if (ce.event.id === reply.id) {
|
||||
delete ce.status
|
||||
}
|
||||
|
||||
return event
|
||||
return ce
|
||||
})
|
||||
)
|
||||
}, 15000)
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('An error occurred in publishing comment', err)
|
||||
} else {
|
||||
log(true, LogType.Error, 'Publishing comment failed.')
|
||||
setCommentEvents((prev) =>
|
||||
prev.map((event) => {
|
||||
if (event.id === signedEvent.id) {
|
||||
prev.map((ce) => {
|
||||
if (ce.event.id === reply.id) {
|
||||
return {
|
||||
...event,
|
||||
event: ce.event,
|
||||
status: CommentEventStatus.Failed
|
||||
}
|
||||
}
|
||||
|
||||
return event
|
||||
return ce
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
return false
|
||||
} catch (error) {
|
||||
toast.error('An error occurred in publishing comment.')
|
||||
log(
|
||||
true,
|
||||
LogType.Error,
|
||||
'An error occurred in publishing comment.',
|
||||
error
|
||||
)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const handleDiscoveredClick = () => {
|
||||
@ -203,14 +161,22 @@ export const Comments = ({ addressable, setCommentCount }: Props) => {
|
||||
let filteredComments = visible
|
||||
if (filterOptions.author === AuthorFilterEnum.Creator_Comments) {
|
||||
filteredComments = filteredComments.filter(
|
||||
(comment) => comment.pubkey === addressable.author
|
||||
(comment) => comment.event.pubkey === addressable.author
|
||||
)
|
||||
}
|
||||
|
||||
if (filterOptions.sort === SortByEnum.Latest) {
|
||||
filteredComments.sort((a, b) => b.created_at - a.created_at)
|
||||
filteredComments.sort((a, b) =>
|
||||
a.event.created_at && b.event.created_at
|
||||
? b.event.created_at - a.event.created_at
|
||||
: 0
|
||||
)
|
||||
} else if (filterOptions.sort === SortByEnum.Oldest) {
|
||||
filteredComments.sort((a, b) => a.created_at - b.created_at)
|
||||
filteredComments.sort((a, b) =>
|
||||
a.event.created_at && b.event.created_at
|
||||
? a.event.created_at - b.event.created_at
|
||||
: 0
|
||||
)
|
||||
}
|
||||
|
||||
return filteredComments
|
||||
@ -241,8 +207,8 @@ export const Comments = ({ addressable, setCommentCount }: Props) => {
|
||||
setFilterOptions={setFilterOptions}
|
||||
/>
|
||||
<div className='IBMSMSMBSSCommentsList'>
|
||||
{comments.map((event) => (
|
||||
<Comment key={event.id} {...event} />
|
||||
{comments.map((comment) => (
|
||||
<Comment key={comment.event.id} comment={comment} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@ -361,20 +327,19 @@ const Filter = React.memo(
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
const Comment = (props: CommentEvent) => {
|
||||
const { findMetadata } = useNDKContext()
|
||||
interface CommentProps {
|
||||
comment: CommentEvent
|
||||
}
|
||||
const Comment = ({ comment }: CommentProps) => {
|
||||
const [profile, setProfile] = useState<UserProfile>()
|
||||
|
||||
useDidMount(() => {
|
||||
findMetadata(props.pubkey).then((res) => {
|
||||
setProfile(res)
|
||||
})
|
||||
comment.event.author.fetchProfile().then((res) => setProfile(res))
|
||||
})
|
||||
|
||||
const profileRoute = getProfilePageRoute(
|
||||
nip19.nprofileEncode({
|
||||
pubkey: props.pubkey
|
||||
pubkey: comment.event.pubkey
|
||||
})
|
||||
)
|
||||
|
||||
@ -398,31 +363,33 @@ const Comment = (props: CommentEvent) => {
|
||||
{profile?.displayName || profile?.name || ''}{' '}
|
||||
</Link>
|
||||
<Link className='IBMSMSMBSSCL_CTD_Address' to={profileRoute}>
|
||||
{hexToNpub(props.pubkey)}
|
||||
{hexToNpub(comment.event.pubkey)}
|
||||
</Link>
|
||||
</div>
|
||||
<div className='IBMSMSMBSSCL_CommentActionsDetails'>
|
||||
<a className='IBMSMSMBSSCL_CADTime'>
|
||||
{formatDate(props.created_at * 1000, 'hh:mm aa')}{' '}
|
||||
</a>
|
||||
<a className='IBMSMSMBSSCL_CADDate'>
|
||||
{formatDate(props.created_at * 1000, 'dd/MM/yyyy')}
|
||||
</a>
|
||||
</div>
|
||||
{comment.event.created_at && (
|
||||
<div className='IBMSMSMBSSCL_CommentActionsDetails'>
|
||||
<a className='IBMSMSMBSSCL_CADTime'>
|
||||
{formatDate(comment.event.created_at * 1000, 'hh:mm aa')}{' '}
|
||||
</a>
|
||||
<a className='IBMSMSMBSSCL_CADDate'>
|
||||
{formatDate(comment.event.created_at * 1000, 'dd/MM/yyyy')}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className='IBMSMSMBSSCL_CommentBottom'>
|
||||
{props.status && (
|
||||
{comment.status && (
|
||||
<p className='IBMSMSMBSSCL_CBTextStatus'>
|
||||
<span className='IBMSMSMBSSCL_CBTextStatusSpan'>Status:</span>
|
||||
{props.status}
|
||||
{comment.status}
|
||||
</p>
|
||||
)}
|
||||
<p className='IBMSMSMBSSCL_CBText'>{props.content}</p>
|
||||
<p className='IBMSMSMBSSCL_CBText'>{comment.event.content}</p>
|
||||
</div>
|
||||
<div className='IBMSMSMBSSCL_CommentActions'>
|
||||
<div className='IBMSMSMBSSCL_CommentActionsInside'>
|
||||
<Reactions {...props} />
|
||||
<Reactions {...comment.event.rawEvent()} />
|
||||
<div
|
||||
className='IBMSMSMBSSCL_CAElement IBMSMSMBSSCL_CAERepost'
|
||||
style={{ cursor: 'not-allowed' }}
|
||||
@ -442,37 +409,41 @@ const Comment = (props: CommentEvent) => {
|
||||
<div className='IBMSMSMBSSCL_CAElementLoad'></div>
|
||||
</div>
|
||||
</div>
|
||||
<Zap {...props} />
|
||||
<div
|
||||
className='IBMSMSMBSSCL_CAElement IBMSMSMBSSCL_CAEReplies'
|
||||
style={{ cursor: 'not-allowed' }}
|
||||
>
|
||||
<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'>0</p>
|
||||
<p className='IBMSMSMBSSCL_CAElementText'>Replies</p>
|
||||
</div>
|
||||
<div
|
||||
className='IBMSMSMBSSCL_CAElement IBMSMSMBSSCL_CAEReply'
|
||||
style={{ cursor: 'not-allowed' }}
|
||||
>
|
||||
<p className='IBMSMSMBSSCL_CAElementText'>Reply</p>
|
||||
</div>
|
||||
<Zap {...comment.event.rawEvent()} />
|
||||
{comment.event.kind === NDKKind.GenericReply && (
|
||||
<>
|
||||
<div
|
||||
className='IBMSMSMBSSCL_CAElement IBMSMSMBSSCL_CAEReplies'
|
||||
style={{ cursor: 'not-allowed' }}
|
||||
>
|
||||
<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'>0</p>
|
||||
<p className='IBMSMSMBSSCL_CAElementText'>Replies</p>
|
||||
</div>
|
||||
<div
|
||||
className='IBMSMSMBSSCL_CAElement IBMSMSMBSSCL_CAEReply'
|
||||
style={{ cursor: 'not-allowed' }}
|
||||
>
|
||||
<p className='IBMSMSMBSSCL_CAElementText'>Reply</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const Reactions = (props: Event) => {
|
||||
const Reactions = (props: NostrEvent) => {
|
||||
const {
|
||||
isDataLoaded,
|
||||
likesCount,
|
||||
@ -482,7 +453,7 @@ const Reactions = (props: Event) => {
|
||||
hasReactedNegatively
|
||||
} = useReactions({
|
||||
pubkey: props.pubkey,
|
||||
eTag: props.id
|
||||
eTag: props.id!
|
||||
})
|
||||
|
||||
return (
|
||||
@ -537,7 +508,7 @@ const Reactions = (props: Event) => {
|
||||
)
|
||||
}
|
||||
|
||||
const Zap = (props: Event) => {
|
||||
const Zap = (props: NostrEvent) => {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [totalZappedAmount, setTotalZappedAmount] = useState(0)
|
||||
const [hasZapped, setHasZapped] = useState(false)
|
||||
@ -550,7 +521,7 @@ const Zap = (props: Event) => {
|
||||
useDidMount(() => {
|
||||
getTotalZapAmount(
|
||||
props.pubkey,
|
||||
props.id,
|
||||
props.id!,
|
||||
undefined,
|
||||
userState.user?.pubkey as string
|
||||
)
|
||||
|
@ -48,7 +48,7 @@ export const useComments = (
|
||||
})
|
||||
|
||||
const filter: NDKFilter = {
|
||||
kinds: [NDKKind.Text],
|
||||
kinds: [NDKKind.Text, NDKKind.GenericReply],
|
||||
'#a': [aTag]
|
||||
}
|
||||
|
||||
@ -73,21 +73,11 @@ export const useComments = (
|
||||
|
||||
subscription.on('event', (ndkEvent) => {
|
||||
setCommentEvents((prev) => {
|
||||
if (prev.find((e) => e.id === ndkEvent.id)) {
|
||||
if (prev.find((e) => e.event.id === ndkEvent.id)) {
|
||||
return [...prev]
|
||||
}
|
||||
|
||||
const commentEvent: CommentEvent = {
|
||||
kind: NDKKind.Text,
|
||||
tags: ndkEvent.tags,
|
||||
content: ndkEvent.content,
|
||||
created_at: ndkEvent.created_at!,
|
||||
pubkey: ndkEvent.pubkey,
|
||||
id: ndkEvent.id,
|
||||
sig: ndkEvent.sig!
|
||||
}
|
||||
|
||||
return [commentEvent, ...prev]
|
||||
return [{ event: ndkEvent }, ...prev]
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -94,6 +94,7 @@ export const blogRouteLoader =
|
||||
])
|
||||
const result: BlogPageLoaderResult = {
|
||||
blog: undefined,
|
||||
event: undefined,
|
||||
latest: [],
|
||||
isAddedToNSFW: false,
|
||||
isBlocked: false
|
||||
@ -102,6 +103,9 @@ export const blogRouteLoader =
|
||||
// Check the blog event result
|
||||
const fetchEventResult = settled[0]
|
||||
if (fetchEventResult.status === 'fulfilled' && fetchEventResult.value) {
|
||||
// Save original event
|
||||
result.event = fetchEventResult.value
|
||||
|
||||
// Extract the blog details from the event
|
||||
result.blog = extractBlogDetails(fetchEventResult.value)
|
||||
} else if (fetchEventResult.status === 'rejected') {
|
||||
|
@ -103,6 +103,7 @@ export const modRouteLoader =
|
||||
|
||||
const result: ModPageLoaderResult = {
|
||||
mod: undefined,
|
||||
event: undefined,
|
||||
latest: [],
|
||||
isAddedToNSFW: false,
|
||||
isBlocked: false,
|
||||
@ -112,6 +113,9 @@ export const modRouteLoader =
|
||||
// Check the mod event result
|
||||
const fetchEventResult = settled[0]
|
||||
if (fetchEventResult.status === 'fulfilled' && fetchEventResult.value) {
|
||||
// Save original event
|
||||
result.event = fetchEventResult.value
|
||||
|
||||
// Extract the mod data from the event
|
||||
result.mod = extractModData(fetchEventResult.value)
|
||||
} else if (fetchEventResult.status === 'rejected') {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { NDKEvent } from '@nostr-dev-kit/ndk'
|
||||
import { SortBy, NSFWFilter, ModeratedFilter } from './modsFilter'
|
||||
|
||||
export interface BlogForm {
|
||||
@ -36,6 +37,7 @@ export interface BlogCardDetails extends BlogDetails {
|
||||
|
||||
export interface BlogPageLoaderResult {
|
||||
blog: Partial<BlogDetails> | undefined
|
||||
event: NDKEvent | undefined
|
||||
latest: Partial<BlogDetails>[]
|
||||
isAddedToNSFW: boolean
|
||||
isBlocked: boolean
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Event } from 'nostr-tools'
|
||||
import { NDKEvent } from '@nostr-dev-kit/ndk'
|
||||
import { BlogDetails } from 'types'
|
||||
|
||||
export enum CommentEventStatus {
|
||||
@ -7,7 +7,8 @@ export enum CommentEventStatus {
|
||||
Failed = 'Failed to publish comment.'
|
||||
}
|
||||
|
||||
export interface CommentEvent extends Event {
|
||||
export interface CommentEvent {
|
||||
event: NDKEvent
|
||||
status?: CommentEventStatus
|
||||
}
|
||||
|
||||
@ -85,6 +86,7 @@ export interface MuteLists {
|
||||
|
||||
export interface ModPageLoaderResult {
|
||||
mod: ModDetails | undefined
|
||||
event: NDKEvent | undefined
|
||||
latest: Partial<BlogDetails>[]
|
||||
isAddedToNSFW: boolean
|
||||
isBlocked: boolean
|
||||
|
Loading…
x
Reference in New Issue
Block a user