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,359 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================================
|
|
3
|
+
* PAGETREE PARSER - List Element Parser
|
|
4
|
+
* ============================================================================
|
|
5
|
+
*
|
|
6
|
+
* BulletList/V1
|
|
7
|
+
*
|
|
8
|
+
* ============================================================================
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
generateId,
|
|
13
|
+
generateFractionalIndex,
|
|
14
|
+
parseInlineStyle,
|
|
15
|
+
parseValueWithUnit,
|
|
16
|
+
normalizeColor,
|
|
17
|
+
sanitizeHtml,
|
|
18
|
+
} from '../utils.js';
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
parseSpacing,
|
|
22
|
+
spacingToAttrsAndParams,
|
|
23
|
+
} from '../styles.js';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Parse BulletList/V1
|
|
27
|
+
*/
|
|
28
|
+
export function parseBulletList(element, parentId, index) {
|
|
29
|
+
const id = generateId();
|
|
30
|
+
const contentEditableId = generateId();
|
|
31
|
+
|
|
32
|
+
const wrapperStyles = parseInlineStyle(element.getAttribute('style') || '');
|
|
33
|
+
const spacing = parseSpacing(wrapperStyles);
|
|
34
|
+
|
|
35
|
+
// Find the ul element
|
|
36
|
+
const ul = element.querySelector('ul');
|
|
37
|
+
const ulStyles = ul ? parseInlineStyle(ul.getAttribute('style') || '') : {};
|
|
38
|
+
|
|
39
|
+
// Get item gap from ul's gap style
|
|
40
|
+
const itemGap = parseValueWithUnit(ulStyles.gap || '8px');
|
|
41
|
+
|
|
42
|
+
// Read data attributes first (most reliable)
|
|
43
|
+
const sizeResolvedAttr = element.getAttribute('data-size-resolved'); // Pre-resolved pixel value
|
|
44
|
+
const textSizeAttr = element.getAttribute('data-text-size'); // Legacy: direct pixel value
|
|
45
|
+
const iconSizeAttr = element.getAttribute('data-icon-size');
|
|
46
|
+
const textColorAttr = element.getAttribute('data-text-color');
|
|
47
|
+
const iconColorAttr = element.getAttribute('data-icon-color');
|
|
48
|
+
const iconAttr = element.getAttribute('data-icon');
|
|
49
|
+
const gapAttr = element.getAttribute('data-gap');
|
|
50
|
+
|
|
51
|
+
// Find list items
|
|
52
|
+
const items = ul ? ul.querySelectorAll('li') : [];
|
|
53
|
+
|
|
54
|
+
// Initialize with defaults, will be overridden by data attrs or inline styles
|
|
55
|
+
let iconClass = 'fas fa-check fa_icon';
|
|
56
|
+
let iconColor = '#10b981';
|
|
57
|
+
let iconMarginRight = 12;
|
|
58
|
+
let iconSize = null;
|
|
59
|
+
let textColor = '#334155';
|
|
60
|
+
let textSize = null;
|
|
61
|
+
let justifyContent = 'flex-start';
|
|
62
|
+
|
|
63
|
+
// Parse from inline styles as fallback
|
|
64
|
+
if (items.length > 0) {
|
|
65
|
+
const firstItem = items[0];
|
|
66
|
+
const icon = firstItem.querySelector('i');
|
|
67
|
+
const textSpan = firstItem.querySelector('span');
|
|
68
|
+
|
|
69
|
+
// Get alignment from li styles
|
|
70
|
+
const liStyles = parseInlineStyle(firstItem.getAttribute('style') || '');
|
|
71
|
+
if (liStyles['justify-content']) {
|
|
72
|
+
justifyContent = liStyles['justify-content'];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (icon) {
|
|
76
|
+
const rawClass = icon.getAttribute('class') || 'fas fa-check';
|
|
77
|
+
// Ensure fa_icon is in the class (only if not from data attr)
|
|
78
|
+
if (!iconAttr) {
|
|
79
|
+
iconClass = rawClass.includes('fa_icon') ? rawClass : `${rawClass} fa_icon`;
|
|
80
|
+
}
|
|
81
|
+
const iconStyles = parseInlineStyle(icon.getAttribute('style') || '');
|
|
82
|
+
// Only use inline style if no data attr
|
|
83
|
+
if (!iconColorAttr) {
|
|
84
|
+
iconColor = normalizeColor(iconStyles.color) || iconColor;
|
|
85
|
+
}
|
|
86
|
+
const marginRight = parseValueWithUnit(iconStyles['margin-right']);
|
|
87
|
+
if (marginRight) iconMarginRight = marginRight.value;
|
|
88
|
+
// Get icon font size from inline style if not from data attr
|
|
89
|
+
if (!iconSizeAttr) {
|
|
90
|
+
const iconFontSize = parseValueWithUnit(iconStyles['font-size']);
|
|
91
|
+
if (iconFontSize) iconSize = iconFontSize;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (textSpan) {
|
|
96
|
+
const textStyles = parseInlineStyle(textSpan.getAttribute('style') || '');
|
|
97
|
+
// Only use inline style if no data attr
|
|
98
|
+
if (!textColorAttr) {
|
|
99
|
+
textColor = normalizeColor(textStyles.color) || textColor;
|
|
100
|
+
}
|
|
101
|
+
// Get text font size from inline style if not from data attr
|
|
102
|
+
if (!textSizeAttr) {
|
|
103
|
+
const textFontSize = parseValueWithUnit(textStyles['font-size']);
|
|
104
|
+
if (textFontSize) textSize = textFontSize;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Apply data attributes (override inline styles)
|
|
110
|
+
if (iconAttr) {
|
|
111
|
+
iconClass = iconAttr.includes('fa_icon') ? iconAttr : `${iconAttr} fa_icon`;
|
|
112
|
+
}
|
|
113
|
+
if (iconColorAttr) {
|
|
114
|
+
iconColor = normalizeColor(iconColorAttr);
|
|
115
|
+
}
|
|
116
|
+
if (textColorAttr) {
|
|
117
|
+
textColor = normalizeColor(textColorAttr);
|
|
118
|
+
}
|
|
119
|
+
// Priority: data-size-resolved > data-size (as px) > data-text-size (legacy)
|
|
120
|
+
if (sizeResolvedAttr) {
|
|
121
|
+
const parsed = parseValueWithUnit(sizeResolvedAttr);
|
|
122
|
+
if (parsed) textSize = parsed;
|
|
123
|
+
} else if (textSizeAttr) {
|
|
124
|
+
const parsed = parseValueWithUnit(textSizeAttr);
|
|
125
|
+
if (parsed) textSize = parsed;
|
|
126
|
+
}
|
|
127
|
+
if (iconSizeAttr) {
|
|
128
|
+
const parsed = parseValueWithUnit(iconSizeAttr);
|
|
129
|
+
if (parsed) iconSize = parsed;
|
|
130
|
+
}
|
|
131
|
+
if (gapAttr) {
|
|
132
|
+
const parsed = parseValueWithUnit(gapAttr);
|
|
133
|
+
if (parsed) iconMarginRight = parsed.value;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Get link color - prefer data attribute, fallback to parsing anchor elements
|
|
137
|
+
const linkColorAttr = element.getAttribute('data-link-color');
|
|
138
|
+
let linkColor = null;
|
|
139
|
+
if (linkColorAttr) {
|
|
140
|
+
linkColor = normalizeColor(linkColorAttr);
|
|
141
|
+
} else if (ul) {
|
|
142
|
+
// Try to find an anchor in any list item
|
|
143
|
+
const anchor = ul.querySelector('a');
|
|
144
|
+
if (anchor) {
|
|
145
|
+
const anchorStyles = parseInlineStyle(anchor.getAttribute('style') || '');
|
|
146
|
+
if (anchorStyles.color) {
|
|
147
|
+
linkColor = normalizeColor(anchorStyles.color);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const node = {
|
|
153
|
+
type: 'BulletList/V1',
|
|
154
|
+
id,
|
|
155
|
+
version: 0,
|
|
156
|
+
parentId,
|
|
157
|
+
fractionalIndex: generateFractionalIndex(index),
|
|
158
|
+
attrs: {
|
|
159
|
+
style: {},
|
|
160
|
+
},
|
|
161
|
+
params: {
|
|
162
|
+
'--style-padding-horizontal--unit': 'px',
|
|
163
|
+
'--style-padding-horizontal': 0,
|
|
164
|
+
'margin-top--unit': 'px',
|
|
165
|
+
},
|
|
166
|
+
selectors: {
|
|
167
|
+
'.elBulletList': {
|
|
168
|
+
attrs: {
|
|
169
|
+
'data-style-guide-content': 'm',
|
|
170
|
+
'data-skip-text-shadow-settings': 'true',
|
|
171
|
+
style: {
|
|
172
|
+
color: textColor,
|
|
173
|
+
...(textSize ? { 'font-size': textSize.value } : {}),
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
params: {
|
|
177
|
+
...(textSize ? { 'font-size--unit': textSize.unit } : {}),
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
'.elBulletList li:not(:first-child)': {
|
|
181
|
+
attrs: {
|
|
182
|
+
style: {
|
|
183
|
+
'margin-top': itemGap ? itemGap.value : 15,
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
params: {
|
|
187
|
+
'margin-top--unit': itemGap ? itemGap.unit : 'px',
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
'.elBulletList .fa_icon': {
|
|
191
|
+
attrs: {
|
|
192
|
+
style: {
|
|
193
|
+
'margin-right': iconMarginRight,
|
|
194
|
+
...(iconSize ? { 'font-size': iconSize.value } : {}),
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
params: {
|
|
198
|
+
'margin-right--unit': 'px',
|
|
199
|
+
...(iconSize ? { 'font-size--unit': iconSize.unit } : {}),
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
'.elBulletList .fa,\n.elBulletList .fas,\n.elBulletList .fa-fw': {
|
|
203
|
+
attrs: {
|
|
204
|
+
style: {
|
|
205
|
+
color: iconColor,
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
'.elBulletList li': {
|
|
210
|
+
attrs: {
|
|
211
|
+
style: {
|
|
212
|
+
'justify-content': justifyContent,
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
children: [
|
|
218
|
+
{
|
|
219
|
+
type: 'ContentEditableNode',
|
|
220
|
+
attrs: {
|
|
221
|
+
'data-align-selector': '.elBulletList li',
|
|
222
|
+
},
|
|
223
|
+
id: contentEditableId,
|
|
224
|
+
version: 0,
|
|
225
|
+
parentId: id,
|
|
226
|
+
fractionalIndex: 'a0',
|
|
227
|
+
children: [],
|
|
228
|
+
},
|
|
229
|
+
],
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// Apply spacing
|
|
233
|
+
const { attrs: spacingAttrs, params: spacingParams } = spacingToAttrsAndParams(spacing);
|
|
234
|
+
Object.assign(node.attrs.style, spacingAttrs.style);
|
|
235
|
+
Object.assign(node.params, spacingParams);
|
|
236
|
+
|
|
237
|
+
// Apply link color if present
|
|
238
|
+
if (linkColor) {
|
|
239
|
+
node.selectors['.elBulletList .elTypographyLink'] = {
|
|
240
|
+
attrs: {
|
|
241
|
+
style: {
|
|
242
|
+
color: linkColor,
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Parse list items into proper structure
|
|
249
|
+
let itemIndex = 0;
|
|
250
|
+
items.forEach((item) => {
|
|
251
|
+
const liId = generateId();
|
|
252
|
+
const iconNodeId = generateId();
|
|
253
|
+
const spanWrapperId = generateId();
|
|
254
|
+
|
|
255
|
+
const textSpan = item.querySelector('span');
|
|
256
|
+
const text = textSpan ? sanitizeHtml(textSpan.innerHTML) : item.textContent;
|
|
257
|
+
|
|
258
|
+
// Build the li children
|
|
259
|
+
const liChildren = [];
|
|
260
|
+
|
|
261
|
+
// IconNode
|
|
262
|
+
liChildren.push({
|
|
263
|
+
type: 'IconNode',
|
|
264
|
+
attrs: {
|
|
265
|
+
className: iconClass,
|
|
266
|
+
contenteditable: 'false',
|
|
267
|
+
},
|
|
268
|
+
id: iconNodeId,
|
|
269
|
+
version: 0,
|
|
270
|
+
parentId: liId,
|
|
271
|
+
fractionalIndex: 'a0',
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// Text span wrapper
|
|
275
|
+
const spanChildren = [];
|
|
276
|
+
|
|
277
|
+
// Parse the text content - handle bold tags
|
|
278
|
+
if (textSpan) {
|
|
279
|
+
const boldEl = textSpan.querySelector('b, strong');
|
|
280
|
+
if (boldEl) {
|
|
281
|
+
const boldId = generateId();
|
|
282
|
+
const boldTextId = generateId();
|
|
283
|
+
spanChildren.push({
|
|
284
|
+
type: 'b',
|
|
285
|
+
id: boldId,
|
|
286
|
+
version: 0,
|
|
287
|
+
parentId: spanWrapperId,
|
|
288
|
+
fractionalIndex: `a${spanChildren.length}`,
|
|
289
|
+
children: [
|
|
290
|
+
{
|
|
291
|
+
innerText: boldEl.textContent,
|
|
292
|
+
type: 'text',
|
|
293
|
+
id: boldTextId,
|
|
294
|
+
version: 0,
|
|
295
|
+
parentId: boldId,
|
|
296
|
+
fractionalIndex: 'a0',
|
|
297
|
+
},
|
|
298
|
+
],
|
|
299
|
+
});
|
|
300
|
+
// Get text after bold
|
|
301
|
+
const afterBold = sanitizeHtml(textSpan.innerHTML.replace(boldEl.outerHTML, '')).trim();
|
|
302
|
+
if (afterBold) {
|
|
303
|
+
spanChildren.push({
|
|
304
|
+
innerText: afterBold,
|
|
305
|
+
type: 'text',
|
|
306
|
+
id: generateId(),
|
|
307
|
+
version: 0,
|
|
308
|
+
parentId: spanWrapperId,
|
|
309
|
+
fractionalIndex: `a${spanChildren.length}`,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
} else {
|
|
313
|
+
// Plain text
|
|
314
|
+
spanChildren.push({
|
|
315
|
+
innerText: text,
|
|
316
|
+
type: 'text',
|
|
317
|
+
id: generateId(),
|
|
318
|
+
version: 0,
|
|
319
|
+
parentId: spanWrapperId,
|
|
320
|
+
fractionalIndex: 'a0',
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
} else {
|
|
324
|
+
spanChildren.push({
|
|
325
|
+
innerText: text,
|
|
326
|
+
type: 'text',
|
|
327
|
+
id: generateId(),
|
|
328
|
+
version: 0,
|
|
329
|
+
parentId: spanWrapperId,
|
|
330
|
+
fractionalIndex: 'a0',
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
liChildren.push({
|
|
335
|
+
type: 'span',
|
|
336
|
+
attrs: {
|
|
337
|
+
className: 'elBulletListTextWrapper',
|
|
338
|
+
},
|
|
339
|
+
id: spanWrapperId,
|
|
340
|
+
version: 0,
|
|
341
|
+
parentId: liId,
|
|
342
|
+
fractionalIndex: 'a1',
|
|
343
|
+
children: spanChildren,
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
node.children[0].children.push({
|
|
347
|
+
type: 'li',
|
|
348
|
+
id: liId,
|
|
349
|
+
version: 0,
|
|
350
|
+
parentId: contentEditableId,
|
|
351
|
+
fractionalIndex: `a${itemIndex}`,
|
|
352
|
+
children: liChildren,
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
itemIndex++;
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
return node;
|
|
359
|
+
}
|