Browse Source

bug-fixes

imwald
Silberengel 3 weeks ago
parent
commit
4e4b20b1c9
  1. 12
      src/components/Content/index.tsx
  2. 25
      src/components/Embedded/HttpNostrAwareUrl.tsx
  3. 30
      src/components/Note/MarkdownArticle/MarkdownArticle.tsx
  4. 56
      src/components/ZapStreamEmbeddedPlayer/index.tsx
  5. 31
      src/components/ZapStreamLiveEventEmbed/index.tsx
  6. 5
      src/lib/youtube-url.ts
  7. 13
      src/lib/zap-stream-url.test.ts
  8. 13
      src/lib/zap-stream-url.ts

12
src/components/Content/index.tsx

@ -26,7 +26,7 @@ import ImageGallery from '../ImageGallery' @@ -26,7 +26,7 @@ import ImageGallery from '../ImageGallery'
import MediaPlayer from '../MediaPlayer'
import SpotifyEmbeddedPlayer from '../SpotifyEmbeddedPlayer'
import YoutubeEmbeddedPlayer from '../YoutubeEmbeddedPlayer'
import ZapStreamEmbeddedPlayer from '../ZapStreamEmbeddedPlayer'
import ZapStreamLiveEventEmbed from '../ZapStreamLiveEventEmbed'
import WebPreview from '../WebPreview'
import { toNote } from '@/lib/link'
import { YOUTUBE_URL_REGEX } from '@/constants'
@ -459,11 +459,12 @@ export default function Content({ @@ -459,11 +459,12 @@ export default function Content({
))}
{zapstreamUrlsFromTags.map((url) => (
<ZapStreamEmbeddedPlayer
<ZapStreamLiveEventEmbed
key={`tag-zapstream-${url}`}
url={url}
className="mt-2"
mustLoad={mustLoadMedia}
containingEvent={event}
showFull={mustLoadMedia}
/>
))}
@ -617,11 +618,12 @@ export default function Content({ @@ -617,11 +618,12 @@ export default function Content({
}
if (node.type === 'zapstream') {
return (
<ZapStreamEmbeddedPlayer
<ZapStreamLiveEventEmbed
key={index}
url={node.data}
className="mt-2"
mustLoad={mustLoadMedia}
containingEvent={event}
showFull={mustLoadMedia}
/>
)
}

25
src/components/Embedded/HttpNostrAwareUrl.tsx

@ -14,6 +14,10 @@ import { EmbeddedMention } from './EmbeddedMention' @@ -14,6 +14,10 @@ import { EmbeddedMention } from './EmbeddedMention'
import { EmbeddedNormalUrl } from './EmbeddedNormalUrl'
import { EmbeddedNote } from './EmbeddedNote'
import WebPreview from '@/components/WebPreview'
import YoutubeEmbeddedPlayer from '@/components/YoutubeEmbeddedPlayer'
import ZapStreamLiveEventEmbed from '@/components/ZapStreamLiveEventEmbed'
import { isEmbeddableYoutubeUrl } from '@/lib/youtube-url'
import { isZapStreamWatchUrl } from '@/lib/zap-stream-url'
type RenderMode = 'note-content' | 'article'
@ -41,6 +45,27 @@ export function HttpNostrAwareUrl({ @@ -41,6 +45,27 @@ export function HttpNostrAwareUrl({
const cleaned = cleanUrl(url) || url
if (isZapStreamWatchUrl(cleaned)) {
return (
<ZapStreamLiveEventEmbed
url={cleaned}
className={className}
containingEvent={containingEvent}
showFull={renderMode === 'article'}
/>
)
}
if (isEmbeddableYoutubeUrl(cleaned)) {
return (
<YoutubeEmbeddedPlayer
url={cleaned}
className={cn('mt-2 max-w-[400px]', className)}
mustLoad={renderMode === 'article'}
/>
)
}
if (sameOriginTarget) {
if (sameOriginTarget.kind === 'event') {
return (

30
src/components/Note/MarkdownArticle/MarkdownArticle.tsx

@ -5,7 +5,7 @@ import Wikilink from '@/components/UniversalContent/Wikilink' @@ -5,7 +5,7 @@ import Wikilink from '@/components/UniversalContent/Wikilink'
import { BookstrContent } from '@/components/Bookstr'
import WebPreview from '@/components/WebPreview'
import SpotifyEmbeddedPlayer from '@/components/SpotifyEmbeddedPlayer'
import ZapStreamEmbeddedPlayer from '@/components/ZapStreamEmbeddedPlayer'
import ZapStreamLiveEventEmbed from '@/components/ZapStreamLiveEventEmbed'
import YoutubeEmbeddedPlayer from '@/components/YoutubeEmbeddedPlayer'
import { getLongFormArticleMetadataFromEvent } from '@/lib/event-metadata'
import { toNoteList } from '@/lib/link'
@ -2239,7 +2239,12 @@ function parseMarkdownContentLegacy( @@ -2239,7 +2239,12 @@ function parseMarkdownContentLegacy(
const { url } = pattern.data
parts.push(
<div key={`zapstream-url-${patternIdx}`} className="my-2">
<ZapStreamEmbeddedPlayer url={url} className="max-w-[400px]" mustLoad={!lazyMedia} />
<ZapStreamLiveEventEmbed
url={url}
className="max-w-[400px]"
containingEvent={containingEvent}
showFull={!lazyMedia}
/>
</div>
)
} else if (pattern.type === 'relay-url') {
@ -3417,7 +3422,12 @@ function parseMarkdownContentMarked( @@ -3417,7 +3422,12 @@ function parseMarkdownContentMarked(
if (isZapStreamUrl(cleaned)) {
return (
<div key={`${key}-line-zapstream-${lineIdx}`} className="my-2">
<ZapStreamEmbeddedPlayer url={cleaned} className="max-w-[400px]" mustLoad={!lazyMedia} />
<ZapStreamLiveEventEmbed
url={cleaned}
className="max-w-[400px]"
containingEvent={containingEvent}
showFull={!lazyMedia}
/>
</div>
)
}
@ -3577,7 +3587,12 @@ function parseMarkdownContentMarked( @@ -3577,7 +3587,12 @@ function parseMarkdownContentMarked(
if (isZapStreamUrl(cleaned)) {
return (
<div key={`${key}-zapstream-url`} className="my-2">
<ZapStreamEmbeddedPlayer url={cleaned} className="max-w-[400px]" mustLoad={!lazyMedia} />
<ZapStreamLiveEventEmbed
url={cleaned}
className="max-w-[400px]"
containingEvent={containingEvent}
showFull={!lazyMedia}
/>
</div>
)
}
@ -5379,7 +5394,12 @@ export default function MarkdownArticle({ @@ -5379,7 +5394,12 @@ export default function MarkdownArticle({
<div className="space-y-4 mb-6">
{leftoverTagZapStreamUrls.map((url) => (
<div key={`tag-zapstream-${url}`} className="my-2">
<ZapStreamEmbeddedPlayer url={url} className="max-w-[400px]" mustLoad={!lazyMedia} />
<ZapStreamLiveEventEmbed
url={url}
className="max-w-[400px]"
containingEvent={event}
showFull={!lazyMedia}
/>
</div>
))}
</div>

56
src/components/ZapStreamEmbeddedPlayer/index.tsx

@ -1,56 +0,0 @@ @@ -1,56 +0,0 @@
import { canonicalZapStreamWatchUrl } from '@/lib/zap-stream-url'
import { cn } from '@/lib/utils'
import { useContentPolicyOptional } from '@/providers/ContentPolicyProvider'
import { useLayoutEffect, useMemo, useState } from 'react'
import ExternalLink from '../ExternalLink'
import LazyMediaTapPlaceholder from '../MediaPlayer/LazyMediaTapPlaceholder'
export default function ZapStreamEmbeddedPlayer({
url,
className,
mustLoad = false
}: {
url: string
className?: string
mustLoad?: boolean
}) {
const contentPolicy = useContentPolicyOptional()
const autoLoadMedia = contentPolicy?.autoLoadMedia ?? true
const [userClickedLoad, setUserClickedLoad] = useState(false)
const embedSrc = useMemo(() => canonicalZapStreamWatchUrl(url), [url])
const showEmbed = mustLoad || autoLoadMedia || userClickedLoad
useLayoutEffect(() => {
if (!autoLoadMedia) setUserClickedLoad(false)
}, [autoLoadMedia])
if (!embedSrc) {
return <ExternalLink url={url} />
}
if (!mustLoad && !showEmbed) {
return (
<LazyMediaTapPlaceholder
src={embedSrc}
mediaKind="video"
onActivate={() => setUserClickedLoad(true)}
className={cn('aspect-video max-h-[min(70vh,28rem)]', className)}
/>
)
}
return (
<iframe
title="zap.stream"
src={embedSrc}
className={cn(
'not-prose rounded-lg border w-full max-w-[400px] aspect-video max-h-[min(70vh,28rem)]',
className
)}
allow="autoplay; encrypted-media; fullscreen; clipboard-write"
allowFullScreen
loading="lazy"
referrerPolicy="no-referrer-when-downgrade"
/>
)
}

31
src/components/ZapStreamLiveEventEmbed/index.tsx

@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
import { EmbeddedNote } from '@/components/Embedded/EmbeddedNote'
import ExternalLink from '@/components/ExternalLink'
import { naddrFromZapStreamWatchUrl } from '@/lib/zap-stream-url'
import { cn } from '@/lib/utils'
import type { Event } from 'nostr-tools'
/** zap.stream `/naddr1…` → fetch kind 30311 and render as embedded note (LiveEvent), not a full-site iframe. */
export default function ZapStreamLiveEventEmbed({
url,
className,
containingEvent,
showFull
}: {
url: string
className?: string
containingEvent?: Event
showFull?: boolean
}) {
const naddr = naddrFromZapStreamWatchUrl(url)
if (!naddr) {
return <ExternalLink url={url} className={cn('not-prose', className)} />
}
return (
<EmbeddedNote
noteId={naddr}
className={cn('not-prose', className)}
containingEvent={containingEvent}
showFull={showFull}
/>
)
}

5
src/lib/youtube-url.ts

@ -48,3 +48,8 @@ export function parseYoutubeUrl(url: string): { videoId: string | null; isShort: @@ -48,3 +48,8 @@ export function parseYoutubeUrl(url: string): { videoId: string | null; isShort:
return { videoId: null, isShort: false }
}
}
/** True when the in-app YouTube player can embed this URL (watch, Shorts, live, youtu.be, embed). */
export function isEmbeddableYoutubeUrl(url: string): boolean {
return parseYoutubeUrl(url).videoId != null
}

13
src/lib/zap-stream-url.test.ts

@ -1,5 +1,9 @@ @@ -1,5 +1,9 @@
import { describe, expect, it } from 'vitest'
import { canonicalZapStreamWatchUrl, isZapStreamWatchUrl } from './zap-stream-url'
import {
canonicalZapStreamWatchUrl,
isZapStreamWatchUrl,
naddrFromZapStreamWatchUrl
} from './zap-stream-url'
const SAMPLE =
'https://zap.stream/naddr1qqjrqcmzv3nr2des95ergdp3956xzdf595uxzefk94nxzefnxgukycnzxyexyqgewaehxw309aex2mrp0yh8xmn0wf6zuum0vd5kzmp0qgsv73dxhgfk8tt76gf6q788zrfyz9dwwgwfk3aar6l5gk82a76v9fgrqsqqqan84z6qnu'
@ -32,4 +36,11 @@ describe('zap-stream-url', () => { @@ -32,4 +36,11 @@ describe('zap-stream-url', () => {
expect(isZapStreamWatchUrl(SAMPLE)).toBe(true)
expect(isZapStreamWatchUrl('https://zap.stream/')).toBe(false)
})
it('naddrFromZapStreamWatchUrl returns the naddr segment', () => {
expect(naddrFromZapStreamWatchUrl(SAMPLE)).toBe(
'naddr1qqjrqcmzv3nr2des95ergdp3956xzdf595uxzefk94nxzefnxgukycnzxyexyqgewaehxw309aex2mrp0yh8xmn0wf6zuum0vd5kzmp0qgsv73dxhgfk8tt76gf6q788zrfyz9dwwgwfk3aar6l5gk82a76v9fgrqsqqqan84z6qnu'
)
expect(naddrFromZapStreamWatchUrl('https://zap.stream/')).toBeNull()
})
})

13
src/lib/zap-stream-url.ts

@ -26,3 +26,16 @@ export function canonicalZapStreamWatchUrl(raw: string): string | null { @@ -26,3 +26,16 @@ export function canonicalZapStreamWatchUrl(raw: string): string | null {
export function isZapStreamWatchUrl(url: string): boolean {
return canonicalZapStreamWatchUrl(url) != null
}
/** NIP-19 `naddr1…` path segment from a zap.stream watch URL (embed as kind 30311 / LiveEvent). */
export function naddrFromZapStreamWatchUrl(raw: string): string | null {
const canon = canonicalZapStreamWatchUrl(raw)
if (!canon) return null
try {
const seg = new URL(canon).pathname.split('/').filter(Boolean)[0]
if (seg?.startsWith('naddr1')) return seg
return null
} catch {
return null
}
}

Loading…
Cancel
Save