astro-tractstack 2.0.3 → 2.0.7
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/dist/index.js +28 -13
- package/package.json +1 -1
- package/templates/custom/minimal/HeaderWidget.astro +35 -0
- package/templates/custom/with-examples/HeaderWidget.astro +35 -0
- package/templates/src/components/compositor/nodes/Widget.tsx +30 -20
- package/templates/src/components/edit/panels/StyleLinkPanel_config.tsx +14 -2
- package/templates/src/components/edit/storyfragment/StoryFragmentPanel_og.tsx +20 -25
- package/templates/src/components/edit/widgets/InteractiveDisclosureWidget.tsx +137 -74
- package/templates/src/components/form/ActionBuilderField.tsx +3 -3
- package/templates/src/layouts/Layout.astro +3 -0
- package/templates/src/lib/resources.ts +69 -0
- package/templates/src/pages/[...slug]/edit.astro +5 -1
- package/templates/src/pages/[...slug].astro +4 -0
- package/templates/src/stores/resources.ts +17 -0
- package/templates/src/utils/layout.ts +41 -11
- package/utils/inject-files.ts +17 -0
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { fileURLToPath as d } from "node:url";
|
|
2
2
|
import { dirname as i, resolve as l } from "node:path";
|
|
3
|
-
import { existsSync as
|
|
3
|
+
import { existsSync as n, mkdirSync as x, copyFileSync as k, writeFileSync as u } from "node:fs";
|
|
4
4
|
import { resolve as a } from "path";
|
|
5
5
|
function b(t) {
|
|
6
6
|
const e = i(d(t));
|
|
@@ -10,7 +10,7 @@ function b(t) {
|
|
|
10
10
|
}
|
|
11
11
|
function g(t, e) {
|
|
12
12
|
e.info("TractStack configuration applied"), t.enableMultiTenant && e.info("Multi-tenant mode enabled"), t.includeExamples && e.info("Example components will be included");
|
|
13
|
-
const c = process.env.PUBLIC_GO_BACKEND,
|
|
13
|
+
const c = process.env.PUBLIC_GO_BACKEND, r = process.env.PUBLIC_TENANTID;
|
|
14
14
|
if (!c)
|
|
15
15
|
e.warn("PUBLIC_GO_BACKEND not set - this will be required at runtime");
|
|
16
16
|
else
|
|
@@ -19,11 +19,11 @@ function g(t, e) {
|
|
|
19
19
|
} catch {
|
|
20
20
|
e.error(`PUBLIC_GO_BACKEND is not a valid URL: ${c}`);
|
|
21
21
|
}
|
|
22
|
-
return
|
|
22
|
+
return r ? /^[a-zA-Z0-9_-]+$/.test(r) ? e.info(`Tenant ID validated: ${r}`) : e.error(`PUBLIC_TENANTID contains invalid characters: ${r}`) : e.warn("PUBLIC_TENANTID not set - this will be required at runtime"), t;
|
|
23
23
|
}
|
|
24
24
|
async function w(t, e, c) {
|
|
25
25
|
e.info("TractStack: Injecting template files");
|
|
26
|
-
const
|
|
26
|
+
const r = [
|
|
27
27
|
// Core Configuration
|
|
28
28
|
{
|
|
29
29
|
src: t("../templates/env.example"),
|
|
@@ -564,6 +564,10 @@ async function w(t, e, c) {
|
|
|
564
564
|
src: t("../templates/src/stores/backend.ts"),
|
|
565
565
|
dest: "src/stores/backend.ts"
|
|
566
566
|
},
|
|
567
|
+
{
|
|
568
|
+
src: t("../templates/src/stores/resources.ts"),
|
|
569
|
+
dest: "src/stores/resources.ts"
|
|
570
|
+
},
|
|
567
571
|
// Compositor stores
|
|
568
572
|
{
|
|
569
573
|
src: t("../templates/src/stores/nodes.ts"),
|
|
@@ -1298,6 +1302,10 @@ async function w(t, e, c) {
|
|
|
1298
1302
|
src: t("../templates/src/lib/session.ts"),
|
|
1299
1303
|
dest: "src/lib/session.ts"
|
|
1300
1304
|
},
|
|
1305
|
+
{
|
|
1306
|
+
src: t("../templates/src/lib/resources.ts"),
|
|
1307
|
+
dest: "src/lib/resources.ts"
|
|
1308
|
+
},
|
|
1301
1309
|
// Client Scripts
|
|
1302
1310
|
{
|
|
1303
1311
|
src: t("../templates/src/client/htmx.min.js"),
|
|
@@ -2056,6 +2064,13 @@ async function w(t, e, c) {
|
|
|
2056
2064
|
dest: "src/custom/CustomRoutes.astro",
|
|
2057
2065
|
protected: !0
|
|
2058
2066
|
},
|
|
2067
|
+
{
|
|
2068
|
+
src: t(
|
|
2069
|
+
c?.includeExamples ? "../templates/custom/with-examples/HeaderWidget.astro" : "../templates/custom/minimal/HeaderWidget.astro"
|
|
2070
|
+
),
|
|
2071
|
+
dest: "src/custom/HeaderWidget.astro",
|
|
2072
|
+
protected: !0
|
|
2073
|
+
},
|
|
2059
2074
|
{
|
|
2060
2075
|
src: t("../templates/src/utils/customHelpers.ts"),
|
|
2061
2076
|
dest: "src/utils/customHelpers.ts",
|
|
@@ -2099,13 +2114,13 @@ async function w(t, e, c) {
|
|
|
2099
2114
|
}
|
|
2100
2115
|
] : []
|
|
2101
2116
|
];
|
|
2102
|
-
for (const s of
|
|
2117
|
+
for (const s of r)
|
|
2103
2118
|
try {
|
|
2104
2119
|
const p = i(s.dest);
|
|
2105
|
-
|
|
2106
|
-
const
|
|
2107
|
-
if (!
|
|
2108
|
-
if (
|
|
2120
|
+
n(p) || x(p, { recursive: !0 });
|
|
2121
|
+
const o = !s.protected && (s.dest === "tailwind.config.cjs" || s.dest.startsWith("src/components/codehooks/") || s.dest.startsWith("src/components/widgets/") || s.dest.startsWith("src/") || s.dest.startsWith("public/client/") || s.dest === ".gitignore");
|
|
2122
|
+
if (!n(s.dest) || o)
|
|
2123
|
+
if (n(s.src))
|
|
2109
2124
|
k(s.src, s.dest), e.info(`Updated ${s.dest}`);
|
|
2110
2125
|
else {
|
|
2111
2126
|
const m = _(s.dest);
|
|
@@ -2113,8 +2128,8 @@ async function w(t, e, c) {
|
|
|
2113
2128
|
}
|
|
2114
2129
|
else s.protected ? e.info(`Protected: ${s.dest} (skipped overwrite)`) : e.info(`Skipped existing ${s.dest}`);
|
|
2115
2130
|
} catch (p) {
|
|
2116
|
-
const
|
|
2117
|
-
e.error(`Failed to create ${s.dest}: ${
|
|
2131
|
+
const o = p instanceof Error ? p.message : String(p);
|
|
2132
|
+
e.error(`Failed to create ${s.dest}: ${o}`);
|
|
2118
2133
|
}
|
|
2119
2134
|
}
|
|
2120
2135
|
function _(t) {
|
|
@@ -2133,7 +2148,7 @@ function C(t = {}) {
|
|
|
2133
2148
|
return {
|
|
2134
2149
|
name: "astro-tractstack",
|
|
2135
2150
|
hooks: {
|
|
2136
|
-
"astro:config:setup": async ({ config: c, updateConfig:
|
|
2151
|
+
"astro:config:setup": async ({ config: c, updateConfig: r, logger: s }) => {
|
|
2137
2152
|
g(t, s);
|
|
2138
2153
|
const p = t.enableMultiTenant || !1;
|
|
2139
2154
|
if (s.info(
|
|
@@ -2153,7 +2168,7 @@ function C(t = {}) {
|
|
|
2153
2168
|
), new Error(
|
|
2154
2169
|
"TractStack requires an SSR adapter. Please add @astrojs/node adapter to your astro.config.mjs"
|
|
2155
2170
|
);
|
|
2156
|
-
|
|
2171
|
+
r({
|
|
2157
2172
|
vite: {
|
|
2158
2173
|
define: {
|
|
2159
2174
|
__TRACTSTACK_VERSION__: JSON.stringify("2.0.0-alpha.1"),
|
package/package.json
CHANGED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
/*
|
|
3
|
+
// ========================================================================================
|
|
4
|
+
// CUSTOM HEADER WIDGET
|
|
5
|
+
// ========================================================================================
|
|
6
|
+
// This widget is rendered on every page above the main header. It is designed to
|
|
7
|
+
// fetch and display site-wide content, such as promotional banners or alerts.
|
|
8
|
+
//
|
|
9
|
+
// To activate this component:
|
|
10
|
+
// 1. Uncomment all the code within these '---' fences.
|
|
11
|
+
// 2. Define your desired resource categories in the `resourceCategories` array.
|
|
12
|
+
// 3. Add your Astro/HTML markup in the empty space after the closing '---' below.
|
|
13
|
+
// ========================================================================================
|
|
14
|
+
|
|
15
|
+
// --- UNCOMMENT BELOW TO ACTIVATE ---
|
|
16
|
+
|
|
17
|
+
// import { getHeaderResources } from '@/lib/resources';
|
|
18
|
+
// import type { ResourceNode } from '@/types/compositorTypes';
|
|
19
|
+
|
|
20
|
+
// export interface Props {
|
|
21
|
+
// tenantId: string;
|
|
22
|
+
// }
|
|
23
|
+
// const { tenantId } = Astro.props;
|
|
24
|
+
|
|
25
|
+
// // Add the slugs of the resource categories you want to fetch here.
|
|
26
|
+
// const resourceCategories = []; // e.g., ['promotions', 'site-alerts']
|
|
27
|
+
|
|
28
|
+
// // This function automatically fetches and caches the resources.
|
|
29
|
+
// const resources: ResourceNode[] =
|
|
30
|
+
// resourceCategories.length > 0
|
|
31
|
+
// ? await getHeaderResources(tenantId, resourceCategories)
|
|
32
|
+
// : [];
|
|
33
|
+
|
|
34
|
+
*/
|
|
35
|
+
---
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
/*
|
|
3
|
+
// ========================================================================================
|
|
4
|
+
// CUSTOM HEADER WIDGET
|
|
5
|
+
// ========================================================================================
|
|
6
|
+
// This widget is rendered on every page above the main header. It is designed to
|
|
7
|
+
// fetch and display site-wide content, such as promotional banners or alerts.
|
|
8
|
+
//
|
|
9
|
+
// To activate this component:
|
|
10
|
+
// 1. Uncomment all the code within these '---' fences.
|
|
11
|
+
// 2. Define your desired resource categories in the `resourceCategories` array.
|
|
12
|
+
// 3. Add your Astro/HTML markup in the empty space after the closing '---' below.
|
|
13
|
+
// ========================================================================================
|
|
14
|
+
|
|
15
|
+
// --- UNCOMMENT BELOW TO ACTIVATE ---
|
|
16
|
+
|
|
17
|
+
// import { getHeaderResources } from '@/lib/resources';
|
|
18
|
+
// import type { ResourceNode } from '@/types/compositorTypes';
|
|
19
|
+
|
|
20
|
+
// export interface Props {
|
|
21
|
+
// tenantId: string;
|
|
22
|
+
// }
|
|
23
|
+
// const { tenantId } = Astro.props;
|
|
24
|
+
|
|
25
|
+
// // Add the slugs of the resource categories you want to fetch here.
|
|
26
|
+
// const resourceCategories = []; // e.g., ['promotions', 'site-alerts']
|
|
27
|
+
|
|
28
|
+
// // This function automatically fetches and caches the resources.
|
|
29
|
+
// const resources: ResourceNode[] =
|
|
30
|
+
// resourceCategories.length > 0
|
|
31
|
+
// ? await getHeaderResources(tenantId, resourceCategories)
|
|
32
|
+
// : [];
|
|
33
|
+
|
|
34
|
+
*/
|
|
35
|
+
---
|
|
@@ -22,15 +22,16 @@ const getWidgetElement = (
|
|
|
22
22
|
classNames: string
|
|
23
23
|
): ReactElement | null => {
|
|
24
24
|
const { hook, value1, value2, value3, nodeId } = props;
|
|
25
|
-
if (!hook
|
|
25
|
+
if (!hook) return null;
|
|
26
26
|
|
|
27
27
|
switch (hook) {
|
|
28
28
|
case 'youtube':
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
if (value1)
|
|
30
|
+
return value2 ? (
|
|
31
|
+
<div className={`${classNames} pointer-events-none`}>
|
|
32
|
+
<YouTubeWrapper embedCode={value1} title={value2} />
|
|
33
|
+
</div>
|
|
34
|
+
) : null;
|
|
34
35
|
|
|
35
36
|
case 'signup':
|
|
36
37
|
return (
|
|
@@ -45,19 +46,21 @@ const getWidgetElement = (
|
|
|
45
46
|
);
|
|
46
47
|
|
|
47
48
|
case 'belief':
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
if (value1)
|
|
50
|
+
return value2 ? (
|
|
51
|
+
<div className={`${classNames} pointer-events-none`}>
|
|
52
|
+
<Belief value={{ slug: value1, scale: value2, extra: value3 }} />
|
|
53
|
+
</div>
|
|
54
|
+
) : null;
|
|
53
55
|
|
|
54
56
|
case 'identifyAs':
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
if (value1)
|
|
58
|
+
return value2 ? (
|
|
59
|
+
<IdentifyAs
|
|
60
|
+
classNames={`${classNames} pointer-events-none`}
|
|
61
|
+
value={{ slug: value1, target: value2, extra: value3 || `` }}
|
|
62
|
+
/>
|
|
63
|
+
) : null;
|
|
61
64
|
|
|
62
65
|
case 'toggle':
|
|
63
66
|
return value2 ? (
|
|
@@ -90,9 +93,16 @@ const getWidgetElement = (
|
|
|
90
93
|
<p className="text-sm font-bold text-gray-700">
|
|
91
94
|
Interactive Disclosure
|
|
92
95
|
</p>
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
+
{value1 ? (
|
|
97
|
+
<p className="mt-1 text-xs text-gray-500">
|
|
98
|
+
Mode: Belief-Driven (<code className="font-bold">{value1}</code>
|
|
99
|
+
)
|
|
100
|
+
</p>
|
|
101
|
+
) : (
|
|
102
|
+
<p className="mt-1 text-xs text-gray-500">
|
|
103
|
+
Mode: Open (Custom Actions)
|
|
104
|
+
</p>
|
|
105
|
+
)}
|
|
96
106
|
</div>
|
|
97
107
|
</div>
|
|
98
108
|
);
|
|
@@ -67,8 +67,20 @@ const StyleLinkConfigPanel = ({ node, config }: StyleLinkConfigPanelProps) => {
|
|
|
67
67
|
linkNode.tagName = 'button';
|
|
68
68
|
} else if (callbackPayload.startsWith('(bunnyMoment')) {
|
|
69
69
|
linkNode.tagName = 'button';
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
if (
|
|
71
|
+
tokens &&
|
|
72
|
+
Array.isArray(tokens) &&
|
|
73
|
+
tokens[0] === 'bunnyMoment' &&
|
|
74
|
+
Array.isArray(tokens[1])
|
|
75
|
+
) {
|
|
76
|
+
const params = tokens[1];
|
|
77
|
+
if (params.length === 2) {
|
|
78
|
+
newButtonPayload.bunnyPayload = {
|
|
79
|
+
videoId: String(params[0]),
|
|
80
|
+
t: String(params[1]),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
72
84
|
} else if (callbackPayload.startsWith('(goto')) {
|
|
73
85
|
const targetUrl =
|
|
74
86
|
tokens && preParseAction([tokens], slug, isContext, config);
|
|
@@ -187,21 +187,27 @@ const StoryFragmentOpenGraphPanel = ({
|
|
|
187
187
|
(item) => item.type === 'Topic' && item.id === 'all-topics'
|
|
188
188
|
);
|
|
189
189
|
|
|
190
|
-
// Convert topic strings to Topic objects with mock IDs
|
|
190
|
+
// Convert topic strings to Topic objects with mock IDs
|
|
191
191
|
const allTopicsArray = topicsContent?.topics || [];
|
|
192
192
|
const topicsWithIds: Topic[] = allTopicsArray.map(
|
|
193
193
|
(topicTitle, index) => ({
|
|
194
|
-
id: index + 1,
|
|
194
|
+
id: index + 1,
|
|
195
195
|
title: topicTitle,
|
|
196
196
|
})
|
|
197
197
|
);
|
|
198
198
|
|
|
199
199
|
setExistingTopics(topicsWithIds);
|
|
200
200
|
|
|
201
|
+
// Prioritize the description from the definitive fullContentMap
|
|
202
|
+
const sfContent = $contentMap.find(
|
|
203
|
+
(item) => item.type === 'StoryFragment' && item.id === nodeId
|
|
204
|
+
);
|
|
205
|
+
const initialDescription = sfContent?.description || '';
|
|
206
|
+
setDraftDetails(initialDescription);
|
|
207
|
+
|
|
201
208
|
let initialTopics: Topic[] = [];
|
|
202
|
-
let initialDescription = '';
|
|
203
209
|
|
|
204
|
-
// Check stored draft data
|
|
210
|
+
// Check stored draft data for topics
|
|
205
211
|
if (storedData) {
|
|
206
212
|
initialTopics = Array.isArray(storedData.topics)
|
|
207
213
|
? storedData.topics.map((t) => ({
|
|
@@ -209,29 +215,18 @@ const StoryFragmentOpenGraphPanel = ({
|
|
|
209
215
|
title: t.title,
|
|
210
216
|
}))
|
|
211
217
|
: [];
|
|
212
|
-
initialDescription = storedData.description || '';
|
|
213
218
|
setDraftTopics(initialTopics);
|
|
214
|
-
|
|
219
|
+
} else if (sfContent && sfContent.topics && sfContent.topics.length > 0) {
|
|
220
|
+
// Fall back to content map data for initial topics if no draft exists
|
|
221
|
+
initialTopics = sfContent.topics.map((topicTitle) => {
|
|
222
|
+
const existingTopic = topicsWithIds.find(
|
|
223
|
+
(t) => t.title.toLowerCase() === topicTitle.toLowerCase()
|
|
224
|
+
);
|
|
225
|
+
return existingTopic || { id: -1, title: topicTitle };
|
|
226
|
+
});
|
|
227
|
+
setDraftTopics(initialTopics);
|
|
215
228
|
} else {
|
|
216
|
-
|
|
217
|
-
const sfContent = $contentMap.find(
|
|
218
|
-
(item) => item.type === 'StoryFragment' && item.id === nodeId
|
|
219
|
-
);
|
|
220
|
-
|
|
221
|
-
if (sfContent && sfContent.topics && sfContent.topics.length > 0) {
|
|
222
|
-
initialTopics = sfContent.topics.map((topicTitle) => {
|
|
223
|
-
const existingTopic = topicsWithIds.find(
|
|
224
|
-
(t) => t.title.toLowerCase() === topicTitle.toLowerCase()
|
|
225
|
-
);
|
|
226
|
-
return existingTopic || { id: -1, title: topicTitle };
|
|
227
|
-
});
|
|
228
|
-
initialDescription = sfContent.description || '';
|
|
229
|
-
setDraftTopics(initialTopics);
|
|
230
|
-
setDraftDetails(initialDescription);
|
|
231
|
-
} else {
|
|
232
|
-
setDraftTopics([]);
|
|
233
|
-
setDraftDetails('');
|
|
234
|
-
}
|
|
229
|
+
setDraftTopics([]);
|
|
235
230
|
}
|
|
236
231
|
|
|
237
232
|
if (initialState.current) {
|
|
@@ -124,7 +124,6 @@ const DisclosureItemEditor = ({
|
|
|
124
124
|
item,
|
|
125
125
|
onUpdate,
|
|
126
126
|
onToggle,
|
|
127
|
-
config,
|
|
128
127
|
onMoveUp,
|
|
129
128
|
onMoveDown,
|
|
130
129
|
isFirst,
|
|
@@ -133,7 +132,6 @@ const DisclosureItemEditor = ({
|
|
|
133
132
|
item: DisclosureItem;
|
|
134
133
|
onUpdate: (updates: Partial<DisclosureItem>) => void;
|
|
135
134
|
onToggle: () => void;
|
|
136
|
-
config: BrandConfig;
|
|
137
135
|
onMoveUp: () => void;
|
|
138
136
|
onMoveDown: () => void;
|
|
139
137
|
isFirst: boolean;
|
|
@@ -258,6 +256,7 @@ export default function InteractiveDisclosureWidget({
|
|
|
258
256
|
onUpdate,
|
|
259
257
|
config,
|
|
260
258
|
}: InteractiveDisclosureWidgetProps) {
|
|
259
|
+
const [mode, setMode] = useState<'belief' | 'open'>('belief');
|
|
261
260
|
const [beliefs, setBeliefs] = useState<BeliefNode[]>([]);
|
|
262
261
|
const [selectedBeliefTag, setSelectedBeliefTag] = useState<string>('');
|
|
263
262
|
const [disclosures, setDisclosures] = useState<DisclosureItem[]>([]);
|
|
@@ -276,14 +275,16 @@ export default function InteractiveDisclosureWidget({
|
|
|
276
275
|
const beliefTag = String(node.codeHookParams?.[0] || '');
|
|
277
276
|
const payloadJson = String(node.codeHookParams?.[1] || '');
|
|
278
277
|
|
|
279
|
-
if (
|
|
280
|
-
|
|
278
|
+
if (beliefTag && beliefTag !== 'BELIEF') {
|
|
279
|
+
setMode('belief');
|
|
280
|
+
} else {
|
|
281
|
+
setMode('open');
|
|
281
282
|
}
|
|
282
283
|
|
|
283
284
|
setSelectedBeliefTag(beliefTag && beliefTag !== 'BELIEF' ? beliefTag : '');
|
|
284
285
|
const currentBelief = beliefs.find((b) => b.slug === beliefTag);
|
|
285
286
|
|
|
286
|
-
if (payloadJson
|
|
287
|
+
if (payloadJson) {
|
|
287
288
|
try {
|
|
288
289
|
const parsed = JSON.parse(payloadJson);
|
|
289
290
|
setWidgetStyles(
|
|
@@ -296,50 +297,62 @@ export default function InteractiveDisclosureWidget({
|
|
|
296
297
|
const loadedDisclosures =
|
|
297
298
|
(parsed.disclosures as StoredDisclosureItem[]) || [];
|
|
298
299
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
300
|
+
if (currentBelief) {
|
|
301
|
+
const scaleKeys =
|
|
302
|
+
currentBelief.scale === 'custom'
|
|
303
|
+
? (currentBelief.customValues || []).map((v) => ({
|
|
304
|
+
slug: v,
|
|
305
|
+
name: v,
|
|
306
|
+
}))
|
|
307
|
+
: heldBeliefsScales[
|
|
308
|
+
currentBelief.scale as keyof typeof heldBeliefsScales
|
|
309
|
+
] || [];
|
|
310
|
+
|
|
311
|
+
const actionCommand =
|
|
312
|
+
currentBelief.scale === 'custom' ? 'identifyAs' : 'declare';
|
|
313
|
+
const finalDisclosures: DisclosureItem[] = loadedDisclosures.map(
|
|
314
|
+
(loadedItem) => {
|
|
315
|
+
const isFromScale = scaleKeys.some(
|
|
316
|
+
(sk) => sk.slug === loadedItem.beliefValue
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
...loadedItem,
|
|
321
|
+
id: generateId(),
|
|
322
|
+
isCustom: !isFromScale,
|
|
323
|
+
actionLisp: isFromScale
|
|
324
|
+
? `(${actionCommand} ${beliefTag} ${quoteIfNecessary(actionCommand, loadedItem.beliefValue)})`
|
|
325
|
+
: loadedItem.actionLisp,
|
|
326
|
+
isDisabled: false,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
);
|
|
330
|
+
scaleKeys.forEach(({ slug, name }) => {
|
|
331
|
+
if (!finalDisclosures.some((d) => d.beliefValue === slug)) {
|
|
332
|
+
finalDisclosures.push({
|
|
333
|
+
id: generateId(),
|
|
334
|
+
beliefValue: slug,
|
|
335
|
+
title: name,
|
|
336
|
+
description: '',
|
|
337
|
+
icon: 'chat-heart-fill',
|
|
338
|
+
actionLisp: `(${actionCommand} ${beliefTag} ${quoteIfNecessary(actionCommand, slug)})`,
|
|
339
|
+
isCustom: false,
|
|
340
|
+
isDisabled: true,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
setDisclosures(finalDisclosures);
|
|
345
|
+
} else {
|
|
346
|
+
const finalDisclosures: DisclosureItem[] = loadedDisclosures.map(
|
|
347
|
+
(loadedItem) => ({
|
|
318
348
|
...loadedItem,
|
|
319
349
|
id: generateId(),
|
|
320
|
-
isCustom:
|
|
321
|
-
actionLisp: isFromScale
|
|
322
|
-
? `(${actionCommand} ${beliefTag} ${quoteIfNecessary(actionCommand, loadedItem.beliefValue)})`
|
|
323
|
-
: loadedItem.actionLisp,
|
|
350
|
+
isCustom: true,
|
|
324
351
|
isDisabled: false,
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
if (!finalDisclosures.some((d) => d.beliefValue === slug)) {
|
|
330
|
-
finalDisclosures.push({
|
|
331
|
-
id: generateId(),
|
|
332
|
-
beliefValue: slug,
|
|
333
|
-
title: name,
|
|
334
|
-
description: '',
|
|
335
|
-
icon: 'chat-heart-fill',
|
|
336
|
-
actionLisp: `(${actionCommand} ${beliefTag} ${quoteIfNecessary(actionCommand, slug)})`,
|
|
337
|
-
isCustom: false,
|
|
338
|
-
isDisabled: true,
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
});
|
|
342
|
-
setDisclosures(finalDisclosures);
|
|
352
|
+
})
|
|
353
|
+
);
|
|
354
|
+
setDisclosures(finalDisclosures);
|
|
355
|
+
}
|
|
343
356
|
} catch (e) {
|
|
344
357
|
console.error('Error parsing disclosure payload:', e);
|
|
345
358
|
}
|
|
@@ -417,6 +430,25 @@ export default function InteractiveDisclosureWidget({
|
|
|
417
430
|
setDisclosures(newDisclosures);
|
|
418
431
|
};
|
|
419
432
|
|
|
433
|
+
const handleModeChange = (newMode: 'belief' | 'open') => {
|
|
434
|
+
if (mode === newMode) return;
|
|
435
|
+
|
|
436
|
+
setMode(newMode);
|
|
437
|
+
setSelectedBeliefTag('');
|
|
438
|
+
setDisclosures([]);
|
|
439
|
+
setWidgetStyles({
|
|
440
|
+
textColor: '#000000',
|
|
441
|
+
bgColor: '#ffffff',
|
|
442
|
+
bgOpacity: 100,
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
if (newMode === 'open') {
|
|
446
|
+
onUpdate(['', '{}']);
|
|
447
|
+
} else {
|
|
448
|
+
onUpdate(['BELIEF', '{}']);
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
|
|
420
452
|
const moveDisclosure = (id: string, direction: 'up' | 'down') => {
|
|
421
453
|
const index = disclosures.findIndex((d) => d.id === id);
|
|
422
454
|
if (index === -1) return;
|
|
@@ -477,35 +509,66 @@ export default function InteractiveDisclosureWidget({
|
|
|
477
509
|
|
|
478
510
|
return (
|
|
479
511
|
<div className="space-y-4">
|
|
480
|
-
<div
|
|
481
|
-
<
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
disabled={hasRealSelection}
|
|
486
|
-
>
|
|
487
|
-
<option value="">Select a Belief...</option>
|
|
488
|
-
{beliefs.map((b) => (
|
|
489
|
-
<option key={b.slug} value={b.slug}>
|
|
490
|
-
{b.title} ({b.scale})
|
|
491
|
-
</option>
|
|
492
|
-
))}
|
|
493
|
-
</select>
|
|
494
|
-
{hasRealSelection && (
|
|
512
|
+
<div>
|
|
513
|
+
<label className="block text-xs font-bold text-gray-600">
|
|
514
|
+
Configuration Mode
|
|
515
|
+
</label>
|
|
516
|
+
<div className="isolate mt-1 inline-flex rounded-md shadow-sm">
|
|
495
517
|
<button
|
|
496
518
|
type="button"
|
|
497
|
-
onClick={() =>
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
519
|
+
onClick={() => handleModeChange('belief')}
|
|
520
|
+
className={`relative inline-flex items-center rounded-l-md px-3 py-2 text-sm font-semibold ring-1 ring-inset ring-gray-300 focus:z-10 ${
|
|
521
|
+
mode === 'belief'
|
|
522
|
+
? 'bg-cyan-600 text-white'
|
|
523
|
+
: 'bg-white text-gray-900 hover:bg-gray-50'
|
|
524
|
+
}`}
|
|
503
525
|
>
|
|
504
|
-
|
|
526
|
+
Belief-Driven
|
|
505
527
|
</button>
|
|
506
|
-
|
|
528
|
+
<button
|
|
529
|
+
type="button"
|
|
530
|
+
onClick={() => handleModeChange('open')}
|
|
531
|
+
className={`relative -ml-px inline-flex items-center rounded-r-md px-3 py-2 text-sm font-semibold ring-1 ring-inset ring-gray-300 focus:z-10 ${
|
|
532
|
+
mode === 'open'
|
|
533
|
+
? 'bg-cyan-600 text-white'
|
|
534
|
+
: 'bg-white text-gray-900 hover:bg-gray-50'
|
|
535
|
+
}`}
|
|
536
|
+
>
|
|
537
|
+
Open
|
|
538
|
+
</button>
|
|
539
|
+
</div>
|
|
507
540
|
</div>
|
|
508
|
-
{
|
|
541
|
+
{mode === 'belief' && (
|
|
542
|
+
<div className="flex items-center gap-2">
|
|
543
|
+
<select
|
|
544
|
+
value={selectedBeliefTag}
|
|
545
|
+
onChange={(e) => handleBeliefChange(e.target.value)}
|
|
546
|
+
className="flex-1 rounded-md border-gray-300 shadow-sm"
|
|
547
|
+
disabled={hasRealSelection}
|
|
548
|
+
>
|
|
549
|
+
<option value="">Select a Belief...</option>
|
|
550
|
+
{beliefs.map((b) => (
|
|
551
|
+
<option key={b.slug} value={b.slug}>
|
|
552
|
+
{b.title} ({b.scale})
|
|
553
|
+
</option>
|
|
554
|
+
))}
|
|
555
|
+
</select>
|
|
556
|
+
{hasRealSelection && (
|
|
557
|
+
<button
|
|
558
|
+
type="button"
|
|
559
|
+
onClick={() => {
|
|
560
|
+
setSelectedBeliefTag('');
|
|
561
|
+
setDisclosures([]);
|
|
562
|
+
onUpdate(['BELIEF', '{}']);
|
|
563
|
+
}}
|
|
564
|
+
className="rounded p-1 text-red-600 hover:bg-gray-100"
|
|
565
|
+
>
|
|
566
|
+
<XMarkIcon className="h-5 w-5" />
|
|
567
|
+
</button>
|
|
568
|
+
)}
|
|
569
|
+
</div>
|
|
570
|
+
)}
|
|
571
|
+
{(hasRealSelection || mode === 'open') && (
|
|
509
572
|
<div className="mt-4 border-t border-gray-200 pt-4">
|
|
510
573
|
<button
|
|
511
574
|
type="button"
|
|
@@ -513,8 +576,8 @@ export default function InteractiveDisclosureWidget({
|
|
|
513
576
|
className="flex w-full items-center justify-center rounded-md bg-gray-100 px-3 py-2 text-sm font-bold text-gray-700 hover:bg-gray-200"
|
|
514
577
|
>
|
|
515
578
|
<ChevronDownIcon className="mr-2 h-5 w-5" />
|
|
516
|
-
Configure {disclosures.filter((d) => !d.isDisabled).length}
|
|
517
|
-
|
|
579
|
+
Configure {disclosures.filter((d) => !d.isDisabled).length}{' '}
|
|
580
|
+
Disclosure(s) & Styles
|
|
518
581
|
</button>
|
|
519
582
|
</div>
|
|
520
583
|
)}
|
|
@@ -546,7 +609,8 @@ export default function InteractiveDisclosureWidget({
|
|
|
546
609
|
<div className="flex h-full flex-col">
|
|
547
610
|
<div className="flex-shrink-0 border-b border-gray-200 bg-white px-6 py-3">
|
|
548
611
|
<Dialog.Title className="text-lg font-bold text-gray-900">
|
|
549
|
-
Disclosure Configuration
|
|
612
|
+
Disclosure Configuration
|
|
613
|
+
{selectedBelief && `: ${selectedBelief.title}`}
|
|
550
614
|
</Dialog.Title>
|
|
551
615
|
</div>
|
|
552
616
|
<div className="flex-1 space-y-6 overflow-y-auto p-4">
|
|
@@ -614,7 +678,6 @@ export default function InteractiveDisclosureWidget({
|
|
|
614
678
|
updateDisclosure(item.id, updates)
|
|
615
679
|
}
|
|
616
680
|
onToggle={() => toggleDisclosure(item.id)}
|
|
617
|
-
config={config}
|
|
618
681
|
onMoveUp={() => moveDisclosure(item.id, 'up')}
|
|
619
682
|
onMoveDown={() => moveDisclosure(item.id, 'down')}
|
|
620
683
|
isFirst={index === 0}
|
|
@@ -87,10 +87,11 @@ export default function ActionBuilderField({
|
|
|
87
87
|
return;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
if (command === '
|
|
90
|
+
if (command === 'bunnyMoment') {
|
|
91
|
+
onChange(trimmedParams);
|
|
92
|
+
} else if (command === 'identifyAs') {
|
|
91
93
|
const firstSpaceIndex = trimmedParams.indexOf(' ');
|
|
92
94
|
if (firstSpaceIndex === -1) {
|
|
93
|
-
// Handle case with only beliefId and no value
|
|
94
95
|
onChange(`(${command} ${trimmedParams})`);
|
|
95
96
|
} else {
|
|
96
97
|
const beliefId = trimmedParams.substring(0, firstSpaceIndex);
|
|
@@ -99,7 +100,6 @@ export default function ActionBuilderField({
|
|
|
99
100
|
onChange(`(${command} ${beliefId} ${finalValue})`);
|
|
100
101
|
}
|
|
101
102
|
} else {
|
|
102
|
-
// Original behavior for all other commands
|
|
103
103
|
onChange(`(${command} ${trimmedParams})`);
|
|
104
104
|
}
|
|
105
105
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { ClientRouter } from 'astro:transitions';
|
|
3
|
+
import HeaderWidget from '@/custom/HeaderWidget.astro';
|
|
3
4
|
import Header from '@/components/Header.astro';
|
|
4
5
|
import Footer from '@/components/Footer.astro';
|
|
5
6
|
import { getBrandConfig } from '@/utils/api/brandConfig';
|
|
@@ -22,6 +23,7 @@ export interface Props {
|
|
|
22
23
|
brandConfig?: any;
|
|
23
24
|
storyfragmentId?: string;
|
|
24
25
|
sessionId?: string;
|
|
26
|
+
description?: string;
|
|
25
27
|
impressions?: ImpressionNode[];
|
|
26
28
|
}
|
|
27
29
|
|
|
@@ -237,6 +239,7 @@ const enableBunny = import.meta.env.PUBLIC_ENABLE_BUNNY === 'true';
|
|
|
237
239
|
</head>
|
|
238
240
|
<body class="font-main w-full">
|
|
239
241
|
<div class="overflow-hidden">
|
|
242
|
+
{!isStoryKeep && !isEditor && <HeaderWidget tenantId={tenantId} />}
|
|
240
243
|
{
|
|
241
244
|
!isEditor && (
|
|
242
245
|
<Header
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { headerResourcesStore, HEADER_RESOURCES_TTL } from '@/stores/resources';
|
|
2
|
+
import type { ResourceNode } from '@/types/compositorTypes';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Fetches resource nodes based on categories, with server-side in-memory caching
|
|
6
|
+
* to prevent redundant API calls for high-traffic, site-wide components.
|
|
7
|
+
*
|
|
8
|
+
* @param tenantId The ID of the current tenant.
|
|
9
|
+
* @param categories An array of resource category slugs to fetch.
|
|
10
|
+
* @param ttl Optional. The Time-To-Live for the cache in milliseconds. Defaults to 5 minutes.
|
|
11
|
+
* @returns A promise that resolves to an array of ResourceNode objects.
|
|
12
|
+
*/
|
|
13
|
+
export async function getHeaderResources(
|
|
14
|
+
tenantId: string,
|
|
15
|
+
categories: string[],
|
|
16
|
+
ttl: number = HEADER_RESOURCES_TTL
|
|
17
|
+
): Promise<ResourceNode[]> {
|
|
18
|
+
const cache = headerResourcesStore.get();
|
|
19
|
+
const now = Date.now();
|
|
20
|
+
|
|
21
|
+
// If we have fresh data in the cache, return it immediately.
|
|
22
|
+
if (cache.data.length > 0 && now - cache.lastFetched < ttl) {
|
|
23
|
+
return cache.data;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// If no categories are requested, there's nothing to fetch.
|
|
27
|
+
if (!categories || categories.length === 0) {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const goBackend =
|
|
32
|
+
import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
// THIS IS THE CORRECTED ENDPOINT
|
|
36
|
+
const response = await fetch(`${goBackend}/api/v1/nodes/resources`, {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers: {
|
|
39
|
+
'Content-Type': 'application/json',
|
|
40
|
+
'X-Tenant-ID': tenantId,
|
|
41
|
+
},
|
|
42
|
+
body: JSON.stringify({ categories }),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
console.error(
|
|
47
|
+
`Failed to fetch header resources. Status: ${response.status}`
|
|
48
|
+
);
|
|
49
|
+
// Gracefully degrade: return old data if we have it, otherwise an empty array.
|
|
50
|
+
return cache.data;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// The backend returns a payload like { resources: [...] }
|
|
54
|
+
const responsePayload = await response.json();
|
|
55
|
+
const resources: ResourceNode[] = responsePayload.resources || [];
|
|
56
|
+
|
|
57
|
+
// Update the store with the new data and timestamp.
|
|
58
|
+
headerResourcesStore.set({
|
|
59
|
+
data: resources,
|
|
60
|
+
lastFetched: now,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return resources;
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error('Error fetching header resources:', error);
|
|
66
|
+
// On network error, also return stale data if available.
|
|
67
|
+
return cache.data;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -125,6 +125,9 @@ const ogImage = storyFragment?.socialImagePath
|
|
|
125
125
|
const fullContentMap = await getFullContentMap(
|
|
126
126
|
Astro.locals.tenant?.id || import.meta.env.PUBLIC_TENANTID || 'default'
|
|
127
127
|
);
|
|
128
|
+
const description = fullContentMap.find(
|
|
129
|
+
(item) => item.id === storyFragmentID
|
|
130
|
+
)?.description;
|
|
128
131
|
const urlParams: Record<string, string | boolean> = {};
|
|
129
132
|
for (const [key, value] of Astro.url.searchParams) {
|
|
130
133
|
urlParams[key] = value === '' ? true : value;
|
|
@@ -133,6 +136,7 @@ for (const [key, value] of Astro.url.searchParams) {
|
|
|
133
136
|
|
|
134
137
|
<Layout
|
|
135
138
|
title={title}
|
|
139
|
+
description={description}
|
|
136
140
|
slug={slug}
|
|
137
141
|
canonicalURL={canonicalURL}
|
|
138
142
|
pubDatetime={pubDatetime}
|
|
@@ -219,5 +223,5 @@ for (const [key, value] of Astro.url.searchParams) {
|
|
|
219
223
|
|
|
220
224
|
<script>
|
|
221
225
|
import { setupLayoutObservers } from '@/utils/layout';
|
|
222
|
-
setupLayoutObservers
|
|
226
|
+
document.addEventListener('astro:page-load', setupLayoutObservers);
|
|
223
227
|
</script>
|
|
@@ -99,6 +99,9 @@ if (!fragmentsData) {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
const fullContentMap = await getFullContentMap(tenantId);
|
|
102
|
+
const description = fullContentMap.find(
|
|
103
|
+
(item) => item.id === storyfragmentId
|
|
104
|
+
)?.description;
|
|
102
105
|
const brandConfig = await getBrandConfig(tenantId);
|
|
103
106
|
|
|
104
107
|
if (!brandConfig.SITE_INIT) {
|
|
@@ -118,6 +121,7 @@ paneIds.forEach((paneId: string) => {
|
|
|
118
121
|
|
|
119
122
|
<Layout
|
|
120
123
|
title={storyfragmentTitle}
|
|
124
|
+
description={description}
|
|
121
125
|
slug={lookup || brandConfig.HOME_SLUG}
|
|
122
126
|
ogImage={ogImage}
|
|
123
127
|
menu={storyData.menu || null}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { atom } from 'nanostores';
|
|
2
|
+
import type { ResourceNode } from '@/types/compositorTypes';
|
|
3
|
+
|
|
4
|
+
export interface ResourcesCache {
|
|
5
|
+
data: ResourceNode[];
|
|
6
|
+
lastFetched: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Initialize with an empty state. This atom will live on the server and persist
|
|
10
|
+
// across page requests for the lifetime of the server instance.
|
|
11
|
+
export const headerResourcesStore = atom<ResourcesCache>({
|
|
12
|
+
data: [],
|
|
13
|
+
lastFetched: 0,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// Default Time-To-Live for the cache: 5 minutes in milliseconds.
|
|
17
|
+
export const HEADER_RESOURCES_TTL = 5 * 60 * 1000;
|
|
@@ -8,15 +8,41 @@ import {
|
|
|
8
8
|
import { debounce } from '@/utils/helpers';
|
|
9
9
|
|
|
10
10
|
let hasScrolledForSettingsPanel = false;
|
|
11
|
+
let currentPaneObserver: IntersectionObserver | null = null;
|
|
12
|
+
let settingsPanelSubscription: (() => void) | null = null;
|
|
13
|
+
let debouncedUpdateListener: (() => void) | null = null;
|
|
14
|
+
|
|
15
|
+
function cleanupLayoutObservers() {
|
|
16
|
+
if (currentPaneObserver) {
|
|
17
|
+
currentPaneObserver.disconnect();
|
|
18
|
+
currentPaneObserver = null;
|
|
19
|
+
}
|
|
20
|
+
if (settingsPanelSubscription) {
|
|
21
|
+
settingsPanelSubscription();
|
|
22
|
+
settingsPanelSubscription = null;
|
|
23
|
+
}
|
|
24
|
+
if (debouncedUpdateListener) {
|
|
25
|
+
window.removeEventListener('scroll', debouncedUpdateListener);
|
|
26
|
+
window.removeEventListener('resize', debouncedUpdateListener);
|
|
27
|
+
debouncedUpdateListener = null;
|
|
28
|
+
}
|
|
29
|
+
const storykeepHeader = document.getElementById('storykeepHeader');
|
|
30
|
+
if (storykeepHeader) {
|
|
31
|
+
document.body.style.paddingTop = '';
|
|
32
|
+
storykeepHeader.style.position = '';
|
|
33
|
+
storykeepHeader.style.top = '';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
11
36
|
|
|
12
|
-
// Replace your existing setupPaneObserver with this one.
|
|
13
37
|
function setupPaneObserver() {
|
|
14
|
-
|
|
38
|
+
if (currentPaneObserver) {
|
|
39
|
+
currentPaneObserver.disconnect();
|
|
40
|
+
}
|
|
15
41
|
|
|
16
|
-
settingsPanelStore.subscribe((signalValue) => {
|
|
17
|
-
if (
|
|
18
|
-
|
|
19
|
-
|
|
42
|
+
settingsPanelSubscription = settingsPanelStore.subscribe((signalValue) => {
|
|
43
|
+
if (currentPaneObserver) {
|
|
44
|
+
currentPaneObserver.disconnect();
|
|
45
|
+
currentPaneObserver = null;
|
|
20
46
|
}
|
|
21
47
|
|
|
22
48
|
if (signalValue && signalValue.nodeId) {
|
|
@@ -28,7 +54,7 @@ function setupPaneObserver() {
|
|
|
28
54
|
document.querySelector(`[data-node-id="${nodeId}"]`);
|
|
29
55
|
|
|
30
56
|
if (targetElement) {
|
|
31
|
-
|
|
57
|
+
currentPaneObserver = new IntersectionObserver(
|
|
32
58
|
([entry]) => {
|
|
33
59
|
const signal = settingsPanelStore.get();
|
|
34
60
|
const now = Date.now();
|
|
@@ -41,7 +67,7 @@ function setupPaneObserver() {
|
|
|
41
67
|
},
|
|
42
68
|
{ threshold: 0 }
|
|
43
69
|
);
|
|
44
|
-
|
|
70
|
+
currentPaneObserver.observe(targetElement);
|
|
45
71
|
}
|
|
46
72
|
}, 100);
|
|
47
73
|
}
|
|
@@ -49,6 +75,8 @@ function setupPaneObserver() {
|
|
|
49
75
|
}
|
|
50
76
|
|
|
51
77
|
export function setupLayoutObservers(): void {
|
|
78
|
+
cleanupLayoutObservers();
|
|
79
|
+
|
|
52
80
|
const storykeepHeader = document.getElementById('storykeepHeader');
|
|
53
81
|
const settingsControls = document.getElementById('settingsControls');
|
|
54
82
|
const standardHeader = document.querySelector('header');
|
|
@@ -86,7 +114,7 @@ export function setupLayoutObservers(): void {
|
|
|
86
114
|
}
|
|
87
115
|
};
|
|
88
116
|
|
|
89
|
-
|
|
117
|
+
debouncedUpdateListener = debounce(() => {
|
|
90
118
|
updateStandardHeaderHeight();
|
|
91
119
|
handleScroll();
|
|
92
120
|
updatePanelPosition();
|
|
@@ -98,8 +126,8 @@ export function setupLayoutObservers(): void {
|
|
|
98
126
|
}
|
|
99
127
|
};
|
|
100
128
|
|
|
101
|
-
window.addEventListener('scroll',
|
|
102
|
-
window.addEventListener('resize',
|
|
129
|
+
window.addEventListener('scroll', debouncedUpdateListener, { passive: true });
|
|
130
|
+
window.addEventListener('resize', debouncedUpdateListener);
|
|
103
131
|
settingsPanelOpenStore.subscribe(handleSettingsPanelChange);
|
|
104
132
|
|
|
105
133
|
setupPaneObserver();
|
|
@@ -128,3 +156,5 @@ export function handleSettingsPanelMobile(isOpen: boolean): void {
|
|
|
128
156
|
hasScrolledForSettingsPanel = false;
|
|
129
157
|
}
|
|
130
158
|
}
|
|
159
|
+
|
|
160
|
+
document.addEventListener('astro:before-swap', cleanupLayoutObservers);
|
package/utils/inject-files.ts
CHANGED
|
@@ -566,6 +566,10 @@ export async function injectTemplateFiles(
|
|
|
566
566
|
src: resolve('../templates/src/stores/backend.ts'),
|
|
567
567
|
dest: 'src/stores/backend.ts',
|
|
568
568
|
},
|
|
569
|
+
{
|
|
570
|
+
src: resolve('../templates/src/stores/resources.ts'),
|
|
571
|
+
dest: 'src/stores/resources.ts',
|
|
572
|
+
},
|
|
569
573
|
|
|
570
574
|
// Compositor stores
|
|
571
575
|
{
|
|
@@ -1322,6 +1326,10 @@ export async function injectTemplateFiles(
|
|
|
1322
1326
|
src: resolve('../templates/src/lib/session.ts'),
|
|
1323
1327
|
dest: 'src/lib/session.ts',
|
|
1324
1328
|
},
|
|
1329
|
+
{
|
|
1330
|
+
src: resolve('../templates/src/lib/resources.ts'),
|
|
1331
|
+
dest: 'src/lib/resources.ts',
|
|
1332
|
+
},
|
|
1325
1333
|
|
|
1326
1334
|
// Client Scripts
|
|
1327
1335
|
{
|
|
@@ -2098,6 +2106,15 @@ export async function injectTemplateFiles(
|
|
|
2098
2106
|
dest: 'src/custom/CustomRoutes.astro',
|
|
2099
2107
|
protected: true,
|
|
2100
2108
|
},
|
|
2109
|
+
{
|
|
2110
|
+
src: resolve(
|
|
2111
|
+
config?.includeExamples
|
|
2112
|
+
? '../templates/custom/with-examples/HeaderWidget.astro'
|
|
2113
|
+
: '../templates/custom/minimal/HeaderWidget.astro'
|
|
2114
|
+
),
|
|
2115
|
+
dest: 'src/custom/HeaderWidget.astro',
|
|
2116
|
+
protected: true,
|
|
2117
|
+
},
|
|
2101
2118
|
{
|
|
2102
2119
|
src: resolve('../templates/src/utils/customHelpers.ts'),
|
|
2103
2120
|
dest: 'src/utils/customHelpers.ts',
|