From 3548bc57b98f8b4e3bc1fd2b52a88261533918fc Mon Sep 17 00:00:00 2001 From: "Calvin A. Allen" Date: Thu, 8 Jan 2026 16:50:24 -0500 Subject: [PATCH] feat(blog): add BlueSky engagement component and auto-update workflow - Add BlueskyEngagement component to display likes and reposts on posts - Add blueskyPostId field to content schema - Update PostLayout to show engagement between post and comments - Add workflow job to update frontmatter with BlueSky post ID after posting - Add custom Giscus theme to hide redundant comment count - Show comment count in Comments header via Giscus metadata - Disable Giscus reactions (using BlueSky engagement instead) --- .github/workflows/bluesky-new-post.yml | 74 ++++++ public/giscus-theme.css | 5 + src/components/BlueskyEngagement.astro | 243 ++++++++++++++++++ .../blog/2026/introducing-dtvem/index.md | 1 + .../index.md | 1 + .../blog/2026/introducing-vscwhere/index.md | 1 + .../index.md | 2 + src/content/config.ts | 1 + src/layouts/PostLayout.astro | 38 ++- src/pages/[...slug].astro | 1 + 10 files changed, 362 insertions(+), 5 deletions(-) create mode 100644 public/giscus-theme.css create mode 100644 src/components/BlueskyEngagement.astro diff --git a/.github/workflows/bluesky-new-post.yml b/.github/workflows/bluesky-new-post.yml index 37f3aab..5fc3ef0 100644 --- a/.github/workflows/bluesky-new-post.yml +++ b/.github/workflows/bluesky-new-post.yml @@ -59,3 +59,77 @@ jobs: secrets: BLUESKY_USERNAME: ${{ secrets.BLUESKY_USERNAME }} BLUESKY_APP_PASSWORD: ${{ secrets.BLUESKY_APP_PASSWORD }} + + update-frontmatter: + needs: [detect, notify] + if: needs.detect.outputs.has_posts == 'true' && needs.notify.outputs.post_id != '' + runs-on: ubuntu-latest + steps: + - name: Checkout blog repo + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Find and update blog post + run: | + POST_URL="${{ needs.detect.outputs.post_url }}" + POST_ID="${{ needs.notify.outputs.post_id }}" + + echo "Post URL: $POST_URL" + echo "Post ID: $POST_ID" + + # Extract slug from URL (e.g., https://www.codingwithcalvin.net/my-post/ -> my-post) + SLUG=$(echo "$POST_URL" | sed -E 's|https?://[^/]+/([^/]+)/?|\1|') + echo "Extracted slug: $SLUG" + + # Find the markdown file (could be in any year directory) + FILE=$(find src/content/blog -path "*/${SLUG}/index.md" | head -1) + + if [ -z "$FILE" ]; then + echo "Could not find file for slug: $SLUG" + exit 1 + fi + + echo "Found file: $FILE" + + # Check if blueskyPostId already exists + if grep -q "^blueskyPostId:" "$FILE"; then + echo "blueskyPostId already exists, skipping" + exit 0 + fi + + # Add blueskyPostId before the closing --- of frontmatter + # Using awk for more reliable YAML manipulation + awk -v post_id="$POST_ID" ' + BEGIN { in_frontmatter = 0; frontmatter_end = 0 } + /^---$/ { + if (in_frontmatter == 0) { + in_frontmatter = 1 + print + next + } else { + print "blueskyPostId: \"" post_id "\"" + in_frontmatter = 0 + frontmatter_end = 1 + } + } + { print } + ' "$FILE" > "$FILE.tmp" && mv "$FILE.tmp" "$FILE" + + echo "Updated $FILE with blueskyPostId: $POST_ID" + echo "New frontmatter:" + head -20 "$FILE" + + - name: Commit and push + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + git add -A + if git diff --staged --quiet; then + echo "No changes to commit" + exit 0 + fi + + git commit -m "chore(blog): add blueskyPostId to post frontmatter" + git push diff --git a/public/giscus-theme.css b/public/giscus-theme.css new file mode 100644 index 0000000..84a5dd3 --- /dev/null +++ b/public/giscus-theme.css @@ -0,0 +1,5 @@ +@import url('https://giscus.app/themes/dark.css'); + +.gsc-comments-count { + display: none; +} diff --git a/src/components/BlueskyEngagement.astro b/src/components/BlueskyEngagement.astro new file mode 100644 index 0000000..c12e00e --- /dev/null +++ b/src/components/BlueskyEngagement.astro @@ -0,0 +1,243 @@ +--- +interface Props { + postId: string; +} + +const { postId } = Astro.props; +const handle = "codingwithcalvin.net"; +--- + +
+
+
+ + + + Engagement on Bluesky +
+ + Join the conversation + + + + +
+ + + + + + +
+ + diff --git a/src/content/blog/2026/introducing-dtvem/index.md b/src/content/blog/2026/introducing-dtvem/index.md index f543bdd..9edb841 100644 --- a/src/content/blog/2026/introducing-dtvem/index.md +++ b/src/content/blog/2026/introducing-dtvem/index.md @@ -3,6 +3,7 @@ title: "Introducing the Developer Tools Virtual Environment Manager!" date: "2026-01-05T12:00:00-05:00" categories: [golang, cli, python, node, ruby] description: "A unified, cross-platform runtime version manager that actually works on Windows" +blueskyPostId: 3mbpawpkn4z26 --- If you've ever tried to manage multiple versions of Python, Node.js, or Ruby on Windows, you know the pain. Tools like `nvm`, `pyenv`, and `rbenv` work great on macOS and Linux, but Windows support ranges from "hacky workarounds" to "just use WSL." And even on Unix systems, you're juggling three different tools with three different configurations. diff --git a/src/content/blog/2026/introducing-the-visual-studio-toolbox/index.md b/src/content/blog/2026/introducing-the-visual-studio-toolbox/index.md index 75dd800..078674a 100644 --- a/src/content/blog/2026/introducing-the-visual-studio-toolbox/index.md +++ b/src/content/blog/2026/introducing-the-visual-studio-toolbox/index.md @@ -3,6 +3,7 @@ title: "Introducing the Visual Studio Toolbox!" date: "2026-01-02T12:00:00-05:00" categories: [dotnet, csharp, visualstudio, winui] description: "Mission Control for your Visual Studio Installations, inspired by JetBrains Toolbox" +blueskyPostId: 3mbhgd6et4b26 --- If you've ever used JetBrains Toolbox, you know how convenient it is. It's the central hub for all your JetBrains IDEs — install new tools, manage updates, launch any version with a single click. Everything in one place, tucked away in the system tray. diff --git a/src/content/blog/2026/introducing-vscwhere/index.md b/src/content/blog/2026/introducing-vscwhere/index.md index 0380be9..cfd1c91 100644 --- a/src/content/blog/2026/introducing-vscwhere/index.md +++ b/src/content/blog/2026/introducing-vscwhere/index.md @@ -3,6 +3,7 @@ title: "Introducing vscwhere!" date: "2026-01-08T12:00:00-05:00" categories: [rust, cli, vscode] description: "A CLI tool for locating Visual Studio Code installations on Windows, inspired by Microsoft's vswhere" +blueskyPostId: 3mbwhet7cbl24 --- If you've ever used Microsoft's [vswhere](https://github.com/microsoft/vswhere), you know how handy it is. Need to find where Visual Studio is installed? Run `vswhere`. Need the path for a CI/CD script? `vswhere -latest -property installationPath`. It just works. diff --git a/src/content/blog/2026/sdk-style-projects-for-your-visual-studio-extensions/index.md b/src/content/blog/2026/sdk-style-projects-for-your-visual-studio-extensions/index.md index 6b2923f..e2eb74d 100644 --- a/src/content/blog/2026/sdk-style-projects-for-your-visual-studio-extensions/index.md +++ b/src/content/blog/2026/sdk-style-projects-for-your-visual-studio-extensions/index.md @@ -3,8 +3,10 @@ title: "SDK-style Projects for your Visual Studio Extensions!" date: "2026-01-01T12:00:00-05:00" categories: [dotnet, csharp, vsix] description: "Remember that MSBuild SDK post from last week? Well, I actually built something with it - an SDK that brings modern project files to Visual Studio extension development." +blueskyPostId: 3mbezw2qfgt2m --- + Remember [that post I wrote last week](https://www.codingwithcalvin.net/creating-your-own-msbuild-sdk-it-s-easier-than-you-think) about creating MSBuild SDKs? Well, I wasn't just writing that for fun - I was actually building something with all that knowledge. I've released [CodingWithCalvin.VsixSdk](https://www.nuget.org/packages/CodingWithCalvin.VsixSdk/), an MSBuild SDK that brings modern SDK-style `.csproj` files to Visual Studio extension development. No more XML soup! diff --git a/src/content/config.ts b/src/content/config.ts index dbe710a..ab7a634 100644 --- a/src/content/config.ts +++ b/src/content/config.ts @@ -10,6 +10,7 @@ const blog = defineCollection({ description: z.string().optional(), image: image().optional(), youtube: z.string().optional(), + blueskyPostId: z.string().optional(), }), }); diff --git a/src/layouts/PostLayout.astro b/src/layouts/PostLayout.astro index 0c2982a..0cac061 100644 --- a/src/layouts/PostLayout.astro +++ b/src/layouts/PostLayout.astro @@ -4,6 +4,7 @@ import type { ImageMetadata } from 'astro'; import BaseLayout from './BaseLayout.astro'; import CategoryBadges from '../components/CategoryBadges.astro'; import YouTubeEmbed from '../components/YouTubeEmbed.astro'; +import BlueskyEngagement from '../components/BlueskyEngagement.astro'; interface Props { title: string; @@ -13,9 +14,10 @@ interface Props { description?: string; image?: ImageMetadata; youtube?: string; + blueskyPostId?: string; } -const { title, slug, date, categories, description, image, youtube } = Astro.props; +const { title, slug, date, categories, description, image, youtube, blueskyPostId } = Astro.props; const formattedDate = date.toLocaleDateString('en-US', { year: 'numeric', @@ -69,8 +71,15 @@ const ogImageUrl = image?.src;
+ {blueskyPostId && ( + <> + +
+ + )} +
-

Comments

+

Comments

+
diff --git a/src/pages/[...slug].astro b/src/pages/[...slug].astro index 329426b..9e8ffdc 100644 --- a/src/pages/[...slug].astro +++ b/src/pages/[...slug].astro @@ -25,6 +25,7 @@ const { Content } = await render(post); description={post.data.description} image={post.resolvedImage} youtube={post.data.youtube} + blueskyPostId={post.data.blueskyPostId} >