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