tangled
alpha
login
or
join now
flo-bit.dev
/
blento
your personal website on atproto - mirror
blento.app
20
fork
atom
overview
issues
pulls
pipelines
improve youtube card
Florian
3 weeks ago
1d2c5f48
edbf90ae
+87
-47
9 changed files
expand all
collapse all
unified
split
.claude
settings.local.json
src
lib
cards
VideoCard
index.ts
YoutubeVideo
YoutubeCard.svelte
YoutubeVideoCard
YoutubeCard.svelte
YoutubeCardSettings.svelte
index.ts
index.ts
utils
YoutubeVideoPlayer.svelte
website
EditableWebsite.svelte
+2
-1
.claude/settings.local.json
···
2
"permissions": {
3
"allow": [
4
"Bash(pnpm check:*)",
5
-
"mcp__ide__getDiagnostics"
0
6
]
7
}
8
}
···
2
"permissions": {
3
"allow": [
4
"Bash(pnpm check:*)",
5
+
"mcp__ide__getDiagnostics",
6
+
"mcp__plugin_svelte_svelte__svelte-autofixer"
7
]
8
}
9
}
+1
-7
src/lib/cards/VideoCard/index.ts
···
59
},
60
settingsComponent: VideoCardSettings,
61
62
-
canChange: (item) => Boolean(item.cardData.video),
63
-
64
-
change: (item) => {
65
-
return item;
66
-
},
67
-
name: 'Video Card',
68
-
sidebarButtonText: 'Video'
69
} as CardDefinition & { type: 'video' };
···
59
},
60
settingsComponent: VideoCardSettings,
61
62
+
name: 'Video Card'
0
0
0
0
0
0
63
} as CardDefinition & { type: 'video' };
-32
src/lib/cards/YoutubeVideo/YoutubeCard.svelte
···
1
-
<script lang="ts">
2
-
import { videoPlayer } from '../utils/YoutubeVideoPlayer.svelte';
3
-
import type { ContentComponentProps } from '../types';
4
-
5
-
let { item }: ContentComponentProps = $props();
6
-
</script>
7
-
8
-
<img
9
-
class={[
10
-
'absolute inset-0 h-full w-full object-cover opacity-100 transition-transform duration-300 ease-in-out',
11
-
item.cardData.href ? 'group-hover:scale-102' : ''
12
-
]}
13
-
src={item.cardData.poster}
14
-
alt=""
15
-
/>
16
-
<button
17
-
onclick={() => {
18
-
videoPlayer.show(item.cardData.youtubeId);
19
-
}}
20
-
class="absolute inset-0 flex h-full w-full cursor-pointer items-center justify-center"
21
-
>
22
-
<span class="sr-only">
23
-
{item.cardData.hrefText ?? 'Learn more'}
24
-
</span>
25
-
26
-
<svg xmlns="http://www.w3.org/2000/svg" class="text-accent-500 w-14" viewBox="0 0 256 180"
27
-
><path
28
-
fill="currentColor"
29
-
d="M250.346 28.075A32.18 32.18 0 0 0 227.69 5.418C207.824 0 127.87 0 127.87 0S47.912.164 28.046 5.582A32.18 32.18 0 0 0 5.39 28.24c-6.009 35.298-8.34 89.084.165 122.97a32.18 32.18 0 0 0 22.656 22.657c19.866 5.418 99.822 5.418 99.822 5.418s79.955 0 99.82-5.418a32.18 32.18 0 0 0 22.657-22.657c6.338-35.348 8.291-89.1-.164-123.134"
30
-
/><path fill="#fff" d="m102.421 128.06l66.328-38.418l-66.328-38.418z" /></svg
31
-
>
32
-
</button>
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
+2
src/lib/cards/YoutubeVideo/index.ts
src/lib/cards/YoutubeVideoCard/index.ts
···
1
import type { CardDefinition } from '../types';
2
import YoutubeCard from './YoutubeCard.svelte';
0
3
4
export const YoutubeCardDefinition = {
5
type: 'youtubeVideo',
6
contentComponent: YoutubeCard,
0
7
createNew: (card) => {
8
card.cardType = 'youtubeVideo';
9
card.cardData = {};
···
1
import type { CardDefinition } from '../types';
2
import YoutubeCard from './YoutubeCard.svelte';
3
+
import YoutubeCardSettings from './YoutubeCardSettings.svelte';
4
5
export const YoutubeCardDefinition = {
6
type: 'youtubeVideo',
7
contentComponent: YoutubeCard,
8
+
settingsComponent: YoutubeCardSettings,
9
createNew: (card) => {
10
card.cardType = 'youtubeVideo';
11
card.cardData = {};
+46
src/lib/cards/YoutubeVideoCard/YoutubeCard.svelte
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
<script lang="ts">
2
+
import { videoPlayer } from '../utils/YoutubeVideoPlayer.svelte';
3
+
import type { ContentComponentProps } from '../types';
4
+
5
+
let { item }: ContentComponentProps = $props();
6
+
7
+
let isPlaying = $state(false);
8
+
</script>
9
+
10
+
{#if isPlaying && item.cardData.showInline}
11
+
<iframe
12
+
class="absolute inset-0 h-full w-full"
13
+
src="https://www.youtube.com/embed/{item.cardData.youtubeId}?autoplay=1"
14
+
title="YouTube video player"
15
+
frameborder="0"
16
+
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
17
+
allowfullscreen
18
+
></iframe>
19
+
{:else}
20
+
<img
21
+
class={[
22
+
'absolute inset-0 h-full w-full object-cover opacity-100 transition-transform duration-300 ease-in-out',
23
+
item.cardData.href ? 'group-hover:scale-102' : ''
24
+
]}
25
+
src={item.cardData.poster}
26
+
alt=""
27
+
/>
28
+
<button
29
+
onclick={() => {
30
+
if (item.cardData.showInline) isPlaying = true;
31
+
else videoPlayer.show(item.cardData.youtubeId);
32
+
}}
33
+
class="absolute inset-0 flex h-full w-full cursor-pointer items-center justify-center"
34
+
>
35
+
<span class="sr-only">
36
+
{item.cardData.hrefText ?? 'Learn more'}
37
+
</span>
38
+
39
+
<svg xmlns="http://www.w3.org/2000/svg" class="text-accent-500 w-14" viewBox="0 0 256 180"
40
+
><path
41
+
fill="currentColor"
42
+
d="M250.346 28.075A32.18 32.18 0 0 0 227.69 5.418C207.824 0 127.87 0 127.87 0S47.912.164 28.046 5.582A32.18 32.18 0 0 0 5.39 28.24c-6.009 35.298-8.34 89.084.165 122.97a32.18 32.18 0 0 0 22.656 22.657c19.866 5.418 99.822 5.418 99.822 5.418s79.955 0 99.82-5.418a32.18 32.18 0 0 0 22.657-22.657c6.338-35.348 8.291-89.1-.164-123.134"
43
+
/><path fill="#fff" d="m102.421 128.06l66.328-38.418l-66.328-38.418z" /></svg
44
+
>
45
+
</button>
46
+
{/if}
+24
src/lib/cards/YoutubeVideoCard/YoutubeCardSettings.svelte
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
<script lang="ts">
2
+
import type { Item } from '$lib/types';
3
+
import { Checkbox, Label } from '@foxui/core';
4
+
5
+
let { item }: { item: Item; onclose: () => void } = $props();
6
+
</script>
7
+
8
+
<div class="flex items-center space-x-2">
9
+
<Checkbox
10
+
bind:checked={
11
+
() => Boolean(item.cardData.showInline), (val) => (item.cardData.showInline = val)
12
+
}
13
+
id="show-inline"
14
+
aria-labelledby="show-inline-label"
15
+
variant="secondary"
16
+
/>
17
+
<Label
18
+
id="show-inline-label"
19
+
for="show-inline"
20
+
class="text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
21
+
>
22
+
Show video in card
23
+
</Label>
24
+
</div>
+1
-1
src/lib/cards/index.ts
···
14
import { TextCardDefinition } from './TextCard';
15
import type { CardDefinition } from './types';
16
import { VideoCardDefinition } from './VideoCard';
17
-
import { YoutubeCardDefinition } from './YoutubeVideo';
18
import { BlueskyProfileCardDefinition } from './BlueskyProfileCard';
19
20
export const AllCardDefinitions = [
···
14
import { TextCardDefinition } from './TextCard';
15
import type { CardDefinition } from './types';
16
import { VideoCardDefinition } from './VideoCard';
17
+
import { YoutubeCardDefinition } from './YoutubeVideoCard';
18
import { BlueskyProfileCardDefinition } from './BlueskyProfileCard';
19
20
export const AllCardDefinitions = [
+9
-6
src/lib/cards/utils/YoutubeVideoPlayer.svelte
···
19
20
<script lang="ts">
21
import { cn } from '@foxui/core';
22
-
import { onMount } from 'svelte';
23
24
const { class: className }: { class?: string } = $props();
25
26
let Plyr = $state();
0
0
0
27
28
onMount(async () => {
29
if (!Plyr) Plyr = (await import('plyr')).default;
···
60
player.play();
61
//player.fullscreen.enter();
62
});
63
-
64
-
return () => {
65
-
player.destroy();
66
-
};
67
});
0
0
0
0
68
69
let glow = 50;
70
</script>
···
142
143
<svg width="0" height="0">
144
<filter id="blur" y="-50%" x="-50%" width="300%" height="300%">
145
-
<feGaussianBlur in="SourceGraphic" stdDeviation={50} result="blurred" />
146
<feColorMatrix type="saturate" in="blurred" values="3" />
147
<feComposite in="SourceGraphic" operator="over" />
148
</filter>
···
19
20
<script lang="ts">
21
import { cn } from '@foxui/core';
22
+
import { onDestroy, onMount } from 'svelte';
23
24
const { class: className }: { class?: string } = $props();
25
26
let Plyr = $state();
27
+
28
+
let player = $state();
29
+
30
31
onMount(async () => {
32
if (!Plyr) Plyr = (await import('plyr')).default;
···
63
player.play();
64
//player.fullscreen.enter();
65
});
0
0
0
0
66
});
67
+
68
+
onDestroy(() => {
69
+
player?.destroy();
70
+
})
71
72
let glow = 50;
73
</script>
···
145
146
<svg width="0" height="0">
147
<filter id="blur" y="-50%" x="-50%" width="300%" height="300%">
148
+
<feGaussianBlur in="SourceGraphic" stdDeviation={glow} result="blurred" />
149
<feColorMatrix type="saturate" in="blurred" values="3" />
150
<feComposite in="SourceGraphic" operator="over" />
151
</filter>
+2
src/lib/website/EditableWebsite.svelte
···
869
</svg>
870
</Button>
871
0
872
<Button
873
size="iconLg"
874
variant="ghost"
···
891
/>
892
</svg>
893
</Button>
0
894
895
<Button
896
size="iconLg"
···
869
</svg>
870
</Button>
871
872
+
{#if dev}
873
<Button
874
size="iconLg"
875
variant="ghost"
···
892
/>
893
</svg>
894
</Button>
895
+
{/if}
896
897
<Button
898
size="iconLg"