Compare commits
5 Commits
485e3a0a91
...
3f24f42c1f
| Author | SHA1 | Date | |
|---|---|---|---|
| 3f24f42c1f | |||
| bfdaac8095 | |||
| e02af4c552 | |||
| fd8ed38572 | |||
| 5ffb51d5ce |
@@ -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',
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -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
24
src/pages/about.astro
Normal 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>
|
||||||
@@ -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
64
src/pages/posts.astro
Normal 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
33
src/plugins.ts
Normal 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 });
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user