-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
v1 of Bluesky embed support #304
Open
shellen
wants to merge
3
commits into
gfscott:main
Choose a base branch
from
shellen:bluesky-embeds
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
title: Bluesky | ||
--- | ||
|
||
https://bsky.app/profile/bsky.app/post/3lgu4lg6j2k2v |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
## 1.0.0 | ||
|
||
### Major Changes | ||
|
||
- Adds support for embedding Bluesky posts using just their URLs. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
# eleventy-plugin-embed-bluesky | ||
|
||
This [Eleventy](https://www.11ty.dev/) plugin automatically embeds Bluesky posts from URLs in markdown files. It's part of the [`eleventy-plugin-embed-everything`](https://gfscott.com/embed-everything/) project. | ||
|
||
## Install in Eleventy | ||
|
||
In your Eleventy project, [install the plugin](https://www.11ty.dev/docs/plugins/#adding-a-plugin) through npm: | ||
|
||
```sh | ||
$ npm i eleventy-plugin-embed-bluesky | ||
``` | ||
|
||
Then add it to your [Eleventy config](https://www.11ty.dev/docs/config/) file (usually `.eleventy.js`): | ||
|
||
```javascript | ||
const embedBluesky = require("eleventy-plugin-embed-bluesky"); | ||
|
||
module.exports = function (eleventyConfig) { | ||
eleventyConfig.addPlugin(embedBluesky); | ||
}; | ||
``` | ||
|
||
## Usage | ||
|
||
To embed a Bluesky post into any markdown page, paste its URL into a new line. The URL should be the only thing on that line. | ||
|
||
### Markdown file example: | ||
|
||
```markdown | ||
... | ||
|
||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam vehicula, elit vel condimentum porta, purus. | ||
|
||
https://bsky.app/profile/shellen.com/post/3ldmp5qd6es2p | ||
|
||
Maecenas non velit nibh. Aenean eu justo et odio commodo ornare. In scelerisque sapien at. | ||
|
||
... | ||
``` | ||
|
||
## Settings | ||
|
||
You can configure the plugin to change its behavior by passing an options object to the `addPlugin` function: | ||
|
||
```javascript | ||
eleventyConfig.addPlugin(embedBluesky, { | ||
// just an example, see default values below: | ||
embedClass: "custom-classname", | ||
embedDomain: "staging.bsky.app", | ||
}); | ||
``` | ||
|
||
### Plugin default options | ||
|
||
The plugin's default settings reside in [lib/defaults.js](lib/defaults.js). All of these values can be customized with an options object passed to the plugin. | ||
|
||
| Option | Type | Default | Notes | | ||
| ------------------- | ------- | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | ||
| `allowFullscreen` | Boolean | `true` | Set to false to prevent the embedded post from being viewed in fullscreen mode. | | ||
| `containerCss` | String | `"position: relative; width: 100%; padding-bottom: 0;"` | CSS applied to the container `<div>` that wraps the embedded post. | | ||
| `embedClass` | String | `"eleventy-plugin-embed-bluesky"` | CSS class applied to the container `<div>` that wraps the embedded post. | | ||
| `iframeCss` | String | `"border: 0; position: relative; width: 100%;"` | CSS applied to the `<iframe>` that contains the embedded post. | | ||
| `iframeFrameborder` | String | `"0"` | Width of the `iframe`'s border in pixels. | | ||
| `iframeHeight` | String | `"300"` | Height of the `iframe`. | | ||
| `iframeScrolling` | String | `"no"` | Whether the `iframe` should have scrollbars. | | ||
| `iframeWidth` | String | `"550"` | Width of the `iframe`. | | ||
| `embedDomain` | String | `"bsky.app"` | Domain to use for embeds. Can be set to `staging.bsky.app` for staging or a custom domain for self-hosted instances. | | ||
|
||
### Supported URL patterns | ||
|
||
The plugin supports common URL variants as well. These will all work: | ||
|
||
```markdown | ||
<!-- No protocol: --> | ||
|
||
bsky.app/profile/bsky.app/post/3lgu4lg6j2k2v | ||
|
||
<!-- With custom domains: --> | ||
|
||
https://bsky.app/profile/shellen.com/post/3ldmp5qd6es2p | ||
|
||
<!-- With DID: --> | ||
|
||
https://bsky.app/profile/did:plc:yc6gmb3bo56qotdsywnsxrxp/post/3lgvpv7k5sc26 | ||
|
||
<!-- With query parameters: --> | ||
|
||
https://bsky.app/profile/bsky.app/post/3lgu4lg6j2k2v?foo=bar | ||
``` | ||
|
||
## Notes and caveats | ||
|
||
- This plugin is deliberately designed _only_ to embed when the URL is on its own line, and not inline with other text. | ||
- To do this, it uses a regular expression to recognize Bluesky URLs, wrapped in an HTML `<p>` tag. If your Markdown parser produces any other output, it won't be recognized. | ||
- The plugin supports both standard handles (e.g., `bsky.app`) and DID-based handles (e.g., `did:plc:yc6gmb3bo56qotdsywnsxrxp`). | ||
- Post IDs must be at least 10 characters long and can contain letters and numbers. | ||
- You can use the `embedDomain` option to embed posts from a staging environment or a self-hosted Bluesky instance. The custom domain must implement the Bluesky embed endpoint at `/profile/:handle/post/:id/embed`. | ||
- This plugin uses [transforms](https://www.11ty.dev/docs/config/#transforms), so it alters Eleventy's HTML output as it's generated. It doesn't alter the source markdown. | ||
|
||
## Credits | ||
|
||
- Bluesky package created by [@shellen](https://github.com/shellen) | ||
- Part of [eleventy-plugin-embed-everything](https://github.com/gfscott/eleventy-plugin-embed-everything) by [@gfscott](https://github.com/gfscott) | ||
|
||
## License | ||
|
||
MIT |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
const pattern = require("./lib/pattern.js"); | ||
const replace = require("./lib/replace.js"); | ||
const defaults = require("./lib/defaults.js"); | ||
|
||
module.exports = function (eleventyConfig, options = {}) { | ||
const config = Object.assign({}, defaults, options); | ||
|
||
eleventyConfig.addTransform( | ||
"embedBluesky", | ||
async function (content, outputPath) { | ||
// Return content untouched if there's no output path or it's not HTML | ||
if (!outputPath || !outputPath.endsWith(".html")) { | ||
return content; | ||
} | ||
|
||
try { | ||
return content.replace(pattern, (...match) => replace(match, config)); | ||
} catch (error) { | ||
console.warn("Error processing Bluesky embeds:", error); | ||
return content; | ||
} | ||
}, | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/** | ||
* Default configuration for Bluesky embeds | ||
* | ||
* embedDomain: The domain to use for embeds. | ||
* - Production: bsky.app | ||
* - Staging: staging.bsky.app | ||
* - Custom: Set to your own Bluesky-compatible instance | ||
* | ||
* Note: Custom domains must implement the Bluesky embed endpoint at /profile/:handle/post/:id/embed | ||
*/ | ||
module.exports = { | ||
// Embed appearance | ||
embedClass: "eleventy-plugin-embed-bluesky", | ||
containerCss: "position: relative; width: 100%; padding-bottom: 0;", | ||
iframeCss: "border: 0; position: relative; width: 100%;", | ||
iframeWidth: "550", | ||
iframeHeight: "300", | ||
iframeFrameborder: "0", | ||
iframeScrolling: "no", | ||
allowFullscreen: true, | ||
|
||
// Bluesky configuration | ||
embedDomain: "bsky.app", // Default to production, can be overridden for staging or custom domains | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
/** | ||
* Regex for parsing Bluesky URLs | ||
* | ||
* Notable points: | ||
* - Matches both bsky.app and staging.bsky.app domains | ||
* - Post IDs are alphanumeric and at least 10 characters long | ||
* - Profile IDs follow the handle format (including DID and custom domains) | ||
* - Supports URLs with and without protocol | ||
* - Handles query parameters | ||
* - Handles trailing slashes | ||
* | ||
* The numbered capture groups are: | ||
* 1. The handle | ||
* 2. The post ID | ||
*/ | ||
|
||
module.exports = /<p>\s*(?:<a [^>]*?>)?\s*(?:https?:)?(?:\/\/)?(?:w{3}\.)?(?:staging\.)?bsky\.app\/profile\/([^/\s]+)\/post\/([a-zA-Z0-9]{10,})(?:\/|\?[^<>\s]*)?\s*(?:<\/a>)?\s*<\/p>/; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
module.exports = function(match, config) { | ||
try { | ||
if (!match || !match[0]) { | ||
return ""; | ||
} | ||
|
||
// Extract handle and post ID from the match array | ||
// match[1] is the handle | ||
// match[2] is the post ID | ||
const handle = match[1]; | ||
const postId = match[2]; | ||
|
||
if (!handle || !postId) { | ||
return match[0]; | ||
} | ||
|
||
// Create the embed URL using Bluesky's embed endpoint | ||
const embedDomain = match[0].includes("staging.bsky.app") ? "staging.bsky.app" : config.embedDomain; | ||
const embedUrl = `https://${embedDomain}/profile/${handle}/post/${postId}/embed`; | ||
|
||
// Build the embed HTML | ||
let embed = `<div class="${config.embedClass}" style="${config.containerCss}">`; | ||
embed += `<iframe src="${embedUrl}" style="${config.iframeCss}" width="${config.iframeWidth}" height="${config.iframeHeight}" frameborder="${config.iframeFrameborder}" scrolling="${config.iframeScrolling}" ${config.allowFullscreen ? "allowfullscreen" : ""}></iframe>`; | ||
embed += "</div>"; | ||
|
||
return embed; | ||
} catch (error) { | ||
console.warn("Error creating Bluesky embed:", error); | ||
return match[0] || ""; | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
{ | ||
"name": "eleventy-plugin-embed-bluesky", | ||
"version": "1.0.0", | ||
"description": "An Eleventy plugin to automatically embed Bluesky posts, using just their URLs.", | ||
"keywords": [ | ||
"11ty", | ||
"eleventy", | ||
"eleventy-plugin", | ||
"bluesky" | ||
], | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "npx ava", | ||
"coverage": "nyc --reporter=lcov ava" | ||
}, | ||
"files": [ | ||
"index.js", | ||
"lib/**" | ||
], | ||
"author": { | ||
"name": "Jason Shellen", | ||
"url": "https://shellen.com" | ||
}, | ||
"license": "MIT", | ||
"homepage": "https://gfscott.com/embed-everything/", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/gfscott/eleventy-plugin-embed-everything.git" | ||
}, | ||
"bugs": "https://github.com/gfscott/eleventy-plugin-embed-everything/issues" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
/** | ||
* Test URLs for Bluesky embeds | ||
*/ | ||
|
||
export const validUrls = [ | ||
"https://bsky.app/profile/bsky.app/post/3lgu4lg6j2k2v", | ||
"https://bsky.app/profile/stereogum.bsky.social/post/3lh27cswiwc27/", | ||
"http://bsky.app/profile/merlevans.bsky.social/post/3lgzl25gr2c2k", | ||
"//bsky.app/profile/bsky.app/post/3le6bze3nus2c", | ||
"bsky.app/profile/bsky.app/post/3lgu4lg6j2k2v", | ||
"https://bsky.app/profile/did:plc:yc6gmb3bo56qotdsywnsxrxp/post/3lgvpv7k5sc26", | ||
"https://bsky.app/profile/merlevans.bsky.social/post/3lgzl25gr2c2k", | ||
"https://bsky.app/profile/shellen.com/post/3ldmp5qd6es2p", | ||
"https://bsky.app/profile/bsky.app/post/3lgu4lg6j2k2v?foo", | ||
"https://bsky.app/profile/bsky.app/post/3lgu4lg6j2k2v?foo=bar", | ||
"https://bsky.app/profile/bsky.app/post/3lgu4lg6j2k2v?foo&bar", | ||
"https://bsky.app/profile/bsky.app/post/3lgu4lg6j2k2v?foo=bar&baz=qux" | ||
]; | ||
|
||
export const invalidUrls = [ | ||
"https://bsky.app/profile/bsky.app/post/abc", | ||
"https://bsky.app/user/bsky.app/post/3lgu4lg6j2k2v", | ||
"https://bsky.app/profile/bsky.app/status/3lgu4lg6j2k2v", | ||
"https://example.com/profile/bsky.app/post/3lgu4lg6j2k2v" | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import test from "ava"; | ||
import pattern from "../lib/pattern.js"; | ||
import { validUrls, invalidUrls } from "./_validUrls.mjs"; | ||
|
||
/** | ||
* Test pattern matching with valid strings | ||
*/ | ||
validUrls.forEach((str) => { | ||
test(`Pattern matches valid string: ${str}`, (t) => { | ||
// Test basic URL | ||
const basicMatch = pattern.exec(`<p>${str}</p>`); | ||
t.truthy(basicMatch, "Basic URL should match"); | ||
|
||
// Test with link tags | ||
const linkedMatch = pattern.exec(`<p><a href="${str}">${str}</a></p>`); | ||
t.truthy(linkedMatch, "URL with link tags should match"); | ||
|
||
// Test with whitespace | ||
const spacedMatch = pattern.exec(`<p> ${str} </p>`); | ||
t.truthy(spacedMatch, "URL with whitespace should match"); | ||
}); | ||
}); | ||
|
||
/** | ||
* Test pattern rejection of invalid strings | ||
*/ | ||
invalidUrls.forEach((str) => { | ||
test(`Pattern rejects invalid string: ${str}`, (t) => { | ||
// Test basic invalid URL | ||
const basicMatch = pattern.exec(`<p>${str}</p>`); | ||
t.falsy(basicMatch, "Invalid URL should not match"); | ||
|
||
// Test with whitespace | ||
const spacedMatch = pattern.exec(`<p> ${str} </p>`); | ||
t.falsy(spacedMatch, "Invalid URL with whitespace should not match"); | ||
}); | ||
}); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would there be a logical maximum character count for the post ID? Just wondering about RegEx efficiency 🤔