diff --git a/CHANGELOG.md b/CHANGELOG.md
deleted file mode 100644
index 23c59401a..000000000
--- a/CHANGELOG.md
+++ /dev/null
@@ -1,4 +0,0 @@
-# Changelog
-
-All notable changes to this project will be documented in this file. See
-[standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
diff --git a/apps/demo-integrations/cypress/support/helpers/demo-paths.ts b/apps/demo-integrations/cypress/support/helpers/demo-paths.ts
index dcfbd3f9f..afb6fb226 100644
--- a/apps/demo-integrations/cypress/support/helpers/demo-paths.ts
+++ b/apps/demo-integrations/cypress/support/helpers/demo-paths.ts
@@ -2,7 +2,7 @@ import type {TuiDocPage, TuiDocPages} from '@taiga-ui/addon-doc';
import {DEMO_PAGES} from '../../../../demo/src/app/app.pages';
-const EXCLUSION_SECTIONS: string[] = [`Other`];
+const EXCLUSION_SECTIONS: string[] = [`Documentation`];
const EXCLUSION_ROUTES: string[] = [`Starter Kit`];
export const DEMO_PATHS = flatPages(DEMO_PAGES)
diff --git a/apps/demo/src/app/app.component.html b/apps/demo/src/app/app.component.html
index 5e8a6067c..03163f521 100644
--- a/apps/demo/src/app/app.component.html
+++ b/apps/demo/src/app/app.component.html
@@ -24,6 +24,16 @@
target="_blank"
class="link"
>
+
+
+ (await import(`./pages/stackblitz/starter/stackblitz-starter.module`))
+ .StackblitzStarterModule,
+ data: {
+ title: `Stackblitz Starter`,
+ },
+ },
{
path: TuiDemoPath.Installation,
loadChildren: async () =>
diff --git a/apps/demo/src/app/constants/demo-path.ts b/apps/demo/src/app/constants/demo-path.ts
index cd7f7ed97..afa2cacf4 100644
--- a/apps/demo/src/app/constants/demo-path.ts
+++ b/apps/demo/src/app/constants/demo-path.ts
@@ -1,5 +1,7 @@
// eslint-disable-next-line no-restricted-syntax
export const enum TuiDemoPath {
+ Stackblitz = `stackblitz`,
+ Changelog = `changelog`,
StarterKit = `starter-kit`,
Installation = `installation`,
ColorPicker = `color-picker`,
@@ -22,5 +24,4 @@ export const enum TuiDemoPath {
EmbedYoutube = `embed/youtube`,
EmbedIframe = `embed/iframe`,
EmbedHtml5 = `embed/html5`,
- Changelog = `changelog`,
}
diff --git a/apps/demo/src/app/pages/changelog/editor-changelog.component.ts b/apps/demo/src/app/pages/changelog/editor-changelog.component.ts
index 21d422482..735972db2 100644
--- a/apps/demo/src/app/pages/changelog/editor-changelog.component.ts
+++ b/apps/demo/src/app/pages/changelog/editor-changelog.component.ts
@@ -1,7 +1,8 @@
import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core';
import {tuiRawLoad} from '@taiga-ui/addon-doc';
+import MarkdownIt from 'markdown-it';
import {of} from 'rxjs';
-import {switchMap} from 'rxjs/operators';
+import {map, switchMap} from 'rxjs/operators';
@Component({
selector: 'changelog',
@@ -11,7 +12,22 @@ import {switchMap} from 'rxjs/operators';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TuiChangelogComponent {
- readonly changelog$ = of(import('../../../../../../CHANGELOG.md?raw')).pipe(
+ readonly changelog$ = of(
+ import('../../../../../../libs/tui-editor/CHANGELOG.md?raw'),
+ ).pipe(
switchMap(tuiRawLoad),
+ map((value: string) =>
+ new MarkdownIt()
+ .render(
+ value
+ .replaceAll(
+ 'All notable changes to this project will be documented in this file. See\n[Conventional Commits](https://conventionalcommits.org) for commit guidelines.',
+ '',
+ )
+ .replace(/# Change Log\n/g, '')
+ .trim(),
+ )
+ .replace(/h1|h2|h3|h4|h5|h6/g, 'h2'),
+ ),
);
}
diff --git a/apps/demo/src/app/pages/changelog/editor-changelog.style.less b/apps/demo/src/app/pages/changelog/editor-changelog.style.less
index f85ee7262..7a9cbee16 100644
--- a/apps/demo/src/app/pages/changelog/editor-changelog.style.less
+++ b/apps/demo/src/app/pages/changelog/editor-changelog.style.less
@@ -1,60 +1,35 @@
-markdown {
+.changelog {
max-width: 58.25rem;
- & > :nth-child(1),
- & > :nth-child(2) {
- display: none;
- }
-
- > * {
- margin-left: 1.25rem;
- }
-
- & h2 {
- font-size: 2em;
- padding-bottom: 0.5em;
- margin-left: 0;
- border-bottom: 1px solid var(--tui-base-03);
- }
-
- & h3 {
- text-transform: uppercase;
- font-weight: normal;
- font-size: 1.5rem;
- margin: 1rem 0;
- }
-
- & h3:not([id^='feat']):not([id^='bug']):not([id^='deprecations']) {
- font-size: 1.75rem;
- padding-bottom: 0.5em;
- margin: 2rem 0 0 0;
- border-bottom: 1px solid var(--tui-base-03);
- }
-
- & h3[id^='breaking'] {
+ > *:not(h2) {
margin-left: 1.25rem;
- color: var(--tui-error-fill);
}
- & code {
- color: #d45d8c;
- }
-
- & h3[id^='feat']:before {
- content: '🚀';
- }
+ a {
+ text-decoration: none;
+ color: var(--tui-link);
- & h3[id^='bug']:before {
- content: '🐞';
+ &:hover,
+ &:active {
+ color: var(--tui-link-hover);
+ }
}
- & h3[id^='deprecations']:before {
- content: '⚠️';
+ li {
+ position: relative;
+ padding-left: 1.5rem;
+ word-wrap: break-word;
+ margin-top: 0.75rem;
}
- & h3[id^='feat']:before,
- & h3[id^='bug']:before,
- & h3[id^='deprecations']:before {
- margin-right: 0.5rem;
+ li:before {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: 0.5rem;
+ width: 0.5rem;
+ height: 0.5rem;
+ border-radius: 100%;
+ background-color: var(--tui-primary);
}
}
diff --git a/apps/demo/src/app/pages/changelog/editor-changelog.template.html b/apps/demo/src/app/pages/changelog/editor-changelog.template.html
index b0e3d74cd..10d1dcd5e 100644
--- a/apps/demo/src/app/pages/changelog/editor-changelog.template.html
+++ b/apps/demo/src/app/pages/changelog/editor-changelog.template.html
@@ -1,3 +1,6 @@
-
+
diff --git a/apps/demo/src/app/pages/stackblitz/classes/index.ts b/apps/demo/src/app/pages/stackblitz/classes/index.ts
new file mode 100644
index 000000000..582f1ddbd
--- /dev/null
+++ b/apps/demo/src/app/pages/stackblitz/classes/index.ts
@@ -0,0 +1,3 @@
+export * from './ts-file.parser';
+export * from './ts-file-component.parser';
+export * from './ts-file-module.parser';
diff --git a/apps/demo/src/app/pages/stackblitz/classes/ts-file-component.parser.ts b/apps/demo/src/app/pages/stackblitz/classes/ts-file-component.parser.ts
new file mode 100644
index 000000000..033288183
--- /dev/null
+++ b/apps/demo/src/app/pages/stackblitz/classes/ts-file-component.parser.ts
@@ -0,0 +1,24 @@
+import {TsFileParser} from './ts-file.parser';
+
+export class TsFileComponentParser extends TsFileParser {
+ set selector(newSelector: string) {
+ this.rawFileContent = this.rawFileContent.replace(
+ /(selector:\s['"`])(.*)(['"`])/gi,
+ `$1${newSelector}$3`,
+ );
+ }
+
+ set templateUrl(newUrl: string) {
+ this.rawFileContent = this.rawFileContent.replace(
+ /(templateUrl:\s['"`])(.*)(['"`])/gi,
+ `$1${newUrl}$3`,
+ );
+ }
+
+ set styleUrls(newUrls: string[] | readonly string[]) {
+ this.rawFileContent = this.rawFileContent.replace(
+ /(styleUrls:\s)(\[.*\])/gi,
+ `$1${JSON.stringify(newUrls)}`,
+ );
+ }
+}
diff --git a/apps/demo/src/app/pages/stackblitz/classes/ts-file-module.parser.ts b/apps/demo/src/app/pages/stackblitz/classes/ts-file-module.parser.ts
new file mode 100644
index 000000000..dbc9e870a
--- /dev/null
+++ b/apps/demo/src/app/pages/stackblitz/classes/ts-file-module.parser.ts
@@ -0,0 +1,24 @@
+import {TsFileParser} from './ts-file.parser';
+
+export class TsFileModuleParser extends TsFileParser {
+ addDeclaration(entity: string): void {
+ this.rawFileContent = this.rawFileContent.replace(
+ `declarations: [`,
+ `declarations: [${entity}, `,
+ );
+ }
+
+ addModuleImport(entity: string): void {
+ this.rawFileContent = this.rawFileContent.replace(
+ `imports: [`,
+ `imports: [\n${entity}, `,
+ );
+ }
+
+ hasDeclarationEntity(entity: string): boolean {
+ const [, declarations = ``] =
+ this.rawFileContent.match(/(?:declarations:\s\[)(.*)(?:\])/i) || [];
+
+ return declarations.includes(entity);
+ }
+}
diff --git a/apps/demo/src/app/pages/stackblitz/classes/ts-file.parser.ts b/apps/demo/src/app/pages/stackblitz/classes/ts-file.parser.ts
new file mode 100644
index 000000000..1b8f5eef0
--- /dev/null
+++ b/apps/demo/src/app/pages/stackblitz/classes/ts-file.parser.ts
@@ -0,0 +1,73 @@
+import {TuiTsParserException} from '@taiga-ui/cdk';
+
+export class TsFileParser {
+ get className(): string {
+ const [, className] = this.rawFileContent.match(/(?:export class\s)(\w*)/i) || [];
+
+ return className || ``;
+ }
+
+ set className(newClassName: string) {
+ this.rawFileContent = this.rawFileContent.replace(
+ /(export class\s)(\w*)/i,
+ `$1${newClassName}`,
+ );
+ }
+
+ get hasNgModule(): boolean {
+ return this.rawFileContent.includes(`@NgModule`);
+ }
+
+ get hasNgComponent(): boolean {
+ return this.rawFileContent.includes(`@Component`);
+ }
+
+ constructor(protected rawFileContent: string) {
+ const classesInside = rawFileContent.match(/export class/gi) || [];
+
+ if (classesInside.length > 1) {
+ throw new TuiTsParserException();
+ }
+
+ this.replaceMetaAssets();
+ }
+
+ addImport(entity: string, packageOrPath: string): void {
+ const fromName = packageOrPath.replace(`.ts`, ``);
+
+ this.rawFileContent = this.rawFileContent.includes(fromName)
+ ? this.addIntoExistingImport(entity, fromName)
+ : `import {${entity}} from '${fromName}';\n${this.rawFileContent};`;
+ }
+
+ toString(): string {
+ return this.rawFileContent;
+ }
+
+ private addIntoExistingImport(entity: string, packageName: string): string {
+ const packageImportsRegex = new RegExp(
+ `(?:import\\s?\\{\\r?\\n?)(?:(?:.*),\\r?\\n?)*?(?:.*?)\\r?\\n?} from (?:'|")${packageName}(?:'|");`,
+ `gm`,
+ );
+
+ return this.rawFileContent.replace(packageImportsRegex, parsed =>
+ parsed.includes(entity) ? parsed : parsed.replace(`{`, `{${entity}, `),
+ );
+ }
+
+ /**
+ * @description:
+ * The 'import.meta' doesn't support on Stackblitz
+ */
+ private replaceMetaAssets(): void {
+ this.rawFileContent = this.rawFileContent.replace(
+ `import {assets} from '@demo/utils';\n`,
+ ``,
+ );
+
+ this.rawFileContent = this.rawFileContent.replace(
+ `assets\``,
+ `\`https://taiga-ui.dev/assets`,
+ );
+ }
+}
diff --git a/apps/demo/src/app/pages/stackblitz/project-files/configs.md b/apps/demo/src/app/pages/stackblitz/project-files/configs.md
new file mode 100644
index 000000000..fc4e65bc7
--- /dev/null
+++ b/apps/demo/src/app/pages/stackblitz/project-files/configs.md
@@ -0,0 +1,82 @@
+# angular.json
+
+```json
+{
+ "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
+ "version": 1,
+ "newProjectRoot": "projects",
+ "projects": {
+ "demo": {
+ "root": "",
+ "sourceRoot": "",
+ "projectType": "application",
+ "prefix": "my-app",
+ "architect": {
+ "build": {
+ "builder": "@angular-devkit/build-angular:browser",
+ "options": {
+ "outputPath": "dist/demo",
+ "index": "src/index.html",
+ "main": "src/main.ts",
+ "polyfills": "src/polyfills.ts",
+ "tsConfig": "src/tsconfig.app.json",
+ "assets": ["src/favicon.ico", "src/assets"],
+ "styles": ["src/styles.less"]
+ },
+ "configurations": {
+ "production": {
+ "fileReplacements": [
+ {
+ "replace": "src/environments/environment.ts",
+ "with": "src/environments/environment.prod.ts"
+ }
+ ],
+ "optimization": true,
+ "outputHashing": "all",
+ "sourceMap": false,
+ "extractCss": true,
+ "namedChunks": false,
+ "aot": true,
+ "extractLicenses": true,
+ "vendorChunk": false,
+ "buildOptimizer": true
+ }
+ }
+ },
+ "serve": {
+ "builder": "@angular-devkit/build-angular:dev-server",
+ "options": {"browserTarget": "demo:build"}
+ }
+ }
+ }
+ },
+ "defaultProject": "demo"
+}
+```
+
+# tsconfig.json
+
+```json
+{
+ "compileOnSave": false,
+ "compilerOptions": {
+ "baseUrl": "./",
+ "outDir": "./dist/out-tsc",
+ "sourceMap": false,
+ "declaration": false,
+ "downlevelIteration": true,
+ "experimentalDecorators": true,
+ "module": "es2020",
+ "moduleResolution": "node",
+ "importHelpers": true,
+ "target": "es5",
+ "typeRoots": ["node_modules/@types"],
+ "lib": ["esnext", "dom"]
+ },
+ "angularCompilerOptions": {
+ "enableIvy": true,
+ "fullTemplateTypeCheck": true,
+ "strictInjectionParameters": true
+ }
+}
+```
diff --git a/apps/demo/src/app/pages/stackblitz/project-files/src/app/app.module.ts.md b/apps/demo/src/app/pages/stackblitz/project-files/src/app/app.module.ts.md
new file mode 100644
index 000000000..91ff8ac4a
--- /dev/null
+++ b/apps/demo/src/app/pages/stackblitz/project-files/src/app/app.module.ts.md
@@ -0,0 +1,27 @@
+```ts
+import {NgModule} from '@angular/core';
+import {BrowserModule} from '@angular/platform-browser';
+import {TuiRootModule} from '@taiga-ui/core';
+import {FormsModule, ReactiveFormsModule} from '@angular/forms';
+import {tuiSvgOptionsProvider, TUI_SANITIZER} from '@taiga-ui/core';
+import {NgDompurifySanitizer} from '@tinkoff/ng-dompurify';
+import {TuiEditorModule, TuiEditorSocketModule} from '@tinkoff/tui-editor';
+
+import {AppComponent} from './app.component';
+
+@NgModule({
+ declarations: [AppComponent],
+ bootstrap: [AppComponent],
+ imports: [BrowserModule, TuiRootModule, TuiEditorModule, TuiEditorSocketModule, FormsModule, ReactiveFormsModule],
+ providers: [
+ tuiSvgOptionsProvider({
+ path: 'https://taiga-ui.dev/assets/taiga-ui/icons',
+ }),
+ {
+ provide: TUI_SANITIZER,
+ useClass: NgDompurifySanitizer,
+ },
+ ],
+})
+export class AppModule {}
+```
diff --git a/apps/demo/src/app/pages/stackblitz/project-files/src/index.html.md b/apps/demo/src/app/pages/stackblitz/project-files/src/index.html.md
new file mode 100644
index 000000000..f9c8b9462
--- /dev/null
+++ b/apps/demo/src/app/pages/stackblitz/project-files/src/index.html.md
@@ -0,0 +1,14 @@
+```html
+
+
+ Taiga UI
+
+
+
+
+
+
+```
diff --git a/apps/demo/src/app/pages/stackblitz/project-files/src/main.ts.md b/apps/demo/src/app/pages/stackblitz/project-files/src/main.ts.md
new file mode 100644
index 000000000..6bd8ad5ed
--- /dev/null
+++ b/apps/demo/src/app/pages/stackblitz/project-files/src/main.ts.md
@@ -0,0 +1,9 @@
+```ts
+import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
+
+import {AppModule} from './app/app.module';
+
+platformBrowserDynamic()
+ .bootstrapModule(AppModule)
+ .catch(err => console.error(err));
+```
diff --git a/apps/demo/src/app/pages/stackblitz/project-files/src/polyfills.ts.md b/apps/demo/src/app/pages/stackblitz/project-files/src/polyfills.ts.md
new file mode 100644
index 000000000..8c13d51c6
--- /dev/null
+++ b/apps/demo/src/app/pages/stackblitz/project-files/src/polyfills.ts.md
@@ -0,0 +1,3 @@
+```ts
+import 'zone.js';
+```
diff --git a/apps/demo/src/app/pages/stackblitz/project-files/src/styles.less.md b/apps/demo/src/app/pages/stackblitz/project-files/src/styles.less.md
new file mode 100644
index 000000000..cf8c99ec6
--- /dev/null
+++ b/apps/demo/src/app/pages/stackblitz/project-files/src/styles.less.md
@@ -0,0 +1,13 @@
+```less
+@import '@taiga-ui/core/styles/taiga-ui-theme.less';
+@import '@taiga-ui/core/styles/taiga-ui-fonts.less';
+@import '@taiga-ui/styles/taiga-ui-global.less';
+
+/* Add application styles & imports to this file! */
+my-app {
+ display: block;
+ padding: 1.5625rem;
+ height: 100%;
+ box-sizing: border-box;
+}
+```
diff --git a/apps/demo/src/app/pages/stackblitz/stackblitz-deps.service.ts b/apps/demo/src/app/pages/stackblitz/stackblitz-deps.service.ts
new file mode 100644
index 000000000..3dbf76cc1
--- /dev/null
+++ b/apps/demo/src/app/pages/stackblitz/stackblitz-deps.service.ts
@@ -0,0 +1,59 @@
+import {Injectable} from '@angular/core';
+
+@Injectable({
+ providedIn: `root`,
+})
+export class StackblitzDepsService {
+ readonly angular = {
+ '@angular/cdk': `12.x.x`,
+ '@angular/core': `12.x.x`,
+ '@angular/common': `12.x.x`,
+ '@angular/compiler': `12.x.x`,
+ '@angular/forms': `12.x.x`,
+ '@angular/localize': `12.x.x`,
+ '@angular/platform-browser': `12.x.x`,
+ '@angular/platform-browser-dynamic': `12.x.x`,
+ '@angular/animations': `12.x.x`,
+ '@angular/router': `12.x.x`,
+ };
+
+ readonly taiga = {
+ '@taiga-ui/cdk': `3.x.x`,
+ '@taiga-ui/i18n': `3.x.x`,
+ '@taiga-ui/core': `3.x.x`,
+ '@taiga-ui/kit': `3.x.x`,
+ '@taiga-ui/icons': `3.x.x`,
+ '@taiga-ui/styles': `3.x.x`,
+ };
+
+ readonly tinkoff = {
+ '@tinkoff/tui-editor': `1.x.x`,
+ '@tinkoff/ng-polymorpheus': `4.x.x`,
+ '@tinkoff/ng-dompurify': `4.x.x`,
+ '@tinkoff/ng-event-plugins': `3.x.x`,
+ };
+
+ readonly webApis = {
+ '@ng-web-apis/common': `3.x.x`,
+ '@ng-web-apis/intersection-observer': `3.x.x`,
+ '@ng-web-apis/resize-observer': `3.x.x`,
+ '@ng-web-apis/mutation-observer': `3.x.x`,
+ };
+
+ readonly common = {
+ 'zone.js': `0.11.8`,
+ dompurify: `2.4.5`,
+ typescript: `4.3.5`,
+ rxjs: `6.6.7`,
+ };
+
+ get(): Record {
+ return {
+ ...this.angular,
+ ...this.taiga,
+ ...this.webApis,
+ ...this.common,
+ ...this.tinkoff,
+ };
+ }
+}
diff --git a/apps/demo/src/app/pages/stackblitz/stackblitz-resources-loader.ts b/apps/demo/src/app/pages/stackblitz/stackblitz-resources-loader.ts
new file mode 100644
index 000000000..358c78b26
--- /dev/null
+++ b/apps/demo/src/app/pages/stackblitz/stackblitz-resources-loader.ts
@@ -0,0 +1,42 @@
+import {tuiRawLoad, tuiTryParseMarkdownCodeBlock} from '@taiga-ui/addon-doc';
+
+interface TuiProjectFiles {
+ angularJson: string;
+ tsconfig: string;
+ mainTs: string;
+ indexHtml: string;
+ polyfills: string;
+ appModuleTs: string;
+ styles: string;
+}
+
+export abstract class AbstractTuiStackblitzResourcesLoader {
+ static async getProjectFiles(): Promise {
+ const [
+ configsContent,
+ mainTsContent,
+ indexHtmlContent,
+ polyfillsContent,
+ stylesContent,
+ appModuleTsContent,
+ ]: string[] = await Promise.all(
+ [
+ import(`./project-files/configs.md?raw`),
+ import(`./project-files/src/main.ts.md?raw`),
+ import(`./project-files/src/index.html.md?raw`),
+ import(`./project-files/src/polyfills.ts.md?raw`),
+ import(`./project-files/src/styles.less.md?raw`),
+ import(`./project-files/src/app/app.module.ts.md?raw`),
+ ].map(tuiRawLoad),
+ );
+
+ const [angularJson, tsconfig] = tuiTryParseMarkdownCodeBlock(configsContent);
+ const [mainTs] = tuiTryParseMarkdownCodeBlock(mainTsContent);
+ const [indexHtml] = tuiTryParseMarkdownCodeBlock(indexHtmlContent);
+ const [polyfills] = tuiTryParseMarkdownCodeBlock(polyfillsContent);
+ const [styles] = tuiTryParseMarkdownCodeBlock(stylesContent);
+ const [appModuleTs] = tuiTryParseMarkdownCodeBlock(appModuleTsContent);
+
+ return {angularJson, tsconfig, mainTs, indexHtml, polyfills, appModuleTs, styles};
+ }
+}
diff --git a/apps/demo/src/app/pages/stackblitz/stackblitz.service.ts b/apps/demo/src/app/pages/stackblitz/stackblitz.service.ts
new file mode 100644
index 000000000..d469641c9
--- /dev/null
+++ b/apps/demo/src/app/pages/stackblitz/stackblitz.service.ts
@@ -0,0 +1,131 @@
+import {Inject, Injectable} from '@angular/core';
+import stackblitz, {OpenOptions, Project} from '@stackblitz/sdk';
+import {TuiCodeEditor} from '@taiga-ui/addon-doc';
+import {PolymorpheusComponent} from '@tinkoff/ng-polymorpheus';
+
+import {TsFileComponentParser, TsFileModuleParser} from './classes';
+import {StackblitzDepsService} from './stackblitz-deps.service';
+import {AbstractTuiStackblitzResourcesLoader} from './stackblitz-resources-loader';
+import {StackblitzEditButtonComponent} from './starter/stackblitz-edit-button.component';
+import {
+ appPrefix,
+ getComponentsClassNames,
+ getSupportFiles,
+ getSupportModules,
+ prepareLess,
+ prepareSupportFiles,
+} from './utils';
+
+const APP_COMP_META = {
+ SELECTOR: `my-app`,
+ TEMPLATE_URL: `./app.component.html`,
+ STYLE_URLS: [`./app.component.less`],
+ CLASS_NAME: `AppComponent`,
+} as const;
+
+@Injectable()
+export class TuiStackblitzService implements TuiCodeEditor {
+ readonly name = `Stackblitz`;
+ readonly content = new PolymorpheusComponent(StackblitzEditButtonComponent);
+
+ constructor(
+ @Inject(StackblitzDepsService)
+ private readonly deps: StackblitzDepsService,
+ ) {}
+
+ async edit(
+ component: string,
+ sampleId: string,
+ content: Record,
+ ): Promise {
+ if (!content.HTML || !content.TypeScript) {
+ return;
+ }
+
+ const {appModuleTs} =
+ await AbstractTuiStackblitzResourcesLoader.getProjectFiles();
+
+ const appModule = new TsFileModuleParser(appModuleTs);
+ const appCompTs = new TsFileComponentParser(content.TypeScript);
+ const supportFilesTuples = getSupportFiles(content);
+ const supportModulesTuples = getSupportModules(supportFilesTuples);
+ const supportCompClassNames = getComponentsClassNames(supportFilesTuples);
+ const modifiedSupportFiles = prepareSupportFiles(supportFilesTuples);
+
+ supportCompClassNames.forEach(([fileName, className]) => {
+ const insideAnotherModule = supportModulesTuples.some(([_, module]) =>
+ module.hasDeclarationEntity(className),
+ );
+
+ if (insideAnotherModule) {
+ return;
+ }
+
+ appModule.addImport(className, `./${fileName}`);
+ appModule.addDeclaration(className);
+ });
+
+ appCompTs.selector = APP_COMP_META.SELECTOR;
+ appCompTs.templateUrl = APP_COMP_META.TEMPLATE_URL;
+ appCompTs.styleUrls = APP_COMP_META.STYLE_URLS;
+ appCompTs.className = APP_COMP_META.CLASS_NAME;
+
+ return stackblitz.openProject({
+ ...this.getStackblitzProjectConfig(),
+ title: `${component}-${sampleId}`,
+ description: `TUI Editor example`,
+ files: {
+ ...(await this.getBaseAngularProjectFiles()),
+ ...modifiedSupportFiles,
+ [appPrefix`app.module.ts`]: appModule.toString(),
+ [appPrefix`app.component.ts`]: appCompTs.toString(),
+ [appPrefix`app.component.html`]: `\n\n${content.HTML}\n`,
+ [appPrefix`app.component.less`]: prepareLess(content.LESS || ``),
+ },
+ });
+ }
+
+ async openStarter(
+ {title, description, files}: Pick,
+ openOptions?: OpenOptions,
+ ): Promise {
+ return stackblitz.openProject(
+ {
+ ...this.getStackblitzProjectConfig(),
+ title,
+ description,
+ files: {
+ ...(await this.getBaseAngularProjectFiles()),
+ ...files,
+ },
+ },
+ openOptions,
+ );
+ }
+
+ private async getBaseAngularProjectFiles(): Promise {
+ const {tsconfig, angularJson, mainTs, polyfills, indexHtml, styles, appModuleTs} =
+ await AbstractTuiStackblitzResourcesLoader.getProjectFiles();
+
+ return {
+ 'tsconfig.json': tsconfig,
+ 'angular.json': angularJson,
+ 'src/main.ts': mainTs,
+ 'src/polyfills.ts': polyfills,
+ 'src/index.html': indexHtml,
+ 'src/styles.less': styles,
+ [appPrefix`app.module.ts`]: appModuleTs.toString(),
+ };
+ }
+
+ private getStackblitzProjectConfig(): Pick<
+ Project,
+ 'dependencies' | 'tags' | 'template'
+ > {
+ return {
+ template: `angular-cli`,
+ dependencies: this.deps.get(),
+ tags: [`Angular`, `Taiga UI`, `Angular components`, `UI Kit`],
+ };
+ }
+}
diff --git a/apps/demo/src/app/pages/stackblitz/starter/files/app.component.html.md b/apps/demo/src/app/pages/stackblitz/starter/files/app.component.html.md
new file mode 100644
index 000000000..38321e122
--- /dev/null
+++ b/apps/demo/src/app/pages/stackblitz/starter/files/app.component.html.md
@@ -0,0 +1,19 @@
+```html
+
+
+ Start typing
+
+
+ HTML:
+
+
+ Text:
+ {{ control.value }}
+
+```
diff --git a/apps/demo/src/app/pages/stackblitz/starter/files/app.component.ts.md b/apps/demo/src/app/pages/stackblitz/starter/files/app.component.ts.md
new file mode 100644
index 000000000..21ffdd993
--- /dev/null
+++ b/apps/demo/src/app/pages/stackblitz/starter/files/app.component.ts.md
@@ -0,0 +1,23 @@
+```ts
+import {ChangeDetectionStrategy, Component} from '@angular/core';
+import {FormControl} from '@angular/forms';
+import {defaultEditorExtensions, defaultEditorTools, TUI_EDITOR_EXTENSIONS, TuiEditorTool} from '@tinkoff/tui-editor';
+
+@Component({
+ selector: `my-app`,
+ templateUrl: `./app.component.html`,
+ styleUrls: [`./app.component.less`],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ providers: [
+ {
+ provide: TUI_EDITOR_EXTENSIONS,
+ useValue: defaultEditorExtensions,
+ },
+ ],
+})
+export class AppComponent {
+ readonly control = new FormControl();
+
+ readonly tools: TuiEditorTool[] = defaultEditorTools;
+}
+```
diff --git a/apps/demo/src/app/pages/stackblitz/starter/files/index.html.md b/apps/demo/src/app/pages/stackblitz/starter/files/index.html.md
new file mode 100644
index 000000000..c8aeb6aed
--- /dev/null
+++ b/apps/demo/src/app/pages/stackblitz/starter/files/index.html.md
@@ -0,0 +1,18 @@
+```html
+
+
+
+
+
+```
diff --git a/apps/demo/src/app/pages/stackblitz/starter/files/styles.less.md b/apps/demo/src/app/pages/stackblitz/starter/files/styles.less.md
new file mode 100644
index 000000000..caf0320e9
--- /dev/null
+++ b/apps/demo/src/app/pages/stackblitz/starter/files/styles.less.md
@@ -0,0 +1,18 @@
+```less
+@import '@taiga-ui/core/styles/taiga-ui-theme.less';
+@import '@taiga-ui/core/styles/taiga-ui-fonts.less';
+@import '@taiga-ui/styles/taiga-ui-global.less';
+
+.header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ border-bottom: 1px solid var(--tui-base-04);
+ padding: 5px 20px;
+}
+
+tui-root {
+ margin: 20px;
+ font-size: 16px;
+}
+```
diff --git a/apps/demo/src/app/pages/stackblitz/starter/stackblitz-edit-button.component.ts b/apps/demo/src/app/pages/stackblitz/starter/stackblitz-edit-button.component.ts
new file mode 100644
index 000000000..0918049a7
--- /dev/null
+++ b/apps/demo/src/app/pages/stackblitz/starter/stackblitz-edit-button.component.ts
@@ -0,0 +1,18 @@
+import {ChangeDetectionStrategy, Component} from '@angular/core';
+
+@Component({
+ selector: 'stackblitz-edit-button',
+ template: `
+
+ `,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class StackblitzEditButtonComponent {}
diff --git a/apps/demo/src/app/pages/stackblitz/starter/stackblitz-starter.component.html b/apps/demo/src/app/pages/stackblitz/starter/stackblitz-starter.component.html
new file mode 100644
index 000000000..12effeef9
--- /dev/null
+++ b/apps/demo/src/app/pages/stackblitz/starter/stackblitz-starter.component.html
@@ -0,0 +1,6 @@
+
diff --git a/apps/demo/src/app/pages/stackblitz/starter/stackblitz-starter.component.ts b/apps/demo/src/app/pages/stackblitz/starter/stackblitz-starter.component.ts
new file mode 100644
index 000000000..8acec4f55
--- /dev/null
+++ b/apps/demo/src/app/pages/stackblitz/starter/stackblitz-starter.component.ts
@@ -0,0 +1,55 @@
+import {ChangeDetectionStrategy, Component, Inject, OnInit} from '@angular/core';
+import {tuiRawLoad, tuiTryParseMarkdownCodeBlock} from '@taiga-ui/addon-doc';
+
+import {TuiStackblitzService} from '../stackblitz.service';
+import {appPrefix} from '../utils';
+
+@Component({
+ selector: 'demo-stackblitz-starter',
+ templateUrl: './stackblitz-starter.component.html',
+ styleUrls: ['./stackblitz-starter.style.less'],
+ providers: [TuiStackblitzService],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class StackblitzStarterComponent implements OnInit {
+ constructor(
+ @Inject(TuiStackblitzService) private readonly stackblitz: TuiStackblitzService,
+ ) {}
+
+ async ngOnInit(): Promise {
+ await this.openStackblitz();
+ }
+
+ async openStackblitz(): Promise {
+ const [appTemplate, appComponent, indexHtml, stylesLess] = await Promise.all(
+ [
+ import('./files/app.component.html.md?raw'),
+ import('./files/app.component.ts.md?raw'),
+ import('./files/index.html.md?raw'),
+ import('./files/styles.less.md?raw'),
+ ].map(tuiRawLoad),
+ ).then(markdowns => markdowns.map(md => tuiTryParseMarkdownCodeBlock(md)[0]));
+
+ return this.stackblitz.openStarter(
+ {
+ title: 'TUI Editor Starter',
+ description:
+ 'A starter for TUI Editor\nDocumentation: https://tinkoff.github.io/tui-editor',
+ files: {
+ 'src/index.html': indexHtml,
+ 'src/styles.less': stylesLess,
+ [appPrefix`app.component.html`]: appTemplate,
+ [appPrefix`app.component.ts`]: appComponent,
+ [appPrefix`app.component.less`]:
+ // eslint-disable-next-line @typescript-eslint/quotes
+ "@import '@taiga-ui/core/styles/taiga-ui-local.less';",
+ },
+ },
+ {
+ newWindow: false,
+ openFile: appPrefix`app.component.html`,
+ hideExplorer: true,
+ },
+ );
+ }
+}
diff --git a/apps/demo/src/app/pages/stackblitz/starter/stackblitz-starter.module.ts b/apps/demo/src/app/pages/stackblitz/starter/stackblitz-starter.module.ts
new file mode 100644
index 000000000..39b4fdb86
--- /dev/null
+++ b/apps/demo/src/app/pages/stackblitz/starter/stackblitz-starter.module.ts
@@ -0,0 +1,17 @@
+import {NgModule} from '@angular/core';
+import {RouterModule} from '@angular/router';
+import {TuiButtonModule, TuiLoaderModule} from '@taiga-ui/core';
+
+import {StackblitzEditButtonComponent} from './stackblitz-edit-button.component';
+import {StackblitzStarterComponent} from './stackblitz-starter.component';
+
+@NgModule({
+ imports: [
+ TuiLoaderModule,
+ TuiButtonModule,
+ RouterModule.forChild([{path: ``, component: StackblitzStarterComponent}]),
+ ],
+ declarations: [StackblitzStarterComponent, StackblitzEditButtonComponent],
+ exports: [StackblitzStarterComponent],
+})
+export class StackblitzStarterModule {}
diff --git a/apps/demo/src/app/pages/stackblitz/starter/stackblitz-starter.style.less b/apps/demo/src/app/pages/stackblitz/starter/stackblitz-starter.style.less
new file mode 100644
index 000000000..06409ea35
--- /dev/null
+++ b/apps/demo/src/app/pages/stackblitz/starter/stackblitz-starter.style.less
@@ -0,0 +1,7 @@
+@import '@taiga-ui/core/styles/taiga-ui-local.less';
+
+.loader {
+ .fullsize(fixed);
+ background: var(--tui-base-01);
+ z-index: 1;
+}
diff --git a/apps/demo/src/app/pages/stackblitz/utils.ts b/apps/demo/src/app/pages/stackblitz/utils.ts
new file mode 100644
index 000000000..85a6d2da1
--- /dev/null
+++ b/apps/demo/src/app/pages/stackblitz/utils.ts
@@ -0,0 +1,106 @@
+import type {Project} from '@stackblitz/sdk';
+import {TUI_EXAMPLE_PRIMARY_FILE_NAME} from '@taiga-ui/addon-doc';
+
+import {TsFileComponentParser, TsFileModuleParser, TsFileParser} from './classes';
+
+export function processTs(fileContent: string): string {
+ const tsFileContent = new TsFileParser(fileContent);
+
+ if (tsFileContent.hasNgComponent) {
+ tsFileContent.addImport(`ChangeDetectionStrategy`, `@angular/core`);
+ }
+
+ return tsFileContent
+ .toString()
+ .replace(/import {encapsulation} from '.*';\n/gm, ``)
+ .replace(/import {changeDetection} from '.*';\n/gm, ``)
+ .replace(/\n +encapsulation,/gm, ``)
+ .replace(
+ /changeDetection,/gm,
+ `changeDetection: ChangeDetectionStrategy.OnPush,`,
+ );
+}
+
+export function processLess(fileContent: string): string {
+ return fileContent.replace(
+ `@import 'taiga-ui-local';`,
+ `@import '@taiga-ui/core/styles/taiga-ui-local.less';`,
+ );
+}
+
+export function isTS(fileName: string): boolean {
+ return fileName === TUI_EXAMPLE_PRIMARY_FILE_NAME.TS || fileName.endsWith(`.ts`);
+}
+
+export function isLess(fileName: string): boolean {
+ return (
+ fileName === TUI_EXAMPLE_PRIMARY_FILE_NAME.LESS || `${fileName}`.endsWith(`.less`)
+ );
+}
+
+export function isPrimaryComponentFile(fileName: string): boolean {
+ return Object.values(TUI_EXAMPLE_PRIMARY_FILE_NAME).includes(fileName);
+}
+
+export const prepareLess = (content: string): string => {
+ return content.replace(
+ /@import.+taiga-ui-local(.less)?';/g,
+ `@import '@taiga-ui/core/styles/taiga-ui-local.less';`,
+ );
+};
+
+export const appPrefix = (stringsPart: TemplateStringsArray, path: string = ``): string =>
+ `src/app/${stringsPart.join(``)}${path}`;
+
+type FileName = string;
+
+type FileContent = string;
+
+export const getSupportFiles = >(
+ files: T,
+): Array<[FileName, FileContent]> => {
+ return Object.entries(files).filter(
+ ([fileName, content]) => content && !isPrimaryComponentFile(fileName),
+ );
+};
+
+export const prepareSupportFiles = (
+ files: Array<[FileName, FileContent]>,
+): Project['files'] => {
+ const processedContent: Project['files'] = {};
+
+ for (const [fileName, fileContent] of files) {
+ const prefixedFileName = appPrefix`${fileName}`;
+
+ processedContent[prefixedFileName] = isLess(fileName)
+ ? prepareLess(fileContent)
+ : fileContent;
+ }
+
+ return processedContent;
+};
+
+export const getComponentsClassNames = (
+ files: Array<[FileName, FileContent]>,
+): Array<[FileName, FileContent]> => {
+ return files
+ .filter(
+ ([fileName, fileContent]) =>
+ isTS(fileName) && new TsFileParser(fileContent).hasNgComponent,
+ )
+ .map(([fileName, fileContent]) => [
+ fileName,
+ new TsFileComponentParser(fileContent).className,
+ ]);
+};
+
+export const getSupportModules = (
+ files: Array<[FileName, FileContent]>,
+): Array<[FileName, TsFileModuleParser]> => {
+ return files
+ .filter(([name, content]) => isTS(name) && new TsFileParser(content).hasNgModule)
+ .map(([fileName, fileContent]) => [
+ fileName,
+ new TsFileModuleParser(fileContent),
+ ]);
+};
diff --git a/tsconfig.json b/tsconfig.json
index ea2133725..74b920e9b 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -27,7 +27,7 @@
"outDir": "./dist",
"target": "es2015",
"module": "es2020",
- "lib": ["es2017", "es2018.asynciterable", "dom"],
+ "lib": ["es2021", "dom"],
"typeRoots": ["node_modules/@types", "libs/tui-editor/types"],
"types": ["ng-dev-mode"],
"skipLibCheck": true,