From 8f6de2cced945b9fab1bf7b4c3567f063a6fd7d0 Mon Sep 17 00:00:00 2001 From: Drew Haven Date: Mon, 16 Mar 2026 11:27:45 -0700 Subject: [PATCH] Indieweb draft, creates per-tag RSS feeds. --- src/consts.ts | 2 +- .../blog/drafts/joining-the-indie-web.md | 9 +++ src/layouts/BlogList.astro | 57 +++++++------------ src/lib/blog.ts | 22 +++++++ src/pages/blog/tag/[...tag].rss.xml.ts | 37 ++++++++++++ 5 files changed, 88 insertions(+), 39 deletions(-) create mode 100644 src/lib/blog.ts create mode 100644 src/pages/blog/tag/[...tag].rss.xml.ts diff --git a/src/consts.ts b/src/consts.ts index 2cbd0e5..69a20cb 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -2,4 +2,4 @@ // You can import this data from anywhere in your site by using the `import` keyword. export const SITE_TITLE = "Blazestar.net"; -export const SITE_DESCRIPTION = "Welcome to Blazestar.net"; +export const SITE_DESCRIPTION = "Periodic musings about systems"; diff --git a/src/content/blog/drafts/joining-the-indie-web.md b/src/content/blog/drafts/joining-the-indie-web.md index ec103db..ef7ab94 100644 --- a/src/content/blog/drafts/joining-the-indie-web.md +++ b/src/content/blog/drafts/joining-the-indie-web.md @@ -15,6 +15,15 @@ The web has turned into a group of five websites, each consisting of screenshots I'm old enough to have grown up in an age where personal websites were common, though many were hosted through sites like the long-gone [GeoCities](https://en.wikipedia.org/wiki/GeoCities) and [AngelFire](https://en.wikipedia.org/wiki/Angelfire). The web was a lot of little sites where people shared their passion or just a little bit about themselves. Search engines used to be one of the major tools for finding these sites, along with directories and [web rings](https://en.wikipedia.org/wiki/Webring). I spent many a teenage year browsing around sites looking for more information on Magic: the Gathering, Dungeons & Dragons, or learning how to build my own sites and wrangle with that new-fangled CSS thing. +## What is the Indie Web + +The Indie Web seems to have come out of Bruce Sterling's IndieWeb Camp way back in 2013. There he outlined a few major principles, which are [largely unchanged today](https://indieweb.org/principles). + +I think it can largely be summed up by two major themes: + +- Build things for people. You are the first customer so design for yourself and what you love. Don't agonize about making things interoperable for machines. +- Build things that last. You should have control over the data and site. It should only loosely depend on APIs and platform + So how do we go about joining the Indie Web? ## Step 1: Have a website diff --git a/src/layouts/BlogList.astro b/src/layouts/BlogList.astro index aaf087b..4f46b3f 100644 --- a/src/layouts/BlogList.astro +++ b/src/layouts/BlogList.astro @@ -1,31 +1,19 @@ --- import NotchedBox from '../components/NotchedBox.astro'; import RootLayout from '../layouts/RootLayout.astro'; -import { getCollection, type CollectionEntry } from 'astro:content'; import FormattedDate from '../components/FormattedDate.astro'; import { Image } from 'astro:assets'; +import { getPublishedPosts, getAllTags } from '../lib/blog'; -interface Params { +export interface Params { selectedTag?: string | undefined; } const { selectedTag } = Astro.props; -function publishedOnly(p: CollectionEntry<'blog'>): p is (CollectionEntry<'blog'> & { data: { pubDate: Date }}) { - return p.data.pubDate !== undefined; -} +const tags = await getAllTags(); -const allPosts = (await getCollection('blog', publishedOnly)) - .sort( - (a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(), - ); - -const tags = allPosts.flatMap(p => p.data.tags); - -const posts = - selectedTag - ? allPosts.filter(p => p.data.tags.includes(selectedTag)) - : allPosts; +const posts = await getPublishedPosts( selectedTag ); --- @@ -37,14 +25,16 @@ const posts = padding: 0; display: flex; flex-direction: row; - gap: 8px; + gap: 16px; align-items: baseline; - li { - padding: 1px 16px - .container { - background-color: var(--color-gray); - } + li a { + color: var(--color-gray); + text-decoration: none; + } + + li.selected-tag a { + color: var(--color-gold); } } @@ -89,22 +79,13 @@ const posts = diff --git a/src/lib/blog.ts b/src/lib/blog.ts new file mode 100644 index 0000000..e3b96e6 --- /dev/null +++ b/src/lib/blog.ts @@ -0,0 +1,22 @@ +import { type CollectionEntry, getCollection } from "astro:content"; + +function publishedOnly( + p: CollectionEntry<"blog">, +): p is CollectionEntry<"blog"> & { data: { pubDate: Date } } { + return p.data.pubDate !== undefined; +} + +export async function getPublishedPosts( + tag?: string, +): Promise[]> { + const allPosts = (await getCollection("blog", publishedOnly)).sort( + (a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(), + ); + return tag ? allPosts.filter((p) => p.data.tags.includes(tag)) : allPosts; +} + +export async function getAllTags(): Promise { + const posts = await getCollection("blog", publishedOnly); + + return posts.flatMap((p) => p.data.tags || []); +} diff --git a/src/pages/blog/tag/[...tag].rss.xml.ts b/src/pages/blog/tag/[...tag].rss.xml.ts new file mode 100644 index 0000000..f00caf7 --- /dev/null +++ b/src/pages/blog/tag/[...tag].rss.xml.ts @@ -0,0 +1,37 @@ +import { type CollectionEntry, getCollection } from "astro:content"; +import type { AstroUserConfig } from "astro"; +import { SITE_TITLE, SITE_DESCRIPTION } from "../../../consts"; +import rss from "@astrojs/rss"; +import { getPublishedPosts } from "../../../lib/blog"; + +function publishedOnly( + p: CollectionEntry<"blog">, +): p is CollectionEntry<"blog"> & { data: { pubDate: Date } } { + return p.data.pubDate !== undefined; +} + +export async function GET(context: AstroUserConfig) { + const { tag: selectedTag } = context.params; + const posts = await getPublishedPosts(selectedTag); + + return rss({ + title: `${SITE_TITLE} - ${selectedTag}`, + description: SITE_DESCRIPTION, + site: context.site as string, + items: posts.map((post) => ({ + ...post.data, + link: `/blog/${post.id}/`, + })), + }); +} + +export async function getStaticPaths() { + const posts = await getCollection("blog", publishedOnly); + + const tags = posts.flatMap((p) => p.data.tags); + + return tags.map((tag) => ({ + params: { tag: tag }, + props: { tag: tag }, + })); +}