@flogeez/angular-tiptap-editor 2.0.3 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,18 @@ 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).
7
7
 
8
+ ## [2.1.0] - 2026-01-20
9
+
10
+ ### Added
11
+ - **Unified Configuration**: Introduced `AteEditorConfig`, a single flatter interface to manage all editor settings (fundamentals, display options, and modules) via a new `[config]` input.
12
+ - **Enhanced Image Upload Config**: Restructured image upload settings with dedicated `AteImageUploadConfig`, supporting quality, dimensions, max size (in MB), and allowed types.
13
+ - **Menu Visibility Controls**: Added root-level boolean flags to toggle specific bubble menus (`showBubbleMenu`, `showTableMenu`, `showCellMenu`, `showImageBubbleMenu`) and slash commands (`enableSlashCommands`).
14
+ - **Improved Developer Experience**: Updated the demo's code generator to produce consolidated `AteEditorConfig` boilerplate, making it easier for developers to copy-paste configurations.
15
+
16
+ ### Changed
17
+ - **Configuration Precedence**: Standardized the merging logic across all components, ensuring individual inputs correctly override global configuration values while maintaining sensible defaults.
18
+ - **Public API**: Exported `AteEditorConfig` in the public API for better type safety in host applications.
19
+
8
20
  ## [2.0.3] - 2026-01-19
9
21
 
10
22
  ### Added
package/README.md CHANGED
@@ -88,12 +88,14 @@ export class ExampleComponent {
88
88
 
89
89
  ### 2. With Custom Configuration
90
90
 
91
+ The editor can be fully configured using a single `[config]` object, which provides a clean and type-safe way to manage all settings.
92
+
91
93
  ```typescript
92
94
  import { Component } from "@angular/core";
93
95
  import {
94
96
  AngularTiptapEditorComponent,
97
+ AteEditorConfig,
95
98
  DEFAULT_TOOLBAR_CONFIG,
96
- DEFAULT_BUBBLE_MENU_CONFIG,
97
99
  } from "@flogeez/angular-tiptap-editor";
98
100
 
99
101
  @Component({
@@ -103,14 +105,7 @@ import {
103
105
  template: `
104
106
  <angular-tiptap-editor
105
107
  [content]="content"
106
- [toolbar]="toolbarConfig"
107
- [bubbleMenu]="bubbleMenuConfig"
108
- [slashCommands]="slashCommandsConfig"
109
- [locale]="'en'"
110
- [height]="400"
111
- [showCharacterCount]="true"
112
- [showWordCount]="true"
113
- [maxCharacters]="500"
108
+ [config]="editorConfig"
114
109
  (contentChange)="onContentChange($event)"
115
110
  />
116
111
  `,
@@ -118,27 +113,29 @@ import {
118
113
  export class AdvancedComponent {
119
114
  content = "<h1>Welcome!</h1><p>Start editing...</p>";
120
115
 
121
- // Use default configurations as base
122
- toolbarConfig = {
123
- ...DEFAULT_TOOLBAR_CONFIG,
124
- clear: true, // Add clear button
125
- };
126
-
127
- bubbleMenuConfig = {
128
- ...DEFAULT_BUBBLE_MENU_CONFIG,
129
- table: true, // Enable table bubble menu
130
- };
131
-
132
- // No config needed if you want all commands enabled
133
- slashCommandsConfig = {
134
- image: true,
135
- table: true,
136
- heading1: true
116
+ editorConfig: AteEditorConfig = {
117
+ locale: "fr", // Force French (default is auto-detect)
118
+ height: "400px", // Set a fixed height
119
+ placeholder: "Commencez à rédiger...",
120
+ showWordCount: false, // Hide the word counter (default is true)
121
+ showEditToggle: true, // Show the button to toggle read-only mode (default is false)
122
+
123
+ // Customize the toolbar by enabling specific features
124
+ toolbar: {
125
+ ...DEFAULT_TOOLBAR_CONFIG,
126
+ clear: true, // Enable the 'Clear' button
127
+ highlight: false, // Disable the highlight button
128
+ },
129
+
130
+ // Only enable specific slash commands
131
+ slashCommands: {
132
+ heading1: true,
133
+ heading2: true,
134
+ image: true,
135
+ table: true,
136
+ },
137
137
  };
138
138
 
139
- // Available keys: "heading1", "heading2", "heading3", "bulletList",
140
- // "orderedList", "blockquote", "code", "image", "horizontalRule", "table"
141
-
142
139
  onContentChange(newContent: string) {
143
140
  this.content = newContent;
144
141
  }
@@ -221,9 +218,7 @@ import { EditorCommandsService } from "@flogeez/angular-tiptap-editor";
221
218
  <button (click)="setContent()">Set Content</button>
222
219
  </div>
223
220
 
224
- <angular-tiptap-editor
225
- (editorCreated)="onEditorCreated($event)"
226
- />
221
+ <angular-tiptap-editor (editorCreated)="onEditorCreated($event)" />
227
222
  </div>
228
223
  `,
229
224
  })
@@ -251,7 +246,7 @@ export class CommandsComponent {
251
246
  if (this.editor) {
252
247
  this.editorCommandsService.setContent(
253
248
  this.editor,
254
- "<h1>New Content</h1>"
249
+ "<h1>New Content</h1>",
255
250
  );
256
251
  }
257
252
  }
@@ -266,14 +261,15 @@ The editor features a dual-layer state architecture: **Automatic Tracking** for
266
261
 
267
262
  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.
268
263
 
269
- * **For Marks**: `state().marks.yourExtensionName` (boolean) and `state().can.toggleYourExtensionName` (boolean).
270
- * **For Nodes**: `state().nodes.yourExtensionName` (boolean).
264
+ - **For Marks**: `state().marks.yourExtensionName` (boolean) and `state().can.toggleYourExtensionName` (boolean).
265
+ - **For Nodes**: `state().nodes.yourExtensionName` (boolean).
271
266
 
272
267
  #### B. Custom State Calculators (Advanced)
273
268
 
274
269
  If you need to extract complex data (like attributes, depth, or custom logic), you can provide a custom `StateCalculator`.
275
270
 
276
271
  1. **Define a Calculator**:
272
+
277
273
  ```typescript
278
274
  import { StateCalculator } from "@flogeez/angular-tiptap-editor";
279
275
 
@@ -281,27 +277,27 @@ import { StateCalculator } from "@flogeez/angular-tiptap-editor";
281
277
  export const MyCustomCalculator: StateCalculator = (editor) => {
282
278
  return {
283
279
  custom: {
284
- hasHighPriority: editor.isActive('priority'),
280
+ hasHighPriority: editor.isActive("priority"),
285
281
  selectionDepth: editor.state.selection.$from.depth,
286
282
  // Any data you need...
287
- }
283
+ },
288
284
  };
289
285
  };
290
286
  ```
291
287
 
292
288
  2. **Register it in the Template**:
289
+
293
290
  ```html
294
- <angular-tiptap-editor
295
- [stateCalculators]="[MyCustomCalculator]"
296
- />
291
+ <angular-tiptap-editor [stateCalculators]="[MyCustomCalculator]" />
297
292
  ```
298
293
 
299
294
  3. **Consume it anywhere**:
295
+
300
296
  ```typescript
301
297
  @Component({ ... })
302
298
  export class MyToolbarComponent {
303
299
  private editorCommands = inject(EditorCommandsService);
304
-
300
+
305
301
  // Access your custom data reactively!
306
302
  isHighPriority = computed(() => this.editorCommands.editorState().custom?.hasHighPriority);
307
303
  }
@@ -343,13 +339,13 @@ slashCommands: SlashCommandsConfig = {
343
339
  // Add custom ones
344
340
  custom: [
345
341
  {
346
- title: 'Magic Action',
347
- description: 'Insert some AI magic',
348
- icon: 'auto_fix',
349
- keywords: ['magic', 'ai'],
350
- command: (editor) => editor.commands.insertContent('✨ Magic happened!')
351
- }
352
- ]
342
+ title: "Magic Action",
343
+ description: "Insert some AI magic",
344
+ icon: "auto_fix",
345
+ keywords: ["magic", "ai"],
346
+ command: (editor) => editor.commands.insertContent("✨ Magic happened!"),
347
+ },
348
+ ],
353
349
  };
354
350
  ```
355
351
 
@@ -373,16 +369,16 @@ The handler can return either an **Observable** or a **Promise**.
373
369
  #### Using Observable (recommended for Angular)
374
370
 
375
371
  ```typescript
376
- import { Component, inject } from '@angular/core';
377
- import { HttpClient } from '@angular/common/http';
378
- import { map } from 'rxjs/operators';
372
+ import { Component, inject } from "@angular/core";
373
+ import { HttpClient } from "@angular/common/http";
374
+ import { map } from "rxjs/operators";
379
375
  import {
380
376
  AngularTiptapEditorComponent,
381
- ImageUploadHandler
382
- } from '@flogeez/angular-tiptap-editor';
377
+ ImageUploadHandler,
378
+ } from "@flogeez/angular-tiptap-editor";
383
379
 
384
380
  @Component({
385
- selector: 'app-custom-upload',
381
+ selector: "app-custom-upload",
386
382
  standalone: true,
387
383
  imports: [AngularTiptapEditorComponent],
388
384
  template: `
@@ -391,19 +387,19 @@ import {
391
387
  [imageUploadHandler]="uploadHandler"
392
388
  (contentChange)="onContentChange($event)"
393
389
  />
394
- `
390
+ `,
395
391
  })
396
392
  export class CustomUploadComponent {
397
393
  private http = inject(HttpClient);
398
- content = '';
394
+ content = "";
399
395
 
400
396
  uploadHandler: ImageUploadHandler = (ctx) => {
401
397
  const formData = new FormData();
402
- formData.append('image', ctx.file);
398
+ formData.append("image", ctx.file);
403
399
 
404
- return this.http.post<{ url: string }>('/api/upload', formData).pipe(
405
- map(result => ({ src: result.url }))
406
- );
400
+ return this.http
401
+ .post<{ url: string }>("/api/upload", formData)
402
+ .pipe(map((result) => ({ src: result.url })));
407
403
  };
408
404
 
409
405
  onContentChange(newContent: string) {
@@ -417,10 +413,10 @@ export class CustomUploadComponent {
417
413
  ```typescript
418
414
  uploadHandler: ImageUploadHandler = async (ctx) => {
419
415
  const formData = new FormData();
420
- formData.append('image', ctx.file);
416
+ formData.append("image", ctx.file);
421
417
 
422
418
  const result = await firstValueFrom(
423
- this.http.post<{ url: string }>('/api/upload', formData)
419
+ this.http.post<{ url: string }>("/api/upload", formData),
424
420
  );
425
421
 
426
422
  return { src: result.url };
@@ -428,6 +424,7 @@ uploadHandler: ImageUploadHandler = async (ctx) => {
428
424
  ```
429
425
 
430
426
  The `ImageUploadContext` provides:
427
+
431
428
  - `file`: The original File object
432
429
  - `width`: Processed image width
433
430
  - `height`: Processed image height
@@ -438,7 +435,6 @@ The handler must return an `ImageUploadHandlerResult` with at least a `src` prop
438
435
 
439
436
  ---
440
437
 
441
-
442
438
  ### 📝 Word & Character Counting
443
439
 
444
440
  Real-time content statistics:
@@ -471,38 +467,101 @@ Open [http://localhost:4200](http://localhost:4200) to view the demo.
471
467
 
472
468
  #### Inputs
473
469
 
474
- | Input | Type | Default | Description |
475
- | -------------------- | -------------------------------------- | ------------------- | -------------------------------- |
476
- | `content` | `string` | `""` | Initial HTML content |
477
- | `placeholder` | `string` | `"Start typing..."` | Placeholder text |
478
- | `locale` | `'en' \| 'fr'` | Auto-detect | Editor language |
479
- | `editable` | `boolean` | `true` | Whether editor is editable |
480
- | `height` | `number` | `undefined` | Fixed height in pixels |
481
- | `maxHeight` | `number` | `undefined` | Maximum height in pixels |
482
- | `minHeight` | `number` | `200` | Minimum height in pixels |
483
- | `maxCharacters` | `number` | `undefined` | Character limit |
484
- | `fillContainer` | `boolean` | `false` | Fill parent container height |
485
- | `autofocus` | `boolean \| 'start' \| 'end' \| 'all'` | `false` | Auto-focus behavior |
486
- | `showToolbar` | `boolean` | `true` | Show toolbar |
487
- | `showBubbleMenu` | `boolean` | `true` | Show text bubble menu |
488
- | `showImageBubbleMenu`| `boolean` | `true` | Show image bubble menu |
489
- | `showTableBubbleMenu`| `boolean` | `true` | Show table bubble menu |
490
- | `showCellBubbleMenu` | `boolean` | `true` | Show cell bubble menu |
491
- | `enableSlashCommands`| `boolean` | `true` | Enable slash commands functionality|
492
- | `enableOfficePaste` | `boolean` | `true` | Enable smart Office pasting |
493
- | `showCharacterCount` | `boolean` | `true` | Show character counter |
494
- | `showWordCount` | `boolean` | `true` | Show word counter |
495
- | `toolbar` | `ToolbarConfig` | All enabled | Toolbar configuration |
496
- | `bubbleMenu` | `BubbleMenuConfig` | All enabled | Bubble menu configuration |
497
- | `imageBubbleMenu` | `ImageBubbleMenuConfig` | All enabled | Image bubble menu config |
498
- | `tableBubbleMenu` | `TableBubbleMenuConfig` | All enabled | Table bubble menu config |
499
- | `cellBubbleMenu` | `CellBubbleMenuConfig` | All enabled | Cell bubble menu config |
500
- | `slashCommands` | `SlashCommandsConfig` | All enabled | Slash commands configuration |
501
- | `imageUploadHandler` | `ImageUploadHandler` | `undefined` | Custom image upload function |
502
- | `stateCalculators` | `StateCalculator[]` | `[]` | Custom reactive state logic |
503
- | `tiptapExtensions` | `(Extension \| Node \| Mark)[]` | `[]` | Additional Tiptap extensions |
504
- | `tiptapOptions` | `Partial<EditorOptions>` | `{}` | Additional Tiptap editor options |
470
+ | Input | Type | Default | Description |
471
+ | --------------------- | ------------------------------------------------ | ------------------- | --------------------------------------------- |
472
+ | `config` | `AteEditorConfig` | `{}` | **Global configuration object** (Recommended) |
473
+ | `content` | `string` | `""` | Initial HTML content |
474
+ | `placeholder` | `string` | `"Start typing..."` | Placeholder text (overrides config) |
475
+ | `locale` | `'en' \| 'fr'` | Auto-detect | Editor language (overrides config) |
476
+ | `editable` | `boolean` | `true` | Whether editor is editable |
477
+ | `height` | `string` | `undefined` | Editor height (e.g. '400px', 'auto') |
478
+ | `maxHeight` | `string` | `undefined` | Maximum height (e.g. '80vh') |
479
+ | `minHeight` | `string` | `undefined` | Minimum height |
480
+ | `maxCharacters` | `number` | `undefined` | Character limit |
481
+ | `fillContainer` | `boolean` | `false` | Fill parent container height |
482
+ | `autofocus` | `boolean \| 'start' \| 'end' \| 'all' \| number` | `false` | Auto-focus behavior |
483
+ | `disabled` | `boolean` | `false` | Disabled state (for forms) |
484
+ | `spellcheck` | `boolean` | `true` | Enable browser spellcheck |
485
+ | `showToolbar` | `boolean` | `true` | Show toolbar |
486
+ | `showFooter` | `boolean` | `true` | Show footer (counters) |
487
+ | `showBubbleMenu` | `boolean` | `true` | Show text bubble menu |
488
+ | `showImageBubbleMenu` | `boolean` | `true` | Show image bubble menu |
489
+ | `showTableMenu` | `boolean` | `true` | Show table bubble menu |
490
+ | `showCellMenu` | `boolean` | `true` | Show cell bubble menu |
491
+ | `enableSlashCommands` | `boolean` | `true` | Enable slash commands functionality |
492
+ | `enableOfficePaste` | `boolean` | `true` | Enable smart Office pasting |
493
+ | `showCharacterCount` | `boolean` | `true` | Show character counter |
494
+ | `showWordCount` | `boolean` | `true` | Show word counter |
495
+ | `toolbar` | `ToolbarConfig` | All enabled | Detailed toolbar configuration |
496
+ | `bubbleMenu` | `BubbleMenuConfig` | All enabled | Detailed bubble menu configuration |
497
+ | `slashCommands` | `SlashCommandsConfig` | All enabled | Detailed slash commands config |
498
+ | `imageUploadHandler` | `ImageUploadHandler` | `undefined` | Custom image upload function |
499
+ | `stateCalculators` | `StateCalculator[]` | `[]` | Custom reactive state logic |
500
+ | `tiptapExtensions` | `(Extension \| Node \| Mark)[]` | `[]` | Additional Tiptap extensions |
501
+ | `tiptapOptions` | `Partial<EditorOptions>` | `{}` | Additional Tiptap editor options |
502
+
503
+ > **Note on Precedence**: Values provided in individual inputs (e.g., `[editable]="false"`) always take precedence over values defined inside the `[config]` object.
504
+
505
+ #### AteEditorConfig Reference
506
+
507
+ The `AteEditorConfig` nested structure allows for complex configurations while remaining flat for core settings:
505
508
 
509
+ ```typescript
510
+ export interface AteEditorConfig {
511
+ // Core Settings
512
+ theme?: "light" | "dark" | "auto";
513
+ height?: string;
514
+ minHeight?: string;
515
+ maxHeight?: string;
516
+ fillContainer?: boolean;
517
+ autofocus?: "start" | "end" | "all" | boolean | number;
518
+ placeholder?: string;
519
+ editable?: boolean;
520
+ disabled?: boolean;
521
+ locale?: string;
522
+ spellcheck?: boolean;
523
+ enableOfficePaste?: boolean;
524
+
525
+ // Visibility Options
526
+ showToolbar?: boolean;
527
+ showFooter?: boolean;
528
+ showCharacterCount?: boolean;
529
+ showWordCount?: boolean;
530
+ showEditToggle?: boolean;
531
+ showBubbleMenu?: boolean;
532
+ showImageBubbleMenu?: boolean;
533
+ showTableMenu?: boolean;
534
+ showCellMenu?: boolean;
535
+ enableSlashCommands?: boolean;
536
+ maxCharacters?: number;
537
+
538
+ // Complex Modules
539
+ toolbar?: ToolbarConfig;
540
+ bubbleMenu?: BubbleMenuConfig;
541
+ imageBubbleMenu?: ImageBubbleMenuConfig;
542
+ tableBubbleMenu?: TableBubbleMenuConfig;
543
+ cellBubbleMenu?: CellBubbleMenuConfig;
544
+ slashCommands?: SlashCommandsConfig;
545
+ imageUpload?: AteImageUploadConfig;
546
+ }
547
+ ```
548
+
549
+ #### Image Upload Configuration
550
+
551
+ The `imageUpload` property in `AteEditorConfig` provides fine-grained control over the image processing pipeline:
552
+
553
+ ```typescript
554
+ export interface AteImageUploadConfig {
555
+ /** Custom handler to upload files to a server */
556
+ handler?: ImageUploadHandler;
557
+ /** Maximum file size in bytes (default: 10MB) */
558
+ maxFileSize?: number;
559
+ /** Accepted file types (default: 'image/*') */
560
+ accept?: string;
561
+ /** Whether to automatically compress images before upload (default: true) */
562
+ autoCompress?: boolean;
563
+ }
564
+ ```
506
565
 
507
566
  #### Outputs
508
567
 
@@ -514,7 +573,6 @@ Open [http://localhost:4200](http://localhost:4200) to view the demo.
514
573
  | `editorFocus` | `{editor, event}` | Emitted when editor gains focus |
515
574
  | `editorBlur` | `{editor, event}` | Emitted when editor loses focus |
516
575
 
517
-
518
576
  ## 🌍 Internationalization
519
577
 
520
578
  The editor comes with built-in support for **English (en)** and **French (fr)**, featuring automatic browser language detection.
@@ -544,14 +602,13 @@ export class MyComponent {
544
602
  toolbar: { bold: 'Negrita', italic: 'Cursiva', ... },
545
603
  editor: { placeholder: 'Empieza a escribir...' }
546
604
  });
547
-
605
+
548
606
  // Switch to Spanish
549
607
  this.i18nService.setLocale('es');
550
608
  }
551
609
  }
552
610
  ```
553
611
 
554
-
555
612
  ### 🎨 CSS Custom Properties
556
613
 
557
614
  Customize the editor appearance using CSS variables with the `--ate-` prefix:
@@ -562,17 +619,25 @@ angular-tiptap-editor {
562
619
  --ate-primary: #2563eb;
563
620
  --ate-primary-contrast: #ffffff;
564
621
  --ate-primary-light: color-mix(in srgb, var(--ate-primary), transparent 90%);
565
- --ate-primary-lighter: color-mix(in srgb, var(--ate-primary), transparent 95%);
566
- --ate-primary-light-alpha: color-mix(in srgb, var(--ate-primary), transparent 85%);
567
-
622
+ --ate-primary-lighter: color-mix(
623
+ in srgb,
624
+ var(--ate-primary),
625
+ transparent 95%
626
+ );
627
+ --ate-primary-light-alpha: color-mix(
628
+ in srgb,
629
+ var(--ate-primary),
630
+ transparent 85%
631
+ );
632
+
568
633
  --ate-surface: #ffffff;
569
634
  --ate-surface-secondary: #f8f9fa;
570
635
  --ate-surface-tertiary: #f1f5f9;
571
-
636
+
572
637
  --ate-text: #2d3748;
573
638
  --ate-text-secondary: #64748b;
574
639
  --ate-text-muted: #a0aec0;
575
-
640
+
576
641
  --ate-border: #e2e8f0;
577
642
 
578
643
  /* And More... */
@@ -621,7 +686,6 @@ angular-tiptap-editor {
621
686
  }
622
687
  ```
623
688
 
624
-
625
689
  ### ⚡ Reactive State & OnPush
626
690
 
627
691
  The library exposes a reactive `editorState` signal via the `EditorCommandsService`. This signal contains everything you need to build custom UIs around the editor:
@@ -711,7 +775,6 @@ This runs the library in watch mode and starts the demo application.
711
775
  - `npm run watch:lib` - Watch library changes
712
776
  - `npm run dev` - Development mode (watch + serve)
713
777
 
714
-
715
778
  ## 📝 License
716
779
 
717
780
  MIT License - see [LICENSE](LICENSE) file for details.
@@ -733,10 +796,8 @@ Contributions are welcome! Please feel free to submit a Pull Request.
733
796
 
734
797
  ### Latest Updates
735
798
 
736
- - ✅ **Reactive State & Signals**: Optimized state management for a faster, smoother experience.
737
- - ✅ **Zero-Config Extensions**: Custom Tiptap Marks and Nodes are tracked automatically.
738
- - ✅ **Multi-Instance Support**: Use multiple independent editors on the same page without state leaks.
739
- - ✅ **Clean Service Architecture**: Decoupled configurations and isolated services for better stability.
799
+ - ✅ **Unified Configuration**: New unified `AteEditorConfig` system for cleaner, type-safe editor setup.
800
+ - ✅ **Enhanced Image Upload**: Advanced image handling with custom upload handlers and auto-compression.
740
801
  - ✅ **Refactored Link Management**: Dedicated link bubble menu with smart UI anchoring and real-time URL sync.
741
802
 
742
803
  ---