refactor: 💨
This commit is contained in:
42
src/components/Note/CommunityDefinition.tsx
Normal file
42
src/components/Note/CommunityDefinition.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { getCommunityDefinition } from '@/lib/event'
|
||||
import { Event } from 'nostr-tools'
|
||||
import { useMemo } from 'react'
|
||||
import ClientSelect from '../ClientSelect'
|
||||
import Image from '../Image'
|
||||
|
||||
export default function CommunityDefinition({
|
||||
event,
|
||||
className
|
||||
}: {
|
||||
event: Event
|
||||
className?: string
|
||||
}) {
|
||||
const metadata = useMemo(() => getCommunityDefinition(event), [event])
|
||||
|
||||
const communityNameComponent = (
|
||||
<div className="text-xl font-semibold line-clamp-1">{metadata.name}</div>
|
||||
)
|
||||
|
||||
const communityDescriptionComponent = metadata.description && (
|
||||
<div className="text-sm text-muted-foreground line-clamp-2">{metadata.description}</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="flex gap-4">
|
||||
{metadata.image && (
|
||||
<Image
|
||||
image={{ url: metadata.image }}
|
||||
className="rounded-lg aspect-square object-cover bg-foreground h-20"
|
||||
hideIfError
|
||||
/>
|
||||
)}
|
||||
<div className="flex-1 w-0 space-y-1">
|
||||
{communityNameComponent}
|
||||
{communityDescriptionComponent}
|
||||
</div>
|
||||
</div>
|
||||
<ClientSelect variant="secondary" className="w-full mt-2" event={event} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
49
src/components/Note/GroupMetadata.tsx
Normal file
49
src/components/Note/GroupMetadata.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import { getGroupMetadata } from '@/lib/event'
|
||||
import { Event } from 'nostr-tools'
|
||||
import { useMemo } from 'react'
|
||||
import ClientSelect from '../ClientSelect'
|
||||
import Image from '../Image'
|
||||
|
||||
export default function GroupMetadata({
|
||||
event,
|
||||
originalNoteId,
|
||||
className
|
||||
}: {
|
||||
event: Event
|
||||
originalNoteId?: string
|
||||
className?: string
|
||||
}) {
|
||||
const metadata = useMemo(() => getGroupMetadata(event), [event])
|
||||
|
||||
const groupNameComponent = (
|
||||
<div className="text-xl font-semibold line-clamp-1">{metadata.name}</div>
|
||||
)
|
||||
|
||||
const groupAboutComponent = metadata.about && (
|
||||
<div className="text-sm text-muted-foreground line-clamp-2">{metadata.about}</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="flex gap-4">
|
||||
{metadata.picture && (
|
||||
<Image
|
||||
image={{ url: metadata.picture }}
|
||||
className="rounded-lg aspect-square object-cover bg-foreground h-20"
|
||||
hideIfError
|
||||
/>
|
||||
)}
|
||||
<div className="flex-1 w-0 space-y-1">
|
||||
{groupNameComponent}
|
||||
{groupAboutComponent}
|
||||
</div>
|
||||
</div>
|
||||
<ClientSelect
|
||||
variant="secondary"
|
||||
className="w-full mt-2"
|
||||
event={event}
|
||||
originalNoteId={originalNoteId}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useFetchEvent, useTranslatedEvent } from '@/hooks'
|
||||
import { createFakeEvent, isSupportedKind } from '@/lib/event'
|
||||
import { createFakeEvent } from '@/lib/event'
|
||||
import { toNjump, toNote } from '@/lib/link'
|
||||
import { isValidPubkey } from '@/lib/pubkey'
|
||||
import { generateEventIdFromATag } from '@/lib/tag'
|
||||
@@ -110,7 +110,7 @@ function HighlightSource({ event }: { event: Event }) {
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
<div className="shrink-0">{t('From')}</div>
|
||||
{pubkey && <UserAvatar userId={pubkey} size="xSmall" className="cursor-pointer" />}
|
||||
{referenceEvent && isSupportedKind(referenceEvent.kind) ? (
|
||||
{referenceEvent ? (
|
||||
<ContentPreview
|
||||
className="truncate underline pointer-events-auto cursor-pointer hover:text-foreground"
|
||||
event={referenceEvent}
|
||||
|
||||
80
src/components/Note/LiveEvent.tsx
Normal file
80
src/components/Note/LiveEvent.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { getLiveEventMetadata } from '@/lib/event'
|
||||
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
||||
import { Event } from 'nostr-tools'
|
||||
import { useMemo } from 'react'
|
||||
import ClientSelect from '../ClientSelect'
|
||||
import Image from '../Image'
|
||||
|
||||
export default function LiveEvent({ event, className }: { event: Event; className?: string }) {
|
||||
const { isSmallScreen } = useScreenSize()
|
||||
const metadata = useMemo(() => getLiveEventMetadata(event), [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 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>
|
||||
)
|
||||
|
||||
if (isSmallScreen) {
|
||||
return (
|
||||
<div className={className}>
|
||||
{metadata.image && (
|
||||
<Image
|
||||
image={{ url: metadata.image }}
|
||||
className="w-full aspect-video object-cover rounded-lg"
|
||||
hideIfError
|
||||
/>
|
||||
)}
|
||||
<div className="space-y-1">
|
||||
{titleComponent}
|
||||
{liveStatusComponent}
|
||||
{summaryComponent}
|
||||
{tagsComponent}
|
||||
<ClientSelect variant="secondary" className="w-full mt-2" event={event} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="flex gap-4">
|
||||
{metadata.image && (
|
||||
<Image
|
||||
image={{ url: metadata.image }}
|
||||
className="rounded-lg aspect-[4/3] xl:aspect-video object-cover bg-foreground h-44"
|
||||
hideIfError
|
||||
/>
|
||||
)}
|
||||
<div className="flex-1 w-0 space-y-1">
|
||||
{titleComponent}
|
||||
{liveStatusComponent}
|
||||
{summaryComponent}
|
||||
{tagsComponent}
|
||||
</div>
|
||||
</div>
|
||||
<ClientSelect variant="secondary" className="w-full mt-2" event={event} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
74
src/components/Note/LongFormArticle.tsx
Normal file
74
src/components/Note/LongFormArticle.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { getLongFormArticleMetadata } from '@/lib/event'
|
||||
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
||||
import { Event } from 'nostr-tools'
|
||||
import { useMemo } from 'react'
|
||||
import ClientSelect from '../ClientSelect'
|
||||
import Image from '../Image'
|
||||
|
||||
export default function LongFormArticle({
|
||||
event,
|
||||
className
|
||||
}: {
|
||||
event: Event
|
||||
className?: string
|
||||
}) {
|
||||
const { isSmallScreen } = useScreenSize()
|
||||
const metadata = useMemo(() => getLongFormArticleMetadata(event), [event])
|
||||
|
||||
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>
|
||||
)
|
||||
|
||||
if (isSmallScreen) {
|
||||
return (
|
||||
<div className={className}>
|
||||
{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}
|
||||
<ClientSelect variant="secondary" className="w-full mt-2" event={event} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="flex gap-4">
|
||||
{metadata.image && (
|
||||
<Image
|
||||
image={{ url: metadata.image }}
|
||||
className="rounded-lg aspect-[4/3] xl:aspect-video object-cover bg-foreground h-44"
|
||||
hideIfError
|
||||
/>
|
||||
)}
|
||||
<div className="flex-1 w-0 space-y-1">
|
||||
{titleComponent}
|
||||
{summaryComponent}
|
||||
{tagsComponent}
|
||||
</div>
|
||||
</div>
|
||||
<ClientSelect variant="secondary" className="w-full mt-2" event={event} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
23
src/components/Note/MutedNote.tsx
Normal file
23
src/components/Note/MutedNote.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Eye } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export default function MutedNote({ show }: { show: () => void }) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<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>
|
||||
)
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { getSharableEventId } from '@/lib/event'
|
||||
import { toNjump } from '@/lib/link'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { ExternalLink } from 'lucide-react'
|
||||
import { Event } from 'nostr-tools'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ClientSelect from '../ClientSelect'
|
||||
|
||||
export function UnknownNote({ event, className }: { event: Event; className?: string }) {
|
||||
const { t } = useTranslation()
|
||||
@@ -17,16 +14,7 @@ export function UnknownNote({ event, className }: { event: Event; className?: st
|
||||
)}
|
||||
>
|
||||
<div>{t('Cannot handle event of kind k', { k: event.kind })}</div>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
window.open(toNjump(getSharableEventId(event)), '_blank')
|
||||
}}
|
||||
variant="outline"
|
||||
>
|
||||
<ExternalLink />
|
||||
<div>{t('View on njump.me')}</div>
|
||||
</Button>
|
||||
<ClientSelect event={event} variant="secondary" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { useSecondaryPage } from '@/PageManager'
|
||||
import { ExtendedKind } from '@/constants'
|
||||
import {
|
||||
extractImageInfosFromEventTags,
|
||||
getParentEventId,
|
||||
getUsingClient,
|
||||
isNsfwEvent,
|
||||
isPictureEvent,
|
||||
isSupportedKind
|
||||
isPictureEvent
|
||||
} from '@/lib/event'
|
||||
import { toNote } from '@/lib/link'
|
||||
import { useMuteList } from '@/providers/MuteListProvider'
|
||||
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
||||
import { Event, kinds } from 'nostr-tools'
|
||||
import { useMemo, useState } from 'react'
|
||||
@@ -20,18 +21,25 @@ import ParentNotePreview from '../ParentNotePreview'
|
||||
import TranslateButton from '../TranslateButton'
|
||||
import UserAvatar from '../UserAvatar'
|
||||
import Username from '../Username'
|
||||
import CommunityDefinition from './CommunityDefinition'
|
||||
import GroupMetadata from './GroupMetadata'
|
||||
import Highlight from './Highlight'
|
||||
import IValue from './IValue'
|
||||
import LiveEvent from './LiveEvent'
|
||||
import LongFormArticle from './LongFormArticle'
|
||||
import MutedNote from './MutedNote'
|
||||
import NsfwNote from './NsfwNote'
|
||||
import { UnknownNote } from './UnknownNote'
|
||||
|
||||
export default function Note({
|
||||
event,
|
||||
originalNoteId,
|
||||
size = 'normal',
|
||||
className,
|
||||
hideParentNotePreview = false
|
||||
}: {
|
||||
event: Event
|
||||
originalNoteId?: string
|
||||
size?: 'normal' | 'small'
|
||||
className?: string
|
||||
hideParentNotePreview?: boolean
|
||||
@@ -48,14 +56,37 @@ export default function Note({
|
||||
)
|
||||
const usingClient = useMemo(() => getUsingClient(event), [event])
|
||||
const [showNsfw, setShowNsfw] = useState(false)
|
||||
const { mutePubkeys } = useMuteList()
|
||||
const [showMuted, setShowMuted] = useState(false)
|
||||
|
||||
let content: React.ReactNode
|
||||
if (!isSupportedKind(event.kind)) {
|
||||
if (
|
||||
![
|
||||
kinds.ShortTextNote,
|
||||
kinds.Highlights,
|
||||
kinds.LongFormArticle,
|
||||
kinds.LiveEvent,
|
||||
kinds.CommunityDefinition,
|
||||
ExtendedKind.GROUP_METADATA,
|
||||
ExtendedKind.PICTURE,
|
||||
ExtendedKind.COMMENT
|
||||
].includes(event.kind)
|
||||
) {
|
||||
content = <UnknownNote className="mt-2" event={event} />
|
||||
} else if (mutePubkeys.includes(event.pubkey) && !showMuted) {
|
||||
content = <MutedNote show={() => setShowMuted(true)} />
|
||||
} else if (isNsfwEvent(event) && !showNsfw) {
|
||||
content = <NsfwNote show={() => setShowNsfw(true)} />
|
||||
} else if (event.kind === kinds.Highlights) {
|
||||
content = <Highlight className="mt-2" event={event} />
|
||||
} else if (event.kind === kinds.LongFormArticle) {
|
||||
content = <LongFormArticle className="mt-2" event={event} />
|
||||
} else if (event.kind === kinds.LiveEvent) {
|
||||
content = <LiveEvent className="mt-2" event={event} />
|
||||
} else if (event.kind === ExtendedKind.GROUP_METADATA) {
|
||||
content = <GroupMetadata className="mt-2" event={event} originalNoteId={originalNoteId} />
|
||||
} else if (event.kind === kinds.CommunityDefinition) {
|
||||
content = <CommunityDefinition className="mt-2" event={event} />
|
||||
} else {
|
||||
content = <Content className="mt-2" event={event} />
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user