diff --git a/.astro/collections/blog.schema.json b/.astro/collections/blog.schema.json new file mode 100644 index 0000000..d4a2a9b --- /dev/null +++ b/.astro/collections/blog.schema.json @@ -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#" +} \ No newline at end of file diff --git a/.astro/collections/posts.schema.json b/.astro/collections/posts.schema.json new file mode 100644 index 0000000..fc20e0d --- /dev/null +++ b/.astro/collections/posts.schema.json @@ -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#" +} \ No newline at end of file diff --git a/.astro/content-modules.mjs b/.astro/content-modules.mjs index 2b8b823..e966991 100644 --- a/.astro/content-modules.mjs +++ b/.astro/content-modules.mjs @@ -1 +1,6 @@ -export default new Map(); \ No newline at end of file + +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")]]); + \ No newline at end of file diff --git a/.astro/content.d.ts b/.astro/content.d.ts index c0082cc..c6b1369 100644 --- a/.astro/content.d.ts +++ b/.astro/content.d.ts @@ -1,3 +1,14 @@ +declare module 'astro:content' { + interface Render { + '.mdx': Promise<{ + Content: import('astro').MDXContent; + headings: import('astro').MarkdownHeading[]; + remarkPluginFrontmatter: Record; + components: import('astro').MDXInstance<{}>['components']; + }>; + } +} + declare module 'astro:content' { export interface RenderResult { Content: import('astro/runtime/server/index.js').AstroComponentFactory; @@ -162,7 +173,15 @@ declare module 'astro:content' { }; type DataEntryMap = { - + "posts": Record; + rendered?: RenderedContent; + filePath?: string; +}>; + }; type AnyEntryMap = ContentEntryMap & DataEntryMap; @@ -194,6 +213,6 @@ declare module 'astro:content' { 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; } diff --git a/.astro/data-store.json b/.astro/data-store.json index 32f6d3d..0da8b0b 100644 --- a/.astro/data-store.json +++ b/.astro/data-store.json @@ -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}}"] \ No newline at end of file +[["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"] \ No newline at end of file diff --git a/astro.config.mjs b/astro.config.mjs index ffc801d..69da4ba 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -1,5 +1,9 @@ import { defineConfig } from "astro/config"; +import mdx from '@astrojs/mdx'; export default defineConfig({ + integrations: [ + mdx(), + ], prefetch: true, }); diff --git a/posts/after.mdx b/posts/after.mdx new file mode 100644 index 0000000..92f52e8 --- /dev/null +++ b/posts/after.mdx @@ -0,0 +1,8 @@ +--- +title: Example after post +date: 2026-02-28 +--- + +## After + +Lorem ipsum dolor sit amet. diff --git a/posts/before.mdx b/posts/before.mdx new file mode 100644 index 0000000..3470ef9 --- /dev/null +++ b/posts/before.mdx @@ -0,0 +1,8 @@ +--- +title: Example previous post +date: 2026-02-21 +--- + +## Before + +Lorem ipsum dolor sit amet. diff --git a/posts/test.mdx b/posts/test.mdx new file mode 100644 index 0000000..d0448f3 --- /dev/null +++ b/posts/test.mdx @@ -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. diff --git a/src/components/Post.astro b/src/components/Post.astro new file mode 100644 index 0000000..6c306c0 --- /dev/null +++ b/src/components/Post.astro @@ -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); +--- + + + +
+
+

{ entry.data.title }

+

{ formatDate(entry.data.date) }

+
+ +
+ +
+ +
+ +
+ +
+ {prevSlug && ( + Older + )} + {nextSlug && ( + Newer + )} +
+
diff --git a/src/content.config.ts b/src/content.config.ts new file mode 100644 index 0000000..21715a1 --- /dev/null +++ b/src/content.config.ts @@ -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 }; diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro index e2ea9a6..15a36e4 100644 --- a/src/layouts/BaseLayout.astro +++ b/src/layouts/BaseLayout.astro @@ -3,8 +3,8 @@ import '@styles/main.css'; --- + -
-

Working Title

-

Subtitle

+
+
+

Working Title

+

Subtitle

+
-
+
-
+
- +
+
diff --git a/src/lib/datefmt.ts b/src/lib/datefmt.ts new file mode 100644 index 0000000..300f3b1 --- /dev/null +++ b/src/lib/datefmt.ts @@ -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}`; +} diff --git a/src/pages/[slug].astro b/src/pages/[slug].astro new file mode 100644 index 0000000..262cab3 --- /dev/null +++ b/src/pages/[slug].astro @@ -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 }, + } + }); + +} + +--- + + + + diff --git a/tsconfig.json b/tsconfig.json index 028dd1d..1510dca 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,9 @@ "exclude": ["dist"], "compilerOptions": { "paths": { + "@components/*": ["./src/components/*"], "@layouts/*": ["./src/layouts/*"], + "@lib/*": ["./src/lib/*"], "@styles/*": ["./src/styles/*"] } }