refactor: bookmarks becomes a content collection with auto-id

This commit is contained in:
Lewis Wynne 2026-01-29 02:10:33 +00:00
parent 23a267a242
commit 1d8ef601bc
6 changed files with 33 additions and 57 deletions

View file

@ -1,6 +1,7 @@
import { defineCollection } from 'astro:content'; import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders'; import { glob, file } from 'astro/loaders';
import { z } from 'astro/zod'; import { z } from 'astro/zod';
import yaml from 'js-yaml';
const posts = defineCollection({ const posts = defineCollection({
loader: glob({ pattern: '**/*.md', base: './src/content/posts' }), loader: glob({ pattern: '**/*.md', base: './src/content/posts' }),
@ -13,4 +14,18 @@ const posts = defineCollection({
}) })
}); });
export const collections = { posts }; const bookmarks = defineCollection({
loader: file('./src/content/bookmarks.yaml', {
parser: (text) => {
const data = yaml.load(text) as Array<Record<string, unknown>>;
return data.map((item, i) => ({ id: String(i), ...item }));
},
}),
schema: z.object({
title: z.string(),
url: z.string().url(),
date: z.coerce.date(),
})
});
export const collections = { posts, bookmarks };

View file

@ -1,16 +1,10 @@
--- ---
import { getCollection } from 'astro:content';
import Layout from '../../layouts/Layout.astro'; import Layout from '../../layouts/Layout.astro';
import yaml from 'js-yaml';
import bookmarksRaw from '../../content/bookmarks.yaml?raw';
interface Bookmark { const bookmarksCollection = await getCollection('bookmarks');
date: string; const bookmarks = bookmarksCollection
title: string; .sort((a, b) => b.data.date.getTime() - a.data.date.getTime());
url: string;
}
const bookmarks = (yaml.load(bookmarksRaw) as Bookmark[])
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
function formatDate(date: Date): string { function formatDate(date: Date): string {
const d = String(date.getDate()).padStart(2, '0'); const d = String(date.getDate()).padStart(2, '0');
@ -19,11 +13,6 @@ function formatDate(date: Date): string {
return `${d}/${m}/${y}`; return `${d}/${m}/${y}`;
} }
function formatBookmarkDate(dateStr: string): string {
const date = new Date(dateStr);
return formatDate(date);
}
function extractDomain(url: string): string { function extractDomain(url: string): string {
try { try {
const parsed = new URL(url); const parsed = new URL(url);
@ -37,6 +26,6 @@ function extractDomain(url: string): string {
<details open> <details open>
<summary>bookmarks</summary> <summary>bookmarks</summary>
<pre set:html={bookmarks.map(b => `<span class="muted">${formatBookmarkDate(b.date)}</span> <a href="${b.url}">${b.title}</a> <span class="muted">(${extractDomain(b.url)})</span>`).join('\n')} /> <pre set:html={bookmarks.map(b => `<span class="muted">${formatDate(b.data.date)}</span> <a href="${b.data.url}">${b.data.title}</a> <span class="muted">(${extractDomain(b.data.url)})</span>`).join('\n')} />
</details> </details>
</Layout> </Layout>

View file

@ -41,7 +41,6 @@ function formatDate(date: Date): string {
<Layout title={`${post.data.title} - lewis m.w.`}> <Layout title={`${post.data.title} - lewis m.w.`}>
<article> <article>
<p class="muted">[DRAFT] <a href="/draft/">back to drafts</a></p>
<h1>{post.data.title}</h1> <h1>{post.data.title}</h1>
<p class="muted" style="margin-top: 0;">{formatDate(post.data.date)}</p> <p class="muted" style="margin-top: 0;">{formatDate(post.data.date)}</p>
<Content /> <Content />

View file

@ -1,18 +1,10 @@
import rss from '@astrojs/rss'; import rss from '@astrojs/rss';
import { getCollection } from 'astro:content'; import { getCollection } from 'astro:content';
import yaml from 'js-yaml';
import bookmarksRaw from '../content/bookmarks.yaml?raw';
import fs from 'node:fs'; import fs from 'node:fs';
import path from 'node:path'; import path from 'node:path';
import type { APIContext } from 'astro'; import type { APIContext } from 'astro';
import { getGitDate } from '../utils'; import { getGitDate } from '../utils';
interface Bookmark {
date: string;
title: string;
url: string;
}
interface TxtFile { interface TxtFile {
name: string; name: string;
date: Date; date: Date;
@ -20,7 +12,7 @@ interface TxtFile {
export async function GET(context: APIContext) { export async function GET(context: APIContext) {
const posts = await getCollection('posts', ({ data }) => data.draft !== true); const posts = await getCollection('posts', ({ data }) => data.draft !== true);
const bookmarks = yaml.load(bookmarksRaw) as Bookmark[]; const bookmarks = await getCollection('bookmarks');
const txtDir = path.join(process.cwd(), 'public/txt'); const txtDir = path.join(process.cwd(), 'public/txt');
const txtFiles: TxtFile[] = fs.existsSync(txtDir) const txtFiles: TxtFile[] = fs.existsSync(txtDir)
@ -46,10 +38,10 @@ export async function GET(context: APIContext) {
description: txt.name, description: txt.name,
})), })),
...bookmarks.map(b => ({ ...bookmarks.map(b => ({
title: b.title, title: b.data.title,
pubDate: new Date(b.date), pubDate: b.data.date,
link: b.url, link: b.data.url,
description: b.title, description: b.data.title,
})), })),
].sort((a, b) => b.pubDate.getTime() - a.pubDate.getTime()); ].sort((a, b) => b.pubDate.getTime() - a.pubDate.getTime());

View file

@ -2,18 +2,11 @@
import { getCollection } from 'astro:content'; import { getCollection } from 'astro:content';
import Layout from '../layouts/Layout.astro'; import Layout from '../layouts/Layout.astro';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import bookmarksRaw from '../content/bookmarks.yaml?raw';
import fs from 'node:fs'; import fs from 'node:fs';
import path from 'node:path'; import path from 'node:path';
import { getApprovedEntries, type GuestbookEntry } from '../lib/db'; import { getApprovedEntries, type GuestbookEntry } from '../lib/db';
import { getGitDate } from '../utils'; import { getGitDate } from '../utils';
interface Bookmark {
date: string;
title: string;
url: string;
}
interface TxtFile { interface TxtFile {
name: string; name: string;
date: Date; date: Date;
@ -50,8 +43,9 @@ for (const category of sortedCategories) {
}); });
} }
const bookmarks = (yaml.load(bookmarksRaw) as Bookmark[]) const bookmarksCollection = await getCollection('bookmarks');
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); const bookmarks = bookmarksCollection
.sort((a, b) => b.data.date.getTime() - a.data.date.getTime());
// Auto-discover txt files from public/txt/ // Auto-discover txt files from public/txt/
const txtDir = path.join(process.cwd(), 'public/txt'); const txtDir = path.join(process.cwd(), 'public/txt');
@ -89,11 +83,6 @@ function formatDate(date: Date): string {
return `${d}/${m}/${y}`; return `${d}/${m}/${y}`;
} }
function formatBookmarkDate(dateStr: string): string {
const date = new Date(dateStr);
return formatDate(date);
}
function extractDomain(url: string): string { function extractDomain(url: string): string {
try { try {
const parsed = new URL(url); const parsed = new URL(url);
@ -129,7 +118,7 @@ function extractDomain(url: string): string {
<details open> <details open>
<summary>bookmarks</summary> <summary>bookmarks</summary>
<pre set:html={[ <pre set:html={[
...bookmarks.slice(0, 10).map(b => `<span class="muted">${formatBookmarkDate(b.date)}</span> <a href="${b.url}">${b.title}</a> <span class="muted">(${extractDomain(b.url)})</span>`), ...bookmarks.slice(0, 10).map(b => `<span class="muted">${formatDate(b.data.date)}</span> <a href="${b.data.url}">${b.data.title}</a> <span class="muted">(${extractDomain(b.data.url)})</span>`),
...(bookmarks.length > 10 ? [`<a href="/bookmarks/">+${bookmarks.length - 10} more</a>`] : []) ...(bookmarks.length > 10 ? [`<a href="/bookmarks/">+${bookmarks.length - 10} more</a>`] : [])
].join('\n')} /> ].join('\n')} />
</details> </details>

View file

@ -1,22 +1,14 @@
import { getCollection } from 'astro:content'; import { getCollection } from 'astro:content';
import yaml from 'js-yaml';
import bookmarksRaw from '../content/bookmarks.yaml?raw';
import fs from 'node:fs'; import fs from 'node:fs';
import path from 'node:path'; import path from 'node:path';
import type { APIContext } from 'astro'; import type { APIContext } from 'astro';
export const prerender = false; export const prerender = false;
interface Bookmark {
date: string;
title: string;
url: string;
}
export async function GET(context: APIContext) { export async function GET(context: APIContext) {
const site = context.site?.origin ?? 'https://wynne.rs'; const site = context.site?.origin ?? 'https://wynne.rs';
const posts = await getCollection('posts', ({ data }) => data.draft !== true); const posts = await getCollection('posts', ({ data }) => data.draft !== true);
const bookmarks = yaml.load(bookmarksRaw) as Bookmark[]; const bookmarks = await getCollection('bookmarks');
const txtDir = path.join(process.cwd(), 'public/txt'); const txtDir = path.join(process.cwd(), 'public/txt');
const txtFiles = fs.existsSync(txtDir) const txtFiles = fs.existsSync(txtDir)
@ -26,7 +18,7 @@ export async function GET(context: APIContext) {
const urls = [ const urls = [
...posts.map(post => `/md/${post.id}`), ...posts.map(post => `/md/${post.id}`),
...txtFiles.map(txt => `/txt/${txt}`), ...txtFiles.map(txt => `/txt/${txt}`),
...bookmarks.map(b => b.url), ...bookmarks.map(b => b.data.url),
]; ];
const random = urls[Math.floor(Math.random() * urls.length)]; const random = urls[Math.floor(Math.random() * urls.length)];