wip: inline diagram SVG
1
bun.lock
@@ -30,6 +30,7 @@
|
||||
"eslint-plugin-astro": "^1.5.0",
|
||||
"prettier": "^3.7.4",
|
||||
"prettier-plugin-astro": "^0.14.1",
|
||||
"svgo": "^4.0.0",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.52.0",
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
document.documentElement.classList.add("dark");
|
||||
}
|
||||
})();
|
||||
</script><link rel="stylesheet" href="/_astro/index.D1CCjkm0.css"></head> <body class="min-h-screen"> <div class="flex flex-col items-center justify-center min-h-screen p-8"> <div class="text-center"> <h1 class="text-[8rem] sm:text-[12rem] font-display font-bold leading-none
|
||||
</script><link rel="stylesheet" href="/_astro/index.fpsHJ0rs.css"></head> <body class="min-h-screen"> <div class="flex flex-col items-center justify-center min-h-screen p-8"> <div class="text-center"> <h1 class="text-[8rem] sm:text-[12rem] font-display font-bold leading-none
|
||||
text-gray-300 dark:text-neutral-700">
|
||||
404
|
||||
</h1> <p class="text-xl mb-2 text-gray-600 dark:text-neutral-400">
|
||||
|
||||
1
docs/_astro/index.fpsHJ0rs.css
Normal file
|
Before Width: | Height: | Size: 18 KiB |
@@ -8,7 +8,7 @@
|
||||
document.documentElement.classList.add("dark");
|
||||
}
|
||||
})();
|
||||
</script><link rel="stylesheet" href="/_astro/index.D1CCjkm0.css">
|
||||
</script><link rel="stylesheet" href="/_astro/index.fpsHJ0rs.css">
|
||||
<style>.dark .shiki{color:var(--shiki-dark)!important;background-color:var(--shiki-dark-bg)!important}.dark .shiki span{color:var(--shiki-dark)!important}
|
||||
</style></head> <body class="min-h-screen"> <header class="sticky top-0 z-50 border-b
|
||||
border-gray-200 dark:border-neutral-800
|
||||
|
||||
|
Before Width: | Height: | Size: 18 KiB |
@@ -8,7 +8,7 @@
|
||||
document.documentElement.classList.add("dark");
|
||||
}
|
||||
})();
|
||||
</script><link rel="stylesheet" href="/_astro/index.D1CCjkm0.css">
|
||||
</script><link rel="stylesheet" href="/_astro/index.fpsHJ0rs.css">
|
||||
<style>.dark .shiki{color:var(--shiki-dark)!important;background-color:var(--shiki-dark-bg)!important}.dark .shiki span{color:var(--shiki-dark)!important}
|
||||
</style></head> <body class="min-h-screen"> <header class="sticky top-0 z-50 border-b
|
||||
border-gray-200 dark:border-neutral-800
|
||||
|
||||
|
Before Width: | Height: | Size: 18 KiB |
@@ -8,7 +8,7 @@
|
||||
document.documentElement.classList.add("dark");
|
||||
}
|
||||
})();
|
||||
</script><link rel="stylesheet" href="/_astro/index.D1CCjkm0.css">
|
||||
</script><link rel="stylesheet" href="/_astro/index.fpsHJ0rs.css">
|
||||
<style>.dark .shiki{color:var(--shiki-dark)!important;background-color:var(--shiki-dark-bg)!important}.dark .shiki span{color:var(--shiki-dark)!important}
|
||||
</style></head> <body class="min-h-screen"> <header class="sticky top-0 z-50 border-b
|
||||
border-gray-200 dark:border-neutral-800
|
||||
|
||||
|
Before Width: | Height: | Size: 18 KiB |
@@ -8,7 +8,7 @@
|
||||
document.documentElement.classList.add("dark");
|
||||
}
|
||||
})();
|
||||
</script><link rel="stylesheet" href="/_astro/index.D1CCjkm0.css">
|
||||
</script><link rel="stylesheet" href="/_astro/index.fpsHJ0rs.css">
|
||||
<style>.dark .shiki{color:var(--shiki-dark)!important;background-color:var(--shiki-dark-bg)!important}.dark .shiki span{color:var(--shiki-dark)!important}
|
||||
</style></head> <body class="min-h-screen"> <header class="sticky top-0 z-50 border-b
|
||||
border-gray-200 dark:border-neutral-800
|
||||
|
||||
|
Before Width: | Height: | Size: 18 KiB |
@@ -8,7 +8,7 @@
|
||||
document.documentElement.classList.add("dark");
|
||||
}
|
||||
})();
|
||||
</script><link rel="stylesheet" href="/_astro/index.D1CCjkm0.css">
|
||||
</script><link rel="stylesheet" href="/_astro/index.fpsHJ0rs.css">
|
||||
<style>.dark .shiki{color:var(--shiki-dark)!important;background-color:var(--shiki-dark-bg)!important}.dark .shiki span{color:var(--shiki-dark)!important}
|
||||
</style></head> <body class="min-h-screen"> <header class="sticky top-0 z-50 border-b
|
||||
border-gray-200 dark:border-neutral-800
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
"eslint-plugin-astro": "^1.5.0",
|
||||
"prettier": "^3.7.4",
|
||||
"prettier-plugin-astro": "^0.14.1",
|
||||
"svgo": "^4.0.0",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.52.0"
|
||||
|
||||
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 18 KiB |
@@ -9,6 +9,7 @@ import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import * as semver from "semver";
|
||||
import { $ } from "bun";
|
||||
import { optimize as optimizeSvg } from "svgo";
|
||||
import { config } from "../src/config";
|
||||
|
||||
const updateConfig = {
|
||||
@@ -18,7 +19,6 @@ version: {{version}}
|
||||
---
|
||||
{{content}}`,
|
||||
outputDir: "src/content/spec",
|
||||
publicDir: "public/spec",
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -119,22 +119,20 @@ function removeStaleSpecs(versionsToKeep: string[]): void {
|
||||
const keepSet = new Set(versionsToKeep);
|
||||
let removedAny = false;
|
||||
|
||||
for (const dir of [updateConfig.outputDir, updateConfig.publicDir]) {
|
||||
if (!fs.existsSync(dir)) continue;
|
||||
if (!fs.existsSync(updateConfig.outputDir)) return;
|
||||
|
||||
const files = fs.readdirSync(dir);
|
||||
for (const file of files) {
|
||||
// Extract version from filename (e.g., "1.0.0-rc.1.md" -> "1.0.0-rc.1")
|
||||
const version = path.basename(file, path.extname(file));
|
||||
if (!keepSet.has(version)) {
|
||||
if (!removedAny) {
|
||||
console.log("\nRemoving stale spec files:");
|
||||
removedAny = true;
|
||||
}
|
||||
const filePath = path.join(dir, file);
|
||||
fs.unlinkSync(filePath);
|
||||
console.log(` ${filePath}`);
|
||||
const files = fs.readdirSync(updateConfig.outputDir);
|
||||
for (const file of files) {
|
||||
// Extract version from filename (e.g., "1.0.0-rc.1.md" -> "1.0.0-rc.1")
|
||||
const version = path.basename(file, path.extname(file));
|
||||
if (!keepSet.has(version)) {
|
||||
if (!removedAny) {
|
||||
console.log("\nRemoving stale spec files:");
|
||||
removedAny = true;
|
||||
}
|
||||
const filePath = path.join(updateConfig.outputDir, file);
|
||||
fs.unlinkSync(filePath);
|
||||
console.log(` ${filePath}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,10 +209,11 @@ async function main(): Promise<void> {
|
||||
const mdPath = path.join(updateConfig.outputDir, `${version}.md`);
|
||||
writeFile(mdPath, spec.body);
|
||||
|
||||
// Write SVG diagram to public directory
|
||||
// Write SVG diagram next to markdown (with metadata stripped)
|
||||
if (spec.diagram) {
|
||||
const svgPath = path.join(updateConfig.publicDir, `${version}.svg`);
|
||||
writeFile(svgPath, spec.diagram);
|
||||
const svgPath = path.join(updateConfig.outputDir, `${version}.svg`);
|
||||
const optimizedSvg = optimizeSvg(spec.diagram).data;
|
||||
writeFile(svgPath, optimizedSvg);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error processing version ${version}:`, error);
|
||||
|
||||
@@ -7,10 +7,10 @@ import { config } from "../config";
|
||||
interface Props {
|
||||
version: string;
|
||||
versions: string[];
|
||||
svgPath: string;
|
||||
svgContent?: string | null;
|
||||
}
|
||||
|
||||
const { version, versions, svgPath } = Astro.props;
|
||||
const { version, versions, svgContent } = Astro.props;
|
||||
|
||||
const navItems = [
|
||||
{ id: "about", label: "About" },
|
||||
@@ -112,20 +112,23 @@ const secondaryClasses = `text-gray-600 dark:text-neutral-400
|
||||
</div>
|
||||
|
||||
<!-- SVG Diagram -->
|
||||
<div
|
||||
class="animate-fade-in-up delay-300
|
||||
relative mx-auto mb-12 py-8 px-6 sm:py-16 sm:px-14
|
||||
bg-white dark:bg-neutral-900
|
||||
rounded-2xl shadow-lg dark:shadow-none
|
||||
border border-gray-200 dark:border-neutral-800"
|
||||
>
|
||||
<img
|
||||
src={svgPath}
|
||||
alt="Git Common-Flow diagram"
|
||||
class="w-full h-auto max-w-3xl mx-auto
|
||||
dark:invert dark:hue-rotate-180 dark:contrast-90"
|
||||
/>
|
||||
</div>
|
||||
{
|
||||
svgContent && (
|
||||
<div
|
||||
class="animate-fade-in-up delay-300
|
||||
relative mx-auto mb-12 py-8 px-6 sm:py-16 sm:px-14
|
||||
bg-white dark:bg-neutral-900
|
||||
rounded-2xl shadow-lg dark:shadow-none
|
||||
border border-gray-200 dark:border-neutral-800"
|
||||
>
|
||||
<div
|
||||
class="w-full max-w-3xl mx-auto [&>svg]:w-full [&>svg]:h-auto
|
||||
dark:invert dark:hue-rotate-180 dark:contrast-90"
|
||||
set:html={svgContent}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<!-- Navigation links -->
|
||||
<nav
|
||||
|
||||
1
src/content/spec/1.0.0-rc.1.svg
Normal file
|
After Width: | Height: | Size: 13 KiB |
1
src/content/spec/1.0.0-rc.2.svg
Normal file
|
After Width: | Height: | Size: 13 KiB |
1
src/content/spec/1.0.0-rc.3.svg
Normal file
|
After Width: | Height: | Size: 13 KiB |
1
src/content/spec/1.0.0-rc.4.svg
Normal file
|
After Width: | Height: | Size: 13 KiB |
1
src/content/spec/1.0.0-rc.5.svg
Normal file
|
After Width: | Height: | Size: 13 KiB |
@@ -28,14 +28,25 @@ const content = fs.readFileSync(filePath, "utf-8");
|
||||
const markdown = content.replace(/^---[\s\S]*?---\n/, "");
|
||||
|
||||
// Parse the content into sections (handles markdown -> HTML internally)
|
||||
const parsed = await parseSpecContent(markdown, version);
|
||||
const parsed = await parseSpecContent(markdown);
|
||||
|
||||
// Read SVG content for inline embedding
|
||||
const svgFilePath = path.join(process.cwd(), "src/content/spec", `${version}.svg`);
|
||||
let svgContent: string | null = null;
|
||||
if (fs.existsSync(svgFilePath)) {
|
||||
svgContent = fs.readFileSync(svgFilePath, "utf-8");
|
||||
}
|
||||
---
|
||||
|
||||
<BaseLayout title={spec.data.title}>
|
||||
<Header version={version} versions={versions} />
|
||||
|
||||
<main>
|
||||
<Hero version={version} versions={versions} svgPath={parsed.svgPath} />
|
||||
<Hero
|
||||
version={version}
|
||||
versions={versions}
|
||||
svgContent={svgContent}
|
||||
/>
|
||||
|
||||
<AboutSection
|
||||
introduction={parsed.introduction}
|
||||
|
||||
@@ -32,7 +32,6 @@ export interface SpecSection {
|
||||
}
|
||||
|
||||
export interface ParsedSpec {
|
||||
svgPath: string;
|
||||
introduction: string;
|
||||
summary: string;
|
||||
terminology: string;
|
||||
@@ -340,10 +339,7 @@ function buildTocItems(parsed: Partial<ParsedSpec>): TocItem[] {
|
||||
*/
|
||||
export async function parseSpecContent(
|
||||
markdown: string,
|
||||
version: string,
|
||||
): Promise<ParsedSpec> {
|
||||
const svgPath = `/spec/${version}.svg`;
|
||||
|
||||
// Parse markdown to AST
|
||||
const tree = unified().use(remarkParse).parse(markdown) as Root;
|
||||
|
||||
@@ -423,7 +419,6 @@ export async function parseSpecContent(
|
||||
}));
|
||||
|
||||
const parsed: ParsedSpec = {
|
||||
svgPath,
|
||||
introduction,
|
||||
summary,
|
||||
terminology,
|
||||
|
||||