create-template-html-css 2.0.4 → 2.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 +436 -0
- package/CODE-SPLITTING-GUIDE.md +274 -0
- package/COMPONENTS-GALLERY.html +143 -8
- package/HTML-VS-REACT.md +289 -0
- package/QUICKSTART-REACT.md +293 -0
- package/REACT-SUPPORT-SUMMARY.md +235 -0
- package/README.md +261 -12
- package/bin/cli.js +100 -759
- package/bin/commands/create.js +288 -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 +97 -0
- package/src/react-component-templates.js +182 -0
- package/src/react-file-operations.js +172 -0
- package/src/react-generator.js +219 -0
- package/src/react-templates.js +418 -0
- package/src/templates/basic-components-templates.js +157 -0
- package/src/templates/form-components-templates.js +194 -0
- package/src/templates/interactive-components-templates.js +139 -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/alert/Alert.css +158 -0
- package/templates-react/alert/Alert.example.jsx +106 -0
- package/templates-react/alert/Alert.jsx +61 -0
- package/templates-react/badge/Badge.css +196 -0
- package/templates-react/badge/Badge.example.jsx +182 -0
- package/templates-react/badge/Badge.jsx +44 -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/checkbox/Checkbox.css +217 -0
- package/templates-react/checkbox/Checkbox.example.jsx +141 -0
- package/templates-react/checkbox/Checkbox.jsx +82 -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/dropdown/Dropdown.css +237 -0
- package/templates-react/dropdown/Dropdown.example.jsx +98 -0
- package/templates-react/dropdown/Dropdown.jsx +154 -0
- package/templates-react/form/Form.css +128 -0
- package/templates-react/form/Form.example.jsx +64 -0
- package/templates-react/form/Form.jsx +125 -0
- package/templates-react/input/Input.css +113 -0
- package/templates-react/input/Input.example.jsx +82 -0
- package/templates-react/input/Input.jsx +87 -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/navbar/Navbar.css +139 -0
- package/templates-react/navbar/Navbar.example.jsx +37 -0
- package/templates-react/navbar/Navbar.jsx +62 -0
- package/templates-react/progress/Progress.css +247 -0
- package/templates-react/progress/Progress.example.jsx +244 -0
- package/templates-react/progress/Progress.jsx +79 -0
- package/templates-react/switch/Switch.css +244 -0
- package/templates-react/switch/Switch.example.jsx +221 -0
- package/templates-react/switch/Switch.jsx +98 -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/templates-react/tooltip/Tooltip.css +165 -0
- package/templates-react/tooltip/Tooltip.example.jsx +166 -0
- package/templates-react/tooltip/Tooltip.jsx +176 -0
package/src/inserter.js
CHANGED
|
@@ -1,150 +1,43 @@
|
|
|
1
|
-
const fs = require("fs").promises;
|
|
2
|
-
const path = require("path");
|
|
3
|
-
|
|
4
|
-
// Security: Validate component name against whitelist
|
|
5
|
-
const VALID_COMPONENTS = [
|
|
6
|
-
"button",
|
|
7
|
-
"card",
|
|
8
|
-
"form",
|
|
9
|
-
"navigation",
|
|
10
|
-
"modal",
|
|
11
|
-
"footer",
|
|
12
|
-
"hero",
|
|
13
|
-
"slider",
|
|
14
|
-
"table",
|
|
15
|
-
"spinner",
|
|
16
|
-
"animated-card",
|
|
17
|
-
"typing-effect",
|
|
18
|
-
"fade-gallery",
|
|
19
|
-
"grid-layout",
|
|
20
|
-
"masonry-grid",
|
|
21
|
-
"dashboard-grid",
|
|
22
|
-
"flex-layout",
|
|
23
|
-
"flex-cards",
|
|
24
|
-
"flex-dashboard",
|
|
25
|
-
"todo-list",
|
|
26
|
-
"counter",
|
|
27
|
-
"accordion",
|
|
28
|
-
"tabs",
|
|
29
|
-
];
|
|
30
|
-
|
|
31
1
|
/**
|
|
32
|
-
*
|
|
2
|
+
* Component insertion utilities
|
|
3
|
+
* Inserts components into existing HTML files
|
|
33
4
|
*/
|
|
34
|
-
function getIndentation(line) {
|
|
35
|
-
const match = line.match(/^(\s*)/);
|
|
36
|
-
return match ? match[1] : "";
|
|
37
|
-
}
|
|
38
5
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
6
|
+
import fs from "fs/promises";
|
|
7
|
+
import path from "path";
|
|
8
|
+
import { ensureDir, writeCssFile, writeJsFile, writeHtmlFile } from "./utils/file-utils.js";
|
|
9
|
+
import {
|
|
10
|
+
VALID_COMPONENTS,
|
|
11
|
+
isComponentAlreadyInserted,
|
|
12
|
+
validateHtmlStructure,
|
|
13
|
+
} from "./inserters/validation-utils.js";
|
|
14
|
+
import { getHtmlIndentation } from "./inserters/html-utils.js";
|
|
15
|
+
import { createBackup } from "./inserters/backup-utils.js";
|
|
16
|
+
import {
|
|
17
|
+
loadComponentHtml,
|
|
18
|
+
loadComponentCss,
|
|
19
|
+
loadComponentJs,
|
|
20
|
+
extractBodyContent,
|
|
21
|
+
} from "./inserters/component-loader.js";
|
|
22
|
+
import {
|
|
23
|
+
normalizeIndentation,
|
|
24
|
+
createInlineStyleTag,
|
|
25
|
+
createInlineScriptTag,
|
|
26
|
+
createStyleLink,
|
|
27
|
+
createScriptTag,
|
|
28
|
+
createComponentInsertion,
|
|
29
|
+
} from "./inserters/indentation-utils.js";
|
|
62
30
|
|
|
63
31
|
/**
|
|
64
|
-
*
|
|
32
|
+
* Inserts a component into an existing HTML file
|
|
33
|
+
* @param {Object} options - Insertion options
|
|
34
|
+
* @param {string} options.component - Component name to insert
|
|
35
|
+
* @param {string} options.targetFile - Target HTML file path
|
|
36
|
+
* @param {string} options.styleMode - Style insertion mode ('inline' or 'separate')
|
|
37
|
+
* @param {string} options.scriptMode - Script insertion mode ('inline' or 'separate')
|
|
38
|
+
* @param {boolean} options.createBackup - Whether to create a backup before insertion
|
|
39
|
+
* @returns {Promise<Object>} Insertion result with success status and paths
|
|
65
40
|
*/
|
|
66
|
-
async function formatCss(cssContent) {
|
|
67
|
-
try {
|
|
68
|
-
const prettier = require("prettier");
|
|
69
|
-
return await prettier.format(cssContent, { parser: "css" });
|
|
70
|
-
} catch (error) {
|
|
71
|
-
// Prettier not installed or error formatting - return original content
|
|
72
|
-
return cssContent;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Formats JavaScript content with prettier (optional - falls back to original if not available)
|
|
78
|
-
*/
|
|
79
|
-
async function formatJs(jsContent) {
|
|
80
|
-
try {
|
|
81
|
-
const prettier = require("prettier");
|
|
82
|
-
return await prettier.format(jsContent, { parser: "babel" });
|
|
83
|
-
} catch (error) {
|
|
84
|
-
// Prettier not installed or error formatting - return original content
|
|
85
|
-
return jsContent;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Gets the indentation level used in an HTML file
|
|
91
|
-
*/
|
|
92
|
-
function getHtmlIndentation(htmlContent) {
|
|
93
|
-
// Look for any indented line to determine the standard indentation
|
|
94
|
-
const match = htmlContent.match(/\n(\s+)\S/);
|
|
95
|
-
return match ? match[1] : " "; // default to 4 spaces
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Validates that HTML file has proper structure
|
|
100
|
-
* @param {string} htmlContent - The HTML content to validate
|
|
101
|
-
* @returns {Object} Object with valid property and any errors
|
|
102
|
-
*/
|
|
103
|
-
function validateHtmlStructure(htmlContent) {
|
|
104
|
-
const errors = [];
|
|
105
|
-
|
|
106
|
-
if (!htmlContent.includes("<!DOCTYPE")) {
|
|
107
|
-
errors.push("Missing DOCTYPE declaration");
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (!htmlContent.includes("<html")) {
|
|
111
|
-
errors.push("Missing <html> tag");
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (!htmlContent.includes("<head>") && !htmlContent.includes("<head ")) {
|
|
115
|
-
errors.push("Missing <head> tag");
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (!htmlContent.includes("</head>")) {
|
|
119
|
-
errors.push("Missing closing </head> tag");
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (!htmlContent.includes("<body>") && !htmlContent.includes("<body ")) {
|
|
123
|
-
errors.push("Missing <body> tag");
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (!htmlContent.includes("</body>")) {
|
|
127
|
-
errors.push("Missing closing </body> tag");
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return {
|
|
131
|
-
valid: errors.length === 0,
|
|
132
|
-
errors,
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Creates a backup of the original file before insertion
|
|
138
|
-
* @param {string} targetPath - Path to the original file
|
|
139
|
-
* @returns {Promise<string>} Path to the backup file
|
|
140
|
-
*/
|
|
141
|
-
async function createBackup(targetPath) {
|
|
142
|
-
const backupPath = `${targetPath}.backup`;
|
|
143
|
-
const content = await fs.readFile(targetPath, "utf-8");
|
|
144
|
-
await fs.writeFile(backupPath, content, "utf-8");
|
|
145
|
-
return backupPath;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
41
|
async function insertComponent(options) {
|
|
149
42
|
const {
|
|
150
43
|
component,
|
|
@@ -203,150 +96,79 @@ async function insertComponent(options) {
|
|
|
203
96
|
throw new Error("Target HTML file does not have a </head> tag");
|
|
204
97
|
}
|
|
205
98
|
|
|
206
|
-
//
|
|
207
|
-
const
|
|
208
|
-
const
|
|
209
|
-
path.join(templateDir, "index.html"),
|
|
210
|
-
"utf-8",
|
|
211
|
-
);
|
|
212
|
-
|
|
213
|
-
// Try to read CSS from css/ subfolder, fall back to root
|
|
214
|
-
let componentCss;
|
|
215
|
-
try {
|
|
216
|
-
componentCss = await fs.readFile(
|
|
217
|
-
path.join(templateDir, "css", "style.css"),
|
|
218
|
-
"utf-8",
|
|
219
|
-
);
|
|
220
|
-
} catch {
|
|
221
|
-
componentCss = await fs.readFile(
|
|
222
|
-
path.join(templateDir, "style.css"),
|
|
223
|
-
"utf-8",
|
|
224
|
-
);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Extract component body content (only the inner content, not the body tags)
|
|
228
|
-
const bodyMatch = componentHtml.match(/<body[^>]*>\s*([\s\S]*?)\s*<\/body>/i);
|
|
229
|
-
if (!bodyMatch) {
|
|
230
|
-
throw new Error("Invalid component template structure");
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
let componentBody = bodyMatch[1].trim();
|
|
99
|
+
// Load component templates
|
|
100
|
+
const componentHtml = await loadComponentHtml(component);
|
|
101
|
+
const componentCss = await loadComponentCss(component);
|
|
234
102
|
|
|
235
|
-
//
|
|
236
|
-
componentBody =
|
|
237
|
-
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "")
|
|
238
|
-
.trim();
|
|
103
|
+
// Extract component body content
|
|
104
|
+
let componentBody = extractBodyContent(componentHtml);
|
|
239
105
|
|
|
240
106
|
// Get indentation used in the HTML file
|
|
241
107
|
const baseIndent = getHtmlIndentation(htmlContent);
|
|
242
108
|
|
|
243
109
|
// Normalize component body indentation
|
|
244
|
-
|
|
245
|
-
.split("\n")
|
|
246
|
-
.map((line) => {
|
|
247
|
-
if (line.trim() === "") return "";
|
|
248
|
-
return baseIndent + line.trim();
|
|
249
|
-
})
|
|
250
|
-
.join("\n");
|
|
251
|
-
componentBody = lines;
|
|
110
|
+
componentBody = normalizeIndentation(componentBody, baseIndent);
|
|
252
111
|
|
|
253
112
|
// Insert component HTML before closing </body> tag
|
|
254
113
|
htmlContent = htmlContent.replace(
|
|
255
114
|
"</body>",
|
|
256
|
-
|
|
115
|
+
createComponentInsertion(component, componentBody, baseIndent),
|
|
257
116
|
);
|
|
258
117
|
|
|
259
118
|
// Handle CSS
|
|
260
119
|
if (styleMode === "inline") {
|
|
261
|
-
// Normalize CSS indentation
|
|
262
|
-
const normalizedCss = componentCss
|
|
263
|
-
.split("\n")
|
|
264
|
-
.map((line) => {
|
|
265
|
-
if (line.trim() === "") return "";
|
|
266
|
-
return baseIndent + " " + line.trim();
|
|
267
|
-
})
|
|
268
|
-
.join("\n");
|
|
269
|
-
|
|
270
120
|
htmlContent = htmlContent.replace(
|
|
271
121
|
"</head>",
|
|
272
|
-
|
|
122
|
+
createInlineStyleTag(component, componentCss, baseIndent),
|
|
273
123
|
);
|
|
274
124
|
} else if (styleMode === "separate") {
|
|
275
125
|
// Create css directory if needed
|
|
276
126
|
const cssDir = path.join(path.dirname(targetPath), "css");
|
|
277
|
-
await
|
|
127
|
+
await ensureDir(cssDir);
|
|
278
128
|
|
|
279
129
|
// Create separate CSS file in css/ folder
|
|
280
130
|
const cssFileName = `${component}-component.css`;
|
|
281
131
|
const cssPath = path.join(cssDir, cssFileName);
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
);
|
|
285
|
-
await fs.writeFile(cssPath, formattedCss);
|
|
132
|
+
const cssWithComment = `/* ${component.toUpperCase()} Component Styles */\n\n${componentCss}`;
|
|
133
|
+
await writeCssFile(cssPath, cssWithComment);
|
|
286
134
|
|
|
287
135
|
// Add link to CSS file
|
|
288
136
|
htmlContent = htmlContent.replace(
|
|
289
137
|
"</head>",
|
|
290
|
-
|
|
138
|
+
createStyleLink(cssFileName, baseIndent),
|
|
291
139
|
);
|
|
292
140
|
}
|
|
293
141
|
|
|
294
142
|
// Handle JavaScript
|
|
295
|
-
|
|
296
|
-
// Try to read JS from js/ subfolder, fall back to root
|
|
297
|
-
let componentJs;
|
|
298
|
-
try {
|
|
299
|
-
componentJs = await fs.readFile(
|
|
300
|
-
path.join(templateDir, "js", "script.js"),
|
|
301
|
-
"utf-8",
|
|
302
|
-
);
|
|
303
|
-
} catch {
|
|
304
|
-
componentJs = await fs.readFile(
|
|
305
|
-
path.join(templateDir, "script.js"),
|
|
306
|
-
"utf-8",
|
|
307
|
-
);
|
|
308
|
-
}
|
|
143
|
+
const componentJs = await loadComponentJs(component);
|
|
309
144
|
|
|
145
|
+
if (componentJs) {
|
|
310
146
|
if (scriptMode === "inline") {
|
|
311
|
-
// Normalize JS indentation
|
|
312
|
-
const normalizedJs = componentJs
|
|
313
|
-
.split("\n")
|
|
314
|
-
.map((line) => {
|
|
315
|
-
if (line.trim() === "") return "";
|
|
316
|
-
return baseIndent + " " + line.trim();
|
|
317
|
-
})
|
|
318
|
-
.join("\n");
|
|
319
|
-
|
|
320
147
|
htmlContent = htmlContent.replace(
|
|
321
148
|
"</body>",
|
|
322
|
-
|
|
149
|
+
createInlineScriptTag(component, componentJs, baseIndent),
|
|
323
150
|
);
|
|
324
151
|
} else if (scriptMode === "separate") {
|
|
325
152
|
// Create js directory if needed
|
|
326
153
|
const jsDir = path.join(path.dirname(targetPath), "js");
|
|
327
|
-
await
|
|
154
|
+
await ensureDir(jsDir);
|
|
328
155
|
|
|
329
156
|
// Create separate JS file in js/ folder
|
|
330
157
|
const jsFileName = `${component}-component.js`;
|
|
331
158
|
const jsPath = path.join(jsDir, jsFileName);
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
);
|
|
335
|
-
await fs.writeFile(jsPath, formattedJs);
|
|
159
|
+
const jsWithComment = `// ${component.toUpperCase()} Component Script\n\n${componentJs}`;
|
|
160
|
+
await writeJsFile(jsPath, jsWithComment);
|
|
336
161
|
|
|
337
162
|
// Add script tag
|
|
338
163
|
htmlContent = htmlContent.replace(
|
|
339
164
|
"</body>",
|
|
340
|
-
|
|
165
|
+
createScriptTag(component, jsFileName, baseIndent),
|
|
341
166
|
);
|
|
342
167
|
}
|
|
343
|
-
} catch (error) {
|
|
344
|
-
// No JavaScript file for this component, skip
|
|
345
168
|
}
|
|
346
169
|
|
|
347
170
|
// Write updated HTML with prettier formatting
|
|
348
|
-
|
|
349
|
-
await fs.writeFile(targetPath, formattedHtml);
|
|
171
|
+
await writeHtmlFile(targetPath, htmlContent);
|
|
350
172
|
|
|
351
173
|
return {
|
|
352
174
|
targetFile: targetPath,
|
|
@@ -358,4 +180,4 @@ async function insertComponent(options) {
|
|
|
358
180
|
};
|
|
359
181
|
}
|
|
360
182
|
|
|
361
|
-
|
|
183
|
+
export { insertComponent, validateHtmlStructure, createBackup };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backup utilities for file operations
|
|
3
|
+
* Handles creating backups before modifying files
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from "fs/promises";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Creates a backup of the original file before insertion
|
|
10
|
+
* @param {string} targetPath - Path to the original file
|
|
11
|
+
* @returns {Promise<string>} Path to the backup file
|
|
12
|
+
*/
|
|
13
|
+
async function createBackup(targetPath) {
|
|
14
|
+
const backupPath = `${targetPath}.backup`;
|
|
15
|
+
const content = await fs.readFile(targetPath, "utf-8");
|
|
16
|
+
await fs.writeFile(backupPath, content, "utf-8");
|
|
17
|
+
return backupPath;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { createBackup };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component template loader
|
|
3
|
+
* Loads HTML, CSS, and JavaScript files from template directories
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
readTemplateHtml,
|
|
8
|
+
readTemplateCss,
|
|
9
|
+
readTemplateJs,
|
|
10
|
+
} from "../utils/template-loader.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Loads component HTML template
|
|
14
|
+
* @param {string} component - Component name
|
|
15
|
+
* @returns {Promise<string>} HTML content
|
|
16
|
+
*/
|
|
17
|
+
async function loadComponentHtml(component) {
|
|
18
|
+
return await readTemplateHtml(component);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Loads component CSS template
|
|
23
|
+
* Tries css/ subfolder first, falls back to root
|
|
24
|
+
* @param {string} component - Component name
|
|
25
|
+
* @returns {Promise<string>} CSS content
|
|
26
|
+
*/
|
|
27
|
+
async function loadComponentCss(component) {
|
|
28
|
+
return await readTemplateCss(component);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Loads component JavaScript template
|
|
33
|
+
* Tries js/ subfolder first, falls back to root
|
|
34
|
+
* @param {string} component - Component name
|
|
35
|
+
* @returns {Promise<string|null>} JavaScript content or null if not found
|
|
36
|
+
*/
|
|
37
|
+
async function loadComponentJs(component) {
|
|
38
|
+
return await readTemplateJs(component);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Extracts body content from component HTML
|
|
43
|
+
* Removes script and style tags
|
|
44
|
+
* @param {string} componentHtml - Full component HTML
|
|
45
|
+
* @returns {string} Extracted body content
|
|
46
|
+
*/
|
|
47
|
+
function extractBodyContent(componentHtml) {
|
|
48
|
+
const bodyMatch = componentHtml.match(/<body[^>]*>\s*([\s\S]*?)\s*<\/body>/i);
|
|
49
|
+
if (!bodyMatch) {
|
|
50
|
+
throw new Error("Invalid component template structure");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let componentBody = bodyMatch[1].trim();
|
|
54
|
+
|
|
55
|
+
// Remove any script and style tags that might be in the body
|
|
56
|
+
componentBody = componentBody
|
|
57
|
+
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "")
|
|
58
|
+
.trim();
|
|
59
|
+
|
|
60
|
+
return componentBody;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export {
|
|
64
|
+
loadComponentHtml,
|
|
65
|
+
loadComponentCss,
|
|
66
|
+
loadComponentJs,
|
|
67
|
+
extractBodyContent,
|
|
68
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML manipulation utilities
|
|
3
|
+
* Handles indentation and HTML parsing operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Extracts the indentation string from a line
|
|
8
|
+
* @param {string} line - Line to extract indentation from
|
|
9
|
+
* @returns {string} The indentation string (spaces or tabs)
|
|
10
|
+
*/
|
|
11
|
+
function getIndentation(line) {
|
|
12
|
+
const match = line.match(/^(\s+)/);
|
|
13
|
+
return match ? match[1] : "";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Determines the standard indentation used in HTML content
|
|
18
|
+
* @param {string} htmlContent - HTML content to analyze
|
|
19
|
+
* @returns {string} The detected indentation string (defaults to 2 spaces)
|
|
20
|
+
*/
|
|
21
|
+
function getHtmlIndentation(htmlContent) {
|
|
22
|
+
const lines = htmlContent.split("\n");
|
|
23
|
+
for (const line of lines) {
|
|
24
|
+
if (line.trim() && line.startsWith(" ")) {
|
|
25
|
+
return getIndentation(line);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return " "; // Default to 2 spaces
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { getIndentation, getHtmlIndentation };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content indentation utilities
|
|
3
|
+
* Handles indentation normalization for HTML, CSS, and JS content
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Normalizes content indentation with base indent
|
|
8
|
+
* @param {string} content - Content to normalize
|
|
9
|
+
* @param {string} baseIndent - Base indentation to apply
|
|
10
|
+
* @param {string} extraIndent - Additional indentation (default: empty)
|
|
11
|
+
* @returns {string} Normalized content with proper indentation
|
|
12
|
+
*/
|
|
13
|
+
export function normalizeIndentation(content, baseIndent, extraIndent = "") {
|
|
14
|
+
return content
|
|
15
|
+
.split("\n")
|
|
16
|
+
.map((line) => {
|
|
17
|
+
if (line.trim() === "") return "";
|
|
18
|
+
return baseIndent + extraIndent + line.trim();
|
|
19
|
+
})
|
|
20
|
+
.join("\n");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Creates inline style tag with normalized indentation
|
|
25
|
+
* @param {string} component - Component name
|
|
26
|
+
* @param {string} cssContent - CSS content
|
|
27
|
+
* @param {string} baseIndent - Base indentation
|
|
28
|
+
* @returns {string} Formatted style tag
|
|
29
|
+
*/
|
|
30
|
+
export function createInlineStyleTag(component, cssContent, baseIndent) {
|
|
31
|
+
const normalizedCss = normalizeIndentation(cssContent, baseIndent, " ");
|
|
32
|
+
return `${baseIndent}<style id="${component}-styles">
|
|
33
|
+
${baseIndent} /* ${component.toUpperCase()} Component Styles */
|
|
34
|
+
${normalizedCss}
|
|
35
|
+
${baseIndent}</style>
|
|
36
|
+
</head>`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Creates inline script tag with normalized indentation
|
|
41
|
+
* @param {string} component - Component name
|
|
42
|
+
* @param {string} jsContent - JavaScript content
|
|
43
|
+
* @param {string} baseIndent - Base indentation
|
|
44
|
+
* @returns {string} Formatted script tag
|
|
45
|
+
*/
|
|
46
|
+
export function createInlineScriptTag(component, jsContent, baseIndent) {
|
|
47
|
+
const normalizedJs = normalizeIndentation(jsContent, baseIndent, " ");
|
|
48
|
+
return `${baseIndent}<script id="${component}-script">
|
|
49
|
+
${baseIndent} // ${component.toUpperCase()} Component Script
|
|
50
|
+
${normalizedJs}
|
|
51
|
+
${baseIndent}</script>
|
|
52
|
+
</body>`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Creates external stylesheet link
|
|
57
|
+
* @param {string} cssFileName - CSS file name
|
|
58
|
+
* @param {string} baseIndent - Base indentation
|
|
59
|
+
* @returns {string} Link tag
|
|
60
|
+
*/
|
|
61
|
+
export function createStyleLink(cssFileName, baseIndent) {
|
|
62
|
+
return `${baseIndent}<link rel="stylesheet" href="css/${cssFileName}">
|
|
63
|
+
</head>`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Creates external script tag
|
|
68
|
+
* @param {string} component - Component name
|
|
69
|
+
* @param {string} jsFileName - JavaScript file name
|
|
70
|
+
* @param {string} baseIndent - Base indentation
|
|
71
|
+
* @returns {string} Script tag
|
|
72
|
+
*/
|
|
73
|
+
export function createScriptTag(component, jsFileName, baseIndent) {
|
|
74
|
+
return `${baseIndent}<script src="js/${jsFileName}" id="${component}-script"></script>
|
|
75
|
+
</body>`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Creates component HTML insertion with comment
|
|
80
|
+
* @param {string} component - Component name
|
|
81
|
+
* @param {string} componentBody - Component HTML body
|
|
82
|
+
* @param {string} baseIndent - Base indentation
|
|
83
|
+
* @returns {string} Formatted HTML with component comment
|
|
84
|
+
*/
|
|
85
|
+
export function createComponentInsertion(component, componentBody, baseIndent) {
|
|
86
|
+
return `${baseIndent}<!-- ${component.toUpperCase()} Component -->
|
|
87
|
+
${componentBody}
|
|
88
|
+
|
|
89
|
+
</body>`;
|
|
90
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component validation utilities for the insert command
|
|
3
|
+
* Validates component names and HTML structure
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { VALID_COMPONENTS } from "../components-registry.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Checks if a component has already been inserted in the HTML content
|
|
10
|
+
* @param {string} htmlContent - HTML content to check
|
|
11
|
+
* @param {string} component - Component name to look for
|
|
12
|
+
* @returns {boolean} True if component is already inserted
|
|
13
|
+
*/
|
|
14
|
+
function isComponentAlreadyInserted(htmlContent, component) {
|
|
15
|
+
const componentComment = `<!-- ${component.toUpperCase()} Component -->`;
|
|
16
|
+
return htmlContent.includes(componentComment);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Validates HTML file structure for required tags
|
|
21
|
+
* @param {string} htmlContent - HTML content to validate
|
|
22
|
+
* @returns {Object} Validation result with valid flag and errors array
|
|
23
|
+
*/
|
|
24
|
+
function validateHtmlStructure(htmlContent) {
|
|
25
|
+
const errors = [];
|
|
26
|
+
|
|
27
|
+
if (!htmlContent.includes("<head>") && !htmlContent.includes("<head ")) {
|
|
28
|
+
errors.push("Missing <head> tag");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!htmlContent.includes("</head>")) {
|
|
32
|
+
errors.push("Missing closing </head> tag");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!htmlContent.includes("<body>") && !htmlContent.includes("<body ")) {
|
|
36
|
+
errors.push("Missing <body> tag");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!htmlContent.includes("</body>")) {
|
|
40
|
+
errors.push("Missing closing </body> tag");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
valid: errors.length === 0,
|
|
45
|
+
errors,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export { VALID_COMPONENTS, isComponentAlreadyInserted, validateHtmlStructure };
|