style: adjust the style of NoteStats (#222)
This commit is contained in:
@@ -167,14 +167,16 @@ export default function Nip22ReplyNoteList({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{(loading || until) && (
|
||||||
<div
|
<div
|
||||||
className={`text-sm text-center text-muted-foreground ${!loading ? 'hover:text-foreground cursor-pointer' : ''}`}
|
className={`text-sm text-center text-muted-foreground mt-2 ${!loading ? 'hover:text-foreground cursor-pointer' : ''}`}
|
||||||
onClick={loadMore}
|
onClick={loadMore}
|
||||||
>
|
>
|
||||||
{loading ? t('loading...') : until ? t('load more older replies') : null}
|
{loading ? t('loading...') : t('load more older replies')}
|
||||||
</div>
|
</div>
|
||||||
{replies.length > 0 && (loading || until) && <Separator className="my-2" />}
|
)}
|
||||||
<div className={cn('mb-4', className)}>
|
{replies.length > 0 && (loading || until) && <Separator className="mt-2" />}
|
||||||
|
<div className={cn('mb-2', className)}>
|
||||||
{replies.map((reply) => {
|
{replies.map((reply) => {
|
||||||
const info = replyMap[reply.id]
|
const info = replyMap[reply.id]
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { Event } from 'nostr-tools'
|
|||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import Content from '../Content'
|
import Content from '../Content'
|
||||||
import { FormattedTimestamp } from '../FormattedTimestamp'
|
import { FormattedTimestamp } from '../FormattedTimestamp'
|
||||||
|
import NoteOptions from '../NoteOptions'
|
||||||
import NoteStats from '../NoteStats'
|
import NoteStats from '../NoteStats'
|
||||||
import ParentNotePreview from '../ParentNotePreview'
|
import ParentNotePreview from '../ParentNotePreview'
|
||||||
import UserAvatar from '../UserAvatar'
|
import UserAvatar from '../UserAvatar'
|
||||||
@@ -36,7 +37,8 @@ export default function Note({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex justify-between items-start gap-2">
|
||||||
|
<div className="flex items-center space-x-2 flex-1">
|
||||||
<UserAvatar userId={event.pubkey} size={size === 'small' ? 'small' : 'normal'} />
|
<UserAvatar userId={event.pubkey} size={size === 'small' ? 'small' : 'normal'} />
|
||||||
<div
|
<div
|
||||||
className={`flex-1 w-0 ${size === 'small' ? 'flex space-x-2 items-center overflow-hidden' : ''}`}
|
className={`flex-1 w-0 ${size === 'small' ? 'flex space-x-2 items-center overflow-hidden' : ''}`}
|
||||||
@@ -56,6 +58,8 @@ export default function Note({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{size === 'normal' && <NoteOptions event={event} className="shrink-0 [&_svg]:size-5" />}
|
||||||
|
</div>
|
||||||
{parentEventId && (
|
{parentEventId && (
|
||||||
<ParentNotePreview
|
<ParentNotePreview
|
||||||
event={parentEvent}
|
event={parentEvent}
|
||||||
|
|||||||
152
src/components/NoteOptions/index.tsx
Normal file
152
src/components/NoteOptions/index.tsx
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
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 { getSharableEventId } from '@/lib/event'
|
||||||
|
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 } from 'lucide-react'
|
||||||
|
import { Event } from 'nostr-tools'
|
||||||
|
import { useMemo, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import RawEventDialog from './RawEventDialog'
|
||||||
|
|
||||||
|
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 { mutePubkey, unmutePubkey, mutePubkeys } = useMuteList()
|
||||||
|
const isMuted = useMemo(() => mutePubkeys.includes(event.pubkey), [mutePubkeys, event])
|
||||||
|
|
||||||
|
const trigger = (
|
||||||
|
<button
|
||||||
|
className="flex items-center text-muted-foreground hover:text-foreground pl-3 h-full"
|
||||||
|
onClick={() => setIsDrawerOpen(true)}
|
||||||
|
>
|
||||||
|
<Ellipsis />
|
||||||
|
</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(getSharableEventId(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)
|
||||||
|
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 && (
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setIsDrawerOpen(false)
|
||||||
|
if (isMuted) {
|
||||||
|
unmutePubkey(event.pubkey)
|
||||||
|
} else {
|
||||||
|
mutePubkey(event.pubkey)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="w-full p-6 justify-start text-destructive text-lg gap-4 [&_svg]:size-5 focus:text-destructive"
|
||||||
|
variant="ghost"
|
||||||
|
>
|
||||||
|
{isMuted ? <Bell /> : <BellOff />}
|
||||||
|
{isMuted ? t('Unmute user') : t('Mute user')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</DrawerContent>
|
||||||
|
</Drawer>
|
||||||
|
{rawEventDialog}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={className} onClick={(e) => e.stopPropagation()}>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>{trigger}</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent collisionPadding={8} className="min-w-52">
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => navigator.clipboard.writeText(getSharableEventId(event))}
|
||||||
|
>
|
||||||
|
<Copy />
|
||||||
|
{t('Copy event ID')}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => navigator.clipboard.writeText(pubkeyToNpub(event.pubkey) ?? '')}
|
||||||
|
>
|
||||||
|
<Copy />
|
||||||
|
{t('Copy user ID')}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem onClick={() => setIsRawEventDialogOpen(true)}>
|
||||||
|
<Code />
|
||||||
|
{t('View raw event')}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
{pubkey && (
|
||||||
|
<>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => (isMuted ? unmutePubkey(event.pubkey) : mutePubkey(event.pubkey))}
|
||||||
|
className="text-destructive focus:text-destructive"
|
||||||
|
>
|
||||||
|
{isMuted ? <Bell /> : <BellOff />}
|
||||||
|
{isMuted ? t('Unmute user') : t('Mute user')}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
{rawEventDialog}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -55,7 +55,7 @@ export default function LikeButton({ event }: { event: Event }) {
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center enabled:hover:text-red-400 gap-1',
|
'flex items-center enabled:hover:text-red-400 gap-1 px-3 h-full',
|
||||||
hasLiked ? 'text-red-400' : 'text-muted-foreground'
|
hasLiked ? 'text-red-400' : 'text-muted-foreground'
|
||||||
)}
|
)}
|
||||||
onClick={like}
|
onClick={like}
|
||||||
@@ -63,9 +63,9 @@ export default function LikeButton({ event }: { event: Event }) {
|
|||||||
title={t('Like')}
|
title={t('Like')}
|
||||||
>
|
>
|
||||||
{liking ? (
|
{liking ? (
|
||||||
<Loader className="animate-spin" size={16} />
|
<Loader className="animate-spin" />
|
||||||
) : (
|
) : (
|
||||||
<Heart size={16} className={hasLiked ? 'fill-red-400' : ''} />
|
<Heart className={hasLiked ? 'fill-red-400' : ''} />
|
||||||
)}
|
)}
|
||||||
{!!likeCount && <div className="text-sm">{formatCount(likeCount)}</div>}
|
{!!likeCount && <div className="text-sm">{formatCount(likeCount)}</div>}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
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 { Bell, BellOff, Code, Copy, Ellipsis } from 'lucide-react'
|
|
||||||
import { Event } from 'nostr-tools'
|
|
||||||
import { useMemo, useState } from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import RawEventDialog from './RawEventDialog'
|
|
||||||
|
|
||||||
export default function NoteOptions({ event }: { event: Event }) {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
const { pubkey } = useNostr()
|
|
||||||
const [isRawEventDialogOpen, setIsRawEventDialogOpen] = useState(false)
|
|
||||||
const { mutePubkey, unmutePubkey, mutePubkeys } = useMuteList()
|
|
||||||
const isMuted = useMemo(() => mutePubkeys.includes(event.pubkey), [mutePubkeys, event])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="h-4" onClick={(e) => e.stopPropagation()}>
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger>
|
|
||||||
<Ellipsis
|
|
||||||
size={16}
|
|
||||||
className="text-muted-foreground hover:text-foreground cursor-pointer"
|
|
||||||
/>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent collisionPadding={8}>
|
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={() => navigator.clipboard.writeText(getSharableEventId(event))}
|
|
||||||
>
|
|
||||||
<Copy />
|
|
||||||
{t('Copy event ID')}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={() => navigator.clipboard.writeText(pubkeyToNpub(event.pubkey) ?? '')}
|
|
||||||
>
|
|
||||||
<Copy />
|
|
||||||
{t('Copy user ID')}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem onClick={() => setIsRawEventDialogOpen(true)}>
|
|
||||||
<Code />
|
|
||||||
{t('View raw event')}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
{pubkey && (
|
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={() => (isMuted ? unmutePubkey(event.pubkey) : mutePubkey(event.pubkey))}
|
|
||||||
className="text-destructive focus:text-destructive"
|
|
||||||
>
|
|
||||||
{isMuted ? <Bell /> : <BellOff />}
|
|
||||||
{isMuted ? t('Unmute user') : t('Mute user')}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
)}
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
<RawEventDialog
|
|
||||||
event={event}
|
|
||||||
isOpen={isRawEventDialogOpen}
|
|
||||||
onClose={() => setIsRawEventDialogOpen(false)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -26,7 +26,7 @@ export default function ReplyButton({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
className="flex gap-1 items-center text-muted-foreground enabled:hover:text-blue-400"
|
className="flex gap-1 items-center text-muted-foreground enabled:hover:text-blue-400 pr-3 h-full"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
checkLogin(() => {
|
checkLogin(() => {
|
||||||
@@ -35,7 +35,7 @@ export default function ReplyButton({
|
|||||||
}}
|
}}
|
||||||
title={t('Reply')}
|
title={t('Reply')}
|
||||||
>
|
>
|
||||||
<MessageCircle size={16} />
|
<MessageCircle />
|
||||||
{variant !== 'reply' && !!replyCount && (
|
{variant !== 'reply' && !!replyCount && (
|
||||||
<div className="text-sm">{formatCount(replyCount)}</div>
|
<div className="text-sm">{formatCount(replyCount)}</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { Drawer, DrawerContent, DrawerOverlay } from '@/components/ui/drawer'
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -9,6 +11,7 @@ import { getSharableEventId } from '@/lib/event'
|
|||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { useNostr } from '@/providers/NostrProvider'
|
import { useNostr } from '@/providers/NostrProvider'
|
||||||
import { useNoteStats } from '@/providers/NoteStatsProvider'
|
import { useNoteStats } from '@/providers/NoteStatsProvider'
|
||||||
|
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
||||||
import client from '@/services/client.service'
|
import client from '@/services/client.service'
|
||||||
import { Loader, PencilLine, Repeat } from 'lucide-react'
|
import { Loader, PencilLine, Repeat } from 'lucide-react'
|
||||||
import { Event } from 'nostr-tools'
|
import { Event } from 'nostr-tools'
|
||||||
@@ -19,10 +22,12 @@ import { formatCount } from './utils'
|
|||||||
|
|
||||||
export default function RepostButton({ event }: { event: Event }) {
|
export default function RepostButton({ event }: { event: Event }) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { isSmallScreen } = useScreenSize()
|
||||||
const { publish, checkLogin, pubkey } = useNostr()
|
const { publish, checkLogin, pubkey } = useNostr()
|
||||||
const { noteStatsMap, updateNoteStatsByEvents, fetchNoteStats } = useNoteStats()
|
const { noteStatsMap, updateNoteStatsByEvents, fetchNoteStats } = useNoteStats()
|
||||||
const [reposting, setReposting] = useState(false)
|
const [reposting, setReposting] = useState(false)
|
||||||
const [isPostDialogOpen, setIsPostDialogOpen] = useState(false)
|
const [isPostDialogOpen, setIsPostDialogOpen] = useState(false)
|
||||||
|
const [isDrawerOpen, setIsDrawerOpen] = useState(false)
|
||||||
const { repostCount, hasReposted } = useMemo(() => {
|
const { repostCount, hasReposted } = useMemo(() => {
|
||||||
const stats = noteStatsMap.get(event.id) || {}
|
const stats = noteStatsMap.get(event.id) || {}
|
||||||
return {
|
return {
|
||||||
@@ -62,22 +67,74 @@ export default function RepostButton({ event }: { event: Event }) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const trigger = (
|
||||||
<>
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<button
|
<button
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex gap-1 items-center enabled:hover:text-lime-500',
|
'flex gap-1 items-center enabled:hover:text-lime-500 px-3 h-full',
|
||||||
hasReposted ? 'text-lime-500' : 'text-muted-foreground'
|
hasReposted ? 'text-lime-500' : 'text-muted-foreground'
|
||||||
)}
|
)}
|
||||||
title={t('Repost')}
|
title={t('Repost')}
|
||||||
|
onClick={() => {
|
||||||
|
if (isSmallScreen) {
|
||||||
|
setIsDrawerOpen(true)
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{reposting ? <Loader className="animate-spin" size={16} /> : <Repeat size={16} />}
|
{reposting ? <Loader className="animate-spin" /> : <Repeat />}
|
||||||
{!!repostCount && <div className="text-sm">{formatCount(repostCount)}</div>}
|
{!!repostCount && <div className="text-sm">{formatCount(repostCount)}</div>}
|
||||||
</button>
|
</button>
|
||||||
</DropdownMenuTrigger>
|
)
|
||||||
<DropdownMenuContent>
|
|
||||||
|
const postEditor = (
|
||||||
|
<PostEditor
|
||||||
|
open={isPostDialogOpen}
|
||||||
|
setOpen={setIsPostDialogOpen}
|
||||||
|
defaultContent={'\nnostr:' + getSharableEventId(event)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isSmallScreen) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{trigger}
|
||||||
|
<Drawer open={isDrawerOpen} onOpenChange={setIsDrawerOpen}>
|
||||||
|
<DrawerOverlay onClick={() => setIsDrawerOpen(false)} />
|
||||||
|
<DrawerContent hideOverlay>
|
||||||
|
<div className="py-2">
|
||||||
|
<Button
|
||||||
|
onClick={repost}
|
||||||
|
disabled={!canRepost}
|
||||||
|
className="w-full p-6 justify-start text-lg gap-4 [&_svg]:size-5"
|
||||||
|
variant="ghost"
|
||||||
|
>
|
||||||
|
<Repeat /> {t('Repost')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
checkLogin(() => {
|
||||||
|
setIsDrawerOpen(false)
|
||||||
|
setIsPostDialogOpen(true)
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
className="w-full p-6 justify-start text-lg gap-4 [&_svg]:size-5"
|
||||||
|
variant="ghost"
|
||||||
|
>
|
||||||
|
<PencilLine /> {t('Quote')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DrawerContent>
|
||||||
|
</Drawer>
|
||||||
|
{postEditor}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>{trigger}</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent className="min-w-44">
|
||||||
<DropdownMenuItem onClick={repost} disabled={!canRepost}>
|
<DropdownMenuItem onClick={repost} disabled={!canRepost}>
|
||||||
<Repeat /> {t('Repost')}
|
<Repeat /> {t('Repost')}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
@@ -93,11 +150,7 @@ export default function RepostButton({ event }: { event: Event }) {
|
|||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
<PostEditor
|
{postEditor}
|
||||||
open={isPostDialogOpen}
|
|
||||||
setOpen={setIsPostDialogOpen}
|
|
||||||
defaultContent={'\nnostr:' + getSharableEventId(event)}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { useSecondaryPage } from '@/PageManager'
|
import { useSecondaryPage } from '@/PageManager'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { Drawer, DrawerContent, DrawerOverlay } from '@/components/ui/drawer'
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -9,39 +11,78 @@ import {
|
|||||||
} from '@/components/ui/dropdown-menu'
|
} from '@/components/ui/dropdown-menu'
|
||||||
import { toRelay } from '@/lib/link'
|
import { toRelay } from '@/lib/link'
|
||||||
import { simplifyUrl } from '@/lib/url'
|
import { simplifyUrl } from '@/lib/url'
|
||||||
|
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
||||||
import client from '@/services/client.service'
|
import client from '@/services/client.service'
|
||||||
import { Server } from 'lucide-react'
|
import { Server } from 'lucide-react'
|
||||||
import { Event } from 'nostr-tools'
|
import { Event } from 'nostr-tools'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import RelayIcon from '../RelayIcon'
|
||||||
|
|
||||||
export default function SeenOnButton({ event }: { event: Event }) {
|
export default function SeenOnButton({ event }: { event: Event }) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { isSmallScreen } = useScreenSize()
|
||||||
const { push } = useSecondaryPage()
|
const { push } = useSecondaryPage()
|
||||||
const [relays, setRelays] = useState<string[]>([])
|
const [relays, setRelays] = useState<string[]>([])
|
||||||
|
const [isDrawerOpen, setIsDrawerOpen] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const seenOn = client.getSeenEventRelayUrls(event.id)
|
const seenOn = client.getSeenEventRelayUrls(event.id)
|
||||||
setRelays(seenOn)
|
setRelays(seenOn)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
const trigger = (
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<button
|
<button
|
||||||
className="flex gap-1 items-center text-muted-foreground enabled:hover:text-primary"
|
className="flex gap-1 items-center text-muted-foreground enabled:hover:text-primary pl-3 h-full"
|
||||||
title={t('Seen on')}
|
title={t('Seen on')}
|
||||||
disabled={relays.length === 0}
|
disabled={relays.length === 0}
|
||||||
|
onClick={() => {
|
||||||
|
if (isSmallScreen) {
|
||||||
|
setIsDrawerOpen(true)
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Server size={16} />
|
<Server />
|
||||||
{relays.length > 0 && <div className="text-sm">{relays.length}</div>}
|
{relays.length > 0 && <div className="text-sm">{relays.length}</div>}
|
||||||
</button>
|
</button>
|
||||||
</DropdownMenuTrigger>
|
)
|
||||||
|
|
||||||
|
if (isSmallScreen) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{trigger}
|
||||||
|
<Drawer open={isDrawerOpen} onOpenChange={setIsDrawerOpen}>
|
||||||
|
<DrawerOverlay onClick={() => setIsDrawerOpen(false)} />
|
||||||
|
<DrawerContent hideOverlay>
|
||||||
|
<div className="py-2">
|
||||||
|
{relays.map((relay) => (
|
||||||
|
<Button
|
||||||
|
className="w-full p-6 justify-start text-lg gap-4"
|
||||||
|
variant="ghost"
|
||||||
|
key={relay}
|
||||||
|
onClick={() => {
|
||||||
|
setIsDrawerOpen(false)
|
||||||
|
push(toRelay(relay))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RelayIcon url={relay} /> {simplifyUrl(relay)}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</DrawerContent>
|
||||||
|
</Drawer>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>{trigger}</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent collisionPadding={8}>
|
<DropdownMenuContent collisionPadding={8}>
|
||||||
<DropdownMenuLabel>{t('Seen on')}</DropdownMenuLabel>
|
<DropdownMenuLabel>{t('Seen on')}</DropdownMenuLabel>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
{relays.map((relay) => (
|
{relays.map((relay) => (
|
||||||
<DropdownMenuItem key={relay} onClick={() => push(toRelay(relay))}>
|
<DropdownMenuItem key={relay} onClick={() => push(toRelay(relay))} className="min-w-52">
|
||||||
|
<RelayIcon url={relay} />
|
||||||
{simplifyUrl(relay)}
|
{simplifyUrl(relay)}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export default function ZapButton({ event }: { event: Event }) {
|
|||||||
const { checkLogin, pubkey } = useNostr()
|
const { checkLogin, pubkey } = useNostr()
|
||||||
const { noteStatsMap, addZap } = useNoteStats()
|
const { noteStatsMap, addZap } = useNoteStats()
|
||||||
const { defaultZapSats, defaultZapComment, quickZap } = useZap()
|
const { defaultZapSats, defaultZapComment, quickZap } = useZap()
|
||||||
|
const [touchStart, setTouchStart] = useState<{ x: number; y: number } | null>(null)
|
||||||
const [openZapDialog, setOpenZapDialog] = useState(false)
|
const [openZapDialog, setOpenZapDialog] = useState(false)
|
||||||
const [zapping, setZapping] = useState(false)
|
const [zapping, setZapping] = useState(false)
|
||||||
const { zapAmount, hasZapped } = useMemo(() => {
|
const { zapAmount, hasZapped } = useMemo(() => {
|
||||||
@@ -71,6 +72,11 @@ export default function ZapButton({ event }: { event: Event }) {
|
|||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
isLongPressRef.current = false
|
isLongPressRef.current = false
|
||||||
|
|
||||||
|
if ('touches' in e) {
|
||||||
|
const touch = e.touches[0]
|
||||||
|
setTouchStart({ x: touch.clientX, y: touch.clientY })
|
||||||
|
}
|
||||||
|
|
||||||
if (quickZap) {
|
if (quickZap) {
|
||||||
timerRef.current = setTimeout(() => {
|
timerRef.current = setTimeout(() => {
|
||||||
isLongPressRef.current = true
|
isLongPressRef.current = true
|
||||||
@@ -89,6 +95,15 @@ export default function ZapButton({ event }: { event: Event }) {
|
|||||||
clearTimeout(timerRef.current)
|
clearTimeout(timerRef.current)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ('touches' in e) {
|
||||||
|
setTouchStart(null)
|
||||||
|
if (!touchStart) return
|
||||||
|
const touch = e.changedTouches[0]
|
||||||
|
const diffX = Math.abs(touch.clientX - touchStart.x)
|
||||||
|
const diffY = Math.abs(touch.clientY - touchStart.y)
|
||||||
|
if (diffX > 10 || diffY > 10) return
|
||||||
|
}
|
||||||
|
|
||||||
if (!quickZap) {
|
if (!quickZap) {
|
||||||
checkLogin(() => {
|
checkLogin(() => {
|
||||||
setOpenZapDialog(true)
|
setOpenZapDialog(true)
|
||||||
@@ -110,7 +125,7 @@ export default function ZapButton({ event }: { event: Event }) {
|
|||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center enabled:hover:text-yellow-400 gap-1 select-none',
|
'flex items-center enabled:hover:text-yellow-400 gap-1 select-none px-3 h-full',
|
||||||
hasZapped ? 'text-yellow-400' : 'text-muted-foreground'
|
hasZapped ? 'text-yellow-400' : 'text-muted-foreground'
|
||||||
)}
|
)}
|
||||||
title={t('Zap')}
|
title={t('Zap')}
|
||||||
@@ -121,9 +136,9 @@ export default function ZapButton({ event }: { event: Event }) {
|
|||||||
onTouchEnd={handleClickEnd}
|
onTouchEnd={handleClickEnd}
|
||||||
>
|
>
|
||||||
{zapping ? (
|
{zapping ? (
|
||||||
<Loader className="animate-spin" size={16} />
|
<Loader className="animate-spin" />
|
||||||
) : (
|
) : (
|
||||||
<Zap size={16} className={hasZapped ? 'fill-yellow-400' : ''} />
|
<Zap className={hasZapped ? 'fill-yellow-400' : ''} />
|
||||||
)}
|
)}
|
||||||
{!!zapAmount && <div className="text-sm">{formatAmount(zapAmount)}</div>}
|
{!!zapAmount && <div className="text-sm">{formatAmount(zapAmount)}</div>}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { useNoteStats } from '@/providers/NoteStatsProvider'
|
import { useNoteStats } from '@/providers/NoteStatsProvider'
|
||||||
|
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
||||||
import { Event } from 'nostr-tools'
|
import { Event } from 'nostr-tools'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import LikeButton from './LikeButton'
|
import LikeButton from './LikeButton'
|
||||||
import NoteOptions from './NoteOptions'
|
|
||||||
import ReplyButton from './ReplyButton'
|
import ReplyButton from './ReplyButton'
|
||||||
import RepostButton from './RepostButton'
|
import RepostButton from './RepostButton'
|
||||||
import SeenOnButton from './SeenOnButton'
|
import SeenOnButton from './SeenOnButton'
|
||||||
@@ -13,14 +13,19 @@ import ZapButton from './ZapButton'
|
|||||||
export default function NoteStats({
|
export default function NoteStats({
|
||||||
event,
|
event,
|
||||||
className,
|
className,
|
||||||
|
classNames,
|
||||||
fetchIfNotExisting = false,
|
fetchIfNotExisting = false,
|
||||||
variant = 'note'
|
variant = 'note'
|
||||||
}: {
|
}: {
|
||||||
event: Event
|
event: Event
|
||||||
className?: string
|
className?: string
|
||||||
|
classNames?: {
|
||||||
|
buttonBar?: string
|
||||||
|
}
|
||||||
fetchIfNotExisting?: boolean
|
fetchIfNotExisting?: boolean
|
||||||
variant?: 'note' | 'reply'
|
variant?: 'note' | 'reply'
|
||||||
}) {
|
}) {
|
||||||
|
const { isSmallScreen } = useScreenSize()
|
||||||
const { fetchNoteStats } = useNoteStats()
|
const { fetchNoteStats } = useNoteStats()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -28,19 +33,39 @@ export default function NoteStats({
|
|||||||
fetchNoteStats(event)
|
fetchNoteStats(event)
|
||||||
}, [event, fetchIfNotExisting])
|
}, [event, fetchIfNotExisting])
|
||||||
|
|
||||||
|
if (isSmallScreen) {
|
||||||
return (
|
return (
|
||||||
<div className={cn('select-none', className)}>
|
<div className={cn('select-none', className)}>
|
||||||
<TopZaps event={event} />
|
<TopZaps event={event} />
|
||||||
<div className="flex justify-between">
|
<div
|
||||||
<div className="flex gap-5 h-4 items-center" onClick={(e) => e.stopPropagation()}>
|
className={cn(
|
||||||
|
'flex justify-between items-center h-5 [&_svg]:size-5',
|
||||||
|
classNames?.buttonBar
|
||||||
|
)}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<ReplyButton event={event} variant={variant} />
|
||||||
|
<RepostButton event={event} />
|
||||||
|
<LikeButton event={event} />
|
||||||
|
<ZapButton event={event} />
|
||||||
|
<SeenOnButton event={event} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn('select-none', className)}>
|
||||||
|
<TopZaps event={event} />
|
||||||
|
<div className="flex justify-between h-5 [&_svg]:size-4">
|
||||||
|
<div className="flex items-center" onClick={(e) => e.stopPropagation()}>
|
||||||
<ReplyButton event={event} variant={variant} />
|
<ReplyButton event={event} variant={variant} />
|
||||||
<RepostButton event={event} />
|
<RepostButton event={event} />
|
||||||
<LikeButton event={event} />
|
<LikeButton event={event} />
|
||||||
<ZapButton event={event} />
|
<ZapButton event={event} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-5 h-4 items-center" onClick={(e) => e.stopPropagation()}>
|
<div className="flex items-center" onClick={(e) => e.stopPropagation()}>
|
||||||
<SeenOnButton event={event} />
|
<SeenOnButton event={event} />
|
||||||
<NoteOptions event={event} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import NoteStats from '../NoteStats'
|
|||||||
import UserAvatar from '../UserAvatar'
|
import UserAvatar from '../UserAvatar'
|
||||||
import Username from '../Username'
|
import Username from '../Username'
|
||||||
import PictureContent from '../PictureContent'
|
import PictureContent from '../PictureContent'
|
||||||
|
import NoteOptions from '../NoteOptions'
|
||||||
|
|
||||||
export default function PictureNote({
|
export default function PictureNote({
|
||||||
event,
|
event,
|
||||||
@@ -22,13 +23,14 @@ export default function PictureNote({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<div className="px-4 flex items-center space-x-2">
|
<div className="px-4 flex justify-between items-start gap-2">
|
||||||
|
<div className="flex items-center space-x-2 flex-1">
|
||||||
<UserAvatar userId={event.pubkey} />
|
<UserAvatar userId={event.pubkey} />
|
||||||
<div className="flex-1 w-0">
|
<div className="flex-1 w-0">
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<Username
|
<Username
|
||||||
userId={event.pubkey}
|
userId={event.pubkey}
|
||||||
className="font-semibold flex"
|
className="font-semibold flex truncate"
|
||||||
skeletonClassName="h-4"
|
skeletonClassName="h-4"
|
||||||
/>
|
/>
|
||||||
{usingClient && (
|
{usingClient && (
|
||||||
@@ -40,6 +42,8 @@ export default function PictureNote({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<NoteOptions event={event} className="shrink-0 [&_svg]:size-5" />
|
||||||
|
</div>
|
||||||
<PictureContent className="mt-2" event={event} />
|
<PictureContent className="mt-2" event={event} />
|
||||||
{!hideStats && (
|
{!hideStats && (
|
||||||
<NoteStats
|
<NoteStats
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useMemo, useState } from 'react'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Content from '../Content'
|
import Content from '../Content'
|
||||||
import { FormattedTimestamp } from '../FormattedTimestamp'
|
import { FormattedTimestamp } from '../FormattedTimestamp'
|
||||||
|
import NoteOptions from '../NoteOptions'
|
||||||
import NoteStats from '../NoteStats'
|
import NoteStats from '../NoteStats'
|
||||||
import ParentNotePreview from '../ParentNotePreview'
|
import ParentNotePreview from '../ParentNotePreview'
|
||||||
import UserAvatar from '../UserAvatar'
|
import UserAvatar from '../UserAvatar'
|
||||||
@@ -31,11 +32,12 @@ export default function ReplyNote({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`flex space-x-2 items-start rounded-lg p-2 transition-colors duration-500 ${highlight ? 'bg-highlight/50' : ''}`}
|
className={`flex space-x-2 items-start px-4 py-3 border-b transition-colors duration-500 ${highlight ? 'bg-highlight/50' : ''}`}
|
||||||
>
|
>
|
||||||
<UserAvatar userId={event.pubkey} size="small" className="shrink-0" />
|
<UserAvatar userId={event.pubkey} size="small" className="shrink-0" />
|
||||||
<div className="w-full overflow-hidden">
|
<div className="w-full overflow-hidden">
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex items-start justify-between gap-2">
|
||||||
|
<div className="flex gap-2 items-center flex-1">
|
||||||
<Username
|
<Username
|
||||||
userId={event.pubkey}
|
userId={event.pubkey}
|
||||||
className="text-sm font-semibold text-muted-foreground hover:text-foreground truncate"
|
className="text-sm font-semibold text-muted-foreground hover:text-foreground truncate"
|
||||||
@@ -45,6 +47,8 @@ export default function ReplyNote({
|
|||||||
<FormattedTimestamp timestamp={event.created_at} />
|
<FormattedTimestamp timestamp={event.created_at} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<NoteOptions event={event} className="shrink-0 [&_svg]:size-5" />
|
||||||
|
</div>
|
||||||
{parentEvent && (
|
{parentEvent && (
|
||||||
<ParentNotePreview
|
<ParentNotePreview
|
||||||
className="mt-1"
|
className="mt-1"
|
||||||
@@ -58,7 +62,12 @@ export default function ReplyNote({
|
|||||||
{show ? (
|
{show ? (
|
||||||
<>
|
<>
|
||||||
<Content className="mt-1" event={event} size="small" />
|
<Content className="mt-1" event={event} size="small" />
|
||||||
<NoteStats className="mt-2" event={event} variant="reply" />
|
<NoteStats
|
||||||
|
className="mt-2"
|
||||||
|
classNames={{ buttonBar: 'justify-start gap-1' }}
|
||||||
|
event={event}
|
||||||
|
variant="reply"
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -178,17 +178,19 @@ export default function ReplyNoteList({ event, className }: { event: NEvent; cla
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{(loading || until) && (
|
||||||
<div
|
<div
|
||||||
className={`text-sm text-center text-muted-foreground ${!loading ? 'hover:text-foreground cursor-pointer' : ''}`}
|
className={`text-sm text-center text-muted-foreground mt-2 ${!loading ? 'hover:text-foreground cursor-pointer' : ''}`}
|
||||||
onClick={loadMore}
|
onClick={loadMore}
|
||||||
>
|
>
|
||||||
{loading ? t('loading...') : until ? t('load more older replies') : null}
|
{loading ? t('loading...') : t('load more older replies')}
|
||||||
</div>
|
</div>
|
||||||
{replies.length === 0 && !loading && !until && (
|
|
||||||
<div className="text-sm text-center text-muted-foreground">{t('no replies')}</div>
|
|
||||||
)}
|
)}
|
||||||
{replies.length > 0 && (loading || until) && <Separator className="my-2" />}
|
{replies.length === 0 && !loading && !until && (
|
||||||
<div className={cn('mb-4', className)}>
|
<div className="text-sm mt-2 text-center text-muted-foreground">{t('no replies')}</div>
|
||||||
|
)}
|
||||||
|
{replies.length > 0 && (loading || until) && <Separator className="mt-2" />}
|
||||||
|
<div className={cn('mb-2', className)}>
|
||||||
{replies.map((reply) => {
|
{replies.map((reply) => {
|
||||||
const info = replyMap[reply.id]
|
const info = replyMap[reply.id]
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -31,10 +31,10 @@ DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
|
|||||||
|
|
||||||
const DrawerContent = React.forwardRef<
|
const DrawerContent = React.forwardRef<
|
||||||
React.ElementRef<typeof DrawerPrimitive.Content>,
|
React.ElementRef<typeof DrawerPrimitive.Content>,
|
||||||
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
|
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content> & { hideOverlay?: boolean }
|
||||||
>(({ className, children, ...props }, ref) => (
|
>(({ className, children, hideOverlay = false, ...props }, ref) => (
|
||||||
<DrawerPortal>
|
<DrawerPortal>
|
||||||
<DrawerOverlay />
|
{!hideOverlay && <DrawerOverlay />}
|
||||||
<DrawerPrimitive.Content
|
<DrawerPrimitive.Content
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|||||||
@@ -58,12 +58,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>
|
||||||
<PictureNote key={`note-${event.id}`} event={event} fetchNoteStats />
|
<PictureNote key={`note-${event.id}`} event={event} fetchNoteStats />
|
||||||
<Separator className="mb-2 mt-4" />
|
<Separator className="mt-4" />
|
||||||
<Nip22ReplyNoteList
|
<Nip22ReplyNoteList key={`nip22-reply-note-list-${event.id}`} event={event} />
|
||||||
key={`nip22-reply-note-list-${event.id}`}
|
|
||||||
event={event}
|
|
||||||
className="px-2"
|
|
||||||
/>
|
|
||||||
</SecondaryPageLayout>
|
</SecondaryPageLayout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -77,15 +73,11 @@ const NotePage = forwardRef(({ id, index }: { id?: string; index?: number }, ref
|
|||||||
<ParentNote key={`parent-note-${event.id}`} eventId={parentEventId} />
|
<ParentNote key={`parent-note-${event.id}`} eventId={parentEventId} />
|
||||||
<Note key={`note-${event.id}`} event={event} fetchNoteStats hideParentNotePreview />
|
<Note key={`note-${event.id}`} event={event} fetchNoteStats hideParentNotePreview />
|
||||||
</div>
|
</div>
|
||||||
<Separator className="mb-2 mt-4" />
|
<Separator className="mt-4" />
|
||||||
{event.kind === kinds.ShortTextNote ? (
|
{event.kind === kinds.ShortTextNote ? (
|
||||||
<ReplyNoteList key={`reply-note-list-${event.id}`} event={event} className="px-2" />
|
<ReplyNoteList key={`reply-note-list-${event.id}`} event={event} />
|
||||||
) : isPictureEvent(event) ? (
|
) : isPictureEvent(event) ? (
|
||||||
<Nip22ReplyNoteList
|
<Nip22ReplyNoteList key={`nip22-reply-note-list-${event.id}`} event={event} />
|
||||||
key={`nip22-reply-note-list-${event.id}`}
|
|
||||||
event={event}
|
|
||||||
className="px-2"
|
|
||||||
/>
|
|
||||||
) : null}
|
) : null}
|
||||||
</SecondaryPageLayout>
|
</SecondaryPageLayout>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user