astro-tractstack 2.1.2 → 2.2.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/README.md +54 -266
- package/bin/create-tractstack.js +9 -6
- package/dist/index.js +109 -71
- package/package.json +4 -2
- package/templates/css/custom.css +5 -0
- package/templates/custom/minimal/CodeHook.astro +1 -0
- package/templates/custom/with-examples/CodeHook.astro +1 -0
- package/templates/icons/code.svg +18 -0
- package/templates/icons/li.svg +4 -0
- package/templates/icons/link.svg +22 -0
- package/templates/icons/p.svg +3 -0
- package/templates/src/client/app.js +80 -1
- package/templates/src/components/Footer.astro +1 -1
- package/templates/src/components/codehooks/BunnyVideoSetup.tsx +6 -6
- package/templates/src/components/codehooks/EpinetDurationSelector.tsx +3 -3
- package/templates/src/components/codehooks/FeaturedArticleSetup.tsx +1 -1
- package/templates/src/components/codehooks/ListContentSetup.tsx +2 -2
- package/templates/src/components/codehooks/ProductCardSetup.tsx +1 -1
- package/templates/src/components/codehooks/ProductGridSetup.tsx +2 -2
- package/templates/src/components/codehooks/SandboxRegisterForm.tsx +3 -3
- package/templates/src/components/compositor/Compositor.tsx +25 -9
- package/templates/src/components/compositor/Node.tsx +168 -496
- package/templates/src/components/compositor/PanelVisibilityWrapper.tsx +1 -0
- package/templates/src/components/compositor/elements/SignUp.tsx +1 -1
- package/templates/src/components/compositor/elements/YouTubeWrapper.tsx +2 -0
- package/templates/src/components/compositor/nodes/CreativePane.tsx +262 -0
- package/templates/src/components/compositor/nodes/GhostInsertBlock.tsx +4 -6
- package/templates/src/components/compositor/nodes/GridLayout.tsx +4 -2
- package/templates/src/components/compositor/nodes/Markdown.tsx +18 -3
- package/templates/src/components/compositor/nodes/Pane.tsx +11 -5
- package/templates/src/components/compositor/nodes/RenderChildren.tsx +1 -1
- package/templates/src/components/compositor/nodes/tagElements/NodeAnchorComponent.tsx +5 -5
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag.tsx +90 -42
- package/templates/src/components/compositor/nodes/tagElements/NodeImg.tsx +2 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeText.tsx +27 -1
- package/templates/src/components/compositor/preview/PaneSnapshotGenerator.tsx +10 -8
- package/templates/src/components/compositor/tools/NodeOverlay.tsx +224 -0
- package/templates/src/components/compositor/tools/PaneOverlay.tsx +122 -0
- package/templates/src/components/edit/Header.tsx +68 -9
- package/templates/src/components/edit/PanelSwitch.tsx +42 -4
- package/templates/src/components/edit/SettingsPanel.tsx +2 -3
- package/templates/src/components/edit/ToolMode.tsx +1 -31
- package/templates/src/components/edit/pane/AddPanePanel_break.tsx +2 -2
- package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +1 -1
- package/templates/src/components/edit/pane/AddPanePanel_new.tsx +193 -659
- package/templates/src/components/edit/pane/AddPanePanel_reuse.tsx +15 -82
- package/templates/src/components/edit/pane/AiRestylePaneModal.tsx +95 -45
- package/templates/src/components/edit/pane/ConfigPanePanel.tsx +137 -49
- package/templates/src/components/edit/pane/RestylePaneModal.tsx +1 -1
- package/templates/src/components/edit/pane/steps/AiCreativeDesignStep.tsx +375 -0
- package/templates/src/components/edit/pane/steps/AiDesignStep.tsx +1 -23
- package/templates/src/components/edit/pane/steps/AiLibraryCopyStep.tsx +327 -0
- package/templates/src/components/edit/pane/steps/AiRefineDesignStep.tsx +267 -0
- package/templates/src/components/edit/pane/steps/AiStandardDesignStep.tsx +371 -0
- package/templates/src/components/edit/pane/steps/CopyInputStep.tsx +201 -76
- package/templates/src/components/edit/pane/steps/CreativeInjectStep.tsx +141 -0
- package/templates/src/components/edit/panels/CreativeImagePanel.tsx +435 -0
- package/templates/src/components/edit/panels/CreativeLinkPanel.tsx +110 -0
- package/templates/src/components/edit/panels/StyleCodeHookPanel.tsx +1 -1
- package/templates/src/components/edit/panels/StyleParentPanel.tsx +118 -126
- package/templates/src/components/edit/panels/StyleParentPanel_add.tsx +3 -2
- package/templates/src/components/edit/panels/StyleParentPanel_deleteLayer.tsx +1 -0
- package/templates/src/components/edit/panels/StyleParentPanel_remove.tsx +3 -1
- package/templates/src/components/edit/panels/StyleParentPanel_update.tsx +3 -1
- package/templates/src/components/edit/panels/StyleWidgetPanel.tsx +1 -1
- package/templates/src/components/edit/state/SaveModal.tsx +19 -787
- package/templates/src/components/edit/state/SaveToLibraryModal.tsx +2 -2
- package/templates/src/components/edit/storyfragment/StoryFragmentPanel_menu.tsx +1 -1
- package/templates/src/components/edit/widgets/BunnyWidget.tsx +5 -5
- package/templates/src/components/edit/widgets/InteractiveDisclosureWidget.tsx +1 -1
- package/templates/src/components/edit/widgets/SignupWidget.tsx +1 -1
- package/templates/src/components/fields/ActionBuilderTimeSelector.tsx +1 -1
- package/templates/src/components/fields/ArtpackImage.tsx +11 -3
- package/templates/src/components/fields/BackgroundImage.tsx +8 -0
- package/templates/src/components/fields/BackgroundImageWrapper.tsx +15 -9
- package/templates/src/components/fields/ImageUpload.tsx +6 -0
- package/templates/src/components/form/ActionBuilderField.tsx +15 -5
- package/templates/src/components/form/ActionBuilderSlugSelector.tsx +1 -1
- package/templates/src/components/form/ColorPicker.tsx +1 -1
- package/templates/src/components/form/EnumSelect.tsx +1 -1
- package/templates/src/components/form/NumberInput.tsx +1 -1
- package/templates/src/components/form/StringArrayInput.tsx +1 -1
- package/templates/src/components/form/StringInput.tsx +1 -1
- package/templates/src/components/form/UnsavedChangesBar.tsx +1 -1
- package/templates/src/components/form/advanced/APIConfigSection.tsx +2 -2
- package/templates/src/components/form/advanced/AuthConfigSection.tsx +2 -2
- package/templates/src/components/profile/ProfileCreate.tsx +1 -1
- package/templates/src/components/profile/ProfileEdit.tsx +1 -1
- package/templates/src/components/storykeep/Dashboard_Advanced.tsx +2 -2
- package/templates/src/components/storykeep/controls/content/BeliefForm.tsx +1 -1
- package/templates/src/components/storykeep/controls/content/ContentSummary.tsx +2 -2
- package/templates/src/components/storykeep/controls/content/KnownResourceTable.tsx +1 -1
- package/templates/src/components/storykeep/controls/content/ManageContent.tsx +6 -6
- package/templates/src/components/storykeep/controls/content/MenuForm.tsx +1 -1
- package/templates/src/components/storykeep/controls/content/PaneTable.tsx +358 -0
- package/templates/src/components/storykeep/controls/content/ResourceTable.tsx +1 -1
- package/templates/src/constants/prompts.json +18 -10
- package/templates/src/constants.ts +3 -0
- package/templates/src/hooks/usePaneFragments.ts +60 -0
- package/templates/src/lib/session.ts +71 -16
- package/templates/src/pages/[...slug].astro +5 -46
- package/templates/src/pages/api/css.ts +149 -0
- package/templates/src/pages/context/[...contextSlug].astro +1 -0
- package/templates/src/pages/maint.astro +1 -1
- package/templates/src/pages/storykeep/login.astro +2 -2
- package/templates/src/stores/nodes.ts +162 -49
- package/templates/src/stores/orphanAnalysis.ts +6 -30
- package/templates/src/stores/previews.ts +7 -0
- package/templates/src/stores/storykeep.ts +0 -8
- package/templates/src/types/compositorTypes.ts +53 -10
- package/templates/src/utils/compositor/aiGeneration.ts +93 -0
- package/templates/src/utils/compositor/allowInsert.ts +2 -0
- package/templates/src/utils/compositor/htmlAst.ts +704 -0
- package/templates/src/utils/compositor/nodesHelper.ts +281 -102
- package/templates/src/utils/compositor/savePipeline.ts +893 -0
- package/templates/src/utils/etl/index.ts +3 -0
- package/templates/src/utils/etl/transformer.ts +10 -0
- package/templates/src/utils/helpers.ts +101 -0
- package/utils/inject-files.ts +100 -62
- package/templates/icons/text.svg +0 -6
- package/templates/src/components/compositor/NodeWithGuid.tsx +0 -69
- package/templates/src/components/compositor/nodes/GridLayout_eraser.tsx +0 -33
- package/templates/src/components/compositor/nodes/Markdown_eraser.tsx +0 -56
- package/templates/src/components/compositor/nodes/Pane_DesignLibrary.tsx +0 -269
- package/templates/src/components/compositor/nodes/Pane_eraser.tsx +0 -186
- package/templates/src/components/compositor/nodes/Pane_layout.tsx +0 -79
- package/templates/src/components/compositor/nodes/tagElements/NodeA_eraser.tsx +0 -26
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_eraser.tsx +0 -61
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_insert.tsx +0 -120
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_settings.tsx +0 -62
- package/templates/src/components/compositor/nodes/tagElements/NodeButton_eraser.tsx +0 -26
|
@@ -1,13 +1,22 @@
|
|
|
1
|
+
const VERBOSE = false;
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Calls the backend's /api/v1/auth/visit endpoint to create a new session.
|
|
3
5
|
* This function is decoupled from Astro-specific objects.
|
|
4
6
|
* @param tenantId The tenant ID to use for the backend request.
|
|
5
|
-
* @returns A promise that resolves to the new session
|
|
7
|
+
* @returns A promise that resolves to the new session object from the backend.
|
|
6
8
|
*/
|
|
7
|
-
export async function createBackendSession(
|
|
9
|
+
export async function createBackendSession(
|
|
10
|
+
tenantId: string
|
|
11
|
+
): Promise<{ sessionId: string; fingerprintId?: string }> {
|
|
8
12
|
const goBackend =
|
|
9
13
|
import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
|
|
10
14
|
|
|
15
|
+
if (VERBOSE)
|
|
16
|
+
console.log(
|
|
17
|
+
`[session.ts] createBackendSession called for tenant: ${tenantId}`
|
|
18
|
+
);
|
|
19
|
+
|
|
11
20
|
try {
|
|
12
21
|
const response = await fetch(`${goBackend}/api/v1/auth/visit`, {
|
|
13
22
|
method: 'POST',
|
|
@@ -28,18 +37,20 @@ export async function createBackendSession(tenantId: string): Promise<string> {
|
|
|
28
37
|
}
|
|
29
38
|
|
|
30
39
|
const result = await response.json();
|
|
40
|
+
if (VERBOSE)
|
|
41
|
+
console.log(`[session.ts] createBackendSession success:`, result);
|
|
42
|
+
|
|
31
43
|
if (!result.sessionId) {
|
|
32
44
|
console.error('Backend did not return a sessionId on warming', result);
|
|
33
45
|
throw new Error('Backend did not return a sessionId');
|
|
34
46
|
}
|
|
35
|
-
return
|
|
47
|
+
return {
|
|
48
|
+
sessionId: result.sessionId,
|
|
49
|
+
fingerprintId: result.fingerprint,
|
|
50
|
+
};
|
|
36
51
|
} catch (error) {
|
|
37
|
-
console.
|
|
38
|
-
|
|
39
|
-
);
|
|
40
|
-
return `ssr-fallback-${Date.now()}-${Math.random()
|
|
41
|
-
.toString(36)
|
|
42
|
-
.substring(2, 11)}`;
|
|
52
|
+
console.error('Failed to initialize backend session:', error);
|
|
53
|
+
throw error;
|
|
43
54
|
}
|
|
44
55
|
}
|
|
45
56
|
|
|
@@ -58,10 +69,26 @@ export async function getOrSetSessionId(
|
|
|
58
69
|
): Promise<string> {
|
|
59
70
|
// Check if we already have a session ID in the cookie
|
|
60
71
|
let sessionId = astro.cookies.get('tractstack_session_id')?.value;
|
|
72
|
+
const fingerprintId = astro.cookies.get('tractstack_fingerprint')?.value;
|
|
73
|
+
|
|
74
|
+
if (VERBOSE)
|
|
75
|
+
console.log(`[session.ts] getOrSetSessionId - Cookies Found:`, {
|
|
76
|
+
sessionId,
|
|
77
|
+
fingerprintId,
|
|
78
|
+
});
|
|
61
79
|
|
|
62
80
|
if (sessionId) {
|
|
63
81
|
// Validate session exists in backend before using it
|
|
64
|
-
|
|
82
|
+
// We pass fingerprintId to allow the backend to restore the session from DB if RAM is empty
|
|
83
|
+
const isValid = await validateSessionWithBackend(
|
|
84
|
+
sessionId,
|
|
85
|
+
tenantId,
|
|
86
|
+
fingerprintId
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
if (VERBOSE)
|
|
90
|
+
console.log(`[session.ts] Session Validation Result:`, isValid);
|
|
91
|
+
|
|
65
92
|
if (!isValid) {
|
|
66
93
|
console.warn(`Session ${sessionId} invalid, creating new session`);
|
|
67
94
|
sessionId = ''; // Force new session creation
|
|
@@ -69,8 +96,12 @@ export async function getOrSetSessionId(
|
|
|
69
96
|
}
|
|
70
97
|
|
|
71
98
|
if (!sessionId) {
|
|
99
|
+
if (VERBOSE)
|
|
100
|
+
console.log(`[session.ts] No valid session found. Creating new one...`);
|
|
101
|
+
|
|
72
102
|
// Call backend to generate collision-free session ID AND warm session
|
|
73
|
-
|
|
103
|
+
const sessionData = await createBackendSession(tenantId);
|
|
104
|
+
sessionId = sessionData.sessionId;
|
|
74
105
|
|
|
75
106
|
// Set cookie with backend-provided session ID
|
|
76
107
|
astro.cookies.set('tractstack_session_id', sessionId, {
|
|
@@ -81,9 +112,20 @@ export async function getOrSetSessionId(
|
|
|
81
112
|
maxAge: 60 * 60 * 24, // 24 hours
|
|
82
113
|
});
|
|
83
114
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
115
|
+
if (sessionData.fingerprintId) {
|
|
116
|
+
astro.cookies.set('tractstack_fingerprint', sessionData.fingerprintId, {
|
|
117
|
+
httpOnly: false,
|
|
118
|
+
secure: import.meta.env.PROD,
|
|
119
|
+
sameSite: 'lax',
|
|
120
|
+
path: '/',
|
|
121
|
+
maxAge: 60 * 60 * 24 * 365, // 1 year persistence
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
if (VERBOSE)
|
|
125
|
+
console.log(`[session.ts] New Cookies Set:`, {
|
|
126
|
+
sessionId,
|
|
127
|
+
fingerprintId: sessionData.fingerprintId,
|
|
128
|
+
});
|
|
87
129
|
}
|
|
88
130
|
|
|
89
131
|
return sessionId;
|
|
@@ -95,13 +137,19 @@ export async function getOrSetSessionId(
|
|
|
95
137
|
*/
|
|
96
138
|
async function validateSessionWithBackend(
|
|
97
139
|
sessionId: string,
|
|
98
|
-
tenantId: string
|
|
140
|
+
tenantId: string,
|
|
141
|
+
fingerprintId?: string
|
|
99
142
|
): Promise<boolean> {
|
|
100
143
|
const goBackend =
|
|
101
144
|
import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
|
|
102
145
|
|
|
146
|
+
if (VERBOSE)
|
|
147
|
+
console.log(`[session.ts] Validating session with backend...`, {
|
|
148
|
+
sessionId,
|
|
149
|
+
fingerprintId,
|
|
150
|
+
});
|
|
151
|
+
|
|
103
152
|
try {
|
|
104
|
-
// Use a lightweight endpoint to check if session exists
|
|
105
153
|
const response = await fetch(`${goBackend}/api/v1/auth/visit`, {
|
|
106
154
|
method: 'POST',
|
|
107
155
|
headers: {
|
|
@@ -110,10 +158,16 @@ async function validateSessionWithBackend(
|
|
|
110
158
|
},
|
|
111
159
|
body: JSON.stringify({
|
|
112
160
|
sessionId: sessionId,
|
|
161
|
+
fingerprintId: fingerprintId, // Explicitly pass for restoration (fixes SSR Cookie Gap)
|
|
113
162
|
}),
|
|
114
163
|
});
|
|
115
164
|
|
|
116
165
|
if (!response.ok) {
|
|
166
|
+
if (VERBOSE)
|
|
167
|
+
console.log(
|
|
168
|
+
`[session.ts] Validation returned non-200 status:`,
|
|
169
|
+
response.status
|
|
170
|
+
);
|
|
117
171
|
return false;
|
|
118
172
|
}
|
|
119
173
|
|
|
@@ -121,6 +175,7 @@ async function validateSessionWithBackend(
|
|
|
121
175
|
return result.success === true;
|
|
122
176
|
} catch (error) {
|
|
123
177
|
console.warn('Session validation failed:', error);
|
|
178
|
+
if (VERBOSE) console.log(`[session.ts] Validation network error:`, error);
|
|
124
179
|
return false;
|
|
125
180
|
}
|
|
126
181
|
}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import Layout from '@/layouts/Layout.astro';
|
|
3
3
|
import CodeHook from '@/custom/CodeHook.astro';
|
|
4
4
|
import { getBrandConfig } from '@/utils/api/brandConfig';
|
|
5
|
-
import { handleFailedResponse } from '@/utils/backend';
|
|
6
5
|
import { getFullContentMap } from '@/stores/analytics';
|
|
7
6
|
import { getOrSetSessionId } from '@/lib/session';
|
|
8
7
|
import { getStoryData } from '@/lib/storyData';
|
|
@@ -23,11 +22,10 @@ if (slug && (slug.startsWith('images/') || slug.startsWith('media/'))) {
|
|
|
23
22
|
}
|
|
24
23
|
|
|
25
24
|
const lookup = slug || '';
|
|
26
|
-
const sessionId = await getOrSetSessionId(Astro, tenantId);
|
|
27
|
-
const goBackend = import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080';
|
|
28
25
|
|
|
29
|
-
let storyData;
|
|
26
|
+
let storyData, sessionId;
|
|
30
27
|
try {
|
|
28
|
+
sessionId = await getOrSetSessionId(Astro, tenantId);
|
|
31
29
|
storyData = await getStoryData(Astro, lookup, sessionId, tenantId);
|
|
32
30
|
} catch (error) {
|
|
33
31
|
if (error instanceof Response && error.status === 404) {
|
|
@@ -52,47 +50,7 @@ if (paneIds.length === 0) {
|
|
|
52
50
|
return Astro.redirect('/storykeep');
|
|
53
51
|
}
|
|
54
52
|
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
const batchResponse = await fetch(`${goBackend}/api/v1/fragments/panes`, {
|
|
58
|
-
method: 'POST',
|
|
59
|
-
headers: {
|
|
60
|
-
'Content-Type': 'application/json',
|
|
61
|
-
'X-Tenant-ID': tenantId,
|
|
62
|
-
'X-StoryFragment-ID': storyfragmentId,
|
|
63
|
-
'X-TractStack-Session-ID': sessionId,
|
|
64
|
-
},
|
|
65
|
-
body: JSON.stringify({ paneIds }),
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
if (batchResponse.ok) {
|
|
69
|
-
const batchData = await batchResponse.json();
|
|
70
|
-
fragmentsData = batchData.fragments || {};
|
|
71
|
-
if (batchData.errors) {
|
|
72
|
-
Object.entries(batchData.errors).forEach(([paneId, error]) => {
|
|
73
|
-
fragmentsData[paneId] =
|
|
74
|
-
`<div class="error">Failed to load pane ${paneId}: ${error}</div>`;
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
} else {
|
|
78
|
-
const failedFragmentsResponse = await handleFailedResponse(
|
|
79
|
-
batchResponse,
|
|
80
|
-
goBackend,
|
|
81
|
-
tenantId,
|
|
82
|
-
Astro.url.pathname
|
|
83
|
-
);
|
|
84
|
-
if (failedFragmentsResponse) {
|
|
85
|
-
return failedFragmentsResponse;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
} catch (error) {
|
|
89
|
-
console.error('Error fetching fragments:', error);
|
|
90
|
-
return Astro.redirect(
|
|
91
|
-
`/maint?from=${encodeURIComponent(Astro.url.pathname)}`
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (!fragmentsData) {
|
|
53
|
+
if (!storyData.fragments) {
|
|
96
54
|
return Astro.redirect(
|
|
97
55
|
`/maint?from=${encodeURIComponent(Astro.url.pathname)}`
|
|
98
56
|
);
|
|
@@ -158,6 +116,7 @@ paneIds.forEach((paneId: string) => {
|
|
|
158
116
|
<div class="relative overflow-hidden">
|
|
159
117
|
<CodeHook
|
|
160
118
|
target={codeHookTargets[paneId]}
|
|
119
|
+
paneId={paneId}
|
|
161
120
|
options={(() => {
|
|
162
121
|
const optionsStr =
|
|
163
122
|
codeHookTargets[paneId + '-' + codeHookTargets[paneId]];
|
|
@@ -202,7 +161,7 @@ paneIds.forEach((paneId: string) => {
|
|
|
202
161
|
</div>
|
|
203
162
|
</div>
|
|
204
163
|
) : (
|
|
205
|
-
<Fragment set:html={
|
|
164
|
+
<Fragment set:html={storyData.fragments[paneId] || ''} />
|
|
206
165
|
)}
|
|
207
166
|
</div>
|
|
208
167
|
</div>
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { APIRoute } from '@/types/astro';
|
|
2
|
+
import { createTailwindcss } from '@mhsdesign/jit-browser-tailwindcss';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import postcss, { type Rule, type AtRule } from 'postcss';
|
|
6
|
+
import selectorParser, {
|
|
7
|
+
type Root,
|
|
8
|
+
type ClassName,
|
|
9
|
+
} from 'postcss-selector-parser';
|
|
10
|
+
|
|
11
|
+
export const POST: APIRoute = async ({ request }) => {
|
|
12
|
+
try {
|
|
13
|
+
const { html, css: userCss } = await request.json();
|
|
14
|
+
|
|
15
|
+
const configPath = path.join(process.cwd(), 'tailwind.config.cjs');
|
|
16
|
+
const configContent = await fs.readFile(configPath, 'utf-8');
|
|
17
|
+
|
|
18
|
+
const tailwindConfig = new Function(
|
|
19
|
+
'module',
|
|
20
|
+
'exports',
|
|
21
|
+
configContent + '; return module.exports;'
|
|
22
|
+
)({ exports: {} }, {});
|
|
23
|
+
|
|
24
|
+
const tailwindCss = createTailwindcss({
|
|
25
|
+
tailwindConfig: {
|
|
26
|
+
...tailwindConfig,
|
|
27
|
+
content: [{ raw: html, extension: 'html' }],
|
|
28
|
+
corePlugins: { preflight: false },
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const generatedCss = await tailwindCss.generateStylesFromContent(
|
|
33
|
+
`@tailwind utilities;`,
|
|
34
|
+
[html]
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const combinedCss = `${generatedCss}\n${userCss || ''}`;
|
|
38
|
+
|
|
39
|
+
const classMap: Record<string, string> = {};
|
|
40
|
+
const ruleMap: Record<string, string> = {};
|
|
41
|
+
const cssBuffer: { hash: string; body: string }[] = [];
|
|
42
|
+
const mediaBuckets: { minWidth: number; hash: string; body: string }[] = [];
|
|
43
|
+
|
|
44
|
+
const hashString = (str: string) => {
|
|
45
|
+
let hash = 5381,
|
|
46
|
+
i = str.length;
|
|
47
|
+
while (i) hash = (hash * 33) ^ str.charCodeAt(--i);
|
|
48
|
+
return (hash >>> 0).toString(16);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const hashPlugin = () => {
|
|
52
|
+
return {
|
|
53
|
+
postcssPlugin: 'hash-styles',
|
|
54
|
+
Root(root: any) {
|
|
55
|
+
root.walkRules((rule: Rule) => {
|
|
56
|
+
const ruleHash = `t8k-${hashString(rule.toString())}`;
|
|
57
|
+
|
|
58
|
+
const transformSelector = selectorParser((selectors: Root) => {
|
|
59
|
+
selectors.walkClasses((classNode: ClassName) => {
|
|
60
|
+
const rawClass = classNode.value;
|
|
61
|
+
|
|
62
|
+
if (!classMap[rawClass]) {
|
|
63
|
+
classMap[rawClass] = ruleHash;
|
|
64
|
+
} else if (!classMap[rawClass].includes(ruleHash)) {
|
|
65
|
+
classMap[rawClass] += ' ' + ruleHash;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
classNode.value = ruleHash;
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
rule.selector = transformSelector.processSync(rule.selector);
|
|
74
|
+
} catch (e) {
|
|
75
|
+
// Skip invalid selectors from user input
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
ruleMap[ruleHash] = rule.toString();
|
|
80
|
+
|
|
81
|
+
let bucketWidth: number | undefined;
|
|
82
|
+
if (
|
|
83
|
+
rule.parent &&
|
|
84
|
+
rule.parent.type === 'atrule' &&
|
|
85
|
+
(rule.parent as AtRule).name === 'media'
|
|
86
|
+
) {
|
|
87
|
+
const params = (rule.parent as AtRule).params;
|
|
88
|
+
const match = params.match(/min-width:\s*(\d+)px/);
|
|
89
|
+
if (match) bucketWidth = parseInt(match[1], 10);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const ruleEntry = {
|
|
93
|
+
hash: ruleHash,
|
|
94
|
+
body: rule.toString(),
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
if (bucketWidth !== undefined) {
|
|
98
|
+
mediaBuckets.push({ minWidth: bucketWidth, ...ruleEntry });
|
|
99
|
+
} else {
|
|
100
|
+
cssBuffer.push(ruleEntry);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
hashPlugin.postcss = true;
|
|
107
|
+
|
|
108
|
+
const result = await postcss([hashPlugin()]).process(combinedCss, {
|
|
109
|
+
from: undefined,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const generateSnapshot = (targetBreakPoint: number) => {
|
|
113
|
+
const applicableMedia = mediaBuckets
|
|
114
|
+
.filter((b) => b.minWidth <= targetBreakPoint)
|
|
115
|
+
.sort((a, b) => a.minWidth - b.minWidth);
|
|
116
|
+
|
|
117
|
+
return [...cssBuffer, ...applicableMedia]
|
|
118
|
+
.map((r) => r.body)
|
|
119
|
+
.join('\n')
|
|
120
|
+
.replace(/(\d*\.?\d+)(vw|vh)/gi, (_, n) => `${n}%`);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const viewportCss = {
|
|
124
|
+
xs: generateSnapshot(0),
|
|
125
|
+
md: generateSnapshot(801),
|
|
126
|
+
xl: generateSnapshot(1367),
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
return new Response(
|
|
130
|
+
JSON.stringify({
|
|
131
|
+
success: true,
|
|
132
|
+
css: result.css,
|
|
133
|
+
viewportCss,
|
|
134
|
+
classMap,
|
|
135
|
+
ruleMap,
|
|
136
|
+
}),
|
|
137
|
+
{ status: 200, headers: { 'Content-Type': 'application/json' } }
|
|
138
|
+
);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error('CSS Compilation Error:', error);
|
|
141
|
+
return new Response(
|
|
142
|
+
JSON.stringify({
|
|
143
|
+
success: false,
|
|
144
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
145
|
+
}),
|
|
146
|
+
{ status: 500, headers: { 'Content-Type': 'application/json' } }
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
@@ -27,7 +27,7 @@ const wordmark = getAssetPath(brandConfig?.WORDMARK, '/brand/wordmark.svg');
|
|
|
27
27
|
<link rel="stylesheet" href="/styles/storykeep.css" />
|
|
28
28
|
</head>
|
|
29
29
|
<body class="h-full">
|
|
30
|
-
<div class="
|
|
30
|
+
<div class="flex min-h-full flex-col justify-center py-12 md:px-6 xl:px-8">
|
|
31
31
|
<div class="mx-auto pb-6">
|
|
32
32
|
<div class="flex flex-col items-center justify-center gap-4">
|
|
33
33
|
<div class="h-16 w-auto">
|
|
@@ -48,7 +48,7 @@ const mainStylesUrl = isDev
|
|
|
48
48
|
<link rel="stylesheet" href={mainStylesUrl} />
|
|
49
49
|
</head>
|
|
50
50
|
<body class="h-full">
|
|
51
|
-
<div class="
|
|
51
|
+
<div class="flex min-h-full flex-col justify-center py-12 md:px-6 xl:px-8">
|
|
52
52
|
<div class="mx-auto pb-6">
|
|
53
53
|
<!-- Logo and Wordmark -->
|
|
54
54
|
<div class="flex flex-col items-center justify-center gap-4">
|
|
@@ -123,7 +123,7 @@ const mainStylesUrl = isDev
|
|
|
123
123
|
name="password"
|
|
124
124
|
type="password"
|
|
125
125
|
required
|
|
126
|
-
class="
|
|
126
|
+
class="block w-full rounded-md border-0 px-3 py-1.5 text-mydarkgrey shadow-sm ring-1 ring-inset ring-mylightgrey placeholder:text-mylightgrey focus:ring-2 focus:ring-inset focus:ring-myorange md:text-sm md:leading-6"
|
|
127
127
|
/>
|
|
128
128
|
</div>
|
|
129
129
|
</div>
|