@feardread/fear 1.0.1
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/FEAR.js +459 -0
- package/FEARServer.js +280 -0
- package/controllers/agent.js +438 -0
- package/controllers/auth/index.js +345 -0
- package/controllers/auth/token.js +50 -0
- package/controllers/blog.js +105 -0
- package/controllers/brand.js +10 -0
- package/controllers/cart.js +425 -0
- package/controllers/category.js +9 -0
- package/controllers/coupon.js +63 -0
- package/controllers/crud/crud.js +508 -0
- package/controllers/crud/index.js +36 -0
- package/controllers/email.js +34 -0
- package/controllers/enquiry.js +65 -0
- package/controllers/events.js +9 -0
- package/controllers/order.js +125 -0
- package/controllers/payment.js +31 -0
- package/controllers/product.js +147 -0
- package/controllers/review.js +247 -0
- package/controllers/tag.js +10 -0
- package/controllers/task.js +10 -0
- package/controllers/upload.js +41 -0
- package/controllers/user.js +401 -0
- package/index.js +7 -0
- package/libs/agent/index.js +561 -0
- package/libs/agent/modules/ai/ai.js +285 -0
- package/libs/agent/modules/ai/chat.js +518 -0
- package/libs/agent/modules/ai/config.js +688 -0
- package/libs/agent/modules/ai/operations.js +787 -0
- package/libs/agent/modules/analyze/api.js +546 -0
- package/libs/agent/modules/analyze/dorks.js +395 -0
- package/libs/agent/modules/ccard/README.md +454 -0
- package/libs/agent/modules/ccard/audit.js +479 -0
- package/libs/agent/modules/ccard/checker.js +674 -0
- package/libs/agent/modules/ccard/payment-processors.json +16 -0
- package/libs/agent/modules/ccard/validator.js +629 -0
- package/libs/agent/modules/code/analyzer.js +303 -0
- package/libs/agent/modules/code/jquery.js +1093 -0
- package/libs/agent/modules/code/react.js +1536 -0
- package/libs/agent/modules/code/refactor.js +499 -0
- package/libs/agent/modules/crypto/exchange.js +564 -0
- package/libs/agent/modules/net/proxy.js +409 -0
- package/libs/agent/modules/security/cve.js +442 -0
- package/libs/agent/modules/security/monitor.js +360 -0
- package/libs/agent/modules/security/scanner.js +300 -0
- package/libs/agent/modules/security/vulnerability.js +506 -0
- package/libs/agent/modules/security/web.js +465 -0
- package/libs/agent/modules/utils/browser.js +492 -0
- package/libs/agent/modules/utils/colorizer.js +285 -0
- package/libs/agent/modules/utils/manager.js +478 -0
- package/libs/cloud/index.js +228 -0
- package/libs/config/db.js +21 -0
- package/libs/config/validator.js +82 -0
- package/libs/db/index.js +318 -0
- package/libs/emailer/imap.js +126 -0
- package/libs/emailer/info.js +41 -0
- package/libs/emailer/smtp.js +77 -0
- package/libs/handler/async.js +3 -0
- package/libs/handler/error.js +66 -0
- package/libs/handler/index.js +161 -0
- package/libs/logger/index.js +49 -0
- package/libs/logger/morgan.js +24 -0
- package/libs/passport/passport.js +109 -0
- package/libs/search/api.js +384 -0
- package/libs/search/features.js +219 -0
- package/libs/search/service.js +64 -0
- package/libs/swagger/config.js +18 -0
- package/libs/swagger/index.js +35 -0
- package/libs/validator/index.js +254 -0
- package/models/blog.js +31 -0
- package/models/brand.js +12 -0
- package/models/cart.js +14 -0
- package/models/category.js +11 -0
- package/models/coupon.js +9 -0
- package/models/customer.js +0 -0
- package/models/enquiry.js +29 -0
- package/models/events.js +13 -0
- package/models/order.js +94 -0
- package/models/product.js +32 -0
- package/models/review.js +14 -0
- package/models/tag.js +10 -0
- package/models/task.js +11 -0
- package/models/user.js +68 -0
- package/package.json +12 -0
- package/routes/agent.js +615 -0
- package/routes/auth.js +13 -0
- package/routes/blog.js +19 -0
- package/routes/brand.js +15 -0
- package/routes/cart.js +105 -0
- package/routes/category.js +16 -0
- package/routes/coupon.js +15 -0
- package/routes/enquiry.js +14 -0
- package/routes/events.js +16 -0
- package/routes/mail.js +170 -0
- package/routes/order.js +19 -0
- package/routes/product.js +22 -0
- package/routes/review.js +11 -0
- package/routes/task.js +12 -0
- package/routes/user.js +17 -0
|
@@ -0,0 +1,1536 @@
|
|
|
1
|
+
// modules/html-to-react.js - Enhanced HTML to React Component Converter
|
|
2
|
+
const fs = require('fs').promises;
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const colorizer = require('../utils/colorizer');
|
|
5
|
+
|
|
6
|
+
const HTMLToReact = function() {
|
|
7
|
+
this.componentCounter = 0;
|
|
8
|
+
this.extractedComponents = [];
|
|
9
|
+
this.externalStylesheets = [];
|
|
10
|
+
this.externalScripts = [];
|
|
11
|
+
this.jsModules = new Map();
|
|
12
|
+
this.globalVariables = new Set();
|
|
13
|
+
this.dependencies = new Set();
|
|
14
|
+
this.helperFunctions = new Set();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
HTMLToReact.prototype = {
|
|
18
|
+
|
|
19
|
+
convert(args) {
|
|
20
|
+
const filePath = args[0];
|
|
21
|
+
const outputDir = args[1] || './react-components';
|
|
22
|
+
|
|
23
|
+
if (!filePath) {
|
|
24
|
+
console.log(colorizer.error('Usage: html-to-react <html-file> [output-dir]'));
|
|
25
|
+
console.log(colorizer.info('Examples:'));
|
|
26
|
+
console.log(colorizer.dim(' html-to-react index.html'));
|
|
27
|
+
console.log(colorizer.dim(' html-to-react page.html ./src/components\n'));
|
|
28
|
+
return Promise.resolve();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
console.log(colorizer.header('HTML to React Converter'));
|
|
32
|
+
console.log(colorizer.separator());
|
|
33
|
+
console.log(colorizer.cyan('Input: ') + colorizer.bright(filePath));
|
|
34
|
+
console.log(colorizer.cyan('Output: ') + colorizer.bright(outputDir));
|
|
35
|
+
console.log();
|
|
36
|
+
|
|
37
|
+
const inputDir = path.dirname(filePath);
|
|
38
|
+
|
|
39
|
+
return fs.readFile(filePath, 'utf8')
|
|
40
|
+
.then(html => {
|
|
41
|
+
console.log(colorizer.info('Parsing HTML...'));
|
|
42
|
+
|
|
43
|
+
const converted = this.convertHTML(html, path.basename(filePath, '.html'), inputDir);
|
|
44
|
+
|
|
45
|
+
return this.saveComponents(converted, outputDir, inputDir);
|
|
46
|
+
})
|
|
47
|
+
.then(files => {
|
|
48
|
+
console.log(colorizer.success('\nConversion complete!'));
|
|
49
|
+
console.log(colorizer.cyan('Generated files:'));
|
|
50
|
+
files.forEach(file => {
|
|
51
|
+
console.log(colorizer.bullet(file));
|
|
52
|
+
});
|
|
53
|
+
console.log();
|
|
54
|
+
|
|
55
|
+
this.printConversionSummary();
|
|
56
|
+
})
|
|
57
|
+
.catch(err => {
|
|
58
|
+
console.log(colorizer.error('Conversion failed: ' + err.message));
|
|
59
|
+
if (err.stack) {
|
|
60
|
+
console.log(colorizer.dim(err.stack));
|
|
61
|
+
}
|
|
62
|
+
console.log();
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
convertHTML(html, baseName, inputDir) {
|
|
67
|
+
this.extractedComponents = [];
|
|
68
|
+
this.externalStylesheets = [];
|
|
69
|
+
this.externalScripts = [];
|
|
70
|
+
this.jsModules.clear();
|
|
71
|
+
this.globalVariables.clear();
|
|
72
|
+
this.dependencies.clear();
|
|
73
|
+
this.helperFunctions.clear();
|
|
74
|
+
|
|
75
|
+
this.extractExternalResources(html, inputDir);
|
|
76
|
+
html = this.cleanHTML(html);
|
|
77
|
+
|
|
78
|
+
const mainComponent = this.createMainComponent(html, baseName);
|
|
79
|
+
|
|
80
|
+
mainComponent.jsx = this.convertInlineStyles(mainComponent.jsx);
|
|
81
|
+
mainComponent.jsx = this.convertAttributes(mainComponent.jsx);
|
|
82
|
+
mainComponent.jsx = this.convertEventHandlers(mainComponent.jsx);
|
|
83
|
+
mainComponent.jsx = this.fixJSXIssues(mainComponent.jsx);
|
|
84
|
+
|
|
85
|
+
this.extractComponents(mainComponent);
|
|
86
|
+
|
|
87
|
+
if (this.externalStylesheets.length > 0) {
|
|
88
|
+
mainComponent.externalStyles = this.externalStylesheets;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (this.externalScripts.length > 0) {
|
|
92
|
+
mainComponent.externalScripts = this.externalScripts;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
mainComponent.dependencies = Array.from(this.dependencies);
|
|
96
|
+
mainComponent.helperFunctions = Array.from(this.helperFunctions);
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
main: mainComponent,
|
|
100
|
+
components: this.extractedComponents,
|
|
101
|
+
jsModules: Array.from(this.jsModules.values())
|
|
102
|
+
};
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
extractExternalResources(html, inputDir) {
|
|
106
|
+
const linkRegex = /<link[^>]+rel=["']stylesheet["'][^>]*href=["']([^"']+)["'][^>]*>/gi;
|
|
107
|
+
let match;
|
|
108
|
+
|
|
109
|
+
while ((match = linkRegex.exec(html)) !== null) {
|
|
110
|
+
const href = match[1];
|
|
111
|
+
if (!href.startsWith('http://') && !href.startsWith('https://') && !href.startsWith('//')) {
|
|
112
|
+
this.externalStylesheets.push({
|
|
113
|
+
original: href,
|
|
114
|
+
local: path.join(inputDir, href)
|
|
115
|
+
});
|
|
116
|
+
console.log(colorizer.info(' Found stylesheet: ' + href));
|
|
117
|
+
} else {
|
|
118
|
+
console.log(colorizer.warning(' Skipping external stylesheet: ' + href));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const scriptRegex = /<script[^>]*src=["']([^"']+)["'][^>]*>[\s\S]*?<\/script>/gi;
|
|
123
|
+
|
|
124
|
+
while ((match = scriptRegex.exec(html)) !== null) {
|
|
125
|
+
const src = match[1];
|
|
126
|
+
const scriptTag = match[0];
|
|
127
|
+
const isModule = /type=["']module["']/i.test(scriptTag);
|
|
128
|
+
const isDefer = /defer/i.test(scriptTag);
|
|
129
|
+
const isAsync = /async/i.test(scriptTag);
|
|
130
|
+
|
|
131
|
+
if (!src.startsWith('http://') && !src.startsWith('https://') && !src.startsWith('//')) {
|
|
132
|
+
this.externalScripts.push({
|
|
133
|
+
original: src,
|
|
134
|
+
local: path.join(inputDir, src),
|
|
135
|
+
isModule,
|
|
136
|
+
isDefer,
|
|
137
|
+
isAsync
|
|
138
|
+
});
|
|
139
|
+
console.log(colorizer.info(' Found script: ' + src + (isModule ? ' (module)' : '')));
|
|
140
|
+
} else {
|
|
141
|
+
this.detectLibrary(src);
|
|
142
|
+
console.log(colorizer.warning(' Skipping CDN script: ' + src));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
detectLibrary(src) {
|
|
148
|
+
const libraries = {
|
|
149
|
+
'jquery': 'jquery',
|
|
150
|
+
'lodash': 'lodash',
|
|
151
|
+
'axios': 'axios',
|
|
152
|
+
'moment': 'moment',
|
|
153
|
+
'chart': 'chart.js',
|
|
154
|
+
'three': 'three',
|
|
155
|
+
'd3': 'd3',
|
|
156
|
+
'gsap': 'gsap'
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
for (const [key, pkg] of Object.entries(libraries)) {
|
|
160
|
+
if (src.toLowerCase().includes(key)) {
|
|
161
|
+
this.dependencies.add(pkg);
|
|
162
|
+
console.log(colorizer.info(' Detected library: ' + pkg));
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
cleanHTML(html) {
|
|
169
|
+
html = html.replace(/<!DOCTYPE[^>]*>/gi, '');
|
|
170
|
+
html = html.replace(/<html[^>]*>/gi, '');
|
|
171
|
+
html = html.replace(/<\/html>/gi, '');
|
|
172
|
+
html = html.replace(/<head[^>]*>[\s\S]*?<\/head>/gi, '');
|
|
173
|
+
html = html.replace(/<body[^>]*>/gi, '');
|
|
174
|
+
html = html.replace(/<\/body>/gi, '');
|
|
175
|
+
html = html.replace(/<!--(?!\[if)[\s\S]*?-->/g, '');
|
|
176
|
+
html = html.replace(/<link[^>]+>/gi, '');
|
|
177
|
+
|
|
178
|
+
return html.trim();
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
createMainComponent(html, baseName) {
|
|
182
|
+
const componentName = this.toPascalCase(baseName);
|
|
183
|
+
|
|
184
|
+
const styleMatches = html.match(/<style[^>]*>([\s\S]*?)<\/style>/gi);
|
|
185
|
+
let css = '';
|
|
186
|
+
|
|
187
|
+
if (styleMatches) {
|
|
188
|
+
styleMatches.forEach(match => {
|
|
189
|
+
const content = match.replace(/<\/?style[^>]*>/gi, '');
|
|
190
|
+
css += content + '\n';
|
|
191
|
+
});
|
|
192
|
+
html = html.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const scriptMatches = html.match(/<script(?![^>]*src=)[^>]*>([\s\S]*?)<\/script>/gi);
|
|
196
|
+
const hooks = [];
|
|
197
|
+
const functions = [];
|
|
198
|
+
const effects = [];
|
|
199
|
+
const utilities = [];
|
|
200
|
+
|
|
201
|
+
if (scriptMatches) {
|
|
202
|
+
scriptMatches.forEach(match => {
|
|
203
|
+
const jsContent = match.replace(/<\/?script[^>]*>/gi, '');
|
|
204
|
+
if (jsContent.trim()) {
|
|
205
|
+
const converted = this.convertJavaScriptToReact(jsContent);
|
|
206
|
+
|
|
207
|
+
if (converted.hooks.length > 0) {
|
|
208
|
+
hooks.push(...converted.hooks);
|
|
209
|
+
}
|
|
210
|
+
if (converted.functions.length > 0) {
|
|
211
|
+
functions.push(...converted.functions);
|
|
212
|
+
}
|
|
213
|
+
if (converted.effects.length > 0) {
|
|
214
|
+
effects.push(...converted.effects);
|
|
215
|
+
}
|
|
216
|
+
if (converted.utilities.length > 0) {
|
|
217
|
+
utilities.push(...converted.utilities);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
html = html.replace(/<script(?![^>]*src=)[^>]*>[\s\S]*?<\/script>/gi, '');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
name: componentName,
|
|
226
|
+
jsx: html,
|
|
227
|
+
css: css,
|
|
228
|
+
imports: this.determineRequiredImports(hooks, functions, effects, utilities),
|
|
229
|
+
hooks: hooks,
|
|
230
|
+
functions: functions,
|
|
231
|
+
effects: effects,
|
|
232
|
+
utilities: utilities
|
|
233
|
+
};
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
determineRequiredImports(hooks, functions, effects, utilities) {
|
|
237
|
+
const imports = new Set(['React']);
|
|
238
|
+
|
|
239
|
+
if (hooks.length > 0 || hooks.some(h => h.includes('useState'))) {
|
|
240
|
+
imports.add('useState');
|
|
241
|
+
}
|
|
242
|
+
if (effects.length > 0 || hooks.some(h => h.includes('useEffect'))) {
|
|
243
|
+
imports.add('useEffect');
|
|
244
|
+
}
|
|
245
|
+
if (hooks.some(h => h.includes('useRef')) || functions.some(f => f.body && f.body.includes('ref'))) {
|
|
246
|
+
imports.add('useRef');
|
|
247
|
+
}
|
|
248
|
+
if (hooks.some(h => h.includes('useCallback')) || functions.some(f => f.useCallback)) {
|
|
249
|
+
imports.add('useCallback');
|
|
250
|
+
}
|
|
251
|
+
if (hooks.some(h => h.includes('useMemo'))) {
|
|
252
|
+
imports.add('useMemo');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return Array.from(imports);
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
convertJavaScriptToReact(jsCode) {
|
|
259
|
+
const hooks = [];
|
|
260
|
+
const functions = [];
|
|
261
|
+
const effects = [];
|
|
262
|
+
const utilities = [];
|
|
263
|
+
|
|
264
|
+
const analysis = this.analyzeJavaScript(jsCode);
|
|
265
|
+
|
|
266
|
+
// Convert variables to state
|
|
267
|
+
analysis.variables.forEach(variable => {
|
|
268
|
+
if (!this.isConstant(variable.name) && this.needsState(variable, jsCode)) {
|
|
269
|
+
const initialValue = variable.value || 'null';
|
|
270
|
+
const stateVar = `const [${variable.name}, set${this.toPascalCase(variable.name)}] = useState(${initialValue});`;
|
|
271
|
+
hooks.push(stateVar);
|
|
272
|
+
this.globalVariables.add(variable.name);
|
|
273
|
+
console.log(colorizer.info(' Converting variable to state: ' + variable.name));
|
|
274
|
+
} else {
|
|
275
|
+
hooks.push(`const ${variable.name} = ${variable.value};`);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// Convert DOM ready handlers
|
|
280
|
+
if (this.hasDOMReadyHandler(jsCode)) {
|
|
281
|
+
const effectCode = this.extractDOMReadyCode(jsCode);
|
|
282
|
+
if (effectCode) {
|
|
283
|
+
effects.push({
|
|
284
|
+
code: effectCode,
|
|
285
|
+
dependencies: '[]',
|
|
286
|
+
comment: 'Component mount effect (converted from DOM ready)'
|
|
287
|
+
});
|
|
288
|
+
console.log(colorizer.info(' Converting DOM ready to useEffect'));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Convert event listeners
|
|
293
|
+
const listeners = this.extractEventListeners(jsCode);
|
|
294
|
+
listeners.forEach(listener => {
|
|
295
|
+
effects.push({
|
|
296
|
+
code: this.convertEventListenerToEffect(listener),
|
|
297
|
+
dependencies: listener.dependencies || '[]',
|
|
298
|
+
comment: `Event listener for ${listener.event}`
|
|
299
|
+
});
|
|
300
|
+
console.log(colorizer.info(' Converting event listener: ' + listener.event));
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// Extract functions with better handling
|
|
304
|
+
const functionMatches = this.extractFunctions(jsCode);
|
|
305
|
+
functionMatches.forEach(func => {
|
|
306
|
+
const convertedFunc = this.convertFunctionToReact(func);
|
|
307
|
+
functions.push(convertedFunc);
|
|
308
|
+
console.log(colorizer.info(' Extracting function: ' + func.name));
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// Extract utility functions that can be outside component
|
|
312
|
+
const utilityFuncs = this.extractUtilityFunctions(jsCode);
|
|
313
|
+
utilityFuncs.forEach(util => {
|
|
314
|
+
utilities.push(util);
|
|
315
|
+
this.helperFunctions.add(util.name);
|
|
316
|
+
console.log(colorizer.info(' Extracting utility function: ' + util.name));
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// Extract classes
|
|
320
|
+
const classes = this.extractClasses(jsCode);
|
|
321
|
+
if (classes.length > 0) {
|
|
322
|
+
console.log(colorizer.warning(' Found ' + classes.length + ' class(es) - consider refactoring to hooks'));
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return { hooks, functions, effects, utilities };
|
|
326
|
+
},
|
|
327
|
+
|
|
328
|
+
needsState(variable, code) {
|
|
329
|
+
// Check if variable is reassigned in the code
|
|
330
|
+
const reassignmentRegex = new RegExp(`\\b${variable.name}\\s*=`, 'g');
|
|
331
|
+
const matches = code.match(reassignmentRegex);
|
|
332
|
+
return matches && matches.length > 1; // More than initial assignment
|
|
333
|
+
},
|
|
334
|
+
|
|
335
|
+
hasDOMReadyHandler(jsCode) {
|
|
336
|
+
return jsCode.includes('DOMContentLoaded') ||
|
|
337
|
+
jsCode.includes('window.onload') ||
|
|
338
|
+
jsCode.includes('$(document).ready') ||
|
|
339
|
+
jsCode.includes('$(function');
|
|
340
|
+
},
|
|
341
|
+
|
|
342
|
+
analyzeJavaScript(code) {
|
|
343
|
+
const variables = [];
|
|
344
|
+
|
|
345
|
+
const varRegex = /(let|var|const)\s+(\w+)\s*=\s*([^;]+);/g;
|
|
346
|
+
let match;
|
|
347
|
+
|
|
348
|
+
while ((match = varRegex.exec(code)) !== null) {
|
|
349
|
+
const [, type, name, value] = match;
|
|
350
|
+
variables.push({ type, name, value: value.trim() });
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return { variables };
|
|
354
|
+
},
|
|
355
|
+
|
|
356
|
+
isConstant(varName) {
|
|
357
|
+
return /^[A-Z_]+$/.test(varName) ||
|
|
358
|
+
varName.startsWith('CONFIG') ||
|
|
359
|
+
varName.startsWith('DEFAULT') ||
|
|
360
|
+
varName.startsWith('CONST');
|
|
361
|
+
},
|
|
362
|
+
|
|
363
|
+
extractDOMReadyCode(jsCode) {
|
|
364
|
+
let code = jsCode;
|
|
365
|
+
|
|
366
|
+
// Remove various DOM ready wrappers
|
|
367
|
+
code = code.replace(/document\.addEventListener\s*\(\s*['"]DOMContentLoaded['"]\s*,\s*function\s*\(\)\s*\{/g, '');
|
|
368
|
+
code = code.replace(/document\.addEventListener\s*\(\s*['"]DOMContentLoaded['"]\s*,\s*\(\)\s*=>\s*\{/g, '');
|
|
369
|
+
code = code.replace(/window\.onload\s*=\s*function\s*\(\)\s*\{/g, '');
|
|
370
|
+
code = code.replace(/window\.onload\s*=\s*\(\)\s*=>\s*\{/g, '');
|
|
371
|
+
code = code.replace(/\$\(document\)\.ready\s*\(\s*function\s*\(\)\s*\{/g, '');
|
|
372
|
+
code = code.replace(/\$\(function\s*\(\)\s*\{/g, '');
|
|
373
|
+
|
|
374
|
+
// Remove closing braces
|
|
375
|
+
code = code.replace(/\}\s*\)\s*;?\s*$/g, '');
|
|
376
|
+
code = code.replace(/\}\s*;?\s*$/g, '');
|
|
377
|
+
|
|
378
|
+
return this.cleanJSCode(code);
|
|
379
|
+
},
|
|
380
|
+
|
|
381
|
+
extractEventListeners(jsCode) {
|
|
382
|
+
const listeners = [];
|
|
383
|
+
|
|
384
|
+
// Standard addEventListener
|
|
385
|
+
const listenerRegex = /(\w+)\.addEventListener\s*\(\s*['"](\w+)['"]\s*,\s*((?:function[^}]+\}|\(\)[^}]+\}|\w+))\s*\)/g;
|
|
386
|
+
let match;
|
|
387
|
+
|
|
388
|
+
while ((match = listenerRegex.exec(jsCode)) !== null) {
|
|
389
|
+
const [, element, event, handler] = match;
|
|
390
|
+
listeners.push({
|
|
391
|
+
element,
|
|
392
|
+
event,
|
|
393
|
+
handler,
|
|
394
|
+
dependencies: this.extractDependencies(handler)
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// jQuery event listeners
|
|
399
|
+
const jqueryRegex = /\$\([^)]+\)\.(on|click|change|submit)\s*\(\s*['"]?(\w+)?['"]?\s*,?\s*(function[^}]+\}|\(\)[^}]+\})\s*\)/g;
|
|
400
|
+
|
|
401
|
+
while ((match = jqueryRegex.exec(jsCode)) !== null) {
|
|
402
|
+
const [, method, event, handler] = match;
|
|
403
|
+
listeners.push({
|
|
404
|
+
element: 'element',
|
|
405
|
+
event: event || method,
|
|
406
|
+
handler,
|
|
407
|
+
dependencies: this.extractDependencies(handler),
|
|
408
|
+
isJQuery: true
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return listeners;
|
|
413
|
+
},
|
|
414
|
+
|
|
415
|
+
extractDependencies(code) {
|
|
416
|
+
const vars = new Set();
|
|
417
|
+
const varRegex = /\b([a-z_$][a-z0-9_$]*)\b/gi;
|
|
418
|
+
let match;
|
|
419
|
+
|
|
420
|
+
const reserved = ['function', 'return', 'const', 'let', 'var', 'if', 'else',
|
|
421
|
+
'for', 'while', 'true', 'false', 'null', 'undefined',
|
|
422
|
+
'this', 'window', 'document', 'console', 'e', 'event'];
|
|
423
|
+
|
|
424
|
+
while ((match = varRegex.exec(code)) !== null) {
|
|
425
|
+
const varName = match[1];
|
|
426
|
+
if (!reserved.includes(varName) && this.globalVariables.has(varName)) {
|
|
427
|
+
vars.add(varName);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return Array.from(vars).length > 0 ? `[${Array.from(vars).join(', ')}]` : '[]';
|
|
432
|
+
},
|
|
433
|
+
|
|
434
|
+
convertEventListenerToEffect(listener) {
|
|
435
|
+
const handlerName = `handle${this.toPascalCase(listener.event)}`;
|
|
436
|
+
let code = '';
|
|
437
|
+
|
|
438
|
+
if (listener.isJQuery) {
|
|
439
|
+
code = `// TODO: Replace jQuery selector with ref\n`;
|
|
440
|
+
code += `const ${handlerName} = ${listener.handler};\n`;
|
|
441
|
+
code += `// $(element).on('${listener.event}', ${handlerName});\n`;
|
|
442
|
+
code += `return () => {}; // Add cleanup`;
|
|
443
|
+
} else {
|
|
444
|
+
code = `const ${handlerName} = ${listener.handler};\n`;
|
|
445
|
+
code += `const element = ${listener.element}; // TODO: Use ref\n`;
|
|
446
|
+
code += `if (element) {\n`;
|
|
447
|
+
code += ` element.addEventListener('${listener.event}', ${handlerName});\n`;
|
|
448
|
+
code += ` return () => element.removeEventListener('${listener.event}', ${handlerName});\n`;
|
|
449
|
+
code += `}`;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return code;
|
|
453
|
+
},
|
|
454
|
+
|
|
455
|
+
extractFunctions(jsCode) {
|
|
456
|
+
const functions = [];
|
|
457
|
+
|
|
458
|
+
// Regular function declarations
|
|
459
|
+
const functionRegex = /(async\s+)?function\s+(\w+)\s*\(([^)]*)\)\s*\{([\s\S]*?)\n\}/g;
|
|
460
|
+
let match;
|
|
461
|
+
|
|
462
|
+
while ((match = functionRegex.exec(jsCode)) !== null) {
|
|
463
|
+
const [, asyncKeyword, name, params, body] = match;
|
|
464
|
+
functions.push({
|
|
465
|
+
name,
|
|
466
|
+
params: params.trim(),
|
|
467
|
+
body: body.trim(),
|
|
468
|
+
isAsync: !!asyncKeyword,
|
|
469
|
+
type: 'function'
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Arrow functions
|
|
474
|
+
const arrowRegex = /(const|let|var)\s+(\w+)\s*=\s*(async\s+)?\(([^)]*)\)\s*=>\s*\{([\s\S]*?)\n\}/g;
|
|
475
|
+
|
|
476
|
+
while ((match = arrowRegex.exec(jsCode)) !== null) {
|
|
477
|
+
const [, , name, asyncKeyword, params, body] = match;
|
|
478
|
+
functions.push({
|
|
479
|
+
name,
|
|
480
|
+
params: params.trim(),
|
|
481
|
+
body: body.trim(),
|
|
482
|
+
isAsync: !!asyncKeyword,
|
|
483
|
+
type: 'arrow'
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Single-line arrow functions
|
|
488
|
+
const shortArrowRegex = /(const|let|var)\s+(\w+)\s*=\s*(async\s+)?\(([^)]*)\)\s*=>\s*([^;{]+);/g;
|
|
489
|
+
|
|
490
|
+
while ((match = shortArrowRegex.exec(jsCode)) !== null) {
|
|
491
|
+
const [, , name, asyncKeyword, params, body] = match;
|
|
492
|
+
functions.push({
|
|
493
|
+
name,
|
|
494
|
+
params: params.trim(),
|
|
495
|
+
body: `return ${body.trim()};`,
|
|
496
|
+
isAsync: !!asyncKeyword,
|
|
497
|
+
type: 'arrow',
|
|
498
|
+
isShort: true
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return functions;
|
|
503
|
+
},
|
|
504
|
+
|
|
505
|
+
extractUtilityFunctions(jsCode) {
|
|
506
|
+
const utilities = [];
|
|
507
|
+
|
|
508
|
+
// Functions that are pure utilities (no DOM, no state dependencies)
|
|
509
|
+
const functionRegex = /function\s+(\w+)\s*\(([^)]*)\)\s*\{([\s\S]*?)\n\}/g;
|
|
510
|
+
let match;
|
|
511
|
+
|
|
512
|
+
while ((match = functionRegex.exec(jsCode)) !== null) {
|
|
513
|
+
const [, name, params, body] = match;
|
|
514
|
+
|
|
515
|
+
// Check if it's a pure utility (no DOM access, no external state)
|
|
516
|
+
if (this.isPureUtility(body)) {
|
|
517
|
+
utilities.push({
|
|
518
|
+
name,
|
|
519
|
+
params: params.trim(),
|
|
520
|
+
body: body.trim(),
|
|
521
|
+
comment: 'Pure utility function'
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
return utilities;
|
|
527
|
+
},
|
|
528
|
+
|
|
529
|
+
isPureUtility(code) {
|
|
530
|
+
// Check if function uses DOM or external state
|
|
531
|
+
const domPatterns = ['document.', 'window.', 'getElementById', 'querySelector', '$'];
|
|
532
|
+
const hasDOM = domPatterns.some(pattern => code.includes(pattern));
|
|
533
|
+
|
|
534
|
+
// Check if it only does computations
|
|
535
|
+
const isComputation = /^[\s\S]*return\s+/.test(code.trim());
|
|
536
|
+
|
|
537
|
+
return !hasDOM && isComputation;
|
|
538
|
+
},
|
|
539
|
+
|
|
540
|
+
extractClasses(jsCode) {
|
|
541
|
+
const classes = [];
|
|
542
|
+
const classRegex = /class\s+(\w+)(?:\s+extends\s+(\w+))?\s*\{([\s\S]*?)\n\}/g;
|
|
543
|
+
let match;
|
|
544
|
+
|
|
545
|
+
while ((match = classRegex.exec(jsCode)) !== null) {
|
|
546
|
+
const [, name, parentClass, body] = match;
|
|
547
|
+
classes.push({ name, parentClass, body });
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
return classes;
|
|
551
|
+
},
|
|
552
|
+
|
|
553
|
+
convertFunctionToReact(func) {
|
|
554
|
+
let converted = {
|
|
555
|
+
name: func.name,
|
|
556
|
+
params: func.params,
|
|
557
|
+
body: func.body,
|
|
558
|
+
isAsync: func.isAsync,
|
|
559
|
+
useCallback: false
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
// Convert DOM manipulation
|
|
563
|
+
converted.body = this.convertDOMManipulation(converted.body);
|
|
564
|
+
|
|
565
|
+
// Convert jQuery calls
|
|
566
|
+
converted.body = this.convertJQueryToReact(converted.body);
|
|
567
|
+
|
|
568
|
+
// Convert AJAX/fetch calls
|
|
569
|
+
converted.body = this.convertAsyncCalls(converted.body);
|
|
570
|
+
|
|
571
|
+
// Detect if function should use useCallback (if it depends on state)
|
|
572
|
+
if (this.shouldUseCallback(converted.body)) {
|
|
573
|
+
converted.useCallback = true;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
return converted;
|
|
577
|
+
},
|
|
578
|
+
|
|
579
|
+
convertDOMManipulation(code) {
|
|
580
|
+
let converted = code;
|
|
581
|
+
|
|
582
|
+
// getElementById
|
|
583
|
+
converted = converted.replace(
|
|
584
|
+
/document\.getElementById\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
585
|
+
(match, id) => {
|
|
586
|
+
this.dependencies.add('useRef');
|
|
587
|
+
return `${this.toCamelCase(id)}Ref.current`;
|
|
588
|
+
}
|
|
589
|
+
);
|
|
590
|
+
|
|
591
|
+
// querySelector
|
|
592
|
+
converted = converted.replace(
|
|
593
|
+
/document\.querySelector\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
594
|
+
(match, selector) => {
|
|
595
|
+
const refName = this.selectorToRefName(selector);
|
|
596
|
+
this.dependencies.add('useRef');
|
|
597
|
+
return `${refName}Ref.current`;
|
|
598
|
+
}
|
|
599
|
+
);
|
|
600
|
+
|
|
601
|
+
// innerHTML - suggest dangerouslySetInnerHTML
|
|
602
|
+
converted = converted.replace(
|
|
603
|
+
/(\w+)\.innerHTML\s*=\s*(['"`])(.*?)\2/g,
|
|
604
|
+
(match, element, quote, content) => {
|
|
605
|
+
return `// TODO: Use state or <div dangerouslySetInnerHTML={{__html: ${quote}${content}${quote}}} />\n// ${match}`;
|
|
606
|
+
}
|
|
607
|
+
);
|
|
608
|
+
|
|
609
|
+
// textContent - suggest state
|
|
610
|
+
converted = converted.replace(
|
|
611
|
+
/(\w+)\.textContent\s*=\s*(.+);/g,
|
|
612
|
+
'// TODO: Use state to update text\n// $1.textContent = $2;'
|
|
613
|
+
);
|
|
614
|
+
|
|
615
|
+
// classList operations
|
|
616
|
+
converted = converted.replace(
|
|
617
|
+
/(\w+)\.classList\.(add|remove|toggle)\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
618
|
+
(match, element, operation, className) => {
|
|
619
|
+
return `// TODO: Use className state or conditional className\n// ${match}`;
|
|
620
|
+
}
|
|
621
|
+
);
|
|
622
|
+
|
|
623
|
+
return converted;
|
|
624
|
+
},
|
|
625
|
+
|
|
626
|
+
convertJQueryToReact(code) {
|
|
627
|
+
let converted = code;
|
|
628
|
+
|
|
629
|
+
// jQuery selectors
|
|
630
|
+
converted = converted.replace(
|
|
631
|
+
/\$\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
632
|
+
(match, selector) => {
|
|
633
|
+
const refName = this.selectorToRefName(selector);
|
|
634
|
+
return `${refName}Ref.current /* TODO: Create ref */`;
|
|
635
|
+
}
|
|
636
|
+
);
|
|
637
|
+
|
|
638
|
+
// jQuery .text()
|
|
639
|
+
converted = converted.replace(
|
|
640
|
+
/\$\([^)]+\)\.text\s*\(\s*([^)]+)\s*\)/g,
|
|
641
|
+
'/* TODO: Use state */ // jQuery .text($1)'
|
|
642
|
+
);
|
|
643
|
+
|
|
644
|
+
// jQuery .html()
|
|
645
|
+
converted = converted.replace(
|
|
646
|
+
/\$\([^)]+\)\.html\s*\(\s*([^)]+)\s*\)/g,
|
|
647
|
+
'/* TODO: Use dangerouslySetInnerHTML or state */ // jQuery .html($1)'
|
|
648
|
+
);
|
|
649
|
+
|
|
650
|
+
// jQuery .val()
|
|
651
|
+
converted = converted.replace(
|
|
652
|
+
/\$\([^)]+\)\.val\s*\(\s*\)/g,
|
|
653
|
+
'/* TODO: Use controlled input with state */ // jQuery .val()'
|
|
654
|
+
);
|
|
655
|
+
|
|
656
|
+
// jQuery AJAX
|
|
657
|
+
converted = converted.replace(
|
|
658
|
+
/\$\.ajax\s*\(/g,
|
|
659
|
+
'/* TODO: Use fetch or axios */ // $.ajax('
|
|
660
|
+
);
|
|
661
|
+
|
|
662
|
+
converted = converted.replace(
|
|
663
|
+
/\$\.get\s*\(/g,
|
|
664
|
+
'/* TODO: Use fetch */ // $.get('
|
|
665
|
+
);
|
|
666
|
+
|
|
667
|
+
converted = converted.replace(
|
|
668
|
+
/\$\.post\s*\(/g,
|
|
669
|
+
'/* TODO: Use fetch with POST */ // $.post('
|
|
670
|
+
);
|
|
671
|
+
|
|
672
|
+
return converted;
|
|
673
|
+
},
|
|
674
|
+
|
|
675
|
+
convertAsyncCalls(code) {
|
|
676
|
+
let converted = code;
|
|
677
|
+
|
|
678
|
+
// Suggest useEffect for fetch
|
|
679
|
+
if (converted.includes('fetch(')) {
|
|
680
|
+
converted = converted.replace(
|
|
681
|
+
/(fetch\s*\([^)]+\))/g,
|
|
682
|
+
'/* Consider wrapping in useEffect */ $1'
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// XMLHttpRequest
|
|
687
|
+
if (converted.includes('XMLHttpRequest')) {
|
|
688
|
+
converted = '/* TODO: Replace XMLHttpRequest with fetch or axios */\n' + converted;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
return converted;
|
|
692
|
+
},
|
|
693
|
+
|
|
694
|
+
shouldUseCallback(body) {
|
|
695
|
+
// If function accesses state variables, it should use useCallback
|
|
696
|
+
let needsCallback = false;
|
|
697
|
+
|
|
698
|
+
this.globalVariables.forEach(varName => {
|
|
699
|
+
if (body.includes(varName)) {
|
|
700
|
+
needsCallback = true;
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
return needsCallback;
|
|
705
|
+
},
|
|
706
|
+
|
|
707
|
+
selectorToRefName(selector) {
|
|
708
|
+
// Convert CSS selector to a valid ref name
|
|
709
|
+
return selector
|
|
710
|
+
.replace(/^[#.]/, '')
|
|
711
|
+
.replace(/[^a-zA-Z0-9]/g, '_')
|
|
712
|
+
.replace(/_+/g, '_')
|
|
713
|
+
.replace(/^_|_$/g, '');
|
|
714
|
+
},
|
|
715
|
+
|
|
716
|
+
cleanJSCode(code) {
|
|
717
|
+
return code
|
|
718
|
+
.split('\n')
|
|
719
|
+
.map(line => line.trim())
|
|
720
|
+
.filter(line => line)
|
|
721
|
+
.join('\n ');
|
|
722
|
+
},
|
|
723
|
+
|
|
724
|
+
convertAttributes(jsx) {
|
|
725
|
+
jsx = jsx.replace(/\sclass=/gi, ' className=');
|
|
726
|
+
jsx = jsx.replace(/\sfor=/gi, ' htmlFor=');
|
|
727
|
+
|
|
728
|
+
const events = {
|
|
729
|
+
'onclick': 'onClick',
|
|
730
|
+
'onchange': 'onChange',
|
|
731
|
+
'onsubmit': 'onSubmit',
|
|
732
|
+
'onmouseover': 'onMouseOver',
|
|
733
|
+
'onmouseout': 'onMouseOut',
|
|
734
|
+
'onmousedown': 'onMouseDown',
|
|
735
|
+
'onmouseup': 'onMouseUp',
|
|
736
|
+
'onmousemove': 'onMouseMove',
|
|
737
|
+
'onkeydown': 'onKeyDown',
|
|
738
|
+
'onkeyup': 'onKeyUp',
|
|
739
|
+
'onkeypress': 'onKeyPress',
|
|
740
|
+
'onfocus': 'onFocus',
|
|
741
|
+
'onblur': 'onBlur',
|
|
742
|
+
'oninput': 'onInput',
|
|
743
|
+
'onscroll': 'onScroll',
|
|
744
|
+
'onload': 'onLoad',
|
|
745
|
+
'onerror': 'onError',
|
|
746
|
+
'ondblclick': 'onDoubleClick',
|
|
747
|
+
'oncontextmenu': 'onContextMenu'
|
|
748
|
+
};
|
|
749
|
+
|
|
750
|
+
Object.entries(events).forEach(([html, react]) => {
|
|
751
|
+
const regex = new RegExp(html + '=', 'gi');
|
|
752
|
+
jsx = jsx.replace(regex, react + '=');
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
jsx = jsx.replace(/\s(checked|disabled|readonly|required|selected|autofocus|autoplay|controls|loop|muted)(?=\s|>|\/)/gi, ' $1={true}');
|
|
756
|
+
jsx = jsx.replace(/<(img|input|br|hr|meta|link|source|track|embed|area|base|col|param)([^>]*)>/gi, '<$1$2 />');
|
|
757
|
+
|
|
758
|
+
// Fix attribute casing
|
|
759
|
+
const attributeMap = {
|
|
760
|
+
'charset': 'charSet',
|
|
761
|
+
'maxlength': 'maxLength',
|
|
762
|
+
'minlength': 'minLength',
|
|
763
|
+
'readonly': 'readOnly',
|
|
764
|
+
'autofocus': 'autoFocus',
|
|
765
|
+
'autocomplete': 'autoComplete',
|
|
766
|
+
'novalidate': 'noValidate',
|
|
767
|
+
'frameborder': 'frameBorder',
|
|
768
|
+
'cellpadding': 'cellPadding',
|
|
769
|
+
'cellspacing': 'cellSpacing',
|
|
770
|
+
'rowspan': 'rowSpan',
|
|
771
|
+
'colspan': 'colSpan',
|
|
772
|
+
'tabindex': 'tabIndex'
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
Object.entries(attributeMap).forEach(([html, react]) => {
|
|
776
|
+
const regex = new RegExp('\\s' + html + '=', 'gi');
|
|
777
|
+
jsx = jsx.replace(regex, ' ' + react + '=');
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
return jsx;
|
|
781
|
+
},
|
|
782
|
+
|
|
783
|
+
convertEventHandlers(jsx) {
|
|
784
|
+
const eventRegex = /on([A-Z]\w+)=["']([^"']+)["']/g;
|
|
785
|
+
|
|
786
|
+
return jsx.replace(eventRegex, (match, eventName, handler) => {
|
|
787
|
+
let cleanHandler = handler
|
|
788
|
+
.replace(/this\./g, '')
|
|
789
|
+
.replace(/;$/, '')
|
|
790
|
+
.trim();
|
|
791
|
+
|
|
792
|
+
if (cleanHandler.includes('(')) {
|
|
793
|
+
return `on${eventName}={() => ${cleanHandler}}`;
|
|
794
|
+
} else {
|
|
795
|
+
return `on${eventName}={${cleanHandler}}`;
|
|
796
|
+
}
|
|
797
|
+
});
|
|
798
|
+
},
|
|
799
|
+
|
|
800
|
+
convertInlineStyles(jsx) {
|
|
801
|
+
const styleRegex = /style=["']([^"']*)["']/gi;
|
|
802
|
+
|
|
803
|
+
return jsx.replace(styleRegex, (match, styleContent) => {
|
|
804
|
+
if (!styleContent.trim()) {
|
|
805
|
+
return '';
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
try {
|
|
809
|
+
const styleObj = this.parseInlineStyle(styleContent);
|
|
810
|
+
const styleString = JSON.stringify(styleObj, null, 0)
|
|
811
|
+
.replace(/"/g, "'");
|
|
812
|
+
return `style={${styleString}}`;
|
|
813
|
+
} catch (e) {
|
|
814
|
+
console.log(colorizer.warning(' Could not parse inline style: ' + styleContent));
|
|
815
|
+
return match;
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
},
|
|
819
|
+
|
|
820
|
+
parseInlineStyle(styleString) {
|
|
821
|
+
const styles = {};
|
|
822
|
+
const declarations = styleString.split(';').filter(s => s.trim());
|
|
823
|
+
|
|
824
|
+
declarations.forEach(decl => {
|
|
825
|
+
const colonIndex = decl.indexOf(':');
|
|
826
|
+
if (colonIndex === -1) return;
|
|
827
|
+
|
|
828
|
+
const property = decl.substring(0, colonIndex).trim();
|
|
829
|
+
const value = decl.substring(colonIndex + 1).trim();
|
|
830
|
+
|
|
831
|
+
if (property && value) {
|
|
832
|
+
const camelProperty = this.toCamelCase(property);
|
|
833
|
+
const numericValue = this.parseStyleValue(value);
|
|
834
|
+
styles[camelProperty] = numericValue;
|
|
835
|
+
}
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
return styles;
|
|
839
|
+
},
|
|
840
|
+
|
|
841
|
+
parseStyleValue(value) {
|
|
842
|
+
if (value.includes('px') || value.includes('%') || value.includes('em') ||
|
|
843
|
+
value.includes('rem') || value.includes('vh') || value.includes('vw')) {
|
|
844
|
+
return value;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
if (/^\d+$/.test(value)) {
|
|
848
|
+
return parseInt(value);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
return value;
|
|
852
|
+
},
|
|
853
|
+
|
|
854
|
+
fixJSXIssues(jsx) {
|
|
855
|
+
jsx = jsx.replace(/>\s*\{(?!\s*\/?\w)/g, '>{"{"}');
|
|
856
|
+
jsx = jsx.replace(/(?<!\w)\}\s*</g, '{"}"}}<');
|
|
857
|
+
jsx = jsx.replace(/ /g, '{" "}');
|
|
858
|
+
jsx = jsx.replace(/</g, '{"<"}');
|
|
859
|
+
jsx = jsx.replace(/>/g, '{">"}');
|
|
860
|
+
jsx = jsx.replace(/&/g, '{"&"}');
|
|
861
|
+
jsx = jsx.replace(/\sclassName=[""']\s*["']/g, '');
|
|
862
|
+
|
|
863
|
+
return jsx;
|
|
864
|
+
},
|
|
865
|
+
|
|
866
|
+
extractComponents(mainComponent) {
|
|
867
|
+
const patterns = [
|
|
868
|
+
{ tag: 'nav', name: 'Navigation', minOccurrences: 1 },
|
|
869
|
+
{ tag: 'header', name: 'Header', minOccurrences: 1 },
|
|
870
|
+
{ tag: 'footer', name: 'Footer', minOccurrences: 1 },
|
|
871
|
+
{ tag: 'aside', name: 'Sidebar', minOccurrences: 1 },
|
|
872
|
+
{ tag: 'article', name: 'Article', minOccurrences: 2 },
|
|
873
|
+
{ tag: 'section', name: 'Section', minOccurrences: 3 }
|
|
874
|
+
];
|
|
875
|
+
|
|
876
|
+
patterns.forEach(pattern => {
|
|
877
|
+
const regex = new RegExp(`<${pattern.tag}[^>]*>([\\s\\S]*?)<\\/${pattern.tag}>`, 'gi');
|
|
878
|
+
const matches = [...mainComponent.jsx.matchAll(regex)];
|
|
879
|
+
|
|
880
|
+
if (matches.length >= pattern.minOccurrences) {
|
|
881
|
+
matches.forEach((match, index) => {
|
|
882
|
+
const componentName = pattern.name + (matches.length > 1 ? index + 1 : '');
|
|
883
|
+
const placeholder = `<${componentName} />`;
|
|
884
|
+
|
|
885
|
+
if (match[0].length > 50) {
|
|
886
|
+
this.extractedComponents.push({
|
|
887
|
+
name: componentName,
|
|
888
|
+
jsx: match[0],
|
|
889
|
+
imports: ['React']
|
|
890
|
+
});
|
|
891
|
+
|
|
892
|
+
mainComponent.jsx = mainComponent.jsx.replace(match[0], placeholder);
|
|
893
|
+
if (!mainComponent.imports.includes(componentName)) {
|
|
894
|
+
mainComponent.imports.push(componentName);
|
|
895
|
+
}
|
|
896
|
+
console.log(colorizer.info(' Extracted component: ' + componentName));
|
|
897
|
+
}
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
});
|
|
901
|
+
},
|
|
902
|
+
|
|
903
|
+
generateComponent(component, isMain = false) {
|
|
904
|
+
const imports = [];
|
|
905
|
+
const hooksList = new Set();
|
|
906
|
+
|
|
907
|
+
if (component.hooks && component.hooks.length > 0) {
|
|
908
|
+
component.hooks.forEach(hook => {
|
|
909
|
+
if (hook.includes('useState')) hooksList.add('useState');
|
|
910
|
+
if (hook.includes('useEffect')) hooksList.add('useEffect');
|
|
911
|
+
if (hook.includes('useRef')) hooksList.add('useRef');
|
|
912
|
+
if (hook.includes('useCallback')) hooksList.add('useCallback');
|
|
913
|
+
if (hook.includes('useMemo')) hooksList.add('useMemo');
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
if (component.effects && component.effects.length > 0) {
|
|
918
|
+
hooksList.add('useEffect');
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
if (component.functions && component.functions.some(f => f.useCallback)) {
|
|
922
|
+
hooksList.add('useCallback');
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
if (hooksList.size > 0) {
|
|
926
|
+
imports.push(`import React, { ${Array.from(hooksList).join(', ')} } from 'react';`);
|
|
927
|
+
} else {
|
|
928
|
+
imports.push("import React from 'react';");
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
if (component.imports && component.imports.length > 0) {
|
|
932
|
+
component.imports.forEach(imp => {
|
|
933
|
+
if (imp !== 'React' && !imp.includes('use')) {
|
|
934
|
+
imports.push(`import ${imp} from './${imp}';`);
|
|
935
|
+
}
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
if (component.css) {
|
|
940
|
+
imports.push(`import './${component.name}.css';`);
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
if (component.externalStyles && component.externalStyles.length > 0) {
|
|
944
|
+
component.externalStyles.forEach(style => {
|
|
945
|
+
const styleName = path.basename(style.original);
|
|
946
|
+
imports.push(`import './${styleName}';`);
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
let code = imports.join('\n') + '\n\n';
|
|
951
|
+
|
|
952
|
+
// Add utility functions outside component
|
|
953
|
+
if (component.utilities && component.utilities.length > 0) {
|
|
954
|
+
code += '// Utility Functions\n';
|
|
955
|
+
component.utilities.forEach(util => {
|
|
956
|
+
if (util.comment) {
|
|
957
|
+
code += `// ${util.comment}\n`;
|
|
958
|
+
}
|
|
959
|
+
code += `function ${util.name}(${util.params}) {\n`;
|
|
960
|
+
code += ` ${util.body.split('\n').join('\n ')}\n`;
|
|
961
|
+
code += `}\n\n`;
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
code += `function ${component.name}() {\n`;
|
|
966
|
+
|
|
967
|
+
// Add hooks
|
|
968
|
+
if (component.hooks && component.hooks.length > 0) {
|
|
969
|
+
component.hooks.forEach(hook => {
|
|
970
|
+
code += ` ${hook}\n`;
|
|
971
|
+
});
|
|
972
|
+
code += '\n';
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// Add effects
|
|
976
|
+
if (component.effects && component.effects.length > 0) {
|
|
977
|
+
component.effects.forEach(effect => {
|
|
978
|
+
if (effect.comment) {
|
|
979
|
+
code += ` // ${effect.comment}\n`;
|
|
980
|
+
}
|
|
981
|
+
code += ` useEffect(() => {\n`;
|
|
982
|
+
code += ` ${effect.code.split('\n').join('\n ')}\n`;
|
|
983
|
+
code += ` }, ${effect.dependencies});\n\n`;
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// Add functions
|
|
988
|
+
if (component.functions && component.functions.length > 0) {
|
|
989
|
+
component.functions.forEach(func => {
|
|
990
|
+
const asyncKeyword = func.isAsync ? 'async ' : '';
|
|
991
|
+
const params = func.params || '';
|
|
992
|
+
|
|
993
|
+
if (func.useCallback) {
|
|
994
|
+
code += ` const ${func.name} = useCallback(${asyncKeyword}(${params}) => {\n`;
|
|
995
|
+
code += ` ${func.body.split('\n').join('\n ')}\n`;
|
|
996
|
+
const deps = this.extractDependencies(func.body);
|
|
997
|
+
code += ` }, ${deps});\n\n`;
|
|
998
|
+
} else {
|
|
999
|
+
code += ` const ${func.name} = ${asyncKeyword}(${params}) => {\n`;
|
|
1000
|
+
code += ` ${func.body.split('\n').join('\n ')}\n`;
|
|
1001
|
+
code += ` };\n\n`;
|
|
1002
|
+
}
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
code += ' return (\n';
|
|
1007
|
+
const formattedJSX = this.formatJSX(component.jsx, 4);
|
|
1008
|
+
code += formattedJSX + '\n';
|
|
1009
|
+
code += ' );\n';
|
|
1010
|
+
code += '}\n\n';
|
|
1011
|
+
code += `export default ${component.name};\n`;
|
|
1012
|
+
|
|
1013
|
+
return code;
|
|
1014
|
+
},
|
|
1015
|
+
|
|
1016
|
+
formatJSX(jsx, indent = 0) {
|
|
1017
|
+
const lines = jsx.split('\n');
|
|
1018
|
+
let indentLevel = 0;
|
|
1019
|
+
const indentStr = ' '.repeat(indent);
|
|
1020
|
+
|
|
1021
|
+
return lines.map(line => {
|
|
1022
|
+
const trimmed = line.trim();
|
|
1023
|
+
if (!trimmed) return '';
|
|
1024
|
+
|
|
1025
|
+
if (trimmed.startsWith('</')) {
|
|
1026
|
+
indentLevel = Math.max(0, indentLevel - 1);
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
const formatted = indentStr + ' '.repeat(indentLevel * 2) + trimmed;
|
|
1030
|
+
|
|
1031
|
+
if (trimmed.startsWith('<') && !trimmed.startsWith('</') && !trimmed.endsWith('/>') && !trimmed.includes('</')) {
|
|
1032
|
+
indentLevel++;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
return formatted;
|
|
1036
|
+
}).filter(line => line.trim()).join('\n');
|
|
1037
|
+
},
|
|
1038
|
+
|
|
1039
|
+
async saveComponents(converted, outputDir, inputDir) {
|
|
1040
|
+
const savedFiles = [];
|
|
1041
|
+
|
|
1042
|
+
try {
|
|
1043
|
+
await fs.mkdir(outputDir, { recursive: true });
|
|
1044
|
+
|
|
1045
|
+
const mainFile = path.join(outputDir, converted.main.name + '.jsx');
|
|
1046
|
+
const mainCode = this.generateComponent(converted.main, true);
|
|
1047
|
+
|
|
1048
|
+
await fs.writeFile(mainFile, mainCode);
|
|
1049
|
+
savedFiles.push(mainFile);
|
|
1050
|
+
console.log(colorizer.success('Created: ' + mainFile));
|
|
1051
|
+
|
|
1052
|
+
if (converted.main.css) {
|
|
1053
|
+
const cssFile = path.join(outputDir, converted.main.name + '.css');
|
|
1054
|
+
await fs.writeFile(cssFile, converted.main.css);
|
|
1055
|
+
savedFiles.push(cssFile);
|
|
1056
|
+
console.log(colorizer.success('Created: ' + cssFile));
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
for (const style of this.externalStylesheets) {
|
|
1060
|
+
const destFile = path.join(outputDir, path.basename(style.original));
|
|
1061
|
+
|
|
1062
|
+
try {
|
|
1063
|
+
await fs.copyFile(style.local, destFile);
|
|
1064
|
+
savedFiles.push(destFile);
|
|
1065
|
+
console.log(colorizer.success('Copied stylesheet: ' + destFile));
|
|
1066
|
+
} catch (err) {
|
|
1067
|
+
console.log(colorizer.warning('Could not copy stylesheet: ' + style.original));
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
if (converted.jsModules.length > 0) {
|
|
1072
|
+
const utilsDir = path.join(outputDir, 'utils');
|
|
1073
|
+
await fs.mkdir(utilsDir, { recursive: true });
|
|
1074
|
+
|
|
1075
|
+
for (const jsModule of converted.jsModules) {
|
|
1076
|
+
const destFile = path.join(utilsDir, jsModule.name);
|
|
1077
|
+
await fs.writeFile(destFile, jsModule.code);
|
|
1078
|
+
savedFiles.push(destFile);
|
|
1079
|
+
console.log(colorizer.success('Created JS module: ' + destFile));
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
for (const script of this.externalScripts) {
|
|
1084
|
+
try {
|
|
1085
|
+
const scriptContent = await fs.readFile(script.local, 'utf8');
|
|
1086
|
+
const convertedScript = await this.convertExternalScript(scriptContent, script);
|
|
1087
|
+
|
|
1088
|
+
const utilsDir = path.join(outputDir, 'utils');
|
|
1089
|
+
await fs.mkdir(utilsDir, { recursive: true });
|
|
1090
|
+
|
|
1091
|
+
const baseName = path.basename(script.original, path.extname(script.original));
|
|
1092
|
+
const destFile = path.join(utilsDir, baseName + (script.isModule ? '.js' : '.util.js'));
|
|
1093
|
+
|
|
1094
|
+
await fs.writeFile(destFile, convertedScript);
|
|
1095
|
+
savedFiles.push(destFile);
|
|
1096
|
+
console.log(colorizer.success('Converted script: ' + destFile));
|
|
1097
|
+
} catch (err) {
|
|
1098
|
+
console.log(colorizer.warning('Could not process script: ' + script.original + ' - ' + err.message));
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
for (const comp of converted.components) {
|
|
1103
|
+
const compFile = path.join(outputDir, comp.name + '.jsx');
|
|
1104
|
+
const compCode = this.generateComponent(comp);
|
|
1105
|
+
|
|
1106
|
+
await fs.writeFile(compFile, compCode);
|
|
1107
|
+
savedFiles.push(compFile);
|
|
1108
|
+
console.log(colorizer.success('Created: ' + compFile));
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
if (this.dependencies.size > 0) {
|
|
1112
|
+
await this.generatePackageJson(outputDir);
|
|
1113
|
+
savedFiles.push(path.join(outputDir, 'package.json'));
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
await this.generateReadme(outputDir, converted);
|
|
1117
|
+
savedFiles.push(path.join(outputDir, 'README.md'));
|
|
1118
|
+
|
|
1119
|
+
return savedFiles;
|
|
1120
|
+
} catch (err) {
|
|
1121
|
+
throw new Error('Failed to save components: ' + err.message);
|
|
1122
|
+
}
|
|
1123
|
+
},
|
|
1124
|
+
|
|
1125
|
+
async convertExternalScript(scriptContent, scriptInfo) {
|
|
1126
|
+
if (scriptInfo.isModule) {
|
|
1127
|
+
return scriptContent;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
let converted = scriptContent;
|
|
1131
|
+
const exports = this.detectExports(scriptContent);
|
|
1132
|
+
|
|
1133
|
+
if (exports.length > 0) {
|
|
1134
|
+
converted += '\n\n// Auto-generated exports\n';
|
|
1135
|
+
converted += `export { ${exports.join(', ')} };\n`;
|
|
1136
|
+
console.log(colorizer.info(' Detected exports: ' + exports.join(', ')));
|
|
1137
|
+
} else {
|
|
1138
|
+
converted = `// Converted from: ${scriptInfo.original}\n// Note: This file may need manual adjustments\n\n${converted}`;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
return converted;
|
|
1142
|
+
},
|
|
1143
|
+
|
|
1144
|
+
detectExports(code) {
|
|
1145
|
+
const exports = [];
|
|
1146
|
+
|
|
1147
|
+
const functionRegex = /function\s+(\w+)\s*\(/g;
|
|
1148
|
+
let match;
|
|
1149
|
+
|
|
1150
|
+
while ((match = functionRegex.exec(code)) !== null) {
|
|
1151
|
+
exports.push(match[1]);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
const varRegex = /^(?:const|let|var)\s+(\w+)\s*=/gm;
|
|
1155
|
+
|
|
1156
|
+
while ((match = varRegex.exec(code)) !== null) {
|
|
1157
|
+
exports.push(match[1]);
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
const classRegex = /class\s+(\w+)/g;
|
|
1161
|
+
|
|
1162
|
+
while ((match = classRegex.exec(code)) !== null) {
|
|
1163
|
+
exports.push(match[1]);
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
return exports;
|
|
1167
|
+
},
|
|
1168
|
+
|
|
1169
|
+
async generatePackageJson(outputDir) {
|
|
1170
|
+
const packageJson = {
|
|
1171
|
+
name: path.basename(outputDir),
|
|
1172
|
+
version: '1.0.0',
|
|
1173
|
+
description: 'React components generated from HTML',
|
|
1174
|
+
main: 'index.js',
|
|
1175
|
+
scripts: {
|
|
1176
|
+
start: 'react-scripts start',
|
|
1177
|
+
build: 'react-scripts build',
|
|
1178
|
+
test: 'react-scripts test'
|
|
1179
|
+
},
|
|
1180
|
+
dependencies: {
|
|
1181
|
+
react: '^18.2.0',
|
|
1182
|
+
'react-dom': '^18.2.0'
|
|
1183
|
+
},
|
|
1184
|
+
devDependencies: {
|
|
1185
|
+
'react-scripts': '^5.0.1'
|
|
1186
|
+
}
|
|
1187
|
+
};
|
|
1188
|
+
|
|
1189
|
+
this.dependencies.forEach(dep => {
|
|
1190
|
+
packageJson.dependencies[dep] = 'latest';
|
|
1191
|
+
});
|
|
1192
|
+
|
|
1193
|
+
const pkgFile = path.join(outputDir, 'package.json');
|
|
1194
|
+
await fs.writeFile(pkgFile, JSON.stringify(packageJson, null, 2));
|
|
1195
|
+
console.log(colorizer.success('Created: ' + pkgFile));
|
|
1196
|
+
},
|
|
1197
|
+
|
|
1198
|
+
async generateReadme(outputDir, converted) {
|
|
1199
|
+
let readme = '# React Components\n\n';
|
|
1200
|
+
readme += 'This project was generated from HTML using html-to-react converter.\n\n';
|
|
1201
|
+
|
|
1202
|
+
readme += '## Installation\n\n';
|
|
1203
|
+
readme += '```bash\n';
|
|
1204
|
+
readme += 'npm install\n';
|
|
1205
|
+
readme += '```\n\n';
|
|
1206
|
+
|
|
1207
|
+
readme += '## Components\n\n';
|
|
1208
|
+
readme += `- **${converted.main.name}** - Main component\n`;
|
|
1209
|
+
|
|
1210
|
+
converted.components.forEach(comp => {
|
|
1211
|
+
readme += `- **${comp.name}** - Extracted component\n`;
|
|
1212
|
+
});
|
|
1213
|
+
|
|
1214
|
+
if (converted.main.hooks.length > 0) {
|
|
1215
|
+
readme += '\n## State Management\n\n';
|
|
1216
|
+
readme += 'The components use React hooks for state management:\n\n';
|
|
1217
|
+
converted.main.hooks.forEach(hook => {
|
|
1218
|
+
if (hook.includes('useState')) {
|
|
1219
|
+
readme += `- ${hook.split('=')[0].trim()}\n`;
|
|
1220
|
+
}
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
if (converted.main.functions && converted.main.functions.length > 0) {
|
|
1225
|
+
readme += '\n## Functions\n\n';
|
|
1226
|
+
readme += 'The following functions were converted:\n\n';
|
|
1227
|
+
converted.main.functions.forEach(func => {
|
|
1228
|
+
readme += `- **${func.name}** ${func.isAsync ? '(async)' : ''}\n`;
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
if (converted.main.utilities && converted.main.utilities.length > 0) {
|
|
1233
|
+
readme += '\n## Utility Functions\n\n';
|
|
1234
|
+
readme += 'Pure utility functions extracted outside the component:\n\n';
|
|
1235
|
+
converted.main.utilities.forEach(util => {
|
|
1236
|
+
readme += `- **${util.name}**\n`;
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
if (this.dependencies.size > 0) {
|
|
1241
|
+
readme += '\n## Dependencies\n\n';
|
|
1242
|
+
readme += 'The following external libraries were detected:\n\n';
|
|
1243
|
+
this.dependencies.forEach(dep => {
|
|
1244
|
+
readme += `- ${dep}\n`;
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
readme += '\n## Manual Review Required\n\n';
|
|
1249
|
+
readme += 'Please review the following:\n\n';
|
|
1250
|
+
readme += '- Event handlers and their implementations\n';
|
|
1251
|
+
readme += '- State management logic\n';
|
|
1252
|
+
readme += '- Any TODO comments in the code\n';
|
|
1253
|
+
readme += '- DOM manipulation converted to refs\n';
|
|
1254
|
+
readme += '- jQuery calls converted to React patterns\n';
|
|
1255
|
+
readme += '- External script integrations\n';
|
|
1256
|
+
readme += '- useCallback dependencies\n';
|
|
1257
|
+
|
|
1258
|
+
const readmeFile = path.join(outputDir, 'README.md');
|
|
1259
|
+
await fs.writeFile(readmeFile, readme);
|
|
1260
|
+
console.log(colorizer.success('Created: ' + readmeFile));
|
|
1261
|
+
},
|
|
1262
|
+
|
|
1263
|
+
printConversionSummary() {
|
|
1264
|
+
console.log(colorizer.section('Conversion Summary'));
|
|
1265
|
+
console.log(colorizer.cyan(' Components created: ') + (this.extractedComponents.length + 1));
|
|
1266
|
+
console.log(colorizer.cyan(' Stylesheets: ') + this.externalStylesheets.length);
|
|
1267
|
+
console.log(colorizer.cyan(' Scripts processed: ') + this.externalScripts.length);
|
|
1268
|
+
console.log(colorizer.cyan(' Helper functions: ') + this.helperFunctions.size);
|
|
1269
|
+
|
|
1270
|
+
if (this.dependencies.size > 0) {
|
|
1271
|
+
console.log(colorizer.cyan(' Dependencies detected: ') + Array.from(this.dependencies).join(', '));
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
if (this.globalVariables.size > 0) {
|
|
1275
|
+
console.log(colorizer.cyan(' State variables: ') + this.globalVariables.size);
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
console.log();
|
|
1279
|
+
},
|
|
1280
|
+
|
|
1281
|
+
// Batch conversion and analysis methods remain the same...
|
|
1282
|
+
convertBatch(args) {
|
|
1283
|
+
const inputDir = args[0] || '.';
|
|
1284
|
+
const outputDir = args[1] || './react-components';
|
|
1285
|
+
|
|
1286
|
+
console.log(colorizer.header('Batch HTML to React Conversion'));
|
|
1287
|
+
console.log(colorizer.separator());
|
|
1288
|
+
console.log(colorizer.cyan('Input Directory: ') + colorizer.bright(inputDir));
|
|
1289
|
+
console.log(colorizer.cyan('Output Directory: ') + colorizer.bright(outputDir));
|
|
1290
|
+
console.log();
|
|
1291
|
+
|
|
1292
|
+
return this.findHTMLFiles(inputDir)
|
|
1293
|
+
.then(files => {
|
|
1294
|
+
if (files.length === 0) {
|
|
1295
|
+
console.log(colorizer.warning('No HTML files found in ' + inputDir + '\n'));
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
console.log(colorizer.info('Found ' + files.length + ' HTML file(s)\n'));
|
|
1300
|
+
|
|
1301
|
+
const promises = files.map(file => {
|
|
1302
|
+
const relativePath = path.relative(inputDir, file);
|
|
1303
|
+
const outputSubDir = path.join(outputDir, path.dirname(relativePath));
|
|
1304
|
+
|
|
1305
|
+
console.log(colorizer.cyan('Converting: ') + relativePath);
|
|
1306
|
+
|
|
1307
|
+
return this.convert([file, outputSubDir])
|
|
1308
|
+
.catch(err => {
|
|
1309
|
+
console.log(colorizer.warning('Failed to convert ' + file + ': ' + err.message));
|
|
1310
|
+
});
|
|
1311
|
+
});
|
|
1312
|
+
|
|
1313
|
+
return Promise.all(promises);
|
|
1314
|
+
})
|
|
1315
|
+
.then(() => {
|
|
1316
|
+
console.log(colorizer.success('\nBatch conversion complete!\n'));
|
|
1317
|
+
})
|
|
1318
|
+
.catch(err => {
|
|
1319
|
+
console.log(colorizer.error('Batch conversion failed: ' + err.message + '\n'));
|
|
1320
|
+
});
|
|
1321
|
+
},
|
|
1322
|
+
|
|
1323
|
+
findHTMLFiles(dir, files = []) {
|
|
1324
|
+
return fs.readdir(dir, { withFileTypes: true })
|
|
1325
|
+
.then(items => {
|
|
1326
|
+
const promises = items.map(item => {
|
|
1327
|
+
const fullPath = path.join(dir, item.name);
|
|
1328
|
+
|
|
1329
|
+
if (['node_modules', '.git', 'dist', 'build', 'react-components'].includes(item.name)) {
|
|
1330
|
+
return Promise.resolve();
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
if (item.isDirectory()) {
|
|
1334
|
+
return this.findHTMLFiles(fullPath, files);
|
|
1335
|
+
} else if (item.isFile() && path.extname(item.name).toLowerCase() === '.html') {
|
|
1336
|
+
files.push(fullPath);
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
return Promise.resolve();
|
|
1340
|
+
});
|
|
1341
|
+
|
|
1342
|
+
return Promise.all(promises);
|
|
1343
|
+
})
|
|
1344
|
+
.then(() => files)
|
|
1345
|
+
.catch(err => {
|
|
1346
|
+
console.log(colorizer.warning('Error reading directory ' + dir + ': ' + err.message));
|
|
1347
|
+
return files;
|
|
1348
|
+
});
|
|
1349
|
+
},
|
|
1350
|
+
|
|
1351
|
+
analyzeHTML(args) {
|
|
1352
|
+
const filePath = args[0];
|
|
1353
|
+
|
|
1354
|
+
if (!filePath) {
|
|
1355
|
+
console.log(colorizer.error('Usage: analyze-html <html-file>\n'));
|
|
1356
|
+
return Promise.resolve();
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
console.log(colorizer.header('HTML Structure Analysis'));
|
|
1360
|
+
console.log(colorizer.separator());
|
|
1361
|
+
console.log(colorizer.cyan('File: ') + colorizer.bright(filePath));
|
|
1362
|
+
console.log();
|
|
1363
|
+
|
|
1364
|
+
return fs.readFile(filePath, 'utf8')
|
|
1365
|
+
.then(html => {
|
|
1366
|
+
const analysis = {
|
|
1367
|
+
totalLines: html.split('\n').length,
|
|
1368
|
+
totalSize: Buffer.byteLength(html, 'utf8'),
|
|
1369
|
+
tags: this.countTags(html),
|
|
1370
|
+
hasCSS: html.includes('<style') || html.includes('style='),
|
|
1371
|
+
hasInlineCSS: html.includes('style='),
|
|
1372
|
+
hasExternalCSS: html.includes('<link') && html.includes('stylesheet'),
|
|
1373
|
+
hasJS: html.includes('<script'),
|
|
1374
|
+
hasInlineJS: /<script(?![^>]*src=)[^>]*>[\s\S]*?<\/script>/gi.test(html),
|
|
1375
|
+
hasExternalJS: /<script[^>]+src=/gi.test(html),
|
|
1376
|
+
forms: (html.match(/<form/gi) || []).length,
|
|
1377
|
+
inputs: (html.match(/<input/gi) || []).length,
|
|
1378
|
+
images: (html.match(/<img/gi) || []).length,
|
|
1379
|
+
links: (html.match(/<a/gi) || []).length,
|
|
1380
|
+
semanticTags: this.countSemanticTags(html),
|
|
1381
|
+
eventHandlers: this.countEventHandlers(html),
|
|
1382
|
+
externalScripts: this.countExternalScripts(html),
|
|
1383
|
+
dataAttributes: (html.match(/data-\w+=/gi) || []).length
|
|
1384
|
+
};
|
|
1385
|
+
|
|
1386
|
+
console.log(colorizer.section('File Statistics'));
|
|
1387
|
+
console.log(colorizer.cyan(' Total Lines: ') + analysis.totalLines);
|
|
1388
|
+
console.log(colorizer.cyan(' File Size: ') + (analysis.totalSize / 1024).toFixed(2) + ' KB');
|
|
1389
|
+
console.log(colorizer.cyan(' Total Tags: ') + analysis.tags);
|
|
1390
|
+
console.log(colorizer.cyan(' Forms: ') + analysis.forms);
|
|
1391
|
+
console.log(colorizer.cyan(' Input Fields: ') + analysis.inputs);
|
|
1392
|
+
console.log(colorizer.cyan(' Images: ') + analysis.images);
|
|
1393
|
+
console.log(colorizer.cyan(' Links: ') + analysis.links);
|
|
1394
|
+
console.log(colorizer.cyan(' Data Attributes: ') + analysis.dataAttributes);
|
|
1395
|
+
|
|
1396
|
+
console.log(colorizer.section('CSS'));
|
|
1397
|
+
console.log(colorizer.cyan(' Has Inline CSS: ') + (analysis.hasInlineCSS ? colorizer.green('Yes') : colorizer.red('No')));
|
|
1398
|
+
console.log(colorizer.cyan(' Has External CSS: ') + (analysis.hasExternalCSS ? colorizer.green('Yes') : colorizer.red('No')));
|
|
1399
|
+
|
|
1400
|
+
console.log(colorizer.section('JavaScript'));
|
|
1401
|
+
console.log(colorizer.cyan(' Has Inline JS: ') + (analysis.hasInlineJS ? colorizer.green('Yes') : colorizer.red('No')));
|
|
1402
|
+
console.log(colorizer.cyan(' Has External JS: ') + (analysis.hasExternalJS ? colorizer.green('Yes') : colorizer.red('No')));
|
|
1403
|
+
console.log(colorizer.cyan(' External Scripts: ') + analysis.externalScripts.length);
|
|
1404
|
+
console.log(colorizer.cyan(' Event Handlers: ') + analysis.eventHandlers);
|
|
1405
|
+
|
|
1406
|
+
if (analysis.externalScripts.length > 0) {
|
|
1407
|
+
console.log(colorizer.cyan(' Detected Scripts:'));
|
|
1408
|
+
analysis.externalScripts.forEach(script => {
|
|
1409
|
+
console.log(colorizer.bullet(script));
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
console.log(colorizer.section('Semantic Tags'));
|
|
1414
|
+
Object.entries(analysis.semanticTags).forEach(([tag, count]) => {
|
|
1415
|
+
if (count > 0) {
|
|
1416
|
+
console.log(colorizer.bullet(tag + ': ' + count));
|
|
1417
|
+
}
|
|
1418
|
+
});
|
|
1419
|
+
|
|
1420
|
+
console.log(colorizer.section('Conversion Complexity'));
|
|
1421
|
+
let complexity = 'Low';
|
|
1422
|
+
let score = 0;
|
|
1423
|
+
|
|
1424
|
+
if (analysis.hasInlineJS) score += 3;
|
|
1425
|
+
if (analysis.hasExternalJS) score += 2;
|
|
1426
|
+
if (analysis.eventHandlers > 10) score += 3;
|
|
1427
|
+
else if (analysis.eventHandlers > 5) score += 2;
|
|
1428
|
+
else if (analysis.eventHandlers > 0) score += 1;
|
|
1429
|
+
if (analysis.forms > 2) score += 2;
|
|
1430
|
+
else if (analysis.forms > 0) score += 1;
|
|
1431
|
+
if (analysis.hasInlineCSS) score += 1;
|
|
1432
|
+
if (analysis.dataAttributes > 10) score += 1;
|
|
1433
|
+
|
|
1434
|
+
if (score >= 7) complexity = 'High';
|
|
1435
|
+
else if (score >= 4) complexity = 'Medium';
|
|
1436
|
+
|
|
1437
|
+
const complexityColor = complexity === 'High' ? colorizer.red :
|
|
1438
|
+
complexity === 'Medium' ? colorizer.yellow :
|
|
1439
|
+
colorizer.green;
|
|
1440
|
+
|
|
1441
|
+
console.log(colorizer.cyan(' Complexity Score: ') + score + '/12');
|
|
1442
|
+
console.log(colorizer.cyan(' Complexity Level: ') + complexityColor(complexity));
|
|
1443
|
+
|
|
1444
|
+
console.log(colorizer.section('Conversion Recommendations'));
|
|
1445
|
+
const recommendations = [];
|
|
1446
|
+
|
|
1447
|
+
if (analysis.semanticTags.nav > 0) recommendations.push('Extract navigation as separate component');
|
|
1448
|
+
if (analysis.semanticTags.header > 0) recommendations.push('Extract header as separate component');
|
|
1449
|
+
if (analysis.semanticTags.footer > 0) recommendations.push('Extract footer as separate component');
|
|
1450
|
+
if (analysis.semanticTags.aside > 0) recommendations.push('Extract sidebar as separate component');
|
|
1451
|
+
if (analysis.forms > 0) recommendations.push('Consider using React Hook Form or Formik for form management');
|
|
1452
|
+
if (analysis.hasInlineJS) recommendations.push('JavaScript will be converted to React hooks and functions');
|
|
1453
|
+
if (analysis.eventHandlers > 5) recommendations.push('Multiple event handlers detected - will be converted to React event props');
|
|
1454
|
+
if (analysis.hasExternalJS) recommendations.push('External scripts will be converted to ES6 modules in utils folder');
|
|
1455
|
+
if (analysis.hasExternalCSS) recommendations.push('External stylesheets will be imported as CSS modules');
|
|
1456
|
+
if (analysis.dataAttributes > 0) recommendations.push('Data attributes detected - consider converting to props or state');
|
|
1457
|
+
if (complexity === 'High') recommendations.push('High complexity - expect significant manual adjustments needed');
|
|
1458
|
+
|
|
1459
|
+
if (recommendations.length > 0) {
|
|
1460
|
+
recommendations.forEach(rec => console.log(colorizer.bullet(rec)));
|
|
1461
|
+
} else {
|
|
1462
|
+
console.log(colorizer.dim(' No specific recommendations - straightforward conversion expected'));
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
console.log();
|
|
1466
|
+
})
|
|
1467
|
+
.catch(err => {
|
|
1468
|
+
console.log(colorizer.error('Analysis failed: ' + err.message));
|
|
1469
|
+
if (err.stack) {
|
|
1470
|
+
console.log(colorizer.dim(err.stack));
|
|
1471
|
+
}
|
|
1472
|
+
console.log();
|
|
1473
|
+
});
|
|
1474
|
+
},
|
|
1475
|
+
|
|
1476
|
+
countTags(html) {
|
|
1477
|
+
const matches = html.match(/<[^>]+>/g);
|
|
1478
|
+
return matches ? matches.length : 0;
|
|
1479
|
+
},
|
|
1480
|
+
|
|
1481
|
+
countSemanticTags(html) {
|
|
1482
|
+
const tags = ['nav', 'header', 'footer', 'main', 'section', 'article', 'aside', 'figure'];
|
|
1483
|
+
const counts = {};
|
|
1484
|
+
|
|
1485
|
+
tags.forEach(tag => {
|
|
1486
|
+
const regex = new RegExp(`<${tag}[^>]*>`, 'gi');
|
|
1487
|
+
const matches = html.match(regex);
|
|
1488
|
+
counts[tag] = matches ? matches.length : 0;
|
|
1489
|
+
});
|
|
1490
|
+
|
|
1491
|
+
return counts;
|
|
1492
|
+
},
|
|
1493
|
+
|
|
1494
|
+
countEventHandlers(html) {
|
|
1495
|
+
const events = ['onclick', 'onchange', 'onsubmit', 'onload', 'onmouseover',
|
|
1496
|
+
'onmouseout', 'onkeydown', 'onkeyup', 'onfocus', 'onblur',
|
|
1497
|
+
'oninput', 'onscroll', 'ondblclick'];
|
|
1498
|
+
let count = 0;
|
|
1499
|
+
|
|
1500
|
+
events.forEach(event => {
|
|
1501
|
+
const regex = new RegExp(event + '=', 'gi');
|
|
1502
|
+
const matches = html.match(regex);
|
|
1503
|
+
if (matches) count += matches.length;
|
|
1504
|
+
});
|
|
1505
|
+
|
|
1506
|
+
return count;
|
|
1507
|
+
},
|
|
1508
|
+
|
|
1509
|
+
countExternalScripts(html) {
|
|
1510
|
+
const scripts = [];
|
|
1511
|
+
const scriptRegex = /<script[^>]+src=["']([^"']+)["'][^>]*>/gi;
|
|
1512
|
+
let match;
|
|
1513
|
+
|
|
1514
|
+
while ((match = scriptRegex.exec(html)) !== null) {
|
|
1515
|
+
scripts.push(match[1]);
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
return scripts;
|
|
1519
|
+
},
|
|
1520
|
+
|
|
1521
|
+
toPascalCase(str) {
|
|
1522
|
+
return str
|
|
1523
|
+
.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : '')
|
|
1524
|
+
.replace(/^(.)/, (_, c) => c.toUpperCase())
|
|
1525
|
+
.replace(/[^a-zA-Z0-9]/g, '');
|
|
1526
|
+
},
|
|
1527
|
+
|
|
1528
|
+
toCamelCase(str) {
|
|
1529
|
+
return str
|
|
1530
|
+
.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : '')
|
|
1531
|
+
.replace(/^(.)/, (_, c) => c.toLowerCase())
|
|
1532
|
+
.replace(/[^a-zA-Z0-9]/g, '');
|
|
1533
|
+
}
|
|
1534
|
+
};
|
|
1535
|
+
|
|
1536
|
+
module.exports = HTMLToReact;
|