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
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import GeneralObserver from './general-observer.svelte';
|
|
3
|
+
import { getPadding } from '../utils/index.js';
|
|
4
|
+
import { extractYouTubeVideoId, getYouTubeEmbedUrl } from '../utils/youtube.js';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
url: string;
|
|
8
|
+
aspectRatio?: string;
|
|
9
|
+
autoPlay?: boolean;
|
|
10
|
+
mute?: boolean;
|
|
11
|
+
controls?: boolean;
|
|
12
|
+
loop?: boolean;
|
|
13
|
+
start?: number;
|
|
14
|
+
disable_observer?: boolean;
|
|
15
|
+
iframe_styles?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let {
|
|
19
|
+
url,
|
|
20
|
+
aspectRatio = '16:9',
|
|
21
|
+
autoPlay = false,
|
|
22
|
+
mute = false,
|
|
23
|
+
controls = true,
|
|
24
|
+
loop = false,
|
|
25
|
+
start = 0,
|
|
26
|
+
disable_observer = false,
|
|
27
|
+
iframe_styles
|
|
28
|
+
}: Props = $props();
|
|
29
|
+
|
|
30
|
+
const videoId = $derived(extractYouTubeVideoId(url));
|
|
31
|
+
const baseEmbedUrl = $derived(getYouTubeEmbedUrl(url));
|
|
32
|
+
const paddingStyle = $derived(getPadding(aspectRatio));
|
|
33
|
+
const embedUrl = $derived.by(() => {
|
|
34
|
+
if (!baseEmbedUrl) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const params = new URLSearchParams({
|
|
39
|
+
autoplay: autoPlay ? '1' : '0',
|
|
40
|
+
mute: mute ? '1' : '0',
|
|
41
|
+
controls: controls ? '1' : '0'
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (loop && videoId) {
|
|
45
|
+
params.set('loop', '1');
|
|
46
|
+
params.set('playlist', videoId);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (start > 0) {
|
|
50
|
+
params.set('start', String(start));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return `${baseEmbedUrl}?${params.toString()}`;
|
|
54
|
+
});
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
<GeneralObserver {disable_observer}>
|
|
58
|
+
{#if embedUrl}
|
|
59
|
+
<div class="wrapper" style={paddingStyle}>
|
|
60
|
+
<iframe
|
|
61
|
+
src={embedUrl}
|
|
62
|
+
title={`youtube-${videoId}`}
|
|
63
|
+
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
|
64
|
+
allowfullscreen
|
|
65
|
+
loading="lazy"
|
|
66
|
+
style={iframe_styles}
|
|
67
|
+
></iframe>
|
|
68
|
+
</div>
|
|
69
|
+
{/if}
|
|
70
|
+
</GeneralObserver>
|
|
71
|
+
|
|
72
|
+
<style>
|
|
73
|
+
.wrapper {
|
|
74
|
+
position: relative;
|
|
75
|
+
width: 100%;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
iframe {
|
|
79
|
+
position: absolute;
|
|
80
|
+
inset: 0;
|
|
81
|
+
width: 100%;
|
|
82
|
+
height: 100%;
|
|
83
|
+
border: 0;
|
|
84
|
+
}
|
|
85
|
+
</style>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
url: string;
|
|
3
|
+
aspectRatio?: string;
|
|
4
|
+
autoPlay?: boolean;
|
|
5
|
+
mute?: boolean;
|
|
6
|
+
controls?: boolean;
|
|
7
|
+
loop?: boolean;
|
|
8
|
+
start?: number;
|
|
9
|
+
disable_observer?: boolean;
|
|
10
|
+
iframe_styles?: string;
|
|
11
|
+
}
|
|
12
|
+
declare const YouTube: import("svelte").Component<Props, {}, "">;
|
|
13
|
+
type YouTube = ReturnType<typeof YouTube>;
|
|
14
|
+
export default YouTube;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export { default as GeneralObserver } from './components/general-observer.svelte';
|
|
2
|
+
export { default as YouTube } from './components/you-tube.svelte';
|
|
3
|
+
export { default as TikTok } from './components/tik-tok.svelte';
|
|
4
|
+
export { default as Twitch } from './components/twitch.svelte';
|
|
5
|
+
export { default as Dailymotion } from './components/dailymotion.svelte';
|
|
6
|
+
export { default as Spotify } from './components/spotify.svelte';
|
|
7
|
+
export { default as AppleMusic } from './components/apple-music.svelte';
|
|
8
|
+
export { default as ApplePodcasts } from './components/apple-podcasts.svelte';
|
|
9
|
+
export { default as SoundCloud } from './components/sound-cloud.svelte';
|
|
10
|
+
export { default as Deezer } from './components/deezer.svelte';
|
|
11
|
+
export { default as Tidal } from './components/tidal.svelte';
|
|
12
|
+
export { default as XEmbed } from './components/x-embed.svelte';
|
|
13
|
+
export { default as Instagram } from './components/instagram.svelte';
|
|
14
|
+
export { default as Threads } from './components/threads.svelte';
|
|
15
|
+
export { default as Bluesky } from './components/bluesky.svelte';
|
|
16
|
+
export { default as Mastodon } from './components/mastodon.svelte';
|
|
17
|
+
export { default as LinkedIn } from './components/linked-in.svelte';
|
|
18
|
+
export { default as Reddit } from './components/reddit.svelte';
|
|
19
|
+
export { default as Pinterest } from './components/pinterest.svelte';
|
|
20
|
+
export * from './utils/index.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export { default as GeneralObserver } from './components/general-observer.svelte';
|
|
2
|
+
export { default as YouTube } from './components/you-tube.svelte';
|
|
3
|
+
export { default as TikTok } from './components/tik-tok.svelte';
|
|
4
|
+
export { default as Twitch } from './components/twitch.svelte';
|
|
5
|
+
export { default as Dailymotion } from './components/dailymotion.svelte';
|
|
6
|
+
export { default as Spotify } from './components/spotify.svelte';
|
|
7
|
+
export { default as AppleMusic } from './components/apple-music.svelte';
|
|
8
|
+
export { default as ApplePodcasts } from './components/apple-podcasts.svelte';
|
|
9
|
+
export { default as SoundCloud } from './components/sound-cloud.svelte';
|
|
10
|
+
export { default as Deezer } from './components/deezer.svelte';
|
|
11
|
+
export { default as Tidal } from './components/tidal.svelte';
|
|
12
|
+
export { default as XEmbed } from './components/x-embed.svelte';
|
|
13
|
+
export { default as Instagram } from './components/instagram.svelte';
|
|
14
|
+
export { default as Threads } from './components/threads.svelte';
|
|
15
|
+
export { default as Bluesky } from './components/bluesky.svelte';
|
|
16
|
+
export { default as Mastodon } from './components/mastodon.svelte';
|
|
17
|
+
export { default as LinkedIn } from './components/linked-in.svelte';
|
|
18
|
+
export { default as Reddit } from './components/reddit.svelte';
|
|
19
|
+
export { default as Pinterest } from './components/pinterest.svelte';
|
|
20
|
+
export * from './utils/index.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type AppleMusicItemType = 'song' | 'album' | 'playlist' | 'artist' | 'station' | 'music-video';
|
|
2
|
+
export interface AppleMusicUrlInfo {
|
|
3
|
+
id: string;
|
|
4
|
+
type: AppleMusicItemType;
|
|
5
|
+
country: string;
|
|
6
|
+
isValid: boolean;
|
|
7
|
+
originalUrl: string;
|
|
8
|
+
}
|
|
9
|
+
export declare const isValidAppleMusicUrl: (url: string) => boolean;
|
|
10
|
+
export declare const parseAppleMusicUrl: (url: string) => AppleMusicUrlInfo;
|
|
11
|
+
export declare const getAppleMusicEmbedUrl: (url: string, theme?: "light" | "dark") => string | null;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export const isValidAppleMusicUrl = (url) => {
|
|
2
|
+
if (!url) {
|
|
3
|
+
return false;
|
|
4
|
+
}
|
|
5
|
+
try {
|
|
6
|
+
const urlObj = new URL(url);
|
|
7
|
+
return urlObj.hostname.includes('music.apple.com');
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
export const parseAppleMusicUrl = (url) => {
|
|
14
|
+
const fallback = {
|
|
15
|
+
id: '',
|
|
16
|
+
type: 'song',
|
|
17
|
+
country: '',
|
|
18
|
+
isValid: false,
|
|
19
|
+
originalUrl: url
|
|
20
|
+
};
|
|
21
|
+
if (!isValidAppleMusicUrl(url)) {
|
|
22
|
+
return fallback;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const urlObj = new URL(url);
|
|
26
|
+
const pathParts = urlObj.pathname.split('/').filter(Boolean);
|
|
27
|
+
if (pathParts.length < 3) {
|
|
28
|
+
return fallback;
|
|
29
|
+
}
|
|
30
|
+
const country = pathParts[0] ?? '';
|
|
31
|
+
const type = (pathParts[1] ?? 'song');
|
|
32
|
+
const id = pathParts[pathParts.length - 1] ?? '';
|
|
33
|
+
const songId = urlObj.searchParams.get('i');
|
|
34
|
+
return {
|
|
35
|
+
id,
|
|
36
|
+
type: songId ? 'song' : type,
|
|
37
|
+
country,
|
|
38
|
+
isValid: Boolean(id),
|
|
39
|
+
originalUrl: url
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return fallback;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
export const getAppleMusicEmbedUrl = (url, theme = 'light') => {
|
|
47
|
+
if (!isValidAppleMusicUrl(url)) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
const embedUrl = new URL(url);
|
|
52
|
+
embedUrl.hostname = 'embed.music.apple.com';
|
|
53
|
+
embedUrl.searchParams.set('theme', theme);
|
|
54
|
+
return embedUrl.toString();
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type ApplePodcastsItemType = 'show' | 'episode';
|
|
2
|
+
export interface ApplePodcastsUrlInfo {
|
|
3
|
+
id: string;
|
|
4
|
+
type: ApplePodcastsItemType;
|
|
5
|
+
country: string;
|
|
6
|
+
isValid: boolean;
|
|
7
|
+
originalUrl: string;
|
|
8
|
+
}
|
|
9
|
+
export declare const isValidApplePodcastsUrl: (url: string) => boolean;
|
|
10
|
+
export declare const parseApplePodcastsUrl: (url: string) => ApplePodcastsUrlInfo;
|
|
11
|
+
export declare const getApplePodcastsEmbedUrl: (url: string, theme?: "light" | "dark") => string | null;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export const isValidApplePodcastsUrl = (url) => {
|
|
2
|
+
if (!url) {
|
|
3
|
+
return false;
|
|
4
|
+
}
|
|
5
|
+
try {
|
|
6
|
+
const urlObj = new URL(url);
|
|
7
|
+
return urlObj.hostname.includes('podcasts.apple.com');
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
export const parseApplePodcastsUrl = (url) => {
|
|
14
|
+
const fallback = {
|
|
15
|
+
id: '',
|
|
16
|
+
type: 'show',
|
|
17
|
+
country: '',
|
|
18
|
+
isValid: false,
|
|
19
|
+
originalUrl: url
|
|
20
|
+
};
|
|
21
|
+
if (!isValidApplePodcastsUrl(url)) {
|
|
22
|
+
return fallback;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const urlObj = new URL(url);
|
|
26
|
+
const pathParts = urlObj.pathname.split('/').filter(Boolean);
|
|
27
|
+
const country = pathParts[0] ?? '';
|
|
28
|
+
const episodeId = urlObj.searchParams.get('i');
|
|
29
|
+
const showId = urlObj.pathname.match(/\/id(\d+)/)?.[1] ?? '';
|
|
30
|
+
const id = episodeId || showId;
|
|
31
|
+
return {
|
|
32
|
+
id,
|
|
33
|
+
type: episodeId ? 'episode' : 'show',
|
|
34
|
+
country,
|
|
35
|
+
isValid: Boolean(id),
|
|
36
|
+
originalUrl: url
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return fallback;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
export const getApplePodcastsEmbedUrl = (url, theme = 'light') => {
|
|
44
|
+
if (!isValidApplePodcastsUrl(url)) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const embedUrl = new URL(url);
|
|
49
|
+
embedUrl.hostname = 'embed.podcasts.apple.com';
|
|
50
|
+
embedUrl.searchParams.set('theme', theme);
|
|
51
|
+
return embedUrl.toString();
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export const parseBlueskyUrl = (url) => {
|
|
2
|
+
const fallback = {
|
|
3
|
+
handle: '',
|
|
4
|
+
postId: '',
|
|
5
|
+
isValid: false,
|
|
6
|
+
originalUrl: url
|
|
7
|
+
};
|
|
8
|
+
try {
|
|
9
|
+
const urlObj = new URL(url);
|
|
10
|
+
const hostname = urlObj.hostname.toLowerCase();
|
|
11
|
+
if (!hostname.includes('bsky.app')) {
|
|
12
|
+
return fallback;
|
|
13
|
+
}
|
|
14
|
+
const match = urlObj.pathname.match(/\/profile\/([^/]+)\/post\/([^/?#]+)/);
|
|
15
|
+
if (!match?.[1] || !match?.[2]) {
|
|
16
|
+
return fallback;
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
handle: match[1],
|
|
20
|
+
postId: match[2],
|
|
21
|
+
isValid: true,
|
|
22
|
+
originalUrl: url
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return fallback;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
export const getBlueskyEmbedUrl = (url) => {
|
|
30
|
+
const info = parseBlueskyUrl(url);
|
|
31
|
+
if (!info.isValid) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
return `https://embed.bsky.app/embed/${info.handle}/post/${info.postId}`;
|
|
35
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const extractDailymotionId = (url) => {
|
|
2
|
+
try {
|
|
3
|
+
const urlObj = new URL(url);
|
|
4
|
+
const hostname = urlObj.hostname.toLowerCase();
|
|
5
|
+
if (hostname.includes('dai.ly')) {
|
|
6
|
+
return urlObj.pathname.split('/').filter(Boolean)[0] ?? null;
|
|
7
|
+
}
|
|
8
|
+
const match = urlObj.pathname.match(/\/video\/([a-zA-Z0-9]+)/);
|
|
9
|
+
return match?.[1] ?? null;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
export const getDailymotionEmbedUrl = (url) => {
|
|
16
|
+
const id = extractDailymotionId(url);
|
|
17
|
+
if (!id) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return `https://www.dailymotion.com/embed/video/${id}`;
|
|
21
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type DeezerItemType = 'track' | 'album' | 'playlist' | 'artist' | 'podcast' | 'episode';
|
|
2
|
+
export interface DeezerUrlInfo {
|
|
3
|
+
id: string;
|
|
4
|
+
type: DeezerItemType;
|
|
5
|
+
isValid: boolean;
|
|
6
|
+
originalUrl: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const isValidDeezerUrl: (url: string) => boolean;
|
|
9
|
+
export declare const parseDeezerUrl: (url: string) => DeezerUrlInfo;
|
|
10
|
+
export declare const getDeezerEmbedUrl: (url: string, theme?: "light" | "dark") => string | null;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const DEEZER_TYPES = ['track', 'album', 'playlist', 'artist', 'podcast', 'episode'];
|
|
2
|
+
export const isValidDeezerUrl = (url) => {
|
|
3
|
+
if (!url) {
|
|
4
|
+
return false;
|
|
5
|
+
}
|
|
6
|
+
try {
|
|
7
|
+
const urlObj = new URL(url);
|
|
8
|
+
return urlObj.hostname.includes('deezer.com');
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
export const parseDeezerUrl = (url) => {
|
|
15
|
+
const fallback = {
|
|
16
|
+
id: '',
|
|
17
|
+
type: 'track',
|
|
18
|
+
isValid: false,
|
|
19
|
+
originalUrl: url
|
|
20
|
+
};
|
|
21
|
+
if (!isValidDeezerUrl(url)) {
|
|
22
|
+
return fallback;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const urlObj = new URL(url);
|
|
26
|
+
const pathParts = urlObj.pathname.split('/').filter(Boolean);
|
|
27
|
+
const firstPart = pathParts[0] ?? '';
|
|
28
|
+
const hasCountryPrefix = firstPart.length === 2 && !DEEZER_TYPES.includes(firstPart);
|
|
29
|
+
const normalizedParts = hasCountryPrefix ? pathParts.slice(1) : pathParts;
|
|
30
|
+
const type = (normalizedParts[0] ?? '');
|
|
31
|
+
const id = normalizedParts[1] ?? '';
|
|
32
|
+
if (!DEEZER_TYPES.includes(type) || !id) {
|
|
33
|
+
return fallback;
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
id,
|
|
37
|
+
type,
|
|
38
|
+
isValid: true,
|
|
39
|
+
originalUrl: url
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return fallback;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
export const getDeezerEmbedUrl = (url, theme = 'light') => {
|
|
47
|
+
const info = parseDeezerUrl(url);
|
|
48
|
+
if (!info.isValid) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const themeSegment = theme === 'dark' ? 'dark' : 'light';
|
|
52
|
+
if (info.type === 'artist') {
|
|
53
|
+
return `https://widget.deezer.com/widget/${themeSegment}/artist/${info.id}/top_tracks`;
|
|
54
|
+
}
|
|
55
|
+
return `https://widget.deezer.com/widget/${themeSegment}/${info.type}/${info.id}`;
|
|
56
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare const getPadding: (aspectRatio?: string) => string;
|
|
2
|
+
export * from './youtube.js';
|
|
3
|
+
export * from './spotify.js';
|
|
4
|
+
export * from './tiktok.js';
|
|
5
|
+
export * from './twitch.js';
|
|
6
|
+
export * from './dailymotion.js';
|
|
7
|
+
export * from './apple-music.js';
|
|
8
|
+
export * from './apple-podcasts.js';
|
|
9
|
+
export * from './soundcloud.js';
|
|
10
|
+
export * from './deezer.js';
|
|
11
|
+
export * from './tidal.js';
|
|
12
|
+
export * from './twitter.js';
|
|
13
|
+
export * from './instagram.js';
|
|
14
|
+
export * from './threads.js';
|
|
15
|
+
export * from './bluesky.js';
|
|
16
|
+
export * from './mastodon.js';
|
|
17
|
+
export * from './linkedin.js';
|
|
18
|
+
export * from './reddit.js';
|
|
19
|
+
export * from './pinterest.js';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const PADDING_BY_ASPECT_RATIO = {
|
|
2
|
+
'1:1': 'padding-top: 100%;',
|
|
3
|
+
'16:9': 'padding-top: 56.25%;',
|
|
4
|
+
'4:3': 'padding-top: 75%;',
|
|
5
|
+
'3:2': 'padding-top: 66.66%;'
|
|
6
|
+
};
|
|
7
|
+
export const getPadding = (aspectRatio = '16:9') => PADDING_BY_ASPECT_RATIO[aspectRatio] ?? PADDING_BY_ASPECT_RATIO['16:9'];
|
|
8
|
+
export * from './youtube.js';
|
|
9
|
+
export * from './spotify.js';
|
|
10
|
+
export * from './tiktok.js';
|
|
11
|
+
export * from './twitch.js';
|
|
12
|
+
export * from './dailymotion.js';
|
|
13
|
+
export * from './apple-music.js';
|
|
14
|
+
export * from './apple-podcasts.js';
|
|
15
|
+
export * from './soundcloud.js';
|
|
16
|
+
export * from './deezer.js';
|
|
17
|
+
export * from './tidal.js';
|
|
18
|
+
export * from './twitter.js';
|
|
19
|
+
export * from './instagram.js';
|
|
20
|
+
export * from './threads.js';
|
|
21
|
+
export * from './bluesky.js';
|
|
22
|
+
export * from './mastodon.js';
|
|
23
|
+
export * from './linkedin.js';
|
|
24
|
+
export * from './reddit.js';
|
|
25
|
+
export * from './pinterest.js';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const extractInstagramId = (url) => {
|
|
2
|
+
try {
|
|
3
|
+
const urlObj = new URL(url);
|
|
4
|
+
const hostname = urlObj.hostname.toLowerCase();
|
|
5
|
+
if (!hostname.includes('instagram.com')) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
const match = urlObj.pathname.match(/\/(?:p|reel|tv)\/([^/?#]+)/);
|
|
9
|
+
return match?.[1] ?? null;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
export const getInstagramEmbedUrl = (url) => {
|
|
16
|
+
const postId = extractInstagramId(url);
|
|
17
|
+
if (!postId) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return `https://www.instagram.com/p/${postId}/embed`;
|
|
21
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export const extractLinkedInPostId = (url) => {
|
|
2
|
+
try {
|
|
3
|
+
const urlObj = new URL(url);
|
|
4
|
+
const hostname = urlObj.hostname.toLowerCase();
|
|
5
|
+
if (!hostname.includes('linkedin.com')) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
const activityId = urlObj.pathname.match(/urn:li:activity:(\d+)/)?.[1];
|
|
9
|
+
if (activityId) {
|
|
10
|
+
return activityId;
|
|
11
|
+
}
|
|
12
|
+
const ugcPostId = urlObj.pathname.match(/urn:li:ugcPost:(\d+)/)?.[1];
|
|
13
|
+
if (ugcPostId) {
|
|
14
|
+
return ugcPostId;
|
|
15
|
+
}
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
export const getLinkedInEmbedUrl = (url) => {
|
|
23
|
+
try {
|
|
24
|
+
const urlObj = new URL(url);
|
|
25
|
+
const hostname = urlObj.hostname.toLowerCase();
|
|
26
|
+
if (!hostname.includes('linkedin.com')) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const path = urlObj.pathname.replace(/\/$/, '');
|
|
30
|
+
if (!path.includes('/feed/update/')) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
return `https://www.linkedin.com/embed${path}`;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface MastodonUrlInfo {
|
|
2
|
+
instance: string;
|
|
3
|
+
path: string;
|
|
4
|
+
isValid: boolean;
|
|
5
|
+
originalUrl: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const parseMastodonUrl: (url: string) => MastodonUrlInfo;
|
|
8
|
+
export declare const getMastodonEmbedInfo: (url: string) => {
|
|
9
|
+
scriptUrl: string;
|
|
10
|
+
embedUrl: string;
|
|
11
|
+
instance: string;
|
|
12
|
+
} | null;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export const parseMastodonUrl = (url) => {
|
|
2
|
+
const fallback = {
|
|
3
|
+
instance: '',
|
|
4
|
+
path: '',
|
|
5
|
+
isValid: false,
|
|
6
|
+
originalUrl: url
|
|
7
|
+
};
|
|
8
|
+
try {
|
|
9
|
+
const urlObj = new URL(url);
|
|
10
|
+
const path = urlObj.pathname.replace(/\/$/, '');
|
|
11
|
+
if (!path.includes('/@')) {
|
|
12
|
+
return fallback;
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
instance: urlObj.origin,
|
|
16
|
+
path,
|
|
17
|
+
isValid: true,
|
|
18
|
+
originalUrl: url
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return fallback;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
export const getMastodonEmbedInfo = (url) => {
|
|
26
|
+
const info = parseMastodonUrl(url);
|
|
27
|
+
if (!info.isValid) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const embedPath = info.path.endsWith('/embed') ? info.path : `${info.path}/embed`;
|
|
31
|
+
return {
|
|
32
|
+
scriptUrl: `${info.instance}/embed.js`,
|
|
33
|
+
embedUrl: `${info.instance}${embedPath}`,
|
|
34
|
+
instance: info.instance
|
|
35
|
+
};
|
|
36
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const extractPinterestId = (url) => {
|
|
2
|
+
try {
|
|
3
|
+
const urlObj = new URL(url);
|
|
4
|
+
const hostname = urlObj.hostname.toLowerCase();
|
|
5
|
+
if (!hostname.includes('pinterest.') && hostname !== 'pin.it') {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
const match = urlObj.pathname.match(/\/pin\/(\d+)/);
|
|
9
|
+
return match?.[1] ?? null;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
export const getPinterestPostUrl = (url) => {
|
|
16
|
+
const pinId = extractPinterestId(url);
|
|
17
|
+
if (!pinId) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return `https://www.pinterest.com/pin/${pinId}/`;
|
|
21
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export const extractRedditPostId = (rawUrl) => {
|
|
2
|
+
const trimmed = rawUrl.trim();
|
|
3
|
+
if (!trimmed) {
|
|
4
|
+
return null;
|
|
5
|
+
}
|
|
6
|
+
try {
|
|
7
|
+
const url = new URL(trimmed);
|
|
8
|
+
const hostname = url.hostname.replace(/^www\./, '');
|
|
9
|
+
const pathParts = url.pathname.split('/').filter(Boolean);
|
|
10
|
+
if (hostname === 'redd.it') {
|
|
11
|
+
return pathParts[0] ?? null;
|
|
12
|
+
}
|
|
13
|
+
const commentsIndex = pathParts.indexOf('comments');
|
|
14
|
+
if (commentsIndex !== -1) {
|
|
15
|
+
return pathParts[commentsIndex + 1] ?? null;
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
export const getRedditEmbedUrl = (rawUrl, theme = 'light') => {
|
|
24
|
+
try {
|
|
25
|
+
const url = new URL(rawUrl);
|
|
26
|
+
const hostname = url.hostname.replace(/^www\./, '');
|
|
27
|
+
if (hostname !== 'reddit.com' && hostname !== 'redd.it') {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const id = extractRedditPostId(rawUrl);
|
|
31
|
+
if (!id) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const pathParts = url.pathname.split('/').filter(Boolean);
|
|
35
|
+
const commentsIndex = pathParts.indexOf('comments');
|
|
36
|
+
if (commentsIndex !== -1 && pathParts.length >= commentsIndex + 2) {
|
|
37
|
+
const embedPath = `/${pathParts.slice(0, commentsIndex + 2).join('/')}/embed`;
|
|
38
|
+
return `https://www.reddit.com${embedPath}?theme=${theme}`;
|
|
39
|
+
}
|
|
40
|
+
return `https://www.reddit.com/comments/${id}/embed?theme=${theme}`;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
};
|