astro-tractstack 2.0.0-rc.8 → 2.0.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/LICENSE +8 -97
- package/README.md +7 -5
- package/bin/create-tractstack.js +35 -11
- package/dist/index.js +106 -29
- package/package.json +10 -5
- package/templates/css/frontend.css +1 -1
- package/templates/custom/minimal/CodeHook.astro +13 -12
- package/templates/custom/minimal/CustomRoutes.astro +25 -31
- package/templates/custom/with-examples/CodeHook.astro +22 -11
- package/templates/custom/with-examples/CustomRoutes.astro +4 -8
- package/templates/custom/with-examples/ProductCard.astro +29 -0
- package/templates/custom/with-examples/ProductCardWrapper.astro +43 -0
- package/templates/custom/with-examples/ProductGrid.astro +64 -0
- package/templates/custom/with-examples/pages/Collections.astro +58 -98
- package/templates/gitignore +42 -0
- package/templates/prettierignore +5 -0
- package/templates/prettierrc +19 -0
- package/templates/src/client/app.js +127 -0
- package/templates/src/client/htmx.min.js +3519 -0
- package/templates/src/client/view.js +429 -0
- package/templates/src/components/Footer.astro +4 -9
- package/templates/src/components/Header.astro +67 -60
- package/templates/src/components/Menu.tsx +188 -52
- package/templates/src/components/codehooks/BunnyVideoSetup.tsx +2 -2
- package/templates/src/components/codehooks/EpinetDurationSelector.tsx +9 -13
- package/templates/src/components/codehooks/EpinetTableView.tsx +11 -7
- package/templates/src/components/codehooks/EpinetWrapper.tsx +1 -0
- package/templates/src/components/codehooks/FeaturedArticle.astro +105 -0
- package/templates/src/components/codehooks/FeaturedArticleSetup.tsx +318 -0
- package/templates/src/components/codehooks/ListContent.astro +32 -162
- package/templates/src/components/codehooks/ListContentSetup.tsx +43 -138
- package/templates/src/components/codehooks/ProductCardSetup.tsx +152 -0
- package/templates/src/components/codehooks/ProductGridSetup.tsx +274 -0
- package/templates/src/components/codehooks/SearchWidget.tsx +453 -0
- package/templates/src/components/compositor/Node.tsx +3 -6
- package/templates/src/components/compositor/PanelVisibilityWrapper.tsx +21 -11
- package/templates/src/components/compositor/elements/BunnyVideo.tsx +21 -20
- package/templates/src/components/compositor/nodes/Pane.tsx +51 -21
- package/templates/src/components/compositor/nodes/RenderChildren.tsx +6 -1
- package/templates/src/components/compositor/nodes/Widget.tsx +16 -2
- package/templates/src/components/compositor/preview/FeaturedArticlePreview.tsx +155 -0
- package/templates/src/components/compositor/preview/PaneSnapshotGenerator.tsx +20 -1
- package/templates/src/components/edit/Header.tsx +10 -4
- package/templates/src/components/edit/PanelSwitch.tsx +11 -7
- package/templates/src/components/edit/SettingsPanel.tsx +29 -18
- package/templates/src/components/edit/ToolBar.tsx +1 -28
- package/templates/src/components/edit/ToolMode.tsx +45 -32
- package/templates/src/components/edit/pane/AddPanePanel_break.tsx +12 -2
- package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +8 -2
- package/templates/src/components/edit/pane/AddPanePanel_newAICopy_modal.tsx +1 -1
- package/templates/src/components/edit/pane/ConfigPanePanel.tsx +17 -27
- package/templates/src/components/edit/pane/PageGenSelector.tsx +16 -16
- package/templates/src/components/edit/pane/PageGenSpecial.tsx +26 -49
- package/templates/src/components/edit/pane/PageGen_preview.tsx +17 -2
- package/templates/src/components/edit/pane/PanePanel_path.tsx +2 -4
- package/templates/src/components/edit/pane/PanePanel_title.tsx +243 -76
- package/templates/src/components/edit/panels/StyleBreakPanel.tsx +17 -19
- package/templates/src/components/edit/panels/StyleCodeHookPanel.tsx +48 -37
- package/templates/src/components/edit/panels/StyleElementPanel_add.tsx +60 -55
- package/templates/src/components/edit/panels/StyleImagePanel_add.tsx +56 -50
- package/templates/src/components/edit/panels/StyleLiElementPanel_add.tsx +54 -47
- package/templates/src/components/edit/panels/StyleLinkPanel_add.tsx +54 -44
- package/templates/src/components/edit/panels/StyleLinkPanel_config.tsx +113 -138
- package/templates/src/components/edit/panels/StyleParentPanel_add.tsx +54 -40
- package/templates/src/components/edit/panels/StyleWidgetPanel.tsx +3 -3
- package/templates/src/components/edit/panels/StyleWidgetPanel_add.tsx +56 -49
- package/templates/src/components/edit/panels/StyleWidgetPanel_config.tsx +14 -5
- package/templates/src/components/edit/state/SaveModal.tsx +316 -169
- package/templates/src/components/edit/storyfragment/StoryFragmentPanel_og.tsx +1 -1
- package/templates/src/components/edit/storyfragment/StoryFragmentPanel_slug.tsx +56 -55
- package/templates/src/components/edit/widgets/BunnyWidget.tsx +538 -59
- package/templates/src/components/edit/widgets/InteractiveDisclosureWidget.tsx +656 -0
- package/templates/src/components/edit/widgets/ToggleWidget.tsx +9 -16
- package/templates/src/components/fields/ArtpackImage.tsx +4 -1
- package/templates/src/components/fields/BackgroundImage.tsx +1 -1
- package/templates/src/components/fields/BackgroundImageWrapper.tsx +127 -35
- package/templates/src/components/fields/ColorPickerCombo.tsx +66 -62
- package/templates/src/components/fields/ImageUpload.tsx +1 -1
- package/templates/src/components/fields/ViewportComboBox.tsx +59 -42
- package/templates/src/components/form/ActionBuilderBeliefSelector.tsx +117 -0
- package/templates/src/components/form/ActionBuilderField.tsx +306 -87
- package/templates/src/components/search/SearchModal.tsx +420 -0
- package/templates/src/components/search/SearchResults.tsx +367 -0
- package/templates/src/components/search/SearchWrapper.tsx +46 -0
- package/templates/src/components/storykeep/Dashboard_Advanced.tsx +1 -1
- package/templates/src/components/storykeep/Dashboard_Analytics.tsx +34 -8
- package/templates/src/components/storykeep/Dashboard_Content.tsx +6 -0
- package/templates/src/components/storykeep/StoryKeepBackdrop.astro +87 -0
- package/templates/src/components/storykeep/controls/content/BeliefForm.tsx +37 -33
- package/templates/src/components/storykeep/controls/content/MenuForm.tsx +55 -7
- package/templates/src/components/storykeep/controls/content/ResourceForm.tsx +17 -2
- package/templates/src/components/storykeep/controls/content/StoryFragmentTable.tsx +5 -8
- package/templates/src/components/storykeep/state/FetchAnalytics.tsx +274 -228
- package/templates/src/components/storykeep/widgets/Wizard.tsx +14 -7
- package/templates/src/components/tenant/RegistrationForm.tsx +1 -1
- package/templates/src/components/widgets/ImpressionWrapper.tsx +0 -1
- package/templates/src/constants/shapes.ts +9 -0
- package/templates/src/constants.ts +2121 -16
- package/templates/src/hooks/useSearch.ts +228 -0
- package/templates/src/layouts/Layout.astro +213 -104
- package/templates/src/lib/storyData.ts +4 -1
- package/templates/src/pages/[...slug]/edit.astro +14 -14
- package/templates/src/pages/[...slug].astro +82 -21
- package/templates/src/pages/api/orphan-analysis.ts +0 -1
- package/templates/src/pages/api/tailwind.ts +23 -21
- package/templates/src/pages/context/[...contextSlug]/edit.astro +14 -14
- package/templates/src/pages/context/[...contextSlug].astro +7 -2
- package/templates/src/pages/storykeep/advanced.astro +5 -4
- package/templates/src/pages/storykeep/branding.astro +5 -4
- package/templates/src/pages/storykeep/content.astro +5 -4
- package/templates/src/pages/storykeep/init.astro +40 -1
- package/templates/src/pages/storykeep/login.astro +1 -1
- package/templates/src/pages/storykeep.astro +5 -4
- package/templates/src/stores/nodes.ts +59 -88
- package/templates/src/stores/orphanAnalysis.ts +19 -21
- package/templates/src/stores/storykeep.ts +7 -0
- package/templates/src/types/compositorTypes.ts +6 -0
- package/templates/src/types/tractstack.ts +17 -0
- package/templates/src/utils/actions/lispLexer.ts +2 -2
- package/templates/src/utils/actions/preParse_Action.ts +3 -0
- package/templates/src/utils/api/beliefHelpers.ts +12 -36
- package/templates/src/utils/api/menuHelpers.ts +2 -2
- package/templates/src/utils/api.ts +26 -0
- package/templates/src/utils/compositor/TemplateNodes.ts +7 -0
- package/templates/src/utils/compositor/allowInsert.ts +5 -3
- package/templates/src/utils/compositor/nodesHelper.ts +4 -0
- package/templates/src/utils/compositor/processMarkdown.ts +16 -2
- package/templates/src/utils/compositor/reduceNodesClassNames.ts +4 -0
- package/templates/src/utils/compositor/templateMarkdownStyles.ts +13 -13
- package/templates/src/utils/compositor/typeGuards.ts +1 -0
- package/templates/src/utils/customHelpers.ts +38 -0
- package/templates/src/utils/helpers.ts +2 -2
- package/templates/src/utils/layout.ts +65 -144
- package/utils/inject-files.ts +95 -18
- package/templates/src/client/analytics-events.js +0 -207
- package/templates/src/client/belief-events.js +0 -191
- package/templates/src/client/sse.js +0 -613
- package/templates/src/components/codehooks/FeaturedContent.astro +0 -273
- package/templates/src/components/codehooks/FeaturedContentSetup.tsx +0 -738
- package/templates/src/components/compositor/preview/FeaturedContentPreview.tsx +0 -128
- package/templates/src/components/edit/pane/PanePanel_slug.tsx +0 -219
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { useState, useCallback, useRef, useMemo } from 'react';
|
|
2
|
+
import { TractStackAPI } from '@/utils/api';
|
|
3
|
+
import type {
|
|
4
|
+
DiscoverySuggestion,
|
|
5
|
+
CategorizedResults,
|
|
6
|
+
} from '@/types/tractstack';
|
|
7
|
+
|
|
8
|
+
interface UseSearchReturn {
|
|
9
|
+
// Discovery phase
|
|
10
|
+
suggestions: DiscoverySuggestion[];
|
|
11
|
+
isDiscovering: boolean;
|
|
12
|
+
discoverError: string | null;
|
|
13
|
+
|
|
14
|
+
// Retrieve phase
|
|
15
|
+
searchResults: CategorizedResults | null;
|
|
16
|
+
isRetrieving: boolean;
|
|
17
|
+
retrieveError: string | null;
|
|
18
|
+
|
|
19
|
+
// Actions
|
|
20
|
+
discoverTerms: (query: string) => void;
|
|
21
|
+
selectSuggestion: (suggestion: DiscoverySuggestion) => void;
|
|
22
|
+
selectExactMatch: (term: string) => void;
|
|
23
|
+
clearAll: () => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const DEBOUNCE_MS = 150;
|
|
27
|
+
const BACKEND_THROTTLE_MS = 1200;
|
|
28
|
+
|
|
29
|
+
export function useSearch(): UseSearchReturn {
|
|
30
|
+
// Discovery state
|
|
31
|
+
const [suggestions, setSuggestions] = useState<DiscoverySuggestion[]>([]);
|
|
32
|
+
const [isDiscovering, setIsDiscovering] = useState(false);
|
|
33
|
+
const [discoverError, setDiscoverError] = useState<string | null>(null);
|
|
34
|
+
|
|
35
|
+
// Retrieve state
|
|
36
|
+
const [searchResults, setSearchResults] = useState<CategorizedResults | null>(
|
|
37
|
+
null
|
|
38
|
+
);
|
|
39
|
+
const [isRetrieving, setIsRetrieving] = useState(false);
|
|
40
|
+
const [retrieveError, setRetrieveError] = useState<string | null>(null);
|
|
41
|
+
|
|
42
|
+
// --- REVISED STATE FOR SEARCH LOGIC ---
|
|
43
|
+
const searchTimerRef = useRef<NodeJS.Timeout>();
|
|
44
|
+
const lastExecutionTimeRef = useRef<number>(0);
|
|
45
|
+
const pendingQueryRef = useRef<string | null>(null);
|
|
46
|
+
const inflightQueryRef = useRef<string | null>(null);
|
|
47
|
+
const api = useMemo(() => new TractStackAPI(), []);
|
|
48
|
+
|
|
49
|
+
const performDiscovery = useCallback(
|
|
50
|
+
async (query: string) => {
|
|
51
|
+
if (!query.trim()) {
|
|
52
|
+
setSuggestions([]);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (inflightQueryRef.current === query.trim()) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
inflightQueryRef.current = query.trim();
|
|
61
|
+
setIsDiscovering(true);
|
|
62
|
+
setDiscoverError(null);
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const response = await api.discover(query.trim());
|
|
66
|
+
|
|
67
|
+
if (inflightQueryRef.current === query.trim()) {
|
|
68
|
+
if (response.success && response.data) {
|
|
69
|
+
setSuggestions(response.data.suggestions);
|
|
70
|
+
} else {
|
|
71
|
+
setDiscoverError(response.error || 'Discovery failed');
|
|
72
|
+
setSuggestions([]);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
} catch (err) {
|
|
76
|
+
if (inflightQueryRef.current === query.trim()) {
|
|
77
|
+
setDiscoverError(
|
|
78
|
+
err instanceof Error ? err.message : 'Discovery failed'
|
|
79
|
+
);
|
|
80
|
+
setSuggestions([]);
|
|
81
|
+
}
|
|
82
|
+
} finally {
|
|
83
|
+
if (inflightQueryRef.current === query.trim()) {
|
|
84
|
+
inflightQueryRef.current = null;
|
|
85
|
+
setIsDiscovering(false);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
[api]
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const performRetrieve = useCallback(
|
|
93
|
+
async (term: string, isTopic: boolean = false) => {
|
|
94
|
+
setIsRetrieving(true);
|
|
95
|
+
setRetrieveError(null);
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const response = await api.retrieve(term, isTopic);
|
|
99
|
+
|
|
100
|
+
if (response.success && response.data) {
|
|
101
|
+
setSearchResults(response.data);
|
|
102
|
+
} else {
|
|
103
|
+
setRetrieveError(response.error || 'Retrieval failed');
|
|
104
|
+
setSearchResults(null);
|
|
105
|
+
}
|
|
106
|
+
} catch (err) {
|
|
107
|
+
setRetrieveError(
|
|
108
|
+
err instanceof Error ? err.message : 'Retrieval failed'
|
|
109
|
+
);
|
|
110
|
+
setSearchResults(null);
|
|
111
|
+
} finally {
|
|
112
|
+
setIsRetrieving(false);
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
[api]
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const discoverTerms = useCallback(
|
|
119
|
+
(query: string) => {
|
|
120
|
+
// Clear any existing timer.
|
|
121
|
+
if (searchTimerRef.current) {
|
|
122
|
+
clearTimeout(searchTimerRef.current);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Clear results when starting new discovery
|
|
126
|
+
setSearchResults(null);
|
|
127
|
+
setRetrieveError(null);
|
|
128
|
+
|
|
129
|
+
// Handle empty queries immediately
|
|
130
|
+
if (!query.trim()) {
|
|
131
|
+
setSuggestions([]);
|
|
132
|
+
setDiscoverError(null);
|
|
133
|
+
setIsDiscovering(false);
|
|
134
|
+
pendingQueryRef.current = null;
|
|
135
|
+
inflightQueryRef.current = null;
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Always store the latest query for the next execution
|
|
140
|
+
pendingQueryRef.current = query;
|
|
141
|
+
|
|
142
|
+
const now = Date.now();
|
|
143
|
+
const timeSinceLastSearch = now - lastExecutionTimeRef.current;
|
|
144
|
+
|
|
145
|
+
// Start with the basic debounce delay
|
|
146
|
+
let delay = DEBOUNCE_MS;
|
|
147
|
+
|
|
148
|
+
// If we are inside the throttle window, we must wait longer
|
|
149
|
+
if (timeSinceLastSearch < BACKEND_THROTTLE_MS) {
|
|
150
|
+
const remainingThrottle = BACKEND_THROTTLE_MS - timeSinceLastSearch;
|
|
151
|
+
delay = Math.max(delay, remainingThrottle);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
searchTimerRef.current = setTimeout(() => {
|
|
155
|
+
// Double check there's a query to run
|
|
156
|
+
if (pendingQueryRef.current !== null) {
|
|
157
|
+
const queryToExecute = pendingQueryRef.current;
|
|
158
|
+
|
|
159
|
+
// Update execution time as soon as the search is initiated
|
|
160
|
+
lastExecutionTimeRef.current = Date.now();
|
|
161
|
+
performDiscovery(queryToExecute);
|
|
162
|
+
}
|
|
163
|
+
}, delay);
|
|
164
|
+
},
|
|
165
|
+
[performDiscovery]
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const selectSuggestion = useCallback(
|
|
169
|
+
(suggestion: DiscoverySuggestion) => {
|
|
170
|
+
// Clear suggestions
|
|
171
|
+
setSuggestions([]);
|
|
172
|
+
setDiscoverError(null);
|
|
173
|
+
|
|
174
|
+
// Perform retrieve based on suggestion type
|
|
175
|
+
const isTopic = suggestion.type === 'TOPIC';
|
|
176
|
+
performRetrieve(suggestion.term, isTopic);
|
|
177
|
+
},
|
|
178
|
+
[performRetrieve]
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
const selectExactMatch = useCallback(
|
|
182
|
+
(term: string) => {
|
|
183
|
+
// Clear suggestions
|
|
184
|
+
setSuggestions([]);
|
|
185
|
+
setDiscoverError(null);
|
|
186
|
+
|
|
187
|
+
// Check if term exists in current suggestions to determine if it's a topic
|
|
188
|
+
const matchingSuggestion = suggestions.find(
|
|
189
|
+
(s) => s.term.toLowerCase() === term.toLowerCase()
|
|
190
|
+
);
|
|
191
|
+
const isTopic = matchingSuggestion?.type === 'TOPIC';
|
|
192
|
+
|
|
193
|
+
performRetrieve(term, isTopic);
|
|
194
|
+
},
|
|
195
|
+
[suggestions, performRetrieve]
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
const clearAll = useCallback(() => {
|
|
199
|
+
// Clear the main search timer
|
|
200
|
+
if (searchTimerRef.current) {
|
|
201
|
+
clearTimeout(searchTimerRef.current);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Reset all state
|
|
205
|
+
setSuggestions([]);
|
|
206
|
+
setIsDiscovering(false);
|
|
207
|
+
setDiscoverError(null);
|
|
208
|
+
setSearchResults(null);
|
|
209
|
+
setIsRetrieving(false);
|
|
210
|
+
setRetrieveError(null);
|
|
211
|
+
pendingQueryRef.current = null;
|
|
212
|
+
inflightQueryRef.current = null;
|
|
213
|
+
lastExecutionTimeRef.current = 0; // Reset throttle timer
|
|
214
|
+
}, []);
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
suggestions,
|
|
218
|
+
isDiscovering,
|
|
219
|
+
discoverError,
|
|
220
|
+
searchResults,
|
|
221
|
+
isRetrieving,
|
|
222
|
+
retrieveError,
|
|
223
|
+
discoverTerms,
|
|
224
|
+
selectSuggestion,
|
|
225
|
+
selectExactMatch,
|
|
226
|
+
clearAll,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
@@ -45,48 +45,42 @@ const {
|
|
|
45
45
|
impressions = [],
|
|
46
46
|
} = Astro.props;
|
|
47
47
|
|
|
48
|
-
const isDev = import.meta.env.DEV;
|
|
49
|
-
|
|
50
|
-
// Get site status from the store
|
|
51
48
|
const isInitialized = !freshInstallStore.get().needsSetup;
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
const goBackend = import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
|
|
50
|
+
const tenantId =
|
|
51
|
+
Astro.locals.tenant?.id || import.meta.env.PUBLIC_TENANTID || 'default';
|
|
52
|
+
const brandConfig = propBrandConfig || (await getBrandConfig(tenantId));
|
|
54
53
|
const cssBasePath = isInitialized ? '/media/css' : '/styles';
|
|
55
54
|
const fontBasePath = isInitialized ? '/media/fonts' : '/fonts';
|
|
56
|
-
const mainStylesUrl =
|
|
57
|
-
|
|
55
|
+
const mainStylesUrl = (() => {
|
|
56
|
+
const baseUrl = isStoryKeep
|
|
58
57
|
? `${cssBasePath}/storykeep.css`
|
|
59
58
|
: `${cssBasePath}/frontend.css`;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
if (!isStoryKeep && brandConfig?.STYLES_VER) {
|
|
60
|
+
return `${baseUrl}?v=${brandConfig.STYLES_VER}`;
|
|
61
|
+
}
|
|
62
|
+
return baseUrl;
|
|
63
|
+
})();
|
|
63
64
|
|
|
64
|
-
// Social media and SEO setup
|
|
65
|
-
const brandConfig = propBrandConfig || (await getBrandConfig(tenantId));
|
|
66
65
|
const defaultFavIcon = brandConfig.FAVICON || `/brand/favicon.ico`;
|
|
67
66
|
const defaultSocialImageURL = ogImage || brandConfig.OG || `/brand/og.png`;
|
|
68
67
|
const defaultSocialLogoURL = brandConfig.OGLOGO || `/brand/oglogo.png`;
|
|
69
68
|
const defaultSocialTitle =
|
|
70
|
-
|
|
71
|
-
? title
|
|
72
|
-
: typeof brandConfig.OGTITLE === `string`
|
|
73
|
-
? brandConfig.OGTITLE
|
|
74
|
-
: `TractStack dynamic website`;
|
|
69
|
+
title || brandConfig.OGTITLE || `TractStack dynamic website`;
|
|
75
70
|
const defaultSocialAuthor = brandConfig.OGAUTHOR || `TractStack`;
|
|
76
71
|
const defaultSocialDesc =
|
|
77
72
|
description ||
|
|
78
73
|
brandConfig.OGDESC ||
|
|
79
74
|
`No-code website builder and content marketing platform`;
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const socialLogoWithVersion = `${defaultSocialLogoURL}?v=${buildVersion}`;
|
|
75
|
+
const socialImageFullURL = brandConfig?.SITE_URL
|
|
76
|
+
? `${brandConfig.SITE_URL.replace(/\/$/, '')}${defaultSocialImageURL}`
|
|
77
|
+
: `${defaultSocialImageURL}`;
|
|
78
|
+
const socialLogoFullURL = brandConfig?.SITE_URL
|
|
79
|
+
? `${brandConfig.SITE_URL.replace(/\/$/, '')}${defaultSocialLogoURL}`
|
|
80
|
+
: `${defaultSocialLogoURL}`;
|
|
87
81
|
const gtagId = brandConfig?.GTAG || false;
|
|
88
82
|
const gtagUrl =
|
|
89
|
-
typeof gtagId === `string` && gtagId.length > 1
|
|
83
|
+
gtagId && typeof gtagId === `string` && gtagId.length > 1
|
|
90
84
|
? `https://www.googletagmanager.com/gtag/js?id=${gtagId}`
|
|
91
85
|
: null;
|
|
92
86
|
const fullCanonicalUrl = brandConfig?.SITE_URL
|
|
@@ -94,6 +88,8 @@ const fullCanonicalUrl = brandConfig?.SITE_URL
|
|
|
94
88
|
? `${brandConfig.SITE_URL.replace(/\/$/, '')}${canonicalURL}`
|
|
95
89
|
: `${brandConfig.SITE_URL.replace(/\/$/, '')}/${canonicalURL}`
|
|
96
90
|
: canonicalURL;
|
|
91
|
+
|
|
92
|
+
const enableBunny = import.meta.env.PUBLIC_ENABLE_BUNNY === 'true';
|
|
97
93
|
---
|
|
98
94
|
|
|
99
95
|
<!doctype html>
|
|
@@ -114,6 +110,11 @@ const fullCanonicalUrl = brandConfig?.SITE_URL
|
|
|
114
110
|
<link rel="stylesheet" href={mainStylesUrl} />
|
|
115
111
|
<link rel="sitemap" href="/sitemap.xml" />
|
|
116
112
|
|
|
113
|
+
<link
|
|
114
|
+
rel="stylesheet"
|
|
115
|
+
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/font/bootstrap-icons.min.css"
|
|
116
|
+
/>
|
|
117
|
+
|
|
117
118
|
<meta name="storyfragment-id" content={storyfragmentId} />
|
|
118
119
|
<meta name="session-id" content={sessionId} />
|
|
119
120
|
|
|
@@ -134,14 +135,14 @@ const fullCanonicalUrl = brandConfig?.SITE_URL
|
|
|
134
135
|
<meta property="og:type" content="website" />
|
|
135
136
|
<meta property="og:description" content={defaultSocialDesc} />
|
|
136
137
|
<meta property="og:url" content={fullCanonicalUrl} />
|
|
137
|
-
<meta property="og:image" content={
|
|
138
|
-
<meta property="og:logo" content={
|
|
138
|
+
<meta property="og:image" content={socialImageFullURL} />
|
|
139
|
+
<meta property="og:logo" content={socialLogoFullURL} />
|
|
139
140
|
|
|
140
141
|
<meta property="twitter:card" content="summary_large_image" />
|
|
141
142
|
<meta property="twitter:url" content={fullCanonicalUrl} />
|
|
142
143
|
<meta property="twitter:title" content={defaultSocialTitle} />
|
|
143
144
|
<meta property="twitter:description" content={defaultSocialDesc} />
|
|
144
|
-
<meta property="twitter:image" content={
|
|
145
|
+
<meta property="twitter:image" content={socialImageFullURL} />
|
|
145
146
|
|
|
146
147
|
{
|
|
147
148
|
pubDatetime && (
|
|
@@ -183,16 +184,17 @@ const fullCanonicalUrl = brandConfig?.SITE_URL
|
|
|
183
184
|
|
|
184
185
|
<ClientRouter />
|
|
185
186
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
187
|
+
{
|
|
188
|
+
enableBunny && (
|
|
189
|
+
<script
|
|
190
|
+
is:inline
|
|
191
|
+
type="text/javascript"
|
|
192
|
+
src="//assets.mediadelivery.net/playerjs/player-0.1.0.min.js"
|
|
193
|
+
/>
|
|
194
|
+
)
|
|
195
|
+
}
|
|
190
196
|
|
|
191
|
-
<script
|
|
192
|
-
src="https://unpkg.com/htmx.org@2.0.4"
|
|
193
|
-
crossorigin="anonymous"
|
|
194
|
-
is:inline
|
|
195
|
-
is:persist></script>
|
|
197
|
+
<script src="/client/htmx.min.js" is:inline></script>
|
|
196
198
|
|
|
197
199
|
<script
|
|
198
200
|
define:vars={{
|
|
@@ -204,87 +206,194 @@ const fullCanonicalUrl = brandConfig?.SITE_URL
|
|
|
204
206
|
}}
|
|
205
207
|
is:inline
|
|
206
208
|
>
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
tenantId: tenantId,
|
|
212
|
-
fontBasePath: fontBasePath,
|
|
213
|
-
storyfragmentId: storyfragmentId,
|
|
214
|
-
sessionId: sessionId,
|
|
215
|
-
};
|
|
216
|
-
}
|
|
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;
|
|
217
213
|
|
|
218
214
|
function updateTractstackConfig() {
|
|
219
|
-
// Get current values from meta tags
|
|
220
215
|
const storyfragmentMeta = document.querySelector(
|
|
221
216
|
'meta[name="storyfragment-id"]'
|
|
222
217
|
);
|
|
223
218
|
const sessionMeta = document.querySelector('meta[name="session-id"]');
|
|
224
219
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
:
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
// Check if config exists.
|
|
238
|
-
// If not, create it. This handles the very first load.
|
|
239
|
-
if (!window.TRACTSTACK_CONFIG) {
|
|
240
|
-
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
|
+
};
|
|
241
232
|
}
|
|
242
233
|
|
|
243
|
-
|
|
234
|
+
updateTractstackConfig();
|
|
244
235
|
document.addEventListener('astro:page-load', updateTractstackConfig);
|
|
245
236
|
</script>
|
|
246
237
|
</head>
|
|
247
238
|
<body class="font-main w-full">
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
239
|
+
<div class="overflow-hidden">
|
|
240
|
+
{
|
|
241
|
+
!isEditor && (
|
|
242
|
+
<Header
|
|
243
|
+
title={title}
|
|
244
|
+
slug={slug}
|
|
245
|
+
brandConfig={brandConfig}
|
|
246
|
+
isContext={isContext}
|
|
247
|
+
isStoryKeep={isStoryKeep}
|
|
248
|
+
isEditable={isEditable}
|
|
249
|
+
sessionId={sessionId}
|
|
250
|
+
menu={menu}
|
|
251
|
+
storyfragmentId={storyfragmentId}
|
|
252
|
+
impressions={impressions}
|
|
253
|
+
/>
|
|
254
|
+
)
|
|
255
|
+
}
|
|
256
|
+
<div
|
|
257
|
+
id="loading-backdrop"
|
|
258
|
+
class="pointer-events-none fixed inset-0"
|
|
259
|
+
style="opacity: 0; z-index: 10069; display: none; background-color: rgba(255, 255, 255, 0.3); backdrop-filter: blur(4px); transition: opacity 300ms;"
|
|
260
|
+
>
|
|
261
|
+
</div>
|
|
262
|
+
<div
|
|
263
|
+
id="loading-indicator"
|
|
264
|
+
class="bg-myorange fixed left-0 top-0 h-1 w-full scale-x-0 transform transition-transform duration-300 ease-out"
|
|
265
|
+
style="opacity: 0.5; filter: blur(0.5px); z-index: 99999;"
|
|
266
|
+
>
|
|
267
|
+
</div>
|
|
268
|
+
|
|
269
|
+
<div id="content" class="transition-opacity duration-300">
|
|
270
|
+
<slot />
|
|
271
|
+
</div>
|
|
272
|
+
{
|
|
273
|
+
!isEditor && (
|
|
274
|
+
<Footer
|
|
275
|
+
slug={slug}
|
|
276
|
+
brandConfig={brandConfig}
|
|
277
|
+
isContext={isContext}
|
|
278
|
+
menu={menu}
|
|
279
|
+
created={created}
|
|
280
|
+
backToTop={true}
|
|
281
|
+
/>
|
|
282
|
+
)
|
|
283
|
+
}
|
|
271
284
|
</div>
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
285
|
+
|
|
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>
|
|
289
|
+
|
|
290
|
+
<script is:inline is:persist>
|
|
291
|
+
let navProgressInterval = null;
|
|
292
|
+
let navSafetyTimeout = null;
|
|
293
|
+
|
|
294
|
+
function startNavLoadingAnimation() {
|
|
295
|
+
const loadingIndicator = document.getElementById('loading-indicator');
|
|
296
|
+
const loadingBackdrop = document.getElementById('loading-backdrop');
|
|
297
|
+
const content = document.getElementById('content');
|
|
298
|
+
|
|
299
|
+
if (
|
|
300
|
+
window.matchMedia('(prefers-reduced-motion: no-preference)')
|
|
301
|
+
.matches &&
|
|
302
|
+
loadingIndicator &&
|
|
303
|
+
loadingBackdrop
|
|
304
|
+
) {
|
|
305
|
+
if (navSafetyTimeout !== null) {
|
|
306
|
+
clearTimeout(navSafetyTimeout);
|
|
307
|
+
navSafetyTimeout = null;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
loadingBackdrop.style.display = 'block';
|
|
311
|
+
void loadingBackdrop.offsetHeight;
|
|
312
|
+
loadingBackdrop.style.opacity = '1';
|
|
313
|
+
|
|
314
|
+
if (content) {
|
|
315
|
+
content.style.opacity = '0.7';
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
loadingIndicator.style.transform = 'scaleX(0)';
|
|
319
|
+
loadingIndicator.style.display = 'block';
|
|
320
|
+
|
|
321
|
+
let progress = 0;
|
|
322
|
+
navProgressInterval = setInterval(() => {
|
|
323
|
+
progress += 2;
|
|
324
|
+
if (progress > 90) {
|
|
325
|
+
if (navProgressInterval !== null) {
|
|
326
|
+
clearInterval(navProgressInterval);
|
|
327
|
+
}
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
loadingIndicator.style.transform = `scaleX(${progress / 100})`;
|
|
331
|
+
}, 20);
|
|
332
|
+
|
|
333
|
+
navSafetyTimeout = setTimeout(() => {
|
|
334
|
+
stopNavLoadingAnimation();
|
|
335
|
+
}, 10000);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function stopNavLoadingAnimation() {
|
|
340
|
+
const loadingIndicator = document.getElementById('loading-indicator');
|
|
341
|
+
const loadingBackdrop = document.getElementById('loading-backdrop');
|
|
342
|
+
const content = document.getElementById('content');
|
|
343
|
+
|
|
344
|
+
if (navSafetyTimeout !== null) {
|
|
345
|
+
clearTimeout(navSafetyTimeout);
|
|
346
|
+
navSafetyTimeout = null;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (
|
|
350
|
+
window.matchMedia('(prefers-reduced-motion: no-preference)')
|
|
351
|
+
.matches &&
|
|
352
|
+
loadingIndicator &&
|
|
353
|
+
loadingBackdrop
|
|
354
|
+
) {
|
|
355
|
+
if (navProgressInterval !== null) {
|
|
356
|
+
clearInterval(navProgressInterval);
|
|
357
|
+
navProgressInterval = null;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
loadingIndicator.style.transform = 'scaleX(1)';
|
|
361
|
+
|
|
362
|
+
if (content) {
|
|
363
|
+
content.style.opacity = '1';
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
loadingBackdrop.style.opacity = '0';
|
|
367
|
+
|
|
368
|
+
setTimeout(() => {
|
|
369
|
+
loadingIndicator.style.display = 'none';
|
|
370
|
+
loadingIndicator.style.transform = 'scaleX(0)';
|
|
371
|
+
loadingBackdrop.style.display = 'none';
|
|
372
|
+
}, 300);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function setupNavigationLoading() {
|
|
377
|
+
document.addEventListener('astro:before-preparation', () => {
|
|
378
|
+
startNavLoadingAnimation();
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
document.addEventListener('astro:page-load', () => {
|
|
382
|
+
stopNavLoadingAnimation();
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
document.addEventListener('astro:after-swap', () => {
|
|
386
|
+
stopNavLoadingAnimation();
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (document.readyState === 'loading') {
|
|
391
|
+
document.addEventListener('DOMContentLoaded', setupNavigationLoading);
|
|
392
|
+
} else {
|
|
393
|
+
setupNavigationLoading();
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
document.addEventListener('astro:page-load', setupNavigationLoading);
|
|
288
397
|
</script>
|
|
289
398
|
</body>
|
|
290
399
|
</html>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { handleFailedResponse } from '@/utils/backend';
|
|
2
|
-
import type { ImpressionNode } from '@/types/compositorTypes';
|
|
2
|
+
import type { ImpressionNode, ResourceNode } from '@/types/compositorTypes';
|
|
3
3
|
|
|
4
4
|
export interface StoryData {
|
|
5
5
|
id: string;
|
|
@@ -7,11 +7,14 @@ export interface StoryData {
|
|
|
7
7
|
slug: string;
|
|
8
8
|
paneIds: string[];
|
|
9
9
|
codeHookTargets: Record<string, string>;
|
|
10
|
+
codeHookVisibility: Record<string, boolean | string[]>;
|
|
11
|
+
resourcesPayload: Record<string, ResourceNode[]>;
|
|
10
12
|
impressions: ImpressionNode[];
|
|
11
13
|
fragments: Record<string, string>;
|
|
12
14
|
menu: any;
|
|
13
15
|
isHome: boolean;
|
|
14
16
|
created: string;
|
|
17
|
+
socialImagePath?: string | null;
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
export async function getStoryData(
|