Skip to content

Commit

Permalink
feat: add BlueskyEmbed
Browse files Browse the repository at this point in the history
adds detection of Bluesky embed codes
and turns them into embeds.

we cannot do URL detection on Bluesky embeds because we need the IPFS cid and uri
of the post to properly display it.
  • Loading branch information
tim-evans committed May 15, 2024
1 parent cfe433d commit f7bd42a
Show file tree
Hide file tree
Showing 7 changed files with 371 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { BlockAnnotation } from "@atjson/document";

export class BlueskyEmbed extends BlockAnnotation<{
/**
* The at protocol uri of the Bluesky embed
*/
uri: string;

/**
* The IPFS content id of the post.
*/
cid: string;

/**
* Layout information, used to indicate mutually
* exclusive layouts, for example sizes, floats, etc.
*/
layout?: string;

/**
* A named identifier used to quickly jump to this item
*/
anchorName?: string;

/**
* The post content at the time of embedding, ususally
* a textual representation of the content with some links.
*/
content?: string;
}> {
static vendorPrefix = "offset";
static type = "bluesky-embed";
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from "./blockquote";
export * from "./bluesky-embed";
export * from "./bold";
export * from "./ceros-embed";
export * from "./cne-audio-embed";
Expand Down
6 changes: 4 additions & 2 deletions packages/@atjson/offset-annotations/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Document from "@atjson/document";
import {
Blockquote,
BlueskyEmbed,
Bold,
CerosEmbed,
CneAudioEmbed,
Expand All @@ -12,11 +13,11 @@ import {
FireworkEmbed,
FixedIndent,
GiphyEmbed,
GroupItem,
Group,
HTML,
GroupItem,
Heading,
HorizontalRule,
HTML,
IframeEmbed,
Image,
InstagramEmbed,
Expand Down Expand Up @@ -48,6 +49,7 @@ export default class OffsetSource extends Document {
static contentType = "application/vnd.atjson+offset";
static schema = [
Blockquote,
BlueskyEmbed,
Bold,
CerosEmbed,
CneAudioEmbed,
Expand Down
17 changes: 17 additions & 0 deletions packages/@atjson/renderer-html/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
AudioEnvironments,
Blockquote,
BlueskyEmbed,
CerosEmbed,
CneAudioEmbed,
CneEventRegistrationEmbed,
Expand Down Expand Up @@ -289,6 +290,22 @@ export default class HTMLRenderer extends Renderer {
}</a></p></div></blockquote> <script async src="//www.instagram.com/embed.js"></script>`;
}

*BlueskyEmbed(embed: Block<BlueskyEmbed>) {
let slice =
embed.attributes.content != null &&
this.getSlice(embed.attributes.content);
console.log(embed);

Check failure on line 297 in packages/@atjson/renderer-html/src/index.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected console statement

return `<blockquote ${this.htmlAttributes({
id: embed.attributes.anchorName,
class: "bluesky-embed",
"data-bluesky-uri": embed.attributes.uri,
"data-bluesky-cid": embed.attributes.cid,
}).join(" ")}>${
slice ? this.render(slice) : ""
}</blockquote><script async src="https://embed.bsky.app/static/embed.js" charset="utf-8"></script>`;
}

*FacebookEmbed(embed: Block<FacebookEmbed>) {
let slice =
embed.attributes.content != null &&
Expand Down
117 changes: 117 additions & 0 deletions packages/@atjson/renderer-html/test/bluesky-embed.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import OffsetSource, { BlueskyEmbed } from "@atjson/offset-annotations";
import { ParseAnnotation } from "@atjson/document";
import Renderer from "../src";

describe("BlueskyEmbed", () => {
test("no content", () => {
let doc = new OffsetSource({
content: "\uFFFC",
annotations: [
new BlueskyEmbed({
start: 0,
end: 1,
attributes: {
cid: "bafyreicg7axsdp6b7f4uj75ggdfhrdl52cqpjah45scox3prmqflwg557i",
uri: "at://did:plc:xrr5j2okn7ew2zvcwsxus3gb/app.bsky.feed.post/3kshwuxmy5o2y",
},
}),
new ParseAnnotation({
start: 0,
end: 1,
}),
],
});

expect(Renderer.render(doc)).toEqual(
`<blockquote class="bluesky-embed" data-bluesky-uri="at://did:plc:xrr5j2okn7ew2zvcwsxus3gb/app.bsky.feed.post/3kshwuxmy5o2y" data-bluesky-cid="bafyreicg7axsdp6b7f4uj75ggdfhrdl52cqpjah45scox3prmqflwg557i"></blockquote><script async src="https://embed.bsky.app/static/embed.js" charset="utf-8"></script>`
);
});

test("with content", () => {
debugger;

Check failure on line 31 in packages/@atjson/renderer-html/test/bluesky-embed.test.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected 'debugger' statement
expect(
Renderer.render({
blocks: [
{
attributes: {
cid: "bafyreicg7axsdp6b7f4uj75ggdfhrdl52cqpjah45scox3prmqflwg557i",
content: "M00000000",
uri: "at://did:plc:xrr5j2okn7ew2zvcwsxus3gb/app.bsky.feed.post/3kshwuxmy5o2y",
},
id: "B00000000",
parents: [],
selfClosing: false,
type: "bluesky-embed",
},
{
attributes: {},
id: "B00000001",
parents: ["bluesky-embed"],
selfClosing: false,
type: "text",
},
{
attributes: {},
id: "B00000002",
parents: ["bluesky-embed", "text"],
selfClosing: true,
type: "line-break",
},
{
attributes: {},
id: "B00000003",
parents: ["bluesky-embed", "text"],
selfClosing: true,
type: "line-break",
},
{
attributes: {},
id: "B00000004",
parents: ["bluesky-embed", "text"],
selfClosing: true,
type: "line-break",
},
],

marks: [
{
attributes: {
refs: ["B00000000"],
},
id: "M00000000",
range: "(1..144]",
type: "slice",
},
{
attributes: {
url: "https://bsky.app/profile/did:plc:xrr5j2okn7ew2zvcwsxus3gb/post/3kshwuxmy5o2y?ref_src=embed",
},
id: "M00000001",
range: "(63..79)",
type: "link",
},
{
attributes: {
url: "https://bsky.app/profile/did:plc:xrr5j2okn7ew2zvcwsxus3gb?ref_src=embed",
},
id: "M00000002",
range: "(96..119)",
type: "link",
},
{
attributes: {
url: "https://bsky.app/profile/did:plc:xrr5j2okn7ew2zvcwsxus3gb/post/3kshwuxmy5o2y?ref_src=embed",
},
id: "M00000003",
range: "(121..144)",
type: "link",
},
],

text: "Lap time al fresco. Photo from my collection, no date/info.[image or embed]— Cats of Yore (@catsofyore.bsky.social) May 14, 2024 at 3:43 PM",
})
).toMatchInlineSnapshot(
`"<blockquote class="bluesky-embed" data-bluesky-uri="at://did:plc:xrr5j2okn7ew2zvcwsxus3gb/app.bsky.feed.post/3kshwuxmy5o2y" data-bluesky-cid="bafyreicg7axsdp6b7f4uj75ggdfhrdl52cqpjah45scox3prmqflwg557i">Lap time al fresco. Photo from my collection, no date/info.<br /><br /><a href="https://bsky.app/profile/did:plc:xrr5j2okn7ew2zvcwsxus3gb/post/3kshwuxmy5o2y?ref_src=embed">[image or embed]</a><br />&#x2014; Cats of Yore (<a href="https://bsky.app/profile/did:plc:xrr5j2okn7ew2zvcwsxus3gb?ref_src=embed">@catsofyore.bsky.social</a>) <a href="https://bsky.app/profile/did:plc:xrr5j2okn7ew2zvcwsxus3gb/post/3kshwuxmy5o2y?ref_src=embed">May 14, 2024 at 3:43 PM</a></blockquote><script async src="https://embed.bsky.app/static/embed.js" charset="utf-8"></script>"`
);
});
});
95 changes: 94 additions & 1 deletion packages/@atjson/source-html/src/converter/social-embeds.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import Document, {
AdjacentBoundaryBehaviour,
Annotation,
ParseAnnotation,
SliceAnnotation,
TextAnnotation,
UnknownAnnotation,
is,
} from "@atjson/document";
import { LineBreak, SocialURLs, TikTokEmbed } from "@atjson/offset-annotations";
import {
BlueskyEmbed,
LineBreak,
SocialURLs,
TikTokEmbed,
} from "@atjson/offset-annotations";
import { Script, Anchor, Blockquote } from "../annotations";

function aCoversB(a: Annotation<any>, b: Annotation<any>) {
Expand Down Expand Up @@ -436,6 +442,93 @@ export default function (doc: Document) {
doc.removeAnnotations(sections);
});

/**
* Bluesky embed structure:
* <blockquote class="bluesky-embed" ...></blockquote>
* <script async src="https://embed.bsky.app/static/embed.js" charset="utf-8"></script>
*/
doc
.where(function isBlueskyBlockquote(a) {
return is(a, Blockquote) && a.attributes.class === "bluesky-embed";
})
.as("blockquote")
.outerJoin(
doc.where({ type: "-html-script" }).as("scripts"),
function scriptRightAfterIframe(blockquote, script: Script) {
let src = script.attributes.src;
return (
(script.start === blockquote.end ||
script.start === blockquote.end + 1) &&
src != null
);
}
)
.outerJoin(
doc.where({ type: "-html-p" }).as("paragraphs"),
function scriptRightAfterIframe({ blockquote }, p) {
return aCoversB(blockquote, p);
}
)
.update(function joinBlockQuoteWithLinksAndScripts({
blockquote,
paragraphs,
scripts,
}) {
doc.insertText(blockquote.start, "\uFFFC");
let content = new SliceAnnotation({
start: blockquote.start + 1,
end: blockquote.end,
attributes: {
refs: [blockquote.id],
},
});

doc.replaceAnnotation(
blockquote,
new BlueskyEmbed({
id: blockquote.id,
start: blockquote.start,
end: blockquote.end,
attributes: {
uri: blockquote.attributes.dataset["bluesky-uri"],
cid: blockquote.attributes.dataset["bluesky-cid"],
content: content?.id,
},
}),
content,
new ParseAnnotation({
start: blockquote.start - 1,
end: blockquote.start,
}),
new TextAnnotation({
start: blockquote.start + 1,
end: blockquote.end,
})
);

let paragraph = paragraphs[0];
if (paragraph) {
doc.insertText(
paragraph.end,
"\uFFFC",
AdjacentBoundaryBehaviour.preserveTrailing
);
doc.addAnnotations(
new LineBreak({
start: paragraph.end,
end: paragraph.end + 1,
}),
new ParseAnnotation({
start: paragraph.end,
end: paragraph.end + 1,
})
);
doc.removeAnnotations(paragraphs);
}

doc.removeAnnotations(scripts);
});

// Handle Giphy embeds; they have
// <iframe></iframe><p><a>via Giphy</a></p>
doc
Expand Down

0 comments on commit f7bd42a

Please sign in to comment.