@geml/geml 1.5.0 → 1.5.1
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/LICENSE +21 -21
- package/README.md +217 -217
- package/codemap/adapters/crg.mjs +120 -120
- package/codemap/adapters/joern.mjs +131 -131
- package/codemap/adapters/scip.mjs +658 -658
- package/codemap/browser-stub.mjs +29 -29
- package/codemap/build.mjs +609 -609
- package/codemap/cross-stack.mjs +303 -303
- package/codemap/detect.mjs +399 -399
- package/codemap/emit.mjs +480 -480
- package/codemap/entries.mjs +129 -129
- package/codemap/exclude.mjs +52 -52
- package/codemap/find.mjs +49 -49
- package/codemap/foldings.mjs +110 -110
- package/codemap/joern-export.sc +83 -83
- package/codemap/mcp-server.mjs +431 -431
- package/codemap/normalize.mjs +275 -275
- package/codemap/recipe-trust.mjs +103 -103
- package/codemap/refresh.mjs +310 -310
- package/codemap/render-all.mjs +77 -77
- package/codemap/serve.mjs +585 -585
- package/codemap/sfc-virtualize.mjs +367 -367
- package/codemap/verify.mjs +148 -148
- package/dist/diagnostics.d.ts +1 -1
- package/dist/diagnostics.js +3 -3
- package/dist/geml.js +156 -128
- package/dist/inline.js +7 -2
- package/dist/mcp.js +19 -19
- package/dist/render-html.js +35 -35
- package/dist/render.js +137 -158
- package/dist/table.d.ts +0 -4
- package/dist/table.js +57 -50
- package/dist/to-md.js +4 -1
- package/package.json +66 -66
package/dist/render.js
CHANGED
|
@@ -611,35 +611,14 @@ export class RenderCtx {
|
|
|
611
611
|
const maxRows = this.isCodemapDoc && id === "modules" ? Infinity : (this.opts.tableRows ?? 500);
|
|
612
612
|
const allRows = t.rows;
|
|
613
613
|
const rows = allRows.length > maxRows ? allRows.slice(0, maxRows) : allRows;
|
|
614
|
-
// Coverage grid for declared spans, so cells a span covers are not emitted.
|
|
615
|
-
const covered = rows.map((r) => r.map(() => false));
|
|
616
|
-
rows.forEach((row, r) => row.forEach((cell, c) => {
|
|
617
|
-
if (!cell.span)
|
|
618
|
-
return;
|
|
619
|
-
// Bound the sweep to the rendered grid regardless of the declared span, so
|
|
620
|
-
// an oversized span can never drive an O(hugerows×hugecols) loop (DoS).
|
|
621
|
-
const spanRows = Math.min(cell.span.rows, rows.length - r);
|
|
622
|
-
const spanCols = Math.min(cell.span.cols, row.length - c);
|
|
623
|
-
for (let dr = 0; dr < spanRows; dr++)
|
|
624
|
-
for (let dc = 0; dc < spanCols; dc++) {
|
|
625
|
-
if (dr === 0 && dc === 0)
|
|
626
|
-
continue;
|
|
627
|
-
const rr = r + dr, cc = c + dc;
|
|
628
|
-
if (covered[rr]?.[cc] !== undefined)
|
|
629
|
-
covered[rr][cc] = true;
|
|
630
|
-
}
|
|
631
|
-
}));
|
|
632
614
|
const thead = t.header
|
|
633
615
|
? `<thead><tr>${t.columns.map((col, c) => `<th${alignStyle(t.align[c])}>${esc(col)}</th>`).join("")}</tr></thead>`
|
|
634
616
|
: "";
|
|
635
617
|
const bodyRows = rows.map((row, r) => {
|
|
636
618
|
const cells = row.map((cell, c) => {
|
|
637
|
-
if (covered[r]?.[c])
|
|
638
|
-
return "";
|
|
639
|
-
const span = cell.span ? `${cell.span.rows > 1 ? ` rowspan="${cell.span.rows}"` : ""}${cell.span.cols > 1 ? ` colspan="${cell.span.cols}"` : ""}` : "";
|
|
640
619
|
const cls = cell.computed ? ' class="computed"' : "";
|
|
641
620
|
const sortVal = typeof cell.value === "number" ? ` data-sort="${cell.value}"` : "";
|
|
642
|
-
return `<td${alignStyle(cell.align ?? t.align[c])}${
|
|
621
|
+
return `<td${alignStyle(cell.align ?? t.align[c])}${cls}${sortVal}>${this.inlines(cell.inlines)}</td>`;
|
|
643
622
|
}).join("");
|
|
644
623
|
return `<tr>${cells}</tr>`;
|
|
645
624
|
}).join("\n");
|
|
@@ -1250,143 +1229,143 @@ function trunc(s, n) {
|
|
|
1250
1229
|
// ---------------------------------------------------------------------------
|
|
1251
1230
|
// Page shell, inline CSS, inline interactivity JS
|
|
1252
1231
|
// ---------------------------------------------------------------------------
|
|
1253
|
-
export const CSS = `
|
|
1254
|
-
:root { --fg:#1f2328; --muted:#656d76; --bd:#d0d7de; --bg:#fff; --accent:#2563eb; --code-bg:#f6f8fa; }
|
|
1255
|
-
* { box-sizing: border-box; }
|
|
1256
|
-
body { margin:0; color:var(--fg); background:#fafbfc; font:16px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,"PingFang SC","Microsoft Yahei",sans-serif; }
|
|
1257
|
-
main { max-width: 860px; margin: 0 auto; padding: 48px 24px 96px; background:var(--bg); }
|
|
1258
|
-
h1,h2,h3,h4,h5,h6 { line-height:1.25; margin:1.6em 0 .6em; scroll-margin-top:16px; }
|
|
1259
|
-
h1 { font-size:2em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }
|
|
1260
|
-
h2 { font-size:1.5em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }
|
|
1261
|
-
h3 { font-size:1.25em; } h4 { font-size:1em; }
|
|
1262
|
-
p { margin:.7em 0; }
|
|
1263
|
-
a { color:var(--accent); text-decoration:none; } a:hover { text-decoration:underline; }
|
|
1264
|
-
code { background:var(--code-bg); padding:.15em .35em; border-radius:6px; font:.88em ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; }
|
|
1265
|
-
pre { background:var(--code-bg); padding:14px 16px; border-radius:8px; overflow:auto; }
|
|
1266
|
-
pre code { background:none; padding:0; font-size:.85em; }
|
|
1267
|
-
pre.output { background:#0d1117; color:#e6edf3; }
|
|
1268
|
-
pre.output code { color:inherit; }
|
|
1269
|
-
ul,ol { padding-left:1.6em; } li { margin:.2em 0; }
|
|
1270
|
-
ul.task-list { list-style:none; padding-left:.2em; }
|
|
1271
|
-
li.task input[type=checkbox] { appearance:none; -webkit-appearance:none; width:1.1em; height:1.1em; margin:0 .5em 0 0; vertical-align:-.2em; border:1.5px solid #c8ccd0; border-radius:4px; background:#fff; position:relative; opacity:1; cursor:default; box-sizing:border-box; }
|
|
1272
|
-
li.task input[type=checkbox]:checked { background-color:#1f883d; border-color:#1f883d; }
|
|
1273
|
-
li.task input[type=checkbox]:checked::after { content:"✓"; position:absolute; top:0; right:0; bottom:0; left:0; display:flex; align-items:center; justify-content:center; color:#fff; font-size:.8em; line-height:1; font-weight:700; }
|
|
1274
|
-
aside.callout { border-left:4px solid var(--accent); background:#f0f6ff; padding:.4em 16px; border-radius:0 8px 8px 0; margin:1em 0; }
|
|
1275
|
-
aside.aside { border-left-color:#8b949e; background:#f6f8fa; }
|
|
1276
|
-
aside.warning { border-left-color:#d97706; background:#fff8f0; }
|
|
1277
|
-
aside.callout > :first-child { margin-top:0; } aside.callout > :last-child { margin-bottom:0; }
|
|
1278
|
-
figure { margin:1.2em 0; }
|
|
1279
|
-
figcaption { color:var(--muted); font-size:.86em; text-align:center; margin-top:.5em; }
|
|
1280
|
-
table.geml-table { border-collapse:collapse; width:100%; font-size:.92em; }
|
|
1281
|
-
table.geml-table th, table.geml-table td { border:1px solid var(--bd); padding:6px 12px; }
|
|
1282
|
-
table.geml-table thead th { background:var(--code-bg); cursor:pointer; user-select:none; white-space:nowrap; }
|
|
1283
|
-
table.geml-table thead th::after { content:" \\2195"; color:var(--muted); font-size:.8em; }
|
|
1284
|
-
table.geml-table thead th.asc::after { content:" \\2191"; color:var(--accent); }
|
|
1285
|
-
table.geml-table thead th.desc::after { content:" \\2193"; color:var(--accent); }
|
|
1286
|
-
table.geml-table tbody tr:nth-child(2n) { background:#fafbfc; }
|
|
1287
|
-
table.geml-table td.computed { color:#0a7c52; }
|
|
1288
|
-
table.geml-table tfoot td { background:var(--code-bg); font-weight:600; border-top:2px solid var(--bd); }
|
|
1289
|
-
.table-tools { margin-bottom:6px; } .table-filter { width:240px; max-width:100%; padding:5px 9px; border:1px solid var(--bd); border-radius:7px; font-size:.85em; }
|
|
1290
|
-
.table-figure details > summary { cursor:pointer; color:var(--muted); font-size:.86em; padding:4px 0; }
|
|
1291
|
-
.table-note { color:var(--muted); font-size:.82em; margin:6px 0 0; }
|
|
1292
|
-
.geml-chart { width:100%; height:auto; background:var(--bg); border:1px solid var(--bd); border-radius:8px; }
|
|
1293
|
-
.c-title { font-size:15px; font-weight:600; fill:var(--fg); }
|
|
1294
|
-
.c-grid { stroke:#eaecef; } .c-axis { stroke:#aab1b8; } .c-tick { font-size:11px; fill:var(--muted); } .c-legend { font-size:12px; fill:var(--fg); }
|
|
1295
|
-
.media { max-width:100%; border-radius:8px; }
|
|
1296
|
-
.diagram-src { color:var(--muted); } .render-error { color:#cf222e; }
|
|
1297
|
-
.math-block { overflow-x:auto; padding:.4em 0; }
|
|
1298
|
-
sup.fn a { font-size:.75em; }
|
|
1299
|
-
.geml-footer { max-width:860px; margin:0 auto; padding:16px 24px 40px; color:var(--muted); font-size:.82em; }
|
|
1300
|
-
.geml-footer code { font-size:.95em; }
|
|
1301
|
-
.code-graph { margin:1.4em 0; }
|
|
1302
|
-
.cg-mount { border:1px solid var(--bd); border-radius:8px; padding:10px 12px; background:var(--bg); }
|
|
1303
|
-
.cg-scroll { overflow:auto; min-height:52vh; max-height:72vh; }
|
|
1304
|
-
.cg-svg { display:block; }
|
|
1305
|
-
.cg-search-wrap { position:relative; display:inline-block; }
|
|
1306
|
-
.cg-search { font:12px/1.4 inherit; padding:2px 7px; border:1px solid var(--bd); border-radius:4px; background:var(--bg); color:var(--fg); min-width:13ch; }
|
|
1307
|
-
.cg-search-menu { position:absolute; z-index:30; top:calc(100% + 2px); left:0; min-width:24ch; max-width:52ch; max-height:52vh; overflow:auto; background:var(--bg); border:1px solid var(--bd); border-radius:6px; box-shadow:0 6px 20px rgba(0,0,0,.18); }
|
|
1308
|
-
.cg-search-row { display:block; width:100%; text-align:left; padding:4px 9px 4px 18px; border:0; background:none; color:var(--fg); cursor:pointer; font:12px/1.4 inherit; }
|
|
1309
|
-
.cg-search-row:hover { background:var(--bd); }
|
|
1310
|
-
.cg-search-count { position:sticky; top:0; padding:4px 9px; font-size:11px; opacity:.65; background:var(--bg); border-bottom:1px solid var(--bd); }
|
|
1311
|
-
.cg-search-grp { padding:6px 9px 2px; font-size:11px; font-weight:600; opacity:.7; border-top:1px solid var(--bd); }
|
|
1312
|
-
.cg-search-grp:first-of-type { border-top:0; }
|
|
1313
|
-
.cg-stage { display:flex; gap:10px; align-items:flex-start; }
|
|
1314
|
-
.cg-stage .cg-scroll { flex:1 1 auto; min-width:0; }
|
|
1315
|
-
.cg-src { flex:0 0 42%; max-width:46%; display:flex; flex-direction:column; border:1px solid var(--bd); border-radius:6px; overflow:hidden; background:var(--bg); }
|
|
1316
|
-
.cg-src-hd { display:flex; gap:8px; align-items:center; justify-content:space-between; padding:4px 8px; border-bottom:1px solid var(--bd); color:var(--muted); font:.76em ui-monospace,Consolas,monospace; word-break:break-all; }
|
|
1317
|
-
.cg-src-hd button { font:inherit; border:1px solid var(--bd); border-radius:5px; background:transparent; color:var(--muted); cursor:pointer; padding:0 6px; }
|
|
1318
|
-
.cg-src-body { margin:0; padding:8px 10px; overflow:auto; max-height:72vh; color:var(--fg); font:12px/1.5 ui-monospace,Consolas,monospace; white-space:pre; }
|
|
1319
|
-
.cg-src-note { color:var(--muted); font-style:italic; white-space:pre-wrap; }
|
|
1320
|
-
.cg-bar { display:flex; gap:8px; align-items:center; flex-wrap:wrap; font-size:.82em; color:var(--muted); margin-bottom:6px; }
|
|
1321
|
-
.cg-bar button { font:inherit; padding:1px 8px; border:1px solid var(--bd); border-radius:5px; background:transparent; cursor:pointer; }
|
|
1322
|
-
.cg-crumb .cg-seg { border:0; border-radius:0; padding:0; background:none; color:var(--accent); cursor:pointer; font:inherit; }
|
|
1323
|
-
.cg-crumb .cg-seg:hover { text-decoration:underline; }
|
|
1324
|
-
.cg-frame { display:block; width:100%; height:72vh; border:0; background:var(--bg); }
|
|
1325
|
-
.cg-flash { color:#b42318; }
|
|
1326
|
-
.cg-legend { display:flex; gap:14px; align-items:center; justify-content:space-between; flex-wrap:wrap; font-size:.75em; color:var(--muted); margin-top:6px; }
|
|
1327
|
-
.cg-upbtn { cursor:pointer; }
|
|
1328
|
-
.cg-upbtn circle { fill:#fff; stroke:#94a3b8; }
|
|
1329
|
-
.cg-upbtn text { font-size:11px; fill:#57606a; }
|
|
1330
|
-
.cg-upbtn:hover circle { stroke:var(--accent); stroke-width:1.6; }
|
|
1331
|
-
.cg-upbtn:hover text { fill:var(--accent); }
|
|
1332
|
-
.cg-uplink { fill:none; stroke:#94a3b8; stroke-dasharray:3 2.5; pointer-events:none; }
|
|
1333
|
-
.cg-groups { display:flex; flex-wrap:wrap; gap:4px 12px; margin-top:6px; font-size:.75em; color:var(--muted); }
|
|
1334
|
-
.cg-chip { display:inline-flex; align-items:center; gap:4px; }
|
|
1335
|
-
.cg-chip i { width:10px; height:10px; border-radius:2px; border:1px solid #94a3b8; display:inline-block; }
|
|
1336
|
-
.cg-note { font-size:.8em; color:#9a6700; }
|
|
1337
|
-
.cg-n rect { fill:#eef2f7; stroke:#94a3b8; }
|
|
1338
|
-
.cg-n text { font-size:12px; fill:var(--fg); font-family:ui-monospace,Consolas,monospace; }
|
|
1339
|
-
.cg-n { cursor:pointer; }
|
|
1340
|
-
.cg-n.root rect { fill:#dbeafe; stroke:#2563eb; stroke-width:2; }
|
|
1341
|
-
.cg-n.leaf { opacity:.45; }
|
|
1342
|
-
.cg-n.test rect { stroke-dasharray:3 2; }
|
|
1343
|
-
.cg-n.grp rect { stroke-width:1.8; }
|
|
1344
|
-
.cg-e { fill:none; stroke:#94a3b8; stroke-width:.9; }
|
|
1345
|
-
.cg-e.cand { stroke-dasharray:2 3; }
|
|
1346
|
-
.cg-e.back { stroke:#dc2626; stroke-dasharray:5 3; }
|
|
1347
|
-
.cg-e.http { stroke:#0891b2; stroke-width:1.5; stroke-dasharray:5 2; } /* cross-stack API link */
|
|
1348
|
-
.cg-e.soft { opacity:.55; }
|
|
1349
|
-
.cg-svg.hl .cg-n { opacity:.22; }
|
|
1350
|
-
.cg-svg.hl .cg-e { opacity:.1; }
|
|
1351
|
-
.cg-svg.hl .cg-n.hl { opacity:1; }
|
|
1352
|
-
.cg-svg.hl .cg-e.hl { opacity:1; stroke-width:1.6; }
|
|
1232
|
+
export const CSS = `
|
|
1233
|
+
:root { --fg:#1f2328; --muted:#656d76; --bd:#d0d7de; --bg:#fff; --accent:#2563eb; --code-bg:#f6f8fa; }
|
|
1234
|
+
* { box-sizing: border-box; }
|
|
1235
|
+
body { margin:0; color:var(--fg); background:#fafbfc; font:16px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,"PingFang SC","Microsoft Yahei",sans-serif; }
|
|
1236
|
+
main { max-width: 860px; margin: 0 auto; padding: 48px 24px 96px; background:var(--bg); }
|
|
1237
|
+
h1,h2,h3,h4,h5,h6 { line-height:1.25; margin:1.6em 0 .6em; scroll-margin-top:16px; }
|
|
1238
|
+
h1 { font-size:2em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }
|
|
1239
|
+
h2 { font-size:1.5em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }
|
|
1240
|
+
h3 { font-size:1.25em; } h4 { font-size:1em; }
|
|
1241
|
+
p { margin:.7em 0; }
|
|
1242
|
+
a { color:var(--accent); text-decoration:none; } a:hover { text-decoration:underline; }
|
|
1243
|
+
code { background:var(--code-bg); padding:.15em .35em; border-radius:6px; font:.88em ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; }
|
|
1244
|
+
pre { background:var(--code-bg); padding:14px 16px; border-radius:8px; overflow:auto; }
|
|
1245
|
+
pre code { background:none; padding:0; font-size:.85em; }
|
|
1246
|
+
pre.output { background:#0d1117; color:#e6edf3; }
|
|
1247
|
+
pre.output code { color:inherit; }
|
|
1248
|
+
ul,ol { padding-left:1.6em; } li { margin:.2em 0; }
|
|
1249
|
+
ul.task-list { list-style:none; padding-left:.2em; }
|
|
1250
|
+
li.task input[type=checkbox] { appearance:none; -webkit-appearance:none; width:1.1em; height:1.1em; margin:0 .5em 0 0; vertical-align:-.2em; border:1.5px solid #c8ccd0; border-radius:4px; background:#fff; position:relative; opacity:1; cursor:default; box-sizing:border-box; }
|
|
1251
|
+
li.task input[type=checkbox]:checked { background-color:#1f883d; border-color:#1f883d; }
|
|
1252
|
+
li.task input[type=checkbox]:checked::after { content:"✓"; position:absolute; top:0; right:0; bottom:0; left:0; display:flex; align-items:center; justify-content:center; color:#fff; font-size:.8em; line-height:1; font-weight:700; }
|
|
1253
|
+
aside.callout { border-left:4px solid var(--accent); background:#f0f6ff; padding:.4em 16px; border-radius:0 8px 8px 0; margin:1em 0; }
|
|
1254
|
+
aside.aside { border-left-color:#8b949e; background:#f6f8fa; }
|
|
1255
|
+
aside.warning { border-left-color:#d97706; background:#fff8f0; }
|
|
1256
|
+
aside.callout > :first-child { margin-top:0; } aside.callout > :last-child { margin-bottom:0; }
|
|
1257
|
+
figure { margin:1.2em 0; }
|
|
1258
|
+
figcaption { color:var(--muted); font-size:.86em; text-align:center; margin-top:.5em; }
|
|
1259
|
+
table.geml-table { border-collapse:collapse; width:100%; font-size:.92em; }
|
|
1260
|
+
table.geml-table th, table.geml-table td { border:1px solid var(--bd); padding:6px 12px; }
|
|
1261
|
+
table.geml-table thead th { background:var(--code-bg); cursor:pointer; user-select:none; white-space:nowrap; }
|
|
1262
|
+
table.geml-table thead th::after { content:" \\2195"; color:var(--muted); font-size:.8em; }
|
|
1263
|
+
table.geml-table thead th.asc::after { content:" \\2191"; color:var(--accent); }
|
|
1264
|
+
table.geml-table thead th.desc::after { content:" \\2193"; color:var(--accent); }
|
|
1265
|
+
table.geml-table tbody tr:nth-child(2n) { background:#fafbfc; }
|
|
1266
|
+
table.geml-table td.computed { color:#0a7c52; }
|
|
1267
|
+
table.geml-table tfoot td { background:var(--code-bg); font-weight:600; border-top:2px solid var(--bd); }
|
|
1268
|
+
.table-tools { margin-bottom:6px; } .table-filter { width:240px; max-width:100%; padding:5px 9px; border:1px solid var(--bd); border-radius:7px; font-size:.85em; }
|
|
1269
|
+
.table-figure details > summary { cursor:pointer; color:var(--muted); font-size:.86em; padding:4px 0; }
|
|
1270
|
+
.table-note { color:var(--muted); font-size:.82em; margin:6px 0 0; }
|
|
1271
|
+
.geml-chart { width:100%; height:auto; background:var(--bg); border:1px solid var(--bd); border-radius:8px; }
|
|
1272
|
+
.c-title { font-size:15px; font-weight:600; fill:var(--fg); }
|
|
1273
|
+
.c-grid { stroke:#eaecef; } .c-axis { stroke:#aab1b8; } .c-tick { font-size:11px; fill:var(--muted); } .c-legend { font-size:12px; fill:var(--fg); }
|
|
1274
|
+
.media { max-width:100%; border-radius:8px; }
|
|
1275
|
+
.diagram-src { color:var(--muted); } .render-error { color:#cf222e; }
|
|
1276
|
+
.math-block { overflow-x:auto; padding:.4em 0; }
|
|
1277
|
+
sup.fn a { font-size:.75em; }
|
|
1278
|
+
.geml-footer { max-width:860px; margin:0 auto; padding:16px 24px 40px; color:var(--muted); font-size:.82em; }
|
|
1279
|
+
.geml-footer code { font-size:.95em; }
|
|
1280
|
+
.code-graph { margin:1.4em 0; }
|
|
1281
|
+
.cg-mount { border:1px solid var(--bd); border-radius:8px; padding:10px 12px; background:var(--bg); }
|
|
1282
|
+
.cg-scroll { overflow:auto; min-height:52vh; max-height:72vh; }
|
|
1283
|
+
.cg-svg { display:block; }
|
|
1284
|
+
.cg-search-wrap { position:relative; display:inline-block; }
|
|
1285
|
+
.cg-search { font:12px/1.4 inherit; padding:2px 7px; border:1px solid var(--bd); border-radius:4px; background:var(--bg); color:var(--fg); min-width:13ch; }
|
|
1286
|
+
.cg-search-menu { position:absolute; z-index:30; top:calc(100% + 2px); left:0; min-width:24ch; max-width:52ch; max-height:52vh; overflow:auto; background:var(--bg); border:1px solid var(--bd); border-radius:6px; box-shadow:0 6px 20px rgba(0,0,0,.18); }
|
|
1287
|
+
.cg-search-row { display:block; width:100%; text-align:left; padding:4px 9px 4px 18px; border:0; background:none; color:var(--fg); cursor:pointer; font:12px/1.4 inherit; }
|
|
1288
|
+
.cg-search-row:hover { background:var(--bd); }
|
|
1289
|
+
.cg-search-count { position:sticky; top:0; padding:4px 9px; font-size:11px; opacity:.65; background:var(--bg); border-bottom:1px solid var(--bd); }
|
|
1290
|
+
.cg-search-grp { padding:6px 9px 2px; font-size:11px; font-weight:600; opacity:.7; border-top:1px solid var(--bd); }
|
|
1291
|
+
.cg-search-grp:first-of-type { border-top:0; }
|
|
1292
|
+
.cg-stage { display:flex; gap:10px; align-items:flex-start; }
|
|
1293
|
+
.cg-stage .cg-scroll { flex:1 1 auto; min-width:0; }
|
|
1294
|
+
.cg-src { flex:0 0 42%; max-width:46%; display:flex; flex-direction:column; border:1px solid var(--bd); border-radius:6px; overflow:hidden; background:var(--bg); }
|
|
1295
|
+
.cg-src-hd { display:flex; gap:8px; align-items:center; justify-content:space-between; padding:4px 8px; border-bottom:1px solid var(--bd); color:var(--muted); font:.76em ui-monospace,Consolas,monospace; word-break:break-all; }
|
|
1296
|
+
.cg-src-hd button { font:inherit; border:1px solid var(--bd); border-radius:5px; background:transparent; color:var(--muted); cursor:pointer; padding:0 6px; }
|
|
1297
|
+
.cg-src-body { margin:0; padding:8px 10px; overflow:auto; max-height:72vh; color:var(--fg); font:12px/1.5 ui-monospace,Consolas,monospace; white-space:pre; }
|
|
1298
|
+
.cg-src-note { color:var(--muted); font-style:italic; white-space:pre-wrap; }
|
|
1299
|
+
.cg-bar { display:flex; gap:8px; align-items:center; flex-wrap:wrap; font-size:.82em; color:var(--muted); margin-bottom:6px; }
|
|
1300
|
+
.cg-bar button { font:inherit; padding:1px 8px; border:1px solid var(--bd); border-radius:5px; background:transparent; cursor:pointer; }
|
|
1301
|
+
.cg-crumb .cg-seg { border:0; border-radius:0; padding:0; background:none; color:var(--accent); cursor:pointer; font:inherit; }
|
|
1302
|
+
.cg-crumb .cg-seg:hover { text-decoration:underline; }
|
|
1303
|
+
.cg-frame { display:block; width:100%; height:72vh; border:0; background:var(--bg); }
|
|
1304
|
+
.cg-flash { color:#b42318; }
|
|
1305
|
+
.cg-legend { display:flex; gap:14px; align-items:center; justify-content:space-between; flex-wrap:wrap; font-size:.75em; color:var(--muted); margin-top:6px; }
|
|
1306
|
+
.cg-upbtn { cursor:pointer; }
|
|
1307
|
+
.cg-upbtn circle { fill:#fff; stroke:#94a3b8; }
|
|
1308
|
+
.cg-upbtn text { font-size:11px; fill:#57606a; }
|
|
1309
|
+
.cg-upbtn:hover circle { stroke:var(--accent); stroke-width:1.6; }
|
|
1310
|
+
.cg-upbtn:hover text { fill:var(--accent); }
|
|
1311
|
+
.cg-uplink { fill:none; stroke:#94a3b8; stroke-dasharray:3 2.5; pointer-events:none; }
|
|
1312
|
+
.cg-groups { display:flex; flex-wrap:wrap; gap:4px 12px; margin-top:6px; font-size:.75em; color:var(--muted); }
|
|
1313
|
+
.cg-chip { display:inline-flex; align-items:center; gap:4px; }
|
|
1314
|
+
.cg-chip i { width:10px; height:10px; border-radius:2px; border:1px solid #94a3b8; display:inline-block; }
|
|
1315
|
+
.cg-note { font-size:.8em; color:#9a6700; }
|
|
1316
|
+
.cg-n rect { fill:#eef2f7; stroke:#94a3b8; }
|
|
1317
|
+
.cg-n text { font-size:12px; fill:var(--fg); font-family:ui-monospace,Consolas,monospace; }
|
|
1318
|
+
.cg-n { cursor:pointer; }
|
|
1319
|
+
.cg-n.root rect { fill:#dbeafe; stroke:#2563eb; stroke-width:2; }
|
|
1320
|
+
.cg-n.leaf { opacity:.45; }
|
|
1321
|
+
.cg-n.test rect { stroke-dasharray:3 2; }
|
|
1322
|
+
.cg-n.grp rect { stroke-width:1.8; }
|
|
1323
|
+
.cg-e { fill:none; stroke:#94a3b8; stroke-width:.9; }
|
|
1324
|
+
.cg-e.cand { stroke-dasharray:2 3; }
|
|
1325
|
+
.cg-e.back { stroke:#dc2626; stroke-dasharray:5 3; }
|
|
1326
|
+
.cg-e.http { stroke:#0891b2; stroke-width:1.5; stroke-dasharray:5 2; } /* cross-stack API link */
|
|
1327
|
+
.cg-e.soft { opacity:.55; }
|
|
1328
|
+
.cg-svg.hl .cg-n { opacity:.22; }
|
|
1329
|
+
.cg-svg.hl .cg-e { opacity:.1; }
|
|
1330
|
+
.cg-svg.hl .cg-n.hl { opacity:1; }
|
|
1331
|
+
.cg-svg.hl .cg-e.hl { opacity:1; stroke-width:1.6; }
|
|
1353
1332
|
`;
|
|
1354
|
-
export const JS = `
|
|
1355
|
-
(function () {
|
|
1356
|
-
function cmp(a, b) {
|
|
1357
|
-
var na = a.dataset.sort, nb = b.dataset.sort;
|
|
1358
|
-
if (na !== undefined && nb !== undefined) return parseFloat(na) - parseFloat(nb);
|
|
1359
|
-
return (a.textContent || "").localeCompare(b.textContent || "");
|
|
1360
|
-
}
|
|
1361
|
-
document.querySelectorAll("table.geml-table").forEach(function (table) {
|
|
1362
|
-
var tbody = table.tBodies[0];
|
|
1363
|
-
if (!tbody) return;
|
|
1364
|
-
// Sort on header click.
|
|
1365
|
-
var ths = table.tHead ? table.tHead.rows[0].cells : [];
|
|
1366
|
-
Array.prototype.forEach.call(ths, function (th, col) {
|
|
1367
|
-
th.addEventListener("click", function () {
|
|
1368
|
-
var dir = th.classList.contains("asc") ? "desc" : "asc";
|
|
1369
|
-
Array.prototype.forEach.call(ths, function (h) { h.classList.remove("asc", "desc"); });
|
|
1370
|
-
th.classList.add(dir);
|
|
1371
|
-
var rows = Array.prototype.slice.call(tbody.rows);
|
|
1372
|
-
rows.sort(function (r1, r2) {
|
|
1373
|
-
var c = cmp(r1.cells[col], r2.cells[col]);
|
|
1374
|
-
return dir === "asc" ? c : -c;
|
|
1375
|
-
});
|
|
1376
|
-
rows.forEach(function (r) { tbody.appendChild(r); });
|
|
1377
|
-
});
|
|
1378
|
-
});
|
|
1379
|
-
// Filter rows.
|
|
1380
|
-
var fig = table.closest(".table-figure");
|
|
1381
|
-
var input = fig ? fig.querySelector(".table-filter") : null;
|
|
1382
|
-
if (input) input.addEventListener("input", function () {
|
|
1383
|
-
var q = input.value.toLowerCase();
|
|
1384
|
-
Array.prototype.forEach.call(tbody.rows, function (r) {
|
|
1385
|
-
r.style.display = (r.textContent || "").toLowerCase().indexOf(q) >= 0 ? "" : "none";
|
|
1386
|
-
});
|
|
1387
|
-
});
|
|
1388
|
-
});
|
|
1389
|
-
})();
|
|
1333
|
+
export const JS = `
|
|
1334
|
+
(function () {
|
|
1335
|
+
function cmp(a, b) {
|
|
1336
|
+
var na = a.dataset.sort, nb = b.dataset.sort;
|
|
1337
|
+
if (na !== undefined && nb !== undefined) return parseFloat(na) - parseFloat(nb);
|
|
1338
|
+
return (a.textContent || "").localeCompare(b.textContent || "");
|
|
1339
|
+
}
|
|
1340
|
+
document.querySelectorAll("table.geml-table").forEach(function (table) {
|
|
1341
|
+
var tbody = table.tBodies[0];
|
|
1342
|
+
if (!tbody) return;
|
|
1343
|
+
// Sort on header click.
|
|
1344
|
+
var ths = table.tHead ? table.tHead.rows[0].cells : [];
|
|
1345
|
+
Array.prototype.forEach.call(ths, function (th, col) {
|
|
1346
|
+
th.addEventListener("click", function () {
|
|
1347
|
+
var dir = th.classList.contains("asc") ? "desc" : "asc";
|
|
1348
|
+
Array.prototype.forEach.call(ths, function (h) { h.classList.remove("asc", "desc"); });
|
|
1349
|
+
th.classList.add(dir);
|
|
1350
|
+
var rows = Array.prototype.slice.call(tbody.rows);
|
|
1351
|
+
rows.sort(function (r1, r2) {
|
|
1352
|
+
var c = cmp(r1.cells[col], r2.cells[col]);
|
|
1353
|
+
return dir === "asc" ? c : -c;
|
|
1354
|
+
});
|
|
1355
|
+
rows.forEach(function (r) { tbody.appendChild(r); });
|
|
1356
|
+
});
|
|
1357
|
+
});
|
|
1358
|
+
// Filter rows.
|
|
1359
|
+
var fig = table.closest(".table-figure");
|
|
1360
|
+
var input = fig ? fig.querySelector(".table-filter") : null;
|
|
1361
|
+
if (input) input.addEventListener("input", function () {
|
|
1362
|
+
var q = input.value.toLowerCase();
|
|
1363
|
+
Array.prototype.forEach.call(tbody.rows, function (r) {
|
|
1364
|
+
r.style.display = (r.textContent || "").toLowerCase().indexOf(q) >= 0 ? "" : "none";
|
|
1365
|
+
});
|
|
1366
|
+
});
|
|
1367
|
+
});
|
|
1368
|
+
})();
|
|
1390
1369
|
`;
|
|
1391
1370
|
// geml-code-graph runtime: layered layout AT DRAW TIME (GEP-0003 / v2-D8) so
|
|
1392
1371
|
// clicking a node re-roots the view inside the embedded slice. Algorithm as
|
package/dist/table.d.ts
CHANGED
package/dist/table.js
CHANGED
|
@@ -127,22 +127,34 @@ function lexExpr(s) {
|
|
|
127
127
|
// Display format: a `[printf]` spec bound to a column/cell name (§6).
|
|
128
128
|
// `FY [%.1f]` → name "FY", fmt "%.1f"; `YoY [%.1f%%]` → "%.1f%%"
|
|
129
129
|
// ---------------------------------------------------------------------------
|
|
130
|
+
// The bracket suffix has to contain a `%` to be a format (§6). Without that
|
|
131
|
+
// test, a column whose own name is bracketed — `[Data] = …` — parses as an
|
|
132
|
+
// empty name plus the format `Data`, and the formula silently targets nothing.
|
|
130
133
|
function splitName(lhs) {
|
|
131
|
-
const m = /^(.*?)\s*\[([^\]]*)\]\s*$/.exec(lhs.trim());
|
|
134
|
+
const m = /^(.*?)\s*\[([^\]]*%[^\]]*)\]\s*$/.exec(lhs.trim());
|
|
132
135
|
let name = (m ? m[1] : lhs).trim();
|
|
133
136
|
if (name.startsWith('"') && name.endsWith('"'))
|
|
134
137
|
name = name.slice(1, -1);
|
|
135
138
|
return m ? { name, fmt: m[2] } : { name };
|
|
136
139
|
}
|
|
140
|
+
// A result IEEE-754 produces but a table cannot hold (§6): `x / 0` is ±∞, `0 / 0`
|
|
141
|
+
// is NaN. The cell keeps no value and displays `-`, which is what a reader sees;
|
|
142
|
+
// naming the cause is the difference between "this row had no data" and "this
|
|
143
|
+
// row divided by zero", so it is said out loud like a substituted cell is.
|
|
144
|
+
const nanMsg = (where, v) => `${where}: ${Number.isNaN(v) ? "result is not a number (0/0)" : "division by zero"}; the cell holds no value and shows \`-\``;
|
|
137
145
|
// Default rendering for an unformatted computed number: drop IEEE-754 display
|
|
138
146
|
// noise (0.1+0.2 → "0.3", sum of 1-dp inputs → "263.6") without altering the
|
|
139
147
|
// stored numeric value.
|
|
140
148
|
function defaultNum(v) {
|
|
149
|
+
if (!isFinite(v))
|
|
150
|
+
return "-";
|
|
141
151
|
return String(parseFloat(v.toPrecision(12)));
|
|
142
152
|
}
|
|
143
153
|
// Minimal printf for a single numeric value: handles %f/%e/%d/%g with optional
|
|
144
154
|
// precision, and `%%` as a literal percent. Width/flags are not padded.
|
|
145
155
|
function applyFormat(fmt, v) {
|
|
156
|
+
if (!isFinite(v))
|
|
157
|
+
return "-";
|
|
146
158
|
return fmt.replace(/%%|%[-+ 0]*\d*(?:\.\d+)?[fFeEgGd]/g, (m) => {
|
|
147
159
|
if (m === "%%")
|
|
148
160
|
return "%";
|
|
@@ -232,16 +244,6 @@ function evalExpr(toks, row, col, agg) {
|
|
|
232
244
|
return v;
|
|
233
245
|
}
|
|
234
246
|
// ---------------------------------------------------------------------------
|
|
235
|
-
// Spans
|
|
236
|
-
// ---------------------------------------------------------------------------
|
|
237
|
-
// Parse `r2c1:2x1` → target cell (1-based row/col over body) + size.
|
|
238
|
-
function parseSpan(s) {
|
|
239
|
-
const m = /^r(\d+)c(\d+):(\d+)x(\d+)$/.exec(s.trim());
|
|
240
|
-
if (!m)
|
|
241
|
-
return null;
|
|
242
|
-
return { row: +m[1], col: +m[2], rows: +m[3], cols: +m[4] };
|
|
243
|
-
}
|
|
244
|
-
// ---------------------------------------------------------------------------
|
|
245
247
|
// Public entry
|
|
246
248
|
// ---------------------------------------------------------------------------
|
|
247
249
|
export function parseTable(body, attrs, line, sink) {
|
|
@@ -310,19 +312,47 @@ export function parseTable(body, attrs, line, sink) {
|
|
|
310
312
|
const v = model.rows[row]?.[ci]?.value;
|
|
311
313
|
return typeof v === "number" ? v : null;
|
|
312
314
|
};
|
|
315
|
+
// A cell a formula reads but cannot read as a number (`x`, `N/A`, `TBD`, an
|
|
316
|
+
// empty cell) counts as 0, so one dirty row does not void the whole column.
|
|
317
|
+
// But counting it silently is how a table quietly reports the wrong total, so
|
|
318
|
+
// say which cell was substituted. One warning per cell, not per mention: a
|
|
319
|
+
// formula naming the same column twice describes one substitution.
|
|
320
|
+
const substituted = new Set();
|
|
313
321
|
const colResolve = (name, row) => {
|
|
314
322
|
const ci = colIndex(name);
|
|
315
|
-
|
|
323
|
+
if (ci < 0)
|
|
324
|
+
return null;
|
|
325
|
+
const n = cellNum(ci, row);
|
|
326
|
+
if (n !== null)
|
|
327
|
+
return n;
|
|
328
|
+
const key = `${ci}\0${row}`;
|
|
329
|
+
if (!substituted.has(key)) {
|
|
330
|
+
substituted.add(key);
|
|
331
|
+
const text = model.rows[row]?.[ci]?.text ?? "";
|
|
332
|
+
diagnostics.push({
|
|
333
|
+
severity: "warning",
|
|
334
|
+
code: "compute-non-numeric-cell",
|
|
335
|
+
message: `column \`${name}\` row ${row + 1} is not a number (${text === "" ? "empty" : `\`${text}\``}); counted as 0`,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
return 0;
|
|
316
339
|
};
|
|
317
340
|
const computeAgg = (fn, ci) => {
|
|
341
|
+
if (fn === "count") {
|
|
342
|
+
let c = 0;
|
|
343
|
+
for (let r = 0; r < model.rows.length; r++) {
|
|
344
|
+
const text = model.rows[r]?.[ci]?.text;
|
|
345
|
+
if (text !== undefined && text !== "")
|
|
346
|
+
c++;
|
|
347
|
+
}
|
|
348
|
+
return c;
|
|
349
|
+
}
|
|
318
350
|
const vals = [];
|
|
319
351
|
for (let r = 0; r < model.rows.length; r++) {
|
|
320
352
|
const v = cellNum(ci, r);
|
|
321
353
|
if (v !== null)
|
|
322
354
|
vals.push(v);
|
|
323
355
|
}
|
|
324
|
-
if (fn === "count")
|
|
325
|
-
return vals.length;
|
|
326
356
|
if (vals.length === 0)
|
|
327
357
|
return 0;
|
|
328
358
|
if (fn === "sum")
|
|
@@ -398,13 +428,14 @@ export function parseTable(body, attrs, line, sink) {
|
|
|
398
428
|
try {
|
|
399
429
|
const v = evalExpr(toks, r, colResolve, aggResolve);
|
|
400
430
|
const cell = ensureCell(model.rows[r], ci);
|
|
401
|
-
|
|
402
|
-
|
|
431
|
+
const text = fmt ? applyFormat(fmt, v) : defaultNum(v);
|
|
432
|
+
cell.text = text;
|
|
433
|
+
cell.computed = true;
|
|
434
|
+
cell.inlines = [{ type: "text", value: text }];
|
|
435
|
+
if (Number.isFinite(v))
|
|
403
436
|
cell.value = v;
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
cell.inlines = [{ type: "text", value: text }];
|
|
407
|
-
}
|
|
437
|
+
else
|
|
438
|
+
diagnostics.push({ severity: "warning", code: "compute-not-a-number", message: nanMsg(`compute \`${name}\` row ${r + 1}`, v) });
|
|
408
439
|
}
|
|
409
440
|
catch (e) {
|
|
410
441
|
diagnostics.push({ severity: "error", code: "compute-error", message: `compute \`${name}\`: ${e.message}` });
|
|
@@ -458,10 +489,12 @@ export function parseTable(body, attrs, line, sink) {
|
|
|
458
489
|
}
|
|
459
490
|
try {
|
|
460
491
|
const v = evalExpr(toks, 0, noRow, aggResolve);
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
492
|
+
const text = fmt ? applyFormat(fmt, v) : defaultNum(v);
|
|
493
|
+
summary[ci] = { text, inlines: [{ type: "text", value: text }], computed: true };
|
|
494
|
+
if (Number.isFinite(v))
|
|
495
|
+
summary[ci].value = v;
|
|
496
|
+
else
|
|
497
|
+
diagnostics.push({ severity: "warning", code: "compute-not-a-number", message: nanMsg(`summary \`${name}\``, v) });
|
|
465
498
|
}
|
|
466
499
|
catch (e) {
|
|
467
500
|
const msg = /unknown column `(.+)`/.exec(e.message);
|
|
@@ -471,32 +504,6 @@ export function parseTable(body, attrs, line, sink) {
|
|
|
471
504
|
}
|
|
472
505
|
model.summary = summary;
|
|
473
506
|
}
|
|
474
|
-
// Spans: `span="r2c1:2x1"` (one or many: span, span2, …).
|
|
475
|
-
const spanDecls = Object.entries(attrs)
|
|
476
|
-
.filter(([k]) => k === "span" || /^span\d+$/.test(k))
|
|
477
|
-
.map(([, v]) => v)
|
|
478
|
-
.filter((v) => typeof v === "string");
|
|
479
|
-
for (const sd of spanDecls) {
|
|
480
|
-
const sp = parseSpan(sd);
|
|
481
|
-
if (!sp) {
|
|
482
|
-
diagnostics.push({ severity: "error", code: "bad-span", message: `bad span \`${sd}\` (want \`rNcM:RxC\`)` });
|
|
483
|
-
continue;
|
|
484
|
-
}
|
|
485
|
-
const cell = model.rows[sp.row - 1]?.[sp.col - 1];
|
|
486
|
-
if (!cell) {
|
|
487
|
-
diagnostics.push({ severity: "warning", code: "span-outside-table", message: `span \`${sd}\` targets a cell outside the table` });
|
|
488
|
-
continue;
|
|
489
|
-
}
|
|
490
|
-
// A span can never extend past the grid: clamp its extent to the rows/cols
|
|
491
|
-
// actually available from the target cell. Without this, `span="r1c1:9e6x9e6"`
|
|
492
|
-
// makes the renderer's O(rows×cols) coverage sweep hang (DoS). Every row has
|
|
493
|
-
// exactly `columns.length` cells (built above), so the column bound is exact.
|
|
494
|
-
const maxRows = model.rows.length - (sp.row - 1);
|
|
495
|
-
const maxCols = columns.length - (sp.col - 1);
|
|
496
|
-
const rows = Math.max(1, Math.min(sp.rows, maxRows));
|
|
497
|
-
const cols = Math.max(1, Math.min(sp.cols, maxCols));
|
|
498
|
-
cell.span = { rows, cols };
|
|
499
|
-
}
|
|
500
507
|
return { model, diagnostics };
|
|
501
508
|
}
|
|
502
509
|
function ensureCell(row, ci) {
|
package/dist/to-md.js
CHANGED
|
@@ -125,7 +125,10 @@ function typedToMd(b, notes) {
|
|
|
125
125
|
return "";
|
|
126
126
|
}
|
|
127
127
|
if (b.mode === "flow") {
|
|
128
|
-
//
|
|
128
|
+
// A note the author marked `.footnote` projects to a Markdown footnote
|
|
129
|
+
// definition. The parser no longer synthesizes this class — the `[^id]: text`
|
|
130
|
+
// definition line was withdrawn from §5.2 — but an author still writes it,
|
|
131
|
+
// and it is the only way this projection can be produced.
|
|
129
132
|
if (b.type === "note" && b.classes.includes("footnote") && b.id) {
|
|
130
133
|
const text = (b.children ?? []).map((c) => block(c, notes)).join(" ").replace(/\n+/g, " ").trim();
|
|
131
134
|
return `[^${b.id}]: ${text}`;
|