Skip to content

Instantly share code, notes, and snippets.

@GreenFootballs
Last active January 3, 2026 02:17
Show Gist options
  • Select an option

  • Save GreenFootballs/d61d33b411fb0f2c519c79a1783ab297 to your computer and use it in GitHub Desktop.

Select an option

Save GreenFootballs/d61d33b411fb0f2c519c79a1783ab297 to your computer and use it in GitHub Desktop.
Render Bluesky embedded posts dynamically, with callback when complete
"use strict"
/**
* bluesky-embed.js
* @author Charles Johnson
* @url https://github.com/GreenFootballs
*
* Adds an object named "_bluesky" to the window element, with an "embed" method.
*
* Runs automatically at window.onload, searches the page for blockquotes with a
* "data-bluesky-uri" attribute, converts them to iframes, and resizes them to fit the content.
*
* To resize dynamically added Bluesky posts, call "_bluesky.embed(div, callback)"
* where "container" is the enclosing element for the dynamic content.
*
* The "container" parameter can be a string with a CSS selector, a reference to a DOM element,
* or left undefined to search the entire document.
*
* The "callback" parameter is an optional function called after all iframes
* in the container are resized.
*/
window.addEventListener('load', () => {
const EMBED_URL = 'https://embed.bsky.app'
window._bluesky = window._bluesky || {
onRender: null,
embed: (container = document, callback = null) => {
window._bluesky.onRender = null
if (typeof container === 'string') {
container = document.querySelector(container)
if (!container) return
}
const quotes = container.querySelectorAll('blockquote[data-bluesky-uri]')
for (const [ index, quote ] of quotes.entries()) {
if (index === quotes.length - 1) {
if (callback !== null) {
window._bluesky.onRender = callback
}
}
const id = 'b' + crypto.randomUUID()
const atUri = quote.getAttribute('data-bluesky-uri').substring('at://'.length)
const searchParams = new URLSearchParams()
searchParams.set('id', id)
const ref_url = location.origin + location.pathname
if (ref_url.startsWith('http')) {
searchParams.set('ref_url', encodeURIComponent(ref_url))
}
if (quote.dataset.blueskyEmbedColorMode) {
searchParams.set('colorMode', quote.dataset.blueskyEmbedColorMode);
}
const iframe = document.createElement('iframe')
iframe.setAttribute('id', id)
iframe.style.cssText = 'border: none;width: 100%;margin: 0 auto;display: block;flex-grow: 1;'
iframe.setAttribute('frameBorder', '0')
iframe.setAttribute('scrolling', 'no')
iframe.src = `${ EMBED_URL }/embed/${ atUri }?${ searchParams.toString() }`
const div = document.createElement('div')
div.style.cssText = 'max-width: 600px; width: 100%; margin: 10px auto; display: flex;'
div.className = 'bluesky-embed'
div.appendChild(iframe)
quote.replaceWith(div)
}
}
}
// When the iframe loads, it sends a message with its ID and content height
window.addEventListener('message', e => {
if (e.origin !== EMBED_URL || !e.data.id || !e.data.height) return
const embed = document.querySelector('#' + e.data.id)
if (!embed) return
embed.style.height = '' + e.data.height + 'px'
embed.parentElement.style.height = '' + e.data.height + 'px'
if (window._bluesky.onRender !== null) {
window._bluesky.onRender()
window._bluesky.onRender = null
}
})
window._bluesky.embed()
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment