@flogeez/angular-tiptap-editor 2.3.0 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +132 -93
  3. package/esm2022/flogeez-angular-tiptap-editor.mjs +5 -0
  4. package/esm2022/lib/ate-editor.provider.mjs +38 -0
  5. package/esm2022/lib/components/bubble-menus/base/ate-base-bubble-menu.mjs +207 -0
  6. package/esm2022/lib/components/bubble-menus/base/ate-base-sub-bubble-menu.mjs +133 -0
  7. package/esm2022/lib/components/bubble-menus/color/ate-color-bubble-menu.component.mjs +365 -0
  8. package/esm2022/lib/components/bubble-menus/image/ate-image-bubble-menu.component.mjs +226 -0
  9. package/esm2022/lib/components/bubble-menus/link/ate-link-bubble-menu.component.mjs +247 -0
  10. package/esm2022/lib/components/bubble-menus/table/ate-cell-bubble-menu.component.mjs +121 -0
  11. package/esm2022/lib/components/bubble-menus/table/ate-table-bubble-menu.component.mjs +266 -0
  12. package/esm2022/lib/components/bubble-menus/text/ate-bubble-menu.component.mjs +294 -0
  13. package/esm2022/lib/components/color-picker/ate-color-picker.component.mjs +122 -0
  14. package/esm2022/lib/components/edit-toggle/ate-edit-toggle.component.mjs +42 -0
  15. package/esm2022/lib/components/editor/angular-tiptap-editor.component.mjs +953 -0
  16. package/esm2022/lib/components/slash-commands/ate-slash-commands.component.mjs +329 -0
  17. package/esm2022/lib/components/toolbar/ate-toolbar.component.mjs +531 -0
  18. package/esm2022/lib/components/ui/ate-button.component.mjs +90 -0
  19. package/esm2022/lib/components/ui/ate-separator.component.mjs +31 -0
  20. package/esm2022/lib/config/ate-editor.config.mjs +76 -0
  21. package/esm2022/lib/config/ate-global-config.token.mjs +6 -0
  22. package/esm2022/lib/config/ate-slash-commands.config.mjs +130 -0
  23. package/esm2022/lib/directives/ate-noop-value-accessor.directive.mjs +40 -0
  24. package/esm2022/lib/extensions/ate-link-click-behavior.extension.mjs +32 -0
  25. package/esm2022/lib/extensions/ate-resizable-image.extension.mjs +270 -0
  26. package/esm2022/lib/extensions/ate-table.extension.mjs +29 -0
  27. package/esm2022/lib/extensions/ate-tiptap-state.extension.mjs +85 -0
  28. package/esm2022/lib/extensions/ate-upload-progress.extension.mjs +195 -0
  29. package/esm2022/lib/extensions/calculators/ate-discovery.calculator.mjs +62 -0
  30. package/esm2022/lib/extensions/calculators/ate-image.calculator.mjs +8 -0
  31. package/esm2022/lib/extensions/calculators/ate-marks.calculator.mjs +75 -0
  32. package/esm2022/lib/extensions/calculators/ate-selection.calculator.mjs +35 -0
  33. package/esm2022/lib/extensions/calculators/ate-structure.calculator.mjs +34 -0
  34. package/esm2022/lib/extensions/calculators/ate-table.calculator.mjs +38 -0
  35. package/esm2022/lib/models/ate-bubble-menu.model.mjs +49 -0
  36. package/esm2022/lib/models/ate-editor-config.model.mjs +2 -0
  37. package/esm2022/lib/models/ate-editor-state.model.mjs +90 -0
  38. package/esm2022/lib/models/ate-image.model.mjs +2 -0
  39. package/esm2022/lib/models/ate-toolbar.model.mjs +35 -0
  40. package/esm2022/lib/node-view/ate-angular-node-view.mjs +97 -0
  41. package/esm2022/lib/node-view/ate-injector.registry.mjs +16 -0
  42. package/esm2022/lib/node-view/ate-node-view.factory.mjs +125 -0
  43. package/esm2022/lib/node-view/ate-node-view.models.mjs +2 -0
  44. package/esm2022/lib/node-view/ate-node-view.renderer.mjs +154 -0
  45. package/esm2022/lib/node-view/ate-register-angular-component.mjs +49 -0
  46. package/esm2022/lib/services/ate-color-picker.service.mjs +193 -0
  47. package/esm2022/lib/services/ate-editor-commands.service.mjs +465 -0
  48. package/esm2022/lib/services/ate-i18n.service.mjs +410 -0
  49. package/esm2022/lib/services/ate-image.service.mjs +368 -0
  50. package/esm2022/lib/services/ate-link.service.mjs +97 -0
  51. package/esm2022/lib/utils/ate-color.utils.mjs +60 -0
  52. package/esm2022/public-api.mjs +93 -0
  53. package/fesm2022/flogeez-angular-tiptap-editor.mjs +1178 -1100
  54. package/fesm2022/flogeez-angular-tiptap-editor.mjs.map +1 -1
  55. package/index.d.ts +3 -1562
  56. package/lib/ate-editor.provider.d.ts +21 -0
  57. package/lib/components/bubble-menus/base/ate-base-bubble-menu.d.ts +71 -0
  58. package/lib/components/bubble-menus/base/ate-base-sub-bubble-menu.d.ts +60 -0
  59. package/lib/components/bubble-menus/color/ate-color-bubble-menu.component.d.ts +77 -0
  60. package/lib/components/bubble-menus/image/ate-image-bubble-menu.component.d.ts +52 -0
  61. package/lib/components/bubble-menus/link/ate-link-bubble-menu.component.d.ts +55 -0
  62. package/lib/components/bubble-menus/table/ate-cell-bubble-menu.component.d.ts +12 -0
  63. package/lib/components/bubble-menus/table/ate-table-bubble-menu.component.d.ts +24 -0
  64. package/lib/components/bubble-menus/text/ate-bubble-menu.component.d.ts +53 -0
  65. package/lib/components/color-picker/ate-color-picker.component.d.ts +54 -0
  66. package/lib/components/edit-toggle/ate-edit-toggle.component.d.ts +13 -0
  67. package/lib/components/editor/angular-tiptap-editor.component.d.ts +235 -0
  68. package/lib/components/slash-commands/ate-slash-commands.component.d.ts +43 -0
  69. package/lib/components/toolbar/ate-toolbar.component.d.ts +47 -0
  70. package/lib/components/ui/ate-button.component.d.ts +25 -0
  71. package/lib/components/ui/ate-separator.component.d.ts +7 -0
  72. package/lib/config/ate-editor.config.d.ts +7 -0
  73. package/lib/config/ate-global-config.token.d.ts +6 -0
  74. package/lib/config/ate-slash-commands.config.d.ts +41 -0
  75. package/lib/directives/ate-noop-value-accessor.directive.d.ts +13 -0
  76. package/lib/extensions/ate-link-click-behavior.extension.d.ts +2 -0
  77. package/lib/extensions/ate-resizable-image.extension.d.ts +27 -0
  78. package/lib/extensions/ate-table.extension.d.ts +2 -0
  79. package/lib/extensions/ate-tiptap-state.extension.d.ts +7 -0
  80. package/lib/extensions/ate-upload-progress.extension.d.ts +7 -0
  81. package/lib/extensions/calculators/ate-discovery.calculator.d.ts +7 -0
  82. package/lib/extensions/calculators/ate-image.calculator.d.ts +2 -0
  83. package/lib/extensions/calculators/ate-marks.calculator.d.ts +2 -0
  84. package/lib/extensions/calculators/ate-selection.calculator.d.ts +2 -0
  85. package/lib/extensions/calculators/ate-structure.calculator.d.ts +2 -0
  86. package/lib/extensions/calculators/ate-table.calculator.d.ts +2 -0
  87. package/lib/models/ate-bubble-menu.model.d.ts +33 -0
  88. package/lib/models/ate-editor-config.model.d.ts +100 -0
  89. package/lib/models/ate-editor-state.model.d.ts +109 -0
  90. package/lib/models/ate-image.model.d.ts +104 -0
  91. package/lib/models/ate-toolbar.model.d.ts +18 -0
  92. package/lib/node-view/ate-angular-node-view.d.ts +115 -0
  93. package/lib/node-view/ate-injector.registry.d.ts +3 -0
  94. package/lib/node-view/ate-node-view.factory.d.ts +10 -0
  95. package/lib/node-view/ate-node-view.models.d.ts +138 -0
  96. package/lib/node-view/ate-node-view.renderer.d.ts +23 -0
  97. package/lib/node-view/ate-register-angular-component.d.ts +35 -0
  98. package/lib/services/ate-color-picker.service.d.ts +65 -0
  99. package/lib/services/ate-editor-commands.service.d.ts +72 -0
  100. package/lib/services/ate-i18n.service.d.ts +394 -0
  101. package/lib/services/ate-image.service.d.ts +71 -0
  102. package/lib/services/ate-link.service.d.ts +42 -0
  103. package/lib/utils/ate-color.utils.d.ts +12 -0
  104. package/package.json +3 -1
  105. package/public-api.d.ts +112 -0
package/CHANGELOG.md CHANGED
@@ -5,6 +5,29 @@ All notable changes to `@flogeez/angular-tiptap-editor` will be documented in th
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), with the exception that the major version is specifically aligned with the major version of [Tiptap](https://tiptap.dev).
7
7
 
8
+ ## [2.4.1] - 2026-05-19
9
+
10
+ ### Fixed
11
+
12
+ - **Angular 18 Compatibility**: Reverted library APIs from Angular 19+ back to Angular 18 standards:
13
+ - Replaced `provideEnvironmentInitializer()` with `APP_INITIALIZER` in `provideAteEditor()` to prevent crashes on Angular 18 environments.
14
+ - Added `{ allowSignalWrites: true }` to all `effect()` blocks to fix signal write errors at runtime.
15
+ - Fixed TypeScript compiler and builder errors under TypeScript 5.4.
16
+
17
+ ## [2.4.0] - 2026-01-29
18
+
19
+ ### Added
20
+
21
+ - **Seamless Angular Integration**: Drastically simplified editor setup and component registration.
22
+ - **Global Provider**: Added `provideAteEditor()` for automatic injector capture and application-wide configuration defaults.
23
+ - **Declarative Angular Nodes**: Support for registering Angular components directly within the `angularNodes` configuration property.
24
+ - **Exhaustive Configuration**: All editor inputs (extensions, options, state calculators) are now part of `AteEditorConfig` for centralized management.
25
+ - **Config Inheritance**: Intelligent merging of global, instance, and input-level configurations.
26
+
27
+ ### Fixed
28
+
29
+ - **NodeView Stability**: Enhanced initialization and lifecycle management for embedded Angular components.
30
+
8
31
  ## [2.3.0] - 2026-01-28
9
32
 
10
33
  ### Added
package/README.md CHANGED
@@ -1,13 +1,16 @@
1
1
  # Angular Tiptap Editor
2
2
 
3
- A modern, customizable rich-text editor for Angular applications, built with Tiptap and featuring complete internationalization support.
3
+ A modern, customizable rich-text editor for Angular, built with Tiptap.
4
4
 
5
5
  [![NPM Version](https://img.shields.io/npm/v/@flogeez/angular-tiptap-editor?style=for-the-badge&logo=npm)](https://www.npmjs.com/package/@flogeez/angular-tiptap-editor) [![Demo](https://img.shields.io/badge/Demo-Live-brightgreen?style=for-the-badge&logo=google-chrome)](https://flogeez.github.io/angular-tiptap-editor/) [![Try it on StackBlitz](https://img.shields.io/badge/Try%20it-StackBlitz-blue?style=for-the-badge&logo=stackblitz)](https://stackblitz.com/edit/angular-tiptap-editor)
6
6
 
7
+ Angular Tiptap Editor is a high-performance WYSIWYG editor engineered for the modern Angular ecosystem. Built on top of Tiptap and powered by a native **Signals** architecture, it features a polished, professional design that feels, I think, clean and modern out of the box.
8
+ Yet, I've worked to keep it fully customizable: you can easily configure the editor, tweak the UI, or even embed your own Angular components as interactive nodes.
9
+
7
10
  ## 🚀 Features
8
11
 
9
12
  - **Modern Angular**: Built with Angular 18+ using Signals and modern patterns for peak performance.
10
- - **Full Rich Text Power**: Powered by Tiptap v2 with extensive formatting and block capabilities.
13
+ - **Full Rich Text Power**: Powered by Tiptap with extensive formatting and block capabilities.
11
14
  - **Modern UX (Notion-like)**: Intuitive slash commands and bubble menus for a keyboard-first experience.
12
15
  - **Highly Customizable**: Easily configure toolbars, bubble menus, and slash command items.
13
16
  - **Signal-Based Reactivity**: Pure Signal architecture natively compatible with `ChangeDetectionStrategy.OnPush`.
@@ -16,6 +19,9 @@ A modern, customizable rich-text editor for Angular applications, built with Tip
16
19
  - **Built-in i18n**: English & French support with a reactive, extensible locale system.
17
20
  - **Word/Character Count**: Real-time statistics with proper pluralization support.
18
21
  - **Office-Ready**: Cleaned-up pasting from Microsoft Word and Excel to maintain layout integrity.
22
+ - **Seamless Angular Integration**: Use a single `provideAteEditor()` to initialize the library and share a root injector across all nodes.
23
+ - **Universal Component Embedding**: Embed _any_ Angular component (library or custom) directly into the editor as a TipTap node.
24
+ - **Global Configuration**: Set application-wide defaults for themes, toolbars, and features with hierarchical inheritance.
19
25
  - **Service Driven**: Deep programmatic control via `AteEditorCommandsService` and isolated instances.
20
26
  - **A11y First**: Built with accessibility best practices and full keyboard navigation.
21
27
 
@@ -51,7 +57,7 @@ Add the required CSS to your `angular.json` file in the `styles` array:
51
57
  "styles": [
52
58
  ...
53
59
  "node_modules/@fontsource/material-symbols-outlined/index.css",
54
- "node_modules/@flogeez/angular-tiptap-editor/src/lib/styles/index.css",
60
+ "node_modules/@flogeez/angular-tiptap-editor/styles/index.css",
55
61
  ...
56
62
  ]
57
63
  }
@@ -138,37 +144,7 @@ export class AdvancedComponent {
138
144
  }
139
145
  ```
140
146
 
141
- ### 3. Registering Custom Extensions
142
-
143
- Easily extend the editor with any standard Tiptap extension or your own custom marks/nodes via the `tiptapExtensions` input.
144
-
145
- ```typescript
146
- import { Component } from "@angular/core";
147
- import { AngularTiptapEditorComponent } from "@flogeez/angular-tiptap-editor";
148
-
149
- @Component({
150
- selector: "app-custom-extensions",
151
- standalone: true,
152
- imports: [AngularTiptapEditorComponent],
153
- template: `
154
- <angular-tiptap-editor
155
- [content]="content"
156
- [tiptapExtensions]="extensions"
157
- (contentChange)="content = $event" />
158
- `,
159
- })
160
- export class CustomExtensionsComponent {
161
- content = "<p>Custom extensions example</p>";
162
-
163
- extensions = [
164
- // Add your custom TipTap extensions here
165
- // Example: Custom extension configuration
166
- // MyCustomExtension.configure({ /* options */ })
167
- ];
168
- }
169
- ```
170
-
171
- ### 4. With Form Integration
147
+ ### 3. With Form Integration
172
148
 
173
149
  ```typescript
174
150
  import { Component } from "@angular/core";
@@ -183,9 +159,7 @@ import { AngularTiptapEditorComponent } from "@flogeez/angular-tiptap-editor";
183
159
  <form>
184
160
  <angular-tiptap-editor
185
161
  [formControl]="contentControl"
186
- placeholder="Enter your content here..."
187
- [showCharacterCount]="true"
188
- [showWordCount]="true" />
162
+ placeholder="Enter your content here..." />
189
163
  <button type="submit">Submit</button>
190
164
  </form>
191
165
  `,
@@ -195,25 +169,90 @@ export class FormComponent {
195
169
  }
196
170
  ```
197
171
 
198
- ### 5. Using EditorCommandsService
172
+ ## ⚙️ Advanced Setup & Extensions
173
+
174
+ ### 1. Global Setup (Recommended)
175
+
176
+ Initialize the library globally in your `app.config.ts` or `main.ts` to capture the root injector and set application-wide defaults.
177
+
178
+ ```typescript
179
+ import { ApplicationConfig } from "@angular/core";
180
+ import { provideAteEditor } from "@flogeez/angular-tiptap-editor";
181
+
182
+ export const appConfig: ApplicationConfig = {
183
+ providers: [
184
+ provideAteEditor({
185
+ theme: "auto",
186
+ mode: "seamless",
187
+ tiptapExtensions: [
188
+ /* Global TipTap Extensions */
189
+ ],
190
+ stateCalculators: [
191
+ /* Global State Calculators */
192
+ ],
193
+ }),
194
+ ],
195
+ };
196
+ ```
197
+
198
+ ### 2. Embedding Angular Components (Angular Nodes)
199
+
200
+ Turn any Angular component into a TipTap node without writing extension code. This project makes it easy to map your existing Angular components directly to the editor's document structure.
201
+
202
+ ```typescript
203
+ import { Component } from "@angular/core";
204
+ import {
205
+ AngularTiptapEditorComponent,
206
+ AteEditorConfig,
207
+ AteAngularNode,
208
+ } from "@flogeez/angular-tiptap-editor";
209
+ import { MyCounterComponent } from "./my-counter.component";
210
+
211
+ @Component({
212
+ selector: "app-custom-nodes",
213
+ standalone: true,
214
+ imports: [AngularTiptapEditorComponent],
215
+ template: ` <angular-tiptap-editor [config]="editorConfig" /> `,
216
+ })
217
+ export class CustomNodesComponent {
218
+ // Use AteAngularNode for explicit typing if needed
219
+ myNodes: AteAngularNode[] = [
220
+ {
221
+ component: MyCounterComponent,
222
+ name: "counter",
223
+ attributes: { count: { default: 0 } },
224
+ group: "block",
225
+ draggable: true,
226
+ },
227
+ ];
228
+
229
+ editorConfig: AteEditorConfig = {
230
+ angularNodes: this.myNodes,
231
+ };
232
+ }
233
+ ```
234
+
235
+ > **Note**: Your component can inherit from `AteAngularNodeView` to access the full TipTap API (`editor`, `node`, `updateAttributes`) via Signals!
236
+
237
+ ### 3. Using EditorCommandsService
238
+
239
+ Deep programmatic control over any editor instance.
199
240
 
200
241
  ```typescript
201
242
  import { Component, inject } from "@angular/core";
202
243
  import { AteEditorCommandsService } from "@flogeez/angular-tiptap-editor";
244
+ import { Editor } from "@tiptap/core";
203
245
 
204
246
  @Component({
205
247
  selector: "app-commands",
206
248
  standalone: true,
207
249
  template: `
208
- <div>
209
- <div class="controls">
210
- <button (click)="clearContent()">Clear Content</button>
211
- <button (click)="focusEditor()">Focus Editor</button>
212
- <button (click)="setContent()">Set Content</button>
213
- </div>
214
-
215
- <angular-tiptap-editor (editorCreated)="onEditorCreated($event)" />
250
+ <div class="controls">
251
+ <button (click)="clearContent()">Clear</button>
252
+ <button (click)="focusEditor()">Focus</button>
253
+ <button (click)="setContent()">Set Content</button>
216
254
  </div>
255
+ <angular-tiptap-editor (editorCreated)="onEditorCreated($event)" />
217
256
  `,
218
257
  })
219
258
  export class CommandsComponent {
@@ -225,72 +264,87 @@ export class CommandsComponent {
225
264
  }
226
265
 
227
266
  clearContent() {
228
- if (this.editor) {
229
- this.editorCommandsService.clearContent(this.editor);
230
- }
267
+ if (this.editor) this.editorCommandsService.clearContent(this.editor);
231
268
  }
232
269
 
233
270
  focusEditor() {
234
- if (this.editor) {
235
- this.editorCommandsService.focus(this.editor);
236
- }
271
+ if (this.editor) this.editorCommandsService.focus(this.editor);
237
272
  }
238
273
 
239
274
  setContent() {
240
- if (this.editor) {
241
- this.editorCommandsService.setContent(this.editor, "<h1>New Content</h1>");
242
- }
275
+ if (this.editor) this.editorCommandsService.setContent(this.editor, "<h1>New!</h1>");
243
276
  }
244
277
  }
245
278
  ```
246
279
 
247
- ### 6. Extending Reactive Editor State
280
+ ### 4. Custom Tiptap Extensions (Low Level)
248
281
 
249
- The editor features a dual-layer state architecture: **Automatic Tracking** for simple extensions and **Custom Calculators** for advanced needs.
282
+ Standard TipTap extensions can be passed via the `tiptapExtensions` property in your config.
283
+
284
+ ```typescript
285
+ import { AteEditorConfig } from "@flogeez/angular-tiptap-editor";
286
+
287
+ @Component({
288
+ template: ` <angular-tiptap-editor [config]="editorConfig" /> `,
289
+ })
290
+ export class CustomExtensionsComponent {
291
+ editorConfig: AteEditorConfig = {
292
+ tiptapExtensions: [
293
+ /* Standard TipTap extensions (Highlight, Link, etc.) */
294
+ ],
295
+ };
296
+ }
297
+ ```
298
+
299
+ ### 5. Extending Reactive Editor State (Calculators)
300
+
301
+ The editor features a dual-layer state architecture for maximum reactivity.
250
302
 
251
303
  #### A. Automatic Extension Tracking (Zero Config)
252
304
 
253
- Any TipTap **Mark** or **Node** you add to `tiptapExtensions` is automatically tracked by our `DiscoveryCalculator`. You don't need to write any extra code to make them reactive.
305
+ Any TipTap **Mark** or **Node** you add to `tiptapExtensions` is automatically tracked. You don't need extra code to make them reactive.
254
306
 
255
- - **For Marks**: `state().marks.yourExtensionName` (boolean) and `state().can.toggleYourExtensionName` (boolean).
307
+ - **For Marks**: `state().marks.yourExtensionName` (boolean).
256
308
  - **For Nodes**: `state().nodes.yourExtensionName` (boolean).
257
309
 
258
310
  #### B. Custom State Calculators (Advanced)
259
311
 
260
- If you need to extract complex data (like attributes, depth, or custom logic), you can provide a custom `StateCalculator`.
312
+ Extract complex data (attributes, depth, custom logic) via specialized Calculators.
261
313
 
262
- 1. **Define a Calculator**:
314
+ **1. Define a Calculator**:
263
315
 
264
316
  ```typescript
265
317
  import { AteStateCalculator } from "@flogeez/angular-tiptap-editor";
266
318
 
267
- // This function will be called on every editor update
268
- export const MyCustomCalculator: AteStateCalculator = editor => {
269
- return {
270
- custom: {
271
- hasHighPriority: editor.isActive("priority"),
272
- selectionDepth: editor.state.selection.$from.depth,
273
- // Any data you need...
274
- },
275
- };
276
- };
319
+ // Called on every editor update
320
+ export const MyCustomCalculator: AteStateCalculator = editor => ({
321
+ custom: { selectionDepth: editor.state.selection.$from.depth },
322
+ });
277
323
  ```
278
324
 
279
- 2. **Register it in the Template**:
325
+ **2. Register in the Config**:
326
+
327
+ ```typescript
328
+ import { AteEditorConfig } from "@flogeez/angular-tiptap-editor";
329
+
330
+ editorConfig: AteEditorConfig = {
331
+ stateCalculators: [MyCustomCalculator],
332
+ };
333
+ ```
280
334
 
281
335
  ```html
282
- <angular-tiptap-editor [stateCalculators]="[MyCustomCalculator]" />
336
+ <angular-tiptap-editor [config]="editorConfig" />
283
337
  ```
284
338
 
285
- 3. **Consume it anywhere**:
339
+ **3. Consume the State**:
286
340
 
287
341
  ```typescript
288
342
  @Component({ ... })
289
343
  export class MyToolbarComponent {
290
344
  private editorCommands = inject(AteEditorCommandsService);
291
345
 
292
- // Access your custom data reactively!
293
- isHighPriority = computed(() => this.editorCommands.editorState().custom?.hasHighPriority);
346
+ // Access your custom data reactively via Signals!
347
+ depth = computed(() => this.editorCommands.editorState().custom?.selectionDepth);
294
348
  }
295
349
  ```
296
350
 
@@ -676,20 +730,6 @@ The library exposes a reactive `editorState` signal via the `AteEditorCommandsSe
676
730
 
677
731
  Since it's built with Signals, your custom toolbar items or UI overlays will only re-render when the specific data they consume changes, making it extremely efficient for `OnPush` applications.
678
732
 
679
- ---
680
-
681
- ### 🧩 Custom Tiptap Extensions
682
-
683
- You are not limited to the built-in extensions. Pass any Tiptap extension, mark, or node:
684
-
685
- ```html
686
- <angular-tiptap-editor [tiptapExtensions]="[MyCustomExtension]" />
687
- ```
688
-
689
- Any custom extension is automatically detected and its state (active/can) is added to the reactive `editorState` snapshot.
690
-
691
- ---
692
-
693
733
  ## 🏗️ Architecture
694
734
 
695
735
  ### Reactive State Management
@@ -776,9 +816,8 @@ Contributions are welcome! Please feel free to submit a Pull Request.
776
816
 
777
817
  ### Latest Updates
778
818
 
779
- - ✅ **Unified Configuration**: New unified `AteEditorConfig` system for cleaner, type-safe editor setup.
780
- - ✅ **Enhanced Image Upload**: Advanced image handling with custom upload handlers and auto-compression.
781
- - ✅ **Refactored Link Management**: Dedicated link bubble menu with smart UI anchoring and real-time URL sync.
819
+ - ✅ **Seamless Integration**: Drastically simplified setup with `provideAteEditor()` and declarative `nodeViews`.
820
+ - ✅ **Universal Component Engine**: Embed any Angular component as an editor node.
782
821
 
783
822
  ---
784
823
 
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Generated bundle index. Do not edit.
3
+ */
4
+ export * from './public-api';
5
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmxvZ2Vlei1hbmd1bGFyLXRpcHRhcC1lZGl0b3IuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9wcm9qZWN0cy9hbmd1bGFyLXRpcHRhcC1lZGl0b3Ivc3JjL2Zsb2dlZXotYW5ndWxhci10aXB0YXAtZWRpdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBRUgsY0FBYyxjQUFjLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEdlbmVyYXRlZCBidW5kbGUgaW5kZXguIERvIG5vdCBlZGl0LlxuICovXG5cbmV4cG9ydCAqIGZyb20gJy4vcHVibGljLWFwaSc7XG4iXX0=
@@ -0,0 +1,38 @@
1
+ import { makeEnvironmentProviders, APP_INITIALIZER, Injector, } from "@angular/core";
2
+ import { setGlobalInjector } from "./node-view/ate-injector.registry";
3
+ import { ATE_GLOBAL_CONFIG } from "./config/ate-global-config.token";
4
+ /**
5
+ * Provides the necessary configuration and initialization for the Angular TipTap Editor.
6
+ * This should be called in your app.config.ts (for standalone) or main.ts.
7
+ *
8
+ * This provider is essential for 'Angular Nodes' to work correctly, as it captures the
9
+ * root Injector required to instantiate custom Angular components within the editor.
10
+ * @example
11
+ * ```ts
12
+ * bootstrapApplication(AppComponent, {
13
+ * providers: [
14
+ * provideAteEditor({
15
+ * theme: 'dark',
16
+ * mode: 'seamless'
17
+ * })
18
+ * ]
19
+ * });
20
+ * ```
21
+ */
22
+ export function provideAteEditor(config) {
23
+ return makeEnvironmentProviders([
24
+ {
25
+ provide: ATE_GLOBAL_CONFIG,
26
+ useValue: config || {},
27
+ },
28
+ {
29
+ provide: APP_INITIALIZER,
30
+ useFactory: (injector) => () => {
31
+ setGlobalInjector(injector);
32
+ },
33
+ deps: [Injector],
34
+ multi: true,
35
+ },
36
+ ]);
37
+ }
38
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXRlLWVkaXRvci5wcm92aWRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3Byb2plY3RzL2FuZ3VsYXItdGlwdGFwLWVkaXRvci9zcmMvbGliL2F0ZS1lZGl0b3IucHJvdmlkZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUVMLHdCQUF3QixFQUN4QixlQUFlLEVBQ2YsUUFBUSxHQUNULE1BQU0sZUFBZSxDQUFDO0FBQ3ZCLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLG1DQUFtQyxDQUFDO0FBRXRFLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLGtDQUFrQyxDQUFDO0FBR3JFOzs7Ozs7Ozs7Ozs7Ozs7OztHQWlCRztBQUNILE1BQU0sVUFBVSxnQkFBZ0IsQ0FBQyxNQUF3QjtJQUN2RCxPQUFPLHdCQUF3QixDQUFDO1FBQzlCO1lBQ0UsT0FBTyxFQUFFLGlCQUFpQjtZQUMxQixRQUFRLEVBQUUsTUFBTSxJQUFJLEVBQUU7U0FDdkI7UUFDRDtZQUNFLE9BQU8sRUFBRSxlQUFlO1lBQ3hCLFVBQVUsRUFBRSxDQUFDLFFBQWtCLEVBQUUsRUFBRSxDQUFDLEdBQUcsRUFBRTtnQkFDdkMsaUJBQWlCLENBQUMsUUFBUSxDQUFDLENBQUM7WUFDOUIsQ0FBQztZQUNELElBQUksRUFBRSxDQUFDLFFBQVEsQ0FBQztZQUNoQixLQUFLLEVBQUUsSUFBSTtTQUNaO0tBQ0YsQ0FBQyxDQUFDO0FBQ0wsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7XG4gIEVudmlyb25tZW50UHJvdmlkZXJzLFxuICBtYWtlRW52aXJvbm1lbnRQcm92aWRlcnMsXG4gIEFQUF9JTklUSUFMSVpFUixcbiAgSW5qZWN0b3IsXG59IGZyb20gXCJAYW5ndWxhci9jb3JlXCI7XG5pbXBvcnQgeyBzZXRHbG9iYWxJbmplY3RvciB9IGZyb20gXCIuL25vZGUtdmlldy9hdGUtaW5qZWN0b3IucmVnaXN0cnlcIjtcblxuaW1wb3J0IHsgQVRFX0dMT0JBTF9DT05GSUcgfSBmcm9tIFwiLi9jb25maWcvYXRlLWdsb2JhbC1jb25maWcudG9rZW5cIjtcbmltcG9ydCB7IEF0ZUVkaXRvckNvbmZpZyB9IGZyb20gXCIuL21vZGVscy9hdGUtZWRpdG9yLWNvbmZpZy5tb2RlbFwiO1xuXG4vKipcbiAqIFByb3ZpZGVzIHRoZSBuZWNlc3NhcnkgY29uZmlndXJhdGlvbiBhbmQgaW5pdGlhbGl6YXRpb24gZm9yIHRoZSBBbmd1bGFyIFRpcFRhcCBFZGl0b3IuXG4gKiBUaGlzIHNob3VsZCBiZSBjYWxsZWQgaW4geW91ciBhcHAuY29uZmlnLnRzIChmb3Igc3RhbmRhbG9uZSkgb3IgbWFpbi50cy5cbiAqXG4gKiBUaGlzIHByb3ZpZGVyIGlzIGVzc2VudGlhbCBmb3IgJ0FuZ3VsYXIgTm9kZXMnIHRvIHdvcmsgY29ycmVjdGx5LCBhcyBpdCBjYXB0dXJlcyB0aGVcbiAqIHJvb3QgSW5qZWN0b3IgcmVxdWlyZWQgdG8gaW5zdGFudGlhdGUgY3VzdG9tIEFuZ3VsYXIgY29tcG9uZW50cyB3aXRoaW4gdGhlIGVkaXRvci5cbiAqIEBleGFtcGxlXG4gKiBgYGB0c1xuICogYm9vdHN0cmFwQXBwbGljYXRpb24oQXBwQ29tcG9uZW50LCB7XG4gKiAgIHByb3ZpZGVyczogW1xuICogICAgIHByb3ZpZGVBdGVFZGl0b3Ioe1xuICogICAgICAgdGhlbWU6ICdkYXJrJyxcbiAqICAgICAgIG1vZGU6ICdzZWFtbGVzcydcbiAqICAgICB9KVxuICogICBdXG4gKiB9KTtcbiAqIGBgYFxuICovXG5leHBvcnQgZnVuY3Rpb24gcHJvdmlkZUF0ZUVkaXRvcihjb25maWc/OiBBdGVFZGl0b3JDb25maWcpOiBFbnZpcm9ubWVudFByb3ZpZGVycyB7XG4gIHJldHVybiBtYWtlRW52aXJvbm1lbnRQcm92aWRlcnMoW1xuICAgIHtcbiAgICAgIHByb3ZpZGU6IEFURV9HTE9CQUxfQ09ORklHLFxuICAgICAgdXNlVmFsdWU6IGNvbmZpZyB8fCB7fSxcbiAgICB9LFxuICAgIHtcbiAgICAgIHByb3ZpZGU6IEFQUF9JTklUSUFMSVpFUixcbiAgICAgIHVzZUZhY3Rvcnk6IChpbmplY3RvcjogSW5qZWN0b3IpID0+ICgpID0+IHtcbiAgICAgICAgc2V0R2xvYmFsSW5qZWN0b3IoaW5qZWN0b3IpO1xuICAgICAgfSxcbiAgICAgIGRlcHM6IFtJbmplY3Rvcl0sXG4gICAgICBtdWx0aTogdHJ1ZSxcbiAgICB9LFxuICBdKTtcbn1cbiJdfQ==
@@ -0,0 +1,207 @@
1
+ import { Directive, input, viewChild, inject, effect, } from "@angular/core";
2
+ import tippy, { sticky } from "tippy.js";
3
+ import { AteEditorCommandsService } from "../../../services/ate-editor-commands.service";
4
+ import { AteI18nService } from "../../../services/ate-i18n.service";
5
+ import * as i0 from "@angular/core";
6
+ /**
7
+ * Base abstract class for all Bubble Menus (Text, Image, Table, Cell).
8
+ * Handles common logic for Tippy.js initialization, positioning, and visibility.
9
+ */
10
+ export class AteBaseBubbleMenu {
11
+ constructor() {
12
+ this.i18nService = inject(AteI18nService);
13
+ this.editorCommands = inject(AteEditorCommandsService);
14
+ // Core Inputs
15
+ this.editor = input.required();
16
+ // Required viewChild for the menu container
17
+ this.menuRef = viewChild.required("menuRef");
18
+ // Internal State
19
+ this.tippyInstance = null;
20
+ this.updateTimeout = null;
21
+ // Toolbar interaction state (from centralized service)
22
+ this.isToolbarInteracting = this.editorCommands.isToolbarInteracting;
23
+ // Reactive state alias for templates
24
+ this.state = this.editorCommands.editorState;
25
+ /**
26
+ * Core logic to decide whether to show or hide the menu.
27
+ */
28
+ this.updateMenu = () => {
29
+ if (this.updateTimeout) {
30
+ clearTimeout(this.updateTimeout);
31
+ }
32
+ this.updateTimeout = window.setTimeout(() => {
33
+ const ed = this.editor();
34
+ if (!ed) {
35
+ return;
36
+ }
37
+ // Hide when interacting with the main toolbar
38
+ if (this.isToolbarInteracting()) {
39
+ this.hideTippy();
40
+ return;
41
+ }
42
+ if (this.shouldShow()) {
43
+ this.showTippy();
44
+ }
45
+ else {
46
+ this.hideTippy();
47
+ }
48
+ }, 10);
49
+ };
50
+ // Effect to reactively update the menu when editor state
51
+ // or toolbar interaction changes.
52
+ effect(() => {
53
+ this.state();
54
+ this.isToolbarInteracting();
55
+ // Also react to link/color edit modes to hide when sub-menus activate
56
+ this.editorCommands.linkEditMode();
57
+ this.editorCommands.colorEditMode();
58
+ this.updateMenu();
59
+ }, { allowSignalWrites: true });
60
+ }
61
+ ngOnInit() {
62
+ this.initTippy();
63
+ }
64
+ ngOnDestroy() {
65
+ if (this.updateTimeout) {
66
+ clearTimeout(this.updateTimeout);
67
+ }
68
+ if (this.tippyInstance) {
69
+ this.tippyInstance.destroy();
70
+ this.tippyInstance = null;
71
+ }
72
+ }
73
+ /**
74
+ * Initializes the Tippy instance.
75
+ * Can be overridden for specific Tippy configurations.
76
+ */
77
+ initTippy() {
78
+ if (!this.menuRef()?.nativeElement) {
79
+ // Re-try if the view child is not yet available
80
+ setTimeout(() => this.initTippy(), 50);
81
+ return;
82
+ }
83
+ const ed = this.editor();
84
+ if (this.tippyInstance) {
85
+ this.tippyInstance.destroy();
86
+ }
87
+ this.tippyInstance = tippy(document.body, {
88
+ content: this.menuRef().nativeElement,
89
+ trigger: "manual",
90
+ placement: "top-start",
91
+ appendTo: () => (ed ? ed.options.element : document.body),
92
+ interactive: true,
93
+ hideOnClick: false,
94
+ arrow: false,
95
+ offset: [0, 8],
96
+ plugins: [sticky],
97
+ sticky: false,
98
+ getReferenceClientRect: () => this.getSelectionRect(),
99
+ popperOptions: {
100
+ modifiers: [
101
+ {
102
+ name: "preventOverflow",
103
+ options: {
104
+ boundary: this.editor().options.element,
105
+ padding: 8,
106
+ },
107
+ },
108
+ {
109
+ name: "flip",
110
+ options: {
111
+ fallbackPlacements: ["bottom-start", "top-end", "bottom-end"],
112
+ },
113
+ },
114
+ ],
115
+ },
116
+ onShow: instance => this.onTippyShow(instance),
117
+ onHide: instance => this.onTippyHide(instance),
118
+ });
119
+ this.updateMenu();
120
+ }
121
+ /**
122
+ * Helper to show the Tippy instance with updated positioning.
123
+ */
124
+ showTippy() {
125
+ if (!this.tippyInstance) {
126
+ return;
127
+ }
128
+ // Update position before showing
129
+ this.tippyInstance.setProps({
130
+ getReferenceClientRect: () => this.getSelectionRect(),
131
+ sticky: "reference",
132
+ });
133
+ this.tippyInstance.show();
134
+ }
135
+ /**
136
+ * Helper to hide the Tippy instance.
137
+ */
138
+ hideTippy() {
139
+ if (this.tippyInstance) {
140
+ this.tippyInstance.setProps({ sticky: false });
141
+ this.tippyInstance.hide();
142
+ }
143
+ }
144
+ /**
145
+ * Common command handler for buttons in the menu.
146
+ */
147
+ onCommand(command, event) {
148
+ if (event) {
149
+ event.preventDefault();
150
+ event.stopPropagation();
151
+ }
152
+ const ed = this.editor();
153
+ if (ed) {
154
+ this.executeCommand(ed, command);
155
+ this.updateMenu();
156
+ }
157
+ }
158
+ /**
159
+ * Shared helper to get the bounding rect of the current selection.
160
+ * Uses a combination of native selection and ProseMirror coordinates.
161
+ */
162
+ getRectForSelection(ed) {
163
+ if (!ed) {
164
+ return new DOMRect(0, 0, 0, 0);
165
+ }
166
+ const { from, to, empty } = ed.state.selection;
167
+ if (empty) {
168
+ return new DOMRect(-9999, -9999, 0, 0);
169
+ }
170
+ // 1. Try native selection for multi-line accuracy
171
+ const selection = window.getSelection();
172
+ if (selection && selection.rangeCount > 0) {
173
+ const range = selection.getRangeAt(0);
174
+ const rect = range.getBoundingClientRect();
175
+ // Ensure the rect is valid and belongs to the editor
176
+ if (rect.width > 0 && rect.height > 0) {
177
+ return rect;
178
+ }
179
+ }
180
+ // 2. Fallback to Tiptap coordinates for precision (single line / edge cases)
181
+ const start = ed.view.coordsAtPos(from);
182
+ const end = ed.view.coordsAtPos(to);
183
+ const top = Math.min(start.top, end.top);
184
+ const bottom = Math.max(start.bottom, end.bottom);
185
+ const left = Math.min(start.left, end.left);
186
+ const right = Math.max(start.right, end.right);
187
+ return new DOMRect(left, top, right - left, bottom - top);
188
+ }
189
+ /**
190
+ * Optional hook called when Tippy is about to be shown.
191
+ */
192
+ onTippyShow(_instance) {
193
+ /* empty */
194
+ }
195
+ /**
196
+ * Optional hook called when Tippy is about to be hidden.
197
+ */
198
+ onTippyHide(_instance) {
199
+ /* empty */
200
+ }
201
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AteBaseBubbleMenu, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
202
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "18.2.14", type: AteBaseBubbleMenu, inputs: { editor: { classPropertyName: "editor", publicName: "editor", isSignal: true, isRequired: true, transformFunction: null } }, viewQueries: [{ propertyName: "menuRef", first: true, predicate: ["menuRef"], descendants: true, isSignal: true }], ngImport: i0 }); }
203
+ }
204
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AteBaseBubbleMenu, decorators: [{
205
+ type: Directive
206
+ }], ctorParameters: () => [] });
207
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXRlLWJhc2UtYnViYmxlLW1lbnUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9wcm9qZWN0cy9hbmd1bGFyLXRpcHRhcC1lZGl0b3Ivc3JjL2xpYi9jb21wb25lbnRzL2J1YmJsZS1tZW51cy9iYXNlL2F0ZS1iYXNlLWJ1YmJsZS1tZW51LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFDTCxTQUFTLEVBQ1QsS0FBSyxFQUNMLFNBQVMsRUFJVCxNQUFNLEVBQ04sTUFBTSxHQUNQLE1BQU0sZUFBZSxDQUFDO0FBQ3ZCLE9BQU8sS0FBSyxFQUFFLEVBQTZCLE1BQU0sRUFBRSxNQUFNLFVBQVUsQ0FBQztBQUVwRSxPQUFPLEVBQUUsd0JBQXdCLEVBQUUsTUFBTSwrQ0FBK0MsQ0FBQztBQUN6RixPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sb0NBQW9DLENBQUM7O0FBRXBFOzs7R0FHRztBQUVILE1BQU0sT0FBZ0IsaUJBQWlCO0lBb0JyQztRQW5CbUIsZ0JBQVcsR0FBRyxNQUFNLENBQUMsY0FBYyxDQUFDLENBQUM7UUFDckMsbUJBQWMsR0FBRyxNQUFNLENBQUMsd0JBQXdCLENBQUMsQ0FBQztRQUVyRSxjQUFjO1FBQ2QsV0FBTSxHQUFHLEtBQUssQ0FBQyxRQUFRLEVBQVUsQ0FBQztRQUVsQyw0Q0FBNEM7UUFDNUMsWUFBTyxHQUFHLFNBQVMsQ0FBQyxRQUFRLENBQTZCLFNBQVMsQ0FBQyxDQUFDO1FBRXBFLGlCQUFpQjtRQUNQLGtCQUFhLEdBQXlCLElBQUksQ0FBQztRQUMzQyxrQkFBYSxHQUFrQixJQUFJLENBQUM7UUFFOUMsdURBQXVEO1FBQ3BDLHlCQUFvQixHQUFHLElBQUksQ0FBQyxjQUFjLENBQUMsb0JBQW9CLENBQUM7UUFFbkYscUNBQXFDO1FBQzVCLFVBQUssR0FBRyxJQUFJLENBQUMsY0FBYyxDQUFDLFdBQVcsQ0FBQztRQWtGakQ7O1dBRUc7UUFDSCxlQUFVLEdBQUcsR0FB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QUFDO0lBRUQ7O09BRUc7SUFDTyxXQUFXLENBQUMsU0FBd0I7UUFDNUMsV0FBVztJQUNiLENBQUM7K0dBNU9tQixpQkFBaUI7bUdBQWpCLGlCQUFpQjs7NEZBQWpCLGlCQUFpQjtrQkFEdEMsU0FBUyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7XG4gIERpcmVjdGl2ZSxcbiAgaW5wdXQsXG4gIHZpZXdDaGlsZCxcbiAgRWxlbWVudFJlZixcbiAgT25Jbml0LFxuICBPbkRlc3Ryb3ksXG4gIGluamVjdCxcbiAgZWZmZWN0LFxufSBmcm9tIFwiQGFuZ3VsYXIvY29yZVwiO1xuaW1wb3J0IHRpcHB5LCB7IEluc3RhbmNlIGFzIFRpcHB5SW5zdGFuY2UsIHN0aWNreSB9IGZyb20gXCJ0aXBweS5qc1wiO1xuaW1wb3J0IHsgRWRpdG9yIH0gZnJvbSBcIkB0aXB0YXAvY29yZVwiO1xuaW1wb3J0IHsgQXRlRWRpdG9yQ29tbWFuZHNTZXJ2aWNlIH0gZnJvbSBcIi4uLy4uLy4uL3NlcnZpY2VzL2F0ZS1lZGl0b3ItY29tbWFuZHMuc2VydmljZVwiO1xuaW1wb3J0IHsgQXRlSTE4blNlcnZpY2UgfSBmcm9tIFwiLi4vLi4vLi4vc2VydmljZXMvYXRlLWkxOG4uc2VydmljZVwiO1xuXG4vKipcbiAqIEJhc2UgYWJzdHJhY3QgY2xhc3MgZm9yIGFsbCBCdWJibGUgTWVudXMgKFRleHQsIEltYWdlLCBUYWJsZSwgQ2VsbCkuXG4gKiBIYW5kbGVzIGNvbW1vbiBsb2dpYyBmb3IgVGlwcHkuanMgaW5pdGlhbGl6YXRpb24sIHBvc2l0aW9uaW5nLCBhbmQgdmlzaWJpbGl0eS5cbiAqL1xuQERpcmVjdGl2ZSgpXG5leHBvcnQgYWJzdHJhY3QgY2xhc3MgQXRlQmFzZUJ1YmJsZU1lbnUgaW1wbGVtZW50cyBPbkluaXQsIE9uRGVzdHJveSB7XG4gIHByb3RlY3RlZCByZWFkb25seSBpMThuU2VydmljZSA9IGluamVjdChBdGVJMThuU2VydmljZSk7XG4gIHByb3RlY3RlZCByZWFkb25seSBlZGl0b3JDb21tYW5kcyA9IGluamVjdChBdGVFZGl0b3JDb21tYW5kc1NlcnZpY2UpO1xuXG4gIC8vIENvcmUgSW5wdXRzXG4gIGVkaXRvciA9IGlucHV0LnJlcXVpcmVkPEVkaXRvcj4oKTtcblxuICAvLyBSZXF1aXJlZCB2aWV3Q2hpbGQgZm9yIHRoZSBtZW51IGNvbnRhaW5lclxuICBtZW51UmVmID0gdmlld0NoaWxkLnJlcXVpcmVkPEVsZW1lbnRSZWY8SFRNTERpdkVsZW1lbnQ+PihcIm1lbnVSZWZcIik7XG5cbiAgLy8gSW50ZXJuYWwgU3RhdGVcbiAgcHJvdGVjdGVkIHRpcHB5SW5zdGFuY2U6IFRpcHB5SW5zdGFuY2UgfCBudWxsID0gbnVsbDtcbiAgcHJvdGVjdGVkIHVwZGF0ZVRpbWVvdXQ6IG51bWJlciB8IG51bGwgPSBudWxsO1xuXG4gIC8vIFRvb2xiYXIgaW50ZXJhY3Rpb24gc3RhdGUgKGZyb20gY2VudHJhbGl6ZWQgc2VydmljZSlcbiAgcHJvdGVjdGVkIHJlYWRvbmx5IGlzVG9vbGJhckludGVyYWN0aW5nID0gdGhpcy5lZGl0b3JDb21tYW5kcy5pc1Rvb2xiYXJJbnRlcmFjdGluZztcblxuICAvLyBSZWFjdGl2ZSBzdGF0ZSBhbGlhcyBmb3IgdGVtcGxhdGVzXG4gIHJlYWRvbmx5IHN0YXRlID0gdGhpcy5lZGl0b3JDb21tYW5kcy5lZGl0b3JTdGF0ZTtcblxuICBjb25zdHJ1Y3RvcigpIHtcbiAgICAvLyBFZmZlY3QgdG8gcmVhY3RpdmVseSB1cGRhdGUgdGhlIG1lbnUgd2hlbiBlZGl0b3Igc3RhdGVcbiAgICAvLyBvciB0b29sYmFyIGludGVyYWN0aW9uIGNoYW5nZXMuXG4gICAgZWZmZWN0KCgpID0+IHtcbiAgICAgIHRoaXMuc3RhdGUoKTtcbiAgICAgIHRoaXMuaXNUb29sYmFySW50ZXJhY3RpbmcoKTtcbiAgICAgIC8vIEFsc28gcmVhY3QgdG8gbGluay9jb2xvciBlZGl0IG1vZGVzIHRvIGhpZGUgd2hlbiBzdWItbWVudXMgYWN0aXZhdGVcbiAgICAgIHRoaXMuZWRpdG9yQ29tbWFuZHMubGlua0VkaXRNb2RlKCk7XG4gICAgICB0aGlzLmVkaXRvckNvbW1hbmRzLmNvbG9yRWRpdE1vZGUoKTtcblxuICAgICAgdGhpcy51cGRhdGVNZW51KCk7XG4gICAgfSwgeyBhbGxvd1NpZ25hbFdyaXRlczogdHJ1ZSB9KTtcbiAgfVxuXG4gIG5nT25Jbml0KCkge1xuICAgIHRoaXMuaW5pdFRpcHB5KCk7XG4gIH1cblxuICBuZ09uRGVzdHJveSgpIHtcbiAgICBpZiAodGhpcy51cGRhdGVUaW1lb3V0KSB7XG4gICAgICBjbGVhclRpbWVvdXQodGhpcy51cGRhdGVUaW1lb3V0KTtcbiAgICB9XG4gICAgaWYgKHRoaXMudGlwcHlJbnN0YW5jZSkge1xuICAgICAgdGhpcy50aXBweUluc3RhbmNlLmRlc3Ryb3koKTtcbiAgICAgIHRoaXMudGlwcHlJbnN0YW5jZSA9IG51bGw7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEluaXRpYWxpemVzIHRoZSBUaXBweSBpbnN0YW5jZS5cbiAgICogQ2FuIGJlIG92ZXJyaWRkZW4gZm9yIHNwZWNpZmljIFRpcHB5IGNvbmZpZ3VyYXRpb25zLlxuICAgKi9cbiAgcHJvdGVjdGVkIGluaXRUaXBweSgpIHtcbiAgICBpZiAoIXRoaXMubWVudVJlZigpPy5uYXRpdmVFbGVtZW50KSB7XG4gICAgICAvLyBSZS10cnkgaWYgdGhlIHZpZXcgY2hpbGQgaXMgbm90IHlldCBhdmFpbGFibGVcbiAgICAgIHNldFRpbWVvdXQoKCkgPT4gdGhpcy5pbml0VGlwcHkoKSwgNTApO1xuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIGNvbnN0IGVkID0gdGhpcy5lZGl0b3IoKTtcbiAgICBpZiAodGhpcy50aXBweUluc3RhbmNlKSB7XG4gICAgICB0aGlzLnRpcHB5SW5zdGFuY2UuZGVzdHJveSgpO1xuICAgIH1cblxuICAgIHRoaXMudGlwcHlJbnN0YW5jZSA9IHRpcHB5KGRvY3VtZW50LmJvZHksIHtcbiAgICAgIGNvbnRlbnQ6IHRoaXMubWVudVJlZigpLm5hdGl2ZUVsZW1lbnQsXG4gICAgICB0cmlnZ2VyOiBcIm1hbnVhbFwiLFxuICAgICAgcGxhY2VtZW50OiBcInRvcC1zdGFydFwiLFxuICAgICAgYXBwZW5kVG86ICgpID0+IChlZCA/IChlZC5vcHRpb25zLmVsZW1lbnQgYXMgSFRNTEVsZW1lbnQpIDogZG9jdW1lbnQuYm9keSksXG4gICAgICBpbnRlcmFjdGl2ZTogdHJ1ZSxcbiAgICAgIGhpZGVPbkNsaWNrOiBmYWxzZSxcbiAgICAgIGFycm93OiBmYWxzZSxcbiAgICAgIG9mZnNldDogWzAsIDhdLFxuICAgICAgcGx1Z2luczogW3N0aWNreV0sXG4gICAgICBzdGlja3k6IGZhbHNlLFxuICAgICAgZ2V0UmVmZXJlbmNlQ2xpZW50UmVjdDogKCkgPT4gdGhpcy5nZXRTZWxlY3Rpb25SZWN0KCksXG4gICAgICBwb3BwZXJPcHRpb25zOiB7XG4gICAgICAgIG1vZGlmaWVyczogW1xuICAgICAgICAgIHtcbiAgICAgICAgICAgIG5hbWU6IFwicHJldmVudE92ZXJmbG93XCIsXG4gICAgICAgICAgICBvcHRpb25zOiB7XG4gICAgICAgICAgICAgIGJvdW5kYXJ5OiB0aGlzLmVkaXRvcigpLm9wdGlvbnMuZWxlbWVudCxcbiAgICAgICAgICAgICAgcGFkZGluZzogOCxcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgfSxcbiAgICAgICAgICB7XG4gICAgICAgICAgICBuYW1lOiBcImZsaXBcIixcbiAgICAgICAgICAgIG9wdGlvbnM6IHtcbiAgICAgICAgICAgICAgZmFsbGJhY2tQbGFjZW1lbnRzOiBbXCJib3R0b20tc3RhcnRcIiwgXCJ0b3AtZW5kXCIsIFwiYm90dG9tLWVuZFwiXSxcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgfSxcbiAgICAgICAgXSxcbiAgICAgIH0sXG4gICAgICBvblNob3c6IGluc3RhbmNlID0+IHRoaXMub25UaXBweVNob3coaW5zdGFuY2UpLFxuICAgICAgb25IaWRlOiBpbnN0YW5jZSA9PiB0aGlzLm9uVGlwcHlIaWRlKGluc3RhbmNlKSxcbiAgICB9KTtcblxuICAgIHRoaXMudXBkYXRlTWVudSgpO1xuICB9XG5cbiAgLyoqXG4gICAqIENvcmUgbG9naWMgdG8gZGVjaWRlIHdoZXRoZXIgdG8gc2hvdyBvciBoaWRlIHRoZSBtZW51LlxuICAgKi9cbiAgdXBkYXRlTWVudSA9ICgpID0+IHtcbiAgICBpZiAodGhpcy51cGRhdGVUaW1lb3V0KSB7XG4gICAgICBjbGVhclRpbWVvdXQodGhpcy51cGRhdGVUaW1lb3V0KTtcbiAgICB9XG5cbiAgICB0aGlzLnVwZGF0ZVRpbWVvdXQgPSB3aW5kb3cuc2V0VGltZW91dCgoKSA9PiB7XG4gICAgICBjb25zdCBlZCA9IHRoaXMuZWRpdG9yKCk7XG4gICAgICBpZiAoIWVkKSB7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cblxuICAgICAgLy8gSGlkZSB3aGVuIGludGVyYWN0aW5nIHdpdGggdGhlIG1haW4gdG9vbGJhclxuICAgICAgaWYgKHRoaXMuaXNUb29sYmFySW50ZXJhY3RpbmcoKSkge1xuICAgICAgICB0aGlzLmhpZGVUaXBweSgpO1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG5cbiAgICAgIGlmICh0aGlzLnNob3VsZFNob3coKSkge1xuICAgICAgICB0aGlzLnNob3dUaXBweSgpO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdGhpcy5oaWRlVGlwcHkoKTtcbiAgICAgIH1cbiAgICB9LCAxMCk7XG4gIH07XG5cbiAgLyoqXG4gICAqIEhlbHBlciB0byBzaG93IHRoZSBUaXBweSBpbnN0YW5jZSB3aXRoIHVwZGF0ZWQgcG9zaXRpb25pbmcuXG4gICAqL1xuICBzaG93VGlwcHkoKSB7XG4gICAgaWYgKCF0aGlzLnRpcHB5SW5zdGFuY2UpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICAvLyBVcGRhdGUgcG9zaXRpb24gYmVmb3JlIHNob3dpbmdcbiAgICB0aGlzLnRpcHB5SW5zdGFuY2Uuc2V0UHJvcHMoe1xuICAgICAgZ2V0UmVmZXJlbmNlQ2xpZW50UmVjdDogKCkgPT4gdGhpcy5nZXRTZWxlY3Rpb25SZWN0KCksXG4gICAgICBzdGlja3k6IFwicmVmZXJlbmNlXCIsXG4gICAgfSk7XG4gICAgdGhpcy50aXBweUluc3RhbmNlLnNob3coKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBIZWxwZXIgdG8gaGlkZSB0aGUgVGlwcHkgaW5zdGFuY2UuXG4gICAqL1xuICBoaWRlVGlwcHkoKSB7XG4gICAgaWYgKHRoaXMudGlwcHlJbnN0YW5jZSkge1xuICAgICAgdGhpcy50aXBweUluc3RhbmNlLnNldFByb3BzKHsgc3RpY2t5OiBmYWxzZSB9KTtcbiAgICAgIHRoaXMudGlwcHlJbnN0YW5jZS5oaWRlKCk7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIENvbW1vbiBjb21tYW5kIGhhbmRsZXIgZm9yIGJ1dHRvbnMgaW4gdGhlIG1lbnUuXG4gICAqL1xuICBvbkNvbW1hbmQoY29tbWFuZDogc3RyaW5nLCBldmVudD86IEV2ZW50KSB7XG4gICAgaWYgKGV2ZW50KSB7XG4gICAgICBldmVudC5wcmV2ZW50RGVmYXVsdCgpO1xuICAgICAgZXZlbnQuc3RvcFByb3BhZ2F0aW9uKCk7XG4gICAgfVxuICAgIGNvbnN0IGVkID0gdGhpcy5lZGl0b3IoKTtcbiAgICBpZiAoZWQpIHtcbiAgICAgIHRoaXMuZXhlY3V0ZUNvbW1hbmQoZWQsIGNvbW1hbmQpO1xuICAgICAgdGhpcy51cGRhdGVNZW51KCk7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFNoYXJlZCBoZWxwZXIgdG8gZ2V0IHRoZSBib3VuZGluZyByZWN0IG9mIHRoZSBjdXJyZW50IHNlbGVjdGlvbi5cbiAgICogVXNlcyBhIGNvbWJpbmF0aW9uIG9mIG5hdGl2ZSBzZWxlY3Rpb24gYW5kIFByb3NlTWlycm9yIGNvb3JkaW5hdGVzLlxuICAgKi9cbiAgcHJvdGVjdGVkIGdldFJlY3RGb3JTZWxlY3Rpb24oZWQ6IEVkaXRvcik6IERPTVJlY3Qge1xuICAgIGlmICghZWQpIHtcbiAgICAgIHJldHVybiBuZXcgRE9NUmVjdCgwLCAwLCAwLCAwKTtcbiAgICB9XG5cbiAgICBjb25zdCB7IGZyb20sIHRvLCBlbXB0eSB9ID0gZWQuc3RhdGUuc2VsZWN0aW9uO1xuICAgIGlmIChlbXB0eSkge1xuICAgICAgcmV0dXJuIG5ldyBET01SZWN0KC05OTk5LCAtOTk5OSwgMCwgMCk7XG4gICAgfVxuXG4gICAgLy8gMS4gVHJ5IG5hdGl2ZSBzZWxlY3Rpb24gZm9yIG11bHRpLWxpbmUgYWNjdXJhY3lcbiAgICBjb25zdCBzZWxlY3Rpb24gPSB3aW5kb3cuZ2V0U2VsZWN0aW9uKCk7XG4gICAgaWYgKHNlbGVjdGlvbiAmJiBzZWxlY3Rpb24ucmFuZ2VDb3VudCA+IDApIHtcbiAgICAgIGNvbnN0IHJhbmdlID0gc2VsZWN0aW9uLmdldFJhbmdlQXQoMCk7XG4gICAgICBjb25zdCByZWN0ID0gcmFuZ2UuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7XG5cbiAgICAgIC8vIEVuc3VyZSB0aGUgcmVjdCBpcyB2YWxpZCBhbmQgYmVsb25ncyB0byB0aGUgZWRpdG9yXG4gICAgICBpZiAocmVjdC53aWR0aCA+IDAgJiYgcmVjdC5oZWlnaHQgPiAwKSB7XG4gICAgICAgIHJldHVybiByZWN0O1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vIDIuIEZhbGxiYWNrIHRvIFRpcHRhcCBjb29yZGluYXRlcyBmb3IgcHJlY2lzaW9uIChzaW5nbGUgbGluZSAvIGVkZ2UgY2FzZXMpXG4gICAgY29uc3Qgc3RhcnQgPSBlZC52aWV3LmNvb3Jkc0F0UG9zKGZyb20pO1xuICAgIGNvbnN0IGVuZCA9IGVkLnZpZXcuY29vcmRzQXRQb3ModG8pO1xuXG4gICAgY29uc3QgdG9wID0gTWF0aC5taW4oc3RhcnQudG9wLCBlbmQudG9wKTtcbiAgICBjb25zdCBib3R0b20gPSBNYXRoLm1heChzdGFydC5ib3R0b20sIGVuZC5ib3R0b20pO1xuICAgIGNvbnN0IGxlZnQgPSBNYXRoLm1pbihzdGFydC5sZWZ0LCBlbmQubGVmdCk7XG4gICAgY29uc3QgcmlnaHQgPSBNYXRoLm1heChzdGFydC5yaWdodCwgZW5kLnJpZ2h0KTtcblxuICAgIHJldHVybiBuZXcgRE9NUmVjdChsZWZ0LCB0b3AsIHJpZ2h0IC0gbGVmdCwgYm90dG9tIC0gdG9wKTtcbiAgfVxuXG4gIC8vIC0tLSBBYnN0cmFjdCBtZXRob2RzIHRvIGJlIGltcGxlbWVudGVkIGJ5IHN1Yi1jb21wb25lbnRzIC0tLVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHRoZSBSZWN0IHJlcHJlc2VudGluZyB0aGUgdGFyZ2V0IGZvciBwb3NpdGlvbmluZyB0aGUgbWVudS5cbiAgICovXG4gIGFic3RyYWN0IGdldFNlbGVjdGlvblJlY3QoKTogRE9NUmVjdDtcblxuICAvKipcbiAgICogUmV0dXJucyB3aGV0aGVyIHRoZSBtZW51IHNob3VsZCBiZSBkaXNwbGF5ZWQgYmFzZWQgb24gY3VycmVudCBzdGF0ZS5cbiAgICovXG4gIGFic3RyYWN0IHNob3VsZFNob3coKTogYm9vbGVhbjtcblxuICAvKipcbiAgICogRXhlY3V0ZXMgdGhlIGdpdmVuIGNvbW1hbmQgb24gdGhlIGVkaXRvci5cbiAgICovXG4gIHByb3RlY3RlZCBhYnN0cmFjdCBleGVjdXRlQ29tbWFuZChlZGl0b3I6IEVkaXRvciwgY29tbWFuZDogc3RyaW5nLCAuLi5hcmdzOiB1bmtub3duW10pOiB2b2lkO1xuXG4gIC8qKlxuICAgKiBPcHRpb25hbCBob29rIGNhbGxlZCB3aGVuIFRpcHB5IGlzIGFib3V0IHRvIGJlIHNob3duLlxuICAgKi9cbiAgcHJvdGVjdGVkIG9uVGlwcHlTaG93KF9pbnN0YW5jZTogVGlwcHlJbnN0YW5jZSkge1xuICAgIC8qIGVtcHR5ICovXG4gIH1cblxuICAvKipcbiAgICogT3B0aW9uYWwgaG9vayBjYWxsZWQgd2hlbiBUaXBweSBpcyBhYm91dCB0byBiZSBoaWRkZW4uXG4gICAqL1xuICBwcm90ZWN0ZWQgb25UaXBweUhpZGUoX2luc3RhbmNlOiBUaXBweUluc3RhbmNlKSB7XG4gICAgLyogZW1wdHkgKi9cbiAgfVxufVxuIl19