From cc6eff22a8c0279f4879641e59144f2ae817043c Mon Sep 17 00:00:00 2001 From: lew Date: Sat, 31 Jan 2026 23:39:29 +0000 Subject: [PATCH] feat: optional dates, otherwise fetched from git --- www/src/content.config.ts | 1 - www/src/lib/git.ts | 44 ++++++++++++++++++++++++++++++++ www/src/lib/md.ts | 32 +++++++++++++++++++---- www/src/lib/txt.ts | 13 ++-------- www/src/pages/draft/[slug].astro | 11 ++++---- www/src/pages/feed.xml.ts | 7 ++--- www/src/pages/md/[slug].astro | 6 ++--- www/src/pages/md/index.astro | 7 ++--- 8 files changed, 90 insertions(+), 31 deletions(-) create mode 100644 www/src/lib/git.ts diff --git a/www/src/content.config.ts b/www/src/content.config.ts index 6268bc5..5836c2c 100644 --- a/www/src/content.config.ts +++ b/www/src/content.config.ts @@ -7,7 +7,6 @@ const md = defineCollection({ loader: glob({ pattern: '**/*.md', base: './src/content/md' }), schema: z.object({ title: z.string(), - date: z.coerce.date(), pinned: z.boolean().optional(), category: z.string().optional(), draft: z.boolean().optional(), diff --git a/www/src/lib/git.ts b/www/src/lib/git.ts new file mode 100644 index 0000000..9a54145 --- /dev/null +++ b/www/src/lib/git.ts @@ -0,0 +1,44 @@ +import { execSync } from 'node:child_process'; + +export function getGitCreationDate(filePath: string): Date { + try { + // Get the oldest commit for this file (first commit that added it) + const timestamp = execSync( + `git log --follow --diff-filter=A --format=%cI -- "${filePath}"`, + { encoding: 'utf8' } + ).trim(); + return timestamp ? new Date(timestamp) : new Date(0); + } catch { + return new Date(0); + } +} + +export function getGitLastModifiedDate(filePath: string): Date { + try { + const timestamp = execSync( + `git log -1 --format=%cI -- "${filePath}"`, + { encoding: 'utf8' } + ).trim(); + return timestamp ? new Date(timestamp) : new Date(0); + } catch { + return new Date(0); + } +} + +export interface GitDates { + created: Date; + updated: Date | null; // null if never updated (created === lastModified) +} + +export function getGitDates(filePath: string): GitDates { + const created = getGitCreationDate(filePath); + const lastModified = getGitLastModifiedDate(filePath); + + // If dates are the same (same commit), there's no update + const hasUpdate = created.getTime() !== lastModified.getTime(); + + return { + created, + updated: hasUpdate ? lastModified : null, + }; +} diff --git a/www/src/lib/md.ts b/www/src/lib/md.ts index 7026087..4695519 100644 --- a/www/src/lib/md.ts +++ b/www/src/lib/md.ts @@ -1,22 +1,44 @@ +import path from 'node:path'; import type { CollectionEntry } from 'astro:content'; +import { getGitDates, type GitDates } from './git'; type Post = CollectionEntry<'md'>; +export interface PostWithDates extends Post { + dates: GitDates; +} + export function getSlug(postId: string): string { const parts = postId.split('/'); return parts[parts.length - 1]; } -function sortPosts(posts: Post[]): Post[] { +function getPostFilePath(post: Post): string { + return path.join(process.cwd(), 'src/content/md', `${post.id}.md`); +} + +export function enrichPostWithDates(post: Post): PostWithDates { + const filePath = getPostFilePath(post); + return { + ...post, + dates: getGitDates(filePath), + }; +} + +export function enrichPostsWithDates(posts: Post[]): PostWithDates[] { + return posts.map(enrichPostWithDates); +} + +function sortPosts(posts: PostWithDates[]): PostWithDates[] { return posts.slice().sort((a, b) => { if (a.data.pinned && !b.data.pinned) return -1; if (!a.data.pinned && b.data.pinned) return 1; - return b.data.date.getTime() - a.data.date.getTime(); + return b.dates.created.getTime() - a.dates.created.getTime(); }); } -export function organizePostsByCategory(posts: Post[]): { - grouped: Record; +export function organizePostsByCategory(posts: PostWithDates[]): { + grouped: Record; categories: string[]; } { const grouped = posts.reduce((acc, post) => { @@ -24,7 +46,7 @@ export function organizePostsByCategory(posts: Post[]): { if (!acc[category]) acc[category] = []; acc[category].push(post); return acc; - }, {} as Record); + }, {} as Record); const categories = Object.keys(grouped).sort((a, b) => { if (a === 'md') return -1; diff --git a/www/src/lib/txt.ts b/www/src/lib/txt.ts index 12a0db1..b8f12be 100644 --- a/www/src/lib/txt.ts +++ b/www/src/lib/txt.ts @@ -1,17 +1,8 @@ -import { execSync } from 'node:child_process'; import fs from 'node:fs'; import path from 'node:path'; import yaml from 'js-yaml'; import { sortByPinnedThenDate } from './format'; - -function getGitDate(filePath: string): Date { - try { - const timestamp = execSync(`git log -1 --format=%cI -- "${filePath}"`, { encoding: 'utf8' }).trim(); - return timestamp ? new Date(timestamp) : new Date(0); - } catch { - return new Date(0); - } -} +import { getGitLastModifiedDate } from './git'; export interface TxtFile { name: string; @@ -45,7 +36,7 @@ export function getTxtFiles(): TxtFile[] { .filter(file => file.endsWith('.txt')) .map(name => ({ name, - date: getGitDate(path.join(txtDir, name)), + date: getGitLastModifiedDate(path.join(txtDir, name)), pinned: pinnedSet.has(name), })); return sortByPinnedThenDate(files); diff --git a/www/src/pages/draft/[slug].astro b/www/src/pages/draft/[slug].astro index 62302af..2ef5d6e 100644 --- a/www/src/pages/draft/[slug].astro +++ b/www/src/pages/draft/[slug].astro @@ -5,27 +5,28 @@ import { getCollection, render } from 'astro:content'; import { requireAdminSession } from '../../lib/auth'; import Layout from '../../layouts/Layout.astro'; import { formatDate } from '../../lib/format'; -import { getSlug } from '../../lib/md'; +import { getSlug, enrichPostWithDates } from '../../lib/md'; const { session, error } = await requireAdminSession(Astro.request); if (error) return error; if (!session) return Astro.redirect('/api/auth/signin'); const slug = Astro.params.slug; -const posts = await getCollection('md', ({ data }) => data.draft === true); -const post = posts.find(p => getSlug(p.id) === slug); +const rawPosts = await getCollection('md', ({ data }) => data.draft === true); +const rawPost = rawPosts.find(p => getSlug(p.id) === slug); -if (!post) { +if (!rawPost) { return new Response('Not found', { status: 404 }); } +const post = enrichPostWithDates(rawPost); const { Content } = await render(post); ---

{post.data.title}

-

{formatDate(post.data.date)}

+

{formatDate(post.dates.created)}{post.dates.updated && ` (updated ${formatDate(post.dates.updated)})`}

diff --git a/www/src/pages/feed.xml.ts b/www/src/pages/feed.xml.ts index 8433614..bb90238 100644 --- a/www/src/pages/feed.xml.ts +++ b/www/src/pages/feed.xml.ts @@ -1,18 +1,19 @@ import rss from '@astrojs/rss'; import { getCollection } from 'astro:content'; import type { APIContext } from 'astro'; -import { getSlug } from '../lib/md'; +import { getSlug, enrichPostsWithDates } from '../lib/md'; import { getTxtFiles } from '../lib/txt'; export async function GET(context: APIContext) { - const posts = await getCollection('md', ({ data }) => data.draft !== true); + const rawPosts = await getCollection('md', ({ data }) => data.draft !== true); + const posts = enrichPostsWithDates(rawPosts); const bookmarks = await getCollection('bookmarks'); const txtFiles = getTxtFiles(); const items = [ ...posts.map(post => ({ title: post.data.title, - pubDate: post.data.date, + pubDate: post.dates.created, link: `/md/${getSlug(post.id)}`, description: post.data.title, })), diff --git a/www/src/pages/md/[slug].astro b/www/src/pages/md/[slug].astro index b91ceb4..8ea8d6c 100644 --- a/www/src/pages/md/[slug].astro +++ b/www/src/pages/md/[slug].astro @@ -2,13 +2,13 @@ import { getCollection, render } from 'astro:content'; import Layout from '../../layouts/Layout.astro'; import { formatDate } from '../../lib/format'; -import { getSlug } from '../../lib/md'; +import { getSlug, enrichPostWithDates } from '../../lib/md'; export async function getStaticPaths() { const posts = await getCollection('md', ({ data }) => data.draft !== true); return posts.map(post => ({ params: { slug: getSlug(post.id) }, - props: { post } + props: { post: enrichPostWithDates(post) } })); } @@ -19,7 +19,7 @@ const { Content } = await render(post);

{post.data.title}

-

{formatDate(post.data.date)}

+

{formatDate(post.dates.created)}{post.dates.updated && ` (updated ${formatDate(post.dates.updated)})`}

diff --git a/www/src/pages/md/index.astro b/www/src/pages/md/index.astro index 7e58533..6c0ff51 100644 --- a/www/src/pages/md/index.astro +++ b/www/src/pages/md/index.astro @@ -2,9 +2,10 @@ import { getCollection } from 'astro:content'; import Layout from '../../layouts/Layout.astro'; import { formatListItem } from '../../lib/format'; -import { organizePostsByCategory, getSlug } from '../../lib/md'; +import { organizePostsByCategory, getSlug, enrichPostsWithDates } from '../../lib/md'; -const posts = await getCollection('md', ({ data }) => data.draft !== true); +const rawPosts = await getCollection('md', ({ data }) => data.draft !== true); +const posts = enrichPostsWithDates(rawPosts); const { grouped, categories: sortedCategories } = organizePostsByCategory(posts); --- @@ -12,7 +13,7 @@ const { grouped, categories: sortedCategories } = organizePostsByCategory(posts) {sortedCategories.map(category => (
{category} -
 formatListItem(post.data.date, `/md/${getSlug(post.id)}`, post.data.title, { pinned: post.data.pinned })).join('\n')} />
+
 formatListItem(post.dates.created, `/md/${getSlug(post.id)}`, post.data.title, { pinned: post.data.pinned })).join('\n')} />
 
))}