@productivemark/snipcss 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/.claude-plugin/marketplace.json +17 -0
- package/.claude-plugin/plugin.json +10 -0
- package/.mcp.json +8 -0
- package/dist/auth/config-manager.d.ts +13 -0
- package/dist/auth/config-manager.d.ts.map +1 -0
- package/dist/auth/config-manager.js +48 -0
- package/dist/auth/config-manager.js.map +1 -0
- package/dist/auth/usage-gate.d.ts +13 -0
- package/dist/auth/usage-gate.d.ts.map +1 -0
- package/dist/auth/usage-gate.js +69 -0
- package/dist/auth/usage-gate.js.map +1 -0
- package/dist/browser/browser-manager.d.ts +15 -0
- package/dist/browser/browser-manager.d.ts.map +1 -0
- package/dist/browser/browser-manager.js +61 -0
- package/dist/browser/browser-manager.js.map +1 -0
- package/dist/browser/viewport-manager.d.ts +8 -0
- package/dist/browser/viewport-manager.d.ts.map +1 -0
- package/dist/browser/viewport-manager.js +50 -0
- package/dist/browser/viewport-manager.js.map +1 -0
- package/dist/extraction/css-variable-resolver.d.ts +27 -0
- package/dist/extraction/css-variable-resolver.d.ts.map +1 -0
- package/dist/extraction/css-variable-resolver.js +105 -0
- package/dist/extraction/css-variable-resolver.js.map +1 -0
- package/dist/extraction/dom-labeler.d.ts +26 -0
- package/dist/extraction/dom-labeler.d.ts.map +1 -0
- package/dist/extraction/dom-labeler.js +124 -0
- package/dist/extraction/dom-labeler.js.map +1 -0
- package/dist/extraction/element-discovery.d.ts +59 -0
- package/dist/extraction/element-discovery.d.ts.map +1 -0
- package/dist/extraction/element-discovery.js +525 -0
- package/dist/extraction/element-discovery.js.map +1 -0
- package/dist/extraction/extraction-pipeline.d.ts +26 -0
- package/dist/extraction/extraction-pipeline.d.ts.map +1 -0
- package/dist/extraction/extraction-pipeline.js +200 -0
- package/dist/extraction/extraction-pipeline.js.map +1 -0
- package/dist/extraction/font-collector.d.ts +26 -0
- package/dist/extraction/font-collector.d.ts.map +1 -0
- package/dist/extraction/font-collector.js +160 -0
- package/dist/extraction/font-collector.js.map +1 -0
- package/dist/extraction/html-cleaner.d.ts +16 -0
- package/dist/extraction/html-cleaner.d.ts.map +1 -0
- package/dist/extraction/html-cleaner.js +149 -0
- package/dist/extraction/html-cleaner.js.map +1 -0
- package/dist/extraction/keyframe-collector.d.ts +16 -0
- package/dist/extraction/keyframe-collector.d.ts.map +1 -0
- package/dist/extraction/keyframe-collector.js +62 -0
- package/dist/extraction/keyframe-collector.js.map +1 -0
- package/dist/extraction/pseudo-state-handler.d.ts +36 -0
- package/dist/extraction/pseudo-state-handler.d.ts.map +1 -0
- package/dist/extraction/pseudo-state-handler.js +210 -0
- package/dist/extraction/pseudo-state-handler.js.map +1 -0
- package/dist/extraction/result-builder.d.ts +25 -0
- package/dist/extraction/result-builder.d.ts.map +1 -0
- package/dist/extraction/result-builder.js +136 -0
- package/dist/extraction/result-builder.js.map +1 -0
- package/dist/extraction/rule-deduplicator.d.ts +39 -0
- package/dist/extraction/rule-deduplicator.d.ts.map +1 -0
- package/dist/extraction/rule-deduplicator.js +107 -0
- package/dist/extraction/rule-deduplicator.js.map +1 -0
- package/dist/extraction/selector-fixer.d.ts +25 -0
- package/dist/extraction/selector-fixer.d.ts.map +1 -0
- package/dist/extraction/selector-fixer.js +111 -0
- package/dist/extraction/selector-fixer.js.map +1 -0
- package/dist/extraction/specificity.d.ts +17 -0
- package/dist/extraction/specificity.d.ts.map +1 -0
- package/dist/extraction/specificity.js +88 -0
- package/dist/extraction/specificity.js.map +1 -0
- package/dist/extraction/style-matcher.d.ts +33 -0
- package/dist/extraction/style-matcher.d.ts.map +1 -0
- package/dist/extraction/style-matcher.js +199 -0
- package/dist/extraction/style-matcher.js.map +1 -0
- package/dist/extraction/stylesheet-collector.d.ts +33 -0
- package/dist/extraction/stylesheet-collector.d.ts.map +1 -0
- package/dist/extraction/stylesheet-collector.js +71 -0
- package/dist/extraction/stylesheet-collector.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +235 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-server.d.ts +3 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +349 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/tailwind/css-to-tailwind.d.ts +17 -0
- package/dist/tailwind/css-to-tailwind.d.ts.map +1 -0
- package/dist/tailwind/css-to-tailwind.js +1583 -0
- package/dist/tailwind/css-to-tailwind.js.map +1 -0
- package/dist/tailwind/shorthand-expander.d.ts +27 -0
- package/dist/tailwind/shorthand-expander.d.ts.map +1 -0
- package/dist/tailwind/shorthand-expander.js +812 -0
- package/dist/tailwind/shorthand-expander.js.map +1 -0
- package/dist/tailwind/tailwind-converter.d.ts +35 -0
- package/dist/tailwind/tailwind-converter.d.ts.map +1 -0
- package/dist/tailwind/tailwind-converter.js +1223 -0
- package/dist/tailwind/tailwind-converter.js.map +1 -0
- package/dist/tailwind/tailwind-helpers.d.ts +95 -0
- package/dist/tailwind/tailwind-helpers.d.ts.map +1 -0
- package/dist/tailwind/tailwind-helpers.js +593 -0
- package/dist/tailwind/tailwind-helpers.js.map +1 -0
- package/dist/tailwind/tailwind-reducer.d.ts +36 -0
- package/dist/tailwind/tailwind-reducer.d.ts.map +1 -0
- package/dist/tailwind/tailwind-reducer.js +189 -0
- package/dist/tailwind/tailwind-reducer.js.map +1 -0
- package/dist/types/index.d.ts +239 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +94 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/helpers.d.ts +34 -0
- package/dist/utils/helpers.d.ts.map +1 -0
- package/dist/utils/helpers.js +120 -0
- package/dist/utils/helpers.js.map +1 -0
- package/dist/utils/parsel.d.ts +41 -0
- package/dist/utils/parsel.d.ts.map +1 -0
- package/dist/utils/parsel.js +314 -0
- package/dist/utils/parsel.js.map +1 -0
- package/package.json +41 -0
- package/skills/workflow/SKILL.md +95 -0
|
@@ -0,0 +1,1223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tailwind conversion orchestrator.
|
|
3
|
+
* Port of tailwind_main.js (1,681 lines)
|
|
4
|
+
*
|
|
5
|
+
* Main entry points:
|
|
6
|
+
* - getTailwindHtml() converts labelled HTML + CSS into Tailwind-classed HTML
|
|
7
|
+
* - getTailwindBodyClasses() extracts body-level Tailwind classes from snipped rules
|
|
8
|
+
*/
|
|
9
|
+
import * as cheerio from 'cheerio';
|
|
10
|
+
import { cssToTailwind, getTransformClasses, getFilterClasses, getFontClasses, getBestTailwindClasses, } from './css-to-tailwind.js';
|
|
11
|
+
import { getPropertyType, expandShorthandProperty, } from './shorthand-expander.js';
|
|
12
|
+
import { mergeRanges, getMediaPrefix, parseMediaQueryToTailwind, resolveCssVariableValue, getPseudos, parseStyleAttribute, } from './tailwind-helpers.js';
|
|
13
|
+
import { reduceTailwindClasses, updatePropSpecifityWithMergedProperties, } from './tailwind-reducer.js';
|
|
14
|
+
// ============================================================
|
|
15
|
+
// Cheerio options (matches the original extension's CHEERIO_OPTIONS)
|
|
16
|
+
// ============================================================
|
|
17
|
+
/**
|
|
18
|
+
* Cheerio load options.
|
|
19
|
+
* The original extension used htmlparser2-specific keys (decodeEntities,
|
|
20
|
+
* normalizeWhitespace, recognizeSelfClosing, _useHtmlParser2).
|
|
21
|
+
* In cheerio 1.x these are passed through the `xml` key.
|
|
22
|
+
*/
|
|
23
|
+
const CHEERIO_OPTIONS = {
|
|
24
|
+
xml: {
|
|
25
|
+
xmlMode: false,
|
|
26
|
+
decodeEntities: false,
|
|
27
|
+
normalizeWhitespace: false,
|
|
28
|
+
recognizeSelfClosing: true,
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
// ============================================================
|
|
32
|
+
// Icon Font Class Preservation for Tailwind Conversion
|
|
33
|
+
// ============================================================
|
|
34
|
+
/** Known icon font class name patterns (fast regex check) */
|
|
35
|
+
const ICON_CLASS_PATTERNS = [
|
|
36
|
+
/^fa[srldb]?$/, // Font Awesome base classes (fa, fas, far, fal, fad, fab)
|
|
37
|
+
/^fa-/, // Font Awesome icons (fa-chevron-right, etc.)
|
|
38
|
+
/^ti$/, // Tabler Icons base
|
|
39
|
+
/^ti-/, // Tabler Icons
|
|
40
|
+
/^bi$/, // Bootstrap Icons base
|
|
41
|
+
/^bi-/, // Bootstrap Icons
|
|
42
|
+
/^material-icons/, // Material Icons
|
|
43
|
+
/^glyphicon/, // Glyphicons
|
|
44
|
+
/^icon-/, // Generic icon prefix
|
|
45
|
+
/^icofont-/, // IcoFont
|
|
46
|
+
/^ri-/, // Remix Icons
|
|
47
|
+
/^bx-?/, // BoxIcons
|
|
48
|
+
/^la-?/, // Line Awesome
|
|
49
|
+
];
|
|
50
|
+
/** Known icon font-family names (for CSS-based detection fallback) */
|
|
51
|
+
const ICON_FONT_FAMILIES = [
|
|
52
|
+
'font awesome',
|
|
53
|
+
'fontawesome',
|
|
54
|
+
'tabler',
|
|
55
|
+
'bootstrap-icons',
|
|
56
|
+
'material icons',
|
|
57
|
+
'glyphicons',
|
|
58
|
+
'icomoon',
|
|
59
|
+
'icofont',
|
|
60
|
+
'remixicon',
|
|
61
|
+
'boxicons',
|
|
62
|
+
'line awesome',
|
|
63
|
+
];
|
|
64
|
+
// ============================================================
|
|
65
|
+
// Tailwind spacing scale for arbitrary-value replacement
|
|
66
|
+
// ============================================================
|
|
67
|
+
const TAILWIND_SPACING_SCALE = {
|
|
68
|
+
0: '0',
|
|
69
|
+
2: '0.5',
|
|
70
|
+
4: '1',
|
|
71
|
+
6: '1.5',
|
|
72
|
+
8: '2',
|
|
73
|
+
10: '2.5',
|
|
74
|
+
12: '3',
|
|
75
|
+
14: '3.5',
|
|
76
|
+
16: '4',
|
|
77
|
+
20: '5',
|
|
78
|
+
24: '6',
|
|
79
|
+
28: '7',
|
|
80
|
+
32: '8',
|
|
81
|
+
36: '9',
|
|
82
|
+
40: '10',
|
|
83
|
+
44: '11',
|
|
84
|
+
48: '12',
|
|
85
|
+
56: '14',
|
|
86
|
+
64: '16',
|
|
87
|
+
80: '20',
|
|
88
|
+
96: '24',
|
|
89
|
+
112: '28',
|
|
90
|
+
128: '32',
|
|
91
|
+
144: '36',
|
|
92
|
+
160: '40',
|
|
93
|
+
176: '44',
|
|
94
|
+
192: '48',
|
|
95
|
+
208: '52',
|
|
96
|
+
224: '56',
|
|
97
|
+
240: '60',
|
|
98
|
+
256: '64',
|
|
99
|
+
288: '72',
|
|
100
|
+
320: '80',
|
|
101
|
+
384: '96',
|
|
102
|
+
};
|
|
103
|
+
const SPACING_PIXEL_VALUES = Object.keys(TAILWIND_SPACING_SCALE).map(Number);
|
|
104
|
+
// ============================================================
|
|
105
|
+
// Utility helpers
|
|
106
|
+
// ============================================================
|
|
107
|
+
/** Check if a class name matches known icon font patterns */
|
|
108
|
+
function isIconClassByName(className) {
|
|
109
|
+
return ICON_CLASS_PATTERNS.some(pattern => pattern.test(className));
|
|
110
|
+
}
|
|
111
|
+
/** Check if any CSS rule for this class sets an icon font-family */
|
|
112
|
+
function isIconClassByCSS(className, tSnippedArr) {
|
|
113
|
+
if (!tSnippedArr)
|
|
114
|
+
return false;
|
|
115
|
+
for (const rule of tSnippedArr) {
|
|
116
|
+
if (!rule.selector || !rule.selector.includes('.' + className))
|
|
117
|
+
continue;
|
|
118
|
+
if (!rule.body)
|
|
119
|
+
continue;
|
|
120
|
+
const fontMatch = rule.body.match(/font-family\s*:\s*([^;]+)/i);
|
|
121
|
+
if (fontMatch) {
|
|
122
|
+
const fontValue = fontMatch[1].toLowerCase();
|
|
123
|
+
if (ICON_FONT_FAMILIES.some(font => fontValue.includes(font))) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
/** Get all icon classes from an element's class attribute */
|
|
131
|
+
function getIconClasses(classAttr, tSnippedArr) {
|
|
132
|
+
if (!classAttr)
|
|
133
|
+
return [];
|
|
134
|
+
const classes = classAttr.split(/\s+/).filter(c => c.trim());
|
|
135
|
+
return classes.filter(cls => !cls.startsWith('snipcss') &&
|
|
136
|
+
(isIconClassByName(cls) || isIconClassByCSS(cls, tSnippedArr)));
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Split a string by commas, respecting balanced parentheses.
|
|
140
|
+
* Port of splitNoParen() from snipbackground.js.
|
|
141
|
+
*/
|
|
142
|
+
function splitNoParen(s) {
|
|
143
|
+
const results = [];
|
|
144
|
+
let str = '';
|
|
145
|
+
let left = 0;
|
|
146
|
+
let right = 0;
|
|
147
|
+
for (let i = 0; i < s.length; i++) {
|
|
148
|
+
switch (s[i]) {
|
|
149
|
+
case ',':
|
|
150
|
+
if (left === right) {
|
|
151
|
+
results.push(str);
|
|
152
|
+
str = '';
|
|
153
|
+
left = right = 0;
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
str += s[i];
|
|
157
|
+
}
|
|
158
|
+
break;
|
|
159
|
+
case '(':
|
|
160
|
+
left++;
|
|
161
|
+
str += s[i];
|
|
162
|
+
break;
|
|
163
|
+
case ')':
|
|
164
|
+
right++;
|
|
165
|
+
str += s[i];
|
|
166
|
+
break;
|
|
167
|
+
default:
|
|
168
|
+
str += s[i];
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
results.push(str);
|
|
172
|
+
return results;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Get override classes for special shorthand properties
|
|
176
|
+
* (margin, padding, transform, filter, backdrop-filter, font).
|
|
177
|
+
*/
|
|
178
|
+
function getOverrideClasses(oProp, oVal) {
|
|
179
|
+
let overrideClasses = null;
|
|
180
|
+
const hasImportant = oVal.includes('!important');
|
|
181
|
+
const cleanValue = oVal.replace(/\s*!important\s*/g, '').trim();
|
|
182
|
+
if (oProp === 'margin' || oProp === 'padding') {
|
|
183
|
+
const expanded = expandShorthandProperty(oProp, cleanValue);
|
|
184
|
+
if (expanded) {
|
|
185
|
+
const iOverwritten = expanded['overwritten_properties'];
|
|
186
|
+
overrideClasses = getBestTailwindClasses(oProp, iOverwritten);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
else if (oProp === 'transform') {
|
|
190
|
+
overrideClasses = getTransformClasses(cleanValue);
|
|
191
|
+
}
|
|
192
|
+
else if (oProp === 'filter') {
|
|
193
|
+
overrideClasses = getFilterClasses(cleanValue);
|
|
194
|
+
}
|
|
195
|
+
else if (oProp === 'backdrop-filter') {
|
|
196
|
+
overrideClasses = getFilterClasses(cleanValue, true);
|
|
197
|
+
}
|
|
198
|
+
else if (oProp === 'font') {
|
|
199
|
+
overrideClasses = getFontClasses(cleanValue);
|
|
200
|
+
}
|
|
201
|
+
// Add '!' prefix to each class if !important was present
|
|
202
|
+
if (hasImportant && Array.isArray(overrideClasses)) {
|
|
203
|
+
overrideClasses = overrideClasses.map(className => `!${className}`);
|
|
204
|
+
}
|
|
205
|
+
return overrideClasses;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Gets the ancestry chain of an element for CSS variable resolution.
|
|
209
|
+
* Parses snip class names to build ancestry (snip classes encode hierarchy).
|
|
210
|
+
*
|
|
211
|
+
* Format: snipcss{device}-{level}-{parentId}-{currId}
|
|
212
|
+
*/
|
|
213
|
+
export function getElementAncestryChain(elemClass, allClassnamesArr) {
|
|
214
|
+
const chain = [];
|
|
215
|
+
let current = elemClass;
|
|
216
|
+
while (current) {
|
|
217
|
+
chain.unshift(current); // parent first
|
|
218
|
+
const parts = current.split('-');
|
|
219
|
+
if (parts.length < 4)
|
|
220
|
+
break;
|
|
221
|
+
const device = parts[0]; // e.g. 'snipcss0'
|
|
222
|
+
const level = parseInt(parts[1], 10);
|
|
223
|
+
const parentId = parseInt(parts[2], 10);
|
|
224
|
+
if (level === 0 || parentId === 0)
|
|
225
|
+
break;
|
|
226
|
+
// Find parent: same device, level-1, currId === our parentId
|
|
227
|
+
const parentClass = allClassnamesArr.find((c) => {
|
|
228
|
+
const pParts = c.split('-');
|
|
229
|
+
if (pParts.length >= 4) {
|
|
230
|
+
return (pParts[0] === device &&
|
|
231
|
+
parseInt(pParts[1], 10) === level - 1 &&
|
|
232
|
+
parseInt(pParts[3], 10) === parentId);
|
|
233
|
+
}
|
|
234
|
+
return false;
|
|
235
|
+
});
|
|
236
|
+
current = parentClass;
|
|
237
|
+
}
|
|
238
|
+
return chain;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Calculate a rough CSS specificity score for a single selector part.
|
|
242
|
+
* This is a simplified fallback; the original uses parsel with a calculateSingle
|
|
243
|
+
* backup.
|
|
244
|
+
*/
|
|
245
|
+
function calculateSpecificity(selectorPart) {
|
|
246
|
+
let a = 0; // ID selectors
|
|
247
|
+
let b = 0; // class selectors, attribute selectors, pseudo-classes
|
|
248
|
+
let c = 0; // type selectors, pseudo-elements
|
|
249
|
+
// Remove :not() content but count its internals
|
|
250
|
+
const withoutNot = selectorPart.replace(/:not\(([^)]*)\)/g, (_m, inner) => {
|
|
251
|
+
// Count specificity of :not() argument
|
|
252
|
+
const innerScore = calculateSpecificity(inner);
|
|
253
|
+
a += Math.floor(innerScore / 100);
|
|
254
|
+
b += Math.floor((innerScore % 100) / 10);
|
|
255
|
+
c += innerScore % 10;
|
|
256
|
+
return '';
|
|
257
|
+
});
|
|
258
|
+
// IDs
|
|
259
|
+
const ids = withoutNot.match(/#[a-zA-Z_][\w-]*/g);
|
|
260
|
+
if (ids)
|
|
261
|
+
a += ids.length;
|
|
262
|
+
// Classes, attribute selectors, pseudo-classes
|
|
263
|
+
const classes = withoutNot.match(/\.[a-zA-Z_][\w-]*/g);
|
|
264
|
+
if (classes)
|
|
265
|
+
b += classes.length;
|
|
266
|
+
const attrs = withoutNot.match(/\[[^\]]+\]/g);
|
|
267
|
+
if (attrs)
|
|
268
|
+
b += attrs.length;
|
|
269
|
+
// Pseudo-classes (single colon, not pseudo-elements with double colon)
|
|
270
|
+
const pseudoClasses = withoutNot.match(/(?<!:):[a-zA-Z][\w-]*/g);
|
|
271
|
+
if (pseudoClasses)
|
|
272
|
+
b += pseudoClasses.length;
|
|
273
|
+
// Pseudo-elements (double colon)
|
|
274
|
+
const pseudoElements = withoutNot.match(/::[a-zA-Z][\w-]*/g);
|
|
275
|
+
if (pseudoElements)
|
|
276
|
+
c += pseudoElements.length;
|
|
277
|
+
// Type selectors (element names) - simplified
|
|
278
|
+
const typeSelectors = withoutNot.replace(/#[a-zA-Z_][\w-]*/g, '')
|
|
279
|
+
.replace(/\.[a-zA-Z_][\w-]*/g, '')
|
|
280
|
+
.replace(/\[[^\]]+\]/g, '')
|
|
281
|
+
.replace(/::?[a-zA-Z][\w-]*/g, '')
|
|
282
|
+
.replace(/[>+~*\s]/g, ' ')
|
|
283
|
+
.trim()
|
|
284
|
+
.split(/\s+/)
|
|
285
|
+
.filter(t => t && /^[a-zA-Z]/.test(t));
|
|
286
|
+
c += typeSelectors.length;
|
|
287
|
+
return a * 100 + b * 10 + c;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Parse a var() function call with proper parenthesis matching.
|
|
291
|
+
* (Local copy used by resolveAllVariables)
|
|
292
|
+
*/
|
|
293
|
+
function parseVarFunction(str, startIndex) {
|
|
294
|
+
if (!str.substring(startIndex).startsWith('var('))
|
|
295
|
+
return null;
|
|
296
|
+
let i = startIndex + 4;
|
|
297
|
+
let parenCount = 1;
|
|
298
|
+
let varName = '';
|
|
299
|
+
let defaultValue = null;
|
|
300
|
+
let inVarName = true;
|
|
301
|
+
let buffer = '';
|
|
302
|
+
while (i < str.length && parenCount > 0) {
|
|
303
|
+
const char = str[i];
|
|
304
|
+
if (char === '(') {
|
|
305
|
+
parenCount++;
|
|
306
|
+
buffer += char;
|
|
307
|
+
}
|
|
308
|
+
else if (char === ')') {
|
|
309
|
+
parenCount--;
|
|
310
|
+
if (parenCount > 0)
|
|
311
|
+
buffer += char;
|
|
312
|
+
}
|
|
313
|
+
else if (char === ',' && parenCount === 1 && inVarName) {
|
|
314
|
+
varName = buffer.trim();
|
|
315
|
+
buffer = '';
|
|
316
|
+
inVarName = false;
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
buffer += char;
|
|
320
|
+
}
|
|
321
|
+
i++;
|
|
322
|
+
}
|
|
323
|
+
if (parenCount === 0) {
|
|
324
|
+
if (inVarName)
|
|
325
|
+
varName = buffer.trim();
|
|
326
|
+
else
|
|
327
|
+
defaultValue = buffer.trim();
|
|
328
|
+
if (!varName.startsWith('--'))
|
|
329
|
+
return null;
|
|
330
|
+
return {
|
|
331
|
+
varName,
|
|
332
|
+
defaultValue,
|
|
333
|
+
endIndex: i,
|
|
334
|
+
fullMatch: str.substring(startIndex, i),
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Resolves all CSS var() references in a value string, using context variables.
|
|
341
|
+
*/
|
|
342
|
+
function resolveAllVariables(value, elementLabels, cssvarDefinedArr, maxDepth = 10, currentDepth = 0) {
|
|
343
|
+
if (currentDepth >= maxDepth) {
|
|
344
|
+
return value;
|
|
345
|
+
}
|
|
346
|
+
let resolvedValue = value;
|
|
347
|
+
const cssVarMap = {};
|
|
348
|
+
let i = 0;
|
|
349
|
+
const replacements = [];
|
|
350
|
+
while (i < resolvedValue.length) {
|
|
351
|
+
const varIndex = resolvedValue.indexOf('var(', i);
|
|
352
|
+
if (varIndex === -1)
|
|
353
|
+
break;
|
|
354
|
+
const parsed = parseVarFunction(resolvedValue, varIndex);
|
|
355
|
+
if (!parsed) {
|
|
356
|
+
i = varIndex + 4;
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
const { varName, defaultValue, endIndex } = parsed;
|
|
360
|
+
const resolvedRanges = resolveCssVariableValue(varName, elementLabels, cssvarDefinedArr, cssVarMap);
|
|
361
|
+
let concreteValue;
|
|
362
|
+
if (resolvedRanges && resolvedRanges.length > 0) {
|
|
363
|
+
concreteValue = resolvedRanges[0].concrete_value;
|
|
364
|
+
}
|
|
365
|
+
else if (defaultValue !== null) {
|
|
366
|
+
concreteValue = defaultValue;
|
|
367
|
+
if (concreteValue.includes('var(')) {
|
|
368
|
+
concreteValue = resolveAllVariables(concreteValue, elementLabels, cssvarDefinedArr, maxDepth, currentDepth + 1);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
// Can't resolve this variable — preserve the original var() reference
|
|
373
|
+
// instead of producing empty values (e.g., rgba(,_1) from unresolved Bootstrap vars)
|
|
374
|
+
concreteValue = `var(${varName})`;
|
|
375
|
+
}
|
|
376
|
+
// Legacy linear-gradient spacing hack
|
|
377
|
+
if (value.indexOf('linear-gradient') >= 0 && concreteValue) {
|
|
378
|
+
concreteValue = concreteValue + ' ';
|
|
379
|
+
}
|
|
380
|
+
replacements.push({ start: varIndex, end: endIndex, replacement: concreteValue });
|
|
381
|
+
i = endIndex;
|
|
382
|
+
}
|
|
383
|
+
// Apply replacements in reverse order to keep indices valid
|
|
384
|
+
for (let j = replacements.length - 1; j >= 0; j--) {
|
|
385
|
+
const { start, end, replacement } = replacements[j];
|
|
386
|
+
resolvedValue = resolvedValue.substring(0, start) + replacement + resolvedValue.substring(end);
|
|
387
|
+
}
|
|
388
|
+
// Recursively resolve any newly introduced var() references
|
|
389
|
+
if (resolvedValue.includes('var(') && currentDepth < maxDepth - 1) {
|
|
390
|
+
resolvedValue = resolveAllVariables(resolvedValue, elementLabels, cssvarDefinedArr, maxDepth, currentDepth + 1);
|
|
391
|
+
}
|
|
392
|
+
return resolvedValue;
|
|
393
|
+
}
|
|
394
|
+
// ============================================================
|
|
395
|
+
// Arbitrary-value post-processing
|
|
396
|
+
// ============================================================
|
|
397
|
+
/** Convert an arbitrary CSS value to a pixel amount (or null if not convertible). */
|
|
398
|
+
function getArbitraryPxValue(value) {
|
|
399
|
+
const v = value.trim().toLowerCase();
|
|
400
|
+
const numValue = parseFloat(v);
|
|
401
|
+
if (isNaN(numValue))
|
|
402
|
+
return null;
|
|
403
|
+
if (v.endsWith('rem'))
|
|
404
|
+
return numValue * 16;
|
|
405
|
+
if (v.endsWith('px'))
|
|
406
|
+
return numValue;
|
|
407
|
+
if (v.endsWith('em'))
|
|
408
|
+
return numValue * 16;
|
|
409
|
+
if (v.endsWith('%') || v.endsWith('vh') || v.endsWith('vw'))
|
|
410
|
+
return null;
|
|
411
|
+
return numValue; // assume px
|
|
412
|
+
}
|
|
413
|
+
/** Find the closest Tailwind spacing token for a pixel value (within 0.5px). */
|
|
414
|
+
function getClosestTailwindSpacingClass(pxValue) {
|
|
415
|
+
let closestValue = null;
|
|
416
|
+
let smallestDifference = Infinity;
|
|
417
|
+
for (const val of SPACING_PIXEL_VALUES) {
|
|
418
|
+
const diff = Math.abs(pxValue - val);
|
|
419
|
+
if (diff < smallestDifference) {
|
|
420
|
+
smallestDifference = diff;
|
|
421
|
+
closestValue = val;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
if (closestValue !== null && smallestDifference <= 0.5) {
|
|
425
|
+
return TAILWIND_SPACING_SCALE[closestValue];
|
|
426
|
+
}
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
/** Replace arbitrary-value Tailwind classes (e.g. `px-[2rem]`) with standard tokens. */
|
|
430
|
+
function replaceArbitraryValueWithTailwindClass(tailClass) {
|
|
431
|
+
const classParts = tailClass.split(':');
|
|
432
|
+
const baseClass = classParts.pop();
|
|
433
|
+
const prefixesToCheck = [
|
|
434
|
+
'm-', 'mt-', 'mb-', 'ml-', 'mr-', 'mx-', 'my-', 'ms-', 'me-',
|
|
435
|
+
'p-', 'pt-', 'pb-', 'pl-', 'pr-', 'px-', 'py-', 'ps-', 'pe-',
|
|
436
|
+
'space-x-', 'space-y-',
|
|
437
|
+
'gap-', 'gap-x-', 'gap-y-',
|
|
438
|
+
'inset-', 'top-', 'right-', 'bottom-', 'left-', 'start-', 'end-',
|
|
439
|
+
'w-', 'h-',
|
|
440
|
+
];
|
|
441
|
+
const arbitraryValueRegex = new RegExp(`^(${prefixesToCheck.join('|').replace(/-/g, '\\-')})\\[(.+)\\]$`);
|
|
442
|
+
const match = baseClass.match(arbitraryValueRegex);
|
|
443
|
+
if (match) {
|
|
444
|
+
const fullPrefix = match[1];
|
|
445
|
+
const value = match[2];
|
|
446
|
+
const pxValue = getArbitraryPxValue(value);
|
|
447
|
+
if (pxValue !== null) {
|
|
448
|
+
const closestTailwindClass = getClosestTailwindSpacingClass(pxValue);
|
|
449
|
+
if (closestTailwindClass) {
|
|
450
|
+
const newBaseClass = fullPrefix + closestTailwindClass;
|
|
451
|
+
return [...classParts, newBaseClass].join(':');
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return tailClass;
|
|
456
|
+
}
|
|
457
|
+
// ============================================================
|
|
458
|
+
// Selectors to skip during conversion
|
|
459
|
+
// ============================================================
|
|
460
|
+
const SKIP_SELECTORS = [
|
|
461
|
+
'*', 'body', 'html', ':root',
|
|
462
|
+
'*,:before,:after', ':backdrop', ':-webkit-scrollbar',
|
|
463
|
+
':before', ':after', ':selection', '::selection',
|
|
464
|
+
':after,:before', ':-webkit-scrollbar', ':-webkit-scrollbar-thumb',
|
|
465
|
+
':-webkit-scrollbar-track', ':-webkit-scrollbar:hover', ':-webkit-scrollbar-track:hover',
|
|
466
|
+
'::-webkit-scrollbar-thumb:hover', ':-webkit-scrollbar-thumb:hover',
|
|
467
|
+
'::placeholder', '::marker', '::spelling-error', '::grammar-error',
|
|
468
|
+
'::-webkit-file-upload-button', '::-webkit-inner-spin-button',
|
|
469
|
+
'::-webkit-outer-spin-button', '::-webkit-resizer',
|
|
470
|
+
'::-webkit-calendar-picker-indicator', '::-webkit-details-marker',
|
|
471
|
+
'::-webkit-scrollbar-corner', '::cue', ':-webkit-autofill', ':-webkit-full-screen',
|
|
472
|
+
];
|
|
473
|
+
// ============================================================
|
|
474
|
+
// Helper: process pseudo-elements for a property (shared logic)
|
|
475
|
+
// ============================================================
|
|
476
|
+
/**
|
|
477
|
+
* Builds propKey and pseudoPrefix from allPseudo, then merges
|
|
478
|
+
* tailwind ranges into propSpecifityWithMediaVals.
|
|
479
|
+
*
|
|
480
|
+
* This is a helper that encapsulates the repeated pseudo-handling
|
|
481
|
+
* pattern used for both shorthand expanded properties and regular
|
|
482
|
+
* properties.
|
|
483
|
+
*/
|
|
484
|
+
function processPseudoRanges(allPseudo, basePropKey, currMediaRanges, modifiedScore, ruleIndex, tailClassFn, overrideClassesFn, propSpecifityWithMediaVals) {
|
|
485
|
+
const hasBefore = allPseudo.includes('before');
|
|
486
|
+
const hasAfter = allPseudo.includes('after');
|
|
487
|
+
const pseudoElementsToProcess = [];
|
|
488
|
+
const otherPseudos = [];
|
|
489
|
+
for (const p of allPseudo) {
|
|
490
|
+
if (p === 'before' || p === 'after') {
|
|
491
|
+
pseudoElementsToProcess.push(p);
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
otherPseudos.push(p);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
if (hasBefore && hasAfter) {
|
|
498
|
+
// Process each pseudo-element separately
|
|
499
|
+
for (const pseudoEl of pseudoElementsToProcess) {
|
|
500
|
+
let propKey = basePropKey;
|
|
501
|
+
let pseudoPrefix = '';
|
|
502
|
+
for (const op of otherPseudos) {
|
|
503
|
+
propKey = op + ':' + propKey;
|
|
504
|
+
pseudoPrefix += op + ':';
|
|
505
|
+
}
|
|
506
|
+
propKey = pseudoEl + ':' + propKey;
|
|
507
|
+
pseudoPrefix += pseudoEl + ':';
|
|
508
|
+
if (!(propKey in propSpecifityWithMediaVals)) {
|
|
509
|
+
propSpecifityWithMediaVals[propKey] = [];
|
|
510
|
+
}
|
|
511
|
+
for (let c = 0; c < currMediaRanges.length; c++) {
|
|
512
|
+
const currMediaRange = JSON.parse(JSON.stringify(currMediaRanges[c]));
|
|
513
|
+
currMediaRange['prop'] = propKey;
|
|
514
|
+
currMediaRange['score'] = modifiedScore;
|
|
515
|
+
currMediaRange['ruleIndex'] = ruleIndex;
|
|
516
|
+
if (overrideClassesFn === null) {
|
|
517
|
+
const tailClass = tailClassFn(pseudoPrefix);
|
|
518
|
+
if (!tailClass)
|
|
519
|
+
continue;
|
|
520
|
+
currMediaRange['tailwind_classes'] = [tailClass];
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
currMediaRange['tailwind_classes'] = [];
|
|
524
|
+
const oc = overrideClassesFn(pseudoPrefix);
|
|
525
|
+
for (const tc of oc) {
|
|
526
|
+
currMediaRange['tailwind_classes'].push(tc);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
propSpecifityWithMediaVals[propKey] = mergeRanges(propSpecifityWithMediaVals[propKey], currMediaRange);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
else {
|
|
534
|
+
// Original chaining behavior
|
|
535
|
+
let propKey = basePropKey;
|
|
536
|
+
let pseudoPrefix = '';
|
|
537
|
+
for (const pe of allPseudo) {
|
|
538
|
+
propKey = pe + ':' + propKey;
|
|
539
|
+
pseudoPrefix += pe + ':';
|
|
540
|
+
}
|
|
541
|
+
if (!(propKey in propSpecifityWithMediaVals)) {
|
|
542
|
+
propSpecifityWithMediaVals[propKey] = [];
|
|
543
|
+
}
|
|
544
|
+
for (let c = 0; c < currMediaRanges.length; c++) {
|
|
545
|
+
const currMediaRange = currMediaRanges[c];
|
|
546
|
+
currMediaRange['prop'] = propKey;
|
|
547
|
+
currMediaRange['score'] = modifiedScore;
|
|
548
|
+
currMediaRange['ruleIndex'] = ruleIndex;
|
|
549
|
+
if (overrideClassesFn === null) {
|
|
550
|
+
const tailClass = tailClassFn(pseudoPrefix);
|
|
551
|
+
if (!tailClass)
|
|
552
|
+
continue;
|
|
553
|
+
currMediaRange['tailwind_classes'] = [
|
|
554
|
+
...(currMediaRange['tailwind_classes'] || []),
|
|
555
|
+
tailClass,
|
|
556
|
+
];
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
const oc = overrideClassesFn(pseudoPrefix);
|
|
560
|
+
for (const tc of oc) {
|
|
561
|
+
currMediaRange['tailwind_classes'] = [
|
|
562
|
+
...(currMediaRange['tailwind_classes'] || []),
|
|
563
|
+
tc,
|
|
564
|
+
];
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
propSpecifityWithMediaVals[propKey] = mergeRanges(propSpecifityWithMediaVals[propKey], currMediaRange);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
// ============================================================
|
|
572
|
+
// Main exports
|
|
573
|
+
// ============================================================
|
|
574
|
+
/**
|
|
575
|
+
* Convert labelled HTML and CSS into Tailwind-classed HTML.
|
|
576
|
+
*
|
|
577
|
+
* For every element identified by a snipcss class, the function:
|
|
578
|
+
* 1. Processes inline style attributes
|
|
579
|
+
* 2. Iterates over matching CSS rules (from ctx.matchingFinalRules)
|
|
580
|
+
* 3. Expands shorthands and resolves variables
|
|
581
|
+
* 4. Converts each property to Tailwind classes
|
|
582
|
+
* 5. Handles pseudo-classes/elements and media queries
|
|
583
|
+
* 6. Merges, reduces and applies the resulting classes via cheerio
|
|
584
|
+
*/
|
|
585
|
+
export function getTailwindHtml(labelHtml, allCss, tSnippedArr, forceBreakpoints, resolveVariables, ctx) {
|
|
586
|
+
const $editHtml = cheerio.load(labelHtml, CHEERIO_OPTIONS, false);
|
|
587
|
+
for (let x = 0; x < ctx.allClassnamesArr.length; x++) {
|
|
588
|
+
try {
|
|
589
|
+
const currClass = ctx.allClassnamesArr[x];
|
|
590
|
+
const theElem = $editHtml.root().find('.' + currClass).get(0);
|
|
591
|
+
if (!theElem)
|
|
592
|
+
continue;
|
|
593
|
+
// Build ancestry chain for CSS variable resolution
|
|
594
|
+
const ancestryChain = getElementAncestryChain(currClass, ctx.allClassnamesArr);
|
|
595
|
+
const propSpecifityWithMediaVals = {};
|
|
596
|
+
// -----------------------------------------------------------
|
|
597
|
+
// 1) Process style attribute declarations
|
|
598
|
+
// -----------------------------------------------------------
|
|
599
|
+
let theStyleAttr = $editHtml(theElem).attr('style');
|
|
600
|
+
if (theStyleAttr) {
|
|
601
|
+
// Decode HTML entities in style values (Cheerio encodes " as " in inline styles)
|
|
602
|
+
theStyleAttr = theStyleAttr
|
|
603
|
+
.replace(/"/g, '"')
|
|
604
|
+
.replace(/&/g, '&')
|
|
605
|
+
.replace(/</g, '<')
|
|
606
|
+
.replace(/>/g, '>')
|
|
607
|
+
.replace(/'/g, "'");
|
|
608
|
+
const declarations = parseStyleAttribute(theStyleAttr);
|
|
609
|
+
// First pass: register CSS variable definitions from inline styles
|
|
610
|
+
declarations.forEach(({ property: inProp, value: inVal }) => {
|
|
611
|
+
if (resolveVariables && inProp.startsWith('--')) {
|
|
612
|
+
const mediaSelector = '';
|
|
613
|
+
const selText = '.' + currClass;
|
|
614
|
+
if (!ctx.cssvarDefinedArr.hasOwnProperty(inProp)) {
|
|
615
|
+
ctx.cssvarDefinedArr[inProp] = [];
|
|
616
|
+
}
|
|
617
|
+
const newKey = mediaSelector + selText + inProp;
|
|
618
|
+
const exists = ctx.cssvarDefinedArr[inProp].some(item => item.key === newKey);
|
|
619
|
+
if (!exists) {
|
|
620
|
+
ctx.cssvarDefinedArr[inProp].push({
|
|
621
|
+
key: newKey,
|
|
622
|
+
label: currClass,
|
|
623
|
+
value: inVal,
|
|
624
|
+
media: mediaSelector,
|
|
625
|
+
selector: selText,
|
|
626
|
+
source: 'inline',
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
});
|
|
631
|
+
// Second pass: convert style attribute properties to Tailwind
|
|
632
|
+
declarations.forEach(({ property: inProp, value: inVal }) => {
|
|
633
|
+
if (resolveVariables && inProp.startsWith('--'))
|
|
634
|
+
return;
|
|
635
|
+
const shortlongType = getPropertyType(inProp);
|
|
636
|
+
if (shortlongType.type === 'short') {
|
|
637
|
+
const expanded = expandShorthandProperty(inProp, inVal);
|
|
638
|
+
if (!expanded)
|
|
639
|
+
return;
|
|
640
|
+
const pOverwritten = expanded['overwritten_properties'];
|
|
641
|
+
for (const longProp in pOverwritten) {
|
|
642
|
+
const longVal = pOverwritten[longProp];
|
|
643
|
+
const tailClass = cssToTailwind(longProp, longVal);
|
|
644
|
+
if (!tailClass)
|
|
645
|
+
continue;
|
|
646
|
+
propSpecifityWithMediaVals[longProp] = [{
|
|
647
|
+
media_min: -1,
|
|
648
|
+
media_max: 9999,
|
|
649
|
+
score: 110,
|
|
650
|
+
tailwind_classes: [tailClass],
|
|
651
|
+
ruleIndex: 1,
|
|
652
|
+
prop: longProp,
|
|
653
|
+
}];
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
else {
|
|
657
|
+
let val = inVal;
|
|
658
|
+
if (resolveVariables && val.indexOf('var') >= 0) {
|
|
659
|
+
val = resolveAllVariables(val, ancestryChain, ctx.cssvarDefinedArr);
|
|
660
|
+
}
|
|
661
|
+
const overrideClasses = getOverrideClasses(inProp, val);
|
|
662
|
+
if (overrideClasses == null) {
|
|
663
|
+
const tailClass = cssToTailwind(inProp, val);
|
|
664
|
+
if (!tailClass)
|
|
665
|
+
return;
|
|
666
|
+
propSpecifityWithMediaVals[inProp] = [{
|
|
667
|
+
media_min: -1,
|
|
668
|
+
media_max: 9999,
|
|
669
|
+
score: 110,
|
|
670
|
+
tailwind_classes: [tailClass],
|
|
671
|
+
ruleIndex: 1,
|
|
672
|
+
prop: inProp,
|
|
673
|
+
}];
|
|
674
|
+
}
|
|
675
|
+
else {
|
|
676
|
+
if (!(inProp in propSpecifityWithMediaVals)) {
|
|
677
|
+
propSpecifityWithMediaVals[inProp] = [];
|
|
678
|
+
}
|
|
679
|
+
const currMediaRange = {
|
|
680
|
+
media_min: -1,
|
|
681
|
+
media_max: 9999,
|
|
682
|
+
score: 110,
|
|
683
|
+
tailwind_classes: [],
|
|
684
|
+
ruleIndex: 1,
|
|
685
|
+
prop: inProp,
|
|
686
|
+
};
|
|
687
|
+
for (const tailClass of overrideClasses) {
|
|
688
|
+
if (!tailClass)
|
|
689
|
+
continue;
|
|
690
|
+
currMediaRange.tailwind_classes.push(tailClass);
|
|
691
|
+
}
|
|
692
|
+
propSpecifityWithMediaVals[inProp] = mergeRanges(propSpecifityWithMediaVals[inProp], currMediaRange);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
// Remove the style attribute after processing
|
|
697
|
+
$editHtml(theElem).removeAttr('style');
|
|
698
|
+
}
|
|
699
|
+
// -----------------------------------------------------------
|
|
700
|
+
// 2) Process matching CSS rules
|
|
701
|
+
// -----------------------------------------------------------
|
|
702
|
+
const matchData = ctx.matchingFinalRules[currClass];
|
|
703
|
+
if (!matchData)
|
|
704
|
+
continue;
|
|
705
|
+
const allSelectors = matchData.selectors;
|
|
706
|
+
const allMedia = matchData.media_queries;
|
|
707
|
+
const allMatchingIndices = matchData.indices;
|
|
708
|
+
const allInheritedTypes = matchData.inherited_type;
|
|
709
|
+
const allInvalidPseudos = matchData.invalid_pseudos;
|
|
710
|
+
const allBodies = matchData.bodies;
|
|
711
|
+
const matchingParts = matchData.matching_parts;
|
|
712
|
+
for (let s = 0; s < allSelectors.length; s++) {
|
|
713
|
+
const aSelector = allSelectors[s];
|
|
714
|
+
const aBody = allBodies[s];
|
|
715
|
+
const aMediaTarget = allMedia[s];
|
|
716
|
+
const myMatchingParts = matchingParts[s];
|
|
717
|
+
const ruleIndex = allMatchingIndices[s];
|
|
718
|
+
const inheritedType = allInheritedTypes[s];
|
|
719
|
+
const invalidPseudo = allInvalidPseudos[s];
|
|
720
|
+
// Skip universal/body/html selectors and pseudo-element-only selectors
|
|
721
|
+
const normalizedSel = aSelector.replace(/\s+/g, '').replace(/::/g, ':');
|
|
722
|
+
if (SKIP_SELECTORS.includes(aSelector) || SKIP_SELECTORS.includes(normalizedSel))
|
|
723
|
+
continue;
|
|
724
|
+
// Also skip selectors that are just * with optional pseudo-elements
|
|
725
|
+
if (/^\*[\s,]/.test(aSelector) || aSelector === '*')
|
|
726
|
+
continue;
|
|
727
|
+
if (invalidPseudo)
|
|
728
|
+
continue;
|
|
729
|
+
if (inheritedType === 'other_inherited' || inheritedType === 'inherited')
|
|
730
|
+
continue;
|
|
731
|
+
// Calculate specificity and collect pseudos for all matching parts
|
|
732
|
+
let highestScore = 0;
|
|
733
|
+
let allPseudo = [];
|
|
734
|
+
const hasPseudoElement = aSelector.indexOf(':before') >= 0 || aSelector.indexOf(':after') >= 0 ||
|
|
735
|
+
aSelector.indexOf('::before') >= 0 || aSelector.indexOf('::after') >= 0;
|
|
736
|
+
for (let m = 0; m < myMatchingParts.length; m++) {
|
|
737
|
+
const mPart = myMatchingParts[m];
|
|
738
|
+
if (mPart.indexOf(':') >= 0) {
|
|
739
|
+
const pseudoParts = getPseudos(mPart);
|
|
740
|
+
// Check for sibling pseudo-elements (before/after) in the full selector
|
|
741
|
+
if (pseudoParts.length > 0) {
|
|
742
|
+
const baseSelector = mPart.replace(/::?(before|after)/g, '');
|
|
743
|
+
for (const pseudo of ['before', 'after']) {
|
|
744
|
+
if (!pseudoParts.includes(pseudo)) {
|
|
745
|
+
const sib1 = baseSelector + ':' + pseudo;
|
|
746
|
+
const sib2 = baseSelector + '::' + pseudo;
|
|
747
|
+
if (aSelector.indexOf(sib1) >= 0 || aSelector.indexOf(sib2) >= 0) {
|
|
748
|
+
pseudoParts.push(pseudo);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
allPseudo = allPseudo.concat(pseudoParts);
|
|
754
|
+
}
|
|
755
|
+
let myScore;
|
|
756
|
+
if (mPart in ctx.selectorSpecifityScore) {
|
|
757
|
+
myScore = ctx.selectorSpecifityScore[mPart];
|
|
758
|
+
}
|
|
759
|
+
else {
|
|
760
|
+
myScore = calculateSpecificity(mPart);
|
|
761
|
+
}
|
|
762
|
+
if (myScore > highestScore) {
|
|
763
|
+
highestScore = myScore;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
// Match against tSnippedArr to find the rule body
|
|
767
|
+
for (let xi = 0; xi < tSnippedArr.length; xi++) {
|
|
768
|
+
const mySelector = tSnippedArr[xi].selector;
|
|
769
|
+
const myBody = tSnippedArr[xi].body;
|
|
770
|
+
const myMedia = tSnippedArr[xi].media;
|
|
771
|
+
if (mySelector.trim() !== aSelector.trim() || aMediaTarget.trim() !== myMedia.trim()) {
|
|
772
|
+
continue;
|
|
773
|
+
}
|
|
774
|
+
const bodyUsedProps = {};
|
|
775
|
+
const theBodySplit = myBody.split(/\r?\n/);
|
|
776
|
+
for (let bb = 0; bb < theBodySplit.length; bb++) {
|
|
777
|
+
const line = theBodySplit[bb].trim();
|
|
778
|
+
if (line === '' || line.startsWith('/*'))
|
|
779
|
+
continue;
|
|
780
|
+
if (line.indexOf(':') < 0)
|
|
781
|
+
continue;
|
|
782
|
+
const tSplit = line.split(/:(.+)/);
|
|
783
|
+
if (tSplit.length < 2)
|
|
784
|
+
continue;
|
|
785
|
+
const tProp = tSplit[0].trim();
|
|
786
|
+
if (tProp.startsWith('--'))
|
|
787
|
+
continue;
|
|
788
|
+
let modifiedScore = highestScore;
|
|
789
|
+
if (tProp in bodyUsedProps) {
|
|
790
|
+
bodyUsedProps[tProp] += 1;
|
|
791
|
+
modifiedScore += bodyUsedProps[tProp];
|
|
792
|
+
}
|
|
793
|
+
else {
|
|
794
|
+
bodyUsedProps[tProp] = 0;
|
|
795
|
+
}
|
|
796
|
+
let tVal = tSplit[1].trim().replace(/;$/, '');
|
|
797
|
+
tVal = tVal.replace(/\/\*[\s\S]*?\*\//g, '').trim();
|
|
798
|
+
const shortlongType = getPropertyType(tProp);
|
|
799
|
+
if (shortlongType.type === 'short') {
|
|
800
|
+
// ------ Shorthand property ------
|
|
801
|
+
const expanded = expandShorthandProperty(tProp, tVal);
|
|
802
|
+
if (!expanded)
|
|
803
|
+
continue;
|
|
804
|
+
const pOverwritten = expanded['overwritten_properties'];
|
|
805
|
+
for (const longProp in pOverwritten) {
|
|
806
|
+
let currMediaRanges = [];
|
|
807
|
+
let longVal = pOverwritten[longProp];
|
|
808
|
+
const individualMediaQueries = splitNoParen(myMedia);
|
|
809
|
+
individualMediaQueries.forEach(individualMedia => {
|
|
810
|
+
const ranges = parseMediaQueryToTailwind(individualMedia, forceBreakpoints);
|
|
811
|
+
currMediaRanges = currMediaRanges.concat(ranges);
|
|
812
|
+
});
|
|
813
|
+
if (allPseudo.length <= 0) {
|
|
814
|
+
// No pseudo-classes
|
|
815
|
+
const propKey = longProp;
|
|
816
|
+
if (!(propKey in propSpecifityWithMediaVals)) {
|
|
817
|
+
propSpecifityWithMediaVals[propKey] = [];
|
|
818
|
+
}
|
|
819
|
+
for (let c = 0; c < currMediaRanges.length; c++) {
|
|
820
|
+
const currMediaRange = currMediaRanges[c];
|
|
821
|
+
currMediaRange['prop'] = propKey;
|
|
822
|
+
currMediaRange['score'] = modifiedScore;
|
|
823
|
+
currMediaRange['ruleIndex'] = ruleIndex;
|
|
824
|
+
if (resolveVariables && longVal.indexOf('var') >= 0) {
|
|
825
|
+
longVal = resolveAllVariables(longVal, ancestryChain, ctx.cssvarDefinedArr);
|
|
826
|
+
}
|
|
827
|
+
const tailClass = cssToTailwind(longProp, longVal);
|
|
828
|
+
if (!tailClass)
|
|
829
|
+
continue;
|
|
830
|
+
currMediaRange['tailwind_classes'] = [
|
|
831
|
+
...(currMediaRange['tailwind_classes'] || []),
|
|
832
|
+
tailClass,
|
|
833
|
+
];
|
|
834
|
+
propSpecifityWithMediaVals[propKey] = mergeRanges(propSpecifityWithMediaVals[propKey], currMediaRange);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
else {
|
|
838
|
+
// With pseudo-classes
|
|
839
|
+
const resolvedLongVal = (resolveVariables && longVal.indexOf('var') >= 0)
|
|
840
|
+
? resolveAllVariables(longVal, ancestryChain, ctx.cssvarDefinedArr)
|
|
841
|
+
: longVal;
|
|
842
|
+
processPseudoRanges(allPseudo, longProp, currMediaRanges, modifiedScore, ruleIndex, (prefix) => {
|
|
843
|
+
const tc = cssToTailwind(longProp, resolvedLongVal);
|
|
844
|
+
return tc ? prefix + tc : null;
|
|
845
|
+
}, null, propSpecifityWithMediaVals);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
else {
|
|
850
|
+
// ------ Not a shorthand property ------
|
|
851
|
+
let currMediaRanges = [];
|
|
852
|
+
const individualMediaQueries = splitNoParen(myMedia);
|
|
853
|
+
individualMediaQueries.forEach(individualMedia => {
|
|
854
|
+
const ranges = parseMediaQueryToTailwind(individualMedia, forceBreakpoints);
|
|
855
|
+
currMediaRanges = currMediaRanges.concat(ranges);
|
|
856
|
+
});
|
|
857
|
+
if (resolveVariables && tVal.indexOf('var') >= 0) {
|
|
858
|
+
tVal = resolveAllVariables(tVal, ancestryChain, ctx.cssvarDefinedArr);
|
|
859
|
+
}
|
|
860
|
+
const overrideClasses = getOverrideClasses(tProp, tVal);
|
|
861
|
+
if (allPseudo.length <= 0) {
|
|
862
|
+
// No pseudo-classes
|
|
863
|
+
const propKey = tProp;
|
|
864
|
+
if (!(propKey in propSpecifityWithMediaVals)) {
|
|
865
|
+
propSpecifityWithMediaVals[propKey] = [];
|
|
866
|
+
}
|
|
867
|
+
for (let c = 0; c < currMediaRanges.length; c++) {
|
|
868
|
+
const currMediaRange = currMediaRanges[c];
|
|
869
|
+
currMediaRange['prop'] = propKey;
|
|
870
|
+
currMediaRange['score'] = modifiedScore;
|
|
871
|
+
currMediaRange['ruleIndex'] = ruleIndex;
|
|
872
|
+
if (overrideClasses == null) {
|
|
873
|
+
const tailClass = cssToTailwind(tProp, tVal);
|
|
874
|
+
if (!tailClass)
|
|
875
|
+
continue;
|
|
876
|
+
currMediaRange['tailwind_classes'] = [
|
|
877
|
+
...(currMediaRange['tailwind_classes'] || []),
|
|
878
|
+
tailClass,
|
|
879
|
+
];
|
|
880
|
+
}
|
|
881
|
+
else {
|
|
882
|
+
for (const tailClass of overrideClasses) {
|
|
883
|
+
currMediaRange['tailwind_classes'] = [
|
|
884
|
+
...(currMediaRange['tailwind_classes'] || []),
|
|
885
|
+
tailClass,
|
|
886
|
+
];
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
propSpecifityWithMediaVals[propKey] = mergeRanges(propSpecifityWithMediaVals[propKey], currMediaRange);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
else {
|
|
893
|
+
// With pseudo-classes
|
|
894
|
+
processPseudoRanges(allPseudo, tProp, currMediaRanges, modifiedScore, ruleIndex, (prefix) => {
|
|
895
|
+
if (overrideClasses == null) {
|
|
896
|
+
const tc = cssToTailwind(tProp, tVal);
|
|
897
|
+
return tc ? prefix + tc : null;
|
|
898
|
+
}
|
|
899
|
+
return null; // handled by overrideClassesFn
|
|
900
|
+
}, overrideClasses != null
|
|
901
|
+
? (prefix) => overrideClasses.map(oc => prefix + oc)
|
|
902
|
+
: null, propSpecifityWithMediaVals);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
// -----------------------------------------------------------
|
|
909
|
+
// 3) Preserve icon font classes, then remove existing classes
|
|
910
|
+
// -----------------------------------------------------------
|
|
911
|
+
const iconClasses = getIconClasses($editHtml(theElem).attr('class'), tSnippedArr);
|
|
912
|
+
$editHtml(theElem).removeAttr('class');
|
|
913
|
+
// -----------------------------------------------------------
|
|
914
|
+
// 4) Merge padding/margin shorthand properties
|
|
915
|
+
// -----------------------------------------------------------
|
|
916
|
+
const mergedPropMap = updatePropSpecifityWithMergedProperties(propSpecifityWithMediaVals);
|
|
917
|
+
// -----------------------------------------------------------
|
|
918
|
+
// 5) Apply Tailwind classes to the element
|
|
919
|
+
// -----------------------------------------------------------
|
|
920
|
+
for (const aProp in mergedPropMap) {
|
|
921
|
+
const ranges = mergedPropMap[aProp];
|
|
922
|
+
let hasImportantClass = false;
|
|
923
|
+
let noMediaAtEdge = true;
|
|
924
|
+
let hasMediaMax = false;
|
|
925
|
+
for (const range of ranges) {
|
|
926
|
+
for (const tailClass of range.tailwind_classes) {
|
|
927
|
+
if (tailClass.startsWith('!')) {
|
|
928
|
+
hasImportantClass = true;
|
|
929
|
+
break;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
if (hasImportantClass)
|
|
933
|
+
break;
|
|
934
|
+
if (range.media_min === -1 || range.media_max === 9999) {
|
|
935
|
+
noMediaAtEdge = false;
|
|
936
|
+
}
|
|
937
|
+
if (range.media_max === 9999) {
|
|
938
|
+
hasMediaMax = true;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
if (hasImportantClass || noMediaAtEdge || !hasMediaMax) {
|
|
942
|
+
// Add classes with media prefix directly
|
|
943
|
+
for (const range of ranges) {
|
|
944
|
+
let mediaPrefix = '';
|
|
945
|
+
if (range.media_min > -1 || range.media_max < 9999) {
|
|
946
|
+
mediaPrefix = getMediaPrefix(range.media_min, range.media_max);
|
|
947
|
+
}
|
|
948
|
+
for (const rawTailClass of range.tailwind_classes) {
|
|
949
|
+
if (!rawTailClass)
|
|
950
|
+
continue;
|
|
951
|
+
let tailClass = mediaPrefix ? mediaPrefix + ':' + rawTailClass : rawTailClass;
|
|
952
|
+
const replaced = replaceArbitraryValueWithTailwindClass(tailClass);
|
|
953
|
+
if (!replaced)
|
|
954
|
+
continue;
|
|
955
|
+
tailClass = replaced;
|
|
956
|
+
$editHtml(theElem).addClass(tailClass);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
else {
|
|
961
|
+
// Reduce the Tailwind classes for this property
|
|
962
|
+
const reducedClasses = reduceTailwindClasses(ranges);
|
|
963
|
+
for (const rawClass of reducedClasses) {
|
|
964
|
+
const replaced = replaceArbitraryValueWithTailwindClass(rawClass);
|
|
965
|
+
if (!replaced)
|
|
966
|
+
continue;
|
|
967
|
+
$editHtml(theElem).addClass(replaced);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
// Restore preserved icon font classes
|
|
972
|
+
if (iconClasses && iconClasses.length > 0) {
|
|
973
|
+
iconClasses.forEach(cls => $editHtml(theElem).addClass(cls));
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
catch (error) {
|
|
977
|
+
// Log but do not abort the whole conversion
|
|
978
|
+
console.error('Error processing Tailwind element:', error);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
const retHtml = $editHtml.root().html() ?? '';
|
|
982
|
+
return retHtml;
|
|
983
|
+
}
|
|
984
|
+
// ============================================================
|
|
985
|
+
// Body-level Tailwind class extraction
|
|
986
|
+
// ============================================================
|
|
987
|
+
/**
|
|
988
|
+
* Extract Tailwind utility classes that should be applied to the <body>
|
|
989
|
+
* element (or an equivalent wrapper) based on CSS rules targeting body,
|
|
990
|
+
* html, :root, or * selectors.
|
|
991
|
+
*/
|
|
992
|
+
export function getTailwindBodyClasses(tSnippedArr, forceBreakpoints, resolveVariables, tailwindUltimateArr, ctx) {
|
|
993
|
+
let propSpecifityWithMediaVals = {};
|
|
994
|
+
const bodyClasses = [];
|
|
995
|
+
// -----------------------------------------------------------
|
|
996
|
+
// 1) Process snipped rules in reverse order (later rules win)
|
|
997
|
+
// -----------------------------------------------------------
|
|
998
|
+
for (let p = tSnippedArr.length - 1; p >= 0; p--) {
|
|
999
|
+
try {
|
|
1000
|
+
const tSelector = tSnippedArr[p].selector;
|
|
1001
|
+
const tBody = tSnippedArr[p].body;
|
|
1002
|
+
const tMedia = tSnippedArr[p].media;
|
|
1003
|
+
const tSelIndex = tSnippedArr.length - 1 - p;
|
|
1004
|
+
const allSelectors = tSelector.split(',');
|
|
1005
|
+
let processProperties = false;
|
|
1006
|
+
for (const oneSelector of allSelectors) {
|
|
1007
|
+
const trimSelector = oneSelector.trim();
|
|
1008
|
+
if (trimSelector === 'body' || trimSelector === '*' || trimSelector === ':root') {
|
|
1009
|
+
processProperties = true;
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
if (!processProperties)
|
|
1013
|
+
continue;
|
|
1014
|
+
const highestScore = 100;
|
|
1015
|
+
const bodyUsedProps = {};
|
|
1016
|
+
const theBodySplit = tBody.split(/\r?\n/);
|
|
1017
|
+
for (let bb = 0; bb < theBodySplit.length; bb++) {
|
|
1018
|
+
const line = theBodySplit[bb].trim();
|
|
1019
|
+
if (line === '' || line.startsWith('/*'))
|
|
1020
|
+
continue;
|
|
1021
|
+
if (line.indexOf(':') < 0)
|
|
1022
|
+
continue;
|
|
1023
|
+
const tSplit = line.split(/:(.+)/);
|
|
1024
|
+
if (tSplit.length < 2)
|
|
1025
|
+
continue;
|
|
1026
|
+
const tProp = tSplit[0].trim();
|
|
1027
|
+
if (tProp.startsWith('--'))
|
|
1028
|
+
continue;
|
|
1029
|
+
let modifiedScore = highestScore;
|
|
1030
|
+
if (tProp in bodyUsedProps) {
|
|
1031
|
+
bodyUsedProps[tProp] += 1;
|
|
1032
|
+
modifiedScore += bodyUsedProps[tProp];
|
|
1033
|
+
}
|
|
1034
|
+
else {
|
|
1035
|
+
bodyUsedProps[tProp] = 0;
|
|
1036
|
+
}
|
|
1037
|
+
let tVal = tSplit[1].trim().replace(/;$/, '');
|
|
1038
|
+
tVal = tVal.replace(/\/\*[\s\S]*?\*\//g, '').trim();
|
|
1039
|
+
const shortlongType = getPropertyType(tProp);
|
|
1040
|
+
if (shortlongType.type === 'short') {
|
|
1041
|
+
// Shorthand property
|
|
1042
|
+
const expanded = expandShorthandProperty(tProp, tVal);
|
|
1043
|
+
if (!expanded)
|
|
1044
|
+
continue;
|
|
1045
|
+
const pOverwritten = expanded['overwritten_properties'];
|
|
1046
|
+
for (const longProp in pOverwritten) {
|
|
1047
|
+
let currMediaRanges = [];
|
|
1048
|
+
const longVal = pOverwritten[longProp];
|
|
1049
|
+
const individualMediaQueries = splitNoParen(tMedia);
|
|
1050
|
+
individualMediaQueries.forEach(individualMedia => {
|
|
1051
|
+
const ranges = parseMediaQueryToTailwind(individualMedia, forceBreakpoints);
|
|
1052
|
+
currMediaRanges = currMediaRanges.concat(ranges);
|
|
1053
|
+
});
|
|
1054
|
+
const propKey = longProp;
|
|
1055
|
+
if (!(propKey in propSpecifityWithMediaVals)) {
|
|
1056
|
+
propSpecifityWithMediaVals[propKey] = [];
|
|
1057
|
+
}
|
|
1058
|
+
for (let c = 0; c < currMediaRanges.length; c++) {
|
|
1059
|
+
const currMediaRange = currMediaRanges[c];
|
|
1060
|
+
currMediaRange['prop'] = propKey;
|
|
1061
|
+
currMediaRange['score'] = modifiedScore;
|
|
1062
|
+
currMediaRange['ruleIndex'] = tSelIndex;
|
|
1063
|
+
let currLongVal = longVal;
|
|
1064
|
+
if (resolveVariables && currLongVal.indexOf('var') >= 0) {
|
|
1065
|
+
currLongVal = resolveAllVariables(currLongVal, ['html', 'body'], ctx.cssvarDefinedArr);
|
|
1066
|
+
}
|
|
1067
|
+
const tailClass = cssToTailwind(longProp, currLongVal);
|
|
1068
|
+
if (!tailClass)
|
|
1069
|
+
continue;
|
|
1070
|
+
currMediaRange['tailwind_classes'] = [
|
|
1071
|
+
...(currMediaRange['tailwind_classes'] || []),
|
|
1072
|
+
tailClass,
|
|
1073
|
+
];
|
|
1074
|
+
propSpecifityWithMediaVals[propKey] = mergeRanges(propSpecifityWithMediaVals[propKey], currMediaRange);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
else {
|
|
1079
|
+
// Non-shorthand property
|
|
1080
|
+
let currMediaRanges = [];
|
|
1081
|
+
const individualMediaQueries = splitNoParen(tMedia);
|
|
1082
|
+
individualMediaQueries.forEach(individualMedia => {
|
|
1083
|
+
const ranges = parseMediaQueryToTailwind(individualMedia, forceBreakpoints);
|
|
1084
|
+
currMediaRanges = currMediaRanges.concat(ranges);
|
|
1085
|
+
});
|
|
1086
|
+
if (resolveVariables && tVal.indexOf('var') >= 0) {
|
|
1087
|
+
tVal = resolveAllVariables(tVal, ['html', 'body'], ctx.cssvarDefinedArr);
|
|
1088
|
+
}
|
|
1089
|
+
const overrideClasses = getOverrideClasses(tProp, tVal);
|
|
1090
|
+
const propKey = tProp;
|
|
1091
|
+
if (!(propKey in propSpecifityWithMediaVals)) {
|
|
1092
|
+
propSpecifityWithMediaVals[propKey] = [];
|
|
1093
|
+
}
|
|
1094
|
+
for (let c = 0; c < currMediaRanges.length; c++) {
|
|
1095
|
+
const currMediaRange = currMediaRanges[c];
|
|
1096
|
+
currMediaRange['prop'] = propKey;
|
|
1097
|
+
currMediaRange['score'] = modifiedScore;
|
|
1098
|
+
currMediaRange['ruleIndex'] = tSelIndex;
|
|
1099
|
+
let currTVal = tVal;
|
|
1100
|
+
if (resolveVariables && currTVal.indexOf('var') >= 0) {
|
|
1101
|
+
currTVal = resolveAllVariables(currTVal, ['html', 'body'], ctx.cssvarDefinedArr);
|
|
1102
|
+
}
|
|
1103
|
+
if (overrideClasses == null) {
|
|
1104
|
+
const tailClass = cssToTailwind(tProp, currTVal);
|
|
1105
|
+
if (!tailClass)
|
|
1106
|
+
continue;
|
|
1107
|
+
currMediaRange['tailwind_classes'] = [
|
|
1108
|
+
...(currMediaRange['tailwind_classes'] || []),
|
|
1109
|
+
tailClass,
|
|
1110
|
+
];
|
|
1111
|
+
}
|
|
1112
|
+
else {
|
|
1113
|
+
for (const tailClass of overrideClasses) {
|
|
1114
|
+
currMediaRange['tailwind_classes'] = [
|
|
1115
|
+
...(currMediaRange['tailwind_classes'] || []),
|
|
1116
|
+
tailClass,
|
|
1117
|
+
];
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
propSpecifityWithMediaVals[propKey] = mergeRanges(propSpecifityWithMediaVals[propKey], currMediaRange);
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
catch (ex) {
|
|
1126
|
+
console.error('Error processing body classes:', ex);
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
// -----------------------------------------------------------
|
|
1130
|
+
// 2) Process the "ultimate" rules (higher-precedence body rules)
|
|
1131
|
+
// -----------------------------------------------------------
|
|
1132
|
+
const ALLOWED_INHERITED_PROPS = [
|
|
1133
|
+
'background-color',
|
|
1134
|
+
'background',
|
|
1135
|
+
'font-size',
|
|
1136
|
+
'font',
|
|
1137
|
+
'font-family',
|
|
1138
|
+
'color',
|
|
1139
|
+
];
|
|
1140
|
+
const ultimateSpecificity = 200;
|
|
1141
|
+
const ultimateRuleIndex = 9999;
|
|
1142
|
+
for (let i = 0; i < tailwindUltimateArr.length; i++) {
|
|
1143
|
+
const rule = tailwindUltimateArr[i];
|
|
1144
|
+
const theSelector = rule.selector.trim();
|
|
1145
|
+
const theBodyLines = rule.body.split(/\r?\n/);
|
|
1146
|
+
const theMedia = rule.media || '';
|
|
1147
|
+
if (theSelector !== 'body')
|
|
1148
|
+
continue;
|
|
1149
|
+
for (let line of theBodyLines) {
|
|
1150
|
+
line = line.trim();
|
|
1151
|
+
if (!line || line.startsWith('/*'))
|
|
1152
|
+
continue;
|
|
1153
|
+
if (line.indexOf(':') < 0)
|
|
1154
|
+
continue;
|
|
1155
|
+
const splitResult = line.split(/:(.+)/);
|
|
1156
|
+
if (!splitResult[1])
|
|
1157
|
+
continue;
|
|
1158
|
+
const prop = splitResult[0].trim();
|
|
1159
|
+
const val = splitResult[1].trim().replace(/;$/, '');
|
|
1160
|
+
if (!ALLOWED_INHERITED_PROPS.includes(prop))
|
|
1161
|
+
continue;
|
|
1162
|
+
let resolvedVal = val;
|
|
1163
|
+
if (resolveVariables && resolvedVal.indexOf('var') >= 0) {
|
|
1164
|
+
resolvedVal = resolveAllVariables(resolvedVal, ['body'], ctx.cssvarDefinedArr);
|
|
1165
|
+
}
|
|
1166
|
+
// Try shorthand override
|
|
1167
|
+
const oc = getOverrideClasses(prop, resolvedVal);
|
|
1168
|
+
let tailClasses = [];
|
|
1169
|
+
if (!oc) {
|
|
1170
|
+
const twClass = cssToTailwind(prop, resolvedVal);
|
|
1171
|
+
if (twClass)
|
|
1172
|
+
tailClasses.push(twClass);
|
|
1173
|
+
}
|
|
1174
|
+
else {
|
|
1175
|
+
tailClasses = oc;
|
|
1176
|
+
}
|
|
1177
|
+
if (tailClasses.length === 0)
|
|
1178
|
+
continue;
|
|
1179
|
+
// Parse media
|
|
1180
|
+
let mediaRanges = parseMediaQueryToTailwind(theMedia, forceBreakpoints);
|
|
1181
|
+
if (mediaRanges.length === 0) {
|
|
1182
|
+
mediaRanges = [{ media_min: -1, media_max: 9999, tailwind_classes: [] }];
|
|
1183
|
+
}
|
|
1184
|
+
if (!propSpecifityWithMediaVals[prop]) {
|
|
1185
|
+
propSpecifityWithMediaVals[prop] = [];
|
|
1186
|
+
}
|
|
1187
|
+
mediaRanges.forEach(mr => {
|
|
1188
|
+
const newObj = {
|
|
1189
|
+
media_min: mr.media_min,
|
|
1190
|
+
media_max: mr.media_max,
|
|
1191
|
+
tailwind_classes: [...tailClasses],
|
|
1192
|
+
score: ultimateSpecificity,
|
|
1193
|
+
ruleIndex: ultimateRuleIndex,
|
|
1194
|
+
prop,
|
|
1195
|
+
};
|
|
1196
|
+
propSpecifityWithMediaVals[prop] = mergeRanges(propSpecifityWithMediaVals[prop], newObj);
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
// -----------------------------------------------------------
|
|
1201
|
+
// 3) Merge padding/margin properties and emit final classes
|
|
1202
|
+
// -----------------------------------------------------------
|
|
1203
|
+
propSpecifityWithMediaVals = updatePropSpecifityWithMergedProperties(propSpecifityWithMediaVals);
|
|
1204
|
+
for (const aProp in propSpecifityWithMediaVals) {
|
|
1205
|
+
const ranges = propSpecifityWithMediaVals[aProp];
|
|
1206
|
+
for (const range of ranges) {
|
|
1207
|
+
let mediaPrefix = '';
|
|
1208
|
+
if (range.media_min > -1 || range.media_max < 9999) {
|
|
1209
|
+
mediaPrefix = getMediaPrefix(range.media_min, range.media_max);
|
|
1210
|
+
}
|
|
1211
|
+
for (let tailClass of range.tailwind_classes) {
|
|
1212
|
+
if (mediaPrefix) {
|
|
1213
|
+
tailClass = mediaPrefix + ':' + tailClass;
|
|
1214
|
+
}
|
|
1215
|
+
if (!bodyClasses.includes(tailClass)) {
|
|
1216
|
+
bodyClasses.push(tailClass);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
return bodyClasses.join(' ');
|
|
1222
|
+
}
|
|
1223
|
+
//# sourceMappingURL=tailwind-converter.js.map
|