cf-pagetree-parser 1.0.4 → 1.0.6

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 CHANGED
@@ -13,14 +13,13 @@ npm install cf-pagetree-parser
13
13
  ### ES Modules (Next.js, Node.js)
14
14
 
15
15
  ```javascript
16
- import { parsePageTree, exportPageTreeJSON } from 'cf-pagetree-parser';
16
+ import { parsePageTree } from 'cf-pagetree-parser';
17
17
 
18
- // Parse a DOM element to PageTree
19
- const rootElement = document.querySelector('[data-type="ContentNode"]');
20
- const pageTree = parsePageTree(rootElement);
18
+ // Parse to PageTree JSON (automatically finds ContentNode)
19
+ const pageTree = parsePageTree();
21
20
 
22
- // Export as JSON string
23
- const json = exportPageTreeJSON(rootElement, true);
21
+ // The result is ready for ClickFunnels import
22
+ console.log(JSON.stringify(pageTree, null, 2));
24
23
  ```
25
24
 
26
25
  ### Browser (Standalone Bundle)
@@ -29,7 +28,9 @@ const json = exportPageTreeJSON(rootElement, true);
29
28
  <script src="dist/cf-pagetree-parser.js"></script>
30
29
  <script>
31
30
  const { parsePageTree } = CFPageTreeParser;
32
- const pageTree = parsePageTree(rootElement);
31
+
32
+ // Automatically finds ContentNode in the document
33
+ const pageTree = parsePageTree();
33
34
  </script>
34
35
  ```
35
36
 
@@ -41,12 +42,10 @@ The `dist/cf-pagetree-parser.js` bundle can be used in sandboxed extension conte
41
42
 
42
43
  ### Main Functions
43
44
 
44
- - `parsePageTree(rootElement)` - Parse DOM to PageTree object
45
- - `parseElement(element, parentId, index)` - Parse single element
46
- - `extractPageSettings(rootElement)` - Extract page settings
47
- - `exportPageTreeJSON(rootElement, formatted?)` - Export as JSON string
48
- - `downloadPageTreeJSON(rootElement, filename?)` - Download JSON file
49
- - `copyPageTreeToClipboard(rootElement)` - Copy to clipboard
45
+ - `parsePageTree(rootElement?)` - Parse DOM to PageTree object. If no element provided, automatically finds ContentNode.
46
+ - `exportPageTreeJSON(rootElement?, formatted?)` - Export as JSON string
47
+ - `downloadPageTree(filename?, rootElement?)` - Download JSON file
48
+ - `copyPageTreeToClipboard(rootElement?)` - Copy to clipboard
50
49
 
51
50
  ### Utilities
52
51
 
@@ -72,8 +71,8 @@ The parser extracts page-level settings from cf-page attributes:
72
71
  | Attribute | Description |
73
72
  |-----------|-------------|
74
73
  | `font` | Page font family (e.g., "Poppins"). Applied to body-level styles. |
75
- | `font-family` | Alias for `font` |
76
- | `text-color` | Default text color (default: #334155) |
74
+ | `color` | Default text color for all elements (default: #334155) |
75
+ | `text-color` | Alias for `color` |
77
76
  | `link-color` | Default link color (default: #3b82f6) |
78
77
  | `font-weight` | Default font weight |
79
78
  | `css` | Custom CSS (URL-encoded) |
@@ -1653,10 +1653,19 @@ function parseTextElement(
1653
1653
  ? parseFontFamily(fontAttr)
1654
1654
  : parseFontFamily(textStyles["font-family"]);
1655
1655
 
1656
+ // Get color: element data-color > inline style > page color > default
1656
1657
  const colorAttr = element.getAttribute("data-color");
1657
- const color = colorAttr
1658
- ? normalizeColor(colorAttr)
1659
- : normalizeColor(textStyles.color || "#000000");
1658
+ let color;
1659
+ if (colorAttr) {
1660
+ color = normalizeColor(colorAttr);
1661
+ } else if (textStyles.color) {
1662
+ color = normalizeColor(textStyles.color);
1663
+ } else {
1664
+ // Fall back to page-level color from ContentNode
1665
+ const contentNode = element.closest('[data-type="ContentNode"]');
1666
+ const pageColor = contentNode?.getAttribute("data-color") || contentNode?.getAttribute("data-text-color");
1667
+ color = pageColor ? normalizeColor(pageColor) : "#000000";
1668
+ }
1660
1669
 
1661
1670
  const alignAttr = element.getAttribute("data-align");
1662
1671
  const textAlign = alignAttr || parseTextAlign(textStyles["text-align"]);
@@ -1872,6 +1881,9 @@ function parseButton(element, parentId, index) {
1872
1881
  const mainTextId = generateId();
1873
1882
  const subTextId = generateId();
1874
1883
 
1884
+ // Get element-id for scroll-to/show-hide targeting
1885
+ const elementId = element.getAttribute('id') || element.getAttribute('data-element-id');
1886
+
1875
1887
  const wrapperStyles = parseInlineStyle(element.getAttribute('style') || '');
1876
1888
  const spacing = parseSpacing(wrapperStyles);
1877
1889
 
@@ -1994,6 +2006,7 @@ function parseButton(element, parentId, index) {
1994
2006
  parentId,
1995
2007
  fractionalIndex: generateFractionalIndex(index),
1996
2008
  attrs: {
2009
+ ...(elementId ? { id: elementId } : {}),
1997
2010
  style: {
1998
2011
  'text-align': textAlign,
1999
2012
  },
@@ -2170,6 +2183,9 @@ function parseButton(element, parentId, index) {
2170
2183
  function parseImage(element, parentId, index) {
2171
2184
  const id = generateId();
2172
2185
 
2186
+ // Get element-id for scroll-to/show-hide targeting
2187
+ const elementId = element.getAttribute('id') || element.getAttribute('data-element-id');
2188
+
2173
2189
  const wrapperStyles = parseInlineStyle(element.getAttribute('style') || '');
2174
2190
  const spacing = parseSpacing(wrapperStyles);
2175
2191
  const textAlign = parseTextAlign(wrapperStyles['text-align']);
@@ -2198,6 +2214,7 @@ function parseImage(element, parentId, index) {
2198
2214
  parentId,
2199
2215
  fractionalIndex: generateFractionalIndex(index),
2200
2216
  attrs: {
2217
+ ...(elementId ? { id: elementId } : {}),
2201
2218
  alt,
2202
2219
  style: {
2203
2220
  'text-align': textAlign,
@@ -2266,6 +2283,9 @@ function parseImage(element, parentId, index) {
2266
2283
  function parseIcon(element, parentId, index) {
2267
2284
  const id = generateId();
2268
2285
 
2286
+ // Get element-id for scroll-to/show-hide targeting
2287
+ const elementId = element.getAttribute('id') || element.getAttribute('data-element-id');
2288
+
2269
2289
  const wrapperStyles = parseInlineStyle(element.getAttribute('style') || '');
2270
2290
  const spacing = parseSpacing(wrapperStyles);
2271
2291
  const textAlign = parseTextAlign(wrapperStyles['text-align']);
@@ -2301,6 +2321,7 @@ function parseIcon(element, parentId, index) {
2301
2321
  parentId,
2302
2322
  fractionalIndex: generateFractionalIndex(index),
2303
2323
  attrs: {
2324
+ ...(elementId ? { id: elementId } : {}),
2304
2325
  style: {},
2305
2326
  ...animationAttrs,
2306
2327
  },
@@ -2349,6 +2370,9 @@ function parseIcon(element, parentId, index) {
2349
2370
  function parseVideo(element, parentId, index) {
2350
2371
  const id = generateId();
2351
2372
 
2373
+ // Get element-id for scroll-to/show-hide targeting
2374
+ const elementId = element.getAttribute('id') || element.getAttribute('data-element-id');
2375
+
2352
2376
  const wrapperStyles = parseInlineStyle(element.getAttribute('style') || '');
2353
2377
  const spacing = parseSpacing(wrapperStyles);
2354
2378
 
@@ -2372,6 +2396,7 @@ function parseVideo(element, parentId, index) {
2372
2396
  parentId,
2373
2397
  fractionalIndex: generateFractionalIndex(index),
2374
2398
  attrs: {
2399
+ ...(elementId ? { id: elementId } : {}),
2375
2400
  'data-video-type': videoType,
2376
2401
  'data-skip-background-settings': background.color ? 'false' : 'true',
2377
2402
  'data-skip-shadow-settings': shadow ? 'false' : 'true',
@@ -2423,6 +2448,9 @@ function parseVideo(element, parentId, index) {
2423
2448
  function parseDivider(element, parentId, index) {
2424
2449
  const id = generateId();
2425
2450
 
2451
+ // Get element-id for scroll-to/show-hide targeting
2452
+ const elementId = element.getAttribute('id') || element.getAttribute('data-element-id');
2453
+
2426
2454
  const wrapperStyles = parseInlineStyle(element.getAttribute('style') || '');
2427
2455
  const spacing = parseSpacing(wrapperStyles);
2428
2456
 
@@ -2468,6 +2496,7 @@ function parseDivider(element, parentId, index) {
2468
2496
  parentId,
2469
2497
  fractionalIndex: generateFractionalIndex(index),
2470
2498
  attrs: {
2499
+ ...(elementId ? { id: elementId } : {}),
2471
2500
  style: {},
2472
2501
  },
2473
2502
  params: {
@@ -2527,6 +2556,9 @@ function parseDivider(element, parentId, index) {
2527
2556
  function parseInput(element, parentId, index) {
2528
2557
  const id = generateId();
2529
2558
 
2559
+ // Get element-id for scroll-to/show-hide targeting
2560
+ const elementId = element.getAttribute('id') || element.getAttribute('data-element-id');
2561
+
2530
2562
  const wrapperStyles = parseInlineStyle(element.getAttribute('style') || '');
2531
2563
  const spacing = parseSpacing(wrapperStyles);
2532
2564
 
@@ -2569,6 +2601,7 @@ function parseInput(element, parentId, index) {
2569
2601
  parentId,
2570
2602
  fractionalIndex: generateFractionalIndex(index),
2571
2603
  attrs: {
2604
+ ...(elementId ? { id: elementId } : {}),
2572
2605
  'data-skip-shadow-settings': shadow ? 'false' : 'true',
2573
2606
  type: inputType === 'phone_number' ? 'tel' : (inputType === 'email' ? 'email' : 'text'),
2574
2607
  style: {
@@ -2687,6 +2720,9 @@ function parseInput(element, parentId, index) {
2687
2720
  function parseTextArea(element, parentId, index) {
2688
2721
  const id = generateId();
2689
2722
 
2723
+ // Get element-id for scroll-to/show-hide targeting
2724
+ const elementId = element.getAttribute('id') || element.getAttribute('data-element-id');
2725
+
2690
2726
  const wrapperStyles = parseInlineStyle(element.getAttribute('style') || '');
2691
2727
  const spacing = parseSpacing(wrapperStyles);
2692
2728
 
@@ -2728,6 +2764,7 @@ function parseTextArea(element, parentId, index) {
2728
2764
  parentId,
2729
2765
  fractionalIndex: generateFractionalIndex(index),
2730
2766
  attrs: {
2767
+ ...(elementId ? { id: elementId } : {}),
2731
2768
  'data-skip-shadow-settings': shadow ? 'false' : 'true',
2732
2769
  style: {
2733
2770
  width,
@@ -2844,6 +2881,9 @@ function parseTextArea(element, parentId, index) {
2844
2881
  function parseSelectBox(element, parentId, index) {
2845
2882
  const id = generateId();
2846
2883
 
2884
+ // Get element-id for scroll-to/show-hide targeting
2885
+ const elementId = element.getAttribute('id') || element.getAttribute('data-element-id');
2886
+
2847
2887
  const wrapperStyles = parseInlineStyle(element.getAttribute('style') || '');
2848
2888
  const spacing = parseSpacing(wrapperStyles);
2849
2889
 
@@ -2912,6 +2952,7 @@ function parseSelectBox(element, parentId, index) {
2912
2952
  parentId,
2913
2953
  fractionalIndex: generateFractionalIndex(index),
2914
2954
  attrs: {
2955
+ ...(elementId ? { id: elementId } : {}),
2915
2956
  style: {
2916
2957
  width,
2917
2958
  'margin-top': marginTop.value,
@@ -3017,6 +3058,9 @@ function parseSelectBox(element, parentId, index) {
3017
3058
  function parseCheckbox(element, parentId, index) {
3018
3059
  const id = generateId();
3019
3060
 
3061
+ // Get element-id for scroll-to/show-hide targeting
3062
+ const elementId = element.getAttribute('id') || element.getAttribute('data-element-id');
3063
+
3020
3064
  const wrapperStyles = parseInlineStyle(element.getAttribute('style') || '');
3021
3065
  const spacing = parseSpacing(wrapperStyles);
3022
3066
 
@@ -3040,9 +3084,16 @@ function parseCheckbox(element, parentId, index) {
3040
3084
  const boxBorderColor = boxBorderMatch ? normalizeColor(boxBorderMatch[2]) : 'rgb(229, 231, 235)';
3041
3085
  const boxBorderRadius = parseBorderRadius(boxStyles);
3042
3086
 
3043
- // Label text styling
3087
+ // Label text styling - inherit page color if not set
3044
3088
  const textStyles = textSpan ? parseInlineStyle(textSpan.getAttribute('style') || '') : {};
3045
- const labelColor = normalizeColor(textStyles.color || '#334155');
3089
+ let labelColor;
3090
+ if (textStyles.color) {
3091
+ labelColor = normalizeColor(textStyles.color);
3092
+ } else {
3093
+ const contentNode = element.closest('[data-type="ContentNode"]');
3094
+ const pageColor = contentNode?.getAttribute("data-color") || contentNode?.getAttribute("data-text-color");
3095
+ labelColor = pageColor ? normalizeColor(pageColor) : '#334155';
3096
+ }
3046
3097
  const labelFontSize = parseValueWithUnit(textStyles['font-size'] || '16px');
3047
3098
 
3048
3099
  // Gap between checkbox and label
@@ -3056,6 +3107,7 @@ function parseCheckbox(element, parentId, index) {
3056
3107
  parentId,
3057
3108
  fractionalIndex: generateFractionalIndex(index),
3058
3109
  attrs: {
3110
+ ...(elementId ? { id: elementId } : {}),
3059
3111
  style: {},
3060
3112
  },
3061
3113
  params: {
@@ -3168,6 +3220,9 @@ function parseBulletList(element, parentId, index) {
3168
3220
  const id = generateId();
3169
3221
  const contentEditableId = generateId();
3170
3222
 
3223
+ // Get element-id for scroll-to/show-hide targeting
3224
+ const elementId = element.getAttribute('id') || element.getAttribute('data-element-id');
3225
+
3171
3226
  const wrapperStyles = parseInlineStyle(element.getAttribute('style') || '');
3172
3227
  const spacing = parseSpacing(wrapperStyles);
3173
3228
 
@@ -3190,12 +3245,17 @@ function parseBulletList(element, parentId, index) {
3190
3245
  // Find list items
3191
3246
  const items = ul ? ul.querySelectorAll('li') : [];
3192
3247
 
3248
+ // Get page-level color from ContentNode for fallback
3249
+ const contentNode = element.closest('[data-type="ContentNode"]');
3250
+ const pageColor = contentNode?.getAttribute("data-color") || contentNode?.getAttribute("data-text-color");
3251
+ const defaultTextColor = pageColor ? normalizeColor(pageColor) : '#334155';
3252
+
3193
3253
  // Initialize with defaults, will be overridden by data attrs or inline styles
3194
3254
  let iconClass = 'fas fa-check fa_icon';
3195
3255
  let iconColor = '#10b981';
3196
3256
  let iconMarginRight = 12;
3197
3257
  let iconSize = null;
3198
- let textColor = '#334155';
3258
+ let textColor = defaultTextColor;
3199
3259
  let textSize = null;
3200
3260
  let justifyContent = 'flex-start';
3201
3261
 
@@ -3295,6 +3355,7 @@ function parseBulletList(element, parentId, index) {
3295
3355
  parentId,
3296
3356
  fractionalIndex: generateFractionalIndex(index),
3297
3357
  attrs: {
3358
+ ...(elementId ? { id: elementId } : {}),
3298
3359
  style: {},
3299
3360
  },
3300
3361
  params: {
@@ -3513,6 +3574,9 @@ function parseBulletList(element, parentId, index) {
3513
3574
  function parseProgressBar(element, parentId, index) {
3514
3575
  const id = generateId();
3515
3576
 
3577
+ // Get element-id for scroll-to/show-hide targeting
3578
+ const elementId = element.getAttribute('id') || element.getAttribute('data-element-id');
3579
+
3516
3580
  const wrapperStyles = parseInlineStyle(element.getAttribute('style') || '');
3517
3581
  const spacing = parseSpacing(wrapperStyles);
3518
3582
 
@@ -3556,6 +3620,7 @@ function parseProgressBar(element, parentId, index) {
3556
3620
  parentId,
3557
3621
  fractionalIndex: generateFractionalIndex(index),
3558
3622
  attrs: {
3623
+ ...(elementId ? { id: elementId } : {}),
3559
3624
  style: {},
3560
3625
  },
3561
3626
  params: {
@@ -3635,6 +3700,9 @@ function parseProgressBar(element, parentId, index) {
3635
3700
  function parseVideoPopup(element, parentId, index) {
3636
3701
  const id = generateId();
3637
3702
 
3703
+ // Get element-id for scroll-to/show-hide targeting
3704
+ const elementId = element.getAttribute('id') || element.getAttribute('data-element-id');
3705
+
3638
3706
  const wrapperStyles = parseInlineStyle(element.getAttribute('style') || '');
3639
3707
  const spacing = parseSpacing(wrapperStyles);
3640
3708
  const textAlign = parseTextAlign(wrapperStyles['text-align']);
@@ -3673,6 +3741,7 @@ function parseVideoPopup(element, parentId, index) {
3673
3741
  parentId,
3674
3742
  fractionalIndex: generateFractionalIndex(index),
3675
3743
  attrs: {
3744
+ ...(elementId ? { id: elementId } : {}),
3676
3745
  alt: alt || 'Video thumbnail',
3677
3746
  },
3678
3747
  params: {
@@ -3750,6 +3819,9 @@ function parseVideoPopup(element, parentId, index) {
3750
3819
  function parseCountdown(element, parentId, index) {
3751
3820
  const id = generateId();
3752
3821
 
3822
+ // Get element-id for scroll-to/show-hide targeting
3823
+ const elementId = element.getAttribute('id') || element.getAttribute('data-element-id');
3824
+
3753
3825
  const wrapperStyles = parseInlineStyle(element.getAttribute('style') || '');
3754
3826
  const spacing = parseSpacing(wrapperStyles);
3755
3827
 
@@ -3823,6 +3895,10 @@ function parseCountdown(element, parentId, index) {
3823
3895
  version: 0,
3824
3896
  parentId,
3825
3897
  fractionalIndex: generateFractionalIndex(index),
3898
+ attrs: {
3899
+ ...(elementId ? { id: elementId } : {}),
3900
+ style: {},
3901
+ },
3826
3902
  params: {
3827
3903
  type: 'countdown',
3828
3904
  countdown_opts: {
@@ -3947,7 +4023,7 @@ function parseCountdown(element, parentId, index) {
3947
4023
 
3948
4024
  // Apply spacing
3949
4025
  const { attrs: spacingAttrs, params: spacingParams } = spacingToAttrsAndParams(spacing);
3950
- Object.assign(node.attrs || (node.attrs = {}), { style: spacingAttrs.style });
4026
+ Object.assign(node.attrs.style, spacingAttrs.style);
3951
4027
  Object.assign(node.params, spacingParams);
3952
4028
 
3953
4029
  // Apply border to amount container
@@ -4467,7 +4543,8 @@ function parsePageTree(rootElement = null) {
4467
4543
  }
4468
4544
 
4469
4545
  // Extract page settings from data attributes
4470
- const textColorRaw = rootElement.getAttribute("data-text-color") || "#334155";
4546
+ // Support both 'color' (simple) and 'text-color' (explicit) for page text color
4547
+ const textColorRaw = rootElement.getAttribute("data-color") || rootElement.getAttribute("data-text-color") || "#334155";
4471
4548
  const linkColorRaw = rootElement.getAttribute("data-link-color") || "#3b82f6";
4472
4549
  const textColor = normalizeColor(textColorRaw);
4473
4550
  const linkColor = normalizeColor(linkColorRaw);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cf-pagetree-parser",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "Parse FunnelWind HTML to ClickFunnels PageTree JSON",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/index.js CHANGED
@@ -221,7 +221,8 @@ export function parsePageTree(rootElement = null) {
221
221
  }
222
222
 
223
223
  // Extract page settings from data attributes
224
- const textColorRaw = rootElement.getAttribute("data-text-color") || "#334155";
224
+ // Support both 'color' (simple) and 'text-color' (explicit) for page text color
225
+ const textColorRaw = rootElement.getAttribute("data-color") || rootElement.getAttribute("data-text-color") || "#334155";
225
226
  const linkColorRaw = rootElement.getAttribute("data-link-color") || "#3b82f6";
226
227
  const textColor = normalizeColor(textColorRaw);
227
228
  const linkColor = normalizeColor(linkColorRaw);
@@ -40,6 +40,9 @@ export function parseButton(element, parentId, index) {
40
40
  const mainTextId = generateId();
41
41
  const subTextId = generateId();
42
42
 
43
+ // Get element-id for scroll-to/show-hide targeting
44
+ const elementId = element.getAttribute('id') || element.getAttribute('data-element-id');
45
+
43
46
  const wrapperStyles = parseInlineStyle(element.getAttribute('style') || '');
44
47
  const spacing = parseSpacing(wrapperStyles);
45
48
 
@@ -162,6 +165,7 @@ export function parseButton(element, parentId, index) {
162
165
  parentId,
163
166
  fractionalIndex: generateFractionalIndex(index),
164
167
  attrs: {
168
+ ...(elementId ? { id: elementId } : {}),
165
169
  style: {
166
170
  'text-align': textAlign,
167
171
  },
@@ -34,6 +34,9 @@ import {
34
34
  export function parseInput(element, parentId, index) {
35
35
  const id = generateId();
36
36
 
37
+ // Get element-id for scroll-to/show-hide targeting
38
+ const elementId = element.getAttribute('id') || element.getAttribute('data-element-id');
39
+
37
40
  const wrapperStyles = parseInlineStyle(element.getAttribute('style') || '');
38
41
  const spacing = parseSpacing(wrapperStyles);
39
42
 
@@ -76,6 +79,7 @@ export function parseInput(element, parentId, index) {
76
79
  parentId,
77
80
  fractionalIndex: generateFractionalIndex(index),
78
81
  attrs: {
82
+ ...(elementId ? { id: elementId } : {}),
79
83
  'data-skip-shadow-settings': shadow ? 'false' : 'true',
80
84
  type: inputType === 'phone_number' ? 'tel' : (inputType === 'email' ? 'email' : 'text'),
81
85
  style: {
@@ -194,6 +198,9 @@ export function parseInput(element, parentId, index) {
194
198
  export function parseTextArea(element, parentId, index) {
195
199
  const id = generateId();
196
200
 
201
+ // Get element-id for scroll-to/show-hide targeting
202
+ const elementId = element.getAttribute('id') || element.getAttribute('data-element-id');
203
+
197
204
  const wrapperStyles = parseInlineStyle(element.getAttribute('style') || '');
198
205
  const spacing = parseSpacing(wrapperStyles);
199
206
 
@@ -235,6 +242,7 @@ export function parseTextArea(element, parentId, index) {
235
242
  parentId,
236
243
  fractionalIndex: generateFractionalIndex(index),
237
244
  attrs: {
245
+ ...(elementId ? { id: elementId } : {}),
238
246
  'data-skip-shadow-settings': shadow ? 'false' : 'true',
239
247
  style: {
240
248
  width,
@@ -351,6 +359,9 @@ export function parseTextArea(element, parentId, index) {
351
359
  export function parseSelectBox(element, parentId, index) {
352
360
  const id = generateId();
353
361
 
362
+ // Get element-id for scroll-to/show-hide targeting
363
+ const elementId = element.getAttribute('id') || element.getAttribute('data-element-id');
364
+
354
365
  const wrapperStyles = parseInlineStyle(element.getAttribute('style') || '');
355
366
  const spacing = parseSpacing(wrapperStyles);
356
367
 
@@ -419,6 +430,7 @@ export function parseSelectBox(element, parentId, index) {
419
430
  parentId,
420
431
  fractionalIndex: generateFractionalIndex(index),
421
432
  attrs: {
433
+ ...(elementId ? { id: elementId } : {}),
422
434
  style: {
423
435
  width,
424
436
  'margin-top': marginTop.value,
@@ -524,6 +536,9 @@ export function parseSelectBox(element, parentId, index) {
524
536
  export function parseCheckbox(element, parentId, index) {
525
537
  const id = generateId();
526
538
 
539
+ // Get element-id for scroll-to/show-hide targeting
540
+ const elementId = element.getAttribute('id') || element.getAttribute('data-element-id');
541
+
527
542
  const wrapperStyles = parseInlineStyle(element.getAttribute('style') || '');
528
543
  const spacing = parseSpacing(wrapperStyles);
529
544
 
@@ -547,9 +562,16 @@ export function parseCheckbox(element, parentId, index) {
547
562
  const boxBorderColor = boxBorderMatch ? normalizeColor(boxBorderMatch[2]) : 'rgb(229, 231, 235)';
548
563
  const boxBorderRadius = parseBorderRadius(boxStyles);
549
564
 
550
- // Label text styling
565
+ // Label text styling - inherit page color if not set
551
566
  const textStyles = textSpan ? parseInlineStyle(textSpan.getAttribute('style') || '') : {};
552
- const labelColor = normalizeColor(textStyles.color || '#334155');
567
+ let labelColor;
568
+ if (textStyles.color) {
569
+ labelColor = normalizeColor(textStyles.color);
570
+ } else {
571
+ const contentNode = element.closest('[data-type="ContentNode"]');
572
+ const pageColor = contentNode?.getAttribute("data-color") || contentNode?.getAttribute("data-text-color");
573
+ labelColor = pageColor ? normalizeColor(pageColor) : '#334155';
574
+ }
553
575
  const labelFontSize = parseValueWithUnit(textStyles['font-size'] || '16px');
554
576
 
555
577
  // Gap between checkbox and label
@@ -563,6 +585,7 @@ export function parseCheckbox(element, parentId, index) {
563
585
  parentId,
564
586
  fractionalIndex: generateFractionalIndex(index),
565
587
  attrs: {
588
+ ...(elementId ? { id: elementId } : {}),
566
589
  style: {},
567
590
  },
568
591
  params: {
@@ -33,6 +33,9 @@ import {
33
33
  export function parseProgressBar(element, parentId, index) {
34
34
  const id = generateId();
35
35
 
36
+ // Get element-id for scroll-to/show-hide targeting
37
+ const elementId = element.getAttribute('id') || element.getAttribute('data-element-id');
38
+
36
39
  const wrapperStyles = parseInlineStyle(element.getAttribute('style') || '');
37
40
  const spacing = parseSpacing(wrapperStyles);
38
41
 
@@ -76,6 +79,7 @@ export function parseProgressBar(element, parentId, index) {
76
79
  parentId,
77
80
  fractionalIndex: generateFractionalIndex(index),
78
81
  attrs: {
82
+ ...(elementId ? { id: elementId } : {}),
79
83
  style: {},
80
84
  },
81
85
  params: {
@@ -155,6 +159,9 @@ export function parseProgressBar(element, parentId, index) {
155
159
  export function parseVideoPopup(element, parentId, index) {
156
160
  const id = generateId();
157
161
 
162
+ // Get element-id for scroll-to/show-hide targeting
163
+ const elementId = element.getAttribute('id') || element.getAttribute('data-element-id');
164
+
158
165
  const wrapperStyles = parseInlineStyle(element.getAttribute('style') || '');
159
166
  const spacing = parseSpacing(wrapperStyles);
160
167
  const textAlign = parseTextAlign(wrapperStyles['text-align']);
@@ -193,6 +200,7 @@ export function parseVideoPopup(element, parentId, index) {
193
200
  parentId,
194
201
  fractionalIndex: generateFractionalIndex(index),
195
202
  attrs: {
203
+ ...(elementId ? { id: elementId } : {}),
196
204
  alt: alt || 'Video thumbnail',
197
205
  },
198
206
  params: {
@@ -270,6 +278,9 @@ export function parseVideoPopup(element, parentId, index) {
270
278
  export function parseCountdown(element, parentId, index) {
271
279
  const id = generateId();
272
280
 
281
+ // Get element-id for scroll-to/show-hide targeting
282
+ const elementId = element.getAttribute('id') || element.getAttribute('data-element-id');
283
+
273
284
  const wrapperStyles = parseInlineStyle(element.getAttribute('style') || '');
274
285
  const spacing = parseSpacing(wrapperStyles);
275
286
 
@@ -343,6 +354,10 @@ export function parseCountdown(element, parentId, index) {
343
354
  version: 0,
344
355
  parentId,
345
356
  fractionalIndex: generateFractionalIndex(index),
357
+ attrs: {
358
+ ...(elementId ? { id: elementId } : {}),
359
+ style: {},
360
+ },
346
361
  params: {
347
362
  type: 'countdown',
348
363
  countdown_opts: {
@@ -467,7 +482,7 @@ export function parseCountdown(element, parentId, index) {
467
482
 
468
483
  // Apply spacing
469
484
  const { attrs: spacingAttrs, params: spacingParams } = spacingToAttrsAndParams(spacing);
470
- Object.assign(node.attrs || (node.attrs = {}), { style: spacingAttrs.style });
485
+ Object.assign(node.attrs.style, spacingAttrs.style);
471
486
  Object.assign(node.params, spacingParams);
472
487
 
473
488
  // Apply border to amount container
@@ -29,6 +29,9 @@ export function parseBulletList(element, parentId, index) {
29
29
  const id = generateId();
30
30
  const contentEditableId = generateId();
31
31
 
32
+ // Get element-id for scroll-to/show-hide targeting
33
+ const elementId = element.getAttribute('id') || element.getAttribute('data-element-id');
34
+
32
35
  const wrapperStyles = parseInlineStyle(element.getAttribute('style') || '');
33
36
  const spacing = parseSpacing(wrapperStyles);
34
37
 
@@ -51,12 +54,17 @@ export function parseBulletList(element, parentId, index) {
51
54
  // Find list items
52
55
  const items = ul ? ul.querySelectorAll('li') : [];
53
56
 
57
+ // Get page-level color from ContentNode for fallback
58
+ const contentNode = element.closest('[data-type="ContentNode"]');
59
+ const pageColor = contentNode?.getAttribute("data-color") || contentNode?.getAttribute("data-text-color");
60
+ const defaultTextColor = pageColor ? normalizeColor(pageColor) : '#334155';
61
+
54
62
  // Initialize with defaults, will be overridden by data attrs or inline styles
55
63
  let iconClass = 'fas fa-check fa_icon';
56
64
  let iconColor = '#10b981';
57
65
  let iconMarginRight = 12;
58
66
  let iconSize = null;
59
- let textColor = '#334155';
67
+ let textColor = defaultTextColor;
60
68
  let textSize = null;
61
69
  let justifyContent = 'flex-start';
62
70
 
@@ -156,6 +164,7 @@ export function parseBulletList(element, parentId, index) {
156
164
  parentId,
157
165
  fractionalIndex: generateFractionalIndex(index),
158
166
  attrs: {
167
+ ...(elementId ? { id: elementId } : {}),
159
168
  style: {},
160
169
  },
161
170
  params: {
@@ -35,6 +35,9 @@ import {
35
35
  export function parseImage(element, parentId, index) {
36
36
  const id = generateId();
37
37
 
38
+ // Get element-id for scroll-to/show-hide targeting
39
+ const elementId = element.getAttribute('id') || element.getAttribute('data-element-id');
40
+
38
41
  const wrapperStyles = parseInlineStyle(element.getAttribute('style') || '');
39
42
  const spacing = parseSpacing(wrapperStyles);
40
43
  const textAlign = parseTextAlign(wrapperStyles['text-align']);
@@ -63,6 +66,7 @@ export function parseImage(element, parentId, index) {
63
66
  parentId,
64
67
  fractionalIndex: generateFractionalIndex(index),
65
68
  attrs: {
69
+ ...(elementId ? { id: elementId } : {}),
66
70
  alt,
67
71
  style: {
68
72
  'text-align': textAlign,
@@ -131,6 +135,9 @@ export function parseImage(element, parentId, index) {
131
135
  export function parseIcon(element, parentId, index) {
132
136
  const id = generateId();
133
137
 
138
+ // Get element-id for scroll-to/show-hide targeting
139
+ const elementId = element.getAttribute('id') || element.getAttribute('data-element-id');
140
+
134
141
  const wrapperStyles = parseInlineStyle(element.getAttribute('style') || '');
135
142
  const spacing = parseSpacing(wrapperStyles);
136
143
  const textAlign = parseTextAlign(wrapperStyles['text-align']);
@@ -166,6 +173,7 @@ export function parseIcon(element, parentId, index) {
166
173
  parentId,
167
174
  fractionalIndex: generateFractionalIndex(index),
168
175
  attrs: {
176
+ ...(elementId ? { id: elementId } : {}),
169
177
  style: {},
170
178
  ...animationAttrs,
171
179
  },
@@ -214,6 +222,9 @@ export function parseIcon(element, parentId, index) {
214
222
  export function parseVideo(element, parentId, index) {
215
223
  const id = generateId();
216
224
 
225
+ // Get element-id for scroll-to/show-hide targeting
226
+ const elementId = element.getAttribute('id') || element.getAttribute('data-element-id');
227
+
217
228
  const wrapperStyles = parseInlineStyle(element.getAttribute('style') || '');
218
229
  const spacing = parseSpacing(wrapperStyles);
219
230
 
@@ -237,6 +248,7 @@ export function parseVideo(element, parentId, index) {
237
248
  parentId,
238
249
  fractionalIndex: generateFractionalIndex(index),
239
250
  attrs: {
251
+ ...(elementId ? { id: elementId } : {}),
240
252
  'data-video-type': videoType,
241
253
  'data-skip-background-settings': background.color ? 'false' : 'true',
242
254
  'data-skip-shadow-settings': shadow ? 'false' : 'true',
@@ -288,6 +300,9 @@ export function parseVideo(element, parentId, index) {
288
300
  export function parseDivider(element, parentId, index) {
289
301
  const id = generateId();
290
302
 
303
+ // Get element-id for scroll-to/show-hide targeting
304
+ const elementId = element.getAttribute('id') || element.getAttribute('data-element-id');
305
+
291
306
  const wrapperStyles = parseInlineStyle(element.getAttribute('style') || '');
292
307
  const spacing = parseSpacing(wrapperStyles);
293
308
 
@@ -333,6 +348,7 @@ export function parseDivider(element, parentId, index) {
333
348
  parentId,
334
349
  fractionalIndex: generateFractionalIndex(index),
335
350
  attrs: {
351
+ ...(elementId ? { id: elementId } : {}),
336
352
  style: {},
337
353
  },
338
354
  params: {
@@ -73,10 +73,19 @@ function parseTextElement(
73
73
  ? parseFontFamily(fontAttr)
74
74
  : parseFontFamily(textStyles["font-family"]);
75
75
 
76
+ // Get color: element data-color > inline style > page color > default
76
77
  const colorAttr = element.getAttribute("data-color");
77
- const color = colorAttr
78
- ? normalizeColor(colorAttr)
79
- : normalizeColor(textStyles.color || "#000000");
78
+ let color;
79
+ if (colorAttr) {
80
+ color = normalizeColor(colorAttr);
81
+ } else if (textStyles.color) {
82
+ color = normalizeColor(textStyles.color);
83
+ } else {
84
+ // Fall back to page-level color from ContentNode
85
+ const contentNode = element.closest('[data-type="ContentNode"]');
86
+ const pageColor = contentNode?.getAttribute("data-color") || contentNode?.getAttribute("data-text-color");
87
+ color = pageColor ? normalizeColor(pageColor) : "#000000";
88
+ }
80
89
 
81
90
  const alignAttr = element.getAttribute("data-align");
82
91
  const textAlign = alignAttr || parseTextAlign(textStyles["text-align"]);