mirror of
https://git.shipoclu.com/moon/plspace.git
synced 2026-08-13 02:42:30 +00:00
youtube embeds
This commit is contained in:
@@ -15,11 +15,13 @@
|
||||
import { displayNameOf, formatCount, fullHandle, profilePath } from '$lib/util/profile'
|
||||
import { renderDisplayName } from '$lib/util/html'
|
||||
import { isoDate, longDate, stampDate } from '$lib/util/time'
|
||||
import { extractYouTubeVideoIds } from '$lib/util/youtube'
|
||||
import Avatar from '../common/Avatar.svelte'
|
||||
import RichText from '../common/RichText.svelte'
|
||||
import Attachments from './Attachments.svelte'
|
||||
import PollView from './PollView.svelte'
|
||||
import PreviewCardView from './PreviewCardView.svelte'
|
||||
import YouTubeEmbeds from './YouTubeEmbeds.svelte'
|
||||
|
||||
interface Props {
|
||||
status: Status
|
||||
@@ -45,6 +47,7 @@
|
||||
const handle = $derived(fullHandle(author, session.host))
|
||||
const permalink = $derived(`#/blog/${entry.id}`)
|
||||
const isMine = $derived(session.me?.id === entry.account.id)
|
||||
const youtubeVideoIds = $derived(extractYouTubeVideoIds(entry.content))
|
||||
|
||||
let busy = $state(false)
|
||||
let actionError = $state<string | null>(null)
|
||||
@@ -188,6 +191,7 @@
|
||||
{#if entry.media_attachments.length > 0}
|
||||
<Attachments attachments={entry.media_attachments} sensitive={entry.sensitive} />
|
||||
{/if}
|
||||
<YouTubeEmbeds videoIds={youtubeVideoIds} sensitive={entry.sensitive} />
|
||||
</details>
|
||||
{:else}
|
||||
<RichText
|
||||
@@ -200,13 +204,14 @@
|
||||
{#if entry.media_attachments.length > 0}
|
||||
<Attachments attachments={entry.media_attachments} sensitive={entry.sensitive} />
|
||||
{/if}
|
||||
<YouTubeEmbeds videoIds={youtubeVideoIds} sensitive={entry.sensitive} />
|
||||
{/if}
|
||||
|
||||
{#if entry.poll}
|
||||
<PollView poll={entry.poll} />
|
||||
{/if}
|
||||
|
||||
{#if entry.card && entry.media_attachments.length === 0}
|
||||
{#if entry.card && entry.media_attachments.length === 0 && youtubeVideoIds.length === 0}
|
||||
<PreviewCardView card={entry.card} />
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { fireEvent, render } from '@testing-library/svelte'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { APP_SERVICES } from '$lib/app-services'
|
||||
import { status, testServices } from '$test/fixtures'
|
||||
import BlogEntry from './BlogEntry.svelte'
|
||||
|
||||
function renderEntry(overrides: Parameters<typeof status>[0]) {
|
||||
return render(BlogEntry, {
|
||||
props: { status: status(overrides) },
|
||||
context: new Map([[APP_SERVICES, testServices()]]),
|
||||
})
|
||||
}
|
||||
|
||||
describe('BlogEntry YouTube embeds', () => {
|
||||
it('turns a YouTube note link into an attachment-style embed', () => {
|
||||
const view = renderEntry({
|
||||
content:
|
||||
'<p>Watch this: <a href="https://www.youtube.com/watch?v=pyNCigSulNs">https://www.youtube.com/watch?v=pyNCigSulNs</a></p>',
|
||||
card: {
|
||||
url: 'https://www.youtube.com/watch?v=pyNCigSulNs',
|
||||
title: 'Server-generated YouTube preview',
|
||||
description: 'This should be replaced by the player.',
|
||||
type: 'video',
|
||||
},
|
||||
})
|
||||
|
||||
const frame = view.getByTitle('YouTube video')
|
||||
expect(frame).toHaveAttribute(
|
||||
'src',
|
||||
'https://www.youtube-nocookie.com/embed/pyNCigSulNs',
|
||||
)
|
||||
expect(frame.closest('.attachment')).toHaveAttribute('data-type', 'youtube')
|
||||
expect(view.getByRole('link', { name: 'Watch on YouTube' })).toHaveAttribute(
|
||||
'href',
|
||||
'https://www.youtube.com/watch?v=pyNCigSulNs',
|
||||
)
|
||||
expect(view.queryByText('Server-generated YouTube preview')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('does not load a sensitive player before the reader reveals it', async () => {
|
||||
const view = renderEntry({
|
||||
content: '<p><a href="https://youtu.be/pyNCigSulNs">video</a></p>',
|
||||
sensitive: true,
|
||||
})
|
||||
|
||||
expect(view.queryByTitle('YouTube video')).not.toBeInTheDocument()
|
||||
await fireEvent.click(view.getByRole('button', { name: 'Show sensitive video' }))
|
||||
expect(view.getByTitle('YouTube video')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,65 @@
|
||||
<script lang="ts">
|
||||
/** Privacy-enhanced YouTube players presented in the normal attachment grid. */
|
||||
import { youtubeEmbedUrl, youtubeVideoId, youtubeWatchUrl } from '$lib/util/youtube'
|
||||
|
||||
interface Props {
|
||||
videoIds: string[]
|
||||
sensitive?: boolean
|
||||
}
|
||||
|
||||
let { videoIds, sensitive = false }: Props = $props()
|
||||
let revealed = $state<Record<string, boolean>>({})
|
||||
|
||||
const validVideoIds = $derived(
|
||||
[...new Set(videoIds.filter((id) => youtubeVideoId(youtubeWatchUrl(id)) === id))].slice(0, 4),
|
||||
)
|
||||
|
||||
function isRevealed(id: string): boolean {
|
||||
return !sensitive || revealed[id] === true
|
||||
}
|
||||
|
||||
function toggle(id: string): void {
|
||||
revealed = { ...revealed, [id]: !revealed[id] }
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if validVideoIds.length > 0}
|
||||
<ul class="attachment-list youtube-attachment-list" aria-label="YouTube videos">
|
||||
{#each validVideoIds as videoId, index (videoId)}
|
||||
<li
|
||||
class="attachment youtube-attachment"
|
||||
data-type="youtube"
|
||||
data-sensitive={sensitive ? 'true' : 'false'}
|
||||
data-revealed={isRevealed(videoId) ? 'true' : 'false'}
|
||||
>
|
||||
<figure class="attachment-figure">
|
||||
<div class="youtube-embed">
|
||||
{#if isRevealed(videoId)}
|
||||
<iframe
|
||||
src={youtubeEmbedUrl(videoId)}
|
||||
title={validVideoIds.length === 1 ? 'YouTube video' : `YouTube video ${index + 1}`}
|
||||
loading="lazy"
|
||||
referrerpolicy="strict-origin-when-cross-origin"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowfullscreen
|
||||
></iframe>
|
||||
{:else}
|
||||
<div class="youtube-embed-placeholder">Sensitive YouTube video</div>
|
||||
{/if}
|
||||
</div>
|
||||
<figcaption class="attachment-caption">
|
||||
<a href={youtubeWatchUrl(videoId)} target="_blank" rel="noopener noreferrer">
|
||||
Watch on YouTube
|
||||
</a>
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
{#if sensitive}
|
||||
<button type="button" class="button button--small attachment-reveal" onclick={() => toggle(videoId)}>
|
||||
{isRevealed(videoId) ? 'Hide' : 'Show'} sensitive video
|
||||
</button>
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
Reference in New Issue
Block a user