@geml/geml 1.7.0 → 1.7.2
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/README.md +20 -4
- package/codemap/build.mjs +20 -6
- package/codemap/exclude.mjs +18 -14
- package/codemap/refresh.mjs +4 -1
- package/codemap/verify.mjs +4 -1
- package/dist/block-edit.js +15 -1
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +2433 -0
- package/dist/from-md.js +7 -2
- package/dist/geml.d.ts +32 -0
- package/dist/geml.js +179 -2039
- package/dist/history.js +7 -3
- package/dist/mcp.js +46 -8
- package/dist/render-html.d.ts +1 -1
- package/dist/render.d.ts +1 -1
- package/dist/render.js +215 -17
- package/dist/selector.d.ts +7 -0
- package/dist/selector.js +41 -1
- package/dist/to-md.js +24 -2
- package/package.json +67 -67
- package/skill/SKILL.md +25 -0
- package/skill/references/authoring.geml +6 -1
package/dist/history.js
CHANGED
|
@@ -38,7 +38,11 @@ export function stampUTC(d) {
|
|
|
38
38
|
// ---------------------------------------------------------------------------
|
|
39
39
|
// Top-level fenced-block locator (for the history file's own structure)
|
|
40
40
|
// ---------------------------------------------------------------------------
|
|
41
|
-
|
|
41
|
+
// Trailing whitespace nested inside the optional attrs group — the flat form
|
|
42
|
+
// let two runs split the same tabs every possible way (quadratic; see the note
|
|
43
|
+
// on geml.ts's FENCE_OPEN, which this mirrors). Same language, verified over
|
|
44
|
+
// 150k random strings.
|
|
45
|
+
const FENCE_OPEN = /^(={3,})[ \t]+([A-Za-z][A-Za-z0-9_-]*)[ \t]*(?:(\{.*\})[ \t]*)?$/;
|
|
42
46
|
function locate(lines) {
|
|
43
47
|
const out = [];
|
|
44
48
|
let i = 0;
|
|
@@ -50,7 +54,7 @@ function locate(lines) {
|
|
|
50
54
|
const idm = /#([A-Za-z][A-Za-z0-9_-]*)/.exec(attrLine);
|
|
51
55
|
let j = i + 1;
|
|
52
56
|
while (j < lines.length) {
|
|
53
|
-
const t = lines[j].
|
|
57
|
+
const t = lines[j].trimEnd();
|
|
54
58
|
if (/^=+$/.test(t) && t.length === fenceLen)
|
|
55
59
|
break;
|
|
56
60
|
j++;
|
|
@@ -108,7 +112,7 @@ function tile(lines) {
|
|
|
108
112
|
id = /#([A-Za-z][A-Za-z0-9_-]*)/.exec(fo[3] ?? "")?.[1];
|
|
109
113
|
i++;
|
|
110
114
|
while (i < n) {
|
|
111
|
-
const t = lines[i].
|
|
115
|
+
const t = lines[i].trimEnd();
|
|
112
116
|
const close = /^=+$/.test(t) && t.length === fenceLen;
|
|
113
117
|
i++;
|
|
114
118
|
if (close)
|
package/dist/mcp.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// `geml mcp` — MCP server for GEML documents and the code graph.
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
// five that write, each named after the CLI verb it wraps (`geml set`
|
|
6
|
-
// `geml_set`, the bare transform entry -> `geml_to`). When that root holds a code graph, the four read-only
|
|
4
|
+
// Eleven tools over a confined root directory of `.geml` documents: six
|
|
5
|
+
// read-only, five that write, each named after the CLI verb it wraps (`geml set`
|
|
6
|
+
// -> `geml_set`, the bare transform entry -> `geml_to`). When that root holds a code graph, the four read-only
|
|
7
7
|
// code-graph tools from `codemap/mcp-server.mjs` are served from this SAME
|
|
8
8
|
// process, so a client registers one server instead of two. That file stays a
|
|
9
9
|
// standalone `geml codemap mcp` entry point; this one imports its tool table
|
|
@@ -273,6 +273,44 @@ export const TOOLS = [
|
|
|
273
273
|
return run.stdout.trim();
|
|
274
274
|
},
|
|
275
275
|
},
|
|
276
|
+
{
|
|
277
|
+
name: "geml_find",
|
|
278
|
+
description: "Search block CONTENT across the served documents and get back ADDRESSES, one row of `<file>\\t<address>` per hit. This is the other half of geml_list: `list` says what a document contains, `find` says which block holds the words you are looking for — and it answers with an address that pastes straight into geml_get or geml_set, never a line number that the next edit invalidates. The address is the innermost block holding the match, and a block that matches on many lines is reported once. Substring, case-insensitive unless `case` is true. Omit `path` to search every `*.geml` under the server root, or give a file or directory to narrow it. No match is not an error: the result is empty.",
|
|
279
|
+
inputSchema: {
|
|
280
|
+
type: "object",
|
|
281
|
+
properties: {
|
|
282
|
+
pattern: { type: "string", description: "Text to look for inside block bodies" },
|
|
283
|
+
path: { type: "string", description: "Optional file or directory under the server root; default: the whole root" },
|
|
284
|
+
case: { type: "boolean", description: "Match case exactly (default: case-insensitive)" },
|
|
285
|
+
head: { type: "boolean", description: "Add the matching line as a third column" },
|
|
286
|
+
},
|
|
287
|
+
required: ["pattern"],
|
|
288
|
+
},
|
|
289
|
+
run: (args) => {
|
|
290
|
+
if (typeof args.pattern !== "string" || args.pattern === "")
|
|
291
|
+
throw new Error("`pattern` is required");
|
|
292
|
+
// `path` goes through the same confinement gate as every other file
|
|
293
|
+
// argument; omitting it searches the root, which is confined by being it.
|
|
294
|
+
const where = args.path === undefined ? OPTS.root : resolveInRoot(args.path);
|
|
295
|
+
const flags = [...(args.case ? ["--case"] : []), ...(args.head ? ["--head"] : [])];
|
|
296
|
+
const run = runCli(["find", args.pattern, where, ...flags]);
|
|
297
|
+
// Exit 1 means "nothing matched" — a result, not a failure. Anything on
|
|
298
|
+
// stderr is the real error case.
|
|
299
|
+
if (!run.ok && run.stderr)
|
|
300
|
+
throw new Error(run.stderr);
|
|
301
|
+
// The CLI prints the path it was given, which here is absolute. Every
|
|
302
|
+
// other tool in this server speaks paths relative to the root, and a
|
|
303
|
+
// model is meant to paste a row's file straight into geml_get — so put
|
|
304
|
+
// the rows in those coordinates, and keep the server's own layout out of
|
|
305
|
+
// the client's view while we are at it.
|
|
306
|
+
const base = realpathSync(OPTS.root) + sep;
|
|
307
|
+
return run.stdout
|
|
308
|
+
.split("\n")
|
|
309
|
+
.map((line) => (line.startsWith(base) ? line.slice(base.length).replace(/\\/g, "/") : line))
|
|
310
|
+
.join("\n")
|
|
311
|
+
.trim();
|
|
312
|
+
},
|
|
313
|
+
},
|
|
276
314
|
{
|
|
277
315
|
name: "geml_get",
|
|
278
316
|
description: "Read ONE block from a GEML document. Use this instead of reading the whole file: it returns only that block, typically a few percent of the document. Call `geml_list` first and pass back the `address` it gives — that also reaches blocks with no `#id`, which an id alone cannot.",
|
|
@@ -282,7 +320,7 @@ export const TOOLS = [
|
|
|
282
320
|
file: FILE_ARG,
|
|
283
321
|
id: {
|
|
284
322
|
type: "string",
|
|
285
|
-
description: "What to read: a block id (with or without `#`), a `## Heading` line (its whole section), `=== type` for every block of a type,
|
|
323
|
+
description: "What to read: a block id (with or without `#`), a `## Heading` line (its whole section), `=== type` for every block of a type, a `@<hex>` content address for a block with no id, or `L27`/`L27-58` for the smallest block holding those lines — the forms `geml_list` prints, plus the line numbers an editor or a diff hunk speaks",
|
|
286
324
|
},
|
|
287
325
|
view: {
|
|
288
326
|
type: "boolean",
|
|
@@ -290,8 +328,8 @@ export const TOOLS = [
|
|
|
290
328
|
},
|
|
291
329
|
part: {
|
|
292
330
|
type: "string",
|
|
293
|
-
enum: ["whole", "head", "body"],
|
|
294
|
-
description: "How much of the block to return (default: whole). `body` is usually what you want together with `view`.",
|
|
331
|
+
enum: ["whole", "head", "intro", "body"],
|
|
332
|
+
description: "How much of the block to return (default: whole). For a SECTION these cut it three ways: `head` is the heading line, `intro` everything under it up to its first subheading, `body` everything under it — so `body` always contains `intro`, and equals it when the section has no subheading. Reach for `intro` to read a section's opening without pulling its subsections into the conversation; a whole `#id` on a top-level heading is often the entire document. Only a heading has an intro. `body` is usually what you want together with `view`.",
|
|
295
333
|
},
|
|
296
334
|
},
|
|
297
335
|
required: ["file", "id"],
|
|
@@ -429,7 +467,7 @@ export const TOOLS = [
|
|
|
429
467
|
// ----- write -----
|
|
430
468
|
{
|
|
431
469
|
name: "geml_set",
|
|
432
|
-
description: "Replace ONE block, leaving every other byte of the document untouched. Prefer this over rewriting a file. The replacement is VALIDATED BEFORE it is written: if it would break the document, nothing is written and you get the diagnostics back — re-read them and fix the body rather than retrying the same content. `part` selects whole block (default),
|
|
470
|
+
description: "Replace ONE block, leaving every other byte of the document untouched. Prefer this over rewriting a file. The replacement is VALIDATED BEFORE it is written: if it would break the document, nothing is written and you get the diagnostics back — re-read them and fix the body rather than retrying the same content. Breaking the document is what gets refused — removing content is not: if your replacement drops blocks, the write goes through and the result names every block that went, unnamed ones included, so check that line whenever you shortened a section. `geml_revert` puts one back. The way to keep a block is to keep it in the text you send; `geml_get` on the same address just handed it to you. `part` selects whole block (default), the head/fence line, a section's `intro`, or the body. An address matching SEVERAL blocks is refused — this writes one block, so narrow it first.",
|
|
433
471
|
inputSchema: {
|
|
434
472
|
type: "object",
|
|
435
473
|
properties: {
|
|
@@ -439,7 +477,7 @@ export const TOOLS = [
|
|
|
439
477
|
description: "Which block to replace: an id (with or without `#`), or a `@<hex>` content address from `geml_list` for a block with no id. Must match exactly one block",
|
|
440
478
|
},
|
|
441
479
|
body: { type: "string", description: "The replacement text" },
|
|
442
|
-
part: { type: "string", enum: ["whole", "head", "body"], description: "What to replace (default: whole)" },
|
|
480
|
+
part: { type: "string", enum: ["whole", "head", "intro", "body"], description: "What to replace (default: whole). `intro` replaces a section's opening — everything under the heading up to its first subheading — and leaves every subsection byte-identical, which is what makes a read-edit-write cycle on a long section safe. An empty intro (a subheading follows the heading immediately) is written into, so this also adds an opening where there was none." },
|
|
443
481
|
},
|
|
444
482
|
required: ["file", "id", "body"],
|
|
445
483
|
},
|
package/dist/render-html.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { type Document } from "./geml.js";
|
|
|
2
2
|
import { type RenderOptions } from "./render.js";
|
|
3
3
|
export declare function renderHtml(doc: Document, opts?: RenderOptions): string;
|
|
4
4
|
export declare const pageAssets: {
|
|
5
|
-
readonly css: "\n:root { --fg:#1f2328; --muted:#656d76; --bd:#d0d7de; --bg:#fff; --accent:#2563eb; --code-bg:#f6f8fa; }\n* { box-sizing: border-box; }\nbody { margin:0; color:var(--fg); background:#fafbfc; font:16px/1.6 -apple-system,BlinkMacSystemFont,\"Segoe UI\",Helvetica,Arial,\"PingFang SC\",\"Microsoft Yahei\",sans-serif; }\nmain { max-width: 860px; margin: 0 auto; padding: 48px 24px 96px; background:var(--bg); }\nh1,h2,h3,h4,h5,h6 { line-height:1.25; margin:1.6em 0 .6em; scroll-margin-top:16px; }\nh1 { font-size:2em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }\nh2 { font-size:1.5em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }\nh3 { font-size:1.25em; } h4 { font-size:1em; }\np { margin:.7em 0; }\na { color:var(--accent); text-decoration:none; } a:hover { text-decoration:underline; }\ncode { background:var(--code-bg); padding:.15em .35em; border-radius:6px; font:.88em ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; }\npre { background:var(--code-bg); padding:14px 16px; border-radius:8px; overflow:auto; }\npre code { background:none; padding:0; font-size:.85em; }\npre.output { background:#0d1117; color:#e6edf3; }\npre.output code { color:inherit; }\nul,ol { padding-left:1.6em; } li { margin:.2em 0; }\nul.task-list { list-style:none; padding-left:.2em; }\nli.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; }\nli.task input[type=checkbox]:checked { background-color:#1f883d; border-color:#1f883d; }\nli.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; }\naside.callout { border-left:4px solid var(--accent); background:#f0f6ff; padding:.4em 16px; border-radius:0 8px 8px 0; margin:1em 0; }\naside.aside { border-left-color:#8b949e; background:#f6f8fa; }\naside.warning { border-left-color:#d97706; background:#fff8f0; }\naside.callout > :first-child { margin-top:0; } aside.callout > :last-child { margin-bottom:0; }\nfigure { margin:1.2em 0; }\nfigcaption { color:var(--muted); font-size:.86em; text-align:center; margin-top:.5em; }\ntable.geml-table { border-collapse:collapse; width:100%; font-size:.92em; }\ntable.geml-table th, table.geml-table td { border:1px solid var(--bd); padding:6px 12px; }\ntable.geml-table thead th { background:var(--code-bg); cursor:pointer; user-select:none; white-space:nowrap; }\ntable.geml-table thead th::after { content:\" \\2195\"; color:var(--muted); font-size:.8em; }\ntable.geml-table thead th.asc::after { content:\" \\2191\"; color:var(--accent); }\ntable.geml-table thead th.desc::after { content:\" \\2193\"; color:var(--accent); }\ntable.geml-table tbody tr:nth-child(2n) { background:#fafbfc; }\ntable.geml-table td.computed { color:#0a7c52; }\ntable.geml-table tfoot td { background:var(--code-bg); font-weight:600; border-top:2px solid var(--bd); }\n.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; }\n.table-figure details > summary { cursor:pointer; color:var(--muted); font-size:.86em; padding:4px 0; }\n.table-note { color:var(--muted); font-size:.82em; margin:6px 0 0; }\n.geml-chart { width:100%; height:auto; background:var(--bg); border:1px solid var(--bd); border-radius:8px; }\n.c-title { font-size:15px; font-weight:600; fill:var(--fg); }\n.c-grid { stroke:#eaecef; } .c-axis { stroke:#aab1b8; } .c-tick { font-size:11px; fill:var(--muted); } .c-legend { font-size:12px; fill:var(--fg); }\n.media { max-width:100%; border-radius:8px; }\n.diagram-src { color:var(--muted); } .render-error { color:#cf222e; }\n.math-block { overflow-x:auto; padding:.4em 0; }\nsup.fn a { font-size:.75em; }\n.geml-footer { max-width:860px; margin:0 auto; padding:16px 24px 40px; color:var(--muted); font-size:.82em; }\n.geml-footer code { font-size:.95em; }\n.code-graph { margin:1.4em 0; }\n.cg-mount { border:1px solid var(--bd); border-radius:8px; padding:10px 12px; background:var(--bg); }\n.cg-scroll { overflow:auto; min-height:52vh; max-height:72vh; }\n.cg-svg { display:block; }\n.cg-search-wrap { position:relative; display:inline-block; }\n.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; }\n.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); }\n.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; }\n.cg-search-row:hover { background:var(--bd); }\n.cg-search-count { position:sticky; top:0; padding:4px 9px; font-size:11px; opacity:.65; background:var(--bg); border-bottom:1px solid var(--bd); }\n.cg-search-grp { padding:6px 9px 2px; font-size:11px; font-weight:600; opacity:.7; border-top:1px solid var(--bd); }\n.cg-search-grp:first-of-type { border-top:0; }\n.cg-stage { display:flex; gap:10px; align-items:flex-start; }\n.cg-stage .cg-scroll { flex:1 1 auto; min-width:0; }\n.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); }\n.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; }\n.cg-src-hd button { font:inherit; border:1px solid var(--bd); border-radius:5px; background:transparent; color:var(--muted); cursor:pointer; padding:0 6px; }\n.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; }\n.cg-src-note { color:var(--muted); font-style:italic; white-space:pre-wrap; }\n.cg-bar { display:flex; gap:8px; align-items:center; flex-wrap:wrap; font-size:.82em; color:var(--muted); margin-bottom:6px; }\n.cg-bar button { font:inherit; padding:1px 8px; border:1px solid var(--bd); border-radius:5px; background:transparent; cursor:pointer; }\n.cg-crumb .cg-seg { border:0; border-radius:0; padding:0; background:none; color:var(--accent); cursor:pointer; font:inherit; }\n.cg-crumb .cg-seg:hover { text-decoration:underline; }\n.cg-frame { display:block; width:100%; height:72vh; border:0; background:var(--bg); }\n.cg-flash { color:#b42318; }\n.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; }\n.cg-upbtn { cursor:pointer; }\n.cg-upbtn circle { fill:#fff; stroke:#94a3b8; }\n.cg-upbtn text { font-size:11px; fill:#57606a; }\n.cg-upbtn:hover circle { stroke:var(--accent); stroke-width:1.6; }\n.cg-upbtn:hover text { fill:var(--accent); }\n.cg-uplink { fill:none; stroke:#94a3b8; stroke-dasharray:3 2.5; pointer-events:none; }\n.cg-groups { display:flex; flex-wrap:wrap; gap:4px 12px; margin-top:6px; font-size:.75em; color:var(--muted); }\n.cg-chip { display:inline-flex; align-items:center; gap:4px; }\n.cg-chip i { width:10px; height:10px; border-radius:2px; border:1px solid #94a3b8; display:inline-block; }\n.cg-note { font-size:.8em; color:#9a6700; }\n.cg-n rect { fill:#eef2f7; stroke:#94a3b8; }\n.cg-n text { font-size:12px; fill:var(--fg); font-family:ui-monospace,Consolas,monospace; }\n.cg-n { cursor:pointer; }\n.cg-n.root rect { fill:#dbeafe; stroke:#2563eb; stroke-width:2; }\n.cg-n.leaf { opacity:.45; }\n.cg-n.test rect { stroke-dasharray:3 2; }\n.cg-n.grp rect { stroke-width:1.8; }\n.cg-e { fill:none; stroke:#94a3b8; stroke-width:.9; }\n.cg-e.cand { stroke-dasharray:2 3; }\n.cg-e.back { stroke:#dc2626; stroke-dasharray:5 3; }\n.cg-e.http { stroke:#0891b2; stroke-width:1.5; stroke-dasharray:5 2; } /* cross-stack API link */\n.cg-e.soft { opacity:.55; }\n.cg-svg.hl .cg-n { opacity:.22; }\n.cg-svg.hl .cg-e { opacity:.1; }\n.cg-svg.hl .cg-n.hl { opacity:1; }\n.cg-svg.hl .cg-e.hl { opacity:1; stroke-width:1.6; }\n";
|
|
5
|
+
readonly css: "\n:root { --fg:#1f2328; --muted:#656d76; --bd:#d0d7de; --bg:#fff; --accent:#2563eb; --code-bg:#f6f8fa; }\n* { box-sizing: border-box; }\nbody { margin:0; color:var(--fg); background:#fafbfc; font:16px/1.6 -apple-system,BlinkMacSystemFont,\"Segoe UI\",Helvetica,Arial,\"PingFang SC\",\"Microsoft Yahei\",sans-serif; }\nmain { max-width: 860px; margin: 0 auto; padding: 48px 24px 96px; background:var(--bg); }\nh1,h2,h3,h4,h5,h6 { line-height:1.25; margin:1.6em 0 .6em; scroll-margin-top:16px; }\nh1 { font-size:2em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }\nh2 { font-size:1.5em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }\nh3 { font-size:1.25em; } h4 { font-size:1em; }\np { margin:.7em 0; }\na { color:var(--accent); text-decoration:none; } a:hover { text-decoration:underline; }\ncode { background:var(--code-bg); padding:.15em .35em; border-radius:6px; font:.88em ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; }\npre { background:var(--code-bg); padding:14px 16px; border-radius:8px; overflow:auto; }\npre code { background:none; padding:0; font-size:.85em; }\npre.output { background:#0d1117; color:#e6edf3; }\npre.output code { color:inherit; }\nul,ol { padding-left:1.6em; } li { margin:.2em 0; }\nul.task-list { list-style:none; padding-left:.2em; }\nli.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; }\nli.task input[type=checkbox]:checked { background-color:#1f883d; border-color:#1f883d; }\nli.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; }\naside.callout { border-left:4px solid var(--accent); background:#f0f6ff; padding:.4em 16px; border-radius:0 8px 8px 0; margin:1em 0; }\naside.aside { border-left-color:#8b949e; background:#f6f8fa; }\naside.warning { border-left-color:#d97706; background:#fff8f0; }\naside.callout > :first-child { margin-top:0; } aside.callout > :last-child { margin-bottom:0; }\nfigure { margin:1.2em 0; }\nfigcaption { color:var(--muted); font-size:.86em; text-align:center; margin-top:.5em; }\ntable.geml-table { border-collapse:collapse; width:100%; font-size:.92em; }\ntable.geml-table th, table.geml-table td { border:1px solid var(--bd); padding:6px 12px; }\ntable.geml-table thead th { background:var(--code-bg); cursor:pointer; user-select:none; white-space:nowrap; }\ntable.geml-table thead th::after { content:\" \\2195\"; color:var(--muted); font-size:.8em; }\ntable.geml-table thead th.asc::after { content:\" \\2191\"; color:var(--accent); }\ntable.geml-table thead th.desc::after { content:\" \\2193\"; color:var(--accent); }\ntable.geml-table tbody tr:nth-child(2n) { background:#fafbfc; }\ntable.geml-table td.computed { color:#0a7c52; }\ntable.geml-table tfoot td { background:var(--code-bg); font-weight:600; border-top:2px solid var(--bd); }\n.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; }\n/* A table wider than the column scrolls INSIDE its own box — the page itself\n must never scroll sideways. Only the table is in here: the filter box above\n and the caption below stay put instead of sliding out of view with it. */\n.table-scroll { overflow-x:auto; max-width:100%; }\n.table-figure details > summary { cursor:pointer; color:var(--muted); font-size:.86em; padding:4px 0; }\n.table-note { color:var(--muted); font-size:.82em; margin:6px 0 0; }\n.geml-chart { width:100%; height:auto; background:var(--bg); border:1px solid var(--bd); border-radius:8px; }\n.c-title { font-size:15px; font-weight:600; fill:var(--fg); }\n.c-grid { stroke:#eaecef; } .c-axis { stroke:#aab1b8; } .c-tick { font-size:11px; fill:var(--muted); } .c-legend { font-size:12px; fill:var(--fg); }\n.media { max-width:100%; border-radius:8px; }\n.diagram-src { color:var(--muted); } .render-error { color:#cf222e; }\n.math-block { overflow-x:auto; padding:.4em 0; }\nsup.fn a { font-size:.75em; }\n.geml-footer { max-width:860px; margin:0 auto; padding:16px 24px 40px; color:var(--muted); font-size:.82em; }\n.geml-footer code { font-size:.95em; }\n.code-graph { margin:1.4em 0; }\n/* The graph is the widest artifact on the page: let it break out of main's\n 860px reading column and take the viewport, centred, leaving the prose\n around it untouched. Negative inline margins, NOT transform — a transform\n would become the containing block for the fullscreen overlay below. */\n@media (min-width:900px) { .code-graph { margin-inline: calc((100% - min(96vw, 1600px)) / 2); } }\n.cg-mount { border:1px solid var(--bd); border-radius:8px; padding:10px 12px; background:var(--bg); }\n.cg-scroll { overflow:auto; min-height:60vh; max-height:84vh; }\n.cg-svg { display:block; }\n/* Fullscreen: one class drives the layout, whether the native Fullscreen API\n took (mount is in the top layer) or we fell back to a fixed overlay. The\n :fullscreen pseudo-class is deliberately NOT in these selectors — a browser\n that doesn't know it would drop the whole rule; the runtime keeps the class\n in sync with fullscreenchange instead. Toolbar/legend keep their height,\n the stage eats the rest. */\n.cg-mount.cg-full { position:fixed; inset:0; z-index:9999; margin:0; border:0; border-radius:0; padding:10px 14px; background:var(--bg); display:flex; flex-direction:column; }\n.cg-mount.cg-full > .cg-bar, .cg-mount.cg-full > .cg-legend, .cg-mount.cg-full > .cg-groups { flex:0 0 auto; }\n.cg-mount.cg-full .cg-stage { flex:1 1 auto; min-height:0; }\n.cg-mount.cg-full .cg-scroll { min-height:0; max-height:none; height:100%; }\n.cg-mount.cg-full .cg-src-body { max-height:none; }\n.cg-mount.cg-full .cg-frame { flex:1 1 auto; height:auto; }\n.cg-search-wrap { position:relative; display:inline-block; }\n.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; }\n.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); }\n.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; }\n.cg-search-row:hover { background:var(--bd); }\n.cg-search-count { position:sticky; top:0; padding:4px 9px; font-size:11px; opacity:.65; background:var(--bg); border-bottom:1px solid var(--bd); }\n.cg-search-grp { padding:6px 9px 2px; font-size:11px; font-weight:600; opacity:.7; border-top:1px solid var(--bd); }\n.cg-search-grp:first-of-type { border-top:0; }\n.cg-stage { display:flex; gap:10px; align-items:flex-start; }\n.cg-stage .cg-scroll { flex:1 1 auto; min-width:0; }\n.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); }\n.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; }\n.cg-src-hd button { font:inherit; border:1px solid var(--bd); border-radius:5px; background:transparent; color:var(--muted); cursor:pointer; padding:0 6px; }\n.cg-src-body { margin:0; padding:8px 10px; overflow:auto; max-height:84vh; color:var(--fg); font:12px/1.5 ui-monospace,Consolas,monospace; white-space:pre; }\n.cg-src-note { color:var(--muted); font-style:italic; white-space:pre-wrap; }\n.cg-bar { display:flex; gap:8px; align-items:center; flex-wrap:wrap; font-size:.82em; color:var(--muted); margin-bottom:6px; }\n.cg-bar button { font:inherit; padding:1px 8px; border:1px solid var(--bd); border-radius:5px; background:transparent; cursor:pointer; }\n.cg-crumb .cg-seg { border:0; border-radius:0; padding:0; background:none; color:var(--accent); cursor:pointer; font:inherit; }\n.cg-crumb .cg-seg:hover { text-decoration:underline; }\n.cg-frame { display:block; width:100%; height:84vh; border:0; background:var(--bg); }\n.cg-flash { color:#b42318; }\n.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; }\n.cg-upbtn { cursor:pointer; }\n.cg-upbtn circle { fill:#fff; stroke:#94a3b8; }\n.cg-upbtn text { font-size:11px; fill:#57606a; }\n.cg-upbtn:hover circle { stroke:var(--accent); stroke-width:1.6; }\n.cg-upbtn:hover text { fill:var(--accent); }\n.cg-uplink { fill:none; stroke:#94a3b8; stroke-dasharray:3 2.5; pointer-events:none; }\n.cg-groups { display:flex; flex-wrap:wrap; gap:4px 12px; margin-top:6px; font-size:.75em; color:var(--muted); }\n.cg-chip { display:inline-flex; align-items:center; gap:4px; }\n.cg-chip i { width:10px; height:10px; border-radius:2px; border:1px solid #94a3b8; display:inline-block; }\n.cg-note { font-size:.8em; color:#9a6700; }\n.cg-n rect { fill:#eef2f7; stroke:#94a3b8; }\n.cg-n text { font-size:12px; fill:var(--fg); font-family:ui-monospace,Consolas,monospace; }\n.cg-n { cursor:pointer; }\n.cg-n.root rect { fill:#dbeafe; stroke:#2563eb; stroke-width:2; }\n.cg-n.leaf { opacity:.45; }\n.cg-n.test rect { stroke-dasharray:3 2; }\n.cg-n.grp rect { stroke-width:1.8; }\n.cg-e { fill:none; stroke:#94a3b8; stroke-width:.9; }\n.cg-e.cand { stroke-dasharray:2 3; }\n.cg-e.back { stroke:#dc2626; stroke-dasharray:5 3; }\n.cg-e.http { stroke:#0891b2; stroke-width:1.5; stroke-dasharray:5 2; } /* cross-stack API link */\n.cg-e.soft { opacity:.55; }\n.cg-svg.hl .cg-n { opacity:.22; }\n.cg-svg.hl .cg-e { opacity:.1; }\n.cg-svg.hl .cg-n.hl { opacity:1; }\n.cg-svg.hl .cg-e.hl { opacity:1; stroke-width:1.6; }\n";
|
|
6
6
|
readonly js: "\n(function () {\n function cmp(a, b) {\n var na = a.dataset.sort, nb = b.dataset.sort;\n if (na !== undefined && nb !== undefined) return parseFloat(na) - parseFloat(nb);\n return (a.textContent || \"\").localeCompare(b.textContent || \"\");\n }\n document.querySelectorAll(\"table.geml-table\").forEach(function (table) {\n var tbody = table.tBodies[0];\n if (!tbody) return;\n // Sort on header click.\n var ths = table.tHead ? table.tHead.rows[0].cells : [];\n Array.prototype.forEach.call(ths, function (th, col) {\n th.addEventListener(\"click\", function () {\n var dir = th.classList.contains(\"asc\") ? \"desc\" : \"asc\";\n Array.prototype.forEach.call(ths, function (h) { h.classList.remove(\"asc\", \"desc\"); });\n th.classList.add(dir);\n var rows = Array.prototype.slice.call(tbody.rows);\n rows.sort(function (r1, r2) {\n var c = cmp(r1.cells[col], r2.cells[col]);\n return dir === \"asc\" ? c : -c;\n });\n rows.forEach(function (r) { tbody.appendChild(r); });\n });\n });\n // Filter rows.\n var fig = table.closest(\".table-figure\");\n var input = fig ? fig.querySelector(\".table-filter\") : null;\n if (input) input.addEventListener(\"input\", function () {\n var q = input.value.toLowerCase();\n Array.prototype.forEach.call(tbody.rows, function (r) {\n r.style.display = (r.textContent || \"\").toLowerCase().indexOf(q) >= 0 ? \"\" : \"none\";\n });\n });\n });\n})();\n";
|
|
7
7
|
readonly codeGraphJs: string;
|
|
8
8
|
};
|
package/dist/render.d.ts
CHANGED
|
@@ -95,7 +95,7 @@ declare function buildCodeGraph(startRel: string, opts: RenderOptions, view?: {
|
|
|
95
95
|
error?: string;
|
|
96
96
|
truncated?: boolean;
|
|
97
97
|
};
|
|
98
|
-
export declare const CSS = "\n:root { --fg:#1f2328; --muted:#656d76; --bd:#d0d7de; --bg:#fff; --accent:#2563eb; --code-bg:#f6f8fa; }\n* { box-sizing: border-box; }\nbody { margin:0; color:var(--fg); background:#fafbfc; font:16px/1.6 -apple-system,BlinkMacSystemFont,\"Segoe UI\",Helvetica,Arial,\"PingFang SC\",\"Microsoft Yahei\",sans-serif; }\nmain { max-width: 860px; margin: 0 auto; padding: 48px 24px 96px; background:var(--bg); }\nh1,h2,h3,h4,h5,h6 { line-height:1.25; margin:1.6em 0 .6em; scroll-margin-top:16px; }\nh1 { font-size:2em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }\nh2 { font-size:1.5em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }\nh3 { font-size:1.25em; } h4 { font-size:1em; }\np { margin:.7em 0; }\na { color:var(--accent); text-decoration:none; } a:hover { text-decoration:underline; }\ncode { background:var(--code-bg); padding:.15em .35em; border-radius:6px; font:.88em ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; }\npre { background:var(--code-bg); padding:14px 16px; border-radius:8px; overflow:auto; }\npre code { background:none; padding:0; font-size:.85em; }\npre.output { background:#0d1117; color:#e6edf3; }\npre.output code { color:inherit; }\nul,ol { padding-left:1.6em; } li { margin:.2em 0; }\nul.task-list { list-style:none; padding-left:.2em; }\nli.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; }\nli.task input[type=checkbox]:checked { background-color:#1f883d; border-color:#1f883d; }\nli.task input[type=checkbox]:checked::after { content:\"\u2713\"; 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; }\naside.callout { border-left:4px solid var(--accent); background:#f0f6ff; padding:.4em 16px; border-radius:0 8px 8px 0; margin:1em 0; }\naside.aside { border-left-color:#8b949e; background:#f6f8fa; }\naside.warning { border-left-color:#d97706; background:#fff8f0; }\naside.callout > :first-child { margin-top:0; } aside.callout > :last-child { margin-bottom:0; }\nfigure { margin:1.2em 0; }\nfigcaption { color:var(--muted); font-size:.86em; text-align:center; margin-top:.5em; }\ntable.geml-table { border-collapse:collapse; width:100%; font-size:.92em; }\ntable.geml-table th, table.geml-table td { border:1px solid var(--bd); padding:6px 12px; }\ntable.geml-table thead th { background:var(--code-bg); cursor:pointer; user-select:none; white-space:nowrap; }\ntable.geml-table thead th::after { content:\" \\2195\"; color:var(--muted); font-size:.8em; }\ntable.geml-table thead th.asc::after { content:\" \\2191\"; color:var(--accent); }\ntable.geml-table thead th.desc::after { content:\" \\2193\"; color:var(--accent); }\ntable.geml-table tbody tr:nth-child(2n) { background:#fafbfc; }\ntable.geml-table td.computed { color:#0a7c52; }\ntable.geml-table tfoot td { background:var(--code-bg); font-weight:600; border-top:2px solid var(--bd); }\n.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; }\n.table-figure details > summary { cursor:pointer; color:var(--muted); font-size:.86em; padding:4px 0; }\n.table-note { color:var(--muted); font-size:.82em; margin:6px 0 0; }\n.geml-chart { width:100%; height:auto; background:var(--bg); border:1px solid var(--bd); border-radius:8px; }\n.c-title { font-size:15px; font-weight:600; fill:var(--fg); }\n.c-grid { stroke:#eaecef; } .c-axis { stroke:#aab1b8; } .c-tick { font-size:11px; fill:var(--muted); } .c-legend { font-size:12px; fill:var(--fg); }\n.media { max-width:100%; border-radius:8px; }\n.diagram-src { color:var(--muted); } .render-error { color:#cf222e; }\n.math-block { overflow-x:auto; padding:.4em 0; }\nsup.fn a { font-size:.75em; }\n.geml-footer { max-width:860px; margin:0 auto; padding:16px 24px 40px; color:var(--muted); font-size:.82em; }\n.geml-footer code { font-size:.95em; }\n.code-graph { margin:1.4em 0; }\n.cg-mount { border:1px solid var(--bd); border-radius:8px; padding:10px 12px; background:var(--bg); }\n.cg-scroll { overflow:auto; min-height:52vh; max-height:72vh; }\n.cg-svg { display:block; }\n.cg-search-wrap { position:relative; display:inline-block; }\n.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; }\n.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); }\n.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; }\n.cg-search-row:hover { background:var(--bd); }\n.cg-search-count { position:sticky; top:0; padding:4px 9px; font-size:11px; opacity:.65; background:var(--bg); border-bottom:1px solid var(--bd); }\n.cg-search-grp { padding:6px 9px 2px; font-size:11px; font-weight:600; opacity:.7; border-top:1px solid var(--bd); }\n.cg-search-grp:first-of-type { border-top:0; }\n.cg-stage { display:flex; gap:10px; align-items:flex-start; }\n.cg-stage .cg-scroll { flex:1 1 auto; min-width:0; }\n.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); }\n.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; }\n.cg-src-hd button { font:inherit; border:1px solid var(--bd); border-radius:5px; background:transparent; color:var(--muted); cursor:pointer; padding:0 6px; }\n.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; }\n.cg-src-note { color:var(--muted); font-style:italic; white-space:pre-wrap; }\n.cg-bar { display:flex; gap:8px; align-items:center; flex-wrap:wrap; font-size:.82em; color:var(--muted); margin-bottom:6px; }\n.cg-bar button { font:inherit; padding:1px 8px; border:1px solid var(--bd); border-radius:5px; background:transparent; cursor:pointer; }\n.cg-crumb .cg-seg { border:0; border-radius:0; padding:0; background:none; color:var(--accent); cursor:pointer; font:inherit; }\n.cg-crumb .cg-seg:hover { text-decoration:underline; }\n.cg-frame { display:block; width:100%; height:72vh; border:0; background:var(--bg); }\n.cg-flash { color:#b42318; }\n.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; }\n.cg-upbtn { cursor:pointer; }\n.cg-upbtn circle { fill:#fff; stroke:#94a3b8; }\n.cg-upbtn text { font-size:11px; fill:#57606a; }\n.cg-upbtn:hover circle { stroke:var(--accent); stroke-width:1.6; }\n.cg-upbtn:hover text { fill:var(--accent); }\n.cg-uplink { fill:none; stroke:#94a3b8; stroke-dasharray:3 2.5; pointer-events:none; }\n.cg-groups { display:flex; flex-wrap:wrap; gap:4px 12px; margin-top:6px; font-size:.75em; color:var(--muted); }\n.cg-chip { display:inline-flex; align-items:center; gap:4px; }\n.cg-chip i { width:10px; height:10px; border-radius:2px; border:1px solid #94a3b8; display:inline-block; }\n.cg-note { font-size:.8em; color:#9a6700; }\n.cg-n rect { fill:#eef2f7; stroke:#94a3b8; }\n.cg-n text { font-size:12px; fill:var(--fg); font-family:ui-monospace,Consolas,monospace; }\n.cg-n { cursor:pointer; }\n.cg-n.root rect { fill:#dbeafe; stroke:#2563eb; stroke-width:2; }\n.cg-n.leaf { opacity:.45; }\n.cg-n.test rect { stroke-dasharray:3 2; }\n.cg-n.grp rect { stroke-width:1.8; }\n.cg-e { fill:none; stroke:#94a3b8; stroke-width:.9; }\n.cg-e.cand { stroke-dasharray:2 3; }\n.cg-e.back { stroke:#dc2626; stroke-dasharray:5 3; }\n.cg-e.http { stroke:#0891b2; stroke-width:1.5; stroke-dasharray:5 2; } /* cross-stack API link */\n.cg-e.soft { opacity:.55; }\n.cg-svg.hl .cg-n { opacity:.22; }\n.cg-svg.hl .cg-e { opacity:.1; }\n.cg-svg.hl .cg-n.hl { opacity:1; }\n.cg-svg.hl .cg-e.hl { opacity:1; stroke-width:1.6; }\n";
|
|
98
|
+
export declare const CSS = "\n:root { --fg:#1f2328; --muted:#656d76; --bd:#d0d7de; --bg:#fff; --accent:#2563eb; --code-bg:#f6f8fa; }\n* { box-sizing: border-box; }\nbody { margin:0; color:var(--fg); background:#fafbfc; font:16px/1.6 -apple-system,BlinkMacSystemFont,\"Segoe UI\",Helvetica,Arial,\"PingFang SC\",\"Microsoft Yahei\",sans-serif; }\nmain { max-width: 860px; margin: 0 auto; padding: 48px 24px 96px; background:var(--bg); }\nh1,h2,h3,h4,h5,h6 { line-height:1.25; margin:1.6em 0 .6em; scroll-margin-top:16px; }\nh1 { font-size:2em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }\nh2 { font-size:1.5em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }\nh3 { font-size:1.25em; } h4 { font-size:1em; }\np { margin:.7em 0; }\na { color:var(--accent); text-decoration:none; } a:hover { text-decoration:underline; }\ncode { background:var(--code-bg); padding:.15em .35em; border-radius:6px; font:.88em ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; }\npre { background:var(--code-bg); padding:14px 16px; border-radius:8px; overflow:auto; }\npre code { background:none; padding:0; font-size:.85em; }\npre.output { background:#0d1117; color:#e6edf3; }\npre.output code { color:inherit; }\nul,ol { padding-left:1.6em; } li { margin:.2em 0; }\nul.task-list { list-style:none; padding-left:.2em; }\nli.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; }\nli.task input[type=checkbox]:checked { background-color:#1f883d; border-color:#1f883d; }\nli.task input[type=checkbox]:checked::after { content:\"\u2713\"; 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; }\naside.callout { border-left:4px solid var(--accent); background:#f0f6ff; padding:.4em 16px; border-radius:0 8px 8px 0; margin:1em 0; }\naside.aside { border-left-color:#8b949e; background:#f6f8fa; }\naside.warning { border-left-color:#d97706; background:#fff8f0; }\naside.callout > :first-child { margin-top:0; } aside.callout > :last-child { margin-bottom:0; }\nfigure { margin:1.2em 0; }\nfigcaption { color:var(--muted); font-size:.86em; text-align:center; margin-top:.5em; }\ntable.geml-table { border-collapse:collapse; width:100%; font-size:.92em; }\ntable.geml-table th, table.geml-table td { border:1px solid var(--bd); padding:6px 12px; }\ntable.geml-table thead th { background:var(--code-bg); cursor:pointer; user-select:none; white-space:nowrap; }\ntable.geml-table thead th::after { content:\" \\2195\"; color:var(--muted); font-size:.8em; }\ntable.geml-table thead th.asc::after { content:\" \\2191\"; color:var(--accent); }\ntable.geml-table thead th.desc::after { content:\" \\2193\"; color:var(--accent); }\ntable.geml-table tbody tr:nth-child(2n) { background:#fafbfc; }\ntable.geml-table td.computed { color:#0a7c52; }\ntable.geml-table tfoot td { background:var(--code-bg); font-weight:600; border-top:2px solid var(--bd); }\n.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; }\n/* A table wider than the column scrolls INSIDE its own box \u2014 the page itself\n must never scroll sideways. Only the table is in here: the filter box above\n and the caption below stay put instead of sliding out of view with it. */\n.table-scroll { overflow-x:auto; max-width:100%; }\n.table-figure details > summary { cursor:pointer; color:var(--muted); font-size:.86em; padding:4px 0; }\n.table-note { color:var(--muted); font-size:.82em; margin:6px 0 0; }\n.geml-chart { width:100%; height:auto; background:var(--bg); border:1px solid var(--bd); border-radius:8px; }\n.c-title { font-size:15px; font-weight:600; fill:var(--fg); }\n.c-grid { stroke:#eaecef; } .c-axis { stroke:#aab1b8; } .c-tick { font-size:11px; fill:var(--muted); } .c-legend { font-size:12px; fill:var(--fg); }\n.media { max-width:100%; border-radius:8px; }\n.diagram-src { color:var(--muted); } .render-error { color:#cf222e; }\n.math-block { overflow-x:auto; padding:.4em 0; }\nsup.fn a { font-size:.75em; }\n.geml-footer { max-width:860px; margin:0 auto; padding:16px 24px 40px; color:var(--muted); font-size:.82em; }\n.geml-footer code { font-size:.95em; }\n.code-graph { margin:1.4em 0; }\n/* The graph is the widest artifact on the page: let it break out of main's\n 860px reading column and take the viewport, centred, leaving the prose\n around it untouched. Negative inline margins, NOT transform \u2014 a transform\n would become the containing block for the fullscreen overlay below. */\n@media (min-width:900px) { .code-graph { margin-inline: calc((100% - min(96vw, 1600px)) / 2); } }\n.cg-mount { border:1px solid var(--bd); border-radius:8px; padding:10px 12px; background:var(--bg); }\n.cg-scroll { overflow:auto; min-height:60vh; max-height:84vh; }\n.cg-svg { display:block; }\n/* Fullscreen: one class drives the layout, whether the native Fullscreen API\n took (mount is in the top layer) or we fell back to a fixed overlay. The\n :fullscreen pseudo-class is deliberately NOT in these selectors \u2014 a browser\n that doesn't know it would drop the whole rule; the runtime keeps the class\n in sync with fullscreenchange instead. Toolbar/legend keep their height,\n the stage eats the rest. */\n.cg-mount.cg-full { position:fixed; inset:0; z-index:9999; margin:0; border:0; border-radius:0; padding:10px 14px; background:var(--bg); display:flex; flex-direction:column; }\n.cg-mount.cg-full > .cg-bar, .cg-mount.cg-full > .cg-legend, .cg-mount.cg-full > .cg-groups { flex:0 0 auto; }\n.cg-mount.cg-full .cg-stage { flex:1 1 auto; min-height:0; }\n.cg-mount.cg-full .cg-scroll { min-height:0; max-height:none; height:100%; }\n.cg-mount.cg-full .cg-src-body { max-height:none; }\n.cg-mount.cg-full .cg-frame { flex:1 1 auto; height:auto; }\n.cg-search-wrap { position:relative; display:inline-block; }\n.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; }\n.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); }\n.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; }\n.cg-search-row:hover { background:var(--bd); }\n.cg-search-count { position:sticky; top:0; padding:4px 9px; font-size:11px; opacity:.65; background:var(--bg); border-bottom:1px solid var(--bd); }\n.cg-search-grp { padding:6px 9px 2px; font-size:11px; font-weight:600; opacity:.7; border-top:1px solid var(--bd); }\n.cg-search-grp:first-of-type { border-top:0; }\n.cg-stage { display:flex; gap:10px; align-items:flex-start; }\n.cg-stage .cg-scroll { flex:1 1 auto; min-width:0; }\n.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); }\n.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; }\n.cg-src-hd button { font:inherit; border:1px solid var(--bd); border-radius:5px; background:transparent; color:var(--muted); cursor:pointer; padding:0 6px; }\n.cg-src-body { margin:0; padding:8px 10px; overflow:auto; max-height:84vh; color:var(--fg); font:12px/1.5 ui-monospace,Consolas,monospace; white-space:pre; }\n.cg-src-note { color:var(--muted); font-style:italic; white-space:pre-wrap; }\n.cg-bar { display:flex; gap:8px; align-items:center; flex-wrap:wrap; font-size:.82em; color:var(--muted); margin-bottom:6px; }\n.cg-bar button { font:inherit; padding:1px 8px; border:1px solid var(--bd); border-radius:5px; background:transparent; cursor:pointer; }\n.cg-crumb .cg-seg { border:0; border-radius:0; padding:0; background:none; color:var(--accent); cursor:pointer; font:inherit; }\n.cg-crumb .cg-seg:hover { text-decoration:underline; }\n.cg-frame { display:block; width:100%; height:84vh; border:0; background:var(--bg); }\n.cg-flash { color:#b42318; }\n.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; }\n.cg-upbtn { cursor:pointer; }\n.cg-upbtn circle { fill:#fff; stroke:#94a3b8; }\n.cg-upbtn text { font-size:11px; fill:#57606a; }\n.cg-upbtn:hover circle { stroke:var(--accent); stroke-width:1.6; }\n.cg-upbtn:hover text { fill:var(--accent); }\n.cg-uplink { fill:none; stroke:#94a3b8; stroke-dasharray:3 2.5; pointer-events:none; }\n.cg-groups { display:flex; flex-wrap:wrap; gap:4px 12px; margin-top:6px; font-size:.75em; color:var(--muted); }\n.cg-chip { display:inline-flex; align-items:center; gap:4px; }\n.cg-chip i { width:10px; height:10px; border-radius:2px; border:1px solid #94a3b8; display:inline-block; }\n.cg-note { font-size:.8em; color:#9a6700; }\n.cg-n rect { fill:#eef2f7; stroke:#94a3b8; }\n.cg-n text { font-size:12px; fill:var(--fg); font-family:ui-monospace,Consolas,monospace; }\n.cg-n { cursor:pointer; }\n.cg-n.root rect { fill:#dbeafe; stroke:#2563eb; stroke-width:2; }\n.cg-n.leaf { opacity:.45; }\n.cg-n.test rect { stroke-dasharray:3 2; }\n.cg-n.grp rect { stroke-width:1.8; }\n.cg-e { fill:none; stroke:#94a3b8; stroke-width:.9; }\n.cg-e.cand { stroke-dasharray:2 3; }\n.cg-e.back { stroke:#dc2626; stroke-dasharray:5 3; }\n.cg-e.http { stroke:#0891b2; stroke-width:1.5; stroke-dasharray:5 2; } /* cross-stack API link */\n.cg-e.soft { opacity:.55; }\n.cg-svg.hl .cg-n { opacity:.22; }\n.cg-svg.hl .cg-e { opacity:.1; }\n.cg-svg.hl .cg-n.hl { opacity:1; }\n.cg-svg.hl .cg-e.hl { opacity:1; stroke-width:1.6; }\n";
|
|
99
99
|
export declare const JS = "\n(function () {\n function cmp(a, b) {\n var na = a.dataset.sort, nb = b.dataset.sort;\n if (na !== undefined && nb !== undefined) return parseFloat(na) - parseFloat(nb);\n return (a.textContent || \"\").localeCompare(b.textContent || \"\");\n }\n document.querySelectorAll(\"table.geml-table\").forEach(function (table) {\n var tbody = table.tBodies[0];\n if (!tbody) return;\n // Sort on header click.\n var ths = table.tHead ? table.tHead.rows[0].cells : [];\n Array.prototype.forEach.call(ths, function (th, col) {\n th.addEventListener(\"click\", function () {\n var dir = th.classList.contains(\"asc\") ? \"desc\" : \"asc\";\n Array.prototype.forEach.call(ths, function (h) { h.classList.remove(\"asc\", \"desc\"); });\n th.classList.add(dir);\n var rows = Array.prototype.slice.call(tbody.rows);\n rows.sort(function (r1, r2) {\n var c = cmp(r1.cells[col], r2.cells[col]);\n return dir === \"asc\" ? c : -c;\n });\n rows.forEach(function (r) { tbody.appendChild(r); });\n });\n });\n // Filter rows.\n var fig = table.closest(\".table-figure\");\n var input = fig ? fig.querySelector(\".table-filter\") : null;\n if (input) input.addEventListener(\"input\", function () {\n var q = input.value.toLowerCase();\n Array.prototype.forEach.call(tbody.rows, function (r) {\n r.style.display = (r.textContent || \"\").toLowerCase().indexOf(q) >= 0 ? \"\" : \"none\";\n });\n });\n });\n})();\n";
|
|
100
100
|
export declare function codeGraphRuntime(root: {
|
|
101
101
|
querySelectorAll(sel: string): ArrayLike<Element>;
|
package/dist/render.js
CHANGED
|
@@ -671,13 +671,13 @@ export class RenderCtx {
|
|
|
671
671
|
if (this.isCodemapDoc) {
|
|
672
672
|
const summary = `${esc(id ? "#" + id : "table")} · ${allRows.length} rows (preview: first ${maxRows})`;
|
|
673
673
|
return `<figure class="table-figure"${idAttr}><details><summary>${summary}</summary>${tools}` +
|
|
674
|
-
`<table class="geml-table">${thead}<tbody>\n${bodyRows}\n</tbody>${tfoot}</table>${note}</details>${cap}</figure>`;
|
|
674
|
+
`<div class="table-scroll"><table class="geml-table">${thead}<tbody>\n${bodyRows}\n</tbody>${tfoot}</table></div>${note}</details>${cap}</figure>`;
|
|
675
675
|
}
|
|
676
676
|
return `<figure class="table-figure"${idAttr}>${tools}` +
|
|
677
|
-
`<table class="geml-table">${thead}<tbody>\n${bodyRows}\n</tbody>${tfoot}</table>${note}${cap}</figure>`;
|
|
677
|
+
`<div class="table-scroll"><table class="geml-table">${thead}<tbody>\n${bodyRows}\n</tbody>${tfoot}</table></div>${note}${cap}</figure>`;
|
|
678
678
|
}
|
|
679
679
|
return `<figure class="table-figure"${idAttr}>${tools}` +
|
|
680
|
-
`<table class="geml-table">${thead}<tbody>\n${bodyRows}\n</tbody>${tfoot}</table>${cap}</figure>`;
|
|
680
|
+
`<div class="table-scroll"><table class="geml-table">${thead}<tbody>\n${bodyRows}\n</tbody>${tfoot}</table></div>${cap}</figure>`;
|
|
681
681
|
}
|
|
682
682
|
}
|
|
683
683
|
// ---------------------------------------------------------------------------
|
|
@@ -1302,6 +1302,10 @@ table.geml-table tbody tr:nth-child(2n) { background:#fafbfc; }
|
|
|
1302
1302
|
table.geml-table td.computed { color:#0a7c52; }
|
|
1303
1303
|
table.geml-table tfoot td { background:var(--code-bg); font-weight:600; border-top:2px solid var(--bd); }
|
|
1304
1304
|
.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; }
|
|
1305
|
+
/* A table wider than the column scrolls INSIDE its own box — the page itself
|
|
1306
|
+
must never scroll sideways. Only the table is in here: the filter box above
|
|
1307
|
+
and the caption below stay put instead of sliding out of view with it. */
|
|
1308
|
+
.table-scroll { overflow-x:auto; max-width:100%; }
|
|
1305
1309
|
.table-figure details > summary { cursor:pointer; color:var(--muted); font-size:.86em; padding:4px 0; }
|
|
1306
1310
|
.table-note { color:var(--muted); font-size:.82em; margin:6px 0 0; }
|
|
1307
1311
|
.geml-chart { width:100%; height:auto; background:var(--bg); border:1px solid var(--bd); border-radius:8px; }
|
|
@@ -1314,9 +1318,26 @@ sup.fn a { font-size:.75em; }
|
|
|
1314
1318
|
.geml-footer { max-width:860px; margin:0 auto; padding:16px 24px 40px; color:var(--muted); font-size:.82em; }
|
|
1315
1319
|
.geml-footer code { font-size:.95em; }
|
|
1316
1320
|
.code-graph { margin:1.4em 0; }
|
|
1321
|
+
/* The graph is the widest artifact on the page: let it break out of main's
|
|
1322
|
+
860px reading column and take the viewport, centred, leaving the prose
|
|
1323
|
+
around it untouched. Negative inline margins, NOT transform — a transform
|
|
1324
|
+
would become the containing block for the fullscreen overlay below. */
|
|
1325
|
+
@media (min-width:900px) { .code-graph { margin-inline: calc((100% - min(96vw, 1600px)) / 2); } }
|
|
1317
1326
|
.cg-mount { border:1px solid var(--bd); border-radius:8px; padding:10px 12px; background:var(--bg); }
|
|
1318
|
-
.cg-scroll { overflow:auto; min-height:
|
|
1327
|
+
.cg-scroll { overflow:auto; min-height:60vh; max-height:84vh; }
|
|
1319
1328
|
.cg-svg { display:block; }
|
|
1329
|
+
/* Fullscreen: one class drives the layout, whether the native Fullscreen API
|
|
1330
|
+
took (mount is in the top layer) or we fell back to a fixed overlay. The
|
|
1331
|
+
:fullscreen pseudo-class is deliberately NOT in these selectors — a browser
|
|
1332
|
+
that doesn't know it would drop the whole rule; the runtime keeps the class
|
|
1333
|
+
in sync with fullscreenchange instead. Toolbar/legend keep their height,
|
|
1334
|
+
the stage eats the rest. */
|
|
1335
|
+
.cg-mount.cg-full { position:fixed; inset:0; z-index:9999; margin:0; border:0; border-radius:0; padding:10px 14px; background:var(--bg); display:flex; flex-direction:column; }
|
|
1336
|
+
.cg-mount.cg-full > .cg-bar, .cg-mount.cg-full > .cg-legend, .cg-mount.cg-full > .cg-groups { flex:0 0 auto; }
|
|
1337
|
+
.cg-mount.cg-full .cg-stage { flex:1 1 auto; min-height:0; }
|
|
1338
|
+
.cg-mount.cg-full .cg-scroll { min-height:0; max-height:none; height:100%; }
|
|
1339
|
+
.cg-mount.cg-full .cg-src-body { max-height:none; }
|
|
1340
|
+
.cg-mount.cg-full .cg-frame { flex:1 1 auto; height:auto; }
|
|
1320
1341
|
.cg-search-wrap { position:relative; display:inline-block; }
|
|
1321
1342
|
.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; }
|
|
1322
1343
|
.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); }
|
|
@@ -1330,13 +1351,13 @@ sup.fn a { font-size:.75em; }
|
|
|
1330
1351
|
.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); }
|
|
1331
1352
|
.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; }
|
|
1332
1353
|
.cg-src-hd button { font:inherit; border:1px solid var(--bd); border-radius:5px; background:transparent; color:var(--muted); cursor:pointer; padding:0 6px; }
|
|
1333
|
-
.cg-src-body { margin:0; padding:8px 10px; overflow:auto; max-height:
|
|
1354
|
+
.cg-src-body { margin:0; padding:8px 10px; overflow:auto; max-height:84vh; color:var(--fg); font:12px/1.5 ui-monospace,Consolas,monospace; white-space:pre; }
|
|
1334
1355
|
.cg-src-note { color:var(--muted); font-style:italic; white-space:pre-wrap; }
|
|
1335
1356
|
.cg-bar { display:flex; gap:8px; align-items:center; flex-wrap:wrap; font-size:.82em; color:var(--muted); margin-bottom:6px; }
|
|
1336
1357
|
.cg-bar button { font:inherit; padding:1px 8px; border:1px solid var(--bd); border-radius:5px; background:transparent; cursor:pointer; }
|
|
1337
1358
|
.cg-crumb .cg-seg { border:0; border-radius:0; padding:0; background:none; color:var(--accent); cursor:pointer; font:inherit; }
|
|
1338
1359
|
.cg-crumb .cg-seg:hover { text-decoration:underline; }
|
|
1339
|
-
.cg-frame { display:block; width:100%; height:
|
|
1360
|
+
.cg-frame { display:block; width:100%; height:84vh; border:0; background:var(--bg); }
|
|
1340
1361
|
.cg-flash { color:#b42318; }
|
|
1341
1362
|
.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; }
|
|
1342
1363
|
.cg-upbtn { cursor:pointer; }
|
|
@@ -1564,7 +1585,11 @@ export function codeGraphRuntime(root) {
|
|
|
1564
1585
|
setData(data0.mode === "modules" && data0.mods && gpath && gpath.length ? deriveView(gpath) : homeData());
|
|
1565
1586
|
// scale null = fit-to-width on first draw. Left-right is the default —
|
|
1566
1587
|
// call flow reads with the text; the toggle persists per reader.
|
|
1567
|
-
|
|
1588
|
+
// autoScale mirrors the last scale the VIEW picked for itself. While
|
|
1589
|
+
// state.scale still equals it the zoom is ours to re-derive (entering
|
|
1590
|
+
// fullscreen re-fits); once the user works the zoom buttons the two
|
|
1591
|
+
// diverge and their choice is left alone.
|
|
1592
|
+
var state = { roots: data.roots.slice(), trail: [], scale: null, autoScale: null, dir: "LR", frame: null, cap: 600, showAcc: false };
|
|
1568
1593
|
// Direction survives module -> container navigation (each page is a fresh
|
|
1569
1594
|
// document); best-effort only — file:// or the DOM stub may lack storage.
|
|
1570
1595
|
try {
|
|
@@ -1573,6 +1598,103 @@ export function codeGraphRuntime(root) {
|
|
|
1573
1598
|
state.dir = sd;
|
|
1574
1599
|
}
|
|
1575
1600
|
catch (e) { /* no storage */ }
|
|
1601
|
+
// Fullscreen — the graph takes the viewport instead of its 84vh slot in the
|
|
1602
|
+
// reading column. Native Fullscreen API when the browser grants it (browser
|
|
1603
|
+
// chrome goes away, Esc is handled for us); a fixed overlay when it does not
|
|
1604
|
+
// — a sandboxed iframe or a denied permission still gets the space. Both
|
|
1605
|
+
// paths flip the SAME .cg-full class, so the stylesheet and the pane
|
|
1606
|
+
// measurement never have to ask which one is in force.
|
|
1607
|
+
var fullBtnEl = null;
|
|
1608
|
+
function isFull() { return !!(mount.classList && mount.classList.contains("cg-full")); }
|
|
1609
|
+
// The canvas was scaled for the old pane; after the class flips it must be
|
|
1610
|
+
// refitted to the new one. draw() publishes the hook (drawFrame's iframe
|
|
1611
|
+
// needs none and clears it), and layout has to settle before the pane can
|
|
1612
|
+
// be measured — hence the frame delay.
|
|
1613
|
+
function refit() { var f = mount._cgRefit; if (typeof f === "function")
|
|
1614
|
+
f(); }
|
|
1615
|
+
function scheduleRefit() { if (typeof requestAnimationFrame === "function")
|
|
1616
|
+
requestAnimationFrame(refit);
|
|
1617
|
+
else
|
|
1618
|
+
refit(); }
|
|
1619
|
+
function syncFullBtn() {
|
|
1620
|
+
if (!fullBtnEl)
|
|
1621
|
+
return;
|
|
1622
|
+
fullBtnEl.textContent = isFull() ? "⛶ exit fullscreen" : "⛶ fullscreen";
|
|
1623
|
+
fullBtnEl.title = isFull() ? "leave fullscreen (Esc)" : "fill the viewport with the graph";
|
|
1624
|
+
}
|
|
1625
|
+
function setFull(on) {
|
|
1626
|
+
if (mount.classList && mount.classList.toggle)
|
|
1627
|
+
mount.classList.toggle("cg-full", !!on);
|
|
1628
|
+
syncFullBtn();
|
|
1629
|
+
scheduleRefit();
|
|
1630
|
+
}
|
|
1631
|
+
function nativeFullEl() {
|
|
1632
|
+
try {
|
|
1633
|
+
return document.fullscreenElement || document.webkitFullscreenElement || null;
|
|
1634
|
+
}
|
|
1635
|
+
catch (e) {
|
|
1636
|
+
return null;
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
function toggleFull() {
|
|
1640
|
+
if (isFull()) {
|
|
1641
|
+
if (nativeFullEl() === mount) {
|
|
1642
|
+
var exit = document.exitFullscreen || document.webkitExitFullscreen;
|
|
1643
|
+
// The class follows in the fullscreenchange handler.
|
|
1644
|
+
if (exit) {
|
|
1645
|
+
try {
|
|
1646
|
+
exit.call(document);
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
catch (e) { /* fall through to the overlay */ }
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
setFull(false);
|
|
1653
|
+
return;
|
|
1654
|
+
}
|
|
1655
|
+
var req = mount.requestFullscreen || mount.webkitRequestFullscreen;
|
|
1656
|
+
if (req) {
|
|
1657
|
+
try {
|
|
1658
|
+
var p = req.call(mount);
|
|
1659
|
+
// A rejected promise means permission denied (sandboxed frame,
|
|
1660
|
+
// permissions policy): take the overlay rather than nothing.
|
|
1661
|
+
if (p && typeof p.catch === "function")
|
|
1662
|
+
p.catch(function () { setFull(true); });
|
|
1663
|
+
setFull(true);
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
catch (e) { /* no native fullscreen — overlay below */ }
|
|
1667
|
+
}
|
|
1668
|
+
setFull(true);
|
|
1669
|
+
}
|
|
1670
|
+
// Esc leaves the OVERLAY fallback; native fullscreen handles its own Esc and
|
|
1671
|
+
// reports through fullscreenchange. The listeners are document-wide because
|
|
1672
|
+
// focus may sit on a node, the search box, or nothing at all.
|
|
1673
|
+
try {
|
|
1674
|
+
document.addEventListener("keydown", function (ev) { if (ev.key === "Escape" && isFull() && !nativeFullEl())
|
|
1675
|
+
setFull(false); });
|
|
1676
|
+
var syncFull = function () {
|
|
1677
|
+
var el = nativeFullEl();
|
|
1678
|
+
if (!el && isFull())
|
|
1679
|
+
setFull(false); // left via Esc / F11 / browser UI
|
|
1680
|
+
else if (el === mount && !isFull())
|
|
1681
|
+
setFull(true);
|
|
1682
|
+
else if (el === mount)
|
|
1683
|
+
scheduleRefit(); // class truthful, geometry still changed
|
|
1684
|
+
};
|
|
1685
|
+
document.addEventListener("fullscreenchange", syncFull);
|
|
1686
|
+
document.addEventListener("webkitfullscreenchange", syncFull);
|
|
1687
|
+
}
|
|
1688
|
+
catch (e) { /* no document listeners (fake-DOM runtime test) */ }
|
|
1689
|
+
// Built into whichever toolbar is drawing — the graph view and the nested
|
|
1690
|
+
// iframe view both get one.
|
|
1691
|
+
function fullBtn(bar) {
|
|
1692
|
+
var b = document.createElement("button");
|
|
1693
|
+
b.onclick = toggleFull;
|
|
1694
|
+
fullBtnEl = b;
|
|
1695
|
+
syncFullBtn();
|
|
1696
|
+
bar.appendChild(b);
|
|
1697
|
+
}
|
|
1576
1698
|
function slice(roots) {
|
|
1577
1699
|
var keep = {}, layer = {}, q = [], qi = 0, order = [];
|
|
1578
1700
|
// Accessor noise (bean get/set/is leaves, .accessor) is hidden unless
|
|
@@ -1678,6 +1800,10 @@ export function codeGraphRuntime(root) {
|
|
|
1678
1800
|
open.href = state.frame.html;
|
|
1679
1801
|
open.textContent = "open standalone ↗";
|
|
1680
1802
|
bar.appendChild(open);
|
|
1803
|
+
// The iframe is height:auto/flex in fullscreen, so it needs no refit —
|
|
1804
|
+
// drop the previous view's hook rather than leave a stale canvas one.
|
|
1805
|
+
mount._cgRefit = null;
|
|
1806
|
+
fullBtn(bar);
|
|
1681
1807
|
mount.appendChild(bar);
|
|
1682
1808
|
var fr = document.createElement("iframe");
|
|
1683
1809
|
fr.className = "cg-frame";
|
|
@@ -1990,7 +2116,45 @@ export function codeGraphRuntime(root) {
|
|
|
1990
2116
|
// Rendered pages sit next to their codemap documents: a live mount
|
|
1991
2117
|
// (viewer/playground) carries data-src, a CLI embed carries the src
|
|
1992
2118
|
// path in data.start — either directory anchors doc-relative links.
|
|
1993
|
-
|
|
2119
|
+
// navBase prefixes EVERY url this view reaches for — the breadcrumb's
|
|
2120
|
+
// `location.href`, the search-index `<script src>`, a hit's jump target.
|
|
2121
|
+
// Both of its inputs are page data (a DOM attribute, the embedded graph
|
|
2122
|
+
// JSON), so a document that carried `data-src="javascript:…"` would turn
|
|
2123
|
+
// a breadcrumb click into script execution, and a `//host/` or
|
|
2124
|
+
// `https://host/` value would pull the search index off another origin.
|
|
2125
|
+
// A base is a doc-relative DIRECTORY and nothing else: anything bearing a
|
|
2126
|
+
// scheme or a network-path prefix is refused outright and links resolve
|
|
2127
|
+
// against the current page instead.
|
|
2128
|
+
// Everything this view navigates to or loads is built from page data, so
|
|
2129
|
+
// every such string is filtered here first: keep it if it is a
|
|
2130
|
+
// document-RELATIVE path, drop it to "" if it carries a scheme
|
|
2131
|
+
// (`javascript:`, `data:`) or a `//host` network-path prefix.
|
|
2132
|
+
//
|
|
2133
|
+
// DECIDE ON THE STRING THE BROWSER WILL SEE, NOT THE ONE WE WERE HANDED.
|
|
2134
|
+
// Before navigating, a browser strips leading and trailing C0 controls and
|
|
2135
|
+
// spaces and ignores TAB/CR/LF anywhere inside the scheme. Testing the raw
|
|
2136
|
+
// bytes let the whole classic bypass family through — ` javascript:…`,
|
|
2137
|
+
// `\tjavascript:…`, `java<TAB>script:…`, `\x01javascript:…`,
|
|
2138
|
+
// ` data:text/html,…`, ` //evil.host/` — each of which fails the scheme
|
|
2139
|
+
// test as written and then executes once the browser normalises it.
|
|
2140
|
+
// Normalise first, judge second, and return the NORMALISED value: handing
|
|
2141
|
+
// the sink the raw string would give it bytes that were never checked.
|
|
2142
|
+
function relOnly(u) {
|
|
2143
|
+
var raw = String(u == null ? "" : u).replace(/[\t\n\r]/g, "");
|
|
2144
|
+
// Trim C0-and-space by scan, not by /^[\x00-\x20]+|[\x00-\x20]+$/ — an
|
|
2145
|
+
// end-anchored run is the very quadratic shape this file's fence
|
|
2146
|
+
// scanners were just cured of.
|
|
2147
|
+
var a = 0, b = raw.length;
|
|
2148
|
+
while (a < b && raw.charCodeAt(a) <= 0x20)
|
|
2149
|
+
a++;
|
|
2150
|
+
while (b > a && raw.charCodeAt(b - 1) <= 0x20)
|
|
2151
|
+
b--;
|
|
2152
|
+
var s = raw.slice(a, b);
|
|
2153
|
+
if (!s)
|
|
2154
|
+
return "";
|
|
2155
|
+
return /^[a-zA-Z][a-zA-Z0-9+.\-]*:/.test(s) || s.slice(0, 2) === "//" ? "" : s;
|
|
2156
|
+
}
|
|
2157
|
+
var navBase = relOnly(String(mount.getAttribute("data-src") || data.start || "").replace(/[^\/]*$/, ""));
|
|
1994
2158
|
// A live mount (viewer/playground/served page) navigates IN PLACE over
|
|
1995
2159
|
// the geml documents through this loader; only truly static pages fall
|
|
1996
2160
|
// back to their pre-rendered sibling .html pages. Read LAZILY on every
|
|
@@ -2028,7 +2192,16 @@ export function codeGraphRuntime(root) {
|
|
|
2028
2192
|
}
|
|
2029
2193
|
catch (e) { /* stub */ }
|
|
2030
2194
|
}
|
|
2031
|
-
function openDoc(
|
|
2195
|
+
function openDoc(rel0, gpath) {
|
|
2196
|
+
// The target can come from a node's own data (a `data-k` key), not just
|
|
2197
|
+
// from a breadcrumb we built — so it goes through the same relative-path
|
|
2198
|
+
// filter as navBase. An absolute or scheme-bearing target says why it
|
|
2199
|
+
// was refused rather than navigating.
|
|
2200
|
+
var rel = relOnly(rel0);
|
|
2201
|
+
if (!rel) {
|
|
2202
|
+
flash("refusing to open " + String(rel0) + " — not a document-relative path");
|
|
2203
|
+
return;
|
|
2204
|
+
}
|
|
2032
2205
|
var lv = live();
|
|
2033
2206
|
if (lv) {
|
|
2034
2207
|
Promise.resolve(lv({ doc: rel })).then(function (nd) {
|
|
@@ -2163,16 +2336,20 @@ export function codeGraphRuntime(root) {
|
|
|
2163
2336
|
stage.className = "cg-stage";
|
|
2164
2337
|
stage.appendChild(scroller);
|
|
2165
2338
|
stage.appendChild(srcPanel);
|
|
2166
|
-
// The scroll pane is capped at
|
|
2167
|
-
// clientHeight is the unconstrained content
|
|
2168
|
-
// from the viewport.
|
|
2169
|
-
//
|
|
2170
|
-
//
|
|
2339
|
+
// The scroll pane is capped at 84vh by CSS (keep the 0.84 here in step with
|
|
2340
|
+
// it); before first layout its clientHeight is the unconstrained content
|
|
2341
|
+
// height, so derive the cap from the viewport. In fullscreen the pane is a
|
|
2342
|
+
// flex child with a real measured height, so trust clientHeight there and
|
|
2343
|
+
// fall back to nearly the whole viewport. Guards keep a collapsed pane
|
|
2344
|
+
// (mid-layout measure) from producing a negative or zero scale — invalid
|
|
2345
|
+
// CSS would silently keep the previous size.
|
|
2171
2346
|
function paneSize() {
|
|
2172
2347
|
var mw = scroller.clientWidth || mount.clientWidth || 0;
|
|
2173
2348
|
var mh = 0;
|
|
2174
2349
|
try {
|
|
2175
|
-
mh =
|
|
2350
|
+
mh = isFull()
|
|
2351
|
+
? Math.max(120, scroller.clientHeight || Math.floor(window.innerHeight * 0.92))
|
|
2352
|
+
: Math.floor(window.innerHeight * 0.84);
|
|
2176
2353
|
}
|
|
2177
2354
|
catch (e) { /* no window (stub) */ }
|
|
2178
2355
|
return { w: mw, h: mh };
|
|
@@ -2215,6 +2392,20 @@ export function codeGraphRuntime(root) {
|
|
|
2215
2392
|
zoomBtn("+", function () { state.scale = Math.min(4, state.scale / 0.75); });
|
|
2216
2393
|
zoomBtn("fit", function () { state.scale = fitScale(); });
|
|
2217
2394
|
zoomBtn("1:1", function () { state.scale = 1; });
|
|
2395
|
+
// Entering or leaving fullscreen resizes the pane under a canvas that was
|
|
2396
|
+
// scaled for the old one. Re-derive the SAME kind of scale the view opens
|
|
2397
|
+
// with (cross-axis fit, floored at 2/3 so text stays readable) — not
|
|
2398
|
+
// "fit", which would shrink a tall graph to a stamp exactly when the user
|
|
2399
|
+
// asked for more room. A hand-picked zoom is never overridden.
|
|
2400
|
+
// applyScale/initialScale are per-draw closures over THIS canvas, so
|
|
2401
|
+
// republish the hook on every draw.
|
|
2402
|
+
mount._cgRefit = function () {
|
|
2403
|
+
if (state.scale !== state.autoScale)
|
|
2404
|
+
return;
|
|
2405
|
+
state.autoScale = state.scale = initialScale();
|
|
2406
|
+
applyScale();
|
|
2407
|
+
};
|
|
2408
|
+
fullBtn(bar);
|
|
2218
2409
|
var dirBtn = document.createElement("button");
|
|
2219
2410
|
dirBtn.textContent = LR ? "top-down" : "left-right";
|
|
2220
2411
|
dirBtn.onclick = function () {
|
|
@@ -2340,8 +2531,15 @@ export function codeGraphRuntime(root) {
|
|
|
2340
2531
|
});
|
|
2341
2532
|
}
|
|
2342
2533
|
}
|
|
2343
|
-
function gotoHit(
|
|
2534
|
+
function gotoHit(doc0, id, locate) {
|
|
2344
2535
|
searchMenu.hidden = true;
|
|
2536
|
+
// `doc` is a row from the loaded search index — page data, same as any
|
|
2537
|
+
// other target, so it gets the same relative-path filter.
|
|
2538
|
+
var doc = relOnly(doc0);
|
|
2539
|
+
if (!doc) {
|
|
2540
|
+
flash("refusing to open " + String(doc0) + " — not a document-relative path");
|
|
2541
|
+
return;
|
|
2542
|
+
}
|
|
2345
2543
|
if (live() && !locate) {
|
|
2346
2544
|
showCallees(doc + "#" + id);
|
|
2347
2545
|
return;
|
|
@@ -2411,7 +2609,7 @@ export function codeGraphRuntime(root) {
|
|
|
2411
2609
|
mount.appendChild(bar);
|
|
2412
2610
|
mount.appendChild(stage);
|
|
2413
2611
|
if (state.scale === null)
|
|
2414
|
-
state.scale = initialScale();
|
|
2612
|
+
state.autoScale = state.scale = initialScale();
|
|
2415
2613
|
applyScale();
|
|
2416
2614
|
if (isUp) {
|
|
2417
2615
|
// The focused method sits at the FAR end of the callers chain —
|
package/dist/selector.d.ts
CHANGED
|
@@ -23,6 +23,10 @@ export type Selector = {
|
|
|
23
23
|
type?: string;
|
|
24
24
|
hex: string;
|
|
25
25
|
nth: number;
|
|
26
|
+
} | {
|
|
27
|
+
form: "line";
|
|
28
|
+
from: number;
|
|
29
|
+
to: number;
|
|
26
30
|
} | {
|
|
27
31
|
form: "attr";
|
|
28
32
|
type: string;
|
|
@@ -52,4 +56,7 @@ export declare function matchContent(sel: Extract<Selector, {
|
|
|
52
56
|
form: "content";
|
|
53
57
|
}>, all: Addressed[]): ContentHit;
|
|
54
58
|
export declare function discoveryHint(where: string): string;
|
|
59
|
+
export declare function matchLine(sel: Extract<Selector, {
|
|
60
|
+
form: "line";
|
|
61
|
+
}>, all: Addressed[]): Unit | undefined;
|
|
55
62
|
export declare function matchType(type: string, all: Addressed[]): Unit[];
|