Skip to content

Commit 3ec081d

Browse files
authored
Merge pull request #760 from spences10/feat/add-bsky-component
feat: ✨ add bsky component
2 parents 92693eb + 9826a10 commit 3ec081d

File tree

4 files changed

+211
-0
lines changed

4 files changed

+211
-0
lines changed

apps/web/src/routes/+page.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { page } from '$app/stores'
44
import {
55
AnchorFm,
6+
Bluesky,
67
Buzzsprout,
78
CodePen,
89
Deezer,
@@ -131,6 +132,7 @@ Your embed not here? Start a
131132
or open a [PR](https://github.com/spences10/sveltekit-embed/pulls).
132133

133134
- [AnchorFm](#anchorfm)
135+
- [Bluesky](#bluesky)
134136
- [Buzzsprout](#buzzsprout)
135137
- [CodePen](#codepen)
136138
- [Deezer](#deezer)
@@ -176,6 +178,38 @@ Output:
176178
episodeUrl="purrfect-dev/embed/episodes/3-6---Effective-Testing-using-Cypress-io-e1vbg9m"
177179
/>
178180

181+
## Bluesky
182+
183+
Get the `did:plc` identifier from the embed post option on Bluesky.
184+
185+
Props:
186+
187+
```ts
188+
post_id = '',
189+
width = '100%',
190+
iframe_styles = '',
191+
```
192+
193+
Usage:
194+
195+
<!-- cSpell:ignore nlvjelw,pddq,qoglleko,bsky,tnwn2k,bluesky,rgba,tiktok -->
196+
197+
```html
198+
<Bluesky
199+
disable_observer="{$disable_observer_store}"
200+
post_id="did:plc:nlvjelw3dy3pddq7qoglleko/app.bsky.feed.post/3l6ud34tnwn2k"
201+
iframe_styles="border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);"
202+
/>
203+
```
204+
205+
Output:
206+
207+
<Bluesky
208+
disable_observer={$disable_observer_store}
209+
post_id="did:plc:nlvjelw3dy3pddq7qoglleko/app.bsky.feed.post/3l6ud34tnwn2k"
210+
iframe_styles="border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);"
211+
/>
212+
179213
## Buzzsprout
180214

181215
Props:
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<script lang="ts">
2+
import { onMount } from 'svelte';
3+
4+
interface Props {
5+
post_id?: string;
6+
width?: string;
7+
iframe_styles?: string;
8+
}
9+
10+
let {
11+
post_id = '',
12+
width = '100%',
13+
iframe_styles = '',
14+
}: Props = $props();
15+
16+
let wrapper_height = $state('174.5px');
17+
18+
const get_embed_url = (post_id: string) => {
19+
return `https://embed.bsky.app/embed/${post_id}`;
20+
};
21+
22+
onMount(() => {
23+
const handle_message = (event: MessageEvent) => {
24+
if (event.origin !== 'https://embed.bsky.app') return;
25+
26+
if (typeof event.data === 'object') {
27+
wrapper_height = `${event.data.height || event.data.h || 500}px`;
28+
}
29+
};
30+
31+
window.addEventListener('message', handle_message);
32+
return () => {
33+
window.removeEventListener('message', handle_message);
34+
};
35+
});
36+
</script>
37+
38+
<div class="bluesky-wrapper-container">
39+
<div class="bluesky-wrapper" style={`height: ${wrapper_height}`}>
40+
<iframe
41+
data-testid="bluesky-embed"
42+
title="Bluesky Post Embed"
43+
src={get_embed_url(post_id)}
44+
{width}
45+
frameborder="0"
46+
scrolling="no"
47+
style={`
48+
position: absolute;
49+
top: 0;
50+
left: 0;
51+
width: 100%;
52+
height: 100%;
53+
border: 0px;
54+
${iframe_styles}
55+
`}
56+
></iframe>
57+
</div>
58+
</div>
59+
60+
<style>
61+
.bluesky-wrapper-container {
62+
display: flex;
63+
justify-content: center;
64+
width: 100%;
65+
}
66+
67+
.bluesky-wrapper {
68+
position: relative;
69+
margin-bottom: 12px;
70+
max-width: 600px;
71+
min-width: 300px;
72+
width: 100%;
73+
}
74+
</style>
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { cleanup, render } from '@testing-library/svelte/svelte5';
2+
import { afterEach, describe, expect, it } from 'vitest';
3+
import Bluesky from './bluesky.svelte';
4+
5+
describe('Bluesky', () => {
6+
afterEach(() => cleanup());
7+
8+
const test_post_id =
9+
'did:plc:nlvjelw3dy3pddq7qoglleko/app.bsky.feed.post/3l6ud34tnwn2k';
10+
11+
it('mounts with default props', async () => {
12+
const { container } = render(Bluesky);
13+
expect(container).toBeTruthy();
14+
});
15+
16+
it('renders iframe with correct embed url', async () => {
17+
const { getByTestId } = render(Bluesky, {
18+
post_id: test_post_id,
19+
});
20+
21+
const iframe = getByTestId('bluesky-embed');
22+
const expected_src = `https://embed.bsky.app/embed/${test_post_id}`;
23+
expect(iframe.getAttribute('src')).toBe(expected_src);
24+
});
25+
26+
it('renders with custom width', async () => {
27+
const { getByTestId } = render(Bluesky, {
28+
post_id: test_post_id,
29+
width: '50%',
30+
});
31+
32+
const iframe = getByTestId('bluesky-embed');
33+
expect(iframe.getAttribute('width')).toBe('50%');
34+
});
35+
36+
it('applies custom iframe styles', async () => {
37+
const custom_styles = 'border-radius: 8px; background: #f0f0f0;';
38+
const { getByTestId } = render(Bluesky, {
39+
post_id: test_post_id,
40+
iframe_styles: custom_styles,
41+
});
42+
43+
const iframe = getByTestId('bluesky-embed');
44+
const style_text = iframe.style.cssText.toLowerCase();
45+
expect(style_text).toContain('border-radius: 8px');
46+
expect(style_text).toContain('background: rgb(240, 240, 240)');
47+
});
48+
49+
it('has correct default styles', async () => {
50+
const { getByTestId } = render(Bluesky, {
51+
post_id: test_post_id,
52+
});
53+
54+
const iframe = getByTestId('bluesky-embed');
55+
const style_text = iframe.style.cssText.toLowerCase();
56+
expect(style_text).toContain('position: absolute');
57+
expect(style_text).toContain('top: 0px');
58+
expect(style_text).toContain('left: 0px');
59+
expect(style_text).toContain('width: 100%');
60+
expect(style_text).toContain('height: 100%');
61+
expect(style_text).toContain('border: 0px');
62+
expect(iframe.getAttribute('frameborder')).toBe('0');
63+
expect(iframe.getAttribute('scrolling')).toBe('no');
64+
});
65+
66+
it('combines default and custom iframe styles correctly', async () => {
67+
const custom_styles = 'border-radius: 8px; margin: 10px;';
68+
const { getByTestId } = render(Bluesky, {
69+
post_id: test_post_id,
70+
iframe_styles: custom_styles,
71+
});
72+
73+
const iframe = getByTestId('bluesky-embed');
74+
const style_text = iframe.style.cssText.toLowerCase();
75+
76+
// Check default styles are preserved
77+
expect(style_text).toContain('position: absolute');
78+
expect(style_text).toContain('width: 100%');
79+
expect(style_text).toContain('height: 100%');
80+
expect(style_text).toContain('border: 0px');
81+
82+
// Check custom styles are applied
83+
expect(style_text).toContain('border-radius: 8px');
84+
expect(style_text).toContain('margin: 10px');
85+
});
86+
87+
it('updates height when receiving message from iframe', () => {
88+
const { getByTestId } = render(Bluesky, {
89+
post_id: test_post_id,
90+
});
91+
92+
const message_event = new MessageEvent('message', {
93+
data: { type: 'height', height: 500 },
94+
origin: 'https://embed.bsky.app',
95+
});
96+
97+
window.dispatchEvent(message_event);
98+
99+
const iframe = getByTestId('bluesky-embed');
100+
expect(iframe.style.height).toBe('100%');
101+
});
102+
});

packages/sveltekit-embed/src/lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export { default as AnchorFm } from './components/anchor-fm.svelte';
2+
export { default as Bluesky } from './components/bluesky.svelte';
23
export { default as Buzzsprout } from './components/buzzsprout.svelte';
34
export { default as CodePen } from './components/code-pen.svelte';
45
export { default as Deezer } from './components/deezer.svelte';

0 commit comments

Comments
 (0)