@bendyline/squisq 2.3.2 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{Doc-BrgZC7SE.d.ts → Doc-DLpyOAXJ.d.ts} +87 -2
- package/dist/{ImageEditDoc-rum0Xb9P.d.ts → ImageEditDoc-Cu30xb9b.d.ts} +1 -1
- package/dist/{chunk-2ZWIXGAC.js → chunk-2S74DPJH.js} +4 -0
- package/dist/{chunk-ZQKZSJAX.js → chunk-2VCNTDNZ.js} +245 -0
- package/dist/{chunk-RS5AP3J4.js → chunk-AE3IUKMM.js} +1 -1
- package/dist/{chunk-QWCFK5FN.js → chunk-CUYHFOFL.js} +5 -1
- package/dist/{chunk-ACZOVWX3.js → chunk-D4LPP3P5.js} +44 -15
- package/dist/{chunk-CKZY6K5R.js → chunk-F2IEPSMA.js} +2565 -1143
- package/dist/{chunk-ZAFLMJPD.js → chunk-KPBZZVGY.js} +37 -7
- package/dist/{chunk-BCCXTMN5.js → chunk-O7JILDEF.js} +7 -0
- package/dist/{chunk-SPTY4C6F.js → chunk-REDUXXDJ.js} +1 -1
- package/dist/{chunk-BCHAXKKI.js → chunk-ZHLNKB2I.js} +1 -1
- package/dist/chunk-ZYO3DBTA.js +983 -0
- package/dist/doc/index.d.ts +111 -6
- package/dist/doc/index.js +11 -7
- package/dist/generate/index.d.ts +1 -1
- package/dist/imageEdit/index.d.ts +3 -3
- package/dist/index.d.ts +6 -6
- package/dist/index.js +34 -14
- package/dist/jsonForm/index.d.ts +1 -1
- package/dist/jsonForm/index.js +2 -2
- package/dist/markdown/index.d.ts +118 -1
- package/dist/markdown/index.js +24 -8
- package/dist/{materializePageSection-DAbyLi-k.d.ts → materializePageSection-CgNs5Qmw.d.ts} +4 -2
- package/dist/narration/index.d.ts +1 -1
- package/dist/narration/index.js +5 -5
- package/dist/recommend/index.js +2 -2
- package/dist/schemas/index.d.ts +4 -4
- package/dist/schemas/index.js +2 -2
- package/dist/{themeLibrary-DJt89gyP.d.ts → themeLibrary-RWsNtlOu.d.ts} +1 -1
- package/dist/transform/index.d.ts +2 -2
- package/dist/transform/index.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-3IGPAPQ7.js +0 -645
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
parseAsciiTimeline,
|
|
22
22
|
pickAutoTemplate,
|
|
23
23
|
profileBlockContents
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-CUYHFOFL.js";
|
|
25
25
|
import {
|
|
26
26
|
DEFAULT_THEME,
|
|
27
27
|
FRONTMATTER_CUSTOM_TEMPLATES_KEY,
|
|
@@ -49,14 +49,15 @@ import {
|
|
|
49
49
|
matchTrailingTemplateAnnotation,
|
|
50
50
|
parseNumber,
|
|
51
51
|
parseTimeSeconds,
|
|
52
|
+
sanitizeUrl,
|
|
52
53
|
splitKeyValueToken,
|
|
53
54
|
tokenizeAttrTokens
|
|
54
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-2VCNTDNZ.js";
|
|
55
56
|
import {
|
|
56
57
|
extractPlainText,
|
|
57
58
|
readFrontmatterThemeId,
|
|
58
59
|
walkMarkdownTree
|
|
59
|
-
} from "./chunk-
|
|
60
|
+
} from "./chunk-O7JILDEF.js";
|
|
60
61
|
import {
|
|
61
62
|
resolveBlockTransition
|
|
62
63
|
} from "./chunk-4VOD55SX.js";
|
|
@@ -422,6 +423,16 @@ var TEMPLATE_ALIASES = Object.freeze({
|
|
|
422
423
|
function resolveTemplateName(name) {
|
|
423
424
|
return TEMPLATE_ALIASES[name] ?? name;
|
|
424
425
|
}
|
|
426
|
+
var TABLE_FED_TEMPLATES = /* @__PURE__ */ new Set([
|
|
427
|
+
"dataTable",
|
|
428
|
+
"barChart",
|
|
429
|
+
"columnChart",
|
|
430
|
+
"pieChart",
|
|
431
|
+
"donutChart",
|
|
432
|
+
"lineChart",
|
|
433
|
+
"areaChart",
|
|
434
|
+
"scatterChart"
|
|
435
|
+
]);
|
|
425
436
|
var CONTAINER_TEMPLATES = /* @__PURE__ */ new Set(["diagram", "drawing", "layout"]);
|
|
426
437
|
function isContainerTemplate(name) {
|
|
427
438
|
return !!name && CONTAINER_TEMPLATES.has(resolveTemplateName(name));
|
|
@@ -534,6 +545,8 @@ function titleBlock(input, context) {
|
|
|
534
545
|
const baseBg = backgroundColor || theme.colors.primary;
|
|
535
546
|
const maxWidthPx = parseFloat(layout.maxTextWidth) / 100 * viewport.width;
|
|
536
547
|
const titleH = estimateTextHeight(title, titleFontSize, maxWidthPx, 1.15);
|
|
548
|
+
const titleLineCount = Math.max(1, Math.round(titleH / (titleFontSize * 1.15)));
|
|
549
|
+
const titleSubtitleGapPx = 56 + (titleLineCount > 1 ? titleFontSize * 0.25 : 0);
|
|
537
550
|
const titleYPct = subtitle ? 42 : 48;
|
|
538
551
|
const px = (v) => v / viewport.height * 100;
|
|
539
552
|
const layers = [
|
|
@@ -618,7 +631,7 @@ function titleBlock(input, context) {
|
|
|
618
631
|
},
|
|
619
632
|
position: {
|
|
620
633
|
x: "50%",
|
|
621
|
-
y: `${titleYPct + px(titleH / 2 +
|
|
634
|
+
y: `${titleYPct + px(titleH / 2 + titleSubtitleGapPx + subH / 2)}%`,
|
|
622
635
|
anchor: "center",
|
|
623
636
|
width: layout.maxTextWidth
|
|
624
637
|
},
|
|
@@ -946,7 +959,7 @@ function sectionHeader(input, context) {
|
|
|
946
959
|
|
|
947
960
|
// src/doc/templates/contentBlock.ts
|
|
948
961
|
function completeBodyText(context) {
|
|
949
|
-
return (context.block?.contents ?? []).map((node) => extractPlainText(node)).join("\n").trim();
|
|
962
|
+
return (context.block?.contents ?? []).filter((node) => !(node.type === "code" && node.lang?.trim().toLowerCase() === "mermaid")).map((node) => extractPlainText(node)).join("\n").trim();
|
|
950
963
|
}
|
|
951
964
|
function bodyFontSize(body, context) {
|
|
952
965
|
const base = body.length > 1800 ? 18 : body.length > 1100 ? 21 : body.length > 650 ? 24 : 28;
|
|
@@ -2021,6 +2034,821 @@ function fullBleedQuote(input, context) {
|
|
|
2021
2034
|
];
|
|
2022
2035
|
}
|
|
2023
2036
|
|
|
2037
|
+
// src/doc/templates/diagramLayout.ts
|
|
2038
|
+
var DEFAULT_OPTS = {
|
|
2039
|
+
gapX: 260,
|
|
2040
|
+
gapY: 140,
|
|
2041
|
+
gridPad: 80,
|
|
2042
|
+
gridOriginX: 80,
|
|
2043
|
+
gridOriginY: 80
|
|
2044
|
+
};
|
|
2045
|
+
function computeDiagramLayout(children, options = {}) {
|
|
2046
|
+
const opts = { ...DEFAULT_OPTS, ...options };
|
|
2047
|
+
const warnings = [];
|
|
2048
|
+
const entries = [];
|
|
2049
|
+
const unpositionedIdx = [];
|
|
2050
|
+
for (let i = 0; i < children.length; i++) {
|
|
2051
|
+
const c = children[i];
|
|
2052
|
+
const hasX = typeof c.x === "number";
|
|
2053
|
+
const hasY = typeof c.y === "number";
|
|
2054
|
+
if (hasX && hasY) {
|
|
2055
|
+
entries.push({ child: c, pinned: true, x: c.x, y: c.y });
|
|
2056
|
+
} else {
|
|
2057
|
+
entries.push({ child: c, pinned: false, x: 0, y: 0 });
|
|
2058
|
+
unpositionedIdx.push(i);
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
if (unpositionedIdx.length > 0) {
|
|
2062
|
+
const cols = Math.max(1, Math.ceil(Math.sqrt(unpositionedIdx.length)));
|
|
2063
|
+
let baseX = opts.gridOriginX;
|
|
2064
|
+
let baseY = opts.gridOriginY;
|
|
2065
|
+
const pinned = entries.filter((e) => e.pinned);
|
|
2066
|
+
if (pinned.length > 0) {
|
|
2067
|
+
const minX = Math.min(...pinned.map((e) => e.x));
|
|
2068
|
+
const maxY = Math.max(...pinned.map((e) => e.y));
|
|
2069
|
+
baseX = minX;
|
|
2070
|
+
baseY = maxY + opts.gridPad + opts.gapY;
|
|
2071
|
+
}
|
|
2072
|
+
unpositionedIdx.forEach((entryIdx, k) => {
|
|
2073
|
+
const col = k % cols;
|
|
2074
|
+
const row = Math.floor(k / cols);
|
|
2075
|
+
entries[entryIdx].x = baseX + col * opts.gapX;
|
|
2076
|
+
entries[entryIdx].y = baseY + row * opts.gapY;
|
|
2077
|
+
});
|
|
2078
|
+
}
|
|
2079
|
+
const nodes = entries.map((e) => ({
|
|
2080
|
+
id: e.child.id,
|
|
2081
|
+
label: e.child.title ?? e.child.id,
|
|
2082
|
+
x: e.x,
|
|
2083
|
+
y: e.y,
|
|
2084
|
+
pinned: e.pinned,
|
|
2085
|
+
...e.child.classes && e.child.classes.length > 0 ? { classes: e.child.classes } : {}
|
|
2086
|
+
}));
|
|
2087
|
+
const nodeIds = new Set(nodes.map((n) => n.id));
|
|
2088
|
+
const edges = [];
|
|
2089
|
+
const seenEdgeIds = /* @__PURE__ */ new Set();
|
|
2090
|
+
for (const child of children) {
|
|
2091
|
+
if (!child.connectsTo) continue;
|
|
2092
|
+
for (const conn of child.connectsTo) {
|
|
2093
|
+
if (!nodeIds.has(conn.target)) {
|
|
2094
|
+
warnings.push(`Edge from "${child.id}" to "${conn.target}" dropped: no such sibling node`);
|
|
2095
|
+
continue;
|
|
2096
|
+
}
|
|
2097
|
+
const edgeId = makeEdgeId(child.id, conn);
|
|
2098
|
+
if (seenEdgeIds.has(edgeId)) continue;
|
|
2099
|
+
seenEdgeIds.add(edgeId);
|
|
2100
|
+
edges.push({
|
|
2101
|
+
id: edgeId,
|
|
2102
|
+
source: child.id,
|
|
2103
|
+
target: conn.target,
|
|
2104
|
+
...conn.type ? { type: conn.type } : {}
|
|
2105
|
+
});
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
return { nodes, edges, warnings };
|
|
2109
|
+
}
|
|
2110
|
+
function makeEdgeId(source, conn) {
|
|
2111
|
+
return conn.type ? `${source}->${conn.target}:${conn.type}` : `${source}->${conn.target}`;
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
// src/doc/asciiDiagram/types.ts
|
|
2115
|
+
var ASCII_CHAR_W = 14;
|
|
2116
|
+
var ASCII_CHAR_H = 28;
|
|
2117
|
+
|
|
2118
|
+
// src/doc/asciiDiagram/mapping.ts
|
|
2119
|
+
function asciiCellToCanvas(col, row) {
|
|
2120
|
+
return { x: col * ASCII_CHAR_W, y: row * ASCII_CHAR_H };
|
|
2121
|
+
}
|
|
2122
|
+
function canvasToAsciiCell(x, y) {
|
|
2123
|
+
return {
|
|
2124
|
+
col: Math.max(0, Math.round(x / ASCII_CHAR_W)),
|
|
2125
|
+
row: Math.max(0, Math.round(y / ASCII_CHAR_H))
|
|
2126
|
+
};
|
|
2127
|
+
}
|
|
2128
|
+
function asciiDiagramToTemplateData(diagram) {
|
|
2129
|
+
const nodes = diagram.nodes.map((n) => ({
|
|
2130
|
+
id: n.id,
|
|
2131
|
+
label: n.label,
|
|
2132
|
+
...asciiCellToCanvas(n.col, n.row),
|
|
2133
|
+
w: n.wCols * ASCII_CHAR_W,
|
|
2134
|
+
h: n.hRows * ASCII_CHAR_H,
|
|
2135
|
+
...n.containerId ? { container: n.containerId } : {}
|
|
2136
|
+
}));
|
|
2137
|
+
const anchors = inferEdgeAnchors(nodes, diagram.edges);
|
|
2138
|
+
const edges = diagram.edges.map((e, index) => ({
|
|
2139
|
+
source: e.source,
|
|
2140
|
+
target: e.target,
|
|
2141
|
+
...e.label ? { label: e.label } : {},
|
|
2142
|
+
...e.directed ? {} : { directed: false },
|
|
2143
|
+
...anchors[index],
|
|
2144
|
+
routing: "orthogonal"
|
|
2145
|
+
}));
|
|
2146
|
+
return { nodes, edges };
|
|
2147
|
+
}
|
|
2148
|
+
function inferEdgeAnchors(nodes, edges) {
|
|
2149
|
+
const nodeById = new Map(nodes.map((node) => [node.id, node]));
|
|
2150
|
+
const pairs = edges.map((edge) => {
|
|
2151
|
+
const source = nodeById.get(edge.source);
|
|
2152
|
+
const target = nodeById.get(edge.target);
|
|
2153
|
+
if (!source || !target) {
|
|
2154
|
+
return {
|
|
2155
|
+
sourceAnchor: { side: "right", offset: 0.5 },
|
|
2156
|
+
targetAnchor: { side: "left", offset: 0.5 }
|
|
2157
|
+
};
|
|
2158
|
+
}
|
|
2159
|
+
const sourceW = source.w ?? ASCII_CHAR_W * 12;
|
|
2160
|
+
const sourceH = source.h ?? ASCII_CHAR_H * 3;
|
|
2161
|
+
const targetW = target.w ?? ASCII_CHAR_W * 12;
|
|
2162
|
+
const targetH = target.h ?? ASCII_CHAR_H * 3;
|
|
2163
|
+
const dx = target.x + targetW / 2 - (source.x + sourceW / 2);
|
|
2164
|
+
const dy = target.y + targetH / 2 - (source.y + sourceH / 2);
|
|
2165
|
+
const horizontalDistance = Math.abs(dx) / Math.max(1, (sourceW + targetW) / 2);
|
|
2166
|
+
const verticalDistance = Math.abs(dy) / Math.max(1, (sourceH + targetH) / 2);
|
|
2167
|
+
if (verticalDistance >= horizontalDistance) {
|
|
2168
|
+
return dy >= 0 ? {
|
|
2169
|
+
sourceAnchor: { side: "bottom", offset: 0.5 },
|
|
2170
|
+
targetAnchor: { side: "top", offset: 0.5 }
|
|
2171
|
+
} : {
|
|
2172
|
+
sourceAnchor: { side: "top", offset: 0.5 },
|
|
2173
|
+
targetAnchor: { side: "bottom", offset: 0.5 }
|
|
2174
|
+
};
|
|
2175
|
+
}
|
|
2176
|
+
return dx >= 0 ? {
|
|
2177
|
+
sourceAnchor: { side: "right", offset: 0.5 },
|
|
2178
|
+
targetAnchor: { side: "left", offset: 0.5 }
|
|
2179
|
+
} : {
|
|
2180
|
+
sourceAnchor: { side: "left", offset: 0.5 },
|
|
2181
|
+
targetAnchor: { side: "right", offset: 0.5 }
|
|
2182
|
+
};
|
|
2183
|
+
});
|
|
2184
|
+
distributeOffsets(pairs, edges, nodes, "source");
|
|
2185
|
+
distributeOffsets(pairs, edges, nodes, "target");
|
|
2186
|
+
coalesceAlignedFanOutAnchors(pairs, edges, nodes);
|
|
2187
|
+
return pairs;
|
|
2188
|
+
}
|
|
2189
|
+
function coalesceAlignedFanOutAnchors(pairs, edges, nodes) {
|
|
2190
|
+
const nodeById = new Map(nodes.map((node) => [node.id, node]));
|
|
2191
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2192
|
+
edges.forEach((edge, index) => {
|
|
2193
|
+
const group = groups.get(edge.source) ?? [];
|
|
2194
|
+
group.push(index);
|
|
2195
|
+
groups.set(edge.source, group);
|
|
2196
|
+
});
|
|
2197
|
+
for (const [sourceId, indexes] of groups) {
|
|
2198
|
+
if (indexes.length < 2) continue;
|
|
2199
|
+
const source = nodeById.get(sourceId);
|
|
2200
|
+
const targets = indexes.map((index) => nodeById.get(edges[index].target)).filter((node) => node !== void 0);
|
|
2201
|
+
if (!source || targets.length !== indexes.length) continue;
|
|
2202
|
+
const sourceW = source.w ?? ASCII_CHAR_W * 12;
|
|
2203
|
+
const sourceH = source.h ?? ASCII_CHAR_H * 3;
|
|
2204
|
+
const sourceRight = source.x + sourceW;
|
|
2205
|
+
const sourceBottom = source.y + sourceH;
|
|
2206
|
+
const targetRight = (target) => target.x + (target.w ?? ASCII_CHAR_W * 12);
|
|
2207
|
+
const targetBottom = (target) => target.y + (target.h ?? ASCII_CHAR_H * 3);
|
|
2208
|
+
let sourceSide;
|
|
2209
|
+
let targetSide;
|
|
2210
|
+
if (targets.every((target) => target.y >= sourceBottom)) {
|
|
2211
|
+
sourceSide = "bottom";
|
|
2212
|
+
targetSide = "top";
|
|
2213
|
+
} else if (targets.every((target) => targetBottom(target) <= source.y)) {
|
|
2214
|
+
sourceSide = "top";
|
|
2215
|
+
targetSide = "bottom";
|
|
2216
|
+
} else if (targets.every((target) => target.x >= sourceRight)) {
|
|
2217
|
+
sourceSide = "right";
|
|
2218
|
+
targetSide = "left";
|
|
2219
|
+
} else if (targets.every((target) => targetRight(target) <= source.x)) {
|
|
2220
|
+
sourceSide = "left";
|
|
2221
|
+
targetSide = "right";
|
|
2222
|
+
}
|
|
2223
|
+
if (!sourceSide || !targetSide) continue;
|
|
2224
|
+
for (const index of indexes) {
|
|
2225
|
+
pairs[index] = {
|
|
2226
|
+
sourceAnchor: { side: sourceSide, offset: 0.5 },
|
|
2227
|
+
targetAnchor: { side: targetSide, offset: 0.5 }
|
|
2228
|
+
};
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
function distributeOffsets(pairs, edges, nodes, endpoint) {
|
|
2233
|
+
const nodeById = new Map(nodes.map((node) => [node.id, node]));
|
|
2234
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2235
|
+
for (let index = 0; index < edges.length; index++) {
|
|
2236
|
+
const edge = edges[index];
|
|
2237
|
+
const anchor = endpoint === "source" ? pairs[index].sourceAnchor : pairs[index].targetAnchor;
|
|
2238
|
+
const nodeId = endpoint === "source" ? edge.source : edge.target;
|
|
2239
|
+
const key = `${nodeId}:${anchor.side}`;
|
|
2240
|
+
const group = groups.get(key) ?? [];
|
|
2241
|
+
group.push(index);
|
|
2242
|
+
groups.set(key, group);
|
|
2243
|
+
}
|
|
2244
|
+
for (const indexes of groups.values()) {
|
|
2245
|
+
if (indexes.length < 2) continue;
|
|
2246
|
+
const side = endpoint === "source" ? pairs[indexes[0]].sourceAnchor.side : pairs[indexes[0]].targetAnchor.side;
|
|
2247
|
+
indexes.sort((a, b) => {
|
|
2248
|
+
const oppositeA = nodeById.get(endpoint === "source" ? edges[a].target : edges[a].source);
|
|
2249
|
+
const oppositeB = nodeById.get(endpoint === "source" ? edges[b].target : edges[b].source);
|
|
2250
|
+
const axisA = side === "top" || side === "bottom" ? oppositeA?.x ?? 0 : oppositeA?.y ?? 0;
|
|
2251
|
+
const axisB = side === "top" || side === "bottom" ? oppositeB?.x ?? 0 : oppositeB?.y ?? 0;
|
|
2252
|
+
return axisA - axisB || a - b;
|
|
2253
|
+
});
|
|
2254
|
+
indexes.forEach((edgeIndex, rank) => {
|
|
2255
|
+
const anchor = endpoint === "source" ? pairs[edgeIndex].sourceAnchor : pairs[edgeIndex].targetAnchor;
|
|
2256
|
+
anchor.offset = (rank + 1) / (indexes.length + 1);
|
|
2257
|
+
});
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
function asciiDiagramFromTemplateData(nodes, edges, options = {}) {
|
|
2261
|
+
const ids = new Set(nodes.map((n) => n.id));
|
|
2262
|
+
const outNodes = nodes.map((n) => {
|
|
2263
|
+
const { col, row } = canvasToAsciiCell(n.x, n.y);
|
|
2264
|
+
const labelLines = n.label.length > 0 ? n.label.split("\n") : [];
|
|
2265
|
+
const innerW = labelLines.reduce((w, l) => Math.max(w, l.length), 0);
|
|
2266
|
+
const wCols = typeof n.w === "number" ? Math.max(3, Math.round(n.w / ASCII_CHAR_W)) : innerW + 4;
|
|
2267
|
+
const hRows = typeof n.h === "number" ? Math.max(3, Math.round(n.h / ASCII_CHAR_H)) : labelLines.length + 2;
|
|
2268
|
+
return {
|
|
2269
|
+
id: n.id,
|
|
2270
|
+
label: n.label,
|
|
2271
|
+
col,
|
|
2272
|
+
row,
|
|
2273
|
+
wCols: Math.max(wCols, 3),
|
|
2274
|
+
hRows: Math.max(hRows, 3),
|
|
2275
|
+
...n.container && ids.has(n.container) && n.container !== n.id ? { containerId: n.container } : {}
|
|
2276
|
+
};
|
|
2277
|
+
});
|
|
2278
|
+
return {
|
|
2279
|
+
nodes: outNodes,
|
|
2280
|
+
edges: edges.filter((e) => ids.has(e.source) && ids.has(e.target)).map((e) => ({
|
|
2281
|
+
source: e.source,
|
|
2282
|
+
target: e.target,
|
|
2283
|
+
...e.label ? { label: e.label } : {},
|
|
2284
|
+
directed: e.directed !== false
|
|
2285
|
+
})),
|
|
2286
|
+
style: options.style ?? "unicode",
|
|
2287
|
+
warnings: []
|
|
2288
|
+
};
|
|
2289
|
+
}
|
|
2290
|
+
function asciiDiagramFromBlocks(children, options = {}) {
|
|
2291
|
+
const layout = computeDiagramLayout(children);
|
|
2292
|
+
const nodes = layout.nodes.map((n) => ({
|
|
2293
|
+
id: n.id,
|
|
2294
|
+
label: n.label,
|
|
2295
|
+
x: n.x,
|
|
2296
|
+
y: n.y
|
|
2297
|
+
}));
|
|
2298
|
+
const edges = layout.edges.map((e) => ({
|
|
2299
|
+
source: e.source,
|
|
2300
|
+
target: e.target,
|
|
2301
|
+
...e.type ? { label: e.type } : {}
|
|
2302
|
+
}));
|
|
2303
|
+
const diagram = asciiDiagramFromTemplateData(nodes, edges, options);
|
|
2304
|
+
diagram.warnings.push(...layout.warnings);
|
|
2305
|
+
return diagram;
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
// src/doc/asciiTimeline/mapping.ts
|
|
2309
|
+
function asciiTimelineToTemplateData(timeline) {
|
|
2310
|
+
const globalStart = timeline.tracks.length > 0 ? Math.min(...timeline.tracks.map((track) => track.startColumn)) : 0;
|
|
2311
|
+
const globalEnd = timeline.tracks.length > 0 ? Math.max(...timeline.tracks.map((track) => track.endColumn)) : 1;
|
|
2312
|
+
const span = Math.max(1, globalEnd - globalStart);
|
|
2313
|
+
const tracks = timeline.tracks.map((track) => ({
|
|
2314
|
+
id: track.id,
|
|
2315
|
+
...track.label ? { label: track.label } : {},
|
|
2316
|
+
...track.endLabel ? { endLabel: track.endLabel } : {},
|
|
2317
|
+
events: track.events.map(
|
|
2318
|
+
(event) => ({
|
|
2319
|
+
id: event.id,
|
|
2320
|
+
label: event.label,
|
|
2321
|
+
...event.description ? { description: event.description } : {},
|
|
2322
|
+
position: clamp01((event.column - globalStart) / span),
|
|
2323
|
+
...event.side ? { side: event.side } : {},
|
|
2324
|
+
...event.descriptionSide ? { descriptionSide: event.descriptionSide } : {},
|
|
2325
|
+
...event.callout === false ? { callout: false } : {},
|
|
2326
|
+
...event.marker ? { marker: event.marker } : {}
|
|
2327
|
+
})
|
|
2328
|
+
)
|
|
2329
|
+
}));
|
|
2330
|
+
const links = timeline.links.map((link) => ({ ...link }));
|
|
2331
|
+
return { tracks, links };
|
|
2332
|
+
}
|
|
2333
|
+
function clamp01(value) {
|
|
2334
|
+
return Math.max(0, Math.min(1, value));
|
|
2335
|
+
}
|
|
2336
|
+
|
|
2337
|
+
// src/doc/treeview/mapping.ts
|
|
2338
|
+
function treeToTemplateData(tree) {
|
|
2339
|
+
return { items: tree.roots.map(toItem) };
|
|
2340
|
+
}
|
|
2341
|
+
function toItem(node) {
|
|
2342
|
+
return {
|
|
2343
|
+
id: node.id,
|
|
2344
|
+
label: node.label,
|
|
2345
|
+
...node.isDir ? { isDir: true } : {},
|
|
2346
|
+
...node.comment ? { comment: node.comment } : {},
|
|
2347
|
+
children: node.children.map(toItem)
|
|
2348
|
+
};
|
|
2349
|
+
}
|
|
2350
|
+
function treeFromTemplateData(items, options = {}) {
|
|
2351
|
+
const roots = items.map(fromItem);
|
|
2352
|
+
return { roots, style: options.style ?? "unicode", warnings: [] };
|
|
2353
|
+
}
|
|
2354
|
+
function fromItem(item) {
|
|
2355
|
+
const children = Array.isArray(item.children) ? item.children.map(fromItem) : [];
|
|
2356
|
+
return {
|
|
2357
|
+
id: typeof item.id === "string" && item.id ? item.id : "node",
|
|
2358
|
+
label: typeof item.label === "string" ? item.label : "",
|
|
2359
|
+
children,
|
|
2360
|
+
isDir: item.isDir === true || children.length > 0,
|
|
2361
|
+
...item.comment ? { comment: item.comment } : {}
|
|
2362
|
+
};
|
|
2363
|
+
}
|
|
2364
|
+
function treeFromMarkdownList(list) {
|
|
2365
|
+
const usedIds = /* @__PURE__ */ new Map();
|
|
2366
|
+
const slugify2 = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "node";
|
|
2367
|
+
const idFor = (label) => {
|
|
2368
|
+
const base = slugify2(label.replace(/\/$/, ""));
|
|
2369
|
+
const count = usedIds.get(base) ?? 0;
|
|
2370
|
+
usedIds.set(base, count + 1);
|
|
2371
|
+
return count === 0 ? base : `${base}-${count + 1}`;
|
|
2372
|
+
};
|
|
2373
|
+
const itemToNode = (item) => {
|
|
2374
|
+
const labelNode = item.children.find((c) => c.type !== "list");
|
|
2375
|
+
const label = labelNode ? extractPlainText(labelNode).trim() : "";
|
|
2376
|
+
const childLists = item.children.filter((c) => c.type === "list");
|
|
2377
|
+
const children = [];
|
|
2378
|
+
for (const cl of childLists) for (const ci of cl.children) children.push(itemToNode(ci));
|
|
2379
|
+
return {
|
|
2380
|
+
id: idFor(label),
|
|
2381
|
+
label,
|
|
2382
|
+
children,
|
|
2383
|
+
isDir: label.endsWith("/") || children.length > 0
|
|
2384
|
+
};
|
|
2385
|
+
};
|
|
2386
|
+
return { roots: list.children.map(itemToNode), style: "unicode", warnings: [] };
|
|
2387
|
+
}
|
|
2388
|
+
function findFirstList(contents) {
|
|
2389
|
+
return contents?.find((n) => n.type === "list");
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
// src/doc/templateInputs.ts
|
|
2393
|
+
var VIDEO_FILE_RE = /\.(?:webm|mp4|mov|m4v|ogv)(?:[?#].*)?$/i;
|
|
2394
|
+
function extractBodyPlainText(contents) {
|
|
2395
|
+
if (!contents || contents.length === 0) return "";
|
|
2396
|
+
return contents.filter((node) => !(node.type === "code" && node.lang?.trim().toLowerCase() === "mermaid")).map((n) => extractPlainText(n)).join("\n").trim();
|
|
2397
|
+
}
|
|
2398
|
+
function extractListItems(contents) {
|
|
2399
|
+
return extractRichListItems(contents).map((item) => item.text);
|
|
2400
|
+
}
|
|
2401
|
+
function escapeInlineHtml(value) {
|
|
2402
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2403
|
+
}
|
|
2404
|
+
function renderInlineHtml(nodes) {
|
|
2405
|
+
return nodes.map((node) => {
|
|
2406
|
+
switch (node.type) {
|
|
2407
|
+
case "text":
|
|
2408
|
+
return escapeInlineHtml(node.value);
|
|
2409
|
+
case "emphasis":
|
|
2410
|
+
return `<em>${renderInlineHtml(node.children)}</em>`;
|
|
2411
|
+
case "strong":
|
|
2412
|
+
return `<strong>${renderInlineHtml(node.children)}</strong>`;
|
|
2413
|
+
case "delete":
|
|
2414
|
+
return `<del>${renderInlineHtml(node.children)}</del>`;
|
|
2415
|
+
case "inlineCode":
|
|
2416
|
+
return `<code>${escapeInlineHtml(node.value)}</code>`;
|
|
2417
|
+
case "link": {
|
|
2418
|
+
const label = renderInlineHtml(node.children);
|
|
2419
|
+
const href = sanitizeUrl(node.url, "link");
|
|
2420
|
+
if (!href) return label;
|
|
2421
|
+
const title = node.title ? ` title="${escapeInlineHtml(node.title)}"` : "";
|
|
2422
|
+
return `<a href="${escapeInlineHtml(href)}"${title}>${label}</a>`;
|
|
2423
|
+
}
|
|
2424
|
+
case "break":
|
|
2425
|
+
return "<br>";
|
|
2426
|
+
case "image":
|
|
2427
|
+
return escapeInlineHtml(node.alt ?? "");
|
|
2428
|
+
case "inlineMath":
|
|
2429
|
+
return escapeInlineHtml(`$${node.value}$`);
|
|
2430
|
+
case "footnoteReference":
|
|
2431
|
+
return escapeInlineHtml(`[^${node.label ?? node.identifier}]`);
|
|
2432
|
+
case "linkReference":
|
|
2433
|
+
case "textDirective":
|
|
2434
|
+
return renderInlineHtml(node.children);
|
|
2435
|
+
case "imageReference":
|
|
2436
|
+
return escapeInlineHtml(node.alt ?? "");
|
|
2437
|
+
case "mention":
|
|
2438
|
+
return escapeInlineHtml(`@${node.displayName}`);
|
|
2439
|
+
case "inlineIcon":
|
|
2440
|
+
return escapeInlineHtml(`{[${node.token}]}`);
|
|
2441
|
+
case "htmlInline":
|
|
2442
|
+
return escapeInlineHtml(node.rawHtml);
|
|
2443
|
+
}
|
|
2444
|
+
}).join("");
|
|
2445
|
+
}
|
|
2446
|
+
function renderListItemHtml(markdown) {
|
|
2447
|
+
return markdown.map(
|
|
2448
|
+
(node) => node.type === "paragraph" || node.type === "heading" ? renderInlineHtml(node.children) : escapeInlineHtml(extractPlainText(node))
|
|
2449
|
+
).join("<br>");
|
|
2450
|
+
}
|
|
2451
|
+
function extractRichListItems(contents) {
|
|
2452
|
+
if (!contents) return [];
|
|
2453
|
+
const items = [];
|
|
2454
|
+
for (const node of contents) {
|
|
2455
|
+
if (node.type !== "list") continue;
|
|
2456
|
+
for (const item of node.children) {
|
|
2457
|
+
const text = extractPlainText(item).trim();
|
|
2458
|
+
if (!text) continue;
|
|
2459
|
+
const html = renderListItemHtml(item.children);
|
|
2460
|
+
const plainHtml = escapeInlineHtml(text).replace(/\r?\n/g, "<br>");
|
|
2461
|
+
items.push({
|
|
2462
|
+
text,
|
|
2463
|
+
markdown: item.children,
|
|
2464
|
+
...html && html !== plainHtml ? { html } : {}
|
|
2465
|
+
});
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
return items;
|
|
2469
|
+
}
|
|
2470
|
+
function parseDim(raw) {
|
|
2471
|
+
if (raw === void 0) return void 0;
|
|
2472
|
+
const n = parseFloat(raw);
|
|
2473
|
+
return Number.isFinite(n) && n > 0 ? n : void 0;
|
|
2474
|
+
}
|
|
2475
|
+
function extractImages(contents, limit = Infinity) {
|
|
2476
|
+
if (!contents || contents.length === 0) return [];
|
|
2477
|
+
const found = [];
|
|
2478
|
+
function fromHtml(nodes) {
|
|
2479
|
+
for (const node of nodes) {
|
|
2480
|
+
if (found.length >= limit) return;
|
|
2481
|
+
if (!node || typeof node !== "object") continue;
|
|
2482
|
+
const n = node;
|
|
2483
|
+
if (n.type === "htmlElement" && n.tagName === "img") {
|
|
2484
|
+
const attrs = n.attributes;
|
|
2485
|
+
if (attrs && typeof attrs.src === "string" && attrs.src) {
|
|
2486
|
+
found.push({
|
|
2487
|
+
src: attrs.src,
|
|
2488
|
+
alt: typeof attrs.alt === "string" ? attrs.alt : "",
|
|
2489
|
+
width: parseDim(attrs.width),
|
|
2490
|
+
height: parseDim(attrs.height)
|
|
2491
|
+
});
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
if (Array.isArray(n.children)) fromHtml(n.children);
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
function walk(node) {
|
|
2498
|
+
if (found.length >= limit) return;
|
|
2499
|
+
if (!node || typeof node !== "object") return;
|
|
2500
|
+
const n = node;
|
|
2501
|
+
if (n.type === "image" && typeof n.url === "string" && n.url) {
|
|
2502
|
+
found.push({ src: n.url, alt: typeof n.alt === "string" ? n.alt : "" });
|
|
2503
|
+
return;
|
|
2504
|
+
}
|
|
2505
|
+
if ((n.type === "htmlBlock" || n.type === "htmlInline") && Array.isArray(n.htmlChildren)) {
|
|
2506
|
+
fromHtml(n.htmlChildren);
|
|
2507
|
+
}
|
|
2508
|
+
if (Array.isArray(n.children)) {
|
|
2509
|
+
for (const child of n.children) walk(child);
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
for (const node of contents) {
|
|
2513
|
+
if (found.length >= limit) break;
|
|
2514
|
+
walk(node);
|
|
2515
|
+
}
|
|
2516
|
+
return found;
|
|
2517
|
+
}
|
|
2518
|
+
function extractFirstImage(contents) {
|
|
2519
|
+
return extractImages(contents, 1)[0] ?? null;
|
|
2520
|
+
}
|
|
2521
|
+
function extractEmbeddedVideos(contents, limit = Infinity) {
|
|
2522
|
+
if (!contents || contents.length === 0) return [];
|
|
2523
|
+
const found = [];
|
|
2524
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2525
|
+
const add = (video) => {
|
|
2526
|
+
if (!video.src || found.length >= limit || seen.has(video.src)) return;
|
|
2527
|
+
seen.add(video.src);
|
|
2528
|
+
found.push(video);
|
|
2529
|
+
};
|
|
2530
|
+
function nestedSource(children) {
|
|
2531
|
+
if (!Array.isArray(children)) return void 0;
|
|
2532
|
+
for (const child of children) {
|
|
2533
|
+
if (!child || typeof child !== "object") continue;
|
|
2534
|
+
const node = child;
|
|
2535
|
+
if (node.tagName === "source") {
|
|
2536
|
+
const attrs = node.attributes;
|
|
2537
|
+
if (typeof attrs?.src === "string" && attrs.src) return attrs.src;
|
|
2538
|
+
}
|
|
2539
|
+
const nested = nestedSource(node.children);
|
|
2540
|
+
if (nested) return nested;
|
|
2541
|
+
}
|
|
2542
|
+
return void 0;
|
|
2543
|
+
}
|
|
2544
|
+
function walk(node) {
|
|
2545
|
+
if (found.length >= limit || !node || typeof node !== "object") return;
|
|
2546
|
+
const n = node;
|
|
2547
|
+
if (n.type === "htmlElement" && n.tagName === "video") {
|
|
2548
|
+
const attrs = n.attributes;
|
|
2549
|
+
const src = attrs?.src || nestedSource(n.children);
|
|
2550
|
+
if (src) {
|
|
2551
|
+
add({
|
|
2552
|
+
src,
|
|
2553
|
+
...attrs?.poster ? { posterSrc: attrs.poster } : {},
|
|
2554
|
+
alt: attrs?.["aria-label"] || attrs?.title || attrs?.alt || ""
|
|
2555
|
+
});
|
|
2556
|
+
}
|
|
2557
|
+
} else if ((n.type === "link" || n.type === "image") && typeof n.url === "string" && VIDEO_FILE_RE.test(n.url)) {
|
|
2558
|
+
add({
|
|
2559
|
+
src: n.url,
|
|
2560
|
+
alt: typeof n.alt === "string" ? n.alt : extractPlainText(n).trim()
|
|
2561
|
+
});
|
|
2562
|
+
}
|
|
2563
|
+
if (Array.isArray(n.children)) n.children.forEach(walk);
|
|
2564
|
+
if (Array.isArray(n.htmlChildren)) n.htmlChildren.forEach(walk);
|
|
2565
|
+
}
|
|
2566
|
+
contents.forEach(walk);
|
|
2567
|
+
return found;
|
|
2568
|
+
}
|
|
2569
|
+
function extractFirstEmbeddedVideo(contents) {
|
|
2570
|
+
return extractEmbeddedVideos(contents, 1)[0] ?? null;
|
|
2571
|
+
}
|
|
2572
|
+
function extractTableFromContents(contents) {
|
|
2573
|
+
if (!contents) return null;
|
|
2574
|
+
for (const node of contents) {
|
|
2575
|
+
if (node.type === "table") {
|
|
2576
|
+
const table = node;
|
|
2577
|
+
const [headerRow, ...bodyRows] = table.children;
|
|
2578
|
+
if (!headerRow) return null;
|
|
2579
|
+
const headers = headerRow.children.map((cell) => extractPlainText(cell).trim());
|
|
2580
|
+
const rows = bodyRows.map((row) => row.children.map((cell) => extractPlainText(cell).trim()));
|
|
2581
|
+
return { headers, rows, align: table.align };
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
return null;
|
|
2585
|
+
}
|
|
2586
|
+
function extractBlockquoteText(contents) {
|
|
2587
|
+
if (!contents) return "";
|
|
2588
|
+
for (const node of contents) {
|
|
2589
|
+
if (node.type === "blockquote") return extractPlainText(node).trim();
|
|
2590
|
+
}
|
|
2591
|
+
return "";
|
|
2592
|
+
}
|
|
2593
|
+
function firstParagraph(contents) {
|
|
2594
|
+
if (!contents) return null;
|
|
2595
|
+
const index = contents.findIndex((node) => node.type === "paragraph");
|
|
2596
|
+
if (index < 0) return null;
|
|
2597
|
+
return {
|
|
2598
|
+
node: contents[index],
|
|
2599
|
+
index
|
|
2600
|
+
};
|
|
2601
|
+
}
|
|
2602
|
+
function leadingStrongStat(paragraph) {
|
|
2603
|
+
for (const child of paragraph.children) {
|
|
2604
|
+
if (child.type === "text" && child.value.trim() === "") continue;
|
|
2605
|
+
if (child.type !== "strong") return null;
|
|
2606
|
+
const text = extractPlainText(child).trim();
|
|
2607
|
+
const match = text.match(/^(?:[$€£¥]\s*)?[+-]?\d+(?:[.,]\d+)*(?:\s?(?:[%‰x×]|[A-Za-z]+))?/);
|
|
2608
|
+
return match?.[0].trim() || null;
|
|
2609
|
+
}
|
|
2610
|
+
return null;
|
|
2611
|
+
}
|
|
2612
|
+
function removeStatPhrase(text, start, end) {
|
|
2613
|
+
const before = text.slice(0, start).trim();
|
|
2614
|
+
let after = text.slice(end);
|
|
2615
|
+
if (!before) after = after.replace(/^\s*(?:[-–—:]\s*)?/, "");
|
|
2616
|
+
else after = after.trim();
|
|
2617
|
+
return [before, after].filter(Boolean).join(" ").trim();
|
|
2618
|
+
}
|
|
2619
|
+
function bodyWithoutParagraphStat(contents, paragraphIndex, paragraphText, start, end) {
|
|
2620
|
+
if (!contents) return "";
|
|
2621
|
+
return contents.map(
|
|
2622
|
+
(node, index) => index === paragraphIndex ? removeStatPhrase(paragraphText, start, end) : extractPlainText(node).trim()
|
|
2623
|
+
).filter(Boolean).join("\n").trim();
|
|
2624
|
+
}
|
|
2625
|
+
function deriveStatHighlightInputs(headingText, contents, bodyText3) {
|
|
2626
|
+
const paragraph = firstParagraph(contents);
|
|
2627
|
+
if (paragraph) {
|
|
2628
|
+
const paragraphText = extractPlainText(paragraph.node).trim();
|
|
2629
|
+
const strongStat = leadingStrongStat(paragraph.node);
|
|
2630
|
+
if (strongStat) {
|
|
2631
|
+
const start = paragraphText.indexOf(strongStat);
|
|
2632
|
+
const description = bodyWithoutParagraphStat(
|
|
2633
|
+
contents,
|
|
2634
|
+
paragraph.index,
|
|
2635
|
+
paragraphText,
|
|
2636
|
+
start,
|
|
2637
|
+
start + strongStat.length
|
|
2638
|
+
);
|
|
2639
|
+
return {
|
|
2640
|
+
stat: strongStat,
|
|
2641
|
+
description: description || headingText || strongStat
|
|
2642
|
+
};
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
const trimmedHeading = headingText.trim();
|
|
2646
|
+
const headingStat = matchNumberHighlight(trimmedHeading);
|
|
2647
|
+
if (headingStat && headingStat.index === 0 && headingStat.end === trimmedHeading.length) {
|
|
2648
|
+
return { stat: headingText, description: bodyText3 || headingText };
|
|
2649
|
+
}
|
|
2650
|
+
if (paragraph) {
|
|
2651
|
+
const paragraphText = extractPlainText(paragraph.node).trim();
|
|
2652
|
+
const bodyStat = matchNumberHighlight(paragraphText);
|
|
2653
|
+
if (bodyStat) {
|
|
2654
|
+
const description = bodyWithoutParagraphStat(
|
|
2655
|
+
contents,
|
|
2656
|
+
paragraph.index,
|
|
2657
|
+
paragraphText,
|
|
2658
|
+
bodyStat.index,
|
|
2659
|
+
bodyStat.end
|
|
2660
|
+
);
|
|
2661
|
+
return {
|
|
2662
|
+
stat: bodyStat.value,
|
|
2663
|
+
description: description || headingText || bodyStat.value
|
|
2664
|
+
};
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
return { stat: headingText, description: bodyText3 || headingText };
|
|
2668
|
+
}
|
|
2669
|
+
function normalizeCoverageText(value) {
|
|
2670
|
+
return value.replace(/\s+/g, " ").trim();
|
|
2671
|
+
}
|
|
2672
|
+
function autoTemplatePreservesContent(templateName, contents) {
|
|
2673
|
+
const nodes = contents ?? [];
|
|
2674
|
+
switch (templateName) {
|
|
2675
|
+
case "quote":
|
|
2676
|
+
return nodes.length === 1 && nodes[0]?.type === "blockquote";
|
|
2677
|
+
case "dataTable":
|
|
2678
|
+
return nodes.length === 1 && nodes[0]?.type === "table";
|
|
2679
|
+
case "list":
|
|
2680
|
+
return nodes.length === 1 && nodes[0]?.type === "list";
|
|
2681
|
+
case "diagram":
|
|
2682
|
+
case "timeline":
|
|
2683
|
+
return nodes.length === 1 && nodes[0]?.type === "code";
|
|
2684
|
+
case "tree":
|
|
2685
|
+
return nodes.length === 1 && (nodes[0]?.type === "code" || nodes[0]?.type === "list");
|
|
2686
|
+
case "photoGrid": {
|
|
2687
|
+
const images = extractImages(nodes);
|
|
2688
|
+
if (images.length < 2 || images.length > 4) return false;
|
|
2689
|
+
const bodyText3 = normalizeCoverageText(extractBodyPlainText(nodes));
|
|
2690
|
+
const representedText = normalizeCoverageText(
|
|
2691
|
+
images.map((image) => image.alt).filter(Boolean).join(" ")
|
|
2692
|
+
);
|
|
2693
|
+
return bodyText3 === "" || bodyText3 === representedText;
|
|
2694
|
+
}
|
|
2695
|
+
case "videoWithCaption": {
|
|
2696
|
+
const videos = extractEmbeddedVideos(nodes);
|
|
2697
|
+
if (videos.length !== 1) return false;
|
|
2698
|
+
const bodyText3 = normalizeCoverageText(extractBodyPlainText(nodes));
|
|
2699
|
+
return bodyText3 === normalizeCoverageText(videos[0]?.alt ?? "");
|
|
2700
|
+
}
|
|
2701
|
+
case "leftFeature":
|
|
2702
|
+
case "rightFeature":
|
|
2703
|
+
case "statHighlight":
|
|
2704
|
+
return true;
|
|
2705
|
+
default:
|
|
2706
|
+
return false;
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
function deriveTemplateInputs(templateName, headingText, contents, options = {}) {
|
|
2710
|
+
const placeholders = options.placeholders === true;
|
|
2711
|
+
const bodyText3 = extractBodyPlainText(contents);
|
|
2712
|
+
switch (templateName) {
|
|
2713
|
+
case "statHighlight": {
|
|
2714
|
+
const inputs = deriveStatHighlightInputs(headingText, contents, bodyText3);
|
|
2715
|
+
if (options.preserveSourceHeading && headingText && inputs.stat !== headingText && inputs.description !== headingText) {
|
|
2716
|
+
return {
|
|
2717
|
+
...inputs,
|
|
2718
|
+
description: headingText,
|
|
2719
|
+
...inputs.description ? { detail: inputs.description } : {}
|
|
2720
|
+
};
|
|
2721
|
+
}
|
|
2722
|
+
return inputs;
|
|
2723
|
+
}
|
|
2724
|
+
case "quote": {
|
|
2725
|
+
const quote = extractBlockquoteText(contents) || bodyText3;
|
|
2726
|
+
if (quote) return { quote, ...headingText ? { title: headingText } : {} };
|
|
2727
|
+
return { quote: headingText };
|
|
2728
|
+
}
|
|
2729
|
+
case "fullBleedQuote":
|
|
2730
|
+
case "pullQuote": {
|
|
2731
|
+
const text = extractBlockquoteText(contents) || bodyText3 || headingText;
|
|
2732
|
+
return { text };
|
|
2733
|
+
}
|
|
2734
|
+
case "factCard":
|
|
2735
|
+
return { fact: headingText, explanation: bodyText3 || headingText };
|
|
2736
|
+
case "comparisonBar":
|
|
2737
|
+
return placeholders ? { leftLabel: "A", leftValue: 60, rightLabel: "B", rightValue: 40 } : null;
|
|
2738
|
+
case "list": {
|
|
2739
|
+
const items = extractListItems(contents);
|
|
2740
|
+
if (items.length > 0) return { items, ...headingText ? { title: headingText } : {} };
|
|
2741
|
+
return placeholders ? { items: ["Item 1", "Item 2", "Item 3"] } : null;
|
|
2742
|
+
}
|
|
2743
|
+
case "definitionCard":
|
|
2744
|
+
return { term: headingText, definition: bodyText3 || headingText };
|
|
2745
|
+
case "dateEvent":
|
|
2746
|
+
return { date: headingText, description: bodyText3 || headingText };
|
|
2747
|
+
case "dataTable": {
|
|
2748
|
+
const tableData = extractTableFromContents(contents);
|
|
2749
|
+
if (tableData) return { ...tableData, ...headingText ? { title: headingText } : {} };
|
|
2750
|
+
return placeholders ? { headers: ["Column"], rows: [["Data"]] } : null;
|
|
2751
|
+
}
|
|
2752
|
+
case "barChart":
|
|
2753
|
+
case "columnChart":
|
|
2754
|
+
case "pieChart":
|
|
2755
|
+
case "donutChart":
|
|
2756
|
+
case "lineChart":
|
|
2757
|
+
case "areaChart":
|
|
2758
|
+
case "scatterChart": {
|
|
2759
|
+
const tableData = extractTableFromContents(contents);
|
|
2760
|
+
if (tableData) {
|
|
2761
|
+
return {
|
|
2762
|
+
headers: tableData.headers,
|
|
2763
|
+
rows: tableData.rows,
|
|
2764
|
+
...headingText ? { title: headingText } : {}
|
|
2765
|
+
};
|
|
2766
|
+
}
|
|
2767
|
+
return placeholders && !bodyText3.trim() ? {
|
|
2768
|
+
headers: ["Quarter", "Revenue", "Costs"],
|
|
2769
|
+
rows: [
|
|
2770
|
+
["Q1", "40", "28"],
|
|
2771
|
+
["Q2", "55", "31"],
|
|
2772
|
+
["Q3", "48", "30"],
|
|
2773
|
+
["Q4", "62", "35"]
|
|
2774
|
+
],
|
|
2775
|
+
...headingText ? { title: headingText } : {}
|
|
2776
|
+
} : null;
|
|
2777
|
+
}
|
|
2778
|
+
case "imageWithCaption": {
|
|
2779
|
+
const img = extractFirstImage(contents);
|
|
2780
|
+
if (!img) return placeholders ? { caption: headingText } : null;
|
|
2781
|
+
return { imageSrc: img.src, imageAlt: img.alt || headingText, caption: headingText };
|
|
2782
|
+
}
|
|
2783
|
+
case "photoGrid": {
|
|
2784
|
+
const images = extractImages(contents, 4);
|
|
2785
|
+
if (images.length < 2) return placeholders ? {} : null;
|
|
2786
|
+
return {
|
|
2787
|
+
images: images.map((i) => ({ src: i.src, alt: i.alt })),
|
|
2788
|
+
caption: headingText
|
|
2789
|
+
};
|
|
2790
|
+
}
|
|
2791
|
+
case "videoWithCaption": {
|
|
2792
|
+
const video = extractFirstEmbeddedVideo(contents);
|
|
2793
|
+
if (!video) return placeholders ? { caption: headingText } : null;
|
|
2794
|
+
return {
|
|
2795
|
+
videoSrc: video.src,
|
|
2796
|
+
posterSrc: video.posterSrc,
|
|
2797
|
+
videoAlt: video.alt || headingText,
|
|
2798
|
+
caption: headingText
|
|
2799
|
+
};
|
|
2800
|
+
}
|
|
2801
|
+
case "leftFeature":
|
|
2802
|
+
case "rightFeature": {
|
|
2803
|
+
const img = extractFirstImage(contents);
|
|
2804
|
+
if (!img && !placeholders) return null;
|
|
2805
|
+
return {
|
|
2806
|
+
imageSrc: img?.src ?? "",
|
|
2807
|
+
imageAlt: img?.alt || headingText,
|
|
2808
|
+
imageWidth: img?.width,
|
|
2809
|
+
imageHeight: img?.height,
|
|
2810
|
+
title: headingText,
|
|
2811
|
+
body: bodyText3
|
|
2812
|
+
};
|
|
2813
|
+
}
|
|
2814
|
+
case "diagram": {
|
|
2815
|
+
const fences = (contents ?? []).filter((n) => n.type === "code");
|
|
2816
|
+
const fence = fences.length === 1 && isEligibleAsciiFenceLang(fences[0].lang) ? fences[0] : void 0;
|
|
2817
|
+
const detection = fence ? detectAsciiDiagram(fence.value, { explicit: isExplicitDiagramLang(fence.lang) }) : void 0;
|
|
2818
|
+
if (!detection?.isDiagram || !detection.diagram) return placeholders ? {} : null;
|
|
2819
|
+
const { nodes, edges } = asciiDiagramToTemplateData(detection.diagram);
|
|
2820
|
+
return { nodes, edges, ...headingText ? { title: headingText } : {} };
|
|
2821
|
+
}
|
|
2822
|
+
case "timeline": {
|
|
2823
|
+
const fences = (contents ?? []).filter((n) => n.type === "code");
|
|
2824
|
+
const fence = fences.length === 1 && isEligibleAsciiTimelineFenceLang(fences[0].lang) ? fences[0] : void 0;
|
|
2825
|
+
const detection = fence ? detectAsciiTimeline(fence.value, { explicit: true }) : void 0;
|
|
2826
|
+
if (!detection?.isTimeline || !detection.timeline) return placeholders ? {} : null;
|
|
2827
|
+
const { tracks, links } = asciiTimelineToTemplateData(detection.timeline);
|
|
2828
|
+
return { tracks, links, ...headingText ? { title: headingText } : {} };
|
|
2829
|
+
}
|
|
2830
|
+
case "tree": {
|
|
2831
|
+
const fences = (contents ?? []).filter((n) => n.type === "code");
|
|
2832
|
+
const fence = fences.length === 1 && isEligibleTreeFenceLang(fences[0].lang) ? fences[0] : void 0;
|
|
2833
|
+
if (fence) {
|
|
2834
|
+
const detection = detectTree(fence.value, { explicit: isExplicitTreeLang(fence.lang) });
|
|
2835
|
+
if (detection.isTree && detection.tree) {
|
|
2836
|
+
const { items } = treeToTemplateData(detection.tree);
|
|
2837
|
+
return { items, ...headingText ? { title: headingText } : {} };
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
const list = findFirstList(contents);
|
|
2841
|
+
if (list) {
|
|
2842
|
+
const { items } = treeToTemplateData(treeFromMarkdownList(list));
|
|
2843
|
+
if (items.length > 0) return { items, ...headingText ? { title: headingText } : {} };
|
|
2844
|
+
}
|
|
2845
|
+
return placeholders ? {} : null;
|
|
2846
|
+
}
|
|
2847
|
+
default:
|
|
2848
|
+
return placeholders ? {} : null;
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2024
2852
|
// src/doc/templates/listBlock.ts
|
|
2025
2853
|
var LIST_ITEM_LINE_HEIGHT = 1.2;
|
|
2026
2854
|
var LIST_ITEM_GAP_PX = 18;
|
|
@@ -2049,6 +2877,7 @@ function estimateWrappedLineCount(text, fontSize, maxWidth) {
|
|
|
2049
2877
|
function listBlock(input, context) {
|
|
2050
2878
|
const { title, accentImage } = input;
|
|
2051
2879
|
const items = Array.isArray(input.items) ? input.items : [];
|
|
2880
|
+
const richItems = extractRichListItems(context.block?.contents);
|
|
2052
2881
|
const { theme } = context;
|
|
2053
2882
|
const accentLayout = accentImage ? getAccentLayout(accentImage.position) : DEFAULT_LAYOUT;
|
|
2054
2883
|
const titleFontSize = themedFontSize(44, context, true);
|
|
@@ -2112,6 +2941,7 @@ function listBlock(input, context) {
|
|
|
2112
2941
|
let itemY = startY;
|
|
2113
2942
|
for (let i = 0; i < items.length; i++) {
|
|
2114
2943
|
const itemText = items[i];
|
|
2944
|
+
const itemHtml = richItems[i]?.text === itemText ? richItems[i]?.html : void 0;
|
|
2115
2945
|
const lineCount = estimateWrappedLineCount(itemText, itemFontSize, bodyWidthPx);
|
|
2116
2946
|
const animation = themedEntrance(context, "text", {
|
|
2117
2947
|
type: "fadeIn",
|
|
@@ -2144,6 +2974,7 @@ function listBlock(input, context) {
|
|
|
2144
2974
|
id: `item-${i}`,
|
|
2145
2975
|
content: {
|
|
2146
2976
|
text: itemText,
|
|
2977
|
+
...itemHtml ? { html: itemHtml } : {},
|
|
2147
2978
|
style: {
|
|
2148
2979
|
fontSize: itemFontSize,
|
|
2149
2980
|
fontFamily: getThemeFont(context, "body"),
|
|
@@ -2985,83 +3816,6 @@ function wrappedLineCount(text, fontSize, maxWidth) {
|
|
|
2985
3816
|
return Math.max(1, lines);
|
|
2986
3817
|
}
|
|
2987
3818
|
|
|
2988
|
-
// src/doc/templates/diagramLayout.ts
|
|
2989
|
-
var DEFAULT_OPTS = {
|
|
2990
|
-
gapX: 260,
|
|
2991
|
-
gapY: 140,
|
|
2992
|
-
gridPad: 80,
|
|
2993
|
-
gridOriginX: 80,
|
|
2994
|
-
gridOriginY: 80
|
|
2995
|
-
};
|
|
2996
|
-
function computeDiagramLayout(children, options = {}) {
|
|
2997
|
-
const opts = { ...DEFAULT_OPTS, ...options };
|
|
2998
|
-
const warnings = [];
|
|
2999
|
-
const entries = [];
|
|
3000
|
-
const unpositionedIdx = [];
|
|
3001
|
-
for (let i = 0; i < children.length; i++) {
|
|
3002
|
-
const c = children[i];
|
|
3003
|
-
const hasX = typeof c.x === "number";
|
|
3004
|
-
const hasY = typeof c.y === "number";
|
|
3005
|
-
if (hasX && hasY) {
|
|
3006
|
-
entries.push({ child: c, pinned: true, x: c.x, y: c.y });
|
|
3007
|
-
} else {
|
|
3008
|
-
entries.push({ child: c, pinned: false, x: 0, y: 0 });
|
|
3009
|
-
unpositionedIdx.push(i);
|
|
3010
|
-
}
|
|
3011
|
-
}
|
|
3012
|
-
if (unpositionedIdx.length > 0) {
|
|
3013
|
-
const cols = Math.max(1, Math.ceil(Math.sqrt(unpositionedIdx.length)));
|
|
3014
|
-
let baseX = opts.gridOriginX;
|
|
3015
|
-
let baseY = opts.gridOriginY;
|
|
3016
|
-
const pinned = entries.filter((e) => e.pinned);
|
|
3017
|
-
if (pinned.length > 0) {
|
|
3018
|
-
const minX = Math.min(...pinned.map((e) => e.x));
|
|
3019
|
-
const maxY = Math.max(...pinned.map((e) => e.y));
|
|
3020
|
-
baseX = minX;
|
|
3021
|
-
baseY = maxY + opts.gridPad + opts.gapY;
|
|
3022
|
-
}
|
|
3023
|
-
unpositionedIdx.forEach((entryIdx, k) => {
|
|
3024
|
-
const col = k % cols;
|
|
3025
|
-
const row = Math.floor(k / cols);
|
|
3026
|
-
entries[entryIdx].x = baseX + col * opts.gapX;
|
|
3027
|
-
entries[entryIdx].y = baseY + row * opts.gapY;
|
|
3028
|
-
});
|
|
3029
|
-
}
|
|
3030
|
-
const nodes = entries.map((e) => ({
|
|
3031
|
-
id: e.child.id,
|
|
3032
|
-
label: e.child.title ?? e.child.id,
|
|
3033
|
-
x: e.x,
|
|
3034
|
-
y: e.y,
|
|
3035
|
-
pinned: e.pinned,
|
|
3036
|
-
...e.child.classes && e.child.classes.length > 0 ? { classes: e.child.classes } : {}
|
|
3037
|
-
}));
|
|
3038
|
-
const nodeIds = new Set(nodes.map((n) => n.id));
|
|
3039
|
-
const edges = [];
|
|
3040
|
-
const seenEdgeIds = /* @__PURE__ */ new Set();
|
|
3041
|
-
for (const child of children) {
|
|
3042
|
-
if (!child.connectsTo) continue;
|
|
3043
|
-
for (const conn of child.connectsTo) {
|
|
3044
|
-
if (!nodeIds.has(conn.target)) {
|
|
3045
|
-
warnings.push(`Edge from "${child.id}" to "${conn.target}" dropped: no such sibling node`);
|
|
3046
|
-
continue;
|
|
3047
|
-
}
|
|
3048
|
-
const edgeId = makeEdgeId(child.id, conn);
|
|
3049
|
-
if (seenEdgeIds.has(edgeId)) continue;
|
|
3050
|
-
seenEdgeIds.add(edgeId);
|
|
3051
|
-
edges.push({
|
|
3052
|
-
id: edgeId,
|
|
3053
|
-
source: child.id,
|
|
3054
|
-
target: conn.target,
|
|
3055
|
-
...conn.type ? { type: conn.type } : {}
|
|
3056
|
-
});
|
|
3057
|
-
}
|
|
3058
|
-
}
|
|
3059
|
-
return { nodes, edges, warnings };
|
|
3060
|
-
}
|
|
3061
|
-
function makeEdgeId(source, conn) {
|
|
3062
|
-
return conn.type ? `${source}->${conn.target}:${conn.type}` : `${source}->${conn.target}`;
|
|
3063
|
-
}
|
|
3064
|
-
|
|
3065
3819
|
// src/doc/templates/diagramBlock.ts
|
|
3066
3820
|
var NODE_WIDTH = 180;
|
|
3067
3821
|
var NODE_HEIGHT = 64;
|
|
@@ -3135,7 +3889,7 @@ function nodesFromTemplateData(input) {
|
|
|
3135
3889
|
}
|
|
3136
3890
|
function diagramBlock(input, context) {
|
|
3137
3891
|
const { theme, viewport, children = [] } = context;
|
|
3138
|
-
const colors = resolveColorScheme(context, input.colorScheme
|
|
3892
|
+
const colors = input.colorScheme ? resolveColorScheme(context, input.colorScheme) : void 0;
|
|
3139
3893
|
const resolved = children.length > 0 ? nodesFromChildren(context) : input.nodes && input.nodes.length > 0 ? nodesFromTemplateData(input) : { nodes: [], edges: [] };
|
|
3140
3894
|
if (resolved.nodes.length === 0) {
|
|
3141
3895
|
return [
|
|
@@ -3205,9 +3959,9 @@ function diagramBlock(input, context) {
|
|
|
3205
3959
|
id: `node-card-${node.id}`,
|
|
3206
3960
|
content: {
|
|
3207
3961
|
shape: "rect",
|
|
3208
|
-
fill: colors
|
|
3962
|
+
fill: colors?.bg ?? theme.colors.backgroundLight,
|
|
3209
3963
|
fillOpacity: 0.25,
|
|
3210
|
-
stroke: colors
|
|
3964
|
+
stroke: colors?.text ?? theme.colors.primary,
|
|
3211
3965
|
strokeWidth: Math.max(1, strokeW - 1),
|
|
3212
3966
|
borderRadius: 10
|
|
3213
3967
|
},
|
|
@@ -3236,7 +3990,7 @@ function diagramBlock(input, context) {
|
|
|
3236
3990
|
id: `edge-${edge.id}`,
|
|
3237
3991
|
content: {
|
|
3238
3992
|
d: connectorPath(edge.routing ?? input.edgeStyle ?? "curved", start, end),
|
|
3239
|
-
stroke: colors
|
|
3993
|
+
stroke: colors?.text ?? theme.colors.primary,
|
|
3240
3994
|
strokeWidth: strokeW,
|
|
3241
3995
|
fill: "none",
|
|
3242
3996
|
...edgeDash ? { dasharray: edgeDash } : {},
|
|
@@ -3274,8 +4028,8 @@ function diagramBlock(input, context) {
|
|
|
3274
4028
|
id: `node-card-${node.id}`,
|
|
3275
4029
|
content: {
|
|
3276
4030
|
shape: "rect",
|
|
3277
|
-
fill: colors
|
|
3278
|
-
stroke: colors
|
|
4031
|
+
fill: colors?.bg ?? theme.colors.backgroundLight,
|
|
4032
|
+
stroke: colors?.text ?? theme.colors.primary,
|
|
3279
4033
|
strokeWidth: strokeW,
|
|
3280
4034
|
borderRadius: input.nodeShape === "pill" ? t.h / 2 : 10
|
|
3281
4035
|
},
|
|
@@ -3293,7 +4047,7 @@ function diagramBlock(input, context) {
|
|
|
3293
4047
|
fontSize: fit.fontSize,
|
|
3294
4048
|
fontFamily: getThemeFont(context, "body"),
|
|
3295
4049
|
fontWeight: "bold",
|
|
3296
|
-
color: colors
|
|
4050
|
+
color: colors?.text ?? theme.colors.text,
|
|
3297
4051
|
textAlign: "center",
|
|
3298
4052
|
lineHeight: DIAGRAM_LABEL_LINE_HEIGHT
|
|
3299
4053
|
}
|
|
@@ -3425,7 +4179,7 @@ function treeBlock(input, context) {
|
|
|
3425
4179
|
}
|
|
3426
4180
|
|
|
3427
4181
|
// src/doc/templates/timelineBlock.ts
|
|
3428
|
-
var
|
|
4182
|
+
var clamp012 = (value) => Math.max(0, Math.min(1, value));
|
|
3429
4183
|
var clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
3430
4184
|
function safeId(value) {
|
|
3431
4185
|
return value.replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "item";
|
|
@@ -3452,7 +4206,7 @@ function coerceTracks(raw) {
|
|
|
3452
4206
|
events.push({
|
|
3453
4207
|
id: eventId,
|
|
3454
4208
|
label: event.label,
|
|
3455
|
-
position:
|
|
4209
|
+
position: clamp012(position),
|
|
3456
4210
|
...typeof event.description === "string" && event.description.trim() ? { description: event.description.trim() } : {},
|
|
3457
4211
|
...side ? { side } : {},
|
|
3458
4212
|
...descriptionSide ? { descriptionSide } : {},
|
|
@@ -3583,7 +4337,7 @@ function timelineBlock(input, context) {
|
|
|
3583
4337
|
const eventFontSize = Math.min(baseEventFontSize, 24 * minViewportScale);
|
|
3584
4338
|
const descriptionFontSize = Math.min(baseDescriptionFontSize, 17 * minViewportScale);
|
|
3585
4339
|
const trackMarkerRadii = tracks.map((track) => {
|
|
3586
|
-
const positions = track.events.map((event) =>
|
|
4340
|
+
const positions = track.events.map((event) => clamp012(event.position)).sort((a, b) => a - b);
|
|
3587
4341
|
let minGapPx = Number.POSITIVE_INFINITY;
|
|
3588
4342
|
for (let index = 1; index < positions.length; index++) {
|
|
3589
4343
|
minGapPx = Math.min(minGapPx, (positions[index] - positions[index - 1]) * axisWidth);
|
|
@@ -3662,7 +4416,7 @@ function timelineBlock(input, context) {
|
|
|
3662
4416
|
event,
|
|
3663
4417
|
trackIndex,
|
|
3664
4418
|
eventIndex,
|
|
3665
|
-
x: axisStartX + axisWidth *
|
|
4419
|
+
x: axisStartX + axisWidth * clamp012(event.position),
|
|
3666
4420
|
y
|
|
3667
4421
|
};
|
|
3668
4422
|
eventPoints.push(point);
|
|
@@ -4860,1119 +5614,1580 @@ function resolveLayer(layer, block, images, item) {
|
|
|
4860
5614
|
|
|
4861
5615
|
// src/doc/templates/customTemplate.ts
|
|
4862
5616
|
function makeCustomTemplateFn(def) {
|
|
4863
|
-
return (input, context) => {
|
|
4864
|
-
const block = context.block ?? input;
|
|
4865
|
-
if (!block) return def.layers.slice();
|
|
4866
|
-
return resolveTokens(def.layers, block);
|
|
4867
|
-
};
|
|
4868
|
-
}
|
|
4869
|
-
|
|
4870
|
-
// src/doc/templates/
|
|
4871
|
-
var
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
// src/doc/utils/applyRenderStyle.ts
|
|
4915
|
-
var AMBIENT_VARIANTS = [
|
|
4916
|
-
{ type: "slowZoom", direction: "in" },
|
|
4917
|
-
{ type: "slowZoom", direction: "out" },
|
|
4918
|
-
{ type: "slowZoom", panDirection: "left" },
|
|
4919
|
-
{ type: "slowZoom", panDirection: "right" }
|
|
4920
|
-
];
|
|
4921
|
-
var MIN_AMBIENT_DURATION_SECONDS = 8;
|
|
4922
|
-
function isZeroish(v) {
|
|
4923
|
-
return v === 0 || v === "0" || v === "0%";
|
|
4924
|
-
}
|
|
4925
|
-
function isFullBleedCoverMedia(layer) {
|
|
4926
|
-
if (layer.type !== "image" && layer.type !== "video") return false;
|
|
4927
|
-
const { position } = layer;
|
|
4928
|
-
if (!isZeroish(position.x) || !isZeroish(position.y)) return false;
|
|
4929
|
-
if (position.width !== "100%" || position.height !== "100%") return false;
|
|
4930
|
-
const fit = layer.content.fit;
|
|
4931
|
-
return fit === void 0 || fit === "cover";
|
|
4932
|
-
}
|
|
4933
|
-
function themeWantsAmbientMotion(theme) {
|
|
4934
|
-
return theme.renderStyle.ambientMotion === true;
|
|
4935
|
-
}
|
|
4936
|
-
function applyRenderStyleToLayers(layers, block, theme) {
|
|
4937
|
-
const speed = theme.style.animationSpeed ?? 1;
|
|
4938
|
-
const ambient = themeWantsAmbientMotion(theme);
|
|
4939
|
-
if (speed === 1 && !ambient) return layers;
|
|
4940
|
-
return layers.map((layer) => {
|
|
4941
|
-
let animation = layer.animation;
|
|
4942
|
-
if (!animation && ambient && isFullBleedCoverMedia(layer)) {
|
|
4943
|
-
const rng = new SeededRandom(hashString(`${block.id}:${layer.id}`));
|
|
4944
|
-
const variant = rng.pickRequired(AMBIENT_VARIANTS);
|
|
4945
|
-
animation = {
|
|
4946
|
-
...variant,
|
|
4947
|
-
duration: Math.max(MIN_AMBIENT_DURATION_SECONDS, block.duration ?? 0)
|
|
4948
|
-
};
|
|
4949
|
-
}
|
|
4950
|
-
if (!animation || animation.type === "none") {
|
|
4951
|
-
return animation === layer.animation ? layer : { ...layer, animation };
|
|
4952
|
-
}
|
|
4953
|
-
if (speed !== 1) {
|
|
4954
|
-
animation = {
|
|
4955
|
-
...animation,
|
|
4956
|
-
...animation.duration != null ? { duration: animation.duration * speed } : {},
|
|
4957
|
-
...animation.delay != null ? { delay: animation.delay * speed } : {}
|
|
4958
|
-
};
|
|
4959
|
-
}
|
|
4960
|
-
return animation === layer.animation ? layer : { ...layer, animation };
|
|
4961
|
-
});
|
|
4962
|
-
}
|
|
4963
|
-
|
|
4964
|
-
// src/doc/utils/nearest.ts
|
|
4965
|
-
function levenshtein(a, b) {
|
|
4966
|
-
if (a === b) return 0;
|
|
4967
|
-
const m = a.length;
|
|
4968
|
-
const n = b.length;
|
|
4969
|
-
if (m === 0) return n;
|
|
4970
|
-
if (n === 0) return m;
|
|
4971
|
-
let prev = new Array(n + 1);
|
|
4972
|
-
let curr = new Array(n + 1);
|
|
4973
|
-
for (let j = 0; j <= n; j++) prev[j] = j;
|
|
4974
|
-
for (let i = 1; i <= m; i++) {
|
|
4975
|
-
curr[0] = i;
|
|
4976
|
-
for (let j = 1; j <= n; j++) {
|
|
4977
|
-
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
4978
|
-
curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost);
|
|
4979
|
-
}
|
|
4980
|
-
[prev, curr] = [curr, prev];
|
|
5617
|
+
return (input, context) => {
|
|
5618
|
+
const block = context.block ?? input;
|
|
5619
|
+
if (!block) return def.layers.slice();
|
|
5620
|
+
return resolveTokens(def.layers, block);
|
|
5621
|
+
};
|
|
5622
|
+
}
|
|
5623
|
+
|
|
5624
|
+
// src/doc/templates/chart/parse.ts
|
|
5625
|
+
var NUMERIC_COLUMN_THRESHOLD = 0.6;
|
|
5626
|
+
function parseCellNumber(raw) {
|
|
5627
|
+
if (typeof raw !== "string") return null;
|
|
5628
|
+
let text = raw.trim();
|
|
5629
|
+
if (!text) return null;
|
|
5630
|
+
let negative = false;
|
|
5631
|
+
const accounting = /^\((.*)\)$/.exec(text);
|
|
5632
|
+
if (accounting) {
|
|
5633
|
+
negative = true;
|
|
5634
|
+
text = accounting[1].trim();
|
|
5635
|
+
}
|
|
5636
|
+
text = text.replace(/^([+-]?)\s*[$€£¥]\s*/, "$1");
|
|
5637
|
+
text = text.replace(/%$/, "").trim();
|
|
5638
|
+
text = text.replace(/,/g, "");
|
|
5639
|
+
if (/^[+-]?\d{1,3}(?: \d{3})+(?:\.\d+)?$/.test(text)) {
|
|
5640
|
+
text = text.replace(/ /g, "");
|
|
5641
|
+
}
|
|
5642
|
+
if (!/^[+-]?(\d+\.?\d*|\.\d+)(e[+-]?\d+)?$/i.test(text)) return null;
|
|
5643
|
+
const value = Number(text);
|
|
5644
|
+
if (!Number.isFinite(value)) return null;
|
|
5645
|
+
return negative ? -value : value;
|
|
5646
|
+
}
|
|
5647
|
+
function isNumericColumn(rows, colIndex) {
|
|
5648
|
+
let nonEmpty = 0;
|
|
5649
|
+
let numeric = 0;
|
|
5650
|
+
for (const row of rows) {
|
|
5651
|
+
const cell = (row[colIndex] ?? "").trim();
|
|
5652
|
+
if (!cell) continue;
|
|
5653
|
+
nonEmpty += 1;
|
|
5654
|
+
if (parseCellNumber(cell) !== null) numeric += 1;
|
|
5655
|
+
}
|
|
5656
|
+
return numeric > 0 && numeric / Math.max(1, nonEmpty) >= NUMERIC_COLUMN_THRESHOLD;
|
|
5657
|
+
}
|
|
5658
|
+
function resolveColumnRef(ref, headers) {
|
|
5659
|
+
const text = String(ref).trim();
|
|
5660
|
+
if (!text) return null;
|
|
5661
|
+
const lower = text.toLowerCase();
|
|
5662
|
+
const byName = headers.findIndex((h) => h.trim().toLowerCase() === lower);
|
|
5663
|
+
if (byName >= 0) return byName;
|
|
5664
|
+
if (/^\d+$/.test(text)) {
|
|
5665
|
+
const index = Number(text);
|
|
5666
|
+
if (index < headers.length) return index;
|
|
4981
5667
|
}
|
|
4982
|
-
return
|
|
5668
|
+
return null;
|
|
4983
5669
|
}
|
|
4984
|
-
function
|
|
4985
|
-
const
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
const
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
5670
|
+
function buildChartData(table, options = {}) {
|
|
5671
|
+
const headers = Array.isArray(table.headers) ? table.headers : [];
|
|
5672
|
+
const rows = Array.isArray(table.rows) ? table.rows : [];
|
|
5673
|
+
if (headers.length === 0 || rows.length === 0) return null;
|
|
5674
|
+
const warnings = [];
|
|
5675
|
+
let labelIndex = 0;
|
|
5676
|
+
if (options.labelColumn !== void 0 && String(options.labelColumn).trim() !== "") {
|
|
5677
|
+
const resolved = resolveColumnRef(String(options.labelColumn), headers);
|
|
5678
|
+
if (resolved === null) {
|
|
5679
|
+
warnings.push(
|
|
5680
|
+
`labelColumn "${options.labelColumn}" matches no column; using the first column`
|
|
5681
|
+
);
|
|
5682
|
+
} else {
|
|
5683
|
+
labelIndex = resolved;
|
|
5684
|
+
}
|
|
5685
|
+
}
|
|
5686
|
+
let valueIndexes = [];
|
|
5687
|
+
const requested = (options.valueColumns ?? []).map((ref) => String(ref)).filter((ref) => ref.trim() !== "");
|
|
5688
|
+
if (requested.length > 0) {
|
|
5689
|
+
for (const ref of requested) {
|
|
5690
|
+
const resolved = resolveColumnRef(ref, headers);
|
|
5691
|
+
if (resolved === null) {
|
|
5692
|
+
warnings.push(`valueColumns entry "${ref}" matches no column; skipped`);
|
|
5693
|
+
} else if (!valueIndexes.includes(resolved)) {
|
|
5694
|
+
valueIndexes.push(resolved);
|
|
5695
|
+
}
|
|
4994
5696
|
}
|
|
5697
|
+
} else {
|
|
5698
|
+
valueIndexes = headers.map((_, index) => index).filter((index) => index !== labelIndex && isNumericColumn(rows, index));
|
|
4995
5699
|
}
|
|
4996
|
-
if (
|
|
4997
|
-
|
|
5700
|
+
if (valueIndexes.length === 0) return null;
|
|
5701
|
+
return {
|
|
5702
|
+
labels: rows.map((row) => (row[labelIndex] ?? "").trim()),
|
|
5703
|
+
labelHeader: headers[labelIndex] ?? "",
|
|
5704
|
+
labelNumbers: rows.map((row) => parseCellNumber(row[labelIndex] ?? "")),
|
|
5705
|
+
series: valueIndexes.map((index) => ({
|
|
5706
|
+
name: (headers[index] ?? `Column ${index + 1}`).trim() || `Column ${index + 1}`,
|
|
5707
|
+
values: rows.map((row) => parseCellNumber(row[index] ?? ""))
|
|
5708
|
+
})),
|
|
5709
|
+
warnings
|
|
5710
|
+
};
|
|
5711
|
+
}
|
|
5712
|
+
|
|
5713
|
+
// src/doc/templates/chart/scale.ts
|
|
5714
|
+
function niceNum(range, round4) {
|
|
5715
|
+
const exponent = Math.floor(Math.log10(range));
|
|
5716
|
+
const fraction = range / 10 ** exponent;
|
|
5717
|
+
let niceFraction;
|
|
5718
|
+
if (round4) {
|
|
5719
|
+
if (fraction < 1.5) niceFraction = 1;
|
|
5720
|
+
else if (fraction < 3) niceFraction = 2;
|
|
5721
|
+
else if (fraction < 7) niceFraction = 5;
|
|
5722
|
+
else niceFraction = 10;
|
|
5723
|
+
} else {
|
|
5724
|
+
if (fraction <= 1) niceFraction = 1;
|
|
5725
|
+
else if (fraction <= 2) niceFraction = 2;
|
|
5726
|
+
else if (fraction <= 5) niceFraction = 5;
|
|
5727
|
+
else niceFraction = 10;
|
|
5728
|
+
}
|
|
5729
|
+
return niceFraction * 10 ** exponent;
|
|
5730
|
+
}
|
|
5731
|
+
function niceTicks(min, max, targetCount = 5) {
|
|
5732
|
+
let lo = Number.isFinite(min) ? min : 0;
|
|
5733
|
+
let hi = Number.isFinite(max) ? max : 0;
|
|
5734
|
+
if (lo > hi) [lo, hi] = [hi, lo];
|
|
5735
|
+
if (lo === hi) {
|
|
5736
|
+
const pad = lo === 0 ? 1 : niceNum(Math.abs(lo), true);
|
|
5737
|
+
lo -= pad;
|
|
5738
|
+
hi += pad;
|
|
5739
|
+
if (min >= 0 && lo < 0) lo = 0;
|
|
5740
|
+
}
|
|
5741
|
+
const count = Math.max(2, targetCount);
|
|
5742
|
+
const range = niceNum(hi - lo, false);
|
|
5743
|
+
const step = niceNum(range / (count - 1), true);
|
|
5744
|
+
const niceMin = Math.floor(lo / step) * step;
|
|
5745
|
+
const niceMax = Math.ceil(hi / step) * step;
|
|
5746
|
+
const ticks = [];
|
|
5747
|
+
for (let tick = niceMin; tick <= niceMax + step * 1e-6; tick += step) {
|
|
5748
|
+
ticks.push(Math.abs(tick) < step * 1e-6 ? 0 : Number(tick.toPrecision(12)));
|
|
5749
|
+
}
|
|
5750
|
+
return { ticks, niceMin, niceMax, step };
|
|
5751
|
+
}
|
|
5752
|
+
function formatChartValue(value, unit) {
|
|
5753
|
+
if (!Number.isFinite(value)) return "\u2014";
|
|
5754
|
+
const sign = value < 0 ? "-" : "";
|
|
5755
|
+
const abs = Math.abs(value);
|
|
5756
|
+
let text;
|
|
5757
|
+
if (abs >= 1e6) {
|
|
5758
|
+
text = `${sign}${trimTrailingZero((abs / 1e6).toFixed(1))}M`;
|
|
5759
|
+
} else if (abs >= 1e3) {
|
|
5760
|
+
text = `${sign}${trimTrailingZero((abs / 1e3).toFixed(abs >= 1e4 ? 0 : 1))}K`;
|
|
5761
|
+
} else {
|
|
5762
|
+
text = `${sign}${abs.toLocaleString("en-US", { maximumFractionDigits: 2 })}`;
|
|
4998
5763
|
}
|
|
4999
|
-
return
|
|
5764
|
+
return unit ? `${text} ${unit}` : text;
|
|
5765
|
+
}
|
|
5766
|
+
function trimTrailingZero(text) {
|
|
5767
|
+
return text.replace(/\.0$/, "");
|
|
5000
5768
|
}
|
|
5001
5769
|
|
|
5002
|
-
// src/doc/templates/
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
{ key: "title", description: "Section title" },
|
|
5029
|
-
{
|
|
5030
|
-
key: "colorScheme",
|
|
5031
|
-
description: "Fallback background color scheme",
|
|
5032
|
-
valueHint: COLOR_SCHEME_HINT
|
|
5033
|
-
},
|
|
5034
|
-
{ key: "imageSrc", description: "Optional background image path" },
|
|
5035
|
-
{ key: "imageAlt", description: "Alt text for the background image" },
|
|
5036
|
-
{ key: "ambientMotion", description: "Slow zoom/pan effect", values: AMBIENT_MOTION_VALUES }
|
|
5037
|
-
],
|
|
5038
|
-
content: [{ key: "title", description: "Heading shown above the complete body" }],
|
|
5039
|
-
statHighlight: [
|
|
5040
|
-
{ key: "stat", description: 'The statistic (e.g. "89%", "2x")' },
|
|
5041
|
-
{ key: "description", description: "What the stat means" },
|
|
5042
|
-
{ key: "detail", description: "Additional detail or context" },
|
|
5043
|
-
{ key: "colorScheme", description: "Color scheme for the stat", valueHint: COLOR_SCHEME_HINT }
|
|
5044
|
-
],
|
|
5045
|
-
quote: [
|
|
5046
|
-
{ key: "title", description: "Optional heading above the quote" },
|
|
5047
|
-
{ key: "quote", description: "The quote text" },
|
|
5048
|
-
{ key: "attribution", description: "Attribution (author, source)" }
|
|
5049
|
-
],
|
|
5050
|
-
pullQuote: [
|
|
5051
|
-
{ key: "text", description: "Quote text" },
|
|
5052
|
-
{ key: "attribution", description: "Attribution" }
|
|
5053
|
-
],
|
|
5054
|
-
twoColumn: [
|
|
5055
|
-
{
|
|
5056
|
-
key: "left",
|
|
5057
|
-
description: 'Left column, as "label|sublabel"',
|
|
5058
|
-
coerce: "labeledPair",
|
|
5059
|
-
required: true
|
|
5060
|
-
},
|
|
5061
|
-
{
|
|
5062
|
-
key: "right",
|
|
5063
|
-
description: 'Right column, as "label|sublabel"',
|
|
5064
|
-
coerce: "labeledPair",
|
|
5065
|
-
required: true
|
|
5066
|
-
},
|
|
5067
|
-
{ key: "header", description: "Optional header above the columns" },
|
|
5068
|
-
{ key: "leftColor", description: "Left column color scheme", valueHint: COLOR_SCHEME_HINT },
|
|
5069
|
-
{ key: "rightColor", description: "Right column color scheme", valueHint: COLOR_SCHEME_HINT }
|
|
5070
|
-
],
|
|
5071
|
-
dateEvent: [
|
|
5072
|
-
{ key: "date", description: 'The date (e.g. "July 14, 1974")' },
|
|
5073
|
-
{ key: "description", description: "What happened" },
|
|
5074
|
-
{ key: "footer", description: "Optional footer text" },
|
|
5075
|
-
{ key: "mood", description: "Emotional tone", values: DATE_EVENT_MOOD_VALUES }
|
|
5076
|
-
],
|
|
5077
|
-
imageWithCaption: [
|
|
5078
|
-
{ key: "imageSrc", description: "Path to the image file", required: true },
|
|
5079
|
-
{ key: "imageAlt", description: "Alt text for accessibility" },
|
|
5080
|
-
{ key: "caption", description: "Caption text" },
|
|
5081
|
-
{ key: "captionPosition", description: "Caption placement", values: CAPTION_POSITION_VALUES },
|
|
5082
|
-
{ key: "ambientMotion", description: "Slow zoom/pan effect", values: AMBIENT_MOTION_VALUES },
|
|
5083
|
-
{ key: "isTitle", description: "Style the caption as a title", coerce: "boolean" },
|
|
5084
|
-
{ key: "subtitle", description: "Subtitle (only when isTitle)" },
|
|
5085
|
-
{ key: "imageCredit", description: "Photo credit / artist name" },
|
|
5086
|
-
{ key: "imageLicense", description: "License identifier" }
|
|
5087
|
-
],
|
|
5088
|
-
leftFeature: FEATURE_DESCRIPTORS(),
|
|
5089
|
-
rightFeature: FEATURE_DESCRIPTORS(),
|
|
5090
|
-
map: [
|
|
5091
|
-
{
|
|
5092
|
-
key: "center",
|
|
5093
|
-
description: 'Map center as "lat,lng"',
|
|
5094
|
-
coerce: "latLng",
|
|
5095
|
-
required: true
|
|
5096
|
-
},
|
|
5097
|
-
{ key: "zoom", description: "Zoom level (4-16)", coerce: "number" },
|
|
5098
|
-
{ key: "mapStyle", description: "Map tile style", values: MAP_STYLE_VALUES },
|
|
5099
|
-
{ key: "title", description: "Optional title overlay" },
|
|
5100
|
-
{ key: "caption", description: "Optional caption" }
|
|
5101
|
-
],
|
|
5102
|
-
photoGrid: [
|
|
5103
|
-
{ key: "images", description: "Comma-separated image paths", coerce: "stringList" },
|
|
5104
|
-
{ key: "caption", description: "Optional caption below the grid" },
|
|
5105
|
-
{
|
|
5106
|
-
key: "ambientMotion",
|
|
5107
|
-
description: "Slow zoom/pan on the largest image",
|
|
5108
|
-
values: AMBIENT_MOTION_VALUES
|
|
5109
|
-
}
|
|
5110
|
-
],
|
|
5111
|
-
comparisonBar: [
|
|
5112
|
-
{ key: "leftLabel", description: "Left bar label", required: true },
|
|
5113
|
-
{ key: "leftValue", description: "Left bar numeric value", coerce: "number", required: true },
|
|
5114
|
-
{ key: "rightLabel", description: "Right bar label", required: true },
|
|
5115
|
-
{ key: "rightValue", description: "Right bar numeric value", coerce: "number", required: true },
|
|
5116
|
-
{ key: "unit", description: 'Unit label (e.g. "km")' },
|
|
5117
|
-
{ key: "colorScheme", description: "Color scheme", valueHint: COLOR_SCHEME_HINT }
|
|
5118
|
-
],
|
|
5119
|
-
dataTable: [
|
|
5120
|
-
{ key: "title", description: "Optional title above the table" },
|
|
5121
|
-
{ key: "headers", description: "Comma-separated header cells", coerce: "stringList" },
|
|
5122
|
-
// `rows`/`align` are structured (string[][] / alignment[]) and can't be
|
|
5123
|
-
// expressed as a single inline string — declared so they aren't flagged
|
|
5124
|
-
// as unknown when supplied via a data fence's overriding params.
|
|
5125
|
-
{ key: "rows", description: "Table rows (supply via a data fence)" },
|
|
5126
|
-
{ key: "align", description: "Per-column alignment (supply via a data fence)" },
|
|
5127
|
-
{ key: "colorScheme", description: "Header color scheme", valueHint: COLOR_SCHEME_HINT }
|
|
5128
|
-
],
|
|
5129
|
-
tree: [
|
|
5130
|
-
{ key: "title", description: "Optional title above the tree" },
|
|
5131
|
-
{ key: "colorScheme", description: "Folder-row color scheme", valueHint: COLOR_SCHEME_HINT },
|
|
5132
|
-
// `items` is a nested structure derived from the tree fence, not an
|
|
5133
|
-
// inline string — declared so it isn't flagged as unknown.
|
|
5134
|
-
{ key: "items", description: "Tree items (derived from the tree fence)" }
|
|
5135
|
-
],
|
|
5136
|
-
timeline: [
|
|
5137
|
-
{ key: "title", description: "Optional title above the timeline" },
|
|
5138
|
-
{
|
|
5139
|
-
key: "colorScheme",
|
|
5140
|
-
description: "Track and marker color scheme",
|
|
5141
|
-
valueHint: COLOR_SCHEME_HINT
|
|
5142
|
-
},
|
|
5143
|
-
// Tracks/events and branch links are nested structures derived from the
|
|
5144
|
-
// timeline source, not values that can be expressed as one inline string.
|
|
5145
|
-
{ key: "tracks", description: "Timeline tracks and events (supply via template data)" },
|
|
5146
|
-
{ key: "links", description: "Branch links between event ids (supply via template data)" }
|
|
5147
|
-
],
|
|
5148
|
-
videoWithCaption: [
|
|
5149
|
-
{ key: "videoSrc", description: "Path to the video file", required: true },
|
|
5150
|
-
{ key: "posterSrc", description: "Poster frame path" },
|
|
5151
|
-
{ key: "videoAlt", description: "Alt text for accessibility" },
|
|
5152
|
-
{ key: "clipStart", description: "Clip start time (seconds)", coerce: "number" },
|
|
5153
|
-
{ key: "clipEnd", description: "Clip end time (seconds)", coerce: "number" },
|
|
5154
|
-
{ key: "caption", description: "Caption text" },
|
|
5155
|
-
{ key: "captionPosition", description: "Caption placement", values: CAPTION_POSITION_VALUES },
|
|
5156
|
-
{ key: "videoCredit", description: "Video credit / artist name" },
|
|
5157
|
-
{ key: "videoLicense", description: "License identifier" }
|
|
5158
|
-
]
|
|
5159
|
-
};
|
|
5160
|
-
function FEATURE_DESCRIPTORS() {
|
|
5770
|
+
// src/doc/templates/chart/geometry.ts
|
|
5771
|
+
function round3(value) {
|
|
5772
|
+
return Math.round(value * 100) / 100;
|
|
5773
|
+
}
|
|
5774
|
+
function pointOnCircle(cx, cy, r, deg) {
|
|
5775
|
+
const rad = (deg - 90) * Math.PI / 180;
|
|
5776
|
+
return { x: round3(cx + r * Math.cos(rad)), y: round3(cy + r * Math.sin(rad)) };
|
|
5777
|
+
}
|
|
5778
|
+
function arcPath(cx, cy, rOuter, rInner, startDeg, endDeg) {
|
|
5779
|
+
let sweep = endDeg - startDeg;
|
|
5780
|
+
if (sweep <= 0) return "";
|
|
5781
|
+
if (sweep >= 359.9) sweep = 359.9;
|
|
5782
|
+
const end = startDeg + sweep;
|
|
5783
|
+
const largeArc = sweep > 180 ? 1 : 0;
|
|
5784
|
+
const o1 = pointOnCircle(cx, cy, rOuter, startDeg);
|
|
5785
|
+
const o2 = pointOnCircle(cx, cy, rOuter, end);
|
|
5786
|
+
if (rInner <= 0) {
|
|
5787
|
+
return [
|
|
5788
|
+
`M ${round3(cx)} ${round3(cy)}`,
|
|
5789
|
+
`L ${o1.x} ${o1.y}`,
|
|
5790
|
+
`A ${round3(rOuter)} ${round3(rOuter)} 0 ${largeArc} 1 ${o2.x} ${o2.y}`,
|
|
5791
|
+
"Z"
|
|
5792
|
+
].join(" ");
|
|
5793
|
+
}
|
|
5794
|
+
const i1 = pointOnCircle(cx, cy, rInner, startDeg);
|
|
5795
|
+
const i2 = pointOnCircle(cx, cy, rInner, end);
|
|
5161
5796
|
return [
|
|
5162
|
-
{
|
|
5163
|
-
{
|
|
5164
|
-
{
|
|
5165
|
-
{
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5797
|
+
`M ${o1.x} ${o1.y}`,
|
|
5798
|
+
`A ${round3(rOuter)} ${round3(rOuter)} 0 ${largeArc} 1 ${o2.x} ${o2.y}`,
|
|
5799
|
+
`L ${i2.x} ${i2.y}`,
|
|
5800
|
+
`A ${round3(rInner)} ${round3(rInner)} 0 ${largeArc} 0 ${i1.x} ${i1.y}`,
|
|
5801
|
+
"Z"
|
|
5802
|
+
].join(" ");
|
|
5803
|
+
}
|
|
5804
|
+
function polylinePath(points) {
|
|
5805
|
+
if (points.length === 0) return "";
|
|
5806
|
+
return points.map((p, i) => `${i === 0 ? "M" : "L"} ${round3(p.x)} ${round3(p.y)}`).join(" ");
|
|
5807
|
+
}
|
|
5808
|
+
function areaPath(points, baselineY) {
|
|
5809
|
+
if (points.length === 0) return "";
|
|
5810
|
+
const open = polylinePath(points);
|
|
5811
|
+
const first = points[0];
|
|
5812
|
+
const last = points[points.length - 1];
|
|
5813
|
+
return `${open} L ${round3(last.x)} ${round3(baselineY)} L ${round3(first.x)} ${round3(baselineY)} Z`;
|
|
5814
|
+
}
|
|
5815
|
+
|
|
5816
|
+
// src/doc/templates/chart/layout.ts
|
|
5817
|
+
function computeChartFrame(options) {
|
|
5818
|
+
const W = options.viewportWidth;
|
|
5819
|
+
const H = options.viewportHeight;
|
|
5820
|
+
const marginX = W * 0.06;
|
|
5821
|
+
const marginY = H * 0.06;
|
|
5822
|
+
const innerWidth = W - marginX * 2;
|
|
5823
|
+
let cursor = marginY;
|
|
5824
|
+
let titleBand;
|
|
5825
|
+
if (options.hasTitle) {
|
|
5826
|
+
titleBand = { x: marginX, y: cursor, width: innerWidth, height: options.titleFontSize * 2.2 };
|
|
5827
|
+
cursor = titleBand.y + titleBand.height;
|
|
5828
|
+
}
|
|
5829
|
+
let tableBand;
|
|
5830
|
+
let tableHeight = 0;
|
|
5831
|
+
if (options.tableRowCount > 0) {
|
|
5832
|
+
tableHeight = Math.min(H * 0.4, (options.tableRowCount + 1) * options.tableFontSize * 2.4);
|
|
5833
|
+
}
|
|
5834
|
+
const legendHeight = options.legendRows > 0 ? options.legendRows * options.legendRowHeight : 0;
|
|
5835
|
+
const chartArea = {
|
|
5836
|
+
x: marginX,
|
|
5837
|
+
y: cursor,
|
|
5838
|
+
width: innerWidth,
|
|
5839
|
+
height: Math.max(40, H - marginY - cursor - legendHeight - tableHeight)
|
|
5840
|
+
};
|
|
5841
|
+
cursor = chartArea.y + chartArea.height;
|
|
5842
|
+
let legendBand;
|
|
5843
|
+
if (legendHeight > 0) {
|
|
5844
|
+
legendBand = { x: marginX, y: cursor, width: innerWidth, height: legendHeight };
|
|
5845
|
+
cursor += legendHeight;
|
|
5846
|
+
}
|
|
5847
|
+
if (tableHeight > 0) {
|
|
5848
|
+
tableBand = { x: marginX, y: cursor, width: innerWidth, height: tableHeight };
|
|
5849
|
+
}
|
|
5850
|
+
const plot = {
|
|
5851
|
+
x: chartArea.x + options.leftGutter,
|
|
5852
|
+
y: chartArea.y,
|
|
5853
|
+
width: Math.max(20, chartArea.width - options.leftGutter),
|
|
5854
|
+
height: Math.max(20, chartArea.height - options.bottomGutter)
|
|
5855
|
+
};
|
|
5856
|
+
return { titleBand, chartArea, plot, legendBand, tableBand };
|
|
5857
|
+
}
|
|
5858
|
+
function stackSeries(series) {
|
|
5859
|
+
const rowCount = series.reduce((max, s) => Math.max(max, s.values.length), 0);
|
|
5860
|
+
const positive = new Array(rowCount).fill(0);
|
|
5861
|
+
const negative = new Array(rowCount).fill(0);
|
|
5862
|
+
return series.map(
|
|
5863
|
+
(s) => Array.from({ length: rowCount }, (_, row) => {
|
|
5864
|
+
const value = s.values[row];
|
|
5865
|
+
if (value === null || value === void 0 || !Number.isFinite(value)) {
|
|
5866
|
+
return { y0: positive[row], y1: positive[row] };
|
|
5867
|
+
}
|
|
5868
|
+
if (value >= 0) {
|
|
5869
|
+
const y0 = positive[row];
|
|
5870
|
+
positive[row] += value;
|
|
5871
|
+
return { y0, y1: positive[row] };
|
|
5872
|
+
}
|
|
5873
|
+
const y1 = negative[row];
|
|
5874
|
+
negative[row] += value;
|
|
5875
|
+
return { y0: negative[row], y1 };
|
|
5876
|
+
})
|
|
5877
|
+
);
|
|
5878
|
+
}
|
|
5879
|
+
function layoutLegend(labels, fontSize, maxWidth) {
|
|
5880
|
+
const swatchSize = fontSize * 0.9;
|
|
5881
|
+
const gap = fontSize * 1.6;
|
|
5882
|
+
const rowHeight = fontSize * 2;
|
|
5883
|
+
const widths = labels.map(
|
|
5884
|
+
(label) => swatchSize + fontSize * 0.5 + label.length * fontSize * 0.58
|
|
5885
|
+
);
|
|
5886
|
+
const rows = [];
|
|
5887
|
+
let current = [];
|
|
5888
|
+
let currentWidth = 0;
|
|
5889
|
+
widths.forEach((width, index) => {
|
|
5890
|
+
const extra = current.length > 0 ? gap : 0;
|
|
5891
|
+
if (current.length > 0 && currentWidth + extra + width > maxWidth) {
|
|
5892
|
+
rows.push(current);
|
|
5893
|
+
current = [];
|
|
5894
|
+
currentWidth = 0;
|
|
5895
|
+
}
|
|
5896
|
+
current.push(index);
|
|
5897
|
+
currentWidth += (current.length > 1 ? gap : 0) + width;
|
|
5898
|
+
});
|
|
5899
|
+
if (current.length > 0) rows.push(current);
|
|
5900
|
+
const items = [];
|
|
5901
|
+
rows.forEach((rowIndexes, rowIndex) => {
|
|
5902
|
+
const rowWidth = rowIndexes.reduce(
|
|
5903
|
+
(sum, index, i) => sum + widths[index] + (i > 0 ? gap : 0),
|
|
5904
|
+
0
|
|
5905
|
+
);
|
|
5906
|
+
let x = Math.max(0, (maxWidth - rowWidth) / 2);
|
|
5907
|
+
rowIndexes.forEach((index) => {
|
|
5908
|
+
items.push({ label: labels[index], index, x, y: rowIndex * rowHeight, swatchSize });
|
|
5909
|
+
x += widths[index] + gap;
|
|
5910
|
+
});
|
|
5911
|
+
});
|
|
5912
|
+
return { items, rows: rows.length, rowHeight };
|
|
5913
|
+
}
|
|
5914
|
+
function categoryLabelStep(categoryCount, axisLength, fontSize) {
|
|
5915
|
+
if (categoryCount <= 1) return 1;
|
|
5916
|
+
const minPitch = fontSize * 6;
|
|
5917
|
+
const pitch = axisLength / categoryCount;
|
|
5918
|
+
return Math.max(1, Math.ceil(minPitch / Math.max(1, pitch)));
|
|
5919
|
+
}
|
|
5920
|
+
|
|
5921
|
+
// src/doc/templates/chart/render.ts
|
|
5922
|
+
var TEMPLATE_ID = {
|
|
5923
|
+
bar: "barChart",
|
|
5924
|
+
column: "columnChart",
|
|
5925
|
+
pie: "pieChart",
|
|
5926
|
+
donut: "donutChart",
|
|
5927
|
+
line: "lineChart",
|
|
5928
|
+
area: "areaChart",
|
|
5929
|
+
scatter: "scatterChart"
|
|
5930
|
+
};
|
|
5931
|
+
function renderChart(kind, input, context) {
|
|
5932
|
+
const table = resolveTable(input, context);
|
|
5933
|
+
const data = table ? buildChartData(table, input) : null;
|
|
5934
|
+
if (!table || !data) return contentFallback(input, context);
|
|
5935
|
+
const { theme, viewport } = context;
|
|
5936
|
+
const templateId = TEMPLATE_ID[kind];
|
|
5937
|
+
const title = (input.title ?? "").trim();
|
|
5938
|
+
const titleFontSize = themedFontSize(44, context, true);
|
|
5939
|
+
const axisFontSize = themedFontSize(20, context, false);
|
|
5940
|
+
const labelFontSize = themedFontSize(22, context, false);
|
|
5941
|
+
const legendFontSize = themedFontSize(22, context, false);
|
|
5942
|
+
const tableFontSize = themedFontSize(20, context, false);
|
|
5943
|
+
const bodyFont = getThemeFont(context, "body");
|
|
5944
|
+
const isRadial = kind === "pie" || kind === "donut";
|
|
5945
|
+
const legendLabels = resolveLegendLabels(kind, input, data);
|
|
5946
|
+
const colors = seriesColors(
|
|
5947
|
+
context,
|
|
5948
|
+
input.colorScheme,
|
|
5949
|
+
isRadial ? data.labels.length : data.series.length
|
|
5950
|
+
);
|
|
5951
|
+
if (isRadial && !data.series[0].values.some((v) => v !== null && v > 0)) {
|
|
5952
|
+
return contentFallback(input, context);
|
|
5953
|
+
}
|
|
5954
|
+
const innerWidth = viewport.width * 0.88;
|
|
5955
|
+
const legend = legendLabels.length > 0 ? layoutLegend(legendLabels, legendFontSize, innerWidth) : null;
|
|
5956
|
+
const gutters = computeGutters(kind, data, input, context, axisFontSize, labelFontSize);
|
|
5957
|
+
const frame = computeChartFrame({
|
|
5958
|
+
viewportWidth: viewport.width,
|
|
5959
|
+
viewportHeight: viewport.height,
|
|
5960
|
+
hasTitle: title.length > 0,
|
|
5961
|
+
titleFontSize,
|
|
5962
|
+
leftGutter: gutters.left,
|
|
5963
|
+
bottomGutter: gutters.bottom,
|
|
5964
|
+
legendRows: legend?.rows ?? 0,
|
|
5965
|
+
legendRowHeight: legendFontSize * 2,
|
|
5966
|
+
tableRowCount: input.showTable === true ? table.rows.length : 0,
|
|
5967
|
+
tableFontSize
|
|
5968
|
+
});
|
|
5969
|
+
const entranceHint = getTemplateHint(context, templateId, "entrance", "staggered");
|
|
5970
|
+
const entrance = (index) => ({
|
|
5971
|
+
type: "fadeIn",
|
|
5972
|
+
duration: 0.6,
|
|
5973
|
+
delay: entranceHint === "subtle" ? 0.1 : Math.min(1.2, 0.2 + index * 0.08)
|
|
5974
|
+
});
|
|
5975
|
+
const paint = {
|
|
5976
|
+
kind,
|
|
5977
|
+
input,
|
|
5978
|
+
context,
|
|
5979
|
+
data,
|
|
5980
|
+
frame,
|
|
5981
|
+
colors,
|
|
5982
|
+
axisFontSize,
|
|
5983
|
+
labelFontSize,
|
|
5984
|
+
bodyFont,
|
|
5985
|
+
gridColor: withAlpha(theme.colors.text, 0.12),
|
|
5986
|
+
axisTextColor: theme.colors.textMuted,
|
|
5987
|
+
entrance
|
|
5988
|
+
};
|
|
5989
|
+
const layers = [createBackgroundLayer("bg", themedSurfaceGradient(context, 170))];
|
|
5990
|
+
if (title && frame.titleBand) {
|
|
5991
|
+
layers.push({
|
|
5992
|
+
type: "text",
|
|
5993
|
+
id: "title",
|
|
5994
|
+
content: {
|
|
5995
|
+
text: title,
|
|
5996
|
+
style: {
|
|
5997
|
+
fontSize: titleFontSize,
|
|
5998
|
+
fontFamily: getThemeFont(context, "title"),
|
|
5999
|
+
fontWeight: "bold",
|
|
6000
|
+
color: theme.colors.text,
|
|
6001
|
+
textAlign: "center",
|
|
6002
|
+
shadow: shouldUseShadow(context)
|
|
6003
|
+
}
|
|
6004
|
+
},
|
|
6005
|
+
position: {
|
|
6006
|
+
x: frame.titleBand.x + frame.titleBand.width / 2,
|
|
6007
|
+
y: frame.titleBand.y + frame.titleBand.height / 2,
|
|
6008
|
+
width: frame.titleBand.width,
|
|
6009
|
+
anchor: "center"
|
|
6010
|
+
},
|
|
6011
|
+
animation: { type: "fadeIn", duration: 0.8 }
|
|
6012
|
+
});
|
|
6013
|
+
}
|
|
6014
|
+
switch (kind) {
|
|
6015
|
+
case "bar":
|
|
6016
|
+
layers.push(...paintBars(paint));
|
|
6017
|
+
break;
|
|
6018
|
+
case "column":
|
|
6019
|
+
layers.push(...paintColumns(paint));
|
|
6020
|
+
break;
|
|
6021
|
+
case "pie":
|
|
6022
|
+
case "donut":
|
|
6023
|
+
layers.push(...paintRadial(paint));
|
|
6024
|
+
break;
|
|
6025
|
+
case "line":
|
|
6026
|
+
case "area":
|
|
6027
|
+
layers.push(...paintLines(paint));
|
|
6028
|
+
break;
|
|
6029
|
+
case "scatter":
|
|
6030
|
+
layers.push(...paintScatter(paint));
|
|
6031
|
+
break;
|
|
6032
|
+
}
|
|
6033
|
+
if (legend && frame.legendBand) {
|
|
6034
|
+
layers.push(...paintLegend(paint, legend, frame.legendBand, legendFontSize));
|
|
6035
|
+
}
|
|
6036
|
+
if (input.showTable === true && frame.tableBand) {
|
|
6037
|
+
layers.push(tableLayer(table, frame.tableBand, tableFontSize, input, context));
|
|
6038
|
+
}
|
|
6039
|
+
return layers;
|
|
5169
6040
|
}
|
|
5170
|
-
function
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
6041
|
+
function resolveTable(input, context) {
|
|
6042
|
+
if (Array.isArray(input.headers) && input.headers.length > 0 && Array.isArray(input.rows) && input.rows.length > 0) {
|
|
6043
|
+
return { headers: input.headers, rows: input.rows };
|
|
6044
|
+
}
|
|
6045
|
+
const contents = input.contents ?? context.block?.contents;
|
|
6046
|
+
return extractTableFromContents(contents);
|
|
5174
6047
|
}
|
|
5175
|
-
function
|
|
5176
|
-
|
|
6048
|
+
function contentFallback(input, context) {
|
|
6049
|
+
const fallbackInput = {
|
|
6050
|
+
...input,
|
|
6051
|
+
template: "content",
|
|
6052
|
+
title: input.title ?? context.block?.title ?? ""
|
|
6053
|
+
};
|
|
6054
|
+
return contentBlock(fallbackInput, context);
|
|
5177
6055
|
}
|
|
5178
|
-
function
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
for (const [key, raw] of Object.entries(params)) {
|
|
5184
|
-
const coerce = byKey?.get(key)?.coerce ?? "string";
|
|
5185
|
-
const { value, warning } = coerceValue(coerce, raw);
|
|
5186
|
-
if (warning) {
|
|
5187
|
-
warnings.push(`"${key}": ${warning}`);
|
|
5188
|
-
input[key] = raw;
|
|
5189
|
-
} else {
|
|
5190
|
-
input[key] = value;
|
|
5191
|
-
}
|
|
6056
|
+
function resolveLegendLabels(kind, input, data) {
|
|
6057
|
+
if (input.showLegend === false) return [];
|
|
6058
|
+
if (kind === "pie" || kind === "donut") return data.labels;
|
|
6059
|
+
if (data.series.length > 1 || input.showLegend === true) {
|
|
6060
|
+
return data.series.map((s) => s.name);
|
|
5192
6061
|
}
|
|
5193
|
-
return
|
|
6062
|
+
return [];
|
|
5194
6063
|
}
|
|
5195
|
-
function
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
6064
|
+
function seriesColors(context, schemeName, count) {
|
|
6065
|
+
const schemes = context.theme.colorSchemes;
|
|
6066
|
+
const names = Object.keys(schemes);
|
|
6067
|
+
const primary = resolveColorScheme(context, schemeName).accent;
|
|
6068
|
+
const palette = [primary];
|
|
6069
|
+
const start = schemeName && names.includes(schemeName) ? names.indexOf(schemeName) : 0;
|
|
6070
|
+
for (let i = 1; i < names.length && palette.length < count; i++) {
|
|
6071
|
+
const accent = schemes[names[(start + i) % names.length]].accent;
|
|
6072
|
+
if (!palette.some((c) => c.toLowerCase() === accent.toLowerCase())) palette.push(accent);
|
|
6073
|
+
}
|
|
6074
|
+
const distinct = palette.length;
|
|
6075
|
+
for (let i = 0; palette.length < count; i++) {
|
|
6076
|
+
palette.push(withAlpha(palette[i % distinct], 0.55));
|
|
6077
|
+
}
|
|
6078
|
+
return palette.slice(0, Math.max(1, count));
|
|
6079
|
+
}
|
|
6080
|
+
function computeGutters(kind, data, input, context, axisFontSize, labelFontSize) {
|
|
6081
|
+
if (kind === "pie" || kind === "donut") return { left: 0, bottom: 0 };
|
|
6082
|
+
if (kind === "bar") {
|
|
6083
|
+
const longest = data.labels.reduce((max, label) => Math.max(max, label.length), 0);
|
|
6084
|
+
const left = Math.min(
|
|
6085
|
+
context.viewport.width * 0.24,
|
|
6086
|
+
Math.max(labelFontSize * 3, longest * labelFontSize * 0.58 + 16)
|
|
6087
|
+
);
|
|
6088
|
+
return { left, bottom: axisFontSize * 2.4 };
|
|
5212
6089
|
}
|
|
6090
|
+
const ticks = valueTicks(kind, data, input);
|
|
6091
|
+
const longestTick = ticks.ticks.reduce(
|
|
6092
|
+
(max, tick) => Math.max(max, formatChartValue(tick, input.unit).length),
|
|
6093
|
+
0
|
|
6094
|
+
);
|
|
6095
|
+
return {
|
|
6096
|
+
left: Math.max(axisFontSize * 2.5, longestTick * axisFontSize * 0.6 + 16),
|
|
6097
|
+
bottom: labelFontSize * 2.6
|
|
6098
|
+
};
|
|
5213
6099
|
}
|
|
5214
|
-
function
|
|
5215
|
-
|
|
5216
|
-
if (
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
}
|
|
5220
|
-
|
|
5221
|
-
const parts = raw.split(",");
|
|
5222
|
-
if (parts.length === 2) {
|
|
5223
|
-
const lat = parseNumber(parts[0]);
|
|
5224
|
-
const lng = parseNumber(parts[1]);
|
|
5225
|
-
if (lat != null && lng != null) return { value: { lat, lng } };
|
|
6100
|
+
function valueTicks(kind, data, input) {
|
|
6101
|
+
let values;
|
|
6102
|
+
if ((kind === "bar" || kind === "column") && input.stacked === true) {
|
|
6103
|
+
const extents = stackSeries(data.series).flat();
|
|
6104
|
+
values = extents.flatMap((e) => [e.y0, e.y1]);
|
|
6105
|
+
} else {
|
|
6106
|
+
values = data.series.flatMap((s) => s.values.filter((v) => v !== null));
|
|
5226
6107
|
}
|
|
5227
|
-
|
|
6108
|
+
if (values.length === 0) values = [0, 1];
|
|
6109
|
+
let min = Math.min(...values);
|
|
6110
|
+
let max = Math.max(...values);
|
|
6111
|
+
if (kind !== "line" && kind !== "scatter") {
|
|
6112
|
+
min = Math.min(0, min);
|
|
6113
|
+
max = Math.max(0, max);
|
|
6114
|
+
}
|
|
6115
|
+
return niceTicks(min, max, 5);
|
|
5228
6116
|
}
|
|
5229
|
-
function
|
|
5230
|
-
const
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
6117
|
+
function paintValueAxisY(paint, scale, yFor) {
|
|
6118
|
+
const { frame, gridColor, axisTextColor, axisFontSize, bodyFont, input } = paint;
|
|
6119
|
+
const layers = [];
|
|
6120
|
+
scale.ticks.forEach((tick, index) => {
|
|
6121
|
+
const y = yFor(tick);
|
|
6122
|
+
layers.push({
|
|
6123
|
+
type: "shape",
|
|
6124
|
+
id: `grid-${index}`,
|
|
6125
|
+
content: {
|
|
6126
|
+
shape: "rect",
|
|
6127
|
+
fill: tick === 0 ? withAlpha(paint.context.theme.colors.text, 0.35) : gridColor
|
|
6128
|
+
},
|
|
6129
|
+
position: {
|
|
6130
|
+
x: frame.plot.x,
|
|
6131
|
+
y: y - (tick === 0 ? 1 : 0.5),
|
|
6132
|
+
width: frame.plot.width,
|
|
6133
|
+
height: tick === 0 ? 2 : 1
|
|
6134
|
+
}
|
|
6135
|
+
});
|
|
6136
|
+
layers.push({
|
|
6137
|
+
type: "text",
|
|
6138
|
+
id: `tick-${index}`,
|
|
6139
|
+
content: {
|
|
6140
|
+
text: formatChartValue(tick, input.unit),
|
|
6141
|
+
style: {
|
|
6142
|
+
fontSize: axisFontSize,
|
|
6143
|
+
fontFamily: bodyFont,
|
|
6144
|
+
color: axisTextColor,
|
|
6145
|
+
textAlign: "center"
|
|
6146
|
+
}
|
|
6147
|
+
},
|
|
6148
|
+
position: {
|
|
6149
|
+
x: frame.chartArea.x + (frame.plot.x - frame.chartArea.x) / 2,
|
|
6150
|
+
y,
|
|
6151
|
+
anchor: "center"
|
|
6152
|
+
}
|
|
6153
|
+
});
|
|
6154
|
+
});
|
|
6155
|
+
return layers;
|
|
5235
6156
|
}
|
|
5236
|
-
function
|
|
5237
|
-
|
|
6157
|
+
function paintCategoryAxisX(paint, xForCenter) {
|
|
6158
|
+
const { frame, data, labelFontSize, axisTextColor, bodyFont } = paint;
|
|
6159
|
+
const step = categoryLabelStep(data.labels.length, frame.plot.width, labelFontSize);
|
|
6160
|
+
const layers = [];
|
|
6161
|
+
data.labels.forEach((label, index) => {
|
|
6162
|
+
if (index % step !== 0 || !label) return;
|
|
6163
|
+
layers.push({
|
|
6164
|
+
type: "text",
|
|
6165
|
+
id: `cat-${index}`,
|
|
6166
|
+
content: {
|
|
6167
|
+
text: label,
|
|
6168
|
+
style: {
|
|
6169
|
+
fontSize: labelFontSize,
|
|
6170
|
+
fontFamily: bodyFont,
|
|
6171
|
+
color: axisTextColor,
|
|
6172
|
+
textAlign: "center"
|
|
6173
|
+
}
|
|
6174
|
+
},
|
|
6175
|
+
position: {
|
|
6176
|
+
x: xForCenter(index),
|
|
6177
|
+
y: frame.plot.y + frame.plot.height + labelFontSize * 1.2,
|
|
6178
|
+
anchor: "center"
|
|
6179
|
+
}
|
|
6180
|
+
});
|
|
6181
|
+
});
|
|
6182
|
+
return layers;
|
|
5238
6183
|
}
|
|
5239
|
-
function
|
|
5240
|
-
const
|
|
5241
|
-
const
|
|
5242
|
-
|
|
5243
|
-
const
|
|
5244
|
-
const
|
|
5245
|
-
const
|
|
5246
|
-
const
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
6184
|
+
function paintColumns(paint) {
|
|
6185
|
+
const { frame, data, colors, input, entrance } = paint;
|
|
6186
|
+
const scale = valueTicks("column", data, input);
|
|
6187
|
+
const yFor = (v) => frame.plot.y + frame.plot.height * (1 - (v - scale.niceMin) / (scale.niceMax - scale.niceMin));
|
|
6188
|
+
const layers = paintValueAxisY(paint, scale, yFor);
|
|
6189
|
+
const n = data.labels.length;
|
|
6190
|
+
const band = frame.plot.width / Math.max(1, n);
|
|
6191
|
+
const innerPad = band * 0.15;
|
|
6192
|
+
layers.push(...paintCategoryAxisX(paint, (i) => frame.plot.x + (i + 0.5) * band));
|
|
6193
|
+
const stacked = input.stacked === true && data.series.length > 1;
|
|
6194
|
+
const extents = stacked ? stackSeries(data.series) : null;
|
|
6195
|
+
const groupWidth = band - innerPad * 2;
|
|
6196
|
+
const barWidth = stacked ? groupWidth : Math.max(2, (groupWidth - 4 * (data.series.length - 1)) / data.series.length);
|
|
6197
|
+
data.series.forEach((series, s) => {
|
|
6198
|
+
series.values.forEach((value, i) => {
|
|
6199
|
+
if (i >= n) return;
|
|
6200
|
+
const extent = extents ? extents[s][i] : value === null ? null : { y0: Math.min(0, value), y1: Math.max(0, value) };
|
|
6201
|
+
if (!extent || extent.y0 === extent.y1) return;
|
|
6202
|
+
const top = yFor(extent.y1);
|
|
6203
|
+
const height = Math.max(1, yFor(extent.y0) - top);
|
|
6204
|
+
const x = stacked ? frame.plot.x + i * band + innerPad : frame.plot.x + i * band + innerPad + s * (barWidth + 4);
|
|
6205
|
+
layers.push({
|
|
6206
|
+
type: "shape",
|
|
6207
|
+
id: `mark-${s}-${i}`,
|
|
6208
|
+
content: { shape: "rect", fill: colors[s], borderRadius: stacked ? 0 : 3 },
|
|
6209
|
+
position: { x, y: top, width: barWidth, height },
|
|
6210
|
+
animation: entrance(i + s)
|
|
6211
|
+
});
|
|
6212
|
+
if (input.showValues === true && value !== null && !stacked) {
|
|
6213
|
+
layers.push(
|
|
6214
|
+
valueLabel(
|
|
6215
|
+
paint,
|
|
6216
|
+
`val-${s}-${i}`,
|
|
6217
|
+
value,
|
|
6218
|
+
x + barWidth / 2,
|
|
6219
|
+
value >= 0 ? top - paint.axisFontSize * 0.9 : top + height + paint.axisFontSize * 0.9
|
|
6220
|
+
)
|
|
6221
|
+
);
|
|
6222
|
+
}
|
|
6223
|
+
});
|
|
6224
|
+
});
|
|
6225
|
+
return layers;
|
|
6226
|
+
}
|
|
6227
|
+
function paintBars(paint) {
|
|
6228
|
+
const {
|
|
6229
|
+
frame,
|
|
6230
|
+
data,
|
|
6231
|
+
colors,
|
|
6232
|
+
input,
|
|
6233
|
+
entrance,
|
|
6234
|
+
labelFontSize,
|
|
6235
|
+
axisFontSize,
|
|
6236
|
+
bodyFont,
|
|
6237
|
+
axisTextColor,
|
|
6238
|
+
gridColor
|
|
6239
|
+
} = paint;
|
|
6240
|
+
const scale = valueTicks("bar", data, input);
|
|
6241
|
+
const labelGap = axisFontSize * 0.6;
|
|
6242
|
+
const labelWidthPx = (value) => formatChartValue(value, input.unit).length * axisFontSize * 0.58;
|
|
6243
|
+
const showValueLabels = input.showValues === true && !(input.stacked === true);
|
|
6244
|
+
const reservedForLabels = showValueLabels ? data.series.flatMap((series) => series.values).filter((v) => v !== null).reduce((max, v) => Math.max(max, labelWidthPx(v)), 0) + labelGap : 0;
|
|
6245
|
+
const axisWidth = Math.max(20, frame.plot.width - reservedForLabels);
|
|
6246
|
+
const xFor = (v) => frame.plot.x + axisWidth * ((v - scale.niceMin) / (scale.niceMax - scale.niceMin));
|
|
6247
|
+
const layers = [];
|
|
6248
|
+
scale.ticks.forEach((tick, index) => {
|
|
6249
|
+
const x = xFor(tick);
|
|
6250
|
+
layers.push({
|
|
6251
|
+
type: "shape",
|
|
6252
|
+
id: `grid-${index}`,
|
|
6253
|
+
content: {
|
|
6254
|
+
shape: "rect",
|
|
6255
|
+
fill: tick === 0 ? withAlpha(paint.context.theme.colors.text, 0.35) : gridColor
|
|
6256
|
+
},
|
|
6257
|
+
position: {
|
|
6258
|
+
x: x - (tick === 0 ? 1 : 0.5),
|
|
6259
|
+
y: frame.plot.y,
|
|
6260
|
+
width: tick === 0 ? 2 : 1,
|
|
6261
|
+
height: frame.plot.height
|
|
6262
|
+
}
|
|
6263
|
+
});
|
|
6264
|
+
layers.push({
|
|
6265
|
+
type: "text",
|
|
6266
|
+
id: `tick-${index}`,
|
|
6267
|
+
content: {
|
|
6268
|
+
text: formatChartValue(tick, input.unit),
|
|
6269
|
+
style: {
|
|
6270
|
+
fontSize: axisFontSize,
|
|
6271
|
+
fontFamily: bodyFont,
|
|
6272
|
+
color: axisTextColor,
|
|
6273
|
+
textAlign: "center"
|
|
6274
|
+
}
|
|
6275
|
+
},
|
|
6276
|
+
position: { x, y: frame.plot.y + frame.plot.height + axisFontSize * 1.2, anchor: "center" }
|
|
6277
|
+
});
|
|
6278
|
+
});
|
|
6279
|
+
const n = data.labels.length;
|
|
6280
|
+
const band = frame.plot.height / Math.max(1, n);
|
|
6281
|
+
const innerPad = band * 0.15;
|
|
6282
|
+
const stacked = input.stacked === true && data.series.length > 1;
|
|
6283
|
+
const extents = stacked ? stackSeries(data.series) : null;
|
|
6284
|
+
const groupHeight = band - innerPad * 2;
|
|
6285
|
+
const barHeight = stacked ? groupHeight : Math.max(2, (groupHeight - 4 * (data.series.length - 1)) / data.series.length);
|
|
6286
|
+
data.labels.forEach((label, i) => {
|
|
6287
|
+
if (!label) return;
|
|
6288
|
+
layers.push({
|
|
6289
|
+
type: "text",
|
|
6290
|
+
id: `cat-${i}`,
|
|
6291
|
+
content: {
|
|
6292
|
+
text: label,
|
|
6293
|
+
style: {
|
|
6294
|
+
fontSize: labelFontSize,
|
|
6295
|
+
fontFamily: bodyFont,
|
|
6296
|
+
color: axisTextColor,
|
|
6297
|
+
textAlign: "center"
|
|
6298
|
+
}
|
|
6299
|
+
},
|
|
6300
|
+
position: {
|
|
6301
|
+
x: frame.chartArea.x + (frame.plot.x - frame.chartArea.x) / 2,
|
|
6302
|
+
y: frame.plot.y + (i + 0.5) * band,
|
|
6303
|
+
anchor: "center"
|
|
6304
|
+
}
|
|
6305
|
+
});
|
|
6306
|
+
});
|
|
6307
|
+
data.series.forEach((series, s) => {
|
|
6308
|
+
series.values.forEach((value, i) => {
|
|
6309
|
+
if (i >= n) return;
|
|
6310
|
+
const extent = extents ? extents[s][i] : value === null ? null : { y0: Math.min(0, value), y1: Math.max(0, value) };
|
|
6311
|
+
if (!extent || extent.y0 === extent.y1) return;
|
|
6312
|
+
const left = xFor(extent.y0);
|
|
6313
|
+
const width = Math.max(1, xFor(extent.y1) - left);
|
|
6314
|
+
const y = stacked ? frame.plot.y + i * band + innerPad : frame.plot.y + i * band + innerPad + s * (barHeight + 4);
|
|
6315
|
+
layers.push({
|
|
6316
|
+
type: "shape",
|
|
6317
|
+
id: `mark-${s}-${i}`,
|
|
6318
|
+
content: { shape: "rect", fill: colors[s], borderRadius: stacked ? 0 : 3 },
|
|
6319
|
+
position: { x: left, y, width, height: barHeight },
|
|
6320
|
+
animation: entrance(i + s)
|
|
6321
|
+
});
|
|
6322
|
+
if (showValueLabels && value !== null) {
|
|
6323
|
+
const half = labelWidthPx(value) / 2;
|
|
6324
|
+
const labelX = value >= 0 ? left + width + labelGap + half : left - labelGap - half;
|
|
6325
|
+
layers.push(valueLabel(paint, `val-${s}-${i}`, value, labelX, y + barHeight / 2));
|
|
6326
|
+
}
|
|
6327
|
+
});
|
|
6328
|
+
});
|
|
6329
|
+
return layers;
|
|
6330
|
+
}
|
|
6331
|
+
function paintRadial(paint) {
|
|
6332
|
+
const { kind, frame, data, colors, input, entrance, context } = paint;
|
|
6333
|
+
const slices = data.labels.map((label, index) => ({ label, value: data.series[0].values[index], index })).filter(
|
|
6334
|
+
(s) => s.value !== null && Number.isFinite(s.value) && s.value > 0
|
|
6335
|
+
);
|
|
6336
|
+
const total = slices.reduce((sum, s) => sum + s.value, 0);
|
|
6337
|
+
const cx = frame.chartArea.x + frame.chartArea.width / 2;
|
|
6338
|
+
const cy = frame.chartArea.y + frame.chartArea.height / 2;
|
|
6339
|
+
const rOuter = Math.min(frame.chartArea.width, frame.chartArea.height) * 0.42;
|
|
6340
|
+
const rInner = kind === "donut" ? rOuter * 0.55 : 0;
|
|
6341
|
+
const layers = [];
|
|
6342
|
+
let startDeg = 0;
|
|
6343
|
+
slices.forEach((slice, order) => {
|
|
6344
|
+
const sweep = slice.value / total * 360;
|
|
6345
|
+
layers.push({
|
|
6346
|
+
type: "path",
|
|
6347
|
+
id: `mark-${slice.index}`,
|
|
6348
|
+
content: {
|
|
6349
|
+
d: arcPath(cx, cy, rOuter, rInner, startDeg, startDeg + sweep),
|
|
6350
|
+
fill: colors[slice.index % colors.length],
|
|
6351
|
+
stroke: context.theme.colors.background,
|
|
6352
|
+
strokeWidth: 2
|
|
6353
|
+
},
|
|
6354
|
+
position: { x: cx - rOuter, y: cy - rOuter, width: rOuter * 2, height: rOuter * 2 },
|
|
6355
|
+
animation: entrance(order)
|
|
6356
|
+
});
|
|
6357
|
+
if (input.showValues === true && sweep >= 14) {
|
|
6358
|
+
const mid = startDeg + sweep / 2;
|
|
6359
|
+
const rad = (mid - 90) * Math.PI / 180;
|
|
6360
|
+
const rLabel = rInner > 0 ? (rInner + rOuter) / 2 : rOuter * 0.68;
|
|
6361
|
+
const pctText = `${Math.round(slice.value / total * 100)}%`;
|
|
6362
|
+
layers.push({
|
|
6363
|
+
type: "text",
|
|
6364
|
+
id: `val-${slice.index}`,
|
|
6365
|
+
content: {
|
|
6366
|
+
text: pctText,
|
|
6367
|
+
style: {
|
|
6368
|
+
fontSize: paint.axisFontSize,
|
|
6369
|
+
fontFamily: paint.bodyFont,
|
|
6370
|
+
fontWeight: "bold",
|
|
6371
|
+
color: pickContrastingText(colors[slice.index % colors.length]),
|
|
6372
|
+
textAlign: "center"
|
|
6373
|
+
}
|
|
6374
|
+
},
|
|
6375
|
+
position: {
|
|
6376
|
+
x: cx + rLabel * Math.cos(rad),
|
|
6377
|
+
y: cy + rLabel * Math.sin(rad),
|
|
6378
|
+
anchor: "center"
|
|
6379
|
+
},
|
|
6380
|
+
animation: entrance(order)
|
|
5257
6381
|
});
|
|
5258
|
-
continue;
|
|
5259
6382
|
}
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
6383
|
+
startDeg += sweep;
|
|
6384
|
+
});
|
|
6385
|
+
return layers;
|
|
6386
|
+
}
|
|
6387
|
+
function paintLines(paint) {
|
|
6388
|
+
const { kind, frame, data, colors, input, entrance } = paint;
|
|
6389
|
+
const scale = valueTicks(kind, data, input);
|
|
6390
|
+
const yFor = (v) => frame.plot.y + frame.plot.height * (1 - (v - scale.niceMin) / (scale.niceMax - scale.niceMin));
|
|
6391
|
+
const n = data.labels.length;
|
|
6392
|
+
const band = frame.plot.width / Math.max(1, n);
|
|
6393
|
+
const xForCenter = (i) => frame.plot.x + (i + 0.5) * band;
|
|
6394
|
+
const layers = paintValueAxisY(paint, scale, yFor);
|
|
6395
|
+
layers.push(...paintCategoryAxisX(paint, xForCenter));
|
|
6396
|
+
const baselineY = yFor(Math.max(scale.niceMin, Math.min(scale.niceMax, 0)));
|
|
6397
|
+
const showPoints = n <= 30;
|
|
6398
|
+
data.series.forEach((series, s) => {
|
|
6399
|
+
const segments = [];
|
|
6400
|
+
let current = [];
|
|
6401
|
+
series.values.forEach((value, i) => {
|
|
6402
|
+
if (i >= n) return;
|
|
6403
|
+
if (value === null) {
|
|
6404
|
+
if (current.length > 0) segments.push(current);
|
|
6405
|
+
current = [];
|
|
6406
|
+
return;
|
|
6407
|
+
}
|
|
6408
|
+
current.push({ x: xForCenter(i), y: yFor(value) });
|
|
6409
|
+
});
|
|
6410
|
+
if (current.length > 0) segments.push(current);
|
|
6411
|
+
segments.forEach((segment, seg) => {
|
|
6412
|
+
if (kind === "area" && segment.length > 1) {
|
|
6413
|
+
layers.push({
|
|
6414
|
+
type: "path",
|
|
6415
|
+
id: `area-${s}-${seg}`,
|
|
6416
|
+
content: {
|
|
6417
|
+
d: areaPath(segment, baselineY),
|
|
6418
|
+
fill: colors[s],
|
|
6419
|
+
fillOpacity: 0.25,
|
|
6420
|
+
stroke: "none",
|
|
6421
|
+
strokeWidth: 0
|
|
6422
|
+
},
|
|
6423
|
+
position: {
|
|
6424
|
+
x: frame.plot.x,
|
|
6425
|
+
y: frame.plot.y,
|
|
6426
|
+
width: frame.plot.width,
|
|
6427
|
+
height: frame.plot.height
|
|
6428
|
+
},
|
|
6429
|
+
animation: entrance(s)
|
|
5267
6430
|
});
|
|
5268
6431
|
}
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
6432
|
+
if (segment.length > 1) {
|
|
6433
|
+
layers.push({
|
|
6434
|
+
type: "path",
|
|
6435
|
+
id: `line-${s}-${seg}`,
|
|
6436
|
+
content: { d: polylinePath(segment), stroke: colors[s], strokeWidth: 4, fill: "none" },
|
|
6437
|
+
position: {
|
|
6438
|
+
x: frame.plot.x,
|
|
6439
|
+
y: frame.plot.y,
|
|
6440
|
+
width: frame.plot.width,
|
|
6441
|
+
height: frame.plot.height
|
|
6442
|
+
},
|
|
6443
|
+
animation: entrance(s)
|
|
6444
|
+
});
|
|
5273
6445
|
}
|
|
6446
|
+
});
|
|
6447
|
+
if (showPoints) {
|
|
6448
|
+
series.values.forEach((value, i) => {
|
|
6449
|
+
if (i >= n || value === null) return;
|
|
6450
|
+
const r = 5;
|
|
6451
|
+
layers.push({
|
|
6452
|
+
type: "shape",
|
|
6453
|
+
id: `pt-${s}-${i}`,
|
|
6454
|
+
content: { shape: "circle", fill: colors[s] },
|
|
6455
|
+
position: { x: xForCenter(i) - r, y: yFor(value) - r, width: r * 2, height: r * 2 },
|
|
6456
|
+
animation: entrance(s)
|
|
6457
|
+
});
|
|
6458
|
+
if (input.showValues === true) {
|
|
6459
|
+
layers.push(
|
|
6460
|
+
valueLabel(
|
|
6461
|
+
paint,
|
|
6462
|
+
`val-${s}-${i}`,
|
|
6463
|
+
value,
|
|
6464
|
+
xForCenter(i),
|
|
6465
|
+
yFor(value) - paint.axisFontSize
|
|
6466
|
+
)
|
|
6467
|
+
);
|
|
6468
|
+
}
|
|
6469
|
+
});
|
|
5274
6470
|
}
|
|
5275
|
-
}
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
6471
|
+
});
|
|
6472
|
+
return layers;
|
|
6473
|
+
}
|
|
6474
|
+
function paintScatter(paint) {
|
|
6475
|
+
const { frame, data, colors, input, entrance } = paint;
|
|
6476
|
+
const scale = valueTicks("scatter", data, input);
|
|
6477
|
+
const yFor = (v) => frame.plot.y + frame.plot.height * (1 - (v - scale.niceMin) / (scale.niceMax - scale.niceMin));
|
|
6478
|
+
const layers = paintValueAxisY(paint, scale, yFor);
|
|
6479
|
+
const numericCount = data.labelNumbers.filter((v) => v !== null).length;
|
|
6480
|
+
const numericX = data.labels.length > 0 && numericCount / data.labels.length >= 0.6;
|
|
6481
|
+
let xFor;
|
|
6482
|
+
if (numericX) {
|
|
6483
|
+
const xs = data.labelNumbers.filter((v) => v !== null);
|
|
6484
|
+
const xScale = niceTicks(Math.min(...xs), Math.max(...xs), 5);
|
|
6485
|
+
const span = xScale.niceMax - xScale.niceMin;
|
|
6486
|
+
xFor = (i) => {
|
|
6487
|
+
const v = data.labelNumbers[i];
|
|
6488
|
+
return v === null ? null : frame.plot.x + frame.plot.width * ((v - xScale.niceMin) / span);
|
|
6489
|
+
};
|
|
6490
|
+
xScale.ticks.forEach((tick, index) => {
|
|
6491
|
+
const x = frame.plot.x + frame.plot.width * ((tick - xScale.niceMin) / span);
|
|
6492
|
+
layers.push({
|
|
6493
|
+
type: "shape",
|
|
6494
|
+
id: `xgrid-${index}`,
|
|
6495
|
+
content: { shape: "rect", fill: paint.gridColor },
|
|
6496
|
+
position: { x: x - 0.5, y: frame.plot.y, width: 1, height: frame.plot.height }
|
|
6497
|
+
});
|
|
6498
|
+
layers.push({
|
|
6499
|
+
type: "text",
|
|
6500
|
+
id: `xtick-${index}`,
|
|
6501
|
+
content: {
|
|
6502
|
+
text: formatChartValue(tick),
|
|
6503
|
+
style: {
|
|
6504
|
+
fontSize: paint.axisFontSize,
|
|
6505
|
+
fontFamily: paint.bodyFont,
|
|
6506
|
+
color: paint.axisTextColor,
|
|
6507
|
+
textAlign: "center"
|
|
6508
|
+
}
|
|
6509
|
+
},
|
|
6510
|
+
position: {
|
|
6511
|
+
x,
|
|
6512
|
+
y: frame.plot.y + frame.plot.height + paint.axisFontSize * 1.2,
|
|
6513
|
+
anchor: "center"
|
|
6514
|
+
}
|
|
6515
|
+
});
|
|
6516
|
+
});
|
|
6517
|
+
} else {
|
|
6518
|
+
const band = frame.plot.width / Math.max(1, data.labels.length);
|
|
6519
|
+
xFor = (i) => frame.plot.x + (i + 0.5) * band;
|
|
6520
|
+
layers.push(...paintCategoryAxisX(paint, (i) => frame.plot.x + (i + 0.5) * band));
|
|
6521
|
+
}
|
|
6522
|
+
const r = 7;
|
|
6523
|
+
data.series.forEach((series, s) => {
|
|
6524
|
+
series.values.forEach((value, i) => {
|
|
6525
|
+
if (value === null) return;
|
|
6526
|
+
const x = xFor(i);
|
|
6527
|
+
if (x === null) return;
|
|
6528
|
+
layers.push({
|
|
6529
|
+
type: "shape",
|
|
6530
|
+
id: `mark-${s}-${i}`,
|
|
6531
|
+
content: { shape: "circle", fill: colors[s], fillOpacity: 0.85 },
|
|
6532
|
+
position: { x: x - r, y: yFor(value) - r, width: r * 2, height: r * 2 },
|
|
6533
|
+
animation: entrance(Math.floor(i / 3) + s)
|
|
5282
6534
|
});
|
|
5283
|
-
}
|
|
5284
|
-
}
|
|
5285
|
-
return
|
|
6535
|
+
});
|
|
6536
|
+
});
|
|
6537
|
+
return layers;
|
|
5286
6538
|
}
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
const { layout } = context;
|
|
5293
|
-
const title = block.title ?? block.id ?? "";
|
|
5294
|
-
const body = extractBodyText(block);
|
|
5295
|
-
const layers = [
|
|
5296
|
-
{
|
|
5297
|
-
type: "shape",
|
|
5298
|
-
id: "fallback-bg",
|
|
5299
|
-
content: { shape: "rect", fill: colors.bg },
|
|
5300
|
-
position: { x: 0, y: 0, width: "100%", height: "100%" }
|
|
5301
|
-
}
|
|
5302
|
-
];
|
|
5303
|
-
if (title) {
|
|
6539
|
+
function paintLegend(paint, legend, band, fontSize) {
|
|
6540
|
+
const layers = [];
|
|
6541
|
+
legend.items.forEach((item, index) => {
|
|
6542
|
+
const color = paint.colors[item.index % paint.colors.length];
|
|
6543
|
+
const rowCenterY = band.y + item.y + legend.rowHeight / 2;
|
|
5304
6544
|
layers.push({
|
|
5305
|
-
type: "
|
|
5306
|
-
id:
|
|
5307
|
-
content: {
|
|
5308
|
-
text: title,
|
|
5309
|
-
style: {
|
|
5310
|
-
fontSize: themedFontSize(56, context, true),
|
|
5311
|
-
fontFamily: getThemeFont(context, "title"),
|
|
5312
|
-
fontWeight: "bold",
|
|
5313
|
-
color: colors.text,
|
|
5314
|
-
textAlign: "center"
|
|
5315
|
-
}
|
|
5316
|
-
},
|
|
6545
|
+
type: "shape",
|
|
6546
|
+
id: `legend-swatch-${index}`,
|
|
6547
|
+
content: { shape: "rect", fill: color, borderRadius: 3 },
|
|
5317
6548
|
position: {
|
|
5318
|
-
x:
|
|
5319
|
-
y:
|
|
5320
|
-
|
|
5321
|
-
|
|
6549
|
+
x: band.x + item.x,
|
|
6550
|
+
y: rowCenterY - item.swatchSize / 2,
|
|
6551
|
+
width: item.swatchSize,
|
|
6552
|
+
height: item.swatchSize
|
|
5322
6553
|
}
|
|
5323
6554
|
});
|
|
5324
|
-
}
|
|
5325
|
-
if (body) {
|
|
5326
6555
|
layers.push({
|
|
5327
6556
|
type: "text",
|
|
5328
|
-
id:
|
|
6557
|
+
id: `legend-label-${index}`,
|
|
5329
6558
|
content: {
|
|
5330
|
-
text:
|
|
5331
|
-
style: {
|
|
5332
|
-
fontSize: themedFontSize(28, context),
|
|
5333
|
-
fontFamily: getThemeFont(context, "body"),
|
|
5334
|
-
color: colors.text,
|
|
5335
|
-
textAlign: "center"
|
|
5336
|
-
}
|
|
6559
|
+
text: item.label,
|
|
6560
|
+
style: { fontSize, fontFamily: paint.bodyFont, color: paint.context.theme.colors.text }
|
|
5337
6561
|
},
|
|
5338
6562
|
position: {
|
|
5339
|
-
x:
|
|
5340
|
-
y:
|
|
5341
|
-
anchor: "center",
|
|
5342
|
-
width: layout.maxTextWidth
|
|
6563
|
+
x: band.x + item.x + item.swatchSize + fontSize * 0.5,
|
|
6564
|
+
y: rowCenterY - fontSize * 0.62
|
|
5343
6565
|
}
|
|
5344
6566
|
});
|
|
5345
|
-
}
|
|
5346
|
-
layers
|
|
6567
|
+
});
|
|
6568
|
+
return layers;
|
|
6569
|
+
}
|
|
6570
|
+
function valueLabel(paint, id, value, x, y) {
|
|
6571
|
+
return {
|
|
5347
6572
|
type: "text",
|
|
5348
|
-
id
|
|
6573
|
+
id,
|
|
5349
6574
|
content: {
|
|
5350
|
-
text:
|
|
6575
|
+
text: formatChartValue(value, paint.input.unit),
|
|
5351
6576
|
style: {
|
|
5352
|
-
fontSize:
|
|
5353
|
-
fontFamily:
|
|
5354
|
-
|
|
6577
|
+
fontSize: paint.axisFontSize,
|
|
6578
|
+
fontFamily: paint.bodyFont,
|
|
6579
|
+
fontWeight: "bold",
|
|
6580
|
+
color: paint.context.theme.colors.text,
|
|
5355
6581
|
textAlign: "center"
|
|
5356
6582
|
}
|
|
5357
6583
|
},
|
|
5358
|
-
position: { x
|
|
5359
|
-
});
|
|
5360
|
-
return layers;
|
|
5361
|
-
}
|
|
5362
|
-
function extractBodyText(block) {
|
|
5363
|
-
if (!block.contents || block.contents.length === 0) return "";
|
|
5364
|
-
const text = block.contents.map((node) => extractPlainText(node)).join("\n").trim();
|
|
5365
|
-
if (text.length <= MAX_BODY_CHARS) return text;
|
|
5366
|
-
return `${text.slice(0, MAX_BODY_CHARS).trimEnd()}\u2026`;
|
|
5367
|
-
}
|
|
5368
|
-
|
|
5369
|
-
// src/doc/asciiDiagram/types.ts
|
|
5370
|
-
var ASCII_CHAR_W = 14;
|
|
5371
|
-
var ASCII_CHAR_H = 28;
|
|
5372
|
-
|
|
5373
|
-
// src/doc/asciiDiagram/mapping.ts
|
|
5374
|
-
function asciiCellToCanvas(col, row) {
|
|
5375
|
-
return { x: col * ASCII_CHAR_W, y: row * ASCII_CHAR_H };
|
|
5376
|
-
}
|
|
5377
|
-
function canvasToAsciiCell(x, y) {
|
|
5378
|
-
return {
|
|
5379
|
-
col: Math.max(0, Math.round(x / ASCII_CHAR_W)),
|
|
5380
|
-
row: Math.max(0, Math.round(y / ASCII_CHAR_H))
|
|
6584
|
+
position: { x, y, anchor: "center" }
|
|
5381
6585
|
};
|
|
5382
6586
|
}
|
|
5383
|
-
function
|
|
5384
|
-
const
|
|
5385
|
-
|
|
5386
|
-
label: n.label,
|
|
5387
|
-
...asciiCellToCanvas(n.col, n.row),
|
|
5388
|
-
w: n.wCols * ASCII_CHAR_W,
|
|
5389
|
-
h: n.hRows * ASCII_CHAR_H,
|
|
5390
|
-
...n.containerId ? { container: n.containerId } : {}
|
|
5391
|
-
}));
|
|
5392
|
-
const anchors = inferEdgeAnchors(nodes, diagram.edges);
|
|
5393
|
-
const edges = diagram.edges.map((e, index) => ({
|
|
5394
|
-
source: e.source,
|
|
5395
|
-
target: e.target,
|
|
5396
|
-
...e.label ? { label: e.label } : {},
|
|
5397
|
-
...e.directed ? {} : { directed: false },
|
|
5398
|
-
...anchors[index],
|
|
5399
|
-
routing: "orthogonal"
|
|
5400
|
-
}));
|
|
5401
|
-
return { nodes, edges };
|
|
5402
|
-
}
|
|
5403
|
-
function inferEdgeAnchors(nodes, edges) {
|
|
5404
|
-
const nodeById = new Map(nodes.map((node) => [node.id, node]));
|
|
5405
|
-
const pairs = edges.map((edge) => {
|
|
5406
|
-
const source = nodeById.get(edge.source);
|
|
5407
|
-
const target = nodeById.get(edge.target);
|
|
5408
|
-
if (!source || !target) {
|
|
5409
|
-
return {
|
|
5410
|
-
sourceAnchor: { side: "right", offset: 0.5 },
|
|
5411
|
-
targetAnchor: { side: "left", offset: 0.5 }
|
|
5412
|
-
};
|
|
5413
|
-
}
|
|
5414
|
-
const sourceW = source.w ?? ASCII_CHAR_W * 12;
|
|
5415
|
-
const sourceH = source.h ?? ASCII_CHAR_H * 3;
|
|
5416
|
-
const targetW = target.w ?? ASCII_CHAR_W * 12;
|
|
5417
|
-
const targetH = target.h ?? ASCII_CHAR_H * 3;
|
|
5418
|
-
const dx = target.x + targetW / 2 - (source.x + sourceW / 2);
|
|
5419
|
-
const dy = target.y + targetH / 2 - (source.y + sourceH / 2);
|
|
5420
|
-
const horizontalDistance = Math.abs(dx) / Math.max(1, (sourceW + targetW) / 2);
|
|
5421
|
-
const verticalDistance = Math.abs(dy) / Math.max(1, (sourceH + targetH) / 2);
|
|
5422
|
-
if (verticalDistance >= horizontalDistance) {
|
|
5423
|
-
return dy >= 0 ? {
|
|
5424
|
-
sourceAnchor: { side: "bottom", offset: 0.5 },
|
|
5425
|
-
targetAnchor: { side: "top", offset: 0.5 }
|
|
5426
|
-
} : {
|
|
5427
|
-
sourceAnchor: { side: "top", offset: 0.5 },
|
|
5428
|
-
targetAnchor: { side: "bottom", offset: 0.5 }
|
|
5429
|
-
};
|
|
5430
|
-
}
|
|
5431
|
-
return dx >= 0 ? {
|
|
5432
|
-
sourceAnchor: { side: "right", offset: 0.5 },
|
|
5433
|
-
targetAnchor: { side: "left", offset: 0.5 }
|
|
5434
|
-
} : {
|
|
5435
|
-
sourceAnchor: { side: "left", offset: 0.5 },
|
|
5436
|
-
targetAnchor: { side: "right", offset: 0.5 }
|
|
5437
|
-
};
|
|
5438
|
-
});
|
|
5439
|
-
distributeOffsets(pairs, edges, nodes, "source");
|
|
5440
|
-
distributeOffsets(pairs, edges, nodes, "target");
|
|
5441
|
-
return pairs;
|
|
5442
|
-
}
|
|
5443
|
-
function distributeOffsets(pairs, edges, nodes, endpoint) {
|
|
5444
|
-
const nodeById = new Map(nodes.map((node) => [node.id, node]));
|
|
5445
|
-
const groups = /* @__PURE__ */ new Map();
|
|
5446
|
-
for (let index = 0; index < edges.length; index++) {
|
|
5447
|
-
const edge = edges[index];
|
|
5448
|
-
const anchor = endpoint === "source" ? pairs[index].sourceAnchor : pairs[index].targetAnchor;
|
|
5449
|
-
const nodeId = endpoint === "source" ? edge.source : edge.target;
|
|
5450
|
-
const key = `${nodeId}:${anchor.side}`;
|
|
5451
|
-
const group = groups.get(key) ?? [];
|
|
5452
|
-
group.push(index);
|
|
5453
|
-
groups.set(key, group);
|
|
5454
|
-
}
|
|
5455
|
-
for (const indexes of groups.values()) {
|
|
5456
|
-
if (indexes.length < 2) continue;
|
|
5457
|
-
const side = endpoint === "source" ? pairs[indexes[0]].sourceAnchor.side : pairs[indexes[0]].targetAnchor.side;
|
|
5458
|
-
indexes.sort((a, b) => {
|
|
5459
|
-
const oppositeA = nodeById.get(endpoint === "source" ? edges[a].target : edges[a].source);
|
|
5460
|
-
const oppositeB = nodeById.get(endpoint === "source" ? edges[b].target : edges[b].source);
|
|
5461
|
-
const axisA = side === "top" || side === "bottom" ? oppositeA?.x ?? 0 : oppositeA?.y ?? 0;
|
|
5462
|
-
const axisB = side === "top" || side === "bottom" ? oppositeB?.x ?? 0 : oppositeB?.y ?? 0;
|
|
5463
|
-
return axisA - axisB || a - b;
|
|
5464
|
-
});
|
|
5465
|
-
indexes.forEach((edgeIndex, rank) => {
|
|
5466
|
-
const anchor = endpoint === "source" ? pairs[edgeIndex].sourceAnchor : pairs[edgeIndex].targetAnchor;
|
|
5467
|
-
anchor.offset = (rank + 1) / (indexes.length + 1);
|
|
5468
|
-
});
|
|
5469
|
-
}
|
|
5470
|
-
}
|
|
5471
|
-
function asciiDiagramFromTemplateData(nodes, edges, options = {}) {
|
|
5472
|
-
const ids = new Set(nodes.map((n) => n.id));
|
|
5473
|
-
const outNodes = nodes.map((n) => {
|
|
5474
|
-
const { col, row } = canvasToAsciiCell(n.x, n.y);
|
|
5475
|
-
const labelLines = n.label.length > 0 ? n.label.split("\n") : [];
|
|
5476
|
-
const innerW = labelLines.reduce((w, l) => Math.max(w, l.length), 0);
|
|
5477
|
-
const wCols = typeof n.w === "number" ? Math.max(3, Math.round(n.w / ASCII_CHAR_W)) : innerW + 4;
|
|
5478
|
-
const hRows = typeof n.h === "number" ? Math.max(3, Math.round(n.h / ASCII_CHAR_H)) : labelLines.length + 2;
|
|
5479
|
-
return {
|
|
5480
|
-
id: n.id,
|
|
5481
|
-
label: n.label,
|
|
5482
|
-
col,
|
|
5483
|
-
row,
|
|
5484
|
-
wCols: Math.max(wCols, 3),
|
|
5485
|
-
hRows: Math.max(hRows, 3),
|
|
5486
|
-
...n.container && ids.has(n.container) && n.container !== n.id ? { containerId: n.container } : {}
|
|
5487
|
-
};
|
|
5488
|
-
});
|
|
6587
|
+
function tableLayer(table, band, fontSize, input, context) {
|
|
6588
|
+
const { theme } = context;
|
|
6589
|
+
const colors = resolveColorScheme(context, input.colorScheme);
|
|
5489
6590
|
return {
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
target: e.target,
|
|
5512
|
-
...e.type ? { label: e.type } : {}
|
|
5513
|
-
}));
|
|
5514
|
-
const diagram = asciiDiagramFromTemplateData(nodes, edges, options);
|
|
5515
|
-
diagram.warnings.push(...layout.warnings);
|
|
5516
|
-
return diagram;
|
|
5517
|
-
}
|
|
5518
|
-
|
|
5519
|
-
// src/doc/asciiTimeline/mapping.ts
|
|
5520
|
-
function asciiTimelineToTemplateData(timeline) {
|
|
5521
|
-
const globalStart = timeline.tracks.length > 0 ? Math.min(...timeline.tracks.map((track) => track.startColumn)) : 0;
|
|
5522
|
-
const globalEnd = timeline.tracks.length > 0 ? Math.max(...timeline.tracks.map((track) => track.endColumn)) : 1;
|
|
5523
|
-
const span = Math.max(1, globalEnd - globalStart);
|
|
5524
|
-
const tracks = timeline.tracks.map((track) => ({
|
|
5525
|
-
id: track.id,
|
|
5526
|
-
...track.label ? { label: track.label } : {},
|
|
5527
|
-
...track.endLabel ? { endLabel: track.endLabel } : {},
|
|
5528
|
-
events: track.events.map(
|
|
5529
|
-
(event) => ({
|
|
5530
|
-
id: event.id,
|
|
5531
|
-
label: event.label,
|
|
5532
|
-
...event.description ? { description: event.description } : {},
|
|
5533
|
-
position: clamp012((event.column - globalStart) / span),
|
|
5534
|
-
...event.side ? { side: event.side } : {},
|
|
5535
|
-
...event.descriptionSide ? { descriptionSide: event.descriptionSide } : {},
|
|
5536
|
-
...event.callout === false ? { callout: false } : {},
|
|
5537
|
-
...event.marker ? { marker: event.marker } : {}
|
|
5538
|
-
})
|
|
5539
|
-
)
|
|
5540
|
-
}));
|
|
5541
|
-
const links = timeline.links.map((link) => ({ ...link }));
|
|
5542
|
-
return { tracks, links };
|
|
5543
|
-
}
|
|
5544
|
-
function clamp012(value) {
|
|
5545
|
-
return Math.max(0, Math.min(1, value));
|
|
6591
|
+
type: "table",
|
|
6592
|
+
id: "table",
|
|
6593
|
+
content: {
|
|
6594
|
+
headers: table.headers,
|
|
6595
|
+
rows: table.rows,
|
|
6596
|
+
align: table.align,
|
|
6597
|
+
style: {
|
|
6598
|
+
headerBackground: colors.accent,
|
|
6599
|
+
headerColor: pickContrastingText(colors.accent),
|
|
6600
|
+
cellBackground: withAlpha(theme.colors.text, 0.04),
|
|
6601
|
+
cellColor: theme.colors.text,
|
|
6602
|
+
borderColor: withAlpha(theme.colors.text, 0.15),
|
|
6603
|
+
fontSize,
|
|
6604
|
+
fontFamily: getThemeFont(context, "body"),
|
|
6605
|
+
headerFontFamily: getThemeFont(context, "title"),
|
|
6606
|
+
borderRadius: 8
|
|
6607
|
+
}
|
|
6608
|
+
},
|
|
6609
|
+
position: { x: band.x, y: band.y, width: band.width, height: band.height },
|
|
6610
|
+
animation: { type: "fadeIn", duration: 1, delay: 0.4 }
|
|
6611
|
+
};
|
|
5546
6612
|
}
|
|
5547
6613
|
|
|
5548
|
-
// src/doc/
|
|
5549
|
-
function
|
|
5550
|
-
return
|
|
6614
|
+
// src/doc/templates/chartBlock.ts
|
|
6615
|
+
function barChart(input, context) {
|
|
6616
|
+
return renderChart("bar", input, context);
|
|
5551
6617
|
}
|
|
5552
|
-
function
|
|
5553
|
-
return
|
|
5554
|
-
id: node.id,
|
|
5555
|
-
label: node.label,
|
|
5556
|
-
...node.isDir ? { isDir: true } : {},
|
|
5557
|
-
...node.comment ? { comment: node.comment } : {},
|
|
5558
|
-
children: node.children.map(toItem)
|
|
5559
|
-
};
|
|
6618
|
+
function columnChart(input, context) {
|
|
6619
|
+
return renderChart("column", input, context);
|
|
5560
6620
|
}
|
|
5561
|
-
function
|
|
5562
|
-
|
|
5563
|
-
return { roots, style: options.style ?? "unicode", warnings: [] };
|
|
6621
|
+
function pieChart(input, context) {
|
|
6622
|
+
return renderChart("pie", input, context);
|
|
5564
6623
|
}
|
|
5565
|
-
function
|
|
5566
|
-
|
|
5567
|
-
return {
|
|
5568
|
-
id: typeof item.id === "string" && item.id ? item.id : "node",
|
|
5569
|
-
label: typeof item.label === "string" ? item.label : "",
|
|
5570
|
-
children,
|
|
5571
|
-
isDir: item.isDir === true || children.length > 0,
|
|
5572
|
-
...item.comment ? { comment: item.comment } : {}
|
|
5573
|
-
};
|
|
6624
|
+
function donutChart(input, context) {
|
|
6625
|
+
return renderChart("donut", input, context);
|
|
5574
6626
|
}
|
|
5575
|
-
function
|
|
5576
|
-
|
|
5577
|
-
const slugify2 = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "node";
|
|
5578
|
-
const idFor = (label) => {
|
|
5579
|
-
const base = slugify2(label.replace(/\/$/, ""));
|
|
5580
|
-
const count = usedIds.get(base) ?? 0;
|
|
5581
|
-
usedIds.set(base, count + 1);
|
|
5582
|
-
return count === 0 ? base : `${base}-${count + 1}`;
|
|
5583
|
-
};
|
|
5584
|
-
const itemToNode = (item) => {
|
|
5585
|
-
const labelNode = item.children.find((c) => c.type !== "list");
|
|
5586
|
-
const label = labelNode ? extractPlainText(labelNode).trim() : "";
|
|
5587
|
-
const childLists = item.children.filter((c) => c.type === "list");
|
|
5588
|
-
const children = [];
|
|
5589
|
-
for (const cl of childLists) for (const ci of cl.children) children.push(itemToNode(ci));
|
|
5590
|
-
return {
|
|
5591
|
-
id: idFor(label),
|
|
5592
|
-
label,
|
|
5593
|
-
children,
|
|
5594
|
-
isDir: label.endsWith("/") || children.length > 0
|
|
5595
|
-
};
|
|
5596
|
-
};
|
|
5597
|
-
return { roots: list.children.map(itemToNode), style: "unicode", warnings: [] };
|
|
6627
|
+
function lineChart(input, context) {
|
|
6628
|
+
return renderChart("line", input, context);
|
|
5598
6629
|
}
|
|
5599
|
-
function
|
|
5600
|
-
return
|
|
6630
|
+
function areaChart(input, context) {
|
|
6631
|
+
return renderChart("area", input, context);
|
|
5601
6632
|
}
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
var VIDEO_FILE_RE = /\.(?:webm|mp4|mov|m4v|ogv)(?:[?#].*)?$/i;
|
|
5605
|
-
function extractBodyPlainText(contents) {
|
|
5606
|
-
if (!contents || contents.length === 0) return "";
|
|
5607
|
-
return contents.filter((node) => !(node.type === "code" && node.lang?.trim().toLowerCase() === "mermaid")).map((n) => extractPlainText(n)).join("\n").trim();
|
|
6633
|
+
function scatterChart(input, context) {
|
|
6634
|
+
return renderChart("scatter", input, context);
|
|
5608
6635
|
}
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
6636
|
+
|
|
6637
|
+
// src/doc/templates/registry.ts
|
|
6638
|
+
var templateRegistry = {
|
|
6639
|
+
title: titleBlock,
|
|
6640
|
+
sectionHeader,
|
|
6641
|
+
content: contentBlock,
|
|
6642
|
+
statHighlight,
|
|
6643
|
+
quote: quoteBlock,
|
|
6644
|
+
factCard,
|
|
6645
|
+
twoColumn,
|
|
6646
|
+
dateEvent,
|
|
6647
|
+
imageWithCaption,
|
|
6648
|
+
leftFeature,
|
|
6649
|
+
rightFeature,
|
|
6650
|
+
map: mapBlock,
|
|
6651
|
+
fullBleedQuote,
|
|
6652
|
+
list: listBlock,
|
|
6653
|
+
photoGrid,
|
|
6654
|
+
definitionCard,
|
|
6655
|
+
comparisonBar,
|
|
6656
|
+
pullQuote,
|
|
6657
|
+
videoWithCaption,
|
|
6658
|
+
videoPullQuote,
|
|
6659
|
+
dataTable,
|
|
6660
|
+
barChart,
|
|
6661
|
+
columnChart,
|
|
6662
|
+
pieChart,
|
|
6663
|
+
donutChart,
|
|
6664
|
+
lineChart,
|
|
6665
|
+
areaChart,
|
|
6666
|
+
scatterChart,
|
|
6667
|
+
diagram: diagramBlock,
|
|
6668
|
+
tree: treeBlock,
|
|
6669
|
+
timeline: timelineBlock,
|
|
6670
|
+
layout: layoutBlock,
|
|
6671
|
+
drawing: drawingBlock
|
|
6672
|
+
};
|
|
6673
|
+
function buildRegistry(custom) {
|
|
6674
|
+
const merged = {
|
|
6675
|
+
...templateRegistry
|
|
6676
|
+
};
|
|
6677
|
+
if (custom) {
|
|
6678
|
+
for (const definition of custom) {
|
|
6679
|
+
if (merged[definition.name]) continue;
|
|
6680
|
+
merged[definition.name] = makeCustomTemplateFn(
|
|
6681
|
+
definition
|
|
6682
|
+
);
|
|
5618
6683
|
}
|
|
5619
6684
|
}
|
|
5620
|
-
return
|
|
6685
|
+
return merged;
|
|
5621
6686
|
}
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
6687
|
+
|
|
6688
|
+
// src/doc/utils/applyRenderStyle.ts
|
|
6689
|
+
var AMBIENT_VARIANTS = [
|
|
6690
|
+
{ type: "slowZoom", direction: "in" },
|
|
6691
|
+
{ type: "slowZoom", direction: "out" },
|
|
6692
|
+
{ type: "slowZoom", panDirection: "left" },
|
|
6693
|
+
{ type: "slowZoom", panDirection: "right" }
|
|
6694
|
+
];
|
|
6695
|
+
var MIN_AMBIENT_DURATION_SECONDS = 8;
|
|
6696
|
+
function isZeroish(v) {
|
|
6697
|
+
return v === 0 || v === "0" || v === "0%";
|
|
5626
6698
|
}
|
|
5627
|
-
function
|
|
5628
|
-
if (
|
|
5629
|
-
const
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
}
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
if (!node || typeof node !== "object") return;
|
|
5652
|
-
const n = node;
|
|
5653
|
-
if (n.type === "image" && typeof n.url === "string" && n.url) {
|
|
5654
|
-
found.push({ src: n.url, alt: typeof n.alt === "string" ? n.alt : "" });
|
|
5655
|
-
return;
|
|
6699
|
+
function isFullBleedCoverMedia(layer) {
|
|
6700
|
+
if (layer.type !== "image" && layer.type !== "video") return false;
|
|
6701
|
+
const { position } = layer;
|
|
6702
|
+
if (!isZeroish(position.x) || !isZeroish(position.y)) return false;
|
|
6703
|
+
if (position.width !== "100%" || position.height !== "100%") return false;
|
|
6704
|
+
const fit = layer.content.fit;
|
|
6705
|
+
return fit === void 0 || fit === "cover";
|
|
6706
|
+
}
|
|
6707
|
+
function themeWantsAmbientMotion(theme) {
|
|
6708
|
+
return theme.renderStyle.ambientMotion === true;
|
|
6709
|
+
}
|
|
6710
|
+
function applyRenderStyleToLayers(layers, block, theme) {
|
|
6711
|
+
const speed = theme.style.animationSpeed ?? 1;
|
|
6712
|
+
const ambient = themeWantsAmbientMotion(theme);
|
|
6713
|
+
if (speed === 1 && !ambient) return layers;
|
|
6714
|
+
return layers.map((layer) => {
|
|
6715
|
+
let animation = layer.animation;
|
|
6716
|
+
if (!animation && ambient && isFullBleedCoverMedia(layer)) {
|
|
6717
|
+
const rng = new SeededRandom(hashString(`${block.id}:${layer.id}`));
|
|
6718
|
+
const variant = rng.pickRequired(AMBIENT_VARIANTS);
|
|
6719
|
+
animation = {
|
|
6720
|
+
...variant,
|
|
6721
|
+
duration: Math.max(MIN_AMBIENT_DURATION_SECONDS, block.duration ?? 0)
|
|
6722
|
+
};
|
|
5656
6723
|
}
|
|
5657
|
-
if (
|
|
5658
|
-
|
|
6724
|
+
if (!animation || animation.type === "none") {
|
|
6725
|
+
return animation === layer.animation ? layer : { ...layer, animation };
|
|
5659
6726
|
}
|
|
5660
|
-
if (
|
|
5661
|
-
|
|
6727
|
+
if (speed !== 1) {
|
|
6728
|
+
animation = {
|
|
6729
|
+
...animation,
|
|
6730
|
+
...animation.duration != null ? { duration: animation.duration * speed } : {},
|
|
6731
|
+
...animation.delay != null ? { delay: animation.delay * speed } : {}
|
|
6732
|
+
};
|
|
5662
6733
|
}
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
if (found.length >= limit) break;
|
|
5666
|
-
walk(node);
|
|
5667
|
-
}
|
|
5668
|
-
return found;
|
|
5669
|
-
}
|
|
5670
|
-
function extractFirstImage(contents) {
|
|
5671
|
-
return extractImages(contents, 1)[0] ?? null;
|
|
6734
|
+
return animation === layer.animation ? layer : { ...layer, animation };
|
|
6735
|
+
});
|
|
5672
6736
|
}
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
const
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
if (typeof attrs?.src === "string" && attrs.src) return attrs.src;
|
|
5690
|
-
}
|
|
5691
|
-
const nested = nestedSource(node.children);
|
|
5692
|
-
if (nested) return nested;
|
|
5693
|
-
}
|
|
5694
|
-
return void 0;
|
|
5695
|
-
}
|
|
5696
|
-
function walk(node) {
|
|
5697
|
-
if (found.length >= limit || !node || typeof node !== "object") return;
|
|
5698
|
-
const n = node;
|
|
5699
|
-
if (n.type === "htmlElement" && n.tagName === "video") {
|
|
5700
|
-
const attrs = n.attributes;
|
|
5701
|
-
const src = attrs?.src || nestedSource(n.children);
|
|
5702
|
-
if (src) {
|
|
5703
|
-
add({
|
|
5704
|
-
src,
|
|
5705
|
-
...attrs?.poster ? { posterSrc: attrs.poster } : {},
|
|
5706
|
-
alt: attrs?.["aria-label"] || attrs?.title || attrs?.alt || ""
|
|
5707
|
-
});
|
|
5708
|
-
}
|
|
5709
|
-
} else if ((n.type === "link" || n.type === "image") && typeof n.url === "string" && VIDEO_FILE_RE.test(n.url)) {
|
|
5710
|
-
add({
|
|
5711
|
-
src: n.url,
|
|
5712
|
-
alt: typeof n.alt === "string" ? n.alt : extractPlainText(n).trim()
|
|
5713
|
-
});
|
|
6737
|
+
|
|
6738
|
+
// src/doc/utils/nearest.ts
|
|
6739
|
+
function levenshtein(a, b) {
|
|
6740
|
+
if (a === b) return 0;
|
|
6741
|
+
const m = a.length;
|
|
6742
|
+
const n = b.length;
|
|
6743
|
+
if (m === 0) return n;
|
|
6744
|
+
if (n === 0) return m;
|
|
6745
|
+
let prev = new Array(n + 1);
|
|
6746
|
+
let curr = new Array(n + 1);
|
|
6747
|
+
for (let j = 0; j <= n; j++) prev[j] = j;
|
|
6748
|
+
for (let i = 1; i <= m; i++) {
|
|
6749
|
+
curr[0] = i;
|
|
6750
|
+
for (let j = 1; j <= n; j++) {
|
|
6751
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
6752
|
+
curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost);
|
|
5714
6753
|
}
|
|
5715
|
-
|
|
5716
|
-
if (Array.isArray(n.htmlChildren)) n.htmlChildren.forEach(walk);
|
|
6754
|
+
[prev, curr] = [curr, prev];
|
|
5717
6755
|
}
|
|
5718
|
-
|
|
5719
|
-
return found;
|
|
5720
|
-
}
|
|
5721
|
-
function extractFirstEmbeddedVideo(contents) {
|
|
5722
|
-
return extractEmbeddedVideos(contents, 1)[0] ?? null;
|
|
6756
|
+
return prev[n];
|
|
5723
6757
|
}
|
|
5724
|
-
function
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
6758
|
+
function nearestName(input, candidates, options) {
|
|
6759
|
+
const maxDistance = options?.maxDistance ?? 2;
|
|
6760
|
+
let best;
|
|
6761
|
+
let bestDist = Infinity;
|
|
6762
|
+
const lower = input.toLowerCase();
|
|
6763
|
+
for (const candidate of candidates) {
|
|
6764
|
+
const dist = levenshtein(lower, candidate.toLowerCase());
|
|
6765
|
+
if (dist < bestDist) {
|
|
6766
|
+
bestDist = dist;
|
|
6767
|
+
best = candidate;
|
|
5734
6768
|
}
|
|
5735
6769
|
}
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
function extractBlockquoteText(contents) {
|
|
5739
|
-
if (!contents) return "";
|
|
5740
|
-
for (const node of contents) {
|
|
5741
|
-
if (node.type === "blockquote") return extractPlainText(node).trim();
|
|
6770
|
+
if (best && (bestDist <= maxDistance || bestDist <= Math.ceil(input.length * 0.4))) {
|
|
6771
|
+
return options?.map ? options.map(best) : best;
|
|
5742
6772
|
}
|
|
5743
|
-
return
|
|
6773
|
+
return void 0;
|
|
5744
6774
|
}
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
6775
|
+
|
|
6776
|
+
// src/doc/templates/inputDescriptors.ts
|
|
6777
|
+
var AMBIENT_MOTION_VALUES = ["zoomIn", "zoomOut", "panLeft", "panRight"];
|
|
6778
|
+
var CAPTION_POSITION_VALUES = ["bottom", "top", "center"];
|
|
6779
|
+
var MAP_STYLE_VALUES = ["terrain", "satellite", "road", "toner", "watercolor"];
|
|
6780
|
+
var DATE_EVENT_MOOD_VALUES = ["neutral", "somber", "celebratory"];
|
|
6781
|
+
var COLOR_SCHEME_HINT = "theme color-scheme name \u2014 e.g. 'blue', 'green'";
|
|
6782
|
+
function CHART_DESCRIPTORS(extra = []) {
|
|
6783
|
+
return [
|
|
6784
|
+
{ key: "title", description: "Optional title above the chart" },
|
|
6785
|
+
{
|
|
6786
|
+
key: "labelColumn",
|
|
6787
|
+
description: "Category/label column",
|
|
6788
|
+
valueHint: "header name or 0-based index"
|
|
6789
|
+
},
|
|
6790
|
+
{
|
|
6791
|
+
key: "valueColumns",
|
|
6792
|
+
description: "Value columns to plot",
|
|
6793
|
+
coerce: "stringList",
|
|
6794
|
+
valueHint: "comma-separated header names or 0-based indexes"
|
|
6795
|
+
},
|
|
6796
|
+
{
|
|
6797
|
+
key: "showTable",
|
|
6798
|
+
description: "Render the source table beneath the chart",
|
|
6799
|
+
coerce: "boolean"
|
|
6800
|
+
},
|
|
6801
|
+
{ key: "showLegend", description: "Show a series legend", coerce: "boolean" },
|
|
6802
|
+
{ key: "showValues", description: "Print numeric value labels on marks", coerce: "boolean" },
|
|
6803
|
+
{ key: "unit", description: 'Unit suffix for tick/value labels (e.g. "km")' },
|
|
6804
|
+
{
|
|
6805
|
+
key: "colorScheme",
|
|
6806
|
+
description: "Color scheme seeding the series color rotation",
|
|
6807
|
+
valueHint: COLOR_SCHEME_HINT
|
|
6808
|
+
},
|
|
6809
|
+
// `headers`/`rows` are normally derived from the block's markdown table;
|
|
6810
|
+
// declared so data-fence-supplied values aren't flagged as unknown.
|
|
6811
|
+
{
|
|
6812
|
+
key: "headers",
|
|
6813
|
+
description: "Header cells (normally derived from the table)",
|
|
6814
|
+
coerce: "stringList"
|
|
6815
|
+
},
|
|
6816
|
+
{ key: "rows", description: "Table rows (supply via a data fence)" },
|
|
6817
|
+
...extra
|
|
6818
|
+
];
|
|
5753
6819
|
}
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
6820
|
+
var STACKED_DESCRIPTOR = {
|
|
6821
|
+
key: "stacked",
|
|
6822
|
+
description: "Stack series instead of grouping them side by side",
|
|
6823
|
+
coerce: "boolean"
|
|
6824
|
+
};
|
|
6825
|
+
var BASE_INPUT_DESCRIPTORS = [
|
|
6826
|
+
{
|
|
6827
|
+
key: "imageTreatment",
|
|
6828
|
+
description: "Per-block photographic grade override ('none' opts out)",
|
|
6829
|
+
values: ["none", "mono", "duotone", "warm", "cool"]
|
|
6830
|
+
},
|
|
6831
|
+
{
|
|
6832
|
+
key: "useBottomLayer",
|
|
6833
|
+
description: "Show the doc's bottom persistent layers",
|
|
6834
|
+
coerce: "boolean"
|
|
6835
|
+
},
|
|
6836
|
+
{ key: "useTopLayer", description: "Show the doc's top persistent layers", coerce: "boolean" }
|
|
6837
|
+
];
|
|
6838
|
+
var TEMPLATE_INPUT_DESCRIPTORS = {
|
|
6839
|
+
title: [
|
|
6840
|
+
{ key: "title", description: "Main title text" },
|
|
6841
|
+
{ key: "subtitle", description: "Subtitle or tagline" },
|
|
6842
|
+
{ key: "backgroundColor", description: "Background color", valueHint: "CSS color" }
|
|
6843
|
+
],
|
|
6844
|
+
sectionHeader: [
|
|
6845
|
+
{ key: "title", description: "Section title" },
|
|
6846
|
+
{
|
|
6847
|
+
key: "colorScheme",
|
|
6848
|
+
description: "Fallback background color scheme",
|
|
6849
|
+
valueHint: COLOR_SCHEME_HINT
|
|
6850
|
+
},
|
|
6851
|
+
{ key: "imageSrc", description: "Optional background image path" },
|
|
6852
|
+
{ key: "imageAlt", description: "Alt text for the background image" },
|
|
6853
|
+
{ key: "ambientMotion", description: "Slow zoom/pan effect", values: AMBIENT_MOTION_VALUES }
|
|
6854
|
+
],
|
|
6855
|
+
content: [{ key: "title", description: "Heading shown above the complete body" }],
|
|
6856
|
+
statHighlight: [
|
|
6857
|
+
{ key: "stat", description: 'The statistic (e.g. "89%", "2x")' },
|
|
6858
|
+
{ key: "description", description: "What the stat means" },
|
|
6859
|
+
{ key: "detail", description: "Additional detail or context" },
|
|
6860
|
+
{ key: "colorScheme", description: "Color scheme for the stat", valueHint: COLOR_SCHEME_HINT }
|
|
6861
|
+
],
|
|
6862
|
+
quote: [
|
|
6863
|
+
{ key: "title", description: "Optional heading above the quote" },
|
|
6864
|
+
{ key: "quote", description: "The quote text" },
|
|
6865
|
+
{ key: "attribution", description: "Attribution (author, source)" }
|
|
6866
|
+
],
|
|
6867
|
+
pullQuote: [
|
|
6868
|
+
{ key: "text", description: "Quote text" },
|
|
6869
|
+
{ key: "attribution", description: "Attribution" }
|
|
6870
|
+
],
|
|
6871
|
+
twoColumn: [
|
|
6872
|
+
{
|
|
6873
|
+
key: "left",
|
|
6874
|
+
description: 'Left column, as "label|sublabel"',
|
|
6875
|
+
coerce: "labeledPair",
|
|
6876
|
+
required: true
|
|
6877
|
+
},
|
|
6878
|
+
{
|
|
6879
|
+
key: "right",
|
|
6880
|
+
description: 'Right column, as "label|sublabel"',
|
|
6881
|
+
coerce: "labeledPair",
|
|
6882
|
+
required: true
|
|
6883
|
+
},
|
|
6884
|
+
{ key: "header", description: "Optional header above the columns" },
|
|
6885
|
+
{ key: "leftColor", description: "Left column color scheme", valueHint: COLOR_SCHEME_HINT },
|
|
6886
|
+
{ key: "rightColor", description: "Right column color scheme", valueHint: COLOR_SCHEME_HINT }
|
|
6887
|
+
],
|
|
6888
|
+
dateEvent: [
|
|
6889
|
+
{ key: "date", description: 'The date (e.g. "July 14, 1974")' },
|
|
6890
|
+
{ key: "description", description: "What happened" },
|
|
6891
|
+
{ key: "footer", description: "Optional footer text" },
|
|
6892
|
+
{ key: "mood", description: "Emotional tone", values: DATE_EVENT_MOOD_VALUES }
|
|
6893
|
+
],
|
|
6894
|
+
imageWithCaption: [
|
|
6895
|
+
{ key: "imageSrc", description: "Path to the image file", required: true },
|
|
6896
|
+
{ key: "imageAlt", description: "Alt text for accessibility" },
|
|
6897
|
+
{ key: "caption", description: "Caption text" },
|
|
6898
|
+
{ key: "captionPosition", description: "Caption placement", values: CAPTION_POSITION_VALUES },
|
|
6899
|
+
{ key: "ambientMotion", description: "Slow zoom/pan effect", values: AMBIENT_MOTION_VALUES },
|
|
6900
|
+
{ key: "isTitle", description: "Style the caption as a title", coerce: "boolean" },
|
|
6901
|
+
{ key: "subtitle", description: "Subtitle (only when isTitle)" },
|
|
6902
|
+
{ key: "imageCredit", description: "Photo credit / artist name" },
|
|
6903
|
+
{ key: "imageLicense", description: "License identifier" }
|
|
6904
|
+
],
|
|
6905
|
+
leftFeature: FEATURE_DESCRIPTORS(),
|
|
6906
|
+
rightFeature: FEATURE_DESCRIPTORS(),
|
|
6907
|
+
map: [
|
|
6908
|
+
{
|
|
6909
|
+
key: "center",
|
|
6910
|
+
description: 'Map center as "lat,lng"',
|
|
6911
|
+
coerce: "latLng",
|
|
6912
|
+
required: true
|
|
6913
|
+
},
|
|
6914
|
+
{ key: "zoom", description: "Zoom level (4-16)", coerce: "number" },
|
|
6915
|
+
{ key: "mapStyle", description: "Map tile style", values: MAP_STYLE_VALUES },
|
|
6916
|
+
{ key: "title", description: "Optional title overlay" },
|
|
6917
|
+
{ key: "caption", description: "Optional caption" }
|
|
6918
|
+
],
|
|
6919
|
+
photoGrid: [
|
|
6920
|
+
{ key: "images", description: "Comma-separated image paths", coerce: "stringList" },
|
|
6921
|
+
{ key: "caption", description: "Optional caption below the grid" },
|
|
6922
|
+
{
|
|
6923
|
+
key: "ambientMotion",
|
|
6924
|
+
description: "Slow zoom/pan on the largest image",
|
|
6925
|
+
values: AMBIENT_MOTION_VALUES
|
|
6926
|
+
}
|
|
6927
|
+
],
|
|
6928
|
+
comparisonBar: [
|
|
6929
|
+
{ key: "leftLabel", description: "Left bar label", required: true },
|
|
6930
|
+
{ key: "leftValue", description: "Left bar numeric value", coerce: "number", required: true },
|
|
6931
|
+
{ key: "rightLabel", description: "Right bar label", required: true },
|
|
6932
|
+
{ key: "rightValue", description: "Right bar numeric value", coerce: "number", required: true },
|
|
6933
|
+
{ key: "unit", description: 'Unit label (e.g. "km")' },
|
|
6934
|
+
{ key: "colorScheme", description: "Color scheme", valueHint: COLOR_SCHEME_HINT }
|
|
6935
|
+
],
|
|
6936
|
+
dataTable: [
|
|
6937
|
+
{ key: "title", description: "Optional title above the table" },
|
|
6938
|
+
{ key: "headers", description: "Comma-separated header cells", coerce: "stringList" },
|
|
6939
|
+
// `rows`/`align` are structured (string[][] / alignment[]) and can't be
|
|
6940
|
+
// expressed as a single inline string — declared so they aren't flagged
|
|
6941
|
+
// as unknown when supplied via a data fence's overriding params.
|
|
6942
|
+
{ key: "rows", description: "Table rows (supply via a data fence)" },
|
|
6943
|
+
{ key: "align", description: "Per-column alignment (supply via a data fence)" },
|
|
6944
|
+
{ key: "colorScheme", description: "Header color scheme", valueHint: COLOR_SCHEME_HINT }
|
|
6945
|
+
],
|
|
6946
|
+
barChart: CHART_DESCRIPTORS([STACKED_DESCRIPTOR]),
|
|
6947
|
+
columnChart: CHART_DESCRIPTORS([STACKED_DESCRIPTOR]),
|
|
6948
|
+
pieChart: CHART_DESCRIPTORS(),
|
|
6949
|
+
donutChart: CHART_DESCRIPTORS(),
|
|
6950
|
+
lineChart: CHART_DESCRIPTORS(),
|
|
6951
|
+
areaChart: CHART_DESCRIPTORS(),
|
|
6952
|
+
scatterChart: CHART_DESCRIPTORS(),
|
|
6953
|
+
tree: [
|
|
6954
|
+
{ key: "title", description: "Optional title above the tree" },
|
|
6955
|
+
{ key: "colorScheme", description: "Folder-row color scheme", valueHint: COLOR_SCHEME_HINT },
|
|
6956
|
+
// `items` is a nested structure derived from the tree fence, not an
|
|
6957
|
+
// inline string — declared so it isn't flagged as unknown.
|
|
6958
|
+
{ key: "items", description: "Tree items (derived from the tree fence)" }
|
|
6959
|
+
],
|
|
6960
|
+
timeline: [
|
|
6961
|
+
{ key: "title", description: "Optional title above the timeline" },
|
|
6962
|
+
{
|
|
6963
|
+
key: "colorScheme",
|
|
6964
|
+
description: "Track and marker color scheme",
|
|
6965
|
+
valueHint: COLOR_SCHEME_HINT
|
|
6966
|
+
},
|
|
6967
|
+
// Tracks/events and branch links are nested structures derived from the
|
|
6968
|
+
// timeline source, not values that can be expressed as one inline string.
|
|
6969
|
+
{ key: "tracks", description: "Timeline tracks and events (supply via template data)" },
|
|
6970
|
+
{ key: "links", description: "Branch links between event ids (supply via template data)" }
|
|
6971
|
+
],
|
|
6972
|
+
videoWithCaption: [
|
|
6973
|
+
{ key: "videoSrc", description: "Path to the video file", required: true },
|
|
6974
|
+
{ key: "posterSrc", description: "Poster frame path" },
|
|
6975
|
+
{ key: "videoAlt", description: "Alt text for accessibility" },
|
|
6976
|
+
{ key: "clipStart", description: "Clip start time (seconds)", coerce: "number" },
|
|
6977
|
+
{ key: "clipEnd", description: "Clip end time (seconds)", coerce: "number" },
|
|
6978
|
+
{ key: "caption", description: "Caption text" },
|
|
6979
|
+
{ key: "captionPosition", description: "Caption placement", values: CAPTION_POSITION_VALUES },
|
|
6980
|
+
{ key: "videoCredit", description: "Video credit / artist name" },
|
|
6981
|
+
{ key: "videoLicense", description: "License identifier" }
|
|
6982
|
+
]
|
|
6983
|
+
};
|
|
6984
|
+
function FEATURE_DESCRIPTORS() {
|
|
6985
|
+
return [
|
|
6986
|
+
{ key: "imageSrc", description: "Path to the feature image", required: true },
|
|
6987
|
+
{ key: "imageAlt", description: "Alt text for accessibility" },
|
|
6988
|
+
{ key: "imageWidth", description: "Explicit display width in px", coerce: "number" },
|
|
6989
|
+
{ key: "imageHeight", description: "Explicit display height in px", coerce: "number" },
|
|
6990
|
+
{ key: "title", description: "Heading text next to the image" },
|
|
6991
|
+
{ key: "body", description: "Body text below the title" }
|
|
6992
|
+
];
|
|
5763
6993
|
}
|
|
5764
|
-
function
|
|
5765
|
-
const
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
else after = after.trim();
|
|
5769
|
-
return [before, after].filter(Boolean).join(" ").trim();
|
|
6994
|
+
function descriptorsFor(canonical) {
|
|
6995
|
+
const specific = TEMPLATE_INPUT_DESCRIPTORS[canonical];
|
|
6996
|
+
if (!specific) return void 0;
|
|
6997
|
+
return [...specific, ...BASE_INPUT_DESCRIPTORS];
|
|
5770
6998
|
}
|
|
5771
|
-
function
|
|
5772
|
-
|
|
5773
|
-
return contents.map(
|
|
5774
|
-
(node, index) => index === paragraphIndex ? removeStatPhrase(paragraphText, start, end) : extractPlainText(node).trim()
|
|
5775
|
-
).filter(Boolean).join("\n").trim();
|
|
6999
|
+
function descriptorMap(descriptors) {
|
|
7000
|
+
return new Map(descriptors.map((d) => [d.key, d]));
|
|
5776
7001
|
}
|
|
5777
|
-
function
|
|
5778
|
-
const
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
);
|
|
5791
|
-
return {
|
|
5792
|
-
stat: strongStat,
|
|
5793
|
-
description: description || headingText || strongStat
|
|
5794
|
-
};
|
|
7002
|
+
function coerceTemplateParams(template, params) {
|
|
7003
|
+
const input = {};
|
|
7004
|
+
const warnings = [];
|
|
7005
|
+
const descriptors = template ? descriptorsFor(resolveTemplateName(template)) : void 0;
|
|
7006
|
+
const byKey = descriptors ? descriptorMap(descriptors) : void 0;
|
|
7007
|
+
for (const [key, raw] of Object.entries(params)) {
|
|
7008
|
+
const coerce = byKey?.get(key)?.coerce ?? "string";
|
|
7009
|
+
const { value, warning } = coerceValue(coerce, raw);
|
|
7010
|
+
if (warning) {
|
|
7011
|
+
warnings.push(`"${key}": ${warning}`);
|
|
7012
|
+
input[key] = raw;
|
|
7013
|
+
} else {
|
|
7014
|
+
input[key] = value;
|
|
5795
7015
|
}
|
|
5796
7016
|
}
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
const bodyStat = matchNumberHighlight(paragraphText);
|
|
5805
|
-
if (bodyStat) {
|
|
5806
|
-
const description = bodyWithoutParagraphStat(
|
|
5807
|
-
contents,
|
|
5808
|
-
paragraph.index,
|
|
5809
|
-
paragraphText,
|
|
5810
|
-
bodyStat.index,
|
|
5811
|
-
bodyStat.end
|
|
5812
|
-
);
|
|
5813
|
-
return {
|
|
5814
|
-
stat: bodyStat.value,
|
|
5815
|
-
description: description || headingText || bodyStat.value
|
|
5816
|
-
};
|
|
7017
|
+
return { input, warnings };
|
|
7018
|
+
}
|
|
7019
|
+
function coerceValue(coerce, raw) {
|
|
7020
|
+
switch (coerce) {
|
|
7021
|
+
case "number": {
|
|
7022
|
+
const n = parseNumber(raw);
|
|
7023
|
+
return n == null ? { value: raw, warning: `not a number (${JSON.stringify(raw)})` } : { value: n };
|
|
5817
7024
|
}
|
|
7025
|
+
case "boolean":
|
|
7026
|
+
return coerceBoolean(raw);
|
|
7027
|
+
case "latLng":
|
|
7028
|
+
return coerceLatLng(raw);
|
|
7029
|
+
case "labeledPair":
|
|
7030
|
+
return { value: coerceLabeledPair(raw) };
|
|
7031
|
+
case "stringList":
|
|
7032
|
+
return { value: coerceStringList(raw) };
|
|
7033
|
+
case "string":
|
|
7034
|
+
default:
|
|
7035
|
+
return { value: raw };
|
|
5818
7036
|
}
|
|
5819
|
-
return { stat: headingText, description: bodyText3 || headingText };
|
|
5820
7037
|
}
|
|
5821
|
-
function
|
|
5822
|
-
|
|
7038
|
+
function coerceBoolean(raw) {
|
|
7039
|
+
const t = raw.trim().toLowerCase();
|
|
7040
|
+
if (t === "true" || t === "") return { value: true };
|
|
7041
|
+
if (t === "false") return { value: false };
|
|
7042
|
+
return { value: raw, warning: `not a boolean (${JSON.stringify(raw)}) \u2014 expected true or false` };
|
|
5823
7043
|
}
|
|
5824
|
-
function
|
|
5825
|
-
const
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
return nodes.length === 1 && nodes[0]?.type === "table";
|
|
5831
|
-
case "list":
|
|
5832
|
-
return nodes.length === 1 && nodes[0]?.type === "list";
|
|
5833
|
-
case "diagram":
|
|
5834
|
-
case "timeline":
|
|
5835
|
-
return nodes.length === 1 && nodes[0]?.type === "code";
|
|
5836
|
-
case "tree":
|
|
5837
|
-
return nodes.length === 1 && (nodes[0]?.type === "code" || nodes[0]?.type === "list");
|
|
5838
|
-
case "photoGrid": {
|
|
5839
|
-
const images = extractImages(nodes);
|
|
5840
|
-
if (images.length < 2 || images.length > 4) return false;
|
|
5841
|
-
const bodyText3 = normalizeCoverageText(extractBodyPlainText(nodes));
|
|
5842
|
-
const representedText = normalizeCoverageText(
|
|
5843
|
-
images.map((image) => image.alt).filter(Boolean).join(" ")
|
|
5844
|
-
);
|
|
5845
|
-
return bodyText3 === "" || bodyText3 === representedText;
|
|
5846
|
-
}
|
|
5847
|
-
case "videoWithCaption": {
|
|
5848
|
-
const videos = extractEmbeddedVideos(nodes);
|
|
5849
|
-
if (videos.length !== 1) return false;
|
|
5850
|
-
const bodyText3 = normalizeCoverageText(extractBodyPlainText(nodes));
|
|
5851
|
-
return bodyText3 === normalizeCoverageText(videos[0]?.alt ?? "");
|
|
5852
|
-
}
|
|
5853
|
-
case "leftFeature":
|
|
5854
|
-
case "rightFeature":
|
|
5855
|
-
case "statHighlight":
|
|
5856
|
-
return true;
|
|
5857
|
-
default:
|
|
5858
|
-
return false;
|
|
7044
|
+
function coerceLatLng(raw) {
|
|
7045
|
+
const parts = raw.split(",");
|
|
7046
|
+
if (parts.length === 2) {
|
|
7047
|
+
const lat = parseNumber(parts[0]);
|
|
7048
|
+
const lng = parseNumber(parts[1]);
|
|
7049
|
+
if (lat != null && lng != null) return { value: { lat, lng } };
|
|
5859
7050
|
}
|
|
7051
|
+
return { value: raw, warning: `not a "lat,lng" pair (${JSON.stringify(raw)})` };
|
|
5860
7052
|
}
|
|
5861
|
-
function
|
|
5862
|
-
const
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
const
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
const items = extractListItems(contents);
|
|
5892
|
-
if (items.length > 0) return { items, ...headingText ? { title: headingText } : {} };
|
|
5893
|
-
return placeholders ? { items: ["Item 1", "Item 2", "Item 3"] } : null;
|
|
5894
|
-
}
|
|
5895
|
-
case "definitionCard":
|
|
5896
|
-
return { term: headingText, definition: bodyText3 || headingText };
|
|
5897
|
-
case "dateEvent":
|
|
5898
|
-
return { date: headingText, description: bodyText3 || headingText };
|
|
5899
|
-
case "dataTable": {
|
|
5900
|
-
const tableData = extractTableFromContents(contents);
|
|
5901
|
-
if (tableData) return { ...tableData, ...headingText ? { title: headingText } : {} };
|
|
5902
|
-
return placeholders ? { headers: ["Column"], rows: [["Data"]] } : null;
|
|
5903
|
-
}
|
|
5904
|
-
case "imageWithCaption": {
|
|
5905
|
-
const img = extractFirstImage(contents);
|
|
5906
|
-
if (!img) return placeholders ? { caption: headingText } : null;
|
|
5907
|
-
return { imageSrc: img.src, imageAlt: img.alt || headingText, caption: headingText };
|
|
5908
|
-
}
|
|
5909
|
-
case "photoGrid": {
|
|
5910
|
-
const images = extractImages(contents, 4);
|
|
5911
|
-
if (images.length < 2) return placeholders ? {} : null;
|
|
5912
|
-
return {
|
|
5913
|
-
images: images.map((i) => ({ src: i.src, alt: i.alt })),
|
|
5914
|
-
caption: headingText
|
|
5915
|
-
};
|
|
5916
|
-
}
|
|
5917
|
-
case "videoWithCaption": {
|
|
5918
|
-
const video = extractFirstEmbeddedVideo(contents);
|
|
5919
|
-
if (!video) return placeholders ? { caption: headingText } : null;
|
|
5920
|
-
return {
|
|
5921
|
-
videoSrc: video.src,
|
|
5922
|
-
posterSrc: video.posterSrc,
|
|
5923
|
-
videoAlt: video.alt || headingText,
|
|
5924
|
-
caption: headingText
|
|
5925
|
-
};
|
|
7053
|
+
function coerceLabeledPair(raw) {
|
|
7054
|
+
const sep = raw.indexOf("|");
|
|
7055
|
+
if (sep < 0) return { label: raw.trim() };
|
|
7056
|
+
const label = raw.slice(0, sep).trim();
|
|
7057
|
+
const sublabel = raw.slice(sep + 1).trim();
|
|
7058
|
+
return sublabel ? { label, sublabel } : { label };
|
|
7059
|
+
}
|
|
7060
|
+
function coerceStringList(raw) {
|
|
7061
|
+
return raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
7062
|
+
}
|
|
7063
|
+
function lintTemplateParams(template, params) {
|
|
7064
|
+
const canonical = resolveTemplateName(template);
|
|
7065
|
+
const specific = TEMPLATE_INPUT_DESCRIPTORS[canonical];
|
|
7066
|
+
if (!specific) return [];
|
|
7067
|
+
const findings = [];
|
|
7068
|
+
const allDescriptors = [...specific, ...BASE_INPUT_DESCRIPTORS];
|
|
7069
|
+
const byKey = descriptorMap(allDescriptors);
|
|
7070
|
+
const knownKeys = /* @__PURE__ */ new Set([...byKey.keys(), ...Object.keys(KNOWN_BLOCK_META_KEYS)]);
|
|
7071
|
+
for (const [key, raw] of Object.entries(params)) {
|
|
7072
|
+
const descriptor = byKey.get(key);
|
|
7073
|
+
if (!descriptor) {
|
|
7074
|
+
if (knownKeys.has(key)) continue;
|
|
7075
|
+
const suggestion = nearestName(key, knownKeys);
|
|
7076
|
+
findings.push({
|
|
7077
|
+
kind: "unknown-input",
|
|
7078
|
+
key,
|
|
7079
|
+
message: `Unknown input "${key}" for template "${canonical}"` + (suggestion ? `. Did you mean "${suggestion}"?` : ""),
|
|
7080
|
+
...suggestion ? { suggestion } : {}
|
|
7081
|
+
});
|
|
7082
|
+
continue;
|
|
5926
7083
|
}
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
7084
|
+
if (descriptor.values) {
|
|
7085
|
+
const v = raw.trim();
|
|
7086
|
+
if (v !== "" && !descriptor.values.includes(v)) {
|
|
7087
|
+
findings.push({
|
|
7088
|
+
kind: "invalid-input-value",
|
|
7089
|
+
key,
|
|
7090
|
+
message: `Invalid value ${JSON.stringify(raw)} for "${key}" \u2014 expected one of: ${descriptor.values.join(", ")}`
|
|
7091
|
+
});
|
|
7092
|
+
}
|
|
7093
|
+
} else if (descriptor.coerce && descriptor.coerce !== "string") {
|
|
7094
|
+
const { warning } = coerceValue(descriptor.coerce, raw);
|
|
7095
|
+
if (warning) {
|
|
7096
|
+
findings.push({ kind: "invalid-input-value", key, message: `"${key}": ${warning}` });
|
|
7097
|
+
}
|
|
5939
7098
|
}
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
7099
|
+
}
|
|
7100
|
+
for (const descriptor of specific) {
|
|
7101
|
+
if (descriptor.required && params[descriptor.key] == null) {
|
|
7102
|
+
findings.push({
|
|
7103
|
+
kind: "missing-input",
|
|
7104
|
+
key: descriptor.key,
|
|
7105
|
+
message: `Missing required input "${descriptor.key}" for template "${canonical}"`
|
|
7106
|
+
});
|
|
5947
7107
|
}
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
7108
|
+
}
|
|
7109
|
+
return findings;
|
|
7110
|
+
}
|
|
7111
|
+
|
|
7112
|
+
// src/doc/templates/fallbackBlock.ts
|
|
7113
|
+
var MAX_BODY_CHARS = 600;
|
|
7114
|
+
function fallbackBlockLayers(block, context, notice) {
|
|
7115
|
+
const colors = resolveColorScheme(context, "blue");
|
|
7116
|
+
const { layout } = context;
|
|
7117
|
+
const title = block.title ?? block.id ?? "";
|
|
7118
|
+
const body = extractBodyText(block);
|
|
7119
|
+
const layers = [
|
|
7120
|
+
{
|
|
7121
|
+
type: "shape",
|
|
7122
|
+
id: "fallback-bg",
|
|
7123
|
+
content: { shape: "rect", fill: colors.bg },
|
|
7124
|
+
position: { x: 0, y: 0, width: "100%", height: "100%" }
|
|
5955
7125
|
}
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
7126
|
+
];
|
|
7127
|
+
if (title) {
|
|
7128
|
+
layers.push({
|
|
7129
|
+
type: "text",
|
|
7130
|
+
id: "fallback-title",
|
|
7131
|
+
content: {
|
|
7132
|
+
text: title,
|
|
7133
|
+
style: {
|
|
7134
|
+
fontSize: themedFontSize(56, context, true),
|
|
7135
|
+
fontFamily: getThemeFont(context, "title"),
|
|
7136
|
+
fontWeight: "bold",
|
|
7137
|
+
color: colors.text,
|
|
7138
|
+
textAlign: "center"
|
|
5964
7139
|
}
|
|
7140
|
+
},
|
|
7141
|
+
position: {
|
|
7142
|
+
x: "50%",
|
|
7143
|
+
y: body ? "28%" : "50%",
|
|
7144
|
+
anchor: "center",
|
|
7145
|
+
width: layout.maxTextWidth
|
|
5965
7146
|
}
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
7147
|
+
});
|
|
7148
|
+
}
|
|
7149
|
+
if (body) {
|
|
7150
|
+
layers.push({
|
|
7151
|
+
type: "text",
|
|
7152
|
+
id: "fallback-body",
|
|
7153
|
+
content: {
|
|
7154
|
+
text: body,
|
|
7155
|
+
style: {
|
|
7156
|
+
fontSize: themedFontSize(28, context),
|
|
7157
|
+
fontFamily: getThemeFont(context, "body"),
|
|
7158
|
+
color: colors.text,
|
|
7159
|
+
textAlign: "center"
|
|
7160
|
+
}
|
|
7161
|
+
},
|
|
7162
|
+
position: {
|
|
7163
|
+
x: "50%",
|
|
7164
|
+
y: "55%",
|
|
7165
|
+
anchor: "center",
|
|
7166
|
+
width: layout.maxTextWidth
|
|
5970
7167
|
}
|
|
5971
|
-
|
|
5972
|
-
}
|
|
5973
|
-
default:
|
|
5974
|
-
return placeholders ? {} : null;
|
|
7168
|
+
});
|
|
5975
7169
|
}
|
|
7170
|
+
layers.push({
|
|
7171
|
+
type: "text",
|
|
7172
|
+
id: "fallback-notice",
|
|
7173
|
+
content: {
|
|
7174
|
+
text: `\u26A0 ${notice}`,
|
|
7175
|
+
style: {
|
|
7176
|
+
fontSize: themedFontSize(18, context),
|
|
7177
|
+
fontFamily: getThemeFont(context, "body"),
|
|
7178
|
+
color: withAlpha(colors.text, 0.6),
|
|
7179
|
+
textAlign: "center"
|
|
7180
|
+
}
|
|
7181
|
+
},
|
|
7182
|
+
position: { x: "50%", y: "92%", anchor: "center", width: layout.maxTextWidth }
|
|
7183
|
+
});
|
|
7184
|
+
return layers;
|
|
7185
|
+
}
|
|
7186
|
+
function extractBodyText(block) {
|
|
7187
|
+
if (!block.contents || block.contents.length === 0) return "";
|
|
7188
|
+
const text = block.contents.map((node) => extractPlainText(node)).join("\n").trim();
|
|
7189
|
+
if (text.length <= MAX_BODY_CHARS) return text;
|
|
7190
|
+
return `${text.slice(0, MAX_BODY_CHARS).trimEnd()}\u2026`;
|
|
5976
7191
|
}
|
|
5977
7192
|
|
|
5978
7193
|
// src/doc/templates/mediaLayoutPolicy.ts
|
|
@@ -6206,6 +7421,55 @@ var BLOCK_MEDIA_LAYOUT_POLICIES = {
|
|
|
6206
7421
|
unconsumedMedia: "reserved-slot",
|
|
6207
7422
|
variants: DENSE_VARIANTS
|
|
6208
7423
|
},
|
|
7424
|
+
barChart: {
|
|
7425
|
+
summary: "The chart remains wide; supplemental media stacks below the dense data region.",
|
|
7426
|
+
noMedia: "template-default",
|
|
7427
|
+
ownership: "supplemental",
|
|
7428
|
+
unconsumedMedia: "reserved-slot",
|
|
7429
|
+
variants: DENSE_VARIANTS
|
|
7430
|
+
},
|
|
7431
|
+
columnChart: {
|
|
7432
|
+
summary: "The chart remains wide; supplemental media stacks below the dense data region.",
|
|
7433
|
+
noMedia: "template-default",
|
|
7434
|
+
ownership: "supplemental",
|
|
7435
|
+
unconsumedMedia: "reserved-slot",
|
|
7436
|
+
variants: DENSE_VARIANTS
|
|
7437
|
+
},
|
|
7438
|
+
pieChart: {
|
|
7439
|
+
summary: "The chart remains wide; supplemental media stacks below the dense data region.",
|
|
7440
|
+
noMedia: "template-default",
|
|
7441
|
+
ownership: "supplemental",
|
|
7442
|
+
unconsumedMedia: "reserved-slot",
|
|
7443
|
+
variants: DENSE_VARIANTS
|
|
7444
|
+
},
|
|
7445
|
+
donutChart: {
|
|
7446
|
+
summary: "The chart remains wide; supplemental media stacks below the dense data region.",
|
|
7447
|
+
noMedia: "template-default",
|
|
7448
|
+
ownership: "supplemental",
|
|
7449
|
+
unconsumedMedia: "reserved-slot",
|
|
7450
|
+
variants: DENSE_VARIANTS
|
|
7451
|
+
},
|
|
7452
|
+
lineChart: {
|
|
7453
|
+
summary: "The chart remains wide; supplemental media stacks below the dense data region.",
|
|
7454
|
+
noMedia: "template-default",
|
|
7455
|
+
ownership: "supplemental",
|
|
7456
|
+
unconsumedMedia: "reserved-slot",
|
|
7457
|
+
variants: DENSE_VARIANTS
|
|
7458
|
+
},
|
|
7459
|
+
areaChart: {
|
|
7460
|
+
summary: "The chart remains wide; supplemental media stacks below the dense data region.",
|
|
7461
|
+
noMedia: "template-default",
|
|
7462
|
+
ownership: "supplemental",
|
|
7463
|
+
unconsumedMedia: "reserved-slot",
|
|
7464
|
+
variants: DENSE_VARIANTS
|
|
7465
|
+
},
|
|
7466
|
+
scatterChart: {
|
|
7467
|
+
summary: "The chart remains wide; supplemental media stacks below the dense data region.",
|
|
7468
|
+
noMedia: "template-default",
|
|
7469
|
+
ownership: "supplemental",
|
|
7470
|
+
unconsumedMedia: "reserved-slot",
|
|
7471
|
+
variants: DENSE_VARIANTS
|
|
7472
|
+
},
|
|
6209
7473
|
diagram: {
|
|
6210
7474
|
summary: "The node-and-edge canvas is intrinsic media and retains its spatial coordinate system.",
|
|
6211
7475
|
noMedia: "intrinsic-visual",
|
|
@@ -7191,6 +8455,34 @@ var TEMPLATE_METADATA = {
|
|
|
7191
8455
|
label: "Data Table",
|
|
7192
8456
|
description: "Renders tabular data in a clean, styled table with a header row."
|
|
7193
8457
|
},
|
|
8458
|
+
barChart: {
|
|
8459
|
+
label: "Bar Chart",
|
|
8460
|
+
description: "Horizontal bars from the block's markdown table \u2014 one bar per row."
|
|
8461
|
+
},
|
|
8462
|
+
columnChart: {
|
|
8463
|
+
label: "Column Chart",
|
|
8464
|
+
description: "Vertical columns from the block's markdown table \u2014 one column per row."
|
|
8465
|
+
},
|
|
8466
|
+
pieChart: {
|
|
8467
|
+
label: "Pie Chart",
|
|
8468
|
+
description: "A pie of the block's markdown table \u2014 each row becomes a labeled slice."
|
|
8469
|
+
},
|
|
8470
|
+
donutChart: {
|
|
8471
|
+
label: "Donut Chart",
|
|
8472
|
+
description: "A donut of the block's markdown table \u2014 each row becomes a labeled slice."
|
|
8473
|
+
},
|
|
8474
|
+
lineChart: {
|
|
8475
|
+
label: "Line Chart",
|
|
8476
|
+
description: "Lines from the block's markdown table \u2014 one line per value column."
|
|
8477
|
+
},
|
|
8478
|
+
areaChart: {
|
|
8479
|
+
label: "Area Chart",
|
|
8480
|
+
description: "Filled area lines from the block's markdown table \u2014 one area per value column."
|
|
8481
|
+
},
|
|
8482
|
+
scatterChart: {
|
|
8483
|
+
label: "Scatter Chart",
|
|
8484
|
+
description: "An x/y scatter plot from the block's markdown table's numeric columns."
|
|
8485
|
+
},
|
|
7194
8486
|
diagram: {
|
|
7195
8487
|
label: "Diagram",
|
|
7196
8488
|
description: "Renders sub-headings as connected nodes \u2014 edit visually as a node-and-edge diagram."
|
|
@@ -7336,6 +8628,48 @@ var TEMPLATE_AUTHORING_METADATA = {
|
|
|
7336
8628
|
safeForContentFirst: false,
|
|
7337
8629
|
placement: "heading"
|
|
7338
8630
|
},
|
|
8631
|
+
barChart: {
|
|
8632
|
+
role: "data",
|
|
8633
|
+
bodyPolicy: "structured",
|
|
8634
|
+
safeForContentFirst: false,
|
|
8635
|
+
placement: "heading"
|
|
8636
|
+
},
|
|
8637
|
+
columnChart: {
|
|
8638
|
+
role: "data",
|
|
8639
|
+
bodyPolicy: "structured",
|
|
8640
|
+
safeForContentFirst: false,
|
|
8641
|
+
placement: "heading"
|
|
8642
|
+
},
|
|
8643
|
+
pieChart: {
|
|
8644
|
+
role: "data",
|
|
8645
|
+
bodyPolicy: "structured",
|
|
8646
|
+
safeForContentFirst: false,
|
|
8647
|
+
placement: "heading"
|
|
8648
|
+
},
|
|
8649
|
+
donutChart: {
|
|
8650
|
+
role: "data",
|
|
8651
|
+
bodyPolicy: "structured",
|
|
8652
|
+
safeForContentFirst: false,
|
|
8653
|
+
placement: "heading"
|
|
8654
|
+
},
|
|
8655
|
+
lineChart: {
|
|
8656
|
+
role: "data",
|
|
8657
|
+
bodyPolicy: "structured",
|
|
8658
|
+
safeForContentFirst: false,
|
|
8659
|
+
placement: "heading"
|
|
8660
|
+
},
|
|
8661
|
+
areaChart: {
|
|
8662
|
+
role: "data",
|
|
8663
|
+
bodyPolicy: "structured",
|
|
8664
|
+
safeForContentFirst: false,
|
|
8665
|
+
placement: "heading"
|
|
8666
|
+
},
|
|
8667
|
+
scatterChart: {
|
|
8668
|
+
role: "data",
|
|
8669
|
+
bodyPolicy: "structured",
|
|
8670
|
+
safeForContentFirst: false,
|
|
8671
|
+
placement: "heading"
|
|
8672
|
+
},
|
|
7339
8673
|
diagram: {
|
|
7340
8674
|
role: "spatial",
|
|
7341
8675
|
bodyPolicy: "structured",
|
|
@@ -8200,6 +9534,18 @@ function time(raw) {
|
|
|
8200
9534
|
if (raw == null) return null;
|
|
8201
9535
|
return parseTimeSeconds(raw);
|
|
8202
9536
|
}
|
|
9537
|
+
function videoPlacement(params) {
|
|
9538
|
+
const explicit = params.placement ?? params.presentation;
|
|
9539
|
+
if (explicit === "content") return "content";
|
|
9540
|
+
if (explicit === "pip" || explicit === "picture-in-picture") return "picture-in-picture";
|
|
9541
|
+
if (explicit === "overlay" || explicit === "full-frame") return "overlay";
|
|
9542
|
+
if (params.pip === "" || params.pip === "true") return "picture-in-picture";
|
|
9543
|
+
if (params.overlay === "" || params.overlay === "true") return "overlay";
|
|
9544
|
+
return void 0;
|
|
9545
|
+
}
|
|
9546
|
+
function lockToBlock(raw) {
|
|
9547
|
+
return raw !== "false" && raw !== "0";
|
|
9548
|
+
}
|
|
8203
9549
|
function toMediaClip(parsed, id) {
|
|
8204
9550
|
const { template, params } = parsed;
|
|
8205
9551
|
if (!template || !MEDIA_TEMPLATES.has(template)) return null;
|
|
@@ -8214,13 +9560,78 @@ function toMediaClip(parsed, id) {
|
|
|
8214
9560
|
startAt: time(params.startAt ?? params.startTime) ?? 0,
|
|
8215
9561
|
anchor
|
|
8216
9562
|
};
|
|
9563
|
+
const placement = kind === "video" ? videoPlacement(params) : void 0;
|
|
9564
|
+
if (placement && placement !== "content") clip.placement = placement;
|
|
9565
|
+
if (kind === "video" && placement && placement !== "content" && params.lockToBlock != null) {
|
|
9566
|
+
const locked = lockToBlock(params.lockToBlock);
|
|
9567
|
+
clip.lockToBlock = locked;
|
|
9568
|
+
clip.anchor = locked ? "block" : "document";
|
|
9569
|
+
if (locked) clip.startAt = 0;
|
|
9570
|
+
}
|
|
8217
9571
|
const clipStart = time(params.clipStart);
|
|
8218
9572
|
const clipEnd = time(params.clipEnd);
|
|
8219
9573
|
if (clipStart != null) clip.clipStart = clipStart;
|
|
8220
9574
|
if (clipEnd != null) clip.clipEnd = clipEnd;
|
|
9575
|
+
if (clip.lockToBlock === true) delete clip.clipEnd;
|
|
8221
9576
|
if (params.spillover === "true" || params.spillover === "") clip.spillover = true;
|
|
8222
9577
|
return clip;
|
|
8223
9578
|
}
|
|
9579
|
+
function firstVideoElement(children) {
|
|
9580
|
+
const visit = (nodes) => {
|
|
9581
|
+
for (const child of nodes) {
|
|
9582
|
+
if (child.type !== "htmlElement") continue;
|
|
9583
|
+
if (child.tagName === "video") return child;
|
|
9584
|
+
const nested = visit(child.children);
|
|
9585
|
+
if (nested) return nested;
|
|
9586
|
+
}
|
|
9587
|
+
return null;
|
|
9588
|
+
};
|
|
9589
|
+
return visit(children);
|
|
9590
|
+
}
|
|
9591
|
+
function standaloneHtmlVideo(node) {
|
|
9592
|
+
if (node.type === "htmlBlock") {
|
|
9593
|
+
if (!/^\s*<video\b/i.test(node.rawHtml)) return null;
|
|
9594
|
+
const element = firstVideoElement(node.htmlChildren);
|
|
9595
|
+
return element ? { element, raw: node.rawHtml.trim() } : null;
|
|
9596
|
+
}
|
|
9597
|
+
if (node.type !== "paragraph" || !node.children.every((child) => child.type === "htmlInline")) {
|
|
9598
|
+
return null;
|
|
9599
|
+
}
|
|
9600
|
+
const raw = node.children.map((child) => child.type === "htmlInline" ? child.rawHtml : "").join("");
|
|
9601
|
+
if (!/^\s*<video\b[\s\S]*<\/video>\s*$/i.test(raw)) return null;
|
|
9602
|
+
for (const child of node.children) {
|
|
9603
|
+
if (child.type !== "htmlInline") continue;
|
|
9604
|
+
const element = firstVideoElement(child.htmlChildren);
|
|
9605
|
+
if (element) return { element, raw: raw.trim() };
|
|
9606
|
+
}
|
|
9607
|
+
return null;
|
|
9608
|
+
}
|
|
9609
|
+
function htmlVideoClip(node, id) {
|
|
9610
|
+
const authored = standaloneHtmlVideo(node);
|
|
9611
|
+
if (!authored) return null;
|
|
9612
|
+
const { element } = authored;
|
|
9613
|
+
const placement = videoPlacement({
|
|
9614
|
+
placement: element.attributes["data-squisq-video-placement"] ?? ""
|
|
9615
|
+
});
|
|
9616
|
+
if (!placement || placement === "content") return null;
|
|
9617
|
+
const src = element.attributes.src;
|
|
9618
|
+
if (!src) return null;
|
|
9619
|
+
const locked = lockToBlock(element.attributes["data-squisq-video-lock-to-block"]);
|
|
9620
|
+
const clip = {
|
|
9621
|
+
id,
|
|
9622
|
+
src,
|
|
9623
|
+
kind: "video",
|
|
9624
|
+
placement,
|
|
9625
|
+
lockToBlock: locked,
|
|
9626
|
+
startAt: locked ? 0 : time(element.attributes["data-squisq-video-start-at"]) ?? 0,
|
|
9627
|
+
anchor: locked ? "block" : "document"
|
|
9628
|
+
};
|
|
9629
|
+
const clipStart = time(element.attributes["data-squisq-video-clip-start"]);
|
|
9630
|
+
const clipEnd = time(element.attributes["data-squisq-video-clip-end"]);
|
|
9631
|
+
if (clipStart != null) clip.clipStart = clipStart;
|
|
9632
|
+
if (!locked && clipEnd != null) clip.clipEnd = clipEnd;
|
|
9633
|
+
return clip;
|
|
9634
|
+
}
|
|
8224
9635
|
function extractMediaFromContents(contents, makeId, blockId) {
|
|
8225
9636
|
const media = [];
|
|
8226
9637
|
const documentMedia = [];
|
|
@@ -8229,17 +9640,19 @@ function extractMediaFromContents(contents, makeId, blockId) {
|
|
|
8229
9640
|
let originalIndex = 0;
|
|
8230
9641
|
for (const node of contents ?? []) {
|
|
8231
9642
|
const parsed = parseStandaloneAnnotation(node);
|
|
8232
|
-
const clip = parsed ? toMediaClip(parsed, "") :
|
|
8233
|
-
if (
|
|
9643
|
+
const clip = parsed ? toMediaClip(parsed, "") : htmlVideoClip(node, "");
|
|
9644
|
+
if (clip) {
|
|
8234
9645
|
clip.id = makeId(clip.src, index++);
|
|
8235
9646
|
const line = node.position?.start.line;
|
|
8236
9647
|
if (line != null) clip.sourceLine = line;
|
|
8237
9648
|
if (blockId !== void 0) {
|
|
8238
|
-
const
|
|
9649
|
+
const authoredHtml = standaloneHtmlVideo(node);
|
|
9650
|
+
const rawText = node.type === "paragraph" && node.children?.[0]?.type === "text" ? node.children[0].value.trim() : authoredHtml?.raw;
|
|
8239
9651
|
clip.origin = {
|
|
8240
9652
|
blockId,
|
|
8241
9653
|
index: originalIndex,
|
|
8242
|
-
...rawText !== void 0 ? { raw: rawText } : {}
|
|
9654
|
+
...rawText !== void 0 ? { raw: rawText } : {},
|
|
9655
|
+
...authoredHtml ? { format: "html" } : {}
|
|
8243
9656
|
};
|
|
8244
9657
|
}
|
|
8245
9658
|
(clip.anchor === "document" ? documentMedia : media).push(clip);
|
|
@@ -8576,6 +9989,12 @@ function markdownToDoc(markdownDoc, options) {
|
|
|
8576
9989
|
}
|
|
8577
9990
|
currentTime += block.duration;
|
|
8578
9991
|
}
|
|
9992
|
+
const blockById = new Map(allBlocks.map((block) => [block.id, block]));
|
|
9993
|
+
for (const clip of documentMedia) {
|
|
9994
|
+
if (clip.lockToBlock === false && clip.origin?.format === "html" && !/\bdata-squisq-video-start-at\s*=/i.test(clip.origin.raw ?? "")) {
|
|
9995
|
+
clip.startAt = blockById.get(clip.origin.blockId)?.startTime ?? 0;
|
|
9996
|
+
}
|
|
9997
|
+
}
|
|
8579
9998
|
const captions = phrases.length > 0 ? {
|
|
8580
9999
|
phrases,
|
|
8581
10000
|
...options?.captionsGeneratedAt ? { generatedAt: options.captionsGeneratedAt } : {},
|
|
@@ -8661,7 +10080,7 @@ function applyStructuredData(block, diagnostics) {
|
|
|
8661
10080
|
}
|
|
8662
10081
|
data = { ...data, ...result.data };
|
|
8663
10082
|
}
|
|
8664
|
-
if (block.template
|
|
10083
|
+
if (TABLE_FED_TEMPLATES.has(resolveTemplateName(block.template ?? ""))) {
|
|
8665
10084
|
const provided = (key) => data && key in data || block.templateOverrides && key in block.templateOverrides;
|
|
8666
10085
|
if (!provided("headers") && !provided("rows")) {
|
|
8667
10086
|
const table = findFirstTable(block.contents);
|
|
@@ -9187,6 +10606,7 @@ export {
|
|
|
9187
10606
|
wrapWithPersistentLayers,
|
|
9188
10607
|
TEMPLATE_ALIASES,
|
|
9189
10608
|
resolveTemplateName,
|
|
10609
|
+
TABLE_FED_TEMPLATES,
|
|
9190
10610
|
CONTAINER_TEMPLATES,
|
|
9191
10611
|
isContainerTemplate,
|
|
9192
10612
|
resolveColorScheme,
|
|
@@ -9218,6 +10638,30 @@ export {
|
|
|
9218
10638
|
rightFeature,
|
|
9219
10639
|
mapBlock,
|
|
9220
10640
|
fullBleedQuote,
|
|
10641
|
+
computeDiagramLayout,
|
|
10642
|
+
ASCII_CHAR_W,
|
|
10643
|
+
ASCII_CHAR_H,
|
|
10644
|
+
asciiCellToCanvas,
|
|
10645
|
+
canvasToAsciiCell,
|
|
10646
|
+
asciiDiagramToTemplateData,
|
|
10647
|
+
asciiDiagramFromTemplateData,
|
|
10648
|
+
asciiDiagramFromBlocks,
|
|
10649
|
+
asciiTimelineToTemplateData,
|
|
10650
|
+
treeToTemplateData,
|
|
10651
|
+
treeFromTemplateData,
|
|
10652
|
+
treeFromMarkdownList,
|
|
10653
|
+
findFirstList,
|
|
10654
|
+
extractBodyPlainText,
|
|
10655
|
+
extractListItems,
|
|
10656
|
+
extractRichListItems,
|
|
10657
|
+
extractImages,
|
|
10658
|
+
extractFirstImage,
|
|
10659
|
+
extractEmbeddedVideos,
|
|
10660
|
+
extractFirstEmbeddedVideo,
|
|
10661
|
+
extractTableFromContents,
|
|
10662
|
+
extractBlockquoteText,
|
|
10663
|
+
autoTemplatePreservesContent,
|
|
10664
|
+
deriveTemplateInputs,
|
|
9221
10665
|
listBlock,
|
|
9222
10666
|
photoGrid,
|
|
9223
10667
|
definitionCard,
|
|
@@ -9226,12 +10670,12 @@ export {
|
|
|
9226
10670
|
videoWithCaption,
|
|
9227
10671
|
videoPullQuote,
|
|
9228
10672
|
dataTable,
|
|
10673
|
+
buildChartData,
|
|
9229
10674
|
DIAGRAM_LABEL_LINE_HEIGHT,
|
|
9230
10675
|
DIAGRAM_LABEL_HORIZONTAL_PADDING,
|
|
9231
10676
|
DIAGRAM_LABEL_VERTICAL_PADDING,
|
|
9232
10677
|
DIAGRAM_LABEL_MIN_FONT_SIZE,
|
|
9233
10678
|
fitDiagramLabel,
|
|
9234
|
-
computeDiagramLayout,
|
|
9235
10679
|
diagramBlock,
|
|
9236
10680
|
treeBlock,
|
|
9237
10681
|
timelineBlock,
|
|
@@ -9252,28 +10696,6 @@ export {
|
|
|
9252
10696
|
coerceTemplateParams,
|
|
9253
10697
|
lintTemplateParams,
|
|
9254
10698
|
fallbackBlockLayers,
|
|
9255
|
-
ASCII_CHAR_W,
|
|
9256
|
-
ASCII_CHAR_H,
|
|
9257
|
-
asciiCellToCanvas,
|
|
9258
|
-
canvasToAsciiCell,
|
|
9259
|
-
asciiDiagramToTemplateData,
|
|
9260
|
-
asciiDiagramFromTemplateData,
|
|
9261
|
-
asciiDiagramFromBlocks,
|
|
9262
|
-
asciiTimelineToTemplateData,
|
|
9263
|
-
treeToTemplateData,
|
|
9264
|
-
treeFromTemplateData,
|
|
9265
|
-
treeFromMarkdownList,
|
|
9266
|
-
findFirstList,
|
|
9267
|
-
extractBodyPlainText,
|
|
9268
|
-
extractListItems,
|
|
9269
|
-
extractImages,
|
|
9270
|
-
extractFirstImage,
|
|
9271
|
-
extractEmbeddedVideos,
|
|
9272
|
-
extractFirstEmbeddedVideo,
|
|
9273
|
-
extractTableFromContents,
|
|
9274
|
-
extractBlockquoteText,
|
|
9275
|
-
autoTemplatePreservesContent,
|
|
9276
|
-
deriveTemplateInputs,
|
|
9277
10699
|
BLOCK_MEDIA_LAYOUT_POLICIES,
|
|
9278
10700
|
getBlockMediaLayoutPolicy,
|
|
9279
10701
|
materializeBlockLayers,
|