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,788 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================================
|
|
3
|
+
* PAGETREE PARSER - Layout Parsers
|
|
4
|
+
* ============================================================================
|
|
5
|
+
*
|
|
6
|
+
* ContentNode, SectionContainer, RowContainer, ColContainer, FlexContainer
|
|
7
|
+
*
|
|
8
|
+
* ============================================================================
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
generateId,
|
|
13
|
+
generateFractionalIndex,
|
|
14
|
+
parseInlineStyle,
|
|
15
|
+
parseValueWithUnit,
|
|
16
|
+
parseAnimationAttrs,
|
|
17
|
+
} from '../utils.js';
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
parseBackground,
|
|
21
|
+
backgroundToParams,
|
|
22
|
+
parseBorder,
|
|
23
|
+
borderToParams,
|
|
24
|
+
parseShadow,
|
|
25
|
+
shadowToParams,
|
|
26
|
+
parseBorderRadius,
|
|
27
|
+
parseSpacing,
|
|
28
|
+
spacingToAttrsAndParams,
|
|
29
|
+
parseFlexDirection,
|
|
30
|
+
parseJustifyContent,
|
|
31
|
+
parseAlignItems,
|
|
32
|
+
} from '../styles.js';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Parse ContentNode (root container)
|
|
36
|
+
*/
|
|
37
|
+
export function parseContentNode(element, parseChildren) {
|
|
38
|
+
const styles = parseInlineStyle(element.getAttribute('style') || '');
|
|
39
|
+
const background = parseBackground(styles);
|
|
40
|
+
const overlay = element.getAttribute('data-overlay');
|
|
41
|
+
const bgStyleClass = element.getAttribute('data-bg-style') || 'bgCoverCenter';
|
|
42
|
+
|
|
43
|
+
const node = {
|
|
44
|
+
type: 'ContentNode',
|
|
45
|
+
id: '',
|
|
46
|
+
version: 0,
|
|
47
|
+
params: {
|
|
48
|
+
...backgroundToParams(background),
|
|
49
|
+
'--style-foreground-color': overlay || '',
|
|
50
|
+
},
|
|
51
|
+
attrs: {
|
|
52
|
+
style: {
|
|
53
|
+
display: 'block',
|
|
54
|
+
'background-position': 'center !important',
|
|
55
|
+
},
|
|
56
|
+
'data-skip-background-settings': background.color || background.imageUrl || background.gradient ? 'false' : 'true',
|
|
57
|
+
className: bgStyleClass,
|
|
58
|
+
},
|
|
59
|
+
children: [],
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Parse children (sections) - skip the overlay div and content wrapper
|
|
63
|
+
const sectionChildren = element.querySelectorAll(':scope > div > section, :scope > div > [data-type="SectionContainer/V1"], :scope > section, :scope > [data-type="SectionContainer/V1"]');
|
|
64
|
+
sectionChildren.forEach((child, index) => {
|
|
65
|
+
const childNode = parseChildren(child, '', index);
|
|
66
|
+
if (childNode) {
|
|
67
|
+
node.children.push(childNode);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return node;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Parse SectionContainer
|
|
76
|
+
*/
|
|
77
|
+
export function parseSectionContainer(element, parentId, index, parseChildren) {
|
|
78
|
+
const id = generateId();
|
|
79
|
+
const elementId = element.getAttribute('id'); // Custom element ID for scroll-to/show-hide
|
|
80
|
+
const styles = parseInlineStyle(element.getAttribute('style') || '');
|
|
81
|
+
const background = parseBackground(styles);
|
|
82
|
+
const border = parseBorder(styles);
|
|
83
|
+
const shadow = parseShadow(styles['box-shadow']);
|
|
84
|
+
const borderRadius = parseBorderRadius(styles);
|
|
85
|
+
const spacing = parseSpacing(styles);
|
|
86
|
+
const rowSpacingWithDefaults = {
|
|
87
|
+
paddingTop: spacing.paddingTop || parseValueWithUnit('0px'),
|
|
88
|
+
paddingBottom: spacing.paddingBottom || parseValueWithUnit('0px'),
|
|
89
|
+
paddingHorizontal: spacing.paddingHorizontal || parseValueWithUnit('0px'),
|
|
90
|
+
marginTop: spacing.marginTop || parseValueWithUnit('0px'),
|
|
91
|
+
};
|
|
92
|
+
const overlay = element.getAttribute('data-overlay');
|
|
93
|
+
const paintColors = element.getAttribute('data-paint-colors');
|
|
94
|
+
|
|
95
|
+
// Video background attributes
|
|
96
|
+
const videoBgUrl = element.getAttribute('data-video-bg-url');
|
|
97
|
+
const videoBgType = element.getAttribute('data-video-bg-type');
|
|
98
|
+
const videoBgHideMobile = element.getAttribute('data-video-bg-hide-mobile');
|
|
99
|
+
const videoBgOverlay = element.getAttribute('data-video-bg-overlay');
|
|
100
|
+
|
|
101
|
+
// Determine container width class
|
|
102
|
+
const maxWidth = styles['max-width'] || '1170px';
|
|
103
|
+
let containerClass = 'wideContainer';
|
|
104
|
+
if (maxWidth.includes('550') || maxWidth.includes('small')) containerClass = 'smallContainer';
|
|
105
|
+
else if (maxWidth.includes('720') || maxWidth.includes('mid')) containerClass = 'midContainer';
|
|
106
|
+
else if (maxWidth.includes('960') || maxWidth.includes('midWide')) containerClass = 'midWideContainer';
|
|
107
|
+
else if (maxWidth.includes('100%') || maxWidth.includes('full')) containerClass = 'fullContainer';
|
|
108
|
+
|
|
109
|
+
// Add background style class when there's a background image
|
|
110
|
+
const bgStyleClass = element.getAttribute('data-bg-style');
|
|
111
|
+
if (background.imageUrl) {
|
|
112
|
+
containerClass += ' ' + (bgStyleClass || 'bgCoverCenter');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const node = {
|
|
116
|
+
type: 'SectionContainer/V1',
|
|
117
|
+
id,
|
|
118
|
+
version: 0,
|
|
119
|
+
parentId,
|
|
120
|
+
fractionalIndex: generateFractionalIndex(index),
|
|
121
|
+
attrs: {
|
|
122
|
+
className: containerClass,
|
|
123
|
+
'data-skip-background-settings': background.color || background.imageUrl || background.gradient ? 'false' : 'true',
|
|
124
|
+
'data-skip-shadow-settings': shadow ? 'false' : 'true',
|
|
125
|
+
'data-skip-corners-settings': borderRadius ? 'false' : 'true',
|
|
126
|
+
style: {},
|
|
127
|
+
},
|
|
128
|
+
params: {},
|
|
129
|
+
children: [],
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// Add custom element ID for scroll-to/show-hide targeting
|
|
133
|
+
if (elementId) {
|
|
134
|
+
node.attrs.id = elementId;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Add paint colors if present
|
|
138
|
+
if (paintColors) {
|
|
139
|
+
node.attrs['data-paint-colors'] = paintColors;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Apply spacing
|
|
143
|
+
const { attrs: spacingAttrs, params: spacingParams } = spacingToAttrsAndParams(rowSpacingWithDefaults);
|
|
144
|
+
Object.assign(node.attrs.style, spacingAttrs.style);
|
|
145
|
+
Object.assign(node.params, spacingParams);
|
|
146
|
+
|
|
147
|
+
// Apply background (always include params for ClickFunnels compatibility)
|
|
148
|
+
Object.assign(node.params, backgroundToParams(background));
|
|
149
|
+
|
|
150
|
+
// Apply overlay
|
|
151
|
+
if (overlay) {
|
|
152
|
+
node.params['--style-foreground-color'] = overlay;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Apply video background params
|
|
156
|
+
if (videoBgUrl && videoBgType === 'youtube') {
|
|
157
|
+
// Required data attributes for video background
|
|
158
|
+
node.attrs['data-skip-background-settings'] = 'false';
|
|
159
|
+
node.attrs['data-skip-background-video-settings'] = 'false';
|
|
160
|
+
|
|
161
|
+
node.params['video-bg-url'] = videoBgUrl;
|
|
162
|
+
node.params['video-bg-type'] = 'youtube';
|
|
163
|
+
node.params['video-bg-thumbnail-background'] = false;
|
|
164
|
+
node.params['video-bg-use-background-as-overlay'] = true;
|
|
165
|
+
node.params['video-bg-hide-on-mobile'] = videoBgHideMobile === 'true';
|
|
166
|
+
|
|
167
|
+
// Always use offset style with 50% vertical offset
|
|
168
|
+
node.params['video-bg-style-type'] = 'offset';
|
|
169
|
+
node.params['video-bg-offset-y'] = 50;
|
|
170
|
+
|
|
171
|
+
// Always set background-image-url to empty string (required by ClickFunnels)
|
|
172
|
+
node.params['--style-background-image-url'] = '';
|
|
173
|
+
|
|
174
|
+
// Set overlay color - must be rgba for video background
|
|
175
|
+
if (videoBgOverlay) {
|
|
176
|
+
node.params['--style-background-color'] = videoBgOverlay;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Apply border
|
|
181
|
+
if (border.width || border.style || border.color) {
|
|
182
|
+
Object.assign(node.params, borderToParams(border));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Apply shadow
|
|
186
|
+
if (shadow) {
|
|
187
|
+
Object.assign(node.params, shadowToParams(shadow));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Apply border-radius and separate corners
|
|
191
|
+
const separateCorners = element.getAttribute('data-separate-corners') === 'true';
|
|
192
|
+
if (separateCorners) {
|
|
193
|
+
node.params['separate-corners'] = true;
|
|
194
|
+
// Parse individual corner radii
|
|
195
|
+
if (styles['border-top-left-radius']) {
|
|
196
|
+
const tl = parseValueWithUnit(styles['border-top-left-radius']);
|
|
197
|
+
if (tl) {
|
|
198
|
+
node.attrs.style['border-top-left-radius'] = tl.value;
|
|
199
|
+
node.params['border-top-left-radius--unit'] = tl.unit;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (styles['border-top-right-radius']) {
|
|
203
|
+
const tr = parseValueWithUnit(styles['border-top-right-radius']);
|
|
204
|
+
if (tr) {
|
|
205
|
+
node.attrs.style['border-top-right-radius'] = tr.value;
|
|
206
|
+
node.params['border-top-right-radius--unit'] = tr.unit;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (styles['border-bottom-left-radius']) {
|
|
210
|
+
const bl = parseValueWithUnit(styles['border-bottom-left-radius']);
|
|
211
|
+
if (bl) {
|
|
212
|
+
node.attrs.style['border-bottom-left-radius'] = bl.value;
|
|
213
|
+
node.params['border-bottom-left-radius--unit'] = bl.unit;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (styles['border-bottom-right-radius']) {
|
|
217
|
+
const br = parseValueWithUnit(styles['border-bottom-right-radius']);
|
|
218
|
+
if (br) {
|
|
219
|
+
node.attrs.style['border-bottom-right-radius'] = br.value;
|
|
220
|
+
node.params['border-bottom-right-radius--unit'] = br.unit;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// Also set the base border-radius if present
|
|
224
|
+
if (borderRadius) {
|
|
225
|
+
node.attrs.style['border-radius'] = borderRadius.value;
|
|
226
|
+
node.params['border-radius--unit'] = borderRadius.unit;
|
|
227
|
+
}
|
|
228
|
+
} else if (borderRadius) {
|
|
229
|
+
node.attrs.style['border-radius'] = borderRadius.value;
|
|
230
|
+
node.params['border-radius--unit'] = borderRadius.unit;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Check for visibility
|
|
234
|
+
const showOnly = element.getAttribute('data-show');
|
|
235
|
+
if (showOnly) {
|
|
236
|
+
node.attrs['data-show-only'] = showOnly;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Parse children (rows) - skip the overlay div and content wrapper
|
|
240
|
+
const rowChildren = element.querySelectorAll(':scope > div > [data-type="RowContainer/V1"], :scope > [data-type="RowContainer/V1"]');
|
|
241
|
+
rowChildren.forEach((child, childIndex) => {
|
|
242
|
+
const childNode = parseChildren(child, id, childIndex);
|
|
243
|
+
if (childNode) {
|
|
244
|
+
node.children.push(childNode);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
return node;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Parse RowContainer
|
|
253
|
+
*/
|
|
254
|
+
export function parseRowContainer(element, parentId, index, parseChildren) {
|
|
255
|
+
const id = generateId();
|
|
256
|
+
const elementId = element.getAttribute('id'); // Custom element ID for scroll-to/show-hide
|
|
257
|
+
const styles = parseInlineStyle(element.getAttribute('style') || '');
|
|
258
|
+
const background = parseBackground(styles);
|
|
259
|
+
const border = parseBorder(styles);
|
|
260
|
+
const shadow = parseShadow(styles['box-shadow']);
|
|
261
|
+
const borderRadius = parseBorderRadius(styles);
|
|
262
|
+
const spacing = parseSpacing(styles);
|
|
263
|
+
const spacingWithDefaults = {
|
|
264
|
+
paddingTop: spacing.paddingTop || parseValueWithUnit('0px'),
|
|
265
|
+
paddingBottom: spacing.paddingBottom || parseValueWithUnit('0px'),
|
|
266
|
+
paddingHorizontal: spacing.paddingHorizontal || parseValueWithUnit('0px'),
|
|
267
|
+
marginTop: spacing.marginTop || parseValueWithUnit('0px'),
|
|
268
|
+
};
|
|
269
|
+
const overlay = element.getAttribute('data-overlay');
|
|
270
|
+
const paintColors = element.getAttribute('data-paint-colors');
|
|
271
|
+
|
|
272
|
+
const width = parseValueWithUnit(styles.width || '1170px');
|
|
273
|
+
|
|
274
|
+
// Determine className - add bg style class when there's a background image
|
|
275
|
+
const bgStyleClass = element.getAttribute('data-bg-style');
|
|
276
|
+
let className = '';
|
|
277
|
+
if (background.imageUrl) {
|
|
278
|
+
className = bgStyleClass || 'bgCoverCenter';
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Parse z-index if present
|
|
282
|
+
const zIndex = styles['z-index'] ? parseInt(styles['z-index'], 10) : null;
|
|
283
|
+
|
|
284
|
+
// Parse animation attributes
|
|
285
|
+
const { attrs: animationAttrs, params: animationParams } = parseAnimationAttrs(element);
|
|
286
|
+
|
|
287
|
+
const node = {
|
|
288
|
+
type: 'RowContainer/V1',
|
|
289
|
+
id,
|
|
290
|
+
version: 0,
|
|
291
|
+
parentId,
|
|
292
|
+
fractionalIndex: generateFractionalIndex(index),
|
|
293
|
+
attrs: {
|
|
294
|
+
'data-skip-background-settings': background.color || background.imageUrl || background.gradient ? 'false' : 'true',
|
|
295
|
+
'data-skip-shadow-settings': shadow ? 'false' : 'true',
|
|
296
|
+
'data-skip-corners-settings': borderRadius ? 'false' : 'true',
|
|
297
|
+
...animationAttrs,
|
|
298
|
+
style: {
|
|
299
|
+
width: width ? width.value : 1170,
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
params: {
|
|
303
|
+
'width--unit': width ? width.unit : 'px',
|
|
304
|
+
...animationParams,
|
|
305
|
+
},
|
|
306
|
+
selectors: {
|
|
307
|
+
'.col-inner': {
|
|
308
|
+
params: { 'height--unit': '%' },
|
|
309
|
+
attrs: { style: { height: 'auto' } },
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
children: [],
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// Apply z-index if present
|
|
316
|
+
if (zIndex !== null && !isNaN(zIndex)) {
|
|
317
|
+
node.attrs.style['z-index'] = zIndex;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Add custom element ID for scroll-to/show-hide targeting
|
|
321
|
+
if (elementId) {
|
|
322
|
+
node.attrs.id = elementId;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Add paint colors if present
|
|
326
|
+
if (paintColors) {
|
|
327
|
+
node.attrs['data-paint-colors'] = paintColors;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Add className if set
|
|
331
|
+
if (className) {
|
|
332
|
+
node.attrs.className = className;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Apply spacing
|
|
336
|
+
const { attrs: spacingAttrs, params: spacingParams } = spacingToAttrsAndParams(spacingWithDefaults);
|
|
337
|
+
Object.assign(node.attrs.style, spacingAttrs.style);
|
|
338
|
+
Object.assign(node.params, spacingParams);
|
|
339
|
+
|
|
340
|
+
// Apply background (always include params for ClickFunnels compatibility)
|
|
341
|
+
Object.assign(node.params, backgroundToParams(background));
|
|
342
|
+
|
|
343
|
+
// Apply overlay
|
|
344
|
+
if (overlay) {
|
|
345
|
+
node.params['--style-foreground-color'] = overlay;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Apply border
|
|
349
|
+
if (border.width || border.style || border.color) {
|
|
350
|
+
Object.assign(node.params, borderToParams(border));
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Apply shadow
|
|
354
|
+
if (shadow) {
|
|
355
|
+
Object.assign(node.params, shadowToParams(shadow));
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Apply border-radius and separate corners
|
|
359
|
+
const rowSeparateCorners = element.getAttribute('data-separate-corners') === 'true';
|
|
360
|
+
if (rowSeparateCorners) {
|
|
361
|
+
node.params['separate-corners'] = true;
|
|
362
|
+
// Parse individual corner radii
|
|
363
|
+
if (styles['border-top-left-radius']) {
|
|
364
|
+
const tl = parseValueWithUnit(styles['border-top-left-radius']);
|
|
365
|
+
if (tl) {
|
|
366
|
+
node.attrs.style['border-top-left-radius'] = tl.value;
|
|
367
|
+
node.params['border-top-left-radius--unit'] = tl.unit;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (styles['border-top-right-radius']) {
|
|
371
|
+
const tr = parseValueWithUnit(styles['border-top-right-radius']);
|
|
372
|
+
if (tr) {
|
|
373
|
+
node.attrs.style['border-top-right-radius'] = tr.value;
|
|
374
|
+
node.params['border-top-right-radius--unit'] = tr.unit;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
if (styles['border-bottom-left-radius']) {
|
|
378
|
+
const bl = parseValueWithUnit(styles['border-bottom-left-radius']);
|
|
379
|
+
if (bl) {
|
|
380
|
+
node.attrs.style['border-bottom-left-radius'] = bl.value;
|
|
381
|
+
node.params['border-bottom-left-radius--unit'] = bl.unit;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
if (styles['border-bottom-right-radius']) {
|
|
385
|
+
const br = parseValueWithUnit(styles['border-bottom-right-radius']);
|
|
386
|
+
if (br) {
|
|
387
|
+
node.attrs.style['border-bottom-right-radius'] = br.value;
|
|
388
|
+
node.params['border-bottom-right-radius--unit'] = br.unit;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
// Also set the base border-radius if present
|
|
392
|
+
if (borderRadius) {
|
|
393
|
+
node.attrs.style['border-radius'] = borderRadius.value;
|
|
394
|
+
node.params['border-radius--unit'] = borderRadius.unit;
|
|
395
|
+
}
|
|
396
|
+
} else if (borderRadius) {
|
|
397
|
+
node.attrs.style['border-radius'] = borderRadius.value;
|
|
398
|
+
node.params['border-radius--unit'] = borderRadius.unit;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Parse children (columns) - skip the overlay div and content wrapper
|
|
402
|
+
let colChildren = element.querySelectorAll(':scope > [data-type="ColContainer/V1"]');
|
|
403
|
+
// If columns are nested in a z-index wrapper (overlay case), find them there
|
|
404
|
+
if (colChildren.length === 0) {
|
|
405
|
+
colChildren = element.querySelectorAll(':scope > div > [data-type="ColContainer/V1"]');
|
|
406
|
+
}
|
|
407
|
+
colChildren.forEach((child, childIndex) => {
|
|
408
|
+
const childNode = parseChildren(child, id, childIndex);
|
|
409
|
+
if (childNode) {
|
|
410
|
+
node.children.push(childNode);
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
return node;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Parse ColContainer
|
|
419
|
+
*/
|
|
420
|
+
export function parseColContainer(element, parentId, index, parseChildren) {
|
|
421
|
+
const id = generateId();
|
|
422
|
+
const elementId = element.getAttribute('id'); // Custom element ID for scroll-to/show-hide
|
|
423
|
+
const styles = parseInlineStyle(element.getAttribute('style') || '');
|
|
424
|
+
|
|
425
|
+
// Calculate column span from width percentage
|
|
426
|
+
const widthStr = styles.width || '100%';
|
|
427
|
+
const widthPercent = parseFloat(widthStr);
|
|
428
|
+
const mdNum = Math.round((widthPercent / 100) * 12) || 12;
|
|
429
|
+
|
|
430
|
+
// Get column direction from data attribute
|
|
431
|
+
const colDirection = element.getAttribute('data-col-direction') || 'left';
|
|
432
|
+
|
|
433
|
+
const node = {
|
|
434
|
+
type: 'ColContainer/V1',
|
|
435
|
+
id,
|
|
436
|
+
version: 0,
|
|
437
|
+
parentId,
|
|
438
|
+
fractionalIndex: generateFractionalIndex(index),
|
|
439
|
+
attrs: {},
|
|
440
|
+
params: {
|
|
441
|
+
mdNum,
|
|
442
|
+
colDirection,
|
|
443
|
+
},
|
|
444
|
+
selectors: {
|
|
445
|
+
'& > .col-inner': {
|
|
446
|
+
params: {},
|
|
447
|
+
attrs: {
|
|
448
|
+
style: {},
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
'.col-inner': {},
|
|
452
|
+
},
|
|
453
|
+
children: [],
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
// Add custom element ID for scroll-to/show-hide targeting
|
|
457
|
+
if (elementId) {
|
|
458
|
+
node.attrs.id = elementId;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Find the col-inner element
|
|
462
|
+
const colInner = element.querySelector(':scope > .col-inner');
|
|
463
|
+
|
|
464
|
+
if (colInner) {
|
|
465
|
+
const innerStyles = parseInlineStyle(colInner.getAttribute('style') || '');
|
|
466
|
+
const background = parseBackground(innerStyles);
|
|
467
|
+
const border = parseBorder(innerStyles);
|
|
468
|
+
const shadow = parseShadow(innerStyles['box-shadow']);
|
|
469
|
+
const spacing = parseSpacing(innerStyles);
|
|
470
|
+
const overlay = colInner.getAttribute('data-overlay');
|
|
471
|
+
const separateCorners = colInner.getAttribute('data-separate-corners') === 'true';
|
|
472
|
+
const borderRadius = parseBorderRadius(innerStyles);
|
|
473
|
+
const paintColors = colInner.getAttribute('data-paint-colors');
|
|
474
|
+
|
|
475
|
+
const colInnerSelector = node.selectors['& > .col-inner'];
|
|
476
|
+
|
|
477
|
+
// Check if there's any actual styling on the col-inner
|
|
478
|
+
const hasMargin = innerStyles['margin-left'] || innerStyles['margin-right'];
|
|
479
|
+
const hasBackground = background.color || background.imageUrl || background.gradient;
|
|
480
|
+
const hasBorder = border.width || border.style || border.color;
|
|
481
|
+
|
|
482
|
+
// Spacing - add values if explicitly set
|
|
483
|
+
if (spacing.paddingTop) {
|
|
484
|
+
colInnerSelector.attrs.style['padding-top'] = spacing.paddingTop.value;
|
|
485
|
+
colInnerSelector.params['padding-top--unit'] = spacing.paddingTop.unit;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (spacing.paddingBottom) {
|
|
489
|
+
colInnerSelector.attrs.style['padding-bottom'] = spacing.paddingBottom.value;
|
|
490
|
+
colInnerSelector.params['padding-bottom--unit'] = spacing.paddingBottom.unit;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if (spacing.paddingHorizontal) {
|
|
494
|
+
colInnerSelector.params['--style-padding-horizontal'] = spacing.paddingHorizontal.value;
|
|
495
|
+
colInnerSelector.params['--style-padding-horizontal--unit'] = spacing.paddingHorizontal.unit;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Margin horizontal
|
|
499
|
+
if (hasMargin) {
|
|
500
|
+
const mx = parseValueWithUnit(innerStyles['margin-left'] || innerStyles['margin-right']);
|
|
501
|
+
if (mx) {
|
|
502
|
+
colInnerSelector.params['--style-margin-horizontal'] = mx.value;
|
|
503
|
+
colInnerSelector.params['--style-margin-horizontal--unit'] = mx.unit;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Background (gradient takes priority, then color)
|
|
508
|
+
if (background.gradient) {
|
|
509
|
+
colInnerSelector.params['--style-background-color'] = background.gradient;
|
|
510
|
+
} else if (background.color) {
|
|
511
|
+
colInnerSelector.params['--style-background-color'] = background.color;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Always include background-image-url param (empty string if no image)
|
|
515
|
+
// This is required for ClickFunnels to recognize background settings
|
|
516
|
+
colInnerSelector.params['--style-background-image-url'] = background.imageUrl || '';
|
|
517
|
+
|
|
518
|
+
if (background.imageUrl) {
|
|
519
|
+
// Add bg style class when there's a background image
|
|
520
|
+
const colInnerBgStyle = colInner.getAttribute('data-bg-style');
|
|
521
|
+
colInnerSelector.attrs.className = colInnerBgStyle || 'bgCoverCenter';
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Overlay (foreground color)
|
|
525
|
+
if (overlay) {
|
|
526
|
+
colInnerSelector.params['--style-foreground-color'] = overlay;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Border
|
|
530
|
+
if (hasBorder) {
|
|
531
|
+
Object.assign(colInnerSelector.params, borderToParams(border));
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Shadow
|
|
535
|
+
if (shadow) {
|
|
536
|
+
Object.assign(colInnerSelector.params, shadowToParams(shadow));
|
|
537
|
+
colInnerSelector.attrs['data-skip-shadow-settings'] = 'false';
|
|
538
|
+
} else {
|
|
539
|
+
colInnerSelector.attrs['data-skip-shadow-settings'] = 'true';
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Border radius
|
|
543
|
+
if (borderRadius) {
|
|
544
|
+
colInnerSelector.attrs.style['border-radius'] = borderRadius.value;
|
|
545
|
+
colInnerSelector.params['border-radius--unit'] = borderRadius.unit;
|
|
546
|
+
colInnerSelector.attrs['data-skip-corners-settings'] = 'false';
|
|
547
|
+
} else {
|
|
548
|
+
colInnerSelector.attrs['data-skip-corners-settings'] = 'true';
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Individual corner radii
|
|
552
|
+
if (separateCorners) {
|
|
553
|
+
colInnerSelector.params['separate-corners'] = true;
|
|
554
|
+
if (innerStyles['border-top-left-radius']) {
|
|
555
|
+
const tl = parseValueWithUnit(innerStyles['border-top-left-radius']);
|
|
556
|
+
if (tl) {
|
|
557
|
+
colInnerSelector.attrs.style['border-top-left-radius'] = tl.value;
|
|
558
|
+
colInnerSelector.params['border-top-left-radius--unit'] = tl.unit;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
if (innerStyles['border-top-right-radius']) {
|
|
562
|
+
const tr = parseValueWithUnit(innerStyles['border-top-right-radius']);
|
|
563
|
+
if (tr) {
|
|
564
|
+
colInnerSelector.attrs.style['border-top-right-radius'] = tr.value;
|
|
565
|
+
colInnerSelector.params['border-top-right-radius--unit'] = tr.unit;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
if (innerStyles['border-bottom-left-radius']) {
|
|
569
|
+
const bl = parseValueWithUnit(innerStyles['border-bottom-left-radius']);
|
|
570
|
+
if (bl) {
|
|
571
|
+
colInnerSelector.attrs.style['border-bottom-left-radius'] = bl.value;
|
|
572
|
+
colInnerSelector.params['border-bottom-left-radius--unit'] = bl.unit;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
if (innerStyles['border-bottom-right-radius']) {
|
|
576
|
+
const br = parseValueWithUnit(innerStyles['border-bottom-right-radius']);
|
|
577
|
+
if (br) {
|
|
578
|
+
colInnerSelector.attrs.style['border-bottom-right-radius'] = br.value;
|
|
579
|
+
colInnerSelector.params['border-bottom-right-radius--unit'] = br.unit;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
colInnerSelector.attrs['data-skip-corners-settings'] = 'false';
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Background settings flag
|
|
586
|
+
colInnerSelector.attrs['data-skip-background-settings'] =
|
|
587
|
+
(hasBackground || overlay) ? 'false' : 'true';
|
|
588
|
+
|
|
589
|
+
// Add paint colors if present
|
|
590
|
+
if (paintColors) {
|
|
591
|
+
colInnerSelector.attrs['data-paint-colors'] = paintColors;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Parse children from col-inner, skipping overlay and content wrapper
|
|
595
|
+
let childIdx = 0;
|
|
596
|
+
const parseColInnerChildren = (container) => {
|
|
597
|
+
Array.from(container.children).forEach((child) => {
|
|
598
|
+
// Skip overlay divs
|
|
599
|
+
if (child.classList && child.classList.contains('cf-overlay')) {
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
// If content wrapper (z-index div), dive into it
|
|
603
|
+
const childStyle = child.getAttribute('style') || '';
|
|
604
|
+
if (!child.getAttribute('data-type') && childStyle.includes('z-index') && child.children.length > 0) {
|
|
605
|
+
parseColInnerChildren(child);
|
|
606
|
+
} else {
|
|
607
|
+
const childNode = parseChildren(child, id, childIdx);
|
|
608
|
+
if (childNode) {
|
|
609
|
+
node.children.push(childNode);
|
|
610
|
+
childIdx++;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
};
|
|
615
|
+
parseColInnerChildren(colInner);
|
|
616
|
+
} else {
|
|
617
|
+
// No col-inner found, parse direct children
|
|
618
|
+
let childIdx = 0;
|
|
619
|
+
Array.from(element.children).forEach((child) => {
|
|
620
|
+
if (child.classList && child.classList.contains('cf-overlay')) {
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
const childNode = parseChildren(child, id, childIdx);
|
|
624
|
+
if (childNode) {
|
|
625
|
+
node.children.push(childNode);
|
|
626
|
+
childIdx++;
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
return node;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Parse FlexContainer
|
|
636
|
+
*/
|
|
637
|
+
export function parseFlexContainer(element, parentId, index, parseChildren) {
|
|
638
|
+
const id = generateId();
|
|
639
|
+
const elementId = element.getAttribute('id'); // Custom element ID for scroll-to/show-hide
|
|
640
|
+
const styles = parseInlineStyle(element.getAttribute('style') || '');
|
|
641
|
+
const background = parseBackground(styles);
|
|
642
|
+
const border = parseBorder(styles);
|
|
643
|
+
const shadow = parseShadow(styles['box-shadow']);
|
|
644
|
+
const borderRadius = parseBorderRadius(styles);
|
|
645
|
+
const spacing = parseSpacing(styles);
|
|
646
|
+
const flexSpacingWithDefaults = {
|
|
647
|
+
paddingTop: spacing.paddingTop || parseValueWithUnit('0px'),
|
|
648
|
+
paddingBottom: spacing.paddingBottom || parseValueWithUnit('0px'),
|
|
649
|
+
paddingHorizontal: spacing.paddingHorizontal || parseValueWithUnit('0px'),
|
|
650
|
+
marginTop: spacing.marginTop || parseValueWithUnit('0px'),
|
|
651
|
+
};
|
|
652
|
+
const overlay = element.getAttribute('data-overlay');
|
|
653
|
+
const paintColors = element.getAttribute('data-paint-colors');
|
|
654
|
+
|
|
655
|
+
const width = parseValueWithUnit(styles.width || '100%', '%');
|
|
656
|
+
const height = styles.height ? parseValueWithUnit(styles.height, 'px') : null;
|
|
657
|
+
const gap = parseValueWithUnit(styles.gap || '1.5em', 'em');
|
|
658
|
+
|
|
659
|
+
// Determine className - add bg style class when there's a background image
|
|
660
|
+
const bgStyleClass = element.getAttribute('data-bg-style');
|
|
661
|
+
let className = 'elFlexWrap elFlexNoWrapMobile';
|
|
662
|
+
if (background.imageUrl) {
|
|
663
|
+
className += ' ' + (bgStyleClass || 'bgCoverCenter');
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
const node = {
|
|
667
|
+
type: 'FlexContainer/V1',
|
|
668
|
+
id,
|
|
669
|
+
version: 0,
|
|
670
|
+
parentId,
|
|
671
|
+
fractionalIndex: generateFractionalIndex(index),
|
|
672
|
+
attrs: {
|
|
673
|
+
className,
|
|
674
|
+
'data-skip-background-settings': background.color || background.imageUrl || background.gradient ? 'false' : 'true',
|
|
675
|
+
'data-skip-shadow-settings': shadow ? 'false' : 'true',
|
|
676
|
+
'data-skip-corners-settings': borderRadius ? 'false' : 'true',
|
|
677
|
+
style: {
|
|
678
|
+
'flex-direction': parseFlexDirection(styles['flex-direction']),
|
|
679
|
+
'justify-content': parseJustifyContent(styles['justify-content']),
|
|
680
|
+
'align-items': parseAlignItems(styles['align-items']),
|
|
681
|
+
gap: gap ? gap.value : 0,
|
|
682
|
+
},
|
|
683
|
+
},
|
|
684
|
+
params: {
|
|
685
|
+
'gap--unit': gap ? gap.unit : 'em',
|
|
686
|
+
},
|
|
687
|
+
children: [],
|
|
688
|
+
};
|
|
689
|
+
|
|
690
|
+
// Add custom element ID for scroll-to/show-hide targeting
|
|
691
|
+
if (elementId) {
|
|
692
|
+
node.attrs.id = elementId;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// Add paint colors if present
|
|
696
|
+
if (paintColors) {
|
|
697
|
+
node.attrs['data-paint-colors'] = paintColors;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// Add width
|
|
701
|
+
node.attrs.style.width = width.value;
|
|
702
|
+
node.params['width--unit'] = width.unit;
|
|
703
|
+
|
|
704
|
+
// Add height if specified
|
|
705
|
+
if (height) {
|
|
706
|
+
node.attrs.style.height = height.value;
|
|
707
|
+
node.params['height--unit'] = height.unit;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// Apply spacing
|
|
711
|
+
const { attrs: spacingAttrs, params: spacingParams } = spacingToAttrsAndParams(flexSpacingWithDefaults);
|
|
712
|
+
Object.assign(node.attrs.style, spacingAttrs.style);
|
|
713
|
+
Object.assign(node.params, spacingParams);
|
|
714
|
+
|
|
715
|
+
// Apply background (includes gradient, color, and imageUrl)
|
|
716
|
+
if (background.color || background.imageUrl || background.gradient) {
|
|
717
|
+
Object.assign(node.params, backgroundToParams(background));
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// Apply overlay
|
|
721
|
+
if (overlay) {
|
|
722
|
+
node.params['--style-foreground-color'] = overlay;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// Apply border
|
|
726
|
+
if (border.width || border.style || border.color) {
|
|
727
|
+
Object.assign(node.params, borderToParams(border));
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// Apply shadow
|
|
731
|
+
if (shadow) {
|
|
732
|
+
Object.assign(node.params, shadowToParams(shadow));
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// Apply border-radius
|
|
736
|
+
if (borderRadius) {
|
|
737
|
+
node.attrs.style['border-radius'] = borderRadius.value;
|
|
738
|
+
node.params['border-radius--unit'] = borderRadius.unit;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// Handle flex-wrap - modify className without losing other classes
|
|
742
|
+
// Default className already includes 'elFlexWrap elFlexNoWrapMobile'
|
|
743
|
+
// If wrap is explicitly NOT set, remove elFlexWrap and keep elFlexNoWrapMobile
|
|
744
|
+
if (styles['flex-wrap'] !== 'wrap') {
|
|
745
|
+
node.attrs.className = node.attrs.className.replace('elFlexWrap ', '');
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// Parse children - skip the overlay div (has class cf-overlay)
|
|
749
|
+
const children = element.children;
|
|
750
|
+
let actualIndex = 0;
|
|
751
|
+
Array.from(children).forEach((child) => {
|
|
752
|
+
// Skip overlay divs
|
|
753
|
+
if (child.classList && child.classList.contains('cf-overlay')) {
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
const childNode = parseChildren(child, id, actualIndex);
|
|
757
|
+
if (childNode) {
|
|
758
|
+
// Set width: auto on flex children to prevent them from expanding to 100%
|
|
759
|
+
// This is critical for proper flex layout in ClickFunnels
|
|
760
|
+
if (!childNode.attrs) childNode.attrs = {};
|
|
761
|
+
if (!childNode.attrs.style) childNode.attrs.style = {};
|
|
762
|
+
if (!childNode.params) childNode.params = {};
|
|
763
|
+
|
|
764
|
+
// Only set width: auto if no explicit width is already set
|
|
765
|
+
// Skip FlexContainer children (they have their own width handling)
|
|
766
|
+
if (childNode.attrs.style.width === undefined && childNode.type !== 'FlexContainer/V1') {
|
|
767
|
+
childNode.attrs.style.width = 'auto';
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
node.children.push(childNode);
|
|
771
|
+
actualIndex++;
|
|
772
|
+
}
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
return node;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* Parse ColInner - wrapper element inside ColContainer
|
|
780
|
+
* This is typically a transparent wrapper, so we just parse its children
|
|
781
|
+
* and return them without creating a node for ColInner itself
|
|
782
|
+
*/
|
|
783
|
+
export function parseColInner(_element, _parentId, _index, _parseChildren) {
|
|
784
|
+
// ColInner is a wrapper element - we don't create a node for it,
|
|
785
|
+
// we just return null and let the parent (ColContainer) handle it
|
|
786
|
+
// The ColContainer parser already handles col-inner styling via selectors
|
|
787
|
+
return null;
|
|
788
|
+
}
|