astro-tractstack 2.0.0-rc.64 → 2.0.0-rc.65
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 +4 -8
- package/package.json +1 -1
- package/templates/src/client/app.js +127 -0
- package/templates/src/client/view.js +423 -0
- package/templates/src/layouts/Layout.astro +24 -65
- package/templates/src/pages/[...slug].astro +1 -1
- package/templates/src/pages/context/[...contextSlug].astro +1 -1
- package/utils/inject-files.ts +4 -8
- package/templates/src/client/analytics-events.js +0 -207
- package/templates/src/client/belief-events.js +0 -210
- package/templates/src/client/sse.js +0 -683
|
@@ -45,41 +45,28 @@ const {
|
|
|
45
45
|
impressions = [],
|
|
46
46
|
} = Astro.props;
|
|
47
47
|
|
|
48
|
-
// Get site status from the store
|
|
49
48
|
const isInitialized = !freshInstallStore.get().needsSetup;
|
|
50
|
-
|
|
51
|
-
// ensure we have brand config!
|
|
52
49
|
const goBackend = import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
|
|
53
50
|
const tenantId =
|
|
54
51
|
Astro.locals.tenant?.id || import.meta.env.PUBLIC_TENANTID || 'default';
|
|
55
52
|
const brandConfig = propBrandConfig || (await getBrandConfig(tenantId));
|
|
56
|
-
|
|
57
|
-
// Conditionally set asset paths based on initialization status
|
|
58
53
|
const cssBasePath = isInitialized ? '/media/css' : '/styles';
|
|
59
54
|
const fontBasePath = isInitialized ? '/media/fonts' : '/fonts';
|
|
60
55
|
const mainStylesUrl = (() => {
|
|
61
|
-
const baseUrl = isStoryKeep
|
|
56
|
+
const baseUrl = isStoryKeep
|
|
62
57
|
? `${cssBasePath}/storykeep.css`
|
|
63
58
|
: `${cssBasePath}/frontend.css`;
|
|
64
|
-
|
|
65
|
-
// Only add version for frontend.css (the dynamic one)
|
|
66
59
|
if (!isStoryKeep && brandConfig?.STYLES_VER) {
|
|
67
60
|
return `${baseUrl}?v=${brandConfig.STYLES_VER}`;
|
|
68
61
|
}
|
|
69
|
-
|
|
70
62
|
return baseUrl;
|
|
71
63
|
})();
|
|
72
64
|
|
|
73
|
-
// Social media and SEO setup
|
|
74
65
|
const defaultFavIcon = brandConfig.FAVICON || `/brand/favicon.ico`;
|
|
75
66
|
const defaultSocialImageURL = ogImage || brandConfig.OG || `/brand/og.png`;
|
|
76
67
|
const defaultSocialLogoURL = brandConfig.OGLOGO || `/brand/oglogo.png`;
|
|
77
68
|
const defaultSocialTitle =
|
|
78
|
-
|
|
79
|
-
? title
|
|
80
|
-
: typeof brandConfig.OGTITLE === `string`
|
|
81
|
-
? brandConfig.OGTITLE
|
|
82
|
-
: `TractStack dynamic website`;
|
|
69
|
+
title || brandConfig.OGTITLE || `TractStack dynamic website`;
|
|
83
70
|
const defaultSocialAuthor = brandConfig.OGAUTHOR || `TractStack`;
|
|
84
71
|
const defaultSocialDesc =
|
|
85
72
|
description ||
|
|
@@ -93,7 +80,7 @@ const socialLogoFullURL = brandConfig?.SITE_URL
|
|
|
93
80
|
: `${defaultSocialLogoURL}`;
|
|
94
81
|
const gtagId = brandConfig?.GTAG || false;
|
|
95
82
|
const gtagUrl =
|
|
96
|
-
typeof gtagId === `string` && gtagId.length > 1
|
|
83
|
+
gtagId && typeof gtagId === `string` && gtagId.length > 1
|
|
97
84
|
? `https://www.googletagmanager.com/gtag/js?id=${gtagId}`
|
|
98
85
|
: null;
|
|
99
86
|
const fullCanonicalUrl = brandConfig?.SITE_URL
|
|
@@ -207,7 +194,7 @@ const enableBunny = import.meta.env.PUBLIC_ENABLE_BUNNY === 'true';
|
|
|
207
194
|
)
|
|
208
195
|
}
|
|
209
196
|
|
|
210
|
-
<script src="/client/htmx.min.js" is:inline
|
|
197
|
+
<script src="/client/htmx.min.js" is:inline></script>
|
|
211
198
|
|
|
212
199
|
<script
|
|
213
200
|
define:vars={{
|
|
@@ -219,43 +206,32 @@ const enableBunny = import.meta.env.PUBLIC_ENABLE_BUNNY === 'true';
|
|
|
219
206
|
}}
|
|
220
207
|
is:inline
|
|
221
208
|
>
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
tenantId: tenantId,
|
|
227
|
-
fontBasePath: fontBasePath,
|
|
228
|
-
storyfragmentId: storyfragmentId,
|
|
229
|
-
sessionId: sessionId,
|
|
230
|
-
};
|
|
231
|
-
}
|
|
209
|
+
// Capture the initial, server-rendered values from define:vars into top-level constants.
|
|
210
|
+
// This makes the scope explicit and resolves the linter error.
|
|
211
|
+
const initialStoryfragmentId = storyfragmentId;
|
|
212
|
+
const initialSessionId = sessionId;
|
|
232
213
|
|
|
233
214
|
function updateTractstackConfig() {
|
|
234
|
-
// Get current values from meta tags
|
|
235
215
|
const storyfragmentMeta = document.querySelector(
|
|
236
216
|
'meta[name="storyfragment-id"]'
|
|
237
217
|
);
|
|
238
218
|
const sessionMeta = document.querySelector('meta[name="session-id"]');
|
|
239
219
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
:
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
// Check if config exists.
|
|
253
|
-
// If not, create it. This handles the very first load.
|
|
254
|
-
if (!window.TRACTSTACK_CONFIG) {
|
|
255
|
-
createTractstackConfig();
|
|
220
|
+
window.TRACTSTACK_CONFIG = {
|
|
221
|
+
configured: true,
|
|
222
|
+
backendUrl: goBackend,
|
|
223
|
+
tenantId: tenantId,
|
|
224
|
+
fontBasePath: fontBasePath,
|
|
225
|
+
// Use the meta tag if it exists (for subsequent client-side loads),
|
|
226
|
+
// otherwise fall back to the initial server-rendered value.
|
|
227
|
+
storyfragmentId: storyfragmentMeta
|
|
228
|
+
? storyfragmentMeta.content
|
|
229
|
+
: initialStoryfragmentId,
|
|
230
|
+
sessionId: sessionMeta ? sessionMeta.content : initialSessionId,
|
|
231
|
+
};
|
|
256
232
|
}
|
|
257
233
|
|
|
258
|
-
|
|
234
|
+
updateTractstackConfig();
|
|
259
235
|
document.addEventListener('astro:page-load', updateTractstackConfig);
|
|
260
236
|
</script>
|
|
261
237
|
</head>
|
|
@@ -307,14 +283,11 @@ const enableBunny = import.meta.env.PUBLIC_ENABLE_BUNNY === 'true';
|
|
|
307
283
|
}
|
|
308
284
|
</div>
|
|
309
285
|
|
|
310
|
-
<script type="module" is:inline is:persist
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
import '/client/analytics-events.js';
|
|
314
|
-
</script>
|
|
286
|
+
<script type="module" src="/client/app.js" is:inline is:persist></script>
|
|
287
|
+
|
|
288
|
+
<script type="module" src="/client/view.js" is:inline></script>
|
|
315
289
|
|
|
316
290
|
<script is:inline is:persist>
|
|
317
|
-
// Navigation loading animation with blur backdrop
|
|
318
291
|
let navProgressInterval = null;
|
|
319
292
|
let navSafetyTimeout = null;
|
|
320
293
|
|
|
@@ -329,24 +302,19 @@ const enableBunny = import.meta.env.PUBLIC_ENABLE_BUNNY === 'true';
|
|
|
329
302
|
loadingIndicator &&
|
|
330
303
|
loadingBackdrop
|
|
331
304
|
) {
|
|
332
|
-
// Clear any existing safety timeout
|
|
333
305
|
if (navSafetyTimeout !== null) {
|
|
334
306
|
clearTimeout(navSafetyTimeout);
|
|
335
307
|
navSafetyTimeout = null;
|
|
336
308
|
}
|
|
337
309
|
|
|
338
|
-
// Show and animate backdrop
|
|
339
310
|
loadingBackdrop.style.display = 'block';
|
|
340
|
-
// Force reflow to ensure display:block is applied before opacity transition
|
|
341
311
|
void loadingBackdrop.offsetHeight;
|
|
342
312
|
loadingBackdrop.style.opacity = '1';
|
|
343
313
|
|
|
344
|
-
// Fade out content slightly
|
|
345
314
|
if (content) {
|
|
346
315
|
content.style.opacity = '0.7';
|
|
347
316
|
}
|
|
348
317
|
|
|
349
|
-
// Animate progress bar
|
|
350
318
|
loadingIndicator.style.transform = 'scaleX(0)';
|
|
351
319
|
loadingIndicator.style.display = 'block';
|
|
352
320
|
|
|
@@ -362,7 +330,6 @@ const enableBunny = import.meta.env.PUBLIC_ENABLE_BUNNY === 'true';
|
|
|
362
330
|
loadingIndicator.style.transform = `scaleX(${progress / 100})`;
|
|
363
331
|
}, 20);
|
|
364
332
|
|
|
365
|
-
// AUTOMATIC SHUTOFF: Always stop after 10 seconds
|
|
366
333
|
navSafetyTimeout = setTimeout(() => {
|
|
367
334
|
stopNavLoadingAnimation();
|
|
368
335
|
}, 10000);
|
|
@@ -374,7 +341,6 @@ const enableBunny = import.meta.env.PUBLIC_ENABLE_BUNNY === 'true';
|
|
|
374
341
|
const loadingBackdrop = document.getElementById('loading-backdrop');
|
|
375
342
|
const content = document.getElementById('content');
|
|
376
343
|
|
|
377
|
-
// Clear safety timeout
|
|
378
344
|
if (navSafetyTimeout !== null) {
|
|
379
345
|
clearTimeout(navSafetyTimeout);
|
|
380
346
|
navSafetyTimeout = null;
|
|
@@ -386,21 +352,17 @@ const enableBunny = import.meta.env.PUBLIC_ENABLE_BUNNY === 'true';
|
|
|
386
352
|
loadingIndicator &&
|
|
387
353
|
loadingBackdrop
|
|
388
354
|
) {
|
|
389
|
-
// Clear interval
|
|
390
355
|
if (navProgressInterval !== null) {
|
|
391
356
|
clearInterval(navProgressInterval);
|
|
392
357
|
navProgressInterval = null;
|
|
393
358
|
}
|
|
394
359
|
|
|
395
|
-
// Complete progress bar
|
|
396
360
|
loadingIndicator.style.transform = 'scaleX(1)';
|
|
397
361
|
|
|
398
|
-
// Restore content opacity
|
|
399
362
|
if (content) {
|
|
400
363
|
content.style.opacity = '1';
|
|
401
364
|
}
|
|
402
365
|
|
|
403
|
-
// Hide backdrop
|
|
404
366
|
loadingBackdrop.style.opacity = '0';
|
|
405
367
|
|
|
406
368
|
setTimeout(() => {
|
|
@@ -411,7 +373,6 @@ const enableBunny = import.meta.env.PUBLIC_ENABLE_BUNNY === 'true';
|
|
|
411
373
|
}
|
|
412
374
|
}
|
|
413
375
|
|
|
414
|
-
// Set up navigation loading
|
|
415
376
|
function setupNavigationLoading() {
|
|
416
377
|
document.addEventListener('astro:before-preparation', () => {
|
|
417
378
|
startNavLoadingAnimation();
|
|
@@ -426,14 +387,12 @@ const enableBunny = import.meta.env.PUBLIC_ENABLE_BUNNY === 'true';
|
|
|
426
387
|
});
|
|
427
388
|
}
|
|
428
389
|
|
|
429
|
-
// Initialize
|
|
430
390
|
if (document.readyState === 'loading') {
|
|
431
391
|
document.addEventListener('DOMContentLoaded', setupNavigationLoading);
|
|
432
392
|
} else {
|
|
433
393
|
setupNavigationLoading();
|
|
434
394
|
}
|
|
435
395
|
|
|
436
|
-
// Re-setup after navigation
|
|
437
396
|
document.addEventListener('astro:page-load', setupNavigationLoading);
|
|
438
397
|
</script>
|
|
439
398
|
</body>
|
|
@@ -148,7 +148,7 @@ paneIds.forEach((paneId: string) => {
|
|
|
148
148
|
}
|
|
149
149
|
hx-get={`/api/v1/fragments/panes/${paneId}`}
|
|
150
150
|
hx-trigger="refresh"
|
|
151
|
-
hx-swap="innerHTML"
|
|
151
|
+
hx-swap="innerHTML scroll:none"
|
|
152
152
|
>
|
|
153
153
|
{codeHookTargets[paneId] ? (
|
|
154
154
|
<div class="relative overflow-hidden">
|
package/utils/inject-files.ts
CHANGED
|
@@ -1317,16 +1317,12 @@ export async function injectTemplateFiles(
|
|
|
1317
1317
|
dest: 'public/client/htmx.min.js',
|
|
1318
1318
|
},
|
|
1319
1319
|
{
|
|
1320
|
-
src: resolve('../templates/src/client/
|
|
1321
|
-
dest: 'public/client/
|
|
1320
|
+
src: resolve('../templates/src/client/view.js'),
|
|
1321
|
+
dest: 'public/client/view.js',
|
|
1322
1322
|
},
|
|
1323
1323
|
{
|
|
1324
|
-
src: resolve('../templates/src/client/
|
|
1325
|
-
dest: 'public/client/
|
|
1326
|
-
},
|
|
1327
|
-
{
|
|
1328
|
-
src: resolve('../templates/src/client/analytics-events.js'),
|
|
1329
|
-
dest: 'public/client/analytics-events.js',
|
|
1324
|
+
src: resolve('../templates/src/client/app.js'),
|
|
1325
|
+
dest: 'public/client/app.js',
|
|
1330
1326
|
},
|
|
1331
1327
|
|
|
1332
1328
|
// StoryKeep Editor (add new section)
|
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
const THRESHOLD_GLOSSED = 7000; // 7 seconds in ms
|
|
2
|
-
const THRESHOLD_READ = 42000; // 42 seconds in ms
|
|
3
|
-
const VERBOSE = false;
|
|
4
|
-
|
|
5
|
-
const paneViewTimes = new Map();
|
|
6
|
-
let hasTrackedEntered =
|
|
7
|
-
localStorage.getItem('tractstack_entered_tracked') === 'true';
|
|
8
|
-
let currentStoryfragmentId = null;
|
|
9
|
-
let isPageInitialized = false;
|
|
10
|
-
let globalObserver = null;
|
|
11
|
-
|
|
12
|
-
function waitForSessionReady() {
|
|
13
|
-
return new Promise((resolve) => {
|
|
14
|
-
if (window.TRACTSTACK_CONFIG?.session?.isReady) {
|
|
15
|
-
resolve();
|
|
16
|
-
} else {
|
|
17
|
-
window.addEventListener('tractstack:session-ready', () => resolve(), {
|
|
18
|
-
once: true,
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export async function initAnalyticsTracking(storyfragmentId) {
|
|
25
|
-
if (isPageInitialized) return;
|
|
26
|
-
isPageInitialized = true;
|
|
27
|
-
|
|
28
|
-
if (VERBOSE)
|
|
29
|
-
console.log('📊 ANALYTICS: Initializing tracking for page view.');
|
|
30
|
-
currentStoryfragmentId = storyfragmentId || null;
|
|
31
|
-
initPaneVisibilityTracking();
|
|
32
|
-
await waitForSessionReady();
|
|
33
|
-
trackEnteredEvent();
|
|
34
|
-
trackPageViewedEvent();
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function trackEnteredEvent() {
|
|
38
|
-
if (!hasTrackedEntered && currentStoryfragmentId) {
|
|
39
|
-
sendAnalyticsEvent({
|
|
40
|
-
contentId: currentStoryfragmentId,
|
|
41
|
-
contentType: 'StoryFragment',
|
|
42
|
-
eventVerb: 'ENTERED',
|
|
43
|
-
});
|
|
44
|
-
hasTrackedEntered = true;
|
|
45
|
-
localStorage.setItem('tractstack_entered_tracked', 'true');
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function trackPageViewedEvent() {
|
|
50
|
-
if (currentStoryfragmentId) {
|
|
51
|
-
// This event is now PURELY for analytics. It no longer triggers
|
|
52
|
-
// a synchronization workaround on the backend.
|
|
53
|
-
sendAnalyticsEvent({
|
|
54
|
-
contentId: currentStoryfragmentId,
|
|
55
|
-
contentType: 'StoryFragment',
|
|
56
|
-
eventVerb: 'PAGEVIEWED',
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function initPaneVisibilityTracking() {
|
|
62
|
-
if (globalObserver) {
|
|
63
|
-
globalObserver.disconnect();
|
|
64
|
-
}
|
|
65
|
-
globalObserver = new IntersectionObserver(
|
|
66
|
-
(entries) => {
|
|
67
|
-
entries.forEach((entry) => {
|
|
68
|
-
const paneId = getPaneIdFromElement(entry.target);
|
|
69
|
-
if (!paneId) return;
|
|
70
|
-
if (entry.isIntersecting) {
|
|
71
|
-
if (!paneViewTimes.has(paneId)) {
|
|
72
|
-
paneViewTimes.set(paneId, Date.now());
|
|
73
|
-
}
|
|
74
|
-
} else {
|
|
75
|
-
const startTime = paneViewTimes.get(paneId);
|
|
76
|
-
if (startTime) {
|
|
77
|
-
const duration = Date.now() - startTime;
|
|
78
|
-
paneViewTimes.delete(paneId);
|
|
79
|
-
let eventVerb = null;
|
|
80
|
-
if (duration >= THRESHOLD_READ) eventVerb = 'READ';
|
|
81
|
-
else if (duration >= THRESHOLD_GLOSSED) eventVerb = 'GLOSSED';
|
|
82
|
-
if (eventVerb) {
|
|
83
|
-
sendAnalyticsEvent({
|
|
84
|
-
contentId: paneId,
|
|
85
|
-
contentType: 'Pane',
|
|
86
|
-
eventVerb,
|
|
87
|
-
duration,
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
},
|
|
94
|
-
{ threshold: 0.1, rootMargin: '0px' }
|
|
95
|
-
);
|
|
96
|
-
observeAllPanes();
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function observeAllPanes() {
|
|
100
|
-
if (!globalObserver) return;
|
|
101
|
-
const panes = document.querySelectorAll('[data-pane-id]');
|
|
102
|
-
panes.forEach((pane) => globalObserver.observe(pane));
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function setupLifecycleListeners() {
|
|
106
|
-
if (window.ANALYTICS_INITIALIZED) return;
|
|
107
|
-
window.ANALYTICS_INITIALIZED = true;
|
|
108
|
-
|
|
109
|
-
if (VERBOSE)
|
|
110
|
-
console.log(
|
|
111
|
-
'📊 ANALYTICS: Setting up lifecycle listeners for the first time.'
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
window.addEventListener('beforeunload', () => {
|
|
115
|
-
flushPendingPaneEvents();
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
document.addEventListener('astro:page-load', () => {
|
|
119
|
-
isPageInitialized = false;
|
|
120
|
-
flushPendingPaneEvents();
|
|
121
|
-
if (window.TRACTSTACK_CONFIG?.storyfragmentId) {
|
|
122
|
-
initAnalyticsTracking(window.TRACTSTACK_CONFIG.storyfragmentId);
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
if (document.readyState === 'complete') {
|
|
127
|
-
if (window.TRACTSTACK_CONFIG?.storyfragmentId) {
|
|
128
|
-
initAnalyticsTracking(window.TRACTSTACK_CONFIG.storyfragmentId);
|
|
129
|
-
}
|
|
130
|
-
} else {
|
|
131
|
-
document.addEventListener(
|
|
132
|
-
'DOMContentLoaded',
|
|
133
|
-
() => {
|
|
134
|
-
if (window.TRACTSTACK_CONFIG?.storyfragmentId) {
|
|
135
|
-
initAnalyticsTracking(window.TRACTSTACK_CONFIG.storyfragmentId);
|
|
136
|
-
}
|
|
137
|
-
},
|
|
138
|
-
{ once: true }
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function flushPendingPaneEvents() {
|
|
144
|
-
if (paneViewTimes.size === 0) return;
|
|
145
|
-
const flushTime = Date.now();
|
|
146
|
-
paneViewTimes.forEach((startTime, paneId) => {
|
|
147
|
-
const duration = flushTime - startTime;
|
|
148
|
-
let eventVerb = null;
|
|
149
|
-
if (duration >= THRESHOLD_READ) eventVerb = 'READ';
|
|
150
|
-
else if (duration >= THRESHOLD_GLOSSED) eventVerb = 'GLOSSED';
|
|
151
|
-
if (eventVerb) {
|
|
152
|
-
sendAnalyticsEvent({
|
|
153
|
-
contentId: paneId,
|
|
154
|
-
contentType: 'Pane',
|
|
155
|
-
eventVerb,
|
|
156
|
-
duration,
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
paneViewTimes.clear();
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
async function sendAnalyticsEvent(event) {
|
|
164
|
-
try {
|
|
165
|
-
const config = window.TRACTSTACK_CONFIG;
|
|
166
|
-
if (!config || !config.sessionId) return; // Use server-provided session ID
|
|
167
|
-
|
|
168
|
-
const sessionId = config.sessionId;
|
|
169
|
-
const formData = {
|
|
170
|
-
beliefId: event.contentId,
|
|
171
|
-
beliefType: event.contentType,
|
|
172
|
-
beliefValue: event.eventVerb,
|
|
173
|
-
paneId: '',
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
if (event.duration !== undefined) {
|
|
177
|
-
formData.duration = event.duration.toString();
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
await fetch(`${config.backendUrl}/api/v1/state`, {
|
|
181
|
-
method: 'POST',
|
|
182
|
-
headers: {
|
|
183
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
184
|
-
'X-Tenant-ID': config.tenantId,
|
|
185
|
-
'X-TractStack-Session-ID': sessionId,
|
|
186
|
-
'X-StoryFragment-ID': config.storyfragmentId,
|
|
187
|
-
},
|
|
188
|
-
body: new URLSearchParams(formData),
|
|
189
|
-
});
|
|
190
|
-
} catch (error) {
|
|
191
|
-
console.error('❌ API ERROR: Analytics event failed', error, event);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function getPaneIdFromElement(element) {
|
|
196
|
-
return element.getAttribute('data-pane-id');
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
window.initAnalyticsTracking = initAnalyticsTracking;
|
|
200
|
-
|
|
201
|
-
setupLifecycleListeners();
|
|
202
|
-
|
|
203
|
-
if (VERBOSE) {
|
|
204
|
-
console.log(
|
|
205
|
-
'📊 ANALYTICS: Analytics events module loaded and is persistent.'
|
|
206
|
-
);
|
|
207
|
-
}
|
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
const VERBOSE = false;
|
|
2
|
-
|
|
3
|
-
function configureHtmx() {
|
|
4
|
-
if (!window.htmx || window.HTMX_LISTENER_ATTACHED) {
|
|
5
|
-
return;
|
|
6
|
-
}
|
|
7
|
-
window.HTMX_LISTENER_ATTACHED = true;
|
|
8
|
-
|
|
9
|
-
if (!window.HTMX_CONFIGURED) {
|
|
10
|
-
window.htmx.config.selfRequestsOnly = false;
|
|
11
|
-
window.HTMX_CONFIGURED = true;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
window.htmx.on(document.body, 'htmx:configRequest', function (evt) {
|
|
15
|
-
const config = window.TRACTSTACK_CONFIG;
|
|
16
|
-
if (!config || !config.sessionId) return;
|
|
17
|
-
|
|
18
|
-
if (evt.detail.path && evt.detail.path.startsWith('/api/v1/')) {
|
|
19
|
-
evt.detail.path = config.backendUrl + evt.detail.path;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const sessionId = config.sessionId;
|
|
23
|
-
evt.detail.headers['X-Tenant-ID'] = config.tenantId;
|
|
24
|
-
evt.detail.headers['X-StoryFragment-ID'] = config.storyfragmentId;
|
|
25
|
-
if (sessionId) {
|
|
26
|
-
evt.detail.headers['X-TractStack-Session-ID'] = sessionId;
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
window.htmx.on(document.body, 'htmx:beforeRequest', async function (evt) {
|
|
31
|
-
const params = evt.detail.requestConfig.parameters;
|
|
32
|
-
if (params && params.beliefVerb === 'IDENTIFY_AS') {
|
|
33
|
-
evt.preventDefault();
|
|
34
|
-
|
|
35
|
-
const originalPayload = params;
|
|
36
|
-
const unsetPayload = {
|
|
37
|
-
unsetBeliefIds: originalPayload.beliefId,
|
|
38
|
-
paneId: originalPayload.paneId || '',
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
await sendBeliefUpdate(unsetPayload);
|
|
43
|
-
await sendBeliefUpdate(originalPayload);
|
|
44
|
-
} catch (error) {
|
|
45
|
-
if (VERBOSE)
|
|
46
|
-
console.error('🔴 BELIEF: Two-step identifyAs update failed', error);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const pageBeliefs = {};
|
|
53
|
-
let activeStoryfragmentId = null;
|
|
54
|
-
|
|
55
|
-
function waitForSessionReady() {
|
|
56
|
-
return new Promise((resolve) => {
|
|
57
|
-
if (window.TRACTSTACK_CONFIG?.session?.isReady) {
|
|
58
|
-
resolve();
|
|
59
|
-
} else {
|
|
60
|
-
window.addEventListener('tractstack:session-ready', () => resolve(), {
|
|
61
|
-
once: true,
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function initializeBeliefs() {
|
|
68
|
-
if (window.BELIEF_INITIALIZED) {
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
window.BELIEF_INITIALIZED = true;
|
|
72
|
-
|
|
73
|
-
if (VERBOSE)
|
|
74
|
-
console.log(
|
|
75
|
-
'🔧 BELIEF: First-time initialization of belief handlers and HTMX config.'
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
configureHtmx();
|
|
79
|
-
|
|
80
|
-
document.addEventListener('change', function (event) {
|
|
81
|
-
const target = event.target;
|
|
82
|
-
if (
|
|
83
|
-
target.matches &&
|
|
84
|
-
(target.matches('select[data-belief-id]') ||
|
|
85
|
-
target.matches('input[type="checkbox"][data-belief-id]'))
|
|
86
|
-
) {
|
|
87
|
-
handleBeliefChange(target);
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async function handleBeliefChange(element) {
|
|
93
|
-
const beliefId = element.getAttribute('data-belief-id');
|
|
94
|
-
const beliefType = element.getAttribute('data-belief-type');
|
|
95
|
-
const paneId = element.getAttribute('data-pane-id');
|
|
96
|
-
|
|
97
|
-
if (!beliefId || !beliefType) {
|
|
98
|
-
if (VERBOSE)
|
|
99
|
-
console.error('🔴 BELIEF: Missing required attributes on', element.id);
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
let beliefValue;
|
|
104
|
-
if (element.type === 'checkbox') {
|
|
105
|
-
beliefValue = element.checked ? 'BELIEVES_YES' : 'BELIEVES_NO';
|
|
106
|
-
} else {
|
|
107
|
-
beliefValue = element.value;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (VERBOSE)
|
|
111
|
-
console.log('🔄 BELIEF: Widget changed', {
|
|
112
|
-
beliefId,
|
|
113
|
-
beliefType,
|
|
114
|
-
beliefValue,
|
|
115
|
-
paneId,
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
trackBeliefState(beliefId, beliefValue);
|
|
119
|
-
|
|
120
|
-
await sendBeliefUpdate({
|
|
121
|
-
beliefId,
|
|
122
|
-
beliefType,
|
|
123
|
-
beliefValue,
|
|
124
|
-
paneId: paneId || '',
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
async function sendBeliefUpdate(data) {
|
|
129
|
-
await waitForSessionReady();
|
|
130
|
-
|
|
131
|
-
try {
|
|
132
|
-
const config = window.TRACTSTACK_CONFIG;
|
|
133
|
-
if (!config || !config.sessionId) return;
|
|
134
|
-
|
|
135
|
-
if (VERBOSE)
|
|
136
|
-
console.log('🚨 FRONTEND DEBUG: Sending belief update with headers:', {
|
|
137
|
-
'X-StoryFragment-ID': config.storyfragmentId,
|
|
138
|
-
beliefData: data,
|
|
139
|
-
currentSSEContext: 'check sse.ts logs',
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
const sessionId = config.sessionId;
|
|
143
|
-
const headers = {
|
|
144
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
145
|
-
'X-Tenant-ID': config.tenantId,
|
|
146
|
-
'X-StoryFragment-ID': config.storyfragmentId,
|
|
147
|
-
'X-TractStack-Session-ID': sessionId,
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
if (VERBOSE)
|
|
151
|
-
console.log('📡 BELIEF: Session ready, sending update.', { data });
|
|
152
|
-
|
|
153
|
-
const response = await fetch(`${config.backendUrl}/api/v1/state`, {
|
|
154
|
-
method: 'POST',
|
|
155
|
-
headers: headers,
|
|
156
|
-
body: new URLSearchParams(data),
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
if (!response.ok) {
|
|
160
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
161
|
-
}
|
|
162
|
-
} catch (error) {
|
|
163
|
-
const errorMessage =
|
|
164
|
-
error instanceof Error ? error.message : 'Unknown error';
|
|
165
|
-
if (VERBOSE)
|
|
166
|
-
console.error('🔴 BELIEF: Update failed', {
|
|
167
|
-
error: errorMessage,
|
|
168
|
-
beliefId: data.beliefId,
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function trackBeliefState(beliefId, beliefValue) {
|
|
174
|
-
if (!activeStoryfragmentId) return;
|
|
175
|
-
|
|
176
|
-
if (!pageBeliefs[activeStoryfragmentId]) {
|
|
177
|
-
pageBeliefs[activeStoryfragmentId] = {};
|
|
178
|
-
}
|
|
179
|
-
pageBeliefs[activeStoryfragmentId][beliefId] = beliefValue;
|
|
180
|
-
if (VERBOSE)
|
|
181
|
-
console.log(`📝 BELIEF: Tracked state for page ${activeStoryfragmentId}`, {
|
|
182
|
-
...pageBeliefs[activeStoryfragmentId],
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function setActiveStoryFragment() {
|
|
187
|
-
if (window.TRACTSTACK_CONFIG?.storyfragmentId) {
|
|
188
|
-
activeStoryfragmentId = window.TRACTSTACK_CONFIG.storyfragmentId;
|
|
189
|
-
if (VERBOSE)
|
|
190
|
-
console.log(
|
|
191
|
-
`📖 BELIEF: Active story fragment set to ${activeStoryfragmentId}`
|
|
192
|
-
);
|
|
193
|
-
|
|
194
|
-
if (!pageBeliefs[activeStoryfragmentId]) {
|
|
195
|
-
pageBeliefs[activeStoryfragmentId] = {};
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
initializeBeliefs();
|
|
201
|
-
|
|
202
|
-
document.addEventListener('astro:page-load', () => {
|
|
203
|
-
configureHtmx();
|
|
204
|
-
setActiveStoryFragment();
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
document.addEventListener('DOMContentLoaded', setActiveStoryFragment);
|
|
208
|
-
|
|
209
|
-
if (VERBOSE)
|
|
210
|
-
console.log('🔧 BELIEF: Belief events module loaded and is persistent.');
|