@bendyline/squisq 2.2.0 → 2.3.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/NOTICE.md +3 -3
- package/README.md +8 -8
- package/dist/{Doc-BubIpWsp.d.ts → Doc-COe-rPQB.d.ts} +6 -4
- package/dist/{ImageEditDoc-CGxk5CHc.d.ts → ImageEditDoc-dtKahVyD.d.ts} +1 -1
- package/dist/{chunk-DYPMOPIW.js → chunk-EK446BUE.js} +1 -1
- package/dist/{chunk-7ARJZYQY.js → chunk-GIIOY6XR.js} +2048 -1200
- package/dist/{chunk-I2O6W7DX.js → chunk-QNTQIEIV.js} +16 -5
- package/dist/{chunk-FDYDYL77.js → chunk-QWCFK5FN.js} +1 -0
- package/dist/{chunk-CB45YGAM.js → chunk-SPTY4C6F.js} +134 -2
- package/dist/doc/index.d.ts +289 -6
- package/dist/doc/index.js +13 -4
- package/dist/generate/index.d.ts +1 -1
- package/dist/generate/index.js +0 -1
- package/dist/icons/index.js +0 -1
- package/dist/imageEdit/index.d.ts +3 -3
- package/dist/index.d.ts +6 -6
- package/dist/index.js +17 -10
- package/dist/jsonForm/index.d.ts +1 -1
- package/dist/jsonForm/index.js +1 -1
- package/dist/{materializePageSection-wSLA2A1g.d.ts → materializePageSection-BSMOocve.d.ts} +1 -1
- package/dist/narration/index.d.ts +1 -1
- package/dist/narration/index.js +3 -4
- package/dist/random/index.js +0 -1
- package/dist/recommend/index.js +1 -2
- package/dist/schemas/index.d.ts +17 -5
- package/dist/schemas/index.js +3 -1
- package/dist/{themeLibrary-DU9mUmVl.d.ts → themeLibrary-BUTWjufL.d.ts} +1 -1
- package/dist/timing/index.js +0 -1
- package/dist/transform/index.d.ts +2 -2
- package/package.json +5 -2
- package/dist/chunk-2L55443L.js +0 -0
- package/dist/chunk-QKPXHDLR.js +0 -0
- package/dist/chunk-WV5ARNDA.js +0 -0
- package/dist/chunk-XWNNA3KE.js +0 -0
- package/dist/chunk-YZNGMV3G.js +0 -0
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
parseAsciiTimeline,
|
|
21
21
|
pickAutoTemplate,
|
|
22
22
|
profileBlockContents
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-QWCFK5FN.js";
|
|
24
24
|
import {
|
|
25
25
|
DEFAULT_THEME,
|
|
26
26
|
FRONTMATTER_CUSTOM_TEMPLATES_KEY,
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
cloneData,
|
|
38
38
|
createTemplateContext,
|
|
39
39
|
getTwoColumnPositions,
|
|
40
|
+
getViewportOrientation,
|
|
40
41
|
isPersistentLayerTemplate,
|
|
41
42
|
isTemplateBlock,
|
|
42
43
|
scaledFontSize2 as scaledFontSize
|
|
@@ -1034,10 +1035,11 @@ function statHighlight(input, context) {
|
|
|
1034
1035
|
|
|
1035
1036
|
// src/doc/templates/quoteBlock.ts
|
|
1036
1037
|
function quoteBlock(input, context) {
|
|
1037
|
-
const { quote, attribution, accentImage } = input;
|
|
1038
|
+
const { title, quote, attribution, accentImage } = input;
|
|
1038
1039
|
const { theme, viewport } = context;
|
|
1039
1040
|
const accentLayout = accentImage ? getAccentLayout(accentImage.position) : DEFAULT_LAYOUT;
|
|
1040
1041
|
const quoteFontSize = themedFontSize(48, context, true);
|
|
1042
|
+
const titleFontSize = themedFontSize(34, context, true);
|
|
1041
1043
|
const attrFontSize = themedFontSize(26, context, false);
|
|
1042
1044
|
const quoteLineHeight = Math.max(theme.typography.titleLineHeight ?? 1.4, 1.25);
|
|
1043
1045
|
const decorativeQuoteFontSize = themedFontSize(280, context, true);
|
|
@@ -1051,6 +1053,30 @@ function quoteBlock(input, context) {
|
|
|
1051
1053
|
)
|
|
1052
1054
|
);
|
|
1053
1055
|
}
|
|
1056
|
+
if (title) {
|
|
1057
|
+
layers.push({
|
|
1058
|
+
type: "text",
|
|
1059
|
+
id: "quote-title",
|
|
1060
|
+
content: {
|
|
1061
|
+
text: title,
|
|
1062
|
+
style: {
|
|
1063
|
+
fontSize: titleFontSize,
|
|
1064
|
+
fontFamily: getThemeFont(context, "body"),
|
|
1065
|
+
fontWeight: "bold",
|
|
1066
|
+
color: theme.colors.textMuted,
|
|
1067
|
+
textAlign: "center",
|
|
1068
|
+
shadow: shouldUseShadow(context)
|
|
1069
|
+
}
|
|
1070
|
+
},
|
|
1071
|
+
position: {
|
|
1072
|
+
x: accentLayout.textCenterX,
|
|
1073
|
+
y: adjustY("14%", accentLayout),
|
|
1074
|
+
width: accentLayout.textWidth,
|
|
1075
|
+
anchor: "center"
|
|
1076
|
+
},
|
|
1077
|
+
animation: { type: "fadeIn", duration: 0.8 }
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1054
1080
|
layers.push({
|
|
1055
1081
|
type: "text",
|
|
1056
1082
|
id: "deco-quote",
|
|
@@ -1069,7 +1095,7 @@ function quoteBlock(input, context) {
|
|
|
1069
1095
|
anchor: "center"
|
|
1070
1096
|
}
|
|
1071
1097
|
});
|
|
1072
|
-
const quoteYPct = attribution ? 45 : 50;
|
|
1098
|
+
const quoteYPct = attribution ? 45 : title ? 52 : 50;
|
|
1073
1099
|
layers.push({
|
|
1074
1100
|
type: "text",
|
|
1075
1101
|
id: "quote",
|
|
@@ -2576,7 +2602,7 @@ function videoWithCaption(input, context) {
|
|
|
2576
2602
|
videoSrc,
|
|
2577
2603
|
posterSrc,
|
|
2578
2604
|
videoAlt,
|
|
2579
|
-
clipStart,
|
|
2605
|
+
clipStart = 0,
|
|
2580
2606
|
clipEnd,
|
|
2581
2607
|
sourceDuration,
|
|
2582
2608
|
caption: rawCaption,
|
|
@@ -2586,6 +2612,7 @@ function videoWithCaption(input, context) {
|
|
|
2586
2612
|
} = input;
|
|
2587
2613
|
const caption = rawCaption ? cleanCaption(rawCaption) : rawCaption;
|
|
2588
2614
|
const { theme } = context;
|
|
2615
|
+
const resolvedClipEnd = Math.max(clipStart, clipEnd ?? clipStart + Math.max(0, input.duration));
|
|
2589
2616
|
const captionFontSize = themedFontSize(30, context, false);
|
|
2590
2617
|
const creditFontSize = themedFontSize(16, context, false);
|
|
2591
2618
|
const layers = [
|
|
@@ -2599,7 +2626,7 @@ function videoWithCaption(input, context) {
|
|
|
2599
2626
|
alt: videoAlt,
|
|
2600
2627
|
fit: "cover",
|
|
2601
2628
|
clipStart,
|
|
2602
|
-
clipEnd,
|
|
2629
|
+
clipEnd: resolvedClipEnd,
|
|
2603
2630
|
sourceDuration,
|
|
2604
2631
|
credit: videoCredit,
|
|
2605
2632
|
license: videoLicense
|
|
@@ -4943,6 +4970,7 @@ var TEMPLATE_INPUT_DESCRIPTORS = {
|
|
|
4943
4970
|
{ key: "colorScheme", description: "Color scheme for the stat", valueHint: COLOR_SCHEME_HINT }
|
|
4944
4971
|
],
|
|
4945
4972
|
quote: [
|
|
4973
|
+
{ key: "title", description: "Optional heading above the quote" },
|
|
4946
4974
|
{ key: "quote", description: "The quote text" },
|
|
4947
4975
|
{ key: "attribution", description: "Attribution (author, source)" }
|
|
4948
4976
|
],
|
|
@@ -5265,1298 +5293,2111 @@ function extractBodyText(block) {
|
|
|
5265
5293
|
return `${text.slice(0, MAX_BODY_CHARS).trimEnd()}\u2026`;
|
|
5266
5294
|
}
|
|
5267
5295
|
|
|
5268
|
-
// src/doc/
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5296
|
+
// src/doc/asciiDiagram/types.ts
|
|
5297
|
+
var ASCII_CHAR_W = 14;
|
|
5298
|
+
var ASCII_CHAR_H = 28;
|
|
5299
|
+
|
|
5300
|
+
// src/doc/asciiDiagram/mapping.ts
|
|
5301
|
+
function asciiCellToCanvas(col, row) {
|
|
5302
|
+
return { x: col * ASCII_CHAR_W, y: row * ASCII_CHAR_H };
|
|
5272
5303
|
}
|
|
5273
|
-
function
|
|
5274
|
-
const theme = options.theme ?? DEFAULT_THEME;
|
|
5275
|
-
const viewport = options.viewport ?? VIEWPORT_PRESETS.landscape;
|
|
5276
|
-
const blockIndex = options.blockIndex ?? 0;
|
|
5277
|
-
const totalBlocks = options.totalBlocks ?? 1;
|
|
5278
|
-
const effectivePersistentLayers = options.persistentLayers === false ? void 0 : options.persistentLayers ?? theme.persistentLayers;
|
|
5279
|
-
const existingLayers = block.layers;
|
|
5280
|
-
if (existingLayers && existingLayers.length > 0 && !isTemplateBlock(block)) {
|
|
5281
|
-
const layers = appendMermaidLayers(cloneData(existingLayers), block, theme, viewport);
|
|
5282
|
-
return {
|
|
5283
|
-
layers: injectPersistentLayers(
|
|
5284
|
-
layers,
|
|
5285
|
-
block,
|
|
5286
|
-
theme,
|
|
5287
|
-
effectivePersistentLayers,
|
|
5288
|
-
runtime.expandedPersistentLayers
|
|
5289
|
-
),
|
|
5290
|
-
source: "authored"
|
|
5291
|
-
};
|
|
5292
|
-
}
|
|
5293
|
-
if (!isTemplateBlock(block)) {
|
|
5294
|
-
const layers = appendMermaidLayers([], block, theme, viewport);
|
|
5295
|
-
return {
|
|
5296
|
-
layers: injectPersistentLayers(
|
|
5297
|
-
layers,
|
|
5298
|
-
block,
|
|
5299
|
-
theme,
|
|
5300
|
-
effectivePersistentLayers,
|
|
5301
|
-
runtime.expandedPersistentLayers
|
|
5302
|
-
),
|
|
5303
|
-
source: layers.length > 0 ? "rich-content" : "empty"
|
|
5304
|
-
};
|
|
5305
|
-
}
|
|
5306
|
-
const context = runtime.templateContext ? cloneData(runtime.templateContext) : createTemplateContext(theme, blockIndex, totalBlocks, viewport);
|
|
5307
|
-
context.block = cloneData(block);
|
|
5308
|
-
const execution = executeTemplateMaterialization(block, context, runtime.registry);
|
|
5309
|
-
if (execution.status === "ok") {
|
|
5310
|
-
const styledLayers = runtime.applyRenderStyle !== false ? applyRenderStyleToLayers(execution.layers, block, theme) : execution.layers;
|
|
5311
|
-
const layers = appendMermaidLayers(styledLayers, block, theme, viewport);
|
|
5312
|
-
return {
|
|
5313
|
-
layers: injectPersistentLayers(
|
|
5314
|
-
layers,
|
|
5315
|
-
block,
|
|
5316
|
-
theme,
|
|
5317
|
-
effectivePersistentLayers,
|
|
5318
|
-
runtime.expandedPersistentLayers
|
|
5319
|
-
),
|
|
5320
|
-
source: "template"
|
|
5321
|
-
};
|
|
5322
|
-
}
|
|
5323
|
-
const diagnostic = toLayerMaterializationDiagnostic(block.template, execution);
|
|
5324
|
-
if ((options.failureMode ?? "fallback") === "empty") {
|
|
5325
|
-
const layers = appendMermaidLayers([], block, theme, viewport);
|
|
5326
|
-
return {
|
|
5327
|
-
layers: injectPersistentLayers(
|
|
5328
|
-
layers,
|
|
5329
|
-
block,
|
|
5330
|
-
theme,
|
|
5331
|
-
effectivePersistentLayers,
|
|
5332
|
-
runtime.expandedPersistentLayers
|
|
5333
|
-
),
|
|
5334
|
-
source: layers.length > 0 ? "rich-content" : "empty",
|
|
5335
|
-
diagnostic
|
|
5336
|
-
};
|
|
5337
|
-
}
|
|
5338
|
-
const fallbackLayers = appendMermaidLayers(
|
|
5339
|
-
fallbackBlockLayers(block, context, diagnostic.message),
|
|
5340
|
-
block,
|
|
5341
|
-
theme,
|
|
5342
|
-
viewport
|
|
5343
|
-
);
|
|
5304
|
+
function canvasToAsciiCell(x, y) {
|
|
5344
5305
|
return {
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
block,
|
|
5348
|
-
theme,
|
|
5349
|
-
effectivePersistentLayers,
|
|
5350
|
-
runtime.expandedPersistentLayers
|
|
5351
|
-
),
|
|
5352
|
-
source: "fallback",
|
|
5353
|
-
diagnostic
|
|
5306
|
+
col: Math.max(0, Math.round(x / ASCII_CHAR_W)),
|
|
5307
|
+
row: Math.max(0, Math.round(y / ASCII_CHAR_H))
|
|
5354
5308
|
};
|
|
5355
5309
|
}
|
|
5356
|
-
function
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
const
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
let y = resolvePositionValue(position.y, viewport.height);
|
|
5374
|
-
const anchor = position.anchor ?? "top-left";
|
|
5375
|
-
if (anchor === "center") {
|
|
5376
|
-
x -= width / 2;
|
|
5377
|
-
y -= height / 2;
|
|
5378
|
-
} else {
|
|
5379
|
-
if (anchor.endsWith("right")) x -= width;
|
|
5380
|
-
if (anchor.startsWith("bottom")) y -= height;
|
|
5381
|
-
}
|
|
5382
|
-
return { x, y, width, height };
|
|
5383
|
-
}
|
|
5384
|
-
function overlapArea(a, b) {
|
|
5385
|
-
const width = Math.max(0, Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x));
|
|
5386
|
-
const height = Math.max(0, Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y));
|
|
5387
|
-
return width * height;
|
|
5388
|
-
}
|
|
5389
|
-
function mermaidRegion(layers, viewport) {
|
|
5390
|
-
const occupied = layers.filter((layer) => layer.type !== "shape" && layer.type !== "path" && layer.type !== "mermaid").map(
|
|
5391
|
-
(layer) => positionRect(
|
|
5392
|
-
layer.position,
|
|
5393
|
-
viewport,
|
|
5394
|
-
layer.type === "text" ? viewport.width * 0.45 : viewport.width * 0.5,
|
|
5395
|
-
layer.type === "text" ? viewport.height * 0.2 : viewport.height * 0.55
|
|
5396
|
-
)
|
|
5397
|
-
);
|
|
5398
|
-
if (occupied.length === 0) {
|
|
5399
|
-
return {
|
|
5400
|
-
x: viewport.width * 0.06,
|
|
5401
|
-
y: viewport.height * 0.08,
|
|
5402
|
-
width: viewport.width * 0.88,
|
|
5403
|
-
height: viewport.height * 0.84
|
|
5404
|
-
};
|
|
5405
|
-
}
|
|
5406
|
-
const candidates = [
|
|
5407
|
-
{
|
|
5408
|
-
x: viewport.width * 0.52,
|
|
5409
|
-
y: viewport.height * 0.12,
|
|
5410
|
-
width: viewport.width * 0.44,
|
|
5411
|
-
height: viewport.height * 0.76
|
|
5412
|
-
},
|
|
5413
|
-
{
|
|
5414
|
-
x: viewport.width * 0.04,
|
|
5415
|
-
y: viewport.height * 0.12,
|
|
5416
|
-
width: viewport.width * 0.44,
|
|
5417
|
-
height: viewport.height * 0.76
|
|
5418
|
-
},
|
|
5419
|
-
{
|
|
5420
|
-
x: viewport.width * 0.15,
|
|
5421
|
-
y: viewport.height * 0.53,
|
|
5422
|
-
width: viewport.width * 0.7,
|
|
5423
|
-
height: viewport.height * 0.41
|
|
5424
|
-
}
|
|
5425
|
-
];
|
|
5426
|
-
return candidates.reduce((best, candidate) => {
|
|
5427
|
-
const score = occupied.reduce((sum, rect) => sum + overlapArea(candidate, rect), 0);
|
|
5428
|
-
const bestScore = occupied.reduce((sum, rect) => sum + overlapArea(best, rect), 0);
|
|
5429
|
-
return score < bestScore ? candidate : best;
|
|
5430
|
-
});
|
|
5431
|
-
}
|
|
5432
|
-
function appendMermaidLayers(layers, block, theme, viewport) {
|
|
5433
|
-
const sources = mermaidSources(block);
|
|
5434
|
-
if (sources.length === 0 || layers.some((layer) => layer.type === "mermaid")) return layers;
|
|
5435
|
-
const region = mermaidRegion(layers, viewport);
|
|
5436
|
-
const columns = sources.length === 1 ? 1 : 2;
|
|
5437
|
-
const rows = Math.ceil(sources.length / columns);
|
|
5438
|
-
const gap = Math.min(viewport.width, viewport.height) * 0.018;
|
|
5439
|
-
const width = (region.width - gap * (columns - 1)) / columns;
|
|
5440
|
-
const height = (region.height - gap * (rows - 1)) / rows;
|
|
5441
|
-
const mermaidLayers = sources.map((source, index) => ({
|
|
5442
|
-
id: `${block.id}-mermaid-${index + 1}`,
|
|
5443
|
-
type: "mermaid",
|
|
5444
|
-
position: {
|
|
5445
|
-
x: region.x + index % columns * (width + gap),
|
|
5446
|
-
y: region.y + Math.floor(index / columns) * (height + gap),
|
|
5447
|
-
width,
|
|
5448
|
-
height
|
|
5449
|
-
},
|
|
5450
|
-
content: {
|
|
5451
|
-
source,
|
|
5452
|
-
background: theme.colors.backgroundLight,
|
|
5453
|
-
foreground: theme.colors.text,
|
|
5454
|
-
padding: Math.max(12, Math.round(Math.min(width, height) * 0.025))
|
|
5455
|
-
}
|
|
5310
|
+
function asciiDiagramToTemplateData(diagram) {
|
|
5311
|
+
const nodes = diagram.nodes.map((n) => ({
|
|
5312
|
+
id: n.id,
|
|
5313
|
+
label: n.label,
|
|
5314
|
+
...asciiCellToCanvas(n.col, n.row),
|
|
5315
|
+
w: n.wCols * ASCII_CHAR_W,
|
|
5316
|
+
h: n.hRows * ASCII_CHAR_H,
|
|
5317
|
+
...n.containerId ? { container: n.containerId } : {}
|
|
5318
|
+
}));
|
|
5319
|
+
const anchors = inferEdgeAnchors(nodes, diagram.edges);
|
|
5320
|
+
const edges = diagram.edges.map((e, index) => ({
|
|
5321
|
+
source: e.source,
|
|
5322
|
+
target: e.target,
|
|
5323
|
+
...e.label ? { label: e.label } : {},
|
|
5324
|
+
...e.directed ? {} : { directed: false },
|
|
5325
|
+
...anchors[index],
|
|
5326
|
+
routing: "orthogonal"
|
|
5456
5327
|
}));
|
|
5457
|
-
return
|
|
5328
|
+
return { nodes, edges };
|
|
5458
5329
|
}
|
|
5459
|
-
function
|
|
5460
|
-
const
|
|
5461
|
-
const
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
}
|
|
5466
|
-
const templateFn = registry[resolveTemplateName(safeTemplateBlock.template)];
|
|
5467
|
-
if (!templateFn) return { status: "unknown-template", layers: [] };
|
|
5468
|
-
const { templateData, templateOverrides } = safeTemplateBlock;
|
|
5469
|
-
const input = templateData || templateOverrides ? {
|
|
5470
|
-
...safeTemplateBlock,
|
|
5471
|
-
...templateData,
|
|
5472
|
-
...coerceTemplateParams(safeTemplateBlock.template, templateOverrides ?? {}).input
|
|
5473
|
-
} : safeTemplateBlock;
|
|
5474
|
-
try {
|
|
5475
|
-
const renderedLayers = templateFn(input, safeContext);
|
|
5476
|
-
if (!Array.isArray(renderedLayers)) {
|
|
5330
|
+
function inferEdgeAnchors(nodes, edges) {
|
|
5331
|
+
const nodeById = new Map(nodes.map((node) => [node.id, node]));
|
|
5332
|
+
const pairs = edges.map((edge) => {
|
|
5333
|
+
const source = nodeById.get(edge.source);
|
|
5334
|
+
const target = nodeById.get(edge.target);
|
|
5335
|
+
if (!source || !target) {
|
|
5477
5336
|
return {
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
receivedType: typeof renderedLayers
|
|
5337
|
+
sourceAnchor: { side: "right", offset: 0.5 },
|
|
5338
|
+
targetAnchor: { side: "left", offset: 0.5 }
|
|
5481
5339
|
};
|
|
5482
5340
|
}
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
|
|
5341
|
+
const sourceW = source.w ?? ASCII_CHAR_W * 12;
|
|
5342
|
+
const sourceH = source.h ?? ASCII_CHAR_H * 3;
|
|
5343
|
+
const targetW = target.w ?? ASCII_CHAR_W * 12;
|
|
5344
|
+
const targetH = target.h ?? ASCII_CHAR_H * 3;
|
|
5345
|
+
const dx = target.x + targetW / 2 - (source.x + sourceW / 2);
|
|
5346
|
+
const dy = target.y + targetH / 2 - (source.y + sourceH / 2);
|
|
5347
|
+
const horizontalDistance = Math.abs(dx) / Math.max(1, (sourceW + targetW) / 2);
|
|
5348
|
+
const verticalDistance = Math.abs(dy) / Math.max(1, (sourceH + targetH) / 2);
|
|
5349
|
+
if (verticalDistance >= horizontalDistance) {
|
|
5350
|
+
return dy >= 0 ? {
|
|
5351
|
+
sourceAnchor: { side: "bottom", offset: 0.5 },
|
|
5352
|
+
targetAnchor: { side: "top", offset: 0.5 }
|
|
5353
|
+
} : {
|
|
5354
|
+
sourceAnchor: { side: "top", offset: 0.5 },
|
|
5355
|
+
targetAnchor: { side: "bottom", offset: 0.5 }
|
|
5356
|
+
};
|
|
5357
|
+
}
|
|
5358
|
+
return dx >= 0 ? {
|
|
5359
|
+
sourceAnchor: { side: "right", offset: 0.5 },
|
|
5360
|
+
targetAnchor: { side: "left", offset: 0.5 }
|
|
5361
|
+
} : {
|
|
5362
|
+
sourceAnchor: { side: "left", offset: 0.5 },
|
|
5363
|
+
targetAnchor: { side: "right", offset: 0.5 }
|
|
5494
5364
|
};
|
|
5495
|
-
}
|
|
5496
|
-
|
|
5365
|
+
});
|
|
5366
|
+
distributeOffsets(pairs, edges, nodes, "source");
|
|
5367
|
+
distributeOffsets(pairs, edges, nodes, "target");
|
|
5368
|
+
return pairs;
|
|
5369
|
+
}
|
|
5370
|
+
function distributeOffsets(pairs, edges, nodes, endpoint) {
|
|
5371
|
+
const nodeById = new Map(nodes.map((node) => [node.id, node]));
|
|
5372
|
+
const groups = /* @__PURE__ */ new Map();
|
|
5373
|
+
for (let index = 0; index < edges.length; index++) {
|
|
5374
|
+
const edge = edges[index];
|
|
5375
|
+
const anchor = endpoint === "source" ? pairs[index].sourceAnchor : pairs[index].targetAnchor;
|
|
5376
|
+
const nodeId = endpoint === "source" ? edge.source : edge.target;
|
|
5377
|
+
const key = `${nodeId}:${anchor.side}`;
|
|
5378
|
+
const group = groups.get(key) ?? [];
|
|
5379
|
+
group.push(index);
|
|
5380
|
+
groups.set(key, group);
|
|
5381
|
+
}
|
|
5382
|
+
for (const indexes of groups.values()) {
|
|
5383
|
+
if (indexes.length < 2) continue;
|
|
5384
|
+
const side = endpoint === "source" ? pairs[indexes[0]].sourceAnchor.side : pairs[indexes[0]].targetAnchor.side;
|
|
5385
|
+
indexes.sort((a, b) => {
|
|
5386
|
+
const oppositeA = nodeById.get(endpoint === "source" ? edges[a].target : edges[a].source);
|
|
5387
|
+
const oppositeB = nodeById.get(endpoint === "source" ? edges[b].target : edges[b].source);
|
|
5388
|
+
const axisA = side === "top" || side === "bottom" ? oppositeA?.x ?? 0 : oppositeA?.y ?? 0;
|
|
5389
|
+
const axisB = side === "top" || side === "bottom" ? oppositeB?.x ?? 0 : oppositeB?.y ?? 0;
|
|
5390
|
+
return axisA - axisB || a - b;
|
|
5391
|
+
});
|
|
5392
|
+
indexes.forEach((edgeIndex, rank) => {
|
|
5393
|
+
const anchor = endpoint === "source" ? pairs[edgeIndex].sourceAnchor : pairs[edgeIndex].targetAnchor;
|
|
5394
|
+
anchor.offset = (rank + 1) / (indexes.length + 1);
|
|
5395
|
+
});
|
|
5396
|
+
}
|
|
5397
|
+
}
|
|
5398
|
+
function asciiDiagramFromTemplateData(nodes, edges, options = {}) {
|
|
5399
|
+
const ids = new Set(nodes.map((n) => n.id));
|
|
5400
|
+
const outNodes = nodes.map((n) => {
|
|
5401
|
+
const { col, row } = canvasToAsciiCell(n.x, n.y);
|
|
5402
|
+
const labelLines = n.label.length > 0 ? n.label.split("\n") : [];
|
|
5403
|
+
const innerW = labelLines.reduce((w, l) => Math.max(w, l.length), 0);
|
|
5404
|
+
const wCols = typeof n.w === "number" ? Math.max(3, Math.round(n.w / ASCII_CHAR_W)) : innerW + 4;
|
|
5405
|
+
const hRows = typeof n.h === "number" ? Math.max(3, Math.round(n.h / ASCII_CHAR_H)) : labelLines.length + 2;
|
|
5497
5406
|
return {
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5407
|
+
id: n.id,
|
|
5408
|
+
label: n.label,
|
|
5409
|
+
col,
|
|
5410
|
+
row,
|
|
5411
|
+
wCols: Math.max(wCols, 3),
|
|
5412
|
+
hRows: Math.max(hRows, 3),
|
|
5413
|
+
...n.container && ids.has(n.container) && n.container !== n.id ? { containerId: n.container } : {}
|
|
5502
5414
|
};
|
|
5503
|
-
}
|
|
5415
|
+
});
|
|
5504
5416
|
return {
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5417
|
+
nodes: outNodes,
|
|
5418
|
+
edges: edges.filter((e) => ids.has(e.source) && ids.has(e.target)).map((e) => ({
|
|
5419
|
+
source: e.source,
|
|
5420
|
+
target: e.target,
|
|
5421
|
+
...e.label ? { label: e.label } : {},
|
|
5422
|
+
directed: e.directed !== false
|
|
5423
|
+
})),
|
|
5424
|
+
style: options.style ?? "unicode",
|
|
5425
|
+
warnings: []
|
|
5509
5426
|
};
|
|
5510
5427
|
}
|
|
5511
|
-
function
|
|
5512
|
-
|
|
5513
|
-
const
|
|
5514
|
-
|
|
5515
|
-
|
|
5428
|
+
function asciiDiagramFromBlocks(children, options = {}) {
|
|
5429
|
+
const layout = computeDiagramLayout(children);
|
|
5430
|
+
const nodes = layout.nodes.map((n) => ({
|
|
5431
|
+
id: n.id,
|
|
5432
|
+
label: n.label,
|
|
5433
|
+
x: n.x,
|
|
5434
|
+
y: n.y
|
|
5435
|
+
}));
|
|
5436
|
+
const edges = layout.edges.map((e) => ({
|
|
5437
|
+
source: e.source,
|
|
5438
|
+
target: e.target,
|
|
5439
|
+
...e.type ? { label: e.type } : {}
|
|
5440
|
+
}));
|
|
5441
|
+
const diagram = asciiDiagramFromTemplateData(nodes, edges, options);
|
|
5442
|
+
diagram.warnings.push(...layout.warnings);
|
|
5443
|
+
return diagram;
|
|
5516
5444
|
}
|
|
5517
5445
|
|
|
5518
|
-
// src/doc/
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
label:
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
}
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
description: "Highlights a date-based event or milestone with a prominent date display."
|
|
5547
|
-
},
|
|
5548
|
-
imageWithCaption: {
|
|
5549
|
-
label: "Image with Caption",
|
|
5550
|
-
description: "Displays a full-bleed image with an optional caption below."
|
|
5551
|
-
},
|
|
5552
|
-
leftFeature: {
|
|
5553
|
-
label: "Left Feature",
|
|
5554
|
-
description: "Image on the left, title and body text stacked on the right."
|
|
5555
|
-
},
|
|
5556
|
-
rightFeature: {
|
|
5557
|
-
label: "Right Feature",
|
|
5558
|
-
description: "Image on the right, title and body text stacked on the left."
|
|
5559
|
-
},
|
|
5560
|
-
map: {
|
|
5561
|
-
label: "Map",
|
|
5562
|
-
description: "Embeds an interactive map centered on a geographic location."
|
|
5563
|
-
},
|
|
5564
|
-
fullBleedQuote: {
|
|
5565
|
-
label: "Full Bleed Quote",
|
|
5566
|
-
description: "A full-width quote that spans the entire slide for maximum impact."
|
|
5567
|
-
},
|
|
5568
|
-
list: {
|
|
5569
|
-
label: "List",
|
|
5570
|
-
description: "Renders a bulleted or numbered list in a clean, card-style layout."
|
|
5571
|
-
},
|
|
5572
|
-
photoGrid: {
|
|
5573
|
-
label: "Photo Grid",
|
|
5574
|
-
description: "Arranges multiple photos in a 2\xD72 or 3\xD73 mosaic grid."
|
|
5575
|
-
},
|
|
5576
|
-
definitionCard: {
|
|
5577
|
-
label: "Definition Card",
|
|
5578
|
-
description: "Shows a term and its definition in a structured, dictionary-style card."
|
|
5579
|
-
},
|
|
5580
|
-
comparisonBar: {
|
|
5581
|
-
label: "Comparison Bar",
|
|
5582
|
-
description: "Visualizes two or more values side-by-side with labeled horizontal bars."
|
|
5583
|
-
},
|
|
5584
|
-
pullQuote: {
|
|
5585
|
-
label: "Pull Quote",
|
|
5586
|
-
description: "A stylized pull quote with large decorative marks and centered text."
|
|
5587
|
-
},
|
|
5588
|
-
videoWithCaption: {
|
|
5589
|
-
label: "Video with Caption",
|
|
5590
|
-
description: "Embeds a video player with an optional caption below."
|
|
5591
|
-
},
|
|
5592
|
-
videoPullQuote: {
|
|
5593
|
-
label: "Video Pull Quote",
|
|
5594
|
-
description: "Combines a video panel with a highlighted pull quote side-by-side."
|
|
5595
|
-
},
|
|
5596
|
-
dataTable: {
|
|
5597
|
-
label: "Data Table",
|
|
5598
|
-
description: "Renders tabular data in a clean, styled table with a header row."
|
|
5599
|
-
},
|
|
5600
|
-
diagram: {
|
|
5601
|
-
label: "Diagram",
|
|
5602
|
-
description: "Renders sub-headings as connected nodes \u2014 edit visually as a node-and-edge diagram."
|
|
5603
|
-
},
|
|
5604
|
-
tree: {
|
|
5605
|
-
label: "Tree",
|
|
5606
|
-
description: "Renders an ASCII file-tree / outline fence as a filesystem-style treeview \u2014 edit as an outline."
|
|
5607
|
-
},
|
|
5608
|
-
timeline: {
|
|
5609
|
-
label: "Timeline",
|
|
5610
|
-
description: "Plots events across one or more horizontal tracks with callouts and optional branching links."
|
|
5611
|
-
},
|
|
5612
|
-
layout: {
|
|
5613
|
-
label: "Layout",
|
|
5614
|
-
description: "Free-form 2D canvas \u2014 drag layers into place. Use for one-off block layouts that don't fit a template."
|
|
5615
|
-
},
|
|
5616
|
-
drawing: {
|
|
5617
|
-
label: "Drawing",
|
|
5618
|
-
description: "Free-form sketches \u2014 draw shapes, paths, and text directly on a 2D surface."
|
|
5619
|
-
}
|
|
5620
|
-
};
|
|
5446
|
+
// src/doc/asciiTimeline/mapping.ts
|
|
5447
|
+
function asciiTimelineToTemplateData(timeline) {
|
|
5448
|
+
const globalStart = timeline.tracks.length > 0 ? Math.min(...timeline.tracks.map((track) => track.startColumn)) : 0;
|
|
5449
|
+
const globalEnd = timeline.tracks.length > 0 ? Math.max(...timeline.tracks.map((track) => track.endColumn)) : 1;
|
|
5450
|
+
const span = Math.max(1, globalEnd - globalStart);
|
|
5451
|
+
const tracks = timeline.tracks.map((track) => ({
|
|
5452
|
+
id: track.id,
|
|
5453
|
+
...track.label ? { label: track.label } : {},
|
|
5454
|
+
...track.endLabel ? { endLabel: track.endLabel } : {},
|
|
5455
|
+
events: track.events.map(
|
|
5456
|
+
(event) => ({
|
|
5457
|
+
id: event.id,
|
|
5458
|
+
label: event.label,
|
|
5459
|
+
...event.description ? { description: event.description } : {},
|
|
5460
|
+
position: clamp012((event.column - globalStart) / span),
|
|
5461
|
+
...event.side ? { side: event.side } : {},
|
|
5462
|
+
...event.descriptionSide ? { descriptionSide: event.descriptionSide } : {},
|
|
5463
|
+
...event.callout === false ? { callout: false } : {},
|
|
5464
|
+
...event.marker ? { marker: event.marker } : {}
|
|
5465
|
+
})
|
|
5466
|
+
)
|
|
5467
|
+
}));
|
|
5468
|
+
const links = timeline.links.map((link) => ({ ...link }));
|
|
5469
|
+
return { tracks, links };
|
|
5470
|
+
}
|
|
5471
|
+
function clamp012(value) {
|
|
5472
|
+
return Math.max(0, Math.min(1, value));
|
|
5473
|
+
}
|
|
5621
5474
|
|
|
5622
|
-
// src/doc/
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
var COVER_TITLE_MIN_BASE_PX = 62;
|
|
5626
|
-
function fitCoverTitleSize(title) {
|
|
5627
|
-
const len = title.trim().length;
|
|
5628
|
-
if (len <= COVER_TITLE_COMFORTABLE_CHARS) return COVER_TITLE_BASE_PX;
|
|
5629
|
-
const scaled = COVER_TITLE_BASE_PX * Math.sqrt(COVER_TITLE_COMFORTABLE_CHARS / len);
|
|
5630
|
-
return Math.max(COVER_TITLE_MIN_BASE_PX, Math.round(scaled));
|
|
5475
|
+
// src/doc/treeview/mapping.ts
|
|
5476
|
+
function treeToTemplateData(tree) {
|
|
5477
|
+
return { items: tree.roots.map(toItem) };
|
|
5631
5478
|
}
|
|
5632
|
-
function
|
|
5633
|
-
|
|
5634
|
-
|
|
5479
|
+
function toItem(node) {
|
|
5480
|
+
return {
|
|
5481
|
+
id: node.id,
|
|
5482
|
+
label: node.label,
|
|
5483
|
+
...node.isDir ? { isDir: true } : {},
|
|
5484
|
+
...node.comment ? { comment: node.comment } : {},
|
|
5485
|
+
children: node.children.map(toItem)
|
|
5486
|
+
};
|
|
5635
5487
|
}
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
{
|
|
5639
|
-
{ pos: 58, alpha: 0.4 },
|
|
5640
|
-
{ pos: 78, alpha: 0.1 },
|
|
5641
|
-
{ pos: 100, alpha: 0 }
|
|
5642
|
-
];
|
|
5643
|
-
function coverScrimGradient(background) {
|
|
5644
|
-
const stops = COVER_SCRIM_STOPS.map((s) => `${withAlpha(background, s.alpha)} ${s.pos}%`);
|
|
5645
|
-
return `linear-gradient(0deg, ${stops.join(", ")})`;
|
|
5488
|
+
function treeFromTemplateData(items, options = {}) {
|
|
5489
|
+
const roots = items.map(fromItem);
|
|
5490
|
+
return { roots, style: options.style ?? "unicode", warnings: [] };
|
|
5646
5491
|
}
|
|
5647
|
-
function
|
|
5648
|
-
const
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5492
|
+
function fromItem(item) {
|
|
5493
|
+
const children = Array.isArray(item.children) ? item.children.map(fromItem) : [];
|
|
5494
|
+
return {
|
|
5495
|
+
id: typeof item.id === "string" && item.id ? item.id : "node",
|
|
5496
|
+
label: typeof item.label === "string" ? item.label : "",
|
|
5497
|
+
children,
|
|
5498
|
+
isDir: item.isDir === true || children.length > 0,
|
|
5499
|
+
...item.comment ? { comment: item.comment } : {}
|
|
5500
|
+
};
|
|
5501
|
+
}
|
|
5502
|
+
function treeFromMarkdownList(list) {
|
|
5503
|
+
const usedIds = /* @__PURE__ */ new Map();
|
|
5504
|
+
const slugify2 = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "node";
|
|
5505
|
+
const idFor = (label) => {
|
|
5506
|
+
const base = slugify2(label.replace(/\/$/, ""));
|
|
5507
|
+
const count = usedIds.get(base) ?? 0;
|
|
5508
|
+
usedIds.set(base, count + 1);
|
|
5509
|
+
return count === 0 ? base : `${base}-${count + 1}`;
|
|
5510
|
+
};
|
|
5511
|
+
const itemToNode = (item) => {
|
|
5512
|
+
const labelNode = item.children.find((c) => c.type !== "list");
|
|
5513
|
+
const label = labelNode ? extractPlainText(labelNode).trim() : "";
|
|
5514
|
+
const childLists = item.children.filter((c) => c.type === "list");
|
|
5515
|
+
const children = [];
|
|
5516
|
+
for (const cl of childLists) for (const ci of cl.children) children.push(itemToNode(ci));
|
|
5517
|
+
return {
|
|
5518
|
+
id: idFor(label),
|
|
5519
|
+
label,
|
|
5520
|
+
children,
|
|
5521
|
+
isDir: label.endsWith("/") || children.length > 0
|
|
5522
|
+
};
|
|
5523
|
+
};
|
|
5524
|
+
return { roots: list.children.map(itemToNode), style: "unicode", warnings: [] };
|
|
5525
|
+
}
|
|
5526
|
+
function findFirstList(contents) {
|
|
5527
|
+
return contents?.find((n) => n.type === "list");
|
|
5528
|
+
}
|
|
5529
|
+
|
|
5530
|
+
// src/doc/templateInputs.ts
|
|
5531
|
+
var VIDEO_FILE_RE = /\.(?:webm|mp4|mov|m4v|ogv)(?:[?#].*)?$/i;
|
|
5532
|
+
function extractBodyPlainText(contents) {
|
|
5533
|
+
if (!contents || contents.length === 0) return "";
|
|
5534
|
+
return contents.filter((node) => !(node.type === "code" && node.lang?.trim().toLowerCase() === "mermaid")).map((n) => extractPlainText(n)).join("\n").trim();
|
|
5535
|
+
}
|
|
5536
|
+
function extractListItems(contents) {
|
|
5537
|
+
if (!contents) return [];
|
|
5538
|
+
const items = [];
|
|
5539
|
+
for (const node of contents) {
|
|
5540
|
+
if (node.type === "list") {
|
|
5541
|
+
for (const item of node.children) {
|
|
5542
|
+
const text = extractPlainText(item).trim();
|
|
5543
|
+
if (text) items.push(text);
|
|
5680
5544
|
}
|
|
5681
|
-
);
|
|
5682
|
-
} else {
|
|
5683
|
-
if (standardLightCover) {
|
|
5684
|
-
layers.push(
|
|
5685
|
-
{
|
|
5686
|
-
type: "shape",
|
|
5687
|
-
id: "cover-bg",
|
|
5688
|
-
content: {
|
|
5689
|
-
shape: "rect",
|
|
5690
|
-
fill: theme.colors.background
|
|
5691
|
-
},
|
|
5692
|
-
position: { x: 0, y: 0, width: "100%", height: "100%" }
|
|
5693
|
-
},
|
|
5694
|
-
{
|
|
5695
|
-
type: "shape",
|
|
5696
|
-
id: "cover-bg-tint",
|
|
5697
|
-
content: {
|
|
5698
|
-
shape: "rect",
|
|
5699
|
-
fill: `radial-gradient(ellipse at 50% 40%, ${withAlpha(theme.colors.primary, 0.16)} 0%, ${withAlpha(theme.colors.primary, 0.07)} 38%, ${withAlpha(theme.colors.primary, 0)} 78%)`
|
|
5700
|
-
},
|
|
5701
|
-
position: { x: 0, y: 0, width: "100%", height: "100%" }
|
|
5702
|
-
}
|
|
5703
|
-
);
|
|
5704
|
-
} else {
|
|
5705
|
-
layers.push({
|
|
5706
|
-
type: "shape",
|
|
5707
|
-
id: "cover-bg",
|
|
5708
|
-
content: {
|
|
5709
|
-
shape: "rect",
|
|
5710
|
-
fill: `radial-gradient(ellipse at 50% 40%, ${theme.colors.primary} 0%, ${theme.colors.background} 100%)`
|
|
5711
|
-
},
|
|
5712
|
-
position: { x: 0, y: 0, width: "100%", height: "100%" }
|
|
5713
|
-
});
|
|
5714
5545
|
}
|
|
5715
|
-
layers.push({
|
|
5716
|
-
type: "shape",
|
|
5717
|
-
id: "cover-accent",
|
|
5718
|
-
content: {
|
|
5719
|
-
shape: "rect",
|
|
5720
|
-
fill: standardLightCover ? withAlpha(theme.colors.primary, 0.24) : "rgba(255, 255, 255, 0.2)"
|
|
5721
|
-
},
|
|
5722
|
-
position: {
|
|
5723
|
-
x: "35%",
|
|
5724
|
-
y: subtitle ? "42%" : "58%",
|
|
5725
|
-
width: "30%",
|
|
5726
|
-
height: "2px"
|
|
5727
|
-
}
|
|
5728
|
-
});
|
|
5729
5546
|
}
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
layers.push({
|
|
5755
|
-
type: "text",
|
|
5756
|
-
id: "cover-subtitle",
|
|
5757
|
-
content: {
|
|
5758
|
-
text: subtitle,
|
|
5759
|
-
style: {
|
|
5760
|
-
fontSize: subtitleFontSize,
|
|
5761
|
-
fontFamily: getThemeFont(context, "body"),
|
|
5762
|
-
fontWeight: standardLightCover ? "bold" : "normal",
|
|
5763
|
-
color: standardLightCover ? withAlpha(theme.colors.text, 0.78) : theme.colors.textMuted,
|
|
5764
|
-
textAlign: "center",
|
|
5765
|
-
lineHeight: 1.5
|
|
5547
|
+
return items;
|
|
5548
|
+
}
|
|
5549
|
+
function parseDim(raw) {
|
|
5550
|
+
if (raw === void 0) return void 0;
|
|
5551
|
+
const n = parseFloat(raw);
|
|
5552
|
+
return Number.isFinite(n) && n > 0 ? n : void 0;
|
|
5553
|
+
}
|
|
5554
|
+
function extractImages(contents, limit = Infinity) {
|
|
5555
|
+
if (!contents || contents.length === 0) return [];
|
|
5556
|
+
const found = [];
|
|
5557
|
+
function fromHtml(nodes) {
|
|
5558
|
+
for (const node of nodes) {
|
|
5559
|
+
if (found.length >= limit) return;
|
|
5560
|
+
if (!node || typeof node !== "object") continue;
|
|
5561
|
+
const n = node;
|
|
5562
|
+
if (n.type === "htmlElement" && n.tagName === "img") {
|
|
5563
|
+
const attrs = n.attributes;
|
|
5564
|
+
if (attrs && typeof attrs.src === "string" && attrs.src) {
|
|
5565
|
+
found.push({
|
|
5566
|
+
src: attrs.src,
|
|
5567
|
+
alt: typeof attrs.alt === "string" ? attrs.alt : "",
|
|
5568
|
+
width: parseDim(attrs.width),
|
|
5569
|
+
height: parseDim(attrs.height)
|
|
5570
|
+
});
|
|
5766
5571
|
}
|
|
5767
|
-
}
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
y: heroSrc ? "82%" : layout.secondaryY,
|
|
5771
|
-
anchor: "center",
|
|
5772
|
-
width: layout.maxTextWidth
|
|
5773
|
-
},
|
|
5774
|
-
animation: { type: "fadeIn", duration: 0.8, delay: 0.2 }
|
|
5775
|
-
});
|
|
5572
|
+
}
|
|
5573
|
+
if (Array.isArray(n.children)) fromHtml(n.children);
|
|
5574
|
+
}
|
|
5776
5575
|
}
|
|
5777
|
-
|
|
5576
|
+
function walk(node) {
|
|
5577
|
+
if (found.length >= limit) return;
|
|
5578
|
+
if (!node || typeof node !== "object") return;
|
|
5579
|
+
const n = node;
|
|
5580
|
+
if (n.type === "image" && typeof n.url === "string" && n.url) {
|
|
5581
|
+
found.push({ src: n.url, alt: typeof n.alt === "string" ? n.alt : "" });
|
|
5582
|
+
return;
|
|
5583
|
+
}
|
|
5584
|
+
if ((n.type === "htmlBlock" || n.type === "htmlInline") && Array.isArray(n.htmlChildren)) {
|
|
5585
|
+
fromHtml(n.htmlChildren);
|
|
5586
|
+
}
|
|
5587
|
+
if (Array.isArray(n.children)) {
|
|
5588
|
+
for (const child of n.children) walk(child);
|
|
5589
|
+
}
|
|
5590
|
+
}
|
|
5591
|
+
for (const node of contents) {
|
|
5592
|
+
if (found.length >= limit) break;
|
|
5593
|
+
walk(node);
|
|
5594
|
+
}
|
|
5595
|
+
return found;
|
|
5778
5596
|
}
|
|
5779
|
-
function
|
|
5780
|
-
return
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5597
|
+
function extractFirstImage(contents) {
|
|
5598
|
+
return extractImages(contents, 1)[0] ?? null;
|
|
5599
|
+
}
|
|
5600
|
+
function extractEmbeddedVideos(contents, limit = Infinity) {
|
|
5601
|
+
if (!contents || contents.length === 0) return [];
|
|
5602
|
+
const found = [];
|
|
5603
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5604
|
+
const add = (video) => {
|
|
5605
|
+
if (!video.src || found.length >= limit || seen.has(video.src)) return;
|
|
5606
|
+
seen.add(video.src);
|
|
5607
|
+
found.push(video);
|
|
5788
5608
|
};
|
|
5609
|
+
function nestedSource(children) {
|
|
5610
|
+
if (!Array.isArray(children)) return void 0;
|
|
5611
|
+
for (const child of children) {
|
|
5612
|
+
if (!child || typeof child !== "object") continue;
|
|
5613
|
+
const node = child;
|
|
5614
|
+
if (node.tagName === "source") {
|
|
5615
|
+
const attrs = node.attributes;
|
|
5616
|
+
if (typeof attrs?.src === "string" && attrs.src) return attrs.src;
|
|
5617
|
+
}
|
|
5618
|
+
const nested = nestedSource(node.children);
|
|
5619
|
+
if (nested) return nested;
|
|
5620
|
+
}
|
|
5621
|
+
return void 0;
|
|
5622
|
+
}
|
|
5623
|
+
function walk(node) {
|
|
5624
|
+
if (found.length >= limit || !node || typeof node !== "object") return;
|
|
5625
|
+
const n = node;
|
|
5626
|
+
if (n.type === "htmlElement" && n.tagName === "video") {
|
|
5627
|
+
const attrs = n.attributes;
|
|
5628
|
+
const src = attrs?.src || nestedSource(n.children);
|
|
5629
|
+
if (src) {
|
|
5630
|
+
add({
|
|
5631
|
+
src,
|
|
5632
|
+
...attrs?.poster ? { posterSrc: attrs.poster } : {},
|
|
5633
|
+
alt: attrs?.["aria-label"] || attrs?.title || attrs?.alt || ""
|
|
5634
|
+
});
|
|
5635
|
+
}
|
|
5636
|
+
} else if ((n.type === "link" || n.type === "image") && typeof n.url === "string" && VIDEO_FILE_RE.test(n.url)) {
|
|
5637
|
+
add({
|
|
5638
|
+
src: n.url,
|
|
5639
|
+
alt: typeof n.alt === "string" ? n.alt : extractPlainText(n).trim()
|
|
5640
|
+
});
|
|
5641
|
+
}
|
|
5642
|
+
if (Array.isArray(n.children)) n.children.forEach(walk);
|
|
5643
|
+
if (Array.isArray(n.htmlChildren)) n.htmlChildren.forEach(walk);
|
|
5644
|
+
}
|
|
5645
|
+
contents.forEach(walk);
|
|
5646
|
+
return found;
|
|
5789
5647
|
}
|
|
5790
|
-
function
|
|
5791
|
-
|
|
5792
|
-
return coverBlock(input, context);
|
|
5648
|
+
function extractFirstEmbeddedVideo(contents) {
|
|
5649
|
+
return extractEmbeddedVideos(contents, 1)[0] ?? null;
|
|
5793
5650
|
}
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
failureMode
|
|
5806
|
-
},
|
|
5807
|
-
{ registry, expandedPersistentLayers }
|
|
5808
|
-
);
|
|
5809
|
-
if (materialized.diagnostic) {
|
|
5810
|
-
onDiagnostic?.(materialized.diagnostic, sourceBlock, blockIndex);
|
|
5651
|
+
function extractTableFromContents(contents) {
|
|
5652
|
+
if (!contents) return null;
|
|
5653
|
+
for (const node of contents) {
|
|
5654
|
+
if (node.type === "table") {
|
|
5655
|
+
const table = node;
|
|
5656
|
+
const [headerRow, ...bodyRows] = table.children;
|
|
5657
|
+
if (!headerRow) return null;
|
|
5658
|
+
const headers = headerRow.children.map((cell) => extractPlainText(cell).trim());
|
|
5659
|
+
const rows = bodyRows.map((row) => row.children.map((cell) => extractPlainText(cell).trim()));
|
|
5660
|
+
return { headers, rows, align: table.align };
|
|
5661
|
+
}
|
|
5811
5662
|
}
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5663
|
+
return null;
|
|
5664
|
+
}
|
|
5665
|
+
function extractBlockquoteText(contents) {
|
|
5666
|
+
if (!contents) return "";
|
|
5667
|
+
for (const node of contents) {
|
|
5668
|
+
if (node.type === "blockquote") return extractPlainText(node).trim();
|
|
5669
|
+
}
|
|
5670
|
+
return "";
|
|
5671
|
+
}
|
|
5672
|
+
function firstParagraph(contents) {
|
|
5673
|
+
if (!contents) return null;
|
|
5674
|
+
const index = contents.findIndex((node) => node.type === "paragraph");
|
|
5675
|
+
if (index < 0) return null;
|
|
5676
|
+
return {
|
|
5677
|
+
node: contents[index],
|
|
5678
|
+
index
|
|
5823
5679
|
};
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5680
|
+
}
|
|
5681
|
+
function leadingStrongStat(paragraph) {
|
|
5682
|
+
for (const child of paragraph.children) {
|
|
5683
|
+
if (child.type === "text" && child.value.trim() === "") continue;
|
|
5684
|
+
if (child.type !== "strong") return null;
|
|
5685
|
+
const text = extractPlainText(child).trim();
|
|
5686
|
+
const match = text.match(/^(?:[$€£¥]\s*)?[+-]?\d+(?:[.,]\d+)*(?:\s?(?:[%‰x×]|[A-Za-z]+))?/);
|
|
5687
|
+
return match?.[0].trim() || null;
|
|
5827
5688
|
}
|
|
5828
|
-
return
|
|
5689
|
+
return null;
|
|
5829
5690
|
}
|
|
5830
|
-
function
|
|
5831
|
-
const
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
const
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
viewport,
|
|
5857
|
-
effectivePersistentLayers,
|
|
5858
|
-
expandedPersistentLayers,
|
|
5859
|
-
registry,
|
|
5860
|
-
failureMode,
|
|
5861
|
-
onDiagnostic
|
|
5691
|
+
function removeStatPhrase(text, start, end) {
|
|
5692
|
+
const before = text.slice(0, start).trim();
|
|
5693
|
+
let after = text.slice(end);
|
|
5694
|
+
if (!before) after = after.replace(/^\s*(?:[-–—:]\s*)?/, "");
|
|
5695
|
+
else after = after.trim();
|
|
5696
|
+
return [before, after].filter(Boolean).join(" ").trim();
|
|
5697
|
+
}
|
|
5698
|
+
function bodyWithoutParagraphStat(contents, paragraphIndex, paragraphText, start, end) {
|
|
5699
|
+
if (!contents) return "";
|
|
5700
|
+
return contents.map(
|
|
5701
|
+
(node, index) => index === paragraphIndex ? removeStatPhrase(paragraphText, start, end) : extractPlainText(node).trim()
|
|
5702
|
+
).filter(Boolean).join("\n").trim();
|
|
5703
|
+
}
|
|
5704
|
+
function deriveStatHighlightInputs(headingText, contents, bodyText3) {
|
|
5705
|
+
const paragraph = firstParagraph(contents);
|
|
5706
|
+
if (paragraph) {
|
|
5707
|
+
const paragraphText = extractPlainText(paragraph.node).trim();
|
|
5708
|
+
const strongStat = leadingStrongStat(paragraph.node);
|
|
5709
|
+
if (strongStat) {
|
|
5710
|
+
const start = paragraphText.indexOf(strongStat);
|
|
5711
|
+
const description = bodyWithoutParagraphStat(
|
|
5712
|
+
contents,
|
|
5713
|
+
paragraph.index,
|
|
5714
|
+
paragraphText,
|
|
5715
|
+
start,
|
|
5716
|
+
start + strongStat.length
|
|
5862
5717
|
);
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5718
|
+
return {
|
|
5719
|
+
stat: strongStat,
|
|
5720
|
+
description: description || headingText || strongStat
|
|
5721
|
+
};
|
|
5722
|
+
}
|
|
5867
5723
|
}
|
|
5868
|
-
const
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5724
|
+
const trimmedHeading = headingText.trim();
|
|
5725
|
+
const headingStat = matchNumberHighlight(trimmedHeading);
|
|
5726
|
+
if (headingStat && headingStat.index === 0 && headingStat.end === trimmedHeading.length) {
|
|
5727
|
+
return { stat: headingText, description: bodyText3 || headingText };
|
|
5728
|
+
}
|
|
5729
|
+
if (paragraph) {
|
|
5730
|
+
const paragraphText = extractPlainText(paragraph.node).trim();
|
|
5731
|
+
const bodyStat = matchNumberHighlight(paragraphText);
|
|
5732
|
+
if (bodyStat) {
|
|
5733
|
+
const description = bodyWithoutParagraphStat(
|
|
5734
|
+
contents,
|
|
5735
|
+
paragraph.index,
|
|
5736
|
+
paragraphText,
|
|
5737
|
+
bodyStat.index,
|
|
5738
|
+
bodyStat.end
|
|
5739
|
+
);
|
|
5740
|
+
return {
|
|
5741
|
+
stat: bodyStat.value,
|
|
5742
|
+
description: description || headingText || bodyStat.value
|
|
5743
|
+
};
|
|
5873
5744
|
}
|
|
5874
|
-
|
|
5875
|
-
}
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5745
|
+
}
|
|
5746
|
+
return { stat: headingText, description: bodyText3 || headingText };
|
|
5747
|
+
}
|
|
5748
|
+
function normalizeCoverageText(value) {
|
|
5749
|
+
return value.replace(/\s+/g, " ").trim();
|
|
5750
|
+
}
|
|
5751
|
+
function autoTemplatePreservesContent(templateName, contents) {
|
|
5752
|
+
const nodes = contents ?? [];
|
|
5753
|
+
switch (templateName) {
|
|
5754
|
+
case "quote":
|
|
5755
|
+
return nodes.length === 1 && nodes[0]?.type === "blockquote";
|
|
5756
|
+
case "dataTable":
|
|
5757
|
+
return nodes.length === 1 && nodes[0]?.type === "table";
|
|
5758
|
+
case "list":
|
|
5759
|
+
return nodes.length === 1 && nodes[0]?.type === "list";
|
|
5760
|
+
case "diagram":
|
|
5761
|
+
case "timeline":
|
|
5762
|
+
return nodes.length === 1 && nodes[0]?.type === "code";
|
|
5763
|
+
case "tree":
|
|
5764
|
+
return nodes.length === 1 && (nodes[0]?.type === "code" || nodes[0]?.type === "list");
|
|
5765
|
+
case "photoGrid": {
|
|
5766
|
+
const images = extractImages(nodes);
|
|
5767
|
+
if (images.length < 2 || images.length > 4) return false;
|
|
5768
|
+
const bodyText3 = normalizeCoverageText(extractBodyPlainText(nodes));
|
|
5769
|
+
const representedText = normalizeCoverageText(
|
|
5770
|
+
images.map((image) => image.alt).filter(Boolean).join(" ")
|
|
5771
|
+
);
|
|
5772
|
+
return bodyText3 === "" || bodyText3 === representedText;
|
|
5899
5773
|
}
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
const
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5774
|
+
case "videoWithCaption": {
|
|
5775
|
+
const videos = extractEmbeddedVideos(nodes);
|
|
5776
|
+
if (videos.length !== 1) return false;
|
|
5777
|
+
const bodyText3 = normalizeCoverageText(extractBodyPlainText(nodes));
|
|
5778
|
+
return bodyText3 === normalizeCoverageText(videos[0]?.alt ?? "");
|
|
5779
|
+
}
|
|
5780
|
+
case "leftFeature":
|
|
5781
|
+
case "rightFeature":
|
|
5782
|
+
case "statHighlight":
|
|
5783
|
+
return true;
|
|
5784
|
+
default:
|
|
5785
|
+
return false;
|
|
5786
|
+
}
|
|
5787
|
+
}
|
|
5788
|
+
function deriveTemplateInputs(templateName, headingText, contents, options = {}) {
|
|
5789
|
+
const placeholders = options.placeholders === true;
|
|
5790
|
+
const bodyText3 = extractBodyPlainText(contents);
|
|
5791
|
+
switch (templateName) {
|
|
5792
|
+
case "statHighlight": {
|
|
5793
|
+
const inputs = deriveStatHighlightInputs(headingText, contents, bodyText3);
|
|
5794
|
+
if (options.preserveSourceHeading && headingText && inputs.stat !== headingText && inputs.description !== headingText) {
|
|
5795
|
+
return {
|
|
5796
|
+
...inputs,
|
|
5797
|
+
description: headingText,
|
|
5798
|
+
...inputs.description ? { detail: inputs.description } : {}
|
|
5799
|
+
};
|
|
5908
5800
|
}
|
|
5801
|
+
return inputs;
|
|
5909
5802
|
}
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
return sum + (block.duration ?? 0);
|
|
5915
|
-
}, 0);
|
|
5916
|
-
const remainingDuration = audioSegment.duration - fixedDuration;
|
|
5917
|
-
const scaleFactor = contentBlockDuration > 0 && remainingDuration > 0 ? remainingDuration / contentBlockDuration : 1;
|
|
5918
|
-
const expandedInfos = [];
|
|
5919
|
-
for (const { block, originalIndex } of segmentBlocks) {
|
|
5920
|
-
const expandedBlock = materializeScheduledBlock(
|
|
5921
|
-
block,
|
|
5922
|
-
originalIndex,
|
|
5923
|
-
totalBlocks,
|
|
5924
|
-
theme,
|
|
5925
|
-
viewport,
|
|
5926
|
-
effectivePersistentLayers,
|
|
5927
|
-
expandedPersistentLayers,
|
|
5928
|
-
registry,
|
|
5929
|
-
failureMode,
|
|
5930
|
-
onDiagnostic
|
|
5931
|
-
);
|
|
5932
|
-
const templateBlock = block;
|
|
5933
|
-
expandedInfos.push({
|
|
5934
|
-
block: expandedBlock,
|
|
5935
|
-
originalIndex,
|
|
5936
|
-
templateBlock,
|
|
5937
|
-
hasSourceTiming: typeof templateBlock.sourceStartTime === "number"
|
|
5938
|
-
});
|
|
5803
|
+
case "quote": {
|
|
5804
|
+
const quote = extractBlockquoteText(contents) || bodyText3;
|
|
5805
|
+
if (quote) return { quote, ...headingText ? { title: headingText } : {} };
|
|
5806
|
+
return { quote: headingText };
|
|
5939
5807
|
}
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
const bIsHeader = b.templateBlock.template === "sectionHeader";
|
|
5945
|
-
if (aIsHeader && !bIsHeader) return -1;
|
|
5946
|
-
if (!aIsHeader && bIsHeader) return 1;
|
|
5947
|
-
if (a.hasSourceTiming && b.hasSourceTiming) {
|
|
5948
|
-
return a.templateBlock.sourceStartTime - b.templateBlock.sourceStartTime;
|
|
5949
|
-
}
|
|
5950
|
-
if (a.hasSourceTiming && !b.hasSourceTiming) return -1;
|
|
5951
|
-
if (!a.hasSourceTiming && b.hasSourceTiming) return 1;
|
|
5952
|
-
}
|
|
5953
|
-
return a.originalIndex - b.originalIndex;
|
|
5954
|
-
});
|
|
5955
|
-
let offsetWithinSegment = 0;
|
|
5956
|
-
for (let i = 0; i < expandedInfos.length; i++) {
|
|
5957
|
-
const info = expandedInfos[i];
|
|
5958
|
-
const { block: expandedBlock, templateBlock } = info;
|
|
5959
|
-
if (info.hasSourceTiming) {
|
|
5960
|
-
const sourceStart = templateBlock.sourceStartTime;
|
|
5961
|
-
expandedBlock.startTime = audioSegment.startTime + sourceStart;
|
|
5962
|
-
if (typeof templateBlock.sourceDuration === "number") {
|
|
5963
|
-
expandedBlock.duration = Math.max(5, templateBlock.sourceDuration);
|
|
5964
|
-
}
|
|
5965
|
-
offsetWithinSegment = sourceStart + expandedBlock.duration;
|
|
5966
|
-
} else if (templateBlock.template === "sectionHeader") {
|
|
5967
|
-
expandedBlock.startTime = audioSegment.startTime + offsetWithinSegment;
|
|
5968
|
-
offsetWithinSegment += expandedBlock.duration;
|
|
5969
|
-
} else {
|
|
5970
|
-
expandedBlock.startTime = audioSegment.startTime + offsetWithinSegment;
|
|
5971
|
-
const scaledDuration = expandedBlock.duration * scaleFactor;
|
|
5972
|
-
expandedBlock.duration = scaledDuration;
|
|
5973
|
-
offsetWithinSegment += scaledDuration;
|
|
5974
|
-
}
|
|
5975
|
-
expandedBlocks[info.originalIndex] = expandedBlock;
|
|
5808
|
+
case "fullBleedQuote":
|
|
5809
|
+
case "pullQuote": {
|
|
5810
|
+
const text = extractBlockquoteText(contents) || bodyText3 || headingText;
|
|
5811
|
+
return { text };
|
|
5976
5812
|
}
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
current.duration = Math.max(0.1, next.startTime - current.startTime);
|
|
5986
|
-
}
|
|
5813
|
+
case "factCard":
|
|
5814
|
+
return { fact: headingText, explanation: bodyText3 || headingText };
|
|
5815
|
+
case "comparisonBar":
|
|
5816
|
+
return placeholders ? { leftLabel: "A", leftValue: 60, rightLabel: "B", rightValue: 40 } : null;
|
|
5817
|
+
case "list": {
|
|
5818
|
+
const items = extractListItems(contents);
|
|
5819
|
+
if (items.length > 0) return { items, ...headingText ? { title: headingText } : {} };
|
|
5820
|
+
return placeholders ? { items: ["Item 1", "Item 2", "Item 3"] } : null;
|
|
5987
5821
|
}
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
lastBlock.duration = 0;
|
|
5997
|
-
lastBlock.startTime = segmentEnd;
|
|
5998
|
-
segmentExpandedBlocks.pop();
|
|
5999
|
-
} else {
|
|
6000
|
-
if (lastBlock.startTime + lastBlock.duration < segmentEnd) {
|
|
6001
|
-
lastBlock.duration = segmentEnd - lastBlock.startTime;
|
|
6002
|
-
}
|
|
6003
|
-
break;
|
|
6004
|
-
}
|
|
5822
|
+
case "definitionCard":
|
|
5823
|
+
return { term: headingText, definition: bodyText3 || headingText };
|
|
5824
|
+
case "dateEvent":
|
|
5825
|
+
return { date: headingText, description: bodyText3 || headingText };
|
|
5826
|
+
case "dataTable": {
|
|
5827
|
+
const tableData = extractTableFromContents(contents);
|
|
5828
|
+
if (tableData) return { ...tableData, ...headingText ? { title: headingText } : {} };
|
|
5829
|
+
return placeholders ? { headers: ["Column"], rows: [["Data"]] } : null;
|
|
6005
5830
|
}
|
|
6006
|
-
|
|
6007
|
-
const
|
|
6008
|
-
if (
|
|
6009
|
-
|
|
6010
|
-
}
|
|
5831
|
+
case "imageWithCaption": {
|
|
5832
|
+
const img = extractFirstImage(contents);
|
|
5833
|
+
if (!img) return placeholders ? { caption: headingText } : null;
|
|
5834
|
+
return { imageSrc: img.src, imageAlt: img.alt || headingText, caption: headingText };
|
|
6011
5835
|
}
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
const blockEnd = block.startTime + block.duration;
|
|
6020
|
-
prev.duration = blockEnd - prev.startTime;
|
|
6021
|
-
block.duration = 0;
|
|
6022
|
-
block.startTime = segmentEnd;
|
|
6023
|
-
segmentExpandedBlocks.splice(i, 1);
|
|
6024
|
-
changed = true;
|
|
6025
|
-
break;
|
|
6026
|
-
}
|
|
6027
|
-
}
|
|
5836
|
+
case "photoGrid": {
|
|
5837
|
+
const images = extractImages(contents, 4);
|
|
5838
|
+
if (images.length < 2) return placeholders ? {} : null;
|
|
5839
|
+
return {
|
|
5840
|
+
images: images.map((i) => ({ src: i.src, alt: i.alt })),
|
|
5841
|
+
caption: headingText
|
|
5842
|
+
};
|
|
6028
5843
|
}
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
5844
|
+
case "videoWithCaption": {
|
|
5845
|
+
const video = extractFirstEmbeddedVideo(contents);
|
|
5846
|
+
if (!video) return placeholders ? { caption: headingText } : null;
|
|
5847
|
+
return {
|
|
5848
|
+
videoSrc: video.src,
|
|
5849
|
+
posterSrc: video.posterSrc,
|
|
5850
|
+
videoAlt: video.alt || headingText,
|
|
5851
|
+
caption: headingText
|
|
5852
|
+
};
|
|
5853
|
+
}
|
|
5854
|
+
case "leftFeature":
|
|
5855
|
+
case "rightFeature": {
|
|
5856
|
+
const img = extractFirstImage(contents);
|
|
5857
|
+
if (!img && !placeholders) return null;
|
|
5858
|
+
return {
|
|
5859
|
+
imageSrc: img?.src ?? "",
|
|
5860
|
+
imageAlt: img?.alt || headingText,
|
|
5861
|
+
imageWidth: img?.width,
|
|
5862
|
+
imageHeight: img?.height,
|
|
5863
|
+
title: headingText,
|
|
5864
|
+
body: bodyText3
|
|
5865
|
+
};
|
|
5866
|
+
}
|
|
5867
|
+
case "diagram": {
|
|
5868
|
+
const fences = (contents ?? []).filter((n) => n.type === "code");
|
|
5869
|
+
const fence = fences.length === 1 && isEligibleAsciiFenceLang(fences[0].lang) ? fences[0] : void 0;
|
|
5870
|
+
const detection = fence ? detectAsciiDiagram(fence.value, { explicit: isExplicitDiagramLang(fence.lang) }) : void 0;
|
|
5871
|
+
if (!detection?.isDiagram || !detection.diagram) return placeholders ? {} : null;
|
|
5872
|
+
const { nodes, edges } = asciiDiagramToTemplateData(detection.diagram);
|
|
5873
|
+
return { nodes, edges, ...headingText ? { title: headingText } : {} };
|
|
5874
|
+
}
|
|
5875
|
+
case "timeline": {
|
|
5876
|
+
const fences = (contents ?? []).filter((n) => n.type === "code");
|
|
5877
|
+
const fence = fences.length === 1 && isEligibleAsciiTimelineFenceLang(fences[0].lang) ? fences[0] : void 0;
|
|
5878
|
+
const detection = fence ? detectAsciiTimeline(fence.value, { explicit: true }) : void 0;
|
|
5879
|
+
if (!detection?.isTimeline || !detection.timeline) return placeholders ? {} : null;
|
|
5880
|
+
const { tracks, links } = asciiTimelineToTemplateData(detection.timeline);
|
|
5881
|
+
return { tracks, links, ...headingText ? { title: headingText } : {} };
|
|
5882
|
+
}
|
|
5883
|
+
case "tree": {
|
|
5884
|
+
const fences = (contents ?? []).filter((n) => n.type === "code");
|
|
5885
|
+
const fence = fences.length === 1 && isEligibleTreeFenceLang(fences[0].lang) ? fences[0] : void 0;
|
|
5886
|
+
if (fence) {
|
|
5887
|
+
const detection = detectTree(fence.value, { explicit: isExplicitTreeLang(fence.lang) });
|
|
5888
|
+
if (detection.isTree && detection.tree) {
|
|
5889
|
+
const { items } = treeToTemplateData(detection.tree);
|
|
5890
|
+
return { items, ...headingText ? { title: headingText } : {} };
|
|
6052
5891
|
}
|
|
6053
5892
|
}
|
|
5893
|
+
const list = findFirstList(contents);
|
|
5894
|
+
if (list) {
|
|
5895
|
+
const { items } = treeToTemplateData(treeFromMarkdownList(list));
|
|
5896
|
+
if (items.length > 0) return { items, ...headingText ? { title: headingText } : {} };
|
|
5897
|
+
}
|
|
5898
|
+
return placeholders ? {} : null;
|
|
6054
5899
|
}
|
|
5900
|
+
default:
|
|
5901
|
+
return placeholders ? {} : null;
|
|
6055
5902
|
}
|
|
6056
|
-
return expandedBlocks.filter((block) => block && block.duration > 0);
|
|
6057
|
-
}
|
|
6058
|
-
function getAvailableTemplates() {
|
|
6059
|
-
return Object.keys(templateRegistry);
|
|
6060
5903
|
}
|
|
6061
|
-
function hasTemplate(name) {
|
|
6062
|
-
return resolveTemplateName(name) in templateRegistry;
|
|
6063
|
-
}
|
|
6064
|
-
|
|
6065
|
-
// src/doc/asciiDiagram/types.ts
|
|
6066
|
-
var ASCII_CHAR_W = 14;
|
|
6067
|
-
var ASCII_CHAR_H = 28;
|
|
6068
5904
|
|
|
6069
|
-
// src/doc/
|
|
6070
|
-
|
|
6071
|
-
|
|
5905
|
+
// src/doc/templates/mediaLayoutPolicy.ts
|
|
5906
|
+
var TITLE_VARIANTS = {
|
|
5907
|
+
landscape: {
|
|
5908
|
+
wide: "title-stack",
|
|
5909
|
+
tall: "split",
|
|
5910
|
+
square: "title-stack",
|
|
5911
|
+
multiple: "title-stack"
|
|
5912
|
+
},
|
|
5913
|
+
square: {
|
|
5914
|
+
wide: "title-stack",
|
|
5915
|
+
tall: "split",
|
|
5916
|
+
square: "title-stack",
|
|
5917
|
+
multiple: "title-stack"
|
|
5918
|
+
},
|
|
5919
|
+
portrait: {
|
|
5920
|
+
wide: "title-stack",
|
|
5921
|
+
tall: "title-stack",
|
|
5922
|
+
square: "title-stack",
|
|
5923
|
+
multiple: "title-stack"
|
|
5924
|
+
}
|
|
5925
|
+
};
|
|
5926
|
+
var TEXT_VARIANTS = {
|
|
5927
|
+
landscape: {
|
|
5928
|
+
wide: "split",
|
|
5929
|
+
tall: "split",
|
|
5930
|
+
square: "split",
|
|
5931
|
+
multiple: "split"
|
|
5932
|
+
},
|
|
5933
|
+
square: {
|
|
5934
|
+
wide: "text-stack",
|
|
5935
|
+
tall: "split",
|
|
5936
|
+
square: "text-stack",
|
|
5937
|
+
multiple: "text-stack"
|
|
5938
|
+
},
|
|
5939
|
+
portrait: {
|
|
5940
|
+
wide: "text-stack",
|
|
5941
|
+
tall: "text-stack",
|
|
5942
|
+
square: "text-stack",
|
|
5943
|
+
multiple: "text-stack"
|
|
5944
|
+
}
|
|
5945
|
+
};
|
|
5946
|
+
var DENSE_VARIANTS = {
|
|
5947
|
+
landscape: {
|
|
5948
|
+
wide: "dense-stack",
|
|
5949
|
+
tall: "dense-stack",
|
|
5950
|
+
square: "dense-stack",
|
|
5951
|
+
multiple: "dense-stack"
|
|
5952
|
+
},
|
|
5953
|
+
square: {
|
|
5954
|
+
wide: "dense-stack",
|
|
5955
|
+
tall: "dense-stack",
|
|
5956
|
+
square: "dense-stack",
|
|
5957
|
+
multiple: "dense-stack"
|
|
5958
|
+
},
|
|
5959
|
+
portrait: {
|
|
5960
|
+
wide: "dense-stack",
|
|
5961
|
+
tall: "dense-stack",
|
|
5962
|
+
square: "dense-stack",
|
|
5963
|
+
multiple: "dense-stack"
|
|
5964
|
+
}
|
|
5965
|
+
};
|
|
5966
|
+
var BLOCK_MEDIA_LAYOUT_POLICIES = {
|
|
5967
|
+
title: {
|
|
5968
|
+
summary: "Text-only by default; supplemental media gets a title band plus a reserved field.",
|
|
5969
|
+
noMedia: "template-default",
|
|
5970
|
+
ownership: "supplemental",
|
|
5971
|
+
unconsumedMedia: "reserved-slot",
|
|
5972
|
+
variants: TITLE_VARIANTS
|
|
5973
|
+
},
|
|
5974
|
+
sectionHeader: {
|
|
5975
|
+
summary: "Uses its authored background image when present; otherwise supplemental media receives a title composition.",
|
|
5976
|
+
noMedia: "template-without-optional-media",
|
|
5977
|
+
ownership: "optional-native",
|
|
5978
|
+
nativeLayout: "background-image",
|
|
5979
|
+
additionalMediaLayout: "overlay-inset",
|
|
5980
|
+
unconsumedMedia: "reserve-when-no-native-media",
|
|
5981
|
+
variants: TITLE_VARIANTS
|
|
5982
|
+
},
|
|
5983
|
+
statHighlight: {
|
|
5984
|
+
summary: "Uses its optional accent image natively; otherwise supplemental media occupies a companion field.",
|
|
5985
|
+
noMedia: "template-without-optional-media",
|
|
5986
|
+
ownership: "optional-native",
|
|
5987
|
+
nativeLayout: "accent-strip-or-inset",
|
|
5988
|
+
additionalMediaLayout: "overlay-inset",
|
|
5989
|
+
unconsumedMedia: "reserve-when-no-native-media",
|
|
5990
|
+
variants: TEXT_VARIANTS
|
|
5991
|
+
},
|
|
5992
|
+
quote: {
|
|
5993
|
+
summary: "Uses its optional accent image natively; otherwise supplemental media is paired with the quote.",
|
|
5994
|
+
noMedia: "template-without-optional-media",
|
|
5995
|
+
ownership: "optional-native",
|
|
5996
|
+
nativeLayout: "accent-strip-or-inset",
|
|
5997
|
+
additionalMediaLayout: "overlay-inset",
|
|
5998
|
+
unconsumedMedia: "reserve-when-no-native-media",
|
|
5999
|
+
variants: TEXT_VARIANTS
|
|
6000
|
+
},
|
|
6001
|
+
factCard: {
|
|
6002
|
+
summary: "Uses its optional accent image natively; otherwise supplemental media is paired with the fact card.",
|
|
6003
|
+
noMedia: "template-without-optional-media",
|
|
6004
|
+
ownership: "optional-native",
|
|
6005
|
+
nativeLayout: "accent-strip-or-inset",
|
|
6006
|
+
additionalMediaLayout: "overlay-inset",
|
|
6007
|
+
unconsumedMedia: "reserve-when-no-native-media",
|
|
6008
|
+
variants: TEXT_VARIANTS
|
|
6009
|
+
},
|
|
6010
|
+
twoColumn: {
|
|
6011
|
+
summary: "Text comparison by default; supplemental media stacks below the dense two-column panel.",
|
|
6012
|
+
noMedia: "template-default",
|
|
6013
|
+
ownership: "supplemental",
|
|
6014
|
+
unconsumedMedia: "reserved-slot",
|
|
6015
|
+
variants: DENSE_VARIANTS
|
|
6016
|
+
},
|
|
6017
|
+
dateEvent: {
|
|
6018
|
+
summary: "Uses its optional accent image natively; otherwise supplemental media is paired with the event.",
|
|
6019
|
+
noMedia: "template-without-optional-media",
|
|
6020
|
+
ownership: "optional-native",
|
|
6021
|
+
nativeLayout: "accent-strip-or-inset",
|
|
6022
|
+
additionalMediaLayout: "overlay-inset",
|
|
6023
|
+
unconsumedMedia: "reserve-when-no-native-media",
|
|
6024
|
+
variants: TEXT_VARIANTS
|
|
6025
|
+
},
|
|
6026
|
+
imageWithCaption: {
|
|
6027
|
+
summary: "Requires one primary image and owns its full-bleed image/caption geometry.",
|
|
6028
|
+
noMedia: "unsupported",
|
|
6029
|
+
ownership: "required-native",
|
|
6030
|
+
nativeLayout: "full-bleed-image",
|
|
6031
|
+
additionalMediaLayout: "overlay-inset",
|
|
6032
|
+
unconsumedMedia: "retain-native-layout"
|
|
6033
|
+
},
|
|
6034
|
+
leftFeature: {
|
|
6035
|
+
summary: "Requires a primary image in the left feature cell; text occupies the right cell.",
|
|
6036
|
+
noMedia: "unsupported",
|
|
6037
|
+
ownership: "required-native",
|
|
6038
|
+
nativeLayout: "feature-left",
|
|
6039
|
+
additionalMediaLayout: "overlay-inset",
|
|
6040
|
+
unconsumedMedia: "retain-native-layout"
|
|
6041
|
+
},
|
|
6042
|
+
rightFeature: {
|
|
6043
|
+
summary: "Requires a primary image in the right feature cell; text occupies the left cell.",
|
|
6044
|
+
noMedia: "unsupported",
|
|
6045
|
+
ownership: "required-native",
|
|
6046
|
+
nativeLayout: "feature-right",
|
|
6047
|
+
additionalMediaLayout: "overlay-inset",
|
|
6048
|
+
unconsumedMedia: "retain-native-layout"
|
|
6049
|
+
},
|
|
6050
|
+
map: {
|
|
6051
|
+
summary: "The map canvas is the primary visual; other media must not reflow its geography.",
|
|
6052
|
+
noMedia: "intrinsic-visual",
|
|
6053
|
+
ownership: "intrinsic-visual",
|
|
6054
|
+
nativeLayout: "map-canvas",
|
|
6055
|
+
additionalMediaLayout: "overlay-inset",
|
|
6056
|
+
unconsumedMedia: "retain-native-layout"
|
|
6057
|
+
},
|
|
6058
|
+
fullBleedQuote: {
|
|
6059
|
+
summary: "Despite its name this is text-only; supplemental media receives the same dramatic title treatment.",
|
|
6060
|
+
noMedia: "template-default",
|
|
6061
|
+
ownership: "supplemental",
|
|
6062
|
+
unconsumedMedia: "reserved-slot",
|
|
6063
|
+
variants: TITLE_VARIANTS
|
|
6064
|
+
},
|
|
6065
|
+
list: {
|
|
6066
|
+
summary: "Uses its optional accent image natively; otherwise supplemental media is paired with the list.",
|
|
6067
|
+
noMedia: "template-without-optional-media",
|
|
6068
|
+
ownership: "optional-native",
|
|
6069
|
+
nativeLayout: "accent-strip-or-inset",
|
|
6070
|
+
additionalMediaLayout: "overlay-inset",
|
|
6071
|
+
unconsumedMedia: "reserve-when-no-native-media",
|
|
6072
|
+
variants: TEXT_VARIANTS
|
|
6073
|
+
},
|
|
6074
|
+
photoGrid: {
|
|
6075
|
+
summary: "Requires a primary image collection and owns the complete tiled media field.",
|
|
6076
|
+
noMedia: "unsupported",
|
|
6077
|
+
ownership: "required-native",
|
|
6078
|
+
nativeLayout: "photo-grid",
|
|
6079
|
+
additionalMediaLayout: "overlay-inset",
|
|
6080
|
+
unconsumedMedia: "retain-native-layout"
|
|
6081
|
+
},
|
|
6082
|
+
definitionCard: {
|
|
6083
|
+
summary: "Uses its optional accent image natively; otherwise supplemental media is paired with the definition.",
|
|
6084
|
+
noMedia: "template-without-optional-media",
|
|
6085
|
+
ownership: "optional-native",
|
|
6086
|
+
nativeLayout: "accent-strip-or-inset",
|
|
6087
|
+
additionalMediaLayout: "overlay-inset",
|
|
6088
|
+
unconsumedMedia: "reserve-when-no-native-media",
|
|
6089
|
+
variants: TEXT_VARIANTS
|
|
6090
|
+
},
|
|
6091
|
+
comparisonBar: {
|
|
6092
|
+
summary: "Data bars are primary; supplemental media stacks below to preserve horizontal scale.",
|
|
6093
|
+
noMedia: "template-default",
|
|
6094
|
+
ownership: "supplemental",
|
|
6095
|
+
unconsumedMedia: "reserved-slot",
|
|
6096
|
+
variants: DENSE_VARIANTS
|
|
6097
|
+
},
|
|
6098
|
+
pullQuote: {
|
|
6099
|
+
summary: "Requires and owns a full-bleed background image behind the quote lockup.",
|
|
6100
|
+
noMedia: "unsupported",
|
|
6101
|
+
ownership: "required-native",
|
|
6102
|
+
nativeLayout: "full-bleed-image",
|
|
6103
|
+
additionalMediaLayout: "overlay-inset",
|
|
6104
|
+
unconsumedMedia: "retain-native-layout"
|
|
6105
|
+
},
|
|
6106
|
+
videoWithCaption: {
|
|
6107
|
+
summary: "Requires one primary video and owns its full-bleed video/caption geometry.",
|
|
6108
|
+
noMedia: "unsupported",
|
|
6109
|
+
ownership: "required-native",
|
|
6110
|
+
nativeLayout: "full-bleed-video",
|
|
6111
|
+
additionalMediaLayout: "overlay-inset",
|
|
6112
|
+
unconsumedMedia: "retain-native-layout"
|
|
6113
|
+
},
|
|
6114
|
+
videoPullQuote: {
|
|
6115
|
+
summary: "Requires and owns a full-bleed video behind the quote lockup.",
|
|
6116
|
+
noMedia: "unsupported",
|
|
6117
|
+
ownership: "required-native",
|
|
6118
|
+
nativeLayout: "full-bleed-video",
|
|
6119
|
+
additionalMediaLayout: "overlay-inset",
|
|
6120
|
+
unconsumedMedia: "retain-native-layout"
|
|
6121
|
+
},
|
|
6122
|
+
dataTable: {
|
|
6123
|
+
summary: "The table remains wide; supplemental media stacks below the dense data region.",
|
|
6124
|
+
noMedia: "template-default",
|
|
6125
|
+
ownership: "supplemental",
|
|
6126
|
+
unconsumedMedia: "reserved-slot",
|
|
6127
|
+
variants: DENSE_VARIANTS
|
|
6128
|
+
},
|
|
6129
|
+
diagram: {
|
|
6130
|
+
summary: "The node-and-edge canvas is intrinsic media and retains its spatial coordinate system.",
|
|
6131
|
+
noMedia: "intrinsic-visual",
|
|
6132
|
+
ownership: "intrinsic-visual",
|
|
6133
|
+
nativeLayout: "spatial-canvas",
|
|
6134
|
+
additionalMediaLayout: "overlay-inset",
|
|
6135
|
+
unconsumedMedia: "retain-native-layout"
|
|
6136
|
+
},
|
|
6137
|
+
tree: {
|
|
6138
|
+
summary: "The interactive tree canvas is intrinsic media and retains its full layout region.",
|
|
6139
|
+
noMedia: "intrinsic-visual",
|
|
6140
|
+
ownership: "intrinsic-visual",
|
|
6141
|
+
nativeLayout: "spatial-canvas",
|
|
6142
|
+
additionalMediaLayout: "overlay-inset",
|
|
6143
|
+
unconsumedMedia: "retain-native-layout"
|
|
6144
|
+
},
|
|
6145
|
+
timeline: {
|
|
6146
|
+
summary: "The track canvas is intrinsic media and retains its shared horizontal time scale.",
|
|
6147
|
+
noMedia: "intrinsic-visual",
|
|
6148
|
+
ownership: "intrinsic-visual",
|
|
6149
|
+
nativeLayout: "spatial-canvas",
|
|
6150
|
+
additionalMediaLayout: "overlay-inset",
|
|
6151
|
+
unconsumedMedia: "retain-native-layout"
|
|
6152
|
+
},
|
|
6153
|
+
layout: {
|
|
6154
|
+
summary: "The authored layer canvas is intrinsic media and must preserve absolute placement.",
|
|
6155
|
+
noMedia: "intrinsic-visual",
|
|
6156
|
+
ownership: "intrinsic-visual",
|
|
6157
|
+
nativeLayout: "freeform-canvas",
|
|
6158
|
+
additionalMediaLayout: "overlay-inset",
|
|
6159
|
+
unconsumedMedia: "retain-native-layout"
|
|
6160
|
+
},
|
|
6161
|
+
drawing: {
|
|
6162
|
+
summary: "The drawing canvas is intrinsic media and must preserve authored geometry.",
|
|
6163
|
+
noMedia: "intrinsic-visual",
|
|
6164
|
+
ownership: "intrinsic-visual",
|
|
6165
|
+
nativeLayout: "freeform-canvas",
|
|
6166
|
+
additionalMediaLayout: "overlay-inset",
|
|
6167
|
+
unconsumedMedia: "retain-native-layout"
|
|
6168
|
+
}
|
|
6169
|
+
};
|
|
6170
|
+
function getBlockMediaLayoutPolicy(template) {
|
|
6171
|
+
if (!template) return void 0;
|
|
6172
|
+
return BLOCK_MEDIA_LAYOUT_POLICIES[template];
|
|
6072
6173
|
}
|
|
6073
|
-
|
|
6174
|
+
|
|
6175
|
+
// src/doc/richMediaLayout.ts
|
|
6176
|
+
function rect(viewport, x, y, width, height) {
|
|
6074
6177
|
return {
|
|
6075
|
-
|
|
6076
|
-
|
|
6178
|
+
x: viewport.width * x,
|
|
6179
|
+
y: viewport.height * y,
|
|
6180
|
+
width: viewport.width * width,
|
|
6181
|
+
height: viewport.height * height
|
|
6077
6182
|
};
|
|
6078
6183
|
}
|
|
6079
|
-
function
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
h: n.hRows * ASCII_CHAR_H,
|
|
6086
|
-
...n.containerId ? { container: n.containerId } : {}
|
|
6087
|
-
}));
|
|
6088
|
-
const anchors = inferEdgeAnchors(nodes, diagram.edges);
|
|
6089
|
-
const edges = diagram.edges.map((e, index) => ({
|
|
6090
|
-
source: e.source,
|
|
6091
|
-
target: e.target,
|
|
6092
|
-
...e.label ? { label: e.label } : {},
|
|
6093
|
-
...e.directed ? {} : { directed: false },
|
|
6094
|
-
...anchors[index],
|
|
6095
|
-
routing: "orthogonal"
|
|
6096
|
-
}));
|
|
6097
|
-
return { nodes, edges };
|
|
6184
|
+
function mediaShape(mediaCount, aspectRatios) {
|
|
6185
|
+
if (mediaCount > 1) return "multiple";
|
|
6186
|
+
const aspect = aspectRatios[0] ?? 16 / 9;
|
|
6187
|
+
if (aspect < 0.8) return "tall";
|
|
6188
|
+
if (aspect > 1.25) return "wide";
|
|
6189
|
+
return "square";
|
|
6098
6190
|
}
|
|
6099
|
-
function
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
return {
|
|
6106
|
-
sourceAnchor: { side: "right", offset: 0.5 },
|
|
6107
|
-
targetAnchor: { side: "left", offset: 0.5 }
|
|
6108
|
-
};
|
|
6109
|
-
}
|
|
6110
|
-
const sourceW = source.w ?? ASCII_CHAR_W * 12;
|
|
6111
|
-
const sourceH = source.h ?? ASCII_CHAR_H * 3;
|
|
6112
|
-
const targetW = target.w ?? ASCII_CHAR_W * 12;
|
|
6113
|
-
const targetH = target.h ?? ASCII_CHAR_H * 3;
|
|
6114
|
-
const dx = target.x + targetW / 2 - (source.x + sourceW / 2);
|
|
6115
|
-
const dy = target.y + targetH / 2 - (source.y + sourceH / 2);
|
|
6116
|
-
const horizontalDistance = Math.abs(dx) / Math.max(1, (sourceW + targetW) / 2);
|
|
6117
|
-
const verticalDistance = Math.abs(dy) / Math.max(1, (sourceH + targetH) / 2);
|
|
6118
|
-
if (verticalDistance >= horizontalDistance) {
|
|
6119
|
-
return dy >= 0 ? {
|
|
6120
|
-
sourceAnchor: { side: "bottom", offset: 0.5 },
|
|
6121
|
-
targetAnchor: { side: "top", offset: 0.5 }
|
|
6122
|
-
} : {
|
|
6123
|
-
sourceAnchor: { side: "top", offset: 0.5 },
|
|
6124
|
-
targetAnchor: { side: "bottom", offset: 0.5 }
|
|
6125
|
-
};
|
|
6126
|
-
}
|
|
6127
|
-
return dx >= 0 ? {
|
|
6128
|
-
sourceAnchor: { side: "right", offset: 0.5 },
|
|
6129
|
-
targetAnchor: { side: "left", offset: 0.5 }
|
|
6130
|
-
} : {
|
|
6131
|
-
sourceAnchor: { side: "left", offset: 0.5 },
|
|
6132
|
-
targetAnchor: { side: "right", offset: 0.5 }
|
|
6191
|
+
function stackedTitleLayout(viewport, orientation) {
|
|
6192
|
+
if (orientation === "portrait") {
|
|
6193
|
+
return {
|
|
6194
|
+
contentRect: rect(viewport, 0.06, 0.04, 0.88, 0.3),
|
|
6195
|
+
mediaRect: rect(viewport, 0.06, 0.39, 0.88, 0.55),
|
|
6196
|
+
fontScale: 0.76
|
|
6133
6197
|
};
|
|
6134
|
-
});
|
|
6135
|
-
distributeOffsets(pairs, edges, nodes, "source");
|
|
6136
|
-
distributeOffsets(pairs, edges, nodes, "target");
|
|
6137
|
-
return pairs;
|
|
6138
|
-
}
|
|
6139
|
-
function distributeOffsets(pairs, edges, nodes, endpoint) {
|
|
6140
|
-
const nodeById = new Map(nodes.map((node) => [node.id, node]));
|
|
6141
|
-
const groups = /* @__PURE__ */ new Map();
|
|
6142
|
-
for (let index = 0; index < edges.length; index++) {
|
|
6143
|
-
const edge = edges[index];
|
|
6144
|
-
const anchor = endpoint === "source" ? pairs[index].sourceAnchor : pairs[index].targetAnchor;
|
|
6145
|
-
const nodeId = endpoint === "source" ? edge.source : edge.target;
|
|
6146
|
-
const key = `${nodeId}:${anchor.side}`;
|
|
6147
|
-
const group = groups.get(key) ?? [];
|
|
6148
|
-
group.push(index);
|
|
6149
|
-
groups.set(key, group);
|
|
6150
|
-
}
|
|
6151
|
-
for (const indexes of groups.values()) {
|
|
6152
|
-
if (indexes.length < 2) continue;
|
|
6153
|
-
const side = endpoint === "source" ? pairs[indexes[0]].sourceAnchor.side : pairs[indexes[0]].targetAnchor.side;
|
|
6154
|
-
indexes.sort((a, b) => {
|
|
6155
|
-
const oppositeA = nodeById.get(endpoint === "source" ? edges[a].target : edges[a].source);
|
|
6156
|
-
const oppositeB = nodeById.get(endpoint === "source" ? edges[b].target : edges[b].source);
|
|
6157
|
-
const axisA = side === "top" || side === "bottom" ? oppositeA?.x ?? 0 : oppositeA?.y ?? 0;
|
|
6158
|
-
const axisB = side === "top" || side === "bottom" ? oppositeB?.x ?? 0 : oppositeB?.y ?? 0;
|
|
6159
|
-
return axisA - axisB || a - b;
|
|
6160
|
-
});
|
|
6161
|
-
indexes.forEach((edgeIndex, rank) => {
|
|
6162
|
-
const anchor = endpoint === "source" ? pairs[edgeIndex].sourceAnchor : pairs[edgeIndex].targetAnchor;
|
|
6163
|
-
anchor.offset = (rank + 1) / (indexes.length + 1);
|
|
6164
|
-
});
|
|
6165
6198
|
}
|
|
6166
|
-
|
|
6167
|
-
function asciiDiagramFromTemplateData(nodes, edges, options = {}) {
|
|
6168
|
-
const ids = new Set(nodes.map((n) => n.id));
|
|
6169
|
-
const outNodes = nodes.map((n) => {
|
|
6170
|
-
const { col, row } = canvasToAsciiCell(n.x, n.y);
|
|
6171
|
-
const labelLines = n.label.length > 0 ? n.label.split("\n") : [];
|
|
6172
|
-
const innerW = labelLines.reduce((w, l) => Math.max(w, l.length), 0);
|
|
6173
|
-
const wCols = typeof n.w === "number" ? Math.max(3, Math.round(n.w / ASCII_CHAR_W)) : innerW + 4;
|
|
6174
|
-
const hRows = typeof n.h === "number" ? Math.max(3, Math.round(n.h / ASCII_CHAR_H)) : labelLines.length + 2;
|
|
6199
|
+
if (orientation === "square") {
|
|
6175
6200
|
return {
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
row,
|
|
6180
|
-
wCols: Math.max(wCols, 3),
|
|
6181
|
-
hRows: Math.max(hRows, 3),
|
|
6182
|
-
...n.container && ids.has(n.container) && n.container !== n.id ? { containerId: n.container } : {}
|
|
6201
|
+
contentRect: rect(viewport, 0.05, 0.04, 0.9, 0.33),
|
|
6202
|
+
mediaRect: rect(viewport, 0.07, 0.42, 0.86, 0.51),
|
|
6203
|
+
fontScale: 0.72
|
|
6183
6204
|
};
|
|
6184
|
-
}
|
|
6185
|
-
return {
|
|
6186
|
-
nodes: outNodes,
|
|
6187
|
-
edges: edges.filter((e) => ids.has(e.source) && ids.has(e.target)).map((e) => ({
|
|
6188
|
-
source: e.source,
|
|
6189
|
-
target: e.target,
|
|
6190
|
-
...e.label ? { label: e.label } : {},
|
|
6191
|
-
directed: e.directed !== false
|
|
6192
|
-
})),
|
|
6193
|
-
style: options.style ?? "unicode",
|
|
6194
|
-
warnings: []
|
|
6195
|
-
};
|
|
6196
|
-
}
|
|
6197
|
-
function asciiDiagramFromBlocks(children, options = {}) {
|
|
6198
|
-
const layout = computeDiagramLayout(children);
|
|
6199
|
-
const nodes = layout.nodes.map((n) => ({
|
|
6200
|
-
id: n.id,
|
|
6201
|
-
label: n.label,
|
|
6202
|
-
x: n.x,
|
|
6203
|
-
y: n.y
|
|
6204
|
-
}));
|
|
6205
|
-
const edges = layout.edges.map((e) => ({
|
|
6206
|
-
source: e.source,
|
|
6207
|
-
target: e.target,
|
|
6208
|
-
...e.type ? { label: e.type } : {}
|
|
6209
|
-
}));
|
|
6210
|
-
const diagram = asciiDiagramFromTemplateData(nodes, edges, options);
|
|
6211
|
-
diagram.warnings.push(...layout.warnings);
|
|
6212
|
-
return diagram;
|
|
6213
|
-
}
|
|
6214
|
-
|
|
6215
|
-
// src/doc/asciiTimeline/mapping.ts
|
|
6216
|
-
function asciiTimelineToTemplateData(timeline) {
|
|
6217
|
-
const globalStart = timeline.tracks.length > 0 ? Math.min(...timeline.tracks.map((track) => track.startColumn)) : 0;
|
|
6218
|
-
const globalEnd = timeline.tracks.length > 0 ? Math.max(...timeline.tracks.map((track) => track.endColumn)) : 1;
|
|
6219
|
-
const span = Math.max(1, globalEnd - globalStart);
|
|
6220
|
-
const tracks = timeline.tracks.map((track) => ({
|
|
6221
|
-
id: track.id,
|
|
6222
|
-
...track.label ? { label: track.label } : {},
|
|
6223
|
-
...track.endLabel ? { endLabel: track.endLabel } : {},
|
|
6224
|
-
events: track.events.map(
|
|
6225
|
-
(event) => ({
|
|
6226
|
-
id: event.id,
|
|
6227
|
-
label: event.label,
|
|
6228
|
-
...event.description ? { description: event.description } : {},
|
|
6229
|
-
position: clamp012((event.column - globalStart) / span),
|
|
6230
|
-
...event.side ? { side: event.side } : {},
|
|
6231
|
-
...event.descriptionSide ? { descriptionSide: event.descriptionSide } : {},
|
|
6232
|
-
...event.callout === false ? { callout: false } : {},
|
|
6233
|
-
...event.marker ? { marker: event.marker } : {}
|
|
6234
|
-
})
|
|
6235
|
-
)
|
|
6236
|
-
}));
|
|
6237
|
-
const links = timeline.links.map((link) => ({ ...link }));
|
|
6238
|
-
return { tracks, links };
|
|
6239
|
-
}
|
|
6240
|
-
function clamp012(value) {
|
|
6241
|
-
return Math.max(0, Math.min(1, value));
|
|
6242
|
-
}
|
|
6243
|
-
|
|
6244
|
-
// src/doc/treeview/mapping.ts
|
|
6245
|
-
function treeToTemplateData(tree) {
|
|
6246
|
-
return { items: tree.roots.map(toItem) };
|
|
6247
|
-
}
|
|
6248
|
-
function toItem(node) {
|
|
6205
|
+
}
|
|
6249
6206
|
return {
|
|
6250
|
-
|
|
6251
|
-
|
|
6252
|
-
|
|
6253
|
-
...node.comment ? { comment: node.comment } : {},
|
|
6254
|
-
children: node.children.map(toItem)
|
|
6207
|
+
contentRect: rect(viewport, 0.04, 0.03, 0.92, 0.32),
|
|
6208
|
+
mediaRect: rect(viewport, 0.08, 0.4, 0.84, 0.52),
|
|
6209
|
+
fontScale: 0.72
|
|
6255
6210
|
};
|
|
6256
6211
|
}
|
|
6257
|
-
function
|
|
6258
|
-
const
|
|
6259
|
-
return { roots, style: options.style ?? "unicode", warnings: [] };
|
|
6260
|
-
}
|
|
6261
|
-
function fromItem(item) {
|
|
6262
|
-
const children = Array.isArray(item.children) ? item.children.map(fromItem) : [];
|
|
6212
|
+
function splitLayout(viewport, orientation) {
|
|
6213
|
+
const square = orientation === "square";
|
|
6263
6214
|
return {
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
isDir: item.isDir === true || children.length > 0,
|
|
6268
|
-
...item.comment ? { comment: item.comment } : {}
|
|
6215
|
+
contentRect: rect(viewport, square ? 0.05 : 0.04, 0.05, square ? 0.42 : 0.43, 0.9),
|
|
6216
|
+
mediaRect: rect(viewport, square ? 0.52 : 0.53, 0.07, square ? 0.43 : 0.43, 0.86),
|
|
6217
|
+
fontScale: 0.72
|
|
6269
6218
|
};
|
|
6270
6219
|
}
|
|
6271
|
-
function
|
|
6272
|
-
|
|
6273
|
-
const slugify2 = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "node";
|
|
6274
|
-
const idFor = (label) => {
|
|
6275
|
-
const base = slugify2(label.replace(/\/$/, ""));
|
|
6276
|
-
const count = usedIds.get(base) ?? 0;
|
|
6277
|
-
usedIds.set(base, count + 1);
|
|
6278
|
-
return count === 0 ? base : `${base}-${count + 1}`;
|
|
6279
|
-
};
|
|
6280
|
-
const itemToNode = (item) => {
|
|
6281
|
-
const labelNode = item.children.find((c) => c.type !== "list");
|
|
6282
|
-
const label = labelNode ? extractPlainText(labelNode).trim() : "";
|
|
6283
|
-
const childLists = item.children.filter((c) => c.type === "list");
|
|
6284
|
-
const children = [];
|
|
6285
|
-
for (const cl of childLists) for (const ci of cl.children) children.push(itemToNode(ci));
|
|
6220
|
+
function stackedTextLayout(viewport, orientation, dense) {
|
|
6221
|
+
if (orientation === "portrait") {
|
|
6286
6222
|
return {
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6223
|
+
contentRect: rect(viewport, 0.06, 0.04, 0.88, dense ? 0.41 : 0.36),
|
|
6224
|
+
mediaRect: rect(viewport, 0.06, dense ? 0.5 : 0.45, 0.88, dense ? 0.44 : 0.49),
|
|
6225
|
+
fontScale: dense ? 0.42 : 0.44
|
|
6226
|
+
};
|
|
6227
|
+
}
|
|
6228
|
+
if (orientation === "square") {
|
|
6229
|
+
return {
|
|
6230
|
+
contentRect: rect(viewport, 0.05, 0.04, 0.9, dense ? 0.43 : 0.36),
|
|
6231
|
+
mediaRect: rect(viewport, 0.07, dense ? 0.52 : 0.45, 0.86, dense ? 0.41 : 0.48),
|
|
6232
|
+
fontScale: dense ? 0.45 : 0.46
|
|
6291
6233
|
};
|
|
6234
|
+
}
|
|
6235
|
+
return {
|
|
6236
|
+
contentRect: rect(viewport, 0.05, 0.03, 0.9, dense ? 0.46 : 0.35),
|
|
6237
|
+
mediaRect: rect(viewport, 0.07, dense ? 0.55 : 0.43, 0.86, dense ? 0.38 : 0.49),
|
|
6238
|
+
fontScale: dense ? 0.5 : 0.48
|
|
6292
6239
|
};
|
|
6293
|
-
return { roots: list.children.map(itemToNode), style: "unicode", warnings: [] };
|
|
6294
|
-
}
|
|
6295
|
-
function findFirstList(contents) {
|
|
6296
|
-
return contents?.find((n) => n.type === "list");
|
|
6297
|
-
}
|
|
6298
|
-
|
|
6299
|
-
// src/doc/templateInputs.ts
|
|
6300
|
-
function extractBodyPlainText(contents) {
|
|
6301
|
-
if (!contents || contents.length === 0) return "";
|
|
6302
|
-
return contents.filter((node) => !(node.type === "code" && node.lang?.trim().toLowerCase() === "mermaid")).map((n) => extractPlainText(n)).join("\n").trim();
|
|
6303
6240
|
}
|
|
6304
|
-
function
|
|
6305
|
-
|
|
6306
|
-
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
|
|
6241
|
+
function overlayMediaRect(viewport, shape) {
|
|
6242
|
+
const orientation = getViewportOrientation(viewport);
|
|
6243
|
+
if (orientation === "portrait") {
|
|
6244
|
+
switch (shape) {
|
|
6245
|
+
case "tall":
|
|
6246
|
+
return rect(viewport, 0.55, 0.5, 0.38, 0.44);
|
|
6247
|
+
case "square":
|
|
6248
|
+
return rect(viewport, 0.49, 0.58, 0.44, 0.35);
|
|
6249
|
+
case "multiple":
|
|
6250
|
+
return rect(viewport, 0.07, 0.67, 0.86, 0.27);
|
|
6251
|
+
case "wide":
|
|
6252
|
+
return rect(viewport, 0.07, 0.71, 0.86, 0.23);
|
|
6253
|
+
}
|
|
6254
|
+
}
|
|
6255
|
+
if (orientation === "square") {
|
|
6256
|
+
switch (shape) {
|
|
6257
|
+
case "tall":
|
|
6258
|
+
return rect(viewport, 0.68, 0.1, 0.27, 0.8);
|
|
6259
|
+
case "square":
|
|
6260
|
+
return rect(viewport, 0.55, 0.52, 0.4, 0.4);
|
|
6261
|
+
case "multiple":
|
|
6262
|
+
return rect(viewport, 0.08, 0.62, 0.84, 0.31);
|
|
6263
|
+
case "wide":
|
|
6264
|
+
return rect(viewport, 0.08, 0.68, 0.84, 0.27);
|
|
6265
|
+
}
|
|
6266
|
+
}
|
|
6267
|
+
switch (shape) {
|
|
6268
|
+
case "tall":
|
|
6269
|
+
return rect(viewport, 0.7, 0.1, 0.26, 0.8);
|
|
6270
|
+
case "square":
|
|
6271
|
+
return rect(viewport, 0.62, 0.22, 0.34, 0.56);
|
|
6272
|
+
case "multiple":
|
|
6273
|
+
return rect(viewport, 0.52, 0.5, 0.44, 0.42);
|
|
6274
|
+
case "wide":
|
|
6275
|
+
return rect(viewport, 0.54, 0.58, 0.42, 0.34);
|
|
6276
|
+
}
|
|
6277
|
+
}
|
|
6278
|
+
function designedLayout(variant, viewport) {
|
|
6279
|
+
const orientation = getViewportOrientation(viewport);
|
|
6280
|
+
switch (variant) {
|
|
6281
|
+
case "title-stack":
|
|
6282
|
+
return stackedTitleLayout(viewport, orientation);
|
|
6283
|
+
case "split":
|
|
6284
|
+
return splitLayout(viewport, orientation);
|
|
6285
|
+
case "text-stack":
|
|
6286
|
+
return stackedTextLayout(viewport, orientation, false);
|
|
6287
|
+
case "dense-stack":
|
|
6288
|
+
return stackedTextLayout(viewport, orientation, true);
|
|
6314
6289
|
}
|
|
6315
|
-
return items;
|
|
6316
6290
|
}
|
|
6317
|
-
function
|
|
6318
|
-
if (
|
|
6319
|
-
|
|
6320
|
-
|
|
6291
|
+
function resolvePositionValue(value, dimension) {
|
|
6292
|
+
if (value === void 0) return 0;
|
|
6293
|
+
if (typeof value === "number") return value;
|
|
6294
|
+
const percent = value.match(/^\s*(-?\d+(?:\.\d+)?)%\s*$/);
|
|
6295
|
+
if (percent) return Number(percent[1]) / 100 * dimension;
|
|
6296
|
+
const numeric = Number.parseFloat(value);
|
|
6297
|
+
return Number.isFinite(numeric) ? numeric : 0;
|
|
6321
6298
|
}
|
|
6322
|
-
function
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
}
|
|
6341
|
-
if (Array.isArray(n.children)) fromHtml(n.children);
|
|
6342
|
-
}
|
|
6299
|
+
function isZero(value) {
|
|
6300
|
+
return value === 0 || value === "0" || value === "0%";
|
|
6301
|
+
}
|
|
6302
|
+
function isFullViewportBackground(layer, viewport) {
|
|
6303
|
+
if (layer.type !== "shape" && layer.type !== "image") return false;
|
|
6304
|
+
const { position } = layer;
|
|
6305
|
+
const fullWidth = position.width === "100%" || position.width === viewport.width;
|
|
6306
|
+
const fullHeight = position.height === "100%" || position.height === viewport.height;
|
|
6307
|
+
return isZero(position.x) && isZero(position.y) && fullWidth && fullHeight;
|
|
6308
|
+
}
|
|
6309
|
+
function mapPosition(position, contentRect, viewport) {
|
|
6310
|
+
const mapped = {
|
|
6311
|
+
...position,
|
|
6312
|
+
x: contentRect.x + resolvePositionValue(position.x, viewport.width) / viewport.width * contentRect.width,
|
|
6313
|
+
y: contentRect.y + resolvePositionValue(position.y, viewport.height) / viewport.height * contentRect.height
|
|
6314
|
+
};
|
|
6315
|
+
if (position.width !== void 0) {
|
|
6316
|
+
mapped.width = resolvePositionValue(position.width, viewport.width) / viewport.width * contentRect.width;
|
|
6343
6317
|
}
|
|
6344
|
-
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
|
|
6348
|
-
|
|
6349
|
-
|
|
6350
|
-
|
|
6318
|
+
if (position.height !== void 0) {
|
|
6319
|
+
mapped.height = resolvePositionValue(position.height, viewport.height) / viewport.height * contentRect.height;
|
|
6320
|
+
}
|
|
6321
|
+
return mapped;
|
|
6322
|
+
}
|
|
6323
|
+
function mapForegroundLayers(layers, contentRect, viewport, fontScale, template) {
|
|
6324
|
+
const mapped = layers.map((layer) => {
|
|
6325
|
+
if (isFullViewportBackground(layer, viewport)) return layer;
|
|
6326
|
+
const position = mapPosition(layer.position, contentRect, viewport);
|
|
6327
|
+
if (layer.type === "text") {
|
|
6328
|
+
return {
|
|
6329
|
+
...layer,
|
|
6330
|
+
position,
|
|
6331
|
+
content: {
|
|
6332
|
+
...layer.content,
|
|
6333
|
+
style: {
|
|
6334
|
+
...layer.content.style,
|
|
6335
|
+
fontSize: Math.max(12, Math.round(layer.content.style.fontSize * fontScale)),
|
|
6336
|
+
...layer.content.style.padding !== void 0 ? { padding: Math.round(layer.content.style.padding * fontScale) } : {}
|
|
6337
|
+
}
|
|
6338
|
+
}
|
|
6339
|
+
};
|
|
6351
6340
|
}
|
|
6352
|
-
|
|
6353
|
-
|
|
6341
|
+
return { ...layer, position };
|
|
6342
|
+
});
|
|
6343
|
+
return arrangeTitleFamily(mapped, template, contentRect, viewport);
|
|
6344
|
+
}
|
|
6345
|
+
function arrangeTitleFamily(layers, template, contentRect, viewport) {
|
|
6346
|
+
const centerX = contentRect.x + contentRect.width / 2;
|
|
6347
|
+
const centerY = contentRect.y + contentRect.height / 2;
|
|
6348
|
+
if (template === "sectionHeader") {
|
|
6349
|
+
return layers.map((layer) => {
|
|
6350
|
+
if (layer.id === "title") {
|
|
6351
|
+
return {
|
|
6352
|
+
...layer,
|
|
6353
|
+
position: {
|
|
6354
|
+
...layer.position,
|
|
6355
|
+
x: centerX,
|
|
6356
|
+
y: centerY,
|
|
6357
|
+
width: contentRect.width * 0.9,
|
|
6358
|
+
anchor: "center"
|
|
6359
|
+
}
|
|
6360
|
+
};
|
|
6361
|
+
}
|
|
6362
|
+
if (layer.id === "line-top" || layer.id === "line-bottom") {
|
|
6363
|
+
const direction = layer.id === "line-top" ? -1 : 1;
|
|
6364
|
+
return {
|
|
6365
|
+
...layer,
|
|
6366
|
+
position: {
|
|
6367
|
+
...layer.position,
|
|
6368
|
+
x: centerX,
|
|
6369
|
+
y: centerY + direction * contentRect.height * 0.28,
|
|
6370
|
+
width: Math.min(contentRect.width * 0.22, viewport.width * 0.2),
|
|
6371
|
+
height: 2,
|
|
6372
|
+
anchor: "center"
|
|
6373
|
+
}
|
|
6374
|
+
};
|
|
6375
|
+
}
|
|
6376
|
+
return layer;
|
|
6377
|
+
});
|
|
6378
|
+
}
|
|
6379
|
+
if (template !== "title") return layers;
|
|
6380
|
+
const title = layers.find((layer) => layer.type === "text" && layer.id === "title");
|
|
6381
|
+
const subtitle = layers.find((layer) => layer.type === "text" && layer.id === "subtitle");
|
|
6382
|
+
if (!title || title.type !== "text") return layers;
|
|
6383
|
+
const subtitleText = subtitle?.type === "text" ? subtitle : void 0;
|
|
6384
|
+
const maxWidth = contentRect.width * 0.9;
|
|
6385
|
+
const decorationGap = Math.max(12, Math.min(viewport.width, viewport.height) * 0.018);
|
|
6386
|
+
const textGap = subtitleText ? decorationGap : 0;
|
|
6387
|
+
const availableHeight = contentRect.height * 0.78;
|
|
6388
|
+
let titleFontSize = title.content.style.fontSize;
|
|
6389
|
+
let subtitleFontSize = subtitleText?.content.style.fontSize ?? 0;
|
|
6390
|
+
const measure = () => {
|
|
6391
|
+
const titleHeight = estimateTextHeight(
|
|
6392
|
+
title.content.text,
|
|
6393
|
+
titleFontSize,
|
|
6394
|
+
maxWidth,
|
|
6395
|
+
title.content.style.lineHeight ?? 1.15
|
|
6396
|
+
);
|
|
6397
|
+
const subtitleHeight = subtitleText ? estimateTextHeight(
|
|
6398
|
+
subtitleText.content.text,
|
|
6399
|
+
subtitleFontSize,
|
|
6400
|
+
maxWidth,
|
|
6401
|
+
subtitleText.content.style.lineHeight ?? 1.5
|
|
6402
|
+
) : 0;
|
|
6403
|
+
return { titleHeight, subtitleHeight, total: titleHeight + textGap + subtitleHeight };
|
|
6404
|
+
};
|
|
6405
|
+
let measured = measure();
|
|
6406
|
+
if (measured.total > availableHeight) {
|
|
6407
|
+
const fit = Math.max(0.5, availableHeight / measured.total);
|
|
6408
|
+
titleFontSize = Math.max(18, Math.round(titleFontSize * fit));
|
|
6409
|
+
subtitleFontSize = Math.max(14, Math.round(subtitleFontSize * fit));
|
|
6410
|
+
measured = measure();
|
|
6411
|
+
}
|
|
6412
|
+
const top = contentRect.y + (contentRect.height - measured.total) / 2;
|
|
6413
|
+
const titleY = top + measured.titleHeight / 2;
|
|
6414
|
+
const subtitleY = top + measured.titleHeight + textGap + measured.subtitleHeight / 2;
|
|
6415
|
+
return layers.map((layer) => {
|
|
6416
|
+
if (layer.id === "title" && layer.type === "text") {
|
|
6417
|
+
return {
|
|
6418
|
+
...layer,
|
|
6419
|
+
position: { ...layer.position, x: centerX, y: titleY, width: maxWidth, anchor: "center" },
|
|
6420
|
+
content: {
|
|
6421
|
+
...layer.content,
|
|
6422
|
+
style: { ...layer.content.style, fontSize: titleFontSize }
|
|
6423
|
+
}
|
|
6424
|
+
};
|
|
6354
6425
|
}
|
|
6355
|
-
if (
|
|
6356
|
-
|
|
6426
|
+
if (layer.id === "subtitle" && layer.type === "text") {
|
|
6427
|
+
return {
|
|
6428
|
+
...layer,
|
|
6429
|
+
position: {
|
|
6430
|
+
...layer.position,
|
|
6431
|
+
x: centerX,
|
|
6432
|
+
y: subtitleY,
|
|
6433
|
+
width: maxWidth,
|
|
6434
|
+
anchor: "center"
|
|
6435
|
+
},
|
|
6436
|
+
content: {
|
|
6437
|
+
...layer.content,
|
|
6438
|
+
style: { ...layer.content.style, fontSize: subtitleFontSize }
|
|
6439
|
+
}
|
|
6440
|
+
};
|
|
6357
6441
|
}
|
|
6442
|
+
if (layer.id === "accent-line") {
|
|
6443
|
+
return {
|
|
6444
|
+
...layer,
|
|
6445
|
+
position: {
|
|
6446
|
+
...layer.position,
|
|
6447
|
+
x: centerX,
|
|
6448
|
+
y: Math.max(contentRect.y + 2, top - decorationGap * 0.75),
|
|
6449
|
+
width: Math.min(contentRect.width * 0.18, viewport.width * 0.12),
|
|
6450
|
+
height: 3,
|
|
6451
|
+
anchor: "center"
|
|
6452
|
+
}
|
|
6453
|
+
};
|
|
6454
|
+
}
|
|
6455
|
+
return layer;
|
|
6456
|
+
});
|
|
6457
|
+
}
|
|
6458
|
+
function positionRect(position, viewport, fallbackWidth, fallbackHeight) {
|
|
6459
|
+
const width = position.width ? resolvePositionValue(position.width, viewport.width) : fallbackWidth;
|
|
6460
|
+
const height = position.height ? resolvePositionValue(position.height, viewport.height) : fallbackHeight;
|
|
6461
|
+
let x = resolvePositionValue(position.x, viewport.width);
|
|
6462
|
+
let y = resolvePositionValue(position.y, viewport.height);
|
|
6463
|
+
const anchor = position.anchor ?? "top-left";
|
|
6464
|
+
if (anchor === "center") {
|
|
6465
|
+
x -= width / 2;
|
|
6466
|
+
y -= height / 2;
|
|
6467
|
+
} else {
|
|
6468
|
+
if (anchor.endsWith("right")) x -= width;
|
|
6469
|
+
if (anchor.startsWith("bottom")) y -= height;
|
|
6358
6470
|
}
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
|
|
6471
|
+
return { x, y, width, height };
|
|
6472
|
+
}
|
|
6473
|
+
function overlapArea(a, b) {
|
|
6474
|
+
const width = Math.max(0, Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x));
|
|
6475
|
+
const height = Math.max(0, Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y));
|
|
6476
|
+
return width * height;
|
|
6477
|
+
}
|
|
6478
|
+
function leastObstructedMediaRect(layers, viewport) {
|
|
6479
|
+
const occupied = layers.filter((layer) => layer.type !== "shape" && layer.type !== "path" && layer.type !== "mermaid").map(
|
|
6480
|
+
(layer) => positionRect(
|
|
6481
|
+
layer.position,
|
|
6482
|
+
viewport,
|
|
6483
|
+
layer.type === "text" ? viewport.width * 0.45 : viewport.width * 0.5,
|
|
6484
|
+
layer.type === "text" ? viewport.height * 0.2 : viewport.height * 0.55
|
|
6485
|
+
)
|
|
6486
|
+
);
|
|
6487
|
+
if (occupied.length === 0) return rect(viewport, 0.06, 0.08, 0.88, 0.84);
|
|
6488
|
+
const candidates = [
|
|
6489
|
+
rect(viewport, 0.52, 0.12, 0.44, 0.76),
|
|
6490
|
+
rect(viewport, 0.04, 0.12, 0.44, 0.76),
|
|
6491
|
+
rect(viewport, 0.15, 0.53, 0.7, 0.41)
|
|
6492
|
+
];
|
|
6493
|
+
return candidates.reduce((best, candidate) => {
|
|
6494
|
+
const score = occupied.reduce((sum, item) => sum + overlapArea(candidate, item), 0);
|
|
6495
|
+
const bestScore = occupied.reduce((sum, item) => sum + overlapArea(best, item), 0);
|
|
6496
|
+
return score < bestScore ? candidate : best;
|
|
6497
|
+
});
|
|
6498
|
+
}
|
|
6499
|
+
function resolveSupplementalMediaLayout(layers, template, viewport, mediaCount, aspectRatios) {
|
|
6500
|
+
const policy = getBlockMediaLayoutPolicy(template);
|
|
6501
|
+
const shape = mediaShape(mediaCount, aspectRatios);
|
|
6502
|
+
const alreadyOwnsMedia = layers.some(
|
|
6503
|
+
(layer) => layer.type === "image" || layer.type === "video" || layer.type === "map" || layer.type === "mermaid"
|
|
6504
|
+
);
|
|
6505
|
+
const retainNativeLayout = policy?.unconsumedMedia === "retain-native-layout" || policy?.unconsumedMedia === "reserve-when-no-native-media" && alreadyOwnsMedia;
|
|
6506
|
+
if (policy?.additionalMediaLayout === "overlay-inset" && retainNativeLayout) {
|
|
6507
|
+
return {
|
|
6508
|
+
layers,
|
|
6509
|
+
mediaRect: overlayMediaRect(viewport, shape),
|
|
6510
|
+
framed: true
|
|
6511
|
+
};
|
|
6362
6512
|
}
|
|
6363
|
-
|
|
6513
|
+
const canReserve = policy?.unconsumedMedia === "reserved-slot" || policy?.unconsumedMedia === "reserve-when-no-native-media" && !alreadyOwnsMedia;
|
|
6514
|
+
if (!policy || !canReserve || !policy.variants) {
|
|
6515
|
+
return {
|
|
6516
|
+
layers,
|
|
6517
|
+
mediaRect: leastObstructedMediaRect(layers, viewport),
|
|
6518
|
+
framed: false
|
|
6519
|
+
};
|
|
6520
|
+
}
|
|
6521
|
+
const orientation = getViewportOrientation(viewport);
|
|
6522
|
+
const layout = designedLayout(policy.variants[orientation][shape], viewport);
|
|
6523
|
+
return {
|
|
6524
|
+
layers: mapForegroundLayers(
|
|
6525
|
+
layers,
|
|
6526
|
+
layout.contentRect,
|
|
6527
|
+
viewport,
|
|
6528
|
+
layout.fontScale,
|
|
6529
|
+
template ?? ""
|
|
6530
|
+
),
|
|
6531
|
+
mediaRect: layout.mediaRect,
|
|
6532
|
+
framed: true
|
|
6533
|
+
};
|
|
6364
6534
|
}
|
|
6365
|
-
|
|
6366
|
-
|
|
6535
|
+
|
|
6536
|
+
// src/doc/materializeBlockLayers.ts
|
|
6537
|
+
function materializeBlockLayers(block, options = {}) {
|
|
6538
|
+
const registry = options.customTemplates && options.customTemplates.length > 0 ? buildRegistry(options.customTemplates) : templateRegistry;
|
|
6539
|
+
return materializeBlockLayersWithRuntime(block, options, { registry });
|
|
6367
6540
|
}
|
|
6368
|
-
function
|
|
6369
|
-
|
|
6370
|
-
|
|
6371
|
-
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
|
|
6541
|
+
function materializeBlockLayersWithRuntime(block, options, runtime) {
|
|
6542
|
+
const theme = options.theme ?? DEFAULT_THEME;
|
|
6543
|
+
const viewport = options.viewport ?? VIEWPORT_PRESETS.landscape;
|
|
6544
|
+
const blockIndex = options.blockIndex ?? 0;
|
|
6545
|
+
const totalBlocks = options.totalBlocks ?? 1;
|
|
6546
|
+
const effectivePersistentLayers = options.persistentLayers === false ? void 0 : options.persistentLayers ?? theme.persistentLayers;
|
|
6547
|
+
const existingLayers = block.layers;
|
|
6548
|
+
if (existingLayers && existingLayers.length > 0 && !isTemplateBlock(block)) {
|
|
6549
|
+
const layers = appendRichContentLayers(
|
|
6550
|
+
cloneData(existingLayers),
|
|
6551
|
+
block,
|
|
6552
|
+
theme,
|
|
6553
|
+
viewport
|
|
6554
|
+
);
|
|
6555
|
+
return {
|
|
6556
|
+
layers: injectPersistentLayers(
|
|
6557
|
+
layers,
|
|
6558
|
+
block,
|
|
6559
|
+
theme,
|
|
6560
|
+
effectivePersistentLayers,
|
|
6561
|
+
runtime.expandedPersistentLayers
|
|
6562
|
+
),
|
|
6563
|
+
source: "authored"
|
|
6564
|
+
};
|
|
6565
|
+
}
|
|
6566
|
+
if (!isTemplateBlock(block)) {
|
|
6567
|
+
const layers = appendRichContentLayers([], block, theme, viewport);
|
|
6568
|
+
return {
|
|
6569
|
+
layers: injectPersistentLayers(
|
|
6570
|
+
layers,
|
|
6571
|
+
block,
|
|
6572
|
+
theme,
|
|
6573
|
+
effectivePersistentLayers,
|
|
6574
|
+
runtime.expandedPersistentLayers
|
|
6575
|
+
),
|
|
6576
|
+
source: layers.length > 0 ? "rich-content" : "empty"
|
|
6577
|
+
};
|
|
6578
|
+
}
|
|
6579
|
+
const context = runtime.templateContext ? cloneData(runtime.templateContext) : createTemplateContext(theme, blockIndex, totalBlocks, viewport);
|
|
6580
|
+
context.block = cloneData(block);
|
|
6581
|
+
const execution = executeTemplateMaterialization(block, context, runtime.registry);
|
|
6582
|
+
if (execution.status === "ok") {
|
|
6583
|
+
const styledLayers = runtime.applyRenderStyle !== false ? applyRenderStyleToLayers(execution.layers, block, theme) : execution.layers;
|
|
6584
|
+
const layers = appendRichContentLayers(styledLayers, block, theme, viewport);
|
|
6585
|
+
return {
|
|
6586
|
+
layers: injectPersistentLayers(
|
|
6587
|
+
layers,
|
|
6588
|
+
block,
|
|
6589
|
+
theme,
|
|
6590
|
+
effectivePersistentLayers,
|
|
6591
|
+
runtime.expandedPersistentLayers
|
|
6592
|
+
),
|
|
6593
|
+
source: "template"
|
|
6594
|
+
};
|
|
6595
|
+
}
|
|
6596
|
+
const diagnostic = toLayerMaterializationDiagnostic(block.template, execution);
|
|
6597
|
+
if ((options.failureMode ?? "fallback") === "empty") {
|
|
6598
|
+
const layers = appendRichContentLayers([], block, theme, viewport);
|
|
6599
|
+
return {
|
|
6600
|
+
layers: injectPersistentLayers(
|
|
6601
|
+
layers,
|
|
6602
|
+
block,
|
|
6603
|
+
theme,
|
|
6604
|
+
effectivePersistentLayers,
|
|
6605
|
+
runtime.expandedPersistentLayers
|
|
6606
|
+
),
|
|
6607
|
+
source: layers.length > 0 ? "rich-content" : "empty",
|
|
6608
|
+
diagnostic
|
|
6609
|
+
};
|
|
6610
|
+
}
|
|
6611
|
+
const fallbackLayers = appendRichContentLayers(
|
|
6612
|
+
fallbackBlockLayers(block, context, diagnostic.message),
|
|
6613
|
+
block,
|
|
6614
|
+
theme,
|
|
6615
|
+
viewport
|
|
6616
|
+
);
|
|
6617
|
+
return {
|
|
6618
|
+
layers: injectPersistentLayers(
|
|
6619
|
+
fallbackLayers,
|
|
6620
|
+
block,
|
|
6621
|
+
theme,
|
|
6622
|
+
effectivePersistentLayers,
|
|
6623
|
+
runtime.expandedPersistentLayers
|
|
6624
|
+
),
|
|
6625
|
+
source: "fallback",
|
|
6626
|
+
diagnostic
|
|
6627
|
+
};
|
|
6628
|
+
}
|
|
6629
|
+
function mermaidSources(block) {
|
|
6630
|
+
return (block.contents ?? []).filter(
|
|
6631
|
+
(node) => node.type === "code" && node.lang?.trim().toLowerCase() === "mermaid"
|
|
6632
|
+
).map((node) => node.value).filter((source) => source.trim().length > 0);
|
|
6633
|
+
}
|
|
6634
|
+
function collectRichMediaItems(layers, block) {
|
|
6635
|
+
const existingImages = new Set(
|
|
6636
|
+
layers.filter((layer) => layer.type === "image").map((layer) => layer.content.src)
|
|
6637
|
+
);
|
|
6638
|
+
const existingVideos = new Set(
|
|
6639
|
+
layers.filter((layer) => layer.type === "video").map((layer) => layer.content.src)
|
|
6640
|
+
);
|
|
6641
|
+
const existingMermaid = new Set(
|
|
6642
|
+
layers.filter((layer) => layer.type === "mermaid").map((layer) => layer.content.source)
|
|
6643
|
+
);
|
|
6644
|
+
const embeddedVideos = extractEmbeddedVideos(block.contents);
|
|
6645
|
+
const videos = embeddedVideos.filter((video) => !existingVideos.has(video.src));
|
|
6646
|
+
const videoSources = new Set(embeddedVideos.map((video) => video.src));
|
|
6647
|
+
const seenImages = new Set(existingImages);
|
|
6648
|
+
const images = extractImages(block.contents).filter((image) => {
|
|
6649
|
+
if (videoSources.has(image.src) || seenImages.has(image.src)) return false;
|
|
6650
|
+
seenImages.add(image.src);
|
|
6651
|
+
return true;
|
|
6652
|
+
});
|
|
6653
|
+
const sources = mermaidSources(block).filter((source) => !existingMermaid.has(source));
|
|
6654
|
+
return [
|
|
6655
|
+
...images.map(
|
|
6656
|
+
(image) => ({
|
|
6657
|
+
kind: "image",
|
|
6658
|
+
src: image.src,
|
|
6659
|
+
alt: image.alt,
|
|
6660
|
+
...image.width && image.height ? { aspectRatio: image.width / image.height } : {}
|
|
6661
|
+
})
|
|
6662
|
+
),
|
|
6663
|
+
...videos.map(
|
|
6664
|
+
(video) => ({
|
|
6665
|
+
kind: "video",
|
|
6666
|
+
src: video.src,
|
|
6667
|
+
...video.posterSrc ? { posterSrc: video.posterSrc } : {},
|
|
6668
|
+
alt: video.alt,
|
|
6669
|
+
aspectRatio: 16 / 9
|
|
6670
|
+
})
|
|
6671
|
+
),
|
|
6672
|
+
...sources.map((source) => ({ kind: "mermaid", source, aspectRatio: 16 / 9 }))
|
|
6673
|
+
];
|
|
6674
|
+
}
|
|
6675
|
+
function gridCells(region, count, viewport, inset) {
|
|
6676
|
+
const unit = Math.min(viewport.width, viewport.height);
|
|
6677
|
+
const padding = inset ? unit * 0.025 : 0;
|
|
6678
|
+
const gap = count > 1 ? unit * 0.018 : 0;
|
|
6679
|
+
const inner = {
|
|
6680
|
+
x: region.x + padding,
|
|
6681
|
+
y: region.y + padding,
|
|
6682
|
+
width: Math.max(0, region.width - padding * 2),
|
|
6683
|
+
height: Math.max(0, region.height - padding * 2)
|
|
6684
|
+
};
|
|
6685
|
+
const columns = Math.min(
|
|
6686
|
+
count,
|
|
6687
|
+
Math.max(1, Math.ceil(Math.sqrt(count * (inner.width / Math.max(1, inner.height)))))
|
|
6688
|
+
);
|
|
6689
|
+
const rows = Math.ceil(count / columns);
|
|
6690
|
+
const width = (inner.width - gap * (columns - 1)) / columns;
|
|
6691
|
+
const height = (inner.height - gap * (rows - 1)) / rows;
|
|
6692
|
+
return Array.from({ length: count }, (_, index) => ({
|
|
6693
|
+
x: inner.x + index % columns * (width + gap),
|
|
6694
|
+
y: inner.y + Math.floor(index / columns) * (height + gap),
|
|
6695
|
+
width,
|
|
6696
|
+
height
|
|
6697
|
+
}));
|
|
6698
|
+
}
|
|
6699
|
+
function effectiveImageTreatment(theme, block) {
|
|
6700
|
+
const override = block.imageTreatment;
|
|
6701
|
+
if (override === "none") return void 0;
|
|
6702
|
+
const base = override ? { type: override, strength: theme.style.imageTreatment?.strength } : theme.style.imageTreatment;
|
|
6703
|
+
if (!base || base.type === "none") return void 0;
|
|
6704
|
+
return base.type === "duotone" && !base.color ? { ...base, color: theme.colors.primary } : base;
|
|
6705
|
+
}
|
|
6706
|
+
function richMediaLayer(item, position, block, theme, kindIndex) {
|
|
6707
|
+
if (item.kind === "image") {
|
|
6708
|
+
const treatment = effectiveImageTreatment(theme, block);
|
|
6709
|
+
return {
|
|
6710
|
+
id: `${block.id}-embedded-image-${kindIndex}`,
|
|
6711
|
+
type: "image",
|
|
6712
|
+
position,
|
|
6713
|
+
content: {
|
|
6714
|
+
src: item.src,
|
|
6715
|
+
alt: item.alt || block.title || "Embedded image",
|
|
6716
|
+
fit: "contain",
|
|
6717
|
+
...treatment ? { treatment } : {}
|
|
6718
|
+
}
|
|
6719
|
+
};
|
|
6720
|
+
}
|
|
6721
|
+
if (item.kind === "video") {
|
|
6722
|
+
return {
|
|
6723
|
+
id: `${block.id}-embedded-video-${kindIndex}`,
|
|
6724
|
+
type: "video",
|
|
6725
|
+
position,
|
|
6726
|
+
content: {
|
|
6727
|
+
src: item.src,
|
|
6728
|
+
...item.posterSrc ? { posterSrc: item.posterSrc } : {},
|
|
6729
|
+
alt: item.alt || block.title || "Embedded video",
|
|
6730
|
+
fit: "contain",
|
|
6731
|
+
clipStart: 0,
|
|
6732
|
+
clipEnd: Math.max(0, block.duration)
|
|
6733
|
+
}
|
|
6734
|
+
};
|
|
6735
|
+
}
|
|
6736
|
+
return {
|
|
6737
|
+
id: `${block.id}-mermaid-${kindIndex}`,
|
|
6738
|
+
type: "mermaid",
|
|
6739
|
+
position,
|
|
6740
|
+
content: {
|
|
6741
|
+
source: item.source,
|
|
6742
|
+
background: theme.colors.backgroundLight,
|
|
6743
|
+
foreground: theme.colors.text,
|
|
6744
|
+
padding: Math.max(12, Math.round(Math.min(position.width, position.height) * 0.025))
|
|
6745
|
+
}
|
|
6746
|
+
};
|
|
6747
|
+
}
|
|
6748
|
+
function appendRichContentLayers(layers, block, theme, viewport) {
|
|
6749
|
+
const items = collectRichMediaItems(layers, block);
|
|
6750
|
+
if (items.length === 0) return layers;
|
|
6751
|
+
const layout = resolveSupplementalMediaLayout(
|
|
6752
|
+
layers,
|
|
6753
|
+
block.template ? resolveTemplateName(block.template) : void 0,
|
|
6754
|
+
viewport,
|
|
6755
|
+
items.length,
|
|
6756
|
+
items.map((item) => item.aspectRatio)
|
|
6757
|
+
);
|
|
6758
|
+
const cells = gridCells(layout.mediaRect, items.length, viewport, layout.framed);
|
|
6759
|
+
const counters = { image: 0, video: 0, mermaid: 0 };
|
|
6760
|
+
const mediaLayers = items.map((item, index) => {
|
|
6761
|
+
counters[item.kind] += 1;
|
|
6762
|
+
return richMediaLayer(item, cells[index], block, theme, counters[item.kind]);
|
|
6763
|
+
});
|
|
6764
|
+
const frame = layout.framed ? {
|
|
6765
|
+
id: `${block.id}-rich-media-frame`,
|
|
6766
|
+
type: "shape",
|
|
6767
|
+
position: layout.mediaRect,
|
|
6768
|
+
content: {
|
|
6769
|
+
shape: "rect",
|
|
6770
|
+
fill: theme.colors.backgroundLight,
|
|
6771
|
+
stroke: withAlpha(theme.colors.text, 0.14),
|
|
6772
|
+
strokeWidth: 2,
|
|
6773
|
+
borderRadius: Math.max(12, Math.round(Math.min(viewport.width, viewport.height) * 0.018))
|
|
6774
|
+
}
|
|
6775
|
+
} : void 0;
|
|
6776
|
+
return [...layout.layers, ...frame ? [frame] : [], ...mediaLayers];
|
|
6777
|
+
}
|
|
6778
|
+
function executeTemplateMaterialization(templateBlock, context, registry) {
|
|
6779
|
+
const safeTemplateBlock = cloneData(templateBlock);
|
|
6780
|
+
const safeContext = cloneData(context);
|
|
6781
|
+
const maybeChildren = safeTemplateBlock.children;
|
|
6782
|
+
if ((!safeContext.children || safeContext.children.length === 0) && maybeChildren?.length) {
|
|
6783
|
+
safeContext.children = maybeChildren;
|
|
6784
|
+
}
|
|
6785
|
+
const templateFn = registry[resolveTemplateName(safeTemplateBlock.template)];
|
|
6786
|
+
if (!templateFn) return { status: "unknown-template", layers: [] };
|
|
6787
|
+
const { templateData, templateOverrides } = safeTemplateBlock;
|
|
6788
|
+
const sourceBlock = safeTemplateBlock;
|
|
6789
|
+
const derivedInputs = sourceBlock.sourceHeading ? deriveTemplateInputs(
|
|
6790
|
+
safeTemplateBlock.template,
|
|
6791
|
+
sourceBlock.title ?? "",
|
|
6792
|
+
sourceBlock.contents
|
|
6793
|
+
) : null;
|
|
6794
|
+
const coercedOverrides = coerceTemplateParams(
|
|
6795
|
+
safeTemplateBlock.template,
|
|
6796
|
+
templateOverrides ?? {}
|
|
6797
|
+
).input;
|
|
6798
|
+
const input = derivedInputs || templateData || templateOverrides ? {
|
|
6799
|
+
...derivedInputs,
|
|
6800
|
+
...safeTemplateBlock,
|
|
6801
|
+
...templateData,
|
|
6802
|
+
...coercedOverrides
|
|
6803
|
+
} : safeTemplateBlock;
|
|
6804
|
+
const explicitQuote = Object.prototype.hasOwnProperty.call(coercedOverrides, "quote") ? coercedOverrides.quote : Object.prototype.hasOwnProperty.call(templateData ?? {}, "quote") ? templateData?.quote : Object.prototype.hasOwnProperty.call(safeTemplateBlock, "quote") ? safeTemplateBlock.quote : void 0;
|
|
6805
|
+
if (resolveTemplateName(safeTemplateBlock.template) === "quote" && derivedInputs && !Object.prototype.hasOwnProperty.call(derivedInputs, "title") && !(typeof explicitQuote === "string" && explicitQuote.trim())) {
|
|
6806
|
+
const quoteInput = input;
|
|
6807
|
+
quoteInput.quote = derivedInputs.quote;
|
|
6808
|
+
if (!Object.prototype.hasOwnProperty.call(templateData ?? {}, "title") && !Object.prototype.hasOwnProperty.call(coercedOverrides, "title")) {
|
|
6809
|
+
delete quoteInput.title;
|
|
6810
|
+
}
|
|
6811
|
+
}
|
|
6812
|
+
try {
|
|
6813
|
+
const renderedLayers = templateFn(input, safeContext);
|
|
6814
|
+
if (!Array.isArray(renderedLayers)) {
|
|
6815
|
+
return {
|
|
6816
|
+
status: "invalid-result",
|
|
6817
|
+
layers: [],
|
|
6818
|
+
receivedType: typeof renderedLayers
|
|
6819
|
+
};
|
|
6820
|
+
}
|
|
6821
|
+
return { status: "ok", layers: cloneData(renderedLayers) };
|
|
6822
|
+
} catch (error) {
|
|
6823
|
+
return { status: "error", layers: [], error };
|
|
6824
|
+
}
|
|
6825
|
+
}
|
|
6826
|
+
function toLayerMaterializationDiagnostic(template, execution) {
|
|
6827
|
+
if (execution.status === "unknown-template") {
|
|
6828
|
+
return {
|
|
6829
|
+
code: "unknown-template",
|
|
6830
|
+
template,
|
|
6831
|
+
message: `Unknown template "${template}"`
|
|
6832
|
+
};
|
|
6833
|
+
}
|
|
6834
|
+
if (execution.status === "invalid-result") {
|
|
6835
|
+
return {
|
|
6836
|
+
code: "invalid-template-result",
|
|
6837
|
+
template,
|
|
6838
|
+
receivedType: execution.receivedType,
|
|
6839
|
+
message: `Template "${template}" returned ${execution.receivedType}, not a layer array`
|
|
6840
|
+
};
|
|
6841
|
+
}
|
|
6842
|
+
return {
|
|
6843
|
+
code: "template-error",
|
|
6844
|
+
template,
|
|
6845
|
+
cause: execution.error,
|
|
6846
|
+
message: `Template "${template}" failed`
|
|
6847
|
+
};
|
|
6848
|
+
}
|
|
6849
|
+
function injectPersistentLayers(layers, block, theme, persistentLayers, expanded) {
|
|
6850
|
+
if (!persistentLayers) return layers;
|
|
6851
|
+
const bottomLayers = expanded?.bottomLayers ?? expandPersistentLayers(persistentLayers.bottomLayers, theme);
|
|
6852
|
+
const topLayers = expanded?.topLayers ?? expandPersistentLayers(persistentLayers.topLayers, theme);
|
|
6853
|
+
return wrapWithPersistentLayers(layers, block, bottomLayers, topLayers);
|
|
6854
|
+
}
|
|
6855
|
+
|
|
6856
|
+
// src/doc/templates/metadata.ts
|
|
6857
|
+
var TEMPLATE_METADATA = {
|
|
6858
|
+
title: {
|
|
6859
|
+
label: "Title",
|
|
6860
|
+
description: "A bold opening slide with large title text, perfect for covers and chapters."
|
|
6861
|
+
},
|
|
6862
|
+
sectionHeader: {
|
|
6863
|
+
label: "Section Header",
|
|
6864
|
+
description: "A clean section break with a prominent title and optional subtitle."
|
|
6865
|
+
},
|
|
6866
|
+
statHighlight: {
|
|
6867
|
+
label: "Stat Highlight",
|
|
6868
|
+
description: "Showcases a single key number or metric with supporting context."
|
|
6869
|
+
},
|
|
6870
|
+
quote: {
|
|
6871
|
+
label: "Quote",
|
|
6872
|
+
description: "Displays a stylized pull quote with decorative marks and attribution."
|
|
6873
|
+
},
|
|
6874
|
+
factCard: {
|
|
6875
|
+
label: "Fact Card",
|
|
6876
|
+
description: "Presents a focused fact or insight with a labeled header and body text."
|
|
6877
|
+
},
|
|
6878
|
+
twoColumn: {
|
|
6879
|
+
label: "Two Column",
|
|
6880
|
+
description: "Divides the slide into two equal side-by-side content columns."
|
|
6881
|
+
},
|
|
6882
|
+
dateEvent: {
|
|
6883
|
+
label: "Date Event",
|
|
6884
|
+
description: "Highlights a date-based event or milestone with a prominent date display."
|
|
6885
|
+
},
|
|
6886
|
+
imageWithCaption: {
|
|
6887
|
+
label: "Image with Caption",
|
|
6888
|
+
description: "Displays a full-bleed image with an optional caption below."
|
|
6889
|
+
},
|
|
6890
|
+
leftFeature: {
|
|
6891
|
+
label: "Left Feature",
|
|
6892
|
+
description: "Image on the left, title and body text stacked on the right."
|
|
6893
|
+
},
|
|
6894
|
+
rightFeature: {
|
|
6895
|
+
label: "Right Feature",
|
|
6896
|
+
description: "Image on the right, title and body text stacked on the left."
|
|
6897
|
+
},
|
|
6898
|
+
map: {
|
|
6899
|
+
label: "Map",
|
|
6900
|
+
description: "Embeds an interactive map centered on a geographic location."
|
|
6901
|
+
},
|
|
6902
|
+
fullBleedQuote: {
|
|
6903
|
+
label: "Full Bleed Quote",
|
|
6904
|
+
description: "A full-width quote that spans the entire slide for maximum impact."
|
|
6905
|
+
},
|
|
6906
|
+
list: {
|
|
6907
|
+
label: "List",
|
|
6908
|
+
description: "Renders a bulleted or numbered list in a clean, card-style layout."
|
|
6909
|
+
},
|
|
6910
|
+
photoGrid: {
|
|
6911
|
+
label: "Photo Grid",
|
|
6912
|
+
description: "Arranges multiple photos in a 2\xD72 or 3\xD73 mosaic grid."
|
|
6913
|
+
},
|
|
6914
|
+
definitionCard: {
|
|
6915
|
+
label: "Definition Card",
|
|
6916
|
+
description: "Shows a term and its definition in a structured, dictionary-style card."
|
|
6917
|
+
},
|
|
6918
|
+
comparisonBar: {
|
|
6919
|
+
label: "Comparison Bar",
|
|
6920
|
+
description: "Visualizes two or more values side-by-side with labeled horizontal bars."
|
|
6921
|
+
},
|
|
6922
|
+
pullQuote: {
|
|
6923
|
+
label: "Pull Quote",
|
|
6924
|
+
description: "A stylized pull quote with large decorative marks and centered text."
|
|
6925
|
+
},
|
|
6926
|
+
videoWithCaption: {
|
|
6927
|
+
label: "Video with Caption",
|
|
6928
|
+
description: "Embeds a video player with an optional caption below."
|
|
6929
|
+
},
|
|
6930
|
+
videoPullQuote: {
|
|
6931
|
+
label: "Video Pull Quote",
|
|
6932
|
+
description: "Combines a video panel with a highlighted pull quote side-by-side."
|
|
6933
|
+
},
|
|
6934
|
+
dataTable: {
|
|
6935
|
+
label: "Data Table",
|
|
6936
|
+
description: "Renders tabular data in a clean, styled table with a header row."
|
|
6937
|
+
},
|
|
6938
|
+
diagram: {
|
|
6939
|
+
label: "Diagram",
|
|
6940
|
+
description: "Renders sub-headings as connected nodes \u2014 edit visually as a node-and-edge diagram."
|
|
6941
|
+
},
|
|
6942
|
+
tree: {
|
|
6943
|
+
label: "Tree",
|
|
6944
|
+
description: "Renders an ASCII file-tree / outline fence as a filesystem-style treeview \u2014 edit as an outline."
|
|
6945
|
+
},
|
|
6946
|
+
timeline: {
|
|
6947
|
+
label: "Timeline",
|
|
6948
|
+
description: "Plots events across one or more horizontal tracks with callouts and optional branching links."
|
|
6949
|
+
},
|
|
6950
|
+
layout: {
|
|
6951
|
+
label: "Layout",
|
|
6952
|
+
description: "Free-form 2D canvas \u2014 drag layers into place. Use for one-off block layouts that don't fit a template."
|
|
6953
|
+
},
|
|
6954
|
+
drawing: {
|
|
6955
|
+
label: "Drawing",
|
|
6956
|
+
description: "Free-form sketches \u2014 draw shapes, paths, and text directly on a 2D surface."
|
|
6957
|
+
}
|
|
6958
|
+
};
|
|
6959
|
+
|
|
6960
|
+
// src/doc/templates/coverBlock.ts
|
|
6961
|
+
var COVER_TITLE_BASE_PX = 120;
|
|
6962
|
+
var COVER_TITLE_COMFORTABLE_CHARS = 26;
|
|
6963
|
+
var COVER_TITLE_MIN_BASE_PX = 62;
|
|
6964
|
+
function fitCoverTitleSize(title) {
|
|
6965
|
+
const len = title.trim().length;
|
|
6966
|
+
if (len <= COVER_TITLE_COMFORTABLE_CHARS) return COVER_TITLE_BASE_PX;
|
|
6967
|
+
const scaled = COVER_TITLE_BASE_PX * Math.sqrt(COVER_TITLE_COMFORTABLE_CHARS / len);
|
|
6968
|
+
return Math.max(COVER_TITLE_MIN_BASE_PX, Math.round(scaled));
|
|
6969
|
+
}
|
|
6970
|
+
function isStandardLightCover(context) {
|
|
6971
|
+
const { theme } = context;
|
|
6972
|
+
return theme.id === "standard" && relativeLuminance(theme.colors.background) > 0.85;
|
|
6973
|
+
}
|
|
6974
|
+
var COVER_SCRIM_STOPS = [
|
|
6975
|
+
{ pos: 0, alpha: 0.95 },
|
|
6976
|
+
{ pos: 38, alpha: 0.86 },
|
|
6977
|
+
{ pos: 58, alpha: 0.4 },
|
|
6978
|
+
{ pos: 78, alpha: 0.1 },
|
|
6979
|
+
{ pos: 100, alpha: 0 }
|
|
6980
|
+
];
|
|
6981
|
+
function coverScrimGradient(background) {
|
|
6982
|
+
const stops = COVER_SCRIM_STOPS.map((s) => `${withAlpha(background, s.alpha)} ${s.pos}%`);
|
|
6983
|
+
return `linear-gradient(0deg, ${stops.join(", ")})`;
|
|
6984
|
+
}
|
|
6985
|
+
function coverBlock(input, context) {
|
|
6986
|
+
const treatment = themedImageTreatment(context, input.imageTreatment);
|
|
6987
|
+
const { heroSrc, heroAlt, title, subtitle, ambientMotion, heroCredit, heroLicense } = input;
|
|
6988
|
+
const { theme, layout } = context;
|
|
6989
|
+
const standardLightCover = !heroSrc && isStandardLightCover(context);
|
|
6990
|
+
const titleFontSize = themedFontSize(fitCoverTitleSize(title), context, true);
|
|
6991
|
+
const subtitleFontSize = themedFontSize(40, context, false);
|
|
6992
|
+
const layers = [];
|
|
6993
|
+
if (heroSrc) {
|
|
6994
|
+
const imageAnimation = mapAmbientMotion(ambientMotion);
|
|
6995
|
+
layers.push(
|
|
6996
|
+
{
|
|
6997
|
+
type: "image",
|
|
6998
|
+
id: "cover-hero",
|
|
6999
|
+
content: {
|
|
7000
|
+
src: heroSrc,
|
|
7001
|
+
alt: heroAlt || title,
|
|
7002
|
+
fit: "cover",
|
|
7003
|
+
credit: heroCredit,
|
|
7004
|
+
license: heroLicense,
|
|
7005
|
+
...treatment ? { treatment } : {}
|
|
7006
|
+
},
|
|
7007
|
+
position: { x: 0, y: 0, width: "100%", height: "100%" },
|
|
7008
|
+
animation: imageAnimation
|
|
7009
|
+
},
|
|
7010
|
+
{
|
|
7011
|
+
type: "shape",
|
|
7012
|
+
id: "cover-gradient",
|
|
7013
|
+
content: {
|
|
7014
|
+
shape: "rect",
|
|
7015
|
+
fill: coverScrimGradient(theme.colors.background)
|
|
7016
|
+
},
|
|
7017
|
+
position: { x: 0, y: 0, width: "100%", height: "100%" }
|
|
7018
|
+
}
|
|
7019
|
+
);
|
|
7020
|
+
} else {
|
|
7021
|
+
if (standardLightCover) {
|
|
7022
|
+
layers.push(
|
|
7023
|
+
{
|
|
7024
|
+
type: "shape",
|
|
7025
|
+
id: "cover-bg",
|
|
7026
|
+
content: {
|
|
7027
|
+
shape: "rect",
|
|
7028
|
+
fill: theme.colors.background
|
|
7029
|
+
},
|
|
7030
|
+
position: { x: 0, y: 0, width: "100%", height: "100%" }
|
|
7031
|
+
},
|
|
7032
|
+
{
|
|
7033
|
+
type: "shape",
|
|
7034
|
+
id: "cover-bg-tint",
|
|
7035
|
+
content: {
|
|
7036
|
+
shape: "rect",
|
|
7037
|
+
fill: `radial-gradient(ellipse at 50% 40%, ${withAlpha(theme.colors.primary, 0.16)} 0%, ${withAlpha(theme.colors.primary, 0.07)} 38%, ${withAlpha(theme.colors.primary, 0)} 78%)`
|
|
7038
|
+
},
|
|
7039
|
+
position: { x: 0, y: 0, width: "100%", height: "100%" }
|
|
7040
|
+
}
|
|
7041
|
+
);
|
|
7042
|
+
} else {
|
|
7043
|
+
layers.push({
|
|
7044
|
+
type: "shape",
|
|
7045
|
+
id: "cover-bg",
|
|
7046
|
+
content: {
|
|
7047
|
+
shape: "rect",
|
|
7048
|
+
fill: `radial-gradient(ellipse at 50% 40%, ${theme.colors.primary} 0%, ${theme.colors.background} 100%)`
|
|
7049
|
+
},
|
|
7050
|
+
position: { x: 0, y: 0, width: "100%", height: "100%" }
|
|
7051
|
+
});
|
|
6378
7052
|
}
|
|
7053
|
+
layers.push({
|
|
7054
|
+
type: "shape",
|
|
7055
|
+
id: "cover-accent",
|
|
7056
|
+
content: {
|
|
7057
|
+
shape: "rect",
|
|
7058
|
+
fill: standardLightCover ? withAlpha(theme.colors.primary, 0.24) : "rgba(255, 255, 255, 0.2)"
|
|
7059
|
+
},
|
|
7060
|
+
position: {
|
|
7061
|
+
x: "35%",
|
|
7062
|
+
y: subtitle ? "42%" : "58%",
|
|
7063
|
+
width: "30%",
|
|
7064
|
+
height: "2px"
|
|
7065
|
+
}
|
|
7066
|
+
});
|
|
6379
7067
|
}
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
6384
|
-
|
|
6385
|
-
|
|
7068
|
+
layers.push({
|
|
7069
|
+
type: "text",
|
|
7070
|
+
id: "cover-title",
|
|
7071
|
+
content: {
|
|
7072
|
+
text: title,
|
|
7073
|
+
style: {
|
|
7074
|
+
fontSize: titleFontSize,
|
|
7075
|
+
fontFamily: getThemeFont(context, "title"),
|
|
7076
|
+
fontWeight: "bold",
|
|
7077
|
+
color: theme.colors.text,
|
|
7078
|
+
textAlign: "center",
|
|
7079
|
+
shadow: !standardLightCover
|
|
7080
|
+
}
|
|
7081
|
+
},
|
|
7082
|
+
position: {
|
|
7083
|
+
x: "50%",
|
|
7084
|
+
y: heroSrc ? subtitle ? "70%" : "75%" : subtitle ? layout.primaryY : "50%",
|
|
7085
|
+
anchor: "center",
|
|
7086
|
+
width: layout.maxTextWidth
|
|
7087
|
+
},
|
|
7088
|
+
// No animation delay - shown immediately at rest
|
|
7089
|
+
animation: { type: "fadeIn", duration: 0.8 }
|
|
7090
|
+
});
|
|
7091
|
+
if (subtitle) {
|
|
7092
|
+
layers.push({
|
|
7093
|
+
type: "text",
|
|
7094
|
+
id: "cover-subtitle",
|
|
7095
|
+
content: {
|
|
7096
|
+
text: subtitle,
|
|
7097
|
+
style: {
|
|
7098
|
+
fontSize: subtitleFontSize,
|
|
7099
|
+
fontFamily: getThemeFont(context, "body"),
|
|
7100
|
+
fontWeight: standardLightCover ? "bold" : "normal",
|
|
7101
|
+
color: standardLightCover ? withAlpha(theme.colors.text, 0.78) : theme.colors.textMuted,
|
|
7102
|
+
textAlign: "center",
|
|
7103
|
+
lineHeight: 1.5
|
|
7104
|
+
}
|
|
7105
|
+
},
|
|
7106
|
+
position: {
|
|
7107
|
+
x: "50%",
|
|
7108
|
+
y: heroSrc ? "82%" : layout.secondaryY,
|
|
7109
|
+
anchor: "center",
|
|
7110
|
+
width: layout.maxTextWidth
|
|
7111
|
+
},
|
|
7112
|
+
animation: { type: "fadeIn", duration: 0.8, delay: 0.2 }
|
|
7113
|
+
});
|
|
6386
7114
|
}
|
|
6387
|
-
return
|
|
7115
|
+
return layers;
|
|
6388
7116
|
}
|
|
6389
|
-
function
|
|
6390
|
-
if (!contents) return null;
|
|
6391
|
-
const index = contents.findIndex((node) => node.type === "paragraph");
|
|
6392
|
-
if (index < 0) return null;
|
|
7117
|
+
function startBlockToCoverInput(config) {
|
|
6393
7118
|
return {
|
|
6394
|
-
|
|
6395
|
-
|
|
7119
|
+
heroSrc: config.heroSrc,
|
|
7120
|
+
heroAlt: config.heroAlt,
|
|
7121
|
+
title: config.title,
|
|
7122
|
+
subtitle: config.subtitle,
|
|
7123
|
+
ambientMotion: config.ambientMotion,
|
|
7124
|
+
heroCredit: config.heroCredit,
|
|
7125
|
+
heroLicense: config.heroLicense
|
|
6396
7126
|
};
|
|
6397
7127
|
}
|
|
6398
|
-
function
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
if (child.type !== "strong") return null;
|
|
6402
|
-
const text = extractPlainText(child).trim();
|
|
6403
|
-
const match = text.match(/^(?:[$€£¥]\s*)?[+-]?\d+(?:[.,]\d+)*(?:\s?(?:[%‰x×]|[A-Za-z]+))?/);
|
|
6404
|
-
return match?.[0].trim() || null;
|
|
6405
|
-
}
|
|
6406
|
-
return null;
|
|
6407
|
-
}
|
|
6408
|
-
function removeStatPhrase(text, start, end) {
|
|
6409
|
-
const before = text.slice(0, start).trim();
|
|
6410
|
-
let after = text.slice(end);
|
|
6411
|
-
if (!before) after = after.replace(/^\s*(?:[-–—:]\s*)?/, "");
|
|
6412
|
-
else after = after.trim();
|
|
6413
|
-
return [before, after].filter(Boolean).join(" ").trim();
|
|
6414
|
-
}
|
|
6415
|
-
function bodyWithoutParagraphStat(contents, paragraphIndex, paragraphText, start, end) {
|
|
6416
|
-
if (!contents) return "";
|
|
6417
|
-
return contents.map(
|
|
6418
|
-
(node, index) => index === paragraphIndex ? removeStatPhrase(paragraphText, start, end) : extractPlainText(node).trim()
|
|
6419
|
-
).filter(Boolean).join("\n").trim();
|
|
7128
|
+
function expandCoverBlock(config, context) {
|
|
7129
|
+
const input = startBlockToCoverInput(config);
|
|
7130
|
+
return coverBlock(input, context);
|
|
6420
7131
|
}
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
};
|
|
6439
|
-
}
|
|
7132
|
+
|
|
7133
|
+
// src/doc/templates/index.ts
|
|
7134
|
+
function materializeScheduledBlock(sourceBlock, blockIndex, totalBlocks, theme, viewport, persistentLayers, expandedPersistentLayers, registry, failureMode, onDiagnostic) {
|
|
7135
|
+
const materialized = materializeBlockLayersWithRuntime(
|
|
7136
|
+
sourceBlock,
|
|
7137
|
+
{
|
|
7138
|
+
theme,
|
|
7139
|
+
viewport,
|
|
7140
|
+
persistentLayers: persistentLayers ?? false,
|
|
7141
|
+
blockIndex,
|
|
7142
|
+
totalBlocks,
|
|
7143
|
+
failureMode
|
|
7144
|
+
},
|
|
7145
|
+
{ registry, expandedPersistentLayers }
|
|
7146
|
+
);
|
|
7147
|
+
if (materialized.diagnostic) {
|
|
7148
|
+
onDiagnostic?.(materialized.diagnostic, sourceBlock, blockIndex);
|
|
6440
7149
|
}
|
|
6441
|
-
const
|
|
6442
|
-
|
|
6443
|
-
|
|
6444
|
-
|
|
7150
|
+
const expandedBlock = isTemplateBlock(sourceBlock) ? {
|
|
7151
|
+
id: sourceBlock.id,
|
|
7152
|
+
startTime: 0,
|
|
7153
|
+
duration: sourceBlock.duration,
|
|
7154
|
+
audioSegment: sourceBlock.audioSegment,
|
|
7155
|
+
...materialized.layers.length > 0 ? { layers: materialized.layers } : {},
|
|
7156
|
+
transition: sourceBlock.transition,
|
|
7157
|
+
template: sourceBlock.template
|
|
7158
|
+
} : {
|
|
7159
|
+
...cloneData(sourceBlock),
|
|
7160
|
+
...materialized.layers.length > 0 ? { layers: materialized.layers } : {}
|
|
7161
|
+
};
|
|
7162
|
+
const transition = resolveBlockTransition(expandedBlock, theme, blockIndex);
|
|
7163
|
+
if (transition !== expandedBlock.transition) {
|
|
7164
|
+
expandedBlock.transition = transition;
|
|
6445
7165
|
}
|
|
6446
|
-
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
|
|
6452
|
-
|
|
6453
|
-
|
|
6454
|
-
|
|
6455
|
-
|
|
7166
|
+
return expandedBlock;
|
|
7167
|
+
}
|
|
7168
|
+
function expandDocBlocks(blocks, options = {}) {
|
|
7169
|
+
const opts = options;
|
|
7170
|
+
const theme = opts.theme ?? DEFAULT_THEME;
|
|
7171
|
+
const viewport = opts.viewport ?? VIEWPORT_PRESETS.landscape;
|
|
7172
|
+
const {
|
|
7173
|
+
persistentLayers,
|
|
7174
|
+
audioSegments,
|
|
7175
|
+
customTemplates,
|
|
7176
|
+
failureMode = "fallback",
|
|
7177
|
+
onDiagnostic
|
|
7178
|
+
} = opts;
|
|
7179
|
+
const totalBlocks = blocks.length;
|
|
7180
|
+
const registry = customTemplates && customTemplates.length > 0 ? buildRegistry(customTemplates) : templateRegistry;
|
|
7181
|
+
const effectivePersistentLayers = persistentLayers === false ? void 0 : persistentLayers ?? theme.persistentLayers;
|
|
7182
|
+
const expandedPersistentLayers = effectivePersistentLayers ? {
|
|
7183
|
+
bottomLayers: expandPersistentLayers(effectivePersistentLayers.bottomLayers, theme),
|
|
7184
|
+
topLayers: expandPersistentLayers(effectivePersistentLayers.topLayers, theme)
|
|
7185
|
+
} : void 0;
|
|
7186
|
+
if (!audioSegments || audioSegments.length === 0) {
|
|
7187
|
+
let currentTime = 0;
|
|
7188
|
+
return blocks.map((block, index) => {
|
|
7189
|
+
const expandedBlock = materializeScheduledBlock(
|
|
7190
|
+
block,
|
|
7191
|
+
index,
|
|
7192
|
+
totalBlocks,
|
|
7193
|
+
theme,
|
|
7194
|
+
viewport,
|
|
7195
|
+
effectivePersistentLayers,
|
|
7196
|
+
expandedPersistentLayers,
|
|
7197
|
+
registry,
|
|
7198
|
+
failureMode,
|
|
7199
|
+
onDiagnostic
|
|
6456
7200
|
);
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
}
|
|
7201
|
+
expandedBlock.startTime = currentTime;
|
|
7202
|
+
currentTime += expandedBlock.duration;
|
|
7203
|
+
return expandedBlock;
|
|
7204
|
+
});
|
|
6462
7205
|
}
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
switch (templateName) {
|
|
6469
|
-
case "statHighlight":
|
|
6470
|
-
return deriveStatHighlightInputs(headingText, contents, bodyText3);
|
|
6471
|
-
case "quote": {
|
|
6472
|
-
const quote = extractBlockquoteText(contents) || bodyText3 || headingText;
|
|
6473
|
-
return { quote };
|
|
6474
|
-
}
|
|
6475
|
-
case "fullBleedQuote":
|
|
6476
|
-
case "pullQuote": {
|
|
6477
|
-
const text = extractBlockquoteText(contents) || bodyText3 || headingText;
|
|
6478
|
-
return { text };
|
|
6479
|
-
}
|
|
6480
|
-
case "factCard":
|
|
6481
|
-
return { fact: headingText, explanation: bodyText3 || headingText };
|
|
6482
|
-
case "comparisonBar":
|
|
6483
|
-
return placeholders ? { leftLabel: "A", leftValue: 60, rightLabel: "B", rightValue: 40 } : null;
|
|
6484
|
-
case "list": {
|
|
6485
|
-
const items = extractListItems(contents);
|
|
6486
|
-
if (items.length > 0) return { items };
|
|
6487
|
-
return placeholders ? { items: ["Item 1", "Item 2", "Item 3"] } : null;
|
|
7206
|
+
const blocksBySegment = /* @__PURE__ */ new Map();
|
|
7207
|
+
blocks.forEach((block, index) => {
|
|
7208
|
+
const segmentIndex = block.audioSegment ?? 0;
|
|
7209
|
+
if (!blocksBySegment.has(segmentIndex)) {
|
|
7210
|
+
blocksBySegment.set(segmentIndex, []);
|
|
6488
7211
|
}
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6496
|
-
|
|
7212
|
+
blocksBySegment.get(segmentIndex).push({ block, originalIndex: index });
|
|
7213
|
+
});
|
|
7214
|
+
const expandedBlocks = new Array(blocks.length);
|
|
7215
|
+
for (const [segmentIndex, segmentBlocks] of blocksBySegment) {
|
|
7216
|
+
const audioSegment = audioSegments[segmentIndex];
|
|
7217
|
+
if (!audioSegment) {
|
|
7218
|
+
let offsetTime = 0;
|
|
7219
|
+
for (const { block, originalIndex } of segmentBlocks) {
|
|
7220
|
+
const expandedBlock = materializeScheduledBlock(
|
|
7221
|
+
block,
|
|
7222
|
+
originalIndex,
|
|
7223
|
+
totalBlocks,
|
|
7224
|
+
theme,
|
|
7225
|
+
viewport,
|
|
7226
|
+
effectivePersistentLayers,
|
|
7227
|
+
expandedPersistentLayers,
|
|
7228
|
+
registry,
|
|
7229
|
+
failureMode,
|
|
7230
|
+
onDiagnostic
|
|
7231
|
+
);
|
|
7232
|
+
expandedBlock.startTime = offsetTime;
|
|
7233
|
+
offsetTime += expandedBlock.duration;
|
|
7234
|
+
expandedBlocks[originalIndex] = expandedBlock;
|
|
7235
|
+
}
|
|
7236
|
+
continue;
|
|
6497
7237
|
}
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
|
|
7238
|
+
const sectionHeaderBlocks = [];
|
|
7239
|
+
const contentBlocks = [];
|
|
7240
|
+
for (const item of segmentBlocks) {
|
|
7241
|
+
const templateBlock = item.block;
|
|
7242
|
+
if (templateBlock.template === "sectionHeader") {
|
|
7243
|
+
sectionHeaderBlocks.push(item);
|
|
7244
|
+
} else {
|
|
7245
|
+
contentBlocks.push(item);
|
|
7246
|
+
}
|
|
6502
7247
|
}
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
6506
|
-
|
|
6507
|
-
|
|
6508
|
-
|
|
6509
|
-
|
|
7248
|
+
const fixedDuration = sectionHeaderBlocks.reduce((sum, { block }) => {
|
|
7249
|
+
return sum + (block.duration ?? 0);
|
|
7250
|
+
}, 0);
|
|
7251
|
+
const contentBlockDuration = contentBlocks.reduce((sum, { block }) => {
|
|
7252
|
+
return sum + (block.duration ?? 0);
|
|
7253
|
+
}, 0);
|
|
7254
|
+
const remainingDuration = audioSegment.duration - fixedDuration;
|
|
7255
|
+
const scaleFactor = contentBlockDuration > 0 && remainingDuration > 0 ? remainingDuration / contentBlockDuration : 1;
|
|
7256
|
+
const expandedInfos = [];
|
|
7257
|
+
for (const { block, originalIndex } of segmentBlocks) {
|
|
7258
|
+
const expandedBlock = materializeScheduledBlock(
|
|
7259
|
+
block,
|
|
7260
|
+
originalIndex,
|
|
7261
|
+
totalBlocks,
|
|
7262
|
+
theme,
|
|
7263
|
+
viewport,
|
|
7264
|
+
effectivePersistentLayers,
|
|
7265
|
+
expandedPersistentLayers,
|
|
7266
|
+
registry,
|
|
7267
|
+
failureMode,
|
|
7268
|
+
onDiagnostic
|
|
7269
|
+
);
|
|
7270
|
+
const templateBlock = block;
|
|
7271
|
+
expandedInfos.push({
|
|
7272
|
+
block: expandedBlock,
|
|
7273
|
+
originalIndex,
|
|
7274
|
+
templateBlock,
|
|
7275
|
+
hasSourceTiming: typeof templateBlock.sourceStartTime === "number"
|
|
7276
|
+
});
|
|
6510
7277
|
}
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
|
|
6522
|
-
|
|
7278
|
+
const hasAnySourceTiming = expandedInfos.some((info) => info.hasSourceTiming);
|
|
7279
|
+
expandedInfos.sort((a, b) => {
|
|
7280
|
+
if (hasAnySourceTiming) {
|
|
7281
|
+
const aIsHeader = a.templateBlock.template === "sectionHeader";
|
|
7282
|
+
const bIsHeader = b.templateBlock.template === "sectionHeader";
|
|
7283
|
+
if (aIsHeader && !bIsHeader) return -1;
|
|
7284
|
+
if (!aIsHeader && bIsHeader) return 1;
|
|
7285
|
+
if (a.hasSourceTiming && b.hasSourceTiming) {
|
|
7286
|
+
return a.templateBlock.sourceStartTime - b.templateBlock.sourceStartTime;
|
|
7287
|
+
}
|
|
7288
|
+
if (a.hasSourceTiming && !b.hasSourceTiming) return -1;
|
|
7289
|
+
if (!a.hasSourceTiming && b.hasSourceTiming) return 1;
|
|
7290
|
+
}
|
|
7291
|
+
return a.originalIndex - b.originalIndex;
|
|
7292
|
+
});
|
|
7293
|
+
let offsetWithinSegment = 0;
|
|
7294
|
+
for (let i = 0; i < expandedInfos.length; i++) {
|
|
7295
|
+
const info = expandedInfos[i];
|
|
7296
|
+
const { block: expandedBlock, templateBlock } = info;
|
|
7297
|
+
if (info.hasSourceTiming) {
|
|
7298
|
+
const sourceStart = templateBlock.sourceStartTime;
|
|
7299
|
+
expandedBlock.startTime = audioSegment.startTime + sourceStart;
|
|
7300
|
+
if (typeof templateBlock.sourceDuration === "number") {
|
|
7301
|
+
expandedBlock.duration = Math.max(5, templateBlock.sourceDuration);
|
|
7302
|
+
}
|
|
7303
|
+
offsetWithinSegment = sourceStart + expandedBlock.duration;
|
|
7304
|
+
} else if (templateBlock.template === "sectionHeader") {
|
|
7305
|
+
expandedBlock.startTime = audioSegment.startTime + offsetWithinSegment;
|
|
7306
|
+
offsetWithinSegment += expandedBlock.duration;
|
|
7307
|
+
} else {
|
|
7308
|
+
expandedBlock.startTime = audioSegment.startTime + offsetWithinSegment;
|
|
7309
|
+
const scaledDuration = expandedBlock.duration * scaleFactor;
|
|
7310
|
+
expandedBlock.duration = scaledDuration;
|
|
7311
|
+
offsetWithinSegment += scaledDuration;
|
|
7312
|
+
}
|
|
7313
|
+
expandedBlocks[info.originalIndex] = expandedBlock;
|
|
6523
7314
|
}
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
const
|
|
6527
|
-
const
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
|
|
7315
|
+
const segmentExpandedBlocks = expandedInfos.map((info) => expandedBlocks[info.originalIndex]).sort((a, b) => a.startTime - b.startTime);
|
|
7316
|
+
for (let i = 0; i < segmentExpandedBlocks.length - 1; i++) {
|
|
7317
|
+
const current = segmentExpandedBlocks[i];
|
|
7318
|
+
const next = segmentExpandedBlocks[i + 1];
|
|
7319
|
+
const currentEnd = current.startTime + current.duration;
|
|
7320
|
+
if (currentEnd > next.startTime) {
|
|
7321
|
+
current.duration = next.startTime - current.startTime;
|
|
7322
|
+
} else if (currentEnd < next.startTime - 0.5) {
|
|
7323
|
+
current.duration = Math.max(0.1, next.startTime - current.startTime);
|
|
7324
|
+
}
|
|
6531
7325
|
}
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
const
|
|
6536
|
-
|
|
6537
|
-
|
|
6538
|
-
|
|
7326
|
+
const MIN_TRANSITION_GAP = 5;
|
|
7327
|
+
const segmentEnd = audioSegment.startTime + audioSegment.duration;
|
|
7328
|
+
while (segmentExpandedBlocks.length > 1) {
|
|
7329
|
+
const lastBlock = segmentExpandedBlocks[segmentExpandedBlocks.length - 1];
|
|
7330
|
+
const timeFromLastToEnd = segmentEnd - lastBlock.startTime;
|
|
7331
|
+
if (timeFromLastToEnd < MIN_TRANSITION_GAP && lastBlock.template !== "sectionHeader") {
|
|
7332
|
+
const prevBlock = segmentExpandedBlocks[segmentExpandedBlocks.length - 2];
|
|
7333
|
+
prevBlock.duration = segmentEnd - prevBlock.startTime;
|
|
7334
|
+
lastBlock.duration = 0;
|
|
7335
|
+
lastBlock.startTime = segmentEnd;
|
|
7336
|
+
segmentExpandedBlocks.pop();
|
|
7337
|
+
} else {
|
|
7338
|
+
if (lastBlock.startTime + lastBlock.duration < segmentEnd) {
|
|
7339
|
+
lastBlock.duration = segmentEnd - lastBlock.startTime;
|
|
7340
|
+
}
|
|
7341
|
+
break;
|
|
7342
|
+
}
|
|
6539
7343
|
}
|
|
6540
|
-
|
|
6541
|
-
const
|
|
6542
|
-
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
|
|
7344
|
+
if (segmentExpandedBlocks.length === 1) {
|
|
7345
|
+
const onlyBlock = segmentExpandedBlocks[0];
|
|
7346
|
+
if (onlyBlock.startTime + onlyBlock.duration < segmentEnd) {
|
|
7347
|
+
onlyBlock.duration = segmentEnd - onlyBlock.startTime;
|
|
7348
|
+
}
|
|
7349
|
+
}
|
|
7350
|
+
let changed = true;
|
|
7351
|
+
while (changed) {
|
|
7352
|
+
changed = false;
|
|
7353
|
+
for (let i = segmentExpandedBlocks.length - 1; i >= 1; i--) {
|
|
7354
|
+
const block = segmentExpandedBlocks[i];
|
|
7355
|
+
if (block.duration > 0 && block.duration < MIN_TRANSITION_GAP) {
|
|
7356
|
+
const prev = segmentExpandedBlocks[i - 1];
|
|
7357
|
+
const blockEnd = block.startTime + block.duration;
|
|
7358
|
+
prev.duration = blockEnd - prev.startTime;
|
|
7359
|
+
block.duration = 0;
|
|
7360
|
+
block.startTime = segmentEnd;
|
|
7361
|
+
segmentExpandedBlocks.splice(i, 1);
|
|
7362
|
+
changed = true;
|
|
7363
|
+
break;
|
|
6548
7364
|
}
|
|
6549
7365
|
}
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
7366
|
+
}
|
|
7367
|
+
const MAX_BLOCK_DURATION = 20;
|
|
7368
|
+
for (let i = 0; i < segmentExpandedBlocks.length; i++) {
|
|
7369
|
+
const block = segmentExpandedBlocks[i];
|
|
7370
|
+
if (block.duration > MAX_BLOCK_DURATION) {
|
|
7371
|
+
const numParts = Math.ceil(block.duration / MAX_BLOCK_DURATION);
|
|
7372
|
+
const partDuration = block.duration / numParts;
|
|
7373
|
+
if (partDuration >= MIN_TRANSITION_GAP) {
|
|
7374
|
+
block.duration = partDuration;
|
|
7375
|
+
for (let p = 1; p < numParts; p++) {
|
|
7376
|
+
const splitBlock = {
|
|
7377
|
+
id: `${block.id}-split-${p}`,
|
|
7378
|
+
startTime: block.startTime + p * partDuration,
|
|
7379
|
+
duration: partDuration,
|
|
7380
|
+
audioSegment: block.audioSegment,
|
|
7381
|
+
layers: (block.layers ?? []).map((layer) => ({
|
|
7382
|
+
...cloneData(layer),
|
|
7383
|
+
id: `${layer.id}-split-${p}`
|
|
7384
|
+
})),
|
|
7385
|
+
transition: { type: "dissolve", duration: 1 },
|
|
7386
|
+
template: block.template
|
|
7387
|
+
};
|
|
7388
|
+
expandedBlocks.push(splitBlock);
|
|
7389
|
+
}
|
|
7390
|
+
}
|
|
6554
7391
|
}
|
|
6555
|
-
return placeholders ? {} : null;
|
|
6556
7392
|
}
|
|
6557
|
-
default:
|
|
6558
|
-
return placeholders ? {} : null;
|
|
6559
7393
|
}
|
|
7394
|
+
return expandedBlocks.filter((block) => block && block.duration > 0);
|
|
7395
|
+
}
|
|
7396
|
+
function getAvailableTemplates() {
|
|
7397
|
+
return Object.keys(templateRegistry);
|
|
7398
|
+
}
|
|
7399
|
+
function hasTemplate(name) {
|
|
7400
|
+
return resolveTemplateName(name) in templateRegistry;
|
|
6560
7401
|
}
|
|
6561
7402
|
|
|
6562
7403
|
// src/doc/customTemplatesFrontmatter.ts
|
|
@@ -7498,8 +8339,10 @@ function applyAutoTemplates(blocks, resolvedDefault, state) {
|
|
|
7498
8339
|
if (block.sourceHeading && !annotated && block.template === resolvedDefault) {
|
|
7499
8340
|
const profile = profileBlockContents(block.contents ?? []);
|
|
7500
8341
|
const picked = pickAutoTemplate(profile, state.featureIndex);
|
|
7501
|
-
if (picked) {
|
|
7502
|
-
const inputs = deriveTemplateInputs(picked, block.title ?? "", block.contents
|
|
8342
|
+
if (picked && autoTemplatePreservesContent(picked, block.contents)) {
|
|
8343
|
+
const inputs = deriveTemplateInputs(picked, block.title ?? "", block.contents, {
|
|
8344
|
+
preserveSourceHeading: true
|
|
8345
|
+
});
|
|
7503
8346
|
if (inputs) {
|
|
7504
8347
|
if (picked === "leftFeature" || picked === "rightFeature") state.featureIndex += 1;
|
|
7505
8348
|
block.template = picked;
|
|
@@ -7997,13 +8840,6 @@ export {
|
|
|
7997
8840
|
coerceTemplateParams,
|
|
7998
8841
|
lintTemplateParams,
|
|
7999
8842
|
fallbackBlockLayers,
|
|
8000
|
-
materializeBlockLayers,
|
|
8001
|
-
TEMPLATE_METADATA,
|
|
8002
|
-
coverBlock,
|
|
8003
|
-
expandCoverBlock,
|
|
8004
|
-
expandDocBlocks,
|
|
8005
|
-
getAvailableTemplates,
|
|
8006
|
-
hasTemplate,
|
|
8007
8843
|
ASCII_CHAR_W,
|
|
8008
8844
|
ASCII_CHAR_H,
|
|
8009
8845
|
asciiCellToCanvas,
|
|
@@ -8020,9 +8856,21 @@ export {
|
|
|
8020
8856
|
extractListItems,
|
|
8021
8857
|
extractImages,
|
|
8022
8858
|
extractFirstImage,
|
|
8859
|
+
extractEmbeddedVideos,
|
|
8860
|
+
extractFirstEmbeddedVideo,
|
|
8023
8861
|
extractTableFromContents,
|
|
8024
8862
|
extractBlockquoteText,
|
|
8863
|
+
autoTemplatePreservesContent,
|
|
8025
8864
|
deriveTemplateInputs,
|
|
8865
|
+
BLOCK_MEDIA_LAYOUT_POLICIES,
|
|
8866
|
+
getBlockMediaLayoutPolicy,
|
|
8867
|
+
materializeBlockLayers,
|
|
8868
|
+
TEMPLATE_METADATA,
|
|
8869
|
+
coverBlock,
|
|
8870
|
+
expandCoverBlock,
|
|
8871
|
+
expandDocBlocks,
|
|
8872
|
+
getAvailableTemplates,
|
|
8873
|
+
hasTemplate,
|
|
8026
8874
|
readCustomTemplatesFromFrontmatter,
|
|
8027
8875
|
writeCustomTemplatesToFrontmatter,
|
|
8028
8876
|
readCustomThemesFromFrontmatter,
|