Browse Source

bug-fixes for performance

imwald
Silberengel 3 weeks ago
parent
commit
24b87496f9
  1. 10
      src/components/Embedded/EmbeddedNote.tsx
  2. 201
      src/components/FavoriteRelaysActiveStrip/index.tsx
  3. 15
      src/components/Image/index.tsx
  4. 10
      src/components/Note/MarkdownArticle/MarkdownArticle.tsx
  5. 5
      src/components/Note/index.tsx
  6. 5
      src/components/NoteCard/MainNoteCard.tsx
  7. 1
      src/components/VideoPlayer/index.tsx
  8. 15
      src/lib/tag.ts
  9. 1
      src/types/index.d.ts

10
src/components/Embedded/EmbeddedNote.tsx

@ -70,11 +70,13 @@ function canSearchOnExternalRelays(noteId: string): boolean {
export function EmbeddedNote({ export function EmbeddedNote({
noteId, noteId,
className, className,
containingEvent containingEvent,
showFull = false
}: { }: {
noteId: string noteId: string
className?: string className?: string
containingEvent?: Event containingEvent?: Event
showFull?: boolean
}) { }) {
const suppress = useSuppressEmbeddedNoteId() const suppress = useSuppressEmbeddedNoteId()
const embeddedHexId = useMemo(() => hexEventIdFromNoteId(noteId), [noteId]) const embeddedHexId = useMemo(() => hexEventIdFromNoteId(noteId), [noteId])
@ -99,6 +101,7 @@ export function EmbeddedNote({
noteId={noteId} noteId={noteId}
className={className} className={className}
containingEvent={containingEvent} containingEvent={containingEvent}
showFull={showFull}
/> />
) )
} }
@ -160,11 +163,13 @@ function EmbeddedNoteInvalid({
function EmbeddedNoteContent({ function EmbeddedNoteContent({
noteId, noteId,
className, className,
containingEvent containingEvent,
showFull = false
}: { }: {
noteId: string noteId: string
className?: string className?: string
containingEvent?: Event containingEvent?: Event
showFull?: boolean
}) { }) {
const { event, isFetching } = useFetchEvent(noteId) const { event, isFetching } = useFetchEvent(noteId)
const [retryEvent, setRetryEvent] = useState<Event | undefined>(undefined) const [retryEvent, setRetryEvent] = useState<Event | undefined>(undefined)
@ -254,6 +259,7 @@ function EmbeddedNoteContent({
className={cn('w-full', className)} className={cn('w-full', className)}
event={finalEvent} event={finalEvent}
embedded embedded
showFull={showFull}
originalNoteId={noteId} originalNoteId={noteId}
/> />
</div> </div>

201
src/components/FavoriteRelaysActiveStrip/index.tsx

@ -1,12 +1,7 @@
import UserAvatar from '@/components/UserAvatar'
import { SimpleUsername } from '@/components/Username'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { useNostr } from '@/providers/NostrProvider' import { useNostr } from '@/providers/NostrProvider'
import { usePrimaryPage } from '@/contexts/primary-page-context' import { usePrimaryPage } from '@/contexts/primary-page-context'
import { useMuteList } from '@/contexts/mute-list-context'
import { muteSetHas } from '@/lib/mute-set'
import { useFavoriteRelaysActivity } from '@/providers/favorite-relays-activity-context' import { useFavoriteRelaysActivity } from '@/providers/favorite-relays-activity-context'
import { RelayPulseActiveNpubsOpenButton } from './RelayPulseActiveNpubsSheet' import { RelayPulseActiveNpubsOpenButton } from './RelayPulseActiveNpubsSheet'
import type { TFunction } from 'i18next' import type { TFunction } from 'i18next'
@ -14,14 +9,6 @@ import { FileText } from 'lucide-react'
import { useEffect, useMemo, useState } from 'react' import { useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
const MOBILE_MAX_FOLLOW = 30
const MOBILE_MAX_OTHER = 30
const SIDEBAR_MAX_FOLLOW = 50
const SIDEBAR_MAX_OTHER = 50
/** Slight overlap so faces stay recognizable */
const AVATAR_OVERLAP = '-ml-1'
function relativePastPhrase(timestampMs: number, t: TFunction): string { function relativePastPhrase(timestampMs: number, t: TFunction): string {
const sec = Math.floor((Date.now() - timestampMs) / 1000) const sec = Math.floor((Date.now() - timestampMs) / 1000)
if (sec < 45) return t('just now') if (sec < 45) return t('just now')
@ -46,127 +33,31 @@ function useRelativePastPhrase(timestampMs: number | null, t: TFunction): string
}, [timestampMs, t, tick]) }, [timestampMs, t, tick])
} }
function OverlappingAvatars({ function ActiveCountGroups({
pubkeys,
max,
avatarSize,
rowClassName
}: {
pubkeys: string[]
max: number
avatarSize: 'small' | 'xSmall' | 'tiny'
rowClassName?: string
}) {
const slice = pubkeys.slice(0, max)
const extra = pubkeys.length - slice.length
const row = (
<div className="flex w-full min-w-0 max-w-full flex-row flex-wrap items-center gap-y-1 pl-0.5">
{slice.map((pk, i) => (
<HoverCard key={pk} openDelay={180} closeDelay={80}>
<HoverCardTrigger asChild>
<div
className={cn(
'relative shrink-0 rounded-full ring-2 ring-background transition-[z-index] duration-150',
i > 0 && AVATAR_OVERLAP
)}
style={{ zIndex: i + 1 }}
>
<UserAvatar userId={pk} size={avatarSize} />
</div>
</HoverCardTrigger>
<HoverCardContent side="top" className="w-auto max-w-[min(18rem,calc(100vw-2rem))] py-2 px-3">
<SimpleUsername userId={pk} showAt className="text-sm font-medium" />
</HoverCardContent>
</HoverCard>
))}
{extra > 0 ? (
<div
className={cn(
'relative z-[20] flex h-7 min-w-7 shrink-0 items-center justify-center rounded-full bg-muted px-1.5 text-xs font-medium text-muted-foreground ring-2 ring-background',
slice.length > 0 && AVATAR_OVERLAP
)}
title={String(extra)}
>
+{extra > 99 ? '99+' : extra}
</div>
) : null}
</div>
)
return (
<div className={cn('flex w-full min-w-0 max-w-full flex-1 items-start', rowClassName)}>
{row}
</div>
)
}
function ActiveAvatarGroups({
followPubkeysForAvatars,
otherPubkeysForAvatars,
followCount, followCount,
otherCount, otherCount,
maxFollow,
maxOther,
avatarSize,
labelClassName, labelClassName,
stackClassName, stackClassName,
variant = 'default', variant = 'default',
onOpenFollowsNotes onOpenFollowsNotes
}: { }: {
/** Subset with kind 0 only (shown as circles); counts use full totals */
followPubkeysForAvatars: string[]
otherPubkeysForAvatars: string[]
followCount: number followCount: number
otherCount: number otherCount: number
maxFollow: number
maxOther: number
avatarSize: 'small' | 'xSmall' | 'tiny'
labelClassName: string labelClassName: string
stackClassName?: string stackClassName?: string
/** Mobile home: label above avatars + scrollable rows; sidebar/default keeps compact rows on wider mini breakpoints */
variant?: 'default' | 'mobileBar' variant?: 'default' | 'mobileBar'
/** Opens search page and expands the notes-from-follows section */
onOpenFollowsNotes?: () => void onOpenFollowsNotes?: () => void
}) { }) {
const { t } = useTranslation() const { t } = useTranslation()
const mobileBar = variant === 'mobileBar' const mobileBar = variant === 'mobileBar'
const groupRowClass = mobileBar const groupRowClass = mobileBar
? 'flex w-full min-w-0 flex-col gap-1.5' ? 'flex w-full min-w-0 items-center gap-1.5'
: 'flex min-w-0 flex-col gap-1 min-[380px]:flex-row min-[380px]:items-center min-[380px]:gap-2' : 'flex min-w-0 items-center gap-1.5'
const followsLabelBlock = (
<div className="flex shrink-0 flex-col gap-1">
<span className={cn('tabular-nums', labelClassName)}>
{t('Relay pulse follows', { count: followCount })}
</span>
{onOpenFollowsNotes && mobileBar ? (
<Button
variant="ghost"
size="icon"
className="size-6 shrink-0 self-start"
aria-label={t('See the newest notes from your follows')}
title={t('See the newest notes from your follows')}
onClick={onOpenFollowsNotes}
>
<FileText className="size-3.5" />
</Button>
) : null}
</div>
)
const sidebarSectionClass = 'flex min-w-0 flex-col gap-1'
return ( return (
<div className={cn('flex min-w-0 flex-col gap-2', stackClassName)}> <div className={cn('flex min-w-0 flex-col gap-1.5', stackClassName)}>
{followCount > 0 ? ( {followCount > 0 ? (
<div <div className={groupRowClass}>
className={
mobileBar ? groupRowClass : sidebarSectionClass
}
>
{mobileBar ? (
<span className="flex min-w-0 shrink-0 items-center gap-1">
<span className={cn('tabular-nums', labelClassName)}> <span className={cn('tabular-nums', labelClassName)}>
{t('Relay pulse follows', { count: followCount })} {t('Relay pulse follows', { count: followCount })}
</span> </span>
@ -174,38 +65,20 @@ function ActiveAvatarGroups({
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
className="size-6 shrink-0" className={cn('shrink-0', mobileBar ? 'size-6' : 'size-5')}
aria-label={t('See the newest notes from your follows')} aria-label={t('See the newest notes from your follows')}
title={t('See the newest notes from your follows')} title={t('See the newest notes from your follows')}
onClick={onOpenFollowsNotes} onClick={onOpenFollowsNotes}
> >
<FileText className="size-3.5" /> <FileText className={mobileBar ? 'size-3.5' : 'size-3'} />
</Button> </Button>
) : null} ) : null}
</span>
) : (
followsLabelBlock
)}
<OverlappingAvatars
pubkeys={followPubkeysForAvatars}
max={maxFollow}
avatarSize={avatarSize}
rowClassName={mobileBar ? undefined : 'justify-start'}
/>
</div> </div>
) : null} ) : null}
{otherCount > 0 ? ( {otherCount > 0 ? (
<div className={mobileBar ? groupRowClass : sidebarSectionClass}> <span className={cn('min-w-0 tabular-nums', labelClassName)}>
<span className={cn('min-w-0 shrink-0 tabular-nums', labelClassName)}>
{t('Relay pulse others', { count: otherCount })} {t('Relay pulse others', { count: otherCount })}
</span> </span>
<OverlappingAvatars
pubkeys={otherPubkeysForAvatars}
max={maxOther}
avatarSize={avatarSize}
rowClassName={mobileBar ? undefined : 'justify-start'}
/>
</div>
) : null} ) : null}
</div> </div>
) )
@ -216,34 +89,15 @@ export function FavoriteRelaysActiveStripMobileBar({ className }: { className?:
const { t } = useTranslation() const { t } = useTranslation()
const { navigate } = usePrimaryPage() const { navigate } = usePrimaryPage()
const { pubkey } = useNostr() const { pubkey } = useNostr()
const { mutePubkeySet } = useMuteList()
const { const {
followPubkeys,
otherPubkeys,
followCount, followCount,
otherCount, otherCount,
totalCount, totalCount,
loading, loading,
relayActivityReady, relayActivityReady,
lastFetchedAtMs, lastFetchedAtMs
profileKind0ByPubkey
} = useFavoriteRelaysActivity() } = useFavoriteRelaysActivity()
const followPubkeysForAvatars = useMemo(
() =>
followPubkeys.filter(
(pk) => profileKind0ByPubkey[pk] && !muteSetHas(mutePubkeySet, pk)
),
[followPubkeys, profileKind0ByPubkey, mutePubkeySet]
)
const otherPubkeysForAvatars = useMemo(
() =>
otherPubkeys.filter(
(pk) => profileKind0ByPubkey[pk] && !muteSetHas(mutePubkeySet, pk)
),
[otherPubkeys, profileKind0ByPubkey, mutePubkeySet]
)
const relativeLabel = useRelativePastPhrase(lastFetchedAtMs, t) const relativeLabel = useRelativePastPhrase(lastFetchedAtMs, t)
if (!relayActivityReady && !loading) { if (!relayActivityReady && !loading) {
@ -288,7 +142,7 @@ export function FavoriteRelaysActiveStripMobileBar({ className }: { className?:
className className
)} )}
> >
<div className="flex w-full min-w-0 flex-col gap-3"> <div className="flex w-full min-w-0 flex-col gap-1.5">
<div className="flex min-w-0 max-w-full items-center justify-between gap-2"> <div className="flex min-w-0 max-w-full items-center justify-between gap-2">
<div className="flex min-w-0 shrink items-center gap-2"> <div className="flex min-w-0 shrink items-center gap-2">
<p className="text-xs font-medium leading-tight text-foreground">{t('Relay pulse')}</p> <p className="text-xs font-medium leading-tight text-foreground">{t('Relay pulse')}</p>
@ -300,15 +154,10 @@ export function FavoriteRelaysActiveStripMobileBar({ className }: { className?:
</p> </p>
) : null} ) : null}
</div> </div>
<ActiveAvatarGroups <ActiveCountGroups
variant="mobileBar" variant="mobileBar"
followPubkeysForAvatars={followPubkeysForAvatars}
otherPubkeysForAvatars={otherPubkeysForAvatars}
followCount={followCount} followCount={followCount}
otherCount={otherCount} otherCount={otherCount}
maxFollow={MOBILE_MAX_FOLLOW}
maxOther={MOBILE_MAX_OTHER}
avatarSize="small"
labelClassName="text-[0.7rem] font-medium text-muted-foreground" labelClassName="text-[0.7rem] font-medium text-muted-foreground"
stackClassName="w-full min-w-0 max-w-full" stackClassName="w-full min-w-0 max-w-full"
onOpenFollowsNotes={pubkey ? () => navigate('follows-latest') : undefined} onOpenFollowsNotes={pubkey ? () => navigate('follows-latest') : undefined}
@ -323,34 +172,15 @@ export function FavoriteRelaysActiveStripSidebar({ className }: { className?: st
const { t } = useTranslation() const { t } = useTranslation()
const { navigate } = usePrimaryPage() const { navigate } = usePrimaryPage()
const { pubkey } = useNostr() const { pubkey } = useNostr()
const { mutePubkeySet } = useMuteList()
const { const {
followPubkeys,
otherPubkeys,
followCount, followCount,
otherCount, otherCount,
totalCount, totalCount,
loading, loading,
relayActivityReady, relayActivityReady,
lastFetchedAtMs, lastFetchedAtMs
profileKind0ByPubkey
} = useFavoriteRelaysActivity() } = useFavoriteRelaysActivity()
const followPubkeysForAvatars = useMemo(
() =>
followPubkeys.filter(
(pk) => profileKind0ByPubkey[pk] && !muteSetHas(mutePubkeySet, pk)
),
[followPubkeys, profileKind0ByPubkey, mutePubkeySet]
)
const otherPubkeysForAvatars = useMemo(
() =>
otherPubkeys.filter(
(pk) => profileKind0ByPubkey[pk] && !muteSetHas(mutePubkeySet, pk)
),
[otherPubkeys, profileKind0ByPubkey, mutePubkeySet]
)
const relativeLabel = useRelativePastPhrase(lastFetchedAtMs, t) const relativeLabel = useRelativePastPhrase(lastFetchedAtMs, t)
if (!relayActivityReady && !loading) { if (!relayActivityReady && !loading) {
@ -437,14 +267,9 @@ export function FavoriteRelaysActiveStripSidebar({ className }: { className?: st
) : null} ) : null}
</div> </div>
<div className="max-xl:flex max-xl:justify-center"> <div className="max-xl:flex max-xl:justify-center">
<ActiveAvatarGroups <ActiveCountGroups
followPubkeysForAvatars={followPubkeysForAvatars}
otherPubkeysForAvatars={otherPubkeysForAvatars}
followCount={followCount} followCount={followCount}
otherCount={otherCount} otherCount={otherCount}
maxFollow={SIDEBAR_MAX_FOLLOW}
maxOther={SIDEBAR_MAX_OTHER}
avatarSize="xSmall"
labelClassName="text-[0.6rem] font-medium text-muted-foreground xl:px-1" labelClassName="text-[0.6rem] font-medium text-muted-foreground xl:px-1"
stackClassName="w-full max-xl:items-center" stackClassName="w-full max-xl:items-center"
onOpenFollowsNotes={pubkey ? () => navigate('follows-latest') : undefined} onOpenFollowsNotes={pubkey ? () => navigate('follows-latest') : undefined}

15
src/components/Image/index.tsx

@ -22,8 +22,14 @@ function wrapperReserveStyle(
return { minHeight: 'min(30vh, 280px)' } return { minHeight: 'min(30vh, 280px)' }
} }
function formatFileSize(bytes: number): string {
if (bytes >= 1_048_576) return `${(bytes / 1_048_576).toFixed(1)} MB`
if (bytes >= 1_024) return `${Math.round(bytes / 1_024)} KB`
return `${bytes} B`
}
export default function Image({ export default function Image({
image: { url, blurHash, dim, alt: imetaAlt, fallback }, image: { url, blurHash, dim, alt: imetaAlt, fallback, size: fileSizeBytes },
alt, alt,
className = '', className = '',
classNames = {}, classNames = {},
@ -167,6 +173,11 @@ export default function Image({
)} )}
/> />
)} )}
{!revealed && holdUntilClick && fileSizeBytes != null && (
<span className="absolute bottom-2 right-2 z-20 rounded-full bg-black/60 px-2 py-0.5 text-[11px] font-medium text-white/90 backdrop-blur-sm select-none pointer-events-none">
{formatFileSize(fileSizeBytes)}
</span>
)}
</span> </span>
)} )}
{!showErrorState && revealed && ( {!showErrorState && revealed && (
@ -176,7 +187,7 @@ export default function Image({
title={finalAlt || undefined} title={finalAlt || undefined}
referrerPolicy="no-referrer" referrerPolicy="no-referrer"
decoding="async" decoding="async"
loading="eager" loading="lazy"
draggable={false} draggable={false}
onLoad={handleLoad} onLoad={handleLoad}
onError={handleError} onError={handleError}

10
src/components/Note/MarkdownArticle/MarkdownArticle.tsx

@ -2423,7 +2423,7 @@ export function parseMarkdownContentLegacy(
// Embedded events should be block-level and fill width // Embedded events should be block-level and fill width
parts.push( parts.push(
<div key={`nostr-${patternIdx}`} className="w-full my-2"> <div key={`nostr-${patternIdx}`} className="w-full my-2">
<EmbeddedNote noteId={bech32Id} /> <EmbeddedNote noteId={bech32Id} showFull={!lazyMedia} />
</div> </div>
) )
} }
@ -3200,7 +3200,7 @@ function parseMarkdownContentMarked(
} }
return ( return (
<div key={`${key}-nostr-event`} className="w-full my-2"> <div key={`${key}-nostr-event`} className="w-full my-2">
<EmbeddedNote noteId={bech32Id} containingEvent={containingEvent} /> <EmbeddedNote noteId={bech32Id} containingEvent={containingEvent} showFull={!lazyMedia} />
</div> </div>
) )
} }
@ -3341,7 +3341,7 @@ function parseMarkdownContentMarked(
} }
return ( return (
<div key={`${key}-line-event-${lineIdx}`} className="w-full my-2"> <div key={`${key}-line-event-${lineIdx}`} className="w-full my-2">
<EmbeddedNote noteId={bech32Id} containingEvent={containingEvent} /> <EmbeddedNote noteId={bech32Id} containingEvent={containingEvent} showFull={!lazyMedia} />
</div> </div>
) )
} }
@ -3386,7 +3386,7 @@ function parseMarkdownContentMarked(
} else { } else {
nodes.push( nodes.push(
<div key={`${key}-nostr-raw-event-${segmentIdx++}`} className="w-full my-2"> <div key={`${key}-nostr-raw-event-${segmentIdx++}`} className="w-full my-2">
<EmbeddedNote noteId={bech32Id} containingEvent={containingEvent} /> <EmbeddedNote noteId={bech32Id} containingEvent={containingEvent} showFull={!lazyMedia} />
</div> </div>
) )
} }
@ -3553,7 +3553,7 @@ function parseMarkdownContentMarked(
} else { } else {
nodes.push( nodes.push(
<div key={`${key}-nostr-inline-event-${idx}`} className="w-full my-2"> <div key={`${key}-nostr-inline-event-${idx}`} className="w-full my-2">
<EmbeddedNote noteId={bech32} containingEvent={containingEvent} /> <EmbeddedNote noteId={bech32} containingEvent={containingEvent} showFull={!lazyMedia} />
</div> </div>
) )
} }

5
src/components/Note/index.tsx

@ -129,6 +129,7 @@ export default function Note({
) )
const contentPolicy = useContentPolicyOptional() const contentPolicy = useContentPolicyOptional()
const defaultShowNsfw = contentPolicy?.defaultShowNsfw ?? true const defaultShowNsfw = contentPolicy?.defaultShowNsfw ?? true
const autoLoadMedia = contentPolicy?.autoLoadMedia ?? true
const [showNsfw, setShowNsfw] = useState(false) const [showNsfw, setShowNsfw] = useState(false)
const muteList = useMuteListOptional() const muteList = useMuteListOptional()
const mutePubkeySet = muteList?.mutePubkeySet ?? new Set<string>() const mutePubkeySet = muteList?.mutePubkeySet ?? new Set<string>()
@ -213,12 +214,12 @@ export default function Note({
className={className} className={className}
event={event} event={event}
hideMetadata={hideMetadata} hideMetadata={hideMetadata}
lazyMedia={!showFull} lazyMedia={!showFull || !autoLoadMedia}
fullCalendarInvite={fullCalendarInvite} fullCalendarInvite={fullCalendarInvite}
/> />
) )
}, },
[event, fullCalendarInvite, showFull] [event, fullCalendarInvite, showFull, autoLoadMedia]
) )
let content: React.ReactNode let content: React.ReactNode

5
src/components/NoteCard/MainNoteCard.tsx

@ -21,7 +21,8 @@ export default function MainNoteCard({
pinned = false, pinned = false,
hideParentNotePreview = false, hideParentNotePreview = false,
zapPollVoteHighlightOption, zapPollVoteHighlightOption,
bottomNoteLabel bottomNoteLabel,
showFull = false
}: { }: {
event: Event event: Event
className?: string className?: string
@ -34,6 +35,7 @@ export default function MainNoteCard({
hideParentNotePreview?: boolean hideParentNotePreview?: boolean
zapPollVoteHighlightOption?: number zapPollVoteHighlightOption?: number
bottomNoteLabel?: string bottomNoteLabel?: string
showFull?: boolean
}) { }) {
const { t } = useTranslation() const { t } = useTranslation()
const { navigateToNote } = useSmartNoteNavigationOptional() const { navigateToNote } = useSmartNoteNavigationOptional()
@ -93,6 +95,7 @@ export default function MainNoteCard({
disableClick={true} disableClick={true}
hideParentNotePreview={hideParentNotePreview} hideParentNotePreview={hideParentNotePreview}
zapPollVoteHighlightOption={zapPollVoteHighlightOption} zapPollVoteHighlightOption={zapPollVoteHighlightOption}
showFull={showFull}
/> />
</Collapsible> </Collapsible>
{showNoteStatsRow ? ( {showNoteStatsRow ? (

1
src/components/VideoPlayer/index.tsx

@ -62,6 +62,7 @@ export default function VideoPlayer({ src, className, poster }: { src: string; c
ref={videoRef} ref={videoRef}
controls controls
playsInline playsInline
preload="none"
className={cn('rounded-lg max-h-[80vh] sm:max-h-[60vh] border w-full h-auto max-w-full', className)} className={cn('rounded-lg max-h-[80vh] sm:max-h-[60vh] border w-full h-auto max-w-full', className)}
src={src} src={src}
poster={poster} poster={poster}

15
src/lib/tag.ts

@ -230,6 +230,21 @@ export function getImetaInfoFromImetaTag(tag: string[], pubkey?: string): TImeta
imeta.thumb = cleanUrl(thumbUrl) imeta.thumb = cleanUrl(thumbUrl)
} }
// Parse file size (bytes)
let fileSize: number | undefined
const sizeItem = tag.find((item) => item.startsWith('size '))
if (sizeItem) {
fileSize = parseInt(sizeItem.slice(5), 10)
} else {
const sizeIndex = tag.findIndex((item) => item === 'size')
if (sizeIndex !== -1 && sizeIndex + 1 < tag.length) {
fileSize = parseInt(tag[sizeIndex + 1], 10)
}
}
if (fileSize && !isNaN(fileSize)) {
imeta.size = fileSize
}
return imeta return imeta
} }

1
src/types/index.d.ts vendored

@ -177,6 +177,7 @@ export type TImetaInfo = {
fallback?: string[] // Array of fallback URLs fallback?: string[] // Array of fallback URLs
image?: string // Poster/thumbnail image URL (for videos) image?: string // Poster/thumbnail image URL (for videos)
thumb?: string // Thumbnail URL for images thumb?: string // Thumbnail URL for images
size?: number // File size in bytes (NIP-94)
} }
export type TPublishOptions = { export type TPublishOptions = {

Loading…
Cancel
Save