astro-tractstack 2.0.18 → 2.0.19

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.
@@ -38,6 +38,7 @@ import type {
38
38
  PaneNode,
39
39
  StoryFragmentNode,
40
40
  Tag,
41
+ TemplateGridLayout,
41
42
  TemplateMarkdown,
42
43
  TemplateNode,
43
44
  TemplatePane,
@@ -1445,124 +1446,126 @@ export class NodesContext {
1445
1446
  }
1446
1447
  break;
1447
1448
 
1448
- case 'TagElement':
1449
- {
1450
- const getButtonClasses = (node: FlatNode) => {
1451
- return {
1452
- mobile: strippedStyles(node.buttonPayload?.buttonClasses || {}),
1453
- tablet: {},
1454
- desktop: {},
1455
- };
1449
+ case 'TagElement': {
1450
+ const getButtonClasses = (node: FlatNode) => {
1451
+ return {
1452
+ mobile: strippedStyles(node.buttonPayload?.buttonClasses || {}),
1453
+ tablet: {},
1454
+ desktop: {},
1456
1455
  };
1456
+ };
1457
1457
 
1458
- const getHoverClasses = (node: FlatNode) => {
1459
- return {
1460
- mobile: strippedStyles(
1461
- node.buttonPayload?.buttonHoverClasses || {}
1462
- ),
1463
- tablet: {},
1464
- desktop: {},
1465
- };
1458
+ const getHoverClasses = (node: FlatNode) => {
1459
+ return {
1460
+ mobile: strippedStyles(
1461
+ node.buttonPayload?.buttonHoverClasses || {}
1462
+ ),
1463
+ tablet: {},
1464
+ desktop: {},
1466
1465
  };
1466
+ };
1467
1467
 
1468
- if (hasButtonPayload(node)) {
1469
- const [classesPayload] = processClassesForViewports(
1470
- getButtonClasses(node),
1471
- {},
1472
- 1
1473
- );
1474
- const [classesHoverPayload] = processClassesForViewports(
1475
- getHoverClasses(node),
1476
- {},
1477
- 1
1478
- );
1479
- return `${classesPayload?.length ? classesPayload[0] : ``} ${
1480
- classesHoverPayload?.length
1481
- ? addHoverPrefix(classesHoverPayload[0])
1482
- : ``
1483
- }`;
1484
- }
1468
+ if (hasButtonPayload(node)) {
1469
+ const [classesPayload] = processClassesForViewports(
1470
+ getButtonClasses(node),
1471
+ {},
1472
+ 1
1473
+ );
1474
+ const [classesHoverPayload] = processClassesForViewports(
1475
+ getHoverClasses(node),
1476
+ {},
1477
+ 1
1478
+ );
1479
+ return `${classesPayload?.length ? classesPayload[0] : ``} ${
1480
+ classesHoverPayload?.length
1481
+ ? addHoverPrefix(classesHoverPayload[0])
1482
+ : ``
1483
+ }`;
1484
+ }
1485
1485
 
1486
- if ('tagName' in node && node.tagName === 'span') {
1487
- const spanNode = node as FlatNode;
1488
- const [all, mobile, tablet, desktop] = processClassesForViewports(
1489
- { mobile: {}, tablet: {}, desktop: {} },
1490
- spanNode.overrideClasses || {},
1491
- 1
1492
- );
1493
- const outlineClass =
1494
- this.toolModeValStore.get().value === 'styles'
1495
- ? ' outline outline-1 outline-dotted outline-gray-400/60'
1496
- : '';
1486
+ if ('tagName' in node && node.tagName === 'span') {
1487
+ const spanNode = node as FlatNode;
1488
+ const [all, mobile, tablet, desktop] = processClassesForViewports(
1489
+ { mobile: {}, tablet: {}, desktop: {} },
1490
+ spanNode.overrideClasses || {},
1491
+ 1
1492
+ );
1493
+ const outlineClass =
1494
+ this.toolModeValStore.get().value === 'styles'
1495
+ ? ' outline outline-1 outline-dotted outline-gray-400/60'
1496
+ : '';
1497
1497
 
1498
- const getClassString = (classes: string[]): string =>
1499
- classes && classes.length > 0 ? classes[0] : '';
1498
+ const getClassString = (classes: string[]): string =>
1499
+ classes && classes.length > 0 ? classes[0] : '';
1500
1500
 
1501
- if (isPreview) return getClassString(desktop) + outlineClass;
1502
- switch (viewport) {
1503
- case 'desktop':
1504
- return getClassString(desktop) + outlineClass;
1505
- case 'tablet':
1506
- return getClassString(tablet) + outlineClass;
1507
- case 'mobile':
1508
- return getClassString(mobile) + outlineClass;
1509
- default:
1510
- return getClassString(all) + outlineClass;
1511
- }
1501
+ if (isPreview) return getClassString(desktop) + outlineClass;
1502
+ switch (viewport) {
1503
+ case 'desktop':
1504
+ return getClassString(desktop) + outlineClass;
1505
+ case 'tablet':
1506
+ return getClassString(tablet) + outlineClass;
1507
+ case 'mobile':
1508
+ return getClassString(mobile) + outlineClass;
1509
+ default:
1510
+ return getClassString(all) + outlineClass;
1512
1511
  }
1512
+ }
1513
1513
 
1514
- // Begin Default Class Lookup Logic
1515
- const markdownParentId = this.getClosestNodeTypeFromId(
1516
- nodeId,
1517
- 'Markdown'
1518
- );
1519
- if (!markdownParentId) break;
1514
+ // Begin Default Class Lookup Logic
1515
+ const markdownParentId = this.getClosestNodeTypeFromId(
1516
+ nodeId,
1517
+ 'Markdown'
1518
+ );
1519
+ if (!markdownParentId) break;
1520
1520
 
1521
- const markdownParentNode = this.allNodes
1522
- .get()
1523
- .get(markdownParentId) as MarkdownPaneFragmentNode;
1524
- if (!markdownParentNode) break;
1525
-
1526
- const tagNameStr = (node as FlatNode).tagName as string;
1527
-
1528
- // By default, assume the markdown node is the source of styles.
1529
- let styleSourceNode: MarkdownPaneFragmentNode | GridLayoutNode =
1530
- markdownParentNode;
1531
- let styles = styleSourceNode.defaultClasses?.[tagNameStr];
1532
-
1533
- // If the markdown node has no styles for this tag, check for a GridLayout grandparent.
1534
- // This handles the case where the MarkdownNode is a column.
1535
- if (!styles || Object.keys(styles.mobile).length === 0) {
1536
- const grandparent = markdownParentNode.parentId
1537
- ? this.allNodes.get().get(markdownParentNode.parentId)
1538
- : null;
1539
-
1540
- if (grandparent && isGridLayoutNode(grandparent)) {
1541
- styleSourceNode = grandparent;
1542
- styles = styleSourceNode.defaultClasses?.[tagNameStr];
1543
- }
1521
+ const markdownParentNode = this.allNodes
1522
+ .get()
1523
+ .get(markdownParentId) as MarkdownPaneFragmentNode;
1524
+ if (!markdownParentNode) break;
1525
+
1526
+ const tagNameStr = (node as FlatNode).tagName as string;
1527
+
1528
+ // By default, assume the markdown node is the source of styles.
1529
+ let styleSourceNode: MarkdownPaneFragmentNode | GridLayoutNode =
1530
+ markdownParentNode;
1531
+ let styles = styleSourceNode.defaultClasses?.[tagNameStr];
1532
+
1533
+ // If the markdown node has no styles for this tag, check for a GridLayout grandparent.
1534
+ // This handles the case where the MarkdownNode is a column.
1535
+ if (!styles || Object.keys(styles.mobile).length === 0) {
1536
+ const grandparent = markdownParentNode.parentId
1537
+ ? this.allNodes.get().get(markdownParentNode.parentId)
1538
+ : null;
1539
+
1540
+ if (grandparent && isGridLayoutNode(grandparent)) {
1541
+ styleSourceNode = grandparent;
1542
+ styles = styleSourceNode.defaultClasses?.[tagNameStr];
1544
1543
  }
1544
+ }
1545
1545
 
1546
- if (styles && styles.mobile) {
1547
- const [all, mobile, tablet, desktop] = processClassesForViewports(
1548
- styles,
1549
- (node as FlatNode)?.overrideClasses || {},
1550
- 1
1551
- );
1552
- if (isPreview) return desktop[0];
1553
- switch (viewport) {
1554
- case 'desktop':
1555
- return desktop[0];
1556
- case 'tablet':
1557
- return tablet[0];
1558
- case 'mobile':
1559
- return mobile[0];
1560
- default:
1561
- return all[0];
1562
- }
1563
- }
1546
+ const baseStyles =
1547
+ styles && styles.mobile
1548
+ ? styles
1549
+ : { mobile: {}, tablet: {}, desktop: {} };
1550
+
1551
+ const [all, mobile, tablet, desktop] = processClassesForViewports(
1552
+ baseStyles,
1553
+ (node as FlatNode)?.overrideClasses || {},
1554
+ 1
1555
+ );
1556
+
1557
+ if (isPreview) return desktop[0];
1558
+ switch (viewport) {
1559
+ case 'desktop':
1560
+ return desktop[0];
1561
+ case 'tablet':
1562
+ return tablet[0];
1563
+ case 'mobile':
1564
+ return mobile[0];
1565
+ default:
1566
+ return all[0];
1564
1567
  }
1565
- break;
1568
+ }
1566
1569
 
1567
1570
  case 'StoryFragment': {
1568
1571
  const storyFragment = node as StoryFragmentNode;
@@ -1775,39 +1778,15 @@ export class NodesContext {
1775
1778
  duplicatedPane.isChanged = true;
1776
1779
 
1777
1780
  // Track all nodes that need to be added
1778
- let allNodes: BaseNode[] = [];
1779
-
1780
- // must generate nodes from markdown
1781
- if (duplicatedPane.markdown) {
1782
- duplicatedPane.markdown = cloneDeep(pane.markdown) as TemplateMarkdown;
1783
- duplicatedPane.markdown.id = pane?.markdown?.id || ulid();
1784
- duplicatedPane.markdown.markdownId = pane?.markdown?.markdownId || ulid();
1785
- duplicatedPane.markdown.parentId = ownerId;
1786
-
1787
- let markdownNodes: TemplateNode[] = [];
1788
- if (duplicatedPane.markdown.markdownBody) {
1789
- const markdownGen = new MarkdownGenerator(this);
1790
- markdownNodes = markdownGen.markdownToFlatNodes(
1791
- duplicatedPane.markdown.markdownBody,
1792
- duplicatedPane.markdown.id
1793
- ) as TemplateNode[];
1794
- allNodes = [...allNodes, duplicatedPane.markdown, ...markdownNodes];
1795
- }
1781
+ // Call the new helper to process markdown, gridLayout, and bgPane
1782
+ const allNodes: BaseNode[] = this._processPaneTemplate(
1783
+ duplicatedPane,
1784
+ ownerId
1785
+ );
1796
1786
 
1797
- // Markdown already as nodes
1798
- else if (
1799
- typeof duplicatedPane.markdown !== `undefined` &&
1800
- typeof duplicatedPane.markdown.id === `string`
1801
- ) {
1802
- duplicatedPane?.markdown.nodes?.forEach((node) => {
1803
- const childrenNodes = this.setupTemplateNodeRecursively(
1804
- node,
1805
- duplicatedPane?.markdown?.id || ''
1806
- );
1807
- markdownNodes.push(...childrenNodes);
1808
- });
1809
- allNodes = [...allNodes, duplicatedPane.markdown, ...markdownNodes];
1810
- }
1787
+ // Remove bgPane from the pane object if it exists, as it's now a separate node
1788
+ if (duplicatedPane.bgPane) {
1789
+ delete duplicatedPane.bgPane;
1811
1790
  }
1812
1791
 
1813
1792
  this.addNode(duplicatedPane as PaneNode);
@@ -1852,85 +1831,15 @@ export class NodesContext {
1852
1831
  }
1853
1832
  }
1854
1833
 
1855
- let allNodes: BaseNode[] = [];
1856
-
1857
- if (duplicatedPane.markdown) {
1858
- duplicatedPane.markdown = cloneDeep(pane.markdown) as TemplateMarkdown;
1859
- duplicatedPane.markdown.id = pane?.markdown?.id || ulid();
1860
- duplicatedPane.markdown.markdownId = pane?.markdown?.markdownId || ulid();
1861
- duplicatedPane.markdown.parentId = duplicatedPaneId;
1862
-
1863
- let markdownNodes: TemplateNode[] = [];
1864
- if (duplicatedPane.markdown.markdownBody) {
1865
- const markdownGen = new MarkdownGenerator(this);
1866
- markdownNodes = markdownGen.markdownToFlatNodes(
1867
- duplicatedPane.markdown.markdownBody,
1868
- duplicatedPane.markdown.id
1869
- ) as TemplateNode[];
1870
- allNodes = [...allNodes, duplicatedPane.markdown, ...markdownNodes];
1871
- } else if (
1872
- typeof duplicatedPane.markdown !== `undefined` &&
1873
- typeof duplicatedPane.markdown.id === `string`
1874
- ) {
1875
- // Create a map to track the original node ID to its duplicated node ID
1876
- const oldToNewIdMap = new Map<string, string>();
1877
- // First pass: Clone nodes and generate new IDs
1878
- const nodesClone =
1879
- duplicatedPane?.markdown?.nodes?.map((originalNode) => {
1880
- const newNode = cloneDeep(originalNode);
1881
- newNode.id = ulid();
1882
- oldToNewIdMap.set(originalNode.id, newNode.id);
1883
- return newNode;
1884
- }) || [];
1885
- // Second pass: Update parent IDs using the mapping
1886
- nodesClone.forEach((node) => {
1887
- // Special case for direct children of markdown
1888
- if (node.parentId === pane?.markdown?.id) {
1889
- node.parentId = duplicatedPane?.markdown?.id || '';
1890
- } else {
1891
- // For all other nodes, use the mapping to find the new parent ID
1892
- const newParentId = oldToNewIdMap.get(node.parentId || '');
1893
- if (newParentId) {
1894
- node.parentId = newParentId;
1895
- }
1896
- }
1897
- markdownNodes.push(node);
1898
- });
1899
- allNodes = [...allNodes, duplicatedPane.markdown, ...markdownNodes];
1900
- }
1901
- }
1834
+ // Call the new helper to process markdown, gridLayout, and bgPane
1835
+ const allNodes: BaseNode[] = this._processPaneTemplate(
1836
+ duplicatedPane,
1837
+ duplicatedPaneId
1838
+ );
1902
1839
 
1840
+ // Remove bgPane from the pane object if it exists, as it's now a separate node
1841
+ // This preserves the original logic
1903
1842
  if (duplicatedPane.bgPane) {
1904
- const bgPaneId = ulid();
1905
-
1906
- if (duplicatedPane.bgPane.type === 'visual-break') {
1907
- const visualBreakPane = duplicatedPane.bgPane as VisualBreakNode;
1908
- const bgPaneNode: VisualBreakNode = {
1909
- id: bgPaneId,
1910
- nodeType: 'BgPane',
1911
- parentId: duplicatedPaneId,
1912
- type: 'visual-break',
1913
- breakDesktop: visualBreakPane.breakDesktop,
1914
- breakTablet: visualBreakPane.breakTablet,
1915
- breakMobile: visualBreakPane.breakMobile,
1916
- };
1917
- allNodes.push(bgPaneNode);
1918
- } else if (duplicatedPane.bgPane.type === 'artpack-image') {
1919
- const artpackBgPane = duplicatedPane.bgPane as ArtpackImageNode;
1920
- const bgPaneNode: ArtpackImageNode = {
1921
- id: bgPaneId,
1922
- nodeType: 'BgPane',
1923
- parentId: duplicatedPaneId,
1924
- type: 'artpack-image',
1925
- collection: artpackBgPane.collection,
1926
- image: artpackBgPane.image,
1927
- src: artpackBgPane.src,
1928
- srcSet: artpackBgPane.srcSet,
1929
- alt: artpackBgPane.alt || `Artpack image`,
1930
- objectFit: artpackBgPane.objectFit || 'cover',
1931
- };
1932
- allNodes.push(bgPaneNode);
1933
- }
1934
1843
  delete duplicatedPane.bgPane;
1935
1844
  }
1936
1845
 
@@ -1973,10 +1882,14 @@ export class NodesContext {
1973
1882
  this.addNodes(allNodes);
1974
1883
  this.notifyNode(ownerId);
1975
1884
 
1885
+ // Combine the pane and all its child nodes for the history patch
1886
+ const nodesToHistory = [duplicatedPane as BaseNode, ...allNodes];
1887
+
1976
1888
  this.history.addPatch({
1977
1889
  op: PatchOp.ADD,
1978
1890
  undo: (ctx) => {
1979
- ctx.deleteNodes(allNodes);
1891
+ // Delete all nodes created (pane + children)
1892
+ ctx.deleteNodes(nodesToHistory);
1980
1893
 
1981
1894
  if (
1982
1895
  storyFragmentNode &&
@@ -1988,8 +1901,6 @@ export class NodesContext {
1988
1901
  );
1989
1902
  storyFragmentNode.isChanged = storyFragmentWasChanged;
1990
1903
  }
1991
-
1992
- ctx.deleteNodes([duplicatedPane]);
1993
1904
  },
1994
1905
  redo: (ctx) => {
1995
1906
  if (storyFragmentNode?.nodeType === 'StoryFragment') {
@@ -2005,13 +1916,13 @@ export class NodesContext {
2005
1916
  storyFragmentNode.isChanged = true;
2006
1917
  }
2007
1918
 
2008
- ctx.addNodes([duplicatedPane]);
1919
+ // Add all nodes back (pane + children)
1920
+ ctx.addNodes(nodesToHistory);
2009
1921
  ctx.linkChildToParent(
2010
1922
  duplicatedPane.id,
2011
1923
  duplicatedPane.parentId,
2012
1924
  specificIdx
2013
1925
  );
2014
- ctx.addNodes(allNodes);
2015
1926
  },
2016
1927
  });
2017
1928
 
@@ -2275,7 +2186,9 @@ export class NodesContext {
2275
2186
 
2276
2187
  node.id = ulid();
2277
2188
  node.parentId = parentId;
2278
- result.push(node);
2189
+ const thisNode = cloneDeep(node);
2190
+ delete thisNode.nodes;
2191
+ result.push(thisNode);
2279
2192
  if ('nodes' in node && node.nodes) {
2280
2193
  for (let i = 0; i < node.nodes.length; ++i) {
2281
2194
  result = result.concat(
@@ -3224,6 +3137,160 @@ export class NodesContext {
3224
3137
 
3225
3138
  return deletedNodes;
3226
3139
  }
3140
+
3141
+ /**
3142
+ * Processes a TemplatePane's content (markdown, grid, or bgPane) and
3143
+ * returns a flat list of all nodes to be added to the store.
3144
+ * This is a de-duplicated helper used by addTemplatePane and addContextTemplatePane.
3145
+ * @param paneTemplate - The TemplatePane object to process.
3146
+ * @param newPaneId - The ID of the parent Pane node.
3147
+ * @returns An array of BaseNode objects to be added to allNodes.
3148
+ */
3149
+ private _processPaneTemplate(
3150
+ paneTemplate: TemplatePane,
3151
+ newPaneId: string
3152
+ ): BaseNode[] {
3153
+ let allNodes: BaseNode[] = [];
3154
+
3155
+ // 1. Process Markdown Content
3156
+ if (paneTemplate.markdown) {
3157
+ const duplicatedMarkdown = cloneDeep(
3158
+ paneTemplate.markdown
3159
+ ) as TemplateMarkdown;
3160
+ duplicatedMarkdown.id = paneTemplate.markdown.id || ulid();
3161
+ duplicatedMarkdown.markdownId =
3162
+ paneTemplate.markdown.markdownId || ulid();
3163
+ duplicatedMarkdown.parentId = newPaneId;
3164
+
3165
+ let markdownNodes: TemplateNode[] = [];
3166
+ if (duplicatedMarkdown.markdownBody) {
3167
+ const markdownGen = new MarkdownGenerator(this);
3168
+ markdownNodes = markdownGen.markdownToFlatNodes(
3169
+ duplicatedMarkdown.markdownBody,
3170
+ duplicatedMarkdown.id
3171
+ ) as TemplateNode[];
3172
+ allNodes = [...allNodes, duplicatedMarkdown, ...markdownNodes];
3173
+ } else if (
3174
+ typeof duplicatedMarkdown !== `undefined` &&
3175
+ typeof duplicatedMarkdown.id === `string`
3176
+ ) {
3177
+ // Create a map to track the original node ID to its duplicated node ID
3178
+ const oldToNewIdMap = new Map<string, string>();
3179
+ // First pass: Clone nodes and generate new IDs
3180
+ const nodesClone =
3181
+ duplicatedMarkdown.nodes?.map((originalNode) => {
3182
+ const newNode = cloneDeep(originalNode);
3183
+ newNode.id = ulid();
3184
+ oldToNewIdMap.set(originalNode.id, newNode.id);
3185
+ return newNode;
3186
+ }) || [];
3187
+ // Second pass: Update parent IDs using the mapping
3188
+ nodesClone.forEach((node) => {
3189
+ // Special case for direct children of markdown
3190
+ if (node.parentId === paneTemplate.markdown?.id) {
3191
+ node.parentId = duplicatedMarkdown.id;
3192
+ } else {
3193
+ // For all other nodes, use the mapping to find the new parent ID
3194
+ const newParentId = oldToNewIdMap.get(node.parentId || '');
3195
+ if (newParentId) {
3196
+ node.parentId = newParentId;
3197
+ }
3198
+ }
3199
+ markdownNodes.push(node);
3200
+ });
3201
+ allNodes = [...allNodes, duplicatedMarkdown, ...markdownNodes];
3202
+ }
3203
+
3204
+ // 2. Process GridLayout Content
3205
+ } else if (paneTemplate.gridLayout) {
3206
+ const duplicatedGrid = cloneDeep(
3207
+ paneTemplate.gridLayout
3208
+ ) as TemplateGridLayout;
3209
+ duplicatedGrid.id = paneTemplate.gridLayout.id || ulid();
3210
+ duplicatedGrid.parentId = newPaneId;
3211
+ allNodes.push(duplicatedGrid as GridLayoutNode);
3212
+
3213
+ // Map for all nodes within the grid
3214
+ const oldToNewIdMap = new Map<string, string>();
3215
+
3216
+ // First pass: Collect all column nodes and their descendant nodes
3217
+ const allOriginalNodes: TemplateNode[] = [];
3218
+ const columnNodes: TemplateMarkdown[] = [];
3219
+
3220
+ duplicatedGrid.nodes?.forEach((originalColumn) => {
3221
+ const newColumn = cloneDeep(originalColumn);
3222
+ newColumn.id = ulid();
3223
+ newColumn.markdownId = ulid();
3224
+ oldToNewIdMap.set(originalColumn.id, newColumn.id);
3225
+ columnNodes.push(newColumn);
3226
+
3227
+ originalColumn.nodes?.forEach((colNode) => {
3228
+ allOriginalNodes.push(colNode);
3229
+ });
3230
+ });
3231
+
3232
+ // Second pass: Clone all descendant nodes
3233
+ const allClonedDescendants = allOriginalNodes.map((originalNode) => {
3234
+ const newNode = cloneDeep(originalNode);
3235
+ newNode.id = ulid();
3236
+ oldToNewIdMap.set(originalNode.id, newNode.id);
3237
+ return newNode;
3238
+ });
3239
+
3240
+ // Third pass: Re-map parent IDs for columns
3241
+ columnNodes.forEach((col) => {
3242
+ col.parentId = duplicatedGrid.id;
3243
+ allNodes.push(col as MarkdownPaneFragmentNode);
3244
+ });
3245
+
3246
+ // Fourth pass: Re-map parent IDs for all descendants
3247
+ allClonedDescendants.forEach((node) => {
3248
+ const newParentId = oldToNewIdMap.get(node.parentId || '');
3249
+ if (newParentId) {
3250
+ node.parentId = newParentId;
3251
+ }
3252
+ allNodes.push(node);
3253
+ });
3254
+ }
3255
+
3256
+ // 3. Process Background Pane
3257
+ if (paneTemplate.bgPane) {
3258
+ const bgPaneId = ulid();
3259
+
3260
+ if (paneTemplate.bgPane.type === 'visual-break') {
3261
+ const visualBreakPane = paneTemplate.bgPane as VisualBreakNode;
3262
+ const bgPaneNode: VisualBreakNode = {
3263
+ id: bgPaneId,
3264
+ nodeType: 'BgPane',
3265
+ parentId: newPaneId,
3266
+ type: 'visual-break',
3267
+ breakDesktop: visualBreakPane.breakDesktop,
3268
+ breakTablet: visualBreakPane.breakTablet,
3269
+ breakMobile: visualBreakPane.breakMobile,
3270
+ };
3271
+ allNodes.push(bgPaneNode);
3272
+ } else if (paneTemplate.bgPane.type === 'artpack-image') {
3273
+ const artpackBgPane = paneTemplate.bgPane as ArtpackImageNode;
3274
+ const bgPaneNode: ArtpackImageNode = {
3275
+ id: bgPaneId,
3276
+ nodeType: 'BgPane',
3277
+ parentId: newPaneId,
3278
+ type: 'artpack-image',
3279
+ collection: artpackBgPane.collection,
3280
+ image: artpackBgPane.image,
3281
+ src: artpackBgPane.src,
3282
+ srcSet: artpackBgPane.srcSet,
3283
+ alt: artpackBgPane.alt || `Artpack image`,
3284
+ objectFit: artpackBgPane.objectFit || 'cover',
3285
+ };
3286
+ allNodes.push(bgPaneNode);
3287
+ }
3288
+ // This helper only processes nodes, it doesn't modify the paneTemplate.
3289
+ // The deletion of `duplicatedPane.bgPane` will remain in `addTemplatePane`.
3290
+ }
3291
+
3292
+ return allNodes;
3293
+ }
3227
3294
  }
3228
3295
 
3229
3296
  export const globalCtx: NodesContext = new NodesContext();
@@ -424,10 +424,15 @@ export type TemplateMarkdown = MarkdownPaneFragmentNode & {
424
424
  markdownBody?: string;
425
425
  };
426
426
 
427
+ export type TemplateGridLayout = GridLayoutNode & {
428
+ nodes?: TemplateMarkdown[];
429
+ };
430
+
427
431
  export type TemplatePane = PaneNode & {
428
432
  id?: string;
429
433
  parentId?: string;
430
434
  markdown?: TemplateMarkdown;
435
+ gridLayout?: TemplateGridLayout;
431
436
  bgPane?: VisualBreakNode | ArtpackImageNode | BgImageNode;
432
437
  };
433
438
 
@@ -463,6 +468,18 @@ export type StorageMarkdown = Omit<
463
468
  nodes?: StorageNode[];
464
469
  };
465
470
 
471
+ export type StorageGridLayoutNode = {
472
+ nodeType: string;
473
+ type: string;
474
+ defaultClasses?: Record<string, any>;
475
+ parentClasses?: Record<string, any>[];
476
+ gridColumns: {
477
+ mobile: number;
478
+ tablet: number;
479
+ desktop: number;
480
+ };
481
+ };
482
+
466
483
  export type StoragePane = Omit<
467
484
  PaneNode,
468
485
  | 'id'
@@ -476,7 +493,8 @@ export type StoragePane = Omit<
476
493
  | 'codeHookPayload'
477
494
  | 'markdown'
478
495
  > & {
479
- markdown?: StorageMarkdown;
496
+ markdowns?: StorageMarkdown[];
497
+ gridLayout?: StorageGridLayoutNode;
480
498
  bgPane?: StorageBgPane;
481
499
  };
482
500
 
@@ -3,6 +3,7 @@ import type { StoragePane } from './compositorTypes';
3
3
  export type DesignLibraryEntry = {
4
4
  category: string;
5
5
  title: string;
6
+ markdownCount: number;
6
7
  template: StoragePane;
7
8
  };
8
9