astro-tractstack 2.0.14 → 2.0.15
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 +18 -0
- package/package.json +1 -1
- package/templates/src/components/compositor/Node.tsx +21 -8
- package/templates/src/components/edit/pane/AddPanePanel_new.tsx +459 -561
- package/templates/src/components/edit/pane/AiPaneGenerator.tsx +18 -81
- package/templates/src/components/edit/pane/steps/AiDesignStep.tsx +140 -0
- package/templates/src/components/edit/pane/steps/CopyInputStep.tsx +105 -0
- package/templates/src/components/edit/pane/steps/DesignLibraryStep.tsx +395 -0
- package/templates/src/constants/prompts.json +3 -1
- package/templates/src/utils/compositor/aiPaneParser.ts +32 -84
- package/templates/src/utils/compositor/designLibraryHelper.ts +87 -2
- package/utils/inject-files.ts +18 -0
- package/templates/src/components/edit/pane/PageGen.tsx +0 -485
- package/templates/src/components/edit/pane/PageGenSelector.tsx +0 -245
- package/templates/src/components/edit/pane/PageGenSpecial.tsx +0 -339
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
import { useState, useMemo } from 'react';
|
|
2
|
-
import { useStore } from '@nanostores/react';
|
|
3
|
-
import { RadioGroup } from '@ark-ui/react/radio-group';
|
|
4
|
-
import CheckCircleIcon from '@heroicons/react/20/solid/CheckCircleIcon';
|
|
5
|
-
//import CubeTransparentIcon from '@heroicons/react/24/outline/CubeTransparentIcon';
|
|
6
|
-
import DocumentIcon from '@heroicons/react/24/outline/DocumentIcon';
|
|
7
|
-
import ExclamationTriangleIcon from '@heroicons/react/24/outline/ExclamationTriangleIcon';
|
|
8
|
-
import AddPanePanel from './AddPanePanel';
|
|
9
|
-
import PageCreationGen from './PageGen';
|
|
10
|
-
import PageCreationSpecial from './PageGenSpecial';
|
|
11
|
-
import {
|
|
12
|
-
/* hasAssemblyAIStore,*/ fullContentMapStore,
|
|
13
|
-
} from '@/stores/storykeep';
|
|
14
|
-
import type { NodesContext } from '@/stores/nodes';
|
|
15
|
-
import type { BrandConfig } from '@/types/tractstack';
|
|
16
|
-
|
|
17
|
-
interface PageCreationSelectorProps {
|
|
18
|
-
nodeId: string;
|
|
19
|
-
ctx: NodesContext;
|
|
20
|
-
isTemplate?: boolean;
|
|
21
|
-
config?: BrandConfig;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
type CreationMode = {
|
|
25
|
-
id: 'design' | 'generate' | 'featured';
|
|
26
|
-
name: string;
|
|
27
|
-
description: string;
|
|
28
|
-
icon: typeof DocumentIcon;
|
|
29
|
-
active: boolean;
|
|
30
|
-
disabled?: boolean;
|
|
31
|
-
disabledReason?: string;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export const PageCreationSelector = ({
|
|
35
|
-
nodeId,
|
|
36
|
-
ctx,
|
|
37
|
-
isTemplate = false,
|
|
38
|
-
config,
|
|
39
|
-
}: PageCreationSelectorProps) => {
|
|
40
|
-
const [selectedMode, setSelectedMode] =
|
|
41
|
-
useState<CreationMode['id']>('design');
|
|
42
|
-
const [showTemplates, setShowTemplates] = useState(false);
|
|
43
|
-
const [showGen, setShowGen] = useState(false);
|
|
44
|
-
const [showFeatured, setShowFeatured] = useState(false);
|
|
45
|
-
const $contentMap = useStore(fullContentMapStore);
|
|
46
|
-
|
|
47
|
-
const validPagesCount = useMemo(() => {
|
|
48
|
-
return $contentMap.filter(
|
|
49
|
-
(item) =>
|
|
50
|
-
item.type === 'StoryFragment' &&
|
|
51
|
-
typeof item.description === 'string' &&
|
|
52
|
-
typeof item.thumbSrc === 'string' &&
|
|
53
|
-
typeof item.thumbSrcSet === 'string' &&
|
|
54
|
-
typeof item.changed === 'string'
|
|
55
|
-
).length;
|
|
56
|
-
}, [$contentMap]);
|
|
57
|
-
|
|
58
|
-
const modes = useMemo(() => {
|
|
59
|
-
const baseModesWithoutFeature = [
|
|
60
|
-
{
|
|
61
|
-
id: 'design',
|
|
62
|
-
name: 'Design from scratch',
|
|
63
|
-
description:
|
|
64
|
-
'Build your page section by section using our design system',
|
|
65
|
-
icon: DocumentIcon,
|
|
66
|
-
active: true,
|
|
67
|
-
},
|
|
68
|
-
/*
|
|
69
|
-
...(hasAssemblyAIStore.get()
|
|
70
|
-
? [
|
|
71
|
-
{
|
|
72
|
-
id: 'generate',
|
|
73
|
-
name: 'Generate with AI',
|
|
74
|
-
description:
|
|
75
|
-
'Tell us what kind of page you want and AI will generate a first draft',
|
|
76
|
-
icon: CubeTransparentIcon,
|
|
77
|
-
active: hasAssemblyAIStore.get(),
|
|
78
|
-
},
|
|
79
|
-
]
|
|
80
|
-
: []),
|
|
81
|
-
*/
|
|
82
|
-
];
|
|
83
|
-
|
|
84
|
-
//const featuredMode = {
|
|
85
|
-
// id: 'featured',
|
|
86
|
-
// name: 'Featured Content home page',
|
|
87
|
-
// description:
|
|
88
|
-
// 'A layout with a prominent hero section showcasing a featured article and grid of additional top articles',
|
|
89
|
-
// icon: NewspaperIcon,
|
|
90
|
-
// active: true,
|
|
91
|
-
// disabled: validPagesCount < 3,
|
|
92
|
-
// disabledReason:
|
|
93
|
-
// validPagesCount === 0
|
|
94
|
-
// ? 'Not yet available; no pages with SEO metadata found.'
|
|
95
|
-
// : `Not yet available; requires at least 3 pages with SEO metadata (currently ${validPagesCount}).`,
|
|
96
|
-
//};
|
|
97
|
-
|
|
98
|
-
return [...baseModesWithoutFeature /*, featuredMode */] as CreationMode[];
|
|
99
|
-
}, [validPagesCount]);
|
|
100
|
-
|
|
101
|
-
const handleContinue = () => {
|
|
102
|
-
if (!selectedMode) return;
|
|
103
|
-
|
|
104
|
-
const selectedModeObj = modes.find((m) => m.id === selectedMode);
|
|
105
|
-
if (selectedModeObj?.disabled) return;
|
|
106
|
-
|
|
107
|
-
if (selectedMode === 'design') {
|
|
108
|
-
setShowTemplates(true);
|
|
109
|
-
} else if (selectedMode === 'generate') {
|
|
110
|
-
setShowGen(true);
|
|
111
|
-
} else if (selectedMode === 'featured') {
|
|
112
|
-
setShowFeatured(true);
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
const radioGroupStyles = `
|
|
117
|
-
.radio-control[data-state="unchecked"] .radio-dot {
|
|
118
|
-
background-color: #d1d5db; /* gray-300 */
|
|
119
|
-
}
|
|
120
|
-
.radio-control[data-state="checked"] .radio-dot {
|
|
121
|
-
background-color: #0891b2; /* cyan-600 */
|
|
122
|
-
}
|
|
123
|
-
.radio-control[data-state="checked"] {
|
|
124
|
-
border-color: #0891b2; /* cyan-600 */
|
|
125
|
-
}
|
|
126
|
-
.radio-item[data-state="checked"] {
|
|
127
|
-
background-color: #f9f9f9;
|
|
128
|
-
color: white;
|
|
129
|
-
}
|
|
130
|
-
.radio-item[data-disabled="true"] {
|
|
131
|
-
background-color: #f9fafb;
|
|
132
|
-
cursor: not-allowed;
|
|
133
|
-
}
|
|
134
|
-
`;
|
|
135
|
-
|
|
136
|
-
if (showTemplates || isTemplate)
|
|
137
|
-
return (
|
|
138
|
-
<AddPanePanel
|
|
139
|
-
nodeId={nodeId}
|
|
140
|
-
first={true}
|
|
141
|
-
ctx={ctx}
|
|
142
|
-
isStoryFragment={true}
|
|
143
|
-
config={config!}
|
|
144
|
-
/>
|
|
145
|
-
);
|
|
146
|
-
else if (showGen) return <PageCreationGen nodeId={nodeId} ctx={ctx} />;
|
|
147
|
-
else if (showFeatured)
|
|
148
|
-
return <PageCreationSpecial nodeId={nodeId} ctx={ctx} />;
|
|
149
|
-
|
|
150
|
-
return (
|
|
151
|
-
<div className="p-0.5 shadow-inner">
|
|
152
|
-
<style>{radioGroupStyles}</style>
|
|
153
|
-
<div className="w-full rounded-md bg-white p-6">
|
|
154
|
-
<h2 className="font-action mb-6 text-2xl font-bold text-gray-900">
|
|
155
|
-
How would you like to create your page?
|
|
156
|
-
</h2>
|
|
157
|
-
|
|
158
|
-
<div className="w-full max-w-3xl">
|
|
159
|
-
<RadioGroup.Root
|
|
160
|
-
defaultValue="design"
|
|
161
|
-
onValueChange={(details) => {
|
|
162
|
-
if (details.value) {
|
|
163
|
-
setSelectedMode(details.value as CreationMode['id']);
|
|
164
|
-
}
|
|
165
|
-
}}
|
|
166
|
-
>
|
|
167
|
-
<RadioGroup.Label className="sr-only">
|
|
168
|
-
Page Creation Mode
|
|
169
|
-
</RadioGroup.Label>
|
|
170
|
-
<div className="space-y-4">
|
|
171
|
-
{modes.map((mode) => (
|
|
172
|
-
<RadioGroup.Item
|
|
173
|
-
key={mode.id}
|
|
174
|
-
value={mode.id}
|
|
175
|
-
disabled={mode.disabled}
|
|
176
|
-
className={`radio-item relative flex cursor-pointer rounded-lg px-5 py-6 shadow-md focus:outline-none ${
|
|
177
|
-
mode.disabled
|
|
178
|
-
? 'bg-gray-50'
|
|
179
|
-
: 'bg-white hover:ring-2 hover:ring-cyan-600 hover:ring-offset-2'
|
|
180
|
-
}`}
|
|
181
|
-
>
|
|
182
|
-
<div className="flex w-full items-center justify-between">
|
|
183
|
-
<div className="flex items-center">
|
|
184
|
-
<div className="flex-shrink-0">
|
|
185
|
-
{mode.disabled ? (
|
|
186
|
-
<ExclamationTriangleIcon
|
|
187
|
-
className="h-8 w-8 text-amber-500"
|
|
188
|
-
aria-hidden="true"
|
|
189
|
-
/>
|
|
190
|
-
) : (
|
|
191
|
-
<mode.icon
|
|
192
|
-
className="h-8 w-8 text-cyan-700 data-[state=checked]:text-white"
|
|
193
|
-
aria-hidden="true"
|
|
194
|
-
/>
|
|
195
|
-
)}
|
|
196
|
-
</div>
|
|
197
|
-
<div className="ml-4">
|
|
198
|
-
<RadioGroup.ItemText>
|
|
199
|
-
<p
|
|
200
|
-
className={`font-bold ${mode.disabled ? 'text-gray-400' : 'text-gray-900 data-[state=checked]:text-white'}`}
|
|
201
|
-
>
|
|
202
|
-
{mode.name}
|
|
203
|
-
</p>
|
|
204
|
-
<span
|
|
205
|
-
className={`inline ${mode.disabled ? 'text-gray-400' : 'text-gray-500 data-[state=checked]:text-cyan-100'}`}
|
|
206
|
-
>
|
|
207
|
-
{mode.description}
|
|
208
|
-
{mode.disabled && mode.disabledReason && (
|
|
209
|
-
<span className="mt-1 block font-bold text-amber-500">
|
|
210
|
-
{mode.disabledReason}
|
|
211
|
-
</span>
|
|
212
|
-
)}
|
|
213
|
-
</span>
|
|
214
|
-
</RadioGroup.ItemText>
|
|
215
|
-
</div>
|
|
216
|
-
</div>
|
|
217
|
-
<div className="hidden shrink-0 text-white data-[state=checked]:block">
|
|
218
|
-
<CheckCircleIcon className="h-6 w-6" />
|
|
219
|
-
</div>
|
|
220
|
-
</div>
|
|
221
|
-
<RadioGroup.ItemControl className="radio-control mr-2 flex h-4 w-4 items-center justify-center rounded-full border border-gray-300">
|
|
222
|
-
<div className="radio-dot h-2 w-2 rounded-full" />
|
|
223
|
-
</RadioGroup.ItemControl>
|
|
224
|
-
<RadioGroup.ItemHiddenInput />
|
|
225
|
-
</RadioGroup.Item>
|
|
226
|
-
))}
|
|
227
|
-
</div>
|
|
228
|
-
</RadioGroup.Root>
|
|
229
|
-
</div>
|
|
230
|
-
|
|
231
|
-
<div className="mt-8 flex justify-end">
|
|
232
|
-
<button
|
|
233
|
-
type="button"
|
|
234
|
-
onClick={handleContinue}
|
|
235
|
-
className="inline-flex justify-center rounded-md bg-cyan-700 px-6 py-2 text-sm font-bold text-white shadow-sm hover:bg-cyan-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-cyan-600"
|
|
236
|
-
>
|
|
237
|
-
Continue
|
|
238
|
-
</button>
|
|
239
|
-
</div>
|
|
240
|
-
</div>
|
|
241
|
-
</div>
|
|
242
|
-
);
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
export default PageCreationSelector;
|
|
@@ -1,339 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
import type { ReactNode } from 'react';
|
|
3
|
-
import { RadioGroup } from '@ark-ui/react/radio-group';
|
|
4
|
-
import { ulid } from 'ulid';
|
|
5
|
-
import VisualBreakPreview from '@/components/compositor/preview/VisualBreakPreview';
|
|
6
|
-
import { getTemplateVisualBreakPane } from '@/utils/compositor/TemplatePanes';
|
|
7
|
-
import { fullContentMapStore } from '@/stores/storykeep';
|
|
8
|
-
import type { NodesContext } from '@/stores/nodes';
|
|
9
|
-
import { findUniqueSlug } from '@/utils/helpers';
|
|
10
|
-
import { tailwindToHex } from '@/utils/compositor/tailwindColors';
|
|
11
|
-
import { SvgBreaks } from '@/constants/shapes';
|
|
12
|
-
import type { StoryFragmentNode, TemplatePane } from '@/types/compositorTypes';
|
|
13
|
-
|
|
14
|
-
// Layout options with IDs, labels, and descriptions
|
|
15
|
-
const layoutOptions = [
|
|
16
|
-
{
|
|
17
|
-
id: 'featured-only',
|
|
18
|
-
label: 'Featured Content Only',
|
|
19
|
-
description: 'A hero section highlighting your most important content',
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
id: 'featured-list',
|
|
23
|
-
label: 'Featured with List',
|
|
24
|
-
description: 'Hero section with a supporting content grid below',
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
id: 'complete-home',
|
|
28
|
-
label: 'Complete Home Layout',
|
|
29
|
-
description: 'Hero section, visual break, and supporting content grid',
|
|
30
|
-
},
|
|
31
|
-
];
|
|
32
|
-
|
|
33
|
-
// Visual break variants for selection
|
|
34
|
-
const breakVariants = [
|
|
35
|
-
{ id: 'cutwide2', label: 'Wave Cut', odd: true },
|
|
36
|
-
{ id: 'cutwide1', label: 'Diagonal Cut', odd: true },
|
|
37
|
-
{ id: 'burstwide2', label: 'Burst', odd: false },
|
|
38
|
-
{ id: 'crookedwide', label: 'Crooked', odd: false },
|
|
39
|
-
];
|
|
40
|
-
|
|
41
|
-
interface PageCreationSpecialProps {
|
|
42
|
-
nodeId: string;
|
|
43
|
-
ctx: NodesContext;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const PageCreationSpecial = ({
|
|
47
|
-
nodeId,
|
|
48
|
-
ctx,
|
|
49
|
-
}: PageCreationSpecialProps): ReactNode => {
|
|
50
|
-
// State for layout and visual break selection
|
|
51
|
-
const [selectedLayout, setSelectedLayout] = useState(layoutOptions[0].id);
|
|
52
|
-
const [selectedBreak, setSelectedBreak] = useState(breakVariants[0].id);
|
|
53
|
-
const [isCreating, setIsCreating] = useState(false);
|
|
54
|
-
|
|
55
|
-
const existingSlugs = fullContentMapStore
|
|
56
|
-
.get()
|
|
57
|
-
.filter((item) => ['Pane', 'StoryFragment'].includes(item.type))
|
|
58
|
-
.map((item) => item.slug);
|
|
59
|
-
|
|
60
|
-
// CSS for RadioGroup styling
|
|
61
|
-
const radioGroupStyles = `
|
|
62
|
-
.radio-control[data-state="unchecked"] .radio-dot {
|
|
63
|
-
background-color: #d1d5db; /* gray-300 */
|
|
64
|
-
}
|
|
65
|
-
.radio-control[data-state="checked"] .radio-dot {
|
|
66
|
-
background-color: #0891b2; /* cyan-600 */
|
|
67
|
-
}
|
|
68
|
-
.radio-control[data-state="checked"] {
|
|
69
|
-
border-color: #0891b2;
|
|
70
|
-
}
|
|
71
|
-
.radio-item[data-state="checked"] {
|
|
72
|
-
border-color: #0891b2;
|
|
73
|
-
background-color: #ecfeff; /* cyan-50 */
|
|
74
|
-
}
|
|
75
|
-
`;
|
|
76
|
-
|
|
77
|
-
// Function to handle continue/apply button
|
|
78
|
-
const handleApply = async () => {
|
|
79
|
-
if (!selectedLayout) return; // Null check
|
|
80
|
-
|
|
81
|
-
try {
|
|
82
|
-
setIsCreating(true);
|
|
83
|
-
|
|
84
|
-
// Get the storyfragment node
|
|
85
|
-
const storyfragment = ctx.allNodes.get().get(nodeId) as StoryFragmentNode;
|
|
86
|
-
if (!storyfragment) {
|
|
87
|
-
console.error('Story fragment not found');
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Create panes array to hold the IDs of all panes we'll create
|
|
92
|
-
const paneIds: string[] = [];
|
|
93
|
-
|
|
94
|
-
// 1. Create Featured Content pane
|
|
95
|
-
const featuredContentPane: TemplatePane = {
|
|
96
|
-
id: ulid(),
|
|
97
|
-
nodeType: 'Pane',
|
|
98
|
-
title: 'Featured Article',
|
|
99
|
-
slug: findUniqueSlug(`featured-article`, existingSlugs),
|
|
100
|
-
isDecorative: false,
|
|
101
|
-
parentId: nodeId,
|
|
102
|
-
codeHookTarget: 'featured-article',
|
|
103
|
-
codeHookPayload: {
|
|
104
|
-
options: JSON.stringify({
|
|
105
|
-
title: 'Featured Article',
|
|
106
|
-
}),
|
|
107
|
-
},
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
// Add the featured content pane
|
|
111
|
-
const featuredContentId = ctx.addTemplatePane(
|
|
112
|
-
nodeId,
|
|
113
|
-
featuredContentPane
|
|
114
|
-
);
|
|
115
|
-
if (featuredContentId) {
|
|
116
|
-
paneIds.push(featuredContentId);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// If layout includes visual break + list content
|
|
120
|
-
if (selectedLayout === 'complete-home') {
|
|
121
|
-
// Get the selected break variant
|
|
122
|
-
const breakVariant = breakVariants.find((b) => b.id === selectedBreak);
|
|
123
|
-
const bgColor = breakVariant?.odd ? 'white' : 'gray-50';
|
|
124
|
-
const fillColor = breakVariant?.odd ? 'gray-50' : 'white';
|
|
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
|
-
|
|
138
|
-
// 2. Create Visual Break pane
|
|
139
|
-
const visualBreakTemplate = getTemplateVisualBreakPane(selectedBreak);
|
|
140
|
-
visualBreakTemplate.id = ulid();
|
|
141
|
-
visualBreakTemplate.title = 'Visual Break';
|
|
142
|
-
visualBreakTemplate.slug = `${storyfragment.slug}-visual-break`;
|
|
143
|
-
visualBreakTemplate.bgColour = finalBgColor;
|
|
144
|
-
|
|
145
|
-
// Configure the SVG fill color
|
|
146
|
-
if (visualBreakTemplate.bgPane) {
|
|
147
|
-
if (visualBreakTemplate.bgPane.type === 'visual-break') {
|
|
148
|
-
if (visualBreakTemplate.bgPane.breakDesktop) {
|
|
149
|
-
visualBreakTemplate.bgPane.breakDesktop.svgFill = finalFillColor;
|
|
150
|
-
}
|
|
151
|
-
if (visualBreakTemplate.bgPane.breakTablet) {
|
|
152
|
-
visualBreakTemplate.bgPane.breakTablet.svgFill = finalFillColor;
|
|
153
|
-
}
|
|
154
|
-
if (visualBreakTemplate.bgPane.breakMobile) {
|
|
155
|
-
visualBreakTemplate.bgPane.breakMobile.svgFill = finalFillColor;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Add the visual break pane
|
|
161
|
-
const visualBreakId = ctx.addTemplatePane(nodeId, visualBreakTemplate);
|
|
162
|
-
if (visualBreakId) {
|
|
163
|
-
paneIds.push(visualBreakId);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// If layout includes list content
|
|
168
|
-
if (
|
|
169
|
-
selectedLayout === 'featured-list' ||
|
|
170
|
-
selectedLayout === 'complete-home'
|
|
171
|
-
) {
|
|
172
|
-
// 3. Create List Content pane
|
|
173
|
-
const listContentPane: TemplatePane = {
|
|
174
|
-
id: ulid(),
|
|
175
|
-
nodeType: 'Pane',
|
|
176
|
-
title: 'Content List',
|
|
177
|
-
slug: `${storyfragment.slug}-content-list`,
|
|
178
|
-
isDecorative: false,
|
|
179
|
-
parentId: nodeId,
|
|
180
|
-
// For complete-home layout, match the background color with the visual break
|
|
181
|
-
bgColour: tailwindToHex(
|
|
182
|
-
selectedLayout === 'complete-home' ? 'gray-50' : 'white',
|
|
183
|
-
null
|
|
184
|
-
),
|
|
185
|
-
codeHookTarget: 'list-content',
|
|
186
|
-
codeHookPayload: {
|
|
187
|
-
options: JSON.stringify({
|
|
188
|
-
title: 'More Articles',
|
|
189
|
-
sortByPopular: 'true',
|
|
190
|
-
showTopics: 'true',
|
|
191
|
-
showDate: 'true',
|
|
192
|
-
limit: '10',
|
|
193
|
-
category: '',
|
|
194
|
-
}),
|
|
195
|
-
},
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
// Add the list content pane
|
|
199
|
-
const listContentId = ctx.addTemplatePane(nodeId, listContentPane);
|
|
200
|
-
if (listContentId) {
|
|
201
|
-
paneIds.push(listContentId);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Update the storyfragment with the new panes
|
|
206
|
-
if (paneIds.length > 0) {
|
|
207
|
-
storyfragment.paneIds = paneIds;
|
|
208
|
-
storyfragment.isChanged = true;
|
|
209
|
-
ctx.modifyNodes([storyfragment]);
|
|
210
|
-
ctx.notifyNode('root');
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Set title and slug if they're not set
|
|
214
|
-
if (!storyfragment.title || !storyfragment.slug) {
|
|
215
|
-
const updatedFragment = {
|
|
216
|
-
...storyfragment,
|
|
217
|
-
title: storyfragment.title || 'Home Page',
|
|
218
|
-
slug: storyfragment.slug || 'home',
|
|
219
|
-
isChanged: true,
|
|
220
|
-
};
|
|
221
|
-
ctx.modifyNodes([updatedFragment]);
|
|
222
|
-
}
|
|
223
|
-
} catch (error) {
|
|
224
|
-
console.error('Error creating special layout:', error);
|
|
225
|
-
} finally {
|
|
226
|
-
setIsCreating(false);
|
|
227
|
-
}
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
return (
|
|
231
|
-
<div className="rounded-md bg-white p-6">
|
|
232
|
-
<style>{radioGroupStyles}</style>
|
|
233
|
-
<div className="text-mydarkgrey mb-6 space-y-6 italic">
|
|
234
|
-
<strong>Note:</strong> when editing web pages (story fragments) be sure
|
|
235
|
-
to click on Topics & Details for each page; (if you see no articles,
|
|
236
|
-
that's why!)
|
|
237
|
-
</div>
|
|
238
|
-
<div className="mb-6 space-y-6">
|
|
239
|
-
<div>
|
|
240
|
-
<RadioGroup.Root
|
|
241
|
-
defaultValue={selectedLayout}
|
|
242
|
-
onValueChange={(details) => {
|
|
243
|
-
if (details.value) {
|
|
244
|
-
setSelectedLayout(details.value);
|
|
245
|
-
}
|
|
246
|
-
}}
|
|
247
|
-
>
|
|
248
|
-
<RadioGroup.Label className="text-lg font-bold">
|
|
249
|
-
Select Layout
|
|
250
|
-
</RadioGroup.Label>
|
|
251
|
-
<div className="mt-2 space-y-4">
|
|
252
|
-
{layoutOptions.map((option) => (
|
|
253
|
-
<RadioGroup.Item
|
|
254
|
-
key={option.id}
|
|
255
|
-
value={option.id}
|
|
256
|
-
className="radio-item flex items-center space-x-3 rounded-lg border p-4"
|
|
257
|
-
>
|
|
258
|
-
<div className="flex items-center">
|
|
259
|
-
<RadioGroup.ItemControl className="radio-control mr-2 flex h-4 w-4 items-center justify-center rounded-full border border-gray-300">
|
|
260
|
-
<div className="radio-dot h-2 w-2 rounded-full" />
|
|
261
|
-
</RadioGroup.ItemControl>
|
|
262
|
-
<RadioGroup.ItemText>
|
|
263
|
-
<div>
|
|
264
|
-
<div className="font-bold">{option.label}</div>
|
|
265
|
-
<div className="text-sm text-gray-500">
|
|
266
|
-
{option.description}
|
|
267
|
-
</div>
|
|
268
|
-
</div>
|
|
269
|
-
</RadioGroup.ItemText>
|
|
270
|
-
</div>
|
|
271
|
-
<RadioGroup.ItemHiddenInput />
|
|
272
|
-
</RadioGroup.Item>
|
|
273
|
-
))}
|
|
274
|
-
</div>
|
|
275
|
-
</RadioGroup.Root>
|
|
276
|
-
</div>
|
|
277
|
-
|
|
278
|
-
{selectedLayout === 'complete-home' && (
|
|
279
|
-
<div>
|
|
280
|
-
<div className="mb-2 text-lg font-bold">
|
|
281
|
-
Select Visual Break Style
|
|
282
|
-
</div>
|
|
283
|
-
<div className="grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-5">
|
|
284
|
-
{breakVariants.map((breakVar) => (
|
|
285
|
-
<div
|
|
286
|
-
key={breakVar.id}
|
|
287
|
-
className={`cursor-pointer rounded-lg border p-2 ${
|
|
288
|
-
selectedBreak === breakVar.id
|
|
289
|
-
? 'border-cyan-600 ring-2 ring-cyan-600 ring-opacity-50'
|
|
290
|
-
: 'border-gray-300'
|
|
291
|
-
}`}
|
|
292
|
-
onClick={() => setSelectedBreak(breakVar.id)}
|
|
293
|
-
>
|
|
294
|
-
<div className="h-16 overflow-hidden rounded">
|
|
295
|
-
<VisualBreakPreview
|
|
296
|
-
bgColour="#ffffff"
|
|
297
|
-
fillColour="#000000"
|
|
298
|
-
variant={breakVar.id}
|
|
299
|
-
height={60}
|
|
300
|
-
/>
|
|
301
|
-
</div>
|
|
302
|
-
<div className="mt-1 text-center text-sm font-bold">
|
|
303
|
-
{breakVar.label}
|
|
304
|
-
</div>
|
|
305
|
-
</div>
|
|
306
|
-
))}
|
|
307
|
-
</div>
|
|
308
|
-
</div>
|
|
309
|
-
)}
|
|
310
|
-
</div>
|
|
311
|
-
|
|
312
|
-
<div className="mt-6 flex justify-end gap-3">
|
|
313
|
-
<button
|
|
314
|
-
onClick={() => {
|
|
315
|
-
ctx.setPanelMode(nodeId, 'add', 'DEFAULT');
|
|
316
|
-
ctx.notifyNode('root');
|
|
317
|
-
}}
|
|
318
|
-
className="rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-bold text-gray-700 hover:bg-gray-50"
|
|
319
|
-
disabled={isCreating}
|
|
320
|
-
>
|
|
321
|
-
Back
|
|
322
|
-
</button>
|
|
323
|
-
<button
|
|
324
|
-
onClick={handleApply}
|
|
325
|
-
disabled={isCreating || !selectedLayout}
|
|
326
|
-
className={`rounded-md px-6 py-2 text-sm font-bold text-white transition-colors ${
|
|
327
|
-
isCreating || !selectedLayout
|
|
328
|
-
? 'bg-gray-400'
|
|
329
|
-
: 'bg-cyan-600 hover:bg-cyan-700'
|
|
330
|
-
}`}
|
|
331
|
-
>
|
|
332
|
-
{isCreating ? 'Creating...' : 'Create Layout'}
|
|
333
|
-
</button>
|
|
334
|
-
</div>
|
|
335
|
-
</div>
|
|
336
|
-
);
|
|
337
|
-
};
|
|
338
|
-
|
|
339
|
-
export default PageCreationSpecial;
|