refactor: 💨
This commit is contained in:
parent
c729c20771
commit
8c5cc1041b
46 changed files with 1008 additions and 879 deletions
|
|
@ -1,74 +0,0 @@
|
|||
import { ExtendedKind } 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({
|
||||
event,
|
||||
className,
|
||||
reposter,
|
||||
embedded,
|
||||
originalNoteId
|
||||
}: {
|
||||
event: Event
|
||||
className?: string
|
||||
reposter?: string
|
||||
embedded?: boolean
|
||||
originalNoteId?: string
|
||||
}) {
|
||||
const [showMuted, setShowMuted] = useState(false)
|
||||
const { mutePubkeys } = useMuteList()
|
||||
|
||||
if (mutePubkeys.includes(event.pubkey) && !showMuted) {
|
||||
return (
|
||||
<MutedNoteCard
|
||||
event={event}
|
||||
className={className}
|
||||
reposter={reposter}
|
||||
embedded={embedded}
|
||||
show={() => setShowMuted(true)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (isSupportedKind(event.kind)) {
|
||||
return (
|
||||
<MainNoteCard event={event} className={className} reposter={reposter} embedded={embedded} />
|
||||
)
|
||||
}
|
||||
if (event.kind === kinds.LongFormArticle) {
|
||||
return (
|
||||
<LongFormArticleCard
|
||||
className={className}
|
||||
reposter={reposter}
|
||||
event={event}
|
||||
embedded={embedded}
|
||||
/>
|
||||
)
|
||||
}
|
||||
if (event.kind === kinds.LiveEvent) {
|
||||
return (
|
||||
<LiveEventCard event={event} className={className} reposter={reposter} embedded={embedded} />
|
||||
)
|
||||
}
|
||||
if (event.kind === ExtendedKind.GROUP_METADATA) {
|
||||
return (
|
||||
<GroupMetadataCard
|
||||
className={className}
|
||||
event={event}
|
||||
originalNoteId={originalNoteId}
|
||||
embedded={embedded}
|
||||
/>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<UnknownNoteCard event={event} className={className} reposter={reposter} embedded={embedded} />
|
||||
)
|
||||
}
|
||||
|
|
@ -1,151 +0,0 @@
|
|||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { getSharableEventId } from '@/lib/event'
|
||||
import { toChachiChat } from '@/lib/link'
|
||||
import { simplifyUrl } from '@/lib/url'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
||||
import client from '@/services/client.service'
|
||||
import { Check, Copy, ExternalLink } from 'lucide-react'
|
||||
import { Event, nip19 } from 'nostr-tools'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { FormattedTimestamp } from '../FormattedTimestamp'
|
||||
import Image from '../Image'
|
||||
import UserAvatar from '../UserAvatar'
|
||||
import Username from '../Username'
|
||||
import RepostDescription from './RepostDescription'
|
||||
|
||||
export default function GroupMetadataCard({
|
||||
event,
|
||||
className,
|
||||
originalNoteId,
|
||||
embedded = false,
|
||||
reposter
|
||||
}: {
|
||||
event: Event
|
||||
className?: string
|
||||
originalNoteId?: string
|
||||
embedded?: boolean
|
||||
reposter?: string
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const { isSmallScreen } = useScreenSize()
|
||||
const [isCopied, setIsCopied] = useState(false)
|
||||
const metadata = useMemo(() => {
|
||||
let d: string | undefined
|
||||
let name: string | undefined
|
||||
let about: string | undefined
|
||||
let picture: string | undefined
|
||||
let relay: string | undefined
|
||||
const tags = new Set<string>()
|
||||
|
||||
if (originalNoteId) {
|
||||
const pointer = nip19.decode(originalNoteId)
|
||||
if (pointer.type === 'naddr' && pointer.data.relays?.length) {
|
||||
relay = pointer.data.relays[0]
|
||||
}
|
||||
}
|
||||
if (!relay) {
|
||||
relay = client.getEventHint(event.id)
|
||||
}
|
||||
|
||||
event.tags.forEach(([tagName, tagValue]) => {
|
||||
if (tagName === 'name') {
|
||||
name = tagValue
|
||||
} else if (tagName === 'about') {
|
||||
about = tagValue
|
||||
} else if (tagName === 'picture') {
|
||||
picture = tagValue
|
||||
} else if (tagName === 't' && tagValue) {
|
||||
tags.add(tagValue.toLocaleLowerCase())
|
||||
} else if (tagName === 'd') {
|
||||
d = tagValue
|
||||
}
|
||||
})
|
||||
|
||||
if (!name) {
|
||||
name = d ?? 'no name'
|
||||
}
|
||||
|
||||
return { d, name, about, picture, tags: Array.from(tags), relay }
|
||||
}, [event, originalNoteId])
|
||||
|
||||
return (
|
||||
<div className={cn('relative', className)}>
|
||||
<div className={cn(embedded ? 'p-2 sm:p-3 border rounded-lg' : 'px-4 py-3')}>
|
||||
<RepostDescription reposter={reposter} />
|
||||
<div className="flex items-center space-x-2">
|
||||
<UserAvatar userId={event.pubkey} size={embedded ? 'small' : 'normal'} />
|
||||
<div
|
||||
className={`flex-1 w-0 ${embedded ? 'flex space-x-2 items-center overflow-hidden' : ''}`}
|
||||
>
|
||||
<Username
|
||||
userId={event.pubkey}
|
||||
className={cn('font-semibold flex truncate', embedded ? 'text-sm' : '')}
|
||||
skeletonClassName={embedded ? 'h-3' : 'h-4'}
|
||||
/>
|
||||
<div className="text-xs text-muted-foreground line-clamp-1">
|
||||
<FormattedTimestamp timestamp={event.created_at} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2 items-start mt-2">
|
||||
{metadata.picture && (
|
||||
<Image
|
||||
image={{ url: metadata.picture }}
|
||||
className="h-32 aspect-square rounded-lg"
|
||||
hideIfError
|
||||
/>
|
||||
)}
|
||||
<div className="flex-1 w-0 space-y-1">
|
||||
<div className="text-xl font-semibold line-clamp-1">{metadata.name}</div>
|
||||
{metadata.about && (
|
||||
<div className="text-sm text-muted-foreground line-clamp-4">{metadata.about}</div>
|
||||
)}
|
||||
{metadata.tags.length > 0 && (
|
||||
<div className="mt-2 flex gap-1 flex-wrap">
|
||||
{metadata.tags.map((tag) => (
|
||||
<Badge key={tag} variant="secondary">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{(!metadata.relay || !metadata.d) && (
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
navigator.clipboard.writeText(originalNoteId ?? getSharableEventId(event))
|
||||
setIsCopied(true)
|
||||
setTimeout(() => setIsCopied(false), 2000)
|
||||
}}
|
||||
variant="ghost"
|
||||
>
|
||||
{isCopied ? <Check /> : <Copy />} Copy group ID
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{!embedded && <Separator />}
|
||||
{!isSmallScreen && metadata.relay && metadata.d && (
|
||||
<div
|
||||
className={cn(
|
||||
'absolute top-0 w-full h-full bg-muted/80 backdrop-blur-sm flex flex-col items-center justify-center cursor-pointer transition-opacity opacity-0 hover:opacity-100',
|
||||
embedded ? 'rounded-lg' : ''
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
window.open(toChachiChat(simplifyUrl(metadata.relay), metadata.d!), '_blank')
|
||||
}}
|
||||
>
|
||||
<div className="flex gap-2 items-center font-semibold">
|
||||
<ExternalLink className="size-4" /> {t('Open in a', { a: 'Chachi' })}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,169 +0,0 @@
|
|||
import { Badge } from '@/components/ui/badge'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { toZapStreamLiveEvent } from '@/lib/link'
|
||||
import { tagNameEquals } from '@/lib/tag'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
||||
import { ExternalLink } from 'lucide-react'
|
||||
import { Event } from 'nostr-tools'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { FormattedTimestamp } from '../FormattedTimestamp'
|
||||
import Image from '../Image'
|
||||
import UserAvatar from '../UserAvatar'
|
||||
import Username from '../Username'
|
||||
import RepostDescription from './RepostDescription'
|
||||
|
||||
export default function LiveEventCard({
|
||||
event,
|
||||
className,
|
||||
embedded = false,
|
||||
reposter
|
||||
}: {
|
||||
event: Event
|
||||
className?: string
|
||||
embedded?: boolean
|
||||
reposter?: string
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const { isSmallScreen } = useScreenSize()
|
||||
const metadata = useMemo(() => {
|
||||
let title: string | undefined
|
||||
let summary: string | undefined
|
||||
let image: string | undefined
|
||||
let status: string | undefined
|
||||
const tags = new Set<string>()
|
||||
|
||||
event.tags.forEach(([tagName, tagValue]) => {
|
||||
if (tagName === 'title') {
|
||||
title = tagValue
|
||||
} else if (tagName === 'summary') {
|
||||
summary = tagValue
|
||||
} else if (tagName === 'image') {
|
||||
image = tagValue
|
||||
} else if (tagName === 'status') {
|
||||
status = tagValue
|
||||
} else if (tagName === 't' && tagValue && tags.size < 6) {
|
||||
tags.add(tagValue.toLocaleLowerCase())
|
||||
}
|
||||
})
|
||||
|
||||
if (!title) {
|
||||
title = event.tags.find(tagNameEquals('d'))?.[1] ?? 'no title'
|
||||
}
|
||||
|
||||
return { title, summary, image, status, tags: Array.from(tags) }
|
||||
}, [event])
|
||||
|
||||
const liveStatusComponent =
|
||||
metadata.status &&
|
||||
(metadata.status === 'live' ? (
|
||||
<Badge className="bg-green-400 hover:bg-green-400">live</Badge>
|
||||
) : metadata.status === 'ended' ? (
|
||||
<Badge variant="destructive">ended</Badge>
|
||||
) : (
|
||||
<Badge variant="secondary">{metadata.status}</Badge>
|
||||
))
|
||||
|
||||
const userInfoComponent = (
|
||||
<div className="flex items-center space-x-2">
|
||||
<UserAvatar userId={event.pubkey} size={embedded ? 'small' : 'normal'} />
|
||||
<div
|
||||
className={`flex-1 w-0 ${embedded ? 'flex space-x-2 items-center overflow-hidden' : ''}`}
|
||||
>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Username
|
||||
userId={event.pubkey}
|
||||
className={cn('font-semibold flex truncate', embedded ? 'text-sm' : '')}
|
||||
skeletonClassName={embedded ? 'h-3' : 'h-4'}
|
||||
/>
|
||||
{liveStatusComponent}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground line-clamp-1">
|
||||
<FormattedTimestamp timestamp={event.created_at} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
const titleComponent = <div className="text-xl font-semibold line-clamp-1">{metadata.title}</div>
|
||||
|
||||
const summaryComponent = metadata.summary && (
|
||||
<div className="text-sm text-muted-foreground line-clamp-4">{metadata.summary}</div>
|
||||
)
|
||||
|
||||
const tagsComponent = metadata.tags.length > 0 && (
|
||||
<div className="flex gap-1 flex-wrap">
|
||||
{metadata.tags.map((tag) => (
|
||||
<Badge key={tag} variant="secondary">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
|
||||
const handleClick = (e: React.MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
window.open(toZapStreamLiveEvent(event), '_blank')
|
||||
}
|
||||
|
||||
if (isSmallScreen) {
|
||||
return (
|
||||
<div className={className}>
|
||||
<div
|
||||
className={cn('flex flex-col gap-2', embedded ? 'p-2 border rounded-lg' : 'px-4 py-3')}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<RepostDescription reposter={reposter} />
|
||||
{userInfoComponent}
|
||||
{metadata.image && (
|
||||
<Image
|
||||
image={{ url: metadata.image }}
|
||||
className="w-full aspect-video object-cover rounded-lg"
|
||||
hideIfError
|
||||
/>
|
||||
)}
|
||||
<div className="space-y-1">
|
||||
{titleComponent}
|
||||
{summaryComponent}
|
||||
{tagsComponent}
|
||||
</div>
|
||||
</div>
|
||||
{!embedded && <Separator />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn('relative', className)}>
|
||||
<div
|
||||
className={cn('flex gap-2 items-center', embedded ? 'p-3 border rounded-lg' : 'px-4 py-3')}
|
||||
>
|
||||
<div className="flex-1 w-0">
|
||||
<RepostDescription reposter={reposter} />
|
||||
{userInfoComponent}
|
||||
<div className="mt-2 space-y-1">
|
||||
{titleComponent}
|
||||
{summaryComponent}
|
||||
{tagsComponent}
|
||||
</div>
|
||||
</div>
|
||||
{metadata.image && (
|
||||
<Image image={{ url: metadata.image }} className="h-36 max-w-44 rounded-lg" hideIfError />
|
||||
)}
|
||||
</div>
|
||||
{!embedded && <Separator />}
|
||||
<div
|
||||
className={cn(
|
||||
'absolute top-0 w-full h-full bg-muted/80 backdrop-blur-sm flex flex-col items-center justify-center cursor-pointer transition-opacity opacity-0 hover:opacity-100',
|
||||
embedded ? 'rounded-lg' : ''
|
||||
)}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<div className="flex gap-2 items-center font-semibold">
|
||||
<ExternalLink className="size-4" /> {t('Open in a', { a: 'Zap Stream' })}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,162 +0,0 @@
|
|||
import { Badge } from '@/components/ui/badge'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { toHablaLongFormArticle } from '@/lib/link'
|
||||
import { tagNameEquals } from '@/lib/tag'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
||||
import { ExternalLink } from 'lucide-react'
|
||||
import { Event } from 'nostr-tools'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Image from '../Image'
|
||||
import UserAvatar from '../UserAvatar'
|
||||
import Username from '../Username'
|
||||
import RepostDescription from './RepostDescription'
|
||||
|
||||
export default function LongFormArticleCard({
|
||||
event,
|
||||
className,
|
||||
embedded = false,
|
||||
reposter
|
||||
}: {
|
||||
event: Event
|
||||
className?: string
|
||||
embedded?: boolean
|
||||
reposter?: string
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const { isSmallScreen } = useScreenSize()
|
||||
const metadata = useMemo(() => {
|
||||
let title: string | undefined
|
||||
let summary: string | undefined
|
||||
let image: string | undefined
|
||||
let publishDateString: string | undefined
|
||||
const tags = new Set<string>()
|
||||
|
||||
event.tags.forEach(([tagName, tagValue]) => {
|
||||
if (tagName === 'title') {
|
||||
title = tagValue
|
||||
} else if (tagName === 'summary') {
|
||||
summary = tagValue
|
||||
} else if (tagName === 'image') {
|
||||
image = tagValue
|
||||
} else if (tagName === 'published_at') {
|
||||
try {
|
||||
const publishedAt = parseInt(tagValue)
|
||||
publishDateString = !isNaN(publishedAt)
|
||||
? new Date(publishedAt * 1000).toLocaleString()
|
||||
: undefined
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
} else if (tagName === 't' && tagValue && tags.size < 6) {
|
||||
tags.add(tagValue.toLocaleLowerCase())
|
||||
}
|
||||
})
|
||||
|
||||
if (!title) {
|
||||
title = event.tags.find(tagNameEquals('d'))?.[1] ?? 'no title'
|
||||
}
|
||||
|
||||
return { title, summary, image, publishDateString, tags: Array.from(tags) }
|
||||
}, [event])
|
||||
|
||||
const userInfoComponent = (
|
||||
<div className="flex items-center space-x-2">
|
||||
<UserAvatar userId={event.pubkey} size={embedded ? 'small' : 'normal'} />
|
||||
<div
|
||||
className={`flex-1 w-0 ${embedded ? 'flex space-x-2 items-center overflow-hidden' : ''}`}
|
||||
>
|
||||
<Username
|
||||
userId={event.pubkey}
|
||||
className={cn('font-semibold flex truncate', embedded ? 'text-sm' : '')}
|
||||
skeletonClassName={embedded ? 'h-3' : 'h-4'}
|
||||
/>
|
||||
{metadata.publishDateString && (
|
||||
<div className="text-xs text-muted-foreground mt-1">{metadata.publishDateString}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
const titleComponent = <div className="text-xl font-semibold line-clamp-2">{metadata.title}</div>
|
||||
|
||||
const tagsComponent = metadata.tags.length > 0 && (
|
||||
<div className="flex gap-1 flex-wrap">
|
||||
{metadata.tags.map((tag) => (
|
||||
<Badge key={tag} variant="secondary">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
|
||||
const summaryComponent = metadata.summary && (
|
||||
<div className="text-sm text-muted-foreground line-clamp-4">{metadata.summary}</div>
|
||||
)
|
||||
|
||||
const handleClick = (e: React.MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
window.open(toHablaLongFormArticle(event), '_blank')
|
||||
}
|
||||
|
||||
if (isSmallScreen) {
|
||||
return (
|
||||
<div className={className}>
|
||||
<div
|
||||
className={cn('flex flex-col gap-2', embedded ? 'p-2 border rounded-lg' : 'px-4 py-3')}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<RepostDescription reposter={reposter} />
|
||||
{userInfoComponent}
|
||||
{metadata.image && (
|
||||
<Image
|
||||
image={{ url: metadata.image }}
|
||||
className="w-full aspect-video object-cover rounded-lg"
|
||||
hideIfError
|
||||
/>
|
||||
)}
|
||||
<div className="space-y-1">
|
||||
{titleComponent}
|
||||
{tagsComponent}
|
||||
{summaryComponent}
|
||||
</div>
|
||||
</div>
|
||||
{!embedded && <Separator />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn('relative', className)}>
|
||||
<div
|
||||
className={cn('flex gap-2 items-center', embedded ? 'p-3 border rounded-lg' : 'px-4 py-3')}
|
||||
>
|
||||
<div className="flex-1 w-0">
|
||||
<RepostDescription reposter={reposter} />
|
||||
{userInfoComponent}
|
||||
<div className="mt-2 space-y-1">
|
||||
{titleComponent}
|
||||
{tagsComponent}
|
||||
{summaryComponent}
|
||||
</div>
|
||||
</div>
|
||||
{metadata.image && (
|
||||
<Image image={{ url: metadata.image }} className="rounded-lg h-36 max-w-48" hideIfError />
|
||||
)}
|
||||
</div>
|
||||
{!embedded && <Separator />}
|
||||
<div
|
||||
className={cn(
|
||||
'absolute top-0 w-full h-full bg-muted/60 backdrop-blur-sm flex flex-col items-center justify-center cursor-pointer transition-opacity opacity-0 hover:opacity-100',
|
||||
embedded ? 'rounded-lg' : ''
|
||||
)}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<div className="flex gap-2 items-center font-semibold">
|
||||
<ExternalLink className="size-4" /> {t('Open in a', { a: 'Habla' })}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -11,12 +11,14 @@ export default function MainNoteCard({
|
|||
event,
|
||||
className,
|
||||
reposter,
|
||||
embedded
|
||||
embedded,
|
||||
originalNoteId
|
||||
}: {
|
||||
event: Event
|
||||
className?: string
|
||||
reposter?: string
|
||||
embedded?: boolean
|
||||
originalNoteId?: string
|
||||
}) {
|
||||
const { push } = useSecondaryPage()
|
||||
|
||||
|
|
@ -25,7 +27,7 @@ export default function MainNoteCard({
|
|||
className={className}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
push(toNote(event))
|
||||
push(toNote(originalNoteId ?? event))
|
||||
}}
|
||||
>
|
||||
<div className={`clickable ${embedded ? 'p-2 sm:p-3 border rounded-lg' : 'py-3'}`}>
|
||||
|
|
@ -35,6 +37,7 @@ export default function MainNoteCard({
|
|||
className={embedded ? '' : 'px-4'}
|
||||
size={embedded ? 'small' : 'normal'}
|
||||
event={event}
|
||||
originalNoteId={originalNoteId}
|
||||
/>
|
||||
</Collapsible>
|
||||
{!embedded && <NoteStats className="mt-3 px-4" event={event} />}
|
||||
|
|
|
|||
|
|
@ -1,63 +0,0 @@
|
|||
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 (
|
||||
<div className={className}>
|
||||
<div className={cn(embedded ? 'p-2 sm:p-3 border rounded-lg' : 'px-4 py-3')}>
|
||||
<RepostDescription reposter={reposter} />
|
||||
<div className="flex items-center space-x-2">
|
||||
<UserAvatar userId={event.pubkey} size={embedded ? 'small' : 'normal'} />
|
||||
<div
|
||||
className={`flex-1 w-0 ${embedded ? 'flex space-x-2 items-center overflow-hidden' : ''}`}
|
||||
>
|
||||
<Username
|
||||
userId={event.pubkey}
|
||||
className={cn('font-semibold flex truncate', embedded ? 'text-sm' : '')}
|
||||
skeletonClassName={embedded ? 'h-3' : 'h-4'}
|
||||
/>
|
||||
<div className="text-xs text-muted-foreground line-clamp-1">
|
||||
<FormattedTimestamp timestamp={event.created_at} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 items-center text-muted-foreground font-medium my-4">
|
||||
<div>{t('This user has been muted')}</div>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
show()
|
||||
}}
|
||||
variant="outline"
|
||||
>
|
||||
<Eye />
|
||||
{t('Temporarily display this note')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{!embedded && <Separator />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ import { useMuteList } from '@/providers/MuteListProvider'
|
|||
import client from '@/services/client.service'
|
||||
import { Event, kinds, nip19, verifyEvent } from 'nostr-tools'
|
||||
import { useEffect, useState } from 'react'
|
||||
import GenericNoteCard from './GenericNoteCard'
|
||||
import MainNoteCard from './MainNoteCard'
|
||||
|
||||
export default function RepostNoteCard({
|
||||
event,
|
||||
|
|
@ -61,5 +61,5 @@ export default function RepostNoteCard({
|
|||
return null
|
||||
}
|
||||
|
||||
return <GenericNoteCard className={className} reposter={event.pubkey} event={targetEvent} />
|
||||
return <MainNoteCard className={className} reposter={event.pubkey} event={targetEvent} />
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
import { Separator } from '@/components/ui/separator'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Event } from 'nostr-tools'
|
||||
import { FormattedTimestamp } from '../FormattedTimestamp'
|
||||
import { UnknownNote } from '../Note/UnknownNote'
|
||||
import UserAvatar from '../UserAvatar'
|
||||
import Username from '../Username'
|
||||
import RepostDescription from './RepostDescription'
|
||||
|
||||
export default function UnknownNoteCard({
|
||||
event,
|
||||
className,
|
||||
embedded = false,
|
||||
reposter
|
||||
}: {
|
||||
event: Event
|
||||
className?: string
|
||||
embedded?: boolean
|
||||
reposter?: string
|
||||
}) {
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className={cn(embedded ? 'p-2 sm:p-3 border rounded-lg' : 'px-4 py-3')}>
|
||||
<RepostDescription reposter={reposter} />
|
||||
<div className="flex items-center space-x-2">
|
||||
<UserAvatar userId={event.pubkey} size={embedded ? 'small' : 'normal'} />
|
||||
<div
|
||||
className={`flex-1 w-0 ${embedded ? 'flex space-x-2 items-center overflow-hidden' : ''}`}
|
||||
>
|
||||
<Username
|
||||
userId={event.pubkey}
|
||||
className={cn('font-semibold flex truncate', embedded ? 'text-sm' : '')}
|
||||
skeletonClassName={embedded ? 'h-3' : 'h-4'}
|
||||
/>
|
||||
<div className="text-xs text-muted-foreground line-clamp-1">
|
||||
<FormattedTimestamp timestamp={event.created_at} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<UnknownNote event={event} />
|
||||
</div>
|
||||
{!embedded && <Separator />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ import { Skeleton } from '@/components/ui/skeleton'
|
|||
import { useMuteList } from '@/providers/MuteListProvider'
|
||||
import { Event, kinds } from 'nostr-tools'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import GenericNoteCard from './GenericNoteCard'
|
||||
import MainNoteCard from './MainNoteCard'
|
||||
import RepostNoteCard from './RepostNoteCard'
|
||||
|
||||
export default function NoteCard({
|
||||
|
|
@ -24,7 +24,7 @@ export default function NoteCard({
|
|||
<RepostNoteCard event={event} className={className} filterMutedNotes={filterMutedNotes} />
|
||||
)
|
||||
}
|
||||
return <GenericNoteCard event={event} className={className} />
|
||||
return <MainNoteCard event={event} className={className} />
|
||||
}
|
||||
|
||||
export function NoteCardLoadingSkeleton({ isPictures }: { isPictures: boolean }) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue