astro-tractstack 2.3.5 → 2.4.0

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 (49) hide show
  1. package/bin/create-tractstack.js +38 -59
  2. package/dist/index.js +60 -36
  3. package/package.json +46 -9
  4. package/templates/custom/minimal/codehooks.ts +13 -0
  5. package/templates/custom/shopify/ShopifyProductGrid.tsx +4 -4
  6. package/templates/custom/shopify/ShopifyServiceList.tsx +4 -4
  7. package/templates/custom/with-examples/codehooks.ts +15 -0
  8. package/templates/src/components/codehooks/EpinetDurationSelector.tsx +38 -23
  9. package/templates/src/components/codehooks/EpinetTableView.tsx +5 -2
  10. package/templates/src/components/codehooks/EpinetWrapper.tsx +10 -5
  11. package/templates/src/components/codehooks/FeaturedArticle.astro +3 -3
  12. package/templates/src/components/codehooks/ListContent.astro +3 -3
  13. package/templates/src/components/compositor/Node.tsx +13 -2
  14. package/templates/src/components/compositor/nodes/Pane.tsx +2 -14
  15. package/templates/src/components/edit/pane/AddPanePanel.tsx +3 -2
  16. package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +35 -14
  17. package/templates/src/components/edit/pane/ConfigPanePanel.tsx +1 -1
  18. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_menu.tsx +2 -2
  19. package/templates/src/components/storykeep/Dashboard_Analytics.tsx +8 -4
  20. package/templates/src/components/storykeep/controls/content/ContentBrowser.tsx +5 -2
  21. package/templates/src/components/storykeep/state/FetchAnalytics.tsx +8 -4
  22. package/templates/src/components/storykeep/widgets/Wizard.tsx +4 -2
  23. package/templates/src/lib/codeHookHelper.ts +156 -0
  24. package/templates/src/lib/resources.ts +41 -0
  25. package/templates/src/lib/storyData.ts +1 -2
  26. package/templates/src/pages/[...slug]/edit.astro +3 -3
  27. package/templates/src/pages/[...slug].astro +76 -70
  28. package/templates/src/pages/codehooks/[...hookId].astro +18 -0
  29. package/templates/src/pages/codehooks/bunny-video.astro +9 -0
  30. package/templates/src/pages/codehooks/custom-hero.astro +6 -0
  31. package/templates/src/pages/codehooks/epinet.astro +15 -0
  32. package/templates/src/pages/codehooks/featured-article.astro +13 -0
  33. package/templates/src/pages/codehooks/get-crafting.astro +8 -0
  34. package/templates/src/pages/codehooks/list-content.astro +13 -0
  35. package/templates/src/pages/codehooks/search-widget.astro +13 -0
  36. package/templates/src/pages/codehooks/shopify-product-grid.astro +25 -0
  37. package/templates/src/pages/codehooks/shopify-service-list.astro +25 -0
  38. package/templates/src/pages/context/[...contextSlug]/edit.astro +3 -3
  39. package/templates/src/pages/context/[...contextSlug].astro +47 -10
  40. package/templates/src/pages/sandbox.astro +3 -14
  41. package/templates/src/stores/analytics.ts +77 -107
  42. package/utils/inject-files.ts +62 -37
  43. package/templates/custom/minimal/CodeHook.astro +0 -72
  44. package/templates/custom/with-examples/CodeHook.astro +0 -81
  45. package/templates/custom/with-examples/ProductCard.astro +0 -29
  46. package/templates/custom/with-examples/ProductCardWrapper.astro +0 -43
  47. package/templates/custom/with-examples/ProductGrid.astro +0 -64
  48. package/templates/src/components/codehooks/ProductCardSetup.tsx +0 -157
  49. package/templates/src/components/codehooks/ProductGridSetup.tsx +0 -279
@@ -1,10 +1,13 @@
1
1
  ---
2
2
  import Layout from '@/layouts/Layout.astro';
3
- import CodeHook from '@/custom/CodeHook.astro';
4
3
  import { getBrandConfig } from '@/utils/api/brandConfig';
5
4
  import { getFullContentMap } from '@/stores/analytics';
6
5
  import { getOrSetSessionId } from '@/lib/session';
7
6
  import { getStoryData } from '@/lib/storyData';
7
+ import {
8
+ fetchCodeHookBladesForPanes,
9
+ resolveSSRFetchOrigin,
10
+ } from '@/lib/codeHookHelper';
8
11
  import { preHealthCheck } from '@/utils/backend';
9
12
 
10
13
  const tenantId =
@@ -43,7 +46,6 @@ const storyfragmentId = storyData.id;
43
46
  const storyfragmentTitle = storyData.title || 'Untitled Story';
44
47
  const paneIds = storyData.paneIds || [];
45
48
  const codeHookTargets = storyData.codeHookTargets || {};
46
- const resourcesPayload = storyData.resourcesPayload || {};
47
49
 
48
50
  if (paneIds.length === 0) {
49
51
  console.log(`Empty Story Fragment. Redirecting to /storykeep`);
@@ -66,6 +68,14 @@ if (!brandConfig.SITE_INIT) {
66
68
  return Astro.redirect('/storykeep');
67
69
  }
68
70
 
71
+ const codeHookHtml = await fetchCodeHookBladesForPanes({
72
+ paneIds,
73
+ codeHookTargets,
74
+ tenantId,
75
+ request: Astro.request,
76
+ origin: resolveSSRFetchOrigin(Astro.url, brandConfig.SITE_URL),
77
+ });
78
+
69
79
  const ogImage =
70
80
  typeof storyData.socialImagePath === `string`
71
81
  ? storyData.socialImagePath
@@ -95,77 +105,73 @@ paneIds.forEach((paneId: string) => {
95
105
  <main id="main-content" class="w-full">
96
106
  <div class="panes-container">
97
107
  {
98
- paneIds.map((paneId: string) => (
99
- <div id={paneSlugLookup[paneId]}>
100
- <div
101
- id={`pane-${paneId}`}
102
- data-pane-id={paneId}
103
- class="pane-fragment-container"
104
- style={
105
- !codeHookTargets[paneId]
106
- ? undefined
107
- : !storyData.codeHookVisibility?.[paneId]
108
- ? 'display:none;'
109
- : 'display:block;'
110
- }
111
- hx-get={`/api/v1/fragments/panes/${paneId}`}
112
- hx-trigger="refresh"
113
- hx-swap="innerHTML scroll:none"
114
- >
115
- {codeHookTargets[paneId] ? (
116
- <div class="relative overflow-hidden">
117
- <CodeHook
118
- target={codeHookTargets[paneId]}
119
- paneId={paneId}
120
- options={(() => {
121
- const optionsStr =
122
- codeHookTargets[paneId + '-' + codeHookTargets[paneId]];
123
- return optionsStr
124
- ? { params: { options: optionsStr } }
125
- : undefined;
126
- })()}
127
- fullContentMap={fullContentMap}
128
- resourcesPayload={resourcesPayload}
129
- />
130
- <div id={`pane-${paneId}-unset`}>
131
- {Array.isArray(storyData.codeHookVisibility?.[paneId]) && (
132
- <button
133
- type="button"
134
- class="absolute right-2 top-2 z-10 rounded-full bg-white p-1.5 text-mydarkgrey hover:bg-black hover:text-white"
135
- title="Go Back"
136
- hx-post="/api/v1/state"
137
- hx-trigger="click"
138
- hx-swap="none"
139
- hx-vals={JSON.stringify({
140
- unsetBeliefIds:
141
- storyData.codeHookVisibility[paneId].join(','),
142
- paneId: paneId,
143
- })}
144
- hx-preserve="true"
145
- >
146
- <svg
147
- class="h-6 w-6"
148
- fill="none"
149
- viewBox="0 0 24 24"
150
- stroke-width="1.5"
151
- stroke="currentColor"
108
+ paneIds.map((paneId: string) => {
109
+ const hookId = codeHookTargets[paneId];
110
+ const isCodeHook = !!hookId;
111
+ return (
112
+ <div id={paneSlugLookup[paneId]}>
113
+ <div
114
+ id={`pane-${paneId}`}
115
+ data-pane-id={paneId}
116
+ class="pane-fragment-container"
117
+ style={
118
+ !isCodeHook
119
+ ? undefined
120
+ : !storyData.codeHookVisibility?.[paneId]
121
+ ? 'display:none;'
122
+ : 'display:block;'
123
+ }
124
+ hx-get={
125
+ !isCodeHook ? `/api/v1/fragments/panes/${paneId}` : undefined
126
+ }
127
+ hx-trigger={!isCodeHook ? 'refresh' : undefined}
128
+ hx-swap={!isCodeHook ? 'innerHTML scroll:none' : undefined}
129
+ >
130
+ {isCodeHook ? (
131
+ <div class="relative overflow-hidden">
132
+ <Fragment set:html={codeHookHtml[paneId] || ''} />
133
+ <div id={`pane-${paneId}-unset`}>
134
+ {Array.isArray(
135
+ storyData.codeHookVisibility?.[paneId]
136
+ ) && (
137
+ <button
138
+ type="button"
139
+ class="absolute right-2 top-2 z-10 rounded-full bg-white p-1.5 text-mydarkgrey hover:bg-black hover:text-white"
140
+ title="Go Back"
141
+ hx-post="/api/v1/state"
142
+ hx-trigger="click"
143
+ hx-swap="none"
144
+ hx-vals={JSON.stringify({
145
+ unsetBeliefIds:
146
+ storyData.codeHookVisibility[paneId].join(','),
147
+ paneId: paneId,
148
+ })}
149
+ hx-preserve="true"
152
150
  >
153
- <path
154
- stroke-linecap="round"
155
- stroke-linejoin="round"
156
- d="M9 15L3 9m0 0l6-6M3 9h12a6 6 0 010 12h-3"
157
- />
158
- </svg>
159
- </button>
160
- )}
151
+ <svg
152
+ class="h-6 w-6"
153
+ fill="none"
154
+ viewBox="0 0 24 24"
155
+ stroke-width="1.5"
156
+ stroke="currentColor"
157
+ >
158
+ <path
159
+ stroke-linecap="round"
160
+ stroke-linejoin="round"
161
+ d="M9 15L3 9m0 0l6-6M3 9h12a6 6 0 010 12h-3"
162
+ />
163
+ </svg>
164
+ </button>
165
+ )}
166
+ </div>
161
167
  </div>
162
- </div>
163
- ) : (
164
- <Fragment set:html={storyData.fragments[paneId] || ''} />
165
- )}
168
+ ) : (
169
+ <Fragment set:html={storyData.fragments[paneId] || ''} />
170
+ )}
171
+ </div>
166
172
  </div>
167
- </div>
168
- ))
173
+ );
174
+ })
169
175
  }
170
176
  </div>
171
177
  </main>
@@ -0,0 +1,18 @@
1
+ ---
2
+ export const partial = true;
3
+ const hookId = Astro.params.hookId || '';
4
+ ---
5
+
6
+ <div class="my-4 w-full bg-gray-50 p-6">
7
+ <div
8
+ class="mb-4 rounded-lg border-2 border-dashed border-gray-300 bg-slate-50 p-6"
9
+ >
10
+ <h3 class="text-lg text-gray-700">
11
+ Code Hook:{' '}
12
+ <span class="font-action font-bold">{hookId || 'unknown'}</span>
13
+ </h3>
14
+ <p class="mt-2 text-sm text-gray-500">
15
+ CodeHook not activated for this install.
16
+ </p>
17
+ </div>
18
+ </div>
@@ -0,0 +1,9 @@
1
+ ---
2
+ export const partial = true;
3
+ import BunnyVideoWrapper from '@/components/codehooks/BunnyVideoWrapper.astro';
4
+ import { parseCodeHookBladeContext } from '@/lib/codeHookHelper';
5
+
6
+ const { options } = parseCodeHookBladeContext(Astro.url.searchParams);
7
+ ---
8
+
9
+ <BunnyVideoWrapper options={options} />
@@ -0,0 +1,6 @@
1
+ ---
2
+ export const partial = true;
3
+ import CustomHero from '@/custom/CustomHero.astro';
4
+ ---
5
+
6
+ <CustomHero />
@@ -0,0 +1,15 @@
1
+ ---
2
+ export const partial = true;
3
+ import EpinetWrapper from '@/components/codehooks/EpinetWrapper';
4
+ import {
5
+ parseCodeHookBladeContext,
6
+ resolveCodeHookFullContentMap,
7
+ } from '@/lib/codeHookHelper';
8
+
9
+ const { tenantId } = parseCodeHookBladeContext(Astro.url.searchParams);
10
+ const fullContentMap = await resolveCodeHookFullContentMap(tenantId);
11
+ ---
12
+
13
+ <div style="min-height: 400px;">
14
+ <EpinetWrapper fullContentMap={fullContentMap} client:only="react" />
15
+ </div>
@@ -0,0 +1,13 @@
1
+ ---
2
+ export const partial = true;
3
+ import FeaturedArticle from '@/components/codehooks/FeaturedArticle.astro';
4
+ import {
5
+ parseCodeHookBladeContext,
6
+ resolveCodeHookFullContentMap,
7
+ } from '@/lib/codeHookHelper';
8
+
9
+ const { tenantId, options } = parseCodeHookBladeContext(Astro.url.searchParams);
10
+ const fullContentMap = await resolveCodeHookFullContentMap(tenantId);
11
+ ---
12
+
13
+ <FeaturedArticle options={options} fullContentMap={fullContentMap} />
@@ -0,0 +1,8 @@
1
+ ---
2
+ export const partial = true;
3
+ import SandboxLauncher from '@/custom/SandboxLauncher';
4
+ ---
5
+
6
+ <div style="min-height: 400px;">
7
+ <SandboxLauncher client:only="react" />
8
+ </div>
@@ -0,0 +1,13 @@
1
+ ---
2
+ export const partial = true;
3
+ import ListContent from '@/components/codehooks/ListContent.astro';
4
+ import {
5
+ parseCodeHookBladeContext,
6
+ resolveCodeHookFullContentMap,
7
+ } from '@/lib/codeHookHelper';
8
+
9
+ const { tenantId, options } = parseCodeHookBladeContext(Astro.url.searchParams);
10
+ const fullContentMap = await resolveCodeHookFullContentMap(tenantId);
11
+ ---
12
+
13
+ <ListContent options={options} fullContentMap={fullContentMap} />
@@ -0,0 +1,13 @@
1
+ ---
2
+ export const partial = true;
3
+ import SearchWidget from '@/components/codehooks/SearchWidget.tsx';
4
+ import {
5
+ parseCodeHookBladeContext,
6
+ resolveCodeHookFullContentMap,
7
+ } from '@/lib/codeHookHelper';
8
+
9
+ const { tenantId } = parseCodeHookBladeContext(Astro.url.searchParams);
10
+ const fullContentMap = await resolveCodeHookFullContentMap(tenantId);
11
+ ---
12
+
13
+ <SearchWidget fullContentMap={fullContentMap} client:load />
@@ -0,0 +1,25 @@
1
+ ---
2
+ export const partial = true;
3
+ import ShopifyProductGrid from '@/custom/shopify/ShopifyProductGrid';
4
+ import {
5
+ parseCodeHookBladeContext,
6
+ resolveCodeHookResources,
7
+ } from '@/lib/codeHookHelper';
8
+
9
+ const { tenantId, optionsStr, options } = parseCodeHookBladeContext(
10
+ Astro.url.searchParams
11
+ );
12
+ const resources = await resolveCodeHookResources(
13
+ tenantId,
14
+ optionsStr,
15
+ 'shopify-product-grid'
16
+ );
17
+ ---
18
+
19
+ <div style="min-height: 400px;">
20
+ <ShopifyProductGrid
21
+ options={options}
22
+ resources={resources}
23
+ client:only="react"
24
+ />
25
+ </div>
@@ -0,0 +1,25 @@
1
+ ---
2
+ export const partial = true;
3
+ import ShopifyServiceList from '@/custom/shopify/ShopifyServiceList';
4
+ import {
5
+ parseCodeHookBladeContext,
6
+ resolveCodeHookResources,
7
+ } from '@/lib/codeHookHelper';
8
+
9
+ const { tenantId, optionsStr, options } = parseCodeHookBladeContext(
10
+ Astro.url.searchParams
11
+ );
12
+ const resources = await resolveCodeHookResources(
13
+ tenantId,
14
+ optionsStr,
15
+ 'shopify-service-list'
16
+ );
17
+ ---
18
+
19
+ <div style="min-height: 300px;">
20
+ <ShopifyServiceList
21
+ options={options}
22
+ resources={resources}
23
+ client:only="react"
24
+ />
25
+ </div>
@@ -6,7 +6,7 @@ import { getFullContentMap } from '@/stores/analytics';
6
6
  import { getBrandConfig } from '@/utils/api/brandConfig';
7
7
  import { joinUrlPaths } from '@/utils/helpers';
8
8
  import { handleFailedResponse } from '@/utils/backend';
9
- import { components as codeHookComponents } from '@/custom/CodeHook.astro';
9
+ import { availableCodeHookIds } from '@/custom/codehooks';
10
10
  import StoryKeepHeader from '@/components/edit/Header';
11
11
  import StoryKeepToolBar from '@/components/edit/ToolBar';
12
12
  import StoryKeepToolMode from '@/components/edit/ToolMode';
@@ -175,7 +175,7 @@ for (const [key, value] of Astro.url.searchParams) {
175
175
  fullContentMap={fullContentMap}
176
176
  fullCanonicalURL={canonicalURL}
177
177
  urlParams={urlParams}
178
- availableCodeHooks={Object.keys(codeHookComponents)}
178
+ availableCodeHooks={availableCodeHookIds}
179
179
  client:only="react"
180
180
  />
181
181
  </div>
@@ -201,7 +201,7 @@ for (const [key, value] of Astro.url.searchParams) {
201
201
  <div class="pointer-events-auto max-h-full">
202
202
  <SettingsPanel
203
203
  config={brandConfig}
204
- availableCodeHooks={Object.keys(codeHookComponents)}
204
+ availableCodeHooks={availableCodeHookIds}
205
205
  client:only="react"
206
206
  />
207
207
  </div>
@@ -1,6 +1,12 @@
1
1
  ---
2
2
  import Layout from '@/layouts/Layout.astro';
3
3
  import { getBrandConfig } from '@/utils/api/brandConfig';
4
+ import { getFullContentMap } from '@/stores/analytics';
5
+ import {
6
+ buildCodeHookBladePath,
7
+ fetchCodeHookBladeHtml,
8
+ resolveSSRFetchOrigin,
9
+ } from '@/lib/codeHookHelper';
4
10
  import { handleFailedResponse } from '@/utils/backend';
5
11
  import { preHealthCheck } from '@/utils/backend';
6
12
 
@@ -56,6 +62,8 @@ try {
56
62
 
57
63
  const paneId = contextPaneData.id;
58
64
  const paneTitle = contextPaneData.title || 'Context';
65
+ const codeHookTarget = contextPaneData.codeHookTarget;
66
+ const codeHookOptions = contextPaneData.codeHookPayload?.options;
59
67
 
60
68
  // Get rendered fragment for the context pane
61
69
  let fragmentData = '';
@@ -94,6 +102,23 @@ const brandConfig = await getBrandConfig(tenantId);
94
102
  if (!brandConfig.SITE_INIT) {
95
103
  return Astro.redirect('/storykeep');
96
104
  }
105
+
106
+ // Warm the in-process content-map store before SSR-fetching the codehook blade.
107
+ await getFullContentMap(tenantId);
108
+
109
+ let codeHookBladeHtml = '';
110
+ if (codeHookTarget) {
111
+ const bladePath = buildCodeHookBladePath(codeHookTarget, {
112
+ paneId,
113
+ tenantId,
114
+ optionsStr: codeHookOptions || '',
115
+ });
116
+ codeHookBladeHtml = await fetchCodeHookBladeHtml(
117
+ bladePath,
118
+ Astro.request,
119
+ resolveSSRFetchOrigin(Astro.url, brandConfig.SITE_URL)
120
+ );
121
+ }
97
122
  ---
98
123
 
99
124
  <Layout
@@ -108,16 +133,28 @@ if (!brandConfig.SITE_INIT) {
108
133
  storyfragmentId={paneId}
109
134
  >
110
135
  <main id="main-content" class="w-full">
111
- <div
112
- id={`pane-${paneId}`}
113
- data-pane-id={paneId}
114
- class="context-pane-container"
115
- hx-get={`/api/v1/fragments/panes/${paneId}`}
116
- hx-trigger="refresh"
117
- hx-swap="innerHTML scroll:none"
118
- >
119
- <Fragment set:html={fragmentData} />
120
- </div>
136
+ {
137
+ codeHookTarget ? (
138
+ <div
139
+ id={`pane-${paneId}`}
140
+ data-pane-id={paneId}
141
+ class="context-pane-container"
142
+ >
143
+ <Fragment set:html={codeHookBladeHtml} />
144
+ </div>
145
+ ) : (
146
+ <div
147
+ id={`pane-${paneId}`}
148
+ data-pane-id={paneId}
149
+ class="context-pane-container"
150
+ hx-get={`/api/v1/fragments/panes/${paneId}`}
151
+ hx-trigger="refresh"
152
+ hx-swap="innerHTML scroll:none"
153
+ >
154
+ <Fragment set:html={fragmentData} />
155
+ </div>
156
+ )
157
+ }
121
158
 
122
159
  <div class="py-12 text-center text-2xl md:text-3xl">
123
160
  <button
@@ -5,9 +5,7 @@ import Layout from '@/layouts/Layout.astro';
5
5
  import Header from '@/components/Header.astro';
6
6
  import { getFullContentMap } from '@/stores/analytics';
7
7
  import { getBrandConfig } from '@/utils/api/brandConfig';
8
- import CodeHook, {
9
- components as codeHookComponents,
10
- } from '@/custom/CodeHook.astro';
8
+ import { availableCodeHookIds } from '@/custom/codehooks';
11
9
  import StoryKeepHeader from '@/components/edit/Header';
12
10
  import StoryKeepToolBar from '@/components/edit/ToolBar';
13
11
  import StoryKeepToolMode from '@/components/edit/ToolMode';
@@ -64,15 +62,6 @@ const sandboxToken = `${timestamp}.${signature}`;
64
62
  isStoryKeep={true}
65
63
  isEditor={true}
66
64
  >
67
- <CodeHook
68
- target="get-tractstack"
69
- options={{
70
- params: {
71
- options: JSON.stringify({ isEmbedded: false }),
72
- },
73
- }}
74
- />
75
-
76
65
  <Header
77
66
  title={title}
78
67
  slug="sandbox"
@@ -115,7 +104,7 @@ const sandboxToken = `${timestamp}.${signature}`;
115
104
  fullContentMap={fullContentMap}
116
105
  fullCanonicalURL="/sandbox"
117
106
  urlParams={urlParams}
118
- availableCodeHooks={Object.keys(codeHookComponents)}
107
+ availableCodeHooks={availableCodeHookIds}
119
108
  isSandboxMode={true}
120
109
  sandboxToken={sandboxToken}
121
110
  client:only="react"
@@ -137,7 +126,7 @@ const sandboxToken = `${timestamp}.${signature}`;
137
126
 
138
127
  <div class="pointer-events-auto max-h-full">
139
128
  <SettingsPanel
140
- availableCodeHooks={Object.keys(codeHookComponents)}
129
+ availableCodeHooks={availableCodeHookIds}
141
130
  client:only="react"
142
131
  />
143
132
  </div>