Indieweb draft, creates per-tag RSS feeds.

This commit is contained in:
2026-03-16 11:27:45 -07:00
parent eb067b6a39
commit 8f6de2cced
5 changed files with 88 additions and 39 deletions

View File

@@ -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";

View File

@@ -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

View File

@@ -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 );
---
<RootLayout>
@@ -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 =
<ul class="tags">
{
tags.map(tag =>
<li>
{
tag === selectedTag
? <a href="/blog">
<NotchedBox color="gray" fillNotches={"left"}>
{tag}
</NotchedBox>
</a>
: <a href={`/blog/tag/${tag}`}>
<NotchedBox color="gray" fillNotches={"none"}>
{tag}
</NotchedBox>
</a>
}
</li>
<li class={ tag === selectedTag ? "selected-tag" : "" }>
<a href={`/blog/tag/${tag}`}>
<NotchedBox color={ tag === selectedTag ? "gold" : "gray" } fillNotches={ tag === selectedTag ? "right" : "none"}>
{tag}
</NotchedBox>
</a>
</li>
)
}
</ul>

22
src/lib/blog.ts Normal file
View File

@@ -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<CollectionEntry<"blog">[]> {
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<string[]> {
const posts = await getCollection("blog", publishedOnly);
return posts.flatMap((p) => p.data.tags || []);
}

View File

@@ -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 },
}));
}