From 1b0619209ad36a7437757624f8eda855268e9c53 Mon Sep 17 00:00:00 2001 From: splincode Date: Wed, 7 Aug 2024 14:00:20 +0300 Subject: [PATCH] feat: support shared toolbar --- .github/workflows/announce.yml | 1 + projects/demo-playwright/utils/goto.ts | 8 +- projects/demo/src/app/app.pages.ts | 7 ++ projects/demo/src/app/app.routes.ts | 5 ++ .../toolbar/shared/examples/1/index.html | 47 +++++++++++ .../toolbar/shared/examples/1/index.less | 17 ++++ .../pages/toolbar/shared/examples/1/index.ts | 49 +++++++++++ .../src/app/pages/toolbar/shared/index.html | 12 +++ .../src/app/pages/toolbar/shared/index.ts | 24 ++++++ projects/demo/src/app/shared/routes.ts | 1 + .../edit-link/edit-link.component.ts | 12 ++- .../components/editor/editor.component.less | 5 +- .../align-content/align-content.component.ts | 60 ++++++++++---- .../align-content/align-content.template.html | 8 +- .../toolbar-tools/code/code.component.ts | 47 ++++++++--- .../details-remove.component.ts | 40 +++++++-- .../details/details.component.ts | 14 +++- .../font-size/font-size.component.ts | 24 ++++-- .../font-style/font-style.component.ts | 61 ++++++++++---- .../font-style/font-style.template.html | 8 +- .../toolbar-tools/group/group.component.ts | 50 ++++++++--- .../highlight-color.component.ts | 45 +++++++--- .../highlight-color.template.html | 2 +- .../list-configs/list-configs.component.ts | 53 ++++++++---- .../list-configs/list-configs.template.html | 6 +- .../table-cell-color.component.ts | 82 ++++++++++++------- .../table-create/table-create.component.ts | 24 ++++-- .../table-merge-cells.component.ts | 54 ++++++++---- .../table-row-column-manager.component.ts | 52 ++++++++---- .../text-color/text-color.component.ts | 39 ++++++--- .../text-color/text-color.template.html | 2 +- .../components/toolbar/toolbar.component.ts | 54 ++++++------ .../src/components/toolbar/toolbar.style.less | 22 ++++- .../components/toolbar/toolbar.template.html | 16 +++- 34 files changed, 723 insertions(+), 228 deletions(-) create mode 100644 projects/demo/src/app/pages/toolbar/shared/examples/1/index.html create mode 100644 projects/demo/src/app/pages/toolbar/shared/examples/1/index.less create mode 100644 projects/demo/src/app/pages/toolbar/shared/examples/1/index.ts create mode 100644 projects/demo/src/app/pages/toolbar/shared/index.html create mode 100644 projects/demo/src/app/pages/toolbar/shared/index.ts diff --git a/.github/workflows/announce.yml b/.github/workflows/announce.yml index accc280bd..a023c5206 100644 --- a/.github/workflows/announce.yml +++ b/.github/workflows/announce.yml @@ -18,6 +18,7 @@ jobs: echo "name=$(node -p "require('./projects/editor/package.json').name")" >> $GITHUB_OUTPUT - name: Announce to Telegram + if: ${{ !contains(steps.info.outputs.version, 'rc') }} uses: taiga-family/ci/actions/messenger/telegram/announce@v1.66.3 with: chatId: ${{ secrets.TAIGA_TELEGRAM_CHAT_ID }} diff --git a/projects/demo-playwright/utils/goto.ts b/projects/demo-playwright/utils/goto.ts index b1f345beb..b971dbefc 100644 --- a/projects/demo-playwright/utils/goto.ts +++ b/projects/demo-playwright/utils/goto.ts @@ -43,11 +43,15 @@ export async function tuiGoto( await expect(page.locator('app')).toHaveClass(/_loaded/, {timeout: 15_000}); if (hideHeader) { - await page.locator('[tuidocheader]').evaluate((el) => el.remove()); + (await page.locator('[tuidocheader]').all()).forEach((e) => + e.evaluate((el) => el.remove()), + ); } if (hideNavigation) { - await page.locator('tui-doc-navigation').evaluate((el) => el.remove()); + (await page.locator('tui-doc-navigation').all()).forEach((e) => + e.evaluate((el) => el.remove()), + ); } await page.waitForTimeout(1000); diff --git a/projects/demo/src/app/app.pages.ts b/projects/demo/src/app/app.pages.ts index fa8bcd83b..101a51e01 100644 --- a/projects/demo/src/app/app.pages.ts +++ b/projects/demo/src/app/app.pages.ts @@ -223,6 +223,13 @@ export const DEMO_PAGES: TuiDocRoutePages = [ 'editor, toolbar, floating, wysiwyg, редактор, текст, подсветка, html, rich, text', route: `/${TuiDemoPath.ToolbarFloating}`, }, + { + section: 'Documentation', + title: 'Shared', + keywords: + 'editor, toolbar, shared, multiple, один на всех, html, rich, text', + route: `/${TuiDemoPath.ToolbarShared}`, + }, ], }, ]; diff --git a/projects/demo/src/app/app.routes.ts b/projects/demo/src/app/app.routes.ts index 6b7bd1bb1..46c7bfafe 100644 --- a/projects/demo/src/app/app.routes.ts +++ b/projects/demo/src/app/app.routes.ts @@ -144,6 +144,11 @@ export const routes: Routes = [ loadComponent: async () => import('./pages/toolbar/floating'), title: 'Editor — Toolbar', }), + route({ + path: TuiDemoPath.ToolbarShared, + loadComponent: async () => import('./pages/toolbar/shared'), + title: 'Editor — Toolbar', + }), { path: '**', redirectTo: TuiDemoPath.StarterKit, diff --git a/projects/demo/src/app/pages/toolbar/shared/examples/1/index.html b/projects/demo/src/app/pages/toolbar/shared/examples/1/index.html new file mode 100644 index 000000000..8ab59bfd5 --- /dev/null +++ b/projects/demo/src/app/pages/toolbar/shared/examples/1/index.html @@ -0,0 +1,47 @@ +
+ + + + Header + + + + Main + + + + Footer + + + + + + Output + + +
{{group.value|json}}
+
+
+
diff --git a/projects/demo/src/app/pages/toolbar/shared/examples/1/index.less b/projects/demo/src/app/pages/toolbar/shared/examples/1/index.less new file mode 100644 index 000000000..5f63bb747 --- /dev/null +++ b/projects/demo/src/app/pages/toolbar/shared/examples/1/index.less @@ -0,0 +1,17 @@ +:host { + display: flex; + flex-direction: column; + gap: 0; +} + +tui-toolbar.toolbar { + position: sticky; + top: 0; + z-index: 1; + background: var(--tui-base-01); +} + +tui-editor.editor { + min-height: auto; + border-radius: 0; +} diff --git a/projects/demo/src/app/pages/toolbar/shared/examples/1/index.ts b/projects/demo/src/app/pages/toolbar/shared/examples/1/index.ts new file mode 100644 index 000000000..fc09ab82b --- /dev/null +++ b/projects/demo/src/app/pages/toolbar/shared/examples/1/index.ts @@ -0,0 +1,49 @@ +import {JsonPipe} from '@angular/common'; +import { + ChangeDetectionStrategy, + Component, + Injector, + ViewEncapsulation, +} from '@angular/core'; +import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms'; +import type {AbstractTuiEditor} from '@taiga-ui/editor'; +import { + TUI_EDITOR_DEFAULT_EXTENSIONS, + TUI_EDITOR_DEFAULT_TOOLS, + TUI_EDITOR_EXTENSIONS, + TuiEditor, + TuiToolbar, +} from '@taiga-ui/editor'; +import {TuiAccordion} from '@taiga-ui/kit'; + +@Component({ + standalone: true, + imports: [JsonPipe, ReactiveFormsModule, TuiAccordion, TuiEditor, TuiToolbar], + templateUrl: './index.html', + styleUrls: ['./index.less'], + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: TUI_EDITOR_EXTENSIONS, + deps: [Injector], + useFactory: (injector: Injector) => [ + ...TUI_EDITOR_DEFAULT_EXTENSIONS, + import('@taiga-ui/editor').then(({tuiCreateImageEditorExtension}) => + tuiCreateImageEditorExtension({injector}), + ), + ], + }, + ], +}) +export default class Example { + public editorRef: AbstractTuiEditor | null = null; + + public readonly builtInTools = TUI_EDITOR_DEFAULT_TOOLS; + + public readonly group = new FormGroup({ + header: new FormControl(''), + main: new FormControl(''), + footer: new FormControl(''), + }); +} diff --git a/projects/demo/src/app/pages/toolbar/shared/index.html b/projects/demo/src/app/pages/toolbar/shared/index.html new file mode 100644 index 000000000..ad334348d --- /dev/null +++ b/projects/demo/src/app/pages/toolbar/shared/index.html @@ -0,0 +1,12 @@ + + + diff --git a/projects/demo/src/app/pages/toolbar/shared/index.ts b/projects/demo/src/app/pages/toolbar/shared/index.ts new file mode 100644 index 000000000..936175542 --- /dev/null +++ b/projects/demo/src/app/pages/toolbar/shared/index.ts @@ -0,0 +1,24 @@ +import {ChangeDetectionStrategy, Component} from '@angular/core'; +import {TuiAddonDoc} from '@taiga-ui/addon-doc'; +import {TUI_EDITOR_DEFAULT_EXTENSIONS, TUI_EDITOR_EXTENSIONS} from '@taiga-ui/editor'; + +@Component({ + standalone: true, + imports: [TuiAddonDoc], + templateUrl: './index.html', + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [ + { + provide: TUI_EDITOR_EXTENSIONS, + useValue: TUI_EDITOR_DEFAULT_EXTENSIONS, + }, + ], +}) +export default class Example { + protected readonly component1 = import('./examples/1'); + protected readonly example1 = { + TypeScript: import('./examples/1/index.ts?raw'), + HTML: import('./examples/1/index.html?raw'), + LESS: import('./examples/1/index.less?raw'), + }; +} diff --git a/projects/demo/src/app/shared/routes.ts b/projects/demo/src/app/shared/routes.ts index a742840bf..9f3a37774 100644 --- a/projects/demo/src/app/shared/routes.ts +++ b/projects/demo/src/app/shared/routes.ts @@ -26,4 +26,5 @@ export const TuiDemoPath = { UploadFiles: 'upload-files', ToolbarBottom: 'toolbar/bottom', ToolbarFloating: 'toolbar/floating', + ToolbarShared: 'toolbar/shared', } as const; diff --git a/projects/editor/src/components/edit-link/edit-link.component.ts b/projects/editor/src/components/edit-link/edit-link.component.ts index a72343b6b..c356ad463 100644 --- a/projects/editor/src/components/edit-link/edit-link.component.ts +++ b/projects/editor/src/components/edit-link/edit-link.component.ts @@ -14,6 +14,7 @@ import {TuiAutoFocus, tuiIsElement} from '@taiga-ui/cdk'; import {TuiButton, TuiLink, TuiScrollbar} from '@taiga-ui/core'; import {TuiInputInline} from '@taiga-ui/kit'; +import type {AbstractTuiEditor} from '../../abstract/editor-adapter.abstract'; import type { TuiEditorLinkPrefix, TuiEditorLinkProtocol, @@ -52,7 +53,7 @@ import {tuiEditLinkParseUrl} from './utils/edit-link-parse-url'; export class TuiEditLink { private readonly doc: Document | null = inject(WA_WINDOW)?.document ?? null; private isOnlyAnchorMode: boolean = this.detectAnchorMode(); - private readonly editor = inject(TuiTiptapEditorService); + private readonly injectionEditor = inject(TuiTiptapEditorService, {optional: true}); protected readonly options = inject(TUI_EDITOR_OPTIONS); protected url: string = this.getHrefOrAnchorId(); @@ -61,6 +62,9 @@ export class TuiEditLink { protected anchorIds = this.getAllAnchorsIds(); protected readonly texts$ = inject(TUI_EDITOR_LINK_TEXTS); + @Input('editor') + public inputEditor: AbstractTuiEditor | null = null; + @Output() public readonly addLink = new EventEmitter(); @@ -77,6 +81,10 @@ export class TuiEditLink { return this.isOnlyAnchorMode; } + protected get editor(): AbstractTuiEditor | null { + return this.injectionEditor ?? this.inputEditor; + } + protected get defaultProtocol(): TuiEditorLinkProtocol { return this.options.linkOptions?.protocol ?? TUI_EDITOR_LINK_HTTPS_PREFIX; } @@ -232,7 +240,7 @@ export class TuiEditLink { private getAllAnchorsIds(): string[] { const nodes: Element[] = Array.from( this.editor - .getOriginTiptapEditor() + ?.getOriginTiptapEditor() ?.view.dom.querySelectorAll('[data-type="jump-anchor"]') ?? [], ); diff --git a/projects/editor/src/components/editor/editor.component.less b/projects/editor/src/components/editor/editor.component.less index 3d2909673..012440119 100644 --- a/projects/editor/src/components/editor/editor.component.less +++ b/projects/editor/src/components/editor/editor.component.less @@ -6,6 +6,7 @@ isolation: isolate; font: var(--tui-font-text-m); border-radius: var(--tui-radius-m); + border: 0.0625rem solid var(--tui-border-normal); max-height: inherit; min-height: 10rem; box-sizing: border-box; @@ -20,10 +21,6 @@ overflow: clip; flex-direction: column; border-radius: inherit; - - &:not([data-focus='true']) { - outline: 0.0625rem solid var(--tui-border-normal); - } } .t-editor-placeholder { diff --git a/projects/editor/src/components/toolbar-tools/align-content/align-content.component.ts b/projects/editor/src/components/toolbar-tools/align-content/align-content.component.ts index 4b29f5545..fa908a9f4 100644 --- a/projects/editor/src/components/toolbar-tools/align-content/align-content.component.ts +++ b/projects/editor/src/components/toolbar-tools/align-content/align-content.component.ts @@ -1,9 +1,12 @@ import {AsyncPipe, NgIf} from '@angular/common'; -import {ChangeDetectionStrategy, Component, inject} from '@angular/core'; +import type {OnInit} from '@angular/core'; +import {ChangeDetectionStrategy, Component, inject, Input} from '@angular/core'; import {TuiLet} from '@taiga-ui/cdk'; import {TuiButton, TuiDropdown, TuiHint} from '@taiga-ui/core'; -import {combineLatest, map} from 'rxjs'; +import type {Observable} from 'rxjs'; +import {combineLatest, map, of} from 'rxjs'; +import type {AbstractTuiEditor} from '../../../abstract/editor-adapter.abstract'; import {TuiTiptapEditorService} from '../../../directives/tiptap-editor/tiptap-editor.service'; import {TUI_EDITOR_OPTIONS} from '../../../tokens/editor-options'; import {TUI_EDITOR_TOOLBAR_TEXTS} from '../../../tokens/i18n'; @@ -16,22 +19,45 @@ import {TUI_EDITOR_TOOLBAR_TEXTS} from '../../../tokens/i18n'; styleUrls: ['../../../../styles/tools-common.less'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class TuiAlignContent { +export class TuiAlignContent implements OnInit { + private localEditor: AbstractTuiEditor | null = null; protected readonly options = inject(TUI_EDITOR_OPTIONS); - protected readonly editor = inject(TuiTiptapEditorService); + protected readonly injectionEditor = inject(TuiTiptapEditorService, {optional: true}); protected readonly texts$ = inject(TUI_EDITOR_TOOLBAR_TEXTS); + protected alignState$: Observable<{ + left: boolean; + right: boolean; + center: boolean; + justify: boolean; + }> | null = null; - protected readonly alignState$ = combineLatest([ - this.editor.isActive$({textAlign: 'left'}), - this.editor.isActive$({textAlign: 'right'}), - this.editor.isActive$({textAlign: 'center'}), - this.editor.isActive$({textAlign: 'justify'}), - ]).pipe( - map(([left, right, center, justify]) => ({ - left, - right, - center, - justify, - })), - ); + @Input('editor') + public set inputEditor(value: AbstractTuiEditor | null) { + this.localEditor = value; + this.initStream(); + } + + public ngOnInit(): void { + this.initStream(); + } + + protected get editor(): AbstractTuiEditor | null { + return this.injectionEditor ?? this.localEditor; + } + + private initStream(): void { + this.alignState$ = combineLatest([ + this.editor?.isActive$({textAlign: 'left'}) ?? of(false), + this.editor?.isActive$({textAlign: 'right'}) ?? of(false), + this.editor?.isActive$({textAlign: 'center'}) ?? of(false), + this.editor?.isActive$({textAlign: 'justify'}) ?? of(false), + ]).pipe( + map(([left, right, center, justify]) => ({ + left, + right, + center, + justify, + })), + ); + } } diff --git a/projects/editor/src/components/toolbar-tools/align-content/align-content.template.html b/projects/editor/src/components/toolbar-tools/align-content/align-content.template.html index 63f7bb780..809fb730e 100644 --- a/projects/editor/src/components/toolbar-tools/align-content/align-content.template.html +++ b/projects/editor/src/components/toolbar-tools/align-content/align-content.template.html @@ -26,7 +26,7 @@ class="t-option t-option_margin" [iconStart]="options.icons.textAlignLeft" [tuiHint]="texts.justifyLeft" - (click)="editor.onAlign('left')" + (click)="editor?.onAlign('left')" > diff --git a/projects/editor/src/components/toolbar-tools/code/code.component.ts b/projects/editor/src/components/toolbar-tools/code/code.component.ts index dcaa70628..452b1812a 100644 --- a/projects/editor/src/components/toolbar-tools/code/code.component.ts +++ b/projects/editor/src/components/toolbar-tools/code/code.component.ts @@ -1,8 +1,11 @@ import {AsyncPipe, NgForOf} from '@angular/common'; -import {ChangeDetectionStrategy, Component, inject} from '@angular/core'; +import type {OnInit} from '@angular/core'; +import {ChangeDetectionStrategy, Component, inject, Input} from '@angular/core'; import {TuiButton, TuiDataList, TuiDropdown, TuiHint} from '@taiga-ui/core'; +import type {Observable} from 'rxjs'; import {distinctUntilChanged, map} from 'rxjs'; +import type {AbstractTuiEditor} from '../../../abstract/editor-adapter.abstract'; import {TuiTiptapEditorService} from '../../../directives/tiptap-editor/tiptap-editor.service'; import {TUI_EDITOR_OPTIONS} from '../../../tokens/editor-options'; import {TUI_EDITOR_CODE_OPTIONS, TUI_EDITOR_TOOLBAR_TEXTS} from '../../../tokens/i18n'; @@ -14,23 +17,47 @@ import {TUI_EDITOR_CODE_OPTIONS, TUI_EDITOR_TOOLBAR_TEXTS} from '../../../tokens templateUrl: './code.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class TuiCode { +export class TuiCode implements OnInit { + private localEditor: AbstractTuiEditor | null = null; protected readonly options = inject(TUI_EDITOR_OPTIONS); - protected readonly editor = inject(TuiTiptapEditorService); + protected readonly injectionEditor = inject(TuiTiptapEditorService, {optional: true}); protected readonly texts$ = inject(TUI_EDITOR_TOOLBAR_TEXTS); protected readonly codeOptionsTexts$ = inject(TUI_EDITOR_CODE_OPTIONS); protected readonly hintText$ = this.texts$.pipe(map((texts) => texts.code)); + protected insideCode$: Observable | null = null; - protected readonly insideCode$ = this.editor.stateChange$.pipe( - map(() => this.editor.isActive('code') || this.editor.isActive('codeBlock')), - distinctUntilChanged(), - ); + @Input('editor') + public set inputEditor(value: AbstractTuiEditor | null) { + this.localEditor = value; + this.initStream(); + } + + public ngOnInit(): void { + this.initStream(); + } - protected onCode(isCodeBlock: boolean): void { + public onCode(isCodeBlock: boolean): void { if (isCodeBlock) { - this.editor.toggleCodeBlock(); + this.editor?.toggleCodeBlock(); } else { - this.editor.toggleCode(); + this.editor?.toggleCode(); } } + + protected get editor(): AbstractTuiEditor | null { + return this.injectionEditor ?? this.localEditor; + } + + private initStream(): void { + this.insideCode$ = + this.editor?.stateChange$.pipe( + map( + () => + (this.editor?.isActive('code') || + this.editor?.isActive('codeBlock')) ?? + false, + ), + distinctUntilChanged(), + ) ?? null; + } } diff --git a/projects/editor/src/components/toolbar-tools/details/details-remove/details-remove.component.ts b/projects/editor/src/components/toolbar-tools/details/details-remove/details-remove.component.ts index 6c99d9ffd..166531dbb 100644 --- a/projects/editor/src/components/toolbar-tools/details/details-remove/details-remove.component.ts +++ b/projects/editor/src/components/toolbar-tools/details/details-remove/details-remove.component.ts @@ -1,8 +1,11 @@ import {AsyncPipe, NgIf} from '@angular/common'; -import {ChangeDetectionStrategy, Component, inject} from '@angular/core'; +import type {OnInit} from '@angular/core'; +import {ChangeDetectionStrategy, Component, inject, Input} from '@angular/core'; import {TuiButton, TuiHint} from '@taiga-ui/core'; +import type {Observable} from 'rxjs'; import {distinctUntilChanged, map} from 'rxjs'; +import type {AbstractTuiEditor} from '../../../../abstract/editor-adapter.abstract'; import {TuiTiptapEditorService} from '../../../../directives/tiptap-editor/tiptap-editor.service'; import {TUI_EDITOR_OPTIONS} from '../../../../tokens/editor-options'; import {TUI_EDITOR_TOOLBAR_TEXTS} from '../../../../tokens/i18n'; @@ -14,17 +17,36 @@ import {TUI_EDITOR_TOOLBAR_TEXTS} from '../../../../tokens/i18n'; templateUrl: './details-remove.template.html', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class TuiDetailsRemove { - protected readonly editor = inject(TuiTiptapEditorService); +export class TuiDetailsRemove implements OnInit { + private localEditor: AbstractTuiEditor | null = null; + protected readonly injectionEditor = inject(TuiTiptapEditorService, {optional: true}); protected readonly texts$ = inject(TUI_EDITOR_TOOLBAR_TEXTS); protected readonly options = inject(TUI_EDITOR_OPTIONS); + protected disabled$: Observable | null = null; - protected readonly disabled$ = this.editor.stateChange$.pipe( - map(() => !this.editor.isActive('details')), - distinctUntilChanged(), - ); + @Input('editor') + public set inputEditor(value: AbstractTuiEditor | null) { + this.localEditor = value; + this.initStream(); + } + + public ngOnInit(): void { + this.initStream(); + } + + public removeDetails(): void { + this.editor?.removeDetails(); + } + + protected get editor(): AbstractTuiEditor | null { + return this.injectionEditor ?? this.localEditor; + } - protected removeDetails(): void { - this.editor.removeDetails(); + private initStream(): void { + this.disabled$ = + this.editor?.stateChange$.pipe( + map(() => !this.editor?.isActive('details') ?? false), + distinctUntilChanged(), + ) ?? null; } } diff --git a/projects/editor/src/components/toolbar-tools/details/details.component.ts b/projects/editor/src/components/toolbar-tools/details/details.component.ts index ea0eea6e7..fa4af25de 100644 --- a/projects/editor/src/components/toolbar-tools/details/details.component.ts +++ b/projects/editor/src/components/toolbar-tools/details/details.component.ts @@ -1,7 +1,8 @@ import {AsyncPipe, NgIf} from '@angular/common'; -import {ChangeDetectionStrategy, Component, inject} from '@angular/core'; +import {ChangeDetectionStrategy, Component, inject, Input} from '@angular/core'; import {TuiButton, TuiHint} from '@taiga-ui/core'; +import type {AbstractTuiEditor} from '../../../abstract/editor-adapter.abstract'; import {TuiTiptapEditorService} from '../../../directives/tiptap-editor/tiptap-editor.service'; import {TUI_EDITOR_OPTIONS} from '../../../tokens/editor-options'; import {TUI_EDITOR_TOOLBAR_TEXTS} from '../../../tokens/i18n'; @@ -14,11 +15,18 @@ import {TUI_EDITOR_TOOLBAR_TEXTS} from '../../../tokens/i18n'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class TuiDetails { - protected readonly editor = inject(TuiTiptapEditorService); + protected readonly injectionEditor = inject(TuiTiptapEditorService, {optional: true}); protected readonly texts$ = inject(TUI_EDITOR_TOOLBAR_TEXTS); protected readonly options = inject(TUI_EDITOR_OPTIONS); + @Input('editor') + public inputEditor: AbstractTuiEditor | null = null; + + protected get editor(): AbstractTuiEditor | null { + return this.injectionEditor ?? this.inputEditor; + } + protected setDetails(): void { - this.editor.setDetails(); + this.editor?.setDetails(); } } diff --git a/projects/editor/src/components/toolbar-tools/font-size/font-size.component.ts b/projects/editor/src/components/toolbar-tools/font-size/font-size.component.ts index 2e98d6cc9..0013b59f8 100644 --- a/projects/editor/src/components/toolbar-tools/font-size/font-size.component.ts +++ b/projects/editor/src/components/toolbar-tools/font-size/font-size.component.ts @@ -1,10 +1,11 @@ import {AsyncPipe, LowerCasePipe, NgClass, NgForOf, NgStyle} from '@angular/common'; -import {ChangeDetectionStrategy, Component, inject} from '@angular/core'; +import {ChangeDetectionStrategy, Component, inject, Input} from '@angular/core'; import {tuiPx} from '@taiga-ui/cdk'; import {TuiButton, TuiDataList, TuiDropdown, TuiHint} from '@taiga-ui/core'; import type {Observable} from 'rxjs'; import {map} from 'rxjs'; +import type {AbstractTuiEditor} from '../../../abstract/editor-adapter.abstract'; import {EDITOR_BLANK_COLOR} from '../../../constants/default-editor-colors'; import {TuiTiptapEditorService} from '../../../directives/tiptap-editor/tiptap-editor.service'; import type {TuiEditorFontOption} from '../../../interfaces/editor-font-option'; @@ -31,7 +32,7 @@ import {TUI_EDITOR_FONT_OPTIONS, TUI_EDITOR_TOOLBAR_TEXTS} from '../../../tokens export class TuiFontSize { private readonly fontOptionsTexts$ = inject(TUI_EDITOR_FONT_OPTIONS); protected readonly options = inject(TUI_EDITOR_OPTIONS); - protected readonly editor = inject(TuiTiptapEditorService); + protected readonly injectionEditor = inject(TuiTiptapEditorService, {optional: true}); protected readonly texts$ = inject(TUI_EDITOR_TOOLBAR_TEXTS); protected readonly fontsOptions$: Observable< ReadonlyArray> @@ -39,24 +40,31 @@ export class TuiFontSize { protected readonly fontText$ = this.texts$.pipe(map((texts) => texts.font)); + @Input('editor') + public inputEditor: AbstractTuiEditor | null = null; + + protected get editor(): AbstractTuiEditor | null { + return this.injectionEditor ?? this.inputEditor; + } + protected setFontOption({headingLevel, px}: Partial): void { - const color = this.editor.getFontColor(); + const color = this.editor?.getFontColor() || EDITOR_BLANK_COLOR; this.clearPreviousTextStyles(); if (headingLevel) { - this.editor.setHeading(headingLevel); + this.editor?.setHeading(headingLevel); } else { - this.editor.setParagraph({fontSize: tuiPx(px || 0)}); + this.editor?.setParagraph({fontSize: tuiPx(px || 0)}); } if (color !== EDITOR_BLANK_COLOR) { - this.editor.setFontColor(color); + this.editor?.setFontColor(color); } } private clearPreviousTextStyles(): void { - this.editor.removeEmptyTextStyle(); - this.editor.toggleMark('textStyle'); + this.editor?.removeEmptyTextStyle(); + this.editor?.toggleMark('textStyle'); } } diff --git a/projects/editor/src/components/toolbar-tools/font-style/font-style.component.ts b/projects/editor/src/components/toolbar-tools/font-style/font-style.component.ts index b2d82f6bd..63aac953b 100644 --- a/projects/editor/src/components/toolbar-tools/font-style/font-style.component.ts +++ b/projects/editor/src/components/toolbar-tools/font-style/font-style.component.ts @@ -1,8 +1,11 @@ import {AsyncPipe, NgIf} from '@angular/common'; +import type {OnInit} from '@angular/core'; import {ChangeDetectionStrategy, Component, inject, Input} from '@angular/core'; import {TuiButton, TuiDropdown, TuiHint} from '@taiga-ui/core'; -import {combineLatest, map} from 'rxjs'; +import type {Observable} from 'rxjs'; +import {combineLatest, map, of} from 'rxjs'; +import type {AbstractTuiEditor} from '../../../abstract/editor-adapter.abstract'; import {TUI_EDITOR_DEFAULT_TOOLS} from '../../../constants/default-editor-tools'; import {TuiTiptapEditorService} from '../../../directives/tiptap-editor/tiptap-editor.service'; import {TUI_EDITOR_OPTIONS} from '../../../tokens/editor-options'; @@ -18,27 +21,25 @@ import {TuiEditorTool} from '../../../types/editor-tool'; styleUrls: ['../../../../styles/tools-common.less'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class TuiFontStyle { +export class TuiFontStyle implements OnInit { private toolsSet = new Set(TUI_EDITOR_DEFAULT_TOOLS); - + private localEditor: AbstractTuiEditor | null = null; protected readonly editorTool: typeof TuiEditorTool = TuiEditorTool; protected readonly options = inject(TUI_EDITOR_OPTIONS); - protected readonly editor = inject(TuiTiptapEditorService); + protected readonly injectionEditor = inject(TuiTiptapEditorService, {optional: true}); protected readonly texts$ = inject(TUI_EDITOR_TOOLBAR_TEXTS); + protected fontStyleState$: Observable<{ + bold: boolean; + italic: boolean; + underline: boolean; + strike: boolean; + }> | null = null; - protected readonly fontStyleState$ = combineLatest([ - this.editor.isActive$('bold'), - this.editor.isActive$('italic'), - this.editor.isActive$('underline'), - this.editor.isActive$('strike'), - ]).pipe( - map(([bold, italic, underline, strike]) => ({ - bold, - italic, - underline, - strike, - })), - ); + @Input('editor') + public set inputEditor(value: AbstractTuiEditor | null) { + this.localEditor = value; + this.initStream(); + } @Input() public set enabledTools( @@ -47,7 +48,31 @@ export class TuiFontStyle { this.toolsSet = new Set(value); } - protected isEnabled(tool: TuiEditorToolType): boolean { + public get editor(): AbstractTuiEditor | null { + return this.injectionEditor ?? this.localEditor; + } + + public ngOnInit(): void { + this.initStream(); + } + + public isEnabled(tool: TuiEditorToolType): boolean { return this.toolsSet.has(tool); } + + private initStream(): void { + this.fontStyleState$ = combineLatest([ + this.editor?.isActive$('bold') ?? of(false), + this.editor?.isActive$('italic') ?? of(false), + this.editor?.isActive$('underline') ?? of(false), + this.editor?.isActive$('strike') ?? of(false), + ]).pipe( + map(([bold, italic, underline, strike]) => ({ + bold, + italic, + underline, + strike, + })), + ); + } } diff --git a/projects/editor/src/components/toolbar-tools/font-style/font-style.template.html b/projects/editor/src/components/toolbar-tools/font-style/font-style.template.html index e45e21aff..d4215b0f3 100644 --- a/projects/editor/src/components/toolbar-tools/font-style/font-style.template.html +++ b/projects/editor/src/components/toolbar-tools/font-style/font-style.template.html @@ -27,7 +27,7 @@ class="t-option t-option_margin" [iconStart]="options.icons.fontStyleBold" [tuiHint]="texts.bold" - (click)="editor.toggleBold()" + (click)="editor?.toggleBold()" > diff --git a/projects/editor/src/components/toolbar-tools/group/group.component.ts b/projects/editor/src/components/toolbar-tools/group/group.component.ts index 996d8ba8d..d44215422 100644 --- a/projects/editor/src/components/toolbar-tools/group/group.component.ts +++ b/projects/editor/src/components/toolbar-tools/group/group.component.ts @@ -1,8 +1,11 @@ import {AsyncPipe} from '@angular/common'; -import {ChangeDetectionStrategy, Component, inject} from '@angular/core'; +import type {OnInit} from '@angular/core'; +import {ChangeDetectionStrategy, Component, inject, Input} from '@angular/core'; import {TuiButton, TuiHint} from '@taiga-ui/core'; +import type {Observable} from 'rxjs'; import {distinctUntilChanged, map} from 'rxjs'; +import type {AbstractTuiEditor} from '../../../abstract/editor-adapter.abstract'; import {TuiTiptapEditorService} from '../../../directives/tiptap-editor/tiptap-editor.service'; import {TUI_EDITOR_OPTIONS} from '../../../tokens/editor-options'; import {TUI_EDITOR_TOOLBAR_TEXTS} from '../../../tokens/i18n'; @@ -15,8 +18,9 @@ import {TUI_EDITOR_TOOLBAR_TEXTS} from '../../../tokens/i18n'; styleUrls: ['../../../../styles/tools-common.less'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class TuiEditorGroupTool { - protected readonly editor = inject(TuiTiptapEditorService); +export class TuiEditorGroupTool implements OnInit { + private localEditor: AbstractTuiEditor | null = null; + protected readonly injectionEditor = inject(TuiTiptapEditorService, {optional: true}); protected readonly texts$ = inject(TUI_EDITOR_TOOLBAR_TEXTS); protected readonly options = inject(TUI_EDITOR_OPTIONS); @@ -28,16 +32,40 @@ export class TuiEditorGroupTool { map((texts) => texts.removeGroup), ); - protected readonly disabled$ = this.editor.stateChange$.pipe( - map(() => !this.editor.isActive('group')), - distinctUntilChanged(), - ); + protected disabled$: Observable | null = null; + + @Input('editor') + public set inputEditor(value: AbstractTuiEditor | null) { + this.localEditor = value; + this.initStream(); + } + + public ngOnInit(): void { + this.initStream(); + } - protected addGroup(): void { - this.editor.setGroup(); + public addGroup(): void { + this.editor?.setGroup(); } - protected removeGroup(): void { - this.editor.removeGroup(); + public removeGroup(): void { + this.editor?.removeGroup(); + } + + protected get editor(): AbstractTuiEditor | null { + return this.injectionEditor ?? this.localEditor; + } + + private initStream(): void { + this.disabled$ = + this.editor?.stateChange$.pipe( + map(() => !this.editor?.isActive('group')), + distinctUntilChanged(), + ) ?? null; + + this.editor?.stateChange$.pipe( + map(() => !this.editor?.isActive('group')), + distinctUntilChanged(), + ); } } diff --git a/projects/editor/src/components/toolbar-tools/highlight-color/highlight-color.component.ts b/projects/editor/src/components/toolbar-tools/highlight-color/highlight-color.component.ts index 8061a39b0..121256004 100644 --- a/projects/editor/src/components/toolbar-tools/highlight-color/highlight-color.component.ts +++ b/projects/editor/src/components/toolbar-tools/highlight-color/highlight-color.component.ts @@ -1,10 +1,13 @@ import {AsyncPipe, NgIf} from '@angular/common'; +import type {OnInit} from '@angular/core'; import {ChangeDetectionStrategy, Component, inject, Input} from '@angular/core'; import {TuiActiveZone, TuiLet} from '@taiga-ui/cdk'; import {TuiButton, TuiDropdown, TuiHint} from '@taiga-ui/core'; import {TuiPaletteModule} from '@taiga-ui/legacy'; +import type {Observable} from 'rxjs'; import {distinctUntilChanged, map} from 'rxjs'; +import type {AbstractTuiEditor} from '../../../abstract/editor-adapter.abstract'; import {TuiTiptapEditorService} from '../../../directives/tiptap-editor/tiptap-editor.service'; import type {TuiEditorOptions} from '../../../tokens/editor-options'; import {TUI_EDITOR_OPTIONS} from '../../../tokens/editor-options'; @@ -27,26 +30,48 @@ import {TUI_EDITOR_TOOLBAR_TEXTS} from '../../../tokens/i18n'; styleUrls: ['../../../../styles/tools-common.less'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class TuiHighlightColor { +export class TuiHighlightColor implements OnInit { + private localEditor: AbstractTuiEditor | null = null; private readonly options = inject(TUI_EDITOR_OPTIONS); - - protected readonly editor = inject(TuiTiptapEditorService); + protected readonly injectionEditor = inject(TuiTiptapEditorService, {optional: true}); protected readonly texts$ = inject(TUI_EDITOR_TOOLBAR_TEXTS); - protected readonly backgroundColor$ = this.editor.stateChange$.pipe( - map(() => this.editor.getBackgroundColor() || this.options.blankColor), - distinctUntilChanged(), - ); - + protected backgroundColor$: Observable | null = null; protected readonly backColorText$ = this.texts$.pipe(map((texts) => texts.backColor)); @Input() public colors: ReadonlyMap = this.options.colors; + @Input('editor') + public set inputEditor(value: AbstractTuiEditor | null) { + this.localEditor = value; + this.initStream(); + } + + public ngOnInit(): void { + this.initStream(); + } + + public isBlankColor(color: string): boolean { + return color === this.options.blankColor; + } + + protected get editor(): AbstractTuiEditor | null { + return this.injectionEditor ?? this.localEditor; + } + protected get icons(): TuiEditorOptions['icons'] { return this.options.icons; } - protected isBlankColor(color: string): boolean { - return color === this.options.blankColor; + private initStream(): void { + this.backgroundColor$ = + this.editor?.stateChange$.pipe( + map( + () => + (this.editor?.getBackgroundColor() || this.options.blankColor) ?? + false, + ), + distinctUntilChanged(), + ) ?? null; } } diff --git a/projects/editor/src/components/toolbar-tools/highlight-color/highlight-color.template.html b/projects/editor/src/components/toolbar-tools/highlight-color/highlight-color.template.html index 60ba0fb66..b7cab9d95 100644 --- a/projects/editor/src/components/toolbar-tools/highlight-color/highlight-color.template.html +++ b/projects/editor/src/components/toolbar-tools/highlight-color/highlight-color.template.html @@ -25,7 +25,7 @@ diff --git a/projects/editor/src/components/toolbar-tools/list-configs/list-configs.component.ts b/projects/editor/src/components/toolbar-tools/list-configs/list-configs.component.ts index c58011ff8..8134779ad 100644 --- a/projects/editor/src/components/toolbar-tools/list-configs/list-configs.component.ts +++ b/projects/editor/src/components/toolbar-tools/list-configs/list-configs.component.ts @@ -1,8 +1,10 @@ import {AsyncPipe, NgIf} from '@angular/common'; -import {ChangeDetectionStrategy, Component, inject} from '@angular/core'; +import {ChangeDetectionStrategy, Component, inject, Input} from '@angular/core'; import {TuiLet} from '@taiga-ui/cdk'; import {TuiButton, TuiDropdown, TuiHint} from '@taiga-ui/core'; -import {combineLatest, map} from 'rxjs'; +import type {AbstractTuiEditor} from 'projects/editor/src/abstract/editor-adapter.abstract'; +import type {Observable} from 'rxjs'; +import {combineLatest, map, of} from 'rxjs'; import {TuiTiptapEditorService} from '../../../directives/tiptap-editor/tiptap-editor.service'; import {TUI_EDITOR_OPTIONS} from '../../../tokens/editor-options'; @@ -17,27 +19,46 @@ import {TUI_EDITOR_TOOLBAR_TEXTS} from '../../../tokens/i18n'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class TuiListConfigs { + private localEditor: AbstractTuiEditor | null = null; protected readonly options = inject(TUI_EDITOR_OPTIONS); - protected readonly editor = inject(TuiTiptapEditorService); + protected readonly injectionEditor = inject(TuiTiptapEditorService, {optional: true}); protected readonly texts$ = inject(TUI_EDITOR_TOOLBAR_TEXTS); - protected readonly listState$ = combineLatest([ - this.editor.isActive$('orderedList'), - this.editor.isActive$('bulletList'), - this.editor.isActive$('taskList'), - ]).pipe( - map(([ordered, unordered, tasked]) => ({ - ordered, - unordered, - tasked, - })), - ); + protected listState$: Observable<{ + ordered: boolean; + unordered: boolean; + tasked: boolean; + }> | null = null; + + @Input('editor') + public set inputEditor(value: AbstractTuiEditor | null) { + this.localEditor = value; + this.initStream(); + } + + protected get editor(): AbstractTuiEditor | null { + return this.injectionEditor ?? this.localEditor; + } protected sinkListItem(): void { - this.editor.sinkListItem(); + this.editor?.sinkListItem(); } protected liftListItem(): void { - this.editor.liftListItem(); + this.editor?.liftListItem(); + } + + private initStream(): void { + this.listState$ = combineLatest([ + this.editor?.isActive$('orderedList') ?? of(false), + this.editor?.isActive$('bulletList') ?? of(false), + this.editor?.isActive$('taskList') ?? of(false), + ]).pipe( + map(([ordered, unordered, tasked]) => ({ + ordered, + unordered, + tasked, + })), + ); } } diff --git a/projects/editor/src/components/toolbar-tools/list-configs/list-configs.template.html b/projects/editor/src/components/toolbar-tools/list-configs/list-configs.template.html index 0557c88b0..cd2dbdf34 100644 --- a/projects/editor/src/components/toolbar-tools/list-configs/list-configs.template.html +++ b/projects/editor/src/components/toolbar-tools/list-configs/list-configs.template.html @@ -27,7 +27,7 @@ class="t-option t-option_margin" [iconStart]="options.icons.listUnOrdered" [tuiHint]="texts.unorderedList" - (click)="editor.toggleUnorderedList()" + (click)="editor?.toggleUnorderedList()" > - +
@@ -78,12 +80,14 @@ *ngIf="enabled(editorTool.Align)" tuiItem class="t-tool t-wrapper" + [editor]="editor" />