astro-tractstack 2.0.15 → 2.0.17

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 (28) hide show
  1. package/dist/index.js +33 -13
  2. package/package.json +1 -1
  3. package/templates/custom/with-examples/CodeHook.astro +4 -0
  4. package/templates/custom/with-examples/SandboxLauncher.tsx +67 -0
  5. package/templates/env.example +3 -0
  6. package/templates/src/components/codehooks/SandboxAuthWrapper.tsx +75 -0
  7. package/templates/src/components/codehooks/SandboxRegisterForm.tsx +202 -0
  8. package/templates/src/components/compositor/Compositor.tsx +2 -0
  9. package/templates/src/components/compositor/Node.tsx +6 -1
  10. package/templates/src/components/compositor/nodes/Pane_DesignLibrary.tsx +13 -11
  11. package/templates/src/components/compositor/nodes/Pane_layout.tsx +16 -14
  12. package/templates/src/components/edit/Header.tsx +8 -2
  13. package/templates/src/components/edit/PanelSwitch.tsx +4 -4
  14. package/templates/src/components/edit/pane/AddPanePanel.tsx +3 -0
  15. package/templates/src/components/edit/pane/AddPanePanel_new.tsx +61 -46
  16. package/templates/src/components/edit/pane/steps/DirectInjectStep.tsx +96 -0
  17. package/templates/src/components/edit/panels/StyleImagePanel.tsx +10 -8
  18. package/templates/src/components/edit/state/SaveModal.tsx +41 -0
  19. package/templates/src/constants.ts +1 -0
  20. package/templates/src/pages/api/sandbox.ts +86 -0
  21. package/templates/src/pages/sandbox.astro +137 -0
  22. package/templates/src/types/nodeProps.ts +1 -0
  23. package/templates/src/utils/compositor/aiPaneParser.ts +8 -2
  24. package/templates/src/utils/profileStorage.ts +13 -0
  25. package/utils/inject-files.ts +33 -14
  26. package/templates/src/components/edit/pane/AiPaneGenerator.tsx +0 -512
  27. package/templates/src/components/edit/pane/AiPanePreview.tsx +0 -107
  28. package/templates/src/utils/aai/getTitleSlug.ts +0 -72
@@ -0,0 +1,137 @@
1
+ ---
2
+ import { ulid } from 'ulid';
3
+ import Layout from '@/layouts/Layout.astro';
4
+ import Header from '@/components/Header.astro';
5
+ import { getFullContentMap } from '@/stores/analytics';
6
+ import { getBrandConfig } from '@/utils/api/brandConfig';
7
+ import { components as codeHookComponents } from '@/custom/CodeHook.astro';
8
+ import StoryKeepHeader from '@/components/edit/Header';
9
+ import StoryKeepToolBar from '@/components/edit/ToolBar';
10
+ import StoryKeepToolMode from '@/components/edit/ToolMode';
11
+ import SettingsPanel from '@/components/edit/SettingsPanel';
12
+ import { Compositor } from '@/components/compositor/Compositor';
13
+ import SandboxAuthWrapper from '@/components/codehooks/SandboxAuthWrapper.tsx';
14
+ import { preHealthCheck } from '@/utils/backend';
15
+
16
+ if (!import.meta.env.PRIVATE_SANDBOX_SECRET) {
17
+ return Astro.redirect('/');
18
+ }
19
+
20
+ const tenantId =
21
+ Astro.locals.tenant?.id || import.meta.env.PUBLIC_TENANTID || 'default';
22
+
23
+ const healthCheckRedirect = await preHealthCheck(tenantId);
24
+ if (healthCheckRedirect !== undefined) {
25
+ return healthCheckRedirect;
26
+ }
27
+
28
+ const brandConfig = await getBrandConfig(tenantId);
29
+
30
+ const emptyStoryFragment = {
31
+ id: ulid(),
32
+ nodeType: 'StoryFragment' as const,
33
+ parentId: null,
34
+ title: 'Sandbox Page',
35
+ slug: 'sandbox-page',
36
+ paneIds: [],
37
+ isChanged: false,
38
+ created: new Date(),
39
+ changed: new Date(),
40
+ };
41
+ const loadData = {
42
+ storyfragmentNodes: [emptyStoryFragment],
43
+ };
44
+ const title = 'Sandbox - TractStack Editor';
45
+ const storyFragmentID = emptyStoryFragment.id;
46
+
47
+ const fullContentMap = await getFullContentMap(tenantId);
48
+ const urlParams: Record<string, string | boolean> = {};
49
+ for (const [key, value] of Astro.url.searchParams) {
50
+ urlParams[key] = value === '' ? true : value;
51
+ }
52
+ ---
53
+
54
+ <Layout
55
+ title={title}
56
+ slug="sandbox"
57
+ brandConfig={brandConfig}
58
+ storyfragmentId={storyFragmentID}
59
+ isStoryKeep={true}
60
+ isEditor={true}
61
+ >
62
+ <SandboxAuthWrapper client:load />
63
+ <Header
64
+ title={title}
65
+ slug="sandbox"
66
+ brandConfig={brandConfig}
67
+ isContext={false}
68
+ isStoryKeep={true}
69
+ isEditable={false}
70
+ menu={null}
71
+ />
72
+
73
+ <section
74
+ id="storykeepHeader"
75
+ role="banner"
76
+ class="z-101 bg-mywhite left-0 right-0 drop-shadow transition-all duration-200"
77
+ >
78
+ <StoryKeepHeader
79
+ slug="sandbox"
80
+ isContext={false}
81
+ isSandboxMode={true}
82
+ client:only="react"
83
+ />
84
+ </section>
85
+
86
+ <div class="flex min-h-screen">
87
+ <StoryKeepToolMode isContext={false} client:only="react" />
88
+
89
+ <main id="mainContent" class="relative flex-1 overflow-x-auto">
90
+ <div class="bg-myblue/20 bg-mylightgrey h-full p-1.5">
91
+ <div
92
+ class="h-fit min-h-screen pb-96"
93
+ style={{
94
+ backgroundImage:
95
+ 'repeating-linear-gradient(135deg, transparent, transparent 10px, rgba(0,0,0,0.05) 10px, rgba(0,0,0,0.05) 20px)',
96
+ }}
97
+ >
98
+ <Compositor
99
+ id={storyFragmentID}
100
+ nodes={loadData}
101
+ config={brandConfig}
102
+ fullContentMap={fullContentMap}
103
+ fullCanonicalURL="/sandbox"
104
+ urlParams={urlParams}
105
+ availableCodeHooks={Object.keys(codeHookComponents)}
106
+ isSandboxMode={true}
107
+ client:only="react"
108
+ />
109
+ </div>
110
+ </div>
111
+ </main>
112
+ </div>
113
+
114
+ <aside
115
+ id="settingsControls"
116
+ class="z-101 pointer-events-none fixed bottom-16 right-2 flex flex-col items-end gap-2 md:bottom-2"
117
+ >
118
+ <div class="pointer-events-none flex-grow"></div>
119
+
120
+ <div class="pointer-events-auto flex-shrink-0">
121
+ <StoryKeepToolBar client:only="react" />
122
+ </div>
123
+
124
+ <div class="pointer-events-auto max-h-full">
125
+ <SettingsPanel
126
+ config={brandConfig}
127
+ availableCodeHooks={Object.keys(codeHookComponents)}
128
+ client:only="react"
129
+ />
130
+ </div>
131
+ </aside>
132
+ </Layout>
133
+
134
+ <script>
135
+ import { setupLayoutObservers } from '@/utils/layout';
136
+ document.addEventListener('astro:page-load', setupLayoutObservers);
137
+ </script>
@@ -27,6 +27,7 @@ export type NodeProps = {
27
27
  ctx?: NodesContext;
28
28
  first?: boolean;
29
29
  onDragStart?: (origin: SelectionOrigin, e: MouseEvent<HTMLElement>) => void;
30
+ isSandboxMode?: boolean;
30
31
  isSelectableText?: boolean;
31
32
  };
32
33
 
@@ -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,
@@ -148,6 +148,13 @@ export class ProfileStorage {
148
148
  StorageManager.set(this.STORAGE_KEYS.profileToken, token);
149
149
  StorageManager.set(this.STORAGE_KEYS.hasProfile, '1');
150
150
  StorageManager.set(this.STORAGE_KEYS.unlockedProfile, '1');
151
+
152
+ try {
153
+ const maxAge = 60 * 60 * 24;
154
+ document.cookie = `tractstack_profile=true; path=/; SameSite=Lax; max-age=${maxAge}`;
155
+ } catch {
156
+ // Silently fail if cookies are blocked
157
+ }
151
158
  }
152
159
 
153
160
  /**
@@ -157,6 +164,12 @@ export class ProfileStorage {
157
164
  StorageManager.remove(this.STORAGE_KEYS.profileToken);
158
165
  StorageManager.remove(this.STORAGE_KEYS.hasProfile);
159
166
  StorageManager.remove(this.STORAGE_KEYS.unlockedProfile);
167
+ try {
168
+ document.cookie =
169
+ 'tractstack_profile=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax';
170
+ } catch {
171
+ // Silently fail
172
+ }
160
173
  }
161
174
 
162
175
  /**
@@ -139,6 +139,18 @@ export async function injectTemplateFiles(
139
139
  ),
140
140
  dest: 'src/components/compositor/nodes/Pane_layout.tsx',
141
141
  },
142
+ {
143
+ src: resolve(
144
+ '../templates/src/components/codehooks/SandboxAuthWrapper.tsx'
145
+ ),
146
+ dest: 'src/components/codehooks/SandboxAuthWrapper.tsx',
147
+ },
148
+ {
149
+ src: resolve(
150
+ '../templates/src/components/codehooks/SandboxRegisterForm.tsx'
151
+ ),
152
+ dest: 'src/components/codehooks/SandboxRegisterForm.tsx',
153
+ },
142
154
  {
143
155
  src: resolve('../templates/src/components/compositor/nodes/Markdown.tsx'),
144
156
  dest: 'src/components/compositor/nodes/Markdown.tsx',
@@ -441,14 +453,6 @@ export async function injectTemplateFiles(
441
453
  ),
442
454
  dest: 'src/components/edit/pane/RestylePaneModal.tsx',
443
455
  },
444
- {
445
- src: resolve('../templates/src/components/edit/pane/AiPaneGenerator.tsx'),
446
- dest: 'src/components/edit/pane/AiPaneGenerator.tsx',
447
- },
448
- {
449
- src: resolve('../templates/src/components/edit/pane/AiPanePreview.tsx'),
450
- dest: 'src/components/edit/pane/AiPanePreview.tsx',
451
- },
452
456
  {
453
457
  src: resolve(
454
458
  '../templates/src/components/edit/pane/steps/CopyInputStep.tsx'
@@ -467,6 +471,12 @@ export async function injectTemplateFiles(
467
471
  ),
468
472
  dest: 'src/components/edit/pane/steps/AiDesignStep.tsx',
469
473
  },
474
+ {
475
+ src: resolve(
476
+ '../templates/src/components/edit/pane/steps/DirectInjectStep.tsx'
477
+ ),
478
+ dest: 'src/components/edit/pane/steps/DirectInjectStep.tsx',
479
+ },
470
480
  {
471
481
  src: resolve(
472
482
  '../templates/src/components/edit/pane/AddPanePanel_newCustomCopy.tsx'
@@ -603,12 +613,6 @@ export async function injectTemplateFiles(
603
613
  dest: 'src/stores/selection.ts',
604
614
  },
605
615
 
606
- // AAI utils
607
- {
608
- src: resolve('../templates/src/utils/aai/getTitleSlug.ts'),
609
- dest: 'src/utils/aai/getTitleSlug.ts',
610
- },
611
-
612
616
  // Compositor utils - etl
613
617
  {
614
618
  src: resolve('../templates/src/utils/etl/index.ts'),
@@ -844,6 +848,10 @@ export async function injectTemplateFiles(
844
848
  ),
845
849
  dest: 'src/pages/context/[...contextSlug]/edit.astro',
846
850
  },
851
+ {
852
+ src: resolve('../templates/src/pages/sandbox.astro'),
853
+ dest: 'src/pages/sandbox.astro',
854
+ },
847
855
  {
848
856
  src: resolve('../templates/src/pages/storykeep.astro'),
849
857
  dest: 'src/pages/storykeep.astro',
@@ -888,6 +896,10 @@ export async function injectTemplateFiles(
888
896
  src: resolve('../templates/src/pages/api/tailwind.ts'),
889
897
  dest: 'src/pages/api/tailwind.ts',
890
898
  },
899
+ {
900
+ src: resolve('../templates/src/pages/api/sandbox.ts'),
901
+ dest: 'src/pages/api/sandbox.ts',
902
+ },
891
903
 
892
904
  // Authentication Pages
893
905
  {
@@ -2156,6 +2168,13 @@ export async function injectTemplateFiles(
2156
2168
  // Example Components (Conditional)
2157
2169
  ...(config?.includeExamples
2158
2170
  ? [
2171
+ {
2172
+ src: resolve(
2173
+ '../templates/custom/with-examples/SandboxLauncher.tsx'
2174
+ ),
2175
+ dest: 'src/custom/SandboxLauncher.tsx',
2176
+ protected: true,
2177
+ },
2159
2178
  {
2160
2179
  src: resolve('../templates/custom/with-examples/CustomHero.astro'),
2161
2180
  dest: 'src/custom/CustomHero.astro',