sveltekit-embeds 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +90 -0
- package/dist/components/apple-music.svelte +39 -0
- package/dist/components/apple-music.svelte.d.ts +11 -0
- package/dist/components/apple-podcasts.svelte +39 -0
- package/dist/components/apple-podcasts.svelte.d.ts +11 -0
- package/dist/components/bluesky.svelte +75 -0
- package/dist/components/bluesky.svelte.d.ts +10 -0
- package/dist/components/dailymotion.svelte +70 -0
- package/dist/components/dailymotion.svelte.d.ts +12 -0
- package/dist/components/deezer.svelte +40 -0
- package/dist/components/deezer.svelte.d.ts +11 -0
- package/dist/components/general-observer.svelte +46 -0
- package/dist/components/general-observer.svelte.d.ts +9 -0
- package/dist/components/instagram.svelte +37 -0
- package/dist/components/instagram.svelte.d.ts +10 -0
- package/dist/components/linked-in.svelte +37 -0
- package/dist/components/linked-in.svelte.d.ts +10 -0
- package/dist/components/mastodon.svelte +78 -0
- package/dist/components/mastodon.svelte.d.ts +10 -0
- package/dist/components/pinterest.svelte +86 -0
- package/dist/components/pinterest.svelte.d.ts +7 -0
- package/dist/components/reddit.svelte +38 -0
- package/dist/components/reddit.svelte.d.ts +11 -0
- package/dist/components/sound-cloud.svelte +48 -0
- package/dist/components/sound-cloud.svelte.d.ts +13 -0
- package/dist/components/spotify.svelte +40 -0
- package/dist/components/spotify.svelte.d.ts +11 -0
- package/dist/components/threads.svelte +88 -0
- package/dist/components/threads.svelte.d.ts +7 -0
- package/dist/components/tidal.svelte +37 -0
- package/dist/components/tidal.svelte.d.ts +10 -0
- package/dist/components/tik-tok.svelte +74 -0
- package/dist/components/tik-tok.svelte.d.ts +13 -0
- package/dist/components/twitch.svelte +71 -0
- package/dist/components/twitch.svelte.d.ts +12 -0
- package/dist/components/x-embed.svelte +94 -0
- package/dist/components/x-embed.svelte.d.ts +8 -0
- package/dist/components/you-tube.svelte +85 -0
- package/dist/components/you-tube.svelte.d.ts +14 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +20 -0
- package/dist/utils/apple-music.d.ts +11 -0
- package/dist/utils/apple-music.js +59 -0
- package/dist/utils/apple-podcasts.d.ts +11 -0
- package/dist/utils/apple-podcasts.js +56 -0
- package/dist/utils/bluesky.d.ts +8 -0
- package/dist/utils/bluesky.js +35 -0
- package/dist/utils/dailymotion.d.ts +2 -0
- package/dist/utils/dailymotion.js +21 -0
- package/dist/utils/deezer.d.ts +10 -0
- package/dist/utils/deezer.js +56 -0
- package/dist/utils/index.d.ts +19 -0
- package/dist/utils/index.js +25 -0
- package/dist/utils/instagram.d.ts +2 -0
- package/dist/utils/instagram.js +21 -0
- package/dist/utils/linkedin.d.ts +7 -0
- package/dist/utils/linkedin.js +38 -0
- package/dist/utils/mastodon.d.ts +12 -0
- package/dist/utils/mastodon.js +36 -0
- package/dist/utils/pinterest.d.ts +2 -0
- package/dist/utils/pinterest.js +21 -0
- package/dist/utils/reddit.d.ts +2 -0
- package/dist/utils/reddit.js +45 -0
- package/dist/utils/soundcloud.d.ts +11 -0
- package/dist/utils/soundcloud.js +26 -0
- package/dist/utils/spotify.d.ts +10 -0
- package/dist/utils/spotify.js +71 -0
- package/dist/utils/threads.d.ts +9 -0
- package/dist/utils/threads.js +49 -0
- package/dist/utils/tidal.d.ts +10 -0
- package/dist/utils/tidal.js +53 -0
- package/dist/utils/tiktok.d.ts +2 -0
- package/dist/utils/tiktok.js +18 -0
- package/dist/utils/twitch.d.ts +9 -0
- package/dist/utils/twitch.js +37 -0
- package/dist/utils/twitter.d.ts +2 -0
- package/dist/utils/twitter.js +35 -0
- package/dist/utils/youtube.d.ts +3 -0
- package/dist/utils/youtube.js +40 -0
- package/package.json +79 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sander Ginn
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# sveltekit-embeds
|
|
2
|
+
|
|
3
|
+
Svelte 5 embed component library for media and social platforms.
|
|
4
|
+
|
|
5
|
+
## What you get
|
|
6
|
+
|
|
7
|
+
- Lazy-loaded embeds via `GeneralObserver` (IntersectionObserver-based)
|
|
8
|
+
- URL parsing and embed URL helpers in `src/lib/utils`
|
|
9
|
+
- Iframe embeds and script-injection embeds in `src/lib/components`
|
|
10
|
+
- TypeScript + Svelte package output (`dist/`) via `svelte-package`
|
|
11
|
+
|
|
12
|
+
## Exported components
|
|
13
|
+
|
|
14
|
+
- `GeneralObserver`
|
|
15
|
+
- `YouTube`, `TikTok`, `Twitch`, `Dailymotion`
|
|
16
|
+
- `Spotify`, `AppleMusic`, `ApplePodcasts`, `SoundCloud`, `Deezer`, `Tidal`
|
|
17
|
+
- `XEmbed`, `Instagram`, `Threads`, `Bluesky`, `Mastodon`, `LinkedIn`, `Reddit`, `Pinterest`
|
|
18
|
+
|
|
19
|
+
Utilities are also re-exported from `src/lib/utils`.
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
```svelte
|
|
24
|
+
<script lang="ts">
|
|
25
|
+
import { YouTube, Spotify, XEmbed } from 'sveltekit-embeds';
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<YouTube url="https://www.youtube.com/watch?v=dQw4w9WgXcQ" />
|
|
29
|
+
<Spotify url="https://open.spotify.com/track/4uLU6hMCjMI75M1A2tKUQC" />
|
|
30
|
+
<XEmbed url="https://x.com/jack/status/20" />
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Most components support:
|
|
34
|
+
|
|
35
|
+
- `url` (required)
|
|
36
|
+
- `disable_observer` (optional, bypass lazy loading)
|
|
37
|
+
- sizing/style props (varies per component, see component files)
|
|
38
|
+
|
|
39
|
+
## Local development
|
|
40
|
+
|
|
41
|
+
From the repository root:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pnpm install
|
|
45
|
+
pnpm --filter sveltekit-embeds dev
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Run static checks:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pnpm --filter sveltekit-embeds check
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Run tests (server utils + browser component tests):
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pnpm --filter sveltekit-embeds test:unit
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Build package output and run publint:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pnpm --filter sveltekit-embeds prepack
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Publish to npm
|
|
67
|
+
|
|
68
|
+
The package is configured for public npm publish (`publishConfig.access=public`).
|
|
69
|
+
|
|
70
|
+
From repository root:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npm login
|
|
74
|
+
pnpm --filter sveltekit-embeds test:unit
|
|
75
|
+
pnpm --filter sveltekit-embeds check
|
|
76
|
+
pnpm --filter sveltekit-embeds pack:dry-run
|
|
77
|
+
pnpm --filter sveltekit-embeds exec npm version patch
|
|
78
|
+
pnpm --filter sveltekit-embeds publish:npm
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Use `npm version minor` or `npm version major` when needed.
|
|
82
|
+
|
|
83
|
+
## Testing notes
|
|
84
|
+
|
|
85
|
+
- Browser component tests use `vitest-browser-svelte` + Playwright Chromium.
|
|
86
|
+
- First run may download browser binaries:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
pnpm --filter sveltekit-embeds exec playwright install chromium
|
|
90
|
+
```
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import GeneralObserver from './general-observer.svelte';
|
|
3
|
+
import { getAppleMusicEmbedUrl, parseAppleMusicUrl } from '../utils/apple-music.js';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
url: string;
|
|
7
|
+
theme?: 'light' | 'dark';
|
|
8
|
+
width?: string;
|
|
9
|
+
height?: string;
|
|
10
|
+
disable_observer?: boolean;
|
|
11
|
+
iframe_styles?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let {
|
|
15
|
+
url,
|
|
16
|
+
theme = 'light',
|
|
17
|
+
width = '100%',
|
|
18
|
+
height = '450',
|
|
19
|
+
disable_observer = false,
|
|
20
|
+
iframe_styles
|
|
21
|
+
}: Props = $props();
|
|
22
|
+
|
|
23
|
+
const parsed = $derived(parseAppleMusicUrl(url));
|
|
24
|
+
const embedUrl = $derived(getAppleMusicEmbedUrl(url, theme));
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<GeneralObserver {disable_observer}>
|
|
28
|
+
{#if embedUrl && parsed.isValid}
|
|
29
|
+
<iframe
|
|
30
|
+
src={embedUrl}
|
|
31
|
+
{width}
|
|
32
|
+
{height}
|
|
33
|
+
title={`apple-music-${parsed.type}-${parsed.id}`}
|
|
34
|
+
allow="autoplay *; encrypted-media *; fullscreen *; clipboard-write"
|
|
35
|
+
loading="lazy"
|
|
36
|
+
style={iframe_styles}
|
|
37
|
+
></iframe>
|
|
38
|
+
{/if}
|
|
39
|
+
</GeneralObserver>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
url: string;
|
|
3
|
+
theme?: 'light' | 'dark';
|
|
4
|
+
width?: string;
|
|
5
|
+
height?: string;
|
|
6
|
+
disable_observer?: boolean;
|
|
7
|
+
iframe_styles?: string;
|
|
8
|
+
}
|
|
9
|
+
declare const AppleMusic: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type AppleMusic = ReturnType<typeof AppleMusic>;
|
|
11
|
+
export default AppleMusic;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import GeneralObserver from './general-observer.svelte';
|
|
3
|
+
import { getApplePodcastsEmbedUrl, parseApplePodcastsUrl } from '../utils/apple-podcasts.js';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
url: string;
|
|
7
|
+
theme?: 'light' | 'dark';
|
|
8
|
+
width?: string;
|
|
9
|
+
height?: string;
|
|
10
|
+
disable_observer?: boolean;
|
|
11
|
+
iframe_styles?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let {
|
|
15
|
+
url,
|
|
16
|
+
theme = 'light',
|
|
17
|
+
width = '100%',
|
|
18
|
+
height = '175',
|
|
19
|
+
disable_observer = false,
|
|
20
|
+
iframe_styles
|
|
21
|
+
}: Props = $props();
|
|
22
|
+
|
|
23
|
+
const parsed = $derived(parseApplePodcastsUrl(url));
|
|
24
|
+
const embedUrl = $derived(getApplePodcastsEmbedUrl(url, theme));
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<GeneralObserver {disable_observer}>
|
|
28
|
+
{#if embedUrl && parsed.isValid}
|
|
29
|
+
<iframe
|
|
30
|
+
src={embedUrl}
|
|
31
|
+
{width}
|
|
32
|
+
{height}
|
|
33
|
+
title={`apple-podcasts-${parsed.type}-${parsed.id}`}
|
|
34
|
+
allow="autoplay *; encrypted-media *; fullscreen *"
|
|
35
|
+
loading="lazy"
|
|
36
|
+
style={iframe_styles}
|
|
37
|
+
></iframe>
|
|
38
|
+
{/if}
|
|
39
|
+
</GeneralObserver>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
url: string;
|
|
3
|
+
theme?: 'light' | 'dark';
|
|
4
|
+
width?: string;
|
|
5
|
+
height?: string;
|
|
6
|
+
disable_observer?: boolean;
|
|
7
|
+
iframe_styles?: string;
|
|
8
|
+
}
|
|
9
|
+
declare const ApplePodcasts: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type ApplePodcasts = ReturnType<typeof ApplePodcasts>;
|
|
11
|
+
export default ApplePodcasts;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import GeneralObserver from './general-observer.svelte';
|
|
3
|
+
import { getBlueskyEmbedUrl, parseBlueskyUrl } from '../utils/bluesky.js';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
url: string;
|
|
7
|
+
width?: string;
|
|
8
|
+
height?: string;
|
|
9
|
+
disable_observer?: boolean;
|
|
10
|
+
iframe_styles?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
url,
|
|
15
|
+
width = '100%',
|
|
16
|
+
height = '500',
|
|
17
|
+
disable_observer = false,
|
|
18
|
+
iframe_styles
|
|
19
|
+
}: Props = $props();
|
|
20
|
+
|
|
21
|
+
const parsed = $derived(parseBlueskyUrl(url));
|
|
22
|
+
const embedUrl = $derived(getBlueskyEmbedUrl(url));
|
|
23
|
+
let iframeElement = $state<HTMLIFrameElement | null>(null);
|
|
24
|
+
let wrapperHeight = $state('500px');
|
|
25
|
+
|
|
26
|
+
$effect(() => {
|
|
27
|
+
wrapperHeight = height;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
$effect(() => {
|
|
31
|
+
if (!embedUrl) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const onMessage = (event: MessageEvent) => {
|
|
36
|
+
if (event.origin !== 'https://embed.bsky.app') {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (event.source !== iframeElement?.contentWindow) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (typeof event.data !== 'object' || event.data === null) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const payload = event.data as { height?: unknown };
|
|
49
|
+
if (typeof payload.height !== 'number' || Number.isNaN(payload.height)) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
wrapperHeight = `${Math.max(payload.height, 100)}px`;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
window.addEventListener('message', onMessage);
|
|
57
|
+
return () => window.removeEventListener('message', onMessage);
|
|
58
|
+
});
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<GeneralObserver {disable_observer}>
|
|
62
|
+
{#if embedUrl && parsed.isValid}
|
|
63
|
+
<div style={`height: ${wrapperHeight}; width: ${width};`}>
|
|
64
|
+
<iframe
|
|
65
|
+
bind:this={iframeElement}
|
|
66
|
+
src={embedUrl}
|
|
67
|
+
width={width}
|
|
68
|
+
height={wrapperHeight}
|
|
69
|
+
title={`bluesky-${parsed.handle}-${parsed.postId}`}
|
|
70
|
+
loading="lazy"
|
|
71
|
+
style={iframe_styles}
|
|
72
|
+
></iframe>
|
|
73
|
+
</div>
|
|
74
|
+
{/if}
|
|
75
|
+
</GeneralObserver>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
url: string;
|
|
3
|
+
width?: string;
|
|
4
|
+
height?: string;
|
|
5
|
+
disable_observer?: boolean;
|
|
6
|
+
iframe_styles?: string;
|
|
7
|
+
}
|
|
8
|
+
declare const Bluesky: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type Bluesky = ReturnType<typeof Bluesky>;
|
|
10
|
+
export default Bluesky;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import GeneralObserver from './general-observer.svelte';
|
|
3
|
+
import { getPadding } from '../utils/index.js';
|
|
4
|
+
import { extractDailymotionId, getDailymotionEmbedUrl } from '../utils/dailymotion.js';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
url: string;
|
|
8
|
+
aspectRatio?: string;
|
|
9
|
+
autoplay?: boolean;
|
|
10
|
+
mute?: boolean;
|
|
11
|
+
controls?: boolean;
|
|
12
|
+
disable_observer?: boolean;
|
|
13
|
+
iframe_styles?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
url,
|
|
18
|
+
aspectRatio = '16:9',
|
|
19
|
+
autoplay = false,
|
|
20
|
+
mute = false,
|
|
21
|
+
controls = true,
|
|
22
|
+
disable_observer = false,
|
|
23
|
+
iframe_styles
|
|
24
|
+
}: Props = $props();
|
|
25
|
+
|
|
26
|
+
const videoId = $derived(extractDailymotionId(url));
|
|
27
|
+
const baseEmbedUrl = $derived(getDailymotionEmbedUrl(url));
|
|
28
|
+
const paddingStyle = $derived(getPadding(aspectRatio));
|
|
29
|
+
const embedUrl = $derived.by(() => {
|
|
30
|
+
if (!baseEmbedUrl) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const query = new URL(baseEmbedUrl);
|
|
35
|
+
query.searchParams.set('autoplay', autoplay ? '1' : '0');
|
|
36
|
+
query.searchParams.set('mute', mute ? '1' : '0');
|
|
37
|
+
query.searchParams.set('controls', controls ? '1' : '0');
|
|
38
|
+
return query.toString();
|
|
39
|
+
});
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<GeneralObserver {disable_observer}>
|
|
43
|
+
{#if embedUrl}
|
|
44
|
+
<div class="wrapper" style={paddingStyle}>
|
|
45
|
+
<iframe
|
|
46
|
+
src={embedUrl}
|
|
47
|
+
title={`dailymotion-${videoId}`}
|
|
48
|
+
allow="autoplay; fullscreen; picture-in-picture"
|
|
49
|
+
allowfullscreen
|
|
50
|
+
loading="lazy"
|
|
51
|
+
style={iframe_styles}
|
|
52
|
+
></iframe>
|
|
53
|
+
</div>
|
|
54
|
+
{/if}
|
|
55
|
+
</GeneralObserver>
|
|
56
|
+
|
|
57
|
+
<style>
|
|
58
|
+
.wrapper {
|
|
59
|
+
position: relative;
|
|
60
|
+
width: 100%;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
iframe {
|
|
64
|
+
position: absolute;
|
|
65
|
+
inset: 0;
|
|
66
|
+
width: 100%;
|
|
67
|
+
height: 100%;
|
|
68
|
+
border: 0;
|
|
69
|
+
}
|
|
70
|
+
</style>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
url: string;
|
|
3
|
+
aspectRatio?: string;
|
|
4
|
+
autoplay?: boolean;
|
|
5
|
+
mute?: boolean;
|
|
6
|
+
controls?: boolean;
|
|
7
|
+
disable_observer?: boolean;
|
|
8
|
+
iframe_styles?: string;
|
|
9
|
+
}
|
|
10
|
+
declare const Dailymotion: import("svelte").Component<Props, {}, "">;
|
|
11
|
+
type Dailymotion = ReturnType<typeof Dailymotion>;
|
|
12
|
+
export default Dailymotion;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import GeneralObserver from './general-observer.svelte';
|
|
3
|
+
import { getDeezerEmbedUrl, parseDeezerUrl } from '../utils/deezer.js';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
url: string;
|
|
7
|
+
theme?: 'light' | 'dark';
|
|
8
|
+
width?: string;
|
|
9
|
+
height?: string;
|
|
10
|
+
disable_observer?: boolean;
|
|
11
|
+
iframe_styles?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let {
|
|
15
|
+
url,
|
|
16
|
+
theme = 'light',
|
|
17
|
+
width = '100%',
|
|
18
|
+
height,
|
|
19
|
+
disable_observer = false,
|
|
20
|
+
iframe_styles
|
|
21
|
+
}: Props = $props();
|
|
22
|
+
|
|
23
|
+
const parsed = $derived(parseDeezerUrl(url));
|
|
24
|
+
const embedUrl = $derived(getDeezerEmbedUrl(url, theme));
|
|
25
|
+
const resolvedHeight = $derived(height ?? (parsed.type === 'track' ? '150' : '380'));
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<GeneralObserver {disable_observer}>
|
|
29
|
+
{#if embedUrl}
|
|
30
|
+
<iframe
|
|
31
|
+
src={embedUrl}
|
|
32
|
+
{width}
|
|
33
|
+
height={resolvedHeight}
|
|
34
|
+
title={`deezer-${parsed.type}-${parsed.id}`}
|
|
35
|
+
allow="encrypted-media; clipboard-write"
|
|
36
|
+
loading="lazy"
|
|
37
|
+
style={iframe_styles}
|
|
38
|
+
></iframe>
|
|
39
|
+
{/if}
|
|
40
|
+
</GeneralObserver>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
url: string;
|
|
3
|
+
theme?: 'light' | 'dark';
|
|
4
|
+
width?: string;
|
|
5
|
+
height?: string;
|
|
6
|
+
disable_observer?: boolean;
|
|
7
|
+
iframe_styles?: string;
|
|
8
|
+
}
|
|
9
|
+
declare const Deezer: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type Deezer = ReturnType<typeof Deezer>;
|
|
11
|
+
export default Deezer;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
threshold?: number;
|
|
6
|
+
disable_observer?: boolean;
|
|
7
|
+
children: Snippet;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let { threshold = 0.5, disable_observer = false, children }: Props = $props();
|
|
11
|
+
let loaded = $state(false);
|
|
12
|
+
let root = $state<HTMLElement | null>(null);
|
|
13
|
+
|
|
14
|
+
$effect(() => {
|
|
15
|
+
if (disable_observer || !root || loaded) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (typeof IntersectionObserver === 'undefined') {
|
|
20
|
+
loaded = true;
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const observer = new IntersectionObserver(
|
|
25
|
+
([entry]) => {
|
|
26
|
+
if (!entry?.isIntersecting) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
loaded = true;
|
|
31
|
+
observer.disconnect();
|
|
32
|
+
},
|
|
33
|
+
{ threshold }
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
observer.observe(root);
|
|
37
|
+
|
|
38
|
+
return () => observer.disconnect();
|
|
39
|
+
});
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<div bind:this={root} data-testid="general-observer">
|
|
43
|
+
{#if disable_observer || loaded}
|
|
44
|
+
{@render children()}
|
|
45
|
+
{/if}
|
|
46
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
threshold?: number;
|
|
4
|
+
disable_observer?: boolean;
|
|
5
|
+
children: Snippet;
|
|
6
|
+
}
|
|
7
|
+
declare const GeneralObserver: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type GeneralObserver = ReturnType<typeof GeneralObserver>;
|
|
9
|
+
export default GeneralObserver;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import GeneralObserver from './general-observer.svelte';
|
|
3
|
+
import { extractInstagramId, getInstagramEmbedUrl } from '../utils/instagram.js';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
url: string;
|
|
7
|
+
width?: string;
|
|
8
|
+
height?: string;
|
|
9
|
+
disable_observer?: boolean;
|
|
10
|
+
iframe_styles?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
url,
|
|
15
|
+
width = '100%',
|
|
16
|
+
height = '560',
|
|
17
|
+
disable_observer = false,
|
|
18
|
+
iframe_styles
|
|
19
|
+
}: Props = $props();
|
|
20
|
+
|
|
21
|
+
const postId = $derived(extractInstagramId(url));
|
|
22
|
+
const embedUrl = $derived(getInstagramEmbedUrl(url));
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<GeneralObserver {disable_observer}>
|
|
26
|
+
{#if embedUrl}
|
|
27
|
+
<iframe
|
|
28
|
+
src={embedUrl}
|
|
29
|
+
{width}
|
|
30
|
+
{height}
|
|
31
|
+
title={`instagram-${postId}`}
|
|
32
|
+
allow="autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share"
|
|
33
|
+
loading="lazy"
|
|
34
|
+
style={iframe_styles}
|
|
35
|
+
></iframe>
|
|
36
|
+
{/if}
|
|
37
|
+
</GeneralObserver>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
url: string;
|
|
3
|
+
width?: string;
|
|
4
|
+
height?: string;
|
|
5
|
+
disable_observer?: boolean;
|
|
6
|
+
iframe_styles?: string;
|
|
7
|
+
}
|
|
8
|
+
declare const Instagram: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type Instagram = ReturnType<typeof Instagram>;
|
|
10
|
+
export default Instagram;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import GeneralObserver from './general-observer.svelte';
|
|
3
|
+
import { extractLinkedInPostId, getLinkedInEmbedUrl } from '../utils/linkedin.js';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
url: string;
|
|
7
|
+
width?: string;
|
|
8
|
+
height?: string;
|
|
9
|
+
disable_observer?: boolean;
|
|
10
|
+
iframe_styles?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
url,
|
|
15
|
+
width = '100%',
|
|
16
|
+
height = '399',
|
|
17
|
+
disable_observer = false,
|
|
18
|
+
iframe_styles
|
|
19
|
+
}: Props = $props();
|
|
20
|
+
|
|
21
|
+
const postId = $derived(extractLinkedInPostId(url));
|
|
22
|
+
const embedUrl = $derived(getLinkedInEmbedUrl(url));
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<GeneralObserver {disable_observer}>
|
|
26
|
+
{#if embedUrl}
|
|
27
|
+
<iframe
|
|
28
|
+
src={embedUrl}
|
|
29
|
+
{width}
|
|
30
|
+
{height}
|
|
31
|
+
title={`linkedin-${postId}`}
|
|
32
|
+
allowfullscreen
|
|
33
|
+
loading="lazy"
|
|
34
|
+
style={iframe_styles}
|
|
35
|
+
></iframe>
|
|
36
|
+
{/if}
|
|
37
|
+
</GeneralObserver>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
url: string;
|
|
3
|
+
width?: string;
|
|
4
|
+
height?: string;
|
|
5
|
+
disable_observer?: boolean;
|
|
6
|
+
iframe_styles?: string;
|
|
7
|
+
}
|
|
8
|
+
declare const LinkedIn: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type LinkedIn = ReturnType<typeof LinkedIn>;
|
|
10
|
+
export default LinkedIn;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import GeneralObserver from './general-observer.svelte';
|
|
3
|
+
import { getMastodonEmbedInfo } from '../utils/mastodon.js';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
url: string;
|
|
7
|
+
width?: string;
|
|
8
|
+
height?: string;
|
|
9
|
+
disable_observer?: boolean;
|
|
10
|
+
iframe_styles?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
url,
|
|
15
|
+
width = '100%',
|
|
16
|
+
height = '400',
|
|
17
|
+
disable_observer = false,
|
|
18
|
+
iframe_styles
|
|
19
|
+
}: Props = $props();
|
|
20
|
+
|
|
21
|
+
const embedInfo = $derived(getMastodonEmbedInfo(url));
|
|
22
|
+
|
|
23
|
+
const ensureMastodonScript = async (scriptUrl: string) => {
|
|
24
|
+
if (typeof window === 'undefined') {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const existingScript = document.querySelector<HTMLScriptElement>(`script[src="${scriptUrl}"]`);
|
|
29
|
+
if (existingScript) {
|
|
30
|
+
if (existingScript.dataset.loaded === 'true') {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
await new Promise<void>((resolve, reject) => {
|
|
35
|
+
existingScript.addEventListener('load', () => resolve(), { once: true });
|
|
36
|
+
existingScript.addEventListener('error', () => reject(new Error('Failed to load Mastodon script')), {
|
|
37
|
+
once: true
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
await new Promise<void>((resolve, reject) => {
|
|
44
|
+
const script = document.createElement('script');
|
|
45
|
+
script.src = scriptUrl;
|
|
46
|
+
script.async = true;
|
|
47
|
+
script.dataset.sveltekitEmbedsScript = 'mastodon';
|
|
48
|
+
script.onload = () => {
|
|
49
|
+
script.dataset.loaded = 'true';
|
|
50
|
+
resolve();
|
|
51
|
+
};
|
|
52
|
+
script.onerror = () => reject(new Error('Failed to load Mastodon script'));
|
|
53
|
+
document.head.appendChild(script);
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
$effect(() => {
|
|
58
|
+
if (!embedInfo) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
void ensureMastodonScript(embedInfo.scriptUrl);
|
|
63
|
+
});
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
<GeneralObserver {disable_observer}>
|
|
67
|
+
{#if embedInfo}
|
|
68
|
+
<iframe
|
|
69
|
+
src={embedInfo.embedUrl}
|
|
70
|
+
{width}
|
|
71
|
+
{height}
|
|
72
|
+
title="mastodon-embed"
|
|
73
|
+
allow="autoplay; encrypted-media; picture-in-picture"
|
|
74
|
+
loading="lazy"
|
|
75
|
+
style={iframe_styles}
|
|
76
|
+
></iframe>
|
|
77
|
+
{/if}
|
|
78
|
+
</GeneralObserver>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
url: string;
|
|
3
|
+
width?: string;
|
|
4
|
+
height?: string;
|
|
5
|
+
disable_observer?: boolean;
|
|
6
|
+
iframe_styles?: string;
|
|
7
|
+
}
|
|
8
|
+
declare const Mastodon: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type Mastodon = ReturnType<typeof Mastodon>;
|
|
10
|
+
export default Mastodon;
|