feat: broadcast

This commit is contained in:
codytseng 2025-08-02 16:04:27 +08:00
parent 5714fae7bd
commit 3f8a9e8efa
20 changed files with 683 additions and 232 deletions

View file

@ -1,32 +1,42 @@
import { Button } from '@/components/ui/button'
import { Drawer, DrawerContent, DrawerOverlay } from '@/components/ui/drawer'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger
} from '@/components/ui/dropdown-menu'
import { getNoteBech32Id } from '@/lib/event'
import { toNjump } from '@/lib/link'
import { pubkeyToNpub } from '@/lib/pubkey'
import { useMuteList } from '@/providers/MuteListProvider'
import { useNostr } from '@/providers/NostrProvider'
import { useScreenSize } from '@/providers/ScreenSizeProvider'
import { Bell, BellOff, Code, Copy, Ellipsis, Link } from 'lucide-react'
import { Ellipsis } from 'lucide-react'
import { Event } from 'nostr-tools'
import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useState } from 'react'
import { DesktopMenu } from './DesktopMenu'
import { MobileMenu } from './MobileMenu'
import RawEventDialog from './RawEventDialog'
import { SubMenuAction, useMenuActions } from './useMenuActions'
export default function NoteOptions({ event, className }: { event: Event; className?: string }) {
const { t } = useTranslation()
const { isSmallScreen } = useScreenSize()
const { pubkey } = useNostr()
const [isRawEventDialogOpen, setIsRawEventDialogOpen] = useState(false)
const [isDrawerOpen, setIsDrawerOpen] = useState(false)
const { mutePubkeyPublicly, mutePubkeyPrivately, unmutePubkey, mutePubkeys } = useMuteList()
const isMuted = useMemo(() => mutePubkeys.includes(event.pubkey), [mutePubkeys, event])
const [showSubMenu, setShowSubMenu] = useState(false)
const [activeSubMenu, setActiveSubMenu] = useState<SubMenuAction[]>([])
const [subMenuTitle, setSubMenuTitle] = useState('')
const closeDrawer = () => {
setIsDrawerOpen(false)
setShowSubMenu(false)
}
const goBackToMainMenu = () => {
setShowSubMenu(false)
}
const showSubMenuActions = (subMenu: SubMenuAction[], title: string) => {
setActiveSubMenu(subMenu)
setSubMenuTitle(title)
setShowSubMenu(true)
}
const menuActions = useMenuActions({
event,
closeDrawer,
showSubMenuActions,
setIsRawEventDialogOpen,
isSmallScreen
})
const trigger = (
<button
@ -37,175 +47,29 @@ export default function NoteOptions({ event, className }: { event: Event; classN
</button>
)
const rawEventDialog = (
<RawEventDialog
event={event}
isOpen={isRawEventDialogOpen}
onClose={() => setIsRawEventDialogOpen(false)}
/>
)
if (isSmallScreen) {
return (
<div className={className} onClick={(e) => e.stopPropagation()}>
{trigger}
<Drawer open={isDrawerOpen} onOpenChange={setIsDrawerOpen}>
<DrawerOverlay onClick={() => setIsDrawerOpen(false)} />
<DrawerContent hideOverlay>
<div className="py-2">
<Button
onClick={() => {
setIsDrawerOpen(false)
navigator.clipboard.writeText(getNoteBech32Id(event))
}}
className="w-full p-6 justify-start text-lg gap-4 [&_svg]:size-5"
variant="ghost"
>
<Copy />
{t('Copy event ID')}
</Button>
<Button
onClick={() => {
navigator.clipboard.writeText(pubkeyToNpub(event.pubkey) ?? '')
setIsDrawerOpen(false)
}}
className="w-full p-6 justify-start text-lg gap-4 [&_svg]:size-5"
variant="ghost"
>
<Copy />
{t('Copy user ID')}
</Button>
<Button
onClick={() => {
setIsDrawerOpen(false)
navigator.clipboard.writeText(toNjump(getNoteBech32Id(event)))
}}
className="w-full p-6 justify-start text-lg gap-4 [&_svg]:size-5"
variant="ghost"
>
<Link />
{t('Copy share link')}
</Button>
<Button
onClick={() => {
setIsDrawerOpen(false)
setIsRawEventDialogOpen(true)
}}
className="w-full p-6 justify-start text-lg gap-4 [&_svg]:size-5"
variant="ghost"
>
<Code />
{t('View raw event')}
</Button>
{pubkey &&
(isMuted ? (
<Button
onClick={() => {
setIsDrawerOpen(false)
unmutePubkey(event.pubkey)
}}
className="w-full p-6 justify-start text-destructive text-lg gap-4 [&_svg]:size-5 focus:text-destructive"
variant="ghost"
>
<Bell />
{t('Unmute user')}
</Button>
) : (
<>
<Button
onClick={() => {
setIsDrawerOpen(false)
mutePubkeyPrivately(event.pubkey)
}}
className="w-full p-6 justify-start text-destructive text-lg gap-4 [&_svg]:size-5 focus:text-destructive"
variant="ghost"
>
<BellOff />
{t('Mute user privately')}
</Button>
<Button
onClick={() => {
setIsDrawerOpen(false)
mutePubkeyPublicly(event.pubkey)
}}
className="w-full p-6 justify-start text-destructive text-lg gap-4 [&_svg]:size-5 focus:text-destructive"
variant="ghost"
>
<BellOff />
{t('Mute user publicly')}
</Button>
</>
))}
</div>
</DrawerContent>
</Drawer>
{rawEventDialog}
</div>
)
}
return (
<div className={className} onClick={(e) => e.stopPropagation()}>
<DropdownMenu>
<DropdownMenuTrigger asChild>{trigger}</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem onClick={() => navigator.clipboard.writeText(getNoteBech32Id(event))}>
<Copy />
{t('Copy event ID')}
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => navigator.clipboard.writeText(pubkeyToNpub(event.pubkey) ?? '')}
>
<Copy />
{t('Copy user ID')}
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => navigator.clipboard.writeText(toNjump(getNoteBech32Id(event)))}
>
<Link />
{t('Copy share link')}
</DropdownMenuItem>
{isSmallScreen ? (
<MobileMenu
menuActions={menuActions}
trigger={trigger}
isDrawerOpen={isDrawerOpen}
setIsDrawerOpen={setIsDrawerOpen}
showSubMenu={showSubMenu}
activeSubMenu={activeSubMenu}
subMenuTitle={subMenuTitle}
closeDrawer={closeDrawer}
goBackToMainMenu={goBackToMainMenu}
/>
) : (
<DesktopMenu menuActions={menuActions} trigger={trigger} />
)}
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => setIsRawEventDialogOpen(true)}>
<Code />
{t('View raw event')}
</DropdownMenuItem>
{pubkey && (
<>
<DropdownMenuSeparator />
{isMuted ? (
<DropdownMenuItem
onClick={() => unmutePubkey(event.pubkey)}
className="text-destructive focus:text-destructive"
>
<Bell />
{t('Unmute user')}
</DropdownMenuItem>
) : (
<>
<DropdownMenuItem
onClick={() => mutePubkeyPrivately(event.pubkey)}
className="text-destructive focus:text-destructive"
>
<BellOff />
{t('Mute user privately')}
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => mutePubkeyPublicly(event.pubkey)}
className="text-destructive focus:text-destructive"
>
<BellOff />
{t('Mute user publicly')}
</DropdownMenuItem>
</>
)}
</>
)}
</DropdownMenuContent>
</DropdownMenu>
{rawEventDialog}
<RawEventDialog
event={event}
isOpen={isRawEventDialogOpen}
onClose={() => setIsRawEventDialogOpen(false)}
/>
</div>
)
}