Astro garden skeleton: Quartz-compatible URLs, Obsidian markdown, odometer homepage
- glob content loader over the vault's Digital_Garden with verbatim-path ids - remark plugin: wikilinks, image embeds (wiki + relative md), callouts - URL parity with live Quartz sitemap verified 102/102 (built set is a strict superset: +30 synthetic folder listings) - KaTeX, lazy mermaid, shiki dual themes, quoted-string draft handling - copy-assets resolves embeds vault-wide (fixes live-site 404s for Blog Scratch/assets images) into flat /assets/ - homepage odometer widget: fetches api.c0smere.net baseline, ticks at lifetime average rate, fail-closed hidden on error - deploy/: nginx try_files config + compose (port 18100) + containerized build script for cyrion Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
---
|
||||
import { getCollection, render } from 'astro:content';
|
||||
import Base from '../layouts/Base.astro';
|
||||
import { slugForId, urlForId } from '../lib/quartz-compat.mjs';
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const entries = await getCollection('garden', (e) => !e.data.draft);
|
||||
const ids = new Set(entries.map((e) => e.id));
|
||||
|
||||
const paths = entries
|
||||
.filter((e) => e.id !== 'index')
|
||||
.map((e) => ({
|
||||
params: { slug: slugForId(e.id) },
|
||||
props: { entry: e, listing: null },
|
||||
}));
|
||||
|
||||
// synthetic listing pages for folders that have no index.md
|
||||
const folders = new Set<string>();
|
||||
for (const id of ids) {
|
||||
let d = id.includes('/') ? id.slice(0, id.lastIndexOf('/')) : '';
|
||||
while (d) {
|
||||
folders.add(d);
|
||||
d = d.includes('/') ? d.slice(0, d.lastIndexOf('/')) : '';
|
||||
}
|
||||
}
|
||||
for (const folder of folders) {
|
||||
if (ids.has(`${folder}/index`)) continue;
|
||||
const children = entries
|
||||
.filter((e) => {
|
||||
const dir = e.id.includes('/') ? e.id.slice(0, e.id.lastIndexOf('/')) : '';
|
||||
return dir === folder && !e.id.endsWith('/index');
|
||||
})
|
||||
.map((e) => ({
|
||||
title: e.data.title ?? e.id.split('/').pop(),
|
||||
url: urlForId(e.id),
|
||||
}));
|
||||
const subfolders = [...folders]
|
||||
.filter((f) => {
|
||||
const dir = f.includes('/') ? f.slice(0, f.lastIndexOf('/')) : '';
|
||||
return dir === folder;
|
||||
})
|
||||
.map((f) => ({
|
||||
title: f.split('/').pop(),
|
||||
url: '/' + slugForId(f) + '/',
|
||||
}));
|
||||
paths.push({
|
||||
params: { slug: slugForId(folder) + '/index' },
|
||||
props: {
|
||||
entry: null,
|
||||
listing: { name: folder.split('/').pop(), items: [...subfolders, ...children] },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
const { entry, listing } = Astro.props;
|
||||
const title = entry
|
||||
? (entry.data.title ?? entry.id.split('/').pop())
|
||||
: listing.name;
|
||||
const Content = entry ? (await render(entry)).Content : null;
|
||||
---
|
||||
|
||||
<Base title={title} description={entry?.data.description}>
|
||||
<h1>{title}</h1>
|
||||
{Content && <Content />}
|
||||
{
|
||||
listing && (
|
||||
<ul>
|
||||
{listing.items.map((item) => (
|
||||
<li>
|
||||
<a href={item.url}>{item.title}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
</Base>
|
||||
Reference in New Issue
Block a user