start working on posts with placeholder content

This commit is contained in:
2026-02-28 09:26:10 -05:00
parent 95b58b5615
commit c28f340333
16 changed files with 372 additions and 15 deletions

View File

@@ -0,0 +1,50 @@
{
"$ref": "#/definitions/blog",
"definitions": {
"blog": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"date": {
"anyOf": [
{
"type": "string",
"format": "date-time"
},
{
"type": "string",
"format": "date"
},
{
"type": "integer",
"format": "unix-time"
}
]
},
"draft": {
"type": "boolean",
"default": false
},
"dropcap": {
"type": "boolean",
"default": false
},
"toc": {
"type": "boolean",
"default": false
},
"$schema": {
"type": "string"
}
},
"required": [
"title",
"date"
],
"additionalProperties": false
}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}

View File

@@ -0,0 +1,50 @@
{
"$ref": "#/definitions/posts",
"definitions": {
"posts": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"date": {
"anyOf": [
{
"type": "string",
"format": "date-time"
},
{
"type": "string",
"format": "date"
},
{
"type": "integer",
"format": "unix-time"
}
]
},
"draft": {
"type": "boolean",
"default": false
},
"dropcap": {
"type": "boolean",
"default": true
},
"toc": {
"type": "boolean",
"default": true
},
"$schema": {
"type": "string"
}
},
"required": [
"title",
"date"
],
"additionalProperties": false
}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}

View File

@@ -1 +1,6 @@
export default new Map();
export default new Map([
["posts/after.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=posts%2Fafter.mdx&astroContentModuleFlag=true")],
["posts/before.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=posts%2Fbefore.mdx&astroContentModuleFlag=true")],
["posts/test.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=posts%2Ftest.mdx&astroContentModuleFlag=true")]]);

23
.astro/content.d.ts vendored
View File

@@ -1,3 +1,14 @@
declare module 'astro:content' {
interface Render {
'.mdx': Promise<{
Content: import('astro').MDXContent;
headings: import('astro').MarkdownHeading[];
remarkPluginFrontmatter: Record<string, any>;
components: import('astro').MDXInstance<{}>['components'];
}>;
}
}
declare module 'astro:content' { declare module 'astro:content' {
export interface RenderResult { export interface RenderResult {
Content: import('astro/runtime/server/index.js').AstroComponentFactory; Content: import('astro/runtime/server/index.js').AstroComponentFactory;
@@ -162,7 +173,15 @@ declare module 'astro:content' {
}; };
type DataEntryMap = { type DataEntryMap = {
"posts": Record<string, {
id: string;
body?: string;
collection: "posts";
data: InferEntrySchema<"posts">;
rendered?: RenderedContent;
filePath?: string;
}>;
}; };
type AnyEntryMap = ContentEntryMap & DataEntryMap; type AnyEntryMap = ContentEntryMap & DataEntryMap;
@@ -194,6 +213,6 @@ declare module 'astro:content' {
LiveContentConfig['collections'][C]['loader'] LiveContentConfig['collections'][C]['loader']
>; >;
export type ContentConfig = typeof import("../src/content.config.mjs"); export type ContentConfig = typeof import("../src/content.config.js");
export type LiveContentConfig = never; export type LiveContentConfig = never;
} }

View File

@@ -1 +1 @@
[["Map",1,2],"meta::meta",["Map",3,4,5,6],"astro-version","5.18.0","astro-config-digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{},\"compressHTML\":true,\"base\":\"/\",\"trailingSlash\":\"ignore\",\"output\":\"static\",\"scopedStyleStrategy\":\"attribute\",\"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astro\",\"serverEntry\":\"entry.mjs\",\"redirects\":true,\"inlineStylesheets\":\"auto\",\"concurrency\":1},\"server\":{\"open\":false,\"host\":false,\"port\":4321,\"streaming\":true,\"allowedHosts\":[]},\"redirects\":{},\"prefetch\":true,\"image\":{\"endpoint\":{\"route\":\"/_image\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\"config\":{}},\"domains\":[],\"remotePatterns\":[],\"responsiveStyles\":false},\"devToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":{\"type\":\"shiki\",\"excludeLangs\":[\"math\"]},\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"theme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPlugins\":[],\"rehypePlugins\":[],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":true},\"security\":{\"checkOrigin\":true,\"allowedDomains\":[],\"actionBodySizeLimit\":1048576},\"env\":{\"schema\":{},\"validateSecrets\":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false,\"headingIdCompat\":false,\"preserveScriptOrder\":false,\"liveContentCollections\":false,\"csp\":false,\"staticImportMetaEnv\":false,\"chromeDevtoolsWorkspace\":false,\"failOnPrerenderConflict\":false,\"svgo\":false},\"legacy\":{\"collections\":false}}"] [["Map",1,2,9,10],"meta::meta",["Map",3,4,5,6,7,8],"astro-version","5.18.0","content-config-digest","cbc06d2b523a5bb1","astro-config-digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{},\"compressHTML\":true,\"base\":\"/\",\"trailingSlash\":\"ignore\",\"output\":\"static\",\"scopedStyleStrategy\":\"attribute\",\"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astro\",\"serverEntry\":\"entry.mjs\",\"redirects\":true,\"inlineStylesheets\":\"auto\",\"concurrency\":1},\"server\":{\"open\":false,\"host\":false,\"port\":4321,\"streaming\":true,\"allowedHosts\":[]},\"redirects\":{},\"prefetch\":true,\"image\":{\"endpoint\":{\"route\":\"/_image\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\"config\":{}},\"domains\":[],\"remotePatterns\":[],\"responsiveStyles\":false},\"devToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":{\"type\":\"shiki\",\"excludeLangs\":[\"math\"]},\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"theme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPlugins\":[],\"rehypePlugins\":[],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":true},\"security\":{\"checkOrigin\":true,\"allowedDomains\":[],\"actionBodySizeLimit\":1048576},\"env\":{\"schema\":{},\"validateSecrets\":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false,\"headingIdCompat\":false,\"preserveScriptOrder\":false,\"liveContentCollections\":false,\"csp\":false,\"staticImportMetaEnv\":false,\"chromeDevtoolsWorkspace\":false,\"failOnPrerenderConflict\":false,\"svgo\":false},\"legacy\":{\"collections\":false}}","posts",["Map",11,12,21,22,29,30],"before",{"id":11,"data":13,"body":18,"filePath":19,"digest":20,"deferredRender":17},{"title":14,"date":15,"draft":16,"dropcap":17,"toc":17},"Example previous post",["Date","2026-02-21T00:00:00.000Z"],false,true,"## Before\n\nLorem ipsum dolor sit amet.","posts/before.mdx","ca3d5f3d23dd17e3","after",{"id":21,"data":23,"body":26,"filePath":27,"digest":28,"deferredRender":17},{"title":24,"date":25,"draft":16,"dropcap":17,"toc":17},"Example after post",["Date","2026-02-28T00:00:00.000Z"],"## After\n\nLorem ipsum dolor sit amet.","posts/after.mdx","cb6771422071e34b","test",{"id":29,"data":31,"body":34,"filePath":35,"digest":36,"deferredRender":17},{"title":32,"date":33,"draft":16,"dropcap":17,"toc":17},"This is a Test",["Date","2026-02-27T00:00:00.000Z"],"This is a test.\n\n## A Heading\n\nLorem ipsum dolor sit amet, consectitur adipiscing elit.","posts/test.mdx","3c4124d8e4034523"]

View File

@@ -1,5 +1,9 @@
import { defineConfig } from "astro/config"; import { defineConfig } from "astro/config";
import mdx from '@astrojs/mdx';
export default defineConfig({ export default defineConfig({
integrations: [
mdx(),
],
prefetch: true, prefetch: true,
}); });

8
posts/after.mdx Normal file
View File

@@ -0,0 +1,8 @@
---
title: Example after post
date: 2026-02-28
---
## After
Lorem ipsum dolor sit amet.

8
posts/before.mdx Normal file
View File

@@ -0,0 +1,8 @@
---
title: Example previous post
date: 2026-02-21
---
## Before
Lorem ipsum dolor sit amet.

10
posts/test.mdx Normal file
View File

@@ -0,0 +1,10 @@
---
title: This is a Test
date: 2026-02-27
---
This is a test.
## A Heading
Lorem ipsum dolor sit amet, consectitur adipiscing elit.

95
src/components/Post.astro Normal file
View File

@@ -0,0 +1,95 @@
---
import type { CollectionEntry } from 'astro:content';
import { render } from 'astro:content';
import { formatDate } from '@lib/datefmt';
export interface Props {
entry: CollectionEntry<'posts'>,
prevSlug: string | null,
nextSlug: string | null,
};
const { entry, prevSlug, nextSlug } = Astro.props;
const { Content } = await render(entry);
---
<style>
/* 3-column grid: left gutter, center content, and right gutter */
article {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, var(--content-width)) minmax(0, 1fr);
/* a bit of breathing room for narrow screens */
padding: 0 var(--content-padding);
}
.left-gutter {
grid-column: 1 / 2;
justify-self: end;
}
.right-gutter {
grid-column: 3 / 4;
justify-self: start;
}
.title {
grid-column: 2 / 3;
}
.subtitle {
font-size: 0.9em;
font-style: italic;
margin-top: -0.75rem;
}
.post {
grid-column: 2 / 3;
}
footer {
grid-column: 2 / 3;
margin-bottom: 2.5rem;
display: flex;
justify-content: space-between;
& a {
font-size: 1.25rem;
color: var(--content-color-faded);
text-decoration: underline;
text-underline-offset: 0.25em;
text-decoration-color: transparent;
transition: 150ms;
&:hover {
text-decoration-color: currentColor;
text-decoration: underline;
}
}
}
</style>
<article>
<header class="title">
<h1>{ entry.data.title }</h1>
<p class="subtitle">{ formatDate(entry.data.date) }</p>
</header>
<div class="left-gutter" />
<section class="post">
<Content />
</section>
<div class="right-gutter" />
<footer>
{prevSlug && (
<a href={`/${prevSlug}`} data-astro-prefetch>Older</a>
)}
{nextSlug && (
<a href={`/${nextSlug}`} data-astro-prefetch>Newer</a>
)}
</footer>
</article>

19
src/content.config.ts Normal file
View File

@@ -0,0 +1,19 @@
import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';
import { z } from 'astro/zod';
const posts = defineCollection({
loader: glob({ pattern: '*.mdx', base: './posts' }),
schema: z.object({
title: z.string(),
date: z.date(),
draft: z.boolean().default(false),
dropcap: z.boolean().default(true),
toc: z.boolean().default(true),
})
});
export const collections = { posts };

View File

@@ -3,8 +3,8 @@ import '@styles/main.css';
--- ---
<style> <style>
.header { header {
background: var(--primary-color-faded); background-color: var(--primary-color-faded);
} }
nav { nav {
@@ -37,13 +37,13 @@ import '@styles/main.css';
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
</head> </head>
<body> <body>
<div class="header"> <header>
<nav> <nav>
<a href="/" data-astro-prefetch>Home</a> <a href="/" data-astro-prefetch>Home</a>
<a href="/posts" data-astro-prefetch>Posts</a> <a href="/posts" data-astro-prefetch>Posts</a>
<a href="/about" data-astro-prefetch>About</a> <a href="/about" data-astro-prefetch>About</a>
</nav> </nav>
</div> </header>
<main> <main>
<slot /> <slot />

View File

@@ -2,19 +2,55 @@
import BaseLayout from '@layouts/BaseLayout.astro'; import BaseLayout from '@layouts/BaseLayout.astro';
--- ---
<style>
/* 3-column grid: left gutter, center content, and right gutter */
article {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, var(--content-width)) minmax(0, 1fr);
/* a bit of breathing room for narrow screens */
padding: 0 var(--content-padding);
}
.left-gutter {
grid-column: 1 / 2;
justify-self: end;
}
.right-gutter {
grid-column: 3 / 4;
justify-self: start;
}
.title {
grid-column: 2 / 3;
}
.subtitle {
font-size: 0.9em;
font-style: italic;
margin-top: -0.75rem;
}
.post {
grid-column: 2 / 3;
}
</style>
<BaseLayout> <BaseLayout>
<div class="page"> <article>
<h1>Working Title</h1> <header class="title">
<p class="subtitle">Subtitle</p> <h1>Working Title</h1>
<p class="subtitle">Subtitle</p>
</header>
<div class="left-gutter" /> <div class="left-gutter" />
<div class="post"> <section class="post">
<slot /> <slot />
</div> </section>
<div class="right-gutter" /> <div class="right-gutter" />
<div class="footer" /> <footer />
</div> </article>
</BaseLayout> </BaseLayout>

27
src/lib/datefmt.ts Normal file
View File

@@ -0,0 +1,27 @@
const months = [
'January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'
];
const ordinals = [
'first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh',
'eighth', 'ninth', 'tenth', 'eleventh', 'twelfth', 'thirteenth',
'fourteenth', 'fifteenth', 'sixteenth', 'seventeenth', 'eighteenth',
'nineteenth', 'twentieth', 'twenty-first', 'twenty-second', 'twenty-third',
'twenty-fourth', 'twenty-fifth', 'twenty-sixth', 'twenty-seventh',
'twenty-eighth', 'twenty-ninth', 'thirtieth', 'thirty-first'
];
const weekdays = [
'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'
];
export function formatDate(date: Date) {
const year = date.getFullYear();
const month = months[date.getMonth() - 1];
const monthday = ordinals[date.getDate() - 1];
const weekday = weekdays[date.getDay() - 1];
return `${weekday}, the ${monthday} of ${month}, A.D. ${year}`;
}

24
src/pages/[slug].astro Normal file
View File

@@ -0,0 +1,24 @@
---
import { getCollection } from 'astro:content';
import BaseLayout from '@layouts/BaseLayout.astro';
import Post from '@components/Post.astro';
export async function getStaticPaths() {
const entries = await getCollection('posts');
entries.sort((a, b) => a.data.date.getTime() - b.data.date.getTime())
return entries.map((entry, idx) => {
const prevSlug = entries[idx - 1]?.id || null;
const nextSlug = entries[idx + 1]?.id || null;
return {
params: { slug: entry.id },
props: { entry, prevSlug, nextSlug },
}
});
}
---
<BaseLayout>
<Post {...Astro.props} />
</BaseLayout>

View File

@@ -4,7 +4,9 @@
"exclude": ["dist"], "exclude": ["dist"],
"compilerOptions": { "compilerOptions": {
"paths": { "paths": {
"@components/*": ["./src/components/*"],
"@layouts/*": ["./src/layouts/*"], "@layouts/*": ["./src/layouts/*"],
"@lib/*": ["./src/lib/*"],
"@styles/*": ["./src/styles/*"] "@styles/*": ["./src/styles/*"]
} }
} }