@bendyline/squisq 2.12.1 → 2.12.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/{Doc-CHD4fxfm.d.ts → Doc-BJSPtsQD.d.ts} +21 -2
  2. package/dist/{ImageEditDoc-DSUM5MiA.d.ts → ImageEditDoc-QIzn1qK5.d.ts} +1 -1
  3. package/dist/{chunk-RKCARBWM.js → chunk-5VFC5T4N.js} +73 -1048
  4. package/dist/{chunk-B2BQFF5B.js → chunk-DTAYI7AV.js} +20 -20
  5. package/dist/{chunk-WAX4QYHA.js → chunk-E2NFHYCB.js} +14 -3
  6. package/dist/{chunk-H54A35NZ.js → chunk-EX75I5HX.js} +3 -3
  7. package/dist/{chunk-3H7SYCZS.js → chunk-FTN6SGW5.js} +1 -1
  8. package/dist/{chunk-QHNO67JS.js → chunk-GX3VSKYS.js} +1 -1
  9. package/dist/{chunk-I4L4GYDN.js → chunk-HYJ63N6V.js} +1 -1
  10. package/dist/{chunk-B3AFOXMA.js → chunk-MPEGJKFJ.js} +1031 -3
  11. package/dist/chunk-MWSGNEH6.js +2376 -0
  12. package/dist/{chunk-M6YXFNGO.js → chunk-P53Z4RYC.js} +10 -0
  13. package/dist/{chunk-LN72K54P.js → chunk-TFMDVOUQ.js} +10 -9
  14. package/dist/chunk-Y25EJEZN.js +178 -0
  15. package/dist/{chunk-ISH4HDH7.js → chunk-YZ66VMBB.js} +1 -1
  16. package/dist/{chunk-VTG7PNOL.js → chunk-ZKDJHO6Y.js} +3 -8
  17. package/dist/doc/index.d.ts +17 -9
  18. package/dist/doc/index.js +41 -39
  19. package/dist/fence/index.d.ts +1 -1
  20. package/dist/generate/index.d.ts +1 -1
  21. package/dist/imageEdit/index.d.ts +3 -3
  22. package/dist/index.d.ts +6 -6
  23. package/dist/index.js +68 -48
  24. package/dist/jsonForm/index.d.ts +1 -1
  25. package/dist/jsonForm/index.js +4 -4
  26. package/dist/markdown/index.d.ts +143 -1
  27. package/dist/markdown/index.js +29 -10
  28. package/dist/{materializePageSection-Dx1Sz0vK.d.ts → materializePageSection-DYnmh8X1.d.ts} +1 -1
  29. package/dist/mediaEdit/index.d.ts +2 -2
  30. package/dist/narration/index.d.ts +1 -1
  31. package/dist/narration/index.js +8 -8
  32. package/dist/recommend/index.js +3 -2
  33. package/dist/schemas/index.d.ts +4 -4
  34. package/dist/schemas/index.js +3 -3
  35. package/dist/{themeLibrary-DLmZ1Ct8.d.ts → themeLibrary-IHAirzT-.d.ts} +1 -1
  36. package/dist/timing/index.d.ts +1 -1
  37. package/dist/transform/index.d.ts +2 -2
  38. package/dist/transform/index.js +8 -8
  39. package/package.json +1 -1
  40. package/dist/chunk-NNJA7ICB.js +0 -1088
  41. package/dist/chunk-ZYWYSM6W.js +0 -657
@@ -1,19 +1,33 @@
1
1
  import {
2
+ asciiDiagramToTemplateData,
3
+ asciiTimelineToTemplateData,
4
+ autoTemplatePreservesContent,
5
+ computeDiagramLayout,
6
+ deriveTemplateInputs,
2
7
  detectAsciiDiagram,
3
8
  detectAsciiTimeline,
4
9
  detectTree,
10
+ extractEmbeddedVideos,
11
+ extractHostedVideos,
12
+ extractImages,
13
+ extractRichListItems,
14
+ extractTableFromContents,
5
15
  isEligibleAsciiFenceLang,
6
16
  isEligibleAsciiTimelineFenceLang,
7
17
  isEligibleTreeFenceLang,
8
18
  isExplicitDiagramLang,
9
19
  isExplicitTimelineLang,
10
20
  isExplicitTreeLang,
11
- matchNumberHighlight,
21
+ isMaterializedMediaNode,
22
+ isPictureFence,
23
+ markdownBlockSeparatorLines,
12
24
  parseAsciiDiagram,
13
25
  parseAsciiTimeline,
14
26
  pickAutoTemplate,
15
- profileBlockContents
16
- } from "./chunk-B3AFOXMA.js";
27
+ profileBlockContents,
28
+ renderMarkdownBlocksHtml,
29
+ treeToTemplateData
30
+ } from "./chunk-MPEGJKFJ.js";
17
31
  import {
18
32
  anchorPoint,
19
33
  base64ToUtf8,
@@ -43,37 +57,37 @@ import {
43
57
  validateCustomTemplateDefinition,
44
58
  validateTheme,
45
59
  withAlpha
46
- } from "./chunk-I4L4GYDN.js";
60
+ } from "./chunk-HYJ63N6V.js";
47
61
  import {
48
62
  mediaEditValuesFromAttributes,
49
63
  mediaEditValuesFromParams,
50
64
  parseMediaEdits
51
65
  } from "./chunk-L7QZORLB.js";
52
66
  import {
53
- KNOWN_BLOCK_META_KEYS,
54
67
  TABLE_FED_TEMPLATES,
55
- coerceAnnotationValues,
56
68
  isContainerTemplate,
57
69
  matchTrailingTemplateAnnotation,
58
- parseNumber,
59
- parseTimeSeconds,
60
70
  resolveTemplateName,
61
- sanitizeUrl,
62
71
  splitKeyValueToken,
63
72
  tokenizeAttrTokens
64
- } from "./chunk-ZYWYSM6W.js";
73
+ } from "./chunk-Y25EJEZN.js";
65
74
  import {
75
+ KNOWN_BLOCK_META_KEYS,
76
+ coerceAnnotationValues,
66
77
  extractPlainText,
67
- findBlockVideoEmbed,
78
+ isSvgIslandLang,
68
79
  isVideoEmbedUrl,
69
80
  mediaKindForUrl,
81
+ parseNumber,
82
+ parseSvgIsland,
83
+ parseTimeSeconds,
70
84
  parseVideoEmbedUrl,
71
85
  readFrontmatterThemeId,
72
86
  walkMarkdownTree
73
- } from "./chunk-NNJA7ICB.js";
87
+ } from "./chunk-MWSGNEH6.js";
74
88
  import {
75
89
  resolveBlockTransition
76
- } from "./chunk-M6YXFNGO.js";
90
+ } from "./chunk-P53Z4RYC.js";
77
91
  import {
78
92
  estimateReadingTime,
79
93
  estimateSpokenWordCount
@@ -1415,998 +1429,6 @@ function bigText(input, context) {
1415
1429
  return layers;
1416
1430
  }
1417
1431
 
1418
- // src/doc/templates/diagramLayout.ts
1419
- var DEFAULT_OPTS = {
1420
- gapX: 260,
1421
- gapY: 140,
1422
- gridPad: 80,
1423
- gridOriginX: 80,
1424
- gridOriginY: 80
1425
- };
1426
- function computeDiagramLayout(children, options = {}) {
1427
- const opts = { ...DEFAULT_OPTS, ...options };
1428
- const warnings = [];
1429
- const entries = [];
1430
- const unpositionedIdx = [];
1431
- for (let i = 0; i < children.length; i++) {
1432
- const c = children[i];
1433
- const hasX = typeof c.x === "number";
1434
- const hasY = typeof c.y === "number";
1435
- if (hasX && hasY) {
1436
- entries.push({ child: c, pinned: true, x: c.x, y: c.y });
1437
- } else {
1438
- entries.push({ child: c, pinned: false, x: 0, y: 0 });
1439
- unpositionedIdx.push(i);
1440
- }
1441
- }
1442
- if (unpositionedIdx.length > 0) {
1443
- const cols = Math.max(1, Math.ceil(Math.sqrt(unpositionedIdx.length)));
1444
- let baseX = opts.gridOriginX;
1445
- let baseY = opts.gridOriginY;
1446
- const pinned = entries.filter((e) => e.pinned);
1447
- if (pinned.length > 0) {
1448
- const minX = Math.min(...pinned.map((e) => e.x));
1449
- const maxY = Math.max(...pinned.map((e) => e.y));
1450
- baseX = minX;
1451
- baseY = maxY + opts.gridPad + opts.gapY;
1452
- }
1453
- unpositionedIdx.forEach((entryIdx, k) => {
1454
- const col = k % cols;
1455
- const row = Math.floor(k / cols);
1456
- entries[entryIdx].x = baseX + col * opts.gapX;
1457
- entries[entryIdx].y = baseY + row * opts.gapY;
1458
- });
1459
- }
1460
- const nodes = entries.map((e) => ({
1461
- id: e.child.id,
1462
- label: e.child.title ?? e.child.id,
1463
- x: e.x,
1464
- y: e.y,
1465
- pinned: e.pinned,
1466
- ...e.child.classes && e.child.classes.length > 0 ? { classes: e.child.classes } : {}
1467
- }));
1468
- const nodeIds = new Set(nodes.map((n) => n.id));
1469
- const edges = [];
1470
- const seenEdgeIds = /* @__PURE__ */ new Set();
1471
- for (const child of children) {
1472
- if (!child.connectsTo) continue;
1473
- for (const conn of child.connectsTo) {
1474
- if (!nodeIds.has(conn.target)) {
1475
- warnings.push(`Edge from "${child.id}" to "${conn.target}" dropped: no such sibling node`);
1476
- continue;
1477
- }
1478
- const edgeId = makeEdgeId(child.id, conn);
1479
- if (seenEdgeIds.has(edgeId)) continue;
1480
- seenEdgeIds.add(edgeId);
1481
- edges.push({
1482
- id: edgeId,
1483
- source: child.id,
1484
- target: conn.target,
1485
- ...conn.type ? { type: conn.type } : {}
1486
- });
1487
- }
1488
- }
1489
- return { nodes, edges, warnings };
1490
- }
1491
- function makeEdgeId(source, conn) {
1492
- return conn.type ? `${source}->${conn.target}:${conn.type}` : `${source}->${conn.target}`;
1493
- }
1494
-
1495
- // src/doc/asciiDiagram/types.ts
1496
- var ASCII_CHAR_W = 14;
1497
- var ASCII_CHAR_H = 28;
1498
-
1499
- // src/doc/asciiDiagram/mapping.ts
1500
- function asciiCellToCanvas(col, row) {
1501
- return { x: col * ASCII_CHAR_W, y: row * ASCII_CHAR_H };
1502
- }
1503
- function canvasToAsciiCell(x, y) {
1504
- return {
1505
- col: Math.max(0, Math.round(x / ASCII_CHAR_W)),
1506
- row: Math.max(0, Math.round(y / ASCII_CHAR_H))
1507
- };
1508
- }
1509
- function asciiDiagramToTemplateData(diagram) {
1510
- const nodes = diagram.nodes.map((n) => ({
1511
- id: n.id,
1512
- label: n.label,
1513
- ...asciiCellToCanvas(n.col, n.row),
1514
- w: n.wCols * ASCII_CHAR_W,
1515
- h: n.hRows * ASCII_CHAR_H,
1516
- ...n.containerId ? { container: n.containerId } : {}
1517
- }));
1518
- const anchors = inferEdgeAnchors(nodes, diagram.edges);
1519
- const edges = diagram.edges.map((e, index) => ({
1520
- source: e.source,
1521
- target: e.target,
1522
- ...e.label ? { label: e.label } : {},
1523
- ...e.directed ? {} : { directed: false },
1524
- ...anchors[index],
1525
- routing: "orthogonal"
1526
- }));
1527
- return { nodes, edges };
1528
- }
1529
- function inferEdgeAnchors(nodes, edges) {
1530
- const nodeById = new Map(nodes.map((node) => [node.id, node]));
1531
- const pairs = edges.map((edge) => {
1532
- const source = nodeById.get(edge.source);
1533
- const target = nodeById.get(edge.target);
1534
- if (!source || !target) {
1535
- return {
1536
- sourceAnchor: { side: "right", offset: 0.5 },
1537
- targetAnchor: { side: "left", offset: 0.5 }
1538
- };
1539
- }
1540
- const sourceW = source.w ?? ASCII_CHAR_W * 12;
1541
- const sourceH = source.h ?? ASCII_CHAR_H * 3;
1542
- const targetW = target.w ?? ASCII_CHAR_W * 12;
1543
- const targetH = target.h ?? ASCII_CHAR_H * 3;
1544
- const dx = target.x + targetW / 2 - (source.x + sourceW / 2);
1545
- const dy = target.y + targetH / 2 - (source.y + sourceH / 2);
1546
- const horizontalDistance = Math.abs(dx) / Math.max(1, (sourceW + targetW) / 2);
1547
- const verticalDistance = Math.abs(dy) / Math.max(1, (sourceH + targetH) / 2);
1548
- if (verticalDistance >= horizontalDistance) {
1549
- return dy >= 0 ? {
1550
- sourceAnchor: { side: "bottom", offset: 0.5 },
1551
- targetAnchor: { side: "top", offset: 0.5 }
1552
- } : {
1553
- sourceAnchor: { side: "top", offset: 0.5 },
1554
- targetAnchor: { side: "bottom", offset: 0.5 }
1555
- };
1556
- }
1557
- return dx >= 0 ? {
1558
- sourceAnchor: { side: "right", offset: 0.5 },
1559
- targetAnchor: { side: "left", offset: 0.5 }
1560
- } : {
1561
- sourceAnchor: { side: "left", offset: 0.5 },
1562
- targetAnchor: { side: "right", offset: 0.5 }
1563
- };
1564
- });
1565
- distributeOffsets(pairs, edges, nodes, "source");
1566
- distributeOffsets(pairs, edges, nodes, "target");
1567
- coalesceAlignedFanOutAnchors(pairs, edges, nodes);
1568
- return pairs;
1569
- }
1570
- function coalesceAlignedFanOutAnchors(pairs, edges, nodes) {
1571
- const nodeById = new Map(nodes.map((node) => [node.id, node]));
1572
- const groups = /* @__PURE__ */ new Map();
1573
- edges.forEach((edge, index) => {
1574
- const group = groups.get(edge.source) ?? [];
1575
- group.push(index);
1576
- groups.set(edge.source, group);
1577
- });
1578
- for (const [sourceId, indexes] of groups) {
1579
- if (indexes.length < 2) continue;
1580
- const source = nodeById.get(sourceId);
1581
- const targets = indexes.map((index) => nodeById.get(edges[index].target)).filter((node) => node !== void 0);
1582
- if (!source || targets.length !== indexes.length) continue;
1583
- const sourceW = source.w ?? ASCII_CHAR_W * 12;
1584
- const sourceH = source.h ?? ASCII_CHAR_H * 3;
1585
- const sourceRight = source.x + sourceW;
1586
- const sourceBottom = source.y + sourceH;
1587
- const targetRight = (target) => target.x + (target.w ?? ASCII_CHAR_W * 12);
1588
- const targetBottom = (target) => target.y + (target.h ?? ASCII_CHAR_H * 3);
1589
- let sourceSide;
1590
- let targetSide;
1591
- if (targets.every((target) => target.y >= sourceBottom)) {
1592
- sourceSide = "bottom";
1593
- targetSide = "top";
1594
- } else if (targets.every((target) => targetBottom(target) <= source.y)) {
1595
- sourceSide = "top";
1596
- targetSide = "bottom";
1597
- } else if (targets.every((target) => target.x >= sourceRight)) {
1598
- sourceSide = "right";
1599
- targetSide = "left";
1600
- } else if (targets.every((target) => targetRight(target) <= source.x)) {
1601
- sourceSide = "left";
1602
- targetSide = "right";
1603
- }
1604
- if (!sourceSide || !targetSide) continue;
1605
- for (const index of indexes) {
1606
- pairs[index] = {
1607
- sourceAnchor: { side: sourceSide, offset: 0.5 },
1608
- targetAnchor: { side: targetSide, offset: 0.5 }
1609
- };
1610
- }
1611
- }
1612
- }
1613
- function distributeOffsets(pairs, edges, nodes, endpoint) {
1614
- const nodeById = new Map(nodes.map((node) => [node.id, node]));
1615
- const groups = /* @__PURE__ */ new Map();
1616
- for (let index = 0; index < edges.length; index++) {
1617
- const edge = edges[index];
1618
- const anchor = endpoint === "source" ? pairs[index].sourceAnchor : pairs[index].targetAnchor;
1619
- const nodeId = endpoint === "source" ? edge.source : edge.target;
1620
- const key = `${nodeId}:${anchor.side}`;
1621
- const group = groups.get(key) ?? [];
1622
- group.push(index);
1623
- groups.set(key, group);
1624
- }
1625
- for (const indexes of groups.values()) {
1626
- if (indexes.length < 2) continue;
1627
- const side = endpoint === "source" ? pairs[indexes[0]].sourceAnchor.side : pairs[indexes[0]].targetAnchor.side;
1628
- indexes.sort((a, b) => {
1629
- const oppositeA = nodeById.get(endpoint === "source" ? edges[a].target : edges[a].source);
1630
- const oppositeB = nodeById.get(endpoint === "source" ? edges[b].target : edges[b].source);
1631
- const axisA = side === "top" || side === "bottom" ? oppositeA?.x ?? 0 : oppositeA?.y ?? 0;
1632
- const axisB = side === "top" || side === "bottom" ? oppositeB?.x ?? 0 : oppositeB?.y ?? 0;
1633
- return axisA - axisB || a - b;
1634
- });
1635
- indexes.forEach((edgeIndex, rank) => {
1636
- const anchor = endpoint === "source" ? pairs[edgeIndex].sourceAnchor : pairs[edgeIndex].targetAnchor;
1637
- anchor.offset = (rank + 1) / (indexes.length + 1);
1638
- });
1639
- }
1640
- }
1641
- function asciiDiagramFromTemplateData(nodes, edges, options = {}) {
1642
- const ids = new Set(nodes.map((n) => n.id));
1643
- const outNodes = nodes.map((n) => {
1644
- const { col, row } = canvasToAsciiCell(n.x, n.y);
1645
- const labelLines = n.label.length > 0 ? n.label.split("\n") : [];
1646
- const innerW = labelLines.reduce((w, l) => Math.max(w, l.length), 0);
1647
- const wCols = typeof n.w === "number" ? Math.max(3, Math.round(n.w / ASCII_CHAR_W)) : innerW + 4;
1648
- const hRows = typeof n.h === "number" ? Math.max(3, Math.round(n.h / ASCII_CHAR_H)) : labelLines.length + 2;
1649
- return {
1650
- id: n.id,
1651
- label: n.label,
1652
- col,
1653
- row,
1654
- wCols: Math.max(wCols, 3),
1655
- hRows: Math.max(hRows, 3),
1656
- ...n.container && ids.has(n.container) && n.container !== n.id ? { containerId: n.container } : {}
1657
- };
1658
- });
1659
- return {
1660
- nodes: outNodes,
1661
- edges: edges.filter((e) => ids.has(e.source) && ids.has(e.target)).map((e) => ({
1662
- source: e.source,
1663
- target: e.target,
1664
- ...e.label ? { label: e.label } : {},
1665
- directed: e.directed !== false
1666
- })),
1667
- style: options.style ?? "unicode",
1668
- warnings: []
1669
- };
1670
- }
1671
- function asciiDiagramFromBlocks(children, options = {}) {
1672
- const layout = computeDiagramLayout(children);
1673
- const nodes = layout.nodes.map((n) => ({
1674
- id: n.id,
1675
- label: n.label,
1676
- x: n.x,
1677
- y: n.y
1678
- }));
1679
- const edges = layout.edges.map((e) => ({
1680
- source: e.source,
1681
- target: e.target,
1682
- ...e.type ? { label: e.type } : {}
1683
- }));
1684
- const diagram = asciiDiagramFromTemplateData(nodes, edges, options);
1685
- diagram.warnings.push(...layout.warnings);
1686
- return diagram;
1687
- }
1688
-
1689
- // src/doc/asciiTimeline/mapping.ts
1690
- function asciiTimelineToTemplateData(timeline) {
1691
- const globalStart = timeline.tracks.length > 0 ? Math.min(...timeline.tracks.map((track) => track.startColumn)) : 0;
1692
- const globalEnd = timeline.tracks.length > 0 ? Math.max(...timeline.tracks.map((track) => track.endColumn)) : 1;
1693
- const span = Math.max(1, globalEnd - globalStart);
1694
- const tracks = timeline.tracks.map((track) => ({
1695
- id: track.id,
1696
- ...track.label ? { label: track.label } : {},
1697
- ...track.endLabel ? { endLabel: track.endLabel } : {},
1698
- events: track.events.map((event) => ({
1699
- id: event.id,
1700
- label: event.label,
1701
- ...event.description ? { description: event.description } : {},
1702
- position: clamp01((event.column - globalStart) / span),
1703
- ...event.side ? { side: event.side } : {},
1704
- ...event.descriptionSide ? { descriptionSide: event.descriptionSide } : {},
1705
- ...event.callout === false ? { callout: false } : {},
1706
- ...event.marker ? { marker: event.marker } : {}
1707
- }))
1708
- }));
1709
- const links = timeline.links.map((link) => ({ ...link }));
1710
- return { tracks, links };
1711
- }
1712
- function clamp01(value) {
1713
- return Math.max(0, Math.min(1, value));
1714
- }
1715
-
1716
- // src/doc/treeview/mapping.ts
1717
- function treeToTemplateData(tree) {
1718
- return { items: tree.roots.map(toItem) };
1719
- }
1720
- function toItem(node) {
1721
- return {
1722
- id: node.id,
1723
- label: node.label,
1724
- ...node.isDir ? { isDir: true } : {},
1725
- ...node.comment ? { comment: node.comment } : {},
1726
- children: node.children.map(toItem)
1727
- };
1728
- }
1729
- function treeFromTemplateData(items, options = {}) {
1730
- const roots = items.map(fromItem);
1731
- return { roots, style: options.style ?? "unicode", warnings: [] };
1732
- }
1733
- function fromItem(item) {
1734
- const children = Array.isArray(item.children) ? item.children.map(fromItem) : [];
1735
- return {
1736
- id: typeof item.id === "string" && item.id ? item.id : "node",
1737
- label: typeof item.label === "string" ? item.label : "",
1738
- children,
1739
- isDir: item.isDir === true || children.length > 0,
1740
- ...item.comment ? { comment: item.comment } : {}
1741
- };
1742
- }
1743
- function treeFromMarkdownList(list) {
1744
- const usedIds = /* @__PURE__ */ new Map();
1745
- const slugify2 = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "node";
1746
- const idFor = (label) => {
1747
- const base = slugify2(label.replace(/\/$/, ""));
1748
- const count = usedIds.get(base) ?? 0;
1749
- usedIds.set(base, count + 1);
1750
- return count === 0 ? base : `${base}-${count + 1}`;
1751
- };
1752
- const itemToNode = (item) => {
1753
- const labelNode = item.children.find((c) => c.type !== "list");
1754
- const label = labelNode ? extractPlainText(labelNode).trim() : "";
1755
- const childLists = item.children.filter((c) => c.type === "list");
1756
- const children = [];
1757
- for (const cl of childLists) for (const ci of cl.children) children.push(itemToNode(ci));
1758
- return {
1759
- id: idFor(label),
1760
- label,
1761
- children,
1762
- isDir: label.endsWith("/") || children.length > 0
1763
- };
1764
- };
1765
- return { roots: list.children.map(itemToNode), style: "unicode", warnings: [] };
1766
- }
1767
- function findFirstList(contents) {
1768
- return contents?.find((n) => n.type === "list");
1769
- }
1770
-
1771
- // src/doc/templateInputs.ts
1772
- var VIDEO_FILE_RE = /\.(?:webm|mp4|mov|m4v|ogv)(?:[?#].*)?$/i;
1773
- function extractHostedVideos(contents, limit = Infinity) {
1774
- const found = [];
1775
- const seen = /* @__PURE__ */ new Set();
1776
- for (const node of contents ?? []) {
1777
- if (found.length >= limit) break;
1778
- const video = findBlockVideoEmbed(node);
1779
- if (!video || seen.has(video.embed.watchUrl)) continue;
1780
- seen.add(video.embed.watchUrl);
1781
- found.push({
1782
- url: video.embed.watchUrl,
1783
- title: video.title,
1784
- aspectRatio: video.embed.aspectRatio
1785
- });
1786
- }
1787
- return found;
1788
- }
1789
- function isMaterializedMediaNode(node) {
1790
- if (node.type === "code") return node.lang?.trim().toLowerCase() === "mermaid";
1791
- return findBlockVideoEmbed(node) !== null;
1792
- }
1793
- function extractBodyPlainText(contents) {
1794
- if (!contents || contents.length === 0) return "";
1795
- return contents.filter((node) => !isMaterializedMediaNode(node)).map((n) => extractPlainText(n)).join("\n").trim();
1796
- }
1797
- function extractListItems(contents) {
1798
- return extractRichListItems(contents).map((item) => item.text);
1799
- }
1800
- function escapeInlineHtml(value) {
1801
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
1802
- }
1803
- function renderInlineHtml(nodes) {
1804
- return nodes.map((node) => {
1805
- switch (node.type) {
1806
- case "text":
1807
- return escapeInlineHtml(node.value);
1808
- case "emphasis":
1809
- return `<em>${renderInlineHtml(node.children)}</em>`;
1810
- case "strong":
1811
- return `<strong>${renderInlineHtml(node.children)}</strong>`;
1812
- case "delete":
1813
- return `<del>${renderInlineHtml(node.children)}</del>`;
1814
- case "superscript":
1815
- return `<sup>${renderInlineHtml(node.children)}</sup>`;
1816
- case "subscript":
1817
- return `<sub>${renderInlineHtml(node.children)}</sub>`;
1818
- case "inlineCode":
1819
- return `<code>${escapeInlineHtml(node.value)}</code>`;
1820
- case "link": {
1821
- const label = renderInlineHtml(node.children);
1822
- const href = sanitizeUrl(node.url, "link");
1823
- if (!href) return label;
1824
- const title = node.title ? ` title="${escapeInlineHtml(node.title)}"` : "";
1825
- return `<a href="${escapeInlineHtml(href)}"${title}>${label}</a>`;
1826
- }
1827
- case "break":
1828
- return "<br>";
1829
- case "image":
1830
- return escapeInlineHtml(node.alt ?? "");
1831
- case "inlineMath":
1832
- return escapeInlineHtml(`$${node.value}$`);
1833
- case "footnoteReference":
1834
- return escapeInlineHtml(`[^${node.label ?? node.identifier}]`);
1835
- case "linkReference":
1836
- case "textDirective":
1837
- return renderInlineHtml(node.children);
1838
- case "imageReference":
1839
- return escapeInlineHtml(node.alt ?? "");
1840
- case "mention":
1841
- return escapeInlineHtml(`@${node.displayName}`);
1842
- case "inlineIcon":
1843
- return `<i class="fa-${node.family} fa-${node.name}" aria-hidden="true"></i>`;
1844
- case "htmlInline":
1845
- return escapeInlineHtml(node.rawHtml);
1846
- }
1847
- }).join("");
1848
- }
1849
- function renderMarkdownBlocksHtml(nodes, builds) {
1850
- const renderBlocks = (blocks) => blocks.map((node, index) => {
1851
- const previous = blocks[index - 1];
1852
- const extraBlankLines = previous ? Math.max(0, markdownBlockSeparatorLines(previous, node) - 2) : 0;
1853
- const sourceGap = '<div data-squisq-source-gap aria-hidden="true"><br></div>'.repeat(
1854
- extraBlankLines
1855
- );
1856
- const html = renderBlock(node);
1857
- const step = builds?.targets.get(node);
1858
- return `${sourceGap}${step === void 0 ? html : `<div data-squisq-build-step="${step}">${html}</div>`}`;
1859
- }).join("");
1860
- const renderList = (node) => {
1861
- const tag = node.ordered ? "ol" : "ul";
1862
- const start = node.ordered && node.start != null ? ` start="${node.start}"` : "";
1863
- const items = node.children.map((item, index) => {
1864
- const taskMarker = item.checked == null ? "" : `<span>${item.checked ? "[x]" : "[ ]"} </span>`;
1865
- const step = builds?.targets.get(item);
1866
- if (step === void 0) return `<li>${taskMarker}${renderBlocks(item.children)}</li>`;
1867
- const marker = item.checked == null ? node.ordered ? `${(node.start ?? 1) + index}.` : "\u2022" : item.checked ? "[x]" : "[ ]";
1868
- let first = true;
1869
- let own = [];
1870
- const fragments = [];
1871
- const flush = () => {
1872
- if (!first && !own.length) return;
1873
- fragments.push(
1874
- `<div data-squisq-build-step="${step}" data-squisq-build-row><span aria-hidden="true">${first ? marker : ""}</span><div>${renderBlocks(own)}</div></div>`
1875
- );
1876
- first = false;
1877
- own = [];
1878
- };
1879
- for (const child of item.children) {
1880
- if (child.type === "list") {
1881
- flush();
1882
- fragments.push(renderList(child));
1883
- } else own.push(child);
1884
- }
1885
- flush();
1886
- return `<li data-squisq-build-item>${fragments.join("")}</li>`;
1887
- }).join("");
1888
- return `<${tag}${start}>${items}</${tag}>`;
1889
- };
1890
- const renderBlock = (node) => {
1891
- switch (node.type) {
1892
- case "paragraph":
1893
- return `<p>${renderInlineHtml(node.children)}</p>`;
1894
- case "heading":
1895
- return `<h${node.depth}>${renderInlineHtml(node.children)}</h${node.depth}>`;
1896
- case "blockquote":
1897
- return `<blockquote>${renderBlocks(node.children)}</blockquote>`;
1898
- case "list":
1899
- return renderList(node);
1900
- case "code":
1901
- return `<pre><code>${escapeInlineHtml(node.value)}</code></pre>`;
1902
- case "thematicBreak":
1903
- return "<hr>";
1904
- case "table": {
1905
- const [head, ...body] = node.children;
1906
- const row = (cells, header) => `<tr>${cells.map((cell, columnIndex) => {
1907
- const tag = header ? "th" : "td";
1908
- const alignment = node.align?.[columnIndex];
1909
- const alignAttribute = alignment ? ` align="${alignment}"` : "";
1910
- return `<${tag}${alignAttribute}>${renderInlineHtml(cell.children)}</${tag}>`;
1911
- }).join("")}</tr>`;
1912
- const thead = head ? `<thead>${row(head.children, true)}</thead>` : "";
1913
- const tbody = body.length ? `<tbody>${body.map((item) => row(item.children, false)).join("")}</tbody>` : "";
1914
- return `<div data-squisq-table-scroll role="region" aria-label="Scrollable data table"><table>${thead}${tbody}</table></div>`;
1915
- }
1916
- case "math":
1917
- return `<pre><code>${escapeInlineHtml(node.value)}</code></pre>`;
1918
- case "definition":
1919
- return "";
1920
- default: {
1921
- const text = extractPlainText(node).trim();
1922
- return text ? `<p>${escapeInlineHtml(text)}</p>` : "";
1923
- }
1924
- }
1925
- };
1926
- return renderBlocks(nodes);
1927
- }
1928
- function markdownBlockSeparatorLines(previous, next) {
1929
- const previousEndLine = previous.position?.end.line;
1930
- const nextStartLine = next.position?.start.line;
1931
- if (previousEndLine == null || nextStartLine == null || nextStartLine <= previousEndLine) {
1932
- return 2;
1933
- }
1934
- return Math.max(2, nextStartLine - previousEndLine);
1935
- }
1936
- function renderListItemHtml(markdown) {
1937
- return markdown.map(
1938
- (node) => node.type === "paragraph" || node.type === "heading" ? renderInlineHtml(node.children) : escapeInlineHtml(extractPlainText(node))
1939
- ).join("<br>");
1940
- }
1941
- function extractRichListItems(contents) {
1942
- if (!contents) return [];
1943
- const items = [];
1944
- for (const node of contents) {
1945
- if (node.type !== "list") continue;
1946
- for (const item of node.children) {
1947
- const text = extractPlainText(item).trim();
1948
- if (!text) continue;
1949
- const html = renderListItemHtml(item.children);
1950
- const plainHtml = escapeInlineHtml(text).replace(/\r?\n/g, "<br>");
1951
- items.push({
1952
- text,
1953
- markdown: item.children,
1954
- ...html && html !== plainHtml ? { html } : {}
1955
- });
1956
- }
1957
- }
1958
- return items;
1959
- }
1960
- function parseDim(raw) {
1961
- if (raw === void 0) return void 0;
1962
- const n = parseFloat(raw);
1963
- return Number.isFinite(n) && n > 0 ? n : void 0;
1964
- }
1965
- function extractImages(contents, limit = Infinity) {
1966
- if (!contents || contents.length === 0) return [];
1967
- const found = [];
1968
- function fromHtml(nodes) {
1969
- for (const node of nodes) {
1970
- if (found.length >= limit) return;
1971
- if (!node || typeof node !== "object") continue;
1972
- const n = node;
1973
- if (n.type === "htmlElement" && n.tagName === "img") {
1974
- const attrs = n.attributes;
1975
- if (attrs && typeof attrs.src === "string" && attrs.src) {
1976
- found.push({
1977
- src: attrs.src,
1978
- alt: typeof attrs.alt === "string" ? attrs.alt : "",
1979
- width: parseDim(attrs.width),
1980
- height: parseDim(attrs.height)
1981
- });
1982
- }
1983
- }
1984
- if (Array.isArray(n.children)) fromHtml(n.children);
1985
- }
1986
- }
1987
- function walk(node) {
1988
- if (found.length >= limit) return;
1989
- if (!node || typeof node !== "object") return;
1990
- const n = node;
1991
- if (n.type === "image" && typeof n.url === "string" && n.url) {
1992
- if (!mediaKindForUrl(n.url)) {
1993
- found.push({ src: n.url, alt: typeof n.alt === "string" ? n.alt : "" });
1994
- }
1995
- return;
1996
- }
1997
- if ((n.type === "htmlBlock" || n.type === "htmlInline") && Array.isArray(n.htmlChildren)) {
1998
- fromHtml(n.htmlChildren);
1999
- }
2000
- if (Array.isArray(n.children)) {
2001
- for (const child of n.children) walk(child);
2002
- }
2003
- }
2004
- for (const node of contents) {
2005
- if (found.length >= limit) break;
2006
- walk(node);
2007
- }
2008
- return found;
2009
- }
2010
- function extractFirstImage(contents) {
2011
- return extractImages(contents, 1)[0] ?? null;
2012
- }
2013
- function extractEmbeddedVideos(contents, limit = Infinity) {
2014
- if (!contents || contents.length === 0) return [];
2015
- const found = [];
2016
- const seen = /* @__PURE__ */ new Set();
2017
- const add = (video) => {
2018
- if (!video.src || found.length >= limit || seen.has(video.src)) return;
2019
- seen.add(video.src);
2020
- found.push(video);
2021
- };
2022
- function nestedSource(children) {
2023
- if (!Array.isArray(children)) return void 0;
2024
- for (const child of children) {
2025
- if (!child || typeof child !== "object") continue;
2026
- const node = child;
2027
- if (node.tagName === "source") {
2028
- const attrs = node.attributes;
2029
- if (typeof attrs?.src === "string" && attrs.src) return attrs.src;
2030
- }
2031
- const nested = nestedSource(node.children);
2032
- if (nested) return nested;
2033
- }
2034
- return void 0;
2035
- }
2036
- function walk(node) {
2037
- if (found.length >= limit || !node || typeof node !== "object") return;
2038
- const n = node;
2039
- if (n.type === "htmlElement" && n.tagName === "video") {
2040
- const attrs = n.attributes;
2041
- const src = attrs?.src || nestedSource(n.children);
2042
- if (src) {
2043
- const startAt = attrs?.["data-squisq-video-start-at"] ? parseTimeSeconds(attrs["data-squisq-video-start-at"]) : null;
2044
- const clipStart = attrs?.["data-squisq-video-clip-start"] ? parseTimeSeconds(attrs["data-squisq-video-clip-start"]) : null;
2045
- const clipEnd = attrs?.["data-squisq-video-clip-end"] ? parseTimeSeconds(attrs["data-squisq-video-clip-end"]) : null;
2046
- add({
2047
- src,
2048
- ...attrs?.poster ? { posterSrc: attrs.poster } : {},
2049
- alt: attrs?.["aria-label"] || attrs?.title || attrs?.alt || "",
2050
- ...startAt != null ? { startAt } : {},
2051
- ...clipStart != null ? { clipStart } : {},
2052
- ...clipEnd != null ? { clipEnd } : {}
2053
- });
2054
- }
2055
- } else if ((n.type === "link" || n.type === "image") && typeof n.url === "string" && VIDEO_FILE_RE.test(n.url)) {
2056
- add({
2057
- src: n.url,
2058
- alt: typeof n.alt === "string" ? n.alt : extractPlainText(n).trim()
2059
- });
2060
- }
2061
- if (Array.isArray(n.children)) n.children.forEach(walk);
2062
- if (Array.isArray(n.htmlChildren)) n.htmlChildren.forEach(walk);
2063
- }
2064
- contents.forEach(walk);
2065
- return found;
2066
- }
2067
- function extractFirstEmbeddedVideo(contents) {
2068
- return extractEmbeddedVideos(contents, 1)[0] ?? null;
2069
- }
2070
- function extractTableFromContents(contents) {
2071
- if (!contents) return null;
2072
- for (const node of contents) {
2073
- if (node.type === "table") {
2074
- const table = node;
2075
- const [headerRow, ...bodyRows] = table.children;
2076
- if (!headerRow) return null;
2077
- const headers = headerRow.children.map((cell) => extractPlainText(cell).trim());
2078
- const rows = bodyRows.map((row) => row.children.map((cell) => extractPlainText(cell).trim()));
2079
- return { headers, rows, align: table.align };
2080
- }
2081
- }
2082
- return null;
2083
- }
2084
- function extractBlockquoteText(contents) {
2085
- if (!contents) return "";
2086
- for (const node of contents) {
2087
- if (node.type === "blockquote") return extractPlainText(node).trim();
2088
- }
2089
- return "";
2090
- }
2091
- function firstParagraph(contents) {
2092
- if (!contents) return null;
2093
- const index = contents.findIndex((node) => node.type === "paragraph");
2094
- if (index < 0) return null;
2095
- return {
2096
- node: contents[index],
2097
- index
2098
- };
2099
- }
2100
- function leadingStrongStat(paragraph) {
2101
- for (const child of paragraph.children) {
2102
- if (child.type === "text" && child.value.trim() === "") continue;
2103
- if (child.type !== "strong") return null;
2104
- const text = extractPlainText(child).trim();
2105
- const match = text.match(/^(?:[$€£¥]\s*)?[+-]?\d+(?:[.,]\d+)*(?:\s?(?:[%‰x×]|[A-Za-z]+))?/);
2106
- return match?.[0].trim() || null;
2107
- }
2108
- return null;
2109
- }
2110
- function removeStatPhrase(text, start, end) {
2111
- const before = text.slice(0, start).trim();
2112
- let after = text.slice(end);
2113
- if (!before) after = after.replace(/^\s*(?:[-–—:]\s*)?/, "");
2114
- else after = after.trim();
2115
- return [before, after].filter(Boolean).join(" ").trim();
2116
- }
2117
- function bodyWithoutParagraphStat(contents, paragraphIndex, paragraphText, start, end) {
2118
- if (!contents) return "";
2119
- return contents.map(
2120
- (node, index) => index === paragraphIndex ? removeStatPhrase(paragraphText, start, end) : extractPlainText(node).trim()
2121
- ).filter(Boolean).join("\n").trim();
2122
- }
2123
- function deriveStatHighlightInputs(headingText, contents, bodyText3) {
2124
- const paragraph = firstParagraph(contents);
2125
- if (paragraph) {
2126
- const paragraphText = extractPlainText(paragraph.node).trim();
2127
- const strongStat = leadingStrongStat(paragraph.node);
2128
- if (strongStat) {
2129
- const start = paragraphText.indexOf(strongStat);
2130
- const description = bodyWithoutParagraphStat(
2131
- contents,
2132
- paragraph.index,
2133
- paragraphText,
2134
- start,
2135
- start + strongStat.length
2136
- );
2137
- return {
2138
- stat: strongStat,
2139
- description: description || headingText || strongStat
2140
- };
2141
- }
2142
- }
2143
- const trimmedHeading = headingText.trim();
2144
- const headingStat = matchNumberHighlight(trimmedHeading);
2145
- if (headingStat && headingStat.index === 0 && headingStat.end === trimmedHeading.length) {
2146
- return { stat: headingText, description: bodyText3 || headingText };
2147
- }
2148
- if (paragraph) {
2149
- const paragraphText = extractPlainText(paragraph.node).trim();
2150
- const bodyStat = matchNumberHighlight(paragraphText);
2151
- if (bodyStat) {
2152
- const description = bodyWithoutParagraphStat(
2153
- contents,
2154
- paragraph.index,
2155
- paragraphText,
2156
- bodyStat.index,
2157
- bodyStat.end
2158
- );
2159
- return {
2160
- stat: bodyStat.value,
2161
- description: description || headingText || bodyStat.value
2162
- };
2163
- }
2164
- }
2165
- return { stat: headingText, description: bodyText3 || headingText };
2166
- }
2167
- function normalizeCoverageText(value) {
2168
- return value.replace(/\s+/g, " ").trim();
2169
- }
2170
- function autoTemplatePreservesContent(templateName, contents) {
2171
- const nodes = contents ?? [];
2172
- switch (templateName) {
2173
- case "quote":
2174
- return nodes.length === 1 && nodes[0]?.type === "blockquote";
2175
- case "dataTable":
2176
- return nodes.length === 1 && nodes[0]?.type === "table";
2177
- case "list":
2178
- return nodes.length === 1 && nodes[0]?.type === "list";
2179
- case "diagram":
2180
- case "timeline":
2181
- return nodes.length === 1 && nodes[0]?.type === "code";
2182
- case "tree":
2183
- return nodes.length === 1 && (nodes[0]?.type === "code" || nodes[0]?.type === "list");
2184
- case "photoGrid": {
2185
- const images = extractImages(nodes);
2186
- if (images.length < 2 || images.length > 4) return false;
2187
- const bodyText3 = normalizeCoverageText(extractBodyPlainText(nodes));
2188
- const representedText = normalizeCoverageText(
2189
- images.map((image) => image.alt).filter(Boolean).join(" ")
2190
- );
2191
- return bodyText3 === "" || bodyText3 === representedText;
2192
- }
2193
- case "videoWithCaption": {
2194
- const videos = extractEmbeddedVideos(nodes);
2195
- if (videos.length + extractHostedVideos(nodes).length !== 1) return false;
2196
- const bodyText3 = normalizeCoverageText(extractBodyPlainText(nodes));
2197
- return bodyText3 === normalizeCoverageText(videos[0]?.alt ?? "");
2198
- }
2199
- case "leftFeature":
2200
- case "rightFeature":
2201
- case "statHighlight":
2202
- return nodes.length > 0 && nodes.every((node) => node.type === "paragraph");
2203
- default:
2204
- return false;
2205
- }
2206
- }
2207
- function deriveTemplateInputs(templateName, headingText, contents, options = {}) {
2208
- const placeholders = options.placeholders === true;
2209
- const bodyText3 = extractBodyPlainText(contents);
2210
- switch (templateName) {
2211
- case "statHighlight": {
2212
- const inputs = deriveStatHighlightInputs(headingText, contents, bodyText3);
2213
- if (options.preserveSourceHeading && headingText && inputs.stat !== headingText && inputs.description !== headingText) {
2214
- return {
2215
- ...inputs,
2216
- description: headingText,
2217
- ...inputs.description ? { detail: inputs.description } : {}
2218
- };
2219
- }
2220
- return inputs;
2221
- }
2222
- case "transcript": {
2223
- const text = extractBlockquoteText(contents) || bodyText3;
2224
- if (!text && !headingText) {
2225
- return placeholders ? { speaker: "Speaker", text: "Message" } : null;
2226
- }
2227
- return {
2228
- speaker: headingText || (placeholders ? "Speaker" : ""),
2229
- text: text || (placeholders ? "Message" : "")
2230
- };
2231
- }
2232
- case "quote": {
2233
- const quote = extractBlockquoteText(contents) || bodyText3;
2234
- if (quote) return { quote, ...headingText ? { title: headingText } : {} };
2235
- return { quote: headingText };
2236
- }
2237
- case "fullBleedQuote": {
2238
- const text = extractBlockquoteText(contents) || bodyText3 || headingText;
2239
- return { text };
2240
- }
2241
- case "pullQuote": {
2242
- const text = extractBlockquoteText(contents) || bodyText3 || headingText;
2243
- const img = extractFirstImage(contents);
2244
- return {
2245
- text,
2246
- ...img ? { backgroundImage: { src: img.src, alt: img.alt || headingText } } : {}
2247
- };
2248
- }
2249
- case "videoPullQuote": {
2250
- const text = extractBlockquoteText(contents) || bodyText3 || headingText;
2251
- const video = extractFirstEmbeddedVideo(contents);
2252
- return {
2253
- text,
2254
- ...video ? {
2255
- backgroundVideo: {
2256
- src: video.src,
2257
- ...video.posterSrc ? { posterSrc: video.posterSrc } : {},
2258
- alt: video.alt || headingText,
2259
- ...video.clipStart != null ? { clipStart: video.clipStart } : {},
2260
- ...video.clipEnd != null ? { clipEnd: video.clipEnd } : {}
2261
- }
2262
- } : {}
2263
- };
2264
- }
2265
- case "factCard":
2266
- return { fact: headingText, explanation: bodyText3 };
2267
- case "twoColumn":
2268
- return headingText ? { header: headingText } : placeholders ? {} : null;
2269
- case "comparisonBar":
2270
- return placeholders ? { leftLabel: "A", leftValue: 60, rightLabel: "B", rightValue: 40 } : null;
2271
- case "list": {
2272
- const items = extractListItems(contents);
2273
- const firstList = (contents ?? []).find((n) => n.type === "list");
2274
- if (items.length > 0) {
2275
- return {
2276
- items,
2277
- // Bullets stay bullets: only a numbered source list renders `1.` markers.
2278
- ordered: firstList?.ordered === true,
2279
- ...headingText ? { title: headingText } : {}
2280
- };
2281
- }
2282
- return placeholders ? { items: ["Item 1", "Item 2", "Item 3"] } : null;
2283
- }
2284
- case "definitionCard":
2285
- return { term: headingText, definition: bodyText3 };
2286
- case "dateEvent":
2287
- return { date: headingText, description: bodyText3 };
2288
- case "dataTable": {
2289
- const tableData = extractTableFromContents(contents);
2290
- if (tableData) return { ...tableData, ...headingText ? { title: headingText } : {} };
2291
- return placeholders ? { headers: ["Column"], rows: [["Data"]] } : null;
2292
- }
2293
- case "barChart":
2294
- case "columnChart":
2295
- case "pieChart":
2296
- case "donutChart":
2297
- case "lineChart":
2298
- case "areaChart":
2299
- case "scatterChart": {
2300
- const tableData = extractTableFromContents(contents);
2301
- if (tableData) {
2302
- return {
2303
- headers: tableData.headers,
2304
- rows: tableData.rows,
2305
- ...headingText ? { title: headingText } : {}
2306
- };
2307
- }
2308
- return placeholders && !bodyText3.trim() ? {
2309
- headers: ["Quarter", "Revenue", "Costs"],
2310
- rows: [
2311
- ["Q1", "40", "28"],
2312
- ["Q2", "55", "31"],
2313
- ["Q3", "48", "30"],
2314
- ["Q4", "62", "35"]
2315
- ],
2316
- ...headingText ? { title: headingText } : {}
2317
- } : null;
2318
- }
2319
- case "imageWithCaption": {
2320
- const img = extractFirstImage(contents);
2321
- if (!img) return placeholders ? { caption: headingText } : null;
2322
- return { imageSrc: img.src, imageAlt: img.alt || headingText, caption: headingText };
2323
- }
2324
- case "bigText": {
2325
- const img = extractFirstImage(contents);
2326
- return {
2327
- title: headingText,
2328
- ...img ? { imageSrc: img.src, imageAlt: img.alt || headingText } : {}
2329
- };
2330
- }
2331
- case "photoGrid": {
2332
- const images = extractImages(contents, 4);
2333
- if (images.length < 2) return placeholders ? {} : null;
2334
- return {
2335
- images: images.map((i) => ({ src: i.src, alt: i.alt })),
2336
- caption: headingText
2337
- };
2338
- }
2339
- case "videoWithCaption": {
2340
- const video = extractFirstEmbeddedVideo(contents);
2341
- if (!video) {
2342
- const hosted = extractHostedVideos(contents, 1)[0];
2343
- if (hosted) {
2344
- return {
2345
- videoSrc: hosted.url,
2346
- videoAlt: hosted.title || headingText,
2347
- caption: headingText
2348
- };
2349
- }
2350
- return placeholders ? { caption: headingText } : null;
2351
- }
2352
- return {
2353
- videoSrc: video.src,
2354
- posterSrc: video.posterSrc,
2355
- videoAlt: video.alt || headingText,
2356
- caption: headingText
2357
- };
2358
- }
2359
- case "leftFeature":
2360
- case "rightFeature": {
2361
- const img = extractFirstImage(contents);
2362
- if (!img && !placeholders) return null;
2363
- return {
2364
- imageSrc: img?.src ?? "",
2365
- imageAlt: img?.alt || headingText,
2366
- imageWidth: img?.width,
2367
- imageHeight: img?.height,
2368
- title: headingText,
2369
- body: bodyText3
2370
- };
2371
- }
2372
- case "diagram": {
2373
- const fences = (contents ?? []).filter((n) => n.type === "code");
2374
- const fence = fences.length === 1 && isEligibleAsciiFenceLang(fences[0].lang) ? fences[0] : void 0;
2375
- const detection = fence ? detectAsciiDiagram(fence.value, { explicit: isExplicitDiagramLang(fence.lang) }) : void 0;
2376
- if (!detection?.isDiagram || !detection.diagram) return placeholders ? {} : null;
2377
- const { nodes, edges } = asciiDiagramToTemplateData(detection.diagram);
2378
- return { nodes, edges, ...headingText ? { title: headingText } : {} };
2379
- }
2380
- case "timeline": {
2381
- const fences = (contents ?? []).filter((n) => n.type === "code");
2382
- const fence = fences.length === 1 && isEligibleAsciiTimelineFenceLang(fences[0].lang) ? fences[0] : void 0;
2383
- const detection = fence ? detectAsciiTimeline(fence.value, { explicit: true }) : void 0;
2384
- if (!detection?.isTimeline || !detection.timeline) return placeholders ? {} : null;
2385
- const { tracks, links } = asciiTimelineToTemplateData(detection.timeline);
2386
- return { tracks, links, ...headingText ? { title: headingText } : {} };
2387
- }
2388
- case "tree": {
2389
- const fences = (contents ?? []).filter((n) => n.type === "code");
2390
- const fence = fences.length === 1 && isEligibleTreeFenceLang(fences[0].lang) ? fences[0] : void 0;
2391
- if (fence) {
2392
- const detection = detectTree(fence.value, { explicit: isExplicitTreeLang(fence.lang) });
2393
- if (detection.isTree && detection.tree) {
2394
- const { items } = treeToTemplateData(detection.tree);
2395
- return { items, ...headingText ? { title: headingText } : {} };
2396
- }
2397
- }
2398
- const list = findFirstList(contents);
2399
- if (list) {
2400
- const { items } = treeToTemplateData(treeFromMarkdownList(list));
2401
- if (items.length > 0) return { items, ...headingText ? { title: headingText } : {} };
2402
- }
2403
- return placeholders ? {} : null;
2404
- }
2405
- default:
2406
- return placeholders ? {} : null;
2407
- }
2408
- }
2409
-
2410
1432
  // src/doc/templates/tokens/resolveTokens.ts
2411
1433
  var TOKEN_RE = /\{(\w+)(?::([^}|]+))?(?:\|([^}]*))?\}/g;
2412
1434
  var ESCAPE_RE = /\{\{([^{}]*)\}\}/g;
@@ -2446,7 +1468,7 @@ function collectListItems(block) {
2446
1468
  }
2447
1469
  function getBlockBodyText(block) {
2448
1470
  if (!block.contents) return "";
2449
- return block.contents.map((node) => extractPlainText(node).trim()).filter((s) => s.length > 0).join(" ");
1471
+ return block.contents.filter((node) => !isPictureFence(node)).map((node) => extractPlainText(node).trim()).filter((s) => s.length > 0).join(" ");
2450
1472
  }
2451
1473
  function getChildrenSummary(block) {
2452
1474
  if (!block.children || block.children.length === 0) return "";
@@ -7180,7 +6202,7 @@ function resolveSupplementalMediaLayout(layers, template, viewport, mediaCount,
7180
6202
  const policy = getBlockMediaLayoutPolicy(template);
7181
6203
  const shape = mediaShape(mediaCount, aspectRatios);
7182
6204
  const alreadyOwnsMedia = layers.some(
7183
- (layer) => layer.type === "image" || layer.type === "video" || layer.type === "videoEmbed" || layer.type === "map" || layer.type === "mermaid"
6205
+ (layer) => layer.type === "image" || layer.type === "video" || layer.type === "videoEmbed" || layer.type === "map" || layer.type === "mermaid" || layer.type === "svg"
7184
6206
  );
7185
6207
  if (!alreadyOwnsMedia && (policy?.nativeLayout === "feature-left" || policy?.nativeLayout === "feature-right")) {
7186
6208
  const { position } = featureMediaSlot(
@@ -7307,15 +6329,19 @@ function unconsumedMedia(block) {
7307
6329
  seen.add(image.src);
7308
6330
  return true;
7309
6331
  });
7310
- const mermaid = contents.filter(
7311
- (node) => node.type === "code" && node.lang?.trim().toLowerCase() === "mermaid" && node.value.trim().length > 0
6332
+ const pictures = contents.filter(
6333
+ (node) => node.type === "code" && isPictureFence(node) && node.value.trim().length > 0
7312
6334
  );
7313
6335
  const aspectRatios = [
7314
6336
  ...images.map(
7315
6337
  (image) => image.width && image.height ? image.width / image.height : void 0
7316
6338
  ),
7317
6339
  ...videos.map(() => 16 / 9),
7318
- ...mermaid.map(() => 16 / 9)
6340
+ // An SVG island knows its shape; a Mermaid diagram does not until drawn.
6341
+ ...pictures.map((fence) => {
6342
+ const island = isSvgIslandLang(fence.lang) ? parseSvgIsland(fence.value) : null;
6343
+ return island?.ok ? island.island.aspectRatio : 16 / 9;
6344
+ })
7319
6345
  ];
7320
6346
  return { count: aspectRatios.length, aspectRatios };
7321
6347
  }
@@ -7860,7 +6886,7 @@ function estimateTextWidth(text, fontSize, charWidthEm = AVERAGE_CHAR_WIDTH_EM)
7860
6886
  }
7861
6887
 
7862
6888
  // src/doc/templates/timelineBlock.ts
7863
- var clamp012 = (value) => Math.max(0, Math.min(1, value));
6889
+ var clamp01 = (value) => Math.max(0, Math.min(1, value));
7864
6890
  var clamp = (value, min, max) => Math.max(min, Math.min(max, value));
7865
6891
  function safeId(value) {
7866
6892
  return value.replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "item";
@@ -7887,7 +6913,7 @@ function coerceTracks(raw) {
7887
6913
  events.push({
7888
6914
  id: eventId,
7889
6915
  label: event.label,
7890
- position: clamp012(position),
6916
+ position: clamp01(position),
7891
6917
  ...typeof event.description === "string" && event.description.trim() ? { description: event.description.trim() } : {},
7892
6918
  ...side ? { side } : {},
7893
6919
  ...descriptionSide ? { descriptionSide } : {},
@@ -8033,7 +7059,7 @@ function timelineBlock(input, context) {
8033
7059
  const eventFontSize = Math.min(baseEventFontSize, 28 * minViewportScale);
8034
7060
  const descriptionFontSize = Math.min(baseDescriptionFontSize, 20 * minViewportScale);
8035
7061
  const trackMarkerRadii = tracks.map((track) => {
8036
- const positions = track.events.map((event) => clamp012(event.position)).sort((a, b) => a - b);
7062
+ const positions = track.events.map((event) => clamp01(event.position)).sort((a, b) => a - b);
8037
7063
  let minGapPx = Number.POSITIVE_INFINITY;
8038
7064
  for (let index = 1; index < positions.length; index++) {
8039
7065
  minGapPx = Math.min(minGapPx, (positions[index] - positions[index - 1]) * axisWidth);
@@ -8112,7 +7138,7 @@ function timelineBlock(input, context) {
8112
7138
  event,
8113
7139
  trackIndex,
8114
7140
  eventIndex,
8115
- x: axisStartX + axisWidth * clamp012(event.position),
7141
+ x: axisStartX + axisWidth * clamp01(event.position),
8116
7142
  y
8117
7143
  };
8118
7144
  eventPoints.push(point);
@@ -8467,7 +7493,7 @@ function normalizeShapeKind(name) {
8467
7493
  function isShapeName(name) {
8468
7494
  return normalizeShapeKind(name) != null;
8469
7495
  }
8470
- var DEFAULT_OPTS2 = {
7496
+ var DEFAULT_OPTS = {
8471
7497
  gapX: 180,
8472
7498
  gapY: 140,
8473
7499
  gridPad: 80,
@@ -8487,7 +7513,7 @@ function defaultSize(kind) {
8487
7513
  return DEFAULT_SIZE[kind] ?? FALLBACK_SIZE;
8488
7514
  }
8489
7515
  function computeDrawingLayout(children, options = {}) {
8490
- const opts = { ...DEFAULT_OPTS2, ...options };
7516
+ const opts = { ...DEFAULT_OPTS, ...options };
8491
7517
  const warnings = [];
8492
7518
  const entries = [];
8493
7519
  const unpositioned = [];
@@ -8664,7 +7690,7 @@ function connectorSemantics(params, kind) {
8664
7690
  }
8665
7691
  function bodyText(block) {
8666
7692
  if (!block.contents || block.contents.length === 0) return "";
8667
- return block.contents.map((node) => extractPlainText(node)).join(" ").trim();
7693
+ return block.contents.filter((node) => !isPictureFence(node)).map((node) => extractPlainText(node)).join(" ").trim();
8668
7694
  }
8669
7695
 
8670
7696
  // src/doc/templates/drawingBlock.ts
@@ -9047,7 +8073,7 @@ function numParam2(params, key) {
9047
8073
  }
9048
8074
  function bodyText2(block) {
9049
8075
  if (!block.contents || block.contents.length === 0) return "";
9050
- return block.contents.map((node) => extractPlainText(node)).join("\n").trim();
8076
+ return block.contents.filter((node) => !isPictureFence(node)).map((node) => extractPlainText(node)).join("\n").trim();
9051
8077
  }
9052
8078
  function round3(n) {
9053
8079
  return Math.round(n * 100) / 100;
@@ -10002,7 +9028,7 @@ function fallbackBlockLayers(block, context, notice) {
10002
9028
  }
10003
9029
  function extractBodyText(block) {
10004
9030
  if (!block.contents || block.contents.length === 0) return "";
10005
- const text = block.contents.map((node) => extractPlainText(node)).join("\n").trim();
9031
+ const text = block.contents.filter((node) => !isPictureFence(node)).map((node) => extractPlainText(node)).join("\n").trim();
10006
9032
  if (text.length <= MAX_BODY_CHARS) return text;
10007
9033
  return `${text.slice(0, MAX_BODY_CHARS).trimEnd()}\u2026`;
10008
9034
  }
@@ -10125,6 +9151,13 @@ function mermaidSources(block) {
10125
9151
  (node) => node.type === "code" && node.lang?.trim().toLowerCase() === "mermaid"
10126
9152
  ).map((node) => node.value).filter((source) => source.trim().length > 0);
10127
9153
  }
9154
+ function svgIslandSources(block) {
9155
+ return (block.contents ?? []).filter((node) => node.type === "code" && isSvgIslandLang(node.lang)).map((node) => node.value).filter((source) => source.trim().length > 0);
9156
+ }
9157
+ function svgIslandAspectRatio(source) {
9158
+ const result = parseSvgIsland(source);
9159
+ return result.ok ? result.island.aspectRatio : 16 / 9;
9160
+ }
10128
9161
  function embeddedSpatialMedia(block) {
10129
9162
  const canonicalTemplate = block.template ? resolveTemplateName(block.template) : void 0;
10130
9163
  const fences = (block.contents ?? []).filter(
@@ -10184,6 +9217,9 @@ function collectRichMediaItems(layers, block) {
10184
9217
  const existingMermaid = new Set(
10185
9218
  layers.filter((layer) => layer.type === "mermaid").map((layer) => layer.content.source)
10186
9219
  );
9220
+ const existingSvg = new Set(
9221
+ layers.filter((layer) => layer.type === "svg").map((layer) => layer.content.source)
9222
+ );
10187
9223
  const existingEmbeds = new Set(
10188
9224
  layers.filter((layer) => layer.type === "videoEmbed").map((layer) => parseVideoEmbedUrl(layer.content.url)?.watchUrl ?? layer.content.url)
10189
9225
  );
@@ -10197,6 +9233,7 @@ function collectRichMediaItems(layers, block) {
10197
9233
  return true;
10198
9234
  });
10199
9235
  const sources = mermaidSources(block).filter((source) => !existingMermaid.has(source));
9236
+ const islands = svgIslandSources(block).filter((source) => !existingSvg.has(source));
10200
9237
  const hosted = extractHostedVideos(block.contents).filter(
10201
9238
  (video) => !existingEmbeds.has(video.url)
10202
9239
  );
@@ -10224,6 +9261,11 @@ function collectRichMediaItems(layers, block) {
10224
9261
  aspectRatio: video.aspectRatio
10225
9262
  })),
10226
9263
  ...sources.map((source) => ({ kind: "mermaid", source, aspectRatio: 16 / 9 })),
9264
+ ...islands.map((source) => ({
9265
+ kind: "svg",
9266
+ source,
9267
+ aspectRatio: svgIslandAspectRatio(source)
9268
+ })),
10227
9269
  ...embeddedSpatialMedia(block)
10228
9270
  ];
10229
9271
  }
@@ -10320,6 +9362,16 @@ function richMediaLayers(item, position, block, theme, kindIndex) {
10320
9362
  }
10321
9363
  ];
10322
9364
  }
9365
+ if (item.kind === "svg") {
9366
+ return [
9367
+ {
9368
+ id: `${block.id}-svg-${kindIndex}`,
9369
+ type: "svg",
9370
+ position,
9371
+ content: { source: item.source, color: theme.colors.text }
9372
+ }
9373
+ ];
9374
+ }
10323
9375
  const localViewport = {
10324
9376
  width: position.width,
10325
9377
  height: position.height,
@@ -10391,6 +9443,7 @@ function appendRichContentLayers(layers, block, theme, viewport, mediaAspectRati
10391
9443
  video: 0,
10392
9444
  videoEmbed: 0,
10393
9445
  mermaid: 0,
9446
+ svg: 0,
10394
9447
  spatial: 0
10395
9448
  };
10396
9449
  const mediaLayers = items.flatMap((item, index) => {
@@ -12409,7 +11462,7 @@ function getBlockBodyText2(block) {
12409
11462
  if (!block.contents || block.contents.length === 0) return "";
12410
11463
  const tmpl = resolveTemplateName(block.template ?? "");
12411
11464
  const consumedFence = tmpl === "diagram" && block.templateData !== void 0 && "nodes" in block.templateData || tmpl === "timeline" && block.templateData !== void 0 && "tracks" in block.templateData || tmpl === "tree" && block.templateData !== void 0 && "items" in block.templateData;
12412
- return block.contents.filter((node) => !(consumedFence && node.type === "code")).filter((node) => findBlockVideoEmbed(node) === null).map((node) => extractPlainText(node)).join("\n").trim();
11465
+ return block.contents.filter((node) => !(consumedFence && node.type === "code")).filter((node) => !isMaterializedMediaNode(node)).map((node) => extractPlainText(node)).join("\n").trim();
12413
11466
  }
12414
11467
  var MAX_PHRASE_WORDS = 12;
12415
11468
  function splitIntoSentences(text) {
@@ -13006,34 +12059,6 @@ export {
13006
12059
  sectionHeader,
13007
12060
  fitBigTextSize,
13008
12061
  bigText,
13009
- computeDiagramLayout,
13010
- ASCII_CHAR_W,
13011
- ASCII_CHAR_H,
13012
- asciiCellToCanvas,
13013
- canvasToAsciiCell,
13014
- asciiDiagramToTemplateData,
13015
- asciiDiagramFromTemplateData,
13016
- asciiDiagramFromBlocks,
13017
- asciiTimelineToTemplateData,
13018
- treeToTemplateData,
13019
- treeFromTemplateData,
13020
- treeFromMarkdownList,
13021
- findFirstList,
13022
- extractHostedVideos,
13023
- isMaterializedMediaNode,
13024
- extractBodyPlainText,
13025
- extractListItems,
13026
- renderMarkdownBlocksHtml,
13027
- markdownBlockSeparatorLines,
13028
- extractRichListItems,
13029
- extractImages,
13030
- extractFirstImage,
13031
- extractEmbeddedVideos,
13032
- extractFirstEmbeddedVideo,
13033
- extractTableFromContents,
13034
- extractBlockquoteText,
13035
- autoTemplatePreservesContent,
13036
- deriveTemplateInputs,
13037
12062
  contentBlock,
13038
12063
  parseStatNumber,
13039
12064
  countUpSpecFor,