Adds notes.

This commit is contained in:
2026-04-21 15:39:16 -07:00
parent 1e5d1f13e8
commit 81c912e3ff
14 changed files with 1525 additions and 830 deletions

2153
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -16,6 +16,7 @@
"@types/react": "^19.1.7", "@types/react": "^19.1.7",
"@types/react-dom": "^19.1.6", "@types/react-dom": "^19.1.6",
"astro": "^5.9.2", "astro": "^5.9.2",
"astro-loader-obsidian": "^0.10.0",
"react": "^19.1.0", "react": "^19.1.0",
"react-dom": "^19.1.0", "react-dom": "^19.1.0",
"sharp": "^0.34.2" "sharp": "^0.34.2"

View File

@@ -18,6 +18,5 @@ const { date } = Astro.props;
<style> <style>
time { time {
font-family: "Fira Code", monospace; font-family: "Fira Code", monospace;
font-size: var(--font-size-sm);
} }
</style> </style>

View File

@@ -33,7 +33,7 @@ import Blazestar from './Blazestar.astro';
</div> </div>
</nav> </nav>
<div class="h-card"> <div class="h-card">
<img class="u-photo" src="/public/photos/Bingley Business.jpg" /> <img class="u-photo" src="/photos/Bingley Business.jpg" />
<a class="p-name u-url" href="https://www.blazestar.net">Drew Haven</a> <a class="p-name u-url" href="https://www.blazestar.net">Drew Haven</a>
a.k.a. <span class="p-nickname">Periodic</span> a.k.a. <span class="p-nickname">Periodic</span>
</div> </div>

View File

@@ -0,0 +1,65 @@
---
import { type Hierarchy } from "../lib/hierarchy.ts";
interface Props {
prefix: string;
hierarchy: Hierarchy;
}
const { prefix, hierarchy } = Astro.props;
const pathname = Astro.url.pathname.replace(import.meta.env.BASE_URL, '/');
---
{
Object.entries(hierarchy).map(([leader, node]) => {
const notePath = `${prefix}/${leader}`;
const isActive = pathname === notePath;
const linkClasses = isActive ? "active" : "";
if (node.children) {
return (
<details open={pathname.startsWith(notePath) && "true"}>
<summary>
{ node.id
? <a href={notePath} class={linkClasses}>{node.title || leader}</a>
: <span class="header-only">{node.title || leader}</span>
}
</summary>
<div class="child-notes">
{ <Astro.self prefix={notePath} hierarchy={node.children} /> }
</div>
</details>
);
}
return (
<div>
<a href={notePath} class={linkClasses}>
{node.title || leader}
</a>
</div>
);
})
}
<style>
.active {
font-weight: bold;
}
.child-notes {
margin-left: 1em;
}
summary {
.header-only {
color: var(--color-gray)
}
&:hover {
color: var(--color-gold);
}
}
</style>

View File

@@ -0,0 +1,29 @@
---
import { makeHierarchy } from '../lib/hierarchy';
import { getCollection } from 'astro:content';
import NotchedBox from './NotchedBox.astro';
import NoteHierarchy from './NoteHierarchy.astro';
const notes = await getCollection('notes');
const hierarchy = makeHierarchy(notes.map(note => [note.id, note.data.title]));
const pathname = Astro.url.pathname.replace(import.meta.env.BASE_URL, '');
const isActive = pathname.startsWith("notes");
---
<NotchedBox fillNotches={isActive ? 'left' : 'none'}>
<div class="notes-header">Notes</div>
<div class="note-links">
<NoteHierarchy prefix="/notes" hierarchy={hierarchy} />
</div>
</NotchedBox>
<style>
.notes-header {
color: var(--color-light-text);
font-size: var(--font-size-lg);
}
.note-links {
margin-left: 8px;
}
</style>

View File

@@ -1,6 +1,7 @@
--- ---
import SidebarLink from './SidebarLink.astro'; import SidebarLink from './SidebarLink.astro';
import Blazestar from './Blazestar.astro'; import Blazestar from './Blazestar.astro';
import NotesLinks from './NotesLinks.astro';
--- ---
<header> <header>
@@ -10,6 +11,7 @@ import Blazestar from './Blazestar.astro';
<SidebarLink href="/">Home</SidebarLink> <SidebarLink href="/">Home</SidebarLink>
<SidebarLink href="/blog">Blog</SidebarLink> <SidebarLink href="/blog">Blog</SidebarLink>
<SidebarLink href="/about">About</SidebarLink> <SidebarLink href="/about">About</SidebarLink>
<NotesLinks />
</div> </div>
<div class="social-links"> <div class="social-links">
<a href="https://mastodon.social/@periodic" target="_blank" rel="me"> <a href="https://mastodon.social/@periodic" target="_blank" rel="me">
@@ -57,13 +59,12 @@ import Blazestar from './Blazestar.astro';
gap: 16px; gap: 16px;
a { a {
color: var(--color-light-text); color: var(--color-light-text);
border-bottom: 4px solid transparent;
text-decoration: none; text-decoration: none;
&:hover { &:hover {
color: var(--color-accent); color: var(--color-accent);
border-bottom: 4px solid transparent;
text-decoration: none;
} }
&.active { &.active {
text-decoration: none; text-decoration: none;
} }

View File

@@ -1,3 +1,7 @@
import {
ObsidianDocumentSchema,
ObsidianMdLoader,
} from "astro-loader-obsidian";
import { glob } from "astro/loaders"; import { glob } from "astro/loaders";
import { defineCollection, z } from "astro:content"; import { defineCollection, z } from "astro:content";
@@ -26,4 +30,15 @@ const page = defineCollection({
}), }),
}); });
export const collections = { blog, page }; const notes = defineCollection({
loader: ObsidianMdLoader({
base: "src/content/notes",
url: "notes",
}),
schema: ({ image }) =>
ObsidianDocumentSchema.extend({
image: image().optional(),
}),
});
export const collections = { blog, page, notes };

View File

@@ -62,6 +62,7 @@ const posts = await getPublishedPosts( selectedTag );
} }
.date { .date {
color: var(--color-gray); color: var(--color-gray);
font-size: var(--font-size-sm);
} }
.entry { .entry {
padding: 0.5em; padding: 0.5em;

View File

@@ -26,6 +26,10 @@ const { slug, title, description, pubDate, updatedDate, heroImage, tags } = Astr
font-size: 80%; font-size: 80%;
} }
.dt-published {
font-size: var(--font-size-sm);
}
.tags { .tags {
display: flex; display: flex;
flex-direction: row; flex-direction: row;

37
src/lib/hierarchy.ts Normal file
View File

@@ -0,0 +1,37 @@
export interface HierarchyNode {
id: string | null;
title: string | null;
children: Hierarchy | null;
}
export interface Hierarchy extends Record<string, HierarchyNode> {}
function addToHierarchy(
tree: Hierarchy,
[noteId, title]: [string, string],
): Hierarchy {
const path = noteId.split("/");
if (path.length === 0) {
return tree;
}
if (!tree[path[0]]) {
tree[path[0]] = { id: null, title: null, children: null };
}
let curr = tree[path[0]];
for (const node of path.slice(1)) {
curr.children ||= {};
if (!curr.children[node]) {
curr.children[node] = { id: null, title: null, children: null };
}
curr = curr.children[node];
}
curr.id = noteId;
curr.title = title;
return tree;
}
export function makeHierarchy(notes: Array<[string, string]>): Hierarchy {
return notes.reduce(addToHierarchy, {});
}

View File

@@ -15,6 +15,6 @@ const { Content } = await render(about);
<RootLayout> <RootLayout>
<NotchedBox fillNotches="left"> <NotchedBox fillNotches="left">
<h1>{about.data.title}</h1> <h1>{about.data.title}</h1>
<Content /> <Content />
</NotchedBox> </NotchedBox>
</RootLayout> </RootLayout>

View File

@@ -0,0 +1,36 @@
---
import RootLayout from '../../layouts/RootLayout.astro';
import NotchedBox from '../../components/NotchedBox.astro';
import FormattedDate from '../../components/FormattedDate.astro';
import { type CollectionEntry, getCollection } from 'astro:content';
import { render } from 'astro:content';
export async function getStaticPaths() {
const notes = await getCollection('notes');
return notes.map((note) => ({
params: { slug: note.id },
props: note,
}));
}
type Props = CollectionEntry<'notes'>;
const note = Astro.props;
const { Content } = await render(note);
---
<RootLayout>
<NotchedBox fillNotches="left">
<h1>{note.data.title}</h1>
<div class="last-updated">
Last updated <FormattedDate date={note.data.updated} />
</div>
<Content />
</NotchedBox>
</RootLayout>
<style>
.last-updated {
font-size: var(--font-size-sm);
color: var(--color-gray);
}
</style>