Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fixes #1395 #1430

Merged
merged 11 commits into from
Sep 28, 2024
6 changes: 5 additions & 1 deletion components/carousel.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,11 @@ export function CarouselProvider ({ children }) {
media.current.set(src, { src, originalSrc, rel })
}, [media.current])

const value = useMemo(() => ({ showCarousel, addMedia }), [showCarousel, addMedia])
const removeMedia = useCallback((src) => {
media.current.delete(src)
}, [media.current])

const value = useMemo(() => ({ showCarousel, addMedia, removeMedia }), [showCarousel, addMedia, removeMedia])
return <CarouselContext.Provider value={value}>{children}</CarouselContext.Provider>
}

Expand Down
204 changes: 204 additions & 0 deletions components/embed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import { memo, useEffect, useRef, useState } from 'react'
import classNames from 'classnames'
import useDarkMode from './dark-mode'
import styles from './text.module.css'
import { Button } from 'react-bootstrap'
import { TwitterTweetEmbed } from 'react-twitter-embed'
import YouTube from 'react-youtube'

function TweetSkeleton ({ className }) {
return (
<div className={classNames(styles.tweetsSkeleton, className)}>
<div className={styles.tweetSkeleton}>
<div className={`${styles.img} clouds`} />
<div className={styles.content1}>
<div className={`${styles.line} clouds`} />
<div className={`${styles.line} clouds`} />
<div className={`${styles.line} clouds`} />
</div>
</div>
</div>
)
}

export const NostrEmbed = memo(function NostrEmbed ({ src, className, topLevel, id }) {
const [show, setShow] = useState(false)
const iframeRef = useRef(null)

useEffect(() => {
if (!iframeRef.current) return

const setHeightFromIframe = (e) => {
if (e.origin !== 'https://njump.me' || !e?.data?.height || e.source !== iframeRef.current.contentWindow) return
iframeRef.current.height = `${e.data.height}px`
}

window?.addEventListener('message', setHeightFromIframe)

// https://github.com/vercel/next.js/issues/39451
iframeRef.current.src = `https://njump.me/${id}?embed=yes`

return () => {
window?.removeEventListener('message', setHeightFromIframe)
}
}, [iframeRef.current])

return (
<div className={classNames(styles.nostrContainer, !show && styles.twitterContained, className)}>
<iframe
ref={iframeRef}
width={topLevel ? '550px' : '350px'}
style={{ maxWidth: '100%' }}
height={iframeRef.current?.height || (topLevel ? '200px' : '150px')}
frameBorder='0'
sandbox='allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox'
allow=''
/>
{!show &&
<Button size='md' variant='info' className={styles.twitterShowFull} onClick={() => setShow(true)}>
<div>show full note</div>
<small className='fw-normal fst-italic'>or other stuff</small>
</Button>}
</div>
)
})

const SpotifyEmbed = function SpotifyEmbed ({ src, className }) {
const iframeRef = useRef(null)

// https://open.spotify.com/track/1KFxcj3MZrpBGiGA8ZWriv?si=f024c3aa52294aa1
// Remove any additional path segments
const url = new URL(src)
url.pathname = url.pathname.replace(/\/intl-\w+\//, '/')

useEffect(() => {
if (!iframeRef.current) return

const id = url.pathname.split('/').pop()

// https://developer.spotify.com/documentation/embeds/tutorials/using-the-iframe-api
window.onSpotifyIframeApiReady = (IFrameAPI) => {
const options = {
uri: `spotify:episode:${id}`
}
const callback = (EmbedController) => {}
IFrameAPI.createController(iframeRef.current, options, callback)
}

return () => { window.onSpotifyIframeApiReady = null }
}, [iframeRef.current, url.pathname])

return (
<div className={classNames(styles.spotifyWrapper, className)}>
<iframe
ref={iframeRef}
title='Spotify Web Player'
src={`https://open.spotify.com/embed${url.pathname}`}
width='100%'
height='152'
allowFullScreen
frameBorder='0'
allow='encrypted-media; clipboard-write;'
style={{ borderRadius: '12px' }}
sandbox='allow-scripts allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-presentation'
/>
</div>
)
}

const Embed = memo(function Embed ({ src, provider, id, meta, className, topLevel, onError }) {
const [darkMode] = useDarkMode()
const [overflowing, setOverflowing] = useState(true)
const [show, setShow] = useState(false)

// This Twitter embed could use similar logic to the video embeds below
if (provider === 'twitter') {
return (
<div className={classNames(styles.twitterContainer, !show && styles.twitterContained, className)}>
<TwitterTweetEmbed
tweetId={id}
options={{ theme: darkMode ? 'dark' : 'light', width: topLevel ? '550px' : '350px' }}
key={darkMode ? '1' : '2'}
placeholder={<TweetSkeleton className={className} />}
onLoad={() => setOverflowing(true)}
/>
{overflowing && !show &&
<Button size='lg' variant='info' className={styles.twitterShowFull} onClick={() => setShow(true)}>
show full tweet
</Button>}
</div>
)
}

if (provider === 'nostr') {
return (
<NostrEmbed src={src} className={className} topLevel={topLevel} id={id} />
)
}

if (provider === 'wavlake') {
return (
<div className={classNames(styles.wavlakeWrapper, className)}>
<iframe
src={`https://embed.wavlake.com/track/${id}`} width='100%' height='380' frameBorder='0'
allow='encrypted-media'
sandbox='allow-scripts allow-popups allow-popups-to-escape-sandbox allow-forms allow-same-origin'
/>
</div>
)
}

if (provider === 'spotify') {
return (
<SpotifyEmbed src={src} className={className} />
)
}

if (provider === 'youtube') {
return (
<div className={classNames(styles.videoWrapper, className)}>
<YouTube
videoId={id} className={styles.videoContainer} opts={{
playerVars: {
start: meta?.start || 0
}
}}
/>
</div>
)
}

if (provider === 'rumble') {
return (
<div className={classNames(styles.videoWrapper, className)}>
<div className={styles.videoContainer}>
<iframe
title='Rumble Video'
allowFullScreen
src={meta?.href}
sandbox='allow-scripts'
/>
</div>
</div>
)
}

if (provider === 'peertube') {
return (
<div className={classNames(styles.videoWrapper, className)}>
<div className={styles.videoContainer}>
<iframe
title='PeerTube Video'
allowFullScreen
src={meta?.href}
sandbox='allow-scripts'
/>
</div>
</div>
)
}

return null
})

export default Embed
2 changes: 1 addition & 1 deletion components/form.js
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ export function MarkdownInput ({ label, topLevel, groupClassName, onChange, onKe
{tab !== 'write' &&
<div className='form-group'>
<div className={`${styles.text} form-control`}>
<Text topLevel={topLevel} noFragments tab={tab}>{meta.value}</Text>
<Text topLevel={topLevel} tab={tab}>{meta.value}</Text>
</div>
</div>}
</div>
Expand Down
Loading
Loading