create-template-html-css 1.6.4 → 1.8.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 +152 -0
- package/COMPONENTS-GALLERY.html +660 -0
- package/PUBLISH-GUIDE.md +112 -0
- package/README.md +253 -14
- package/bin/cli.js +296 -5
- package/create-template-html-css-1.8.0.tgz +0 -0
- package/package.json +3 -2
- package/src/generator.js +512 -3
- package/src/inserter.js +7 -1
- package/templates/login/index.html +58 -0
- package/templates/login/script.js +83 -0
- package/templates/login/style.css +232 -0
- package/templates/navigation/index.html +51 -0
- package/templates/navigation/script.js +109 -3
- package/templates/navigation/style.css +269 -0
- package/templates/register/index.html +85 -0
- package/templates/register/script.js +205 -0
- package/templates/register/style.css +298 -0
- package/templates/skeleton/index.html +115 -0
- package/templates/skeleton/script.js +28 -0
- package/templates/skeleton/style.css +409 -0
- package/demo/css/accordion-component.css +0 -135
- package/demo/css/button-component.css +0 -110
- package/demo/css/card-component.css +0 -381
- package/demo/index.html +0 -169
- package/demo/js/accordion-component.js +0 -31
- package/demo/js/button-component.js +0 -17
- package/demo/js/card-component.js +0 -124
package/src/generator.js
CHANGED
|
@@ -40,6 +40,179 @@ async function formatJs(jsContent) {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Color scheme presets
|
|
45
|
+
*/
|
|
46
|
+
const COLOR_SCHEMES = {
|
|
47
|
+
vibrant: {
|
|
48
|
+
name: "Vibrant",
|
|
49
|
+
primary: "#FF6B6B",
|
|
50
|
+
secondary: "#4ECDC4",
|
|
51
|
+
description: "Bold reds and teals - energetic and modern"
|
|
52
|
+
},
|
|
53
|
+
pastel: {
|
|
54
|
+
name: "Pastel",
|
|
55
|
+
primary: "#FFB3BA",
|
|
56
|
+
secondary: "#FFDFBA",
|
|
57
|
+
description: "Soft pinks and peaches - calm and friendly"
|
|
58
|
+
},
|
|
59
|
+
ocean: {
|
|
60
|
+
name: "Ocean",
|
|
61
|
+
primary: "#0066CC",
|
|
62
|
+
secondary: "#00CCFF",
|
|
63
|
+
description: "Deep blues and cyans - professional and cool"
|
|
64
|
+
},
|
|
65
|
+
sunset: {
|
|
66
|
+
name: "Sunset",
|
|
67
|
+
primary: "#FF6B35",
|
|
68
|
+
secondary: "#FFA500",
|
|
69
|
+
description: "Warm oranges and reds - energetic glow"
|
|
70
|
+
},
|
|
71
|
+
forest: {
|
|
72
|
+
name: "Forest",
|
|
73
|
+
primary: "#2D6A4F",
|
|
74
|
+
secondary: "#40916C",
|
|
75
|
+
description: "Green tones - natural and organic"
|
|
76
|
+
},
|
|
77
|
+
purple: {
|
|
78
|
+
name: "Purple",
|
|
79
|
+
primary: "#7209B7",
|
|
80
|
+
secondary: "#B5179E",
|
|
81
|
+
description: "Deep purples - elegant and premium"
|
|
82
|
+
},
|
|
83
|
+
minimal: {
|
|
84
|
+
name: "Minimal",
|
|
85
|
+
primary: "#1A1A1A",
|
|
86
|
+
secondary: "#666666",
|
|
87
|
+
description: "Grays and blacks - clean and simple"
|
|
88
|
+
},
|
|
89
|
+
coral: {
|
|
90
|
+
name: "Coral",
|
|
91
|
+
primary: "#FF6B9D",
|
|
92
|
+
secondary: "#FFA07A",
|
|
93
|
+
description: "Coral pinks and salmon - warm and playful"
|
|
94
|
+
},
|
|
95
|
+
teal: {
|
|
96
|
+
name: "Teal",
|
|
97
|
+
primary: "#008B8B",
|
|
98
|
+
secondary: "#20B2AA",
|
|
99
|
+
description: "Teal hues - balanced and professional"
|
|
100
|
+
},
|
|
101
|
+
neon: {
|
|
102
|
+
name: "Neon",
|
|
103
|
+
primary: "#FF006E",
|
|
104
|
+
secondary: "#00D9FF",
|
|
105
|
+
description: "Bright neon colors - bold and futuristic"
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get color scheme by preset name
|
|
111
|
+
*/
|
|
112
|
+
function getColorScheme(schemeName) {
|
|
113
|
+
if (COLOR_SCHEMES[schemeName]) {
|
|
114
|
+
return COLOR_SCHEMES[schemeName];
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Apply custom colors to CSS content using CSS variables
|
|
121
|
+
*/
|
|
122
|
+
function applyCustomColors(cssContent, primaryColor, secondaryColor) {
|
|
123
|
+
if (!primaryColor && !secondaryColor) {
|
|
124
|
+
return cssContent;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Create CSS variable overrides
|
|
128
|
+
let colorVariables = ":root {\n";
|
|
129
|
+
|
|
130
|
+
if (primaryColor) {
|
|
131
|
+
colorVariables += ` --primary-color: ${primaryColor};\n`;
|
|
132
|
+
// Also add as root variable that can override gradients
|
|
133
|
+
colorVariables += ` --primary-rgb: ${hexToRgb(primaryColor)};\n`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (secondaryColor) {
|
|
137
|
+
colorVariables += ` --secondary-color: ${secondaryColor};\n`;
|
|
138
|
+
colorVariables += ` --secondary-rgb: ${hexToRgb(secondaryColor)};\n`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
colorVariables += "}\n\n";
|
|
142
|
+
|
|
143
|
+
// If CSS doesn't have :root, add it
|
|
144
|
+
if (!cssContent.includes(":root")) {
|
|
145
|
+
return colorVariables + cssContent;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Replace existing :root with extended one
|
|
149
|
+
return cssContent.replace(/:root\s*{/, colorVariables.trim() + "\n\n:root {");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Convert hex color to RGB values
|
|
154
|
+
*/
|
|
155
|
+
function hexToRgb(hex) {
|
|
156
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
157
|
+
if (result) {
|
|
158
|
+
const r = parseInt(result[1], 16);
|
|
159
|
+
const g = parseInt(result[2], 16);
|
|
160
|
+
const b = parseInt(result[3], 16);
|
|
161
|
+
return `${r}, ${g}, ${b}`;
|
|
162
|
+
}
|
|
163
|
+
return "102, 126, 234"; // fallback to default primary
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Add dark mode styles to CSS
|
|
168
|
+
*/
|
|
169
|
+
function addDarkModeStyles(cssContent) {
|
|
170
|
+
if (cssContent.includes("prefers-color-scheme")) {
|
|
171
|
+
return cssContent; // Already has dark mode
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Create dark mode media query
|
|
175
|
+
const darkModeStyles = `
|
|
176
|
+
/* Dark Mode Support */
|
|
177
|
+
@media (prefers-color-scheme: dark) {
|
|
178
|
+
:root {
|
|
179
|
+
--bg-primary: #1a1a1a;
|
|
180
|
+
--bg-secondary: #2d2d2d;
|
|
181
|
+
--text-primary: #ffffff;
|
|
182
|
+
--text-secondary: #b0b0b0;
|
|
183
|
+
--border-color: #404040;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
body {
|
|
187
|
+
background-color: var(--bg-primary, #1a1a1a);
|
|
188
|
+
color: var(--text-primary, #ffffff);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.container,
|
|
192
|
+
.card,
|
|
193
|
+
.modal-content,
|
|
194
|
+
.form-container {
|
|
195
|
+
background-color: var(--bg-secondary, #2d2d2d);
|
|
196
|
+
color: var(--text-primary, #ffffff);
|
|
197
|
+
border-color: var(--border-color, #404040);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
input,
|
|
201
|
+
textarea,
|
|
202
|
+
select {
|
|
203
|
+
background-color: var(--bg-primary, #1a1a1a);
|
|
204
|
+
color: var(--text-primary, #ffffff);
|
|
205
|
+
border-color: var(--border-color, #404040);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
input::placeholder {
|
|
209
|
+
color: var(--text-secondary, #b0b0b0);
|
|
210
|
+
}
|
|
211
|
+
}`;
|
|
212
|
+
|
|
213
|
+
return cssContent + darkModeStyles;
|
|
214
|
+
}
|
|
215
|
+
|
|
43
216
|
// Security: Validate component name against whitelist
|
|
44
217
|
const VALID_COMPONENTS = [
|
|
45
218
|
"button",
|
|
@@ -65,6 +238,9 @@ const VALID_COMPONENTS = [
|
|
|
65
238
|
"counter",
|
|
66
239
|
"accordion",
|
|
67
240
|
"tabs",
|
|
241
|
+
"login",
|
|
242
|
+
"register",
|
|
243
|
+
"skeleton",
|
|
68
244
|
];
|
|
69
245
|
|
|
70
246
|
// Security: Sanitize filename to prevent path traversal
|
|
@@ -81,8 +257,305 @@ function sanitizeFilename(filename) {
|
|
|
81
257
|
return sanitized.replace(/[<>:"|?*]/g, "").trim();
|
|
82
258
|
}
|
|
83
259
|
|
|
260
|
+
/**
|
|
261
|
+
* Filter button variations based on user selection
|
|
262
|
+
*/
|
|
263
|
+
function filterButtonVariations(htmlContent, buttonVariations, selectedButtons) {
|
|
264
|
+
if (buttonVariations === "all" || !selectedButtons || selectedButtons.length === 0) {
|
|
265
|
+
return htmlContent; // Return all buttons
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Map selection to button classes
|
|
269
|
+
const buttonMap = {
|
|
270
|
+
primary: "btn-primary",
|
|
271
|
+
secondary: "btn-secondary",
|
|
272
|
+
success: "btn-success",
|
|
273
|
+
danger: "btn-danger",
|
|
274
|
+
outlined: "btn-outlined",
|
|
275
|
+
disabled: "disabled"
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
let filteredHtml = htmlContent;
|
|
279
|
+
|
|
280
|
+
// Remove buttons that weren't selected
|
|
281
|
+
for (const [key, className] of Object.entries(buttonMap)) {
|
|
282
|
+
if (!selectedButtons.includes(key)) {
|
|
283
|
+
// Remove button and its comment
|
|
284
|
+
const patterns = [
|
|
285
|
+
new RegExp(`\\s*<!-- ${key.charAt(0).toUpperCase() + key.slice(1)} Button -->\\s*\\n\\s*<button[^>]*${className}[^>]*>.*?<\\/button>\\s*\\n`, 'gis'),
|
|
286
|
+
new RegExp(`\\s*<button[^>]*${className}[^>]*>.*?<\\/button>\\s*\\n`, 'gis')
|
|
287
|
+
];
|
|
288
|
+
|
|
289
|
+
patterns.forEach(pattern => {
|
|
290
|
+
filteredHtml = filteredHtml.replace(pattern, '\n ');
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return filteredHtml;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Filter card variations based on user selection
|
|
300
|
+
*/
|
|
301
|
+
function filterCardVariations(htmlContent, cardVariations, selectedCards) {
|
|
302
|
+
if (cardVariations === "all" || !selectedCards || selectedCards.length === 0) {
|
|
303
|
+
return htmlContent; // Return all cards
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const cardComments = {
|
|
307
|
+
modern: "Card 1 - Basic",
|
|
308
|
+
premium: "Card 2 - With Price",
|
|
309
|
+
blog: "Card 3 - With Tags",
|
|
310
|
+
minimal: "Card 4 - Minimal",
|
|
311
|
+
user: "Card 5 - With Avatar",
|
|
312
|
+
interactive: "Card 6 - Interactive"
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
let filteredHtml = htmlContent;
|
|
316
|
+
|
|
317
|
+
// Remove cards that weren't selected
|
|
318
|
+
for (const [key, comment] of Object.entries(cardComments)) {
|
|
319
|
+
if (!selectedCards.includes(key)) {
|
|
320
|
+
// Remove entire card div including comment
|
|
321
|
+
const pattern = new RegExp(`\\s*<!-- ${comment.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')} -->\\s*\\n\\s*<div class="card[^>]*>.*?<\\/div>\\s*\\n\\s*<\\/div>\\s*\\n`, 'gis');
|
|
322
|
+
filteredHtml = filteredHtml.replace(pattern, '\n ');
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return filteredHtml;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Filter spinner variations based on user selection
|
|
331
|
+
*/
|
|
332
|
+
function filterSpinnerVariations(htmlContent, spinnerVariations, selectedSpinners) {
|
|
333
|
+
if (spinnerVariations === "all" || !selectedSpinners || selectedSpinners.length === 0) {
|
|
334
|
+
return htmlContent; // Return all spinners
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const spinnerTypes = {
|
|
338
|
+
circle: "Circle Spinner",
|
|
339
|
+
dots: "Bouncing Dots",
|
|
340
|
+
pulse: "Pulse Loader",
|
|
341
|
+
bars: "Bar Loader",
|
|
342
|
+
gradient: "Gradient Ring"
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
let filteredHtml = htmlContent;
|
|
346
|
+
|
|
347
|
+
// Remove spinners that weren't selected
|
|
348
|
+
for (const [key, title] of Object.entries(spinnerTypes)) {
|
|
349
|
+
if (!selectedSpinners.includes(key)) {
|
|
350
|
+
// Remove entire spinner-group div
|
|
351
|
+
const pattern = new RegExp(`\\s*<!-- ${title} Spinner -->\\s*\\n\\s*<div class="spinner-group">.*?<\\/div>\\s*\\n\\s*<\\/div>\\s*\\n`, 'gis');
|
|
352
|
+
filteredHtml = filteredHtml.replace(pattern, '\n ');
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return filteredHtml;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Generate custom navigation items based on user input
|
|
361
|
+
*/
|
|
362
|
+
function generateNavigationItems(htmlContent, navItems) {
|
|
363
|
+
// Parse the comma-separated navigation items
|
|
364
|
+
const items = navItems.split(',').map(item => item.trim()).filter(item => item.length > 0);
|
|
365
|
+
|
|
366
|
+
// Generate navigation HTML
|
|
367
|
+
let navHtml = '';
|
|
368
|
+
items.forEach((item, index) => {
|
|
369
|
+
const itemId = item.toLowerCase().replace(/\s+/g, '-');
|
|
370
|
+
const activeClass = index === 0 ? ' active' : '';
|
|
371
|
+
navHtml += ` <li class="nav-item">
|
|
372
|
+
<a href="#${itemId}" class="nav-link${activeClass}">${item}</a>
|
|
373
|
+
</li>\n`;
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
// Replace the navigation items in the HTML
|
|
377
|
+
const navMenuRegex = /<ul class="nav-menu"[^>]*>[\s\S]*?<\/ul>/;
|
|
378
|
+
const replacement = `<ul class="nav-menu" id="navMenu">
|
|
379
|
+
${navHtml} </ul>`;
|
|
380
|
+
|
|
381
|
+
htmlContent = htmlContent.replace(navMenuRegex, replacement);
|
|
382
|
+
|
|
383
|
+
// Generate sections for each navigation item
|
|
384
|
+
let sectionsHtml = '';
|
|
385
|
+
items.forEach(item => {
|
|
386
|
+
const itemId = item.toLowerCase().replace(/\s+/g, '-');
|
|
387
|
+
sectionsHtml += ` <section id="${itemId}" class="section">
|
|
388
|
+
<h1>${item}</h1>
|
|
389
|
+
<p>Content for ${item} section</p>
|
|
390
|
+
</section>
|
|
391
|
+
|
|
392
|
+
`;
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
// Replace the sections in the HTML
|
|
396
|
+
const mainContentRegex = /<main class="main-content">[\s\S]*?<\/main>/;
|
|
397
|
+
const sectionsReplacement = `<main class="main-content">
|
|
398
|
+
${sectionsHtml} </main>`;
|
|
399
|
+
|
|
400
|
+
htmlContent = htmlContent.replace(mainContentRegex, sectionsReplacement);
|
|
401
|
+
|
|
402
|
+
return htmlContent;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Generate custom form fields based on user selection
|
|
407
|
+
*/
|
|
408
|
+
function generateFormFields(htmlContent, formFields, customFormFields) {
|
|
409
|
+
let fieldsHtml = '';
|
|
410
|
+
|
|
411
|
+
// Add standard fields
|
|
412
|
+
formFields.forEach(field => {
|
|
413
|
+
switch(field) {
|
|
414
|
+
case 'name':
|
|
415
|
+
fieldsHtml += ` <div class="form-group">
|
|
416
|
+
<label for="name">Full Name</label>
|
|
417
|
+
<input type="text" id="name" name="name" required>
|
|
418
|
+
</div>
|
|
419
|
+
|
|
420
|
+
`;
|
|
421
|
+
break;
|
|
422
|
+
case 'email':
|
|
423
|
+
fieldsHtml += ` <div class="form-group">
|
|
424
|
+
<label for="email">Email</label>
|
|
425
|
+
<input type="email" id="email" name="email" required>
|
|
426
|
+
</div>
|
|
427
|
+
|
|
428
|
+
`;
|
|
429
|
+
break;
|
|
430
|
+
case 'phone':
|
|
431
|
+
fieldsHtml += ` <div class="form-group">
|
|
432
|
+
<label for="phone">Phone</label>
|
|
433
|
+
<input type="tel" id="phone" name="phone">
|
|
434
|
+
</div>
|
|
435
|
+
|
|
436
|
+
`;
|
|
437
|
+
break;
|
|
438
|
+
case 'subject':
|
|
439
|
+
fieldsHtml += ` <div class="form-group">
|
|
440
|
+
<label for="subject">Subject</label>
|
|
441
|
+
<select id="subject" name="subject" required>
|
|
442
|
+
<option value="">Select a subject</option>
|
|
443
|
+
<option value="support">Technical Support</option>
|
|
444
|
+
<option value="sales">Sales</option>
|
|
445
|
+
<option value="general">General</option>
|
|
446
|
+
</select>
|
|
447
|
+
</div>
|
|
448
|
+
|
|
449
|
+
`;
|
|
450
|
+
break;
|
|
451
|
+
case 'message':
|
|
452
|
+
fieldsHtml += ` <div class="form-group">
|
|
453
|
+
<label for="message">Message</label>
|
|
454
|
+
<textarea id="message" name="message" rows="5" required></textarea>
|
|
455
|
+
</div>
|
|
456
|
+
|
|
457
|
+
`;
|
|
458
|
+
break;
|
|
459
|
+
case 'terms':
|
|
460
|
+
fieldsHtml += ` <div class="form-group checkbox-group">
|
|
461
|
+
<input type="checkbox" id="terms" name="terms" required>
|
|
462
|
+
<label for="terms">I agree to the terms of service</label>
|
|
463
|
+
</div>
|
|
464
|
+
|
|
465
|
+
`;
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
// Add custom fields if provided
|
|
471
|
+
if (customFormFields && customFormFields.trim().length > 0) {
|
|
472
|
+
const customFields = customFormFields.split(',').map(f => f.trim()).filter(f => f.length > 0);
|
|
473
|
+
|
|
474
|
+
customFields.forEach(field => {
|
|
475
|
+
const [type, label] = field.split(':').map(s => s.trim());
|
|
476
|
+
if (type && label) {
|
|
477
|
+
const fieldId = label.toLowerCase().replace(/\s+/g, '-');
|
|
478
|
+
|
|
479
|
+
if (type === 'textarea') {
|
|
480
|
+
fieldsHtml += ` <div class="form-group">
|
|
481
|
+
<label for="${fieldId}">${label}</label>
|
|
482
|
+
<textarea id="${fieldId}" name="${fieldId}" rows="5"></textarea>
|
|
483
|
+
</div>
|
|
484
|
+
|
|
485
|
+
`;
|
|
486
|
+
} else if (type === 'checkbox') {
|
|
487
|
+
fieldsHtml += ` <div class="form-group checkbox-group">
|
|
488
|
+
<input type="checkbox" id="${fieldId}" name="${fieldId}">
|
|
489
|
+
<label for="${fieldId}">${label}</label>
|
|
490
|
+
</div>
|
|
491
|
+
|
|
492
|
+
`;
|
|
493
|
+
} else if (type === 'select') {
|
|
494
|
+
fieldsHtml += ` <div class="form-group">
|
|
495
|
+
<label for="${fieldId}">${label}</label>
|
|
496
|
+
<select id="${fieldId}" name="${fieldId}">
|
|
497
|
+
<option value="">Select an option</option>
|
|
498
|
+
<option value="option1">Option 1</option>
|
|
499
|
+
<option value="option2">Option 2</option>
|
|
500
|
+
</select>
|
|
501
|
+
</div>
|
|
502
|
+
|
|
503
|
+
`;
|
|
504
|
+
} else {
|
|
505
|
+
// Default to input with specified type
|
|
506
|
+
fieldsHtml += ` <div class="form-group">
|
|
507
|
+
<label for="${fieldId}">${label}</label>
|
|
508
|
+
<input type="${type}" id="${fieldId}" name="${fieldId}">
|
|
509
|
+
</div>
|
|
510
|
+
|
|
511
|
+
`;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Replace the form fields in the HTML
|
|
518
|
+
const formRegex = /<form class="form"[^>]*>[\s\S]*?<button type="submit"/;
|
|
519
|
+
const replacement = `<form class="form" id="contactForm">
|
|
520
|
+
<h1 class="form-title">Contact Us</h1>
|
|
521
|
+
<p class="form-subtitle">Fill in the details and we'll get back to you soon</p>
|
|
522
|
+
|
|
523
|
+
${fieldsHtml} <button type="submit"`;
|
|
524
|
+
|
|
525
|
+
htmlContent = htmlContent.replace(formRegex, replacement);
|
|
526
|
+
|
|
527
|
+
return htmlContent;
|
|
528
|
+
}
|
|
529
|
+
|
|
84
530
|
async function generateTemplate(options) {
|
|
85
|
-
const {
|
|
531
|
+
const {
|
|
532
|
+
component,
|
|
533
|
+
name,
|
|
534
|
+
includeJs,
|
|
535
|
+
navItems,
|
|
536
|
+
formFields,
|
|
537
|
+
customFormFields,
|
|
538
|
+
buttonVariations,
|
|
539
|
+
selectedButtons,
|
|
540
|
+
cardVariations,
|
|
541
|
+
selectedCards,
|
|
542
|
+
spinnerVariations,
|
|
543
|
+
selectedSpinners,
|
|
544
|
+
colorScheme,
|
|
545
|
+
primaryColor,
|
|
546
|
+
secondaryColor,
|
|
547
|
+
darkMode
|
|
548
|
+
} = options;
|
|
549
|
+
|
|
550
|
+
// If colorScheme is provided, use those colors
|
|
551
|
+
let finalPrimaryColor = primaryColor;
|
|
552
|
+
let finalSecondaryColor = secondaryColor;
|
|
553
|
+
|
|
554
|
+
if (colorScheme && COLOR_SCHEMES[colorScheme]) {
|
|
555
|
+
const scheme = COLOR_SCHEMES[colorScheme];
|
|
556
|
+
finalPrimaryColor = scheme.primary;
|
|
557
|
+
finalSecondaryColor = scheme.secondary;
|
|
558
|
+
}
|
|
86
559
|
|
|
87
560
|
// Security: Validate component name
|
|
88
561
|
if (!VALID_COMPONENTS.includes(component)) {
|
|
@@ -117,6 +590,31 @@ async function generateTemplate(options) {
|
|
|
117
590
|
|
|
118
591
|
// Replace placeholder name
|
|
119
592
|
htmlContent = htmlContent.replace(/{{name}}/g, safeName);
|
|
593
|
+
|
|
594
|
+
// Filter button variations
|
|
595
|
+
if (component === "button") {
|
|
596
|
+
htmlContent = filterButtonVariations(htmlContent, buttonVariations, selectedButtons);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// Filter card variations
|
|
600
|
+
if (component === "card") {
|
|
601
|
+
htmlContent = filterCardVariations(htmlContent, cardVariations, selectedCards);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Filter spinner variations
|
|
605
|
+
if (component === "spinner") {
|
|
606
|
+
htmlContent = filterSpinnerVariations(htmlContent, spinnerVariations, selectedSpinners);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Custom navigation items
|
|
610
|
+
if (component === "navigation" && navItems) {
|
|
611
|
+
htmlContent = generateNavigationItems(htmlContent, navItems);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// Custom form fields
|
|
615
|
+
if (component === "form" && formFields) {
|
|
616
|
+
htmlContent = generateFormFields(htmlContent, formFields, customFormFields);
|
|
617
|
+
}
|
|
120
618
|
|
|
121
619
|
// Add script tag if JavaScript is included (pointing to js/ folder)
|
|
122
620
|
if (includeJs) {
|
|
@@ -139,10 +637,21 @@ async function generateTemplate(options) {
|
|
|
139
637
|
await fs.writeFile(path.join(outputDir, "index.html"), htmlContent);
|
|
140
638
|
|
|
141
639
|
// Copy CSS file to css/ folder
|
|
142
|
-
|
|
640
|
+
let cssContent = await fs.readFile(
|
|
143
641
|
path.join(templateDir, "style.css"),
|
|
144
642
|
"utf-8",
|
|
145
643
|
);
|
|
644
|
+
|
|
645
|
+
// Apply custom colors if provided (either from colorScheme or direct colors)
|
|
646
|
+
if (finalPrimaryColor || finalSecondaryColor) {
|
|
647
|
+
cssContent = applyCustomColors(cssContent, finalPrimaryColor, finalSecondaryColor);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Add dark mode support if requested
|
|
651
|
+
if (darkMode) {
|
|
652
|
+
cssContent = addDarkModeStyles(cssContent);
|
|
653
|
+
}
|
|
654
|
+
|
|
146
655
|
const formattedCss = await formatCss(cssContent);
|
|
147
656
|
await fs.writeFile(path.join(cssDir, "style.css"), formattedCss);
|
|
148
657
|
|
|
@@ -165,4 +674,4 @@ async function generateTemplate(options) {
|
|
|
165
674
|
return outputDir;
|
|
166
675
|
}
|
|
167
676
|
|
|
168
|
-
module.exports = { generateTemplate };
|
|
677
|
+
module.exports = { generateTemplate, COLOR_SCHEMES, getColorScheme };
|
package/src/inserter.js
CHANGED
|
@@ -161,8 +161,14 @@ async function insertComponent(options) {
|
|
|
161
161
|
);
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
//
|
|
164
|
+
// Security: Prevent path traversal attacks
|
|
165
165
|
const targetPath = path.resolve(process.cwd(), targetFile);
|
|
166
|
+
const cwd = process.cwd();
|
|
167
|
+
if (!targetPath.startsWith(cwd)) {
|
|
168
|
+
throw new Error(`Security error: Cannot access files outside current directory`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Check if target file exists
|
|
166
172
|
try {
|
|
167
173
|
await fs.access(targetPath);
|
|
168
174
|
} catch {
|
|
@@ -0,0 +1,58 @@
|
|
|
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>{{name}} - Login</title>
|
|
7
|
+
<link rel="stylesheet" href="style.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div class="container">
|
|
11
|
+
<div class="login-card">
|
|
12
|
+
<h1 class="login-title">Welcome Back</h1>
|
|
13
|
+
<p class="login-subtitle">Login to your account</p>
|
|
14
|
+
|
|
15
|
+
<form class="login-form" id="loginForm">
|
|
16
|
+
<div class="form-group">
|
|
17
|
+
<label for="email">Email Address</label>
|
|
18
|
+
<input type="email" id="email" name="email" placeholder="you@example.com" required>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<div class="form-group">
|
|
22
|
+
<label for="password">Password</label>
|
|
23
|
+
<input type="password" id="password" name="password" placeholder="Enter your password" required>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div class="form-group remember-forgot">
|
|
27
|
+
<div class="checkbox-wrapper">
|
|
28
|
+
<input type="checkbox" id="remember" name="remember">
|
|
29
|
+
<label for="remember">Remember me</label>
|
|
30
|
+
</div>
|
|
31
|
+
<a href="#" class="forgot-link">Forgot Password?</a>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<button type="submit" class="login-btn">Login</button>
|
|
35
|
+
</form>
|
|
36
|
+
|
|
37
|
+
<div class="form-divider">
|
|
38
|
+
<span>Or continue with</span>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div class="social-buttons">
|
|
42
|
+
<button class="social-btn google-btn">
|
|
43
|
+
<span>Google</span>
|
|
44
|
+
</button>
|
|
45
|
+
<button class="social-btn github-btn">
|
|
46
|
+
<span>GitHub</span>
|
|
47
|
+
</button>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<p class="signup-link">
|
|
51
|
+
Don't have an account? <a href="#">Sign up here</a>
|
|
52
|
+
</p>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<script src="script.js"></script>
|
|
57
|
+
</body>
|
|
58
|
+
</html>
|