fix: 🐛

This commit is contained in:
codytseng
2025-06-25 11:01:53 +08:00
parent cb32439896
commit 6c2cd0ff42
7 changed files with 131 additions and 19 deletions

View File

@@ -1,5 +1,6 @@
import { Skeleton } from '@/components/ui/skeleton' import { Skeleton } from '@/components/ui/skeleton'
import { useFetchEvent } from '@/hooks' import { useFetchEvent } from '@/hooks'
import { isSupportedKind } from '@/lib/event'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { useMuteList } from '@/providers/MuteListProvider' import { useMuteList } from '@/providers/MuteListProvider'
import { useMemo } from 'react' import { useMemo } from 'react'
@@ -68,6 +69,8 @@ export default function ParentNotePreview({
{event && <UserAvatar className="shrink-0" userId={event.pubkey} size="tiny" />} {event && <UserAvatar className="shrink-0" userId={event.pubkey} size="tiny" />}
{isMuted ? ( {isMuted ? (
<div className="truncate">[{t('This user has been muted')}]</div> <div className="truncate">[{t('This user has been muted')}]</div>
) : !isSupportedKind(event.kind) ? (
<div className="truncate">[{t('Cannot handle event of kind k', { k: event.kind })}]</div>
) : ( ) : (
<ContentPreview className="truncate" event={event} /> <ContentPreview className="truncate" event={event} />
)} )}

View File

@@ -24,7 +24,6 @@ export default function Uploader({
try { try {
for (const file of event.target.files) { for (const file of event.target.files) {
const result = await mediaUpload.upload(file) const result = await mediaUpload.upload(file)
console.log('File uploaded successfully', result)
onUploadSuccess(result) onUploadSuccess(result)
} }
} catch (error) { } catch (error) {

View File

@@ -1,8 +1,11 @@
import { BIG_RELAY_URLS, ExtendedKind } from '@/constants' import { BIG_RELAY_URLS, ExtendedKind } from '@/constants'
import { import {
getEventCoordinate,
getParentEventTag, getParentEventTag,
getRootAddressableEventTag,
getRootEventHexId, getRootEventHexId,
getRootEventTag, getRootEventTag,
isReplaceable,
isReplyNoteEvent isReplyNoteEvent
} from '@/lib/event' } from '@/lib/event'
import { generateEventIdFromETag, tagNameEquals } from '@/lib/tag' import { generateEventIdFromETag, tagNameEquals } from '@/lib/tag'
@@ -16,7 +19,10 @@ import { useTranslation } from 'react-i18next'
import { LoadingBar } from '../LoadingBar' import { LoadingBar } from '../LoadingBar'
import ReplyNote, { ReplyNoteSkeleton } from '../ReplyNote' import ReplyNote, { ReplyNoteSkeleton } from '../ReplyNote'
type TRootInfo = { type: 'event'; id: string; pubkey: string } | { type: 'I'; id: string } type TRootInfo =
| { type: 'E'; id: string; pubkey: string }
| { type: 'A'; id: string; eventId: string; pubkey: string; relay?: string }
| { type: 'I'; id: string }
const LIMIT = 100 const LIMIT = 100
const SHOW_COUNT = 10 const SHOW_COUNT = 10
@@ -30,7 +36,8 @@ export default function ReplyNoteList({ index, event }: { index?: number; event:
const replies = useMemo(() => { const replies = useMemo(() => {
const replyIdSet = new Set<string>() const replyIdSet = new Set<string>()
const replyEvents: NEvent[] = [] const replyEvents: NEvent[] = []
let parentEventIds = [event.id] const currentEventId = isReplaceable(event.kind) ? getEventCoordinate(event) : event.id
let parentEventIds = [currentEventId]
while (parentEventIds.length > 0) { while (parentEventIds.length > 0) {
const events = parentEventIds.flatMap((id) => repliesMap.get(id)?.events || []) const events = parentEventIds.flatMap((id) => repliesMap.get(id)?.events || [])
events.forEach((evt) => { events.forEach((evt) => {
@@ -52,22 +59,36 @@ export default function ReplyNoteList({ index, event }: { index?: number; event:
useEffect(() => { useEffect(() => {
const fetchRootEvent = async () => { const fetchRootEvent = async () => {
let root: TRootInfo = { type: 'event', id: event.id, pubkey: event.pubkey } let root: TRootInfo = isReplaceable(event.kind)
? {
type: 'A',
id: getEventCoordinate(event),
eventId: event.id,
pubkey: event.pubkey,
relay: client.getEventHint(event.id)
}
: { type: 'E', id: event.id, pubkey: event.pubkey }
const rootEventTag = getRootEventTag(event) const rootEventTag = getRootEventTag(event)
if (rootEventTag) { if (rootEventTag) {
const [, rootEventHexId, , , rootEventPubkey] = rootEventTag const [, rootEventHexId, , , rootEventPubkey] = rootEventTag
if (rootEventHexId && rootEventPubkey) { if (rootEventHexId && rootEventPubkey) {
root = { type: 'event', id: rootEventHexId, pubkey: rootEventPubkey } root = { type: 'E', id: rootEventHexId, pubkey: rootEventPubkey }
} else { } else {
const rootEventId = generateEventIdFromETag(rootEventTag) const rootEventId = generateEventIdFromETag(rootEventTag)
if (rootEventId) { if (rootEventId) {
const rootEvent = await client.fetchEvent(rootEventId) const rootEvent = await client.fetchEvent(rootEventId)
if (rootEvent) { if (rootEvent) {
root = { type: 'event', id: rootEvent.id, pubkey: rootEvent.pubkey } root = { type: 'E', id: rootEvent.id, pubkey: rootEvent.pubkey }
} }
} }
} }
} else if (event.kind === ExtendedKind.COMMENT) { } else if (event.kind === ExtendedKind.COMMENT) {
const rootATag = getRootAddressableEventTag(event)
if (rootATag) {
const [, coordinate, relay] = rootATag
const [, pubkey] = coordinate.split(':')
root = { type: 'A', id: coordinate, eventId: event.id, pubkey, relay }
}
const rootITag = event.tags.find(tagNameEquals('I')) const rootITag = event.tags.find(tagNameEquals('I'))
if (rootITag) { if (rootITag) {
root = { type: 'I', id: rootITag[1] } root = { type: 'I', id: rootITag[1] }
@@ -110,13 +131,18 @@ export default function ReplyNoteList({ index, event }: { index?: number; event:
(rootInfo as { pubkey?: string }).pubkey ?? event.pubkey (rootInfo as { pubkey?: string }).pubkey ?? event.pubkey
) )
const relayUrls = relayList.read.concat(BIG_RELAY_URLS) const relayUrls = relayList.read.concat(BIG_RELAY_URLS)
const seenOn = client.getSeenEventRelayUrls(rootInfo.id) const seenOn =
rootInfo.type === 'E'
? client.getSeenEventRelayUrls(rootInfo.id)
: rootInfo.type === 'A'
? client.getCurrentRelayUrls()
: []
relayUrls.unshift(...seenOn) relayUrls.unshift(...seenOn)
const filters: (Omit<Filter, 'since' | 'until'> & { const filters: (Omit<Filter, 'since' | 'until'> & {
limit: number limit: number
})[] = [] })[] = []
if (rootInfo.type === 'event') { if (rootInfo.type === 'E') {
filters.push({ filters.push({
'#e': [rootInfo.id], '#e': [rootInfo.id],
kinds: [kinds.ShortTextNote], kinds: [kinds.ShortTextNote],
@@ -129,6 +155,15 @@ export default function ReplyNoteList({ index, event }: { index?: number; event:
limit: LIMIT limit: LIMIT
}) })
} }
} else if (rootInfo.type === 'A') {
filters.push({
'#A': [rootInfo.id],
kinds: [ExtendedKind.COMMENT],
limit: LIMIT
})
if (rootInfo.relay) {
relayUrls.push(rootInfo.relay)
}
} else { } else {
filters.push({ filters.push({
'#I': [rootInfo.id], '#I': [rootInfo.id],

View File

@@ -187,10 +187,12 @@ export async function createCommentDraftEvent(
const { const {
quoteEventIds, quoteEventIds,
rootEventId, rootEventId,
rootCoordinateTag,
rootKind, rootKind,
rootPubkey, rootPubkey,
rootUrl, rootUrl,
parentEventId, parentEventId,
parentCoordinate,
parentKind, parentKind,
parentPubkey parentPubkey
} = await extractCommentMentions(content, parentEvent) } = await extractCommentMentions(content, parentEvent)
@@ -207,7 +209,9 @@ export async function createCommentDraftEvent(
tags.push(...mentions.filter((pubkey) => pubkey !== parentPubkey).map((pubkey) => ['p', pubkey])) tags.push(...mentions.filter((pubkey) => pubkey !== parentPubkey).map((pubkey) => ['p', pubkey]))
if (rootEventId) { if (rootCoordinateTag) {
tags.push(rootCoordinateTag)
} else if (rootEventId) {
tags.push( tags.push(
rootPubkey rootPubkey
? ['E', rootEventId, client.getEventHint(rootEventId), rootPubkey] ? ['E', rootEventId, client.getEventHint(rootEventId), rootPubkey]
@@ -225,7 +229,9 @@ export async function createCommentDraftEvent(
} }
tags.push( tags.push(
...[ ...[
['e', parentEventId, client.getEventHint(parentEventId), parentPubkey], parentCoordinate
? ['a', parentCoordinate, client.getEventHint(parentEventId)]
: ['e', parentEventId, client.getEventHint(parentEventId), parentPubkey],
['k', parentKind.toString()], ['k', parentKind.toString()],
['p', parentPubkey] ['p', parentPubkey]
] ]

View File

@@ -7,6 +7,7 @@ import { getAmountFromInvoice, getLightningAddressFromProfile } from './lightnin
import { formatPubkey, pubkeyToNpub } from './pubkey' import { formatPubkey, pubkeyToNpub } from './pubkey'
import { import {
extractImageInfoFromTag, extractImageInfoFromTag,
generateEventIdFromATag,
generateEventIdFromETag, generateEventIdFromETag,
isReplyETag, isReplyETag,
isRootETag, isRootETag,
@@ -27,7 +28,7 @@ export function isNsfwEvent(event: Event) {
export function isReplyNoteEvent(event: Event) { export function isReplyNoteEvent(event: Event) {
if (event.kind === ExtendedKind.COMMENT) { if (event.kind === ExtendedKind.COMMENT) {
return !!getParentEventTag(event) return !!getParentEventTag(event) || !!getParentAddressableEventTag(event)
} }
if (event.kind !== kinds.ShortTextNote) return false if (event.kind !== kinds.ShortTextNote) return false
@@ -88,16 +89,27 @@ export function getParentEventTag(event?: Event) {
return tag return tag
} }
export function getParentAddressableEventTag(event?: Event) {
if (!event || event.kind !== ExtendedKind.COMMENT) return undefined
return event.tags.find(tagNameEquals('a')) ?? event.tags.find(tagNameEquals('A'))
}
export function getParentEventHexId(event?: Event) { export function getParentEventHexId(event?: Event) {
const tag = getParentEventTag(event) const tag = getParentEventTag(event)
return tag?.[1] return tag?.[1]
} }
export function getParentEventId(event?: Event) { export function getParentEventId(event?: Event) {
const tag = getParentEventTag(event) const eTag = getParentEventTag(event)
if (!tag) return undefined if (!eTag) {
const aTag = getParentAddressableEventTag(event)
if (!aTag) return undefined
return generateEventIdFromETag(tag) return generateEventIdFromATag(aTag)
}
return generateEventIdFromETag(eTag)
} }
export function getRootEventTag(event?: Event) { export function getRootEventTag(event?: Event) {
@@ -117,6 +129,12 @@ export function getRootEventTag(event?: Event) {
return tag return tag
} }
export function getRootAddressableEventTag(event?: Event) {
if (!event || event.kind !== ExtendedKind.COMMENT) return undefined
return event.tags.find(tagNameEquals('A'))
}
export function getRootEventHexId(event?: Event) { export function getRootEventHexId(event?: Event) {
const tag = getRootEventTag(event) const tag = getRootEventTag(event)
return tag?.[1] return tag?.[1]
@@ -124,7 +142,12 @@ export function getRootEventHexId(event?: Event) {
export function getRootEventId(event?: Event) { export function getRootEventId(event?: Event) {
const tag = getRootEventTag(event) const tag = getRootEventTag(event)
if (!tag) return undefined if (!tag) {
const aTag = getRootAddressableEventTag(event)
if (!aTag) return undefined
return generateEventIdFromATag(aTag)
}
return generateEventIdFromETag(tag) return generateEventIdFromETag(tag)
} }
@@ -356,6 +379,13 @@ export async function extractRelatedEventIds(content: string, parentEvent?: Even
export async function extractCommentMentions(content: string, parentEvent: Event) { export async function extractCommentMentions(content: string, parentEvent: Event) {
const quoteEventIds: string[] = [] const quoteEventIds: string[] = []
const parentEventIsReplaceable = isReplaceable(parentEvent.kind)
const rootCoordinateTag =
parentEvent.kind === ExtendedKind.COMMENT
? parentEvent.tags.find(tagNameEquals('A'))
: isReplaceable(parentEvent.kind)
? ['A', getEventCoordinate(parentEvent), client.getEventHint(parentEvent.id)]
: undefined
const rootEventId = const rootEventId =
parentEvent.kind === ExtendedKind.COMMENT parentEvent.kind === ExtendedKind.COMMENT
? parentEvent.tags.find(tagNameEquals('E'))?.[1] ? parentEvent.tags.find(tagNameEquals('E'))?.[1]
@@ -374,6 +404,7 @@ export async function extractCommentMentions(content: string, parentEvent: Event
: undefined : undefined
const parentEventId = parentEvent.id const parentEventId = parentEvent.id
const parentCoordinate = parentEventIsReplaceable ? getEventCoordinate(parentEvent) : undefined
const parentKind = parentEvent.kind const parentKind = parentEvent.kind
const parentPubkey = parentEvent.pubkey const parentPubkey = parentEvent.pubkey
@@ -399,10 +430,12 @@ export async function extractCommentMentions(content: string, parentEvent: Event
return { return {
quoteEventIds, quoteEventIds,
rootEventId, rootEventId,
rootCoordinateTag,
rootKind, rootKind,
rootPubkey, rootPubkey,
rootUrl, rootUrl,
parentEventId, parentEventId,
parentCoordinate,
parentKind, parentKind,
parentPubkey parentPubkey
} }

View File

@@ -11,7 +11,7 @@ import { Skeleton } from '@/components/ui/skeleton'
import { ExtendedKind } from '@/constants' import { ExtendedKind } from '@/constants'
import { useFetchEvent } from '@/hooks' import { useFetchEvent } from '@/hooks'
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout' import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
import { getParentEventId, getRootEventId, isPictureEvent } from '@/lib/event' import { getParentEventId, getRootEventId, isPictureEvent, isSupportedKind } from '@/lib/event'
import { toNote, toNoteList } from '@/lib/link' import { toNote, toNoteList } from '@/lib/link'
import { tagNameEquals } from '@/lib/tag' import { tagNameEquals } from '@/lib/tag'
import { useMuteList } from '@/providers/MuteListProvider' import { useMuteList } from '@/providers/MuteListProvider'
@@ -158,6 +158,21 @@ function ParentNote({ eventId }: { eventId?: string }) {
) )
} }
if (!isSupportedKind(event.kind)) {
return (
<div>
<Card
className="flex space-x-1 p-1 items-center clickable text-sm text-muted-foreground hover:text-foreground"
onClick={() => push(toNote(eventId))}
>
<UserAvatar userId={event.pubkey} size="tiny" className="shrink-0" />
<div className="shrink-0">[{t('Cannot handle event of kind k', { k: event.kind })}]</div>
</Card>
<div className="ml-5 w-px h-2 bg-border" />
</div>
)
}
return ( return (
<div> <div>
<Card <Card

View File

@@ -1,4 +1,9 @@
import { getParentEventTag, getRootEventTag } from '@/lib/event' import {
getParentAddressableEventTag,
getParentEventTag,
getRootAddressableEventTag,
getRootEventTag
} from '@/lib/event'
import { Event } from 'nostr-tools' import { Event } from 'nostr-tools'
import { createContext, useCallback, useContext, useState } from 'react' import { createContext, useCallback, useContext, useState } from 'react'
@@ -29,15 +34,31 @@ export function ReplyProvider({ children }: { children: React.ReactNode }) {
if (newReplyIdSet.has(reply.id)) return if (newReplyIdSet.has(reply.id)) return
newReplyIdSet.add(reply.id) newReplyIdSet.add(reply.id)
let rootId: string | undefined
const rootETag = getRootEventTag(reply) const rootETag = getRootEventTag(reply)
if (rootETag) { if (rootETag) {
const rootId = rootETag[1] rootId = rootETag[1]
} else {
const rootATag = getRootAddressableEventTag(reply)
if (rootATag) {
rootId = rootATag[1]
}
}
if (rootId) {
newReplyEventMap.set(rootId, [...(newReplyEventMap.get(rootId) || []), reply]) newReplyEventMap.set(rootId, [...(newReplyEventMap.get(rootId) || []), reply])
} }
let parentId: string | undefined
const parentETag = getParentEventTag(reply) const parentETag = getParentEventTag(reply)
if (parentETag) { if (parentETag) {
const parentId = parentETag[1] parentId = parentETag[1]
} else {
const parentATag = getParentAddressableEventTag(reply)
if (parentATag) {
parentId = parentATag[1]
}
}
if (parentId && parentId !== rootId) {
newReplyEventMap.set(parentId, [...(newReplyEventMap.get(parentId) || []), reply]) newReplyEventMap.set(parentId, [...(newReplyEventMap.get(parentId) || []), reply])
} }
}) })