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.
- package/bin/create-tractstack.js +38 -59
- package/dist/index.js +60 -36
- package/package.json +46 -9
- package/templates/custom/minimal/codehooks.ts +13 -0
- package/templates/custom/shopify/ShopifyProductGrid.tsx +4 -4
- package/templates/custom/shopify/ShopifyServiceList.tsx +4 -4
- package/templates/custom/with-examples/codehooks.ts +15 -0
- package/templates/src/components/codehooks/EpinetDurationSelector.tsx +38 -23
- package/templates/src/components/codehooks/EpinetTableView.tsx +5 -2
- package/templates/src/components/codehooks/EpinetWrapper.tsx +10 -5
- package/templates/src/components/codehooks/FeaturedArticle.astro +3 -3
- package/templates/src/components/codehooks/ListContent.astro +3 -3
- package/templates/src/components/compositor/Node.tsx +13 -2
- package/templates/src/components/compositor/nodes/Pane.tsx +2 -14
- package/templates/src/components/edit/pane/AddPanePanel.tsx +3 -2
- package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +35 -14
- package/templates/src/components/edit/pane/ConfigPanePanel.tsx +1 -1
- package/templates/src/components/edit/storyfragment/StoryFragmentPanel_menu.tsx +2 -2
- package/templates/src/components/storykeep/Dashboard_Analytics.tsx +8 -4
- package/templates/src/components/storykeep/controls/content/ContentBrowser.tsx +5 -2
- package/templates/src/components/storykeep/state/FetchAnalytics.tsx +8 -4
- package/templates/src/components/storykeep/widgets/Wizard.tsx +4 -2
- package/templates/src/lib/codeHookHelper.ts +156 -0
- package/templates/src/lib/resources.ts +41 -0
- package/templates/src/lib/storyData.ts +1 -2
- package/templates/src/pages/[...slug]/edit.astro +3 -3
- package/templates/src/pages/[...slug].astro +76 -70
- package/templates/src/pages/codehooks/[...hookId].astro +18 -0
- package/templates/src/pages/codehooks/bunny-video.astro +9 -0
- package/templates/src/pages/codehooks/custom-hero.astro +6 -0
- package/templates/src/pages/codehooks/epinet.astro +15 -0
- package/templates/src/pages/codehooks/featured-article.astro +13 -0
- package/templates/src/pages/codehooks/get-crafting.astro +8 -0
- package/templates/src/pages/codehooks/list-content.astro +13 -0
- package/templates/src/pages/codehooks/search-widget.astro +13 -0
- package/templates/src/pages/codehooks/shopify-product-grid.astro +25 -0
- package/templates/src/pages/codehooks/shopify-service-list.astro +25 -0
- package/templates/src/pages/context/[...contextSlug]/edit.astro +3 -3
- package/templates/src/pages/context/[...contextSlug].astro +47 -10
- package/templates/src/pages/sandbox.astro +3 -14
- package/templates/src/stores/analytics.ts +77 -107
- package/utils/inject-files.ts +62 -37
- package/templates/custom/minimal/CodeHook.astro +0 -72
- package/templates/custom/with-examples/CodeHook.astro +0 -81
- package/templates/custom/with-examples/ProductCard.astro +0 -29
- package/templates/custom/with-examples/ProductCardWrapper.astro +0 -43
- package/templates/custom/with-examples/ProductGrid.astro +0 -64
- package/templates/src/components/codehooks/ProductCardSetup.tsx +0 -157
- package/templates/src/components/codehooks/ProductGridSetup.tsx +0 -279
|
@@ -149,6 +149,7 @@ export const Node = memo((props: NodeProps) => {
|
|
|
149
149
|
first={true}
|
|
150
150
|
ctx={ctx}
|
|
151
151
|
isContextPane={true}
|
|
152
|
+
isSandboxMode={props.isSandboxMode || false}
|
|
152
153
|
/>
|
|
153
154
|
</>
|
|
154
155
|
);
|
|
@@ -201,7 +202,12 @@ export const Node = memo((props: NodeProps) => {
|
|
|
201
202
|
element = (
|
|
202
203
|
<>
|
|
203
204
|
{first && (
|
|
204
|
-
<AddPanePanel
|
|
205
|
+
<AddPanePanel
|
|
206
|
+
nodeId={props.nodeId}
|
|
207
|
+
first={true}
|
|
208
|
+
ctx={ctx}
|
|
209
|
+
isSandboxMode={props.isSandboxMode || false}
|
|
210
|
+
/>
|
|
205
211
|
)}
|
|
206
212
|
<div className="py-0.5">
|
|
207
213
|
<ConfigPanePanel
|
|
@@ -217,7 +223,12 @@ export const Node = memo((props: NodeProps) => {
|
|
|
217
223
|
{content}
|
|
218
224
|
</PanelVisibilityWrapper>
|
|
219
225
|
</div>
|
|
220
|
-
<AddPanePanel
|
|
226
|
+
<AddPanePanel
|
|
227
|
+
nodeId={props.nodeId}
|
|
228
|
+
first={false}
|
|
229
|
+
ctx={ctx}
|
|
230
|
+
isSandboxMode={props.isSandboxMode || false}
|
|
231
|
+
/>
|
|
221
232
|
</>
|
|
222
233
|
);
|
|
223
234
|
}
|
|
@@ -5,8 +5,6 @@ import { RenderChildren } from './RenderChildren';
|
|
|
5
5
|
import FeaturedArticleSetup from '@/components/codehooks/FeaturedArticleSetup';
|
|
6
6
|
import ListContentSetup from '@/components/codehooks/ListContentSetup';
|
|
7
7
|
import BunnyVideoSetup from '@/components/codehooks/BunnyVideoSetup';
|
|
8
|
-
import { ProductCardSetup } from '@/components/codehooks/ProductCardSetup';
|
|
9
|
-
import { ProductGridSetup } from '@/components/codehooks/ProductGridSetup';
|
|
10
8
|
import { PaneOverlay } from '@/components/compositor/tools/PaneOverlay';
|
|
11
9
|
import type {
|
|
12
10
|
PaneNode,
|
|
@@ -15,13 +13,7 @@ import type {
|
|
|
15
13
|
} from '@/types/compositorTypes';
|
|
16
14
|
import type { NodeProps } from '@/types/nodeProps';
|
|
17
15
|
|
|
18
|
-
const TARGETS = [
|
|
19
|
-
'list-content',
|
|
20
|
-
'featured-article',
|
|
21
|
-
'bunny-video',
|
|
22
|
-
'product-card',
|
|
23
|
-
'product-grid',
|
|
24
|
-
];
|
|
16
|
+
const TARGETS = ['list-content', 'featured-article', 'bunny-video'];
|
|
25
17
|
|
|
26
18
|
const CodeHookContainer = ({
|
|
27
19
|
payload,
|
|
@@ -171,11 +163,7 @@ const Pane = memo(
|
|
|
171
163
|
id={getCtx(props).getNodeSlug(props.nodeId)}
|
|
172
164
|
className={useFlexLayout ? '' : wrapperClasses}
|
|
173
165
|
>
|
|
174
|
-
{codeHookPayload && codeHookTarget === '
|
|
175
|
-
<ProductCardSetup nodeId={props.nodeId} params={codeHookParams} />
|
|
176
|
-
) : codeHookPayload && codeHookTarget === 'product-grid' ? (
|
|
177
|
-
<ProductGridSetup nodeId={props.nodeId} params={codeHookParams} />
|
|
178
|
-
) : codeHookPayload && codeHookTarget === 'featured-article' ? (
|
|
166
|
+
{codeHookPayload && codeHookTarget === 'featured-article' ? (
|
|
179
167
|
<FeaturedArticleSetup
|
|
180
168
|
nodeId={props.nodeId}
|
|
181
169
|
params={codeHookParams}
|
|
@@ -94,13 +94,14 @@ const AddPanePanel = ({
|
|
|
94
94
|
/>
|
|
95
95
|
) : mode === PaneAddMode.REUSE && !isContextPane ? (
|
|
96
96
|
<AddPaneReUsePanel nodeId={nodeId} first={first} setMode={setMode} />
|
|
97
|
-
) : mode === PaneAddMode.CODEHOOK
|
|
97
|
+
) : mode === PaneAddMode.CODEHOOK ? (
|
|
98
98
|
<AddPaneCodeHookPanel
|
|
99
99
|
nodeId={nodeId}
|
|
100
100
|
first={first}
|
|
101
101
|
setMode={setMode}
|
|
102
102
|
isStoryFragment={isStoryFragment}
|
|
103
103
|
isContextPane={isContextPane}
|
|
104
|
+
isSandboxMode={isSandboxMode}
|
|
104
105
|
/>
|
|
105
106
|
) : mode === PaneAddMode.PASTE ? (
|
|
106
107
|
<AddPanePanel_paste
|
|
@@ -139,7 +140,7 @@ const AddPanePanel = ({
|
|
|
139
140
|
)}
|
|
140
141
|
</>
|
|
141
142
|
)}
|
|
142
|
-
{!isTemplate &&
|
|
143
|
+
{!isTemplate && (
|
|
143
144
|
<button
|
|
144
145
|
onClick={() => setMode(PaneAddMode.CODEHOOK)}
|
|
145
146
|
className="rounded bg-white px-2 py-1 text-sm text-cyan-700 shadow-sm transition-colors hover:bg-cyan-700 hover:text-white"
|
|
@@ -5,7 +5,11 @@ import { Combobox } from '@ark-ui/react';
|
|
|
5
5
|
import { createListCollection } from '@ark-ui/react/collection';
|
|
6
6
|
import ChevronUpDownIcon from '@heroicons/react/20/solid/ChevronUpDownIcon';
|
|
7
7
|
import CheckIcon from '@heroicons/react/20/solid/CheckIcon';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
brandConfigStore,
|
|
10
|
+
codehookMapStore,
|
|
11
|
+
fullContentMapStore,
|
|
12
|
+
} from '@/stores/storykeep';
|
|
9
13
|
import { getCtx } from '@/stores/nodes';
|
|
10
14
|
import { findUniqueSlug } from '@/utils/helpers';
|
|
11
15
|
import { PaneAddMode, type TemplatePane } from '@/types/compositorTypes';
|
|
@@ -16,6 +20,7 @@ interface AddPaneCodeHookPanelProps {
|
|
|
16
20
|
setMode: (mode: PaneAddMode) => void;
|
|
17
21
|
isStoryFragment?: boolean;
|
|
18
22
|
isContextPane?: boolean;
|
|
23
|
+
isSandboxMode?: boolean;
|
|
19
24
|
}
|
|
20
25
|
|
|
21
26
|
const AddPaneCodeHookPanel = ({
|
|
@@ -24,10 +29,13 @@ const AddPaneCodeHookPanel = ({
|
|
|
24
29
|
setMode,
|
|
25
30
|
isStoryFragment = false,
|
|
26
31
|
isContextPane = false,
|
|
32
|
+
isSandboxMode = false,
|
|
27
33
|
}: AddPaneCodeHookPanelProps) => {
|
|
28
34
|
const [selected, setSelected] = useState<string | null>(null);
|
|
29
35
|
const [query, setQuery] = useState('');
|
|
30
36
|
const $contentMap = useStore(fullContentMapStore);
|
|
37
|
+
const brandConfig = useStore(brandConfigStore);
|
|
38
|
+
const hasShopify = brandConfig?.HAS_SHOPIFY === true;
|
|
31
39
|
|
|
32
40
|
const existingSlugs = $contentMap
|
|
33
41
|
.filter((item) => ['Pane', 'StoryFragment'].includes(item.type))
|
|
@@ -39,9 +47,22 @@ const AddPaneCodeHookPanel = ({
|
|
|
39
47
|
|
|
40
48
|
const availableCodeHooks = codehookMapStore.get();
|
|
41
49
|
|
|
42
|
-
|
|
50
|
+
const isHookVisibleInPicker = (hookName: string) => {
|
|
51
|
+
if (
|
|
52
|
+
(hookName === 'shopify-product-grid' ||
|
|
53
|
+
hookName === 'shopify-service-list') &&
|
|
54
|
+
!hasShopify
|
|
55
|
+
) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
if (hookName === 'get-crafting' && !isSandboxMode) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
return true;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Filter hooks based on search query and tenant/sandbox gates
|
|
43
65
|
const filteredHooks = useMemo(() => {
|
|
44
|
-
// Start with available hooks
|
|
45
66
|
const hooks =
|
|
46
67
|
query === ''
|
|
47
68
|
? [...availableCodeHooks]
|
|
@@ -49,9 +70,8 @@ const AddPaneCodeHookPanel = ({
|
|
|
49
70
|
hook.toLowerCase().includes(query.toLowerCase())
|
|
50
71
|
);
|
|
51
72
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}, [availableCodeHooks, query]);
|
|
73
|
+
return hooks.filter(isHookVisibleInPicker);
|
|
74
|
+
}, [availableCodeHooks, query, hasShopify, isSandboxMode]);
|
|
55
75
|
|
|
56
76
|
// Create collection for Ark UI Combobox
|
|
57
77
|
const collection = useMemo(() => {
|
|
@@ -64,21 +84,22 @@ const AddPaneCodeHookPanel = ({
|
|
|
64
84
|
|
|
65
85
|
const isHookAvailable = (hookName: string) => {
|
|
66
86
|
if (
|
|
67
|
-
(hookName === '
|
|
68
|
-
hookName === 'list-content' ||
|
|
69
|
-
hookName === 'featured-article') &&
|
|
87
|
+
(hookName === 'list-content' || hookName === 'featured-article') &&
|
|
70
88
|
!hasStoryFragments
|
|
71
89
|
) {
|
|
72
|
-
return
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
if (
|
|
93
|
+
hookName === 'bunny-video' &&
|
|
94
|
+
import.meta.env.PUBLIC_ENABLE_BUNNY !== 'true'
|
|
95
|
+
) {
|
|
96
|
+
return false;
|
|
73
97
|
}
|
|
74
98
|
return true;
|
|
75
99
|
};
|
|
76
100
|
|
|
77
101
|
const getDisplayName = (hookName: string) => {
|
|
78
|
-
if (
|
|
79
|
-
(hookName === 'featured-content' || hookName === 'list-content') &&
|
|
80
|
-
!hasStoryFragments
|
|
81
|
-
) {
|
|
102
|
+
if (hookName === 'list-content' && !hasStoryFragments) {
|
|
82
103
|
return `${hookName} (not yet available; no pages found)`;
|
|
83
104
|
}
|
|
84
105
|
return hookName;
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
type MenuNode,
|
|
14
14
|
} from '@/types/compositorTypes';
|
|
15
15
|
import MenuForm from '@/components/storykeep/controls/content/MenuForm';
|
|
16
|
-
import {
|
|
16
|
+
import { getFullContentMap, setTenantFullContentMap } from '@/stores/analytics';
|
|
17
17
|
import type { FullContentMapItem } from '@/types/tractstack';
|
|
18
18
|
|
|
19
19
|
interface StoryFragmentMenuPanelProps {
|
|
@@ -53,7 +53,7 @@ const StoryFragmentMenuPanel = ({
|
|
|
53
53
|
if (!contentMap) {
|
|
54
54
|
const currentContentMap = await api.getContentMapWithTimestamp();
|
|
55
55
|
if (currentContentMap.success && currentContentMap.data) {
|
|
56
|
-
|
|
56
|
+
setTenantFullContentMap(tenantId, currentContentMap.data);
|
|
57
57
|
setContentMap(currentContentMap.data.data);
|
|
58
58
|
}
|
|
59
59
|
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { useState, useCallback, useMemo, Component } from 'react';
|
|
2
2
|
import type { ReactNode } from 'react';
|
|
3
3
|
import { useStore } from '@nanostores/react';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
epinetCustomFilters,
|
|
6
|
+
getEpinetCustomFilters,
|
|
7
|
+
setEpinetCustomFilters,
|
|
8
|
+
} from '@/stores/analytics';
|
|
5
9
|
import { classNames } from '@/utils/helpers';
|
|
6
10
|
import ArrowDownTrayIcon from '@heroicons/react/24/outline/ArrowDownTrayIcon';
|
|
7
11
|
import DashboardActivity from './Dashboard_Activity';
|
|
@@ -167,7 +171,7 @@ export default function StoryKeepDashboard_Analytics({
|
|
|
167
171
|
|
|
168
172
|
const handleBeliefFilterChange = (beliefSlug: string, value: string) => {
|
|
169
173
|
const tenantId = window.TRACTSTACK_CONFIG?.tenantId || 'default';
|
|
170
|
-
const currentFilters =
|
|
174
|
+
const currentFilters = getEpinetCustomFilters();
|
|
171
175
|
let newFilters = [...(currentFilters.appliedFilters || [])];
|
|
172
176
|
|
|
173
177
|
if (value === 'All') {
|
|
@@ -183,7 +187,7 @@ export default function StoryKeepDashboard_Analytics({
|
|
|
183
187
|
}
|
|
184
188
|
}
|
|
185
189
|
|
|
186
|
-
|
|
190
|
+
setEpinetCustomFilters(tenantId, {
|
|
187
191
|
...currentFilters,
|
|
188
192
|
appliedFilters: newFilters,
|
|
189
193
|
});
|
|
@@ -220,7 +224,7 @@ export default function StoryKeepDashboard_Analytics({
|
|
|
220
224
|
nowUTC.getTime() - hoursBack * 60 * 60 * 1000
|
|
221
225
|
);
|
|
222
226
|
|
|
223
|
-
|
|
227
|
+
setEpinetCustomFilters(window.TRACTSTACK_CONFIG?.tenantId || 'default', {
|
|
224
228
|
...$epinetCustomFilters,
|
|
225
229
|
enabled: true,
|
|
226
230
|
startTimeUTC: startTimeUTC.toISOString(),
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { useState, useEffect, useRef } from 'react';
|
|
2
2
|
import { Switch } from '@ark-ui/react';
|
|
3
3
|
import { useStore } from '@nanostores/react';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
epinetCustomFilters,
|
|
6
|
+
setEpinetCustomFilters,
|
|
7
|
+
} from '@/stores/analytics';
|
|
5
8
|
import { classNames } from '@/utils/helpers';
|
|
6
9
|
import type { FullContentMapItem } from '@/types/tractstack';
|
|
7
10
|
|
|
@@ -104,7 +107,7 @@ const ContentBrowser = ({
|
|
|
104
107
|
const setStandardDuration = (hours: number) => {
|
|
105
108
|
const nowUTC = new Date();
|
|
106
109
|
const startTimeUTC = new Date(nowUTC.getTime() - hours * 60 * 60 * 1000);
|
|
107
|
-
|
|
110
|
+
setEpinetCustomFilters(window.TRACTSTACK_CONFIG?.tenantId || 'default', {
|
|
108
111
|
...$epinetCustomFilters,
|
|
109
112
|
enabled: true,
|
|
110
113
|
startTimeUTC: startTimeUTC.toISOString(),
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { useEffect, useRef } from 'react';
|
|
2
2
|
import { useStore } from '@nanostores/react';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
epinetCustomFilters,
|
|
5
|
+
getEpinetCustomFilters,
|
|
6
|
+
setEpinetCustomFilters,
|
|
7
|
+
} from '@/stores/analytics';
|
|
4
8
|
import { TractStackAPI } from '@/utils/api';
|
|
5
9
|
|
|
6
10
|
const VERBOSE = false;
|
|
@@ -252,7 +256,7 @@ class AnalyticsService {
|
|
|
252
256
|
this.setCachedResponse(cacheKey, analyticsData);
|
|
253
257
|
onUpdate(analyticsData);
|
|
254
258
|
|
|
255
|
-
|
|
259
|
+
setEpinetCustomFilters(window.TRACTSTACK_CONFIG?.tenantId || 'default', {
|
|
256
260
|
...filters,
|
|
257
261
|
availableFilters: data.availableFilters || [],
|
|
258
262
|
});
|
|
@@ -298,9 +302,9 @@ class AnalyticsService {
|
|
|
298
302
|
if (VERBOSE) console.log('🏁 Initializing analytics filters');
|
|
299
303
|
const nowUTC = new Date();
|
|
300
304
|
const oneWeekAgoUTC = new Date(nowUTC.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
301
|
-
const current =
|
|
305
|
+
const current = getEpinetCustomFilters();
|
|
302
306
|
if (!current.enabled) {
|
|
303
|
-
|
|
307
|
+
setEpinetCustomFilters(tenantId, {
|
|
304
308
|
enabled: true,
|
|
305
309
|
visitorType: 'all',
|
|
306
310
|
selectedUserId: null,
|
|
@@ -139,7 +139,7 @@ export default function Wizard({
|
|
|
139
139
|
const buildWizardData = async () => {
|
|
140
140
|
try {
|
|
141
141
|
const homePage = activeContentMap.find(
|
|
142
|
-
(item) => item.slug === homeSlug
|
|
142
|
+
(item: FullContentMapItem) => item.slug === homeSlug
|
|
143
143
|
);
|
|
144
144
|
|
|
145
145
|
let homeData = null;
|
|
@@ -173,7 +173,9 @@ export default function Wizard({
|
|
|
173
173
|
hasPanes: !!homePage?.panes?.length,
|
|
174
174
|
hasSeo: !!homePage?.description,
|
|
175
175
|
hasMenu: !!homeData?.menuId,
|
|
176
|
-
hasAnyMenu: activeContentMap.some(
|
|
176
|
+
hasAnyMenu: activeContentMap.some(
|
|
177
|
+
(item: FullContentMapItem) => item.type === 'Menu'
|
|
178
|
+
),
|
|
177
179
|
};
|
|
178
180
|
|
|
179
181
|
setWizardData(data);
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { getCachedFullContentMap, getFullContentMap } from '@/stores/analytics';
|
|
2
|
+
import { getCodeHookResources } from '@/lib/resources';
|
|
3
|
+
import type { ResourceNode } from '@/types/compositorTypes';
|
|
4
|
+
|
|
5
|
+
export interface CodeHookBladeContext {
|
|
6
|
+
tenantId: string;
|
|
7
|
+
paneId: string;
|
|
8
|
+
optionsStr: string;
|
|
9
|
+
options?: { params: { options: string } };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface CodeHookResourceFilters {
|
|
13
|
+
categories: string[];
|
|
14
|
+
slugs: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function parseCodeHookBladeContext(
|
|
18
|
+
searchParams: URLSearchParams
|
|
19
|
+
): CodeHookBladeContext {
|
|
20
|
+
const tenantId = searchParams.get('tenantId') || 'default';
|
|
21
|
+
const paneId = searchParams.get('paneId') || '';
|
|
22
|
+
const optionsStr = searchParams.get('options') || '';
|
|
23
|
+
const options = optionsStr ? { params: { options: optionsStr } } : undefined;
|
|
24
|
+
return { tenantId, paneId, optionsStr, options };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function resolveCodeHookFullContentMap(
|
|
28
|
+
tenantId: string
|
|
29
|
+
): Promise<any[]> {
|
|
30
|
+
let fullContentMap = getCachedFullContentMap(tenantId);
|
|
31
|
+
if (!fullContentMap.length) {
|
|
32
|
+
fullContentMap = await getFullContentMap(tenantId);
|
|
33
|
+
}
|
|
34
|
+
return fullContentMap;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function parseCodeHookResourceFilters(
|
|
38
|
+
optionsStr: string,
|
|
39
|
+
logLabel?: string
|
|
40
|
+
): CodeHookResourceFilters {
|
|
41
|
+
const categories: string[] = [];
|
|
42
|
+
const slugs: string[] = [];
|
|
43
|
+
if (!optionsStr) {
|
|
44
|
+
return { categories, slugs };
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const parsed = JSON.parse(optionsStr);
|
|
48
|
+
if (typeof parsed.category === 'string' && parsed.category) {
|
|
49
|
+
categories.push(...parsed.category.split('|'));
|
|
50
|
+
}
|
|
51
|
+
if (typeof parsed.slugs === 'string' && parsed.slugs) {
|
|
52
|
+
slugs.push(...parsed.slugs.split(','));
|
|
53
|
+
}
|
|
54
|
+
if (typeof parsed.slug === 'string' && parsed.slug) {
|
|
55
|
+
slugs.push(parsed.slug);
|
|
56
|
+
}
|
|
57
|
+
} catch (e) {
|
|
58
|
+
console.error(`Invalid options for ${logLabel ?? 'codehook resources'}`, e);
|
|
59
|
+
}
|
|
60
|
+
return { categories, slugs };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function resolveCodeHookResources(
|
|
64
|
+
tenantId: string,
|
|
65
|
+
optionsStr: string,
|
|
66
|
+
logLabel?: string
|
|
67
|
+
): Promise<ResourceNode[]> {
|
|
68
|
+
const { categories, slugs } = parseCodeHookResourceFilters(
|
|
69
|
+
optionsStr,
|
|
70
|
+
logLabel
|
|
71
|
+
);
|
|
72
|
+
return getCodeHookResources(tenantId, categories, slugs);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function buildCodeHookBladePath(
|
|
76
|
+
hookId: string,
|
|
77
|
+
ctx: Pick<CodeHookBladeContext, 'paneId' | 'tenantId' | 'optionsStr'>
|
|
78
|
+
): string {
|
|
79
|
+
const params = new URLSearchParams({
|
|
80
|
+
paneId: ctx.paneId,
|
|
81
|
+
tenantId: ctx.tenantId,
|
|
82
|
+
});
|
|
83
|
+
if (ctx.optionsStr) {
|
|
84
|
+
params.set('options', ctx.optionsStr);
|
|
85
|
+
}
|
|
86
|
+
return `/codehooks/${hookId}?${params.toString()}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function resolveSSRFetchOrigin(pageUrl: URL, siteUrl?: string): string {
|
|
90
|
+
if (siteUrl) {
|
|
91
|
+
try {
|
|
92
|
+
return new URL(siteUrl).origin;
|
|
93
|
+
} catch {
|
|
94
|
+
// fall through to page origin
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return pageUrl.origin;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function fetchCodeHookBladeHtml(
|
|
101
|
+
bladePath: string,
|
|
102
|
+
request: Request,
|
|
103
|
+
origin: string
|
|
104
|
+
): Promise<string> {
|
|
105
|
+
try {
|
|
106
|
+
const url = new URL(bladePath, origin);
|
|
107
|
+
const res = await fetch(url, {
|
|
108
|
+
headers: {
|
|
109
|
+
cookie: request.headers.get('cookie') ?? '',
|
|
110
|
+
accept: 'text/html',
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
if (!res.ok) {
|
|
114
|
+
console.error(
|
|
115
|
+
`Failed to SSR-fetch codehook blade ${bladePath}. Status: ${res.status}`
|
|
116
|
+
);
|
|
117
|
+
return '';
|
|
118
|
+
}
|
|
119
|
+
return res.text();
|
|
120
|
+
} catch (error) {
|
|
121
|
+
console.error(`Error SSR-fetching codehook blade ${bladePath}:`, error);
|
|
122
|
+
return '';
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function fetchCodeHookBladesForPanes(options: {
|
|
127
|
+
paneIds: string[];
|
|
128
|
+
codeHookTargets: Record<string, string>;
|
|
129
|
+
tenantId: string;
|
|
130
|
+
request: Request;
|
|
131
|
+
origin: string;
|
|
132
|
+
}): Promise<Record<string, string>> {
|
|
133
|
+
const { paneIds, codeHookTargets, tenantId, request, origin } = options;
|
|
134
|
+
const result: Record<string, string> = {};
|
|
135
|
+
|
|
136
|
+
await Promise.all(
|
|
137
|
+
paneIds
|
|
138
|
+
.filter((paneId) => codeHookTargets[paneId])
|
|
139
|
+
.map(async (paneId) => {
|
|
140
|
+
const hookId = codeHookTargets[paneId];
|
|
141
|
+
const optionsStr = codeHookTargets[`${paneId}-${hookId}`] || '';
|
|
142
|
+
const bladePath = buildCodeHookBladePath(hookId, {
|
|
143
|
+
paneId,
|
|
144
|
+
tenantId,
|
|
145
|
+
optionsStr,
|
|
146
|
+
});
|
|
147
|
+
result[paneId] = await fetchCodeHookBladeHtml(
|
|
148
|
+
bladePath,
|
|
149
|
+
request,
|
|
150
|
+
origin
|
|
151
|
+
);
|
|
152
|
+
})
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
@@ -1,6 +1,47 @@
|
|
|
1
1
|
import { headerResourcesStore, HEADER_RESOURCES_TTL } from '@/stores/resources';
|
|
2
2
|
import type { ResourceNode } from '@/types/compositorTypes';
|
|
3
3
|
|
|
4
|
+
// Stateless resource fetch for codehook blades. POSTs categories/slugs to the
|
|
5
|
+
// nodes/resources endpoint, unwraps the { resources, count } envelope, and
|
|
6
|
+
// returns the FLAT ResourceNode[] (the API shape) unchanged - no grouping.
|
|
7
|
+
// Components that need a keyed view reshape internally. No cache.
|
|
8
|
+
export async function getCodeHookResources(
|
|
9
|
+
tenantId: string,
|
|
10
|
+
categories: string[] = [],
|
|
11
|
+
slugs: string[] = []
|
|
12
|
+
): Promise<ResourceNode[]> {
|
|
13
|
+
if (categories.length === 0 && slugs.length === 0) {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const goBackend =
|
|
18
|
+
import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const response = await fetch(`${goBackend}/api/v1/nodes/resources`, {
|
|
22
|
+
method: 'POST',
|
|
23
|
+
headers: {
|
|
24
|
+
'Content-Type': 'application/json',
|
|
25
|
+
'X-Tenant-ID': tenantId,
|
|
26
|
+
},
|
|
27
|
+
body: JSON.stringify({ categories, slugs }),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
console.error(
|
|
32
|
+
`Failed to fetch codehook resources. Status: ${response.status}`
|
|
33
|
+
);
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const payload = await response.json();
|
|
38
|
+
return (payload.resources as ResourceNode[]) || [];
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error('Error fetching codehook resources:', error);
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
4
45
|
export async function getHeaderResources(
|
|
5
46
|
tenantId: string,
|
|
6
47
|
categories: string[],
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { handleFailedResponse } from '@/utils/backend';
|
|
2
|
-
import type { ImpressionNode
|
|
2
|
+
import type { ImpressionNode } from '@/types/compositorTypes';
|
|
3
3
|
|
|
4
4
|
export interface StoryData {
|
|
5
5
|
id: string;
|
|
@@ -8,7 +8,6 @@ export interface StoryData {
|
|
|
8
8
|
paneIds: string[];
|
|
9
9
|
codeHookTargets: Record<string, string>;
|
|
10
10
|
codeHookVisibility: Record<string, boolean | string[]>;
|
|
11
|
-
resourcesPayload: Record<string, ResourceNode[]>;
|
|
12
11
|
impressions: ImpressionNode[];
|
|
13
12
|
fragments: Record<string, string>;
|
|
14
13
|
menu: any;
|
|
@@ -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 {
|
|
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';
|
|
@@ -188,7 +188,7 @@ for (const [key, value] of Astro.url.searchParams) {
|
|
|
188
188
|
fullContentMap={fullContentMap}
|
|
189
189
|
fullCanonicalURL={fullCanonicalURL}
|
|
190
190
|
urlParams={urlParams}
|
|
191
|
-
availableCodeHooks={
|
|
191
|
+
availableCodeHooks={availableCodeHookIds}
|
|
192
192
|
client:only="react"
|
|
193
193
|
/>
|
|
194
194
|
</div>
|
|
@@ -213,7 +213,7 @@ for (const [key, value] of Astro.url.searchParams) {
|
|
|
213
213
|
}
|
|
214
214
|
<div class="pointer-events-auto max-h-full">
|
|
215
215
|
<SettingsPanel
|
|
216
|
-
availableCodeHooks={
|
|
216
|
+
availableCodeHooks={availableCodeHookIds}
|
|
217
217
|
client:only="react"
|
|
218
218
|
/>
|
|
219
219
|
</div>
|