From 393222c033f5dec1acb1dd205e8b512c4cb32184 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Mon, 4 Nov 2024 23:17:41 +0000 Subject: [PATCH 01/19] wip: add BlueskyPost --- demo/src/pages/bluesky.astro | 20 +++ demo/src/pages/integration.mdx | 2 + demo/src/pages/markdown.mdx | 6 +- package-lock.json | 111 +++++++++++- packages/astro-embed-bluesky/README.md | 5 + packages/astro-embed-bluesky/package.json | 36 ++++ .../astro-embed-bluesky/src/external.astro | 69 ++++++++ .../astro-embed-bluesky/src/image-grid.astro | 83 +++++++++ packages/astro-embed-bluesky/src/index.ts | 1 + packages/astro-embed-bluesky/src/list.astro | 108 ++++++++++++ packages/astro-embed-bluesky/src/matcher.ts | 9 + packages/astro-embed-bluesky/src/media.astro | 48 ++++++ packages/astro-embed-bluesky/src/post.astro | 162 ++++++++++++++++++ .../src/record-with-media.astro | 27 +++ packages/astro-embed-bluesky/src/record.astro | 25 +++ .../src/starter-pack.astro | 103 +++++++++++ packages/astro-embed-bluesky/src/types.ts | 5 + packages/astro-embed-bluesky/src/utils.ts | 95 ++++++++++ packages/astro-embed-bluesky/src/video.astro | 62 +++++++ .../astro-embed-bluesky/src/view-record.astro | 132 ++++++++++++++ .../astro-embed-integration/remark-plugin.ts | 2 + packages/astro-embed/index.ts | 1 + packages/astro-embed/package.json | 3 +- 23 files changed, 1109 insertions(+), 6 deletions(-) create mode 100644 demo/src/pages/bluesky.astro create mode 100644 packages/astro-embed-bluesky/README.md create mode 100644 packages/astro-embed-bluesky/package.json create mode 100644 packages/astro-embed-bluesky/src/external.astro create mode 100644 packages/astro-embed-bluesky/src/image-grid.astro create mode 100644 packages/astro-embed-bluesky/src/index.ts create mode 100644 packages/astro-embed-bluesky/src/list.astro create mode 100644 packages/astro-embed-bluesky/src/matcher.ts create mode 100644 packages/astro-embed-bluesky/src/media.astro create mode 100644 packages/astro-embed-bluesky/src/post.astro create mode 100644 packages/astro-embed-bluesky/src/record-with-media.astro create mode 100644 packages/astro-embed-bluesky/src/record.astro create mode 100644 packages/astro-embed-bluesky/src/starter-pack.astro create mode 100644 packages/astro-embed-bluesky/src/types.ts create mode 100644 packages/astro-embed-bluesky/src/utils.ts create mode 100644 packages/astro-embed-bluesky/src/video.astro create mode 100644 packages/astro-embed-bluesky/src/view-record.astro diff --git a/demo/src/pages/bluesky.astro b/demo/src/pages/bluesky.astro new file mode 100644 index 0000000..0478901 --- /dev/null +++ b/demo/src/pages/bluesky.astro @@ -0,0 +1,20 @@ +--- +import { BlueskyPost } from "@astro-community/astro-embed-bluesky"; +import Base from "../layouts/Base.astro"; +--- + + +

Basic

+ +

Images

+ +

External link

+ +

Video

+ +

External media with quote

+ +

External link with quote

+ +

Starter pack

+ diff --git a/demo/src/pages/integration.mdx b/demo/src/pages/integration.mdx index abac248..7171957 100644 --- a/demo/src/pages/integration.mdx +++ b/demo/src/pages/integration.mdx @@ -18,6 +18,8 @@ defineConfig({ https://twitter.com/astrodotbuild/status/1511750228428435457 +https://bsky.app/profile/antfu.me/post/3la5f4i4znh2c + https://vimeo.com/32001208 http://www.youtube.com/watch?v=Hoe-woAhq_k diff --git a/demo/src/pages/markdown.mdx b/demo/src/pages/markdown.mdx index 996eb22..c33250b 100644 --- a/demo/src/pages/markdown.mdx +++ b/demo/src/pages/markdown.mdx @@ -3,7 +3,7 @@ title: Components in MDX layout: ../layouts/Base.astro --- -import { Tweet, YouTube, LinkPreview } from 'astro-embed'; +import { Tweet, YouTube, LinkPreview, BlueskyPost } from 'astro-embed'; The embeds on this page are imported as components in the MDX frontmatter’s `setup` block and then used in the document body. @@ -27,3 +27,7 @@ setup: | ## `` + +## `` + + diff --git a/package-lock.json b/package-lock.json index 5af8e50..2d94e67 100644 --- a/package-lock.json +++ b/package-lock.json @@ -121,6 +121,10 @@ "resolved": "packages/astro-embed-baseline-status", "link": true }, + "node_modules/@astro-community/astro-embed-bluesky": { + "resolved": "packages/astro-embed-bluesky", + "link": true + }, "node_modules/@astro-community/astro-embed-demo": { "resolved": "demo", "link": true @@ -485,6 +489,58 @@ "node": ">=8" } }, + "node_modules/@atproto/api": { + "version": "0.13.14", + "resolved": "https://registry.npmjs.org/@atproto/api/-/api-0.13.14.tgz", + "integrity": "sha512-CG5UpjI1WwSasSJTGadmr07EwWvl5JV658YZHcwIIg+Psk5sDloQOUJckuo1MP6wke1z6p/BUoPL4lxAATzMzA==", + "dependencies": { + "@atproto/common-web": "^0.3.1", + "@atproto/lexicon": "^0.4.2", + "@atproto/syntax": "^0.3.0", + "@atproto/xrpc": "^0.6.3", + "await-lock": "^2.2.2", + "multiformats": "^9.9.0", + "tlds": "^1.234.0", + "zod": "^3.23.8" + } + }, + "node_modules/@atproto/common-web": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@atproto/common-web/-/common-web-0.3.1.tgz", + "integrity": "sha512-N7wiTnus5vAr+lT//0y8m/FaHHLJ9LpGuEwkwDAeV3LCiPif4m/FS8x/QOYrx1PdZQwKso95RAPzCGWQBH5j6Q==", + "dependencies": { + "graphemer": "^1.4.0", + "multiformats": "^9.9.0", + "uint8arrays": "3.0.0", + "zod": "^3.23.8" + } + }, + "node_modules/@atproto/lexicon": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.4.2.tgz", + "integrity": "sha512-CXoOkhcdF3XVUnR2oNgCs2ljWfo/8zUjxL5RIhJW/UNLp/FSl+KpF8Jm5fbk8Y/XXVPGRAsv9OYfxyU/14N/pw==", + "dependencies": { + "@atproto/common-web": "^0.3.1", + "@atproto/syntax": "^0.3.0", + "iso-datestring-validator": "^2.2.2", + "multiformats": "^9.9.0", + "zod": "^3.23.8" + } + }, + "node_modules/@atproto/syntax": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.3.0.tgz", + "integrity": "sha512-Weq0ZBxffGHDXHl9U7BQc2BFJi/e23AL+k+i5+D9hUq/bzT4yjGsrCejkjq0xt82xXDjmhhvQSZ0LqxyZ5woxA==" + }, + "node_modules/@atproto/xrpc": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@atproto/xrpc/-/xrpc-0.6.3.tgz", + "integrity": "sha512-S3tRvOdA9amPkKLll3rc4vphlDitLrkN5TwWh5Tu/jzk7mnobVVE3akYgICV9XCNHKjWM+IAPxFFI2qi+VW6nQ==", + "dependencies": { + "@atproto/lexicon": "^0.4.2", + "zod": "^3.23.8" + } + }, "node_modules/@babel/code-frame": { "version": "7.25.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.7.tgz", @@ -3871,6 +3927,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/await-lock": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/await-lock/-/await-lock-2.2.2.tgz", + "integrity": "sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==" + }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -6249,8 +6310,7 @@ "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" }, "node_modules/gray-matter": { "version": "4.0.3", @@ -7402,6 +7462,11 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/iso-datestring-validator": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/iso-datestring-validator/-/iso-datestring-validator-2.2.2.tgz", + "integrity": "sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -8883,6 +8948,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==" + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -11669,6 +11739,14 @@ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.0.tgz", "integrity": "sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==" }, + "node_modules/tlds": { + "version": "1.255.0", + "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.255.0.tgz", + "integrity": "sha512-tcwMRIioTcF/FcxLev8MJWxCp+GUALRhFEqbDoZrnowmKSGqPrl5pqS+Sut2m8BgJ6S4FExCSSpGffZ0Tks6Aw==", + "bin": { + "tlds": "bin.js" + } + }, "node_modules/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -11745,6 +11823,11 @@ "typescript": ">=4.2.0" } }, + "node_modules/ts-pattern": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/ts-pattern/-/ts-pattern-5.5.0.tgz", + "integrity": "sha512-jqbIpTsa/KKTJYWgPNsFNbLVpwCgzXfFJ1ukNn4I8hMwyQzHMJnk/BqWzggB0xpkILuKzaO/aMYhS0SkaJyKXg==" + }, "node_modules/tslib": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", @@ -11978,6 +12061,14 @@ "resolved": "https://registry.npmjs.org/uhyphen/-/uhyphen-0.2.0.tgz", "integrity": "sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==" }, + "node_modules/uint8arrays": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.0.0.tgz", + "integrity": "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==", + "dependencies": { + "multiformats": "^9.4.2" + } + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -12623,7 +12714,7 @@ }, "packages/astro-embed-baseline-status": { "name": "@astro-community/astro-embed-baseline-status", - "version": "0.1.1", + "version": "0.1.2", "license": "MIT", "dependencies": { "@astro-community/astro-embed-utils": "^0.1.0" @@ -12632,6 +12723,18 @@ "astro": "^4.0.0-beta || ^5.0.0-beta" } }, + "packages/astro-embed-bluesky": { + "name": "@astro-community/astro-embed-bluesky", + "version": "0.0.0", + "license": "MIT", + "dependencies": { + "@atproto/api": "^0.13.14", + "ts-pattern": "^5.5.0" + }, + "peerDependencies": { + "astro": "^4.0.0 || ^5.0.0-beta.0" + } + }, "packages/astro-embed-integration": { "name": "@astro-community/astro-embed-integration", "version": "0.7.2", @@ -12694,7 +12797,7 @@ }, "packages/astro-embed-youtube": { "name": "@astro-community/astro-embed-youtube", - "version": "0.5.5", + "version": "0.5.6", "license": "MIT", "dependencies": { "lite-youtube-embed": "^0.3.3" diff --git a/packages/astro-embed-bluesky/README.md b/packages/astro-embed-bluesky/README.md new file mode 100644 index 0000000..e71ad72 --- /dev/null +++ b/packages/astro-embed-bluesky/README.md @@ -0,0 +1,5 @@ +# `@astro-community/astro-embed-bluesky` + +This package contains a component for embedding Bluesky posts in Astro projects. + +[Read the `` component docs](https://astro-embed.netlify.app/components/bluesky/). diff --git a/packages/astro-embed-bluesky/package.json b/packages/astro-embed-bluesky/package.json new file mode 100644 index 0000000..dcad320 --- /dev/null +++ b/packages/astro-embed-bluesky/package.json @@ -0,0 +1,36 @@ +{ + "name": "@astro-community/astro-embed-bluesky", + "version": "0.0.0", + "description": "", + "type": "module", + "main": "./src/index.ts", + "files": [ + "src" + ], + "exports": { + ".": "./src/index.ts", + "./matcher": "./src/matcher.ts" + }, + "scripts": { + "check": "publint && attw $(pnpm pack) --ignore-rules=cjs-resolves-to-esm", + "test": "vitest" + }, + "peerDependencies": { + "astro": "^4.0.0 || ^5.0.0-beta.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com:ascorbic/astro-bluesky-embed.git", + "directory": "packages/embed" + }, + "homepage": "https://github.com/ascorbic/astro-bluesky-embed", + "keywords": [ + "withastro" + ], + "author": "Matt Kane", + "license": "MIT", + "dependencies": { + "@atproto/api": "^0.13.14", + "ts-pattern": "^5.5.0" + } +} \ No newline at end of file diff --git a/packages/astro-embed-bluesky/src/external.astro b/packages/astro-embed-bluesky/src/external.astro new file mode 100644 index 0000000..70b6efe --- /dev/null +++ b/packages/astro-embed-bluesky/src/external.astro @@ -0,0 +1,69 @@ +--- +import { AppBskyEmbedExternal } from "@atproto/api"; + +interface Props { + embed: AppBskyEmbedExternal.View; + compact?: boolean; +} + +const { uri, thumb, title, description } = Astro.props.embed.external; + +const domain = new URL(uri).hostname; + +const { compact } = Astro.props; +--- + + + {compact ? null : {title}} +
+

{domain}

+ {compact ? null : (

{title}

+

{description}

)} +
+
+ + diff --git a/packages/astro-embed-bluesky/src/image-grid.astro b/packages/astro-embed-bluesky/src/image-grid.astro new file mode 100644 index 0000000..6077d57 --- /dev/null +++ b/packages/astro-embed-bluesky/src/image-grid.astro @@ -0,0 +1,83 @@ +--- +import { AppBskyEmbedImages } from "@atproto/api"; + +type Props = { + embed: AppBskyEmbedImages.View; +}; + +const { images } = Astro.props.embed; +--- + + +
+
+ { + images.map((image) => ( +
+ {image.alt +
+ )) + } +
+
+ + diff --git a/packages/astro-embed-bluesky/src/index.ts b/packages/astro-embed-bluesky/src/index.ts new file mode 100644 index 0000000..1b23c9f --- /dev/null +++ b/packages/astro-embed-bluesky/src/index.ts @@ -0,0 +1 @@ +export { default as BlueskyPost } from "./post.astro"; diff --git a/packages/astro-embed-bluesky/src/list.astro b/packages/astro-embed-bluesky/src/list.astro new file mode 100644 index 0000000..7e0c471 --- /dev/null +++ b/packages/astro-embed-bluesky/src/list.astro @@ -0,0 +1,108 @@ +--- +import { AppBskyGraphDefs } from "@atproto/api"; +import { atUriToListUri } from "./utils"; + +interface Props { + record: AppBskyGraphDefs.ListView; +} + +const { record } = Astro.props; + +const list = AppBskyGraphDefs.isListView(record) ? record : null; + +const purposes: Record = { + "app.bsky.graph.defs#curatelist": "User list", + "app.bsky.graph.defs#modlist": "Moderation list", + "app.bsky.graph.defs#referencelist": "List", +}; + +const purpose = (list && purposes[list.purpose]) ?? "List"; +--- + + +
+
+
+ {record.creator.displayName} +
+
+

{list?.name}

+

{purpose} by {record.creator.displayName}

+
+
+

{list?.description}

+
+
+ + diff --git a/packages/astro-embed-bluesky/src/matcher.ts b/packages/astro-embed-bluesky/src/matcher.ts new file mode 100644 index 0000000..41eb285 --- /dev/null +++ b/packages/astro-embed-bluesky/src/matcher.ts @@ -0,0 +1,9 @@ +const urlPattern = /^https:\/\/bsky\.app\/profile\/[^/]+\/post\/[^/]+$/; +/** + * Tests if a URL is a Bluesky post URL. If it is, returns the URL, otherwise returns undefined. + */ +export default function matcher(url: string): string | undefined { + const match = url.match(urlPattern); + console.log('match', match); + return match ? url : undefined; +} diff --git a/packages/astro-embed-bluesky/src/media.astro b/packages/astro-embed-bluesky/src/media.astro new file mode 100644 index 0000000..48ec58d --- /dev/null +++ b/packages/astro-embed-bluesky/src/media.astro @@ -0,0 +1,48 @@ +--- +import type { Post } from "./types"; +import { match, P } from "ts-pattern"; +import { + AppBskyEmbedExternal, + AppBskyEmbedImages, + AppBskyEmbedRecord, + AppBskyEmbedRecordWithMedia, + AppBskyEmbedVideo, +} from "@atproto/api"; +import External from "./external.astro"; +import ImageGrid from "./image-grid.astro"; +import RecordWithMedia from "./record-with-media.astro"; +import Video from "./video.astro"; +import Record from "./record.astro"; + +type Props = { + media: Post["embed"]; + postUrl: string; + compact?: boolean; +}; + +const { media, postUrl, compact } = Astro.props; +--- + +{ + match(media) + .when(AppBskyEmbedExternal.isView, (media) => ( + + )) + .when(AppBskyEmbedImages.isView, (media) => ( + + + + )) + .when(AppBskyEmbedRecordWithMedia.isView, (media) => ( + + )) + .when(AppBskyEmbedRecord.isView, (media) => ( + + )) + .when(AppBskyEmbedVideo.isView, (media) => ( + + + )) + .otherwise((media) => media?.$type) +} diff --git a/packages/astro-embed-bluesky/src/post.astro b/packages/astro-embed-bluesky/src/post.astro new file mode 100644 index 0000000..fcc4847 --- /dev/null +++ b/packages/astro-embed-bluesky/src/post.astro @@ -0,0 +1,162 @@ +--- +import { renderPostAsHtml, resolvePost } from "./utils.js"; +import Media from "./media.astro"; + +type Props = { + id: string; + post?: string; +}; + +const postUrl = Astro.props.id ?? Astro.props.post; + +const post = await resolvePost(postUrl)!; + +if (!post) { + return new Response(""); +} + +const { record, embed, author } = post; + +const authorUrl = `https://bsky.app/profile/${author?.handle}`; + +const body = renderPostAsHtml(post); + +const formatter = new Intl.DateTimeFormat("en-US", { + dateStyle: "long", + timeStyle: "short", +}); +--- + +
+
+
+ + {author?.displayName} + + + + + +
+ +

+ + {embed && } + {formatter.format(new Date(record.createdAt ?? ""))} +

+
+ + diff --git a/packages/astro-embed-bluesky/src/record-with-media.astro b/packages/astro-embed-bluesky/src/record-with-media.astro new file mode 100644 index 0000000..c165a27 --- /dev/null +++ b/packages/astro-embed-bluesky/src/record-with-media.astro @@ -0,0 +1,27 @@ +--- +import { AppBskyEmbedRecordWithMedia } from "@atproto/api"; +import Media from "./media.astro"; +import Record from "./record.astro"; + +interface Props { + embed: AppBskyEmbedRecordWithMedia.View; + postUrl: string; + compact?: boolean; +} + +const { compact } = Astro.props; + +const { media, record } = Astro.props.embed; +--- + +
+ + {compact ? null : } +
+ diff --git a/packages/astro-embed-bluesky/src/record.astro b/packages/astro-embed-bluesky/src/record.astro new file mode 100644 index 0000000..ea9d113 --- /dev/null +++ b/packages/astro-embed-bluesky/src/record.astro @@ -0,0 +1,25 @@ +--- +import { AppBskyEmbedRecord, AppBskyGraphDefs } from "@atproto/api"; +import { match, P } from "ts-pattern"; +import ViewRecord from "./view-record.astro"; +import StarterPack from "./starter-pack.astro"; +import List from "./list.astro"; + +interface Props { + embed: AppBskyEmbedRecord.View; + compact?: boolean; +} + +--- + +{ + match(Astro.props.embed.record) + .when(AppBskyEmbedRecord.isViewRecord, (record) => ( + + )) + .when(AppBskyGraphDefs.isStarterPackViewBasic, (record) => ( + + )) + .when(AppBskyGraphDefs.isListView, (record) => ) + .otherwise((record) => <>) +} diff --git a/packages/astro-embed-bluesky/src/starter-pack.astro b/packages/astro-embed-bluesky/src/starter-pack.astro new file mode 100644 index 0000000..90ea597 --- /dev/null +++ b/packages/astro-embed-bluesky/src/starter-pack.astro @@ -0,0 +1,103 @@ +--- +import { AppBskyGraphDefs, AppBskyGraphStarterpack } from "@atproto/api"; +import { atUriToStarterPackUri, starterPackOgImage } from "./utils"; + +interface Props { + record: AppBskyGraphDefs.StarterPackViewBasic; +} + +const { record } = Astro.props; + +const pack = AppBskyGraphStarterpack.isRecord(record.record) + ? record.record + : null; +--- + + + +
+
+
+ {record.creator.displayName} +
+
+

{pack?.name}

+

Starter pack by {record.creator.displayName}

+
+
+

{pack?.description}

+
+
+ + diff --git a/packages/astro-embed-bluesky/src/types.ts b/packages/astro-embed-bluesky/src/types.ts new file mode 100644 index 0000000..d9783f7 --- /dev/null +++ b/packages/astro-embed-bluesky/src/types.ts @@ -0,0 +1,5 @@ +import { AppBskyFeedPost, AppBskyFeedDefs } from "@atproto/api"; + +export interface Post extends AppBskyFeedDefs.PostView { + record: AppBskyFeedPost.Record; +} diff --git a/packages/astro-embed-bluesky/src/utils.ts b/packages/astro-embed-bluesky/src/utils.ts new file mode 100644 index 0000000..17d412a --- /dev/null +++ b/packages/astro-embed-bluesky/src/utils.ts @@ -0,0 +1,95 @@ +import { AtpAgent, RichText } from "@atproto/api"; +import type { PostView } from "@atproto/api/dist/client/types/app/bsky/feed/defs"; +import type { Post } from "./types"; + +const escapeMap: Record = { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", +}; + +export const escapeHTML = (str?: string) => + str?.replace(/[&<>"']/g, (match) => escapeMap[match] || match) ?? ""; + +export function renderPostAsHtml(post?: PostView | Post) { + if (!post) { + return ""; + } + const rt = new RichText(post.record as any); + let html = ""; + for (const segment of rt.segments()) { + if (segment.isLink()) { + html += `${escapeHTML(segment.text)}`; + } else if (segment.isMention()) { + html += `${escapeHTML(segment.text)}`; + } else if (segment.isTag()) { + html += `#${escapeHTML(segment.tag?.tag)}`; + } else { + html += escapeHTML(segment.text); + } + } + return html; +} + +const agent = new AtpAgent({ + service: "https://public.api.bsky.app", +}); + +export async function resolvePost( + postUrl: string | Post | PostView, +): Promise { + let atUri; + + if (typeof postUrl === "object") { + return postUrl as Post; + } + + if (postUrl.startsWith("at:")) { + atUri = postUrl; + } else { + if (!postUrl.startsWith("https://bsky.app/")) { + return undefined; + } + const urlParts = new URL(postUrl).pathname.split("/"); + let did = urlParts[2]!; + const postId = urlParts[4]!; + if (!did || !postId) { + return undefined; + } + if (!did.startsWith("did:")) { + const handleResolution = await agent.resolveHandle({ handle: did }); + if (!handleResolution.data.did) { + return undefined; + } + did = handleResolution.data.did; + } + + atUri = `at://${did}/app.bsky.feed.post/${postId}`; + } + + const hydratedPost = await agent.getPosts({ uris: [atUri] }); + + return hydratedPost.data.posts[0] as unknown as Post; +} + +export function atUriToPostUri(atUri: string) { + const [, , did, , postId] = atUri.split("/"); + return `https://bsky.app/profile/${did}/post/${postId}`; +} + +export function atUriToStarterPackUri(atUri: string) { + const [, , did, , packId] = atUri.split("/"); + return `https://bsky.app/starter-pack/${did}/${packId}`; +} + +export function atUriToListUri(atUri: string) { + const [, , did, , listId] = atUri.split("/"); + return `https://bsky.app/profile/${did}/lists/${listId}`; +} + +export function starterPackOgImage(uri: string) { + const [, , did, , packId] = uri.split("/"); + return `https://ogcard.cdn.bsky.app/start/${did}/${packId}`; +} diff --git a/packages/astro-embed-bluesky/src/video.astro b/packages/astro-embed-bluesky/src/video.astro new file mode 100644 index 0000000..910aa5f --- /dev/null +++ b/packages/astro-embed-bluesky/src/video.astro @@ -0,0 +1,62 @@ +--- +import type { AppBskyEmbedVideo } from "@atproto/api"; + +interface Props { + embed: AppBskyEmbedVideo.View; +} + +const { thumbnail, aspectRatio } = Astro.props.embed; +--- + +
+ Video +
+ Play button +
+
+ + diff --git a/packages/astro-embed-bluesky/src/view-record.astro b/packages/astro-embed-bluesky/src/view-record.astro new file mode 100644 index 0000000..0f60e5b --- /dev/null +++ b/packages/astro-embed-bluesky/src/view-record.astro @@ -0,0 +1,132 @@ +--- +import { AppBskyEmbedRecord } from "@atproto/api"; +import Media from "./media.astro"; +import { atUriToPostUri } from "./utils"; + +interface Props { + record: AppBskyEmbedRecord.ViewRecord; +} + +const { record } = Astro.props; + +const postUrl = atUriToPostUri(record.uri); +--- + +
+ + +

{(record.value as any).text}

+ +
+ + diff --git a/packages/astro-embed-integration/remark-plugin.ts b/packages/astro-embed-integration/remark-plugin.ts index 1707955..32f97e9 100644 --- a/packages/astro-embed-integration/remark-plugin.ts +++ b/packages/astro-embed-integration/remark-plugin.ts @@ -1,10 +1,12 @@ import { type Node, select, selectAll } from 'unist-util-select'; +import blueskyMatcher from '@astro-community/astro-embed-bluesky/matcher'; import twitterMatcher from '@astro-community/astro-embed-twitter/matcher'; import vimeoMatcher from '@astro-community/astro-embed-vimeo/matcher'; import youtubeMatcher from '@astro-community/astro-embed-youtube/matcher'; import linkPreviewMatcher from '@astro-community/astro-embed-link-preview/matcher'; const matchers = [ + [blueskyMatcher, 'BlueskyPost'], [twitterMatcher, 'Tweet'], [vimeoMatcher, 'Vimeo'], [youtubeMatcher, 'YouTube'], diff --git a/packages/astro-embed/index.ts b/packages/astro-embed/index.ts index 806f1c0..14e54c7 100644 --- a/packages/astro-embed/index.ts +++ b/packages/astro-embed/index.ts @@ -3,3 +3,4 @@ export { YouTube } from '@astro-community/astro-embed-youtube'; export { Vimeo } from '@astro-community/astro-embed-vimeo'; export { LinkPreview } from '@astro-community/astro-embed-link-preview'; export { BaselineStatus } from '@astro-community/astro-embed-baseline-status'; +export { BlueskyPost } from '@astro-community/astro-embed-bluesky'; diff --git a/packages/astro-embed/package.json b/packages/astro-embed/package.json index e80fba4..b9b2774 100644 --- a/packages/astro-embed/package.json +++ b/packages/astro-embed/package.json @@ -32,6 +32,7 @@ }, "homepage": "https://astro-embed.netlify.app/", "dependencies": { + "@astro-community/astro-embed-bluesky": "^0.0.0", "@astro-community/astro-embed-baseline-status": "^0.1.0", "@astro-community/astro-embed-link-preview": "^0.2.2", "@astro-community/astro-embed-integration": "^0.7.2", @@ -42,4 +43,4 @@ "peerDependencies": { "astro": "^2.0.0 || ^3.0.0-beta || ^4.0.0-beta || ^5.0.0-beta" } -} +} \ No newline at end of file From 55e7e27631c47be9c71948795114efeabe955c2e Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Tue, 5 Nov 2024 19:03:13 +0000 Subject: [PATCH 02/19] Refactor --- demo/src/pages/bluesky.astro | 9 +- demo/src/pages/index.astro | 4 +- demo/src/pages/markdown.mdx | 2 +- .../content/docs/components/link-preview.mdx | 5 +- .../docs/examples/tweet-with-js-dark.mdx | 10 +- docs/tsconfig.json | 2 +- packages/astro-embed-bluesky/package.json | 2 +- .../astro-embed-bluesky/src/external.astro | 109 +++---- .../astro-embed-bluesky/src/image-grid.astro | 83 ------ packages/astro-embed-bluesky/src/index.ts | 2 +- packages/astro-embed-bluesky/src/list.astro | 115 ++------ packages/astro-embed-bluesky/src/matcher.ts | 1 - packages/astro-embed-bluesky/src/media.astro | 19 +- packages/astro-embed-bluesky/src/post.astro | 270 +++++++++--------- packages/astro-embed-bluesky/src/record.astro | 4 +- .../astro-embed-bluesky/src/shared/card.astro | 111 +++++++ .../src/shared/image-grid.astro | 84 ++++++ .../src/shared/media-container.astro | 40 +++ .../astro-embed-bluesky/src/shared/styles.css | 36 +++ .../src/shared/video-thumbnail.astro | 44 +++ .../src/starter-pack.astro | 101 +------ packages/astro-embed-bluesky/src/types.ts | 4 +- packages/astro-embed-bluesky/src/utils.ts | 132 +++++---- packages/astro-embed-bluesky/src/video.astro | 91 +++--- packages/astro-embed/package.json | 2 +- 25 files changed, 695 insertions(+), 587 deletions(-) delete mode 100644 packages/astro-embed-bluesky/src/image-grid.astro create mode 100644 packages/astro-embed-bluesky/src/shared/card.astro create mode 100644 packages/astro-embed-bluesky/src/shared/image-grid.astro create mode 100644 packages/astro-embed-bluesky/src/shared/media-container.astro create mode 100644 packages/astro-embed-bluesky/src/shared/styles.css create mode 100644 packages/astro-embed-bluesky/src/shared/video-thumbnail.astro diff --git a/demo/src/pages/bluesky.astro b/demo/src/pages/bluesky.astro index 0478901..9f7785a 100644 --- a/demo/src/pages/bluesky.astro +++ b/demo/src/pages/bluesky.astro @@ -1,7 +1,8 @@ --- -import { BlueskyPost } from "@astro-community/astro-embed-bluesky"; -import Base from "../layouts/Base.astro"; +import { BlueskyPost } from '@astro-community/astro-embed-bluesky'; +import Base from '../layouts/Base.astro'; --- +

Basic

@@ -9,7 +10,9 @@ import Base from "../layouts/Base.astro";

Images

External link

- +

Video

External media with quote

diff --git a/demo/src/pages/index.astro b/demo/src/pages/index.astro index 9c33185..b76e3ef 100644 --- a/demo/src/pages/index.astro +++ b/demo/src/pages/index.astro @@ -20,7 +20,9 @@ import Base from '../layouts/Base.astro'; >
  • - <BaselineStatus/> component examples + <BaselineStatus/> component examples
  • Other examples

    diff --git a/demo/src/pages/markdown.mdx b/demo/src/pages/markdown.mdx index c33250b..22cdc58 100644 --- a/demo/src/pages/markdown.mdx +++ b/demo/src/pages/markdown.mdx @@ -30,4 +30,4 @@ setup: | ## `` - + diff --git a/docs/src/content/docs/components/link-preview.mdx b/docs/src/content/docs/components/link-preview.mdx index a93aef3..bd0db24 100644 --- a/docs/src/content/docs/components/link-preview.mdx +++ b/docs/src/content/docs/components/link-preview.mdx @@ -46,20 +46,17 @@ The above code produces the following result: - ### Hiding media If a URL's image or video is unwanted, add `hideMedia` as a prop. - ```astro ``` The above code produces the following result: - - + ### Limitations diff --git a/docs/src/content/docs/examples/tweet-with-js-dark.mdx b/docs/src/content/docs/examples/tweet-with-js-dark.mdx index e944f4e..0bfd07e 100644 --- a/docs/src/content/docs/examples/tweet-with-js-dark.mdx +++ b/docs/src/content/docs/examples/tweet-with-js-dark.mdx @@ -13,13 +13,19 @@ Adding Twitter’s widget script will hydrate the HTML markup of ``. ```astro - + ``` The above code produces the following result: - + diff --git a/docs/tsconfig.json b/docs/tsconfig.json index 3fd7ae6..fbc2f5f 100644 --- a/docs/tsconfig.json +++ b/docs/tsconfig.json @@ -1,3 +1,3 @@ { "extends": "astro/tsconfigs/strictest" -} \ No newline at end of file +} diff --git a/packages/astro-embed-bluesky/package.json b/packages/astro-embed-bluesky/package.json index dcad320..5e23808 100644 --- a/packages/astro-embed-bluesky/package.json +++ b/packages/astro-embed-bluesky/package.json @@ -33,4 +33,4 @@ "@atproto/api": "^0.13.14", "ts-pattern": "^5.5.0" } -} \ No newline at end of file +} diff --git a/packages/astro-embed-bluesky/src/external.astro b/packages/astro-embed-bluesky/src/external.astro index 70b6efe..64216ec 100644 --- a/packages/astro-embed-bluesky/src/external.astro +++ b/packages/astro-embed-bluesky/src/external.astro @@ -1,69 +1,80 @@ --- -import { AppBskyEmbedExternal } from "@atproto/api"; +import { AppBskyEmbedExternal } from '@atproto/api'; +import './shared/styles.css'; interface Props { - embed: AppBskyEmbedExternal.View; - compact?: boolean; + embed: AppBskyEmbedExternal.View; + compact?: boolean; } const { uri, thumb, title, description } = Astro.props.embed.external; - const domain = new URL(uri).hostname; - const { compact } = Astro.props; --- - {compact ? null : {title}} -
    -

    {domain}

    - {compact ? null : (

    {title}

    -

    {description}

    )} -
    + {!compact && {title}} +
    +

    {domain}

    + { + !compact && ( + <> +

    {title}

    +

    {description}

    + + ) + } +
    diff --git a/packages/astro-embed-bluesky/src/image-grid.astro b/packages/astro-embed-bluesky/src/image-grid.astro deleted file mode 100644 index 6077d57..0000000 --- a/packages/astro-embed-bluesky/src/image-grid.astro +++ /dev/null @@ -1,83 +0,0 @@ ---- -import { AppBskyEmbedImages } from "@atproto/api"; - -type Props = { - embed: AppBskyEmbedImages.View; -}; - -const { images } = Astro.props.embed; ---- - - -
    -
    - { - images.map((image) => ( -
    - {image.alt -
    - )) - } -
    -
    - - diff --git a/packages/astro-embed-bluesky/src/index.ts b/packages/astro-embed-bluesky/src/index.ts index 1b23c9f..d7c284a 100644 --- a/packages/astro-embed-bluesky/src/index.ts +++ b/packages/astro-embed-bluesky/src/index.ts @@ -1 +1 @@ -export { default as BlueskyPost } from "./post.astro"; +export { default as BlueskyPost } from './post.astro'; diff --git a/packages/astro-embed-bluesky/src/list.astro b/packages/astro-embed-bluesky/src/list.astro index 7e0c471..1dc8bd1 100644 --- a/packages/astro-embed-bluesky/src/list.astro +++ b/packages/astro-embed-bluesky/src/list.astro @@ -1,108 +1,29 @@ --- -import { AppBskyGraphDefs } from "@atproto/api"; -import { atUriToListUri } from "./utils"; +import { AppBskyGraphDefs } from '@atproto/api'; +import { atUriToListUri } from './utils'; +import Card from './shared/card.astro'; interface Props { - record: AppBskyGraphDefs.ListView; + record: AppBskyGraphDefs.ListView; } const { record } = Astro.props; - const list = AppBskyGraphDefs.isListView(record) ? record : null; - const purposes: Record = { - "app.bsky.graph.defs#curatelist": "User list", - "app.bsky.graph.defs#modlist": "Moderation list", - "app.bsky.graph.defs#referencelist": "List", + 'app.bsky.graph.defs#curatelist': 'User list', + 'app.bsky.graph.defs#modlist': 'Moderation list', + 'app.bsky.graph.defs#referencelist': 'List', }; - -const purpose = (list && purposes[list.purpose]) ?? "List"; +const purpose = (list && purposes[list.purpose]) ?? 'List'; --- - -
    -
    -
    - {record.creator.displayName} -
    -
    -

    {list?.name}

    -

    {purpose} by {record.creator.displayName}

    -
    -
    -

    {list?.description}

    -
    -
    - - + diff --git a/packages/astro-embed-bluesky/src/matcher.ts b/packages/astro-embed-bluesky/src/matcher.ts index 41eb285..1ce236b 100644 --- a/packages/astro-embed-bluesky/src/matcher.ts +++ b/packages/astro-embed-bluesky/src/matcher.ts @@ -4,6 +4,5 @@ const urlPattern = /^https:\/\/bsky\.app\/profile\/[^/]+\/post\/[^/]+$/; */ export default function matcher(url: string): string | undefined { const match = url.match(urlPattern); - console.log('match', match); return match ? url : undefined; } diff --git a/packages/astro-embed-bluesky/src/media.astro b/packages/astro-embed-bluesky/src/media.astro index 48ec58d..3483a51 100644 --- a/packages/astro-embed-bluesky/src/media.astro +++ b/packages/astro-embed-bluesky/src/media.astro @@ -1,6 +1,6 @@ --- import type { Post } from "./types"; -import { match, P } from "ts-pattern"; +import { match } from "ts-pattern"; import { AppBskyEmbedExternal, AppBskyEmbedImages, @@ -9,16 +9,16 @@ import { AppBskyEmbedVideo, } from "@atproto/api"; import External from "./external.astro"; -import ImageGrid from "./image-grid.astro"; +import ImageGrid from "./shared/image-grid.astro"; +import VideoThumbnail from "./shared/video-thumbnail.astro"; import RecordWithMedia from "./record-with-media.astro"; -import Video from "./video.astro"; import Record from "./record.astro"; -type Props = { +interface Props { media: Post["embed"]; postUrl: string; - compact?: boolean; -}; + compact?: boolean | undefined; +} const { media, postUrl, compact } = Astro.props; --- @@ -30,7 +30,7 @@ const { media, postUrl, compact } = Astro.props; )) .when(AppBskyEmbedImages.isView, (media) => ( - + )) .when(AppBskyEmbedRecordWithMedia.isView, (media) => ( @@ -41,7 +41,10 @@ const { media, postUrl, compact } = Astro.props; )) .when(AppBskyEmbedVideo.isView, (media) => ( - )) .otherwise((media) => media?.$type) diff --git a/packages/astro-embed-bluesky/src/post.astro b/packages/astro-embed-bluesky/src/post.astro index fcc4847..a1c2a40 100644 --- a/packages/astro-embed-bluesky/src/post.astro +++ b/packages/astro-embed-bluesky/src/post.astro @@ -1,10 +1,10 @@ --- -import { renderPostAsHtml, resolvePost } from "./utils.js"; -import Media from "./media.astro"; +import { renderPostAsHtml, resolvePost } from './utils.js'; +import Media from './media.astro'; type Props = { id: string; - post?: string; + post?: string; }; const postUrl = Astro.props.id ?? Astro.props.post; @@ -12,7 +12,7 @@ const postUrl = Astro.props.id ?? Astro.props.post; const post = await resolvePost(postUrl)!; if (!post) { - return new Response(""); + return new Response(''); } const { record, embed, author } = post; @@ -21,142 +21,142 @@ const authorUrl = `https://bsky.app/profile/${author?.handle}`; const body = renderPostAsHtml(post); -const formatter = new Intl.DateTimeFormat("en-US", { - dateStyle: "long", - timeStyle: "short", +const formatter = new Intl.DateTimeFormat('en-US', { + dateStyle: 'long', + timeStyle: 'short', }); ---
    -
    -
    - - {author?.displayName} - - - - - -
    - -

    - - {embed && } - {formatter.format(new Date(record.createdAt ?? ""))} -

    +
    +
    + + {author?.displayName} + + + + + +
    + +

    + + {embed && } + {formatter.format(new Date(record.createdAt ?? ''))} +

    diff --git a/packages/astro-embed-bluesky/src/record.astro b/packages/astro-embed-bluesky/src/record.astro index ea9d113..260b313 100644 --- a/packages/astro-embed-bluesky/src/record.astro +++ b/packages/astro-embed-bluesky/src/record.astro @@ -1,6 +1,6 @@ --- import { AppBskyEmbedRecord, AppBskyGraphDefs } from "@atproto/api"; -import { match, P } from "ts-pattern"; +import { match } from "ts-pattern"; import ViewRecord from "./view-record.astro"; import StarterPack from "./starter-pack.astro"; import List from "./list.astro"; @@ -21,5 +21,5 @@ interface Props { )) .when(AppBskyGraphDefs.isListView, (record) => ) - .otherwise((record) => <>) + .otherwise(() => null) } diff --git a/packages/astro-embed-bluesky/src/shared/card.astro b/packages/astro-embed-bluesky/src/shared/card.astro new file mode 100644 index 0000000..1191d2e --- /dev/null +++ b/packages/astro-embed-bluesky/src/shared/card.astro @@ -0,0 +1,111 @@ +--- +import './styles.css'; + +interface Props { + href: string; + image?: + | { + src: string; + alt?: string; + } + | undefined; + avatar: { + src: string | undefined; + alt: string | undefined; + }; + title: string; + subtitle: string; + description?: string | undefined; +} + +const { href, image, avatar, title, subtitle, description } = Astro.props; +--- + + + {image && {image.alt} +
    +
    +
    + {avatar.alt} +
    +
    +

    {title}

    +

    {subtitle}

    +
    +
    + {description &&

    {description}

    } +
    +
    + + diff --git a/packages/astro-embed-bluesky/src/shared/image-grid.astro b/packages/astro-embed-bluesky/src/shared/image-grid.astro new file mode 100644 index 0000000..ea7043a --- /dev/null +++ b/packages/astro-embed-bluesky/src/shared/image-grid.astro @@ -0,0 +1,84 @@ +--- +import "./styles.css"; + +interface Props { + images: Array<{ + thumb: string; + alt: string | undefined; + }>; +} + +const { images } = Astro.props; +--- + +
    +
    + {images.map((image) => ( +
    + {image.alt +
    + ))} +
    +
    + + diff --git a/packages/astro-embed-bluesky/src/shared/media-container.astro b/packages/astro-embed-bluesky/src/shared/media-container.astro new file mode 100644 index 0000000..9e70de7 --- /dev/null +++ b/packages/astro-embed-bluesky/src/shared/media-container.astro @@ -0,0 +1,40 @@ +--- +import "./styles.css"; + +interface Props { + aspectRatio?: { width: number; height: number } | undefined; + onClick?: (() => void) | undefined; + className?: string | undefined; +} + +const { aspectRatio, onClick, className = '' } = Astro.props; +--- + +
    + +
    + + diff --git a/packages/astro-embed-bluesky/src/shared/styles.css b/packages/astro-embed-bluesky/src/shared/styles.css new file mode 100644 index 0000000..8a811ae --- /dev/null +++ b/packages/astro-embed-bluesky/src/shared/styles.css @@ -0,0 +1,36 @@ +/* styles.css */ +:root { + /* Colors */ + --color-text: #000; + --color-text-light: #666; + --color-border: #e5e5e5; + --color-background: #fff; + --color-background-hover: #fafafa; + --color-brand: rgb(10, 122, 255); + + /* Typography */ + --font-size-sm: 0.875rem; + --line-height-title: 21px; + --line-height-subtitle: 18px; + + /* Spacing */ + --space-xs: 0.25rem; + --space-sm: 0.5rem; + --space-md: 0.75rem; + --space-lg: 1rem; + + /* Layout */ + --radius-sm: 0.25rem; + --radius-md: 0.5rem; + --radius-full: 9999px; + + /* Avatar Sizes */ + --avatar-sm: 1rem; + --avatar-md: 2.5rem; + --avatar-lg: 40px; + + /* Content */ + --content-padding: 0.75rem 1rem; + --card-gap: 0.5rem; + --aspect-ratio-thumb: 1.91/1; +} diff --git a/packages/astro-embed-bluesky/src/shared/video-thumbnail.astro b/packages/astro-embed-bluesky/src/shared/video-thumbnail.astro new file mode 100644 index 0000000..ddd6aff --- /dev/null +++ b/packages/astro-embed-bluesky/src/shared/video-thumbnail.astro @@ -0,0 +1,44 @@ +--- +import MediaContainer from "./media-container.astro"; +import "./styles.css"; + +interface Props { + thumbnail?: string | undefined; + aspectRatio?: { width: number; height: number } | undefined; +} + +const { thumbnail, aspectRatio } = Astro.props; +--- + + + {thumbnail && Video thumbnail} +
    + Play button +
    +
    + + diff --git a/packages/astro-embed-bluesky/src/starter-pack.astro b/packages/astro-embed-bluesky/src/starter-pack.astro index 90ea597..338d794 100644 --- a/packages/astro-embed-bluesky/src/starter-pack.astro +++ b/packages/astro-embed-bluesky/src/starter-pack.astro @@ -1,6 +1,7 @@ --- import { AppBskyGraphDefs, AppBskyGraphStarterpack } from "@atproto/api"; import { atUriToStarterPackUri, starterPackOgImage } from "./utils"; +import Card from "./shared/card.astro"; interface Props { record: AppBskyGraphDefs.StarterPackViewBasic; @@ -13,91 +14,17 @@ const pack = AppBskyGraphStarterpack.isRecord(record.record) : null; --- - - -
    -
    -
    - {record.creator.displayName} -
    -
    -

    {pack?.name}

    -

    Starter pack by {record.creator.displayName}

    -
    -
    -

    {pack?.description}

    -
    -
    - - + image={{ + src: starterPackOgImage(record.uri), + alt: pack?.name || "Starter pack cover image" + }} + avatar={{ + src: record.creator.avatar, + alt: record.creator.displayName + }} + title={pack?.name || ""} + subtitle={`Starter pack by ${record.creator.displayName}`} + description={pack?.description} +/> diff --git a/packages/astro-embed-bluesky/src/types.ts b/packages/astro-embed-bluesky/src/types.ts index d9783f7..1fa5abf 100644 --- a/packages/astro-embed-bluesky/src/types.ts +++ b/packages/astro-embed-bluesky/src/types.ts @@ -1,5 +1,5 @@ -import { AppBskyFeedPost, AppBskyFeedDefs } from "@atproto/api"; +import { AppBskyFeedPost, AppBskyFeedDefs } from '@atproto/api'; export interface Post extends AppBskyFeedDefs.PostView { - record: AppBskyFeedPost.Record; + record: AppBskyFeedPost.Record; } diff --git a/packages/astro-embed-bluesky/src/utils.ts b/packages/astro-embed-bluesky/src/utils.ts index 17d412a..672887a 100644 --- a/packages/astro-embed-bluesky/src/utils.ts +++ b/packages/astro-embed-bluesky/src/utils.ts @@ -1,95 +1,101 @@ -import { AtpAgent, RichText } from "@atproto/api"; -import type { PostView } from "@atproto/api/dist/client/types/app/bsky/feed/defs"; -import type { Post } from "./types"; +import { AtpAgent, RichText } from '@atproto/api'; +import type { PostView } from '@atproto/api/dist/client/types/app/bsky/feed/defs'; +import type { Post } from './types'; const escapeMap: Record = { - "&": "&", - "<": "<", - ">": ">", - '"': """, - "'": "'", + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', }; export const escapeHTML = (str?: string) => - str?.replace(/[&<>"']/g, (match) => escapeMap[match] || match) ?? ""; + str?.replace(/[&<>"']/g, (match) => escapeMap[match] || match) ?? ''; export function renderPostAsHtml(post?: PostView | Post) { - if (!post) { - return ""; - } - const rt = new RichText(post.record as any); - let html = ""; - for (const segment of rt.segments()) { - if (segment.isLink()) { - html += `${escapeHTML(segment.text)}`; - } else if (segment.isMention()) { - html += `${escapeHTML(segment.text)}`; - } else if (segment.isTag()) { - html += `#${escapeHTML(segment.tag?.tag)}`; - } else { - html += escapeHTML(segment.text); - } - } - return html; + if (!post) { + return ''; + } + const rt = new RichText(post.record as any); + let html = ''; + for (const segment of rt.segments()) { + if (segment.isLink()) { + html += `${escapeHTML( + segment.text + )}`; + } else if (segment.isMention()) { + html += `${escapeHTML(segment.text)}`; + } else if (segment.isTag()) { + html += `#${escapeHTML(segment.tag?.tag)}`; + } else { + html += escapeHTML(segment.text); + } + } + return html; } const agent = new AtpAgent({ - service: "https://public.api.bsky.app", + service: 'https://public.api.bsky.app', }); export async function resolvePost( - postUrl: string | Post | PostView, + postUrl: string | Post | PostView ): Promise { - let atUri; + let atUri; - if (typeof postUrl === "object") { - return postUrl as Post; - } + if (typeof postUrl === 'object') { + return postUrl as Post; + } - if (postUrl.startsWith("at:")) { - atUri = postUrl; - } else { - if (!postUrl.startsWith("https://bsky.app/")) { - return undefined; - } - const urlParts = new URL(postUrl).pathname.split("/"); - let did = urlParts[2]!; - const postId = urlParts[4]!; - if (!did || !postId) { - return undefined; - } - if (!did.startsWith("did:")) { - const handleResolution = await agent.resolveHandle({ handle: did }); - if (!handleResolution.data.did) { - return undefined; - } - did = handleResolution.data.did; - } + if (postUrl.startsWith('at:')) { + atUri = postUrl; + } else { + if (!postUrl.startsWith('https://bsky.app/')) { + return undefined; + } + const urlParts = new URL(postUrl).pathname.split('/'); + let did = urlParts[2]!; + const postId = urlParts[4]!; + if (!did || !postId) { + return undefined; + } + if (!did.startsWith('did:')) { + const handleResolution = await agent.resolveHandle({ handle: did }); + if (!handleResolution.data.did) { + return undefined; + } + did = handleResolution.data.did; + } - atUri = `at://${did}/app.bsky.feed.post/${postId}`; - } + atUri = `at://${did}/app.bsky.feed.post/${postId}`; + } - const hydratedPost = await agent.getPosts({ uris: [atUri] }); + const hydratedPost = await agent.getPosts({ uris: [atUri] }); - return hydratedPost.data.posts[0] as unknown as Post; + return hydratedPost.data.posts[0] as unknown as Post; } export function atUriToPostUri(atUri: string) { - const [, , did, , postId] = atUri.split("/"); - return `https://bsky.app/profile/${did}/post/${postId}`; + const [, , did, , postId] = atUri.split('/'); + return `https://bsky.app/profile/${did}/post/${postId}`; } export function atUriToStarterPackUri(atUri: string) { - const [, , did, , packId] = atUri.split("/"); - return `https://bsky.app/starter-pack/${did}/${packId}`; + const [, , did, , packId] = atUri.split('/'); + return `https://bsky.app/starter-pack/${did}/${packId}`; } export function atUriToListUri(atUri: string) { - const [, , did, , listId] = atUri.split("/"); - return `https://bsky.app/profile/${did}/lists/${listId}`; + const [, , did, , listId] = atUri.split('/'); + return `https://bsky.app/profile/${did}/lists/${listId}`; } export function starterPackOgImage(uri: string) { - const [, , did, , packId] = uri.split("/"); - return `https://ogcard.cdn.bsky.app/start/${did}/${packId}`; + const [, , did, , packId] = uri.split('/'); + return `https://ogcard.cdn.bsky.app/start/${did}/${packId}`; } diff --git a/packages/astro-embed-bluesky/src/video.astro b/packages/astro-embed-bluesky/src/video.astro index 910aa5f..64cf0df 100644 --- a/packages/astro-embed-bluesky/src/video.astro +++ b/packages/astro-embed-bluesky/src/video.astro @@ -1,62 +1,63 @@ --- -import type { AppBskyEmbedVideo } from "@atproto/api"; +import type { AppBskyEmbedVideo } from '@atproto/api'; +import './shared/styles.css'; interface Props { - embed: AppBskyEmbedVideo.View; + embed: AppBskyEmbedVideo.View; } const { thumbnail, aspectRatio } = Astro.props.embed; ---
    - Video -
    - Play button -
    + Video +
    + Play button +
    diff --git a/packages/astro-embed/package.json b/packages/astro-embed/package.json index b9b2774..e4f25b3 100644 --- a/packages/astro-embed/package.json +++ b/packages/astro-embed/package.json @@ -43,4 +43,4 @@ "peerDependencies": { "astro": "^2.0.0 || ^3.0.0-beta || ^4.0.0-beta || ^5.0.0-beta" } -} \ No newline at end of file +} From 395406bcf002d3f6f6e8870a0be82ab5ab779bbe Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Tue, 5 Nov 2024 19:30:37 +0000 Subject: [PATCH 03/19] Re-org --- packages/astro-embed-bluesky/src/{shared => }/card.astro | 0 packages/astro-embed-bluesky/src/external.astro | 4 ++-- .../astro-embed-bluesky/src/{shared => }/image-grid.astro | 0 packages/astro-embed-bluesky/src/list.astro | 2 +- .../src/{shared => }/media-container.astro | 0 packages/astro-embed-bluesky/src/media.astro | 4 ++-- packages/astro-embed-bluesky/src/record-with-media.astro | 2 +- packages/astro-embed-bluesky/src/record.astro | 2 +- packages/astro-embed-bluesky/src/starter-pack.astro | 2 +- packages/astro-embed-bluesky/src/{shared => }/styles.css | 0 .../src/{shared => }/video-thumbnail.astro | 0 11 files changed, 8 insertions(+), 8 deletions(-) rename packages/astro-embed-bluesky/src/{shared => }/card.astro (100%) rename packages/astro-embed-bluesky/src/{shared => }/image-grid.astro (100%) rename packages/astro-embed-bluesky/src/{shared => }/media-container.astro (100%) rename packages/astro-embed-bluesky/src/{shared => }/styles.css (100%) rename packages/astro-embed-bluesky/src/{shared => }/video-thumbnail.astro (100%) diff --git a/packages/astro-embed-bluesky/src/shared/card.astro b/packages/astro-embed-bluesky/src/card.astro similarity index 100% rename from packages/astro-embed-bluesky/src/shared/card.astro rename to packages/astro-embed-bluesky/src/card.astro diff --git a/packages/astro-embed-bluesky/src/external.astro b/packages/astro-embed-bluesky/src/external.astro index 64216ec..8154e04 100644 --- a/packages/astro-embed-bluesky/src/external.astro +++ b/packages/astro-embed-bluesky/src/external.astro @@ -1,10 +1,10 @@ --- import { AppBskyEmbedExternal } from '@atproto/api'; -import './shared/styles.css'; +import './styles.css'; interface Props { embed: AppBskyEmbedExternal.View; - compact?: boolean; + compact?: boolean | undefined; } const { uri, thumb, title, description } = Astro.props.embed.external; diff --git a/packages/astro-embed-bluesky/src/shared/image-grid.astro b/packages/astro-embed-bluesky/src/image-grid.astro similarity index 100% rename from packages/astro-embed-bluesky/src/shared/image-grid.astro rename to packages/astro-embed-bluesky/src/image-grid.astro diff --git a/packages/astro-embed-bluesky/src/list.astro b/packages/astro-embed-bluesky/src/list.astro index 1dc8bd1..5a4ccbe 100644 --- a/packages/astro-embed-bluesky/src/list.astro +++ b/packages/astro-embed-bluesky/src/list.astro @@ -1,7 +1,7 @@ --- import { AppBskyGraphDefs } from '@atproto/api'; import { atUriToListUri } from './utils'; -import Card from './shared/card.astro'; +import Card from './card.astro'; interface Props { record: AppBskyGraphDefs.ListView; diff --git a/packages/astro-embed-bluesky/src/shared/media-container.astro b/packages/astro-embed-bluesky/src/media-container.astro similarity index 100% rename from packages/astro-embed-bluesky/src/shared/media-container.astro rename to packages/astro-embed-bluesky/src/media-container.astro diff --git a/packages/astro-embed-bluesky/src/media.astro b/packages/astro-embed-bluesky/src/media.astro index 3483a51..13eba6b 100644 --- a/packages/astro-embed-bluesky/src/media.astro +++ b/packages/astro-embed-bluesky/src/media.astro @@ -9,8 +9,8 @@ import { AppBskyEmbedVideo, } from "@atproto/api"; import External from "./external.astro"; -import ImageGrid from "./shared/image-grid.astro"; -import VideoThumbnail from "./shared/video-thumbnail.astro"; +import ImageGrid from "./image-grid.astro"; +import VideoThumbnail from "./video-thumbnail.astro"; import RecordWithMedia from "./record-with-media.astro"; import Record from "./record.astro"; diff --git a/packages/astro-embed-bluesky/src/record-with-media.astro b/packages/astro-embed-bluesky/src/record-with-media.astro index c165a27..6bb6f5a 100644 --- a/packages/astro-embed-bluesky/src/record-with-media.astro +++ b/packages/astro-embed-bluesky/src/record-with-media.astro @@ -6,7 +6,7 @@ import Record from "./record.astro"; interface Props { embed: AppBskyEmbedRecordWithMedia.View; postUrl: string; - compact?: boolean; + compact?: boolean | undefined; } const { compact } = Astro.props; diff --git a/packages/astro-embed-bluesky/src/record.astro b/packages/astro-embed-bluesky/src/record.astro index 260b313..3b5f268 100644 --- a/packages/astro-embed-bluesky/src/record.astro +++ b/packages/astro-embed-bluesky/src/record.astro @@ -7,7 +7,7 @@ import List from "./list.astro"; interface Props { embed: AppBskyEmbedRecord.View; - compact?: boolean; + compact?: boolean | undefined; } --- diff --git a/packages/astro-embed-bluesky/src/starter-pack.astro b/packages/astro-embed-bluesky/src/starter-pack.astro index 338d794..437ea87 100644 --- a/packages/astro-embed-bluesky/src/starter-pack.astro +++ b/packages/astro-embed-bluesky/src/starter-pack.astro @@ -1,7 +1,7 @@ --- import { AppBskyGraphDefs, AppBskyGraphStarterpack } from "@atproto/api"; import { atUriToStarterPackUri, starterPackOgImage } from "./utils"; -import Card from "./shared/card.astro"; +import Card from "./card.astro"; interface Props { record: AppBskyGraphDefs.StarterPackViewBasic; diff --git a/packages/astro-embed-bluesky/src/shared/styles.css b/packages/astro-embed-bluesky/src/styles.css similarity index 100% rename from packages/astro-embed-bluesky/src/shared/styles.css rename to packages/astro-embed-bluesky/src/styles.css diff --git a/packages/astro-embed-bluesky/src/shared/video-thumbnail.astro b/packages/astro-embed-bluesky/src/video-thumbnail.astro similarity index 100% rename from packages/astro-embed-bluesky/src/shared/video-thumbnail.astro rename to packages/astro-embed-bluesky/src/video-thumbnail.astro From c7eb05a935fa46e5278a92bc80b11dcae6923fbe Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Tue, 5 Nov 2024 21:30:40 +0000 Subject: [PATCH 04/19] Docs and refactor --- demo/src/pages/bluesky.astro | 4 + demo/src/pages/index.astro | 3 + demo/src/pages/markdown.mdx | 2 +- demo/src/post.json | 77 +++++++++++++++++++ docs/src/content/docs/components/bluesky.mdx | 73 ++++++++++++++++++ packages/astro-embed-bluesky/src/card.astro | 2 +- .../astro-embed-bluesky/src/external.astro | 4 +- packages/astro-embed-bluesky/src/post.astro | 44 ++++++----- packages/astro-embed-bluesky/src/styles.css | 4 +- .../astro-embed-bluesky/src/view-record.astro | 17 ++-- 10 files changed, 200 insertions(+), 30 deletions(-) create mode 100644 demo/src/post.json create mode 100644 docs/src/content/docs/components/bluesky.mdx diff --git a/demo/src/pages/bluesky.astro b/demo/src/pages/bluesky.astro index 9f7785a..5cd8585 100644 --- a/demo/src/pages/bluesky.astro +++ b/demo/src/pages/bluesky.astro @@ -1,9 +1,12 @@ --- import { BlueskyPost } from '@astro-community/astro-embed-bluesky'; import Base from '../layouts/Base.astro'; +import post from '../post.json'; --- + +

    Raw data

    Basic

    @@ -20,4 +23,5 @@ import Base from '../layouts/Base.astro';

    External link with quote

    Starter pack

    + diff --git a/demo/src/pages/index.astro b/demo/src/pages/index.astro index b76e3ef..21d39aa 100644 --- a/demo/src/pages/index.astro +++ b/demo/src/pages/index.astro @@ -24,6 +24,9 @@ import Base from '../layouts/Base.astro'; ><BaselineStatus/> component examples +
  • + <BlueskyPost/> component examples +
  • Other examples