astro-tractstack 2.0.11 → 2.0.13

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.
@@ -387,12 +387,6 @@ export const Compositor = (props: CompositorProps) => {
387
387
  const ctx = getCtx(props);
388
388
  const range = $selection;
389
389
 
390
- if ($selection.pendingAction === 'style') {
391
- if (VERBOSE) console.log(LOG_PREFIX + 'useEffect acting on: style');
392
- await ctx.wrapRangeInSpan(range as SelectionStoreState, 'span');
393
- resetSelectionStore();
394
- }
395
-
396
390
  if ($selection.pendingAction === 'link') {
397
391
  if (VERBOSE) console.log(LOG_PREFIX + 'useEffect acting on: link');
398
392
  const newAnchorNodeId = await ctx.wrapRangeInAnchor(
@@ -402,6 +396,20 @@ export const Compositor = (props: CompositorProps) => {
402
396
  ctx.handleInsertSignal('a', newAnchorNodeId);
403
397
  }
404
398
  resetSelectionStore();
399
+ } else if ($selection.pendingAction === 'style') {
400
+ if (VERBOSE) console.log(LOG_PREFIX + 'useEffect acting on: style');
401
+ const newSpanNodeId = await ctx.wrapRangeInSpan(
402
+ range as SelectionStoreState,
403
+ 'span'
404
+ );
405
+ if (newSpanNodeId) {
406
+ settingsPanelStore.set({
407
+ action: 'style-element',
408
+ nodeId: newSpanNodeId,
409
+ expanded: true,
410
+ });
411
+ }
412
+ resetSelectionStore();
405
413
  }
406
414
  ctx.notifyNode('root');
407
415
  };
@@ -45,6 +45,8 @@ import type {
45
45
  BaseNode,
46
46
  FlatNode,
47
47
  } from '@/types/compositorTypes';
48
+ import { handleClickEventDefault } from '@/utils/compositor/handleClickEvent';
49
+ import { selectionStore } from '@/stores/selection';
48
50
  import type { NodeProps, SelectionOrigin } from '@/types/nodeProps';
49
51
 
50
52
  const VERBOSE = false;
@@ -153,6 +155,7 @@ const getElement = (
153
155
  nodeId={props.nodeId}
154
156
  ctx={getCtx(props)}
155
157
  isTemplate={isTemplate}
158
+ config={props.config!}
156
159
  />
157
160
  ) : (
158
161
  <>
@@ -213,6 +216,7 @@ const getElement = (
213
216
  nodeId={node.id}
214
217
  first={true}
215
218
  ctx={getCtx(props)}
219
+ config={props.config!}
216
220
  isContextPane={true}
217
221
  />
218
222
  </PanelVisibilityWrapper>
@@ -240,7 +244,12 @@ const getElement = (
240
244
  panelType="add"
241
245
  ctx={getCtx(props)}
242
246
  >
243
- <AddPanePanel nodeId={node.id} first={true} ctx={getCtx(props)} />
247
+ <AddPanePanel
248
+ nodeId={node.id}
249
+ first={true}
250
+ ctx={getCtx(props)}
251
+ config={props.config!}
252
+ />
244
253
  </PanelVisibilityWrapper>
245
254
  )}
246
255
  <div className="py-0.5">
@@ -264,7 +273,12 @@ const getElement = (
264
273
  panelType="add"
265
274
  ctx={getCtx(props)}
266
275
  >
267
- <AddPanePanel nodeId={node.id} first={false} ctx={getCtx(props)} />
276
+ <AddPanePanel
277
+ nodeId={node.id}
278
+ first={false}
279
+ ctx={getCtx(props)}
280
+ config={props.config!}
281
+ />
268
282
  </PanelVisibilityWrapper>
269
283
  </>
270
284
  );
@@ -291,6 +305,31 @@ const getElement = (
291
305
  viewportKeyStore.get().value
292
306
  );
293
307
 
308
+ const handleElementClick = (e: MouseEvent<HTMLElement>) => {
309
+ // 1. ALWAYS stop the event from bubbling up to the Pane.
310
+ e.stopPropagation();
311
+
312
+ // 2. Check the selection store. The handleMouseUp in Compositor.tsx
313
+ // has already run by the time this 'click' event fires.
314
+ //
315
+ // - If it was a drag, Compositor.tsx set 'isActive: true' (line 267).
316
+ // - If it was a click, Compositor.tsx called 'resetSelectionStore'
317
+ // (line 242), so 'isActive: false'.
318
+ //
319
+ const { isActive } = selectionStore.get();
320
+
321
+ if (isActive) {
322
+ // A drag just finished. The user's intent was to select text.
323
+ // Do NOT open the panel.
324
+ return;
325
+ }
326
+
327
+ // 3. 'isActive' was false. This was a genuine click.
328
+ // We can safely open the settings panel.
329
+ // 'node' is already in scope from the getElement function.
330
+ handleClickEventDefault(node as FlatNode, true);
331
+ };
332
+
294
333
  const handleMouseDown = (e: MouseEvent<HTMLElement>) => {
295
334
  if (VERBOSE)
296
335
  console.log('[Node.tsx] handleMouseDown FIRED', { event: e });
@@ -346,6 +385,7 @@ const getElement = (
346
385
  {
347
386
  className: className,
348
387
  onMouseDown: handleMouseDown,
388
+ onClick: handleElementClick,
349
389
  'data-node-id': node.id,
350
390
  style: { userSelect: 'none' },
351
391
  },
@@ -294,11 +294,6 @@ const Pane = memo(
294
294
  (prevProps: NodeProps, nextProps: NodeProps) => {
295
295
  const isEqual =
296
296
  prevProps.nodeId === nextProps.nodeId && prevProps.ctx === nextProps.ctx;
297
- if (!isEqual) {
298
- console.log(
299
- ` !! Pane rerender triggered by props change: ${prevProps.nodeId} -> ${nextProps.nodeId}`
300
- );
301
- }
302
297
  return isEqual;
303
298
  }
304
299
  );
@@ -379,8 +379,9 @@ export const NodeBasicTag = (props: NodeTagProps) => {
379
379
  .filter(
380
380
  (childNode): childNode is FlatNode =>
381
381
  'tagName' in childNode &&
382
- ['a', 'button'].includes(childNode.tagName as string)
382
+ ['a', 'button', 'span'].includes(childNode.tagName as string)
383
383
  ) as FlatNode[];
384
+ if (VERBOSE) console.log('originalNodes to save:', originalNodes);
384
385
 
385
386
  const parsedNodes = processRichTextToNodes(
386
387
  currentContent,
@@ -5,8 +5,9 @@ import AddPaneNewPanel from './AddPanePanel_new';
5
5
  import AddPaneBreakPanel from './AddPanePanel_break';
6
6
  import AddPaneReUsePanel from './AddPanePanel_reuse';
7
7
  import AddPaneCodeHookPanel from './AddPanePanel_codehook';
8
- import { NodesContext, ROOT_NODE_NAME, getCtx } from '@/stores/nodes'; // Import getCtx
8
+ import { NodesContext, ROOT_NODE_NAME, getCtx } from '@/stores/nodes';
9
9
  import { PaneAddMode } from '@/types/compositorTypes';
10
+ import type { BrandConfig } from '@/types/tractstack';
10
11
 
11
12
  interface AddPanePanelProps {
12
13
  nodeId: string;
@@ -14,6 +15,7 @@ interface AddPanePanelProps {
14
15
  ctx?: NodesContext;
15
16
  isStoryFragment?: boolean;
16
17
  isContextPane?: boolean;
18
+ config?: BrandConfig;
17
19
  }
18
20
 
19
21
  const AddPanePanel = ({
@@ -22,6 +24,7 @@ const AddPanePanel = ({
22
24
  ctx,
23
25
  isStoryFragment = false,
24
26
  isContextPane = false,
27
+ config,
25
28
  }: AddPanePanelProps) => {
26
29
  const [reset, setReset] = useState(false);
27
30
  const lookup = first ? `${nodeId}-0` : nodeId;
@@ -62,6 +65,7 @@ const AddPanePanel = ({
62
65
  ctx={nodesCtx}
63
66
  isStoryFragment={isStoryFragment}
64
67
  isContextPane={isContextPane}
68
+ config={config!}
65
69
  />
66
70
  ) : mode === PaneAddMode.BREAK && !isContextPane ? (
67
71
  <AddPaneBreakPanel
@@ -24,13 +24,11 @@ import {
24
24
  hasAssemblyAIStore,
25
25
  } from '@/stores/storykeep';
26
26
  import { templateCategories } from '@/utils/compositor/templateMarkdownStyles';
27
- import { AddPanePanel_newAICopy } from './AddPanePanel_newAICopy';
28
- import { AddPaneNewCopyMode, type CopyMode } from './AddPanePanel_newCopyMode';
27
+ import { AiPaneGenerator } from './AiPaneGenerator';
29
28
  import { AddPaneNewCustomCopy } from './AddPanePanel_newCustomCopy';
30
- import { getTitleSlug } from '@/utils/aai/getTitleSlug';
31
- import { fullContentMapStore } from '@/stores/storykeep';
32
- import { themes, type Theme } from '@/types/tractstack';
33
- import { PaneAddMode } from '@/types/compositorTypes';
29
+ import { themes, type Theme, type BrandConfig } from '@/types/tractstack';
30
+ import { PaneAddMode, type TemplatePane } from '@/types/compositorTypes';
31
+ import { useStore } from '@nanostores/react';
34
32
 
35
33
  interface AddPaneNewPanelProps {
36
34
  nodeId: string;
@@ -39,6 +37,7 @@ interface AddPaneNewPanelProps {
39
37
  ctx?: NodesContext;
40
38
  isStoryFragment?: boolean;
41
39
  isContextPane?: boolean;
40
+ config?: BrandConfig;
42
41
  }
43
42
 
44
43
  interface PreviewPane {
@@ -58,47 +57,47 @@ interface TemplateCategory {
58
57
 
59
58
  const ITEMS_PER_PAGE = 8;
60
59
 
60
+ type Mode = 'template' | 'custom' | 'ai';
61
+
61
62
  const AddPaneNewPanel = ({
62
63
  nodeId,
63
64
  first,
64
- setMode,
65
+ setMode: setParentMode, // Renamed prop to avoid conflict with local state
65
66
  ctx,
66
67
  isStoryFragment = false,
67
68
  isContextPane = false,
69
+ config,
68
70
  }: AddPaneNewPanelProps) => {
69
- const brand = brandColourStore.get();
70
- const hasAssemblyAI = hasAssemblyAIStore.get();
71
- const [copyMode, setCopyMode] = useState<CopyMode>('design');
71
+ const brand = useStore(brandColourStore);
72
+ const hasAssemblyAI = useStore(hasAssemblyAIStore);
73
+ const [mode, setMode] = useState<Mode>('template'); // Local mode state
72
74
  const [customMarkdown, setCustomMarkdown] = useState<string>(`...`);
73
75
  const [previews, setPreviews] = useState<PreviewPane[]>([]);
74
76
  const [currentPage, setCurrentPage] = useState(0);
75
77
  const [renderedPages, setRenderedPages] = useState<Set<number>>(new Set());
76
78
  const [selectedTheme, setSelectedTheme] = useState<Theme>(
77
- preferredThemeStore.get()
79
+ useStore(preferredThemeStore)
78
80
  );
79
81
  const [useOddVariant, setUseOddVariant] = useState(false);
80
82
  const [selectedCategory, setSelectedCategory] = useState<TemplateCategory>(
81
83
  templateCategories[isContextPane ? 1 : first ? 4 : 0]
82
84
  );
83
85
  const [isInserting, setIsInserting] = useState(false);
84
- const [aiContentGenerated, setAiContentGenerated] = useState(false);
85
86
  const [fragmentsToGenerate, setFragmentsToGenerate] = useState<
86
87
  PanePreviewRequest[]
87
88
  >([]);
88
- const shouldShowDesigns = copyMode !== 'ai' || aiContentGenerated;
89
89
 
90
90
  const categoryCollection = useMemo(() => {
91
- const categories =
92
- copyMode === `ai` || isContextPane
93
- ? [templateCategories[1]]
94
- : templateCategories;
91
+ const categories = isContextPane
92
+ ? [templateCategories[1]]
93
+ : templateCategories;
95
94
 
96
95
  return createListCollection({
97
96
  items: categories,
98
97
  itemToValue: (item) => item.id,
99
98
  itemToString: (item) => item.title,
100
99
  });
101
- }, [copyMode, isContextPane]);
100
+ }, [isContextPane]);
102
101
 
103
102
  const themesCollection = useMemo(() => {
104
103
  return createListCollection({
@@ -109,38 +108,25 @@ const AddPaneNewPanel = ({
109
108
  }, []);
110
109
 
111
110
  const filteredTemplates = useMemo(() => {
112
- if (copyMode === `ai` || isContextPane)
113
- return templateCategories[1].getTemplates(
114
- selectedTheme,
115
- brand,
116
- useOddVariant
117
- );
118
- if (isContextPane)
119
- return templateCategories[1].getTemplates(
120
- selectedTheme,
121
- brand,
122
- useOddVariant
123
- );
124
111
  return selectedCategory.getTemplates(selectedTheme, brand, useOddVariant);
125
- }, [selectedTheme, useOddVariant, selectedCategory, copyMode, isContextPane]);
112
+ }, [selectedTheme, useOddVariant, selectedCategory, brand]);
126
113
 
127
114
  useEffect(() => {
128
- if (copyMode !== 'ai') setAiContentGenerated(false);
129
- if (copyMode !== 'ai' || isContextPane)
130
- setSelectedCategory(templateCategories[first ? 4 : 0]);
131
- }, [copyMode, first, isContextPane]);
132
-
133
- const handleAiContentGenerated = (content: string) => {
134
- setCustomMarkdown(content);
135
- setAiContentGenerated(true);
136
- };
115
+ if (isContextPane) {
116
+ setSelectedCategory(templateCategories[1]);
117
+ } else if (first) {
118
+ setSelectedCategory(templateCategories[4]);
119
+ } else {
120
+ setSelectedCategory(templateCategories[0]);
121
+ }
122
+ }, [isContextPane, first]);
137
123
 
138
124
  useEffect(() => {
139
125
  const newPreviews = filteredTemplates.map((template, index: number) => {
140
- const ctx = new NodesContext();
141
- ctx.addNode(createEmptyStorykeep('tmp'));
126
+ const previewCtx = new NodesContext();
127
+ previewCtx.addNode(createEmptyStorykeep('tmp'));
142
128
  const thisTemplate =
143
- copyMode === 'custom' || (copyMode === 'ai' && aiContentGenerated)
129
+ mode === 'custom'
144
130
  ? {
145
131
  ...template,
146
132
  markdown: template.markdown && {
@@ -149,13 +135,13 @@ const AddPaneNewPanel = ({
149
135
  },
150
136
  }
151
137
  : template;
152
- ctx.addTemplatePane('tmp', thisTemplate);
153
- return { ctx, template: thisTemplate, index };
138
+ previewCtx.addTemplatePane('tmp', thisTemplate);
139
+ return { ctx: previewCtx, template: thisTemplate, index };
154
140
  });
155
141
  setPreviews(newPreviews);
156
142
  setCurrentPage(0);
157
143
  setRenderedPages(new Set());
158
- }, [filteredTemplates, customMarkdown, copyMode, aiContentGenerated]);
144
+ }, [filteredTemplates, customMarkdown, mode]);
159
145
 
160
146
  const totalPages = Math.ceil(previews.length / ITEMS_PER_PAGE);
161
147
 
@@ -223,81 +209,47 @@ const AddPaneNewPanel = ({
223
209
  });
224
210
  };
225
211
 
226
- const handleTemplateInsert = async (
227
- template: any,
228
- nodeId: string,
229
- first: boolean
230
- ) => {
231
- if (isInserting) return;
212
+ const handleApplyTemplate = async (template: any) => {
213
+ if (isInserting || !ctx) return;
232
214
  setIsInserting(true);
233
215
 
234
216
  try {
235
- if (ctx) {
236
- const hasMarkdownContent =
237
- template?.markdown?.markdownBody &&
238
- template.markdown.markdownBody.trim() !== '...' &&
239
- template.markdown.markdownBody.trim().length > 0;
240
-
241
- const insertTemplate = [`blank`, `custom`].includes(copyMode)
217
+ const insertTemplate =
218
+ mode === 'custom'
242
219
  ? {
243
220
  ...cloneDeep(template),
244
221
  markdown: template.markdown && {
245
222
  ...template.markdown,
246
- markdownBody: copyMode === `blank` ? `...` : customMarkdown,
223
+ markdownBody: customMarkdown,
247
224
  },
248
225
  }
249
226
  : cloneDeep(template);
250
227
 
251
- const markdownContent = [`blank`].includes(copyMode)
252
- ? null
253
- : copyMode === `custom`
254
- ? customMarkdown
255
- : insertTemplate?.markdown?.markdownBody;
256
-
257
- insertTemplate.title = '';
258
- insertTemplate.slug = '';
259
-
260
- if (
261
- copyMode === `ai` &&
262
- hasAssemblyAI &&
263
- markdownContent &&
264
- hasMarkdownContent
265
- ) {
266
- const existingSlugs = fullContentMapStore
267
- .get()
268
- .filter((item) => ['Pane', 'StoryFragment'].includes(item.type))
269
- .map((item) => item.slug);
270
-
271
- const titleSlugResult = await getTitleSlug(
272
- markdownContent,
273
- existingSlugs
274
- );
275
-
276
- if (titleSlugResult) {
277
- insertTemplate.title = titleSlugResult.title;
278
- insertTemplate.slug = titleSlugResult.slug;
279
- }
280
- }
281
-
282
- const ownerId =
283
- isStoryFragment || isContextPane
284
- ? nodeId
285
- : ctx.getClosestNodeTypeFromId(nodeId, 'StoryFragment');
286
-
287
- if (isContextPane) {
288
- insertTemplate.isContextPane = true;
289
- ctx.addContextTemplatePane(ownerId, insertTemplate);
290
- } else {
291
- const newPaneId = ctx.addTemplatePane(
228
+ insertTemplate.title = '';
229
+ insertTemplate.slug = '';
230
+
231
+ const ownerId =
232
+ isStoryFragment || isContextPane
233
+ ? nodeId
234
+ : ctx.getClosestNodeTypeFromId(nodeId, 'StoryFragment');
235
+
236
+ if (isContextPane) {
237
+ insertTemplate.isContextPane = true;
238
+ await ctx.applyAtomicUpdate(async (tmpCtx) => {
239
+ tmpCtx.addContextTemplatePane(ownerId, insertTemplate);
240
+ });
241
+ } else {
242
+ await ctx.applyAtomicUpdate(async (tmpCtx) => {
243
+ tmpCtx.addTemplatePane(
292
244
  ownerId,
293
245
  insertTemplate,
294
246
  nodeId,
295
247
  first ? 'before' : 'after'
296
248
  );
297
- if (newPaneId) ctx.notifyNode(`root`);
298
- }
299
- setMode(PaneAddMode.DEFAULT, false);
249
+ });
250
+ ctx.notifyNode(`root`);
300
251
  }
252
+ setParentMode(PaneAddMode.DEFAULT, false);
301
253
  } catch (error) {
302
254
  console.error('Error inserting template:', error);
303
255
  } finally {
@@ -305,6 +257,39 @@ const AddPaneNewPanel = ({
305
257
  }
306
258
  };
307
259
 
260
+ const handleApplyGeneratedPane = async (pane: TemplatePane) => {
261
+ if (isInserting || !ctx) return;
262
+ setIsInserting(true);
263
+ try {
264
+ const ownerId =
265
+ isStoryFragment || isContextPane
266
+ ? nodeId
267
+ : ctx.getClosestNodeTypeFromId(nodeId, 'StoryFragment');
268
+
269
+ if (isContextPane) {
270
+ pane.isContextPane = true;
271
+ await ctx.applyAtomicUpdate(async (tmpCtx) => {
272
+ tmpCtx.addContextTemplatePane(ownerId, pane);
273
+ });
274
+ } else {
275
+ await ctx.applyAtomicUpdate(async (tmpCtx) => {
276
+ tmpCtx.addTemplatePane(
277
+ ownerId,
278
+ pane,
279
+ nodeId,
280
+ first ? 'before' : 'after'
281
+ );
282
+ });
283
+ ctx.notifyNode(`root`);
284
+ }
285
+ setParentMode(PaneAddMode.DEFAULT, false);
286
+ } catch (error) {
287
+ console.error('Error applying generated pane:', error);
288
+ } finally {
289
+ setIsInserting(false);
290
+ }
291
+ };
292
+
308
293
  const handleThemeChange = (details: { value: string[] }) => {
309
294
  const newTheme = details.value[0] as Theme;
310
295
  if (newTheme) {
@@ -338,8 +323,9 @@ const AddPaneNewPanel = ({
338
323
  <style>{customStyles}</style>
339
324
  <div className="group flex w-full gap-1 rounded-md bg-white p-1.5">
340
325
  <button
341
- onClick={() => setMode(PaneAddMode.DEFAULT, first)}
326
+ onClick={() => setParentMode(PaneAddMode.DEFAULT, first)}
342
327
  className="w-fit rounded bg-gray-100 px-3 py-1 text-sm text-gray-700 transition-colors hover:bg-gray-200 focus:bg-gray-200"
328
+ type="button"
343
329
  >
344
330
  ← Go Back
345
331
  </button>
@@ -348,11 +334,45 @@ const AddPaneNewPanel = ({
348
334
  <div className="font-action flex-none rounded px-2 py-2.5 text-sm font-bold text-cyan-700 shadow-sm">
349
335
  + Design New Pane
350
336
  </div>
337
+ <div className="flex items-center space-x-2 rounded-lg bg-gray-100 p-1">
338
+ <button
339
+ onClick={() => setMode('template')}
340
+ className={`rounded-md px-3 py-1 text-sm font-medium transition-colors ${
341
+ mode === 'template'
342
+ ? 'bg-white text-cyan-700 shadow'
343
+ : 'text-gray-600 hover:text-gray-800'
344
+ }`}
345
+ type="button"
346
+ >
347
+ Use Template
348
+ </button>
349
+ <button
350
+ onClick={() => setMode('custom')}
351
+ className={`rounded-md px-3 py-1 text-sm font-medium transition-colors ${
352
+ mode === 'custom'
353
+ ? 'bg-white text-cyan-700 shadow'
354
+ : 'text-gray-600 hover:text-gray-800'
355
+ }`}
356
+ type="button"
357
+ >
358
+ Paste Markdown
359
+ </button>
360
+ {hasAssemblyAI && (
361
+ <button
362
+ onClick={() => setMode('ai')}
363
+ className={`rounded-md px-3 py-1 text-sm font-medium transition-colors ${
364
+ mode === 'ai'
365
+ ? 'rounded-md border border-transparent bg-cyan-600 px-3 py-1.5 text-sm font-bold text-white shadow-sm hover:bg-cyan-700 focus:outline-none focus:ring-2 focus:ring-cyan-500 focus:ring-offset-2'
366
+ : 'text-gray-600 hover:text-gray-800'
367
+ }`}
368
+ type="button"
369
+ >
370
+ ✨ Generate with AI
371
+ </button>
372
+ )}
373
+ </div>
351
374
 
352
- {!(copyMode === 'ai' && aiContentGenerated) && (
353
- <AddPaneNewCopyMode selected={copyMode} onChange={setCopyMode} />
354
- )}
355
- {copyMode === 'custom' && (
375
+ {mode === 'custom' && (
356
376
  <div className="mt-4 w-full">
357
377
  <AddPaneNewCustomCopy
358
378
  value={customMarkdown}
@@ -360,18 +380,20 @@ const AddPaneNewPanel = ({
360
380
  />
361
381
  </div>
362
382
  )}
363
- {copyMode === 'ai' && !aiContentGenerated && (
383
+ {mode === 'ai' && (
364
384
  <div className="mt-4 w-full">
365
- <AddPanePanel_newAICopy
366
- onChange={handleAiContentGenerated}
367
- isContextPane={isContextPane}
385
+ <AiPaneGenerator
386
+ ownerId={nodeId}
387
+ onComplete={handleApplyGeneratedPane}
388
+ onCancel={() => setMode('template')}
389
+ config={config!}
368
390
  />
369
391
  </div>
370
392
  )}
371
393
  </div>
372
394
  </div>
373
395
 
374
- {shouldShowDesigns && (
396
+ {mode !== 'ai' && (
375
397
  <>
376
398
  <h3 className="font-action px-3.5 pb-1.5 pt-4 text-xl font-bold text-black">
377
399
  1. Template design settings
@@ -521,8 +543,7 @@ const AddPaneNewPanel = ({
521
543
  onClick={
522
544
  isInserting
523
545
  ? undefined
524
- : () =>
525
- handleTemplateInsert(preview.template, nodeId, first)
546
+ : () => handleApplyTemplate(preview.template)
526
547
  }
527
548
  className={`bg-mywhite group relative w-full rounded-sm shadow-inner ${
528
549
  isInserting
@@ -588,6 +609,7 @@ const AddPaneNewPanel = ({
588
609
  onClick={() => handlePageChange(currentPage - 1)}
589
610
  disabled={currentPage === 0}
590
611
  className="rounded bg-gray-100 px-3 py-1 text-sm text-gray-700 transition-colors hover:bg-gray-200 focus:bg-gray-200 disabled:cursor-not-allowed disabled:opacity-50"
612
+ type="button"
591
613
  >
592
614
  Previous
593
615
  </button>
@@ -601,6 +623,7 @@ const AddPaneNewPanel = ({
601
623
  ? 'bg-cyan-700 text-white'
602
624
  : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
603
625
  }`}
626
+ type="button"
604
627
  >
605
628
  {index + 1}
606
629
  </button>
@@ -610,6 +633,7 @@ const AddPaneNewPanel = ({
610
633
  onClick={() => handlePageChange(currentPage + 1)}
611
634
  disabled={currentPage === totalPages - 1}
612
635
  className="rounded bg-gray-100 px-3 py-1 text-sm text-gray-700 transition-colors hover:bg-gray-200 focus:bg-gray-200 disabled:cursor-not-allowed disabled:opacity-50"
636
+ type="button"
613
637
  >
614
638
  Next
615
639
  </button>