statics, table of contents

This commit is contained in:
Theis Pieter Hollebeek 2025-10-13 13:23:05 +02:00
parent 6663c46f71
commit 544a9f3663
5 changed files with 140 additions and 88 deletions

70
docs/build.ts Normal file
View File

@ -0,0 +1,70 @@
import { Renderer } from "@libs/markdown";
import HighlightPlugin from "@libs/markdown/plugins/highlighting";
import { walk } from "@std/fs";
import * as pathTools from "@std/path";
function injectIntoTemplate(name: string, rendered: string): string {
let content = [
"<!DOCTYPE html>",
"<html>",
"<head>",
[
`<title>${name}</title>`,
'<script src="header_ids.js" defer></script>',
'<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/default.min.css">',
],
"</head>",
"<body>",
[
rendered,
],
"</body>",
"</html>",
];
while (content.some((x) => Array.isArray(x))) {
content = content.flat();
}
return content.join("");
}
async function renderMarkdown() {
const renderer = await Renderer.with({
plugins: [HighlightPlugin],
});
for await (
const entry of walk("src", { exts: [".md"], includeDirs: false })
) {
const parsed = pathTools.parse(entry.path);
parsed.dir = parsed.dir.replace(/^src[\\/]?/, "");
parsed.dir = pathTools.join("-", parsed.dir);
parsed.ext = ".html";
parsed.base = `${parsed.name}${parsed.ext}`;
await Deno.mkdir(parsed.dir, { recursive: true });
const content = await renderer.render(await Deno.readTextFile(entry.path));
await Deno.writeTextFile(
pathTools.format(parsed),
injectIntoTemplate(parsed.name, content),
);
}
}
async function copyStaticFiles() {
for await (const entry of walk("static", { includeDirs: false })) {
const parsed = pathTools.parse(entry.path);
parsed.dir = parsed.dir.replace(/^static[\\/]?/, "");
parsed.dir = pathTools.join("-", parsed.dir);
await Deno.mkdir(parsed.dir, { recursive: true });
await Deno.copyFile(entry.path, pathTools.format(parsed));
}
}
async function main() {
await renderMarkdown();
await copyStaticFiles();
}
if (import.meta.main) {
main();
}

View File

@ -7,5 +7,8 @@
"@libs/markdown": "jsr:@libs/markdown@^2.0.4",
"@std/fs": "jsr:@std/fs@^1.0.19",
"@std/path": "jsr:@std/path@^1.1.2"
},
"tasks": {
"build": "deno run -A build.ts"
}
}

View File

@ -1,86 +0,0 @@
import { Renderer } from "@libs/markdown";
import HighlightPlugin from "@libs/markdown/plugins/highlighting";
import { walk } from "@std/fs";
import * as pathTools from "@std/path";
function injectIntoTemplate(name: string, rendered: string): string {
let content = [
"<!DOCTYPE html>",
"<html>",
"<head>",
[
`<title>${name}</title>`,
],
"</head>",
"<body>",
[
rendered,
"<script>",
[
"function slugify(elements) {",
`return elements
.map((element, i) => {
if (i === 0) return null;
const content = element.textContent.trim();
if (content.startsWith("lib.")) {
const [name] = content.match(/^lib\\.[^\(]+/);
return name.trim();
}
return content;
})
.filter(x => x !== null)
.join("-");`,
"}",
'const headers = document.querySelectorAll("h1,h2,h3,h4,h5,h6");',
"const ancestors = [];",
"for (const header of headers) {",
[
"const depth = parseInt(header.tagName.slice(1));",
"while (ancestors.length >= depth) {",
[
"ancestors.pop();",
],
"}",
"ancestors.push(header);",
"if (depth > 1) header.id = slugify(ancestors);",
],
"}",
],
"</script>",
],
"</body>",
"</html>",
];
while (content.some((x) => Array.isArray(x))) {
content = content.flat();
}
return content.join("");
}
async function main() {
const renderer = await Renderer.with({
plugins: [HighlightPlugin],
});
for await (
const entry of walk("src", {
exts: [".md"],
})
) {
const parsed = pathTools.parse(entry.path);
parsed.dir = parsed.dir.replace(/^src[\\/]?/, "");
parsed.dir = pathTools.join("-", parsed.dir);
parsed.ext = ".html";
parsed.base = `${parsed.name}${parsed.ext}`;
await Deno.mkdir(parsed.dir, { recursive: true });
const content = await renderer.render(await Deno.readTextFile(entry.path));
await Deno.writeTextFile(
pathTools.format(parsed),
injectIntoTemplate(parsed.name, content),
);
}
}
if (import.meta.main) {
main();
}

View File

@ -1,5 +1,9 @@
# Gamelib
## Contents
<table-of-contents></table-of-contents>
## Models
### `Color`
@ -22,8 +26,8 @@ lib.drawRect(100, 0, 100, 100, lib.rgb(192, 127, 172));
A type of string.
Represents JavaScript's `KeyboardEvent.key`
([link](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key)) property.
Represents JavaScript's
[`KeyboardEvent.key`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key) property.
Example:

61
docs/static/header_ids.js vendored Normal file
View File

@ -0,0 +1,61 @@
function renderTableOfContents() {
const root = document.querySelector("table-of-contents");
const list = document.createElement("ul");
const headers = document.querySelectorAll("h2,h3,h4,h5,h6");
const ancestors = [];
for (const header of headers) {
const depth = parseInt(header.tagName.slice(1)) - 1;
while (ancestors.length >= depth) {
ancestors.pop();
}
const li = document.createElement("li");
const a = document.createElement("a");
a.textContent = header.textContent;
a.href = `#${header.id}`;
const ul = document.createElement("ul");
li.replaceChildren(a, ul);
const mostRecent = ancestors[ancestors.length - 1];
if (mostRecent === undefined) {
list.appendChild(li);
} else {
mostRecent.appendChild(li);
}
ancestors.push(ul);
}
root.replaceChildren(list);
}
function slugify(elements) {
console.log(elements);
return elements
.map((element, i) => {
if (i === 0) return null;
const content = element.textContent.trim();
if (content.startsWith("lib.")) {
const [name] = content.match(/^lib\.[^\(]+/);
return name.trim();
}
return content;
})
.filter((x) => x !== null)
.join("-");
}
function attachIdToHeaders() {
const headers = document.querySelectorAll("h1,h2,h3,h4,h5,h6");
const ancestors = [];
for (const header of headers) {
const depth = parseInt(header.tagName.slice(1));
while (ancestors.length >= depth) {
ancestors.pop();
}
ancestors.push(header);
if (depth > 1) header.id = slugify(ancestors);
}
}
attachIdToHeaders();
renderTableOfContents();