Skip to content

Commit 93d7f6d

Browse files
committed
Blog archive now displays dates
1 parent a8f1fe4 commit 93d7f6d

File tree

9 files changed

+148
-18
lines changed

9 files changed

+148
-18
lines changed

.pnp.cjs

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Binary file not shown.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"@types/katex": "^0.16.7",
2828
"@types/node": "^22.13.10",
2929
"astro": "5.5.2",
30+
"dayjs": "^1.11.13",
3031
"hast-util-from-html": "2.0.3",
3132
"hast-util-to-text": "^4.0.2",
3233
"katex": "^0.16.21",

src/helpers/timezoneless-date.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Filename: timezoneless-date.ts
3+
* Author: simshadows <contact@simshadows.com>
4+
* License: GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later)
5+
*
6+
* A wrapper class to enforce the use of a pre-existing date implementation
7+
* as a simple timezoneless calendar date.
8+
*/
9+
10+
import dayjs from "dayjs";
11+
//import utc from "dayjs/plugin/utc";
12+
13+
//dayjs.extend(utc);
14+
15+
const iso8601DateRegex = /^[0-9]{4}-[01][0-9]-[0-3][0-9]$/;
16+
17+
export class TimezonelessDate {
18+
private __d: dayjs.Dayjs;
19+
20+
/*
21+
* y is the calendar year
22+
* m is an integer 0-11
23+
* d is an integer 1-31
24+
*/
25+
constructor(y: number, m: number, d: number) {
26+
this.__d = dayjs(0).year(y).month(m).date(d);
27+
}
28+
29+
toString(): string {
30+
return this.toISOString();
31+
}
32+
toISOString(): string {
33+
return this.__d.format("YYYY-MM-DD");
34+
}
35+
toDebugString(): string {
36+
return this.__d.toISOString();
37+
}
38+
39+
isAfter(other: TimezonelessDate): boolean {
40+
return this.__d.isAfter(other.__d, "date");
41+
}
42+
43+
static parseISODate(s: string): TimezonelessDate {
44+
if (s.length !== 10) {
45+
throw new Error(`Failed to parse string as a date. Must be exactly 10 characters. Instead got: ${s}`);
46+
}
47+
if (!iso8601DateRegex.test(s)) {
48+
throw new Error(`Failed to parse string as a date. String must be an ISO8601 date. Instead got: ${s}`);
49+
}
50+
const subStrs = s.split("-");
51+
const y = subStrs[0];
52+
const m = subStrs[1];
53+
const d = subStrs[2];
54+
if ((subStrs.length !== 3) || (!y) || (!m) || (!d)) {
55+
throw new Error("Expected three integers.");
56+
}
57+
return new TimezonelessDate(Number(y), Number(m) - 1, Number(d));
58+
}
59+
}
60+

src/pages/blog/[...slug].astro

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import {getCollection, render} from "astro:content";
33
import Layout from "@layouts/GeneralLayout.astro";
44
5-
import {makeFrontmatter} from "@helpers/frontmatter";
5+
import {postToFrontmatter} from "./_common/post-to-frontmatter";
66
77
export async function getStaticPaths() {
88
const posts = await getCollection("blogPosts");
@@ -15,12 +15,9 @@ export async function getStaticPaths() {
1515
const {post} = Astro.props;
1616
const {Content} = await render(post);
1717
18-
const frontmatter = makeFrontmatter({
19-
description: post.data.title || "(no description)",
20-
...post.data,
21-
});
18+
const bfrontmatter = postToFrontmatter(post);
2219
---
2320

24-
<Layout {...frontmatter}>
21+
<Layout {...bfrontmatter.frontmatter}>
2522
<Content/>
2623
</Layout>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Filename: post-to-frontmatter.ts
3+
* Author: simshadows <contact@simshadows.com>
4+
*/
5+
6+
import {type CollectionEntry} from "astro:content";
7+
8+
import {TimezonelessDate} from "@helpers/timezoneless-date";
9+
10+
import {
11+
type Frontmatter,
12+
makeFrontmatter,
13+
} from "@helpers/frontmatter";
14+
15+
type ThisCollectionEntry = CollectionEntry<"blogPosts">;
16+
17+
export interface BlogFrontmatter {
18+
date: TimezonelessDate;
19+
post: ThisCollectionEntry;
20+
frontmatter: Frontmatter;
21+
};
22+
23+
function filenameErr(filename: string): Error {
24+
filename; // TODO: I should actually make the error message specific about
25+
// what the format expectations are.
26+
return new Error(`Invalid blog post filename '${filename}'. Filenames must start with an ISO8601 date like '2025-03-26-foobar.md'`);
27+
}
28+
29+
export function postToFrontmatter(post: ThisCollectionEntry): BlogFrontmatter {
30+
const idMandatoryPrefix = post.id.slice(0, 11);
31+
32+
if (
33+
post.id.length < 12
34+
|| idMandatoryPrefix.length !== 11
35+
|| idMandatoryPrefix.slice(-1) !== "-"
36+
) {
37+
throw filenameErr(post.id);
38+
}
39+
40+
const date = TimezonelessDate.parseISODate(idMandatoryPrefix.slice(0, 10));
41+
const description = post.data.title || "(no description)";
42+
43+
return {
44+
date,
45+
post,
46+
frontmatter: makeFrontmatter({
47+
description,
48+
...post.data,
49+
}),
50+
};
51+
}

src/pages/blog/archive.astro

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ import {getCollection} from "astro:content";
44
55
import {fm} from "@helpers/frontmatter";
66
7-
const allPosts = await getCollection("blogPosts");
7+
import {postToFrontmatter} from "./_common/post-to-frontmatter";
8+
9+
const allPosts = (await getCollection("blogPosts")).map(postToFrontmatter);
10+
11+
// TODO: Sort posts by date
812
913
export const frontmatter = fm({
1014
title: "Sim's Blog Archive",
@@ -17,15 +21,11 @@ export const frontmatter = fm({
1721

1822
<h1>Blog Archive</h1>
1923

20-
<p><em>This is an early WIP focused on experimenting with ways to manage my blog posts before migrating <a href="https://blog.simshadows.com/">my old blog</a> to this website.</em></p>
21-
22-
<ul>
23-
{
24-
allPosts.map((post) => (
25-
<li><a href={`/blog/${post.id}/`}>{post.data.title}</a></li>
26-
))
27-
}
28-
</ul>
24+
{
25+
allPosts.map((post) => (
26+
<p>{post.date.toISOString()} &raquo; <a href={`/blog/${post.post.id}/`}>{post.frontmatter.title}</a></p>
27+
))
28+
}
2929

3030
</Layout>
3131

src/pages/blog/index.astro

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import {getCollection} from "astro:content";
44
55
import {fm} from "@helpers/frontmatter";
66
7-
const allPosts = await getCollection("blogPosts");
7+
import {postToFrontmatter} from "./_common/post-to-frontmatter";
8+
9+
const allPosts = (await getCollection("blogPosts")).map(postToFrontmatter);
810
911
export const frontmatter = fm({
1012
title: "Sim's Blog",
@@ -24,7 +26,7 @@ export const frontmatter = fm({
2426
<ul>
2527
{
2628
allPosts.map((post) => (
27-
<li><a href={`/blog/${post.id}/`}>{post.data.title}</a></li>
29+
<li><a href={`/blog/${post.post.id}/`}>{post.frontmatter.title}</a></li>
2830
))
2931
}
3032
</ul>

yarn.lock

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2569,6 +2569,13 @@ __metadata:
25692569
languageName: node
25702570
linkType: hard
25712571

2572+
"dayjs@npm:^1.11.13":
2573+
version: 1.11.13
2574+
resolution: "dayjs@npm:1.11.13"
2575+
checksum: 10/7374d63ab179b8d909a95e74790def25c8986e329ae989840bacb8b1888be116d20e1c4eee75a69ea0dfbae13172efc50ef85619d304ee7ca3c01d5878b704f5
2576+
languageName: node
2577+
linkType: hard
2578+
25722579
"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.3.1, debug@npm:^4.3.3, debug@npm:^4.3.4":
25732580
version: 4.3.4
25742581
resolution: "debug@npm:4.3.4"
@@ -5841,6 +5848,7 @@ __metadata:
58415848
"@types/katex": "npm:^0.16.7"
58425849
"@types/node": "npm:^22.13.10"
58435850
astro: "npm:5.5.2"
5851+
dayjs: "npm:^1.11.13"
58445852
hast-util-from-html: "npm:2.0.3"
58455853
hast-util-to-text: "npm:^4.0.2"
58465854
katex: "npm:^0.16.21"

0 commit comments

Comments
 (0)