find-duplicate-js 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/find-duplicates-ui.js +67 -308
- package/find-duplicates.js +3 -0
- package/package.json +3 -1
- package/ui-styles.css +204 -0
- package/ui-template.html +49 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.2.0] - 2025-12-31
|
|
6
|
+
|
|
7
|
+
### 🚀 New Features
|
|
8
|
+
|
|
9
|
+
#### Programmatic API Support
|
|
10
|
+
- **Export Functions**: Package now exports core functions for programmatic use
|
|
11
|
+
- `findDuplicates` - Main function to find duplicate code
|
|
12
|
+
- `findJsFiles` - Find all JavaScript files in a directory
|
|
13
|
+
- `extractFunctions` - Extract functions from code
|
|
14
|
+
- `normalizeCode` - Normalize code for comparison
|
|
15
|
+
- `calculateSimilarity` - Calculate similarity between code snippets
|
|
16
|
+
- **Usage Example**:
|
|
17
|
+
```javascript
|
|
18
|
+
import { findDuplicates, findJsFiles } from 'find-duplicate-js';
|
|
19
|
+
|
|
20
|
+
const result = findDuplicates('./src', 70);
|
|
21
|
+
console.log(result);
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### ✨ Improvements
|
|
25
|
+
|
|
26
|
+
#### UI Code Refactoring
|
|
27
|
+
- **Separated Concerns**: Split HTML, CSS, and JavaScript into separate files
|
|
28
|
+
- `ui-template.html` - HTML structure
|
|
29
|
+
- `ui-styles.css` - All styling
|
|
30
|
+
- `find-duplicates-ui.js` - Logic only
|
|
31
|
+
- **Better Maintainability**: Easier to update and customize the UI
|
|
32
|
+
- **Cleaner Code**: More readable and organized codebase
|
|
33
|
+
|
|
34
|
+
### 🐛 Bug Fixes
|
|
35
|
+
- Fixed module export issue that prevented importing functions from the package
|
|
36
|
+
- Resolved "does not provide an export named 'findDuplicates'" error
|
|
37
|
+
|
|
5
38
|
## [1.1.0] - 2025-12-30
|
|
6
39
|
|
|
7
40
|
### 🐛 Bug Fixes
|
package/find-duplicates-ui.js
CHANGED
|
@@ -2,8 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
import { exec as open } from "child_process";
|
|
4
4
|
import http from "http";
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
5
8
|
import { findDuplicates, findJsFiles } from "./find-duplicates-core.js";
|
|
6
9
|
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = path.dirname(__filename);
|
|
12
|
+
|
|
7
13
|
const PORT = 2712;
|
|
8
14
|
|
|
9
15
|
/**
|
|
@@ -14,315 +20,68 @@ const PORT = 2712;
|
|
|
14
20
|
* @description Creates a responsive, interactive web interface with statistics dashboard and side-by-side code comparison
|
|
15
21
|
*/
|
|
16
22
|
function generateHTML(duplicates, stats) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
padding: 25px;
|
|
72
|
-
border-radius: 12px;
|
|
73
|
-
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
|
74
|
-
text-align: center;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
.stat-card .number {
|
|
78
|
-
font-size: 2.5em;
|
|
79
|
-
font-weight: bold;
|
|
80
|
-
color: #667eea;
|
|
81
|
-
margin-bottom: 5px;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
.stat-card .label {
|
|
85
|
-
color: #666;
|
|
86
|
-
font-size: 0.9em;
|
|
87
|
-
text-transform: uppercase;
|
|
88
|
-
letter-spacing: 1px;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
.no-duplicates {
|
|
92
|
-
background: white;
|
|
93
|
-
padding: 60px;
|
|
94
|
-
border-radius: 15px;
|
|
95
|
-
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
|
96
|
-
text-align: center;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
.no-duplicates .icon {
|
|
100
|
-
font-size: 5em;
|
|
101
|
-
margin-bottom: 20px;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
.no-duplicates h2 {
|
|
105
|
-
color: #4CAF50;
|
|
106
|
-
font-size: 2em;
|
|
107
|
-
margin-bottom: 10px;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
.duplicate-card {
|
|
111
|
-
background: white;
|
|
112
|
-
border-radius: 15px;
|
|
113
|
-
box-shadow: 0 5px 20px rgba(0,0,0,0.1);
|
|
114
|
-
margin-bottom: 25px;
|
|
115
|
-
overflow: hidden;
|
|
116
|
-
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
.duplicate-card:hover {
|
|
120
|
-
transform: translateY(-5px);
|
|
121
|
-
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
.duplicate-header {
|
|
125
|
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
126
|
-
color: white;
|
|
127
|
-
padding: 20px 30px;
|
|
128
|
-
display: flex;
|
|
129
|
-
justify-content: space-between;
|
|
130
|
-
align-items: center;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
.duplicate-header h3 {
|
|
134
|
-
font-size: 1.3em;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
.similarity-badge {
|
|
138
|
-
background: rgba(255,255,255,0.2);
|
|
139
|
-
padding: 8px 20px;
|
|
140
|
-
border-radius: 20px;
|
|
141
|
-
font-weight: bold;
|
|
142
|
-
font-size: 1.2em;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
.duplicate-body {
|
|
146
|
-
padding: 30px;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
.function-comparison {
|
|
150
|
-
display: grid;
|
|
151
|
-
grid-template-columns: 1fr 1fr;
|
|
152
|
-
gap: 30px;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
.function-info {
|
|
156
|
-
background: #f8f9fa;
|
|
157
|
-
padding: 20px;
|
|
158
|
-
border-radius: 10px;
|
|
159
|
-
border-left: 4px solid #667eea;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
.function-info h4 {
|
|
163
|
-
color: #667eea;
|
|
164
|
-
margin-bottom: 10px;
|
|
165
|
-
font-size: 1.1em;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
.file-path {
|
|
169
|
-
color: #666;
|
|
170
|
-
font-size: 0.85em;
|
|
171
|
-
margin-bottom: 8px;
|
|
172
|
-
word-break: break-all;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
.function-name {
|
|
176
|
-
font-family: 'Courier New', monospace;
|
|
177
|
-
background: #e3f2fd;
|
|
178
|
-
padding: 5px 10px;
|
|
179
|
-
border-radius: 5px;
|
|
180
|
-
display: inline-block;
|
|
181
|
-
margin-bottom: 15px;
|
|
182
|
-
color: #1976d2;
|
|
183
|
-
font-weight: bold;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
.code-preview {
|
|
187
|
-
background: #263238;
|
|
188
|
-
color: #aed581;
|
|
189
|
-
padding: 15px;
|
|
190
|
-
border-radius: 8px;
|
|
191
|
-
font-family: 'Courier New', monospace;
|
|
192
|
-
font-size: 0.9em;
|
|
193
|
-
overflow-x: auto;
|
|
194
|
-
white-space: pre-wrap;
|
|
195
|
-
word-break: break-all;
|
|
196
|
-
max-height: 200px;
|
|
197
|
-
overflow-y: auto;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
@media (max-width: 768px) {
|
|
201
|
-
.function-comparison {
|
|
202
|
-
grid-template-columns: 1fr;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
.header h1 {
|
|
206
|
-
font-size: 1.8em;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
.refresh-btn {
|
|
211
|
-
background: white;
|
|
212
|
-
color: #667eea;
|
|
213
|
-
border: 2px solid #667eea;
|
|
214
|
-
padding: 12px 30px;
|
|
215
|
-
border-radius: 25px;
|
|
216
|
-
font-size: 1em;
|
|
217
|
-
font-weight: bold;
|
|
218
|
-
cursor: pointer;
|
|
219
|
-
transition: all 0.3s ease;
|
|
220
|
-
margin-top: 20px;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
.refresh-btn:hover {
|
|
224
|
-
background: #667eea;
|
|
225
|
-
color: white;
|
|
226
|
-
transform: scale(1.05);
|
|
227
|
-
}
|
|
228
|
-
</style>
|
|
229
|
-
</head>
|
|
230
|
-
<body>
|
|
231
|
-
<div class="container">
|
|
232
|
-
<div class="header">
|
|
233
|
-
<h1>🔍 JavaScript Duplicate Finder</h1>
|
|
234
|
-
<p>Intelligent code duplication detection</p>
|
|
235
|
-
<button class="refresh-btn" onclick="location.reload()">🔄 Refresh Analysis</button>
|
|
236
|
-
</div>
|
|
237
|
-
|
|
238
|
-
<div class="stats">
|
|
239
|
-
<div class="stat-card">
|
|
240
|
-
<div class="number">${stats.filesScanned}</div>
|
|
241
|
-
<div class="label">Files Scanned</div>
|
|
242
|
-
</div>
|
|
243
|
-
<div class="stat-card">
|
|
244
|
-
<div class="number">${stats.functionsFound}</div>
|
|
245
|
-
<div class="label">Functions Found</div>
|
|
246
|
-
</div>
|
|
247
|
-
<div class="stat-card">
|
|
248
|
-
<div class="number">${stats.duplicatesFound}</div>
|
|
249
|
-
<div class="label">Duplicate Pairs</div>
|
|
250
|
-
</div>
|
|
251
|
-
<div class="stat-card">
|
|
252
|
-
<div class="number">${stats.threshold}%</div>
|
|
253
|
-
<div class="label">Threshold</div>
|
|
254
|
-
</div>
|
|
255
|
-
</div>
|
|
256
|
-
|
|
257
|
-
${
|
|
258
|
-
duplicates.length === 0
|
|
259
|
-
? `
|
|
260
|
-
<div class="no-duplicates">
|
|
261
|
-
<div class="icon">✅</div>
|
|
262
|
-
<h2>Great! No Duplicates Found</h2>
|
|
263
|
-
<p>Your code is clean and well-organized.</p>
|
|
264
|
-
</div>
|
|
265
|
-
`
|
|
266
|
-
: `
|
|
267
|
-
${duplicates
|
|
268
|
-
.map(
|
|
269
|
-
(dup, index) => `
|
|
270
|
-
<div class="duplicate-card">
|
|
271
|
-
<div class="duplicate-header">
|
|
272
|
-
<h3>📋 Match #${index + 1}</h3>
|
|
273
|
-
<div class="similarity-badge">${
|
|
274
|
-
dup.similarity
|
|
275
|
-
}% Similar</div>
|
|
276
|
-
</div>
|
|
277
|
-
<div class="duplicate-body">
|
|
278
|
-
<div class="function-comparison">
|
|
279
|
-
<div class="function-info">
|
|
280
|
-
<h4>Function 1</h4>
|
|
281
|
-
<div class="file-path">📁 ${
|
|
282
|
-
dup.func1.filePath
|
|
283
|
-
}</div>
|
|
284
|
-
<div class="function-name">${
|
|
285
|
-
dup.func1.name
|
|
286
|
-
}()</div>
|
|
287
|
-
<div class="code-preview">${escapeHtml(
|
|
288
|
-
dup.func1.originalBody.substring(0, 200)
|
|
289
|
-
)}${
|
|
290
|
-
dup.func1.originalBody.length > 200 ? "..." : ""
|
|
291
|
-
}</div>
|
|
292
|
-
</div>
|
|
293
|
-
<div class="function-info">
|
|
294
|
-
<h4>Function 2</h4>
|
|
295
|
-
<div class="file-path">📁 ${
|
|
296
|
-
dup.func2.filePath
|
|
297
|
-
}</div>
|
|
298
|
-
<div class="function-name">${
|
|
299
|
-
dup.func2.name
|
|
300
|
-
}()</div>
|
|
301
|
-
<div class="code-preview">${escapeHtml(
|
|
302
|
-
dup.func2.originalBody.substring(0, 200)
|
|
303
|
-
)}${
|
|
304
|
-
dup.func2.originalBody.length > 200 ? "..." : ""
|
|
305
|
-
}</div>
|
|
306
|
-
</div>
|
|
307
|
-
</div>
|
|
308
|
-
</div>
|
|
309
|
-
</div>
|
|
310
|
-
`
|
|
311
|
-
)
|
|
312
|
-
.join("")}
|
|
23
|
+
const templatePath = path.join(__dirname, "ui-template.html");
|
|
24
|
+
const cssPath = path.join(__dirname, "ui-styles.css");
|
|
25
|
+
|
|
26
|
+
let template = fs.readFileSync(templatePath, "utf-8");
|
|
27
|
+
const css = fs.readFileSync(cssPath, "utf-8");
|
|
28
|
+
|
|
29
|
+
// Inject CSS inline
|
|
30
|
+
template = template.replace('<link rel="stylesheet" href="ui-styles.css">', `<style>${css}</style>`);
|
|
31
|
+
|
|
32
|
+
// Replace stats placeholders
|
|
33
|
+
template = template
|
|
34
|
+
.replace("{{filesScanned}}", stats.filesScanned)
|
|
35
|
+
.replace("{{functionsFound}}", stats.functionsFound)
|
|
36
|
+
.replace("{{duplicatesFound}}", stats.duplicatesFound)
|
|
37
|
+
.replace("{{threshold}}", stats.threshold);
|
|
38
|
+
|
|
39
|
+
// Generate results HTML
|
|
40
|
+
const resultsHTML = duplicates.length === 0
|
|
41
|
+
? `
|
|
42
|
+
<div class="no-duplicates">
|
|
43
|
+
<div class="icon">✅</div>
|
|
44
|
+
<h2>Great! No Duplicates Found</h2>
|
|
45
|
+
<p>Your code is clean and well-organized.</p>
|
|
46
|
+
</div>
|
|
47
|
+
`
|
|
48
|
+
: duplicates
|
|
49
|
+
.map(
|
|
50
|
+
(dup, index) => `
|
|
51
|
+
<div class="duplicate-card">
|
|
52
|
+
<div class="duplicate-header">
|
|
53
|
+
<h3>📋 Match #${index + 1}</h3>
|
|
54
|
+
<div class="similarity-badge">${dup.similarity}% Similar</div>
|
|
55
|
+
</div>
|
|
56
|
+
<div class="duplicate-body">
|
|
57
|
+
<div class="function-comparison">
|
|
58
|
+
<div class="function-info">
|
|
59
|
+
<h4>Function 1</h4>
|
|
60
|
+
<div class="file-path">📁 ${dup.func1.filePath}</div>
|
|
61
|
+
<div class="function-name">${dup.func1.name}()</div>
|
|
62
|
+
<div class="code-preview">${escapeHtml(
|
|
63
|
+
dup.func1.originalBody.substring(0, 200)
|
|
64
|
+
)}${dup.func1.originalBody.length > 200 ? "..." : ""}</div>
|
|
65
|
+
</div>
|
|
66
|
+
<div class="function-info">
|
|
67
|
+
<h4>Function 2</h4>
|
|
68
|
+
<div class="file-path">📁 ${dup.func2.filePath}</div>
|
|
69
|
+
<div class="function-name">${dup.func2.name}()</div>
|
|
70
|
+
<div class="code-preview">${escapeHtml(
|
|
71
|
+
dup.func2.originalBody.substring(0, 200)
|
|
72
|
+
)}${dup.func2.originalBody.length > 200 ? "..." : ""}</div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
313
77
|
`
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
return div.innerHTML;
|
|
322
|
-
}
|
|
323
|
-
</script>
|
|
324
|
-
</body>
|
|
325
|
-
</html>`;
|
|
78
|
+
)
|
|
79
|
+
.join("");
|
|
80
|
+
|
|
81
|
+
// Inject results
|
|
82
|
+
template = template.replace('<div id="results">', `<div id="results">${resultsHTML}`);
|
|
83
|
+
|
|
84
|
+
return template;
|
|
326
85
|
}
|
|
327
86
|
|
|
328
87
|
/**
|
package/find-duplicates.js
CHANGED
|
@@ -54,3 +54,6 @@ const result = findDuplicates(directory, threshold);
|
|
|
54
54
|
console.log(`📊 Found ${result.totalFunctions} functions total\n`);
|
|
55
55
|
|
|
56
56
|
displayResults(result);
|
|
57
|
+
|
|
58
|
+
// Export functions for programmatic use
|
|
59
|
+
export { findDuplicates, findJsFiles, extractFunctions, normalizeCode, calculateSimilarity } from './find-duplicates-core.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "find-duplicate-js",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "A tool to find duplicate code in JavaScript functions",
|
|
5
5
|
"main": "find-duplicates.js",
|
|
6
6
|
"type": "module",
|
|
@@ -42,6 +42,8 @@
|
|
|
42
42
|
"find-duplicates.js",
|
|
43
43
|
"find-duplicates-core.js",
|
|
44
44
|
"find-duplicates-ui.js",
|
|
45
|
+
"ui-template.html",
|
|
46
|
+
"ui-styles.css",
|
|
45
47
|
"README.md",
|
|
46
48
|
"CHANGELOG.md",
|
|
47
49
|
"LICENSE"
|
package/ui-styles.css
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
* {
|
|
2
|
+
margin: 0;
|
|
3
|
+
padding: 0;
|
|
4
|
+
box-sizing: border-box;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
body {
|
|
8
|
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
9
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
10
|
+
min-height: 100vh;
|
|
11
|
+
padding: 20px;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.container {
|
|
15
|
+
max-width: 1200px;
|
|
16
|
+
margin: 0 auto;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.header {
|
|
20
|
+
background: white;
|
|
21
|
+
padding: 30px;
|
|
22
|
+
border-radius: 15px;
|
|
23
|
+
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
|
24
|
+
margin-bottom: 30px;
|
|
25
|
+
text-align: center;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.header h1 {
|
|
29
|
+
color: #667eea;
|
|
30
|
+
font-size: 2.5em;
|
|
31
|
+
margin-bottom: 10px;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.header p {
|
|
35
|
+
color: #666;
|
|
36
|
+
font-size: 1.1em;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.stats {
|
|
40
|
+
display: grid;
|
|
41
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
42
|
+
gap: 20px;
|
|
43
|
+
margin-bottom: 30px;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.stat-card {
|
|
47
|
+
background: white;
|
|
48
|
+
padding: 25px;
|
|
49
|
+
border-radius: 12px;
|
|
50
|
+
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
|
51
|
+
text-align: center;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.stat-card .number {
|
|
55
|
+
font-size: 2.5em;
|
|
56
|
+
font-weight: bold;
|
|
57
|
+
color: #667eea;
|
|
58
|
+
margin-bottom: 5px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.stat-card .label {
|
|
62
|
+
color: #666;
|
|
63
|
+
font-size: 0.9em;
|
|
64
|
+
text-transform: uppercase;
|
|
65
|
+
letter-spacing: 1px;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.no-duplicates {
|
|
69
|
+
background: white;
|
|
70
|
+
padding: 60px;
|
|
71
|
+
border-radius: 15px;
|
|
72
|
+
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
|
73
|
+
text-align: center;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.no-duplicates .icon {
|
|
77
|
+
font-size: 5em;
|
|
78
|
+
margin-bottom: 20px;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.no-duplicates h2 {
|
|
82
|
+
color: #4CAF50;
|
|
83
|
+
font-size: 2em;
|
|
84
|
+
margin-bottom: 10px;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.duplicate-card {
|
|
88
|
+
background: white;
|
|
89
|
+
border-radius: 15px;
|
|
90
|
+
box-shadow: 0 5px 20px rgba(0,0,0,0.1);
|
|
91
|
+
margin-bottom: 25px;
|
|
92
|
+
overflow: hidden;
|
|
93
|
+
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.duplicate-card:hover {
|
|
97
|
+
transform: translateY(-5px);
|
|
98
|
+
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.duplicate-header {
|
|
102
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
103
|
+
color: white;
|
|
104
|
+
padding: 20px 30px;
|
|
105
|
+
display: flex;
|
|
106
|
+
justify-content: space-between;
|
|
107
|
+
align-items: center;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.duplicate-header h3 {
|
|
111
|
+
font-size: 1.3em;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.similarity-badge {
|
|
115
|
+
background: rgba(255,255,255,0.2);
|
|
116
|
+
padding: 8px 20px;
|
|
117
|
+
border-radius: 20px;
|
|
118
|
+
font-weight: bold;
|
|
119
|
+
font-size: 1.2em;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.duplicate-body {
|
|
123
|
+
padding: 30px;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.function-comparison {
|
|
127
|
+
display: grid;
|
|
128
|
+
grid-template-columns: 1fr 1fr;
|
|
129
|
+
gap: 30px;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.function-info {
|
|
133
|
+
background: #f8f9fa;
|
|
134
|
+
padding: 20px;
|
|
135
|
+
border-radius: 10px;
|
|
136
|
+
border-left: 4px solid #667eea;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.function-info h4 {
|
|
140
|
+
color: #667eea;
|
|
141
|
+
margin-bottom: 10px;
|
|
142
|
+
font-size: 1.1em;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.file-path {
|
|
146
|
+
color: #666;
|
|
147
|
+
font-size: 0.85em;
|
|
148
|
+
margin-bottom: 8px;
|
|
149
|
+
word-break: break-all;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.function-name {
|
|
153
|
+
font-family: 'Courier New', monospace;
|
|
154
|
+
background: #e3f2fd;
|
|
155
|
+
padding: 5px 10px;
|
|
156
|
+
border-radius: 5px;
|
|
157
|
+
display: inline-block;
|
|
158
|
+
margin-bottom: 15px;
|
|
159
|
+
color: #1976d2;
|
|
160
|
+
font-weight: bold;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.code-preview {
|
|
164
|
+
background: #263238;
|
|
165
|
+
color: #aed581;
|
|
166
|
+
padding: 15px;
|
|
167
|
+
border-radius: 8px;
|
|
168
|
+
font-family: 'Courier New', monospace;
|
|
169
|
+
font-size: 0.9em;
|
|
170
|
+
overflow-x: auto;
|
|
171
|
+
white-space: pre-wrap;
|
|
172
|
+
word-break: break-all;
|
|
173
|
+
max-height: 200px;
|
|
174
|
+
overflow-y: auto;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
@media (max-width: 768px) {
|
|
178
|
+
.function-comparison {
|
|
179
|
+
grid-template-columns: 1fr;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.header h1 {
|
|
183
|
+
font-size: 1.8em;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.refresh-btn {
|
|
188
|
+
background: white;
|
|
189
|
+
color: #667eea;
|
|
190
|
+
border: 2px solid #667eea;
|
|
191
|
+
padding: 12px 30px;
|
|
192
|
+
border-radius: 25px;
|
|
193
|
+
font-size: 1em;
|
|
194
|
+
font-weight: bold;
|
|
195
|
+
cursor: pointer;
|
|
196
|
+
transition: all 0.3s ease;
|
|
197
|
+
margin-top: 20px;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.refresh-btn:hover {
|
|
201
|
+
background: #667eea;
|
|
202
|
+
color: white;
|
|
203
|
+
transform: scale(1.05);
|
|
204
|
+
}
|
package/ui-template.html
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>JavaScript Duplicate Finder</title>
|
|
7
|
+
<link rel="stylesheet" href="ui-styles.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div class="container">
|
|
11
|
+
<div class="header">
|
|
12
|
+
<h1>🔍 JavaScript Duplicate Finder</h1>
|
|
13
|
+
<p>Intelligent code duplication detection</p>
|
|
14
|
+
<button class="refresh-btn" onclick="location.reload()">🔄 Refresh Analysis</button>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div class="stats">
|
|
18
|
+
<div class="stat-card">
|
|
19
|
+
<div class="number">{{filesScanned}}</div>
|
|
20
|
+
<div class="label">Files Scanned</div>
|
|
21
|
+
</div>
|
|
22
|
+
<div class="stat-card">
|
|
23
|
+
<div class="number">{{functionsFound}}</div>
|
|
24
|
+
<div class="label">Functions Found</div>
|
|
25
|
+
</div>
|
|
26
|
+
<div class="stat-card">
|
|
27
|
+
<div class="number">{{duplicatesFound}}</div>
|
|
28
|
+
<div class="label">Duplicate Pairs</div>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="stat-card">
|
|
31
|
+
<div class="number">{{threshold}}%</div>
|
|
32
|
+
<div class="label">Threshold</div>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div id="results">
|
|
37
|
+
<!-- Results will be injected here -->
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<script>
|
|
42
|
+
function escapeHtml(text) {
|
|
43
|
+
const div = document.createElement('div');
|
|
44
|
+
div.textContent = text;
|
|
45
|
+
return div.innerHTML;
|
|
46
|
+
}
|
|
47
|
+
</script>
|
|
48
|
+
</body>
|
|
49
|
+
</html>
|