Compare commits
10 Commits
c77dd99175
...
ea9c1181bb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea9c1181bb | ||
|
|
dfcb1c5656 | ||
|
|
0842268f28 | ||
|
|
bcb2c381fb | ||
|
|
784c1311f4 | ||
|
|
a98b1a0464 | ||
|
|
210ff6ca57 | ||
|
|
720e4faa75 | ||
|
|
9df773c091 | ||
|
|
6af8e5633a |
169
.github/README.md
vendored
Normal file
169
.github/README.md
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
[🇬🇧 English](#english) | [🇷🇺 Русский](#russian)
|
||||
|
||||
> [!WARNING]
|
||||
> **This is a mirror!** Main repository: [https://gromlab.ru/gromov/MyTemplateGenerator](https://gromlab.ru/gromov/MyTemplateGenerator)
|
||||
>
|
||||
> GitHub has lost its status as a reliable platform for open-source, applying political repository blocks instead of maintaining neutrality. The blocks have affected developers from Russia (Donetsk, Luhansk, Crimea, Kherson Oblast, Zaporizhzhia Oblast), Iran, Syria, Cuba, and North Korea.
|
||||
>
|
||||
> - ⚠️ Create pull requests and issues only in the main repository
|
||||
> - 🔄 Mirror is updated automatically
|
||||
> - 🚫 Changes here will be overwritten
|
||||
|
||||
<div id="english">🇬🇧 English</div>
|
||||
|
||||
# MyTemplateGenerator — template and component generator for React, Vue, Next.js, Angular, and more
|
||||
|
||||
myTemplateGenerator is a powerful VSCode extension for generating templates, components, and project structures for all popular frameworks: React, Vue, Next.js, Angular, Svelte, Nuxt, NestJS, Express, Gatsby, Remix, SolidJS, Preact. Instantly scaffold files, folders, and boilerplate code for any modern JavaScript or TypeScript project.
|
||||
|
||||
## Features
|
||||
- Syntax highlighting and autocomplete for template variables in template files (`{{name}}`, `{{name.camelCase}}`, etc.)
|
||||
- Generate project structure, files, and folders for popular frameworks: **React**, **Vue**, **Next.js**, **Angular**, **Svelte**, **Nuxt**, **NestJS**, **Express**, **Gatsby**, **Remix**, **SolidJS**, **Preact**
|
||||
- Quickly create components: create React components, Vue components, Next.js components, Angular components, and more
|
||||
- Visual configurator and localization support (Russian/English)
|
||||
- Flexible settings: templates folder path, variable input mode, overwrite protection
|
||||
|
||||

|
||||

|
||||
|
||||
**How to use:**
|
||||
1. Create a folder with templates (default: `.templates`).
|
||||
2. Use variables in templates: `{{name}}`, `{{name.pascalCase}}`, etc.
|
||||
3. Right-click any folder in your project → **Create from template...**
|
||||
4. Select a template, fill in variables — the structure is generated automatically.
|
||||
|
||||
**Example template:**
|
||||
```
|
||||
.templates/
|
||||
component/
|
||||
{{name}}/
|
||||
index.tsx
|
||||
{{name.camelCase}}.module.css
|
||||
```
|
||||
|
||||
**Available modifiers:**
|
||||
|
||||
| Modifier | Example (`name = myComponent`) |
|
||||
|-----------------------|-------------------------------|
|
||||
| `{{name}}` | myComponent |
|
||||
| `{{name.pascalCase}}` | MyComponent |
|
||||
| `{{name.camelCase}}` | myComponent |
|
||||
| `{{name.snakeCase}}` | my_component |
|
||||
| `{{name.kebabCase}}` | my-component |
|
||||
| `{{name.screamingSnakeCase}}` | MY_COMPONENT |
|
||||
| `{{name.upperCase}}` | Mycomponent |
|
||||
| `{{name.lowerCase}}` | mycomponent |
|
||||
| `{{name.upperCaseAll}}` | MYCOMPONENT |
|
||||
| `{{name.lowerCaseAll}}` | mycomponent |
|
||||
|
||||
**Supported modifiers:** pascalCase, camelCase, snakeCase, kebabCase, upperCase, lowerCase, and more.
|
||||
|
||||
**Framework compatibility:**
|
||||
|
||||
This extension works with **any framework** — you define your own templates for any structure you need!
|
||||
|
||||
| Framework | Components | Store/State | Pages/Routes | Services | Utils |
|
||||
|--------------|:----------:|:-----------:|:------------:|:--------:|:-----:|
|
||||
| React | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Vue | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Angular | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Svelte | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Next.js | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Nuxt | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Solid | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Vanilla JS/TS| ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
|
||||
Just create a template for your favorite stack — and generate any structure you want! 🎉
|
||||
|
||||
**Configuration:**
|
||||
All settings are managed via the standard VSCode user settings (or the visual configurator).
|
||||
|
||||
To open the settings menu, press <kbd>Ctrl</kbd>+<kbd>P</kbd>, type `Configure myTemplateGenerator...` and select the menu item.
|
||||
|
||||
You can also find all options in VSCode settings under `myTemplateGenerator`.
|
||||
|
||||
**Keywords:**
|
||||
template, templates, template generator, component generator, scaffold, scaffolding, boilerplate, starter template, project structure, file generator, folder generator, structure generator, react component generator, vue component generator, nextjs component generator, angular component generator, svelte component generator, nuxt component generator, nestjs generator, express generator, gatsby generator, remix generator, solidjs generator, preact generator, react templates, vue templates, nextjs templates, angular templates, svelte templates, nuxt templates, nestjs templates, express templates, gatsby templates, remix templates, solidjs templates, preact templates, project templates, framework templates, file templates, folder templates, create react component, create vue component, create nextjs component, create angular component, create svelte component, create nuxt component, create nestjs module, create express route, create gatsby page, create remix route, create solidjs component, create preact component, generate files, generate folders, generate structure
|
||||
|
||||
<div id="russian">🇷🇺 Русский</div>
|
||||
|
||||
> [!WARNING]
|
||||
> **Это зеркало!** Основной репозиторий: [https://gromlab.ru/gromov/MyTemplateGenerator](https://gromlab.ru/gromov/MyTemplateGenerator)
|
||||
>
|
||||
> GitHub утратил статус надежной площадки для open-source, применяя политические блокировки репозиториев вместо сохранения нейтралитета. Блокировки коснулись разработчиков из России (Донецк, Луганск, Крым, Херсонская обл., Запорожская обл.), Ирана, Сирии, Кубы и Северной Кореи.
|
||||
>
|
||||
> - ⚠️ Pull requests и issues создавайте только в основном репозитории
|
||||
> - 🔄 Зеркало обновляется автоматически
|
||||
> - 🚫 Изменения здесь будут перезаписаны
|
||||
|
||||
# MyTemplateGenerator — генерация шаблонов и компонентов для React, Vue, Next.js, Angular и других
|
||||
|
||||
myTemplateGenerator — это расширение для VSCode, предназначенное для генерации шаблонов, компонентов и структуры проектов для всех популярных фреймворков: React, Vue, Next.js, Angular, Svelte, Nuxt, NestJS, Express, Gatsby, Remix, SolidJS, Preact. Мгновенно создавайте файлы, папки и boilerplate-код для любых современных проектов на JavaScript и TypeScript.
|
||||
|
||||
## Возможности
|
||||
- Подсветка и автокомплит переменных в шаблонных файлах (`{{name}}`, `{{name.camelCase}}` и др.)
|
||||
- Генерация структуры проекта, файлов и папок для популярных фреймворков: **React**, **Vue**, **Next.js**, **Angular**, **Svelte**, **Nuxt**, **NestJS**, **Express**, **Gatsby**, **Remix**, **SolidJS**, **Preact**
|
||||
- Быстрое создание компонентов: создание React компонентов, Vue компонентов, Next.js компонентов, Angular компонентов и других
|
||||
- Визуальный конфигуратор и поддержка локализации (русский/английский)
|
||||
- Гибкая настройка: путь к шаблонам, режим ввода переменных, запрет/разрешение перезаписи файлов
|
||||
|
||||

|
||||

|
||||
|
||||
**Как использовать:**
|
||||
1. Создайте папку с шаблонами (по умолчанию `.templates`).
|
||||
2. Используйте переменные в шаблонах: `{{name}}`, `{{name.pascalCase}}` и т.д.
|
||||
3. Кликните правой кнопкой по папке в проекте → **Создать из шаблона...**
|
||||
4. Выберите шаблон, заполните переменные — структура будет создана автоматически.
|
||||
|
||||
**Пример шаблона:**
|
||||
```
|
||||
.templates/
|
||||
component/
|
||||
{{name}}/
|
||||
index.tsx
|
||||
{{name.camelCase}}.module.css
|
||||
```
|
||||
|
||||
**Доступные модификаторы:**
|
||||
|
||||
| Модификатор | Пример (`name = myComponent`) |
|
||||
|----------------------|-------------------------------|
|
||||
| `{{name}}` | myComponent |
|
||||
| `{{name.pascalCase}}`| MyComponent |
|
||||
| `{{name.camelCase}}` | myComponent |
|
||||
| `{{name.snakeCase}}` | my_component |
|
||||
| `{{name.kebabCase}}` | my-component |
|
||||
| `{{name.screamingSnakeCase}}` | MY_COMPONENT |
|
||||
| `{{name.upperCase}}` | Mycomponent |
|
||||
| `{{name.lowerCase}}` | mycomponent |
|
||||
| `{{name.upperCaseAll}}` | MYCOMPONENT |
|
||||
| `{{name.lowerCaseAll}}` | mycomponent |
|
||||
|
||||
**Поддерживаемые модификаторы:** pascalCase, camelCase, snakeCase, kebabCase, upperCase, lowerCase и др.
|
||||
|
||||
**Совместимость с фреймворками:**
|
||||
|
||||
Плагин подходит для **любых фреймворков** — вы сами задаёте шаблоны для любой структуры!
|
||||
|
||||
| Фреймворк | Компоненты | Store/State | Страницы/Роуты | Сервисы | Утилиты |
|
||||
|--------------|:----------:|:-----------:|:--------------:|:-------:|:-------:|
|
||||
| React | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Vue | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Angular | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Svelte | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Next.js | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Nuxt | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Solid | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Vanilla JS/TS| ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
|
||||
Создайте шаблон под свой стек — и генерируйте любые структуры! 🎉
|
||||
|
||||
**Настройка:**
|
||||
Все параметры задаются через стандартные пользовательские настройки VSCode (или визуальный конфигуратор).
|
||||
|
||||
Чтобы открыть меню настроек, нажмите <kbd>Ctrl</kbd>+<kbd>P</kbd>, введите `Настроить myTemplateGenerator...` (или `Configure myTemplateGenerator...` для английского интерфейса) и выберите соответствующий пункт.
|
||||
|
||||
Также вы можете найти все параметры в настройках VSCode по ключу `myTemplateGenerator`.
|
||||
|
||||
**Ключевые слова:**
|
||||
создание шаблонов, генерация шаблонов, шаблоны компонентов, создание react компонентов, создание vue компонентов, создание nextjs компонентов, создание angular компонентов, создание svelte компонентов, создание nuxt компонентов, создание nestjs модулей, создание express роутов, создание gatsby страниц, создание remix роутов, создание solidjs компонентов, создание preact компонентов, генератор компонентов, генератор react компонентов, генератор vue компонентов, генератор nextjs компонентов, генератор angular компонентов, генератор svelte компонентов, генератор nuxt компонентов, генератор nestjs модулей, генератор express роутов, генератор gatsby страниц, генератор remix роутов, генератор solidjs компонентов, генератор preact компонентов, react шаблоны, vue шаблоны, nextjs шаблоны, angular шаблоны, svelte шаблоны, nuxt шаблоны, nestjs шаблоны, express шаблоны, gatsby шаблоны, remix шаблоны, solidjs шаблоны, preact шаблоны, шаблоны для проектов, шаблоны для фреймворков, структура проекта, структура папок, генерация файлов, генерация папок, файловый генератор, генератор папок, стартер шаблон
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -130,3 +130,5 @@ dist
|
||||
.pnp.*
|
||||
|
||||
.DS_Store
|
||||
|
||||
*.vsix
|
||||
@@ -3,6 +3,7 @@
|
||||
out/**
|
||||
node_modules/**
|
||||
src/**
|
||||
!media/**
|
||||
.gitignore
|
||||
.yarnrc
|
||||
webpack.config.js
|
||||
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 gormov1122
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
302
README.md
302
README.md
@@ -1,213 +1,151 @@
|
||||
# myTemplateGenerator
|
||||
[🇬🇧 English](#english) | [🇷🇺 Русский](#russian)
|
||||
|
||||
[English](#english) | [Русский](#русский)
|
||||
<div id="english">🇬🇧 English</div>
|
||||
|
||||
---
|
||||
# MyTemplateGenerator — template and component generator for React, Vue, Next.js, Angular, and more
|
||||
|
||||
# English
|
||||
myTemplateGenerator is a powerful VSCode extension for generating templates, components, and project structures for all popular frameworks: React, Vue, Next.js, Angular, Svelte, Nuxt, NestJS, Express, Gatsby, Remix, SolidJS, Preact. Instantly scaffold files, folders, and boilerplate code for any modern JavaScript or TypeScript project.
|
||||
|
||||
## My Template Generator — Template-based structure generation for VSCode
|
||||
## Features
|
||||
- Syntax highlighting and autocomplete for template variables in template files (`{{name}}`, `{{name.camelCase}}`, etc.)
|
||||
- Generate project structure, files, and folders for popular frameworks: **React**, **Vue**, **Next.js**, **Angular**, **Svelte**, **Nuxt**, **NestJS**, **Express**, **Gatsby**, **Remix**, **SolidJS**, **Preact**
|
||||
- Quickly create components: create React components, Vue components, Next.js components, Angular components, and more
|
||||
- Visual configurator and localization support (Russian/English)
|
||||
- Flexible settings: templates folder path, variable input mode, overwrite protection
|
||||
|
||||

|
||||
**Features:**
|
||||
- Syntax highlighting and autocomplete for template variables in templates
|
||||
- Generate files and folders from templates with variable substitution
|
||||
- Full localization (English/Russian) for all UI, messages, and menus
|
||||
- Choose variable input mode: Webview (form) or inputBox (one by one)
|
||||
- Overwrite control: allow or forbid overwriting existing files/folders
|
||||
- Smart conflict handling: clear notifications if structure already exists
|
||||
|
||||
Context menu "Create from template..." is available by right-clicking any folder.
|
||||

|
||||
|
||||
Convenient UI for creation: just select a template and specify values for the variables used in the template. (The list of variables updates automatically)
|
||||

|
||||
|
||||
User-friendly settings UI.
|
||||

|
||||
**How to use:**
|
||||
1. Create a folder with templates (default: `.templates`).
|
||||
2. Use variables in templates: `{{name}}`, `{{name.pascalCase}}`, etc.
|
||||
3. Right-click any folder in your project → **Create from template...**
|
||||
4. Select a template, fill in variables — the structure is generated automatically.
|
||||
|
||||
### Quick Start
|
||||
1. Create a `templates` folder in your project root.
|
||||
2. Add subfolders for different templates (e.g., `components`, `store`).
|
||||
3. Use variables like `{{name}}` or `{{name.pascalCase}}` in file/folder names and file contents.
|
||||
4. Right-click a folder in VSCode and select **Create from template...**
|
||||
5. Choose a template, fill in variables, and click "Create".
|
||||
|
||||
### Example template structure
|
||||
**Example template:**
|
||||
```
|
||||
templates/
|
||||
components/
|
||||
.templates/
|
||||
component/
|
||||
{{name}}/
|
||||
index.js
|
||||
style.module.css
|
||||
store/
|
||||
{{name}}Store.js
|
||||
index.tsx
|
||||
{{name.camelCase}}.module.css
|
||||
```
|
||||
|
||||
### Supported variables and modifiers
|
||||
**Available modifiers:**
|
||||
|
||||
You can use variables with modifiers via dot notation:
|
||||
| Modifier | Example (`name = myComponent`) |
|
||||
|-----------------------|-------------------------------|
|
||||
| `{{name}}` | myComponent |
|
||||
| `{{name.pascalCase}}` | MyComponent |
|
||||
| `{{name.camelCase}}` | myComponent |
|
||||
| `{{name.snakeCase}}` | my_component |
|
||||
| `{{name.kebabCase}}` | my-component |
|
||||
| `{{name.screamingSnakeCase}}` | MY_COMPONENT |
|
||||
| `{{name.upperCase}}` | Mycomponent |
|
||||
| `{{name.lowerCase}}` | mycomponent |
|
||||
| `{{name.upperCaseAll}}` | MYCOMPONENT |
|
||||
| `{{name.lowerCaseAll}}` | mycomponent |
|
||||
|
||||
- `{{name}}` — as entered by user
|
||||
- `{{name.pascalCase}}` — PascalCase
|
||||
- `{{name.camelCase}}` — camelCase
|
||||
- `{{name.snakeCase}}` — snake_case
|
||||
- `{{name.kebabCase}}` — kebab-case
|
||||
- `{{name.screamingSnakeCase}}` — SCREAMING_SNAKE_CASE
|
||||
- `{{name.upperCase}}` — First letter uppercase
|
||||
- `{{name.lowerCase}}` — all lowercase
|
||||
- `{{name.upperCaseAll}}` — ALLUPPERCASE (no separators)
|
||||
- `{{name.lowerCaseAll}}` — alllowercase (no separators)
|
||||
**Supported modifiers:** pascalCase, camelCase, snakeCase, kebabCase, upperCase, lowerCase, and more.
|
||||
|
||||
> When searching for variables for the form, only the name before the dot is considered. For example, `{{name}}` and `{{name.pascalCase}}` are the same variable.
|
||||
**Framework compatibility:**
|
||||
|
||||
### Example usage in template
|
||||
This extension works with **any framework** — you define your own templates for any structure you need!
|
||||
|
||||
| Framework | Components | Store/State | Pages/Routes | Services | Utils |
|
||||
|--------------|:----------:|:-----------:|:------------:|:--------:|:-----:|
|
||||
| React | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Vue | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Angular | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Svelte | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Next.js | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Nuxt | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Solid | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Vanilla JS/TS| ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
|
||||
Just create a template for your favorite stack — and generate any structure you want! 🎉
|
||||
|
||||
**Configuration:**
|
||||
All settings are managed via the standard VSCode user settings (or the visual configurator).
|
||||
|
||||
To open the settings menu, press <kbd>Ctrl</kbd>+<kbd>P</kbd>, type `Configure myTemplateGenerator...` and select the menu item.
|
||||
|
||||
You can also find all options in VSCode settings under `myTemplateGenerator`.
|
||||
|
||||
**Keywords / Ключевые слова:**
|
||||
template, templates, template generator, component generator, scaffold, scaffolding, boilerplate, starter template, project structure, file generator, folder generator, structure generator, react component generator, vue component generator, nextjs component generator, angular component generator, svelte component generator, nuxt component generator, nestjs generator, express generator, gatsby generator, remix generator, solidjs generator, preact generator, react templates, vue templates, nextjs templates, angular templates, svelte templates, nuxt templates, nestjs templates, express templates, gatsby templates, remix templates, solidjs templates, preact templates, project templates, framework templates, file templates, folder templates, create react component, create vue component, create nextjs component, create angular component, create svelte component, create nuxt component, create nestjs module, create express route, create gatsby page, create remix route, create solidjs component, create preact component, generate files, generate folders, generate structure
|
||||
|
||||
<div id="russian">🇷🇺 Русский</div>
|
||||
|
||||
# MyTemplateGenerator — генерация шаблонов и компонентов для React, Vue, Next.js, Angular и других
|
||||
|
||||
myTemplateGenerator — это расширение для VSCode, предназначенное для генерации шаблонов, компонентов и структуры проектов для всех популярных фреймворков: React, Vue, Next.js, Angular, Svelte, Nuxt, NestJS, Express, Gatsby, Remix, SolidJS, Preact. Мгновенно создавайте файлы, папки и boilerplate-код для любых современных проектов на JavaScript и TypeScript.
|
||||
|
||||
## Возможности
|
||||
- Подсветка и автокомплит переменных в шаблонных файлах (`{{name}}`, `{{name.camelCase}}` и др.)
|
||||
- Генерация структуры проекта, файлов и папок для популярных фреймворков: **React**, **Vue**, **Next.js**, **Angular**, **Svelte**, **Nuxt**, **NestJS**, **Express**, **Gatsby**, **Remix**, **SolidJS**, **Preact**
|
||||
- Быстрое создание компонентов: создание React компонентов, Vue компонентов, Next.js компонентов, Angular компонентов и других
|
||||
- Визуальный конфигуратор и поддержка локализации (русский/английский)
|
||||
- Гибкая настройка: путь к шаблонам, режим ввода переменных, запрет/разрешение перезаписи файлов
|
||||
|
||||

|
||||

|
||||
|
||||
**Как использовать:**
|
||||
1. Создайте папку с шаблонами (по умолчанию `.templates`).
|
||||
2. Используйте переменные в шаблонах: `{{name}}`, `{{name.pascalCase}}` и т.д.
|
||||
3. Кликните правой кнопкой по папке в проекте → **Создать из шаблона...**
|
||||
4. Выберите шаблон, заполните переменные — структура будет создана автоматически.
|
||||
|
||||
**Пример шаблона:**
|
||||
```
|
||||
components/
|
||||
{{name.pascalCase}}/
|
||||
index.js
|
||||
{{name.camelCase}}.service.js
|
||||
{{name.snakeCase}}.test.js
|
||||
```
|
||||
And in file contents:
|
||||
```
|
||||
export class {{name.pascalCase}} {}
|
||||
const name = '{{name}}';
|
||||
```
|
||||
|
||||
### Configuration
|
||||
**To open the visual configurator, press <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>P</kbd>, type `Configure myTemplateGenerator...` and select the command.**
|
||||
|
||||
Use `mytemplategenerator.json` in your project root or the visual configurator (**Configure myTemplateGenerator...**):
|
||||
```json
|
||||
{
|
||||
"templatesPath": "templates",
|
||||
"overwriteFiles": false,
|
||||
"inputMode": "webview", // or "inputBox"
|
||||
"language": "en" // or "ru"
|
||||
}
|
||||
```
|
||||
- **templatesPath** — path to templates folder
|
||||
- **overwriteFiles** — allow or forbid overwriting existing files/folders
|
||||
- **inputMode** — variable input mode: "webview" (form) or "inputBox" (one by one)
|
||||
- **language** — plugin UI language (en/ru)
|
||||
|
||||
### Localization
|
||||
- All UI, messages, errors, and menus are localized.
|
||||
- Webview and messages use the language from config.
|
||||
- Menu/command language depends on VSCode interface language.
|
||||
|
||||
### Key commands
|
||||
- **Create from template...** — generate structure (context menu)
|
||||
- **Configure myTemplateGenerator...** — open visual configurator (command palette)
|
||||
|
||||
### Error handling & overwrite
|
||||
- If structure or file exists and overwrite is forbidden, generation is cancelled and a clear notification is shown.
|
||||
- Any file creation error stops generation and shows the reason.
|
||||
|
||||
---
|
||||
|
||||
# Русский
|
||||
|
||||
## My Template Generator — Генерация структуры из шаблонов для VSCode
|
||||
|
||||

|
||||
**Возможности:**
|
||||
- Подсветка синтаксиса и автокомплит переменных в шаблонах
|
||||
- Генерация файлов и папок по шаблонам с подстановкой переменных
|
||||
- Полная локализация (русский/английский) для всего интерфейса, сообщений и меню
|
||||
- Выбор способа ввода переменных: Webview (форма) или inputBox (по одной)
|
||||
- Контроль перезаписи: можно запретить или разрешить перезапись существующих файлов/папок
|
||||
- Умная обработка конфликтов: понятные уведомления, если структура уже существует
|
||||
|
||||
Контекстное меню "Создать из шаблона.." доступно правым кликом по любой папке.
|
||||

|
||||
|
||||
Удобный UI создания, нужно только выбрать шаблок и указать значения переменных используемых в шаблоне. (Список переменных обновляется автоматически)
|
||||

|
||||
|
||||
Удобный UI интерфейс настроек.
|
||||

|
||||
|
||||
|
||||
### Быстрый старт
|
||||
1. В корне проекта создайте папку `templates`.
|
||||
2. Внутри неё создайте подпапки для разных шаблонов (например, `components`, `store`).
|
||||
3. Внутри шаблонов используйте переменные вида `{{name}}` или `{{name.pascalCase}}` в именах файлов/папок и в содержимом файлов.
|
||||
4. Кликните правой кнопкой мыши на нужной папке в VSCode и выберите пункт **Создать из шаблона...**
|
||||
5. В появившемся окне выберите шаблон, заполните переменные и нажмите "Создать".
|
||||
|
||||
### Пример структуры шаблонов
|
||||
```
|
||||
templates/
|
||||
components/
|
||||
.templates/
|
||||
component/
|
||||
{{name}}/
|
||||
index.js
|
||||
style.module.css
|
||||
store/
|
||||
{{name}}Store.js
|
||||
index.tsx
|
||||
{{name.camelCase}}.module.css
|
||||
```
|
||||
|
||||
### Переменные и модификаторы
|
||||
**Доступные модификаторы:**
|
||||
|
||||
В шаблонах можно использовать переменные с модификаторами через точку:
|
||||
| Модификатор | Пример (`name = myComponent`) |
|
||||
|----------------------|-------------------------------|
|
||||
| `{{name}}` | myComponent |
|
||||
| `{{name.pascalCase}}`| MyComponent |
|
||||
| `{{name.camelCase}}` | myComponent |
|
||||
| `{{name.snakeCase}}` | my_component |
|
||||
| `{{name.kebabCase}}` | my-component |
|
||||
| `{{name.screamingSnakeCase}}` | MY_COMPONENT |
|
||||
| `{{name.upperCase}}` | Mycomponent |
|
||||
| `{{name.lowerCase}}` | mycomponent |
|
||||
| `{{name.upperCaseAll}}` | MYCOMPONENT |
|
||||
| `{{name.lowerCaseAll}}` | mycomponent |
|
||||
|
||||
- `{{name}}` — как ввёл пользователь
|
||||
- `{{name.pascalCase}}` — PascalCase
|
||||
- `{{name.camelCase}}` — camelCase
|
||||
- `{{name.snakeCase}}` — snake_case
|
||||
- `{{name.kebabCase}}` — kebab-case
|
||||
- `{{name.screamingSnakeCase}}` — SCREAMING_SNAKE_CASE
|
||||
- `{{name.upperCase}}` — Первая буква большая
|
||||
- `{{name.lowerCase}}` — все буквы маленькие
|
||||
- `{{name.upperCaseAll}}` — ВСЕ БУКВЫ БОЛЬШИЕ (без разделителей)
|
||||
- `{{name.lowerCaseAll}}` — все буквы маленькие (без разделителей)
|
||||
**Поддерживаемые модификаторы:** pascalCase, camelCase, snakeCase, kebabCase, upperCase, lowerCase и др.
|
||||
|
||||
> При поиске переменных для формы учитывается только имя до точки. Например, `{{name}}` и `{{name.pascalCase}}` — это одна переменная.
|
||||
**Совместимость с фреймворками:**
|
||||
|
||||
### Пример использования в шаблоне
|
||||
```
|
||||
components/
|
||||
{{name.pascalCase}}/
|
||||
index.js
|
||||
{{name.camelCase}}.service.js
|
||||
{{name.snakeCase}}.test.js
|
||||
```
|
||||
Внутри файлов также можно использовать эти переменные:
|
||||
```
|
||||
export class {{name.pascalCase}} {}
|
||||
const name = '{{name}}';
|
||||
```
|
||||
Плагин подходит для **любых фреймворков** — вы сами задаёте шаблоны для любой структуры!
|
||||
|
||||
### Конфигурация
|
||||
**Чтобы открыть визуальный конфигуратор, нажмите <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>P</kbd>, введите `Настроить myTemplateGenerator...` и выберите команду.**
|
||||
| Фреймворк | Компоненты | Store/State | Страницы/Роуты | Сервисы | Утилиты |
|
||||
|--------------|:----------:|:-----------:|:--------------:|:-------:|:-------:|
|
||||
| React | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Vue | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Angular | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Svelte | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Next.js | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Nuxt | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Solid | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Vanilla JS/TS| ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
|
||||
Для гибкой настройки используйте файл `mytemplategenerator.json` в корне проекта или визуальный конфигуратор (команда **Настроить myTemplateGenerator...**):
|
||||
Создайте шаблон под свой стек — и генерируйте любые структуры! 🎉
|
||||
|
||||
```json
|
||||
{
|
||||
"templatesPath": "templates",
|
||||
"overwriteFiles": false,
|
||||
"inputMode": "webview", // или "inputBox"
|
||||
"language": "ru" // или "en"
|
||||
}
|
||||
```
|
||||
- **templatesPath** — путь к папке с шаблонами
|
||||
- **overwriteFiles** — разрешать ли перезапись существующих файлов/папок
|
||||
- **inputMode** — способ ввода переменных: "webview" (форма) или "inputBox" (по одной)
|
||||
- **language** — язык интерфейса плагина (ru/en)
|
||||
**Настройка:**
|
||||
Все параметры задаются через стандартные пользовательские настройки VSCode (или визуальный конфигуратор).
|
||||
|
||||
### Локализация
|
||||
- Все сообщения, Webview, ошибки и пункты меню локализованы.
|
||||
- Язык Webview и сообщений выбирается в конфигураторе.
|
||||
- Язык пунктов меню и команд зависит от языка интерфейса VSCode.
|
||||
Чтобы открыть меню настроек, нажмите <kbd>Ctrl</kbd>+<kbd>P</kbd>, введите `Настроить myTemplateGenerator...` (или `Configure myTemplateGenerator...` для английского интерфейса) и выберите соответствующий пункт.
|
||||
|
||||
### Важные команды
|
||||
- **Создать из шаблона...** — генерация структуры (контекстное меню)
|
||||
- **Настроить myTemplateGenerator...** — открыть визуальный конфигуратор (палитра команд)
|
||||
Также вы можете найти все параметры в настройках VSCode по ключу `myTemplateGenerator`.
|
||||
|
||||
### Обработка ошибок и перезаписи
|
||||
- Если структура или файл уже существуют и перезапись запрещена, генерация не выполняется и выводится понятное уведомление.
|
||||
- Если при создании любого файла возникает ошибка — генерация полностью прекращается, и пользователь видит причину.
|
||||
|
||||
---
|
||||
**Ключевые слова:**
|
||||
создание шаблонов, генерация шаблонов, шаблоны компонентов, создание react компонентов, создание vue компонентов, создание nextjs компонентов, создание angular компонентов, создание svelte компонентов, создание nuxt компонентов, создание nestjs модулей, создание express роутов, создание gatsby страниц, создание remix роутов, создание solidjs компонентов, создание preact компонентов, генератор компонентов, генератор react компонентов, генератор vue компонентов, генератор nextjs компонентов, генератор angular компонентов, генератор svelte компонентов, генератор nuxt компонентов, генератор nestjs модулей, генератор express роутов, генератор gatsby страниц, генератор remix роутов, генератор solidjs компонентов, генератор preact компонентов, react шаблоны, vue шаблоны, nextjs шаблоны, angular шаблоны, svelte шаблоны, nuxt шаблоны, nestjs шаблоны, express шаблоны, gatsby шаблоны, remix шаблоны, solidjs шаблоны, preact шаблоны, шаблоны для проектов, шаблоны для фреймворков, структура проекта, структура папок, генерация файлов, генерация папок, файловый генератор, генератор папок, стартер шаблон
|
||||
124
media/styles.css
Normal file
124
media/styles.css
Normal file
@@ -0,0 +1,124 @@
|
||||
:root {
|
||||
--bg: #f7f7fa;
|
||||
--panel-bg: #fff;
|
||||
--text: #222;
|
||||
--label: #555;
|
||||
--input-bg: #f0f0f3;
|
||||
--input-border: #d0d0d7;
|
||||
--input-focus: #1976d2;
|
||||
--button-bg: #1976d2;
|
||||
--button-text: #fff;
|
||||
--button-hover: #1565c0;
|
||||
--border-radius: 8px;
|
||||
--shadow: 0 2px 12px rgba(0,0,0,0.07);
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--bg: #181a1b;
|
||||
--panel-bg: #23272e;
|
||||
--text: #f3f3f3;
|
||||
--label: #b0b0b0;
|
||||
--input-bg: #23272e;
|
||||
--input-border: #33363b;
|
||||
--input-focus: #90caf9;
|
||||
--button-bg: #1976d2;
|
||||
--button-text: #fff;
|
||||
--button-hover: #1565c0;
|
||||
--border-radius: 8px;
|
||||
--shadow: 0 2px 12px rgba(0,0,0,0.25);
|
||||
}
|
||||
}
|
||||
body {
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-family: 'Segoe UI', 'Roboto', Arial, sans-serif;
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.create-container, .config-container {
|
||||
max-width: 420px;
|
||||
margin: 48px auto;
|
||||
background: var(--panel-bg);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: var(--shadow);
|
||||
padding: 32px 36px 28px 36px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18px;
|
||||
}
|
||||
.head-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.create-container h2, .config-container h2 {
|
||||
margin: 0;
|
||||
font-size: 1.5em;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
label {
|
||||
color: var(--label);
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
}
|
||||
input, select {
|
||||
background: var(--input-bg);
|
||||
color: var(--text);
|
||||
border: 1.5px solid var(--input-border);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 8px 10px;
|
||||
font-size: 1em;
|
||||
transition: border 0.2s, box-shadow 0.2s;
|
||||
outline: none;
|
||||
}
|
||||
input:focus, select:focus {
|
||||
border-color: var(--input-focus);
|
||||
box-shadow: 0 0 0 2px var(--input-focus)33;
|
||||
}
|
||||
button, .btn {
|
||||
margin-top: 10px;
|
||||
background: var(--button-bg);
|
||||
color: var(--button-text);
|
||||
border: none;
|
||||
border-radius: var(--border-radius);
|
||||
padding: 10px 15px;
|
||||
font-size: 1.1em;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s, box-shadow 0.2s;
|
||||
box-shadow: 0 1px 4px rgba(25, 118, 210, 0.08);
|
||||
}
|
||||
button:hover, button:focus, .btn:hover, .btn:focus {
|
||||
background: var(--button-hover);
|
||||
}
|
||||
.destination {
|
||||
margin-bottom: 16px;
|
||||
color: #888;
|
||||
font-size: 13px;
|
||||
}
|
||||
.lang-select {
|
||||
display: block;
|
||||
}
|
||||
.var-hint {
|
||||
color: #888;
|
||||
font-size: 13px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.template-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
#configForm {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
Binary file not shown.
51
package-lock.json
generated
51
package-lock.json
generated
@@ -1,13 +1,15 @@
|
||||
{
|
||||
"name": "mytemplategenerator",
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mytemplategenerator",
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.5",
|
||||
"dependencies": {
|
||||
"change-case": "^5.4.4",
|
||||
"change-case-all": "^2.1.0",
|
||||
"handlebars": "^4.7.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -1333,6 +1335,24 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/change-case": {
|
||||
"version": "5.4.4",
|
||||
"resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz",
|
||||
"integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/change-case-all": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/change-case-all/-/change-case-all-2.1.0.tgz",
|
||||
"integrity": "sha512-v6b0WWWkZUMHVuYk82l+WROgkUm4qEN2w5hKRNWtEOYwWqUGoi8C6xH0l1RLF1EoWqDFK6MFclmN3od6ws3/uw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"change-case": "^5.2.0",
|
||||
"sponge-case": "^2.0.2",
|
||||
"swap-case": "^3.0.2",
|
||||
"title-case": "^3.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
@@ -3801,6 +3821,12 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sponge-case": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sponge-case/-/sponge-case-2.0.3.tgz",
|
||||
"integrity": "sha512-i4h9ZGRfxV6Xw3mpZSFOfbXjf0cQcYmssGWutgNIfFZ2VM+YIWfD71N/kjjwK6X/AAHzBr+rciEcn/L34S8TGw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/stdin-discarder": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz",
|
||||
@@ -3967,6 +3993,12 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/swap-case": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/swap-case/-/swap-case-3.0.3.tgz",
|
||||
"integrity": "sha512-6p4op8wE9CQv7uDFzulI6YXUw4lD9n4oQierdbFThEKVWVQcbQcUjdP27W8XE7V4QnWmnq9jueSHceyyQnqQVA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz",
|
||||
@@ -4092,6 +4124,15 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/title-case": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/title-case/-/title-case-3.0.3.tgz",
|
||||
"integrity": "sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
@@ -4139,6 +4180,12 @@
|
||||
"webpack": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||
|
||||
167
package.json
167
package.json
@@ -1,18 +1,134 @@
|
||||
{
|
||||
"name": "mytemplategenerator",
|
||||
"displayName": "myTemplateGenerator",
|
||||
"description": "Generate files and folders from customizable templates with variable substitution in VSCode.",
|
||||
"version": "0.0.2",
|
||||
"displayName": "Template & Component Generator: React, Vue, Next.js, Angular",
|
||||
"description": "Powerful VSCode extension for generating templates, components, and project structures for React, Vue, Next.js, Angular, Svelte, Nuxt, NestJS, Express, Gatsby, Remix, SolidJS, Preact, and more. Instantly scaffold files, folders, and boilerplate code with variable substitution.",
|
||||
"version": "0.0.9",
|
||||
"publisher": "MyTemplateGenerator",
|
||||
"author": "Sergey Gromov",
|
||||
"icon": "src/images/logo.svg",
|
||||
"categories": [
|
||||
"Snippets",
|
||||
"Programming Languages",
|
||||
"Other"
|
||||
],
|
||||
"keywords": [
|
||||
"template",
|
||||
"templates",
|
||||
"template generator",
|
||||
"component generator",
|
||||
"scaffold",
|
||||
"scaffolding",
|
||||
"boilerplate",
|
||||
"starter template",
|
||||
"project structure",
|
||||
"file generator",
|
||||
"folder generator",
|
||||
"structure generator",
|
||||
"react component generator",
|
||||
"vue component generator",
|
||||
"nextjs component generator",
|
||||
"angular component generator",
|
||||
"svelte component generator",
|
||||
"nuxt component generator",
|
||||
"nestjs generator",
|
||||
"express generator",
|
||||
"gatsby generator",
|
||||
"remix generator",
|
||||
"solidjs generator",
|
||||
"preact generator",
|
||||
"react templates",
|
||||
"vue templates",
|
||||
"nextjs templates",
|
||||
"angular templates",
|
||||
"svelte templates",
|
||||
"nuxt templates",
|
||||
"nestjs templates",
|
||||
"express templates",
|
||||
"gatsby templates",
|
||||
"remix templates",
|
||||
"solidjs templates",
|
||||
"preact templates",
|
||||
"project templates",
|
||||
"framework templates",
|
||||
"file templates",
|
||||
"folder templates",
|
||||
"create react component",
|
||||
"create vue component",
|
||||
"create nextjs component",
|
||||
"create angular component",
|
||||
"create svelte component",
|
||||
"create nuxt component",
|
||||
"create nestjs module",
|
||||
"create express route",
|
||||
"create gatsby page",
|
||||
"create remix route",
|
||||
"create solidjs component",
|
||||
"create preact component",
|
||||
"generate files",
|
||||
"generate folders",
|
||||
"generate structure",
|
||||
"создание шаблонов",
|
||||
"генерация шаблонов",
|
||||
"шаблоны компонентов",
|
||||
"создание react компонентов",
|
||||
"создание vue компонентов",
|
||||
"создание nextjs компонентов",
|
||||
"создание angular компонентов",
|
||||
"создание svelte компонентов",
|
||||
"создание nuxt компонентов",
|
||||
"создание nestjs модулей",
|
||||
"создание express роутов",
|
||||
"создание gatsby страниц",
|
||||
"создание remix роутов",
|
||||
"создание solidjs компонентов",
|
||||
"создание preact компонентов",
|
||||
"генератор компонентов",
|
||||
"генератор react компонентов",
|
||||
"генератор vue компонентов",
|
||||
"генератор nextjs компонентов",
|
||||
"генератор angular компонентов",
|
||||
"генератор svelte компонентов",
|
||||
"генератор nuxt компонентов",
|
||||
"генератор nestjs модулей",
|
||||
"генератор express роутов",
|
||||
"генератор gatsby страниц",
|
||||
"генератор remix роутов",
|
||||
"генератор solidjs компонентов",
|
||||
"генератор preact компонентов",
|
||||
"react шаблоны",
|
||||
"vue шаблоны",
|
||||
"nextjs шаблоны",
|
||||
"angular шаблоны",
|
||||
"svelte шаблоны",
|
||||
"nuxt шаблоны",
|
||||
"nestjs шаблоны",
|
||||
"express шаблоны",
|
||||
"gatsby шаблоны",
|
||||
"remix шаблоны",
|
||||
"solidjs шаблоны",
|
||||
"preact шаблоны",
|
||||
"шаблоны для проектов",
|
||||
"шаблоны для фреймворков",
|
||||
"структура проекта",
|
||||
"структура папок",
|
||||
"генерация файлов",
|
||||
"генерация папок",
|
||||
"файловый генератор",
|
||||
"генератор папок",
|
||||
"стартер шаблон"
|
||||
],
|
||||
"icon": "logo.png",
|
||||
"url": "https://github.com/gormov1122/MyTemplateGenerator",
|
||||
"homepage": "https://github.com/gormov1122/MyTemplateGenerator",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/gormov1122/MyTemplateGenerator"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/gormov1122/MyTemplateGenerator/issues"
|
||||
},
|
||||
"engines": {
|
||||
"vscode": "^1.60.0"
|
||||
},
|
||||
"categories": [
|
||||
"Other"
|
||||
],
|
||||
"activationEvents": [
|
||||
"onStartupFinished",
|
||||
"onLanguage:javascript",
|
||||
@@ -41,6 +157,41 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"configuration": {
|
||||
"title": "myTemplateGenerator",
|
||||
"properties": {
|
||||
"myTemplateGenerator.templatesPath": {
|
||||
"type": "string",
|
||||
"default": ".templates",
|
||||
"description": "%mytemplategenerator.config.templatesPath.description%",
|
||||
"markdownDescription": "%mytemplategenerator.config.templatesPath.description%",
|
||||
"scope": "application"
|
||||
},
|
||||
"myTemplateGenerator.overwriteFiles": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "%mytemplategenerator.config.overwriteFiles.description%",
|
||||
"markdownDescription": "%mytemplategenerator.config.overwriteFiles.description%",
|
||||
"scope": "application"
|
||||
},
|
||||
"myTemplateGenerator.inputMode": {
|
||||
"type": "string",
|
||||
"enum": ["webview", "inputBox"],
|
||||
"default": "webview",
|
||||
"description": "%mytemplategenerator.config.inputMode.description%",
|
||||
"markdownDescription": "%mytemplategenerator.config.inputMode.description%",
|
||||
"scope": "application"
|
||||
},
|
||||
"myTemplateGenerator.language": {
|
||||
"type": "string",
|
||||
"enum": ["ru", "en"],
|
||||
"default": "en",
|
||||
"description": "%mytemplategenerator.config.language.description%",
|
||||
"markdownDescription": "%mytemplategenerator.config.language.description%",
|
||||
"scope": "application"
|
||||
}
|
||||
}
|
||||
},
|
||||
"semanticTokenColors": [
|
||||
{
|
||||
"token": "bracket",
|
||||
@@ -82,6 +233,8 @@
|
||||
"webpack-cli": "^6.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"change-case": "^5.4.4",
|
||||
"change-case-all": "^2.1.0",
|
||||
"handlebars": "^4.7.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"mytemplategenerator.createFromTemplate.title": "Create from template...",
|
||||
"mytemplategenerator.configure.title": "Configure myTemplateGenerator..."
|
||||
"mytemplategenerator.configure.title": "Configure myTemplateGenerator...",
|
||||
"mytemplategenerator.config.templatesPath.description": "Path to the templates folder (relative to the project root)",
|
||||
"mytemplategenerator.config.overwriteFiles.description": "Overwrite existing files when generating from template",
|
||||
"mytemplategenerator.config.inputMode.description": "Variable input mode: webview or inputBox",
|
||||
"mytemplategenerator.config.language.description": "Extension interface language"
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"mytemplategenerator.createFromTemplate.title": "Создать из шаблона...",
|
||||
"mytemplategenerator.configure.title": "Настроить myTemplateGenerator..."
|
||||
"mytemplategenerator.configure.title": "Настроить myTemplateGenerator...",
|
||||
"mytemplategenerator.config.templatesPath.description": "Путь к папке с шаблонами (относительно корня проекта)",
|
||||
"mytemplategenerator.config.overwriteFiles.description": "Перезаписывать ли существующие файлы при генерации",
|
||||
"mytemplategenerator.config.inputMode.description": "Режим ввода переменных: webview или inputBox",
|
||||
"mytemplategenerator.config.language.description": "Язык интерфейса расширения"
|
||||
}
|
||||
37
src/core/config.ts
Normal file
37
src/core/config.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
// Работа с конфигом расширения
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export interface MyTemplateGeneratorConfig {
|
||||
templatesPath: string;
|
||||
overwriteFiles: boolean;
|
||||
inputMode: 'webview' | 'inputBox';
|
||||
language?: string;
|
||||
}
|
||||
|
||||
export function readConfig(): MyTemplateGeneratorConfig {
|
||||
const config = vscode.workspace.getConfiguration('myTemplateGenerator');
|
||||
return {
|
||||
templatesPath: config.get<string>('templatesPath', '.templates'),
|
||||
overwriteFiles: config.get<boolean>('overwriteFiles', false),
|
||||
inputMode: config.get<'webview' | 'inputBox'>('inputMode', 'webview'),
|
||||
language: config.get<string>('language', 'en'),
|
||||
};
|
||||
}
|
||||
|
||||
export async function writeConfig(newConfig: Partial<MyTemplateGeneratorConfig>) {
|
||||
const config = vscode.workspace.getConfiguration('myTemplateGenerator');
|
||||
if (newConfig.templatesPath !== undefined) {
|
||||
await config.update('templatesPath', newConfig.templatesPath, vscode.ConfigurationTarget.Global);
|
||||
}
|
||||
if (newConfig.overwriteFiles !== undefined) {
|
||||
await config.update('overwriteFiles', newConfig.overwriteFiles, vscode.ConfigurationTarget.Global);
|
||||
}
|
||||
if (newConfig.inputMode !== undefined) {
|
||||
await config.update('inputMode', newConfig.inputMode, vscode.ConfigurationTarget.Global);
|
||||
}
|
||||
if (newConfig.language !== undefined) {
|
||||
await config.update('language', newConfig.language, vscode.ConfigurationTarget.Global);
|
||||
}
|
||||
}
|
||||
71
src/core/i18n.ts
Normal file
71
src/core/i18n.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
// Словари локализации и утилиты для i18n
|
||||
import * as vscode from 'vscode';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
export const I18N_DICTIONARIES: Record<string, Record<string, string>> = {
|
||||
ru: {
|
||||
destinationPath: 'Путь назначения',
|
||||
chooseTemplate: 'Выберите шаблон',
|
||||
enterVariables: 'Введите значения переменных',
|
||||
varInputHint: 'без скобок {{ }}',
|
||||
create: 'Создать',
|
||||
selectTemplate: 'Шаблон',
|
||||
fileExistsNoOverwrite: 'Файл или папка уже существует и перезапись запрещена',
|
||||
fileExists: 'Файл или папка уже существует',
|
||||
createSuccess: 'Структура {{template}} успешно создана.',
|
||||
createError: 'Ошибка при создании структуры',
|
||||
noTemplates: 'В папке шаблонов нет шаблонов.',
|
||||
templatesNotFound: 'Папка шаблонов не найдена:',
|
||||
noFolders: 'Нет открытых папок рабочего пространства.',
|
||||
inputName: 'Введите имя для шаблона',
|
||||
},
|
||||
en: {
|
||||
destinationPath: 'Destination path',
|
||||
chooseTemplate: 'Choose template',
|
||||
enterVariables: 'Enter variables',
|
||||
varInputHint: 'without curly braces {{ }}',
|
||||
create: 'Create',
|
||||
selectTemplate: 'Template',
|
||||
fileExistsNoOverwrite: 'File or folder already exists and overwrite is disabled',
|
||||
fileExists: 'File or folder already exists',
|
||||
createSuccess: 'Structure {{template}} created successfully.',
|
||||
createError: 'Error creating structure',
|
||||
noTemplates: 'No templates found in templates folder.',
|
||||
templatesNotFound: 'Templates folder not found:',
|
||||
noFolders: 'No workspace folders open.',
|
||||
inputName: 'Enter name for template',
|
||||
}
|
||||
};
|
||||
|
||||
export const SETTINGS_I18N: Record<string, Record<string, string>> = {
|
||||
ru: {
|
||||
title: 'Настройки myTemplateGenerator',
|
||||
templatesPath: 'Путь к шаблонам:',
|
||||
overwriteFiles: 'Перезаписывать существующие файлы',
|
||||
inputMode: 'Способ ввода переменных:',
|
||||
inputModeWebview: 'Webview (форма)',
|
||||
inputModeInputBox: 'InputBox (по одной)',
|
||||
language: 'Язык интерфейса:',
|
||||
save: 'Сохранить'
|
||||
},
|
||||
en: {
|
||||
title: 'myTemplateGenerator Settings',
|
||||
templatesPath: 'Templates path:',
|
||||
overwriteFiles: 'Overwrite existing files',
|
||||
inputMode: 'Variable input method:',
|
||||
inputModeWebview: 'Webview (form)',
|
||||
inputModeInputBox: 'InputBox (one by one)',
|
||||
language: 'Interface language:',
|
||||
save: 'Save'
|
||||
}
|
||||
};
|
||||
|
||||
export async function pickTemplate(templatesDir: string): Promise<string | undefined> {
|
||||
const templates = fs.readdirSync(templatesDir).filter(f => fs.statSync(path.join(templatesDir, f)).isDirectory());
|
||||
if (templates.length === 0) {
|
||||
vscode.window.showWarningMessage('В папке templates нет шаблонов.');
|
||||
return undefined;
|
||||
}
|
||||
return vscode.window.showQuickPick(templates, { placeHolder: 'Выберите шаблон' });
|
||||
}
|
||||
118
src/core/templateUtils.ts
Normal file
118
src/core/templateUtils.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
// Работа с шаблонами и преобразование кейсов
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as Handlebars from 'handlebars';
|
||||
// @ts-expect-error: Нет типов для change-case-all, но пакет работает корректно
|
||||
import { camelCase, pascalCase, snakeCase, kebabCase, constantCase, upperCase, lowerCase } from 'change-case-all';
|
||||
|
||||
export const CASE_MODIFIERS: Record<string, (str: string) => string> = {
|
||||
pascalCase,
|
||||
camelCase,
|
||||
snakeCase,
|
||||
kebabCase,
|
||||
screamingSnakeCase: constantCase,
|
||||
upperCase,
|
||||
lowerCase,
|
||||
upperCaseAll: (s: string) => s.replace(/[-_\s]+/g, '').toUpperCase(),
|
||||
lowerCaseAll: (s: string) => s.replace(/[-_\s]+/g, '').toLowerCase(),
|
||||
};
|
||||
|
||||
export function readDirRecursive(src: string): string[] {
|
||||
let results: string[] = [];
|
||||
const list = fs.readdirSync(src);
|
||||
list.forEach(function(file) {
|
||||
const filePath = path.join(src, file);
|
||||
const stat = fs.statSync(filePath);
|
||||
if (stat && stat.isDirectory()) {
|
||||
results = results.concat(readDirRecursive(filePath));
|
||||
} else {
|
||||
results.push(filePath);
|
||||
}
|
||||
});
|
||||
return results;
|
||||
}
|
||||
|
||||
export function copyTemplate(templateDir: string, targetDir: string, name: string) {
|
||||
const vars = {
|
||||
name,
|
||||
nameUpperCase: CASE_MODIFIERS.upperCase(name),
|
||||
nameLowerCase: CASE_MODIFIERS.lowerCase(name),
|
||||
namePascalCase: CASE_MODIFIERS.pascalCase(name),
|
||||
nameCamelCase: CASE_MODIFIERS.camelCase(name),
|
||||
nameSnakeCase: CASE_MODIFIERS.snakeCase(name),
|
||||
nameKebabCase: CASE_MODIFIERS.kebabCase(name),
|
||||
nameScreamingSnakeCase: CASE_MODIFIERS.screamingSnakeCase(name),
|
||||
nameUpperCaseAll: CASE_MODIFIERS.upperCaseAll(name),
|
||||
nameLowerCaseAll: CASE_MODIFIERS.lowerCaseAll(name)
|
||||
};
|
||||
const files = readDirRecursive(templateDir);
|
||||
for (const file of files) {
|
||||
const relPath = path.relative(templateDir, file);
|
||||
const relPathTmpl = Handlebars.compile(relPath);
|
||||
const targetRelPath = relPathTmpl(vars);
|
||||
const targetPath = path.join(targetDir, targetRelPath);
|
||||
const content = fs.readFileSync(file, 'utf8');
|
||||
const contentTmpl = Handlebars.compile(content);
|
||||
const rendered = contentTmpl(vars);
|
||||
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
||||
fs.writeFileSync(targetPath, rendered, { flag: 'wx' });
|
||||
}
|
||||
}
|
||||
|
||||
export function getAllTemplateVariables(templateDir: string): Set<string> {
|
||||
const files = readDirRecursive(templateDir);
|
||||
const varRegex = /{{\s*([\w]+)(?:\.[\w]+)?\s*}}/g;
|
||||
const vars = new Set<string>();
|
||||
for (const file of files) {
|
||||
let relPath = path.relative(templateDir, file);
|
||||
let match;
|
||||
while ((match = varRegex.exec(relPath)) !== null) {
|
||||
vars.add(match[1]);
|
||||
}
|
||||
const content = fs.readFileSync(file, 'utf8');
|
||||
while ((match = varRegex.exec(content)) !== null) {
|
||||
vars.add(match[1]);
|
||||
}
|
||||
}
|
||||
return vars;
|
||||
}
|
||||
|
||||
export function applyTemplate(str: string, vars: Record<string, string>, modifiers: Record<string, (s: string) => string>): string {
|
||||
return str.replace(/{{\s*([a-zA-Z0-9_]+)(?:\.([a-zA-Z0-9_]+))?\s*}}/g, (_, varName, mod) => {
|
||||
let value = vars[varName];
|
||||
if (value === undefined) return '';
|
||||
if (mod && modifiers[mod]) {
|
||||
return modifiers[mod](value);
|
||||
}
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
export function copyTemplateWithVars(templateDir: string, targetDir: string, vars: Record<string, string>, overwriteFiles: boolean = false, dict?: Record<string, string>, templateName?: string): boolean {
|
||||
const files = readDirRecursive(templateDir);
|
||||
const firstLevelDirs = new Set<string>();
|
||||
for (const file of files) {
|
||||
const relPath = path.relative(templateDir, file);
|
||||
const targetRelPath = applyTemplate(relPath, vars, CASE_MODIFIERS);
|
||||
const firstLevel = targetRelPath.split(path.sep)[0];
|
||||
firstLevelDirs.add(firstLevel);
|
||||
}
|
||||
if (!overwriteFiles && dict) {
|
||||
for (const dir of firstLevelDirs) {
|
||||
const checkPath = path.join(targetDir, dir);
|
||||
if (fs.existsSync(checkPath)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const file of files) {
|
||||
const relPath = path.relative(templateDir, file);
|
||||
const targetRelPath = applyTemplate(relPath, vars, CASE_MODIFIERS);
|
||||
const targetPath = path.join(targetDir, targetRelPath);
|
||||
const content = fs.readFileSync(file, 'utf8');
|
||||
const rendered = applyTemplate(content, vars, CASE_MODIFIERS);
|
||||
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
||||
fs.writeFileSync(targetPath, rendered, { flag: overwriteFiles ? 'w' : 'wx' });
|
||||
}
|
||||
return true;
|
||||
}
|
||||
28
src/core/vars.ts
Normal file
28
src/core/vars.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
// Работа с переменными шаблонов
|
||||
import { CASE_MODIFIERS } from './templateUtils';
|
||||
|
||||
export function buildVarsObject(userVars: Record<string, string>): Record<string, string> {
|
||||
const result: Record<string, string> = {};
|
||||
for (const [base, value] of Object.entries(userVars)) {
|
||||
result[base] = value;
|
||||
for (const [mod, fn] of Object.entries(CASE_MODIFIERS)) {
|
||||
result[`${base}.${mod}`] = fn(value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export async function collectUserVars(baseVars: Set<string>): Promise<Record<string, string>> {
|
||||
const result: Record<string, string> = {};
|
||||
for (const v of baseVars) {
|
||||
const input = await vscode.window.showInputBox({
|
||||
prompt: `Введите значение для ${v}`,
|
||||
placeHolder: `{{${v}}}`
|
||||
});
|
||||
if (!input) throw new Error(`Значение для ${v} не введено`);
|
||||
result[v] = input;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
1057
src/extension.ts
1057
src/extension.ts
File diff suppressed because it is too large
Load Diff
@@ -3,18 +3,8 @@ import * as assert from 'assert';
|
||||
// You can import and use all API from the 'vscode' module
|
||||
// as well as import your extension to test it
|
||||
import * as vscode from 'vscode';
|
||||
import {
|
||||
toPascalCase,
|
||||
toCamelCase,
|
||||
toSnakeCase,
|
||||
toKebabCase,
|
||||
toScreamingSnakeCase,
|
||||
toUpperCaseFirst,
|
||||
toUpperCaseAll,
|
||||
toLowerCaseAll,
|
||||
buildVarsObject,
|
||||
CASE_MODIFIERS
|
||||
} from '../extension';
|
||||
import { CASE_MODIFIERS } from '../core/templateUtils';
|
||||
import { buildVarsObject } from '../core/vars';
|
||||
|
||||
suite('Extension Test Suite', () => {
|
||||
vscode.window.showInformationMessage('Start all tests.');
|
||||
@@ -28,30 +18,6 @@ suite('Extension Test Suite', () => {
|
||||
suite('Template Variable Modifiers', () => {
|
||||
const input = 'my super-name';
|
||||
|
||||
test('toPascalCase', () => {
|
||||
assert.strictEqual(toPascalCase(input), 'MySuperName');
|
||||
});
|
||||
test('toCamelCase', () => {
|
||||
assert.strictEqual(toCamelCase(input), 'mySuperName');
|
||||
});
|
||||
test('toSnakeCase', () => {
|
||||
assert.strictEqual(toSnakeCase(input), 'my_super_name');
|
||||
});
|
||||
test('toKebabCase', () => {
|
||||
assert.strictEqual(toKebabCase(input), 'my-super-name');
|
||||
});
|
||||
test('toScreamingSnakeCase', () => {
|
||||
assert.strictEqual(toScreamingSnakeCase(input), 'MY_SUPER_NAME');
|
||||
});
|
||||
test('toUpperCaseFirst', () => {
|
||||
assert.strictEqual(toUpperCaseFirst(input), 'My super-name');
|
||||
});
|
||||
test('toUpperCaseAll', () => {
|
||||
assert.strictEqual(toUpperCaseAll(input), 'MYSUPERNAME');
|
||||
});
|
||||
test('toLowerCaseAll', () => {
|
||||
assert.strictEqual(toLowerCaseAll(input), 'mysupername');
|
||||
});
|
||||
test('CASE_MODIFIERS map covers all', () => {
|
||||
for (const [mod, fn] of Object.entries(CASE_MODIFIERS)) {
|
||||
assert.strictEqual(typeof fn(input), 'string');
|
||||
|
||||
57
src/vscode/completion.ts
Normal file
57
src/vscode/completion.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
// Регистрация и обработка автодополнения шаблонов
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import { getAllTemplateVariables, CASE_MODIFIERS } from '../core/templateUtils';
|
||||
import { readConfig } from '../core/config';
|
||||
import * as fs from 'fs';
|
||||
|
||||
function isInTemplatesDir(filePath: string, templatesDir: string): boolean {
|
||||
const rel = path.relative(templatesDir, filePath);
|
||||
return !rel.startsWith('..') && !path.isAbsolute(rel);
|
||||
}
|
||||
|
||||
export function registerTemplateCompletionAndHighlight(context: vscode.ExtensionContext) {
|
||||
const completionProvider = {
|
||||
provideCompletionItems(document: vscode.TextDocument, position: vscode.Position) {
|
||||
const config = readConfig();
|
||||
const templatesPath = config.templatesPath || 'templates';
|
||||
const workspaceFolders = vscode.workspace.workspaceFolders;
|
||||
if (!workspaceFolders || workspaceFolders.length === 0) return;
|
||||
const templatesDir = path.join(workspaceFolders[0].uri.fsPath, templatesPath);
|
||||
if (!isInTemplatesDir(document.uri.fsPath, templatesDir)) {
|
||||
return undefined;
|
||||
}
|
||||
const line = document.lineAt(position).text;
|
||||
const textBefore = line.slice(0, position.character);
|
||||
const match = /{{\s*([\w]+)?(?:\.([\w]*))?[^}]*$/.exec(textBefore);
|
||||
if (!match) return undefined;
|
||||
const allVars = getAllTemplateVariables(templatesDir);
|
||||
const items = [];
|
||||
if (match[2] !== undefined) {
|
||||
for (const mod of Object.keys(CASE_MODIFIERS)) {
|
||||
if (!match[2] || mod.startsWith(match[2])) {
|
||||
const item = new vscode.CompletionItem(mod, vscode.CompletionItemKind.EnumMember);
|
||||
item.insertText = mod;
|
||||
items.push(item);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const v of allVars) {
|
||||
if (!match[1] || v.startsWith(match[1])) {
|
||||
const item = new vscode.CompletionItem(v, vscode.CompletionItemKind.Variable);
|
||||
item.insertText = v;
|
||||
items.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
};
|
||||
context.subscriptions.push(
|
||||
vscode.languages.registerCompletionItemProvider(
|
||||
'*',
|
||||
completionProvider,
|
||||
'{', '.'
|
||||
)
|
||||
);
|
||||
}
|
||||
109
src/vscode/decorations.ts
Normal file
109
src/vscode/decorations.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
// Декорации и диагностика шаблонов
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import { getAllTemplateVariables } from '../core/templateUtils';
|
||||
import { readConfig } from '../core/config';
|
||||
|
||||
const bracketDecoration = vscode.window.createTextEditorDecorationType({
|
||||
color: '#43A047', // зелёный для скобок
|
||||
rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed,
|
||||
fontWeight: 'bold'
|
||||
});
|
||||
const variableDecoration = vscode.window.createTextEditorDecorationType({
|
||||
color: '#FF9800', // оранжевый для имени переменной
|
||||
rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed,
|
||||
fontWeight: 'bold'
|
||||
});
|
||||
const modifierDecoration = vscode.window.createTextEditorDecorationType({
|
||||
color: '#00ACC1', // бирюзовый для модификатора
|
||||
rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed,
|
||||
fontWeight: 'bold'
|
||||
});
|
||||
|
||||
function isInTemplatesDir(filePath: string, templatesDir: string): boolean {
|
||||
const rel = path.relative(templatesDir, filePath);
|
||||
return !rel.startsWith('..') && !path.isAbsolute(rel);
|
||||
}
|
||||
|
||||
function updateTemplateDecorations(editor: vscode.TextEditor) {
|
||||
if (!editor) return;
|
||||
const config = readConfig();
|
||||
const templatesPath = config.templatesPath || 'templates';
|
||||
const workspaceFolders = vscode.workspace.workspaceFolders;
|
||||
if (!workspaceFolders || workspaceFolders.length === 0) return;
|
||||
const templatesDir = path.join(workspaceFolders[0].uri.fsPath, templatesPath);
|
||||
if (!isInTemplatesDir(editor.document.uri.fsPath, templatesDir)) return;
|
||||
const brackets: vscode.DecorationOptions[] = [];
|
||||
const variables: vscode.DecorationOptions[] = [];
|
||||
const modifiers: vscode.DecorationOptions[] = [];
|
||||
for (let lineNum = 0; lineNum < editor.document.lineCount; lineNum++) {
|
||||
const line = editor.document.lineAt(lineNum).text;
|
||||
// Ищем все {{variable.modifier}} или {{variable}}
|
||||
const reg = /{{\s*([a-zA-Z0-9_]+)(?:\.([a-zA-Z0-9_]+))?\s*}}/g;
|
||||
let match;
|
||||
while ((match = reg.exec(line)) !== null) {
|
||||
const start = match.index;
|
||||
const end = start + match[0].length;
|
||||
// Скобки {{ и }}
|
||||
brackets.push({
|
||||
range: new vscode.Range(lineNum, start, lineNum, start + 2)
|
||||
});
|
||||
brackets.push({
|
||||
range: new vscode.Range(lineNum, end - 2, lineNum, end)
|
||||
});
|
||||
// Имя переменной
|
||||
const varStart = start + 2 + line.slice(start + 2).search(/\S/); // после {{
|
||||
variables.push({
|
||||
range: new vscode.Range(lineNum, varStart, lineNum, varStart + match[1].length)
|
||||
});
|
||||
// Модификатор (если есть)
|
||||
if (match[2]) {
|
||||
const modStart = varStart + match[1].length + 1; // +1 за точку
|
||||
modifiers.push({
|
||||
range: new vscode.Range(lineNum, modStart, lineNum, modStart + match[2].length)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
editor.setDecorations(bracketDecoration, brackets);
|
||||
editor.setDecorations(variableDecoration, variables);
|
||||
editor.setDecorations(modifierDecoration, modifiers);
|
||||
}
|
||||
|
||||
export function registerTemplateDecorations(context: vscode.ExtensionContext) {
|
||||
function decorateActiveEditor() {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
if (editor) {
|
||||
updateTemplateDecorations(editor);
|
||||
}
|
||||
}
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.window.onDidChangeActiveTextEditor(editor => {
|
||||
if (editor) decorateActiveEditor();
|
||||
}),
|
||||
vscode.workspace.onDidChangeTextDocument(event => {
|
||||
const editor = vscode.window.visibleTextEditors.find(e => e.document === event.document);
|
||||
if (editor) updateTemplateDecorations(editor);
|
||||
})
|
||||
);
|
||||
// Инициализация при активации
|
||||
setTimeout(() => {
|
||||
vscode.window.visibleTextEditors.forEach(editor => updateTemplateDecorations(editor));
|
||||
}, 300);
|
||||
}
|
||||
|
||||
export function decorateActiveEditor() {
|
||||
// Логика декорирования активного редактора
|
||||
// ...
|
||||
}
|
||||
|
||||
export function clearDiagnosticsForEditor(editor: vscode.TextEditor, templatesDir: string) {
|
||||
// Очистка диагностик для редактора
|
||||
// ...
|
||||
}
|
||||
|
||||
export function clearDiagnosticsForTemplates(context: vscode.ExtensionContext) {
|
||||
// Очистка диагностик для всех шаблонов
|
||||
// ...
|
||||
}
|
||||
66
src/vscode/semanticHighlight.ts
Normal file
66
src/vscode/semanticHighlight.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
// Семантическая подсветка шаблонов
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import { getAllTemplateVariables } from '../core/templateUtils';
|
||||
import { readConfig } from '../core/config';
|
||||
|
||||
function isInTemplatesDir(filePath: string, templatesDir: string): boolean {
|
||||
const rel = path.relative(templatesDir, filePath);
|
||||
return !rel.startsWith('..') && !path.isAbsolute(rel);
|
||||
}
|
||||
|
||||
export function registerTemplateSemanticHighlight(context: vscode.ExtensionContext) {
|
||||
const legend = new vscode.SemanticTokensLegend(['bracket', 'variable', 'modifier']);
|
||||
const disposable = vscode.languages.registerDocumentSemanticTokensProvider(
|
||||
{ pattern: '**' }, // теперь на все файлы
|
||||
{
|
||||
provideDocumentSemanticTokens(document: any) {
|
||||
const config = readConfig();
|
||||
const templatesPath = config.templatesPath || 'templates';
|
||||
const workspaceFolders = vscode.workspace.workspaceFolders;
|
||||
if (!workspaceFolders || workspaceFolders.length === 0) return;
|
||||
const templatesDir = path.join(workspaceFolders[0].uri.fsPath, templatesPath);
|
||||
// Проверяем, что файл в папке шаблонов
|
||||
if (!isInTemplatesDir(document.uri.fsPath, templatesDir)) {
|
||||
return;
|
||||
}
|
||||
const tokens: number[] = [];
|
||||
for (let lineNum = 0; lineNum < document.lineCount; lineNum++) {
|
||||
const line = document.lineAt(lineNum).text;
|
||||
// Ищем все {{variable.modifier}} или {{variable}} или {{variable.}}
|
||||
const reg = /({{)|(}})|{{\s*([a-zA-Z0-9_]+)(?:\.(\w*))?\s*}}/g;
|
||||
let match;
|
||||
while ((match = reg.exec(line)) !== null) {
|
||||
if (match[1]) {
|
||||
// {{
|
||||
tokens.push(lineNum, match.index, 2, 0, 0); // bracket
|
||||
} else if (match[2]) {
|
||||
// }}
|
||||
tokens.push(lineNum, match.index, 2, 0, 0); // bracket
|
||||
} else if (match[3]) {
|
||||
// variable (имя)
|
||||
const varStart = match.index + 2 + line.slice(match.index + 2).search(/\S/); // после {{
|
||||
tokens.push(lineNum, varStart, match[3].length, 1, 0); // variable
|
||||
if (typeof match[4] === 'string') {
|
||||
// Если есть точка, но модификатор не введён ({{name.}})
|
||||
if (match[4] === '') {
|
||||
// Подсвечиваем только точку как variable
|
||||
const dotStart = varStart + match[3].length;
|
||||
tokens.push(lineNum, dotStart, 1, 1, 0); // variable (точка)
|
||||
} else if (match[4]) {
|
||||
// .modifier
|
||||
const modStart = varStart + match[3].length + 1; // +1 за точку
|
||||
tokens.push(lineNum, modStart, match[4].length, 2, 0); // modifier
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return new vscode.SemanticTokens(new Uint32Array(tokens));
|
||||
}
|
||||
},
|
||||
legend
|
||||
);
|
||||
context.subscriptions.push(disposable);
|
||||
return disposable;
|
||||
}
|
||||
139
src/webview/configWebview.ts
Normal file
139
src/webview/configWebview.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
// Webview для конфигурации расширения
|
||||
import * as vscode from 'vscode';
|
||||
import { MyTemplateGeneratorConfig, readConfig, writeConfig } from '../core/config';
|
||||
|
||||
const LOCALIZATION: Record<'ru'|'en', {
|
||||
title: string;
|
||||
templatesPath: string;
|
||||
overwriteFiles: string;
|
||||
inputMode: string;
|
||||
inputBox: string;
|
||||
webview: string;
|
||||
language: string;
|
||||
save: string;
|
||||
russian: string;
|
||||
english: string;
|
||||
}> = {
|
||||
ru: {
|
||||
title: 'Настройки генератора шаблонов',
|
||||
templatesPath: 'Путь к шаблонам:',
|
||||
overwriteFiles: 'Перезаписывать файлы',
|
||||
inputMode: 'Режим ввода:',
|
||||
inputBox: 'InputBox',
|
||||
webview: 'Webview',
|
||||
language: 'Язык:',
|
||||
save: 'Сохранить',
|
||||
russian: 'Русский',
|
||||
english: 'English',
|
||||
},
|
||||
en: {
|
||||
title: 'Template Generator Settings',
|
||||
templatesPath: 'Templates path:',
|
||||
overwriteFiles: 'Overwrite files',
|
||||
inputMode: 'Input mode:',
|
||||
inputBox: 'InputBox',
|
||||
webview: 'Webview',
|
||||
language: 'Language:',
|
||||
save: 'Save',
|
||||
russian: 'Russian',
|
||||
english: 'English',
|
||||
}
|
||||
};
|
||||
|
||||
export async function showConfigWebview(context: vscode.ExtensionContext) {
|
||||
const panel = vscode.window.createWebviewPanel(
|
||||
'myTemplateGeneratorConfig',
|
||||
'Настройки MyTemplateGenerator',
|
||||
vscode.ViewColumn.One,
|
||||
{ enableScripts: true }
|
||||
);
|
||||
let config = readConfig();
|
||||
// Стили теперь лежат в media/styles.css (папка для статики)
|
||||
const stylePath = vscode.Uri.joinPath(context.extensionUri, 'media', 'styles.css');
|
||||
const styleUri = panel.webview.asWebviewUri(stylePath);
|
||||
setHtml((config.language === 'en' ? 'en' : 'ru'));
|
||||
|
||||
function setHtml(language: 'ru'|'en') {
|
||||
panel.webview.html = getHtml(language);
|
||||
}
|
||||
|
||||
function getHtml(language: 'ru'|'en'): string {
|
||||
const dict = LOCALIZATION[language] || LOCALIZATION['ru'];
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html lang="${language}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${panel.webview.cspSource}; script-src 'unsafe-inline';">
|
||||
<title>${dict.title}</title>
|
||||
<link rel="stylesheet" href="${styleUri}">
|
||||
</head>
|
||||
<body>
|
||||
<div class="config-container">
|
||||
<h2>${dict.title}</h2>
|
||||
<form id="configForm">
|
||||
<div class="form-group">
|
||||
<label>${dict.templatesPath}
|
||||
<input name="templatesPath" value="${config.templatesPath}" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<input type="checkbox" name="overwriteFiles" ${config.overwriteFiles ? 'checked' : ''}/>
|
||||
${dict.overwriteFiles}
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>${dict.inputMode}
|
||||
<select name="inputMode">
|
||||
<option value="inputBox" ${config.inputMode === 'inputBox' ? 'selected' : ''}>${dict.inputBox}</option>
|
||||
<option value="webview" ${config.inputMode === 'webview' ? 'selected' : ''}>${dict.webview}</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>${dict.language}
|
||||
<select name="language" id="languageSelect">
|
||||
<option value="ru" ${language === 'ru' ? 'selected' : ''}>${dict.russian}</option>
|
||||
<option value="en" ${language === 'en' ? 'selected' : ''}>${dict.english}</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<button type="submit" class="btn">${dict.save}</button>
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
const vscode = acquireVsCodeApi();
|
||||
document.getElementById('configForm').onsubmit = function(e) {
|
||||
e.preventDefault();
|
||||
const data = Object.fromEntries(new FormData(this));
|
||||
data.overwriteFiles = !!this.overwriteFiles.checked;
|
||||
vscode.postMessage({ type: 'save', data });
|
||||
};
|
||||
document.getElementById('languageSelect').onchange = function(e) {
|
||||
vscode.postMessage({ type: 'setLanguage', language: this.value });
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
panel.webview.onDidReceiveMessage(
|
||||
async msg => {
|
||||
if (msg.type === 'save') {
|
||||
await writeConfig(msg.data);
|
||||
vscode.window.showInformationMessage('Настройки сохранены!');
|
||||
panel.dispose();
|
||||
}
|
||||
if (msg.type === 'setLanguage') {
|
||||
// Сохраняем язык в конфиг и перерисовываем webview
|
||||
config.language = msg.language;
|
||||
await writeConfig(config);
|
||||
setHtml(msg.language === 'en' ? 'en' : 'ru');
|
||||
}
|
||||
},
|
||||
undefined,
|
||||
context.subscriptions
|
||||
);
|
||||
}
|
||||
124
src/webview/styles.css
Normal file
124
src/webview/styles.css
Normal file
@@ -0,0 +1,124 @@
|
||||
:root {
|
||||
--bg: #f7f7fa;
|
||||
--panel-bg: #fff;
|
||||
--text: #222;
|
||||
--label: #555;
|
||||
--input-bg: #f0f0f3;
|
||||
--input-border: #d0d0d7;
|
||||
--input-focus: #1976d2;
|
||||
--button-bg: #1976d2;
|
||||
--button-text: #fff;
|
||||
--button-hover: #1565c0;
|
||||
--border-radius: 8px;
|
||||
--shadow: 0 2px 12px rgba(0,0,0,0.07);
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--bg: #181a1b;
|
||||
--panel-bg: #23272e;
|
||||
--text: #f3f3f3;
|
||||
--label: #b0b0b0;
|
||||
--input-bg: #23272e;
|
||||
--input-border: #33363b;
|
||||
--input-focus: #90caf9;
|
||||
--button-bg: #1976d2;
|
||||
--button-text: #fff;
|
||||
--button-hover: #1565c0;
|
||||
--border-radius: 8px;
|
||||
--shadow: 0 2px 12px rgba(0,0,0,0.25);
|
||||
}
|
||||
}
|
||||
body {
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-family: 'Segoe UI', 'Roboto', Arial, sans-serif;
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.create-container, .config-container {
|
||||
max-width: 420px;
|
||||
margin: 48px auto;
|
||||
background: var(--panel-bg);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: var(--shadow);
|
||||
padding: 32px 36px 28px 36px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18px;
|
||||
}
|
||||
.head-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.create-container h2, .config-container h2 {
|
||||
margin: 0;
|
||||
font-size: 1.5em;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
label {
|
||||
color: var(--label);
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
}
|
||||
input, select {
|
||||
background: var(--input-bg);
|
||||
color: var(--text);
|
||||
border: 1.5px solid var(--input-border);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 8px 10px;
|
||||
font-size: 1em;
|
||||
transition: border 0.2s, box-shadow 0.2s;
|
||||
outline: none;
|
||||
}
|
||||
input:focus, select:focus {
|
||||
border-color: var(--input-focus);
|
||||
box-shadow: 0 0 0 2px var(--input-focus)33;
|
||||
}
|
||||
button, .btn {
|
||||
margin-top: 10px;
|
||||
background: var(--button-bg);
|
||||
color: var(--button-text);
|
||||
border: none;
|
||||
border-radius: var(--border-radius);
|
||||
padding: 10px 15px;
|
||||
font-size: 1.1em;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s, box-shadow 0.2s;
|
||||
box-shadow: 0 1px 4px rgba(25, 118, 210, 0.08);
|
||||
}
|
||||
button:hover, button:focus, .btn:hover, .btn:focus {
|
||||
background: var(--button-hover);
|
||||
}
|
||||
.destination {
|
||||
margin-bottom: 16px;
|
||||
color: #888;
|
||||
font-size: 13px;
|
||||
}
|
||||
.lang-select {
|
||||
display: block;
|
||||
}
|
||||
.var-hint {
|
||||
color: #888;
|
||||
font-size: 13px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.template-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
#configForm {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
193
src/webview/templateVarsWebview.ts
Normal file
193
src/webview/templateVarsWebview.ts
Normal file
@@ -0,0 +1,193 @@
|
||||
// Webview для выбора шаблона и переменных
|
||||
import * as vscode from 'vscode';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { getAllTemplateVariables } from '../core/templateUtils';
|
||||
import { I18N_DICTIONARIES } from '../core/i18n';
|
||||
import { writeConfig, readConfig } from '../core/config';
|
||||
|
||||
export async function showTemplateAndVarsWebview(
|
||||
context: vscode.ExtensionContext,
|
||||
templatesDir: string,
|
||||
targetPath: string,
|
||||
initialLanguage: string
|
||||
): Promise<{ template: string, vars: Record<string, string> } | undefined> {
|
||||
let language = initialLanguage;
|
||||
function getDict() {
|
||||
return I18N_DICTIONARIES[language] || I18N_DICTIONARIES['ru'];
|
||||
}
|
||||
const templates = fs.readdirSync(templatesDir).filter(f => fs.statSync(path.join(templatesDir, f)).isDirectory());
|
||||
// Стили теперь лежат в media/styles.css (папка для статики)
|
||||
const stylePath = vscode.Uri.joinPath(context.extensionUri, 'media', 'styles.css');
|
||||
return new Promise((resolve) => {
|
||||
const panel = vscode.window.createWebviewPanel(
|
||||
'templateVars',
|
||||
getDict().create,
|
||||
vscode.ViewColumn.Active,
|
||||
{ enableScripts: true }
|
||||
);
|
||||
const styleUri = panel.webview.asWebviewUri(stylePath);
|
||||
let currentVars: string[] = [];
|
||||
let currentTemplate = templates[0] || '';
|
||||
let disposed = false;
|
||||
function getVarsHtml(vars: string[], values: Record<string, string> = {}) {
|
||||
const dict = getDict();
|
||||
if (!vars.length) return '';
|
||||
return `<h3>${dict.enterVariables}</h3>
|
||||
<div class="var-hint">${dict.varInputHint}</div>
|
||||
<form id="varsForm">
|
||||
${vars.map(v => `
|
||||
<label><input name="${v}" placeholder="{{${v}}}" value="${values[v] || ''}" required /></label><br/><br/>
|
||||
`).join('')}
|
||||
<button type="submit" class="btn">${dict.create}</button>
|
||||
</form>`;
|
||||
}
|
||||
function getTemplatesRadioHtml(templates: string[], selected: string) {
|
||||
const dict = getDict();
|
||||
return `<form id="templateForm">
|
||||
<h3>${dict.chooseTemplate}:</h3>
|
||||
<div class="template-list">
|
||||
${templates.map(t => `
|
||||
<label><input type="radio" name="templateRadio" value="${t}" ${selected === t ? 'checked' : ''}/> ${t}</label>
|
||||
`).join('')}
|
||||
</div>
|
||||
</form>`;
|
||||
}
|
||||
function getLanguageSelectorHtml(selected: string) {
|
||||
return `<label class="lang-select">
|
||||
<select id="languageSelect">
|
||||
<option value="ru" ${selected === 'ru' ? 'selected' : ''}>Русский</option>
|
||||
<option value="en" ${selected === 'en' ? 'selected' : ''}>English</option>
|
||||
</select>
|
||||
</label>`;
|
||||
}
|
||||
function setHtml(templatesHtml: string, varsHtml: string) {
|
||||
const dict = getDict();
|
||||
panel.webview.html = `
|
||||
<!DOCTYPE html>
|
||||
<html lang="${language}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${panel.webview.cspSource}; script-src 'unsafe-inline';">
|
||||
<title>${dict.create}</title>
|
||||
<link rel="stylesheet" href="${styleUri}">
|
||||
</head>
|
||||
<body>
|
||||
<div class="create-container">
|
||||
<div class="head-wrap">
|
||||
<h2>${dict.create}</h2>
|
||||
${getLanguageSelectorHtml(language)}
|
||||
</div>
|
||||
<div id="templatesBlock">
|
||||
${templatesHtml}
|
||||
</div>
|
||||
<div id="varsBlock">
|
||||
${varsHtml}
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function() {
|
||||
const vscode = acquireVsCodeApi();
|
||||
function initHandlers() {
|
||||
// Template radio
|
||||
const templateRadios = document.querySelectorAll('input[name="templateRadio"]');
|
||||
templateRadios.forEach(radio => {
|
||||
radio.addEventListener('change', (e) => {
|
||||
vscode.postMessage({ type: 'selectTemplate', template: e.target.value, language: document.getElementById('languageSelect').value });
|
||||
});
|
||||
});
|
||||
// Vars form
|
||||
const varsForm = document.getElementById('varsForm');
|
||||
if (varsForm) {
|
||||
varsForm.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
const data = {};
|
||||
Array.from(varsForm.elements).forEach(el => {
|
||||
if (el.name) data[el.name] = el.value;
|
||||
});
|
||||
vscode.postMessage({ type: 'submit', template: document.querySelector('input[name="templateRadio"]:checked')?.value || '', data, language: document.getElementById('languageSelect').value });
|
||||
});
|
||||
}
|
||||
// Language select
|
||||
const langSel = document.getElementById('languageSelect');
|
||||
if (langSel) {
|
||||
langSel.addEventListener('change', (e) => {
|
||||
vscode.postMessage({ type: 'setLanguage', language: e.target.value, template: document.querySelector('input[name="templateRadio"]:checked')?.value || '' });
|
||||
});
|
||||
}
|
||||
}
|
||||
window.initHandlers = initHandlers;
|
||||
document.addEventListener('DOMContentLoaded', initHandlers);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
// После перерисовки HTML вызываем initHandlers
|
||||
setTimeout(() => {
|
||||
panel.webview.postMessage({ type: 'callInitHandlers' });
|
||||
}, 0);
|
||||
}
|
||||
// Инициализация: сразу выбран первый шаблон и форма переменных
|
||||
let initialVars: string[] = [];
|
||||
if (currentTemplate) {
|
||||
const templateDir = path.join(templatesDir, currentTemplate);
|
||||
const allVars = getAllTemplateVariables(templateDir);
|
||||
initialVars = Array.from(allVars);
|
||||
currentVars = initialVars;
|
||||
}
|
||||
setHtml(getTemplatesRadioHtml(templates, currentTemplate), getVarsHtml(initialVars));
|
||||
// Обработка сообщений
|
||||
panel.webview.onDidReceiveMessage(
|
||||
async message => {
|
||||
if (message.type === 'selectTemplate') {
|
||||
currentTemplate = message.template;
|
||||
if (message.language) language = message.language;
|
||||
if (!currentTemplate) {
|
||||
setHtml(getTemplatesRadioHtml(templates, ''), '');
|
||||
return;
|
||||
}
|
||||
// Получаем переменные для выбранного шаблона
|
||||
const templateDir = path.join(templatesDir, currentTemplate);
|
||||
const allVars = getAllTemplateVariables(templateDir);
|
||||
currentVars = Array.from(allVars);
|
||||
setHtml(getTemplatesRadioHtml(templates, currentTemplate), getVarsHtml(currentVars));
|
||||
} else if (message.type === 'setLanguage') {
|
||||
if (message.language) language = message.language;
|
||||
// Сохраняем язык в конфиг
|
||||
const oldConfig = readConfig();
|
||||
await writeConfig({ ...oldConfig, language });
|
||||
currentTemplate = message.template || templates[0] || '';
|
||||
// Получаем переменные для выбранного шаблона
|
||||
let baseVars: string[] = [];
|
||||
if (currentTemplate) {
|
||||
const templateDir = path.join(templatesDir, currentTemplate);
|
||||
const allVars = getAllTemplateVariables(templateDir);
|
||||
baseVars = Array.from(allVars);
|
||||
currentVars = baseVars;
|
||||
}
|
||||
setHtml(getTemplatesRadioHtml(templates, currentTemplate), getVarsHtml(currentVars));
|
||||
} else if (message.type === 'changeLanguage') {
|
||||
// legacy, не нужен
|
||||
} else if (message.type === 'submit') {
|
||||
if (message.language) language = message.language;
|
||||
if (!disposed) {
|
||||
disposed = true;
|
||||
panel.dispose();
|
||||
resolve({ template: message.template, vars: message.data });
|
||||
}
|
||||
} else if (message.type === 'callInitHandlers') {
|
||||
// Ничего не делаем, скрипт внутри webview вызовет window.initHandlers
|
||||
}
|
||||
},
|
||||
undefined,
|
||||
context.subscriptions
|
||||
);
|
||||
panel.onDidDispose(() => {
|
||||
if (!disposed) {
|
||||
disposed = true;
|
||||
resolve(undefined);
|
||||
}
|
||||
}, null, context.subscriptions);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user