@geml/geml 1.7.1 → 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/dist/block-edit.js +15 -1
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +2433 -0
- package/dist/from-md.js +1 -1
- package/dist/geml.d.ts +32 -0
- package/dist/geml.js +172 -2038
- 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 +180 -15
- package/dist/selector.d.ts +7 -0
- package/dist/selector.js +41 -1
- 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";
|
|
@@ -2003,8 +2129,29 @@ export function codeGraphRuntime(root) {
|
|
|
2003
2129
|
// every such string is filtered here first: keep it if it is a
|
|
2004
2130
|
// document-RELATIVE path, drop it to "" if it carries a scheme
|
|
2005
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.
|
|
2006
2142
|
function relOnly(u) {
|
|
2007
|
-
var
|
|
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 "";
|
|
2008
2155
|
return /^[a-zA-Z][a-zA-Z0-9+.\-]*:/.test(s) || s.slice(0, 2) === "//" ? "" : s;
|
|
2009
2156
|
}
|
|
2010
2157
|
var navBase = relOnly(String(mount.getAttribute("data-src") || data.start || "").replace(/[^\/]*$/, ""));
|
|
@@ -2189,16 +2336,20 @@ export function codeGraphRuntime(root) {
|
|
|
2189
2336
|
stage.className = "cg-stage";
|
|
2190
2337
|
stage.appendChild(scroller);
|
|
2191
2338
|
stage.appendChild(srcPanel);
|
|
2192
|
-
// The scroll pane is capped at
|
|
2193
|
-
// clientHeight is the unconstrained content
|
|
2194
|
-
// from the viewport.
|
|
2195
|
-
//
|
|
2196
|
-
//
|
|
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.
|
|
2197
2346
|
function paneSize() {
|
|
2198
2347
|
var mw = scroller.clientWidth || mount.clientWidth || 0;
|
|
2199
2348
|
var mh = 0;
|
|
2200
2349
|
try {
|
|
2201
|
-
mh =
|
|
2350
|
+
mh = isFull()
|
|
2351
|
+
? Math.max(120, scroller.clientHeight || Math.floor(window.innerHeight * 0.92))
|
|
2352
|
+
: Math.floor(window.innerHeight * 0.84);
|
|
2202
2353
|
}
|
|
2203
2354
|
catch (e) { /* no window (stub) */ }
|
|
2204
2355
|
return { w: mw, h: mh };
|
|
@@ -2241,6 +2392,20 @@ export function codeGraphRuntime(root) {
|
|
|
2241
2392
|
zoomBtn("+", function () { state.scale = Math.min(4, state.scale / 0.75); });
|
|
2242
2393
|
zoomBtn("fit", function () { state.scale = fitScale(); });
|
|
2243
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);
|
|
2244
2409
|
var dirBtn = document.createElement("button");
|
|
2245
2410
|
dirBtn.textContent = LR ? "top-down" : "left-right";
|
|
2246
2411
|
dirBtn.onclick = function () {
|
|
@@ -2444,7 +2609,7 @@ export function codeGraphRuntime(root) {
|
|
|
2444
2609
|
mount.appendChild(bar);
|
|
2445
2610
|
mount.appendChild(stage);
|
|
2446
2611
|
if (state.scale === null)
|
|
2447
|
-
state.scale = initialScale();
|
|
2612
|
+
state.autoScale = state.scale = initialScale();
|
|
2448
2613
|
applyScale();
|
|
2449
2614
|
if (isUp) {
|
|
2450
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[];
|
package/dist/selector.js
CHANGED
|
@@ -20,8 +20,15 @@ import { createHash } from "node:crypto";
|
|
|
20
20
|
export function sha8(text) {
|
|
21
21
|
return createHash("sha256").update(Buffer.from(text, "utf8")).digest("hex").slice(0, 8);
|
|
22
22
|
}
|
|
23
|
-
|
|
23
|
+
// Each optional part carries its OWN trailing whitespace. Written flat —
|
|
24
|
+
// `…[ \t]*(@…)?[ \t]*(\{.*\})?[ \t]*$` — three runs competed for the same tabs
|
|
25
|
+
// and the engine tried every way to divide them: `=== note` plus 8k tabs and
|
|
26
|
+
// one stray byte took 84 SECONDS. Nested, each absent part leaves exactly one
|
|
27
|
+
// run and the match is immediate. Same language: 150k random strings, identical
|
|
28
|
+
// groups. (Mirrors geml.ts's FENCE_OPEN, which had the same shape.)
|
|
29
|
+
const FENCE_SEL = /^={3,}[ \t]*([A-Za-z][A-Za-z0-9_-]*)[ \t]*(?:(@[0-9a-fA-F]{1,}(?:~\d+)?)[ \t]*)?(?:(\{.*\})[ \t]*)?$/;
|
|
24
30
|
const BARE_AT = /^@([0-9a-fA-F]+)(?:~(\d+))?$/;
|
|
31
|
+
const BARE_LINE = /^[Ll](\d+)(?:-(\d+))?$/;
|
|
25
32
|
// Parse selector TEXT. Never touches a document: every form is decided by
|
|
26
33
|
// lexis alone, which is also what keeps the two selector namespaces on
|
|
27
34
|
// `history get <file> <rev> <selector>` from overlapping (history design §10.2).
|
|
@@ -53,6 +60,19 @@ export function parseSelector(raw, attrsIdOf) {
|
|
|
53
60
|
}
|
|
54
61
|
return { form: "type", type };
|
|
55
62
|
}
|
|
63
|
+
// Checked before the id fallthrough, so a bare `L27` is a position. An id
|
|
64
|
+
// that really is spelled `L27` keeps its explicit key form, `#L27` — the
|
|
65
|
+
// same "short form for the common case, key form always available" rule
|
|
66
|
+
// `@<hex>` already relies on.
|
|
67
|
+
const line = s.match(BARE_LINE);
|
|
68
|
+
if (line) {
|
|
69
|
+
const from = Number(line[1]);
|
|
70
|
+
const to = line[2] !== undefined ? Number(line[2]) : from;
|
|
71
|
+
// Reject rather than clamp: `L0` and `L10-5` are typos, and a selector that
|
|
72
|
+
// silently means something else is worse than one that refuses.
|
|
73
|
+
if (from >= 1 && to >= from)
|
|
74
|
+
return { form: "line", from, to };
|
|
75
|
+
}
|
|
56
76
|
// Anything else is an id or a pasted heading line; the caller resolves it.
|
|
57
77
|
return { form: "id", raw: s };
|
|
58
78
|
}
|
|
@@ -104,6 +124,26 @@ export function matchContent(sel, all) {
|
|
|
104
124
|
export function discoveryHint(where) {
|
|
105
125
|
return ` — run \`geml get ${where}\` to list every addressable block`;
|
|
106
126
|
}
|
|
127
|
+
// Match a position selector: the SMALLEST unit that fully contains the range.
|
|
128
|
+
// "Smallest" is what makes this ≤1 match instead of N. Spans nest — a heading's
|
|
129
|
+
// span covers its whole section, so line 30 of the comparison doc sits inside
|
|
130
|
+
// both `#capability-matrix` (L25-65) and `#caps` (L27-58) — and returning both
|
|
131
|
+
// would emit the inner block twice, once alone and once inside its section.
|
|
132
|
+
// The innermost is also the answer the question actually wants: a line number
|
|
133
|
+
// arrived from grep or a stack trace, and the caller means "the thing I have to
|
|
134
|
+
// edit", which is never the enclosing chapter.
|
|
135
|
+
export function matchLine(sel, all) {
|
|
136
|
+
let best;
|
|
137
|
+
for (const a of all) {
|
|
138
|
+
const start = a.unit.span.start + 1; // spans are 0-based; selectors are 1-based, as the listing prints them
|
|
139
|
+
const end = a.unit.span.end;
|
|
140
|
+
if (start > sel.from || end < sel.to)
|
|
141
|
+
continue; // must FULLY contain the range
|
|
142
|
+
if (best === undefined || end - start < best.span.end - (best.span.start + 1))
|
|
143
|
+
best = a.unit;
|
|
144
|
+
}
|
|
145
|
+
return best;
|
|
146
|
+
}
|
|
107
147
|
// Match a type filter: every block of that type in document order. Blocks that
|
|
108
148
|
// carry an id are INCLUDED — the selector says nothing about ids, so filtering
|
|
109
149
|
// by whether one is present would be a rule nobody wrote down (§2).
|