create-template-html-css 1.4.0 → 1.4.1
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 +24 -0
- package/INSERT-DEMO.md +31 -2
- package/bin/cli.js +116 -57
- package/package.json +1 -1
- package/src/inserter.js +69 -22
- package/test-insert.html +54 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.4.1] - 2025-01-29
|
|
9
|
+
|
|
10
|
+
### Improved
|
|
11
|
+
- **Enhanced Insert Feature**:
|
|
12
|
+
- Better indentation handling - respects existing HTML file formatting
|
|
13
|
+
- Improved duplicate detection - prevents inserting same component twice
|
|
14
|
+
- Unique component IDs - all styles/scripts get unique identifiers
|
|
15
|
+
- File validation - checks if HTML file exists before insertion
|
|
16
|
+
- All 19 components now available in insert command (was only 9 basic ones)
|
|
17
|
+
|
|
18
|
+
- **Improved CLI**:
|
|
19
|
+
- Better visual design with emojis and separators
|
|
20
|
+
- Organized component lists with categories
|
|
21
|
+
- File existence validation before insertion
|
|
22
|
+
- Enhanced help text and examples
|
|
23
|
+
- Better status messages with clear summaries
|
|
24
|
+
- Separated files as default for CSS insertion
|
|
25
|
+
|
|
26
|
+
### Technical Details
|
|
27
|
+
- Simplified indentation detection using HTML file's existing format
|
|
28
|
+
- Added `path` and `fs` imports for better file handling
|
|
29
|
+
- Improved error messages for better debugging
|
|
30
|
+
- Extended insert command with all template categories
|
|
31
|
+
|
|
8
32
|
## [1.4.0] - 2025-01-18
|
|
9
33
|
|
|
10
34
|
### Added
|
package/INSERT-DEMO.md
CHANGED
|
@@ -2,7 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## How to Use the Insert Command
|
|
4
4
|
|
|
5
|
-
The `insert` command allows you to add pre-styled components to your existing HTML pages without creating new projects.
|
|
5
|
+
The `insert` command allows you to add pre-styled components to your existing HTML pages without creating new projects. Components are smartly integrated with proper indentation and ID attributes for easy customization.
|
|
6
|
+
|
|
7
|
+
## How It Works
|
|
8
|
+
|
|
9
|
+
When inserting a component:
|
|
10
|
+
1. ✅ Detects existing indentation in your HTML file
|
|
11
|
+
2. ✅ Respects your code formatting style
|
|
12
|
+
3. ✅ Adds unique IDs to styles and scripts (e.g., `button-styles`, `button-script`)
|
|
13
|
+
4. ✅ Prevents duplicate insertions (warns if component already exists)
|
|
14
|
+
5. ✅ Validates HTML structure (checks for `<head>` and `<body>` tags)
|
|
6
15
|
|
|
7
16
|
## Example Usage
|
|
8
17
|
|
|
@@ -132,7 +141,9 @@ npm run insert
|
|
|
132
141
|
|
|
133
142
|
## Available Components
|
|
134
143
|
|
|
135
|
-
All
|
|
144
|
+
All 19 templates are available for insertion:
|
|
145
|
+
|
|
146
|
+
### Basic Components
|
|
136
147
|
- Button - Styled button variations
|
|
137
148
|
- Card - Product/content cards
|
|
138
149
|
- Form - Contact/input forms
|
|
@@ -140,3 +151,21 @@ All 7 templates are available for insertion:
|
|
|
140
151
|
- Modal - Dialog popups
|
|
141
152
|
- Footer - Page footer
|
|
142
153
|
- Hero - Hero sections with CTA
|
|
154
|
+
- Slider - Image carousel
|
|
155
|
+
- Table - Data tables with features
|
|
156
|
+
|
|
157
|
+
### Animation Templates
|
|
158
|
+
- Spinner - Loading animations
|
|
159
|
+
- Animated Card - Interactive card effects
|
|
160
|
+
- Typing Effect - Text animations
|
|
161
|
+
- Fade Gallery - Image gallery with effects
|
|
162
|
+
|
|
163
|
+
### Grid Layouts (CSS Grid)
|
|
164
|
+
- Grid Layout - CSS Grid examples
|
|
165
|
+
- Masonry Grid - Pinterest-style layout
|
|
166
|
+
- Dashboard Grid - Admin dashboard
|
|
167
|
+
|
|
168
|
+
### Flexbox Layouts
|
|
169
|
+
- Flex Layout - Flexbox patterns
|
|
170
|
+
- Flex Cards - Equal-height cards
|
|
171
|
+
- Flex Dashboard - Admin dashboard with Flexbox
|
package/bin/cli.js
CHANGED
|
@@ -2,26 +2,40 @@
|
|
|
2
2
|
|
|
3
3
|
const { program } = require('commander');
|
|
4
4
|
const inquirer = require('inquirer').default || require('inquirer');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const fs = require('fs').promises;
|
|
5
7
|
const { generateTemplate } = require('../src/generator');
|
|
6
8
|
const { insertComponent } = require('../src/inserter');
|
|
7
9
|
const chalk = require('chalk');
|
|
8
10
|
|
|
9
11
|
program
|
|
10
12
|
.name('create-template')
|
|
11
|
-
.description('
|
|
12
|
-
.version('1.
|
|
13
|
+
.description(chalk.cyan('🎨 Create HTML/CSS UI component templates in seconds'))
|
|
14
|
+
.version('1.4.0');
|
|
15
|
+
|
|
16
|
+
// Add intro message
|
|
17
|
+
program.on('--help', () => {
|
|
18
|
+
console.log('\n' + chalk.cyan('Examples:'));
|
|
19
|
+
console.log(' $ create-template create # Create a new template');
|
|
20
|
+
console.log(' $ create-template insert # Insert into existing HTML');
|
|
21
|
+
console.log(' $ create-template list # List all templates');
|
|
22
|
+
console.log('');
|
|
23
|
+
});
|
|
13
24
|
|
|
14
25
|
program
|
|
15
26
|
.command('create')
|
|
16
|
-
.description('Create a new HTML/CSS template')
|
|
27
|
+
.description(chalk.green('Create a new HTML/CSS template component'))
|
|
17
28
|
.action(async () => {
|
|
18
29
|
try {
|
|
30
|
+
console.log(chalk.cyan('\n✨ Creating a new template component...\n'));
|
|
31
|
+
|
|
19
32
|
const answers = await inquirer.prompt([
|
|
20
33
|
{
|
|
21
34
|
type: 'list',
|
|
22
35
|
name: 'component',
|
|
23
36
|
message: 'What component would you like to create?',
|
|
24
37
|
choices: [
|
|
38
|
+
new inquirer.Separator(chalk.gray('─ Basic Components')),
|
|
25
39
|
{ name: 'Button', value: 'button' },
|
|
26
40
|
{ name: 'Card', value: 'card' },
|
|
27
41
|
{ name: 'Form', value: 'form' },
|
|
@@ -31,15 +45,18 @@ program
|
|
|
31
45
|
{ name: 'Hero Section', value: 'hero' },
|
|
32
46
|
{ name: 'Slider', value: 'slider' },
|
|
33
47
|
{ name: 'Table', value: 'table' },
|
|
48
|
+
new inquirer.Separator(chalk.gray('─ Animation Templates')),
|
|
34
49
|
{ name: 'Spinner (Loading Animations)', value: 'spinner' },
|
|
35
50
|
{ name: 'Animated Card (Interactive Cards)', value: 'animated-card' },
|
|
36
51
|
{ name: 'Typing Effect (Text Animations)', value: 'typing-effect' },
|
|
37
52
|
{ name: 'Fade Gallery (Image Gallery)', value: 'fade-gallery' },
|
|
38
|
-
|
|
53
|
+
new inquirer.Separator(chalk.gray('─ Grid Layouts (CSS Grid)')),
|
|
54
|
+
{ name: 'Grid Layout', value: 'grid-layout' },
|
|
39
55
|
{ name: 'Masonry Grid (Pinterest-style)', value: 'masonry-grid' },
|
|
40
56
|
{ name: 'Dashboard Grid (Admin Panel)', value: 'dashboard-grid' },
|
|
57
|
+
new inquirer.Separator(chalk.gray('─ Flexbox Layouts')),
|
|
41
58
|
{ name: 'Flex Layout (Flexbox Patterns)', value: 'flex-layout' },
|
|
42
|
-
{ name: 'Flex Cards (
|
|
59
|
+
{ name: 'Flex Cards (Equal-height cards)', value: 'flex-cards' },
|
|
43
60
|
{ name: 'Flex Dashboard (Flexbox Admin)', value: 'flex-dashboard' }
|
|
44
61
|
]
|
|
45
62
|
},
|
|
@@ -65,37 +82,50 @@ program
|
|
|
65
82
|
type: 'confirm',
|
|
66
83
|
name: 'includeJs',
|
|
67
84
|
message: 'Include JavaScript file?',
|
|
68
|
-
default:
|
|
85
|
+
default: true
|
|
69
86
|
}
|
|
70
87
|
]);
|
|
71
88
|
|
|
72
89
|
await generateTemplate(answers);
|
|
73
|
-
|
|
90
|
+
|
|
91
|
+
console.log('\n' + chalk.green('✓ Template created successfully!'));
|
|
92
|
+
console.log(chalk.gray(` Location: ./${answers.name}/`));
|
|
93
|
+
console.log(chalk.gray(` Files: index.html, style.css${answers.includeJs ? ', script.js' : ''}`));
|
|
94
|
+
console.log('');
|
|
74
95
|
} catch (error) {
|
|
75
|
-
console.error(chalk.red('Error
|
|
96
|
+
console.error(chalk.red('✗ Error:'), error.message);
|
|
76
97
|
process.exit(1);
|
|
77
98
|
}
|
|
78
99
|
});
|
|
79
100
|
|
|
80
101
|
program
|
|
81
102
|
.command('insert')
|
|
82
|
-
.description('Insert a component into an existing HTML page')
|
|
103
|
+
.description(chalk.green('Insert a component into an existing HTML page'))
|
|
83
104
|
.action(async () => {
|
|
84
105
|
try {
|
|
106
|
+
console.log(chalk.cyan('\n🚀 Inserting component into HTML file...\n'));
|
|
107
|
+
|
|
85
108
|
const answers = await inquirer.prompt([
|
|
86
109
|
{
|
|
87
110
|
type: 'input',
|
|
88
111
|
name: 'targetFile',
|
|
89
112
|
message: 'Enter the path to your HTML file:',
|
|
90
113
|
default: 'index.html',
|
|
91
|
-
validate: (input) => {
|
|
114
|
+
validate: async (input) => {
|
|
92
115
|
if (!input || input.trim().length === 0) {
|
|
93
116
|
return 'Please enter a file path';
|
|
94
117
|
}
|
|
95
118
|
if (!input.toLowerCase().endsWith('.html')) {
|
|
96
119
|
return 'File must be an HTML file (.html)';
|
|
97
120
|
}
|
|
98
|
-
|
|
121
|
+
|
|
122
|
+
// Check if file exists
|
|
123
|
+
try {
|
|
124
|
+
await fs.access(path.resolve(process.cwd(), input));
|
|
125
|
+
return true;
|
|
126
|
+
} catch {
|
|
127
|
+
return `File not found: ${input}`;
|
|
128
|
+
}
|
|
99
129
|
}
|
|
100
130
|
},
|
|
101
131
|
{
|
|
@@ -103,6 +133,7 @@ program
|
|
|
103
133
|
name: 'component',
|
|
104
134
|
message: 'Which component would you like to insert?',
|
|
105
135
|
choices: [
|
|
136
|
+
new inquirer.Separator(chalk.gray('─ Basic Components')),
|
|
106
137
|
{ name: 'Button', value: 'button' },
|
|
107
138
|
{ name: 'Card', value: 'card' },
|
|
108
139
|
{ name: 'Form', value: 'form' },
|
|
@@ -111,78 +142,106 @@ program
|
|
|
111
142
|
{ name: 'Footer', value: 'footer' },
|
|
112
143
|
{ name: 'Hero Section', value: 'hero' },
|
|
113
144
|
{ name: 'Slider', value: 'slider' },
|
|
114
|
-
{ name: 'Table', value: 'table' }
|
|
145
|
+
{ name: 'Table', value: 'table' },
|
|
146
|
+
new inquirer.Separator(chalk.gray('─ Animation Templates')),
|
|
147
|
+
{ name: 'Spinner', value: 'spinner' },
|
|
148
|
+
{ name: 'Animated Card', value: 'animated-card' },
|
|
149
|
+
{ name: 'Typing Effect', value: 'typing-effect' },
|
|
150
|
+
{ name: 'Fade Gallery', value: 'fade-gallery' },
|
|
151
|
+
new inquirer.Separator(chalk.gray('─ Grid Layouts')),
|
|
152
|
+
{ name: 'Grid Layout', value: 'grid-layout' },
|
|
153
|
+
{ name: 'Masonry Grid', value: 'masonry-grid' },
|
|
154
|
+
{ name: 'Dashboard Grid', value: 'dashboard-grid' },
|
|
155
|
+
new inquirer.Separator(chalk.gray('─ Flexbox Layouts')),
|
|
156
|
+
{ name: 'Flex Layout', value: 'flex-layout' },
|
|
157
|
+
{ name: 'Flex Cards', value: 'flex-cards' },
|
|
158
|
+
{ name: 'Flex Dashboard', value: 'flex-dashboard' }
|
|
115
159
|
]
|
|
116
160
|
},
|
|
117
|
-
{
|
|
118
|
-
type: 'list',
|
|
119
|
-
name: 'styleMode',
|
|
120
|
-
message: 'How should the CSS be added?',
|
|
121
|
-
choices: [
|
|
122
|
-
{ name: 'Inline (inside <style> tag)', value: 'inline' },
|
|
123
|
-
{ name: 'Separate file', value: 'separate' },
|
|
124
|
-
{ name: 'Skip (I\'ll add it manually)', value: 'skip' }
|
|
125
|
-
],
|
|
126
|
-
default: 'inline'
|
|
127
|
-
},
|
|
128
161
|
{
|
|
129
162
|
type: 'list',
|
|
130
163
|
name: 'scriptMode',
|
|
131
164
|
message: 'How should the JavaScript be added?',
|
|
132
165
|
choices: [
|
|
166
|
+
{ name: 'Separate file (recommended)', value: 'separate' },
|
|
133
167
|
{ name: 'Inline (inside <script> tag)', value: 'inline' },
|
|
134
|
-
{ name: 'Separate file', value: 'separate' },
|
|
135
168
|
{ name: 'Skip (I\'ll add it manually)', value: 'skip' }
|
|
136
169
|
],
|
|
137
|
-
default: '
|
|
170
|
+
default: 'separate'
|
|
138
171
|
}
|
|
139
172
|
]);
|
|
140
173
|
|
|
174
|
+
// CSS is always separate (external)
|
|
175
|
+
answers.styleMode = 'separate';
|
|
176
|
+
|
|
141
177
|
const result = await insertComponent(answers);
|
|
142
|
-
|
|
143
|
-
console.log(chalk.
|
|
144
|
-
console.log(chalk.cyan(
|
|
145
|
-
console.log(chalk.
|
|
146
|
-
console.log(chalk.
|
|
178
|
+
|
|
179
|
+
console.log('\n' + chalk.green('✓ Component inserted successfully!'));
|
|
180
|
+
console.log(chalk.cyan(' Summary:'));
|
|
181
|
+
console.log(chalk.gray(` File: ${path.relative(process.cwd(), result.targetFile)}`));
|
|
182
|
+
console.log(chalk.gray(` Component: ${chalk.bold(result.component)}`));
|
|
183
|
+
console.log(chalk.gray(` CSS: ${chalk.yellow('external file')}`));
|
|
184
|
+
console.log(chalk.gray(` JS: ${chalk.yellow(result.scriptMode)}`));
|
|
185
|
+
console.log(chalk.gray(`\n Component IDs: ${result.component}-styles, ${result.component}-script`));
|
|
186
|
+
console.log('');
|
|
147
187
|
} catch (error) {
|
|
148
|
-
console.error(chalk.red('Error
|
|
188
|
+
console.error('\n' + chalk.red('✗ Error:'), error.message);
|
|
149
189
|
process.exit(1);
|
|
150
190
|
}
|
|
151
191
|
});
|
|
152
192
|
|
|
153
193
|
program
|
|
154
194
|
.command('list')
|
|
155
|
-
.description('List all available templates')
|
|
195
|
+
.description(chalk.green('List all available templates'))
|
|
156
196
|
.action(() => {
|
|
157
|
-
console.log(chalk.blue('
|
|
158
|
-
|
|
159
|
-
console.log('
|
|
160
|
-
console.log('
|
|
161
|
-
console.log('
|
|
162
|
-
console.log('
|
|
163
|
-
console.log('
|
|
164
|
-
console.log('
|
|
165
|
-
console.log('
|
|
166
|
-
console.log('
|
|
167
|
-
console.log('
|
|
168
|
-
console.log(
|
|
169
|
-
|
|
170
|
-
console.log('
|
|
171
|
-
console.log('
|
|
172
|
-
console.log('
|
|
173
|
-
console.log(
|
|
174
|
-
console.log('
|
|
175
|
-
|
|
176
|
-
console.log('
|
|
177
|
-
console.log(
|
|
178
|
-
console.log('
|
|
179
|
-
console.log('
|
|
180
|
-
|
|
197
|
+
console.log('\n' + chalk.blue('📦 Available Components (19 total)\n'));
|
|
198
|
+
|
|
199
|
+
console.log(chalk.yellow('━ Basic Components (9)'));
|
|
200
|
+
console.log(' button Styled button component');
|
|
201
|
+
console.log(' card Card component with image and content');
|
|
202
|
+
console.log(' form Form with input fields and validation');
|
|
203
|
+
console.log(' navigation Responsive navigation bar');
|
|
204
|
+
console.log(' modal Modal dialog component');
|
|
205
|
+
console.log(' footer Footer section');
|
|
206
|
+
console.log(' hero Hero section with CTA button');
|
|
207
|
+
console.log(' slider Image carousel with navigation');
|
|
208
|
+
console.log(' table Data table with search and filtering');
|
|
209
|
+
|
|
210
|
+
console.log('\n' + chalk.magenta('━ Animation Templates (4)'));
|
|
211
|
+
console.log(' spinner 5 loading spinner variations');
|
|
212
|
+
console.log(' animated-card 6 interactive card animations');
|
|
213
|
+
console.log(' typing-effect Text typing animations');
|
|
214
|
+
console.log(' fade-gallery Image gallery with fade effects');
|
|
215
|
+
|
|
216
|
+
console.log('\n' + chalk.cyan('━ Grid Layouts (3)'));
|
|
217
|
+
console.log(' grid-layout CSS Grid patterns and examples');
|
|
218
|
+
console.log(' masonry-grid Pinterest-style masonry layout');
|
|
219
|
+
console.log(' dashboard-grid Complete admin dashboard (Grid)');
|
|
220
|
+
|
|
221
|
+
console.log('\n' + chalk.green('━ Flexbox Layouts (3)'));
|
|
222
|
+
console.log(' flex-layout Flexbox patterns and examples');
|
|
223
|
+
console.log(' flex-cards Equal-height card layouts');
|
|
224
|
+
console.log(' flex-dashboard Complete admin dashboard (Flexbox)');
|
|
225
|
+
|
|
226
|
+
console.log('\n' + chalk.gray('Usage:'));
|
|
227
|
+
console.log(' create-template create Create a new component');
|
|
228
|
+
console.log(' create-template insert Insert into HTML file');
|
|
181
229
|
console.log('');
|
|
182
230
|
});
|
|
183
231
|
|
|
184
232
|
program.parse(process.argv);
|
|
185
233
|
|
|
186
234
|
if (!process.argv.slice(2).length) {
|
|
187
|
-
|
|
235
|
+
console.log('\n' + chalk.cyan('🎨 Create HTML/CSS UI Templates\n'));
|
|
236
|
+
console.log(chalk.white('Usage: create-template [command]') + '\n');
|
|
237
|
+
console.log(chalk.yellow('Commands:'));
|
|
238
|
+
console.log(' create Create a new template component');
|
|
239
|
+
console.log(' insert Insert component into existing HTML file');
|
|
240
|
+
console.log(' list Show all available templates');
|
|
241
|
+
console.log(' help Display help information\n');
|
|
242
|
+
console.log(chalk.gray('Examples:'));
|
|
243
|
+
console.log(' $ create-template create # Interactive template creation');
|
|
244
|
+
console.log(' $ create-template insert # Interactive component insertion');
|
|
245
|
+
console.log(' $ create-template list # View all 19 templates');
|
|
246
|
+
console.log(' $ create-template --help # Show full help\n');
|
|
188
247
|
}
|
package/package.json
CHANGED
package/src/inserter.js
CHANGED
|
@@ -4,6 +4,31 @@ const path = require('path');
|
|
|
4
4
|
// Security: Validate component name against whitelist
|
|
5
5
|
const VALID_COMPONENTS = ['button', 'card', 'form', 'navigation', 'modal', 'footer', 'hero', 'slider', 'table', 'spinner', 'animated-card', 'typing-effect', 'fade-gallery', 'grid-layout', 'masonry-grid', 'dashboard-grid', 'flex-layout', 'flex-cards', 'flex-dashboard'];
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Extracts indentation from a line
|
|
9
|
+
*/
|
|
10
|
+
function getIndentation(line) {
|
|
11
|
+
const match = line.match(/^(\s*)/);
|
|
12
|
+
return match ? match[1] : '';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Checks if a component is already inserted in the HTML
|
|
17
|
+
*/
|
|
18
|
+
function isComponentAlreadyInserted(htmlContent, component) {
|
|
19
|
+
const commentPattern = new RegExp(`<!-- ${component.toUpperCase()} Component -->`, 'i');
|
|
20
|
+
return commentPattern.test(htmlContent);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Gets the indentation level used in an HTML file
|
|
25
|
+
*/
|
|
26
|
+
function getHtmlIndentation(htmlContent) {
|
|
27
|
+
// Look for any indented line to determine the standard indentation
|
|
28
|
+
const match = htmlContent.match(/\n(\s+)\S/);
|
|
29
|
+
return match ? match[1] : ' '; // default to 4 spaces
|
|
30
|
+
}
|
|
31
|
+
|
|
7
32
|
async function insertComponent(options) {
|
|
8
33
|
const { component, targetFile, styleMode, scriptMode } = options;
|
|
9
34
|
|
|
@@ -23,12 +48,26 @@ async function insertComponent(options) {
|
|
|
23
48
|
// Read target HTML file
|
|
24
49
|
let htmlContent = await fs.readFile(targetPath, 'utf-8');
|
|
25
50
|
|
|
51
|
+
// Check if component is already inserted
|
|
52
|
+
if (isComponentAlreadyInserted(htmlContent, component)) {
|
|
53
|
+
throw new Error(`Component "${component}" is already inserted in this file`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Validate HTML structure
|
|
57
|
+
if (!htmlContent.includes('</body>')) {
|
|
58
|
+
throw new Error('Target HTML file does not have a closing </body> tag');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!htmlContent.includes('</head>')) {
|
|
62
|
+
throw new Error('Target HTML file does not have a </head> tag');
|
|
63
|
+
}
|
|
64
|
+
|
|
26
65
|
// Get component templates
|
|
27
66
|
const templateDir = path.join(__dirname, '..', 'templates', component);
|
|
28
67
|
const componentHtml = await fs.readFile(path.join(templateDir, 'index.html'), 'utf-8');
|
|
29
68
|
const componentCss = await fs.readFile(path.join(templateDir, 'style.css'), 'utf-8');
|
|
30
69
|
|
|
31
|
-
// Extract component body content
|
|
70
|
+
// Extract component body content
|
|
32
71
|
const bodyMatch = componentHtml.match(/<body[^>]*>([\s\S]*)<\/body>/i);
|
|
33
72
|
if (!bodyMatch) {
|
|
34
73
|
throw new Error('Invalid component template structure');
|
|
@@ -36,31 +75,36 @@ async function insertComponent(options) {
|
|
|
36
75
|
|
|
37
76
|
let componentBody = bodyMatch[1].trim();
|
|
38
77
|
|
|
78
|
+
// Get indentation used in the HTML file
|
|
79
|
+
const baseIndent = getHtmlIndentation(htmlContent);
|
|
80
|
+
|
|
81
|
+
// Normalize component body indentation
|
|
82
|
+
const lines = componentBody.split('\n').map(line => {
|
|
83
|
+
if (line.trim() === '') return '';
|
|
84
|
+
return baseIndent + line.trim();
|
|
85
|
+
}).join('\n');
|
|
86
|
+
componentBody = lines;
|
|
87
|
+
|
|
39
88
|
// Insert component HTML before closing </body> tag
|
|
40
|
-
|
|
41
|
-
htmlContent = htmlContent.replace('</body>', `\n <!-- ${component.toUpperCase()} Component -->\n${componentBody}\n</body>`);
|
|
42
|
-
} else {
|
|
43
|
-
throw new Error('Target HTML file does not have a closing </body> tag');
|
|
44
|
-
}
|
|
89
|
+
htmlContent = htmlContent.replace('</body>', `${baseIndent}<!-- ${component.toUpperCase()} Component -->\n${componentBody}\n\n</body>`);
|
|
45
90
|
|
|
46
91
|
// Handle CSS
|
|
47
92
|
if (styleMode === 'inline') {
|
|
48
|
-
//
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
93
|
+
// Normalize CSS indentation
|
|
94
|
+
const normalizedCss = componentCss.split('\n').map(line => {
|
|
95
|
+
if (line.trim() === '') return '';
|
|
96
|
+
return baseIndent + ' ' + line.trim();
|
|
97
|
+
}).join('\n');
|
|
98
|
+
|
|
99
|
+
htmlContent = htmlContent.replace('</head>', `${baseIndent}<style id="${component}-styles">\n${baseIndent} /* ${component.toUpperCase()} Component Styles */\n${normalizedCss}\n${baseIndent}</style>\n</head>`);
|
|
53
100
|
} else if (styleMode === 'separate') {
|
|
54
101
|
// Create separate CSS file
|
|
55
102
|
const cssFileName = `${component}-component.css`;
|
|
56
103
|
const cssPath = path.join(path.dirname(targetPath), cssFileName);
|
|
57
|
-
await fs.writeFile(cssPath, `/* ${component.toUpperCase()} Component Styles */\n${componentCss}`);
|
|
104
|
+
await fs.writeFile(cssPath, `/* ${component.toUpperCase()} Component Styles */\n\n${componentCss}`);
|
|
58
105
|
|
|
59
106
|
// Add link to CSS file
|
|
60
|
-
|
|
61
|
-
if (htmlContent.includes('</head>')) {
|
|
62
|
-
htmlContent = htmlContent.replace('</head>', `${linkTag}\n</head>`);
|
|
63
|
-
}
|
|
107
|
+
htmlContent = htmlContent.replace('</head>', `${baseIndent}<link rel="stylesheet" href="${cssFileName}">\n</head>`);
|
|
64
108
|
}
|
|
65
109
|
|
|
66
110
|
// Handle JavaScript
|
|
@@ -68,18 +112,21 @@ async function insertComponent(options) {
|
|
|
68
112
|
const componentJs = await fs.readFile(path.join(templateDir, 'script.js'), 'utf-8');
|
|
69
113
|
|
|
70
114
|
if (scriptMode === 'inline') {
|
|
71
|
-
//
|
|
72
|
-
const
|
|
73
|
-
|
|
115
|
+
// Normalize JS indentation
|
|
116
|
+
const normalizedJs = componentJs.split('\n').map(line => {
|
|
117
|
+
if (line.trim() === '') return '';
|
|
118
|
+
return baseIndent + ' ' + line.trim();
|
|
119
|
+
}).join('\n');
|
|
120
|
+
|
|
121
|
+
htmlContent = htmlContent.replace('</body>', `${baseIndent}<script id="${component}-script">\n${baseIndent} // ${component.toUpperCase()} Component Script\n${normalizedJs}\n${baseIndent}</script>\n</body>`);
|
|
74
122
|
} else if (scriptMode === 'separate') {
|
|
75
123
|
// Create separate JS file
|
|
76
124
|
const jsFileName = `${component}-component.js`;
|
|
77
125
|
const jsPath = path.join(path.dirname(targetPath), jsFileName);
|
|
78
|
-
await fs.writeFile(jsPath, `// ${component.toUpperCase()} Component Script\n${componentJs}`);
|
|
126
|
+
await fs.writeFile(jsPath, `// ${component.toUpperCase()} Component Script\n\n${componentJs}`);
|
|
79
127
|
|
|
80
128
|
// Add script tag
|
|
81
|
-
|
|
82
|
-
htmlContent = htmlContent.replace('</body>', `${scriptTag}</body>`);
|
|
129
|
+
htmlContent = htmlContent.replace('</body>', `${baseIndent}<script src="${jsFileName}" id="${component}-script"></script>\n</body>`);
|
|
83
130
|
}
|
|
84
131
|
} catch (error) {
|
|
85
132
|
// No JavaScript file for this component, skip
|
package/test-insert.html
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>Test Page</title>
|
|
6
|
+
<link rel="stylesheet" href="button-component.css">
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<h1>Welcome</h1>
|
|
10
|
+
<p>Test content</p>
|
|
11
|
+
<!-- BUTTON Component -->
|
|
12
|
+
<div class="container">
|
|
13
|
+
<h1>Button Component</h1>
|
|
14
|
+
|
|
15
|
+
<!-- Primary Button -->
|
|
16
|
+
<button class="btn btn-primary">Click Here</button>
|
|
17
|
+
|
|
18
|
+
<!-- Secondary Button -->
|
|
19
|
+
<button class="btn btn-secondary">Secondary</button>
|
|
20
|
+
|
|
21
|
+
<!-- Success Button -->
|
|
22
|
+
<button class="btn btn-success">Success</button>
|
|
23
|
+
|
|
24
|
+
<!-- Danger Button -->
|
|
25
|
+
<button class="btn btn-danger">Delete</button>
|
|
26
|
+
|
|
27
|
+
<!-- Outlined Button -->
|
|
28
|
+
<button class="btn btn-outlined">Outlined</button>
|
|
29
|
+
|
|
30
|
+
<!-- Disabled Button -->
|
|
31
|
+
<button class="btn btn-primary" disabled>Disabled</button>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<script id="button-script">
|
|
35
|
+
// BUTTON Component Script
|
|
36
|
+
// Button click animations
|
|
37
|
+
document.querySelectorAll('.btn').forEach(button => {
|
|
38
|
+
button.addEventListener('click', function(e) {
|
|
39
|
+
if (!this.disabled) {
|
|
40
|
+
console.log('Button clicked:', this.textContent);
|
|
41
|
+
|
|
42
|
+
// Ripple effect
|
|
43
|
+
const ripple = document.createElement('span');
|
|
44
|
+
ripple.classList.add('ripple');
|
|
45
|
+
this.appendChild(ripple);
|
|
46
|
+
|
|
47
|
+
setTimeout(() => ripple.remove(), 600);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
</script>
|
|
53
|
+
</body>
|
|
54
|
+
</html>
|