cf-pagetree-parser 1.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/README.md +79 -0
- package/dist/cf-pagetree-parser.js +5044 -0
- package/package.json +33 -0
- package/src/index.js +708 -0
- package/src/parsers/button.js +379 -0
- package/src/parsers/form.js +658 -0
- package/src/parsers/index.js +65 -0
- package/src/parsers/interactive.js +484 -0
- package/src/parsers/layout.js +788 -0
- package/src/parsers/list.js +359 -0
- package/src/parsers/media.js +376 -0
- package/src/parsers/placeholders.js +196 -0
- package/src/parsers/popup.js +133 -0
- package/src/parsers/text.js +278 -0
- package/src/styles.js +444 -0
- package/src/utils.js +402 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================================
|
|
3
|
+
* PAGETREE PARSER - Media Element Parsers
|
|
4
|
+
* ============================================================================
|
|
5
|
+
*
|
|
6
|
+
* Image/V2, Icon/V1, Video/V1, Divider/V1
|
|
7
|
+
*
|
|
8
|
+
* ============================================================================
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
generateId,
|
|
13
|
+
generateFractionalIndex,
|
|
14
|
+
parseInlineStyle,
|
|
15
|
+
parseValueWithUnit,
|
|
16
|
+
normalizeColor,
|
|
17
|
+
parseAnimationAttrs,
|
|
18
|
+
} from '../utils.js';
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
parseSpacing,
|
|
22
|
+
spacingToAttrsAndParams,
|
|
23
|
+
parseBorderRadius,
|
|
24
|
+
parseBorder,
|
|
25
|
+
borderToParams,
|
|
26
|
+
parseShadow,
|
|
27
|
+
shadowToParams,
|
|
28
|
+
parseTextAlign,
|
|
29
|
+
parseBackground,
|
|
30
|
+
} from '../styles.js';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Parse Image/V2
|
|
34
|
+
*/
|
|
35
|
+
export function parseImage(element, parentId, index) {
|
|
36
|
+
const id = generateId();
|
|
37
|
+
|
|
38
|
+
const wrapperStyles = parseInlineStyle(element.getAttribute('style') || '');
|
|
39
|
+
const spacing = parseSpacing(wrapperStyles);
|
|
40
|
+
const textAlign = parseTextAlign(wrapperStyles['text-align']);
|
|
41
|
+
|
|
42
|
+
// Find the img element
|
|
43
|
+
const img = element.querySelector('img');
|
|
44
|
+
const imgStyles = img ? parseInlineStyle(img.getAttribute('style') || '') : {};
|
|
45
|
+
|
|
46
|
+
const src = img ? img.getAttribute('src') : '';
|
|
47
|
+
const alt = img ? img.getAttribute('alt') : '';
|
|
48
|
+
|
|
49
|
+
const width = parseValueWithUnit(imgStyles.width || '100%', '%');
|
|
50
|
+
const height = parseValueWithUnit(imgStyles.height, 'px');
|
|
51
|
+
const borderRadius = parseBorderRadius(imgStyles);
|
|
52
|
+
const border = parseBorder(imgStyles);
|
|
53
|
+
const shadow = parseShadow(imgStyles['box-shadow']);
|
|
54
|
+
const objectFit = imgStyles['object-fit'] || 'cover';
|
|
55
|
+
|
|
56
|
+
// Parse animation attributes
|
|
57
|
+
const { attrs: animationAttrs, params: animationParams } = parseAnimationAttrs(element);
|
|
58
|
+
|
|
59
|
+
const node = {
|
|
60
|
+
type: 'Image/V2',
|
|
61
|
+
id,
|
|
62
|
+
version: 0,
|
|
63
|
+
parentId,
|
|
64
|
+
fractionalIndex: generateFractionalIndex(index),
|
|
65
|
+
attrs: {
|
|
66
|
+
alt,
|
|
67
|
+
style: {
|
|
68
|
+
'text-align': textAlign,
|
|
69
|
+
},
|
|
70
|
+
...animationAttrs,
|
|
71
|
+
},
|
|
72
|
+
params: {
|
|
73
|
+
imageUrl: [{ type: 'text', innerText: src }],
|
|
74
|
+
'padding-top--unit': 'px',
|
|
75
|
+
'padding-bottom--unit': 'px',
|
|
76
|
+
'--style-padding-horizontal--unit': 'px',
|
|
77
|
+
'--style-padding-horizontal': 0,
|
|
78
|
+
...animationParams,
|
|
79
|
+
},
|
|
80
|
+
selectors: {
|
|
81
|
+
'.elImage': {
|
|
82
|
+
attrs: {
|
|
83
|
+
style: {
|
|
84
|
+
width: width ? width.value : 100,
|
|
85
|
+
'object-fit': objectFit,
|
|
86
|
+
},
|
|
87
|
+
'data-image-quality': 100,
|
|
88
|
+
'data-skip-corners-settings': borderRadius ? 'false' : 'true',
|
|
89
|
+
'data-skip-shadow-settings': shadow ? 'false' : 'true',
|
|
90
|
+
},
|
|
91
|
+
params: {
|
|
92
|
+
'width--unit': width ? width.unit : '%',
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Apply spacing
|
|
99
|
+
const { attrs: spacingAttrs, params: spacingParams } = spacingToAttrsAndParams(spacing);
|
|
100
|
+
Object.assign(node.attrs.style, spacingAttrs.style);
|
|
101
|
+
Object.assign(node.params, spacingParams);
|
|
102
|
+
|
|
103
|
+
// Apply height if specified
|
|
104
|
+
if (height) {
|
|
105
|
+
node.selectors['.elImage'].attrs.style.height = height.value;
|
|
106
|
+
node.selectors['.elImage'].params['height--unit'] = height.unit;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Apply border-radius
|
|
110
|
+
if (borderRadius) {
|
|
111
|
+
node.selectors['.elImage'].attrs.style['border-radius'] = borderRadius.value;
|
|
112
|
+
node.selectors['.elImage'].params['border-radius--unit'] = borderRadius.unit;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Apply border
|
|
116
|
+
if (border.width || border.style || border.color) {
|
|
117
|
+
Object.assign(node.selectors['.elImage'].params, borderToParams(border));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Apply shadow
|
|
121
|
+
if (shadow) {
|
|
122
|
+
Object.assign(node.selectors['.elImage'].params, shadowToParams(shadow));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return node;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Parse Icon/V1
|
|
130
|
+
*/
|
|
131
|
+
export function parseIcon(element, parentId, index) {
|
|
132
|
+
const id = generateId();
|
|
133
|
+
|
|
134
|
+
const wrapperStyles = parseInlineStyle(element.getAttribute('style') || '');
|
|
135
|
+
const spacing = parseSpacing(wrapperStyles);
|
|
136
|
+
const textAlign = parseTextAlign(wrapperStyles['text-align']);
|
|
137
|
+
|
|
138
|
+
// Find the icon element
|
|
139
|
+
const icon = element.querySelector('i');
|
|
140
|
+
// Prefer data-icon attribute, fallback to class from <i> element
|
|
141
|
+
const iconAttr = element.getAttribute('data-icon');
|
|
142
|
+
const iconClass = iconAttr || (icon ? icon.getAttribute('class') : 'fas fa-star');
|
|
143
|
+
const iconStyles = icon ? parseInlineStyle(icon.getAttribute('style') || '') : {};
|
|
144
|
+
|
|
145
|
+
// Read from data attributes first (most reliable), fallback to inline styles
|
|
146
|
+
const sizeAttr = element.getAttribute('data-size');
|
|
147
|
+
const fontSize = sizeAttr
|
|
148
|
+
? parseValueWithUnit(sizeAttr)
|
|
149
|
+
: parseValueWithUnit(iconStyles['font-size'] || '48px');
|
|
150
|
+
|
|
151
|
+
const colorAttr = element.getAttribute('data-color');
|
|
152
|
+
const color = colorAttr
|
|
153
|
+
? normalizeColor(colorAttr)
|
|
154
|
+
: normalizeColor(iconStyles.color || '#3b82f6');
|
|
155
|
+
|
|
156
|
+
const opacityAttr = element.getAttribute('data-opacity');
|
|
157
|
+
const opacity = opacityAttr || iconStyles.opacity;
|
|
158
|
+
|
|
159
|
+
// Parse animation attributes
|
|
160
|
+
const { attrs: animationAttrs, params: animationParams } = parseAnimationAttrs(element);
|
|
161
|
+
|
|
162
|
+
const node = {
|
|
163
|
+
type: 'Icon/V1',
|
|
164
|
+
id,
|
|
165
|
+
version: 0,
|
|
166
|
+
parentId,
|
|
167
|
+
fractionalIndex: generateFractionalIndex(index),
|
|
168
|
+
attrs: {
|
|
169
|
+
style: {},
|
|
170
|
+
...animationAttrs,
|
|
171
|
+
},
|
|
172
|
+
params: {
|
|
173
|
+
...animationParams,
|
|
174
|
+
},
|
|
175
|
+
selectors: {
|
|
176
|
+
'.fa_icon': {
|
|
177
|
+
attrs: {
|
|
178
|
+
className: iconClass,
|
|
179
|
+
style: {
|
|
180
|
+
'font-size': fontSize ? fontSize.value : 48,
|
|
181
|
+
color,
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
params: {
|
|
185
|
+
'font-size--unit': fontSize ? fontSize.unit : 'px',
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
'.iconElement': {
|
|
189
|
+
attrs: {
|
|
190
|
+
style: {
|
|
191
|
+
'text-align': textAlign,
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// Apply opacity if present
|
|
199
|
+
if (opacity) {
|
|
200
|
+
node.selectors['.fa_icon'].attrs.style.opacity = parseFloat(opacity);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Apply spacing
|
|
204
|
+
const { attrs: spacingAttrs, params: spacingParams } = spacingToAttrsAndParams(spacing);
|
|
205
|
+
Object.assign(node.attrs.style, spacingAttrs.style);
|
|
206
|
+
Object.assign(node.params, spacingParams);
|
|
207
|
+
|
|
208
|
+
return node;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Parse Video/V1
|
|
213
|
+
*/
|
|
214
|
+
export function parseVideo(element, parentId, index) {
|
|
215
|
+
const id = generateId();
|
|
216
|
+
|
|
217
|
+
const wrapperStyles = parseInlineStyle(element.getAttribute('style') || '');
|
|
218
|
+
const spacing = parseSpacing(wrapperStyles);
|
|
219
|
+
|
|
220
|
+
// Get video URL and type from data attributes
|
|
221
|
+
const videoUrl = element.getAttribute('data-video-url') || '';
|
|
222
|
+
const videoType = element.getAttribute('data-video-type') || 'youtube';
|
|
223
|
+
|
|
224
|
+
// Find container div for styling
|
|
225
|
+
const container = element.querySelector('div');
|
|
226
|
+
const containerStyles = container ? parseInlineStyle(container.getAttribute('style') || '') : {};
|
|
227
|
+
|
|
228
|
+
const borderRadius = parseBorderRadius(containerStyles);
|
|
229
|
+
const shadow = parseShadow(containerStyles['box-shadow']);
|
|
230
|
+
const border = parseBorder(containerStyles);
|
|
231
|
+
const background = parseBackground(containerStyles);
|
|
232
|
+
|
|
233
|
+
const node = {
|
|
234
|
+
type: 'Video/V1',
|
|
235
|
+
id,
|
|
236
|
+
version: 0,
|
|
237
|
+
parentId,
|
|
238
|
+
fractionalIndex: generateFractionalIndex(index),
|
|
239
|
+
attrs: {
|
|
240
|
+
'data-video-type': videoType,
|
|
241
|
+
'data-skip-background-settings': background.color ? 'false' : 'true',
|
|
242
|
+
'data-skip-shadow-settings': shadow ? 'false' : 'true',
|
|
243
|
+
'data-skip-corners-settings': borderRadius ? 'false' : 'true',
|
|
244
|
+
style: {},
|
|
245
|
+
},
|
|
246
|
+
params: {
|
|
247
|
+
'video_url': videoUrl,
|
|
248
|
+
'padding-top--unit': 'px',
|
|
249
|
+
'padding-bottom--unit': 'px',
|
|
250
|
+
'--style-padding-horizontal--unit': 'px',
|
|
251
|
+
'--style-padding-horizontal': 0,
|
|
252
|
+
},
|
|
253
|
+
selectors: {},
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
// Apply spacing
|
|
257
|
+
const { attrs: spacingAttrs, params: spacingParams } = spacingToAttrsAndParams(spacing);
|
|
258
|
+
Object.assign(node.attrs.style, spacingAttrs.style);
|
|
259
|
+
Object.assign(node.params, spacingParams);
|
|
260
|
+
|
|
261
|
+
// Apply border-radius
|
|
262
|
+
if (borderRadius) {
|
|
263
|
+
node.attrs.style['border-radius'] = borderRadius.value;
|
|
264
|
+
node.params['border-radius--unit'] = borderRadius.unit;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Apply shadow
|
|
268
|
+
if (shadow) {
|
|
269
|
+
Object.assign(node.params, shadowToParams(shadow));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Apply border
|
|
273
|
+
if (border.width || border.style || border.color) {
|
|
274
|
+
Object.assign(node.params, borderToParams(border));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Apply background color
|
|
278
|
+
if (background.color) {
|
|
279
|
+
node.params['--style-background-color'] = background.color;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return node;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Parse Divider/V1
|
|
287
|
+
*/
|
|
288
|
+
export function parseDivider(element, parentId, index) {
|
|
289
|
+
const id = generateId();
|
|
290
|
+
|
|
291
|
+
const wrapperStyles = parseInlineStyle(element.getAttribute('style') || '');
|
|
292
|
+
const spacing = parseSpacing(wrapperStyles);
|
|
293
|
+
|
|
294
|
+
// Find the divider line element
|
|
295
|
+
const line = element.querySelector('div');
|
|
296
|
+
const lineStyles = line ? parseInlineStyle(line.getAttribute('style') || '') : {};
|
|
297
|
+
|
|
298
|
+
// Parse border-top for thickness, style, and color
|
|
299
|
+
// Format: "1px solid #e2e8f0" or "3px dashed rgb(181, 69, 69)"
|
|
300
|
+
let borderWidth = 1;
|
|
301
|
+
let borderStyle = 'solid';
|
|
302
|
+
let borderColor = '#e2e8f0';
|
|
303
|
+
|
|
304
|
+
if (lineStyles['border-top']) {
|
|
305
|
+
// Match: thickness style color (where color can be hex, rgb, rgba, or named)
|
|
306
|
+
const match = lineStyles['border-top'].match(/^(\d+(?:\.\d+)?px)\s+(solid|dashed|dotted)\s+(.+)$/i);
|
|
307
|
+
if (match) {
|
|
308
|
+
borderWidth = parseFloat(match[1]);
|
|
309
|
+
borderStyle = match[2].toLowerCase();
|
|
310
|
+
borderColor = normalizeColor(match[3].trim());
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const width = parseValueWithUnit(lineStyles.width || '100%', '%');
|
|
315
|
+
|
|
316
|
+
// Parse alignment from margin
|
|
317
|
+
// "0 auto" = center, "0 auto 0 0" = left, "0 0 0 auto" = right
|
|
318
|
+
let margin = '0 auto';
|
|
319
|
+
if (lineStyles.margin) {
|
|
320
|
+
margin = lineStyles.margin;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Parse box shadow
|
|
324
|
+
const shadow = parseShadow(lineStyles['box-shadow']);
|
|
325
|
+
|
|
326
|
+
// Check data attribute for shadow settings
|
|
327
|
+
const skipShadowSettings = element.getAttribute('data-skip-shadow-settings') !== 'false';
|
|
328
|
+
|
|
329
|
+
const node = {
|
|
330
|
+
type: 'Divider/V1',
|
|
331
|
+
id,
|
|
332
|
+
version: 0,
|
|
333
|
+
parentId,
|
|
334
|
+
fractionalIndex: generateFractionalIndex(index),
|
|
335
|
+
attrs: {
|
|
336
|
+
style: {},
|
|
337
|
+
},
|
|
338
|
+
params: {
|
|
339
|
+
'padding-top--unit': 'px',
|
|
340
|
+
'padding-bottom--unit': 'px',
|
|
341
|
+
'--style-padding-horizontal--unit': 'px',
|
|
342
|
+
'--style-padding-horizontal': 0,
|
|
343
|
+
'margin-top--unit': 'px',
|
|
344
|
+
},
|
|
345
|
+
selectors: {
|
|
346
|
+
'.elDivider': {
|
|
347
|
+
attrs: {
|
|
348
|
+
style: {
|
|
349
|
+
width: width ? width.value : 100,
|
|
350
|
+
margin: margin,
|
|
351
|
+
},
|
|
352
|
+
'data-skip-shadow-settings': skipShadowSettings ? 'true' : 'false',
|
|
353
|
+
},
|
|
354
|
+
params: {
|
|
355
|
+
'width--unit': width ? width.unit : '%',
|
|
356
|
+
'--style-border-top-width': borderWidth,
|
|
357
|
+
'--style-border-top-width--unit': 'px',
|
|
358
|
+
'--style-border-style': borderStyle,
|
|
359
|
+
'--style-border-color': borderColor,
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
// Apply spacing
|
|
366
|
+
const { attrs: spacingAttrs, params: spacingParams } = spacingToAttrsAndParams(spacing);
|
|
367
|
+
Object.assign(node.attrs.style, spacingAttrs.style);
|
|
368
|
+
Object.assign(node.params, spacingParams);
|
|
369
|
+
|
|
370
|
+
// Apply shadow if present
|
|
371
|
+
if (shadow) {
|
|
372
|
+
Object.assign(node.selectors['.elDivider'].params, shadowToParams(shadow));
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return node;
|
|
376
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================================
|
|
3
|
+
* PAGETREE PARSER - Placeholder Parsers
|
|
4
|
+
* ============================================================================
|
|
5
|
+
*
|
|
6
|
+
* Parsers for placeholder elements that output actual ClickFunnels element types.
|
|
7
|
+
* These placeholders render as visual boxes in FunnelWind but output the
|
|
8
|
+
* proper ClickFunnels element types in the pagetree for seamless import.
|
|
9
|
+
*
|
|
10
|
+
* - CheckoutPlaceholder → Checkout/V2
|
|
11
|
+
* - OrderSummaryPlaceholder → CheckoutOrderSummary/V1
|
|
12
|
+
* - ConfirmationPlaceholder → OrderConfirmation/V1
|
|
13
|
+
*
|
|
14
|
+
* ============================================================================
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
generateId,
|
|
19
|
+
generateFractionalIndex,
|
|
20
|
+
parseInlineStyle,
|
|
21
|
+
} from '../utils.js';
|
|
22
|
+
|
|
23
|
+
import { parseSpacing, spacingToAttrsAndParams } from '../styles.js';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Parse CheckoutPlaceholder
|
|
27
|
+
* Outputs a Checkout/V2 element - the actual ClickFunnels checkout form
|
|
28
|
+
*/
|
|
29
|
+
export function parseCheckoutPlaceholder(element, parentId, index) {
|
|
30
|
+
const id = generateId();
|
|
31
|
+
const tosTextId = generateId();
|
|
32
|
+
const tosTextInner = generateId();
|
|
33
|
+
const ctaHeaderId = generateId();
|
|
34
|
+
const ctaHeaderInner = generateId();
|
|
35
|
+
|
|
36
|
+
const styles = parseInlineStyle(element.getAttribute('style') || '');
|
|
37
|
+
const spacing = parseSpacing(styles);
|
|
38
|
+
|
|
39
|
+
const node = {
|
|
40
|
+
type: 'Checkout/V2',
|
|
41
|
+
id,
|
|
42
|
+
version: 0,
|
|
43
|
+
parentId,
|
|
44
|
+
fractionalIndex: generateFractionalIndex(index),
|
|
45
|
+
attrs: {
|
|
46
|
+
style: {
|
|
47
|
+
'--container-font-family': 'var(--style-guide-font-family-content)',
|
|
48
|
+
'--input-headline-font-family': 'var(--style-guide-font-family-subheadline)',
|
|
49
|
+
'--multiple-payments-font-family': 'sans-serif',
|
|
50
|
+
'--input-background-color': '#FFFFFF',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
selectors: {
|
|
54
|
+
'.elButton': {
|
|
55
|
+
params: {
|
|
56
|
+
'--style-border-style': 'solid',
|
|
57
|
+
'border-radius--unit': 'px',
|
|
58
|
+
},
|
|
59
|
+
attrs: {
|
|
60
|
+
style: {
|
|
61
|
+
'border-style': 'none',
|
|
62
|
+
'border-radius': 6,
|
|
63
|
+
},
|
|
64
|
+
'data-skip-corners-settings': 'false',
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
children: [
|
|
69
|
+
{
|
|
70
|
+
type: 'p',
|
|
71
|
+
slotName: 'tos-text',
|
|
72
|
+
id: tosTextId,
|
|
73
|
+
version: 0,
|
|
74
|
+
parentId: id,
|
|
75
|
+
fractionalIndex: 'a0',
|
|
76
|
+
children: [
|
|
77
|
+
{
|
|
78
|
+
type: 'text',
|
|
79
|
+
innerText: 'By completing this purchase, you agree to our terms of service.',
|
|
80
|
+
id: tosTextInner,
|
|
81
|
+
version: 0,
|
|
82
|
+
parentId: tosTextId,
|
|
83
|
+
fractionalIndex: 'a0',
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
type: 'p',
|
|
89
|
+
slotName: 'cta-header',
|
|
90
|
+
id: ctaHeaderId,
|
|
91
|
+
version: 0,
|
|
92
|
+
parentId: id,
|
|
93
|
+
fractionalIndex: 'a1',
|
|
94
|
+
children: [
|
|
95
|
+
{
|
|
96
|
+
type: 'text',
|
|
97
|
+
innerText: 'Complete Your Order Today!',
|
|
98
|
+
id: ctaHeaderInner,
|
|
99
|
+
version: 0,
|
|
100
|
+
parentId: ctaHeaderId,
|
|
101
|
+
fractionalIndex: 'a0',
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// Apply spacing
|
|
109
|
+
const { attrs: spacingAttrs } = spacingToAttrsAndParams(spacing);
|
|
110
|
+
if (spacingAttrs.style) {
|
|
111
|
+
Object.assign(node.attrs.style, spacingAttrs.style);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return node;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Parse OrderSummaryPlaceholder
|
|
119
|
+
* Outputs a CheckoutOrderSummary/V1 element - displays cart items and totals
|
|
120
|
+
*/
|
|
121
|
+
export function parseOrderSummaryPlaceholder(element, parentId, index) {
|
|
122
|
+
const id = generateId();
|
|
123
|
+
|
|
124
|
+
const styles = parseInlineStyle(element.getAttribute('style') || '');
|
|
125
|
+
const spacing = parseSpacing(styles);
|
|
126
|
+
|
|
127
|
+
const node = {
|
|
128
|
+
type: 'CheckoutOrderSummary/V1',
|
|
129
|
+
id,
|
|
130
|
+
version: 0,
|
|
131
|
+
parentId,
|
|
132
|
+
fractionalIndex: generateFractionalIndex(index),
|
|
133
|
+
params: {
|
|
134
|
+
open: true,
|
|
135
|
+
state: 'ok',
|
|
136
|
+
linkWithCheckout: true,
|
|
137
|
+
// linkedCheckoutId will need to be set manually in ClickFunnels to link to the checkout
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// Apply spacing if present
|
|
142
|
+
const { attrs: spacingAttrs, params: spacingParams } = spacingToAttrsAndParams(spacing);
|
|
143
|
+
if (spacingAttrs.style && Object.keys(spacingAttrs.style).length > 0) {
|
|
144
|
+
node.attrs = { style: spacingAttrs.style };
|
|
145
|
+
}
|
|
146
|
+
if (Object.keys(spacingParams).length > 0) {
|
|
147
|
+
Object.assign(node.params, spacingParams);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return node;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Parse ConfirmationPlaceholder
|
|
155
|
+
* Outputs an OrderConfirmation/V1 element - shows purchase receipt
|
|
156
|
+
*/
|
|
157
|
+
export function parseConfirmationPlaceholder(element, parentId, index) {
|
|
158
|
+
const id = generateId();
|
|
159
|
+
|
|
160
|
+
const styles = parseInlineStyle(element.getAttribute('style') || '');
|
|
161
|
+
const spacing = parseSpacing(styles);
|
|
162
|
+
|
|
163
|
+
const node = {
|
|
164
|
+
type: 'OrderConfirmation/V1',
|
|
165
|
+
id,
|
|
166
|
+
version: 0,
|
|
167
|
+
parentId,
|
|
168
|
+
fractionalIndex: generateFractionalIndex(index),
|
|
169
|
+
selectors: {
|
|
170
|
+
'.elOrderConfirmationV1': {
|
|
171
|
+
attrs: {
|
|
172
|
+
'data-skip-corners-settings': 'false',
|
|
173
|
+
style: {
|
|
174
|
+
'border-radius': '8px',
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
params: {
|
|
178
|
+
'--style-border-width': '1px',
|
|
179
|
+
'--style-border-style': 'solid',
|
|
180
|
+
'--style-border-color': '#ECF0F5',
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// Apply spacing if present
|
|
187
|
+
const { attrs: spacingAttrs, params: spacingParams } = spacingToAttrsAndParams(spacing);
|
|
188
|
+
if (spacingAttrs.style && Object.keys(spacingAttrs.style).length > 0) {
|
|
189
|
+
node.attrs = { style: spacingAttrs.style };
|
|
190
|
+
}
|
|
191
|
+
if (Object.keys(spacingParams).length > 0) {
|
|
192
|
+
node.params = spacingParams;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return node;
|
|
196
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================================
|
|
3
|
+
* PAGETREE PARSER - Popup Parser
|
|
4
|
+
* ============================================================================
|
|
5
|
+
*
|
|
6
|
+
* Parses cf-popup (ModalContainer/V1) elements for the popup property.
|
|
7
|
+
*
|
|
8
|
+
* ============================================================================
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
generateId,
|
|
13
|
+
parseInlineStyle,
|
|
14
|
+
parseValueWithUnit,
|
|
15
|
+
} from '../utils.js';
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
parseShadow,
|
|
19
|
+
shadowToParams,
|
|
20
|
+
} from '../styles.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Parse ModalContainer (popup) from cf-popup rendered HTML
|
|
24
|
+
*
|
|
25
|
+
* @param {HTMLElement} element - The popup wrapper element with data-type="ModalContainer/V1"
|
|
26
|
+
* @param {function} parseChildren - Callback to parse child elements
|
|
27
|
+
* @returns {Object} The ModalContainer/V1 pagetree node
|
|
28
|
+
*/
|
|
29
|
+
export function parseModalContainer(element, parseChildren) {
|
|
30
|
+
const id = generateId();
|
|
31
|
+
|
|
32
|
+
// Get popup settings from data attributes
|
|
33
|
+
const width = element.getAttribute('data-popup-width') || '750px';
|
|
34
|
+
const overlay = element.getAttribute('data-popup-overlay') || 'rgba(0,0,0,0.5)';
|
|
35
|
+
const rounded = element.getAttribute('data-popup-rounded') || '16px';
|
|
36
|
+
const border = element.getAttribute('data-popup-border');
|
|
37
|
+
const borderColor = element.getAttribute('data-popup-border-color') || '#000000';
|
|
38
|
+
const shadowAttr = element.getAttribute('data-popup-shadow');
|
|
39
|
+
|
|
40
|
+
// Find the modal container to get actual styles
|
|
41
|
+
const modalEl = element.querySelector('.cf-popup-modal, .containerModal');
|
|
42
|
+
const modalStyles = modalEl ? parseInlineStyle(modalEl.getAttribute('style') || '') : {};
|
|
43
|
+
|
|
44
|
+
// Find the inner container for width
|
|
45
|
+
const innerEl = element.querySelector('.elModalInnerContainer');
|
|
46
|
+
|
|
47
|
+
// Parse values
|
|
48
|
+
const parsedWidth = parseValueWithUnit(width, 'px');
|
|
49
|
+
const parsedRounded = parseValueWithUnit(rounded, 'px');
|
|
50
|
+
const parsedMt = parseValueWithUnit(modalStyles['margin-top'] || '45px', 'px');
|
|
51
|
+
const parsedMb = parseValueWithUnit(modalStyles['margin-bottom'] || '10px', 'px');
|
|
52
|
+
|
|
53
|
+
// Build the node
|
|
54
|
+
const node = {
|
|
55
|
+
id,
|
|
56
|
+
version: 0,
|
|
57
|
+
type: 'ModalContainer/V1',
|
|
58
|
+
selectors: {
|
|
59
|
+
'.containerModal': {
|
|
60
|
+
attrs: {
|
|
61
|
+
'data-skip-corners-settings': parsedRounded ? 'false' : 'true',
|
|
62
|
+
'data-skip-shadow-settings': shadowAttr ? 'false' : 'true',
|
|
63
|
+
style: {
|
|
64
|
+
'margin-bottom': parsedMb ? parsedMb.value : 10,
|
|
65
|
+
'margin-top': parsedMt ? parsedMt.value : 45,
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
params: {
|
|
69
|
+
'margin-bottom--unit': parsedMb ? parsedMb.unit : 'px',
|
|
70
|
+
'margin-top--unit': parsedMt ? parsedMt.unit : 'px',
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
'.modal-wrapper': {
|
|
74
|
+
params: {
|
|
75
|
+
'--style-background-color': overlay,
|
|
76
|
+
'--style-padding-horizontal--unit': 'px',
|
|
77
|
+
'--style-padding-horizontal': 0,
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
'.elModalInnerContainer': {
|
|
81
|
+
params: {
|
|
82
|
+
'width--unit': parsedWidth ? parsedWidth.unit : 'px',
|
|
83
|
+
},
|
|
84
|
+
attrs: {
|
|
85
|
+
style: {
|
|
86
|
+
width: parsedWidth ? parsedWidth.value : 750,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
children: [],
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Apply border-radius
|
|
95
|
+
if (parsedRounded) {
|
|
96
|
+
node.selectors['.containerModal'].attrs.style['border-radius'] = parsedRounded.value;
|
|
97
|
+
node.selectors['.containerModal'].params['border-radius--unit'] = parsedRounded.unit;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Apply border if present
|
|
101
|
+
if (border) {
|
|
102
|
+
const parsedBorder = parseValueWithUnit(border, 'px');
|
|
103
|
+
if (parsedBorder) {
|
|
104
|
+
node.selectors['.containerModal'].params['--style-border-style'] = 'solid';
|
|
105
|
+
node.selectors['.containerModal'].params['--style-border-width'] = parsedBorder.value;
|
|
106
|
+
node.selectors['.containerModal'].params['--style-border-width--unit'] = parsedBorder.unit;
|
|
107
|
+
node.selectors['.containerModal'].params['--style-border-color'] = borderColor;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Apply shadow if present
|
|
112
|
+
if (shadowAttr) {
|
|
113
|
+
const shadow = parseShadow(modalStyles['box-shadow']);
|
|
114
|
+
if (shadow) {
|
|
115
|
+
Object.assign(node.selectors['.containerModal'].params, shadowToParams(shadow));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Parse children (sections inside the popup)
|
|
120
|
+
// Look for sections inside the inner container
|
|
121
|
+
const sectionsContainer = innerEl || modalEl;
|
|
122
|
+
if (sectionsContainer) {
|
|
123
|
+
const sectionChildren = sectionsContainer.querySelectorAll(':scope > section, :scope > [data-type="SectionContainer/V1"]');
|
|
124
|
+
sectionChildren.forEach((child, index) => {
|
|
125
|
+
const childNode = parseChildren(child, id, index);
|
|
126
|
+
if (childNode) {
|
|
127
|
+
node.children.push(childNode);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return node;
|
|
133
|
+
}
|