diff --git a/src/components/NoteCard/GenericNoteCard.tsx b/src/components/NoteCard/GenericNoteCard.tsx index 55a3c022..3899fb1d 100644 --- a/src/components/NoteCard/GenericNoteCard.tsx +++ b/src/components/NoteCard/GenericNoteCard.tsx @@ -1,10 +1,13 @@ import { GROUP_METADATA_EVENT_KIND } from '@/constants' import { isSupportedKind } from '@/lib/event' +import { useMuteList } from '@/providers/MuteListProvider' import { Event, kinds } from 'nostr-tools' +import { useState } from 'react' import GroupMetadataCard from './GroupMetadataCard' import LiveEventCard from './LiveEventCard' import LongFormArticleCard from './LongFormArticleCard' import MainNoteCard from './MainNoteCard' +import MutedNoteCard from './MutedNoteCard' import UnknownNoteCard from './UnknownNoteCard' export default function GenericNoteCard({ @@ -20,6 +23,21 @@ export default function GenericNoteCard({ embedded?: boolean originalNoteId?: string }) { + const [showMuted, setShowMuted] = useState(false) + const { mutePubkeys } = useMuteList() + + if (mutePubkeys.includes(event.pubkey) && !showMuted) { + return ( + setShowMuted(true)} + /> + ) + } + if (isSupportedKind(event.kind)) { return ( diff --git a/src/components/NoteCard/MutedNoteCard.tsx b/src/components/NoteCard/MutedNoteCard.tsx new file mode 100644 index 00000000..53b73075 --- /dev/null +++ b/src/components/NoteCard/MutedNoteCard.tsx @@ -0,0 +1,63 @@ +import { Button } from '@/components/ui/button' +import { Separator } from '@/components/ui/separator' +import { cn } from '@/lib/utils' +import { Eye } from 'lucide-react' +import { Event } from 'nostr-tools' +import { useTranslation } from 'react-i18next' +import { FormattedTimestamp } from '../FormattedTimestamp' +import UserAvatar from '../UserAvatar' +import Username from '../Username' +import RepostDescription from './RepostDescription' + +export default function MutedNoteCard({ + event, + show, + reposter, + embedded, + className +}: { + event: Event + show: () => void + reposter?: string + embedded?: boolean + className?: string +}) { + const { t } = useTranslation() + + return ( +
+
+ +
+ +
+ +
+ +
+
+
+
+
{t('This user is muted')}
+ +
+
+ {!embedded && } +
+ ) +} diff --git a/src/components/NoteCard/UnknownNoteCard.tsx b/src/components/NoteCard/UnknownNoteCard.tsx index c579f149..ae86a655 100644 --- a/src/components/NoteCard/UnknownNoteCard.tsx +++ b/src/components/NoteCard/UnknownNoteCard.tsx @@ -5,11 +5,11 @@ import { cn } from '@/lib/utils' import { Check, Copy } from 'lucide-react' import { Event } from 'nostr-tools' import { useState } from 'react' -import RepostDescription from './RepostDescription' +import { useTranslation } from 'react-i18next' +import { FormattedTimestamp } from '../FormattedTimestamp' import UserAvatar from '../UserAvatar' import Username from '../Username' -import { FormattedTimestamp } from '../FormattedTimestamp' -import { useTranslation } from 'react-i18next' +import RepostDescription from './RepostDescription' export default function UnknownNoteCard({ event, @@ -44,7 +44,7 @@ export default function UnknownNoteCard({ -
+
{t('Cannot handle event of kind k', { k: event.kind })}
diff --git a/src/components/NoteStats/NoteOptions/index.tsx b/src/components/NoteStats/NoteOptions/index.tsx index 6c3aa6a8..fbe13d62 100644 --- a/src/components/NoteStats/NoteOptions/index.tsx +++ b/src/components/NoteStats/NoteOptions/index.tsx @@ -5,11 +5,12 @@ import { DropdownMenuTrigger } from '@/components/ui/dropdown-menu' import { getSharableEventId } from '@/lib/event' +import { pubkeyToNpub } from '@/lib/pubkey' import { useMuteList } from '@/providers/MuteListProvider' import { useNostr } from '@/providers/NostrProvider' -import { BellOff, Code, Copy, Ellipsis } from 'lucide-react' +import { Bell, BellOff, Code, Copy, Ellipsis } from 'lucide-react' import { Event } from 'nostr-tools' -import { useState } from 'react' +import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import RawEventDialog from './RawEventDialog' @@ -17,7 +18,8 @@ export default function NoteOptions({ event }: { event: Event }) { const { t } = useTranslation() const { pubkey } = useNostr() const [isRawEventDialogOpen, setIsRawEventDialogOpen] = useState(false) - const { mutePubkey } = useMuteList() + const { mutePubkey, unmutePubkey, mutePubkeys } = useMuteList() + const isMuted = useMemo(() => mutePubkeys.includes(event.pubkey), [mutePubkeys, event]) return (
e.stopPropagation()}> @@ -30,22 +32,28 @@ export default function NoteOptions({ event }: { event: Event }) { navigator.clipboard.writeText('nostr:' + getSharableEventId(event))} + onClick={() => navigator.clipboard.writeText(getSharableEventId(event))} > - {t('copy embedded code')} + {t('Copy event ID')} + + navigator.clipboard.writeText(pubkeyToNpub(event.pubkey) ?? '')} + > + + {t('Copy user ID')} setIsRawEventDialogOpen(true)}> - {t('raw event')} + {t('View raw event')} {pubkey && ( mutePubkey(event.pubkey)} + onClick={() => (isMuted ? unmutePubkey(event.pubkey) : mutePubkey(event.pubkey))} className="text-destructive focus:text-destructive" > - - {t('mute author')} + {isMuted ? : } + {isMuted ? t('Unmute user') : t('Mute user')} )} diff --git a/src/components/NotificationList/index.tsx b/src/components/NotificationList/index.tsx index a9343d05..e0d4c6e7 100644 --- a/src/components/NotificationList/index.tsx +++ b/src/components/NotificationList/index.tsx @@ -5,6 +5,7 @@ import { extractEmbeddedNotesFromContent, extractImagesFromContent } from '@/lib import { toNote } from '@/lib/link' import { tagNameEquals } from '@/lib/tag' import { useSecondaryPage } from '@/PageManager' +import { useMuteList } from '@/providers/MuteListProvider' import { useNostr } from '@/providers/NostrProvider' import client from '@/services/client.service' import dayjs from 'dayjs' @@ -185,6 +186,10 @@ NotificationList.displayName = 'NotificationList' export default NotificationList function NotificationItem({ notification }: { notification: Event }) { + const { mutePubkeys } = useMuteList() + if (mutePubkeys.includes(notification.pubkey)) { + return null + } if (notification.kind === kinds.Reaction) { return } diff --git a/src/components/ParentNotePreview/index.tsx b/src/components/ParentNotePreview/index.tsx index d3f4e4e4..4e59673e 100644 --- a/src/components/ParentNotePreview/index.tsx +++ b/src/components/ParentNotePreview/index.tsx @@ -1,5 +1,7 @@ import { cn } from '@/lib/utils' +import { useMuteList } from '@/providers/MuteListProvider' import { Event } from 'nostr-tools' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import UserAvatar from '../UserAvatar' @@ -13,6 +15,9 @@ export default function ParentNotePreview({ onClick?: React.MouseEventHandler | undefined }) { const { t } = useTranslation() + const { mutePubkeys } = useMuteList() + const isMuted = useMemo(() => mutePubkeys.includes(event.pubkey), [mutePubkeys, event]) + return (
{t('reply to')}
-
{event.content}
+ {isMuted ? ( +
{t('[muted]')}
+ ) : ( +
{event.content}
+ )}
) } diff --git a/src/components/ProfileOptions/index.tsx b/src/components/ProfileOptions/index.tsx index 4d0c020e..0b54840b 100644 --- a/src/components/ProfileOptions/index.tsx +++ b/src/components/ProfileOptions/index.tsx @@ -30,7 +30,7 @@ export default function ProfileOptions({ pubkey }: { pubkey: string }) { onClick={() => navigator.clipboard.writeText('nostr:' + pubkeyToNpub(pubkey))} > - {t('copy embedded code')} + {t('Copy user ID')} {mutePubkeys.includes(pubkey) ? ( - {t('unmute user')} + {t('Unmute user')} ) : ( - {t('mute user')} + {t('Mute user')} )} diff --git a/src/components/ReplyNote/index.tsx b/src/components/ReplyNote/index.tsx index a64be3e5..30fcb712 100644 --- a/src/components/ReplyNote/index.tsx +++ b/src/components/ReplyNote/index.tsx @@ -1,4 +1,8 @@ +import { Button } from '@/components/ui/button' +import { useMuteList } from '@/providers/MuteListProvider' import { Event } from 'nostr-tools' +import { useMemo, useState } from 'react' +import { useTranslation } from 'react-i18next' import Content from '../Content' import { FormattedTimestamp } from '../FormattedTimestamp' import NoteStats from '../NoteStats' @@ -17,6 +21,14 @@ export default function ReplyNote({ onClickParent?: (eventId: string) => void highlight?: boolean }) { + const { t } = useTranslation() + const { mutePubkeys } = useMuteList() + const [showMuted, setShowMuted] = useState(false) + const show = useMemo( + () => showMuted || !mutePubkeys.includes(event.pubkey), + [showMuted, mutePubkeys, event] + ) + return (
)} - - + {show ? ( + <> + + + + ) : ( + + )}
) diff --git a/src/i18n/en.ts b/src/i18n/en.ts index 6ea6a4ce..f62390bf 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -41,8 +41,9 @@ export default { 'Your post has been published': 'Your post has been published', Repost: 'Repost', Quote: 'Quote', - 'copy embedded code': 'copy embedded code', - 'raw event': 'raw event', + 'Copy event ID': 'Copy event ID', + 'Copy user ID': 'Copy user ID', + 'View raw event': 'View raw event', Like: 'Like', 'switch to light theme': 'switch to light theme', 'switch to dark theme': 'switch to dark theme', @@ -153,9 +154,8 @@ export default { Mute: 'Mute', Muted: 'Muted', Unmute: 'Unmute', - 'mute author': 'mute author', - 'mute user': 'mute user', - 'unmute user': 'unmute user', + 'Mute user': 'Mute user', + 'Unmute user': 'Unmute user', 'Append n relays': 'Append {{n}} relays', Append: 'Append', 'Select relays to append': 'Select relays to append', diff --git a/src/i18n/zh.ts b/src/i18n/zh.ts index db545e44..8fe62d5d 100644 --- a/src/i18n/zh.ts +++ b/src/i18n/zh.ts @@ -41,8 +41,9 @@ export default { 'Your post has been published': '您的笔记已发布', Repost: '转发', Quote: '引用', - 'copy embedded code': '复制嵌入代码', - 'raw event': '原始事件', + 'Copy event ID': '复制事件 ID', + 'Copy user ID': '复制用户 ID', + 'View raw event': '查看原始事件', Like: '点赞', 'switch to light theme': '切换到浅色主题', 'switch to dark theme': '切换到深色主题', @@ -154,9 +155,8 @@ export default { Mute: '屏蔽', Muted: '已屏蔽', Unmute: '取消屏蔽', - 'mute author': '屏蔽作者', - 'mute user': '屏蔽用户', - 'unmute user': '取消屏蔽用户', + 'Mute user': '屏蔽用户', + 'Unmute user': '取消屏蔽用户', 'Append n relays': '追加 {{n}} 个服务器', Append: '追加', 'Select relays to append': '选择要追加的服务器', diff --git a/src/lib/draft-event.ts b/src/lib/draft-event.ts index bd8e8041..d884a7ce 100644 --- a/src/lib/draft-event.ts +++ b/src/lib/draft-event.ts @@ -38,7 +38,7 @@ export function createReactionDraftEvent(event: Event): TDraftEvent { export function createRepostDraftEvent(event: Event): TDraftEvent { const isProtected = isProtectedEvent(event) const tags = [ - ['e', event.id, client.getEventHint(event.id), 'mentions', event.pubkey], + ['e', event.id, client.getEventHint(event.id), '', event.pubkey], ['p', event.pubkey] ]