astro-tractstack 2.0.0-rc.57 → 2.0.0-rc.58
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/package.json +1 -1
- package/templates/src/components/Header.astro +5 -1
- package/templates/src/components/codehooks/BunnyVideoSetup.tsx +2 -2
- package/templates/src/components/codehooks/FeaturedArticle.astro +5 -1
- package/templates/src/components/codehooks/FeaturedArticleSetup.tsx +38 -3
- package/templates/src/components/codehooks/ListContent.astro +6 -1
- package/templates/src/components/codehooks/ListContentSetup.tsx +7 -113
- package/templates/src/components/compositor/nodes/Pane.tsx +3 -2
- package/templates/src/components/compositor/preview/PaneSnapshotGenerator.tsx +9 -0
- package/templates/src/components/edit/pane/AddPanePanel_break.tsx +12 -2
- package/templates/src/components/edit/pane/PageGenSpecial.tsx +22 -5
- package/templates/src/components/edit/pane/PageGen_preview.tsx +17 -2
- package/templates/src/components/edit/panels/StyleBreakPanel.tsx +17 -19
- package/templates/src/components/fields/ColorPickerCombo.tsx +1 -1
- package/templates/src/components/storykeep/controls/content/MenuForm.tsx +55 -7
- package/templates/src/constants/shapes.ts +9 -0
- package/templates/src/layouts/Layout.astro +77 -40
- package/templates/src/pages/[...slug].astro +70 -63
- package/templates/src/utils/compositor/processMarkdown.ts +16 -2
- package/templates/src/utils/compositor/templateMarkdownStyles.ts +143 -143
package/package.json
CHANGED
|
@@ -185,7 +185,11 @@ const authStatus = {
|
|
|
185
185
|
) : null
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
-
|
|
188
|
+
{
|
|
189
|
+
!isStoryKeep && (
|
|
190
|
+
<SearchWrapper contentMap={fullContentMap} client:load />
|
|
191
|
+
)
|
|
192
|
+
}
|
|
189
193
|
|
|
190
194
|
<script is:inline define:vars={{ sessionId }}>
|
|
191
195
|
function initRememberMe() {
|
|
@@ -31,11 +31,15 @@ const featuredStory = contentMap.find(
|
|
|
31
31
|
item.panes.length > 0 &&
|
|
32
32
|
item.thumbSrc
|
|
33
33
|
);
|
|
34
|
+
const bgColor = parsedOptions.bgColor || '';
|
|
34
35
|
---
|
|
35
36
|
|
|
36
37
|
{
|
|
37
38
|
featuredStory ? (
|
|
38
|
-
<div
|
|
39
|
+
<div
|
|
40
|
+
class="mx-auto w-full max-w-7xl px-8 py-12"
|
|
41
|
+
style={bgColor ? `background-color: ${bgColor}` : ''}
|
|
42
|
+
>
|
|
39
43
|
<div class="grid grid-cols-1 items-center gap-x-16 gap-y-12 md:grid-cols-2">
|
|
40
44
|
<div class="w-full">
|
|
41
45
|
<a href={`/${featuredStory.slug}`} class="block">
|
|
@@ -6,11 +6,14 @@ import { ChevronUpDownIcon, CheckIcon } from '@heroicons/react/20/solid';
|
|
|
6
6
|
import { fullContentMapStore, viewportKeyStore } from '@/stores/storykeep';
|
|
7
7
|
import { getCtx } from '@/stores/nodes';
|
|
8
8
|
import { cloneDeep } from '@/utils/helpers';
|
|
9
|
+
import ColorPickerCombo from '@/components/fields/ColorPickerCombo';
|
|
9
10
|
import type { PaneNode } from '@/types/compositorTypes';
|
|
11
|
+
import type { BrandConfig } from '@/types/tractstack';
|
|
10
12
|
|
|
11
13
|
interface FeaturedArticleSetupProps {
|
|
12
|
-
params
|
|
14
|
+
params: Record<string, string>;
|
|
13
15
|
nodeId: string;
|
|
16
|
+
config: BrandConfig;
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
const comboboxItemStyles = `
|
|
@@ -25,6 +28,7 @@ const comboboxItemStyles = `
|
|
|
25
28
|
const FeaturedArticleSetup = ({
|
|
26
29
|
params,
|
|
27
30
|
nodeId,
|
|
31
|
+
config,
|
|
28
32
|
}: FeaturedArticleSetupProps) => {
|
|
29
33
|
const $contentMap = useStore(fullContentMapStore);
|
|
30
34
|
const $viewportKey = useStore(viewportKeyStore);
|
|
@@ -52,6 +56,7 @@ const FeaturedArticleSetup = ({
|
|
|
52
56
|
const [isPanelOpen, setIsPanelOpen] = useState(false);
|
|
53
57
|
const [selectedSlug, setSelectedSlug] = useState(initialSlug);
|
|
54
58
|
const [query, setQuery] = useState(initialStory?.title || '');
|
|
59
|
+
const [bgColor, setBgColor] = useState(params?.bgColor || '');
|
|
55
60
|
|
|
56
61
|
const selectedStory = useMemo(
|
|
57
62
|
() => availableStories.find((story) => story.slug === selectedSlug),
|
|
@@ -81,10 +86,20 @@ const FeaturedArticleSetup = ({
|
|
|
81
86
|
...paneNode,
|
|
82
87
|
codeHookTarget: 'featured-article',
|
|
83
88
|
codeHookPayload: {
|
|
84
|
-
options: JSON.stringify({
|
|
89
|
+
options: JSON.stringify({
|
|
90
|
+
slug: selectedSlug,
|
|
91
|
+
bgColor: bgColor,
|
|
92
|
+
}),
|
|
85
93
|
},
|
|
94
|
+
bgColour: bgColor || undefined,
|
|
86
95
|
isChanged: true,
|
|
87
96
|
};
|
|
97
|
+
|
|
98
|
+
// If bgColor is empty, remove the property
|
|
99
|
+
if (!bgColor) {
|
|
100
|
+
delete updatedNode.bgColour;
|
|
101
|
+
}
|
|
102
|
+
|
|
88
103
|
ctx.modifyNodes([updatedNode]);
|
|
89
104
|
}
|
|
90
105
|
};
|
|
@@ -96,7 +111,7 @@ const FeaturedArticleSetup = ({
|
|
|
96
111
|
}
|
|
97
112
|
const timeoutId = setTimeout(updatePaneNode, 500);
|
|
98
113
|
return () => clearTimeout(timeoutId);
|
|
99
|
-
}, [selectedSlug]);
|
|
114
|
+
}, [selectedSlug, bgColor]);
|
|
100
115
|
|
|
101
116
|
const handleSelection = (details: { value: string[] }) => {
|
|
102
117
|
const slug = details.value[0] || '';
|
|
@@ -268,6 +283,26 @@ const FeaturedArticleSetup = ({
|
|
|
268
283
|
</Combobox.Root>
|
|
269
284
|
</div>
|
|
270
285
|
|
|
286
|
+
<div className="rounded-lg bg-white p-4 shadow">
|
|
287
|
+
<div className="border-b border-gray-200 pb-4">
|
|
288
|
+
<h3 className="text-lg font-bold text-gray-900">Display Settings</h3>
|
|
289
|
+
</div>
|
|
290
|
+
<div className="space-y-4 pt-4">
|
|
291
|
+
<div>
|
|
292
|
+
<ColorPickerCombo
|
|
293
|
+
title="Background Color"
|
|
294
|
+
defaultColor={bgColor}
|
|
295
|
+
onColorChange={(color: string) => setBgColor(color)}
|
|
296
|
+
config={config!}
|
|
297
|
+
allowNull={true}
|
|
298
|
+
/>
|
|
299
|
+
<p className="mt-1 text-xs text-gray-500">
|
|
300
|
+
Set a background color for the featured article section
|
|
301
|
+
</p>
|
|
302
|
+
</div>
|
|
303
|
+
</div>
|
|
304
|
+
</div>
|
|
305
|
+
|
|
271
306
|
{selectedStory && (
|
|
272
307
|
<div className="rounded-lg bg-white p-4 shadow">
|
|
273
308
|
<h3 className="border-b border-gray-200 pb-2 text-lg font-bold">
|
|
@@ -62,9 +62,14 @@ const sortedStories = [...filteredStories].sort((a, b) => {
|
|
|
62
62
|
// The initial display
|
|
63
63
|
const initialStories = sortedStories.slice(0, pageSize);
|
|
64
64
|
const totalPages = Math.ceil(sortedStories.length / pageSize);
|
|
65
|
+
|
|
66
|
+
const bgColor = parsedOptions.bgColor || '';
|
|
65
67
|
---
|
|
66
68
|
|
|
67
|
-
<div
|
|
69
|
+
<div
|
|
70
|
+
class="mx-auto max-w-7xl p-4 py-12"
|
|
71
|
+
style={bgColor ? `background-color: ${bgColor}` : ''}
|
|
72
|
+
>
|
|
68
73
|
{
|
|
69
74
|
initialStories.length === 0 && (
|
|
70
75
|
<div class="rounded-lg bg-gray-50 px-4 py-12 text-center">
|
|
@@ -9,24 +9,10 @@ import type { PaneNode } from '@/types/compositorTypes';
|
|
|
9
9
|
|
|
10
10
|
const PER_PAGE = 20;
|
|
11
11
|
|
|
12
|
-
// V2 Analytics Data Structure
|
|
13
|
-
interface StoryfragmentAnalytics {
|
|
14
|
-
id: string;
|
|
15
|
-
total_actions: number;
|
|
16
|
-
unique_visitors: number;
|
|
17
|
-
last_24h_actions: number;
|
|
18
|
-
last_7d_actions: number;
|
|
19
|
-
last_28d_actions: number;
|
|
20
|
-
last_24h_unique_visitors: number;
|
|
21
|
-
last_7d_unique_visitors: number;
|
|
22
|
-
last_28d_unique_visitors: number;
|
|
23
|
-
total_leads: number;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
12
|
interface ListContentSetupProps {
|
|
27
13
|
params?: Record<string, string>;
|
|
28
14
|
nodeId: string;
|
|
29
|
-
config
|
|
15
|
+
config: BrandConfig;
|
|
30
16
|
}
|
|
31
17
|
|
|
32
18
|
const ListContentSetup = ({
|
|
@@ -34,17 +20,9 @@ const ListContentSetup = ({
|
|
|
34
20
|
nodeId,
|
|
35
21
|
config,
|
|
36
22
|
}: ListContentSetupProps) => {
|
|
37
|
-
const [analyticsData, setAnalyticsData] = useState<
|
|
38
|
-
Record<string, StoryfragmentAnalytics>
|
|
39
|
-
>({});
|
|
40
|
-
const [isAnalyticsLoading, setIsAnalyticsLoading] = useState(true);
|
|
41
23
|
const $contentMap = useStore(fullContentMapStore);
|
|
42
|
-
|
|
43
24
|
const [isPanelOpen, setIsPanelOpen] = useState(false);
|
|
44
25
|
|
|
45
|
-
const [selectedMode, setSelectedMode] = useState(
|
|
46
|
-
params?.defaultMode || 'recent'
|
|
47
|
-
);
|
|
48
26
|
const [excludedIds, setExcludedIds] = useState<string[]>(
|
|
49
27
|
params?.excludedIds ? params.excludedIds.split(',') : []
|
|
50
28
|
);
|
|
@@ -89,47 +67,6 @@ const ListContentSetup = ({
|
|
|
89
67
|
}
|
|
90
68
|
});
|
|
91
69
|
|
|
92
|
-
const fetchAnalyticsData = async () => {
|
|
93
|
-
try {
|
|
94
|
-
setIsAnalyticsLoading(true);
|
|
95
|
-
// Updated to use V2 API endpoint
|
|
96
|
-
const response = await fetch('/api/v1/analytics/storyfragments', {
|
|
97
|
-
headers: {
|
|
98
|
-
'X-Tenant-ID': window.TRACTSTACK_CONFIG?.tenantId || 'default',
|
|
99
|
-
},
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
if (!response.ok) {
|
|
103
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const analyticsArray = await response.json();
|
|
107
|
-
|
|
108
|
-
// Transform array to a map keyed by ID for easier lookup
|
|
109
|
-
// V2 API returns array directly, not wrapped in a success/data structure
|
|
110
|
-
const analyticsById = Array.isArray(analyticsArray)
|
|
111
|
-
? analyticsArray.reduce(
|
|
112
|
-
(
|
|
113
|
-
acc: Record<string, StoryfragmentAnalytics>,
|
|
114
|
-
item: StoryfragmentAnalytics
|
|
115
|
-
) => {
|
|
116
|
-
acc[item.id] = item;
|
|
117
|
-
return acc;
|
|
118
|
-
},
|
|
119
|
-
{}
|
|
120
|
-
)
|
|
121
|
-
: {};
|
|
122
|
-
|
|
123
|
-
setAnalyticsData(analyticsById);
|
|
124
|
-
} catch (error) {
|
|
125
|
-
console.error('Error fetching analytics data:', error);
|
|
126
|
-
// Set empty analytics on error to prevent blocking the UI
|
|
127
|
-
setAnalyticsData({});
|
|
128
|
-
} finally {
|
|
129
|
-
setIsAnalyticsLoading(false);
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
|
|
133
70
|
const topics = Array.from(topicMap.entries())
|
|
134
71
|
.map(([name, { count, pageIds }]) => ({
|
|
135
72
|
name,
|
|
@@ -147,12 +84,8 @@ const ListContentSetup = ({
|
|
|
147
84
|
);
|
|
148
85
|
});
|
|
149
86
|
|
|
87
|
+
// Always sort by most recent
|
|
150
88
|
const sortedPages = [...filteredPages].sort((a, b) => {
|
|
151
|
-
if (selectedMode === 'popular') {
|
|
152
|
-
const aViews = analyticsData[a.id]?.total_actions || 0;
|
|
153
|
-
const bViews = analyticsData[b.id]?.total_actions || 0;
|
|
154
|
-
return bViews - aViews;
|
|
155
|
-
}
|
|
156
89
|
const bDate = b.changed ? new Date(b.changed) : new Date(0);
|
|
157
90
|
const aDate = a.changed ? new Date(a.changed) : new Date(0);
|
|
158
91
|
return bDate.getTime() - aDate.getTime();
|
|
@@ -174,7 +107,6 @@ const ListContentSetup = ({
|
|
|
174
107
|
codeHookTarget: 'list-content',
|
|
175
108
|
codeHookPayload: {
|
|
176
109
|
options: JSON.stringify({
|
|
177
|
-
defaultMode: selectedMode,
|
|
178
110
|
excludedIds: excludedIds.join(','),
|
|
179
111
|
topics: selectedTopics.join(','),
|
|
180
112
|
pageSize: pageSize,
|
|
@@ -195,10 +127,6 @@ const ListContentSetup = ({
|
|
|
195
127
|
}
|
|
196
128
|
};
|
|
197
129
|
|
|
198
|
-
useEffect(() => {
|
|
199
|
-
fetchAnalyticsData();
|
|
200
|
-
}, []);
|
|
201
|
-
|
|
202
130
|
useEffect(() => {
|
|
203
131
|
if (isInitialMount.current) {
|
|
204
132
|
isInitialMount.current = false;
|
|
@@ -210,7 +138,7 @@ const ListContentSetup = ({
|
|
|
210
138
|
}, 500);
|
|
211
139
|
|
|
212
140
|
return () => clearTimeout(timeoutId);
|
|
213
|
-
}, [
|
|
141
|
+
}, [excludedIds, selectedTopics, pageSize, bgColor]);
|
|
214
142
|
|
|
215
143
|
// Toggle a page's exclusion status
|
|
216
144
|
const toggleExclude = (id: string) => {
|
|
@@ -336,7 +264,6 @@ const ListContentSetup = ({
|
|
|
336
264
|
);
|
|
337
265
|
}
|
|
338
266
|
|
|
339
|
-
if (isAnalyticsLoading) return null;
|
|
340
267
|
return (
|
|
341
268
|
<div className="w-full space-y-6 bg-slate-50 p-6">
|
|
342
269
|
<div className="flex items-center justify-between">
|
|
@@ -377,29 +304,6 @@ const ListContentSetup = ({
|
|
|
377
304
|
</select>
|
|
378
305
|
</div>
|
|
379
306
|
|
|
380
|
-
<div>
|
|
381
|
-
<label
|
|
382
|
-
htmlFor="sort-mode"
|
|
383
|
-
className="block text-sm font-bold text-gray-700"
|
|
384
|
-
>
|
|
385
|
-
Default sort order
|
|
386
|
-
</label>
|
|
387
|
-
<select
|
|
388
|
-
id="sort-mode"
|
|
389
|
-
name="sort-mode"
|
|
390
|
-
className="mt-1 block w-full rounded-md border-gray-300 py-2 pl-3 pr-10 text-base focus:border-cyan-600 focus:outline-none focus:ring-cyan-600 sm:text-sm"
|
|
391
|
-
value={selectedMode}
|
|
392
|
-
onChange={(e) => setSelectedMode(e.target.value)}
|
|
393
|
-
>
|
|
394
|
-
<option value="recent">Most Recent</option>
|
|
395
|
-
<option value="popular">Most Popular</option>
|
|
396
|
-
</select>
|
|
397
|
-
<p className="mt-1 text-xs text-gray-500">
|
|
398
|
-
Note: Users can toggle between views regardless of the default
|
|
399
|
-
setting
|
|
400
|
-
</p>
|
|
401
|
-
</div>
|
|
402
|
-
|
|
403
307
|
<div>
|
|
404
308
|
<ColorPickerCombo
|
|
405
309
|
title="Background Color"
|
|
@@ -533,7 +437,6 @@ const ListContentSetup = ({
|
|
|
533
437
|
<div className="divide-y divide-gray-200">
|
|
534
438
|
{paginatedPages.map((page) => {
|
|
535
439
|
const isExcluded = excludedIds.includes(page.id);
|
|
536
|
-
const analytics = analyticsData[page.id];
|
|
537
440
|
|
|
538
441
|
return (
|
|
539
442
|
<div key={page.id} className="flex items-center p-4">
|
|
@@ -586,19 +489,10 @@ const ListContentSetup = ({
|
|
|
586
489
|
</div>
|
|
587
490
|
)}
|
|
588
491
|
<div className="mt-1 flex items-center text-xs text-gray-500">
|
|
589
|
-
{
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
<>
|
|
594
|
-
<span className="mx-2">•</span>
|
|
595
|
-
<span>
|
|
596
|
-
Updated{' '}
|
|
597
|
-
{new Date(page.changed).toLocaleDateString()}
|
|
598
|
-
</span>
|
|
599
|
-
</>
|
|
600
|
-
)}
|
|
601
|
-
</>
|
|
492
|
+
{page.changed && (
|
|
493
|
+
<span>
|
|
494
|
+
Updated {new Date(page.changed).toLocaleDateString()}
|
|
495
|
+
</span>
|
|
602
496
|
)}
|
|
603
497
|
</div>
|
|
604
498
|
</div>
|
|
@@ -161,18 +161,19 @@ const Pane = memo(
|
|
|
161
161
|
<FeaturedArticleSetup
|
|
162
162
|
nodeId={props.nodeId}
|
|
163
163
|
params={codeHookParams}
|
|
164
|
+
config={props.config!}
|
|
164
165
|
/>
|
|
165
166
|
) : codeHookPayload && codeHookTarget === 'list-content' ? (
|
|
166
167
|
<ListContentSetup
|
|
167
168
|
nodeId={props.nodeId}
|
|
168
169
|
params={codeHookParams}
|
|
169
|
-
config={props.config}
|
|
170
|
+
config={props.config!}
|
|
170
171
|
/>
|
|
171
172
|
) : codeHookPayload && codeHookTarget === 'bunny-video' ? (
|
|
172
173
|
<BunnyVideoSetup
|
|
173
174
|
nodeId={props.nodeId}
|
|
174
175
|
params={codeHookParams}
|
|
175
|
-
config={props.config}
|
|
176
|
+
config={props.config!}
|
|
176
177
|
/>
|
|
177
178
|
) : codeHookPayload && codeHookTarget ? (
|
|
178
179
|
<CodeHookContainer
|
|
@@ -195,5 +195,14 @@ export const PaneSnapshotGenerator = ({
|
|
|
195
195
|
generateSnapshot();
|
|
196
196
|
}, [id, htmlString, isGenerating, onComplete, onError, config, outputWidth]);
|
|
197
197
|
|
|
198
|
+
// Show spinner while generating
|
|
199
|
+
if (isGenerating) {
|
|
200
|
+
return (
|
|
201
|
+
<div className="flex h-24 items-center justify-center">
|
|
202
|
+
<div className="h-8 w-8 animate-spin rounded-full border-b-2 border-cyan-600"></div>
|
|
203
|
+
</div>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
198
207
|
return null;
|
|
199
208
|
};
|
|
@@ -14,7 +14,9 @@ import {
|
|
|
14
14
|
type SnapshotData,
|
|
15
15
|
} from '@/components/compositor/preview/PaneSnapshotGenerator';
|
|
16
16
|
import { createEmptyStorykeep } from '@/utils/compositor/nodesHelper';
|
|
17
|
+
import { tailwindToHex } from '@/utils/compositor/tailwindColors';
|
|
17
18
|
import { getTemplateVisualBreakPane } from '@/utils/compositor/TemplatePanes';
|
|
19
|
+
import { SvgBreaks } from '@/constants/shapes';
|
|
18
20
|
import {
|
|
19
21
|
PaneAddMode,
|
|
20
22
|
type PaneNode,
|
|
@@ -243,8 +245,16 @@ const AddPaneBreakPanel = ({
|
|
|
243
245
|
}
|
|
244
246
|
}
|
|
245
247
|
|
|
246
|
-
|
|
247
|
-
const
|
|
248
|
+
const shapeName = `kCz${variant}`;
|
|
249
|
+
const isFlipped = SvgBreaks[shapeName]?.flipped || false;
|
|
250
|
+
template.bgColour = tailwindToHex(
|
|
251
|
+
isFlipped ? aboveColor : belowColor,
|
|
252
|
+
null
|
|
253
|
+
);
|
|
254
|
+
const svgFill = tailwindToHex(
|
|
255
|
+
isFlipped ? belowColor : aboveColor === belowColor ? 'black' : aboveColor,
|
|
256
|
+
null
|
|
257
|
+
);
|
|
248
258
|
|
|
249
259
|
if (template.bgPane) {
|
|
250
260
|
if (template.bgPane.type === 'visual-break') {
|
|
@@ -7,6 +7,8 @@ import { getTemplateVisualBreakPane } from '@/utils/compositor/TemplatePanes';
|
|
|
7
7
|
import { fullContentMapStore } from '@/stores/storykeep';
|
|
8
8
|
import type { NodesContext } from '@/stores/nodes';
|
|
9
9
|
import { findUniqueSlug } from '@/utils/helpers';
|
|
10
|
+
import { tailwindToHex } from '@/utils/compositor/tailwindColors';
|
|
11
|
+
import { SvgBreaks } from '@/constants/shapes';
|
|
10
12
|
import type { StoryFragmentNode, TemplatePane } from '@/types/compositorTypes';
|
|
11
13
|
|
|
12
14
|
// Layout options with IDs, labels, and descriptions
|
|
@@ -121,24 +123,36 @@ const PageCreationSpecial = ({
|
|
|
121
123
|
const bgColor = breakVariant?.odd ? 'white' : 'gray-50';
|
|
122
124
|
const fillColor = breakVariant?.odd ? 'gray-50' : 'white';
|
|
123
125
|
|
|
126
|
+
const shapeName = `kCz${selectedBreak}`;
|
|
127
|
+
const isFlipped = SvgBreaks[shapeName]?.flipped || false;
|
|
128
|
+
|
|
129
|
+
const finalBgColor = tailwindToHex(
|
|
130
|
+
isFlipped ? fillColor : bgColor,
|
|
131
|
+
null
|
|
132
|
+
);
|
|
133
|
+
const finalFillColor = tailwindToHex(
|
|
134
|
+
isFlipped ? bgColor : fillColor,
|
|
135
|
+
null
|
|
136
|
+
);
|
|
137
|
+
|
|
124
138
|
// 2. Create Visual Break pane
|
|
125
139
|
const visualBreakTemplate = getTemplateVisualBreakPane(selectedBreak);
|
|
126
140
|
visualBreakTemplate.id = ulid();
|
|
127
141
|
visualBreakTemplate.title = 'Visual Break';
|
|
128
142
|
visualBreakTemplate.slug = `${storyfragment.slug}-visual-break`;
|
|
129
|
-
visualBreakTemplate.bgColour =
|
|
143
|
+
visualBreakTemplate.bgColour = finalBgColor;
|
|
130
144
|
|
|
131
145
|
// Configure the SVG fill color
|
|
132
146
|
if (visualBreakTemplate.bgPane) {
|
|
133
147
|
if (visualBreakTemplate.bgPane.type === 'visual-break') {
|
|
134
148
|
if (visualBreakTemplate.bgPane.breakDesktop) {
|
|
135
|
-
visualBreakTemplate.bgPane.breakDesktop.svgFill =
|
|
149
|
+
visualBreakTemplate.bgPane.breakDesktop.svgFill = finalFillColor;
|
|
136
150
|
}
|
|
137
151
|
if (visualBreakTemplate.bgPane.breakTablet) {
|
|
138
|
-
visualBreakTemplate.bgPane.breakTablet.svgFill =
|
|
152
|
+
visualBreakTemplate.bgPane.breakTablet.svgFill = finalFillColor;
|
|
139
153
|
}
|
|
140
154
|
if (visualBreakTemplate.bgPane.breakMobile) {
|
|
141
|
-
visualBreakTemplate.bgPane.breakMobile.svgFill =
|
|
155
|
+
visualBreakTemplate.bgPane.breakMobile.svgFill = finalFillColor;
|
|
142
156
|
}
|
|
143
157
|
}
|
|
144
158
|
}
|
|
@@ -164,7 +178,10 @@ const PageCreationSpecial = ({
|
|
|
164
178
|
isDecorative: false,
|
|
165
179
|
parentId: nodeId,
|
|
166
180
|
// For complete-home layout, match the background color with the visual break
|
|
167
|
-
bgColour:
|
|
181
|
+
bgColour: tailwindToHex(
|
|
182
|
+
selectedLayout === 'complete-home' ? 'gray-50' : 'white',
|
|
183
|
+
null
|
|
184
|
+
),
|
|
168
185
|
codeHookTarget: 'list-content',
|
|
169
186
|
codeHookPayload: {
|
|
170
187
|
options: JSON.stringify({
|
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
getIntroDesign,
|
|
13
13
|
getWithArtpackImageDesign,
|
|
14
14
|
} from '@/utils/compositor/templateMarkdownStyles';
|
|
15
|
+
import { tailwindToHex } from '@/utils/compositor/tailwindColors';
|
|
16
|
+
import { SvgBreaks } from '@/constants/shapes';
|
|
15
17
|
import {
|
|
16
18
|
parsePageMarkdown,
|
|
17
19
|
validatePageMarkdown,
|
|
@@ -243,8 +245,21 @@ export const PageCreationPreview = ({
|
|
|
243
245
|
? design.visualBreaks.odd()
|
|
244
246
|
: design.visualBreaks.even();
|
|
245
247
|
|
|
246
|
-
|
|
247
|
-
const
|
|
248
|
+
const breakData = breakTemplate.bgPane?.breakDesktop;
|
|
249
|
+
const shapeName = breakData
|
|
250
|
+
? `${breakData.collection}${breakData.image}`
|
|
251
|
+
: '';
|
|
252
|
+
const isFlipped =
|
|
253
|
+
(shapeName && SvgBreaks[shapeName]?.flipped) || false;
|
|
254
|
+
|
|
255
|
+
breakTemplate.bgColour = tailwindToHex(
|
|
256
|
+
isFlipped ? belowColor : aboveColor,
|
|
257
|
+
null
|
|
258
|
+
);
|
|
259
|
+
const svgFill = tailwindToHex(
|
|
260
|
+
isFlipped ? aboveColor : belowColor,
|
|
261
|
+
null
|
|
262
|
+
);
|
|
248
263
|
if (breakTemplate.bgPane) {
|
|
249
264
|
if (breakTemplate.bgPane.breakDesktop) {
|
|
250
265
|
breakTemplate.bgPane.breakDesktop.svgFill = svgFill;
|
|
@@ -155,25 +155,23 @@ const StyleBreakPanel = ({ node, parentNode, config }: BasePanelProps) => {
|
|
|
155
155
|
</div>
|
|
156
156
|
|
|
157
157
|
<div className="space-y-4">
|
|
158
|
-
<
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
/>
|
|
176
|
-
</div>
|
|
158
|
+
<ColorPickerCombo
|
|
159
|
+
title="Shape Color"
|
|
160
|
+
defaultColor={settings.svgFill}
|
|
161
|
+
onColorChange={(color: string) =>
|
|
162
|
+
setSettings((prev) => ({ ...prev, svgFill: color }))
|
|
163
|
+
}
|
|
164
|
+
config={config!}
|
|
165
|
+
/>
|
|
166
|
+
<ColorPickerCombo
|
|
167
|
+
title="Background Color"
|
|
168
|
+
defaultColor={settings.bgColor}
|
|
169
|
+
onColorChange={(color: string) =>
|
|
170
|
+
setSettings((prev) => ({ ...prev, bgColor: color }))
|
|
171
|
+
}
|
|
172
|
+
config={config!}
|
|
173
|
+
allowNull={true}
|
|
174
|
+
/>
|
|
177
175
|
</div>
|
|
178
176
|
</div>
|
|
179
177
|
);
|
|
@@ -236,7 +236,7 @@ const ColorPickerCombo = ({
|
|
|
236
236
|
<Combobox.Control ref={comboboxRef}>
|
|
237
237
|
<div className="relative">
|
|
238
238
|
<Combobox.Input
|
|
239
|
-
className="border-mydarkgrey focus:border-myblue focus:ring-myblue xs:text-sm w-full max-w-
|
|
239
|
+
className="border-mydarkgrey focus:border-myblue focus:ring-myblue xs:text-sm w-full max-w-xl rounded-md py-2 pl-3 pr-10 shadow-sm"
|
|
240
240
|
placeholder="Search Tailwind colors..."
|
|
241
241
|
autoComplete="off"
|
|
242
242
|
/>
|
|
@@ -12,6 +12,8 @@ import StringInput from '@/components/form/StringInput';
|
|
|
12
12
|
import EnumSelect from '@/components/form/EnumSelect';
|
|
13
13
|
import ActionBuilderField from '@/components/form/ActionBuilderField';
|
|
14
14
|
import UnsavedChangesBar from '@/components/form/UnsavedChangesBar';
|
|
15
|
+
import ArrowUpIcon from '@heroicons/react/24/outline/ArrowUpIcon';
|
|
16
|
+
import ArrowDownIcon from '@heroicons/react/24/outline/ArrowDownIcon';
|
|
15
17
|
import type {
|
|
16
18
|
MenuNode,
|
|
17
19
|
MenuNodeState,
|
|
@@ -88,6 +90,26 @@ export default function MenuForm({
|
|
|
88
90
|
formState.updateField('menuLinks', newState.menuLinks);
|
|
89
91
|
};
|
|
90
92
|
|
|
93
|
+
const handleMoveUp = (index: number) => {
|
|
94
|
+
if (index === 0) return;
|
|
95
|
+
const newMenuLinks = [...formState.state.menuLinks];
|
|
96
|
+
[newMenuLinks[index - 1], newMenuLinks[index]] = [
|
|
97
|
+
newMenuLinks[index],
|
|
98
|
+
newMenuLinks[index - 1],
|
|
99
|
+
];
|
|
100
|
+
formState.updateField('menuLinks', newMenuLinks);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const handleMoveDown = (index: number) => {
|
|
104
|
+
if (index === formState.state.menuLinks.length - 1) return;
|
|
105
|
+
const newMenuLinks = [...formState.state.menuLinks];
|
|
106
|
+
[newMenuLinks[index], newMenuLinks[index + 1]] = [
|
|
107
|
+
newMenuLinks[index + 1],
|
|
108
|
+
newMenuLinks[index],
|
|
109
|
+
];
|
|
110
|
+
formState.updateField('menuLinks', newMenuLinks);
|
|
111
|
+
};
|
|
112
|
+
|
|
91
113
|
const handleCancel = () => {
|
|
92
114
|
onClose?.(false);
|
|
93
115
|
};
|
|
@@ -155,13 +177,39 @@ export default function MenuForm({
|
|
|
155
177
|
<h4 className="text-sm font-bold text-gray-900">
|
|
156
178
|
Link {index + 1}
|
|
157
179
|
</h4>
|
|
158
|
-
<
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
180
|
+
<div className="flex items-center gap-2">
|
|
181
|
+
{/* Reorder buttons */}
|
|
182
|
+
<div className="flex items-center gap-1">
|
|
183
|
+
{index > 0 && (
|
|
184
|
+
<button
|
|
185
|
+
type="button"
|
|
186
|
+
onClick={() => handleMoveUp(index)}
|
|
187
|
+
className="rounded p-1 text-gray-600 hover:bg-gray-100 hover:text-gray-900"
|
|
188
|
+
title="Move up"
|
|
189
|
+
>
|
|
190
|
+
<ArrowUpIcon className="h-4 w-4" />
|
|
191
|
+
</button>
|
|
192
|
+
)}
|
|
193
|
+
{index < formState.state.menuLinks.length - 1 && (
|
|
194
|
+
<button
|
|
195
|
+
type="button"
|
|
196
|
+
onClick={() => handleMoveDown(index)}
|
|
197
|
+
className="rounded p-1 text-gray-600 hover:bg-gray-100 hover:text-gray-900"
|
|
198
|
+
title="Move down"
|
|
199
|
+
>
|
|
200
|
+
<ArrowDownIcon className="h-4 w-4" />
|
|
201
|
+
</button>
|
|
202
|
+
)}
|
|
203
|
+
</div>
|
|
204
|
+
{/* Remove button */}
|
|
205
|
+
<button
|
|
206
|
+
type="button"
|
|
207
|
+
onClick={() => handleRemoveLink(index)}
|
|
208
|
+
className="text-sm font-bold text-red-600 hover:text-red-800"
|
|
209
|
+
>
|
|
210
|
+
Remove
|
|
211
|
+
</button>
|
|
212
|
+
</div>
|
|
165
213
|
</div>
|
|
166
214
|
|
|
167
215
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|