astro-tractstack 2.2.7 → 2.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-tractstack",
3
- "version": "2.2.7",
3
+ "version": "2.2.9",
4
4
  "description": "Astro integration for TractStack - the free web press by At Risk Media",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -127,7 +127,7 @@ export const NodeOverlay = ({
127
127
  zIndexClass,
128
128
  toolMode === 'text' ? outlineClass : '',
129
129
  isReorderMode && !isDragging
130
- ? 'cursor-grab hover:outline hover:outline-dotted hover:outline-2 hover:outline-offset-2 hover:outline-cyan-500'
130
+ ? 'cursor-grab hover:outline-dotted hover:outline-2 hover:outline-offset-2 hover:outline-cyan-500'
131
131
  : ''
132
132
  )}
133
133
  style={isInline ? { display: 'inline-block' } : {}}
@@ -13,6 +13,9 @@ import ArchiveBoxArrowDownIcon from '@heroicons/react/24/outline/ArchiveBoxArrow
13
13
  import ArrowPathRoundedSquareIcon from '@heroicons/react/24/outline/ArrowPathRoundedSquareIcon';
14
14
  import ArrowDownTrayIcon from '@heroicons/react/24/outline/ArrowDownTrayIcon';
15
15
  import SparklesIcon from '@heroicons/react/24/solid/SparklesIcon';
16
+ import TrashIcon from '@heroicons/react/24/outline/TrashIcon';
17
+ import ArrowUpIcon from '@heroicons/react/24/outline/ArrowUpIcon';
18
+ import ArrowDownIcon from '@heroicons/react/24/outline/ArrowDownIcon';
16
19
  import {
17
20
  isContextPaneNode,
18
21
  hasBeliefPayload,
@@ -27,7 +30,11 @@ import { AiRestylePaneModal } from '@/components/edit/pane/AiRestylePaneModal';
27
30
  import PaneTitlePanel from './PanePanel_title';
28
31
  import PaneMagicPathPanel from './PanePanel_path';
29
32
  import PaneImpressionPanel from './PanePanel_impression';
30
- import { PaneConfigMode, type PaneNode } from '@/types/compositorTypes';
33
+ import {
34
+ PaneConfigMode,
35
+ type PaneNode,
36
+ type StoryFragmentNode,
37
+ } from '@/types/compositorTypes';
31
38
 
32
39
  interface ConfigPanePanelProps {
33
40
  nodeId: string;
@@ -61,6 +68,23 @@ const ConfigPanePanel = ({
61
68
  const buttonClass =
62
69
  'px-2 py-1 bg-white text-cyan-700 text-sm rounded hover:bg-cyan-700 hover:text-white focus:bg-cyan-700 focus:text-white shadow-sm transition-colors whitespace-nowrap mb-1';
63
70
 
71
+ // Determine Position for Reordering
72
+ const parentNode = paneNode.parentId
73
+ ? (allNodes.get(paneNode.parentId) as StoryFragmentNode)
74
+ : null;
75
+ let isFirst = false;
76
+ let isLast = false;
77
+
78
+ if (parentNode && parentNode.nodeType === 'StoryFragment') {
79
+ if (parentNode.paneIds && Array.isArray(parentNode.paneIds)) {
80
+ const idx = parentNode.paneIds.indexOf(nodeId);
81
+ if (idx !== -1) {
82
+ isFirst = idx === 0;
83
+ isLast = idx === parentNode.paneIds.length - 1;
84
+ }
85
+ }
86
+ }
87
+
64
88
  const [mode, setMode] = useState<PaneConfigMode>(
65
89
  isActiveMode && activePaneMode.mode
66
90
  ? (activePaneMode.mode as PaneConfigMode)
@@ -147,6 +171,30 @@ const ConfigPanePanel = ({
147
171
  }
148
172
  };
149
173
 
174
+ // Delete & Reorder Handlers
175
+ const handleDelete = (e: MouseEvent) => {
176
+ e.stopPropagation();
177
+ if (window.confirm('Are you sure you want to delete this pane?')) {
178
+ ctx.deleteNode(nodeId);
179
+ }
180
+ };
181
+
182
+ const handleMoveUp = (e: MouseEvent) => {
183
+ e.stopPropagation();
184
+ if (!isFirst) {
185
+ ctx.moveNode(nodeId, 'before');
186
+ if (paneNode.parentId) ctx.notifyNode(paneNode.parentId);
187
+ }
188
+ };
189
+
190
+ const handleMoveDown = (e: MouseEvent) => {
191
+ e.stopPropagation();
192
+ if (!isLast) {
193
+ ctx.moveNode(nodeId, 'after');
194
+ if (paneNode.parentId) ctx.notifyNode(paneNode.parentId);
195
+ }
196
+ };
197
+
150
198
  if (mode === PaneConfigMode.TITLE) {
151
199
  return <PaneTitlePanel nodeId={nodeId} setMode={setSaveMode} />;
152
200
  } else if (mode === PaneConfigMode.PATH) {
@@ -251,8 +299,46 @@ const ConfigPanePanel = ({
251
299
  )}
252
300
  </div>
253
301
 
254
- {/* Design Library Tools (Right Aligned) */}
255
- <div className="ml-auto flex items-center gap-2 border-l border-gray-300 px-2">
302
+ {/* Right Aligned Tools */}
303
+ <div className="ml-auto flex items-center gap-2 px-2">
304
+ {/* Delete & Reorder Tools */}
305
+ {!isTemplate && !isContextPane && !isHtmlAstPane && (
306
+ <div className="flex items-center gap-1 border-r border-gray-300 pr-2">
307
+ <button
308
+ onClick={handleMoveUp}
309
+ disabled={isFirst}
310
+ title={isFirst ? 'First pane' : 'Move pane up'}
311
+ className={`flex h-7 w-7 items-center justify-center rounded-full p-1 shadow-sm transition-colors ${
312
+ isFirst
313
+ ? 'cursor-not-allowed bg-gray-200 text-gray-400'
314
+ : 'bg-white text-gray-600 hover:bg-gray-100'
315
+ }`}
316
+ >
317
+ <ArrowUpIcon className="h-4 w-4" />
318
+ </button>
319
+ <button
320
+ onClick={handleMoveDown}
321
+ disabled={isLast}
322
+ title={isLast ? 'Last pane' : 'Move pane down'}
323
+ className={`flex h-7 w-7 items-center justify-center rounded-full p-1 shadow-sm transition-colors ${
324
+ isLast
325
+ ? 'cursor-not-allowed bg-gray-200 text-gray-400'
326
+ : 'bg-white text-gray-600 hover:bg-gray-100'
327
+ }`}
328
+ >
329
+ <ArrowDownIcon className="h-4 w-4" />
330
+ </button>
331
+ <button
332
+ onClick={handleDelete}
333
+ title="Delete Pane"
334
+ className="flex h-7 w-7 items-center justify-center rounded-full bg-white p-1 text-red-500 shadow-sm hover:bg-red-50 hover:text-red-700"
335
+ >
336
+ <TrashIcon className="h-4 w-4" />
337
+ </button>
338
+ </div>
339
+ )}
340
+
341
+ {/* Design Library Tools */}
256
342
  {!isHtmlAstPane && !isSandboxMode && (
257
343
  <button
258
344
  title="Save Pane to Design Library"
@@ -2451,8 +2451,14 @@ export class NodesContext {
2451
2451
  .get()
2452
2452
  .get(parentId) as StoryFragmentNode;
2453
2453
  if (storyFragment) {
2454
- paneIdx = storyFragment.paneIds.indexOf(targetNodeId);
2455
- storyFragment.paneIds.splice(paneIdx, 1);
2454
+ // Use modifyNodes to ensure StoryFragment is marked dirty (isChanged: true)
2455
+ // We disable history recording here because we create a specific REMOVE patch below
2456
+ const updatedFragment = cloneDeep(storyFragment);
2457
+ paneIdx = updatedFragment.paneIds.indexOf(targetNodeId);
2458
+ if (paneIdx !== -1) {
2459
+ updatedFragment.paneIds.splice(paneIdx, 1);
2460
+ this.modifyNodes([updatedFragment], { recordHistory: false });
2461
+ }
2456
2462
  }
2457
2463
  } else if (targetNode.nodeType === 'TagElement') {
2458
2464
  // mark pane as changed
@@ -2488,16 +2494,35 @@ export class NodesContext {
2488
2494
  undo: (ctx) => {
2489
2495
  ctx.addNodes(toDelete);
2490
2496
  if (targetNode.nodeType === 'Pane' && parentId !== null) {
2491
- const storyFragment = this.allNodes
2497
+ const storyFragment = ctx.allNodes
2498
+ .get()
2499
+ .get(parentId) as StoryFragmentNode;
2500
+ if (storyFragment) {
2501
+ // Use modifyNodes in Undo to correctly restore state and links
2502
+ const updatedFragment = cloneDeep(storyFragment);
2503
+ updatedFragment.paneIds.splice(paneIdx, 0, targetNodeId);
2504
+ ctx.modifyNodes([updatedFragment], { recordHistory: false });
2505
+ ctx.linkChildToParent(targetNodeId, parentId, paneIdx);
2506
+ }
2507
+ }
2508
+ },
2509
+ redo: (ctx) => {
2510
+ ctx.deleteNodes(toDelete);
2511
+ // Ensure Redo also updates the parent StoryFragment correctly
2512
+ if (targetNode.nodeType === 'Pane' && parentId !== null) {
2513
+ const storyFragment = ctx.allNodes
2492
2514
  .get()
2493
2515
  .get(parentId) as StoryFragmentNode;
2494
2516
  if (storyFragment) {
2495
- storyFragment.paneIds.splice(paneIdx, 0, targetNodeId);
2496
- this.linkChildToParent(targetNodeId, parentId, paneIdx);
2517
+ const updatedFragment = cloneDeep(storyFragment);
2518
+ const idx = updatedFragment.paneIds.indexOf(targetNodeId);
2519
+ if (idx !== -1) {
2520
+ updatedFragment.paneIds.splice(idx, 1);
2521
+ ctx.modifyNodes([updatedFragment], { recordHistory: false });
2522
+ }
2497
2523
  }
2498
2524
  }
2499
2525
  },
2500
- redo: (ctx) => ctx.deleteNodes(toDelete),
2501
2526
  });
2502
2527
  }
2503
2528
  }