carlos / carloku Public

Clone
git clone https://amadan.net/carlos/carloku

Plain git — no account needed to clone.

Download

Download this file

1// πŸ€– Build-time only β€” the served site ships no JavaScript beyond site.js.
2import markdownIt from "markdown-it";
3import markdownItAnchor from "markdown-it-anchor";
4
5// slugify must match internal/docsite's Anchor in the platform repo
6// exactly, or an internal fragment link passes that repo's Go gate and
7// 404s in a browser. Both sides are deliberately plain β€” lowercase
8// alphanumerics, everything else a separator β€” because
9// markdown-it-anchor's own default URI-encodes punctuation, which would
10// anchor a heading naming `db.Open` differently on each side.
11// src/_data/docsanchors.json (vendored from docs/site/anchors.json) is
12// the contract both sides are checked against.
13export function slugify(text) {
14 let out = "";
15 let pendingSep = false;
16 for (const ch of String(text).toLowerCase()) {
17 if ((ch >= "a" && ch <= "z") || (ch >= "0" && ch <= "9")) {
18 if (pendingSep && out.length > 0) out += "-";
19 pendingSep = false;
20 out += ch;
21 } else {
22 pendingSep = true;
23 }
24 }
25 return out;
26}
27
28// fenceDelim/sameFence port internal/docsite/docsite.go's fence-matching
29// exactly, for docsSections below: a trimmed line opens or closes a
30// fence, and a closer only closes if it is the same character as the
31// opener and at least as long a run.
32function fenceDelim(line) {
33 let r = null;
34 if (line.startsWith("```")) r = "`";
35 else if (line.startsWith("~~~")) r = "~";
36 else return null;
37 let n = 0;
38 while (n < line.length && line[n] === r) n++;
39 return { mark: line.slice(0, n), info: line.slice(n).trim() };
40}
41
42function sameFence(closing, opening) {
43 if (!closing || !opening || closing[0] !== opening[0]) return false;
44 return closing.length >= opening.length;
45}
46
47export default function (eleventyConfig) {
48 eleventyConfig.addPassthroughCopy({ "src/index.html": "index.html" });
49 eleventyConfig.addPassthroughCopy({ "src/get-started": "get-started" });
50 eleventyConfig.addPassthroughCopy({ "src/site.css": "site.css" });
51 eleventyConfig.addPassthroughCopy({ "src/docs.css": "docs.css" });
52 eleventyConfig.addPassthroughCopy({ "src/site.js": "site.js" });
53 eleventyConfig.addPassthroughCopy({ "src/favicon.svg": "favicon.svg" });
54 eleventyConfig.addPassthroughCopy({ "src/fonts": "fonts" });
55 eleventyConfig.addPassthroughCopy({ "src/img": "img" });
56
57 const md = markdownIt({ html: true, linkify: false, typographer: false }).use(
58 markdownItAnchor,
59 { slugify, level: [2, 3, 4], tabIndex: false },
60 );
61 eleventyConfig.setLibrary("md", md);
62
63 // The docs corpus is authored here, by hand, under src/docs/ β€” there
64 // is no vendoring and no sync step. Pages are addressed by slug β€”
65 // "cli" β€” which is also how src/_data/docsnav.json (the corpus's
66 // hand-maintained table of contents) and every internal /docs link
67 // name them.
68 eleventyConfig.addCollection("docs", (api) =>
69 api
70 .getFilteredByGlob("src/docs/**/*.md")
71 .map((page) => {
72 const slug = page.filePathStem.replace(/^\/docs\//, "");
73 return { slug, page };
74 })
75 .filter(({ slug }) => slug !== "index"),
76 );
77
78 // The /docs index page's own prose, as its own collection.
79 //
80 // docs-index.njk renders this page's content above the cards. It must
81 // NOT reach for collections.all to find it: that collection contains
82 // docs-index.njk itself, and Eleventy rejects a template that reads
83 // templateContent from a collection it belongs to as a circular
84 // reference. A one-page collection it is not a member of is the way
85 // to read another template's rendered output.
86 eleventyConfig.addCollection("docsIndex", (api) =>
87 api.getFilteredByGlob("src/docs/index.md"),
88 );
89
90 // Section headings of one page, for the sidebar's sub-nav. Read from
91 // the source rather than the rendered HTML so it needs no DOM.
92 //
93 // Fence tracking is ported from internal/docsite/docsite.go's
94 // fenceDelim/sameFence in the platform repo, not invented here: a
95 // closing marker must match the opener's character and be at least as
96 // long, so a ``` block nested inside an outer ~~~ fence stays inside
97 // that outer fence instead of prematurely toggling out of it (which
98 // would both hide real "##" headings after the true close and expose
99 // fence-internal "##" lines as fake ones).
100 eleventyConfig.addFilter("docsSections", (raw) => {
101 const out = [];
102 let inFence = false;
103 let fenceMark = "";
104 for (const line of String(raw).split("\n")) {
105 const trimmed = line.trim();
106 const fd = fenceDelim(trimmed);
107 if (fd) {
108 if (!inFence) {
109 inFence = true;
110 fenceMark = fd.mark;
111 continue;
112 } else if (sameFence(fd.mark, fenceMark)) {
113 inFence = false;
114 continue;
115 }
116 // A fence-looking line that doesn't close the current fence
117 // (different character, or a shorter run) is body content of
118 // the still-open fence, same as the Go side.
119 }
120 if (inFence) continue;
121 const m = /^##\s+(.+?)\s*#*$/.exec(trimmed);
122 if (m) out.push({ title: m[1], anchor: slugify(m[1]) });
123 }
124 return out;
125 });
126
127 eleventyConfig.addFilter("docsSlugify", slugify);
128
129 // A page's own "# " title. The docs markdown carries no Eleventy front
130 // matter β€” it's plain prose with a leading "# " heading, authored that
131 // way on purpose so the corpus reads the same whether you're in an
132 // editor, on GitHub, or fetched raw via docsmd.njk β€” so the title is
133 // read from the source rather than declared twice.
134 eleventyConfig.addFilter("docsTitle", (raw) => {
135 for (const line of String(raw).split("\n")) {
136 const m = /^#\s+(.+?)\s*#*$/.exec(line);
137 if (m) return m[1].replace(/^πŸ€–\s*/, "");
138 }
139 return "";
140 });
141
142 // A page's raw markdown, looked up from the collection by slug.
143 //
144 // NOT passed through eleventyComputed: a computed value in Nunjucks
145 // front matter is a string interpolation, which HTML-escapes it, and
146 // an escaped apostrophe turns "carlos's" into "carlos-39-s" in every
147 // anchor derived from it. The headings themselves are fine β€”
148 // markdown-it-anchor reads the raw token β€” so the result was a
149 // sub-nav whose links pointed at ids no page had.
150 eleventyConfig.addFilter("docsRawFor", (docs, slug) => {
151 for (const doc of docs) {
152 if (doc.slug === slug) return doc.page.rawInput;
153 }
154 return "";
155 });
156
157 // A page's one-line blurb, from nav.json β€” the same string the /docs
158 // index renders on its card, reused as the meta description so the
159 // two cannot drift.
160 eleventyConfig.addFilter("docsBlurb", (nav, slug) => {
161 for (const section of nav.sections) {
162 for (const entry of section.pages) {
163 if (entry.slug === slug) return entry.blurb;
164 }
165 }
166 return "";
167 });
168
169 return {
170 // "html" is deliberately excluded: index.html and get-started/index.html
171 // are hand-written and must ship byte-identical. If html stayed a
172 // template format, Eleventy's default html engine (liquid) would
173 // render them as templates in addition to the passthrough copies
174 // above, and any literal "{{" in their inline SVG/script would break.
175 templateFormats: ["njk", "md"],
176 dir: { input: "src", includes: "_includes", data: "_data", output: "_site" },
177 };
178}
179