@suzumiyaaoba/mdxr 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +719 -214
- package/dist/components.d.mts +6 -6
- package/dist/components.mjs +1 -1
- package/dist/{config-DhBFpSdB.mjs → config-DQeZaek7.mjs} +1 -1
- package/dist/index.mjs +2 -2
- package/dist/{ui-DOxuV3n8.mjs → ui-D89i0Cq-.mjs} +394 -299
- package/package.json +1 -1
- package/src/ascii/ast.ts +129 -92
- package/src/ascii/charts.ts +2 -3
- package/src/ascii/document.ts +2 -4
- package/src/ascii/extended.ts +7 -4
- package/src/ascii/glyphs.ts +3 -1
- package/src/ascii/investigation.ts +38 -36
- package/src/ascii/output.ts +12 -4
- package/src/ascii/parts.ts +12 -1
- package/src/ascii/planning.ts +151 -87
- package/src/ascii/reports.ts +29 -23
- package/src/ascii/shadcn.ts +26 -32
- package/src/catalog.ts +60 -44
- package/src/client/doc-events.ts +12 -13
- package/src/components/ui/calendar.tsx +1 -0
- package/src/components/ui/carousel.tsx +39 -17
- package/src/components/ui/chart.tsx +2 -1
- package/src/components/ui/pagination.tsx +0 -1
- package/src/components/ui/slider.tsx +3 -6
- package/src/components/ui/tabs.tsx +1 -0
- package/src/components/ui/toc.tsx +560 -0
- package/src/components/ui/toggle-group.tsx +6 -3
- package/src/extended/data.ts +6 -4
- package/src/extended/diagrams.ts +3 -2
- package/src/extended/differences.ts +13 -9
- package/src/extended/plots.ts +36 -16
- package/src/extended/profiles.ts +3 -1
- package/src/extended/reports.ts +10 -5
- package/src/html.ts +20 -0
- package/src/hydrate/import-scan.ts +9 -6
- package/src/hydrate/runtime-module.ts +1 -0
- package/src/hydrate.ts +5 -0
- package/src/lines.ts +5 -1
- package/src/mdx.ts +4 -0
- package/src/page-toc-js.ts +46 -0
- package/src/page-toc-runtime.ts +22 -0
- package/src/remark/headings.ts +23 -1
- package/src/render.ts +51 -0
- package/src/ui/attrs.ts +5 -2
- package/src/ui/bar-chart.tsx +11 -18
- package/src/ui/bridge.tsx +15 -15
- package/src/ui/chart.ts +113 -34
- package/src/ui/children.ts +21 -1
- package/src/ui/comments.tsx +9 -8
- package/src/ui/data-context.ts +2 -1
- package/src/ui/data-table.tsx +3 -2
- package/src/ui/data-view.tsx +2 -1
- package/src/ui/diff-parse.ts +19 -1
- package/src/ui/diffstat.tsx +6 -5
- package/src/ui/document-inputs.tsx +13 -10
- package/src/ui/document-media.tsx +3 -2
- package/src/ui/document-tabs.tsx +4 -3
- package/src/ui/file-icon.ts +1 -1
- package/src/ui/funnel.tsx +12 -12
- package/src/ui/gantt.tsx +24 -9
- package/src/ui/graph.tsx +1 -1
- package/src/ui/line-chart.tsx +14 -17
- package/src/ui/matrix.tsx +16 -7
- package/src/ui/page-toc.tsx +54 -0
- package/src/ui/pie-chart.tsx +12 -12
- package/src/ui/props.tsx +15 -13
- package/src/ui/quadrant.tsx +8 -16
- package/src/ui/radar.tsx +9 -12
- package/src/ui/release.tsx +11 -5
- package/src/ui/sankey.tsx +4 -4
- package/src/ui/scatter.tsx +14 -17
- package/src/ui/series.tsx +2 -3
- package/src/ui/spark.tsx +2 -13
- package/src/ui/summary.tsx +1 -1
- package/src/ui/table-controls.tsx +9 -8
- package/src/ui/tests.tsx +4 -4
- package/src/ui/treemap.tsx +17 -17
package/dist/cli.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { Ap as
|
|
3
|
-
import { a as loadUserModule, c as srcDir, i as importBundledCode, n as loadConfig, o as resolveModuleEntry, r as SHARED_PACKAGES, s as pkgRoot } from "./config-
|
|
2
|
+
import { Ap as parseLineRange, Bp as urlScheme, Fp as isComponent, Gd as reportModel, Ip as isRecord, Jf as isStatus, Kd as comparisonModel, Kf as STATUSES, Lp as nonEmpty, Mp as SKIP_LANGS, Op as fenceFilename, Pp as langForPath, Rp as own, Tp as DocContext, V as shadcnModules, Vd as calculate, Wd as REPORT_SPECS, _l as PLOT_NAMES, _p as numOf, af as display, ap as numList, cf as parseJson, dc as queryPlanModel, df as tableRecords, fc as diagramModel, gp as attrTrue, if as columnsOf, jl as PlanHeader, jp as splitPathLines, kp as fenceLang, lc as bundleModel, lf as readRecords, of as isDataRecord, op as textList, pc as flameModel, qd as schemaRows, sf as numberValue, t as builtinComponents, uc as dependencyMatrixModel, uf as records, vl as plotModel, wp as takeUsedIcons, xf as parseDiff, zp as safeHref } from "./ui-D89i0Cq-.mjs";
|
|
3
|
+
import { a as loadUserModule, c as srcDir, i as importBundledCode, n as loadConfig, o as resolveModuleEntry, r as SHARED_PACKAGES, s as pkgRoot } from "./config-DQeZaek7.mjs";
|
|
4
4
|
import module from "node:module";
|
|
5
5
|
import fsp, { access, readFile, readdir, writeFile } from "node:fs/promises";
|
|
6
6
|
import path from "node:path";
|
|
@@ -23,15 +23,18 @@ import { visit } from "unist-util-visit";
|
|
|
23
23
|
import fs, { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
24
24
|
import { visitParents } from "unist-util-visit-parents";
|
|
25
25
|
import * as valibot from "valibot";
|
|
26
|
-
import { Fragment, createElement } from "react";
|
|
26
|
+
import { Fragment, createContext, createElement, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
27
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
27
28
|
import { icons } from "@iconify-json/lucide";
|
|
28
29
|
import { icons as icons$1 } from "@iconify-json/vscode-icons";
|
|
30
|
+
import { differenceInCalendarDays, isValid, parseISO } from "date-fns";
|
|
31
|
+
import { cn } from "cn";
|
|
29
32
|
import { build } from "esbuild";
|
|
30
33
|
import os from "node:os";
|
|
31
34
|
import { spawn } from "node:child_process";
|
|
32
35
|
import { once } from "node:events";
|
|
33
|
-
import { compile } from "@mdx-js/mdx";
|
|
34
36
|
import { renderToStaticMarkup, renderToString } from "react-dom/server";
|
|
37
|
+
import { compile } from "@mdx-js/mdx";
|
|
35
38
|
import rehypeKatex from "rehype-katex";
|
|
36
39
|
import { transformerMetaHighlight, transformerMetaWordHighlight, transformerNotationDiff, transformerNotationErrorLevel, transformerNotationFocus, transformerNotationHighlight, transformerNotationWordHighlight, transformerRemoveNotationEscape } from "@shikijs/transformers";
|
|
37
40
|
import { createHighlighter } from "shiki";
|
|
@@ -1177,8 +1180,11 @@ const toList = (items) => ({
|
|
|
1177
1180
|
* 2. `<Toc>` elements (`:::toc` included) get a nested link list injected as
|
|
1178
1181
|
* children — `depth`/`min` attributes bound the heading levels included
|
|
1179
1182
|
* (defaults: h2–h3, i.e. min=2 depth=3).
|
|
1183
|
+
*
|
|
1184
|
+
* The flat heading list is also left on `file.data.mdxrHeadings` for the
|
|
1185
|
+
* page-level sidebar ToC.
|
|
1180
1186
|
*/
|
|
1181
|
-
const remarkMdxrHeadings = () => (tree) => {
|
|
1187
|
+
const remarkMdxrHeadings = () => (tree, file) => {
|
|
1182
1188
|
const counts = /* @__PURE__ */ new Map();
|
|
1183
1189
|
const used = /* @__PURE__ */ new Set();
|
|
1184
1190
|
const headings = [];
|
|
@@ -1209,6 +1215,11 @@ const remarkMdxrHeadings = () => (tree) => {
|
|
|
1209
1215
|
const items = headings.filter((h) => h.depth >= minDepth && h.depth <= maxDepth && h.text !== "");
|
|
1210
1216
|
node.children = items.length === 0 ? [] : [toList(nest(items))];
|
|
1211
1217
|
});
|
|
1218
|
+
file.data.mdxrHeadings = headings.filter((h) => h.text !== "").map(({ depth, slug, text }) => ({
|
|
1219
|
+
depth,
|
|
1220
|
+
slug,
|
|
1221
|
+
text
|
|
1222
|
+
}));
|
|
1212
1223
|
};
|
|
1213
1224
|
//#endregion
|
|
1214
1225
|
//#region src/remark/no-js.ts
|
|
@@ -1483,6 +1494,8 @@ const flag = (node, name) => {
|
|
|
1483
1494
|
for (const a of node.attributes) if (isRecord(a) && a.name === name) return a.value === null || a.value === true || attrTrue(a.value);
|
|
1484
1495
|
return false;
|
|
1485
1496
|
};
|
|
1497
|
+
/** Attribute explicitly set to the string `"false"` (open="false" opt-outs). */
|
|
1498
|
+
const flagOff = (node, name) => attr(node, name) === "false";
|
|
1486
1499
|
/** Numeric part of an attribute — `"120ms"` → 120. */
|
|
1487
1500
|
const num = (node, name) => numOf(attr(node, name));
|
|
1488
1501
|
/** Comma-separated attribute list (`labels="a,b"`). */
|
|
@@ -1501,36 +1514,29 @@ const hoistable = (c) => c.type === "paragraph" && isParent(c) && c.children.eve
|
|
|
1501
1514
|
* elements hoisted out of element-only paragraphs (see `hoistable`).
|
|
1502
1515
|
*/
|
|
1503
1516
|
const els = (node, name) => {
|
|
1504
|
-
|
|
1505
|
-
for (const c of node.children ?? []) {
|
|
1506
|
-
if (isMdxEl(c)) {
|
|
1507
|
-
if (name === void 0 || c.name === name) out.push(c);
|
|
1508
|
-
continue;
|
|
1509
|
-
}
|
|
1510
|
-
if (hoistable(c)) {
|
|
1511
|
-
for (const cc of c.children) if (isMdxEl(cc) && (name === void 0 || cc.name === name)) out.push(cc);
|
|
1512
|
-
}
|
|
1513
|
-
}
|
|
1514
|
-
return out;
|
|
1517
|
+
return (node.children ?? []).flatMap((c) => hoistable(c) ? c.children : [c]).filter((c) => isMdxEl(c) && (name === void 0 || c.name === name));
|
|
1515
1518
|
};
|
|
1516
1519
|
/**
|
|
1517
1520
|
* `node` minus the named JSX children — direct elements removed, matching
|
|
1518
1521
|
* elements stripped out of paragraphs (now-empty paragraphs dropped).
|
|
1519
1522
|
*/
|
|
1520
|
-
const withoutEls = (node, names) =>
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1523
|
+
const withoutEls = (node, names) => {
|
|
1524
|
+
const removed = new Set(names);
|
|
1525
|
+
return {
|
|
1526
|
+
...node,
|
|
1527
|
+
children: (node.children ?? []).flatMap((c) => {
|
|
1528
|
+
if (isMdxEl(c)) return removed.has(c.name ?? "") ? [] : [c];
|
|
1529
|
+
if (hoistable(c)) {
|
|
1530
|
+
const kids = c.children.filter((cc) => !(isMdxEl(cc) && removed.has(cc.name ?? "")));
|
|
1531
|
+
return kids.every((cc) => cc.type === "text" && "value" in cc && typeof cc.value === "string" && cc.value.trim() === "") ? [] : [{
|
|
1532
|
+
...c,
|
|
1533
|
+
children: kids
|
|
1534
|
+
}];
|
|
1535
|
+
}
|
|
1536
|
+
return [c];
|
|
1537
|
+
})
|
|
1538
|
+
};
|
|
1539
|
+
};
|
|
1534
1540
|
/** All literal text under `node` (text + inlineCode values, tree order). */
|
|
1535
1541
|
const textOf = (node) => Array.isArray(node) ? node.map(textContent$1).join("") : textContent$1(node);
|
|
1536
1542
|
const txt = (value) => ({
|
|
@@ -1663,6 +1669,53 @@ const phrasing = (nodes) => nodes.flatMap((n) => {
|
|
|
1663
1669
|
const t = deepText([n]).trim();
|
|
1664
1670
|
return t === "" ? [] : [txt(t)];
|
|
1665
1671
|
});
|
|
1672
|
+
const boldHtml = (node, ctx) => [strong(ctx.inline(node))];
|
|
1673
|
+
const emphasisHtml = (node, ctx) => [em(ctx.inline(node))];
|
|
1674
|
+
const deleteHtml = (node, ctx) => [del(ctx.inline(node))];
|
|
1675
|
+
const codeHtml = (node) => [icode(deepText(node.children ?? []))];
|
|
1676
|
+
const HTML_INLINE = {
|
|
1677
|
+
a: (node, ctx) => {
|
|
1678
|
+
const href = attr(node, "href");
|
|
1679
|
+
const inner = ctx.inline(node);
|
|
1680
|
+
return href === void 0 ? inner : [link(href, inner)];
|
|
1681
|
+
},
|
|
1682
|
+
b: boldHtml,
|
|
1683
|
+
br: () => [brk()],
|
|
1684
|
+
code: codeHtml,
|
|
1685
|
+
del: deleteHtml,
|
|
1686
|
+
em: emphasisHtml,
|
|
1687
|
+
i: emphasisHtml,
|
|
1688
|
+
img: (node) => {
|
|
1689
|
+
return [{
|
|
1690
|
+
alt: attr(node, "alt"),
|
|
1691
|
+
type: "image",
|
|
1692
|
+
url: attr(node, "src") ?? ""
|
|
1693
|
+
}];
|
|
1694
|
+
},
|
|
1695
|
+
kbd: codeHtml,
|
|
1696
|
+
s: deleteHtml,
|
|
1697
|
+
samp: codeHtml,
|
|
1698
|
+
strike: deleteHtml,
|
|
1699
|
+
strong: boldHtml
|
|
1700
|
+
};
|
|
1701
|
+
/** Wrap adjacent inline nodes while retaining nested block structure. */
|
|
1702
|
+
const blocksOf = (nodes) => {
|
|
1703
|
+
const blocks = [];
|
|
1704
|
+
let inline = [];
|
|
1705
|
+
for (const node of nodes) {
|
|
1706
|
+
if (PHRASING.has(node.type)) {
|
|
1707
|
+
inline.push(...phrasing([node]));
|
|
1708
|
+
continue;
|
|
1709
|
+
}
|
|
1710
|
+
if (inline.length > 0) {
|
|
1711
|
+
blocks.push(para(inline));
|
|
1712
|
+
inline = [];
|
|
1713
|
+
}
|
|
1714
|
+
blocks.push(node);
|
|
1715
|
+
}
|
|
1716
|
+
if (inline.length > 0) blocks.push(para(inline));
|
|
1717
|
+
return blocks;
|
|
1718
|
+
};
|
|
1666
1719
|
/**
|
|
1667
1720
|
* Raw HTML elements written into a document are mdxJsx elements too —
|
|
1668
1721
|
* give the meaningful ones a markdown form (`<a>` → link, `<code>` →
|
|
@@ -1672,49 +1725,52 @@ const htmlEl = (node, ctx, inline) => {
|
|
|
1672
1725
|
const name = node.name ?? "";
|
|
1673
1726
|
const kids = () => phrasing(ctx.children(node));
|
|
1674
1727
|
const blocks = () => ctx.children(node);
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
const inner = kids();
|
|
1678
|
-
return href === void 0 ? inner : [link(href, inner)];
|
|
1679
|
-
}
|
|
1680
|
-
if (name === "img") return [{
|
|
1681
|
-
alt: attr(node, "alt"),
|
|
1682
|
-
type: "image",
|
|
1683
|
-
url: attr(node, "src") ?? ""
|
|
1684
|
-
}];
|
|
1685
|
-
if (name === "br") return [brk()];
|
|
1686
|
-
if (name === "strong" || name === "b") return [strong(kids())];
|
|
1687
|
-
if (name === "em" || name === "i") return [em(kids())];
|
|
1688
|
-
if (name === "del" || name === "s" || name === "strike") return [del(kids())];
|
|
1689
|
-
if (name === "code" || name === "kbd" || name === "samp") return [icode(deepText(node.children ?? []))];
|
|
1728
|
+
const inlineRenderer = own(HTML_INLINE, name);
|
|
1729
|
+
if (inlineRenderer !== void 0) return inlineRenderer(node, ctx);
|
|
1690
1730
|
if (name === "hr") return [thematic()];
|
|
1691
1731
|
if (name === "p") return [para(kids())];
|
|
1692
1732
|
if (/^h[1-6]$/u.test(name)) return [heading(Number(name[1]), kids())];
|
|
1693
1733
|
if (name === "blockquote") return [quote(blocks())];
|
|
1694
1734
|
if (name === "pre") return [pre(deepText(node.children ?? []))];
|
|
1695
1735
|
if (name === "ul" || name === "ol") {
|
|
1696
|
-
const items = els(node, "li").map((li) => item(
|
|
1736
|
+
const items = els(node, "li").map((li) => item(blocksOf(ctx.children(li))));
|
|
1697
1737
|
return items.length === 0 ? blocks() : [list(items, name === "ol")];
|
|
1698
1738
|
}
|
|
1699
1739
|
return inline ? kids() : blocks();
|
|
1700
1740
|
};
|
|
1741
|
+
const renderMdxElement = (node, ctx, registry) => {
|
|
1742
|
+
const name = node.name ?? "";
|
|
1743
|
+
const entry = own(registry, name);
|
|
1744
|
+
if (entry === void 0) {
|
|
1745
|
+
if (/^[a-z]/u.test(name)) return htmlEl(node, ctx, node.type === "mdxJsxTextElement");
|
|
1746
|
+
ctx.warn(name);
|
|
1747
|
+
return ctx.children(node);
|
|
1748
|
+
}
|
|
1749
|
+
if (node.type === "mdxJsxTextElement") return entry.text?.(node, ctx) ?? phrasing(entry.flow?.(node, ctx) ?? ctx.children(node));
|
|
1750
|
+
if (entry.flow !== void 0) return entry.flow(node, ctx);
|
|
1751
|
+
const inlineOut = entry.text?.(node, ctx);
|
|
1752
|
+
return inlineOut === void 0 ? ctx.children(node) : [para(inlineOut)];
|
|
1753
|
+
};
|
|
1701
1754
|
/** Replace every mdxJsx element under `root` with its ASCII representation. */
|
|
1702
1755
|
const transformAscii = (root, registry, serialize, warn) => {
|
|
1703
1756
|
const transformElement = (n, ctx) => {
|
|
1704
|
-
if (isMdxEl(n))
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1757
|
+
if (isMdxEl(n)) return renderMdxElement(n, ctx, registry);
|
|
1758
|
+
if (isParent(n)) {
|
|
1759
|
+
if (hoistable(n)) {
|
|
1760
|
+
const children = n.children.filter(isMdxEl).map((child) => ({
|
|
1761
|
+
...child,
|
|
1762
|
+
type: "mdxJsxFlowElement"
|
|
1763
|
+
}));
|
|
1764
|
+
return blocksOf(ctx.children({
|
|
1765
|
+
...n,
|
|
1766
|
+
children
|
|
1767
|
+
}));
|
|
1711
1768
|
}
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1769
|
+
return [{
|
|
1770
|
+
...n,
|
|
1771
|
+
children: ctx.children(n)
|
|
1772
|
+
}];
|
|
1716
1773
|
}
|
|
1717
|
-
if (isParent(n)) n.children = ctx.children(n);
|
|
1718
1774
|
return [n];
|
|
1719
1775
|
};
|
|
1720
1776
|
const transformNodes = (nodes, ctx) => nodes.flatMap((node) => {
|
|
@@ -1784,7 +1840,7 @@ const STATUS_ICON = {
|
|
|
1784
1840
|
unverified: "?",
|
|
1785
1841
|
up: "✓"
|
|
1786
1842
|
};
|
|
1787
|
-
const statusIcon = (status) => status === void 0 ? "•" : STATUS_ICON
|
|
1843
|
+
const statusIcon = (status) => status === void 0 ? "•" : own(STATUS_ICON, status.toLowerCase()) ?? "•";
|
|
1788
1844
|
/** Left-align `s` in a `width`-wide column (no padding past the width). */
|
|
1789
1845
|
const pad = (s, width) => s.length >= width ? s : s + " ".repeat(width - s.length);
|
|
1790
1846
|
//#endregion
|
|
@@ -1796,6 +1852,11 @@ const suffix = (parts) => {
|
|
|
1796
1852
|
};
|
|
1797
1853
|
/** Bare `a · b · c` joining (no leading separator). */
|
|
1798
1854
|
const joined = (parts) => parts.filter(nonEmpty).join(" · ");
|
|
1855
|
+
/** A disclosure with literal summary text and an already serialized body. */
|
|
1856
|
+
const detailsBlock = (summary, body) => {
|
|
1857
|
+
const escaped = summary.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
1858
|
+
return html(`<details>\n<summary>${escaped}</summary>\n\n${body.trim()}\n\n</details>`);
|
|
1859
|
+
};
|
|
1799
1860
|
/**
|
|
1800
1861
|
* `**Title — extra**` caption paragraph, emitted only when something is
|
|
1801
1862
|
* there. A title-less caption drops a leading ` — `/space so `extra` alone
|
|
@@ -1824,7 +1885,7 @@ const tally$1 = (entries, key) => {
|
|
|
1824
1885
|
//#endregion
|
|
1825
1886
|
//#region src/ascii/charts.ts
|
|
1826
1887
|
/** Attribute → number list (`values="12, 8, 4"`). */
|
|
1827
|
-
const nums = (node, key) =>
|
|
1888
|
+
const nums = (node, key) => numList(attr(node, key));
|
|
1828
1889
|
/** `label ██████░░ 42` fence rows from name/value pairs. */
|
|
1829
1890
|
const barRows = (rows, max) => {
|
|
1830
1891
|
const W = 24;
|
|
@@ -2190,7 +2251,7 @@ const term = (node, ctx) => [para([strong([txt(attr(node, "name") ?? "")]), ...c
|
|
|
2190
2251
|
const details = (node, ctx) => {
|
|
2191
2252
|
const summary = attr(node, "summary") ?? "Details";
|
|
2192
2253
|
const body = ctx.serialize(ctx.children(node)).trim();
|
|
2193
|
-
return [
|
|
2254
|
+
return [detailsBlock(summary, body)];
|
|
2194
2255
|
};
|
|
2195
2256
|
const documentRenderers = {
|
|
2196
2257
|
Callout: { flow: (n, ctx) => {
|
|
@@ -2286,7 +2347,7 @@ const nodeRows = (node) => {
|
|
|
2286
2347
|
if (grid && isParent(grid)) return tableRecords(grid.children.map((row) => isParent(row) ? row.children.map(textOf) : []));
|
|
2287
2348
|
return els(node, "RecordItem").map(nodeAttributes);
|
|
2288
2349
|
};
|
|
2289
|
-
const recordsTable = (rows, cols) => rows.length ? [table(cols ?? columnsOf(rows), rows.map((row) => (cols ?? columnsOf(rows)).map((key) => display(row
|
|
2350
|
+
const recordsTable = (rows, cols) => rows.length ? [table(cols ?? columnsOf(rows), rows.map((row) => (cols ?? columnsOf(rows)).map((key) => display(own(row, key)) || "—")))] : [para([txt("No data")])];
|
|
2290
2351
|
const narrative = (node, ctx) => {
|
|
2291
2352
|
if (attr(node, "data") !== void 0) return ctx.children(node);
|
|
2292
2353
|
const children = node.children?.filter((child) => {
|
|
@@ -2411,14 +2472,14 @@ Object.assign(extendedRenderers, {
|
|
|
2411
2472
|
"pnpm",
|
|
2412
2473
|
"yarn",
|
|
2413
2474
|
"bun"
|
|
2414
|
-
].map((manager) => `${manager} ${manager === "npm" ? "install" : "add"}${
|
|
2475
|
+
].map((manager) => `${manager} ${manager === "npm" ? "install" : "add"}${flag(node, "dev") ? " -D" : ""} ${attr(node, "packages") ?? ""}`).join("\n"), "sh")] },
|
|
2415
2476
|
PageBreak: { flow: () => [{ type: "thematicBreak" }] },
|
|
2416
2477
|
PdfPreview: { flow: (node) => sourceLink(node, "src", `${attr(node, "title") ?? "PDF document"} (page ${attr(node, "page") ?? "1"})`) },
|
|
2417
2478
|
PrintLayout: { flow: (node, ctx) => ctx.children(node) },
|
|
2418
2479
|
PromptTemplate: { flow: (node) => {
|
|
2419
2480
|
const rows = nodeRows(node);
|
|
2420
2481
|
const defaults = Object.fromEntries(rows.map((row) => [display(row.name), display(row.value)]));
|
|
2421
|
-
const prompt = (attr(node, "template") ?? "").replaceAll(/\{\{\s*(?<variable>[\w.-]+)\s*\}\}/gu, (match, name) => defaults
|
|
2482
|
+
const prompt = (attr(node, "template") ?? "").replaceAll(/\{\{\s*(?<variable>[\w.-]+)\s*\}\}/gu, (match, name) => own(defaults, name) ?? match);
|
|
2422
2483
|
return [
|
|
2423
2484
|
...caption(attr(node, "title") ?? "Prompt template"),
|
|
2424
2485
|
...recordsTable(rows),
|
|
@@ -2534,28 +2595,20 @@ const numbered = (entries, statusKey, ctx) => list(entries.map((e) => item([para
|
|
|
2534
2595
|
])])), true);
|
|
2535
2596
|
const NOTE_RE = /\s+(?:—|#)\s+/u;
|
|
2536
2597
|
const PLACEHOLDER_RE = /^(?:\.{3}|…)$/u;
|
|
2537
|
-
const
|
|
2538
|
-
const
|
|
2539
|
-
const
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
const labelText = li.children.filter((c) => c.type !== "list").map((c) => textOf(c)).join("").trim();
|
|
2545
|
-
const m = NOTE_RE.exec(labelText);
|
|
2546
|
-
out.push({
|
|
2547
|
-
children: treeEntries(nested),
|
|
2548
|
-
name: (m === null ? labelText : labelText.slice(0, m.index)).trim(),
|
|
2549
|
-
...m === null ? {} : { note: labelText.slice(m.index + m[0].length).trim() }
|
|
2550
|
-
});
|
|
2551
|
-
}
|
|
2552
|
-
return;
|
|
2553
|
-
}
|
|
2554
|
-
if (isParent(n)) for (const c of n.children) walk(c);
|
|
2598
|
+
const treeEntry = (node, children) => {
|
|
2599
|
+
const label = node.children.filter((child) => child.type !== "list").map(textOf).join("").trim();
|
|
2600
|
+
const note = NOTE_RE.exec(label);
|
|
2601
|
+
return {
|
|
2602
|
+
children,
|
|
2603
|
+
name: (note === null ? label : label.slice(0, note.index)).trim(),
|
|
2604
|
+
...note === null ? {} : { note: label.slice(note.index + note[0].length).trim() }
|
|
2555
2605
|
};
|
|
2556
|
-
for (const c of nodes) walk(c);
|
|
2557
|
-
return out;
|
|
2558
2606
|
};
|
|
2607
|
+
const treeEntries = (nodes) => nodes.flatMap((node) => {
|
|
2608
|
+
if (!isParent(node)) return [];
|
|
2609
|
+
if (node.type === "list") return node.children.filter((child) => child.type === "listItem" && isParent(child)).map((child) => treeEntry(child, treeEntries(child.children.filter((nested) => nested.type === "list"))));
|
|
2610
|
+
return treeEntries(node.children);
|
|
2611
|
+
});
|
|
2559
2612
|
const treeLines = (entries, prefix) => entries.flatMap((e, i) => {
|
|
2560
2613
|
const last = i === entries.length - 1;
|
|
2561
2614
|
const name = PLACEHOLDER_RE.test(e.name) ? "…" : e.name;
|
|
@@ -2652,14 +2705,18 @@ const investigationRenderers = {
|
|
|
2652
2705
|
...nonEmpty(attr(n, "path")) ? [txt(" — "), icode(pathAt(n))] : [],
|
|
2653
2706
|
...ctx.inline(n).length === 0 ? [] : [txt(": "), ...ctx.inline(n)]
|
|
2654
2707
|
])] },
|
|
2655
|
-
Hypotheses: { flow: (n, ctx) => [
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2708
|
+
Hypotheses: { flow: (n, ctx) => [
|
|
2709
|
+
...caption(attr(n, "title")),
|
|
2710
|
+
list(els(n, "Hypothesis").map((h) => {
|
|
2711
|
+
const status = (attr(h, "status") ?? "untested").toLowerCase();
|
|
2712
|
+
return item([para([
|
|
2713
|
+
txt(`${statusIcon(status)} `),
|
|
2714
|
+
...nonEmpty(attr(h, "title")) ? [strong([txt(attr(h, "title") ?? "")])] : [],
|
|
2715
|
+
...ctx.inline(h).length === 0 ? [] : [txt(" — "), ...ctx.inline(h)]
|
|
2716
|
+
])], status === "supported" ? true : void 0);
|
|
2717
|
+
})),
|
|
2718
|
+
...ctx.children(withoutEls(n, ["Hypothesis"]))
|
|
2719
|
+
] },
|
|
2663
2720
|
Hypothesis: { flow: (n, ctx) => [para([txt(`${statusIcon(attr(n, "status"))} `), ...nonEmpty(attr(n, "title")) ? [strong([txt(attr(n, "title") ?? "")])] : []]), ...ctx.children(n)] },
|
|
2664
2721
|
Prop: { flow: (n, ctx) => [para([
|
|
2665
2722
|
icode(attr(n, "name") ?? ""),
|
|
@@ -2784,12 +2841,13 @@ const comment = (node, ctx) => {
|
|
|
2784
2841
|
const graph = (node, ctx) => {
|
|
2785
2842
|
const nodes = els(node, "Node");
|
|
2786
2843
|
const edges = els(node, "Edge");
|
|
2844
|
+
const connected = new Set(edges.flatMap((edge) => [attr(edge, "from"), attr(edge, "to")]));
|
|
2787
2845
|
const nodeNotes = nodes.flatMap((nd) => {
|
|
2788
2846
|
const label = attr(nd, "label");
|
|
2789
2847
|
const note = attr(nd, "note");
|
|
2790
2848
|
const path = attr(nd, "path");
|
|
2791
2849
|
const status = attr(nd, "status");
|
|
2792
|
-
if ((label === void 0 || label === attr(nd, "id")) && note === void 0 && path === void 0 && status === void 0) return [];
|
|
2850
|
+
if ((label === void 0 || label === attr(nd, "id")) && note === void 0 && path === void 0 && status === void 0 && connected.has(attr(nd, "id"))) return [];
|
|
2793
2851
|
const head = [
|
|
2794
2852
|
icode(attr(nd, "id") ?? ""),
|
|
2795
2853
|
txt(" = "),
|
|
@@ -2912,9 +2970,10 @@ const waterfall = (node, ctx) => {
|
|
|
2912
2970
|
const title = attr(node, "title");
|
|
2913
2971
|
if (nonEmpty(title)) lines.push(`${title} (0 → ${total}${unit})`);
|
|
2914
2972
|
for (const s of spans) {
|
|
2915
|
-
const
|
|
2916
|
-
const
|
|
2917
|
-
const
|
|
2973
|
+
const scale = Math.max(1, total);
|
|
2974
|
+
const from = Math.max(0, Math.min(W, Math.round(s.start / scale * W)));
|
|
2975
|
+
const to = Math.max(from, Math.min(W, Math.round((s.start + Math.max(0, s.duration)) / scale * W)));
|
|
2976
|
+
const cells = `${" ".repeat(from)}${"█".repeat(to - from)}`;
|
|
2918
2977
|
lines.push(`${pad(s.name, nameW)} ${cells} ${s.durationRaw || `${s.duration}${unit}`}${nonEmpty(s.note) ? ` ${s.note}` : ""}`);
|
|
2919
2978
|
}
|
|
2920
2979
|
return [pre(lines.join("\n")), ...ctx.children(withoutEls(node, ["Span"]))];
|
|
@@ -2970,6 +3029,7 @@ const outputRenderers = {
|
|
|
2970
3029
|
};
|
|
2971
3030
|
//#endregion
|
|
2972
3031
|
//#region src/ascii/planning.ts
|
|
3032
|
+
/** ASCII renderers for planning & status components. */
|
|
2973
3033
|
/** `owner` attr → a normalized `@name` chip (leading `@`s collapsed). */
|
|
2974
3034
|
const ownerChip = (node) => {
|
|
2975
3035
|
const owner = attr(node, "owner");
|
|
@@ -3009,8 +3069,8 @@ const approvalStatus = (s) => {
|
|
|
3009
3069
|
const DAY_MS = 864e5;
|
|
3010
3070
|
const dayOf = (iso) => {
|
|
3011
3071
|
if (iso === void 0) return;
|
|
3012
|
-
const
|
|
3013
|
-
return
|
|
3072
|
+
const date = parseISO(iso);
|
|
3073
|
+
return isValid(date) ? differenceInCalendarDays(date, new Date(1970, 0, 1)) : void 0;
|
|
3014
3074
|
};
|
|
3015
3075
|
const TASK_CHAR = {
|
|
3016
3076
|
blocked: "▒",
|
|
@@ -3018,56 +3078,75 @@ const TASK_CHAR = {
|
|
|
3018
3078
|
done: "█",
|
|
3019
3079
|
todo: "░"
|
|
3020
3080
|
};
|
|
3081
|
+
const GANTT_WIDTH = 40;
|
|
3082
|
+
const ganttTasks = (node) => els(node, "Task").flatMap((el) => {
|
|
3083
|
+
const start = dayOf(attr(el, "start"));
|
|
3084
|
+
if (start === void 0) return [];
|
|
3085
|
+
return [{
|
|
3086
|
+
el,
|
|
3087
|
+
end: Math.max(start, dayOf(attr(el, "end")) ?? start),
|
|
3088
|
+
name: attr(el, "name") ?? "",
|
|
3089
|
+
progress: num(el, "progress"),
|
|
3090
|
+
start,
|
|
3091
|
+
status: (attr(el, "status") ?? "todo").toLowerCase()
|
|
3092
|
+
}];
|
|
3093
|
+
});
|
|
3094
|
+
const ganttMilestones = (node) => els(node, "Milestone").flatMap((el) => {
|
|
3095
|
+
const date = dayOf(attr(el, "date"));
|
|
3096
|
+
return date === void 0 ? [] : [{
|
|
3097
|
+
date,
|
|
3098
|
+
el,
|
|
3099
|
+
name: attr(el, "name") ?? ""
|
|
3100
|
+
}];
|
|
3101
|
+
});
|
|
3102
|
+
const ganttRange = (node, tasks, milestones) => {
|
|
3103
|
+
const start = dayOf(attr(node, "start"));
|
|
3104
|
+
const end = dayOf(attr(node, "end"));
|
|
3105
|
+
let first = start ?? Math.min(...tasks.map((task) => task.start), ...milestones.map((milestone) => milestone.date));
|
|
3106
|
+
let last = end ?? Math.max(...tasks.map((task) => task.end), ...milestones.map((milestone) => milestone.date));
|
|
3107
|
+
if (last < first) {
|
|
3108
|
+
if (end !== void 0 && start === void 0) first = last;
|
|
3109
|
+
else last = first;
|
|
3110
|
+
}
|
|
3111
|
+
return {
|
|
3112
|
+
first,
|
|
3113
|
+
last
|
|
3114
|
+
};
|
|
3115
|
+
};
|
|
3116
|
+
const ganttColumn = (day, range) => Math.max(0, Math.min(GANTT_WIDTH, Math.round((day - range.first) / (range.last - range.first + 1) * GANTT_WIDTH)));
|
|
3117
|
+
const ganttTaskLine = (task, range, nameWidth) => {
|
|
3118
|
+
const from = ganttColumn(task.start, range);
|
|
3119
|
+
const to = Math.max(from + 1, ganttColumn(task.end + 1, range));
|
|
3120
|
+
const outside = task.end < range.first || task.start > range.last;
|
|
3121
|
+
const glyph = own(TASK_CHAR, task.status) ?? "░";
|
|
3122
|
+
const filled = (to - from) * Math.min(100, Math.max(0, task.progress ?? 0)) / 100;
|
|
3123
|
+
const cells = Array.from({ length: GANTT_WIDTH }, (_, i) => {
|
|
3124
|
+
if (outside || i < from || i >= to) return " ";
|
|
3125
|
+
return i - from < filled ? "█" : glyph;
|
|
3126
|
+
}).join("");
|
|
3127
|
+
const tail = joined([ownerChip(task.el), attr(task.el, "note")]);
|
|
3128
|
+
return `${pad(task.name, nameWidth)} ${cells}${tail === "" ? "" : ` ${tail}`}`;
|
|
3129
|
+
};
|
|
3130
|
+
const ganttMilestoneLine = (milestone, range, nameWidth) => {
|
|
3131
|
+
const position = milestone.date < range.first || milestone.date > range.last ? -1 : Math.min(39, ganttColumn(milestone.date, range));
|
|
3132
|
+
const cells = Array.from({ length: GANTT_WIDTH }, (_, i) => i === position ? "◆" : " ").join("");
|
|
3133
|
+
const tail = joined([attr(milestone.el, "status"), attr(milestone.el, "note")]);
|
|
3134
|
+
return `${pad(milestone.name, nameWidth)} ${cells}${tail === "" ? "" : ` ${tail}`}`;
|
|
3135
|
+
};
|
|
3021
3136
|
const gantt = (node, ctx) => {
|
|
3022
|
-
const tasks =
|
|
3023
|
-
|
|
3024
|
-
return start === void 0 ? [] : [{
|
|
3025
|
-
el: t,
|
|
3026
|
-
end: dayOf(attr(t, "end")) ?? start,
|
|
3027
|
-
name: attr(t, "name") ?? "",
|
|
3028
|
-
progress: num(t, "progress"),
|
|
3029
|
-
start,
|
|
3030
|
-
status: (attr(t, "status") ?? "todo").toLowerCase()
|
|
3031
|
-
}];
|
|
3032
|
-
});
|
|
3033
|
-
const milestones = els(node, "Milestone").flatMap((m) => {
|
|
3034
|
-
const date = dayOf(attr(m, "date"));
|
|
3035
|
-
return date === void 0 ? [] : [{
|
|
3036
|
-
date,
|
|
3037
|
-
el: m,
|
|
3038
|
-
name: attr(m, "name") ?? ""
|
|
3039
|
-
}];
|
|
3040
|
-
});
|
|
3137
|
+
const tasks = ganttTasks(node);
|
|
3138
|
+
const milestones = ganttMilestones(node);
|
|
3041
3139
|
if (tasks.length === 0 && milestones.length === 0) return ctx.children(node);
|
|
3042
|
-
const
|
|
3043
|
-
const
|
|
3044
|
-
const
|
|
3045
|
-
const W = 40;
|
|
3046
|
-
const at = (day) => Math.max(0, Math.min(W, Math.round((day - lo) / span * W)));
|
|
3047
|
-
const nameW = Math.min(24, Math.max(8, ...tasks.map((t) => t.name.length), ...milestones.map((m) => m.name.length)));
|
|
3048
|
-
const iso = (d) => (/* @__PURE__ */ new Date(d * DAY_MS)).toISOString().slice(0, 10);
|
|
3049
|
-
const lines = [];
|
|
3140
|
+
const range = ganttRange(node, tasks, milestones);
|
|
3141
|
+
const nameWidth = Math.min(24, Math.max(8, ...tasks.map((task) => task.name.length), ...milestones.map((milestone) => milestone.name.length)));
|
|
3142
|
+
const iso = (day) => (/* @__PURE__ */ new Date(day * DAY_MS)).toISOString().slice(0, 10);
|
|
3050
3143
|
const title = attr(node, "title");
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
if (i < from || i >= to) return " ";
|
|
3058
|
-
const ch = TASK_CHAR[t.status] ?? "░";
|
|
3059
|
-
if (t.progress === void 0 || t.progress <= 0) return ch;
|
|
3060
|
-
return i - from < (to - from) * Math.min(100, t.progress) / 100 ? "█" : ch;
|
|
3061
|
-
}).join("");
|
|
3062
|
-
const owner = attr(t.el, "owner");
|
|
3063
|
-
const tail = joined([nonEmpty(owner) ? `@${owner.replace(/^@+/u, "")}` : void 0, attr(t.el, "note")]);
|
|
3064
|
-
lines.push(`${pad(t.name, nameW)} ${cells}${tail === "" ? "" : ` ${tail}`}`);
|
|
3065
|
-
}
|
|
3066
|
-
for (const m of milestones) {
|
|
3067
|
-
const cells = Array.from({ length: W }, (_, i) => i === at(m.date) ? "◆" : " ").join("");
|
|
3068
|
-
const tail = joined([attr(m.el, "status"), attr(m.el, "note")]);
|
|
3069
|
-
lines.push(`${pad(m.name, nameW)} ${cells}${tail === "" ? "" : ` ${tail}`}`);
|
|
3070
|
-
}
|
|
3144
|
+
const lines = [
|
|
3145
|
+
...nonEmpty(title) ? [title] : [],
|
|
3146
|
+
`${pad("", nameWidth)} ${iso(range.first)} → ${iso(range.last)}`,
|
|
3147
|
+
...tasks.map((task) => ganttTaskLine(task, range, nameWidth)),
|
|
3148
|
+
...milestones.map((milestone) => ganttMilestoneLine(milestone, range, nameWidth))
|
|
3149
|
+
];
|
|
3071
3150
|
return [pre(lines.join("\n")), ...ctx.children(withoutEls(node, ["Task", "Milestone"]))];
|
|
3072
3151
|
};
|
|
3073
3152
|
const CELL_GLYPH = {
|
|
@@ -3103,7 +3182,7 @@ const CELL_GLYPH = {
|
|
|
3103
3182
|
};
|
|
3104
3183
|
const matrixCell = (cell) => {
|
|
3105
3184
|
const key = cell.trim().toLowerCase();
|
|
3106
|
-
return key === "" ? "—" : CELL_GLYPH
|
|
3185
|
+
return key === "" ? "—" : own(CELL_GLYPH, key) ?? cell.trim();
|
|
3107
3186
|
};
|
|
3108
3187
|
/** `- label | a | b` list items → matrix rows. */
|
|
3109
3188
|
const matrixRows = (node) => {
|
|
@@ -3135,12 +3214,16 @@ const planningRenderers = {
|
|
|
3135
3214
|
attr(a, "role"),
|
|
3136
3215
|
attr(a, "status"),
|
|
3137
3216
|
attr(a, "date")
|
|
3138
|
-
])],
|
|
3217
|
+
])], ctx.children(a)))), ...ctx.children(withoutEls(n, ["Approval"]))] },
|
|
3139
3218
|
Board: { flow: (n, ctx) => [
|
|
3140
3219
|
...caption(attr(n, "title")),
|
|
3141
3220
|
...els(n, "Lane").flatMap((lane) => {
|
|
3142
3221
|
const cards = els(lane, "BoardCard");
|
|
3143
|
-
return [
|
|
3222
|
+
return [
|
|
3223
|
+
para([txt(`${statusIcon(attr(lane, "status"))} `), strong([txt(`${attr(lane, "title") ?? ""} (${cards.length})`)])]),
|
|
3224
|
+
list(cards.map((c) => boardCard(c, ctx))),
|
|
3225
|
+
...ctx.children(withoutEls(lane, ["BoardCard"]))
|
|
3226
|
+
];
|
|
3144
3227
|
}),
|
|
3145
3228
|
...ctx.children(withoutEls(n, ["Lane"]))
|
|
3146
3229
|
] },
|
|
@@ -3202,11 +3285,11 @@ const planningRenderers = {
|
|
|
3202
3285
|
])],
|
|
3203
3286
|
text: (n) => [strong([txt(attr(n, "value") ?? "")]), txt(` ${attr(n, "label") ?? ""}`)]
|
|
3204
3287
|
},
|
|
3205
|
-
Stats: { flow: (n) => [list(els(n, "Stat").map((s) => item([para([
|
|
3288
|
+
Stats: { flow: (n, ctx) => [list(els(n, "Stat").map((s) => item([para([
|
|
3206
3289
|
strong([txt(attr(s, "value") ?? "")]),
|
|
3207
3290
|
txt(` ${attr(s, "label") ?? ""}`),
|
|
3208
3291
|
...nonEmpty(attr(s, "delta")) ? [txt(` (${attr(s, "delta")})`)] : []
|
|
3209
|
-
])])))] },
|
|
3292
|
+
])]))), ...ctx.children(withoutEls(n, ["Stat"]))] },
|
|
3210
3293
|
StatusBadge: { text: (n) => [icode(attr(n, "status") ?? "")] },
|
|
3211
3294
|
Step: { flow: (n, ctx) => [para([
|
|
3212
3295
|
txt(`${statusIcon(attr(n, "status"))} `),
|
|
@@ -3269,7 +3352,7 @@ const VERDICT_LABEL = {
|
|
|
3269
3352
|
pass: "Passed",
|
|
3270
3353
|
warn: "Warning"
|
|
3271
3354
|
};
|
|
3272
|
-
const verdictLabel = (s) => s === void 0 ? void 0 : VERDICT_LABEL
|
|
3355
|
+
const verdictLabel = (s) => s === void 0 ? void 0 : own(VERDICT_LABEL, s.toLowerCase()) ?? s;
|
|
3273
3356
|
const DURATION_MS = {
|
|
3274
3357
|
h: 36e5,
|
|
3275
3358
|
m: 6e4,
|
|
@@ -3318,9 +3401,14 @@ const DAY_CELL = {
|
|
|
3318
3401
|
const uptimeStrip = (node) => {
|
|
3319
3402
|
const days = els(node, "Day");
|
|
3320
3403
|
if (days.length === 0) return "";
|
|
3321
|
-
return days.map((d) => DAY_CELL
|
|
3404
|
+
return days.map((d) => own(DAY_CELL, (attr(d, "status") ?? "up").toLowerCase()) ?? "█").join("");
|
|
3405
|
+
};
|
|
3406
|
+
const percentText = (value) => nonEmpty(value) && !value.endsWith("%") ? `${value}%` : value;
|
|
3407
|
+
const serviceRow = (node, ctx) => statusItem$1(attr(node, "status"), [txt(attr(node, "name") ?? ""), ...suffix([attr(node, "status"), percentText(attr(node, "uptime"))])], ctx.children(node));
|
|
3408
|
+
const releaseVersion = (node) => {
|
|
3409
|
+
const version = attr(node, "version") ?? "";
|
|
3410
|
+
return version.startsWith("v") ? version : `v${version}`;
|
|
3322
3411
|
};
|
|
3323
|
-
const serviceRow = (node) => statusItem$1(attr(node, "status"), [txt(attr(node, "name") ?? ""), ...suffix([attr(node, "status"), nonEmpty(attr(node, "uptime")) ? `${attr(node, "uptime")}%` : void 0])], []);
|
|
3324
3412
|
const entryRow = (node, ctx) => {
|
|
3325
3413
|
const scope = attr(node, "scope");
|
|
3326
3414
|
return item([para([
|
|
@@ -3368,7 +3456,7 @@ const reportRenderers = {
|
|
|
3368
3456
|
attr(b, "before") ?? "",
|
|
3369
3457
|
attr(b, "after") ?? "",
|
|
3370
3458
|
d,
|
|
3371
|
-
attr(b, "note")
|
|
3459
|
+
joined([attr(b, "note"), ctx.text(b)])
|
|
3372
3460
|
];
|
|
3373
3461
|
})),
|
|
3374
3462
|
...ctx.children(withoutEls(n, ["Bench"]))
|
|
@@ -3439,7 +3527,8 @@ const reportRenderers = {
|
|
|
3439
3527
|
"field",
|
|
3440
3528
|
"type",
|
|
3441
3529
|
"flags",
|
|
3442
|
-
"default"
|
|
3530
|
+
"default",
|
|
3531
|
+
"description"
|
|
3443
3532
|
], els(n, "DbField").map((f) => {
|
|
3444
3533
|
const flags = [
|
|
3445
3534
|
flag(f, "pk") ? "pk" : void 0,
|
|
@@ -3451,7 +3540,8 @@ const reportRenderers = {
|
|
|
3451
3540
|
[icode(attr(f, "name") ?? "")],
|
|
3452
3541
|
[icode(attr(f, "type") ?? "")],
|
|
3453
3542
|
flags,
|
|
3454
|
-
attr(f, "default") ?? ""
|
|
3543
|
+
attr(f, "default") ?? "",
|
|
3544
|
+
ctx.text(f)
|
|
3455
3545
|
];
|
|
3456
3546
|
})),
|
|
3457
3547
|
...ctx.children(withoutEls(n, ["DbField"]))
|
|
@@ -3464,7 +3554,7 @@ const reportRenderers = {
|
|
|
3464
3554
|
EnvVar: { flow: (n, ctx) => [para([
|
|
3465
3555
|
icode(attr(n, "name") ?? ""),
|
|
3466
3556
|
...flag(n, "required") ? [txt(" "), icode("required")] : [],
|
|
3467
|
-
...suffix([flag(n, "secret") ? "secret" : attr(n, "value"), nonEmpty(attr(n, "default")) ? `default ${attr(n, "default")}` : void 0]),
|
|
3557
|
+
...suffix([flag(n, "secret") ? "secret" : attr(n, "value"), !flag(n, "secret") && nonEmpty(attr(n, "default")) ? `default ${attr(n, "default")}` : void 0]),
|
|
3468
3558
|
...ctx.inline(n).length === 0 ? [] : [txt(" — "), ...ctx.inline(n)]
|
|
3469
3559
|
])] },
|
|
3470
3560
|
EnvVars: { flow: (n, ctx) => [
|
|
@@ -3479,7 +3569,7 @@ const reportRenderers = {
|
|
|
3479
3569
|
[icode(attr(e, "name") ?? "")],
|
|
3480
3570
|
flag(e, "required") ? "yes" : "",
|
|
3481
3571
|
flag(e, "secret") ? "•••" : attr(e, "value") ?? "",
|
|
3482
|
-
attr(e, "default") ?? "",
|
|
3572
|
+
flag(e, "secret") ? "•••" : attr(e, "default") ?? "",
|
|
3483
3573
|
ctx.text(e)
|
|
3484
3574
|
])),
|
|
3485
3575
|
...ctx.children(withoutEls(n, ["EnvVar"]))
|
|
@@ -3553,7 +3643,7 @@ const reportRenderers = {
|
|
|
3553
3643
|
attr(p, "version") ?? "",
|
|
3554
3644
|
attr(p, "kind") ?? "",
|
|
3555
3645
|
attr(p, "license") ?? "",
|
|
3556
|
-
ctx.text(p)
|
|
3646
|
+
joined([attr(p, "note"), ctx.text(p)])
|
|
3557
3647
|
])),
|
|
3558
3648
|
...ctx.children(withoutEls(n, ["Package"]))
|
|
3559
3649
|
] },
|
|
@@ -3563,17 +3653,17 @@ const reportRenderers = {
|
|
|
3563
3653
|
...ctx.children(withoutEls(n, ["Stop"]))
|
|
3564
3654
|
] },
|
|
3565
3655
|
Release: { flow: (n, ctx) => [
|
|
3566
|
-
heading(3, [...nonEmpty(attr(n, "href")) ? [link(attr(n, "href") ?? "", [txt(
|
|
3656
|
+
heading(3, [...nonEmpty(attr(n, "href")) ? [link(attr(n, "href") ?? "", [txt(releaseVersion(n))])] : [icode(releaseVersion(n))], ...nonEmpty(attr(n, "title")) ? [txt(` — ${attr(n, "title")}`)] : []]),
|
|
3567
3657
|
...nonEmpty(attr(n, "date")) ? [para([em([txt(attr(n, "date") ?? "")])])] : [],
|
|
3568
3658
|
...els(n, "Entry").length === 0 ? ctx.children(n) : [list(els(n, "Entry").map((e) => entryRow(e, ctx))), ...ctx.children(withoutEls(n, ["Entry"]))]
|
|
3569
3659
|
] },
|
|
3570
3660
|
Review: { flow: (n, ctx) => [...caption(attr(n, "title"), `${nonEmpty(attr(n, "verdict")) ? ` — ${verdictLabel(attr(n, "verdict"))}` : ""}${tally$1(els(n, "Comment"), "severity")}`), ...ctx.children(n)] },
|
|
3571
3661
|
Schema: { flow: (n, ctx) => [...caption(attr(n, "title"), nonEmpty(attr(n, "engine")) ? ` (${attr(n, "engine")})` : ""), ...ctx.children(n)] },
|
|
3572
|
-
Service: { flow: (n) => [para([
|
|
3662
|
+
Service: { flow: (n, ctx) => [para([
|
|
3573
3663
|
txt(`${statusIcon(attr(n, "status"))} `),
|
|
3574
3664
|
txt(attr(n, "name") ?? ""),
|
|
3575
|
-
...suffix([attr(n, "status"),
|
|
3576
|
-
])] },
|
|
3665
|
+
...suffix([attr(n, "status"), percentText(attr(n, "uptime"))])
|
|
3666
|
+
]), ...ctx.children(n)] },
|
|
3577
3667
|
Severity: { text: (n) => [icode((attr(n, "level") ?? "").toUpperCase())] },
|
|
3578
3668
|
StatusPage: { flow: (n, ctx) => {
|
|
3579
3669
|
const services = els(n, "Service");
|
|
@@ -3588,7 +3678,7 @@ const reportRenderers = {
|
|
|
3588
3678
|
...suffix([[attr(u, "from"), attr(u, "to")].filter(nonEmpty).join(" → ")])
|
|
3589
3679
|
]), ...strip === "" ? [] : [pre(strip)]];
|
|
3590
3680
|
}),
|
|
3591
|
-
...services.length === 0 ? [] : [list(services.map(serviceRow))],
|
|
3681
|
+
...services.length === 0 ? [] : [list(services.map((service) => serviceRow(service, ctx)))],
|
|
3592
3682
|
...ctx.children(withoutEls(n, ["Service", "Uptime"]))
|
|
3593
3683
|
];
|
|
3594
3684
|
} },
|
|
@@ -3661,33 +3751,39 @@ const menuItem = {
|
|
|
3661
3751
|
text: (n, ctx) => ctx.inline(n)
|
|
3662
3752
|
};
|
|
3663
3753
|
/** `[x]`/`[ ]`/`( )` control from a checked-ish attribute. */
|
|
3754
|
+
const controlOn = (n) => {
|
|
3755
|
+
if (flag(n, "checked") || flagOff(n, "checked")) return flag(n, "checked");
|
|
3756
|
+
return flag(n, "defaultChecked") || flag(n, "pressed");
|
|
3757
|
+
};
|
|
3664
3758
|
const control = (mark) => ({
|
|
3665
3759
|
flow: (n, ctx) => {
|
|
3666
3760
|
const inner = ctx.inline(n);
|
|
3667
3761
|
const label = attr(n, "label");
|
|
3668
3762
|
return [para([
|
|
3669
|
-
icode(mark(
|
|
3763
|
+
icode(mark(controlOn(n))),
|
|
3670
3764
|
...inner.length === 0 ? [] : [txt(" "), ...inner],
|
|
3671
3765
|
...nonEmpty(label) ? [txt(` ${label}`)] : []
|
|
3672
3766
|
])];
|
|
3673
3767
|
},
|
|
3674
|
-
text: (n, ctx) => [icode(mark(
|
|
3768
|
+
text: (n, ctx) => [icode(mark(controlOn(n))), ...ctx.inline(n).length === 0 ? [] : [txt(" "), ...ctx.inline(n)]]
|
|
3675
3769
|
});
|
|
3676
3770
|
/** `` `[____]` ``-style empty input stand-in. */
|
|
3771
|
+
const inputText = (n) => {
|
|
3772
|
+
const value = attr(n, "value") ?? attr(n, "defaultValue");
|
|
3773
|
+
if (value !== void 0) return attr(n, "type") === "password" ? "••••" : value;
|
|
3774
|
+
return attr(n, "placeholder") ?? attr(n, "label") ?? "____";
|
|
3775
|
+
};
|
|
3677
3776
|
const input = {
|
|
3678
|
-
flow: (n) => [pre(`[ ${
|
|
3679
|
-
text: (n) => [icode(`[${
|
|
3777
|
+
flow: (n) => [pre(`[ ${inputText(n)} ]`)],
|
|
3778
|
+
text: (n) => [icode(`[${inputText(n)}]`)]
|
|
3680
3779
|
};
|
|
3681
3780
|
/** `<details>` disclosure — summary from AccordionTrigger/collapsible label. */
|
|
3682
3781
|
const disclosure = (trigger, fallback) => ({ flow: (n, ctx) => {
|
|
3683
3782
|
const [head] = els(n, trigger);
|
|
3684
3783
|
const summary = head === void 0 ? attr(n, "label") ?? fallback : ctx.text(head);
|
|
3685
|
-
const rest = ctx.children(
|
|
3686
|
-
...n,
|
|
3687
|
-
children: (n.children ?? []).filter((c) => !named(c, trigger))
|
|
3688
|
-
});
|
|
3784
|
+
const rest = ctx.children(withoutEls(n, [trigger]));
|
|
3689
3785
|
const body = ctx.serialize(rest).trim();
|
|
3690
|
-
return [
|
|
3786
|
+
return [detailsBlock(summary, body)];
|
|
3691
3787
|
} });
|
|
3692
3788
|
/** `<Tabs>` → `**tab**` headings + content sections. */
|
|
3693
3789
|
const tabs = { flow: (n, ctx) => {
|
|
@@ -3762,7 +3858,7 @@ const frac = (n) => {
|
|
|
3762
3858
|
const max = num(n, "max") ?? 100;
|
|
3763
3859
|
return {
|
|
3764
3860
|
text: `${attr(n, "value") ?? Math.round(value)}${nonEmpty(attr(n, "max")) ? `/${attr(n, "max")}` : "%"}`,
|
|
3765
|
-
v: max
|
|
3861
|
+
v: max <= 0 ? 0 : Math.max(0, Math.min(1, value / max))
|
|
3766
3862
|
};
|
|
3767
3863
|
};
|
|
3768
3864
|
const progress = {
|
|
@@ -3813,7 +3909,7 @@ const otp = {
|
|
|
3813
3909
|
return [pre(`[ ${"_ ".repeat(count).trim()} ]`)];
|
|
3814
3910
|
},
|
|
3815
3911
|
text: (n) => {
|
|
3816
|
-
const count = num(n, "maxLength") ?? 6;
|
|
3912
|
+
const count = Math.max(0, num(n, "maxLength") ?? 6);
|
|
3817
3913
|
return [icode(`[${"_".repeat(count)}]`)];
|
|
3818
3914
|
}
|
|
3819
3915
|
};
|
|
@@ -4091,50 +4187,54 @@ const mdxToAscii = async (source, filePath = "document.mdx") => {
|
|
|
4091
4187
|
};
|
|
4092
4188
|
//#endregion
|
|
4093
4189
|
//#region src/catalog.ts
|
|
4190
|
+
const OPTIONAL_TYPES = /* @__PURE__ */ new Set([
|
|
4191
|
+
"exact_optional",
|
|
4192
|
+
"nullish",
|
|
4193
|
+
"optional"
|
|
4194
|
+
]);
|
|
4195
|
+
const describeLiteral = (value) => {
|
|
4196
|
+
if (typeof value === "bigint") return `${value}n`;
|
|
4197
|
+
return JSON.stringify(value) ?? String(value);
|
|
4198
|
+
};
|
|
4199
|
+
const pipeConstraint = (schema) => {
|
|
4200
|
+
if (!Array.isArray(schema.pipe)) return;
|
|
4201
|
+
return schema.pipe.find((s) => isRecord(s) && (s.type === "picklist" || s.type === "union" || s.type === "literal"));
|
|
4202
|
+
};
|
|
4094
4203
|
/** Structural view of a valibot schema — enough for catalog introspection. */
|
|
4095
4204
|
const describeSchema = (schema) => {
|
|
4096
4205
|
if (!isRecord(schema)) return "unknown";
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4206
|
+
switch (schema.type) {
|
|
4207
|
+
case "nullable":
|
|
4208
|
+
case "nullish": return `${describeSchema(schema.wrapped)} | null`;
|
|
4209
|
+
case "optional":
|
|
4210
|
+
case "exact_optional": return describeSchema(schema.wrapped);
|
|
4211
|
+
case "array": {
|
|
4212
|
+
const item = describeSchema(schema.item);
|
|
4213
|
+
return item.includes(" | ") ? `(${item})[]` : `${item}[]`;
|
|
4214
|
+
}
|
|
4215
|
+
case "literal": return describeLiteral(schema.literal);
|
|
4216
|
+
case "picklist":
|
|
4217
|
+
case "union": if (Array.isArray(schema.options)) {
|
|
4218
|
+
const describe = schema.type === "picklist" ? describeLiteral : describeSchema;
|
|
4219
|
+
return schema.options.map(describe).join(" | ");
|
|
4220
|
+
}
|
|
4104
4221
|
}
|
|
4222
|
+
const constrained = pipeConstraint(schema);
|
|
4223
|
+
if (constrained !== void 0) return describeSchema(constrained);
|
|
4105
4224
|
return typeof schema.type === "string" ? schema.type : "unknown";
|
|
4106
4225
|
};
|
|
4107
|
-
const WRAPPER_TYPES = /* @__PURE__ */ new Set([
|
|
4108
|
-
"nullable",
|
|
4109
|
-
"nullish",
|
|
4110
|
-
"optional"
|
|
4111
|
-
]);
|
|
4112
|
-
const unwrapProp = (raw) => {
|
|
4113
|
-
let s = isRecord(raw) ? raw : {};
|
|
4114
|
-
let required = true;
|
|
4115
|
-
let def;
|
|
4116
|
-
while (typeof s.type === "string" && WRAPPER_TYPES.has(s.type)) {
|
|
4117
|
-
required = false;
|
|
4118
|
-
def ??= s.default;
|
|
4119
|
-
s = isRecord(s.wrapped) ? s.wrapped : {};
|
|
4120
|
-
}
|
|
4121
|
-
return {
|
|
4122
|
-
def,
|
|
4123
|
-
required,
|
|
4124
|
-
s
|
|
4125
|
-
};
|
|
4126
|
-
};
|
|
4127
4226
|
const propsOf = (schema) => {
|
|
4128
4227
|
if (!isRecord(schema) || !isRecord(schema.entries)) return {};
|
|
4129
4228
|
const out = {};
|
|
4130
4229
|
for (const [key, raw] of Object.entries(schema.entries)) {
|
|
4131
|
-
const
|
|
4230
|
+
const optional = isRecord(raw) && typeof raw.type === "string" && OPTIONAL_TYPES.has(raw.type);
|
|
4231
|
+
const def = optional ? raw.default : void 0;
|
|
4132
4232
|
Object.defineProperty(out, key, {
|
|
4133
4233
|
configurable: true,
|
|
4134
4234
|
enumerable: true,
|
|
4135
4235
|
value: {
|
|
4136
|
-
required,
|
|
4137
|
-
type: describeSchema(
|
|
4236
|
+
required: !optional,
|
|
4237
|
+
type: describeSchema(raw),
|
|
4138
4238
|
...def === void 0 ? {} : { default: def }
|
|
4139
4239
|
},
|
|
4140
4240
|
writable: true
|
|
@@ -4451,7 +4551,7 @@ const openInBrowser = async (target) => {
|
|
|
4451
4551
|
};
|
|
4452
4552
|
//#endregion
|
|
4453
4553
|
//#region src/client-js.ts
|
|
4454
|
-
let cache;
|
|
4554
|
+
let cache$1;
|
|
4455
4555
|
const buildClientJs = async () => {
|
|
4456
4556
|
return (await build({
|
|
4457
4557
|
absWorkingDir: pkgRoot,
|
|
@@ -4479,11 +4579,11 @@ const buildClientJs = async () => {
|
|
|
4479
4579
|
* bundle is identical for every render in a process.
|
|
4480
4580
|
*/
|
|
4481
4581
|
const clientJs = async () => {
|
|
4482
|
-
cache ??= buildClientJs();
|
|
4582
|
+
cache$1 ??= buildClientJs();
|
|
4483
4583
|
try {
|
|
4484
|
-
return await cache;
|
|
4584
|
+
return await cache$1;
|
|
4485
4585
|
} catch (error) {
|
|
4486
|
-
cache = void 0;
|
|
4586
|
+
cache$1 = void 0;
|
|
4487
4587
|
throw error;
|
|
4488
4588
|
}
|
|
4489
4589
|
};
|
|
@@ -4577,6 +4677,338 @@ addEventListener('DOMContentLoaded', function () {
|
|
|
4577
4677
|
});
|
|
4578
4678
|
`;
|
|
4579
4679
|
//#endregion
|
|
4680
|
+
//#region src/components/ui/toc.tsx
|
|
4681
|
+
/**
|
|
4682
|
+
* Table of contents with active-anchor tracking and depth-aware line styling.
|
|
4683
|
+
* Adapted from the Crux UI / ABUI registry (`@abui/toc`), itself derived from
|
|
4684
|
+
* fumadocs' TOC. Two list variants: `TOCItems` (straight line) and
|
|
4685
|
+
* `ClerkTOCItems` (line that follows the heading hierarchy).
|
|
4686
|
+
*/
|
|
4687
|
+
const ActiveAnchorContext = createContext([]);
|
|
4688
|
+
const ScrollContext = createContext({ current: null });
|
|
4689
|
+
const TOCContext = createContext([]);
|
|
4690
|
+
/** The ids of visible anchors. */
|
|
4691
|
+
const useActiveAnchors = () => useContext(ActiveAnchorContext);
|
|
4692
|
+
const useTOCItems = () => useContext(TOCContext);
|
|
4693
|
+
const mergeRefs = (...refs) => (value) => {
|
|
4694
|
+
for (const ref of refs) if (typeof ref === "function") ref(value);
|
|
4695
|
+
else if (ref !== null && ref !== void 0) ref.current = value;
|
|
4696
|
+
};
|
|
4697
|
+
/** Track which of the `watch` heading ids are on screen. */
|
|
4698
|
+
const useAnchorObserver = (watch, single) => {
|
|
4699
|
+
const observerRef = useRef(null);
|
|
4700
|
+
const visibleRef = useRef(/* @__PURE__ */ new Set());
|
|
4701
|
+
const [activeAnchor, setActiveAnchor] = useState([]);
|
|
4702
|
+
const onChange = useCallback((entries) => {
|
|
4703
|
+
const visible = visibleRef.current;
|
|
4704
|
+
for (const entry of entries) if (entry.isIntersecting) visible.add(entry.target.id);
|
|
4705
|
+
else visible.delete(entry.target.id);
|
|
4706
|
+
if (visible.size > 0) {
|
|
4707
|
+
const items = watch.filter((item) => visible.has(item));
|
|
4708
|
+
setActiveAnchor(single ? items.slice(0, 1) : items);
|
|
4709
|
+
return;
|
|
4710
|
+
}
|
|
4711
|
+
const viewTop = entries[0]?.rootBounds?.top ?? 0;
|
|
4712
|
+
let fallback;
|
|
4713
|
+
let min = -1;
|
|
4714
|
+
for (const id of watch) {
|
|
4715
|
+
const element = document.getElementById(id);
|
|
4716
|
+
if (element === null) continue;
|
|
4717
|
+
const d = Math.abs(viewTop - element.getBoundingClientRect().top);
|
|
4718
|
+
if (min === -1 || d < min) {
|
|
4719
|
+
fallback = element;
|
|
4720
|
+
min = d;
|
|
4721
|
+
}
|
|
4722
|
+
}
|
|
4723
|
+
setActiveAnchor(fallback === void 0 ? [] : [fallback.id]);
|
|
4724
|
+
}, [watch, single]);
|
|
4725
|
+
useEffect(() => {
|
|
4726
|
+
if (observerRef.current !== null) return;
|
|
4727
|
+
observerRef.current = new IntersectionObserver(onChange, {
|
|
4728
|
+
rootMargin: "0px",
|
|
4729
|
+
threshold: .98
|
|
4730
|
+
});
|
|
4731
|
+
return () => {
|
|
4732
|
+
observerRef.current?.disconnect();
|
|
4733
|
+
observerRef.current = null;
|
|
4734
|
+
};
|
|
4735
|
+
}, [onChange]);
|
|
4736
|
+
useEffect(() => {
|
|
4737
|
+
const observer = observerRef.current;
|
|
4738
|
+
if (observer === null) return;
|
|
4739
|
+
const elements = watch.flatMap((heading) => document.getElementById(heading) ?? []);
|
|
4740
|
+
for (const element of elements) observer.observe(element);
|
|
4741
|
+
return () => {
|
|
4742
|
+
for (const element of elements) observer.unobserve(element);
|
|
4743
|
+
};
|
|
4744
|
+
}, [watch]);
|
|
4745
|
+
return activeAnchor;
|
|
4746
|
+
};
|
|
4747
|
+
const TOCProvider = ({ toc, single = false, children }) => {
|
|
4748
|
+
const headings = useMemo(() => toc.map((item) => item.url.split("#")[1] ?? ""), [toc]);
|
|
4749
|
+
const activeAnchors = useAnchorObserver(headings, single);
|
|
4750
|
+
return /* @__PURE__ */ jsx(TOCContext.Provider, {
|
|
4751
|
+
value: toc,
|
|
4752
|
+
children: /* @__PURE__ */ jsx(ActiveAnchorContext.Provider, {
|
|
4753
|
+
value: activeAnchors,
|
|
4754
|
+
children
|
|
4755
|
+
})
|
|
4756
|
+
});
|
|
4757
|
+
};
|
|
4758
|
+
/** Scrollable container; the first active item is kept in view. */
|
|
4759
|
+
const TOCScrollArea = ({ ref, className, children, ...props }) => {
|
|
4760
|
+
const viewRef = useRef(null);
|
|
4761
|
+
return /* @__PURE__ */ jsx("div", {
|
|
4762
|
+
className: cn("relative ms-px min-h-0 [scrollbar-width:none] overflow-auto [mask-image:linear-gradient(to_bottom,transparent,white_16px,white_calc(100%-16px),transparent)] py-3 text-sm", className),
|
|
4763
|
+
ref: mergeRefs(viewRef, ref),
|
|
4764
|
+
...props,
|
|
4765
|
+
children: /* @__PURE__ */ jsx(ScrollContext.Provider, {
|
|
4766
|
+
value: viewRef,
|
|
4767
|
+
children
|
|
4768
|
+
})
|
|
4769
|
+
});
|
|
4770
|
+
};
|
|
4771
|
+
const calcThumb = (container, active) => {
|
|
4772
|
+
if (active.length === 0 || container.clientHeight === 0) return [0, 0];
|
|
4773
|
+
let upper = Number.MAX_VALUE;
|
|
4774
|
+
let lower = 0;
|
|
4775
|
+
for (const item of active) {
|
|
4776
|
+
const element = container.querySelector(`a[href="#${CSS.escape(item)}"]`);
|
|
4777
|
+
if (element === null) continue;
|
|
4778
|
+
const styles = getComputedStyle(element);
|
|
4779
|
+
upper = Math.min(upper, element.offsetTop + Number.parseFloat(styles.paddingTop));
|
|
4780
|
+
lower = Math.max(lower, element.offsetTop + element.clientHeight - Number.parseFloat(styles.paddingBottom));
|
|
4781
|
+
}
|
|
4782
|
+
return [upper, lower - upper];
|
|
4783
|
+
};
|
|
4784
|
+
const updateThumb = (element, [top, height]) => {
|
|
4785
|
+
element.style.setProperty("--toc-top", `${top}px`);
|
|
4786
|
+
element.style.setProperty("--toc-height", `${height}px`);
|
|
4787
|
+
};
|
|
4788
|
+
/** Active indicator that slides alongside the visible entries. */
|
|
4789
|
+
const TocThumb = ({ containerRef, ...props }) => {
|
|
4790
|
+
const thumbRef = useRef(null);
|
|
4791
|
+
const active = useActiveAnchors();
|
|
4792
|
+
useEffect(() => {
|
|
4793
|
+
const container = containerRef.current;
|
|
4794
|
+
if (container === null) return;
|
|
4795
|
+
const onUpdate = () => {
|
|
4796
|
+
if (thumbRef.current !== null) updateThumb(thumbRef.current, calcThumb(container, active));
|
|
4797
|
+
};
|
|
4798
|
+
const observer = new ResizeObserver(onUpdate);
|
|
4799
|
+
observer.observe(container);
|
|
4800
|
+
onUpdate();
|
|
4801
|
+
return () => {
|
|
4802
|
+
observer.disconnect();
|
|
4803
|
+
};
|
|
4804
|
+
}, [containerRef, active]);
|
|
4805
|
+
return /* @__PURE__ */ jsx("div", {
|
|
4806
|
+
ref: thumbRef,
|
|
4807
|
+
role: "none",
|
|
4808
|
+
...props
|
|
4809
|
+
});
|
|
4810
|
+
};
|
|
4811
|
+
const TOCItem = ({ ref, onActiveChange, children, ...props }) => {
|
|
4812
|
+
const containerRef = useContext(ScrollContext);
|
|
4813
|
+
const anchorRef = useRef(null);
|
|
4814
|
+
const activeOrder = useActiveAnchors().indexOf(props.href.slice(1));
|
|
4815
|
+
const isActive = activeOrder !== -1;
|
|
4816
|
+
const shouldScroll = activeOrder === 0;
|
|
4817
|
+
useLayoutEffect(() => {
|
|
4818
|
+
const anchor = anchorRef.current;
|
|
4819
|
+
const container = containerRef.current;
|
|
4820
|
+
if (container === null || anchor === null || !shouldScroll || !anchor.isConnected) return;
|
|
4821
|
+
const containerRect = container.getBoundingClientRect();
|
|
4822
|
+
const anchorRect = anchor.getBoundingClientRect();
|
|
4823
|
+
if (!(anchorRect.top < containerRect.top || anchorRect.bottom > containerRect.bottom)) return;
|
|
4824
|
+
const anchorCenter = anchor.offsetTop - container.offsetTop + anchor.offsetHeight / 2;
|
|
4825
|
+
container.scrollTo({
|
|
4826
|
+
behavior: "smooth",
|
|
4827
|
+
top: Math.max(0, anchorCenter - container.clientHeight / 2)
|
|
4828
|
+
});
|
|
4829
|
+
}, [containerRef, shouldScroll]);
|
|
4830
|
+
useEffect(() => {
|
|
4831
|
+
onActiveChange?.(isActive);
|
|
4832
|
+
}, [isActive, onActiveChange]);
|
|
4833
|
+
return /* @__PURE__ */ jsx("a", {
|
|
4834
|
+
"data-active": isActive,
|
|
4835
|
+
ref: mergeRefs(anchorRef, ref),
|
|
4836
|
+
...props,
|
|
4837
|
+
children
|
|
4838
|
+
});
|
|
4839
|
+
};
|
|
4840
|
+
const EMPTY_CLS = "rounded-lg border bg-card p-3 text-xs text-muted-foreground";
|
|
4841
|
+
const ITEM_CLS = "py-1.5 text-sm text-muted-foreground transition-colors [overflow-wrap:anywhere] first:pt-0 last:pb-0 hover:text-accent-foreground data-[active=true]:text-primary";
|
|
4842
|
+
const SimpleTOCItem = ({ item }) => /* @__PURE__ */ jsx(TOCItem, {
|
|
4843
|
+
className: cn(ITEM_CLS, item.depth <= 2 && "ps-3", item.depth === 3 && "ps-6", item.depth >= 4 && "ps-8"),
|
|
4844
|
+
href: item.url,
|
|
4845
|
+
children: item.title
|
|
4846
|
+
});
|
|
4847
|
+
/** TOC list with a straight border line. */
|
|
4848
|
+
const TOCItems = ({ ref, className, emptyText = "No Headings", ...props }) => {
|
|
4849
|
+
const containerRef = useRef(null);
|
|
4850
|
+
const items = useTOCItems();
|
|
4851
|
+
if (items.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
4852
|
+
className: EMPTY_CLS,
|
|
4853
|
+
children: emptyText
|
|
4854
|
+
});
|
|
4855
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(TocThumb, {
|
|
4856
|
+
className: "bg-primary absolute top-(--toc-top) h-(--toc-height) w-px transition-all",
|
|
4857
|
+
containerRef
|
|
4858
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
4859
|
+
className: cn("border-foreground/10 flex flex-col border-s", className),
|
|
4860
|
+
ref: mergeRefs(ref, containerRef),
|
|
4861
|
+
...props,
|
|
4862
|
+
children: items.map((item) => /* @__PURE__ */ jsx(SimpleTOCItem, { item }, item.url))
|
|
4863
|
+
})] });
|
|
4864
|
+
};
|
|
4865
|
+
const getItemOffset = (depth) => {
|
|
4866
|
+
if (depth <= 2) return 14;
|
|
4867
|
+
return depth === 3 ? 26 : 36;
|
|
4868
|
+
};
|
|
4869
|
+
const getLineOffset = (depth) => depth >= 3 ? 10 : 0;
|
|
4870
|
+
/** One vertical segment per entry, shifted right for nested headings. */
|
|
4871
|
+
const clerkLine = (container, items) => {
|
|
4872
|
+
let width = 0;
|
|
4873
|
+
let height = 0;
|
|
4874
|
+
const d = [];
|
|
4875
|
+
for (const [i, item] of items.entries()) {
|
|
4876
|
+
const element = container.querySelector(`a[href="${CSS.escape(item.url)}"]`);
|
|
4877
|
+
if (element === null) continue;
|
|
4878
|
+
const styles = getComputedStyle(element);
|
|
4879
|
+
const offset = getLineOffset(item.depth) + 1;
|
|
4880
|
+
const top = element.offsetTop + Number.parseFloat(styles.paddingTop);
|
|
4881
|
+
const bottom = element.offsetTop + element.clientHeight - Number.parseFloat(styles.paddingBottom);
|
|
4882
|
+
width = Math.max(offset, width);
|
|
4883
|
+
height = Math.max(height, bottom);
|
|
4884
|
+
d.push(`${i === 0 ? "M" : "L"}${offset} ${top}`, `L${offset} ${bottom}`);
|
|
4885
|
+
}
|
|
4886
|
+
return {
|
|
4887
|
+
height,
|
|
4888
|
+
path: d.join(" "),
|
|
4889
|
+
width: width + 1
|
|
4890
|
+
};
|
|
4891
|
+
};
|
|
4892
|
+
const ClerkTOCItemElement = ({ item, upper = item.depth, lower = item.depth }) => {
|
|
4893
|
+
const offset = getLineOffset(item.depth);
|
|
4894
|
+
const upperOffset = getLineOffset(upper);
|
|
4895
|
+
const lowerOffset = getLineOffset(lower);
|
|
4896
|
+
return /* @__PURE__ */ jsxs(TOCItem, {
|
|
4897
|
+
className: cn("relative", ITEM_CLS),
|
|
4898
|
+
href: item.url,
|
|
4899
|
+
style: { paddingInlineStart: getItemOffset(item.depth) },
|
|
4900
|
+
children: [
|
|
4901
|
+
offset === upperOffset ? null : /* @__PURE__ */ jsx("svg", {
|
|
4902
|
+
"aria-hidden": "true",
|
|
4903
|
+
className: "absolute start-0 -top-1.5 size-4 rtl:-scale-x-100",
|
|
4904
|
+
viewBox: "0 0 16 16",
|
|
4905
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
4906
|
+
children: /* @__PURE__ */ jsx("line", {
|
|
4907
|
+
className: "stroke-foreground/10",
|
|
4908
|
+
strokeWidth: "1",
|
|
4909
|
+
x1: upperOffset,
|
|
4910
|
+
x2: offset,
|
|
4911
|
+
y1: "0",
|
|
4912
|
+
y2: "12"
|
|
4913
|
+
})
|
|
4914
|
+
}),
|
|
4915
|
+
/* @__PURE__ */ jsx("div", {
|
|
4916
|
+
className: cn("bg-foreground/10 absolute inset-y-0 w-px", offset !== upperOffset && "top-1.5", offset !== lowerOffset && "bottom-1.5"),
|
|
4917
|
+
style: { insetInlineStart: offset }
|
|
4918
|
+
}),
|
|
4919
|
+
item.title
|
|
4920
|
+
]
|
|
4921
|
+
});
|
|
4922
|
+
};
|
|
4923
|
+
/** TOC list whose line bends to follow the heading hierarchy. */
|
|
4924
|
+
const ClerkTOCItems = ({ ref, className, emptyText = "No Headings", ...props }) => {
|
|
4925
|
+
const containerRef = useRef(null);
|
|
4926
|
+
const items = useTOCItems();
|
|
4927
|
+
const [svg, setSvg] = useState();
|
|
4928
|
+
useEffect(() => {
|
|
4929
|
+
const container = containerRef.current;
|
|
4930
|
+
if (container === null) return;
|
|
4931
|
+
const onResize = () => {
|
|
4932
|
+
if (container.clientHeight > 0) setSvg(clerkLine(container, items));
|
|
4933
|
+
};
|
|
4934
|
+
const observer = new ResizeObserver(onResize);
|
|
4935
|
+
onResize();
|
|
4936
|
+
observer.observe(container);
|
|
4937
|
+
return () => {
|
|
4938
|
+
observer.disconnect();
|
|
4939
|
+
};
|
|
4940
|
+
}, [items]);
|
|
4941
|
+
if (items.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
4942
|
+
className: EMPTY_CLS,
|
|
4943
|
+
children: emptyText
|
|
4944
|
+
});
|
|
4945
|
+
const mask = svg && {
|
|
4946
|
+
height: svg.height,
|
|
4947
|
+
maskImage: `url("data:image/svg+xml,${encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${svg.width} ${svg.height}"><path d="${svg.path}" stroke="black" stroke-width="1" fill="none" /></svg>`)}")`,
|
|
4948
|
+
width: svg.width
|
|
4949
|
+
};
|
|
4950
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [mask ? /* @__PURE__ */ jsx("div", {
|
|
4951
|
+
className: "absolute start-0 top-0 rtl:-scale-x-100",
|
|
4952
|
+
style: mask,
|
|
4953
|
+
children: /* @__PURE__ */ jsx(TocThumb, {
|
|
4954
|
+
className: "bg-primary mt-(--toc-top) h-(--toc-height) transition-all",
|
|
4955
|
+
containerRef
|
|
4956
|
+
})
|
|
4957
|
+
}) : null, /* @__PURE__ */ jsx("div", {
|
|
4958
|
+
className: cn("flex flex-col", className),
|
|
4959
|
+
ref: mergeRefs(containerRef, ref),
|
|
4960
|
+
...props,
|
|
4961
|
+
children: items.map((item, i) => /* @__PURE__ */ jsx(ClerkTOCItemElement, {
|
|
4962
|
+
item,
|
|
4963
|
+
lower: items[i + 1]?.depth,
|
|
4964
|
+
upper: items[i - 1]?.depth
|
|
4965
|
+
}, item.url))
|
|
4966
|
+
})] });
|
|
4967
|
+
};
|
|
4968
|
+
const PageTOC = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
|
|
4969
|
+
className: cn("flex flex-col gap-3", className),
|
|
4970
|
+
...props
|
|
4971
|
+
});
|
|
4972
|
+
const PageTOCItems = ({ variant = "default", ...props }) => /* @__PURE__ */ jsx(TOCScrollArea, { children: variant === "clerk" ? /* @__PURE__ */ jsx(ClerkTOCItems, { ...props }) : /* @__PURE__ */ jsx(TOCItems, { ...props }) });
|
|
4973
|
+
//#endregion
|
|
4974
|
+
//#region src/ui/page-toc.tsx
|
|
4975
|
+
/** `<aside>` holding the sidebar and the JSON island with its headings. */
|
|
4976
|
+
const PAGE_TOC_ID = "mdxr-page-toc";
|
|
4977
|
+
const PAGE_TOC_DATA_ID = "mdxr-page-toc-data";
|
|
4978
|
+
/** Heading levels listed in the sidebar (h2–h3, like `:::toc`). */
|
|
4979
|
+
const MIN_DEPTH = 2;
|
|
4980
|
+
const MAX_DEPTH = 3;
|
|
4981
|
+
/** A single entry isn't worth a sidebar. */
|
|
4982
|
+
const MIN_ENTRIES = 2;
|
|
4983
|
+
/** The headings the sidebar lists — empty when the page gets no sidebar. */
|
|
4984
|
+
const pageTocHeadings = (headings) => {
|
|
4985
|
+
const items = headings.filter((h) => h.depth >= MIN_DEPTH && h.depth <= MAX_DEPTH);
|
|
4986
|
+
return items.length < MIN_ENTRIES ? [] : items;
|
|
4987
|
+
};
|
|
4988
|
+
/**
|
|
4989
|
+
* Page-level "On this page" sidebar built on the Crux UI ToC: a clerk-style
|
|
4990
|
+
* outline whose thumb follows the headings currently on screen. Rendered next
|
|
4991
|
+
* to `<main>` by `htmlDocument` (not part of the MDX tree) and hydrated by its
|
|
4992
|
+
* own root, so it works with or without the document hydration bundle.
|
|
4993
|
+
*/
|
|
4994
|
+
const PageToc = ({ headings }) => {
|
|
4995
|
+
const toc = useMemo(() => headings.map((h) => ({
|
|
4996
|
+
depth: h.depth,
|
|
4997
|
+
title: h.text,
|
|
4998
|
+
url: `#${h.slug}`
|
|
4999
|
+
})), [headings]);
|
|
5000
|
+
return /* @__PURE__ */ jsx(TOCProvider, {
|
|
5001
|
+
toc,
|
|
5002
|
+
children: /* @__PURE__ */ jsxs(PageTOC, {
|
|
5003
|
+
className: "min-h-0",
|
|
5004
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
5005
|
+
className: "text-muted-foreground m-0 text-xs font-semibold",
|
|
5006
|
+
children: "On this page"
|
|
5007
|
+
}), /* @__PURE__ */ jsx(PageTOCItems, { variant: "clerk" })]
|
|
5008
|
+
})
|
|
5009
|
+
});
|
|
5010
|
+
};
|
|
5011
|
+
//#endregion
|
|
4580
5012
|
//#region src/html.ts
|
|
4581
5013
|
/**
|
|
4582
5014
|
* JS destined for an inline `<script>` element: neutralize the two byte
|
|
@@ -4612,6 +5044,10 @@ const iconSvg = (name) => {
|
|
|
4612
5044
|
* `handleDocEvent`); THEME_JS restores the stored choice before paint.
|
|
4613
5045
|
*/
|
|
4614
5046
|
const THEME_TOGGLE_HTML = `<button type="button" class="mdxr-theme" data-mdxr-theme data-mode="auto" title="Theme: auto" aria-label="Switch theme (current: auto)"><span class="mdxr-theme-i mdxr-theme-i-auto">${iconSvg("sun-moon")}</span><span class="mdxr-theme-i mdxr-theme-i-light">${iconSvg("sun")}</span><span class="mdxr-theme-i mdxr-theme-i-dark">${iconSvg("moon")}</span></button>`;
|
|
5047
|
+
/** JSON for an inline `<script type="application/json">` island. */
|
|
5048
|
+
const jsonIsland = (data) => JSON.stringify(data).replaceAll("<", String.raw`\u003c`);
|
|
5049
|
+
const pageTocHtml = (toc) => toc === void 0 ? "" : `<aside id="${PAGE_TOC_ID}" aria-label="On this page" class="fixed top-20 left-[calc(50%+25.5rem)] hidden max-h-[calc(100vh-7rem)] w-52 flex-col xl:flex print:hidden">${toc.html}</aside>
|
|
5050
|
+
<script type="application/json" id="${PAGE_TOC_DATA_ID}">${jsonIsland(toc.headings)}<\/script>`;
|
|
4615
5051
|
const htmlDocument = (o) => `<!doctype html>
|
|
4616
5052
|
<html lang="en">
|
|
4617
5053
|
<head>
|
|
@@ -4626,10 +5062,12 @@ ${o.needsKatex === true ? `<link rel="stylesheet" href="${KATEX_CDN_URL}">` : ""
|
|
|
4626
5062
|
<body class="bg-white text-neutral-900 antialiased dark:bg-neutral-950 dark:text-neutral-100">
|
|
4627
5063
|
${THEME_TOGGLE_HTML}
|
|
4628
5064
|
<main id="mdxr-root" class="prose prose-neutral dark:prose-invert mx-auto max-w-3xl px-6 py-10">${o.body}</main>
|
|
5065
|
+
${pageTocHtml(o.pageToc)}
|
|
4629
5066
|
<script>${inlineScript(o.clientJs)}<\/script>
|
|
4630
5067
|
${o.needsMermaid ? `<script type="module">${inlineScript(MERMAID_JS)}<\/script>` : ""}
|
|
4631
5068
|
${o.liveReload === true ? `<script>${inlineScript(LIVE_RELOAD_JS)}<\/script>` : ""}
|
|
4632
5069
|
${o.hydrateJs === void 0 ? "" : `<script>${inlineScript(o.hydrateJs)}<\/script>`}
|
|
5070
|
+
${o.pageTocJs === void 0 ? "" : `<script>${inlineScript(o.pageTocJs)}<\/script>`}
|
|
4633
5071
|
</body>
|
|
4634
5072
|
</html>
|
|
4635
5073
|
`;
|
|
@@ -4883,14 +5321,16 @@ const iconSets = (usedIcons) => ({
|
|
|
4883
5321
|
*/
|
|
4884
5322
|
const scanVProps = (stripped, alias) => {
|
|
4885
5323
|
const esc = alias.replaceAll(/[$()*+.?[\\\]^{|}]/gu, "\\$&");
|
|
4886
|
-
const
|
|
5324
|
+
const identifierPart = String.raw`[\p{ID_Continue}$\u200C\u200D]`;
|
|
5325
|
+
const start = `(?<!${identifierPart})${esc}`;
|
|
5326
|
+
const access = new RegExp(`${start}\\s*\\??\\.\\s*(\\w+)`, "gu");
|
|
4887
5327
|
const props = /* @__PURE__ */ new Set();
|
|
4888
5328
|
for (const use of stripped.matchAll(access)) {
|
|
4889
|
-
const prop = use
|
|
5329
|
+
const [, prop] = use;
|
|
4890
5330
|
if (prop !== void 0) props.add(prop);
|
|
4891
5331
|
}
|
|
4892
5332
|
const leftover = stripped.replaceAll(access, "");
|
|
4893
|
-
return new RegExp(
|
|
5333
|
+
return new RegExp(`${start}(?!${identifierPart})`, "u").test(leftover) ? "all" : props;
|
|
4894
5334
|
};
|
|
4895
5335
|
/**
|
|
4896
5336
|
* `/* … *\/` and `// …` are legal inside a multiline import clause, and a
|
|
@@ -4988,7 +5428,8 @@ const scanMdxrImports = async (importer) => {
|
|
|
4988
5428
|
const EXTRA_MODULES = {
|
|
4989
5429
|
builtinComponents: path.join(srcDir, "ui/index.js"),
|
|
4990
5430
|
defineConfig: path.join(srcDir, "config.js"),
|
|
4991
|
-
mountDocument: path.join(srcDir, "hydrate-runtime.js")
|
|
5431
|
+
mountDocument: path.join(srcDir, "hydrate-runtime.js"),
|
|
5432
|
+
mountPageToc: path.join(srcDir, "page-toc-runtime.js")
|
|
4992
5433
|
};
|
|
4993
5434
|
/**
|
|
4994
5435
|
* The virtual module's `v` export. `export * as v` would materialize the
|
|
@@ -5086,6 +5527,7 @@ const buildHydrateScript = async (spec) => {
|
|
|
5086
5527
|
};
|
|
5087
5528
|
const mountDocument = bind("mountDocument");
|
|
5088
5529
|
const planHeader = spec.header === void 0 ? "undefined" : bind("PlanHeader");
|
|
5530
|
+
const mountPageToc = spec.pageToc === true ? `${bind("mountPageToc")}();` : "";
|
|
5089
5531
|
for (const name of spec.usedComponents) {
|
|
5090
5532
|
if (!Object.hasOwn(builtinComponents, name)) continue;
|
|
5091
5533
|
const exported = name === "pre" ? "Pre" : name;
|
|
@@ -5104,6 +5546,7 @@ ${mountDocument}({
|
|
|
5104
5546
|
planHeader: ${planHeader},
|
|
5105
5547
|
userModule: ${spec.componentsPath === void 0 ? "undefined" : "__mdxrUser"},
|
|
5106
5548
|
});
|
|
5549
|
+
${mountPageToc}
|
|
5107
5550
|
`;
|
|
5108
5551
|
const result = await build({
|
|
5109
5552
|
absWorkingDir: pkgRoot,
|
|
@@ -5605,6 +6048,7 @@ const mdxToHtml = async (source, components, filePath = "document.mdx", opts = {
|
|
|
5605
6048
|
context,
|
|
5606
6049
|
fileLinks: Object.fromEntries(fileLinks),
|
|
5607
6050
|
frontmatter,
|
|
6051
|
+
headings: compiled.data.mdxrHeadings ?? [],
|
|
5608
6052
|
hydrated,
|
|
5609
6053
|
renderWithHeader: (header) => {
|
|
5610
6054
|
try {
|
|
@@ -5624,6 +6068,44 @@ const mdxToHtml = async (source, components, filePath = "document.mdx", opts = {
|
|
|
5624
6068
|
};
|
|
5625
6069
|
};
|
|
5626
6070
|
//#endregion
|
|
6071
|
+
//#region src/page-toc-js.ts
|
|
6072
|
+
let cache;
|
|
6073
|
+
const buildPageTocJs = async () => {
|
|
6074
|
+
return (await build({
|
|
6075
|
+
absWorkingDir: pkgRoot,
|
|
6076
|
+
bundle: true,
|
|
6077
|
+
define: { "process.env.NODE_ENV": "\"production\"" },
|
|
6078
|
+
format: "iife",
|
|
6079
|
+
jsx: "automatic",
|
|
6080
|
+
jsxImportSource: "react",
|
|
6081
|
+
logLevel: "silent",
|
|
6082
|
+
minify: true,
|
|
6083
|
+
platform: "browser",
|
|
6084
|
+
stdin: {
|
|
6085
|
+
contents: "import { mountPageToc } from \"./page-toc-runtime.js\"; mountPageToc();",
|
|
6086
|
+
loader: "js",
|
|
6087
|
+
resolveDir: path.join(pkgRoot, "src"),
|
|
6088
|
+
sourcefile: "mdxr-page-toc.js"
|
|
6089
|
+
},
|
|
6090
|
+
target: "es2022",
|
|
6091
|
+
write: false
|
|
6092
|
+
})).outputFiles[0]?.text ?? "";
|
|
6093
|
+
};
|
|
6094
|
+
/**
|
|
6095
|
+
* Standalone bundle that hydrates the sidebar ToC — used only when the
|
|
6096
|
+
* document has no hydration bundle of its own (that one mounts the ToC with
|
|
6097
|
+
* the React copy it already ships). Memoized: identical for every render.
|
|
6098
|
+
*/
|
|
6099
|
+
const pageTocJs = async () => {
|
|
6100
|
+
cache ??= buildPageTocJs();
|
|
6101
|
+
try {
|
|
6102
|
+
return await cache;
|
|
6103
|
+
} catch (error) {
|
|
6104
|
+
cache = void 0;
|
|
6105
|
+
throw error;
|
|
6106
|
+
}
|
|
6107
|
+
};
|
|
6108
|
+
//#endregion
|
|
5627
6109
|
//#region src/assets/css.ts
|
|
5628
6110
|
/** Base CSS appended after Tailwind utilities (covers what utilities can't). */
|
|
5629
6111
|
const BASE_CSS = `
|
|
@@ -6131,6 +6613,7 @@ const buildHydrateBundle = async (args) => {
|
|
|
6131
6613
|
fileLinks: args.fileLinks,
|
|
6132
6614
|
header: args.headerProps,
|
|
6133
6615
|
now: args.now,
|
|
6616
|
+
pageToc: args.pageToc,
|
|
6134
6617
|
usedComponents: args.usedComponents,
|
|
6135
6618
|
usedIcons: args.usedIcons
|
|
6136
6619
|
});
|
|
@@ -6167,6 +6650,23 @@ const frontmatterHeader = (fm, body) => {
|
|
|
6167
6650
|
version: fm("version")
|
|
6168
6651
|
};
|
|
6169
6652
|
};
|
|
6653
|
+
/** Sidebar ToC entries — frontmatter `toc: false` opts the page out. */
|
|
6654
|
+
const sidebarHeadings = (frontmatter, headings) => frontmatter.toc === false || frontmatter.toc === "false" ? [] : pageTocHeadings(headings);
|
|
6655
|
+
/**
|
|
6656
|
+
* SSR markup for the sidebar ToC plus the script that hydrates it. The
|
|
6657
|
+
* document bundle mounts the ToC itself when there is one; otherwise the
|
|
6658
|
+
* standalone bundle does. Without JS the sidebar stays plain anchor links.
|
|
6659
|
+
*/
|
|
6660
|
+
const renderPageToc = async (headings, interactive, hasDocumentBundle) => {
|
|
6661
|
+
if (headings.length === 0) return {};
|
|
6662
|
+
return {
|
|
6663
|
+
pageToc: {
|
|
6664
|
+
headings,
|
|
6665
|
+
html: (interactive ? renderToString : renderToStaticMarkup)(createElement(PageToc, { headings }))
|
|
6666
|
+
},
|
|
6667
|
+
pageTocJs: interactive && !hasDocumentBundle ? await pageTocJs() : void 0
|
|
6668
|
+
};
|
|
6669
|
+
};
|
|
6170
6670
|
/** Render MDX source text to a standalone HTML document. */
|
|
6171
6671
|
const render = async (source, opts = {}) => {
|
|
6172
6672
|
const dir = path.resolve(opts.dir ?? process.cwd());
|
|
@@ -6179,7 +6679,7 @@ const render = async (source, opts = {}) => {
|
|
|
6179
6679
|
...builtinComponents,
|
|
6180
6680
|
...user.components
|
|
6181
6681
|
};
|
|
6182
|
-
const { body, code, fileLinks, frontmatter, renderedAt, renderWithHeader, usedComponents, usedIcons } = await mdxToHtml(source, components, filePath, {
|
|
6682
|
+
const { body, code, fileLinks, frontmatter, headings, renderedAt, renderWithHeader, usedComponents, usedIcons } = await mdxToHtml(source, components, filePath, {
|
|
6183
6683
|
editor: config.editor,
|
|
6184
6684
|
hydrate: opts.hydrate
|
|
6185
6685
|
});
|
|
@@ -6228,6 +6728,7 @@ const render = async (source, opts = {}) => {
|
|
|
6228
6728
|
];
|
|
6229
6729
|
const { css, dependencies } = await buildCss(sources, config.themePath);
|
|
6230
6730
|
opts.onDependencies?.(dependencies);
|
|
6731
|
+
const tocHeadings = sidebarHeadings(frontmatter, headings);
|
|
6231
6732
|
const [js, hydrateJs] = await Promise.all([clientJs(), buildHydrateBundle({
|
|
6232
6733
|
code,
|
|
6233
6734
|
config,
|
|
@@ -6235,9 +6736,11 @@ const render = async (source, opts = {}) => {
|
|
|
6235
6736
|
headerProps,
|
|
6236
6737
|
hydrate: opts.hydrate,
|
|
6237
6738
|
now: renderedAt,
|
|
6739
|
+
pageToc: tocHeadings.length > 0,
|
|
6238
6740
|
usedComponents,
|
|
6239
6741
|
usedIcons: docIcons
|
|
6240
6742
|
})]);
|
|
6743
|
+
const { pageToc, pageTocJs: tocJs } = await renderPageToc(tocHeadings, opts.hydrate ?? true, hydrateJs !== void 0);
|
|
6241
6744
|
return htmlDocument({
|
|
6242
6745
|
body: docBody,
|
|
6243
6746
|
clientJs: js,
|
|
@@ -6246,6 +6749,8 @@ const render = async (source, opts = {}) => {
|
|
|
6246
6749
|
liveReload: opts.liveReload,
|
|
6247
6750
|
needsKatex: /class="[^"]*katex/u.test(docBody),
|
|
6248
6751
|
needsMermaid: /class="[^"]*mermaid/u.test(docBody),
|
|
6752
|
+
pageToc,
|
|
6753
|
+
pageTocJs: tocJs,
|
|
6249
6754
|
title
|
|
6250
6755
|
});
|
|
6251
6756
|
};
|