Compare commits
2 Commits
eb067b6a39
...
0e4c438869
| Author | SHA1 | Date | |
|---|---|---|---|
| 0e4c438869 | |||
| 8f6de2cced |
@@ -13,6 +13,13 @@ interface Props {
|
|||||||
|
|
||||||
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
|
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
|
||||||
|
|
||||||
|
const blogTagMatch = Astro.url.pathname.match(/blog\/tag\/(\w+)/);
|
||||||
|
|
||||||
|
const rssUrl =
|
||||||
|
blogTagMatch
|
||||||
|
? new URL(`blog/tag/${blogTagMatch[1]}.rss.xml`, Astro.site)
|
||||||
|
: new URL(`rss.xml`, Astro.site);
|
||||||
|
|
||||||
const { title, description, image } = Astro.props;
|
const { title, description, image } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -25,7 +32,7 @@ const { title, description, image } = Astro.props;
|
|||||||
rel="alternate"
|
rel="alternate"
|
||||||
type="application/rss+xml"
|
type="application/rss+xml"
|
||||||
title={SITE_TITLE}
|
title={SITE_TITLE}
|
||||||
href={new URL('rss.xml', Astro.site)}
|
href={rssUrl}
|
||||||
/>
|
/>
|
||||||
<meta name="generator" content={Astro.generator} />
|
<meta name="generator" content={Astro.generator} />
|
||||||
|
|
||||||
|
|||||||
@@ -2,4 +2,4 @@
|
|||||||
// You can import this data from anywhere in your site by using the `import` keyword.
|
// You can import this data from anywhere in your site by using the `import` keyword.
|
||||||
|
|
||||||
export const SITE_TITLE = "Blazestar.net";
|
export const SITE_TITLE = "Blazestar.net";
|
||||||
export const SITE_DESCRIPTION = "Welcome to Blazestar.net";
|
export const SITE_DESCRIPTION = "Periodic musings about systems";
|
||||||
|
|||||||
@@ -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.
|
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?
|
So how do we go about joining the Indie Web?
|
||||||
|
|
||||||
## Step 1: Have a website
|
## Step 1: Have a website
|
||||||
|
|||||||
@@ -1,31 +1,20 @@
|
|||||||
---
|
---
|
||||||
import NotchedBox from '../components/NotchedBox.astro';
|
import NotchedBox from '../components/NotchedBox.astro';
|
||||||
import RootLayout from '../layouts/RootLayout.astro';
|
import RootLayout from '../layouts/RootLayout.astro';
|
||||||
import { getCollection, type CollectionEntry } from 'astro:content';
|
|
||||||
import FormattedDate from '../components/FormattedDate.astro';
|
import FormattedDate from '../components/FormattedDate.astro';
|
||||||
import { Image } from 'astro:assets';
|
import { Image } from 'astro:assets';
|
||||||
|
import { getPublishedPosts, getAllTags } from '../lib/blog';
|
||||||
|
|
||||||
interface Params {
|
export interface Params {
|
||||||
selectedTag?: string | undefined;
|
selectedTag?: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { selectedTag } = Astro.props;
|
const { selectedTag } = Astro.props;
|
||||||
|
|
||||||
function publishedOnly(p: CollectionEntry<'blog'>): p is (CollectionEntry<'blog'> & { data: { pubDate: Date }}) {
|
const tags = await getAllTags();
|
||||||
return p.data.pubDate !== undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const allPosts = (await getCollection('blog', publishedOnly))
|
const posts = await getPublishedPosts( selectedTag );
|
||||||
.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;
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<RootLayout>
|
<RootLayout>
|
||||||
@@ -37,14 +26,16 @@ const posts =
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 8px;
|
gap: 16px;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
|
|
||||||
li {
|
li a {
|
||||||
padding: 1px 16px
|
color: var(--color-gray);
|
||||||
.container {
|
text-decoration: none;
|
||||||
background-color: var(--color-gray);
|
}
|
||||||
}
|
|
||||||
|
li.selected-tag a {
|
||||||
|
color: var(--color-gold);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,22 +80,13 @@ const posts =
|
|||||||
<ul class="tags">
|
<ul class="tags">
|
||||||
{
|
{
|
||||||
tags.map(tag =>
|
tags.map(tag =>
|
||||||
<li>
|
<li class={ tag === selectedTag ? "selected-tag" : "" }>
|
||||||
{
|
<a href={`/blog/tag/${tag}`}>
|
||||||
tag === selectedTag
|
<NotchedBox color={ tag === selectedTag ? "gold" : "gray" } fillNotches={ tag === selectedTag ? "right" : "none"}>
|
||||||
? <a href="/blog">
|
{tag}
|
||||||
<NotchedBox color="gray" fillNotches={"left"}>
|
</NotchedBox>
|
||||||
{tag}
|
</a>
|
||||||
|
</li>
|
||||||
</NotchedBox>
|
|
||||||
</a>
|
|
||||||
: <a href={`/blog/tag/${tag}`}>
|
|
||||||
<NotchedBox color="gray" fillNotches={"none"}>
|
|
||||||
{tag}
|
|
||||||
</NotchedBox>
|
|
||||||
</a>
|
|
||||||
}
|
|
||||||
</li>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
22
src/lib/blog.ts
Normal file
22
src/lib/blog.ts
Normal 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 || []);
|
||||||
|
}
|
||||||
37
src/pages/blog/tag/[...tag].rss.xml.ts
Normal file
37
src/pages/blog/tag/[...tag].rss.xml.ts
Normal 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 },
|
||||||
|
}));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user