Compare commits

...

5 Commits

9 changed files with 152 additions and 22 deletions

View File

@@ -1,6 +1,7 @@
import { defineConfig } from "astro/config"; import { defineConfig } from "astro/config";
import mdx from '@astrojs/mdx'; import mdx from '@astrojs/mdx';
import vue from '@astrojs/vue'; import vue from '@astrojs/vue';
import { remarkDescription } from './src/plugins.ts';
export default defineConfig({ export default defineConfig({
integrations: [ integrations: [
@@ -9,6 +10,7 @@ export default defineConfig({
], ],
prefetch: true, prefetch: true,
markdown: { markdown: {
remarkPlugins: [remarkDescription],
shikiConfig: { shikiConfig: {
themes: { themes: {
light: 'dracula', light: 'dracula',

View File

@@ -10,8 +10,8 @@ import { formatDate } from '@lib/datefmt';
export interface Props { export interface Props {
entry: CollectionEntry<'posts'>, entry: CollectionEntry<'posts'>,
prevSlug: string | null, prevSlug?: string | null,
nextSlug: string | null, nextSlug?: string | null,
}; };
const { entry, prevSlug, nextSlug } = Astro.props; const { entry, prevSlug, nextSlug } = Astro.props;

View File

@@ -38,26 +38,15 @@ button {
} }
/* hide by default, i.e. if JS isn't enabled and the data-theme attribute didn't get set, */ /* hide by default, i.e. if JS isn't enabled and the data-theme attribute didn't get set, */
visibility: hidden; display: none;
opacity: 0;
transition:
color 0.2s ease,
opacity 0.5s ease,
transform 0.5s ease;
} }
:global(html[data-theme="light"]) button#switch-to-dark { :global(html[data-theme="light"]) button#switch-to-dark {
opacity: 1; display: block;
visibility: visible;
transform: rotate(360deg);
/* whichever one is currently active should be on top */
z-index: 10;
} }
:global(html[data-theme="dark"]) button#switch-to-light { :global(html[data-theme="dark"]) button#switch-to-light {
opacity: 1; display: block;
visibility: visible;
transform: rotate(-360deg);
z-index: 10;
} }
</style> </style>

View File

@@ -2,6 +2,12 @@
import '@styles/main.css'; import '@styles/main.css';
import '@fontsource-variable/baskervville-sc'; import '@fontsource-variable/baskervville-sc';
import ThemeSwitcher from '@components/ThemeSwitcher.astro'; import ThemeSwitcher from '@components/ThemeSwitcher.astro';
export interface Props {
pageTitle: string,
}
const { pageTitle } = Astro.props;
--- ---
<html lang="en"> <html lang="en">
@@ -9,6 +15,8 @@ import ThemeSwitcher from '@components/ThemeSwitcher.astro';
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<title>{pageTitle}</title>
<!-- avoid FOUC by setting the color schme here in the header --> <!-- avoid FOUC by setting the color schme here in the header -->
<script> <script>
const explicitPref = localStorage.getItem('theme-preference'); const explicitPref = localStorage.getItem('theme-preference');

View File

@@ -5,7 +5,8 @@ import Post from '@components/Post.astro';
export async function getStaticPaths() { export async function getStaticPaths() {
const entries = await getCollection('posts'); const entries = await getCollection('posts');
entries.sort((a, b) => a.data.date.getTime() - b.data.date.getTime()) // unlike `/index` and `/posts`, we want to sort ascending by date here
entries.sort((a, b) => a.data.date.getTime() - b.data.date.getTime());
// for each route, the page gets passed the entry itself, plus the previous and next slugs // for each route, the page gets passed the entry itself, plus the previous and next slugs
// (if any), so that it can render links to them // (if any), so that it can render links to them
@@ -20,8 +21,9 @@ export async function getStaticPaths() {
} }
const { entry, prevSlug, nextSlug } = Astro.props;
--- ---
<BaseLayout> <BaseLayout pageTitle={entry.data.title}>
<Post {...Astro.props} /> <Post {entry} {prevSlug} {nextSlug} />
</BaseLayout> </BaseLayout>

24
src/pages/about.astro Normal file
View File

@@ -0,0 +1,24 @@
---
import BaseLayout from '@layouts/BaseLayout.astro';
---
<BaseLayout pageTitle="Joe's Blog - About">
<article class="prose">
<h1>About Me</h1>
<p>(Joe's wife wrote this because Joe feels weird writing about himself.)</p>
<p>Joe is a quirky, techy Tolkienite with a beautiful singing voice, an uncanny ability to do mental math, a bony, un-cuddleable frame, and a big mushy heart. He enjoys bike riding, computers, watching TV, reading about computers, playing Breath of the Wild, building computers, talking about something called "programming languages", and spending time with his family (which often involves fixing their computers). He graduated with a Liberal Arts degree from Thomas Aquinas College, the school of his forebears. He often remarks that he has greatly benefitted from the critical thinking skills he acquired at his alma mater in his current line of work.</p>
<p>He has spent, at the current time, about 2 years working on this blog. Most of his posts are about all of the work it took and everything he learned making this blog. Unlike most "bloggers", he has started with many blog posts and no blog, rather than a blog without posts. "Someday", he says, "I will actually get that blog up". I always nod encouragingly.</p>
<p>If you are reading this, then that day has arrived. We hope you enjoy it, and maybe even learn something along the way.</p>
</article>
</BaseLayout>
<style>
article {
max-width: var(--content-width);
margin: 0 auto;
}
</style>

View File

@@ -1,7 +1,15 @@
--- ---
import { getCollection } from 'astro:content';
import BaseLayout from '@layouts/BaseLayout.astro'; import BaseLayout from '@layouts/BaseLayout.astro';
import Post from '@components/Post.astro';
const entries = await getCollection('posts');
entries.sort((a, b) => b.data.date.getTime() - a.data.date.getTime());
// there will always be at leaste one entry
const entry = entries[0]!;
const prevSlug = entries[1] ? entries[1]?.id : null;
--- ---
<BaseLayout> <BaseLayout pageTitle={entry.data.title}>
<p>Index file</p> <Post {entry} {prevSlug} />
</BaseLayout> </BaseLayout>

64
src/pages/posts.astro Normal file
View File

@@ -0,0 +1,64 @@
---
import '@styles/prose.css';
import { getCollection, render } from 'astro:content';
import BaseLayout from '@layouts/BaseLayout.astro';
// return all posts
let entries = await getCollection('posts', ({ data }) => !data.draft || import.meta.env.PROD );
entries.sort((a, b) => b.data.date.getTime() - a.data.date.getTime());
const posts = await Promise.all(entries.map(async entry => {
const { remarkPluginFrontmatter } = await render(entry);
return { ...entry, remarkPluginFrontmatter };
}));
---
<BaseLayout pageTitle="Posts">
<div class="prose">
<h1>All Posts</h1>
<ul>
{posts.map( (p, idx) =>
<li>
{idx > 0 && <hr></hr>}
<h2>
<a href={`/${p.id}`} data-astro-prefetch>
{p.data.title}
</a>
</h2>
<p>
{p.remarkPluginFrontmatter.description}
</p>
</li>
)}
</ul>
</div>
</BaseLayout>
<style>
div.prose {
max-width: var(--content-width);
padding: var(--content-padding);
margin: 0 auto;
& h2 a {
color: var(--content-color);
}
}
h1 {
text-align: center;
margin-bottom: 2rem;
}
ul {
list-style: none;
margin-left: 0;
}
hr {
border: none;
height: 1px;
margin: 2.5rem 0;
background-color: var(--content-color);
}
</style>

33
src/plugins.ts Normal file
View File

@@ -0,0 +1,33 @@
import type { Root, Paragraph, PhrasingContent } from 'mdast';
import type { VFile } from 'vfile';
import { visit } from 'unist-util-visit';
import { toString } from 'mdast-util-to-string';
export function remarkDescription() {
return (tree: Root, vfile: VFile) => {
let description: string | null = null;
visit(tree, 'paragraph', (node: Paragraph) => {
if (description !== null) return;
description = summarize(node);
});
// Astro exposes this as `remarkPluginFrontmatter` from render()
const astro = (vfile.data.astro ??= { frontmatter: {} }) as { frontmatter: Record<string, unknown> };
astro.frontmatter.description = description;
};
}
/**
* Convert a paragraph node to a plain-text string, stripping any
* MDX JSX elements (e.g. <Sidenote>) so their content doesn't
* leak into the summary.
*/
function summarize(par: Paragraph): string {
const filtered = par.children.filter(
(child: PhrasingContent) => !(child.type as string).startsWith('mdxJsx')
);
return toString({ type: 'paragraph', children: filtered });
}