feat: improve addressable video (kind 34235/34236) display with title and hashtags
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b00ff341c8
commit
4fb40e81b3
4 changed files with 55 additions and 2 deletions
|
|
@ -1,5 +1,8 @@
|
||||||
|
import { ExtendedKind } from '@/constants'
|
||||||
|
import { getVideoMetadataFromEvent } from '@/lib/event-metadata'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { Event } from 'nostr-tools'
|
import { Event } from 'nostr-tools'
|
||||||
|
import { useMemo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export default function VideoNotePreview({
|
export default function VideoNotePreview({
|
||||||
|
|
@ -10,10 +13,18 @@ export default function VideoNotePreview({
|
||||||
className?: string
|
className?: string
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const isAddressable =
|
||||||
|
event.kind === ExtendedKind.ADDRESSABLE_NORMAL_VIDEO ||
|
||||||
|
event.kind === ExtendedKind.ADDRESSABLE_SHORT_VIDEO
|
||||||
|
const metadata = useMemo(
|
||||||
|
() => (isAddressable ? getVideoMetadataFromEvent(event) : null),
|
||||||
|
[event, isAddressable]
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('pointer-events-none', className)}>
|
<div className={cn('pointer-events-none', className)}>
|
||||||
[{t('Media')}] <span className="pr-0.5 italic">{event.content}</span>
|
[{t('Media')}]{' '}
|
||||||
|
<span className="pr-0.5 italic">{metadata?.title || event.content}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,12 @@ export default function ContentPreview({
|
||||||
return <LongFormArticlePreview event={event} className={className} />
|
return <LongFormArticlePreview event={event} className={className} />
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.kind === ExtendedKind.VIDEO || event.kind === ExtendedKind.SHORT_VIDEO) {
|
if (
|
||||||
|
event.kind === ExtendedKind.VIDEO ||
|
||||||
|
event.kind === ExtendedKind.SHORT_VIDEO ||
|
||||||
|
event.kind === ExtendedKind.ADDRESSABLE_NORMAL_VIDEO ||
|
||||||
|
event.kind === ExtendedKind.ADDRESSABLE_SHORT_VIDEO
|
||||||
|
) {
|
||||||
return <VideoNotePreview event={event} className={className} />
|
return <VideoNotePreview event={event} className={className} />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,33 @@
|
||||||
|
import { ExtendedKind } from '@/constants'
|
||||||
import { getImetaInfosFromEvent } from '@/lib/event'
|
import { getImetaInfosFromEvent } from '@/lib/event'
|
||||||
|
import { getVideoMetadataFromEvent } from '@/lib/event-metadata'
|
||||||
import { Event } from 'nostr-tools'
|
import { Event } from 'nostr-tools'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import Content from '../Content'
|
import Content from '../Content'
|
||||||
|
import { EmbeddedHashtag } from '../Embedded'
|
||||||
import MediaPlayer from '../MediaPlayer'
|
import MediaPlayer from '../MediaPlayer'
|
||||||
|
|
||||||
export default function VideoNote({ event, className }: { event: Event; className?: string }) {
|
export default function VideoNote({ event, className }: { event: Event; className?: string }) {
|
||||||
const videoInfos = useMemo(() => getImetaInfosFromEvent(event), [event])
|
const videoInfos = useMemo(() => getImetaInfosFromEvent(event), [event])
|
||||||
|
const isAddressable =
|
||||||
|
event.kind === ExtendedKind.ADDRESSABLE_NORMAL_VIDEO ||
|
||||||
|
event.kind === ExtendedKind.ADDRESSABLE_SHORT_VIDEO
|
||||||
|
const metadata = useMemo(
|
||||||
|
() => (isAddressable ? getVideoMetadataFromEvent(event) : null),
|
||||||
|
[event, isAddressable]
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
|
{metadata?.title && <div className="font-semibold">{metadata.title}</div>}
|
||||||
<Content event={event} />
|
<Content event={event} />
|
||||||
|
{metadata && metadata.tags.length > 0 && (
|
||||||
|
<div className="mt-1 flex flex-wrap gap-1">
|
||||||
|
{metadata.tags.map((tag) => (
|
||||||
|
<EmbeddedHashtag key={tag} hashtag={tag} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{videoInfos.map((video) => (
|
{videoInfos.map((video) => (
|
||||||
<MediaPlayer src={video.url} key={video.url} className="mt-2" />
|
<MediaPlayer src={video.url} key={video.url} className="mt-2" />
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -387,6 +387,25 @@ export function getEmojisFromEvent(event: Event): TEmoji[] {
|
||||||
return info.emojis
|
return info.emojis
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getVideoMetadataFromEvent(event: Event) {
|
||||||
|
let title: string | undefined
|
||||||
|
const tags = new Set<string>()
|
||||||
|
|
||||||
|
event.tags.forEach(([tagName, tagValue]) => {
|
||||||
|
if (tagName === 'title') {
|
||||||
|
title = tagValue
|
||||||
|
} else if (tagName === 't' && tagValue && tags.size < 6) {
|
||||||
|
tags.add(tagValue.toLocaleLowerCase())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!title) {
|
||||||
|
title = event.tags.find(tagNameEquals('d'))?.[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return { title, tags: Array.from(tags) }
|
||||||
|
}
|
||||||
|
|
||||||
export function getStarsFromRelayReviewEvent(event: Event): number {
|
export function getStarsFromRelayReviewEvent(event: Event): number {
|
||||||
const ratingTag = event.tags.find((t) => t[0] === 'rating')
|
const ratingTag = event.tags.find((t) => t[0] === 'rating')
|
||||||
if (ratingTag) {
|
if (ratingTag) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue