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
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Color scheme presets for templates
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const COLOR_SCHEMES = {
|
|
6
|
+
vibrant: {
|
|
7
|
+
name: "Vibrant",
|
|
8
|
+
primary: "#FF6B6B",
|
|
9
|
+
secondary: "#4ECDC4",
|
|
10
|
+
description: "Bold reds and teals - energetic and modern",
|
|
11
|
+
},
|
|
12
|
+
pastel: {
|
|
13
|
+
name: "Pastel",
|
|
14
|
+
primary: "#FFB3BA",
|
|
15
|
+
secondary: "#FFDFBA",
|
|
16
|
+
description: "Soft pinks and peaches - calm and friendly",
|
|
17
|
+
},
|
|
18
|
+
ocean: {
|
|
19
|
+
name: "Ocean",
|
|
20
|
+
primary: "#0066CC",
|
|
21
|
+
secondary: "#00CCFF",
|
|
22
|
+
description: "Deep blues and cyans - professional and cool",
|
|
23
|
+
},
|
|
24
|
+
sunset: {
|
|
25
|
+
name: "Sunset",
|
|
26
|
+
primary: "#FF6B35",
|
|
27
|
+
secondary: "#FFA500",
|
|
28
|
+
description: "Warm oranges and reds - energetic glow",
|
|
29
|
+
},
|
|
30
|
+
forest: {
|
|
31
|
+
name: "Forest",
|
|
32
|
+
primary: "#2D6A4F",
|
|
33
|
+
secondary: "#40916C",
|
|
34
|
+
description: "Green tones - natural and organic",
|
|
35
|
+
},
|
|
36
|
+
purple: {
|
|
37
|
+
name: "Purple",
|
|
38
|
+
primary: "#7209B7",
|
|
39
|
+
secondary: "#B5179E",
|
|
40
|
+
description: "Deep purples - elegant and premium",
|
|
41
|
+
},
|
|
42
|
+
minimal: {
|
|
43
|
+
name: "Minimal",
|
|
44
|
+
primary: "#1A1A1A",
|
|
45
|
+
secondary: "#666666",
|
|
46
|
+
description: "Grays and blacks - clean and simple",
|
|
47
|
+
},
|
|
48
|
+
coral: {
|
|
49
|
+
name: "Coral",
|
|
50
|
+
primary: "#FF6B9D",
|
|
51
|
+
secondary: "#FFA07A",
|
|
52
|
+
description: "Coral pinks and salmon - warm and playful",
|
|
53
|
+
},
|
|
54
|
+
teal: {
|
|
55
|
+
name: "Teal",
|
|
56
|
+
primary: "#008B8B",
|
|
57
|
+
secondary: "#20B2AA",
|
|
58
|
+
description: "Teal hues - balanced and professional",
|
|
59
|
+
},
|
|
60
|
+
neon: {
|
|
61
|
+
name: "Neon",
|
|
62
|
+
primary: "#FF006E",
|
|
63
|
+
secondary: "#00D9FF",
|
|
64
|
+
description: "Bright neon colors - bold and futuristic",
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get color scheme by preset name
|
|
70
|
+
* @param {string} schemeName - Name of the color scheme
|
|
71
|
+
* @returns {Object|null} Color scheme object or null if not found
|
|
72
|
+
*/
|
|
73
|
+
export function getColorScheme(schemeName) {
|
|
74
|
+
if (COLOR_SCHEMES[schemeName]) {
|
|
75
|
+
return COLOR_SCHEMES[schemeName];
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Color utility functions for CSS styling
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Convert hex color to RGB values
|
|
7
|
+
* @param {string} hex - Hex color code (e.g., "#FF6B6B")
|
|
8
|
+
* @returns {string} RGB values as string (e.g., "255, 107, 107")
|
|
9
|
+
*/
|
|
10
|
+
export function hexToRgb(hex) {
|
|
11
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
12
|
+
if (result) {
|
|
13
|
+
const r = parseInt(result[1], 16);
|
|
14
|
+
const g = parseInt(result[2], 16);
|
|
15
|
+
const b = parseInt(result[3], 16);
|
|
16
|
+
return `${r}, ${g}, ${b}`;
|
|
17
|
+
}
|
|
18
|
+
return "102, 126, 234"; // fallback to default primary
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Apply custom colors to CSS content using CSS variables
|
|
23
|
+
* @param {string} cssContent - Original CSS content
|
|
24
|
+
* @param {string} primaryColor - Primary color hex code
|
|
25
|
+
* @param {string} secondaryColor - Secondary color hex code
|
|
26
|
+
* @returns {string} Modified CSS with custom colors
|
|
27
|
+
*/
|
|
28
|
+
export function applyCustomColors(cssContent, primaryColor, secondaryColor) {
|
|
29
|
+
if (!primaryColor && !secondaryColor) {
|
|
30
|
+
return cssContent;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Create CSS variable overrides
|
|
34
|
+
let colorVariables = ":root {\n";
|
|
35
|
+
|
|
36
|
+
if (primaryColor) {
|
|
37
|
+
colorVariables += ` --primary-color: ${primaryColor};\n`;
|
|
38
|
+
// Also add as root variable that can override gradients
|
|
39
|
+
colorVariables += ` --primary-rgb: ${hexToRgb(primaryColor)};\n`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (secondaryColor) {
|
|
43
|
+
colorVariables += ` --secondary-color: ${secondaryColor};\n`;
|
|
44
|
+
colorVariables += ` --secondary-rgb: ${hexToRgb(secondaryColor)};\n`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
colorVariables += "}\n\n";
|
|
48
|
+
|
|
49
|
+
// If CSS doesn't have :root, add it
|
|
50
|
+
if (!cssContent.includes(":root")) {
|
|
51
|
+
return colorVariables + cssContent;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Replace existing :root with extended one
|
|
55
|
+
return cssContent.replace(/:root\s*{/, colorVariables.trim() + "\n\n:root {");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Add dark mode styles to CSS
|
|
60
|
+
* @param {string} cssContent - Original CSS content
|
|
61
|
+
* @returns {string} CSS with dark mode media query added
|
|
62
|
+
*/
|
|
63
|
+
export function addDarkModeStyles(cssContent) {
|
|
64
|
+
if (cssContent.includes("prefers-color-scheme")) {
|
|
65
|
+
return cssContent; // Already has dark mode
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Create dark mode media query
|
|
69
|
+
const darkModeStyles = `
|
|
70
|
+
/* Dark Mode Support */
|
|
71
|
+
@media (prefers-color-scheme: dark) {
|
|
72
|
+
:root {
|
|
73
|
+
--bg-primary: #1a1a1a;
|
|
74
|
+
--bg-secondary: #2d2d2d;
|
|
75
|
+
--text-primary: #ffffff;
|
|
76
|
+
--text-secondary: #b0b0b0;
|
|
77
|
+
--border-color: #404040;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
body {
|
|
81
|
+
background-color: var(--bg-primary, #1a1a1a);
|
|
82
|
+
color: var(--text-primary, #ffffff);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.container,
|
|
86
|
+
.card,
|
|
87
|
+
.modal-content,
|
|
88
|
+
.form-container {
|
|
89
|
+
background-color: var(--bg-secondary, #2d2d2d);
|
|
90
|
+
color: var(--text-primary, #ffffff);
|
|
91
|
+
border-color: var(--border-color, #404040);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
input,
|
|
95
|
+
textarea,
|
|
96
|
+
select {
|
|
97
|
+
background-color: var(--bg-primary, #1a1a1a);
|
|
98
|
+
color: var(--text-primary, #ffffff);
|
|
99
|
+
border-color: var(--border-color, #404040);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
input::placeholder {
|
|
103
|
+
color: var(--text-secondary, #b0b0b0);
|
|
104
|
+
}
|
|
105
|
+
}`;
|
|
106
|
+
|
|
107
|
+
return cssContent + darkModeStyles;
|
|
108
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component filtering utilities
|
|
3
|
+
* Filters variations for buttons, cards, and spinners
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Filter button variations based on user selection
|
|
8
|
+
* @param {string} htmlContent - Original HTML content
|
|
9
|
+
* @param {string} buttonVariations - "all" or "select"
|
|
10
|
+
* @param {Array<string>} selectedButtons - Array of selected button types
|
|
11
|
+
* @returns {string} Filtered HTML content
|
|
12
|
+
*/
|
|
13
|
+
export function filterButtonVariations(
|
|
14
|
+
htmlContent,
|
|
15
|
+
buttonVariations,
|
|
16
|
+
selectedButtons
|
|
17
|
+
) {
|
|
18
|
+
if (
|
|
19
|
+
buttonVariations === "all" ||
|
|
20
|
+
!selectedButtons ||
|
|
21
|
+
selectedButtons.length === 0
|
|
22
|
+
) {
|
|
23
|
+
return htmlContent; // Return all buttons
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Map selection to button classes
|
|
27
|
+
const buttonMap = {
|
|
28
|
+
primary: "btn-primary",
|
|
29
|
+
secondary: "btn-secondary",
|
|
30
|
+
success: "btn-success",
|
|
31
|
+
danger: "btn-danger",
|
|
32
|
+
outlined: "btn-outlined",
|
|
33
|
+
disabled: "disabled",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
let filteredHtml = htmlContent;
|
|
37
|
+
|
|
38
|
+
// Remove buttons that weren't selected
|
|
39
|
+
for (const [key, className] of Object.entries(buttonMap)) {
|
|
40
|
+
if (!selectedButtons.includes(key)) {
|
|
41
|
+
// Remove button and its comment
|
|
42
|
+
const patterns = [
|
|
43
|
+
new RegExp(
|
|
44
|
+
`\\s*<!-- ${key.charAt(0).toUpperCase() + key.slice(1)} Button -->\\s*\\n\\s*<button[^>]*${className}[^>]*>.*?<\\/button>\\s*\\n`,
|
|
45
|
+
"gis"
|
|
46
|
+
),
|
|
47
|
+
new RegExp(
|
|
48
|
+
`\\s*<button[^>]*${className}[^>]*>.*?<\\/button>\\s*\\n`,
|
|
49
|
+
"gis"
|
|
50
|
+
),
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
patterns.forEach((pattern) => {
|
|
54
|
+
filteredHtml = filteredHtml.replace(pattern, "\n ");
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return filteredHtml;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Filter card variations based on user selection
|
|
64
|
+
* @param {string} htmlContent - Original HTML content
|
|
65
|
+
* @param {string} cardVariations - "all" or "select"
|
|
66
|
+
* @param {Array<string>} selectedCards - Array of selected card types
|
|
67
|
+
* @returns {string} Filtered HTML content
|
|
68
|
+
*/
|
|
69
|
+
export function filterCardVariations(
|
|
70
|
+
htmlContent,
|
|
71
|
+
cardVariations,
|
|
72
|
+
selectedCards
|
|
73
|
+
) {
|
|
74
|
+
if (
|
|
75
|
+
cardVariations === "all" ||
|
|
76
|
+
!selectedCards ||
|
|
77
|
+
selectedCards.length === 0
|
|
78
|
+
) {
|
|
79
|
+
return htmlContent; // Return all cards
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const cardComments = {
|
|
83
|
+
modern: "Card 1 - Basic",
|
|
84
|
+
premium: "Card 2 - With Price",
|
|
85
|
+
blog: "Card 3 - With Tags",
|
|
86
|
+
minimal: "Card 4 - Minimal",
|
|
87
|
+
user: "Card 5 - With Avatar",
|
|
88
|
+
interactive: "Card 6 - Interactive",
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
let filteredHtml = htmlContent;
|
|
92
|
+
|
|
93
|
+
// Remove cards that weren't selected
|
|
94
|
+
for (const [key, comment] of Object.entries(cardComments)) {
|
|
95
|
+
if (!selectedCards.includes(key)) {
|
|
96
|
+
// Remove entire card div including comment
|
|
97
|
+
const pattern = new RegExp(
|
|
98
|
+
`\\s*<!-- ${comment.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&")} -->\\s*\\n\\s*<div class="card[^>]*>.*?<\\/div>\\s*\\n\\s*<\\/div>\\s*\\n`,
|
|
99
|
+
"gis"
|
|
100
|
+
);
|
|
101
|
+
filteredHtml = filteredHtml.replace(pattern, "\n ");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return filteredHtml;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Filter spinner variations based on user selection
|
|
110
|
+
* @param {string} htmlContent - Original HTML content
|
|
111
|
+
* @param {string} spinnerVariations - "all" or "select"
|
|
112
|
+
* @param {Array<string>} selectedSpinners - Array of selected spinner types
|
|
113
|
+
* @returns {string} Filtered HTML content
|
|
114
|
+
*/
|
|
115
|
+
export function filterSpinnerVariations(
|
|
116
|
+
htmlContent,
|
|
117
|
+
spinnerVariations,
|
|
118
|
+
selectedSpinners
|
|
119
|
+
) {
|
|
120
|
+
if (
|
|
121
|
+
spinnerVariations === "all" ||
|
|
122
|
+
!selectedSpinners ||
|
|
123
|
+
selectedSpinners.length === 0
|
|
124
|
+
) {
|
|
125
|
+
return htmlContent; // Return all spinners
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const spinnerTypes = {
|
|
129
|
+
circle: "Circle Spinner",
|
|
130
|
+
dots: "Bouncing Dots",
|
|
131
|
+
pulse: "Pulse Loader",
|
|
132
|
+
bars: "Bar Loader",
|
|
133
|
+
gradient: "Gradient Ring",
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
let filteredHtml = htmlContent;
|
|
137
|
+
|
|
138
|
+
// Remove spinners that weren't selected
|
|
139
|
+
for (const [key, title] of Object.entries(spinnerTypes)) {
|
|
140
|
+
if (!selectedSpinners.includes(key)) {
|
|
141
|
+
// Remove entire spinner-group div
|
|
142
|
+
const pattern = new RegExp(
|
|
143
|
+
`\\s*<!-- ${title} Spinner -->\\s*\\n\\s*<div class="spinner-group">.*?<\\/div>\\s*\\n\\s*<\\/div>\\s*\\n`,
|
|
144
|
+
"gis"
|
|
145
|
+
);
|
|
146
|
+
filteredHtml = filteredHtml.replace(pattern, "\n ");
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return filteredHtml;
|
|
151
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML content generators for navigation and forms
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { textToId, parseCommaSeparated, parseKeyValuePairs } from "../utils/string-utils.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Generate custom navigation items based on user input
|
|
9
|
+
* @param {string} htmlContent - Original HTML content
|
|
10
|
+
* @param {string} navItems - Comma-separated list of navigation items
|
|
11
|
+
* @returns {string} HTML with custom navigation
|
|
12
|
+
*/
|
|
13
|
+
export function generateNavigationItems(htmlContent, navItems) {
|
|
14
|
+
// Parse the comma-separated navigation items
|
|
15
|
+
const items = parseCommaSeparated(navItems);
|
|
16
|
+
|
|
17
|
+
// Generate navigation HTML
|
|
18
|
+
let navHtml = "";
|
|
19
|
+
items.forEach((item, index) => {
|
|
20
|
+
const itemId = textToId(item);
|
|
21
|
+
const activeClass = index === 0 ? " active" : "";
|
|
22
|
+
navHtml += ` <li class="nav-item">
|
|
23
|
+
<a href="#${itemId}" class="nav-link${activeClass}">${item}</a>
|
|
24
|
+
</li>\n`;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Replace the navigation items in the HTML
|
|
28
|
+
const navMenuRegex = /<ul class="nav-menu"[^>]*>[\s\S]*?<\/ul>/;
|
|
29
|
+
const replacement = `<ul class="nav-menu" id="navMenu">
|
|
30
|
+
${navHtml} </ul>`;
|
|
31
|
+
|
|
32
|
+
htmlContent = htmlContent.replace(navMenuRegex, replacement);
|
|
33
|
+
|
|
34
|
+
// Generate sections for each navigation item
|
|
35
|
+
let sectionsHtml = "";
|
|
36
|
+
items.forEach((item) => {
|
|
37
|
+
const itemId = textToId(item);
|
|
38
|
+
sectionsHtml += ` <section id="${itemId}" class="section">
|
|
39
|
+
<h1>${item}</h1>
|
|
40
|
+
<p>Content for ${item} section</p>
|
|
41
|
+
</section>
|
|
42
|
+
|
|
43
|
+
`;
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Replace the sections in the HTML
|
|
47
|
+
const mainContentRegex = /<main class="main-content">[\s\S]*?<\/main>/;
|
|
48
|
+
const sectionsReplacement = `<main class="main-content">
|
|
49
|
+
${sectionsHtml} </main>`;
|
|
50
|
+
|
|
51
|
+
htmlContent = htmlContent.replace(mainContentRegex, sectionsReplacement);
|
|
52
|
+
|
|
53
|
+
return htmlContent;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Generate custom form fields based on user selection
|
|
58
|
+
* @param {string} htmlContent - Original HTML content
|
|
59
|
+
* @param {Array<string>} formFields - Array of standard field types
|
|
60
|
+
* @param {string} customFormFields - Custom fields in format "type:label"
|
|
61
|
+
* @returns {string} HTML with custom form fields
|
|
62
|
+
*/
|
|
63
|
+
export function generateFormFields(htmlContent, formFields, customFormFields) {
|
|
64
|
+
let fieldsHtml = "";
|
|
65
|
+
|
|
66
|
+
// Add standard fields
|
|
67
|
+
formFields.forEach((field) => {
|
|
68
|
+
switch (field) {
|
|
69
|
+
case "name":
|
|
70
|
+
fieldsHtml += ` <div class="form-group">
|
|
71
|
+
<label for="name">Full Name</label>
|
|
72
|
+
<input type="text" id="name" name="name" required>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
`;
|
|
76
|
+
break;
|
|
77
|
+
case "email":
|
|
78
|
+
fieldsHtml += ` <div class="form-group">
|
|
79
|
+
<label for="email">Email</label>
|
|
80
|
+
<input type="email" id="email" name="email" required>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
`;
|
|
84
|
+
break;
|
|
85
|
+
case "phone":
|
|
86
|
+
fieldsHtml += ` <div class="form-group">
|
|
87
|
+
<label for="phone">Phone</label>
|
|
88
|
+
<input type="tel" id="phone" name="phone">
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
`;
|
|
92
|
+
break;
|
|
93
|
+
case "subject":
|
|
94
|
+
fieldsHtml += ` <div class="form-group">
|
|
95
|
+
<label for="subject">Subject</label>
|
|
96
|
+
<select id="subject" name="subject" required>
|
|
97
|
+
<option value="">Select a subject</option>
|
|
98
|
+
<option value="support">Technical Support</option>
|
|
99
|
+
<option value="sales">Sales</option>
|
|
100
|
+
<option value="general">General</option>
|
|
101
|
+
</select>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
`;
|
|
105
|
+
break;
|
|
106
|
+
case "message":
|
|
107
|
+
fieldsHtml += ` <div class="form-group">
|
|
108
|
+
<label for="message">Message</label>
|
|
109
|
+
<textarea id="message" name="message" rows="5" required></textarea>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
`;
|
|
113
|
+
break;
|
|
114
|
+
case "terms":
|
|
115
|
+
fieldsHtml += ` <div class="form-group checkbox-group">
|
|
116
|
+
<input type="checkbox" id="terms" name="terms" required>
|
|
117
|
+
<label for="terms">I agree to the terms of service</label>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
`;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Add custom fields if provided
|
|
126
|
+
if (customFormFields && customFormFields.trim().length > 0) {
|
|
127
|
+
const customFields = parseKeyValuePairs(customFormFields);
|
|
128
|
+
|
|
129
|
+
customFields.forEach(({ key: type, value: label }) => {
|
|
130
|
+
const fieldId = textToId(label);
|
|
131
|
+
|
|
132
|
+
if (type === "textarea") {
|
|
133
|
+
fieldsHtml += ` <div class="form-group">
|
|
134
|
+
<label for="${fieldId}">${label}</label>
|
|
135
|
+
<textarea id="${fieldId}" name="${fieldId}" rows="5"></textarea>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
`;
|
|
139
|
+
} else if (type === "checkbox") {
|
|
140
|
+
fieldsHtml += ` <div class="form-group checkbox-group">
|
|
141
|
+
<input type="checkbox" id="${fieldId}" name="${fieldId}">
|
|
142
|
+
<label for="${fieldId}">${label}</label>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
`;
|
|
146
|
+
} else if (type === "select") {
|
|
147
|
+
fieldsHtml += ` <div class="form-group">
|
|
148
|
+
<label for="${fieldId}">${label}</label>
|
|
149
|
+
<select id="${fieldId}" name="${fieldId}">
|
|
150
|
+
<option value="">Select an option</option>
|
|
151
|
+
<option value="option1">Option 1</option>
|
|
152
|
+
<option value="option2">Option 2</option>
|
|
153
|
+
</select>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
`;
|
|
157
|
+
} else {
|
|
158
|
+
// Default to input with specified type
|
|
159
|
+
fieldsHtml += ` <div class="form-group">
|
|
160
|
+
<label for="${fieldId}">${label}</label>
|
|
161
|
+
<input type="${type}" id="${fieldId}" name="${fieldId}">
|
|
162
|
+
</div>
|
|
163
|
+
|
|
164
|
+
`;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Replace the form fields in the HTML
|
|
170
|
+
const formRegex = /<form class="form"[^>]*>[\s\S]*?<button type="submit"/;
|
|
171
|
+
const replacement = `<form class="form" id="contactForm">
|
|
172
|
+
<h1 class="form-title">Contact Us</h1>
|
|
173
|
+
<p class="form-subtitle">Fill in the details and we'll get back to you soon</p>
|
|
174
|
+
|
|
175
|
+
${fieldsHtml} <button type="submit"`;
|
|
176
|
+
|
|
177
|
+
htmlContent = htmlContent.replace(formRegex, replacement);
|
|
178
|
+
|
|
179
|
+
return htmlContent;
|
|
180
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component validation and security utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { VALID_COMPONENTS } from "../components-registry.js";
|
|
6
|
+
import { sanitizeForFilename } from "../utils/string-utils.js";
|
|
7
|
+
|
|
8
|
+
export { VALID_COMPONENTS };
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Sanitize filename to prevent path traversal
|
|
12
|
+
* @param {string} filename - The filename to sanitize
|
|
13
|
+
* @returns {string|null} Sanitized filename or null if invalid
|
|
14
|
+
*/
|
|
15
|
+
export function sanitizeFilename(filename) {
|
|
16
|
+
// Check for path traversal attempts
|
|
17
|
+
if (
|
|
18
|
+
filename.includes("/") ||
|
|
19
|
+
filename.includes("\\") ||
|
|
20
|
+
filename === ".." ||
|
|
21
|
+
filename.startsWith("../") ||
|
|
22
|
+
filename.startsWith("..\\") ||
|
|
23
|
+
filename.includes("/../") ||
|
|
24
|
+
filename.includes("\\..\\")
|
|
25
|
+
) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Additional validation: ensure name contains at least one alphanumeric character
|
|
30
|
+
if (!filename || !/[a-zA-Z0-9]/.test(filename)) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Remove any remaining dangerous characters
|
|
35
|
+
const sanitized = filename.replace(/[<>:"|?*]/g, "").trim();
|
|
36
|
+
|
|
37
|
+
// Final check: ensure not empty after sanitization
|
|
38
|
+
if (!sanitized || sanitized.length === 0) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return sanitized;
|
|
43
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
export { generateTemplate } from "./generator.js";
|
|
2
|
+
export { generateReactTemplate } from "./react-generator.js";
|