astro-tractstack 2.0.16 → 2.0.18

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 (65) hide show
  1. package/dist/index.js +24 -0
  2. package/package.json +1 -1
  3. package/templates/custom/with-examples/SandboxLauncher.tsx +11 -9
  4. package/templates/src/components/codehooks/FeaturedArticleSetup.tsx +1 -1
  5. package/templates/src/components/codehooks/ListContentSetup.tsx +1 -1
  6. package/templates/src/components/compositor/Compositor.tsx +1 -0
  7. package/templates/src/components/compositor/Node.tsx +41 -17
  8. package/templates/src/components/compositor/nodes/GhostInsertBlock.tsx +9 -6
  9. package/templates/src/components/compositor/nodes/GridLayout.tsx +124 -0
  10. package/templates/src/components/compositor/nodes/GridLayout_eraser.tsx +33 -0
  11. package/templates/src/components/compositor/nodes/Markdown.tsx +67 -37
  12. package/templates/src/components/compositor/nodes/Markdown_eraser.tsx +56 -0
  13. package/templates/src/components/compositor/nodes/Pane_DesignLibrary.tsx +1 -1
  14. package/templates/src/components/compositor/preview/FeaturedArticlePreview.tsx +8 -2
  15. package/templates/src/components/edit/PanelSwitch.tsx +232 -75
  16. package/templates/src/components/edit/SettingsPanel.tsx +0 -1
  17. package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +3 -3
  18. package/templates/src/components/edit/pane/AddPanePanel_new.tsx +184 -151
  19. package/templates/src/components/edit/pane/AddPanePanel_reuse.tsx +2 -2
  20. package/templates/src/components/edit/pane/ConfigPanePanel.tsx +1 -7
  21. package/templates/src/components/edit/pane/PanePanel_impression.tsx +1 -1
  22. package/templates/src/components/edit/pane/RestylePaneModal.tsx +8 -5
  23. package/templates/src/components/edit/pane/steps/AiDesignStep.tsx +6 -6
  24. package/templates/src/components/edit/pane/steps/CopyInputStep.tsx +3 -3
  25. package/templates/src/components/edit/pane/steps/DesignLibraryStep.tsx +4 -4
  26. package/templates/src/components/edit/pane/steps/DirectInjectStep.tsx +96 -0
  27. package/templates/src/components/edit/panels/StyleElementPanel.tsx +11 -4
  28. package/templates/src/components/edit/panels/StyleElementPanel_add.tsx +8 -8
  29. package/templates/src/components/edit/panels/StyleElementPanel_remove.tsx +14 -4
  30. package/templates/src/components/edit/panels/StyleElementPanel_update.tsx +16 -4
  31. package/templates/src/components/edit/panels/StyleImagePanel.tsx +8 -3
  32. package/templates/src/components/edit/panels/StyleImagePanel_add.tsx +9 -2
  33. package/templates/src/components/edit/panels/StyleImagePanel_remove.tsx +5 -2
  34. package/templates/src/components/edit/panels/StyleImagePanel_update.tsx +5 -2
  35. package/templates/src/components/edit/panels/StyleLiElementPanel.tsx +7 -3
  36. package/templates/src/components/edit/panels/StyleLiElementPanel_add.tsx +9 -2
  37. package/templates/src/components/edit/panels/StyleLiElementPanel_remove.tsx +5 -2
  38. package/templates/src/components/edit/panels/StyleLiElementPanel_update.tsx +5 -2
  39. package/templates/src/components/edit/panels/StyleParentPanel.tsx +530 -171
  40. package/templates/src/components/edit/panels/StyleParentPanel_add.tsx +77 -42
  41. package/templates/src/components/edit/panels/StyleParentPanel_deleteLayer.tsx +38 -22
  42. package/templates/src/components/edit/panels/StyleParentPanel_remove.tsx +171 -66
  43. package/templates/src/components/edit/panels/StyleParentPanel_update.tsx +166 -98
  44. package/templates/src/components/edit/panels/StyleWidgetPanel.tsx +7 -3
  45. package/templates/src/components/edit/panels/StyleWidgetPanel_add.tsx +9 -2
  46. package/templates/src/components/edit/panels/StyleWidgetPanel_remove.tsx +5 -2
  47. package/templates/src/components/edit/panels/StyleWidgetPanel_update.tsx +6 -2
  48. package/templates/src/components/edit/state/SaveModal.tsx +10 -2
  49. package/templates/src/components/edit/state/SaveToLibraryModal.tsx +6 -6
  50. package/templates/src/components/fields/PaneBreakShapeSelector.tsx +1 -1
  51. package/templates/src/components/widgets/ImpressionWrapper.tsx +4 -1
  52. package/templates/src/constants/prompts.json +1 -1
  53. package/templates/src/constants.ts +1 -0
  54. package/templates/src/stores/nodes.ts +110 -33
  55. package/templates/src/stores/storykeep.ts +3 -1
  56. package/templates/src/types/compositorTypes.ts +37 -2
  57. package/templates/src/utils/compositor/TemplateNodes.ts +8 -0
  58. package/templates/src/utils/compositor/aiPaneParser.ts +8 -2
  59. package/templates/src/utils/compositor/nodesHelper.ts +229 -0
  60. package/templates/src/utils/compositor/reduceNodesClassNames.ts +40 -1
  61. package/templates/src/utils/compositor/typeGuards.ts +7 -0
  62. package/templates/src/utils/etl/extractor.ts +1 -5
  63. package/templates/src/utils/etl/index.ts +1 -0
  64. package/templates/src/utils/etl/transformer.ts +70 -25
  65. package/utils/inject-files.ts +24 -0
@@ -15,6 +15,7 @@ import {
15
15
  hasTagName,
16
16
  isDefined,
17
17
  isValidTag,
18
+ isGridLayoutNode,
18
19
  toTag,
19
20
  } from '@/utils/compositor/typeGuards';
20
21
  import { startLoadingAnimation } from '@/utils/helpers';
@@ -29,6 +30,7 @@ import type {
29
30
  BaseNode,
30
31
  FlatNode,
31
32
  ImpressionNode,
33
+ GridLayoutNode,
32
34
  MarkdownPaneFragmentNode,
33
35
  MenuNode,
34
36
  NodeType,
@@ -1390,6 +1392,30 @@ export class NodesContext {
1390
1392
  if (!node) return '';
1391
1393
 
1392
1394
  switch (node.nodeType) {
1395
+ case 'GridLayoutNode': {
1396
+ const gridNode = node as GridLayoutNode;
1397
+ if (gridNode.parentClasses) {
1398
+ const [all, mobile, tablet, desktop] = processClassesForViewports(
1399
+ gridNode.parentClasses[depth],
1400
+ {}, // No override classes for GridLayout parent case
1401
+ 1
1402
+ );
1403
+
1404
+ if (isPreview) return desktop[0];
1405
+ switch (viewport) {
1406
+ case 'desktop':
1407
+ return desktop[0];
1408
+ case 'tablet':
1409
+ return tablet[0];
1410
+ case 'mobile':
1411
+ return mobile[0];
1412
+ default:
1413
+ return all[0];
1414
+ }
1415
+ }
1416
+ break;
1417
+ }
1418
+
1393
1419
  case 'Markdown':
1394
1420
  {
1395
1421
  const markdownFragment = node as MarkdownPaneFragmentNode;
@@ -1485,33 +1511,54 @@ export class NodesContext {
1485
1511
  }
1486
1512
  }
1487
1513
 
1488
- const closestPaneId = this.getClosestNodeTypeFromId(
1514
+ // Begin Default Class Lookup Logic
1515
+ const markdownParentId = this.getClosestNodeTypeFromId(
1489
1516
  nodeId,
1490
1517
  'Markdown'
1491
1518
  );
1492
- const paneNode = this.allNodes
1519
+ if (!markdownParentId) break;
1520
+
1521
+ const markdownParentNode = this.allNodes
1493
1522
  .get()
1494
- .get(closestPaneId) as MarkdownPaneFragmentNode;
1495
- if (paneNode && 'tagName' in node) {
1496
- const tagNameStr = node.tagName as string;
1497
- const styles = paneNode.defaultClasses![tagNameStr];
1498
- if (styles && styles.mobile) {
1499
- const [all, mobile, tablet, desktop] = processClassesForViewports(
1500
- styles,
1501
- (node as FlatNode)?.overrideClasses || {},
1502
- 1
1503
- );
1504
- if (isPreview) return desktop[0];
1505
- switch (viewport) {
1506
- case 'desktop':
1507
- return desktop[0];
1508
- case 'tablet':
1509
- return tablet[0];
1510
- case 'mobile':
1511
- return mobile[0];
1512
- default:
1513
- return all[0];
1514
- }
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
+ }
1544
+ }
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];
1515
1562
  }
1516
1563
  }
1517
1564
  }
@@ -1584,19 +1631,44 @@ export class NodesContext {
1584
1631
  }
1585
1632
 
1586
1633
  switch (node.nodeType) {
1587
- case `TagElement`:
1588
- case `BgPane`:
1589
- case `Markdown`: {
1634
+ case 'GridLayoutNode':
1635
+ case 'TagElement':
1636
+ case 'BgPane':
1637
+ case 'Markdown': {
1638
+ const nodesToDirty: BaseNode[] = [];
1590
1639
  const paneNodeId = this.getClosestNodeTypeFromId(node.id, 'Pane');
1591
- const paneNode = cloneDeep(
1592
- this.allNodes.get().get(paneNodeId)
1593
- ) as PaneNode;
1594
- this.modifyNodes([{ ...paneNode, isChanged: true }], {
1595
- notify: false,
1596
- });
1640
+
1641
+ if (paneNodeId) {
1642
+ const paneNode = this.allNodes.get().get(paneNodeId);
1643
+ if (paneNode && !paneNode.isChanged) {
1644
+ nodesToDirty.push({ ...paneNode, isChanged: true });
1645
+ }
1646
+ }
1647
+
1648
+ if (node.parentId) {
1649
+ const parentNode = this.allNodes.get().get(node.parentId);
1650
+ if (
1651
+ parentNode &&
1652
+ parentNode.nodeType === 'GridLayoutNode' &&
1653
+ !parentNode.isChanged
1654
+ ) {
1655
+ if (!nodesToDirty.some((n) => n.id === parentNode.id)) {
1656
+ nodesToDirty.push({ ...parentNode, isChanged: true });
1657
+ }
1658
+ }
1659
+ }
1660
+
1661
+ if (nodesToDirty.length > 0) {
1662
+ this.modifyNodes(nodesToDirty, {
1663
+ notify: false,
1664
+ recordHistory: false,
1665
+ });
1666
+ }
1667
+
1597
1668
  this.notifyNode(ROOT_NODE_NAME);
1598
1669
  break;
1599
1670
  }
1671
+
1600
1672
  case `Menu`:
1601
1673
  case `Pane`:
1602
1674
  case `StoryFragment`:
@@ -1970,7 +2042,9 @@ export class NodesContext {
1970
2042
  });
1971
2043
  break;
1972
2044
  }
1973
- this.toolModeValStore.set({ value: 'styles' });
2045
+ if ([`p`, `h2`, `h3`, `h4`, `li`].includes(tagName))
2046
+ this.toolModeValStore.set({ value: 'text' });
2047
+ else this.toolModeValStore.set({ value: 'styles' });
1974
2048
  this.notifyNode('root');
1975
2049
  }
1976
2050
 
@@ -2024,6 +2098,7 @@ export class NodesContext {
2024
2098
 
2025
2099
  let autoCreatedMarkdownNode: MarkdownPaneFragmentNode | null = null;
2026
2100
 
2101
+ console.log(`--- [TRAP - TEMPLATE BEFORE] ---`, cloneDeep(node));
2027
2102
  // 3. HANDLE EMPTY PANE BY AUTO-CREATING A MARKDOWN NODE
2028
2103
  if (targetNode.nodeType === 'Pane') {
2029
2104
  // Create a minimal markdown node to act as the container
@@ -2101,6 +2176,8 @@ export class NodesContext {
2101
2176
  );
2102
2177
  }
2103
2178
 
2179
+ console.log(`--- [TRAP - FLATTENED AFTER] ---`, cloneDeep(flattenedNodes));
2180
+
2104
2181
  // 5. PERFORM REMAINING STATE MUTATIONS
2105
2182
  if (originalPaneNode) {
2106
2183
  this.modifyNodes([{ ...originalPaneNode, isChanged: true }], {
@@ -157,7 +157,9 @@ export const resetStoryKeepState = () => {
157
157
  };
158
158
 
159
159
  export const settingsPanelStore = atom<SettingsPanelSignal | null>(null);
160
-
160
+ export const stylePanelTargetMemoryStore = atom<Map<string, number>>(
161
+ new Map<string, number>()
162
+ );
161
163
  export const styleElementInfoStore = map<{
162
164
  markdownParentId: string | null;
163
165
  tagName: string | null;
@@ -76,6 +76,7 @@ export type SettingsPanelSignal = {
76
76
  minimized?: boolean;
77
77
  expanded?: boolean;
78
78
  editLock?: number;
79
+ targetProperty?: 'parentClasses' | 'gridClasses';
79
80
  };
80
81
 
81
82
  export interface OgImageParams {
@@ -173,6 +174,7 @@ export type NodeType =
173
174
  | 'Pane'
174
175
  | 'StoryFragment'
175
176
  | 'BgPane'
177
+ | 'GridLayoutNode'
176
178
  | 'Markdown'
177
179
  | 'TagElement'
178
180
  | 'TractStack'
@@ -253,7 +255,12 @@ export interface TractStackNode extends BaseNode {
253
255
  }
254
256
 
255
257
  export interface PaneFragmentNode extends BaseNode {
256
- type: 'markdown' | 'visual-break' | 'background-image' | 'artpack-image';
258
+ type:
259
+ | 'markdown'
260
+ | 'visual-break'
261
+ | 'background-image'
262
+ | 'artpack-image'
263
+ | 'grid-layout';
257
264
  hiddenViewportMobile?: boolean;
258
265
  hiddenViewportTablet?: boolean;
259
266
  hiddenViewportDesktop?: boolean;
@@ -272,6 +279,22 @@ export interface MarkdownPaneFragmentNode extends PaneFragmentNode {
272
279
  >;
273
280
  parentClasses?: ParentClassesPayload;
274
281
  parentCss?: string[];
282
+ gridClasses?: DefaultClassValue;
283
+ }
284
+
285
+ export interface GridLayoutNode extends PaneFragmentNode {
286
+ nodeType: 'GridLayoutNode';
287
+ type: 'grid-layout';
288
+ parentClasses?: ParentClassesPayload;
289
+ defaultClasses?: Record<
290
+ string,
291
+ {
292
+ mobile: Record<string, string>;
293
+ tablet: Record<string, string>;
294
+ desktop: Record<string, string>;
295
+ }
296
+ >;
297
+ gridColumns: { mobile: number; tablet: number; desktop: number };
275
298
  }
276
299
 
277
300
  export interface ArtpackImageNode extends PaneFragmentNode {
@@ -481,7 +504,7 @@ export type LoadData = {
481
504
  paneNodes?: PaneNode[];
482
505
  tractstackNodes?: TractStackNode[];
483
506
  childNodes?: (BaseNode | FlatNode)[];
484
- paneFragmentNodes?: PaneFragmentNode[];
507
+ paneFragmentNodes?: (PaneFragmentNode | GridLayoutNode)[];
485
508
  flatNodes?: FlatNode[];
486
509
  impressionNodes?: ImpressionNode[];
487
510
  beliefNodes?: BeliefNode[];
@@ -511,6 +534,18 @@ export interface BasePanelProps {
511
534
  onTitleChange?: (title: string) => void;
512
535
  }
513
536
 
537
+ export type ParentBasePanelProps = {
538
+ node: MarkdownPaneFragmentNode | GridLayoutNode | null;
539
+ parentNode?: FlatNode | PaneNode;
540
+ config?: BrandConfig | null;
541
+ layer?: number;
542
+ className?: string;
543
+ childId?: string;
544
+ availableCodeHooks?: string[];
545
+ onTitleChange?: (title: string) => void;
546
+ targetProperty?: 'parentClasses' | 'gridClasses';
547
+ };
548
+
514
549
  interface WidgetParameterDefinition {
515
550
  label: string;
516
551
  defaultValue: string;
@@ -7,6 +7,7 @@ export const TemplateH2Node = {
7
7
  {
8
8
  copy: 'Catchy title',
9
9
  tagName: 'text',
10
+ nodeType: 'TagElement',
10
11
  },
11
12
  ],
12
13
  } as TemplateNode;
@@ -18,6 +19,7 @@ export const TemplateH3Node = {
18
19
  {
19
20
  copy: 'Catchy sub-title',
20
21
  tagName: 'text',
22
+ nodeType: 'TagElement',
21
23
  },
22
24
  ],
23
25
  } as TemplateNode;
@@ -29,6 +31,7 @@ export const TemplateH4Node = {
29
31
  {
30
32
  copy: 'Catchy sub-title',
31
33
  tagName: 'text',
34
+ nodeType: 'TagElement',
32
35
  },
33
36
  ],
34
37
  } as TemplateNode;
@@ -40,6 +43,7 @@ export const TemplatePNode = {
40
43
  {
41
44
  copy: '...',
42
45
  tagName: 'text',
46
+ nodeType: 'TagElement',
43
47
  },
44
48
  ],
45
49
  } as TemplateNode;
@@ -51,6 +55,7 @@ export const TemplateOLNode = {
51
55
  {
52
56
  copy: '...',
53
57
  tagName: 'text',
58
+ nodeType: 'TagElement',
54
59
  },
55
60
  ],
56
61
  } as TemplateNode;
@@ -62,6 +67,7 @@ export const TemplateULNode = {
62
67
  {
63
68
  copy: '...',
64
69
  tagName: 'text',
70
+ nodeType: 'TagElement',
65
71
  },
66
72
  ],
67
73
  } as TemplateNode;
@@ -73,6 +79,7 @@ export const TemplateLINode = {
73
79
  {
74
80
  copy: '...',
75
81
  tagName: 'text',
82
+ nodeType: 'TagElement',
76
83
  },
77
84
  ],
78
85
  } as TemplateNode;
@@ -84,6 +91,7 @@ export const TemplateLINode = {
84
91
  // {
85
92
  // copy: "aside node",
86
93
  // tagName: "text",
94
+ //nodeType: 'TagElement',
87
95
  // },
88
96
  // ],
89
97
  //} as TemplateNode;
@@ -60,6 +60,8 @@ let BUTTON_CLASS_LOOKUP: Map<string, { key: string; value: string }> | null =
60
60
  null;
61
61
 
62
62
  const ALLOWED_TAGS = new Set([
63
+ 'ul',
64
+ 'li',
63
65
  'h2',
64
66
  'h3',
65
67
  'h4',
@@ -69,6 +71,7 @@ const ALLOWED_TAGS = new Set([
69
71
  'em',
70
72
  'strong',
71
73
  'button',
74
+ 'a',
72
75
  ]);
73
76
 
74
77
  function buildKeyNormalizationLookup(): Map<string, string> {
@@ -309,7 +312,10 @@ function walkDom(
309
312
  if (tagName === 'button') {
310
313
  let finalParentId = parentId;
311
314
 
312
- if (parentId === markdownId) {
315
+ const parentDomEl = el.parentNode as Element;
316
+ const parentTagName = parentDomEl?.tagName?.toLowerCase();
317
+
318
+ if (parentId === markdownId || parentTagName === 'li') {
313
319
  const pNodeId = ulid();
314
320
  const pNode: TemplateNode = {
315
321
  id: pNodeId,
@@ -330,7 +336,7 @@ function walkDom(
330
336
  id: ulid(),
331
337
  nodeType: 'TagElement',
332
338
  parentId: finalParentId,
333
- tagName: 'a', // Buttons are converted to anchor tags for our system
339
+ tagName: 'a',
334
340
  href: '#',
335
341
  buttonPayload: {
336
342
  ...buttonPayload,
@@ -14,13 +14,19 @@ import {
14
14
  TemplateYoutubeNode,
15
15
  } from './TemplateNodes';
16
16
  import { getCtx, NodesContext } from '@/stores/nodes';
17
+ import { settingsPanelStore } from '@/stores/storykeep';
18
+ import { PatchOp } from '@/stores/nodesHistory';
17
19
  import { cloneDeep } from '@/utils/helpers';
20
+ import { isPaneNode } from './typeGuards';
18
21
  import type {
19
22
  BaseNode,
20
23
  FlatNode,
21
24
  StoryFragmentNode,
22
25
  TemplateNode,
23
26
  ToolAddMode,
27
+ MarkdownPaneFragmentNode,
28
+ GridLayoutNode,
29
+ PaneNode,
24
30
  } from '@/types/compositorTypes';
25
31
  import type { NodeTagProps } from '@/types/nodeProps';
26
32
 
@@ -512,3 +518,226 @@ export function extractClassesFromNodes(dirtyNodes: BaseNode[]): string[] {
512
518
 
513
519
  return Array.from(uniqueClasses);
514
520
  }
521
+
522
+ export function revertFromGrid(gridLayoutId: string) {
523
+ const ctx = getCtx();
524
+ const originalAllNodes = new Map(ctx.allNodes.get());
525
+ const originalParentNodes = new Map(ctx.parentNodes.get());
526
+
527
+ const gridLayoutNode = originalAllNodes.get(gridLayoutId) as GridLayoutNode;
528
+ if (!gridLayoutNode || !gridLayoutNode.parentId) return;
529
+
530
+ const paneNode = originalAllNodes.get(gridLayoutNode.parentId) as PaneNode;
531
+ if (!paneNode || !isPaneNode(paneNode)) return;
532
+
533
+ const childIds = originalParentNodes.get(gridLayoutId) || [];
534
+
535
+ const redoLogic = () => {
536
+ const newAllNodes = new Map(originalAllNodes);
537
+ const newParentNodes = new Map(originalParentNodes);
538
+
539
+ if (childIds.length === 0) {
540
+ const paneChildren = [...(newParentNodes.get(paneNode.id) || [])];
541
+ const gridIndex = paneChildren.indexOf(gridLayoutId);
542
+ if (gridIndex > -1) {
543
+ paneChildren.splice(gridIndex, 1);
544
+ }
545
+ newParentNodes.set(paneNode.id, paneChildren);
546
+ newAllNodes.delete(gridLayoutId);
547
+ newParentNodes.delete(gridLayoutId);
548
+ } else {
549
+ const markdownNodeToKeepId = childIds[0];
550
+ const markdownNodeToKeep = cloneDeep(
551
+ newAllNodes.get(markdownNodeToKeepId)
552
+ ) as MarkdownPaneFragmentNode;
553
+
554
+ markdownNodeToKeep.parentId = paneNode.id;
555
+ markdownNodeToKeep.parentClasses = gridLayoutNode.parentClasses || [];
556
+ markdownNodeToKeep.defaultClasses = gridLayoutNode.defaultClasses || {};
557
+ markdownNodeToKeep.isChanged = true;
558
+ newAllNodes.set(markdownNodeToKeepId, markdownNodeToKeep);
559
+
560
+ const paneChildren = [...(newParentNodes.get(paneNode.id) || [])];
561
+ const gridIndex = paneChildren.indexOf(gridLayoutId);
562
+ if (gridIndex > -1) {
563
+ paneChildren.splice(gridIndex, 1, markdownNodeToKeepId);
564
+ }
565
+ newParentNodes.set(paneNode.id, paneChildren);
566
+
567
+ const nodesToDeleteIds = [gridLayoutId, ...childIds.slice(1)];
568
+ nodesToDeleteIds.forEach((id) => {
569
+ newAllNodes.delete(id);
570
+ newParentNodes.delete(id);
571
+ });
572
+ }
573
+
574
+ const updatedPaneNode = cloneDeep(paneNode);
575
+ updatedPaneNode.isChanged = true;
576
+ newAllNodes.set(paneNode.id, updatedPaneNode);
577
+
578
+ ctx.allNodes.set(newAllNodes);
579
+ ctx.parentNodes.set(newParentNodes);
580
+ ctx.notifyNode('root');
581
+ settingsPanelStore.set(null);
582
+ };
583
+
584
+ const undoLogic = () => {
585
+ ctx.allNodes.set(originalAllNodes);
586
+ ctx.parentNodes.set(originalParentNodes);
587
+ ctx.notifyNode('root');
588
+ };
589
+
590
+ ctx.history.addPatch({
591
+ op: PatchOp.REPLACE,
592
+ undo: undoLogic,
593
+ redo: redoLogic,
594
+ });
595
+
596
+ redoLogic();
597
+ }
598
+
599
+ export function convertToGrid(markdownNodeId: string) {
600
+ const ctx = getCtx();
601
+
602
+ const originalAllNodes = new Map(ctx.allNodes.get());
603
+ const originalParentNodes = new Map(ctx.parentNodes.get());
604
+
605
+ const markdownNode = originalAllNodes.get(
606
+ markdownNodeId
607
+ ) as MarkdownPaneFragmentNode;
608
+ if (!markdownNode || !markdownNode.parentId) return;
609
+
610
+ const paneNode = originalAllNodes.get(markdownNode.parentId) as PaneNode & {
611
+ markdownId?: string;
612
+ markdownBody?: string;
613
+ };
614
+ if (!paneNode || !isPaneNode(paneNode)) return;
615
+
616
+ const gridLayoutId = ulid();
617
+
618
+ const redoLogic = () => {
619
+ const newAllNodes = new Map(originalAllNodes);
620
+ const newParentNodes = new Map(originalParentNodes);
621
+
622
+ const newGridLayoutNode: GridLayoutNode = {
623
+ id: gridLayoutId,
624
+ parentId: paneNode.id,
625
+ nodeType: 'GridLayoutNode',
626
+ type: 'grid-layout',
627
+ parentClasses: markdownNode.parentClasses || [],
628
+ defaultClasses: markdownNode.defaultClasses || {},
629
+ gridColumns: { mobile: 1, tablet: 2, desktop: 2 },
630
+ isChanged: true,
631
+ };
632
+
633
+ const updatedMarkdownNode = cloneDeep(markdownNode);
634
+ updatedMarkdownNode.parentId = gridLayoutId;
635
+ updatedMarkdownNode.parentClasses = [];
636
+ updatedMarkdownNode.parentCss = [];
637
+ updatedMarkdownNode.defaultClasses = {};
638
+ updatedMarkdownNode.isChanged = true;
639
+
640
+ // Create a new, truly empty MarkdownNode for the second column.
641
+ const newColumnNodeId = ulid();
642
+ const newColumnNode: MarkdownPaneFragmentNode = {
643
+ id: newColumnNodeId,
644
+ parentId: gridLayoutId,
645
+ nodeType: 'Markdown',
646
+ type: 'markdown',
647
+ markdownId: ulid(),
648
+ defaultClasses: {},
649
+ parentClasses: [],
650
+ isChanged: true,
651
+ };
652
+
653
+ newAllNodes.set(gridLayoutId, newGridLayoutNode);
654
+ newAllNodes.set(markdownNodeId, updatedMarkdownNode);
655
+ newAllNodes.set(paneNode.id, { ...cloneDeep(paneNode), isChanged: true });
656
+ newAllNodes.set(newColumnNodeId, newColumnNode);
657
+
658
+ const paneChildren = [...(newParentNodes.get(paneNode.id) || [])];
659
+ const mdIndex = paneChildren.indexOf(markdownNodeId);
660
+ if (mdIndex > -1) {
661
+ paneChildren.splice(mdIndex, 1, gridLayoutId);
662
+ }
663
+ newParentNodes.set(paneNode.id, paneChildren);
664
+ newParentNodes.set(gridLayoutId, [markdownNodeId, newColumnNodeId]);
665
+ newParentNodes.set(newColumnNodeId, []); // Set children to an empty array
666
+
667
+ ctx.allNodes.set(newAllNodes);
668
+ ctx.parentNodes.set(newParentNodes);
669
+ ctx.notifyNode('root');
670
+ settingsPanelStore.set(null);
671
+ };
672
+
673
+ const undoLogic = () => {
674
+ ctx.allNodes.set(originalAllNodes);
675
+ ctx.parentNodes.set(originalParentNodes);
676
+ ctx.notifyNode('root');
677
+ };
678
+
679
+ ctx.history.addPatch({
680
+ op: PatchOp.REPLACE,
681
+ undo: undoLogic,
682
+ redo: redoLogic,
683
+ });
684
+
685
+ redoLogic();
686
+ }
687
+
688
+ export function addColumn(gridLayoutId: string) {
689
+ const ctx = getCtx();
690
+ const originalAllNodes = new Map(ctx.allNodes.get());
691
+ const originalParentNodes = new Map(ctx.parentNodes.get());
692
+
693
+ const gridLayoutNode = originalAllNodes.get(gridLayoutId);
694
+ if (!gridLayoutNode) return;
695
+
696
+ const newMarkdownNodeId = ulid();
697
+
698
+ const redoLogic = () => {
699
+ const newAllNodes = new Map(originalAllNodes);
700
+ const newParentNodes = new Map(originalParentNodes);
701
+
702
+ const newColumnNode: MarkdownPaneFragmentNode = {
703
+ id: newMarkdownNodeId,
704
+ parentId: gridLayoutId,
705
+ nodeType: 'Markdown',
706
+ type: 'markdown',
707
+ markdownId: ulid(),
708
+ defaultClasses: {},
709
+ parentClasses: [],
710
+ gridClasses: { mobile: {}, tablet: {}, desktop: {} },
711
+ isChanged: true,
712
+ };
713
+
714
+ newAllNodes.set(newMarkdownNodeId, newColumnNode);
715
+
716
+ const gridChildren = [...(newParentNodes.get(gridLayoutId) || [])];
717
+ gridChildren.push(newMarkdownNodeId);
718
+ newParentNodes.set(gridLayoutId, gridChildren);
719
+ newParentNodes.set(newMarkdownNodeId, []); // Set children to an empty array
720
+
721
+ const updatedGridLayoutNode = cloneDeep(gridLayoutNode);
722
+ updatedGridLayoutNode.isChanged = true;
723
+ newAllNodes.set(gridLayoutId, updatedGridLayoutNode);
724
+
725
+ ctx.allNodes.set(newAllNodes);
726
+ ctx.parentNodes.set(newParentNodes);
727
+ ctx.notifyNode('root');
728
+ };
729
+
730
+ const undoLogic = () => {
731
+ ctx.allNodes.set(originalAllNodes);
732
+ ctx.parentNodes.set(originalParentNodes);
733
+ ctx.notifyNode('root');
734
+ };
735
+
736
+ ctx.history.addPatch({
737
+ op: PatchOp.ADD,
738
+ undo: undoLogic,
739
+ redo: redoLogic,
740
+ });
741
+
742
+ redoLogic();
743
+ }
@@ -1,5 +1,9 @@
1
1
  import { tailwindClasses, tailwindCoreLayoutClasses } from './tailwindClasses';
2
- import type { TupleValue, ViewportKey } from '@/types/compositorTypes';
2
+ import type {
3
+ TupleValue,
4
+ ViewportKey,
5
+ DefaultClassValue,
6
+ } from '@/types/compositorTypes';
3
7
 
4
8
  const tailwindModifier = ['', 'md:', 'xl:'];
5
9
  const tailwindCoreModifier = ['xs:', 'md:', 'xl:'];
@@ -194,3 +198,38 @@ function getTailwindClassInfo(selector: string): {
194
198
  useKeyAsClass: classInfo.useKeyAsClass,
195
199
  };
196
200
  }
201
+
202
+ export function processGridClassesToString(
203
+ classes?: DefaultClassValue
204
+ ): string {
205
+ if (!classes) {
206
+ return '';
207
+ }
208
+
209
+ const finalClasses: string[] = [];
210
+ const mobileStyles = classes.mobile || {};
211
+ const tabletStyles = classes.tablet || {};
212
+ const desktopStyles = classes.desktop || {};
213
+
214
+ for (const [selector, value] of Object.entries(mobileStyles)) {
215
+ if (value) {
216
+ finalClasses.push(reduceClassName(selector, value, 0));
217
+ }
218
+ }
219
+
220
+ for (const [selector, value] of Object.entries(tabletStyles)) {
221
+ if (value && mobileStyles[selector] !== value) {
222
+ finalClasses.push(reduceClassName(selector, value, 1));
223
+ }
224
+ }
225
+
226
+ for (const [selector, value] of Object.entries(desktopStyles)) {
227
+ const tabletValue = tabletStyles[selector];
228
+ const mobileValue = mobileStyles[selector];
229
+ if (value && tabletValue !== value && mobileValue !== value) {
230
+ finalClasses.push(reduceClassName(selector, value, 2));
231
+ }
232
+ }
233
+
234
+ return finalClasses.filter(Boolean).join(' ');
235
+ }