Adds notes.
This commit is contained in:
2153
package-lock.json
generated
2153
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
65
src/components/NoteHierarchy.astro
Normal file
65
src/components/NoteHierarchy.astro
Normal 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>
|
||||||
29
src/components/NotesLinks.astro
Normal file
29
src/components/NotesLinks.astro
Normal 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>
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
37
src/lib/hierarchy.ts
Normal 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, {});
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
36
src/pages/notes/[...slug].astro
Normal file
36
src/pages/notes/[...slug].astro
Normal 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>
|
||||||
Reference in New Issue
Block a user