feat: distinguish between mention and reply notifications
This commit is contained in:
@@ -1,13 +1,16 @@
|
|||||||
|
import { getEmbeddedPubkeys } from '@/lib/event'
|
||||||
import { toNote } from '@/lib/link'
|
import { toNote } from '@/lib/link'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { useSecondaryPage } from '@/PageManager'
|
import { useSecondaryPage } from '@/PageManager'
|
||||||
import { MessageCircle } from 'lucide-react'
|
import { useNostr } from '@/providers/NostrProvider'
|
||||||
|
import { AtSign, MessageCircle } from 'lucide-react'
|
||||||
import { Event } from 'nostr-tools'
|
import { Event } from 'nostr-tools'
|
||||||
|
import { useMemo } from 'react'
|
||||||
import ContentPreview from '../../ContentPreview'
|
import ContentPreview from '../../ContentPreview'
|
||||||
import { FormattedTimestamp } from '../../FormattedTimestamp'
|
import { FormattedTimestamp } from '../../FormattedTimestamp'
|
||||||
import UserAvatar from '../../UserAvatar'
|
import UserAvatar from '../../UserAvatar'
|
||||||
|
|
||||||
export function CommentNotification({
|
export function MentionNotification({
|
||||||
notification,
|
notification,
|
||||||
isNew = false
|
isNew = false
|
||||||
}: {
|
}: {
|
||||||
@@ -15,6 +18,12 @@ export function CommentNotification({
|
|||||||
isNew?: boolean
|
isNew?: boolean
|
||||||
}) {
|
}) {
|
||||||
const { push } = useSecondaryPage()
|
const { push } = useSecondaryPage()
|
||||||
|
const { pubkey } = useNostr()
|
||||||
|
const isMention = useMemo(() => {
|
||||||
|
if (!pubkey) return false
|
||||||
|
const mentions = getEmbeddedPubkeys(notification)
|
||||||
|
return mentions.includes(pubkey)
|
||||||
|
}, [pubkey, notification])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -22,7 +31,11 @@ export function CommentNotification({
|
|||||||
onClick={() => push(toNote(notification))}
|
onClick={() => push(toNote(notification))}
|
||||||
>
|
>
|
||||||
<UserAvatar userId={notification.pubkey} size="small" />
|
<UserAvatar userId={notification.pubkey} size="small" />
|
||||||
<MessageCircle size={24} className="text-blue-400" />
|
{isMention ? (
|
||||||
|
<AtSign size={24} className="text-pink-400" />
|
||||||
|
) : (
|
||||||
|
<MessageCircle size={24} className="text-blue-400" />
|
||||||
|
)}
|
||||||
<ContentPreview
|
<ContentPreview
|
||||||
className={cn('truncate flex-1 w-0', isNew ? 'font-semibold' : 'text-muted-foreground')}
|
className={cn('truncate flex-1 w-0', isNew ? 'font-semibold' : 'text-muted-foreground')}
|
||||||
event={notification}
|
event={notification}
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import { toNote } from '@/lib/link'
|
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
import { useSecondaryPage } from '@/PageManager'
|
|
||||||
import { MessageCircle } from 'lucide-react'
|
|
||||||
import { Event } from 'nostr-tools'
|
|
||||||
import ContentPreview from '../../ContentPreview'
|
|
||||||
import { FormattedTimestamp } from '../../FormattedTimestamp'
|
|
||||||
import UserAvatar from '../../UserAvatar'
|
|
||||||
|
|
||||||
export function ReplyNotification({
|
|
||||||
notification,
|
|
||||||
isNew = false
|
|
||||||
}: {
|
|
||||||
notification: Event
|
|
||||||
isNew?: boolean
|
|
||||||
}) {
|
|
||||||
const { push } = useSecondaryPage()
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="flex gap-2 items-center cursor-pointer py-2"
|
|
||||||
onClick={() => push(toNote(notification))}
|
|
||||||
>
|
|
||||||
<UserAvatar userId={notification.pubkey} size="small" />
|
|
||||||
<MessageCircle size={24} className="text-blue-400" />
|
|
||||||
<ContentPreview
|
|
||||||
className={cn('truncate flex-1 w-0', isNew ? 'font-semibold' : 'text-muted-foreground')}
|
|
||||||
event={notification}
|
|
||||||
/>
|
|
||||||
<div className="text-muted-foreground">
|
|
||||||
<FormattedTimestamp timestamp={notification.created_at} short />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
import { ExtendedKind } from '@/constants'
|
import { ExtendedKind } from '@/constants'
|
||||||
import { useMuteList } from '@/providers/MuteListProvider'
|
import { useMuteList } from '@/providers/MuteListProvider'
|
||||||
import { Event, kinds } from 'nostr-tools'
|
import { Event, kinds } from 'nostr-tools'
|
||||||
import { CommentNotification } from './CommentNotification'
|
import { MentionNotification } from './MentionNotification'
|
||||||
import { PollResponseNotification } from './PollResponseNotification'
|
import { PollResponseNotification } from './PollResponseNotification'
|
||||||
import { ReactionNotification } from './ReactionNotification'
|
import { ReactionNotification } from './ReactionNotification'
|
||||||
import { ReplyNotification } from './ReplyNotification'
|
|
||||||
import { RepostNotification } from './RepostNotification'
|
import { RepostNotification } from './RepostNotification'
|
||||||
import { ZapNotification } from './ZapNotification'
|
import { ZapNotification } from './ZapNotification'
|
||||||
|
|
||||||
@@ -22,8 +21,13 @@ export function NotificationItem({
|
|||||||
if (notification.kind === kinds.Reaction) {
|
if (notification.kind === kinds.Reaction) {
|
||||||
return <ReactionNotification notification={notification} isNew={isNew} />
|
return <ReactionNotification notification={notification} isNew={isNew} />
|
||||||
}
|
}
|
||||||
if (notification.kind === kinds.ShortTextNote) {
|
if (
|
||||||
return <ReplyNotification notification={notification} isNew={isNew} />
|
notification.kind === kinds.ShortTextNote ||
|
||||||
|
notification.kind === ExtendedKind.COMMENT ||
|
||||||
|
notification.kind === ExtendedKind.VOICE_COMMENT ||
|
||||||
|
notification.kind === ExtendedKind.POLL
|
||||||
|
) {
|
||||||
|
return <MentionNotification notification={notification} isNew={isNew} />
|
||||||
}
|
}
|
||||||
if (notification.kind === kinds.Repost) {
|
if (notification.kind === kinds.Repost) {
|
||||||
return <RepostNotification notification={notification} isNew={isNew} />
|
return <RepostNotification notification={notification} isNew={isNew} />
|
||||||
@@ -31,12 +35,6 @@ export function NotificationItem({
|
|||||||
if (notification.kind === kinds.Zap) {
|
if (notification.kind === kinds.Zap) {
|
||||||
return <ZapNotification notification={notification} isNew={isNew} />
|
return <ZapNotification notification={notification} isNew={isNew} />
|
||||||
}
|
}
|
||||||
if (
|
|
||||||
notification.kind === ExtendedKind.COMMENT ||
|
|
||||||
notification.kind === ExtendedKind.VOICE_COMMENT
|
|
||||||
) {
|
|
||||||
return <CommentNotification notification={notification} isNew={isNew} />
|
|
||||||
}
|
|
||||||
if (notification.kind === ExtendedKind.POLL_RESPONSE) {
|
if (notification.kind === ExtendedKind.POLL_RESPONSE) {
|
||||||
return <PollResponseNotification notification={notification} isNew={isNew} />
|
return <PollResponseNotification notification={notification} isNew={isNew} />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,12 @@ const NotificationList = forwardRef((_, ref) => {
|
|||||||
const filterKinds = useMemo(() => {
|
const filterKinds = useMemo(() => {
|
||||||
switch (notificationType) {
|
switch (notificationType) {
|
||||||
case 'mentions':
|
case 'mentions':
|
||||||
return [kinds.ShortTextNote, ExtendedKind.COMMENT, ExtendedKind.VOICE_COMMENT]
|
return [
|
||||||
|
kinds.ShortTextNote,
|
||||||
|
ExtendedKind.COMMENT,
|
||||||
|
ExtendedKind.VOICE_COMMENT,
|
||||||
|
ExtendedKind.POLL
|
||||||
|
]
|
||||||
case 'reactions':
|
case 'reactions':
|
||||||
return [kinds.Reaction, kinds.Repost, ExtendedKind.POLL_RESPONSE]
|
return [kinds.Reaction, kinds.Repost, ExtendedKind.POLL_RESPONSE]
|
||||||
case 'zaps':
|
case 'zaps':
|
||||||
@@ -52,7 +57,8 @@ const NotificationList = forwardRef((_, ref) => {
|
|||||||
kinds.Zap,
|
kinds.Zap,
|
||||||
ExtendedKind.COMMENT,
|
ExtendedKind.COMMENT,
|
||||||
ExtendedKind.POLL_RESPONSE,
|
ExtendedKind.POLL_RESPONSE,
|
||||||
ExtendedKind.VOICE_COMMENT
|
ExtendedKind.VOICE_COMMENT,
|
||||||
|
ExtendedKind.POLL
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}, [notificationType])
|
}, [notificationType])
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ExtendedKind } from '@/constants'
|
import { EMBEDDED_MENTION_REGEX, ExtendedKind } from '@/constants'
|
||||||
import client from '@/services/client.service'
|
import client from '@/services/client.service'
|
||||||
import { TImageInfo } from '@/types'
|
import { TImageInfo } from '@/types'
|
||||||
import { LRUCache } from 'lru-cache'
|
import { LRUCache } from 'lru-cache'
|
||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
} from './tag'
|
} from './tag'
|
||||||
|
|
||||||
const EVENT_EMBEDDED_NOTES_CACHE = new LRUCache<string, string[]>({ max: 10000 })
|
const EVENT_EMBEDDED_NOTES_CACHE = new LRUCache<string, string[]>({ max: 10000 })
|
||||||
|
const EVENT_EMBEDDED_PUBKEYS_CACHE = new LRUCache<string, string[]>({ max: 10000 })
|
||||||
const EVENT_IS_REPLY_NOTE_CACHE = new LRUCache<string, boolean>({ max: 10000 })
|
const EVENT_IS_REPLY_NOTE_CACHE = new LRUCache<string, boolean>({ max: 10000 })
|
||||||
|
|
||||||
export function isNsfwEvent(event: Event) {
|
export function isNsfwEvent(event: Event) {
|
||||||
@@ -199,6 +200,28 @@ export function getEmbeddedNoteBech32Ids(event: Event) {
|
|||||||
return embeddedNoteBech32Ids
|
return embeddedNoteBech32Ids
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getEmbeddedPubkeys(event: Event) {
|
||||||
|
const cache = EVENT_EMBEDDED_PUBKEYS_CACHE.get(event.id)
|
||||||
|
if (cache) return cache
|
||||||
|
|
||||||
|
const embeddedPubkeySet = new Set<string>()
|
||||||
|
;(event.content.match(EMBEDDED_MENTION_REGEX) || []).forEach((mention) => {
|
||||||
|
try {
|
||||||
|
const { type, data } = nip19.decode(mention.split(':')[1])
|
||||||
|
if (type === 'npub') {
|
||||||
|
embeddedPubkeySet.add(data)
|
||||||
|
} else if (type === 'nprofile') {
|
||||||
|
embeddedPubkeySet.add(data.pubkey)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const embeddedPubkeys = Array.from(embeddedPubkeySet)
|
||||||
|
EVENT_EMBEDDED_PUBKEYS_CACHE.set(event.id, embeddedPubkeys)
|
||||||
|
return embeddedPubkeys
|
||||||
|
}
|
||||||
|
|
||||||
export function getLatestEvent(events: Event[]) {
|
export function getLatestEvent(events: Event[]) {
|
||||||
return events.sort((a, b) => b.created_at - a.created_at)[0]
|
return events.sort((a, b) => b.created_at - a.created_at)[0]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user