fscss 1.1.6 → 1.1.7
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 +65 -20
- package/bin/fscss.js +39 -0
- package/devcontainer.json +22 -2
- package/e/exec.js +5 -7
- package/e/index.html +8 -0
- package/e/run.js +44 -0
- package/e/xfscss.js +7 -0
- package/example.css +9 -0
- package/example.fscss +7 -0
- package/index.js +0 -1
- package/lib/functions/all.js +1060 -0
- package/lib/functions/impSel.js +59 -0
- package/lib/functions/procImp.js +50 -0
- package/lib/index.js +2 -0
- package/lib/processor.js +47 -0
- package/package.json +26 -23
- package/public/test.html +24 -0
- package/public/vars.fscss +7 -0
- package/style.fscss +4 -0
- package/xfscss.min.js +3 -3
- package/e/index.js +0 -9
- package/e/xfscss.min.js +0 -9
- package/strg/de +0 -5
- package/strg/xvars.fscss +0 -23
|
@@ -0,0 +1,1060 @@
|
|
|
1
|
+
|
|
2
|
+
function procNum(css){
|
|
3
|
+
const regex = /num\((.*?)\)/g;
|
|
4
|
+
function evaluateExpression(expression) {
|
|
5
|
+
try {
|
|
6
|
+
return eval(expression);
|
|
7
|
+
} catch (e) {
|
|
8
|
+
console.error('Invalid expression:', expression);
|
|
9
|
+
return expression;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const processedCSS = css.replace(regex, (match, expression) => {
|
|
14
|
+
|
|
15
|
+
return evaluateExpression(expression);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
return (processedCSS);
|
|
19
|
+
}
|
|
20
|
+
const arraysExfscss = {}; // Renamed the global variable
|
|
21
|
+
const orderedxFscssRandom = {};
|
|
22
|
+
|
|
23
|
+
const exfMAX_DEPTH = 10; // Prevent infinite recursion
|
|
24
|
+
function extractBlock(css, startIndex) {
|
|
25
|
+
let depth = 0;
|
|
26
|
+
let i = startIndex;
|
|
27
|
+
while (i < css.length) {
|
|
28
|
+
if (css[i] === '{') depth++;
|
|
29
|
+
else if (css[i] === '}') depth--;
|
|
30
|
+
if (depth === 0) break;
|
|
31
|
+
i++;
|
|
32
|
+
}
|
|
33
|
+
return css.slice(startIndex, i + 1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function parseConditionBlocks(block) {
|
|
37
|
+
const blocks = [];
|
|
38
|
+
// Adjusted regex to correctly capture the block content within curly braces
|
|
39
|
+
const conditionRegex = /(if|el-if|el)\s*([^{}]*?)\s*\{([\s\S]*?)\}/g;
|
|
40
|
+
let match;
|
|
41
|
+
while ((match = conditionRegex.exec(block)) !== null) {
|
|
42
|
+
blocks.push({
|
|
43
|
+
type: match[1],
|
|
44
|
+
condition: match[2].trim(),
|
|
45
|
+
block: match[3].trim()
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return blocks;
|
|
49
|
+
}
|
|
50
|
+
function procExC(css) {
|
|
51
|
+
const regex = /exec\((_log|_error|_warn|_info),\s*(?:"([^"]*)"|'([^']*)'|([^)]*))\)/g;
|
|
52
|
+
let jsCode = '';
|
|
53
|
+
let match;
|
|
54
|
+
|
|
55
|
+
// Replace exec(...) with nothing (remove from CSS) while collecting code
|
|
56
|
+
const cleanedCSS = css.replace(regex, (full, method, dQ, sQ, raw) => {
|
|
57
|
+
const arg = dQ || sQ || raw;
|
|
58
|
+
|
|
59
|
+
if (!['_log', '_error', '_warn', '_info'].includes(method)) {
|
|
60
|
+
console.warn(`fscss[exec(console)]: Unsupported method: ${method}`);
|
|
61
|
+
return ''; // strip it from CSS
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!arg) {
|
|
65
|
+
console.warn(`fscss[exec(console)]: Empty argument for method: ${method}`);
|
|
66
|
+
return ''; // strip it from CSS
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
jsCode += `console.${method.slice(1)}("${arg.replace(/"/g, '\\"')}");\n`;
|
|
70
|
+
return ''; // ensure CSS isn’t broken
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Run console code safely
|
|
74
|
+
if (jsCode) {
|
|
75
|
+
try {
|
|
76
|
+
new Function(jsCode)();
|
|
77
|
+
} catch (e) {
|
|
78
|
+
console.error("fscss[exec(console)]: Error executing transformed code:", e);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return cleanedCSS;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
function procEv(css) {
|
|
87
|
+
const functionMap = {};
|
|
88
|
+
const funcDefRegex = /@event\s+([\w-]+)\(([^)]*)\)\s*:?{/g;
|
|
89
|
+
let funcMatch;
|
|
90
|
+
let modifiedCSS = css;
|
|
91
|
+
const removalRanges = [];
|
|
92
|
+
|
|
93
|
+
// First pass: extract and mark function definitions
|
|
94
|
+
while ((funcMatch = funcDefRegex.exec(css)) !== null) {
|
|
95
|
+
const funcName = funcMatch[1];
|
|
96
|
+
const argsStr = funcMatch[2];
|
|
97
|
+
const blockStart = funcMatch.index + funcMatch[0].length - 1;
|
|
98
|
+
|
|
99
|
+
if (blockStart >= css.length) {
|
|
100
|
+
console.warn(`fscss[parsing] Warning: Unexpected end of CSS after @event ${funcName} definition.`);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const fullBlock = extractBlock(css, blockStart);
|
|
105
|
+
|
|
106
|
+
if (fullBlock.length === 0 || fullBlock[fullBlock.length - 1] !== '}') {
|
|
107
|
+
console.warn(`fscss[parsing] Warning: Malformed block for @event '${funcName}'. Missing closing '}'.`);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const fullFunc = css.slice(funcMatch.index, blockStart + fullBlock.length);
|
|
112
|
+
|
|
113
|
+
const conditionBlocks = parseConditionBlocks(fullBlock);
|
|
114
|
+
const args = argsStr.split(',').map(arg => arg.trim()).filter(arg => arg !== '');
|
|
115
|
+
|
|
116
|
+
if (functionMap[funcName]) {
|
|
117
|
+
console.warn(`fscss[definition] Warning: Duplicate @event definition for '${funcName}'. The last one will be used.`);
|
|
118
|
+
}
|
|
119
|
+
functionMap[funcName] = { args, conditionBlocks };
|
|
120
|
+
|
|
121
|
+
removalRanges.push([funcMatch.index, blockStart + fullBlock.length]);
|
|
122
|
+
}
|
|
123
|
+
for (let i = removalRanges.length - 1; i >= 0; i--) {
|
|
124
|
+
const [start, end] = removalRanges[i];
|
|
125
|
+
modifiedCSS = modifiedCSS.slice(0, start) + modifiedCSS.slice(end);
|
|
126
|
+
}
|
|
127
|
+
modifiedCSS = modifiedCSS.replace(/@event\.([\w-]+)\(([^)]*)\)/g, (match, funcName, argValuesStr) => {
|
|
128
|
+
const func = functionMap[funcName];
|
|
129
|
+
if (!func) {
|
|
130
|
+
console.warn(`fscss[call] Warning: @event function '${funcName}' not found during call.`);
|
|
131
|
+
return match;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const context = {};
|
|
135
|
+
const argValues = argValuesStr.split(',').map(v => v.trim()).filter(v => v !== '');
|
|
136
|
+
|
|
137
|
+
if (argValues.length !== func.args.length) {
|
|
138
|
+
console.warn(`fscss[call] Warning: Argument count mismatch for @event '${funcName}'. Expected ${func.args.length}, got ${argValues.length}.`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
func.args.forEach((argName, i) => {
|
|
142
|
+
if (argValues[i] !== undefined) {
|
|
143
|
+
context[argName] = argValues[i];
|
|
144
|
+
} else {
|
|
145
|
+
console.warn(`fscss[call] Warning: Missing value for argument '${argName}' in @event '${funcName}' call.`);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
let result = '';
|
|
150
|
+
let matched = false;
|
|
151
|
+
let elBlockFound = false;
|
|
152
|
+
|
|
153
|
+
for (const block of func.conditionBlocks) {
|
|
154
|
+
if (block.type === 'el') {
|
|
155
|
+
if (elBlockFound) {
|
|
156
|
+
console.warn(`fscss[logic] Warning: Multiple 'el' (else) blocks found in @event '${funcName}'. Only the first 'el' block will be considered.`);
|
|
157
|
+
}
|
|
158
|
+
elBlockFound = true;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (matched && block.type !== 'el') {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (block.type === 'el') {
|
|
166
|
+
if (!matched) {
|
|
167
|
+
matched = true;
|
|
168
|
+
} else {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
const conditions = block.condition.split(',').map(c => c.trim()).filter(c => c !== '');
|
|
173
|
+
if (conditions.length === 0) {
|
|
174
|
+
console.warn(`fscss[logic] Warning: Empty condition in '${block.type}' block for @event '${funcName}'.`);
|
|
175
|
+
matched = true;
|
|
176
|
+
} else {
|
|
177
|
+
matched = conditions.every(cond => {
|
|
178
|
+
const comparisonMatch = cond.match(/^(\w+)\s*(==|!=|>=|<=|>|<)\s*([^]+)$/);
|
|
179
|
+
if (comparisonMatch) {
|
|
180
|
+
const [, varName, operator, expected] = comparisonMatch;
|
|
181
|
+
if (!(varName in context)) {
|
|
182
|
+
console.warn(`fscss[logic] Warning: Condition variable '${varName}' not provided in @event '${funcName}' context. Treating as false.`);
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
const actual = context[varName];
|
|
186
|
+
|
|
187
|
+
const numActual = isNaN(actual) ? actual : Number(actual);
|
|
188
|
+
const numExpected = isNaN(expected) ? expected : Number(expected);
|
|
189
|
+
switch (operator) {
|
|
190
|
+
case '==': return numActual == numExpected;
|
|
191
|
+
case '!=': return numActual != numExpected;
|
|
192
|
+
case '>': return numActual > numExpected;
|
|
193
|
+
case '<': return numActual < numExpected;
|
|
194
|
+
case '>=': return numActual >= numExpected;
|
|
195
|
+
case '<=': return numActual <= numExpected;
|
|
196
|
+
default: return false;
|
|
197
|
+
}
|
|
198
|
+
} else {
|
|
199
|
+
const parts = cond.split(':').map(s => s.trim());
|
|
200
|
+
if (parts.length !== 2) {
|
|
201
|
+
console.warn(`fscss[logic] Warning: Malformed condition '${cond}' in @event '${funcName}'. Expected 'variable operator value' or 'variable:value'.`);
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
const [varName, expected] = parts;
|
|
205
|
+
if (!(varName in context)) {
|
|
206
|
+
console.warn(`fscss[logic] Warning: Condition variable '${varName}' not provided in @event '${funcName}' context. Treating as false.`);
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
return context[varName] === expected;
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (matched) {
|
|
216
|
+
const assignMatch = block.block.match(/(\w+)\s*(?:\:\s*([^;]*);?|\|([^\|]+)\|?)/);
|
|
217
|
+
if (assignMatch && assignMatch[2]) {
|
|
218
|
+
result = assignMatch[2].trim();
|
|
219
|
+
}
|
|
220
|
+
else if (assignMatch && assignMatch[3]) {
|
|
221
|
+
result = assignMatch[3].trim();
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
console.warn(`fscss[logic] Warning: No valid CSS property assignment found in matched block for @event '${funcName}'. Block content: '${block.block}'.`);
|
|
225
|
+
}
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
if (!result && func.conditionBlocks.length > 0 && !matched) {
|
|
232
|
+
console.warn(`fscss[call] Warning: No condition matched for @event '${funcName}' with provided arguments. Returning original call string.`);
|
|
233
|
+
} else if (!result && func.conditionBlocks.length === 0) {
|
|
234
|
+
console.warn(`fscss[definition] Warning: @event '${funcName}' has no condition blocks defined. Returning original call string.`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return result || match;
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
return modifiedCSS.trim();
|
|
241
|
+
}
|
|
242
|
+
function initlibraries(css){
|
|
243
|
+
css = css.replace(/exec\(\s*_init\sisjs\s*\)/g, "exec(https://cdn.jsdelivr.net/gh/fscss-ttr/FSCSS@main/xf/styles/isjs.fscss)");
|
|
244
|
+
css = css.replace(/exec\(\s*_init\sthemes\s*\)/g, "exec(https://cdn.jsdelivr.net/gh/fscss-ttr/FSCSS@main/xf/styles/trshapes.fthemes.fscss)")
|
|
245
|
+
css = css.replace(/exec\(_init\sarray1to500\s*\)/g, "exec(https://cdn.jsdelivr.net/gh/fscss-ttr/FSCSS@main/xf/styles/1to500.fscss)");
|
|
246
|
+
return css;
|
|
247
|
+
}
|
|
248
|
+
function procVar(vcss) {
|
|
249
|
+
function processSCSS(scssCode) {
|
|
250
|
+
const globalVars = {};
|
|
251
|
+
const processedLines = [];
|
|
252
|
+
const lines = scssCode.split('\n');
|
|
253
|
+
|
|
254
|
+
let inBlock = false;
|
|
255
|
+
const blockVars = {};
|
|
256
|
+
|
|
257
|
+
for (let i = 0; i < lines.length; i++) {
|
|
258
|
+
let line = lines[i].trim();
|
|
259
|
+
|
|
260
|
+
if (line.includes('{')) {
|
|
261
|
+
inBlock = true;
|
|
262
|
+
processedLines.push(line);
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (line.includes('}')) {
|
|
267
|
+
inBlock = false;
|
|
268
|
+
for (const varName in blockVars) {
|
|
269
|
+
delete blockVars[varName];
|
|
270
|
+
}
|
|
271
|
+
processedLines.push(line);
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const varDeclarationRegex = /^\s*\$([a-zA-Z0-9_-]+)\s*:\s*([^;]+);/;
|
|
276
|
+
const varMatch = line.match(varDeclarationRegex);
|
|
277
|
+
|
|
278
|
+
if (varMatch) {
|
|
279
|
+
const [, varName, varValue] = varMatch;
|
|
280
|
+
if (inBlock) {
|
|
281
|
+
blockVars[varName] = varValue.trim();
|
|
282
|
+
// Do not include block-scoped declarations in the final CSS
|
|
283
|
+
} else {
|
|
284
|
+
globalVars[varName] = varValue.trim();
|
|
285
|
+
// Include global variable declarations in the final CSS
|
|
286
|
+
processedLines.push(line);
|
|
287
|
+
}
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const varUsageRegex = /\$\/?([a-zA-Z0-9_-]+)(!)?/g;
|
|
292
|
+
|
|
293
|
+
line = line.replace(varUsageRegex, (match, varName) => {
|
|
294
|
+
if (blockVars[varName] !== undefined) {
|
|
295
|
+
return blockVars[varName];
|
|
296
|
+
} else if (globalVars[varName] !== undefined) {
|
|
297
|
+
return globalVars[varName];
|
|
298
|
+
}
|
|
299
|
+
return match;
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
processedLines.push(line);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function getVariable(varName) {
|
|
306
|
+
return globalVars[varName] || null;
|
|
307
|
+
}
|
|
308
|
+
const finalCss = processedLines.join('\n');
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
css: finalCss,
|
|
312
|
+
getVariable
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const result = processSCSS(vcss);
|
|
317
|
+
return result.css;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function procExt(css) {
|
|
321
|
+
let extractedVariables = {};
|
|
322
|
+
let tempCSS = css;
|
|
323
|
+
|
|
324
|
+
// Step 1: Process string literals
|
|
325
|
+
tempCSS = tempCSS.replace(/("(?:[^"\\]|\\.)*")|('(?:[^'\\]|\\.)*')/g, function(fullMatch) {
|
|
326
|
+
let quote = fullMatch[0];
|
|
327
|
+
let content = fullMatch.slice(1, -1);
|
|
328
|
+
const directiveRegex = /@ext\((-?\d+),(\d+):\s*([^)]+)\)/g;
|
|
329
|
+
let match;
|
|
330
|
+
let directivesToProcess = [];
|
|
331
|
+
|
|
332
|
+
while ((match = directiveRegex.exec(content)) !== null) {
|
|
333
|
+
directivesToProcess.push({
|
|
334
|
+
fullMatch: match[0],
|
|
335
|
+
start: parseInt(match[1]),
|
|
336
|
+
length: parseInt(match[2]),
|
|
337
|
+
varName: match[3].trim(),
|
|
338
|
+
index: match.index
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
for (let i = directivesToProcess.length - 1; i >= 0; i--) {
|
|
343
|
+
let d = directivesToProcess[i];
|
|
344
|
+
let s = d.start < 0 ? content.length + d.start : d.start;
|
|
345
|
+
s = Math.max(0, s);
|
|
346
|
+
let extracted = content.substring(s, s + d.length);
|
|
347
|
+
|
|
348
|
+
if (s + d.length > content.length || s < 0) {
|
|
349
|
+
console.warn(`fscss:[@ext]Warning: @ext directive for variable '${d.varName}' in string literal specifies an out-of-bounds range. Extraction may be incomplete or incorrect.`);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (extractedVariables[d.varName] !== undefined) {
|
|
353
|
+
console.warn(`fscss:[@ext]Warning: Duplicate variable name '${d.varName}' found in string literal. The last extracted value will be used.`);
|
|
354
|
+
}
|
|
355
|
+
extractedVariables[d.varName] = extracted;
|
|
356
|
+
|
|
357
|
+
// Remove @ext from content
|
|
358
|
+
content = content.slice(0, d.index) + content.slice(d.index + d.fullMatch.length);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return quote + content + quote;
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
// Step 2: Outside strings
|
|
365
|
+
tempCSS = tempCSS.replace(/([#.\w-]+)\s*@ext\((-?\d+),(\d+):\s*([^)]+)\)/g, function(match, token, start, len, varName) {
|
|
366
|
+
start = parseInt(start);
|
|
367
|
+
len = parseInt(len);
|
|
368
|
+
varName = varName.trim();
|
|
369
|
+
let s = start < 0 ? token.length + start : start;
|
|
370
|
+
s = Math.max(0, s);
|
|
371
|
+
let extracted = token.substring(s, s + len);
|
|
372
|
+
|
|
373
|
+
if (s + len > token.length || s < 0) {
|
|
374
|
+
console.warn(`fscss:[@ext]Warning: @ext directive for variable '${varName}' on token '${token}' specifies an out-of-bounds range. Extraction may be incomplete or incorrect.`);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (extractedVariables[varName] !== undefined) {
|
|
378
|
+
console.warn(`fscss:[@ext]Warning: Duplicate variable name '${varName}' found outside string literals. The last extracted value will be used.`);
|
|
379
|
+
}
|
|
380
|
+
extractedVariables[varName] = extracted;
|
|
381
|
+
return token;
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
// Step 3: Replace @ext.varName references
|
|
385
|
+
tempCSS = tempCSS.replace(/@ext\.(\w+)\!?/g, function(match, varName) {
|
|
386
|
+
if (extractedVariables[varName] === undefined) {
|
|
387
|
+
console.warn(`fscss:[@ext]Warning: Reference to undefined variable '@ext.${varName}'. It will not be replaced.`);
|
|
388
|
+
return match;
|
|
389
|
+
}
|
|
390
|
+
return extractedVariables[varName];
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
return tempCSS;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
function procRan(input) {
|
|
398
|
+
return input.replace(/@random\(\[([^\]]+)\](?:, *ordered)?\)/g, (match, valuesStr) => {
|
|
399
|
+
const isOrdered = /, *ordered\)/.test(match);
|
|
400
|
+
const values = valuesStr.split(',').map(v => v.trim());
|
|
401
|
+
|
|
402
|
+
if (values.length === 0) {
|
|
403
|
+
console.warn("fscss[@random] Warning: Empty array provided for @random. Returning empty string.");
|
|
404
|
+
return '';
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (isOrdered) {
|
|
408
|
+
// Create consistent key for value sequences
|
|
409
|
+
const sequenceKey = values.join(':');
|
|
410
|
+
|
|
411
|
+
if (!orderedxFscssRandom[sequenceKey]) {
|
|
412
|
+
orderedxFscssRandom[sequenceKey] = {
|
|
413
|
+
values,
|
|
414
|
+
index: 0,
|
|
415
|
+
};
|
|
416
|
+
console.warn(`fscss[@random] Warning: New ordered sequence created for [${valuesStr}].`);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const store = orderedxFscssRandom[sequenceKey];
|
|
420
|
+
const val = store.values[store.index % store.values.length];
|
|
421
|
+
|
|
422
|
+
if (store.index >= store.values.length && store.index % store.values.length === 0) {
|
|
423
|
+
console.warn(`fscss[@random] Warning: Ordered sequence [${valuesStr}] is looping back to the beginning.`);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
store.index++;
|
|
427
|
+
return val;
|
|
428
|
+
} else {
|
|
429
|
+
// Regular random selection
|
|
430
|
+
const randIndex = Math.floor(Math.random() * values.length);
|
|
431
|
+
return values[randIndex];
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
function procArr(input) {
|
|
436
|
+
// 1. Parse array declarations
|
|
437
|
+
const arrayDeclarationRegex = /@arr\(?\s*([\w\-_—0-9]+)\)?\[([^\]]+)\]\)?/g;
|
|
438
|
+
let match;
|
|
439
|
+
while ((match = arrayDeclarationRegex.exec(input)) !== null) {
|
|
440
|
+
const arrayName = match[1];
|
|
441
|
+
const arrayValues = match[2].split(',').map(item => item.trim());
|
|
442
|
+
arraysExfscss[arrayName] = arrayValues;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
let output = input;
|
|
446
|
+
|
|
447
|
+
// 2. Process loops using @arr.name[]
|
|
448
|
+
output = output.replace(/([^\{\}]+)\{\s*([^}]*@arr\.([\w\-_—0-9]+)\[\][^}]*)\s*\}/g,
|
|
449
|
+
(fullMatch, selector, content, arrayName) => {
|
|
450
|
+
const arr = arraysExfscss[arrayName];
|
|
451
|
+
if (!arr) {
|
|
452
|
+
console.warn(`fscss[@arr] Warning: Array '${arrayName}' not found for loop processing.`);
|
|
453
|
+
return fullMatch;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return arr.map((value, index) => {
|
|
457
|
+
const sel = selector.replace(new RegExp(`@arr\\.${arrayName}\\[\\]`, 'g'), index + 1);
|
|
458
|
+
const body = content.replace(new RegExp(`@arr\\.${arrayName}\\[\\]`, 'g'), value);
|
|
459
|
+
return `${sel.trim()} {\n ${body.trim()}\n}`;
|
|
460
|
+
}).join('\n');
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
// 3. Specific array access: @arr.name[index]
|
|
464
|
+
output = output.replace(/@arr\.([\w\-_—0-9]+)\[(\d+)\]/g,
|
|
465
|
+
(fullMatch, arrayName, index) => {
|
|
466
|
+
const idx = parseInt(index) - 1;
|
|
467
|
+
const arr = arraysExfscss[arrayName];
|
|
468
|
+
if (!arr) {
|
|
469
|
+
console.warn(`fscss[@arr] Warning: Array '${arrayName}' not found.`);
|
|
470
|
+
return fullMatch;
|
|
471
|
+
}
|
|
472
|
+
return arr[idx] !== undefined ? arr[idx] : fullMatch;
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
// 4. Direct array access: @arr.name or @arr.name(separator)
|
|
476
|
+
output = output.replace(/@arr\.([\w\-_—0-9]+)(?:\(([^)]*)\))?/g,
|
|
477
|
+
(fullMatch, arrayName, separator) => {
|
|
478
|
+
const arr = arraysExfscss[arrayName];
|
|
479
|
+
if (!arr) {
|
|
480
|
+
console.warn(`fscss[@arr] Warning: Array '${arrayName}' not found for direct access.`);
|
|
481
|
+
return fullMatch;
|
|
482
|
+
}
|
|
483
|
+
const sep = (separator !== undefined && separator !== "") ? separator : ' ';
|
|
484
|
+
return arr.join(sep);
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
// 5. Clean up array declarations
|
|
488
|
+
return output
|
|
489
|
+
.replace(arrayDeclarationRegex, '')
|
|
490
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
491
|
+
.trim();
|
|
492
|
+
}
|
|
493
|
+
function procFun(code) {
|
|
494
|
+
const variables = {};
|
|
495
|
+
|
|
496
|
+
function parseStyle(styleStr) {
|
|
497
|
+
const props = {};
|
|
498
|
+
const lines = styleStr.split(';');
|
|
499
|
+
for (let line of lines) {
|
|
500
|
+
line = line.trim();
|
|
501
|
+
if (!line) continue;
|
|
502
|
+
const colonIdx = line.indexOf(':');
|
|
503
|
+
if (colonIdx === -1) {
|
|
504
|
+
console.warn(`fscss[@fun] Invalid style line (missing colon): "${line}"`);
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
const prop = line.substring(0, colonIdx).trim();
|
|
508
|
+
const value = line.substring(colonIdx + 1).trim();
|
|
509
|
+
if (prop) {
|
|
510
|
+
props[prop] = value;
|
|
511
|
+
} else {
|
|
512
|
+
console.warn(`fscss[@fun] Empty property name in line: "${line}"`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return props;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const funRegex = /@fun\(([\w\-\_\—0-9]+)\)\s*\{([\s\S]*?)\}\s*/g;
|
|
519
|
+
let funMatch;
|
|
520
|
+
while ((funMatch = funRegex.exec(code)) !== null) {
|
|
521
|
+
const varName = funMatch[1];
|
|
522
|
+
const rawStyles = funMatch[2].trim();
|
|
523
|
+
if (variables[varName]) {
|
|
524
|
+
console.warn(`fscss[@fun] Duplicate @fun variable declaration: "${varName}". The last one will overwrite previous declarations.`);
|
|
525
|
+
}
|
|
526
|
+
variables[varName] = {
|
|
527
|
+
raw: rawStyles,
|
|
528
|
+
props: parseStyle(rawStyles)
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
let processedCode = code;
|
|
533
|
+
|
|
534
|
+
// Handle value extraction (e.g., @fun.varname2.bg.value)
|
|
535
|
+
processedCode = processedCode.replace(/@fun\.([\w\-\_\—0-9]+)\.([\w\-\_\—0-9]+)\.value\!?/g, (match, varName, prop) => {
|
|
536
|
+
if (variables[varName] && variables[varName].props[prop]) {
|
|
537
|
+
return variables[varName].props[prop];
|
|
538
|
+
} else {
|
|
539
|
+
console.warn(`fscss[@fun] Value extraction failed for "@fun.${varName}.${prop}.value". Variable or property not found.`);
|
|
540
|
+
}
|
|
541
|
+
return match;
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
// Handle single property rule (e.g., @fun.varname2.background)
|
|
545
|
+
processedCode = processedCode.replace(/@fun\.([\w\-\_\—0-9]+)\.([\w\-\_\—0-9]+)\!?/g, (match, varName, prop) => {
|
|
546
|
+
if (variables[varName] && variables[varName].props[prop]) {
|
|
547
|
+
return `${prop}: ${variables[varName].props[prop]};`;
|
|
548
|
+
} else {
|
|
549
|
+
console.warn(`fscss[@fun] Single property rule failed for "@fun.${varName}.${prop}". Variable or property not found.`);
|
|
550
|
+
}
|
|
551
|
+
return match;
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
// Handle full variable block (e.g., @fun.varname2)
|
|
555
|
+
processedCode = processedCode.replace(/@fun\.([\w\-\_\—0-9]+)(?=[\s;}])\!?/g, (match, varName) => {
|
|
556
|
+
if (variables[varName]) {
|
|
557
|
+
return variables[varName].raw;
|
|
558
|
+
} else {
|
|
559
|
+
console.warn(`[@fun] Full variable block replacement failed for "@fun.${varName}". Variable not found.`);
|
|
560
|
+
}
|
|
561
|
+
return match;
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
// Clean up code
|
|
565
|
+
processedCode = processedCode.replace(/@fun\(([\w\-\_\d\—]+)\s*\{[\s\S]*?\}\s*/g, '');
|
|
566
|
+
processedCode = processedCode.replace(/^\s*[\r\n]/gm, '');
|
|
567
|
+
processedCode = processedCode.trim();
|
|
568
|
+
|
|
569
|
+
return processedCode;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Extracts values using copy() and creates CSS custom properties
|
|
573
|
+
function flattenNestedCSS(css, options = {}) {
|
|
574
|
+
const {
|
|
575
|
+
preserveComments = false,
|
|
576
|
+
indent = ' ',
|
|
577
|
+
validate = true,
|
|
578
|
+
errorHandler = (msg) => console.warn(msg),
|
|
579
|
+
} = options;
|
|
580
|
+
|
|
581
|
+
// Remove comments unless preserved
|
|
582
|
+
if (!preserveComments) {
|
|
583
|
+
css = css.replace(/\/\*[\s\S]*?\*\//g, '').trim();
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function isValidSelector(selector) {
|
|
587
|
+
// Allow modern CSS features (:has(), > selector, etc.)
|
|
588
|
+
return selector && selector.trim() !== '' &&
|
|
589
|
+
!/[^a-zA-Z0-9\-_@*.\#:,\s>&~+()\[\]'"]|\/\//.test(selector);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function isValidProperty(prop) {
|
|
593
|
+
const [name, ...rest] = prop.split(':').map(s => s.trim());
|
|
594
|
+
return !validate || /^(--|[\w-]+)$/.test(name);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function parseBlock(css, start, parentSelector = '') {
|
|
598
|
+
let output = '';
|
|
599
|
+
let pos = start;
|
|
600
|
+
const stack = [];
|
|
601
|
+
let current = '';
|
|
602
|
+
let inString = false;
|
|
603
|
+
let quote = null;
|
|
604
|
+
let depth = 0;
|
|
605
|
+
|
|
606
|
+
while (pos < css.length) {
|
|
607
|
+
const char = css[pos];
|
|
608
|
+
|
|
609
|
+
if (char === '\\' && inString) {
|
|
610
|
+
current += char;
|
|
611
|
+
pos++;
|
|
612
|
+
if (pos < css.length) {
|
|
613
|
+
current += css[pos];
|
|
614
|
+
}
|
|
615
|
+
pos++;
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
if ((char === '"' || char === "'") && !inString) {
|
|
620
|
+
inString = true;
|
|
621
|
+
quote = char;
|
|
622
|
+
current += char;
|
|
623
|
+
} else if (char === quote && inString) {
|
|
624
|
+
inString = false;
|
|
625
|
+
quote = null;
|
|
626
|
+
current += char;
|
|
627
|
+
} else if (char === '{' && !inString) {
|
|
628
|
+
if (depth === 0) {
|
|
629
|
+
const selector = current.trim();
|
|
630
|
+
current = '';
|
|
631
|
+
stack.push({ selector, parent: parentSelector });
|
|
632
|
+
} else {
|
|
633
|
+
current += char;
|
|
634
|
+
}
|
|
635
|
+
depth++;
|
|
636
|
+
} else if (char === '}' && !inString) {
|
|
637
|
+
depth--;
|
|
638
|
+
if (depth === 0) {
|
|
639
|
+
const block = stack.pop();
|
|
640
|
+
if (!block) continue;
|
|
641
|
+
|
|
642
|
+
let fullSelector = '';
|
|
643
|
+
if (block.selector.includes('&')) {
|
|
644
|
+
fullSelector = block.selector.replace(/&/g, block.parent);
|
|
645
|
+
} else {
|
|
646
|
+
fullSelector = block.parent ? `${block.parent} ${block.selector}` : block.selector;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Parse nested content
|
|
650
|
+
const nested = parseNestedContent(current, fullSelector);
|
|
651
|
+
|
|
652
|
+
if (nested.properties.length > 0 || nested.keyframes.length > 0) {
|
|
653
|
+
output += `${fullSelector} {\n`;
|
|
654
|
+
if (nested.properties.length > 0) {
|
|
655
|
+
output += indent + nested.properties.join(`;\n${indent}`) + ';\n';
|
|
656
|
+
}
|
|
657
|
+
output += nested.keyframes.join('\n');
|
|
658
|
+
output += '}\n\n';
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
output += nested.nestedBlocks;
|
|
662
|
+
current = '';
|
|
663
|
+
} else {
|
|
664
|
+
current += char;
|
|
665
|
+
}
|
|
666
|
+
} else if (char === '@' && !inString && depth === 0) {
|
|
667
|
+
// Handle at-rules at root level
|
|
668
|
+
const atRuleEnd = findAtRuleEnd(css, pos);
|
|
669
|
+
if (atRuleEnd === -1) break;
|
|
670
|
+
|
|
671
|
+
output += css.substring(pos, atRuleEnd).trim() + '\n\n';
|
|
672
|
+
pos = atRuleEnd;
|
|
673
|
+
continue;
|
|
674
|
+
} else {
|
|
675
|
+
current += char;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
pos++;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
return { output, pos };
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
function findAtRuleEnd(css, start) {
|
|
685
|
+
let depth = 0;
|
|
686
|
+
let inString = false;
|
|
687
|
+
let quote = null;
|
|
688
|
+
let pos = start;
|
|
689
|
+
|
|
690
|
+
while (pos < css.length) {
|
|
691
|
+
const char = css[pos];
|
|
692
|
+
|
|
693
|
+
if (char === '\\' && inString) {
|
|
694
|
+
pos += 2;
|
|
695
|
+
continue;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
if ((char === '"' || char === "'") && !inString) {
|
|
699
|
+
inString = true;
|
|
700
|
+
quote = char;
|
|
701
|
+
} else if (char === quote && inString) {
|
|
702
|
+
inString = false;
|
|
703
|
+
quote = null;
|
|
704
|
+
} else if (char === '{' && !inString) {
|
|
705
|
+
depth++;
|
|
706
|
+
} else if (char === '}' && !inString) {
|
|
707
|
+
depth--;
|
|
708
|
+
if (depth === 0) {
|
|
709
|
+
return pos + 1;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
pos++;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
return -1;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
function parseNestedContent(content, parentSelector) {
|
|
720
|
+
const result = {
|
|
721
|
+
properties: [],
|
|
722
|
+
nestedBlocks: '',
|
|
723
|
+
keyframes: []
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
let current = '';
|
|
727
|
+
let inString = false;
|
|
728
|
+
let quote = null;
|
|
729
|
+
let depth = 0;
|
|
730
|
+
let pos = 0;
|
|
731
|
+
|
|
732
|
+
while (pos < content.length) {
|
|
733
|
+
const char = content[pos];
|
|
734
|
+
|
|
735
|
+
if (char === '\\' && inString) {
|
|
736
|
+
current += char;
|
|
737
|
+
pos++;
|
|
738
|
+
if (pos < content.length) {
|
|
739
|
+
current += content[pos];
|
|
740
|
+
}
|
|
741
|
+
pos++;
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
if ((char === '"' || char === "'") && !inString) {
|
|
746
|
+
inString = true;
|
|
747
|
+
quote = char;
|
|
748
|
+
current += char;
|
|
749
|
+
} else if (char === quote && inString) {
|
|
750
|
+
inString = false;
|
|
751
|
+
quote = null;
|
|
752
|
+
current += char;
|
|
753
|
+
} else if (char === '{' && !inString) {
|
|
754
|
+
depth++;
|
|
755
|
+
current += char;
|
|
756
|
+
} else if (char === '}' && !inString) {
|
|
757
|
+
depth--;
|
|
758
|
+
current += char;
|
|
759
|
+
if (depth === 0) {
|
|
760
|
+
// Found a complete nested block
|
|
761
|
+
const block = parseBlock(current, 0, parentSelector).output;
|
|
762
|
+
result.nestedBlocks += block;
|
|
763
|
+
current = '';
|
|
764
|
+
}
|
|
765
|
+
} else if (char === ';' && !inString && depth === 0) {
|
|
766
|
+
// Property handling
|
|
767
|
+
const prop = current.trim();
|
|
768
|
+
if (prop) {
|
|
769
|
+
if (isValidProperty(prop)) {
|
|
770
|
+
result.properties.push(prop);
|
|
771
|
+
} else if (validate) {
|
|
772
|
+
errorHandler(`Invalid property: ${prop}`);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
current = '';
|
|
776
|
+
} else if (char === '@' && !inString && depth === 0) {
|
|
777
|
+
// Handle keyframes inside blocks
|
|
778
|
+
const atEnd = findAtRuleEnd(content, pos);
|
|
779
|
+
if (atEnd === -1) break;
|
|
780
|
+
|
|
781
|
+
const atContent = content.substring(pos, atEnd);
|
|
782
|
+
result.keyframes.push(atContent.trim());
|
|
783
|
+
pos = atEnd;
|
|
784
|
+
current = '';
|
|
785
|
+
continue;
|
|
786
|
+
} else {
|
|
787
|
+
current += char;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
pos++;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// Handle trailing property
|
|
794
|
+
const lastProp = current.trim();
|
|
795
|
+
if (lastProp && depth === 0) {
|
|
796
|
+
if (isValidProperty(lastProp)) {
|
|
797
|
+
result.properties.push(lastProp);
|
|
798
|
+
} else if (validate) {
|
|
799
|
+
errorHandler(`Invalid property: ${lastProp}`);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
return result;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
const result = parseBlock(css, 0);
|
|
807
|
+
return result.output;
|
|
808
|
+
}
|
|
809
|
+
function procP(text) {
|
|
810
|
+
return text.replace(/%(\d+)\(([^[]+)\[\s*([^\]]+)\]\)/g, (match, number, properties, value) => {
|
|
811
|
+
const propList = properties.split(',').map(p => p.trim());
|
|
812
|
+
if (propList.length != number) {
|
|
813
|
+
console.warn(`Number of properties ${propList.length} does not match %${number}`);
|
|
814
|
+
return match;
|
|
815
|
+
}
|
|
816
|
+
return propList.map(prop => `${prop}${value}`).join("");
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
function transformCssValues(css) {
|
|
821
|
+
const customProperties = new Set();
|
|
822
|
+
const copyRegex = /(:\s*)(["']?)(.*?)(["']?)\s*copy\(([-]?\d+),\s*([^\;^\)^\(^,^ ]*)\)/g;
|
|
823
|
+
|
|
824
|
+
const transformedCss = css.replace(copyRegex, (match, prefix, quote1, value, quote2, lengthStr, variableName) => {
|
|
825
|
+
const length = parseInt(lengthStr);
|
|
826
|
+
const sanitizedVar = variableName.replace(/[^a-zA-Z0-9_-]/g, '');
|
|
827
|
+
let extractedValue = '';
|
|
828
|
+
|
|
829
|
+
if (length >= 0) {
|
|
830
|
+
extractedValue = value.substring(0, length);
|
|
831
|
+
} else {
|
|
832
|
+
extractedValue = value.substring(value.length + length);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
customProperties.add(`--${sanitizedVar}:${extractedValue};`);
|
|
836
|
+
return `${prefix}${quote1}${value}${quote2}`;
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
// Append custom properties to :root if any were created
|
|
840
|
+
if (customProperties.size > 0) {
|
|
841
|
+
const rootBlock = `:root{${Array.from(customProperties).join('\n')}\n}`;
|
|
842
|
+
return transformedCss + `\n${rootBlock}`;
|
|
843
|
+
}
|
|
844
|
+
return transformedCss;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// Repeats a string while handling quotes
|
|
848
|
+
function repeatString(str, count) {
|
|
849
|
+
return str.replace(/^['"]|['"]$/g, '').repeat(Math.max(0, parseInt(count)));
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// Processes recursive CSS patterns (re() function)
|
|
853
|
+
function replaceRe(css) {
|
|
854
|
+
// Enhanced regex to capture re() declarations with flexibility
|
|
855
|
+
const reRegex = /(?:store|str|re)\(\s*([^:,]+)\s*[,:]\s*(?:"([^"]*)"|'([^']*)')\s*\)/gi;
|
|
856
|
+
const variableMap = new Map();
|
|
857
|
+
|
|
858
|
+
// Step 1: Remove re() declarations and store variable-value mappings
|
|
859
|
+
let cleanedCss = css.replace(reRegex, (match, variable, dqValue, sqValue) => {
|
|
860
|
+
const value = dqValue || sqValue;
|
|
861
|
+
variable = variable.trim();
|
|
862
|
+
variableMap.set(variable, value);
|
|
863
|
+
return ''; // Completely remove the re() call
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
// If no variables found, return cleaned CSS
|
|
867
|
+
if (variableMap.size === 0) return cleanedCss;
|
|
868
|
+
|
|
869
|
+
// Step 2: Replace variables throughout the CSS
|
|
870
|
+
let changed;
|
|
871
|
+
let iterations = 0;
|
|
872
|
+
const maxIterations = 100;
|
|
873
|
+
let current = cleanedCss;
|
|
874
|
+
|
|
875
|
+
do {
|
|
876
|
+
changed = false;
|
|
877
|
+
for (const [variable, value] of variableMap.entries()) {
|
|
878
|
+
// Use word boundaries to avoid partial replacements
|
|
879
|
+
const varRegex = new RegExp(`\\b${escapeRegExp(variable)}\\b`, 'g');
|
|
880
|
+
const newCss = current.replace(varRegex, value);
|
|
881
|
+
|
|
882
|
+
if (newCss !== current) {
|
|
883
|
+
changed = true;
|
|
884
|
+
current = newCss;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
iterations++;
|
|
888
|
+
} while (changed && iterations < maxIterations);
|
|
889
|
+
|
|
890
|
+
if (iterations >= maxIterations) {
|
|
891
|
+
console.warn('Maximum iterations reached. Possible circular dependency.');
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
return current;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
function escapeRegExp(string) {
|
|
898
|
+
return string.replace(/[.*+?^${}|[\]\\]/g, '\\$&');
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
|
|
902
|
+
// Applies all FSCSS transformations to CSS content
|
|
903
|
+
function applyFscssTransformations(css) {
|
|
904
|
+
// Handle mx/mxs padding shorthands
|
|
905
|
+
css = css.replace(/(?:mxs|\$p)\((([^\,]*)\,)?(([^\,]*)\,)?(([^\,]*)\,)?(([^\,]*)\,)?(([^\,]*)\,)?(([^\,]*)\,\s*)?("([^"]*)"|'([^']*)')\)/gi, '$2:$14$15;$4:$14$15;$6:$14$15;$8:$14$15;$10:$14$15;$12:$14$15;')
|
|
906
|
+
.replace(/(?:mx|\$m)\((([^\,]*)\,)?(([^\,]*)\,)?(([^\,]*)\,)?(([^\,]*)\,)?(([^\,]*)\,)?(([^\,]*)\,\s*)?("([^"]*)"|'([^']*)')\)/gi, '$2$14$15$4$14$15$6$14$15$8$14$15$10$14$15$12$14$15')
|
|
907
|
+
|
|
908
|
+
// Handle string repetition (rpt)
|
|
909
|
+
.replace(/rpt\((\d+)\,\s*("([^"]*)"|'([^']*)')\)/gi, (match, count, quotedStr) => repeatString(quotedStr, count))
|
|
910
|
+
|
|
911
|
+
// Process CSS variable declarations and references
|
|
912
|
+
.replace(/\$(([\_\-\d\w]+)\:(\"[^\"]*\"|\'[^\']*\'|[^\;]*)\;)/gi, ':root{--$1}')
|
|
913
|
+
.replace(/\$([^\!\s]+)!/gi, 'var(--$1)')
|
|
914
|
+
.replace(/\$([\w\-\_\d]+)/gi, 'var(--$1)')
|
|
915
|
+
|
|
916
|
+
// Handle vendor prefix expansion
|
|
917
|
+
.replace(/\-\*\-(([^\:]+)\:(\"[^\"]*\"|\'[^\']*\'|[^\;]*)\;)/gi, '-webkit-$1-moz-$1-ms-$1-o-$1')
|
|
918
|
+
// Process list-based shorthands (%i, %6-%1)
|
|
919
|
+
.replace(/%i\((([^\,\[\]]*)\,)?(([^\,\[\]]*)\,)?(([^\,\[\]]*)\,)?(([^\,\[\]]*)\,)?(([^\,\[\]]*)\,)?(([^\,\[\]]*)\,)?(([^\,\]\[]*)\,)?(([^\,\]\[]*)\,)?(([^\,\[\]]*))?\s*\[([^\]\[]*)\]\)/gi, '$2$21$4$21$6$21$8$21$10$21$12$21$14$21$16$21$18$21$20$21')
|
|
920
|
+
.replace(/%6\((([^\,\[\]]*)\,)?(([^\,\[\]]*)\,)?(([^\,\[\]]*)\,)?(([^\,\]\[]*)\,)?(([^\,\]\[]*)\,)?(([^\,\[\]]*))?\s*\[([^\]\[]*)\]\)/gi, '$2$13$4$13$6$13$8$13$10$13$12$13')
|
|
921
|
+
.replace(/%5\((([^\,\[\]]*)\,)?(([^\,\[\]]*)\,)?(([^\,\[\]]*)\,)?(([^\,\]\[]*)\,)?(([^\,\]\[]*))?\s*\[([^\]\[]*)\]\)/gi, '$2$11$4$11$6$11$8$11$10$11')
|
|
922
|
+
.replace(/%4\((([^\,\[\]]*)\,)?(([^\,\[\]]*)\,)?(([^\,\[\]]*)\,)?(([^\,\[\]]*))?\s*\[([^\]\[]*)\]\)/gi, '$2$9$4$9$6$9$8$9')
|
|
923
|
+
.replace(/%3\((([^\,\[\]]*)\,)?(([^\,\[\]]*)\,)?(([^\,\[\]]*))?\s*\[([^\]\[]*)\]\)/gi, '$2$7$4$7$6$7')
|
|
924
|
+
.replace(/%2\((([^\,\[\]]*)\,)?(([^\,\]\[]*))?\s*\[([^\]\[]*)\]\)/gi, '$2$5$4$5')
|
|
925
|
+
.replace(/%1\((([^\,\]\[]*))?\s*\[([^\]\[]*)\]\)/gi, '$2$3');
|
|
926
|
+
css = procP(css);
|
|
927
|
+
css = css.replace(/\$\(\s*@keyframes\s*(\S+)\)/gi, '$1{animation-name:$1;}@keyframes $1')
|
|
928
|
+
.replace(/\$\(\s*(\@[\w\-\*]*)\s*([^\{\}\,&]*)(\s*,\s*[^\{\}&]*)?&?(\[([^\{\}]*)\])?\s*\)/gi, '$2$3{animation:$2 $5;}$1 $2')
|
|
929
|
+
|
|
930
|
+
// Process property references
|
|
931
|
+
.replace(/\$\(\s*--([^\{\}]*)\)/gi, '$1')
|
|
932
|
+
.replace(/\$\(([^\:]*):\s*([^\)\:]*)\)/gi, '[$1=\'$2\']')
|
|
933
|
+
|
|
934
|
+
// Handle grouping syntax (g)
|
|
935
|
+
.replace(/g\(([^"'\s]*)\,\s*(("([^"]*)"|'([^']*)')\,\s*)?("([^"]*)"|'([^']*)')\s*\)/gi, '$1 $4$5$1 $7$8')
|
|
936
|
+
.replace(/\$\(([^\:]*):\s*([^\)\:]*)\)/gi, '[$1=\'$2\']')
|
|
937
|
+
.replace(/\$\(([^\:^\)]*)\)/gi, '[$1]');
|
|
938
|
+
return css;
|
|
939
|
+
}
|
|
940
|
+
async function impSel(text) {
|
|
941
|
+
const validImpExt = [".fscss", ".css", ".txt", ".scss", ".less", "xfscss"]
|
|
942
|
+
const regex = /@import\(exec\(([^)]+)\)\s*\.\s*(?:pick|find)\(([^)]+)\)\)/g;
|
|
943
|
+
const matches = [...text.matchAll(regex)];
|
|
944
|
+
|
|
945
|
+
let result = text;
|
|
946
|
+
|
|
947
|
+
for (const match of matches) {
|
|
948
|
+
const [fullMatch, urlSrc, part] = match;
|
|
949
|
+
try {
|
|
950
|
+
const impUrl = urlSrc.replace(/["']/g, "");
|
|
951
|
+
const impExt = impUrl.slice(impUrl.lastIndexOf(".")).toLowerCase();
|
|
952
|
+
if (impUrl.trim().startsWith("_init") && impUrl.includes(" ")) {
|
|
953
|
+
console.warn(`fscss[@import] library not found for: ${impUrl}`);
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
if (!validImpExt.includes(impExt)) {
|
|
958
|
+
console.warn(`fscss[@import] invalid extension for: ${impUrl}`);
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
const response = await fetch(impUrl);
|
|
963
|
+
if (!response.ok) throw new Error(`fscss[@import] HTTP ${response.status} for ${urlSrc}`);
|
|
964
|
+
const resText = await response.text();
|
|
965
|
+
const extracted = extractOnlyBlock(resText, part.trim());
|
|
966
|
+
result = result.replace(fullMatch, extracted);
|
|
967
|
+
} catch (err) {
|
|
968
|
+
console.error(`fscss[@import] Failed: ${urlSrc} `, err);
|
|
969
|
+
result = result.replace(fullMatch, `/* Failed import: ${urlSrc} */`);
|
|
970
|
+
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
return result;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
function extractOnlyBlock(cssText, blockName) {
|
|
978
|
+
const regex = new RegExp(`${blockName}\\s*{[^}]*}`, "g");
|
|
979
|
+
const match = cssText.match(regex);
|
|
980
|
+
return match ? match.join("\n") : console.warn(`fscss[@import pick] No block matches: ${blockName} `);
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
const VALID_EXTENSIONS = ['.fscss', '.css', '.txt', '.scss', '.less', '.xfscss'];
|
|
984
|
+
|
|
985
|
+
async function processImports(cssText, depth = 0, baseURL = window.location.href) {
|
|
986
|
+
if (depth > exfMAX_DEPTH) {
|
|
987
|
+
console.warn('fscss[@import]\n Maximum import depth exceeded. Skipping further imports.');
|
|
988
|
+
return cssText;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
const importRegex = /@import\s*\(\s*exec\s*\(\s*((?:'[^']*'|"[^"]*"|[^'")]\S*)\s*)\)\s*\)/g;
|
|
992
|
+
const matches = Array.from(cssText.matchAll(importRegex));
|
|
993
|
+
|
|
994
|
+
if (matches.length === 0) return cssText;
|
|
995
|
+
|
|
996
|
+
const fetchedContents = await Promise.all(
|
|
997
|
+
matches.map(async (match) => {
|
|
998
|
+
const [fullMatch, urlSpec] = match;
|
|
999
|
+
try {
|
|
1000
|
+
const cleanUrl = urlSpec.replace(/^['"](.*)['"]$/, '$1').trim();
|
|
1001
|
+
const absoluteUrl = new URL(cleanUrl, baseURL).href;
|
|
1002
|
+
|
|
1003
|
+
// --- New code for extension validation ---
|
|
1004
|
+
const urlPath = new URL(absoluteUrl).pathname;
|
|
1005
|
+
const extension = urlPath.slice(urlPath.lastIndexOf('.')).toLowerCase();
|
|
1006
|
+
|
|
1007
|
+
if (absoluteUrl.trim().startsWith("_init") && absoluteUrl.includes(" ")){
|
|
1008
|
+
console.warn(`fscss[@import] library not found for: ${absoluteUrl}`);
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
if (!VALID_EXTENSIONS.includes(extension)) {
|
|
1013
|
+
console.warn(`fscss[@import] \n Invalid import URL extension "${extension}" for "${absoluteUrl}". Only ${VALID_EXTENSIONS.join(', ')} are allowed.`);
|
|
1014
|
+
return `/* Invalid extension for "${absoluteUrl}" */`;
|
|
1015
|
+
}
|
|
1016
|
+
// --- End of new code ---
|
|
1017
|
+
|
|
1018
|
+
const response = await fetch(absoluteUrl);
|
|
1019
|
+
if (!response.ok) throw new Error(`HTTP ${response.status} for ${absoluteUrl}`);
|
|
1020
|
+
|
|
1021
|
+
const importedText = await response.text();
|
|
1022
|
+
return processImports(importedText, depth + 1, absoluteUrl);
|
|
1023
|
+
} catch (error) {
|
|
1024
|
+
console.warn(`fscss[@import]\n Failed to import "${urlSpec}" from "${baseURL}":`, error);
|
|
1025
|
+
return `/* Error importing "${urlSpec}": ${error.message} */`;
|
|
1026
|
+
}
|
|
1027
|
+
})
|
|
1028
|
+
);
|
|
1029
|
+
|
|
1030
|
+
let lastIndex = 0;
|
|
1031
|
+
let result = '';
|
|
1032
|
+
matches.forEach((match, i) => {
|
|
1033
|
+
result += cssText.slice(lastIndex, match.index);
|
|
1034
|
+
result += fetchedContents[i];
|
|
1035
|
+
lastIndex = match.index + match[0].length;
|
|
1036
|
+
});
|
|
1037
|
+
result += cssText.slice(lastIndex);
|
|
1038
|
+
|
|
1039
|
+
return result;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
async function procImp(css) {
|
|
1043
|
+
try {
|
|
1044
|
+
const processedCSS = await processImports(css);
|
|
1045
|
+
return processedCSS;
|
|
1046
|
+
} catch (error) {
|
|
1047
|
+
console.warn('fscss[]\n Processing failed:', error);
|
|
1048
|
+
console.warn(`fscss[@import] Warning: can't resolve imports`);
|
|
1049
|
+
return css;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
function execObj(css){
|
|
1054
|
+
return css.replace(/exec\.obj\.block\([^\)\n]*\)\;?/g, "");
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
export { initlibraries,
|
|
1058
|
+
impSel, procImp, replaceRe, procExt, procVar,procFun, procArr, procEv, procRan, transformCssValues, procNum,applyFscssTransformations,procExC, execObj
|
|
1059
|
+
}
|
|
1060
|
+
|