feat: 💨
This commit is contained in:
@@ -1,10 +1,8 @@
|
|||||||
import { ExtendedKind } from '@/constants'
|
|
||||||
import { toNote } from '@/lib/link'
|
import { toNote } from '@/lib/link'
|
||||||
import { tagNameEquals } from '@/lib/tag'
|
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { useSecondaryPage } from '@/PageManager'
|
import { useSecondaryPage } from '@/PageManager'
|
||||||
import { MessageCircle } from 'lucide-react'
|
import { MessageCircle } from 'lucide-react'
|
||||||
import { Event, kinds } from 'nostr-tools'
|
import { Event } from 'nostr-tools'
|
||||||
import ContentPreview from '../../ContentPreview'
|
import ContentPreview from '../../ContentPreview'
|
||||||
import { FormattedTimestamp } from '../../FormattedTimestamp'
|
import { FormattedTimestamp } from '../../FormattedTimestamp'
|
||||||
import UserAvatar from '../../UserAvatar'
|
import UserAvatar from '../../UserAvatar'
|
||||||
@@ -17,22 +15,11 @@ export function CommentNotification({
|
|||||||
isNew?: boolean
|
isNew?: boolean
|
||||||
}) {
|
}) {
|
||||||
const { push } = useSecondaryPage()
|
const { push } = useSecondaryPage()
|
||||||
const rootEventId = notification.tags.find(tagNameEquals('E'))?.[1]
|
|
||||||
const rootPubkey = notification.tags.find(tagNameEquals('P'))?.[1]
|
|
||||||
const rootKind = notification.tags.find(tagNameEquals('K'))?.[1]
|
|
||||||
if (
|
|
||||||
!rootEventId ||
|
|
||||||
!rootPubkey ||
|
|
||||||
!rootKind ||
|
|
||||||
![kinds.ShortTextNote, ExtendedKind.PICTURE].includes(parseInt(rootKind))
|
|
||||||
) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex gap-2 items-center cursor-pointer py-2"
|
className="flex gap-2 items-center cursor-pointer py-2"
|
||||||
onClick={() => push(toNote({ id: rootEventId, pubkey: rootPubkey }))}
|
onClick={() => push(toNote(notification))}
|
||||||
>
|
>
|
||||||
<UserAvatar userId={notification.pubkey} size="small" />
|
<UserAvatar userId={notification.pubkey} size="small" />
|
||||||
<MessageCircle size={24} className="text-blue-400" />
|
<MessageCircle size={24} className="text-blue-400" />
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
getRootEventTag,
|
getRootEventTag,
|
||||||
isReplyNoteEvent
|
isReplyNoteEvent
|
||||||
} from '@/lib/event'
|
} from '@/lib/event'
|
||||||
import { generateEventIdFromETag } from '@/lib/tag'
|
import { generateEventIdFromETag, tagNameEquals } from '@/lib/tag'
|
||||||
import { useSecondaryPage } from '@/PageManager'
|
import { useSecondaryPage } from '@/PageManager'
|
||||||
import { useReply } from '@/providers/ReplyProvider'
|
import { useReply } from '@/providers/ReplyProvider'
|
||||||
import client from '@/services/client.service'
|
import client from '@/services/client.service'
|
||||||
@@ -15,6 +15,8 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import ReplyNote from '../ReplyNote'
|
import ReplyNote from '../ReplyNote'
|
||||||
|
|
||||||
|
type TRootInfo = { type: 'event'; id: string; pubkey: string } | { type: 'I'; id: string }
|
||||||
|
|
||||||
const LIMIT = 100
|
const LIMIT = 100
|
||||||
const SHOW_COUNT = 10
|
const SHOW_COUNT = 10
|
||||||
|
|
||||||
@@ -29,7 +31,7 @@ export default function ReplyNoteList({
|
|||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { currentIndex } = useSecondaryPage()
|
const { currentIndex } = useSecondaryPage()
|
||||||
const [rootInfo, setRootInfo] = useState<{ id: string; pubkey: string } | undefined>(undefined)
|
const [rootInfo, setRootInfo] = useState<TRootInfo | undefined>(undefined)
|
||||||
const { repliesMap, addReplies } = useReply()
|
const { repliesMap, addReplies } = useReply()
|
||||||
const replies = useMemo(() => {
|
const replies = useMemo(() => {
|
||||||
const replyIdSet = new Set<string>()
|
const replyIdSet = new Set<string>()
|
||||||
@@ -56,21 +58,26 @@ export default function ReplyNoteList({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchRootEvent = async () => {
|
const fetchRootEvent = async () => {
|
||||||
let root = { id: event.id, pubkey: event.pubkey }
|
let root: TRootInfo = { type: 'event', 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 = { id: rootEventHexId, pubkey: rootEventPubkey }
|
root = { type: 'event', 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 = { id: rootEvent.id, pubkey: rootEvent.pubkey }
|
root = { type: 'event', id: rootEvent.id, pubkey: rootEvent.pubkey }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (event.kind === ExtendedKind.COMMENT) {
|
||||||
|
const rootITag = event.tags.find(tagNameEquals('I'))
|
||||||
|
if (rootITag) {
|
||||||
|
root = { type: 'I', id: rootITag[1] }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setRootInfo(root)
|
setRootInfo(root)
|
||||||
}
|
}
|
||||||
@@ -105,20 +112,22 @@ export default function ReplyNoteList({
|
|||||||
setLoading(true)
|
setLoading(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const relayList = await client.fetchRelayList(rootInfo.pubkey)
|
const relayList = await client.fetchRelayList(
|
||||||
|
(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 = client.getSeenEventRelayUrls(rootInfo.id)
|
||||||
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') {
|
||||||
|
filters.push({
|
||||||
'#e': [rootInfo.id],
|
'#e': [rootInfo.id],
|
||||||
kinds: [kinds.ShortTextNote],
|
kinds: [kinds.ShortTextNote],
|
||||||
limit: LIMIT
|
limit: LIMIT
|
||||||
}
|
})
|
||||||
]
|
|
||||||
if (event.kind !== kinds.ShortTextNote) {
|
if (event.kind !== kinds.ShortTextNote) {
|
||||||
filters.push({
|
filters.push({
|
||||||
'#E': [rootInfo.id],
|
'#E': [rootInfo.id],
|
||||||
@@ -126,6 +135,13 @@ export default function ReplyNoteList({
|
|||||||
limit: LIMIT
|
limit: LIMIT
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
filters.push({
|
||||||
|
'#I': [rootInfo.id],
|
||||||
|
kinds: [ExtendedKind.COMMENT],
|
||||||
|
limit: LIMIT
|
||||||
|
})
|
||||||
|
}
|
||||||
const { closer, timelineKey } = await client.subscribeTimeline(
|
const { closer, timelineKey } = await client.subscribeTimeline(
|
||||||
filters.map((filter) => ({
|
filters.map((filter) => ({
|
||||||
urls: relayUrls.slice(0, 5),
|
urls: relayUrls.slice(0, 5),
|
||||||
|
|||||||
@@ -354,15 +354,15 @@ 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[] = []
|
||||||
let rootEventId =
|
const rootEventId =
|
||||||
parentEvent.kind === ExtendedKind.COMMENT
|
parentEvent.kind === ExtendedKind.COMMENT
|
||||||
? parentEvent.tags.find(tagNameEquals('E'))?.[1]
|
? parentEvent.tags.find(tagNameEquals('E'))?.[1]
|
||||||
: parentEvent.id
|
: parentEvent.id
|
||||||
let rootKind =
|
const rootKind =
|
||||||
parentEvent.kind === ExtendedKind.COMMENT
|
parentEvent.kind === ExtendedKind.COMMENT
|
||||||
? parentEvent.tags.find(tagNameEquals('K'))?.[1]
|
? parentEvent.tags.find(tagNameEquals('K'))?.[1]
|
||||||
: parentEvent.kind
|
: parentEvent.kind
|
||||||
let rootPubkey =
|
const rootPubkey =
|
||||||
parentEvent.kind === ExtendedKind.COMMENT
|
parentEvent.kind === ExtendedKind.COMMENT
|
||||||
? parentEvent.tags.find(tagNameEquals('P'))?.[1]
|
? parentEvent.tags.find(tagNameEquals('P'))?.[1]
|
||||||
: parentEvent.pubkey
|
: parentEvent.pubkey
|
||||||
@@ -371,12 +371,6 @@ export async function extractCommentMentions(content: string, parentEvent: Event
|
|||||||
? parentEvent.tags.find(tagNameEquals('I'))?.[1]
|
? parentEvent.tags.find(tagNameEquals('I'))?.[1]
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
if (parentEvent.kind === ExtendedKind.COMMENT && !rootEventId) {
|
|
||||||
rootEventId = parentEvent.id
|
|
||||||
rootKind = parentEvent.kind
|
|
||||||
rootPubkey = parentEvent.pubkey
|
|
||||||
}
|
|
||||||
|
|
||||||
const parentEventId = parentEvent.id
|
const parentEventId = parentEvent.id
|
||||||
const parentKind = parentEvent.kind
|
const parentKind = parentEvent.kind
|
||||||
const parentPubkey = parentEvent.pubkey
|
const parentPubkey = parentEvent.pubkey
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ import UserAvatar from '@/components/UserAvatar'
|
|||||||
import { Card } from '@/components/ui/card'
|
import { Card } from '@/components/ui/card'
|
||||||
import { Separator } from '@/components/ui/separator'
|
import { Separator } from '@/components/ui/separator'
|
||||||
import { Skeleton } from '@/components/ui/skeleton'
|
import { Skeleton } from '@/components/ui/skeleton'
|
||||||
|
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 } from '@/lib/event'
|
||||||
import { toNote } from '@/lib/link'
|
import { toNote } from '@/lib/link'
|
||||||
|
import { tagNameEquals } from '@/lib/tag'
|
||||||
import { useMuteList } from '@/providers/MuteListProvider'
|
import { useMuteList } from '@/providers/MuteListProvider'
|
||||||
import { forwardRef, useMemo } from 'react'
|
import { forwardRef, useMemo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@@ -22,6 +24,10 @@ const NotePage = forwardRef(({ id, index }: { id?: string; index?: number }, ref
|
|||||||
const { event, isFetching } = useFetchEvent(id)
|
const { event, isFetching } = useFetchEvent(id)
|
||||||
const parentEventId = useMemo(() => getParentEventId(event), [event])
|
const parentEventId = useMemo(() => getParentEventId(event), [event])
|
||||||
const rootEventId = useMemo(() => getRootEventId(event), [event])
|
const rootEventId = useMemo(() => getRootEventId(event), [event])
|
||||||
|
const rootITag = useMemo(
|
||||||
|
() => (event?.kind === ExtendedKind.COMMENT ? event.tags.find(tagNameEquals('I')) : undefined),
|
||||||
|
[event]
|
||||||
|
)
|
||||||
|
|
||||||
if (!event && isFetching) {
|
if (!event && isFetching) {
|
||||||
return (
|
return (
|
||||||
@@ -65,7 +71,8 @@ const NotePage = forwardRef(({ id, index }: { id?: string; index?: number }, ref
|
|||||||
return (
|
return (
|
||||||
<SecondaryPageLayout ref={ref} index={index} title={t('Note')} displayScrollToTopButton>
|
<SecondaryPageLayout ref={ref} index={index} title={t('Note')} displayScrollToTopButton>
|
||||||
<div className="px-4">
|
<div className="px-4">
|
||||||
{rootEventId !== parentEventId && (
|
{rootITag && <OtherRoot value={rootITag[1]} />}
|
||||||
|
{!rootITag && rootEventId !== parentEventId && (
|
||||||
<ParentNote key={`root-note-${event.id}`} eventId={rootEventId} />
|
<ParentNote key={`root-note-${event.id}`} eventId={rootEventId} />
|
||||||
)}
|
)}
|
||||||
<ParentNote key={`parent-note-${event.id}`} eventId={parentEventId} />
|
<ParentNote key={`parent-note-${event.id}`} eventId={parentEventId} />
|
||||||
@@ -85,6 +92,33 @@ const NotePage = forwardRef(({ id, index }: { id?: string; index?: number }, ref
|
|||||||
NotePage.displayName = 'NotePage'
|
NotePage.displayName = 'NotePage'
|
||||||
export default NotePage
|
export default NotePage
|
||||||
|
|
||||||
|
function OtherRoot({ value }: { value: string }) {
|
||||||
|
const type = useMemo(() => (value.startsWith('http') ? 'url' : 'other'), [value])
|
||||||
|
|
||||||
|
if (type === 'url') {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Card
|
||||||
|
className="flex space-x-1 p-1 pl-2 clickable text-sm text-muted-foreground hover:text-foreground"
|
||||||
|
onClick={() => window.open(value, '_blank')}
|
||||||
|
>
|
||||||
|
<div className="truncate">{value}</div>
|
||||||
|
</Card>
|
||||||
|
<div className="ml-5 w-px h-2 bg-border" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Card className="flex space-x-1 p-1 text-sm text-muted-foreground">
|
||||||
|
<div className="truncate">{value}</div>
|
||||||
|
</Card>
|
||||||
|
<div className="ml-5 w-px h-2 bg-border" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function ParentNote({ eventId }: { eventId?: string }) {
|
function ParentNote({ eventId }: { eventId?: string }) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { push } = useSecondaryPage()
|
const { push } = useSecondaryPage()
|
||||||
|
|||||||
@@ -30,14 +30,16 @@ export function ReplyProvider({ children }: { children: React.ReactNode }) {
|
|||||||
newReplyIdSet.add(reply.id)
|
newReplyIdSet.add(reply.id)
|
||||||
|
|
||||||
const rootETag = getRootEventTag(reply)
|
const rootETag = getRootEventTag(reply)
|
||||||
if (!rootETag) return
|
if (rootETag) {
|
||||||
const rootId = rootETag[1]
|
const rootId = rootETag[1]
|
||||||
newReplyEventMap.set(rootId, [...(newReplyEventMap.get(rootId) || []), reply])
|
newReplyEventMap.set(rootId, [...(newReplyEventMap.get(rootId) || []), reply])
|
||||||
|
}
|
||||||
|
|
||||||
const parentETag = getParentEventTag(reply)
|
const parentETag = getParentEventTag(reply)
|
||||||
if (!parentETag) return
|
if (parentETag) {
|
||||||
const parentId = parentETag[1]
|
const parentId = parentETag[1]
|
||||||
newReplyEventMap.set(parentId, [...(newReplyEventMap.get(parentId) || []), reply])
|
newReplyEventMap.set(parentId, [...(newReplyEventMap.get(parentId) || []), reply])
|
||||||
|
}
|
||||||
})
|
})
|
||||||
if (newReplyEventMap.size === 0) return
|
if (newReplyEventMap.size === 0) return
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user