create-template-html-css 2.0.4 → 2.1.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 +305 -0
- package/HTML-VS-REACT.md +289 -0
- package/QUICKSTART-REACT.md +293 -0
- package/REACT-SUPPORT-SUMMARY.md +235 -0
- package/README.md +193 -12
- package/bin/cli.js +98 -759
- package/bin/commands/create.js +272 -0
- package/bin/commands/gallery.js +42 -0
- package/bin/commands/insert.js +123 -0
- package/bin/commands/list.js +73 -0
- package/package.json +10 -3
- package/src/component-choices.js +7 -0
- package/src/components-registry.js +112 -0
- package/src/format-utils.js +49 -0
- package/src/generator.js +83 -594
- package/src/generators/color-schemes.js +78 -0
- package/src/generators/color-utils.js +108 -0
- package/src/generators/component-filters.js +151 -0
- package/src/generators/html-generators.js +180 -0
- package/src/generators/validation.js +43 -0
- package/src/index.js +2 -1
- package/src/inserter.js +55 -233
- package/src/inserters/backup-utils.js +20 -0
- package/src/inserters/component-loader.js +68 -0
- package/src/inserters/html-utils.js +31 -0
- package/src/inserters/indentation-utils.js +90 -0
- package/src/inserters/validation-utils.js +49 -0
- package/src/react-component-choices.js +45 -0
- package/src/react-file-operations.js +172 -0
- package/src/react-generator.js +208 -0
- package/src/react-templates.js +350 -0
- package/src/utils/file-utils.js +97 -0
- package/src/utils/path-utils.js +32 -0
- package/src/utils/string-utils.js +51 -0
- package/src/utils/template-loader.js +91 -0
- package/templates/_shared/PATTERNS.md +246 -0
- package/templates/_shared/README.md +74 -0
- package/templates/_shared/base.css +18 -0
- package/templates/blackjack/index.html +1 -1
- package/templates/breakout/index.html +1 -1
- package/templates/connect-four/index.html +1 -1
- package/templates/dice-game/index.html +1 -1
- package/templates/flappy-bird/index.html +1 -1
- package/templates/pong/index.html +1 -1
- package/templates/skeleton/index.html +4 -4
- package/templates/slot-machine/index.html +1 -1
- package/templates/tetris/index.html +1 -1
- package/templates-react/README.md +126 -0
- package/templates-react/button/Button.css +88 -0
- package/templates-react/button/Button.example.jsx +40 -0
- package/templates-react/button/Button.jsx +29 -0
- package/templates-react/card/Card.css +86 -0
- package/templates-react/card/Card.example.jsx +49 -0
- package/templates-react/card/Card.jsx +35 -0
- package/templates-react/counter/Counter.css +99 -0
- package/templates-react/counter/Counter.example.jsx +45 -0
- package/templates-react/counter/Counter.jsx +70 -0
- package/templates-react/form/Form.css +128 -0
- package/templates-react/form/Form.example.jsx +65 -0
- package/templates-react/form/Form.jsx +125 -0
- package/templates-react/modal/Modal.css +152 -0
- package/templates-react/modal/Modal.example.jsx +90 -0
- package/templates-react/modal/Modal.jsx +46 -0
- package/templates-react/todo-list/TodoList.css +236 -0
- package/templates-react/todo-list/TodoList.example.jsx +15 -0
- package/templates-react/todo-list/TodoList.jsx +84 -0
package/src/generator.js
CHANGED
|
@@ -1,555 +1,48 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { promises as fs } from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { getDirname } from "./utils/path-utils.js";
|
|
4
|
+
import {
|
|
5
|
+
createComponentDirs,
|
|
6
|
+
writeHtmlFile,
|
|
7
|
+
writeCssFile,
|
|
8
|
+
writeJsFile,
|
|
9
|
+
} from "./utils/file-utils.js";
|
|
10
|
+
import {
|
|
11
|
+
getTemplatePath,
|
|
12
|
+
readTemplateHtml,
|
|
13
|
+
readTemplateCss,
|
|
14
|
+
readTemplateJs,
|
|
15
|
+
} from "./utils/template-loader.js";
|
|
16
|
+
import { COLOR_SCHEMES, getColorScheme } from "./generators/color-schemes.js";
|
|
17
|
+
import {
|
|
18
|
+
applyCustomColors,
|
|
19
|
+
addDarkModeStyles,
|
|
20
|
+
} from "./generators/color-utils.js";
|
|
21
|
+
import {
|
|
22
|
+
filterButtonVariations,
|
|
23
|
+
filterCardVariations,
|
|
24
|
+
filterSpinnerVariations,
|
|
25
|
+
} from "./generators/component-filters.js";
|
|
26
|
+
import {
|
|
27
|
+
generateNavigationItems,
|
|
28
|
+
generateFormFields,
|
|
29
|
+
} from "./generators/html-generators.js";
|
|
30
|
+
import { VALID_COMPONENTS, sanitizeFilename } from "./generators/validation.js";
|
|
31
|
+
|
|
32
|
+
const __dirname = getDirname(import.meta.url);
|
|
3
33
|
|
|
4
34
|
/**
|
|
5
|
-
*
|
|
35
|
+
* Generate a template component with all its files
|
|
36
|
+
* @param {Object} options - Generation options
|
|
37
|
+
* @returns {Promise<string>} Path to generated directory
|
|
6
38
|
*/
|
|
7
|
-
async function formatHtml(htmlContent) {
|
|
8
|
-
try {
|
|
9
|
-
const prettier = require("prettier");
|
|
10
|
-
return await prettier.format(htmlContent, { parser: "html" });
|
|
11
|
-
} catch (error) {
|
|
12
|
-
// Prettier not installed or error formatting - return original content
|
|
13
|
-
return htmlContent;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Formats CSS content with prettier (optional - falls back to original if not available)
|
|
19
|
-
*/
|
|
20
|
-
async function formatCss(cssContent) {
|
|
21
|
-
try {
|
|
22
|
-
const prettier = require("prettier");
|
|
23
|
-
return await prettier.format(cssContent, { parser: "css" });
|
|
24
|
-
} catch (error) {
|
|
25
|
-
// Prettier not installed or error formatting - return original content
|
|
26
|
-
return cssContent;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Formats JavaScript content with prettier (optional - falls back to original if not available)
|
|
32
|
-
*/
|
|
33
|
-
async function formatJs(jsContent) {
|
|
34
|
-
try {
|
|
35
|
-
const prettier = require("prettier");
|
|
36
|
-
return await prettier.format(jsContent, { parser: "babel" });
|
|
37
|
-
} catch (error) {
|
|
38
|
-
// Prettier not installed or error formatting - return original content
|
|
39
|
-
return jsContent;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
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
|
-
|
|
216
|
-
// Security: Validate component name against whitelist
|
|
217
|
-
const VALID_COMPONENTS = [
|
|
218
|
-
"button",
|
|
219
|
-
"card",
|
|
220
|
-
"form",
|
|
221
|
-
"navigation",
|
|
222
|
-
"modal",
|
|
223
|
-
"footer",
|
|
224
|
-
"hero",
|
|
225
|
-
"slider",
|
|
226
|
-
"table",
|
|
227
|
-
"spinner",
|
|
228
|
-
"animated-card",
|
|
229
|
-
"typing-effect",
|
|
230
|
-
"fade-gallery",
|
|
231
|
-
"grid-layout",
|
|
232
|
-
"masonry-grid",
|
|
233
|
-
"dashboard-grid",
|
|
234
|
-
"flex-layout",
|
|
235
|
-
"flex-cards",
|
|
236
|
-
"flex-dashboard",
|
|
237
|
-
"todo-list",
|
|
238
|
-
"counter",
|
|
239
|
-
"accordion",
|
|
240
|
-
"tabs",
|
|
241
|
-
"login",
|
|
242
|
-
"register",
|
|
243
|
-
"skeleton",
|
|
244
|
-
"tic-tac-toe",
|
|
245
|
-
"memory-game",
|
|
246
|
-
"snake-game",
|
|
247
|
-
"guess-number",
|
|
248
|
-
"game-2048",
|
|
249
|
-
"whack-a-mole",
|
|
250
|
-
"simon-says",
|
|
251
|
-
"rock-paper-scissors",
|
|
252
|
-
"breakout",
|
|
253
|
-
"tetris",
|
|
254
|
-
"flappy-bird",
|
|
255
|
-
"connect-four",
|
|
256
|
-
"blackjack",
|
|
257
|
-
"slot-machine",
|
|
258
|
-
"dice-game",
|
|
259
|
-
"pong",
|
|
260
|
-
];
|
|
261
|
-
|
|
262
|
-
// Security: Sanitize filename to prevent path traversal
|
|
263
|
-
function sanitizeFilename(filename) {
|
|
264
|
-
// Remove any path separators and parent directory references
|
|
265
|
-
const sanitized = filename.replace(/[\/\\]/g, "").replace(/\.\.+/g, ".");
|
|
266
|
-
|
|
267
|
-
// Additional validation: ensure name contains at least one alphanumeric character
|
|
268
|
-
if (!sanitized || !/[a-zA-Z0-9]/.test(sanitized)) {
|
|
269
|
-
return null;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Remove any remaining dangerous characters
|
|
273
|
-
return sanitized.replace(/[<>:"|?*]/g, "").trim();
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Filter button variations based on user selection
|
|
278
|
-
*/
|
|
279
|
-
function filterButtonVariations(htmlContent, buttonVariations, selectedButtons) {
|
|
280
|
-
if (buttonVariations === "all" || !selectedButtons || selectedButtons.length === 0) {
|
|
281
|
-
return htmlContent; // Return all buttons
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Map selection to button classes
|
|
285
|
-
const buttonMap = {
|
|
286
|
-
primary: "btn-primary",
|
|
287
|
-
secondary: "btn-secondary",
|
|
288
|
-
success: "btn-success",
|
|
289
|
-
danger: "btn-danger",
|
|
290
|
-
outlined: "btn-outlined",
|
|
291
|
-
disabled: "disabled"
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
let filteredHtml = htmlContent;
|
|
295
|
-
|
|
296
|
-
// Remove buttons that weren't selected
|
|
297
|
-
for (const [key, className] of Object.entries(buttonMap)) {
|
|
298
|
-
if (!selectedButtons.includes(key)) {
|
|
299
|
-
// Remove button and its comment
|
|
300
|
-
const patterns = [
|
|
301
|
-
new RegExp(`\\s*<!-- ${key.charAt(0).toUpperCase() + key.slice(1)} Button -->\\s*\\n\\s*<button[^>]*${className}[^>]*>.*?<\\/button>\\s*\\n`, 'gis'),
|
|
302
|
-
new RegExp(`\\s*<button[^>]*${className}[^>]*>.*?<\\/button>\\s*\\n`, 'gis')
|
|
303
|
-
];
|
|
304
|
-
|
|
305
|
-
patterns.forEach(pattern => {
|
|
306
|
-
filteredHtml = filteredHtml.replace(pattern, '\n ');
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
return filteredHtml;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* Filter card variations based on user selection
|
|
316
|
-
*/
|
|
317
|
-
function filterCardVariations(htmlContent, cardVariations, selectedCards) {
|
|
318
|
-
if (cardVariations === "all" || !selectedCards || selectedCards.length === 0) {
|
|
319
|
-
return htmlContent; // Return all cards
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const cardComments = {
|
|
323
|
-
modern: "Card 1 - Basic",
|
|
324
|
-
premium: "Card 2 - With Price",
|
|
325
|
-
blog: "Card 3 - With Tags",
|
|
326
|
-
minimal: "Card 4 - Minimal",
|
|
327
|
-
user: "Card 5 - With Avatar",
|
|
328
|
-
interactive: "Card 6 - Interactive"
|
|
329
|
-
};
|
|
330
|
-
|
|
331
|
-
let filteredHtml = htmlContent;
|
|
332
|
-
|
|
333
|
-
// Remove cards that weren't selected
|
|
334
|
-
for (const [key, comment] of Object.entries(cardComments)) {
|
|
335
|
-
if (!selectedCards.includes(key)) {
|
|
336
|
-
// Remove entire card div including comment
|
|
337
|
-
const pattern = new RegExp(`\\s*<!-- ${comment.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')} -->\\s*\\n\\s*<div class="card[^>]*>.*?<\\/div>\\s*\\n\\s*<\\/div>\\s*\\n`, 'gis');
|
|
338
|
-
filteredHtml = filteredHtml.replace(pattern, '\n ');
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
return filteredHtml;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
* Filter spinner variations based on user selection
|
|
347
|
-
*/
|
|
348
|
-
function filterSpinnerVariations(htmlContent, spinnerVariations, selectedSpinners) {
|
|
349
|
-
if (spinnerVariations === "all" || !selectedSpinners || selectedSpinners.length === 0) {
|
|
350
|
-
return htmlContent; // Return all spinners
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
const spinnerTypes = {
|
|
354
|
-
circle: "Circle Spinner",
|
|
355
|
-
dots: "Bouncing Dots",
|
|
356
|
-
pulse: "Pulse Loader",
|
|
357
|
-
bars: "Bar Loader",
|
|
358
|
-
gradient: "Gradient Ring"
|
|
359
|
-
};
|
|
360
|
-
|
|
361
|
-
let filteredHtml = htmlContent;
|
|
362
|
-
|
|
363
|
-
// Remove spinners that weren't selected
|
|
364
|
-
for (const [key, title] of Object.entries(spinnerTypes)) {
|
|
365
|
-
if (!selectedSpinners.includes(key)) {
|
|
366
|
-
// Remove entire spinner-group div
|
|
367
|
-
const pattern = new RegExp(`\\s*<!-- ${title} Spinner -->\\s*\\n\\s*<div class="spinner-group">.*?<\\/div>\\s*\\n\\s*<\\/div>\\s*\\n`, 'gis');
|
|
368
|
-
filteredHtml = filteredHtml.replace(pattern, '\n ');
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
return filteredHtml;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
/**
|
|
376
|
-
* Generate custom navigation items based on user input
|
|
377
|
-
*/
|
|
378
|
-
function generateNavigationItems(htmlContent, navItems) {
|
|
379
|
-
// Parse the comma-separated navigation items
|
|
380
|
-
const items = navItems.split(',').map(item => item.trim()).filter(item => item.length > 0);
|
|
381
|
-
|
|
382
|
-
// Generate navigation HTML
|
|
383
|
-
let navHtml = '';
|
|
384
|
-
items.forEach((item, index) => {
|
|
385
|
-
const itemId = item.toLowerCase().replace(/\s+/g, '-');
|
|
386
|
-
const activeClass = index === 0 ? ' active' : '';
|
|
387
|
-
navHtml += ` <li class="nav-item">
|
|
388
|
-
<a href="#${itemId}" class="nav-link${activeClass}">${item}</a>
|
|
389
|
-
</li>\n`;
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
// Replace the navigation items in the HTML
|
|
393
|
-
const navMenuRegex = /<ul class="nav-menu"[^>]*>[\s\S]*?<\/ul>/;
|
|
394
|
-
const replacement = `<ul class="nav-menu" id="navMenu">
|
|
395
|
-
${navHtml} </ul>`;
|
|
396
|
-
|
|
397
|
-
htmlContent = htmlContent.replace(navMenuRegex, replacement);
|
|
398
|
-
|
|
399
|
-
// Generate sections for each navigation item
|
|
400
|
-
let sectionsHtml = '';
|
|
401
|
-
items.forEach(item => {
|
|
402
|
-
const itemId = item.toLowerCase().replace(/\s+/g, '-');
|
|
403
|
-
sectionsHtml += ` <section id="${itemId}" class="section">
|
|
404
|
-
<h1>${item}</h1>
|
|
405
|
-
<p>Content for ${item} section</p>
|
|
406
|
-
</section>
|
|
407
|
-
|
|
408
|
-
`;
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
// Replace the sections in the HTML
|
|
412
|
-
const mainContentRegex = /<main class="main-content">[\s\S]*?<\/main>/;
|
|
413
|
-
const sectionsReplacement = `<main class="main-content">
|
|
414
|
-
${sectionsHtml} </main>`;
|
|
415
|
-
|
|
416
|
-
htmlContent = htmlContent.replace(mainContentRegex, sectionsReplacement);
|
|
417
|
-
|
|
418
|
-
return htmlContent;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
/**
|
|
422
|
-
* Generate custom form fields based on user selection
|
|
423
|
-
*/
|
|
424
|
-
function generateFormFields(htmlContent, formFields, customFormFields) {
|
|
425
|
-
let fieldsHtml = '';
|
|
426
|
-
|
|
427
|
-
// Add standard fields
|
|
428
|
-
formFields.forEach(field => {
|
|
429
|
-
switch(field) {
|
|
430
|
-
case 'name':
|
|
431
|
-
fieldsHtml += ` <div class="form-group">
|
|
432
|
-
<label for="name">Full Name</label>
|
|
433
|
-
<input type="text" id="name" name="name" required>
|
|
434
|
-
</div>
|
|
435
|
-
|
|
436
|
-
`;
|
|
437
|
-
break;
|
|
438
|
-
case 'email':
|
|
439
|
-
fieldsHtml += ` <div class="form-group">
|
|
440
|
-
<label for="email">Email</label>
|
|
441
|
-
<input type="email" id="email" name="email" required>
|
|
442
|
-
</div>
|
|
443
|
-
|
|
444
|
-
`;
|
|
445
|
-
break;
|
|
446
|
-
case 'phone':
|
|
447
|
-
fieldsHtml += ` <div class="form-group">
|
|
448
|
-
<label for="phone">Phone</label>
|
|
449
|
-
<input type="tel" id="phone" name="phone">
|
|
450
|
-
</div>
|
|
451
|
-
|
|
452
|
-
`;
|
|
453
|
-
break;
|
|
454
|
-
case 'subject':
|
|
455
|
-
fieldsHtml += ` <div class="form-group">
|
|
456
|
-
<label for="subject">Subject</label>
|
|
457
|
-
<select id="subject" name="subject" required>
|
|
458
|
-
<option value="">Select a subject</option>
|
|
459
|
-
<option value="support">Technical Support</option>
|
|
460
|
-
<option value="sales">Sales</option>
|
|
461
|
-
<option value="general">General</option>
|
|
462
|
-
</select>
|
|
463
|
-
</div>
|
|
464
|
-
|
|
465
|
-
`;
|
|
466
|
-
break;
|
|
467
|
-
case 'message':
|
|
468
|
-
fieldsHtml += ` <div class="form-group">
|
|
469
|
-
<label for="message">Message</label>
|
|
470
|
-
<textarea id="message" name="message" rows="5" required></textarea>
|
|
471
|
-
</div>
|
|
472
|
-
|
|
473
|
-
`;
|
|
474
|
-
break;
|
|
475
|
-
case 'terms':
|
|
476
|
-
fieldsHtml += ` <div class="form-group checkbox-group">
|
|
477
|
-
<input type="checkbox" id="terms" name="terms" required>
|
|
478
|
-
<label for="terms">I agree to the terms of service</label>
|
|
479
|
-
</div>
|
|
480
|
-
|
|
481
|
-
`;
|
|
482
|
-
break;
|
|
483
|
-
}
|
|
484
|
-
});
|
|
485
|
-
|
|
486
|
-
// Add custom fields if provided
|
|
487
|
-
if (customFormFields && customFormFields.trim().length > 0) {
|
|
488
|
-
const customFields = customFormFields.split(',').map(f => f.trim()).filter(f => f.length > 0);
|
|
489
|
-
|
|
490
|
-
customFields.forEach(field => {
|
|
491
|
-
const [type, label] = field.split(':').map(s => s.trim());
|
|
492
|
-
if (type && label) {
|
|
493
|
-
const fieldId = label.toLowerCase().replace(/\s+/g, '-');
|
|
494
|
-
|
|
495
|
-
if (type === 'textarea') {
|
|
496
|
-
fieldsHtml += ` <div class="form-group">
|
|
497
|
-
<label for="${fieldId}">${label}</label>
|
|
498
|
-
<textarea id="${fieldId}" name="${fieldId}" rows="5"></textarea>
|
|
499
|
-
</div>
|
|
500
|
-
|
|
501
|
-
`;
|
|
502
|
-
} else if (type === 'checkbox') {
|
|
503
|
-
fieldsHtml += ` <div class="form-group checkbox-group">
|
|
504
|
-
<input type="checkbox" id="${fieldId}" name="${fieldId}">
|
|
505
|
-
<label for="${fieldId}">${label}</label>
|
|
506
|
-
</div>
|
|
507
|
-
|
|
508
|
-
`;
|
|
509
|
-
} else if (type === 'select') {
|
|
510
|
-
fieldsHtml += ` <div class="form-group">
|
|
511
|
-
<label for="${fieldId}">${label}</label>
|
|
512
|
-
<select id="${fieldId}" name="${fieldId}">
|
|
513
|
-
<option value="">Select an option</option>
|
|
514
|
-
<option value="option1">Option 1</option>
|
|
515
|
-
<option value="option2">Option 2</option>
|
|
516
|
-
</select>
|
|
517
|
-
</div>
|
|
518
|
-
|
|
519
|
-
`;
|
|
520
|
-
} else {
|
|
521
|
-
// Default to input with specified type
|
|
522
|
-
fieldsHtml += ` <div class="form-group">
|
|
523
|
-
<label for="${fieldId}">${label}</label>
|
|
524
|
-
<input type="${type}" id="${fieldId}" name="${fieldId}">
|
|
525
|
-
</div>
|
|
526
|
-
|
|
527
|
-
`;
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
});
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
// Replace the form fields in the HTML
|
|
534
|
-
const formRegex = /<form class="form"[^>]*>[\s\S]*?<button type="submit"/;
|
|
535
|
-
const replacement = `<form class="form" id="contactForm">
|
|
536
|
-
<h1 class="form-title">Contact Us</h1>
|
|
537
|
-
<p class="form-subtitle">Fill in the details and we'll get back to you soon</p>
|
|
538
|
-
|
|
539
|
-
${fieldsHtml} <button type="submit"`;
|
|
540
|
-
|
|
541
|
-
htmlContent = htmlContent.replace(formRegex, replacement);
|
|
542
|
-
|
|
543
|
-
return htmlContent;
|
|
544
|
-
}
|
|
545
|
-
|
|
546
39
|
async function generateTemplate(options) {
|
|
547
|
-
const {
|
|
548
|
-
component,
|
|
549
|
-
name,
|
|
550
|
-
includeJs,
|
|
551
|
-
navItems,
|
|
552
|
-
formFields,
|
|
40
|
+
const {
|
|
41
|
+
component,
|
|
42
|
+
name,
|
|
43
|
+
includeJs,
|
|
44
|
+
navItems,
|
|
45
|
+
formFields,
|
|
553
46
|
customFormFields,
|
|
554
47
|
buttonVariations,
|
|
555
48
|
selectedButtons,
|
|
@@ -560,13 +53,13 @@ async function generateTemplate(options) {
|
|
|
560
53
|
colorScheme,
|
|
561
54
|
primaryColor,
|
|
562
55
|
secondaryColor,
|
|
563
|
-
darkMode
|
|
56
|
+
darkMode,
|
|
564
57
|
} = options;
|
|
565
58
|
|
|
566
59
|
// If colorScheme is provided, use those colors
|
|
567
60
|
let finalPrimaryColor = primaryColor;
|
|
568
61
|
let finalSecondaryColor = secondaryColor;
|
|
569
|
-
|
|
62
|
+
|
|
570
63
|
if (colorScheme && COLOR_SCHEMES[colorScheme]) {
|
|
571
64
|
const scheme = COLOR_SCHEMES[colorScheme];
|
|
572
65
|
finalPrimaryColor = scheme.primary;
|
|
@@ -576,7 +69,7 @@ async function generateTemplate(options) {
|
|
|
576
69
|
// Security: Validate component name
|
|
577
70
|
if (!VALID_COMPONENTS.includes(component)) {
|
|
578
71
|
throw new Error(
|
|
579
|
-
`Invalid component: ${component}. Must be one of: ${VALID_COMPONENTS.join(", ")}
|
|
72
|
+
`Invalid component: ${component}. Must be one of: ${VALID_COMPONENTS.join(", ")}`
|
|
580
73
|
);
|
|
581
74
|
}
|
|
582
75
|
|
|
@@ -588,45 +81,49 @@ async function generateTemplate(options) {
|
|
|
588
81
|
|
|
589
82
|
// Create output directory structure
|
|
590
83
|
const outputDir = path.join(process.cwd(), safeName);
|
|
591
|
-
const cssDir =
|
|
592
|
-
const jsDir = path.join(outputDir, "js");
|
|
593
|
-
|
|
594
|
-
await fs.mkdir(outputDir, { recursive: true });
|
|
595
|
-
await fs.mkdir(cssDir, { recursive: true });
|
|
596
|
-
await fs.mkdir(jsDir, { recursive: true });
|
|
84
|
+
const { cssDir, jsDir } = await createComponentDirs(outputDir);
|
|
597
85
|
|
|
598
86
|
// Get template content
|
|
599
|
-
const templateDir =
|
|
87
|
+
const templateDir = getTemplatePath(component);
|
|
600
88
|
|
|
601
89
|
// Copy HTML file
|
|
602
|
-
let htmlContent = await
|
|
603
|
-
path.join(templateDir, "index.html"),
|
|
604
|
-
"utf-8",
|
|
605
|
-
);
|
|
90
|
+
let htmlContent = await readTemplateHtml(component);
|
|
606
91
|
|
|
607
92
|
// Replace placeholder name
|
|
608
93
|
htmlContent = htmlContent.replace(/{{name}}/g, safeName);
|
|
609
|
-
|
|
94
|
+
|
|
610
95
|
// Filter button variations
|
|
611
96
|
if (component === "button") {
|
|
612
|
-
htmlContent = filterButtonVariations(
|
|
97
|
+
htmlContent = filterButtonVariations(
|
|
98
|
+
htmlContent,
|
|
99
|
+
buttonVariations,
|
|
100
|
+
selectedButtons
|
|
101
|
+
);
|
|
613
102
|
}
|
|
614
|
-
|
|
103
|
+
|
|
615
104
|
// Filter card variations
|
|
616
105
|
if (component === "card") {
|
|
617
|
-
htmlContent = filterCardVariations(
|
|
106
|
+
htmlContent = filterCardVariations(
|
|
107
|
+
htmlContent,
|
|
108
|
+
cardVariations,
|
|
109
|
+
selectedCards
|
|
110
|
+
);
|
|
618
111
|
}
|
|
619
|
-
|
|
112
|
+
|
|
620
113
|
// Filter spinner variations
|
|
621
114
|
if (component === "spinner") {
|
|
622
|
-
htmlContent = filterSpinnerVariations(
|
|
115
|
+
htmlContent = filterSpinnerVariations(
|
|
116
|
+
htmlContent,
|
|
117
|
+
spinnerVariations,
|
|
118
|
+
selectedSpinners
|
|
119
|
+
);
|
|
623
120
|
}
|
|
624
|
-
|
|
121
|
+
|
|
625
122
|
// Custom navigation items
|
|
626
123
|
if (component === "navigation" && navItems) {
|
|
627
124
|
htmlContent = generateNavigationItems(htmlContent, navItems);
|
|
628
125
|
}
|
|
629
|
-
|
|
126
|
+
|
|
630
127
|
// Custom form fields
|
|
631
128
|
if (component === "form" && formFields) {
|
|
632
129
|
htmlContent = generateFormFields(htmlContent, formFields, customFormFields);
|
|
@@ -637,30 +134,28 @@ async function generateTemplate(options) {
|
|
|
637
134
|
// Insert script tag before closing </body> tag
|
|
638
135
|
htmlContent = htmlContent.replace(
|
|
639
136
|
"</body>",
|
|
640
|
-
' <script src="js/script.js"></script>\n</body>'
|
|
137
|
+
' <script src="js/script.js"></script>\n</body>'
|
|
641
138
|
);
|
|
642
139
|
}
|
|
643
140
|
|
|
644
141
|
// Update CSS link to point to css/ folder
|
|
645
142
|
htmlContent = htmlContent.replace(
|
|
646
143
|
/<link[^>]*href="style\.css"[^>]*>/g,
|
|
647
|
-
' <link rel="stylesheet" href="css/style.css">'
|
|
144
|
+
' <link rel="stylesheet" href="css/style.css">'
|
|
648
145
|
);
|
|
649
146
|
|
|
650
|
-
|
|
651
|
-
htmlContent = await formatHtml(htmlContent);
|
|
652
|
-
|
|
653
|
-
await fs.writeFile(path.join(outputDir, "index.html"), htmlContent);
|
|
147
|
+
await writeHtmlFile(path.join(outputDir, "index.html"), htmlContent);
|
|
654
148
|
|
|
655
149
|
// Copy CSS file to css/ folder
|
|
656
|
-
let cssContent = await
|
|
657
|
-
path.join(templateDir, "style.css"),
|
|
658
|
-
"utf-8",
|
|
659
|
-
);
|
|
150
|
+
let cssContent = await readTemplateCss(component);
|
|
660
151
|
|
|
661
152
|
// Apply custom colors if provided (either from colorScheme or direct colors)
|
|
662
153
|
if (finalPrimaryColor || finalSecondaryColor) {
|
|
663
|
-
cssContent = applyCustomColors(
|
|
154
|
+
cssContent = applyCustomColors(
|
|
155
|
+
cssContent,
|
|
156
|
+
finalPrimaryColor,
|
|
157
|
+
finalSecondaryColor
|
|
158
|
+
);
|
|
664
159
|
}
|
|
665
160
|
|
|
666
161
|
// Add dark mode support if requested
|
|
@@ -668,26 +163,20 @@ async function generateTemplate(options) {
|
|
|
668
163
|
cssContent = addDarkModeStyles(cssContent);
|
|
669
164
|
}
|
|
670
165
|
|
|
671
|
-
|
|
672
|
-
await fs.writeFile(path.join(cssDir, "style.css"), formattedCss);
|
|
166
|
+
await writeCssFile(path.join(cssDir, "style.css"), cssContent);
|
|
673
167
|
|
|
674
168
|
// Copy JS file to js/ folder if requested
|
|
675
169
|
if (includeJs) {
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
);
|
|
681
|
-
const formattedJs = await formatJs(jsContent);
|
|
682
|
-
await fs.writeFile(path.join(jsDir, "script.js"), formattedJs);
|
|
683
|
-
} catch (error) {
|
|
170
|
+
const jsContent = await readTemplateJs(component);
|
|
171
|
+
if (jsContent) {
|
|
172
|
+
await writeJsFile(path.join(jsDir, "script.js"), jsContent);
|
|
173
|
+
} else {
|
|
684
174
|
// If no JS template exists, create a basic one
|
|
685
|
-
|
|
686
|
-
await fs.writeFile(path.join(jsDir, "script.js"), basicJs);
|
|
175
|
+
await writeJsFile(path.join(jsDir, "script.js"), "// Add your JavaScript here\n");
|
|
687
176
|
}
|
|
688
177
|
}
|
|
689
178
|
|
|
690
179
|
return outputDir;
|
|
691
180
|
}
|
|
692
181
|
|
|
693
|
-
|
|
182
|
+
export { generateTemplate, COLOR_SCHEMES, getColorScheme };
|