@flogeez/angular-tiptap-editor 2.0.1 → 2.0.3
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 +256 -242
- package/LICENSE +21 -21
- package/README.md +744 -744
- package/fesm2022/flogeez-angular-tiptap-editor.mjs +1790 -1722
- package/fesm2022/flogeez-angular-tiptap-editor.mjs.map +1 -1
- package/index.d.ts +8 -1
- package/package.json +2 -1
- package/src/lib/styles/bubble-menu.global.css +52 -52
- package/src/lib/styles/index.css +40 -40
- package/src/lib/styles/material-symbols.css +17 -17
package/README.md
CHANGED
|
@@ -1,744 +1,744 @@
|
|
|
1
|
-
# Angular Tiptap Editor
|
|
2
|
-
|
|
3
|
-
A modern, customizable rich-text editor for Angular applications, built with Tiptap and featuring complete internationalization support.
|
|
4
|
-
|
|
5
|
-
[](https://flogeez.github.io/angular-tiptap-editor/) [](https://stackblitz.com/edit/angular-tiptap-editor)
|
|
6
|
-
|
|
7
|
-
## 🚀 Features
|
|
8
|
-
|
|
9
|
-
- **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.
|
|
11
|
-
- **Modern UX (Notion-like)**: Intuitive slash commands and bubble menus for a keyboard-first experience.
|
|
12
|
-
- **Highly Customizable**: Easily configure toolbars, bubble menus, and slash command items.
|
|
13
|
-
- **Signal-Based Reactivity**: Pure Signal architecture natively compatible with `ChangeDetectionStrategy.OnPush`.
|
|
14
|
-
- **Advanced Table Support**: Full table management with cell selection and context-aware bubble menus.
|
|
15
|
-
- **Professional Media**: Advanced image handling with resizing, auto-compression, and custom uploaders.
|
|
16
|
-
- **Built-in i18n**: English & French support with a reactive, extensible locale system.
|
|
17
|
-
- **Word/Character Count**: Real-time statistics with proper pluralization support.
|
|
18
|
-
- **Office-Ready**: Cleaned-up pasting from Microsoft Word and Excel to maintain layout integrity.
|
|
19
|
-
- **Service Driven**: Deep programmatic control via `EditorCommandsService` and isolated instances.
|
|
20
|
-
- **A11y First**: Built with accessibility best practices and full keyboard navigation.
|
|
21
|
-
|
|
22
|
-
## 💎 Why this editor?
|
|
23
|
-
|
|
24
|
-
Most Angular wrappers for Tiptap provide a basic component but leave the heavy lifting to you. **Angular Tiptap Editor** is built to solve common production hurdles:
|
|
25
|
-
|
|
26
|
-
- **True Scalability**: Thanks to **isolated services** provided at the component level, you can host multiple independent editors with different configurations and languages on the same page without a single state leak.
|
|
27
|
-
- **OnPush by Default**: The entire UI (toolbar, menus) is powered by **Angular Signals**. The `editorState` snapshot logic ensures that your components only re-render when necessary, even in complex `OnPush` applications.
|
|
28
|
-
- **Deep i18n & Extensibility**: Not just English/French — you can inject **custom translations** and **custom Tiptap extensions**. Our `DiscoveryCalculator` automatically tracks any new mark or node you add, making them reactive without extra code.
|
|
29
|
-
- **Clean Office UX**: Professional-grade pasting from **Word and Excel** plus smart image handling (auto-compression, resizing handles) ensures a polished experience for end-users.
|
|
30
|
-
|
|
31
|
-
## 🛠️ Extensions included
|
|
32
|
-
|
|
33
|
-
The library comes with a pre-configured set of standard and custom extensions:
|
|
34
|
-
|
|
35
|
-
- **Nodes**: `StarterKit`, `Heading`, `Table`, `Image`, `HorizontalRule`, `CodeBlock`.
|
|
36
|
-
- **Marks**: `Bold`, `Italic`, `Underline`, `Strike`, `Code`, `Link`, `Highlight`, `TextStyle`, `Color`, `Superscript`, `Subscript`.
|
|
37
|
-
- **Utilities**: `Placeholder`, `CharacterCount`, `Typography`, `Focus`, `BubbleMenu`, `Gapcursor`, `Dropcursor`, `ResizableImage` (Custom).
|
|
38
|
-
|
|
39
|
-
## 📦 Installation
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
npm install @flogeez/angular-tiptap-editor
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
### CSS Styles
|
|
46
|
-
|
|
47
|
-
Add the required CSS to your `angular.json` file in the `styles` array:
|
|
48
|
-
|
|
49
|
-
```json
|
|
50
|
-
{
|
|
51
|
-
"styles": [
|
|
52
|
-
...
|
|
53
|
-
"node_modules/@fontsource/material-symbols-outlined/index.css",
|
|
54
|
-
"node_modules/@flogeez/angular-tiptap-editor/src/lib/styles/index.css",
|
|
55
|
-
...
|
|
56
|
-
]
|
|
57
|
-
}
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## 🎯 Quick Start
|
|
61
|
-
|
|
62
|
-
### 1. Basic Usage
|
|
63
|
-
|
|
64
|
-
```typescript
|
|
65
|
-
import { Component } from "@angular/core";
|
|
66
|
-
import { AngularTiptapEditorComponent } from "@flogeez/angular-tiptap-editor";
|
|
67
|
-
|
|
68
|
-
@Component({
|
|
69
|
-
selector: "app-example",
|
|
70
|
-
standalone: true,
|
|
71
|
-
imports: [AngularTiptapEditorComponent],
|
|
72
|
-
template: `
|
|
73
|
-
<angular-tiptap-editor
|
|
74
|
-
[content]="content"
|
|
75
|
-
(contentChange)="onContentChange($event)"
|
|
76
|
-
/>
|
|
77
|
-
`,
|
|
78
|
-
})
|
|
79
|
-
export class ExampleComponent {
|
|
80
|
-
content = "<p>Hello <strong>World</strong>!</p>";
|
|
81
|
-
|
|
82
|
-
onContentChange(newContent: string) {
|
|
83
|
-
this.content = newContent;
|
|
84
|
-
console.log("Content updated:", newContent);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
### 2. With Custom Configuration
|
|
90
|
-
|
|
91
|
-
```typescript
|
|
92
|
-
import { Component } from "@angular/core";
|
|
93
|
-
import {
|
|
94
|
-
AngularTiptapEditorComponent,
|
|
95
|
-
DEFAULT_TOOLBAR_CONFIG,
|
|
96
|
-
DEFAULT_BUBBLE_MENU_CONFIG,
|
|
97
|
-
} from "@flogeez/angular-tiptap-editor";
|
|
98
|
-
|
|
99
|
-
@Component({
|
|
100
|
-
selector: "app-advanced",
|
|
101
|
-
standalone: true,
|
|
102
|
-
imports: [AngularTiptapEditorComponent],
|
|
103
|
-
template: `
|
|
104
|
-
<angular-tiptap-editor
|
|
105
|
-
[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"
|
|
114
|
-
(contentChange)="onContentChange($event)"
|
|
115
|
-
/>
|
|
116
|
-
`,
|
|
117
|
-
})
|
|
118
|
-
export class AdvancedComponent {
|
|
119
|
-
content = "<h1>Welcome!</h1><p>Start editing...</p>";
|
|
120
|
-
|
|
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
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
// Available keys: "heading1", "heading2", "heading3", "bulletList",
|
|
140
|
-
// "orderedList", "blockquote", "code", "image", "horizontalRule", "table"
|
|
141
|
-
|
|
142
|
-
onContentChange(newContent: string) {
|
|
143
|
-
this.content = newContent;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
### 3. Registering Custom Extensions
|
|
149
|
-
|
|
150
|
-
Easily extend the editor with any standard Tiptap extension or your own custom marks/nodes via the `tiptapExtensions` input.
|
|
151
|
-
|
|
152
|
-
```typescript
|
|
153
|
-
import { Component } from "@angular/core";
|
|
154
|
-
import { AngularTiptapEditorComponent } from "@flogeez/angular-tiptap-editor";
|
|
155
|
-
|
|
156
|
-
@Component({
|
|
157
|
-
selector: "app-custom-extensions",
|
|
158
|
-
standalone: true,
|
|
159
|
-
imports: [AngularTiptapEditorComponent],
|
|
160
|
-
template: `
|
|
161
|
-
<angular-tiptap-editor
|
|
162
|
-
[content]="content"
|
|
163
|
-
[tiptapExtensions]="extensions"
|
|
164
|
-
(contentChange)="content = $event"
|
|
165
|
-
/>
|
|
166
|
-
`,
|
|
167
|
-
})
|
|
168
|
-
export class CustomExtensionsComponent {
|
|
169
|
-
content = "<p>Custom extensions example</p>";
|
|
170
|
-
|
|
171
|
-
extensions = [
|
|
172
|
-
// Add your custom TipTap extensions here
|
|
173
|
-
// Example: Custom extension configuration
|
|
174
|
-
// MyCustomExtension.configure({ /* options */ })
|
|
175
|
-
];
|
|
176
|
-
}
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
### 4. With Form Integration
|
|
180
|
-
|
|
181
|
-
```typescript
|
|
182
|
-
import { Component } from "@angular/core";
|
|
183
|
-
import { FormControl, ReactiveFormsModule } from "@angular/forms";
|
|
184
|
-
import { AngularTiptapEditorComponent } from "@flogeez/angular-tiptap-editor";
|
|
185
|
-
|
|
186
|
-
@Component({
|
|
187
|
-
selector: "app-form",
|
|
188
|
-
standalone: true,
|
|
189
|
-
imports: [AngularTiptapEditorComponent, ReactiveFormsModule],
|
|
190
|
-
template: `
|
|
191
|
-
<form>
|
|
192
|
-
<angular-tiptap-editor
|
|
193
|
-
[formControl]="contentControl"
|
|
194
|
-
placeholder="Enter your content here..."
|
|
195
|
-
[showCharacterCount]="true"
|
|
196
|
-
[showWordCount]="true"
|
|
197
|
-
/>
|
|
198
|
-
<button type="submit">Submit</button>
|
|
199
|
-
</form>
|
|
200
|
-
`,
|
|
201
|
-
})
|
|
202
|
-
export class FormComponent {
|
|
203
|
-
contentControl = new FormControl("<p>Initial content</p>");
|
|
204
|
-
}
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
### 5. Using EditorCommandsService
|
|
208
|
-
|
|
209
|
-
```typescript
|
|
210
|
-
import { Component, inject } from "@angular/core";
|
|
211
|
-
import { EditorCommandsService } from "@flogeez/angular-tiptap-editor";
|
|
212
|
-
|
|
213
|
-
@Component({
|
|
214
|
-
selector: "app-commands",
|
|
215
|
-
standalone: true,
|
|
216
|
-
template: `
|
|
217
|
-
<div>
|
|
218
|
-
<div class="controls">
|
|
219
|
-
<button (click)="clearContent()">Clear Content</button>
|
|
220
|
-
<button (click)="focusEditor()">Focus Editor</button>
|
|
221
|
-
<button (click)="setContent()">Set Content</button>
|
|
222
|
-
</div>
|
|
223
|
-
|
|
224
|
-
<angular-tiptap-editor
|
|
225
|
-
(editorCreated)="onEditorCreated($event)"
|
|
226
|
-
/>
|
|
227
|
-
</div>
|
|
228
|
-
`,
|
|
229
|
-
})
|
|
230
|
-
export class CommandsComponent {
|
|
231
|
-
private editorCommandsService = inject(EditorCommandsService);
|
|
232
|
-
private editor: Editor | null = null;
|
|
233
|
-
|
|
234
|
-
onEditorCreated(editor: Editor) {
|
|
235
|
-
this.editor = editor;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
clearContent() {
|
|
239
|
-
if (this.editor) {
|
|
240
|
-
this.editorCommandsService.clearContent(this.editor);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
focusEditor() {
|
|
245
|
-
if (this.editor) {
|
|
246
|
-
this.editorCommandsService.focus(this.editor);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
setContent() {
|
|
251
|
-
if (this.editor) {
|
|
252
|
-
this.editorCommandsService.setContent(
|
|
253
|
-
this.editor,
|
|
254
|
-
"<h1>New Content</h1>"
|
|
255
|
-
);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
### 6. Extending Reactive Editor State
|
|
262
|
-
|
|
263
|
-
The editor features a dual-layer state architecture: **Automatic Tracking** for simple extensions and **Custom Calculators** for advanced needs.
|
|
264
|
-
|
|
265
|
-
#### A. Automatic Extension Tracking (Zero Config)
|
|
266
|
-
|
|
267
|
-
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
|
-
|
|
269
|
-
* **For Marks**: `state().marks.yourExtensionName` (boolean) and `state().can.toggleYourExtensionName` (boolean).
|
|
270
|
-
* **For Nodes**: `state().nodes.yourExtensionName` (boolean).
|
|
271
|
-
|
|
272
|
-
#### B. Custom State Calculators (Advanced)
|
|
273
|
-
|
|
274
|
-
If you need to extract complex data (like attributes, depth, or custom logic), you can provide a custom `StateCalculator`.
|
|
275
|
-
|
|
276
|
-
1. **Define a Calculator**:
|
|
277
|
-
```typescript
|
|
278
|
-
import { StateCalculator } from "@flogeez/angular-tiptap-editor";
|
|
279
|
-
|
|
280
|
-
// This function will be called on every editor update
|
|
281
|
-
export const MyCustomCalculator: StateCalculator = (editor) => {
|
|
282
|
-
return {
|
|
283
|
-
custom: {
|
|
284
|
-
hasHighPriority: editor.isActive('priority'),
|
|
285
|
-
selectionDepth: editor.state.selection.$from.depth,
|
|
286
|
-
// Any data you need...
|
|
287
|
-
}
|
|
288
|
-
};
|
|
289
|
-
};
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
2. **Register it in the Template**:
|
|
293
|
-
```html
|
|
294
|
-
<angular-tiptap-editor
|
|
295
|
-
[stateCalculators]="[MyCustomCalculator]"
|
|
296
|
-
/>
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
3. **Consume it anywhere**:
|
|
300
|
-
```typescript
|
|
301
|
-
@Component({ ... })
|
|
302
|
-
export class MyToolbarComponent {
|
|
303
|
-
private editorCommands = inject(EditorCommandsService);
|
|
304
|
-
|
|
305
|
-
// Access your custom data reactively!
|
|
306
|
-
isHighPriority = computed(() => this.editorCommands.editorState().custom?.hasHighPriority);
|
|
307
|
-
}
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
## ✨ Key Features
|
|
311
|
-
|
|
312
|
-
### 📊 Table Management
|
|
313
|
-
|
|
314
|
-
Full table support with intuitive bubble menus:
|
|
315
|
-
|
|
316
|
-
- **Table Creation**: Insert tables via slash commands (`/table`)
|
|
317
|
-
- **Cell Selection**: Click and drag to select multiple cells
|
|
318
|
-
- **Bubble Menus**: Context-aware menus for table operations
|
|
319
|
-
- **Row/Column Management**: Add, remove, and merge cells
|
|
320
|
-
- **Styling**: Custom table styling with proper borders
|
|
321
|
-
|
|
322
|
-
### ⚡ Slash Commands
|
|
323
|
-
|
|
324
|
-
Quick content insertion with slash commands:
|
|
325
|
-
|
|
326
|
-
- **Headings**: `/h1`, `/h2`, `/h3`
|
|
327
|
-
- **Lists**: `/bullet`, `/numbered`
|
|
328
|
-
- **Blocks**: `/quote`, `/code`, `/line`
|
|
329
|
-
- **Media**: `/image`, `/table`
|
|
330
|
-
- **Fully Internationalized**: All commands translated
|
|
331
|
-
|
|
332
|
-
#### Custom Slash Commands
|
|
333
|
-
|
|
334
|
-
The `slashCommands` object also allows you to add completely custom command items:
|
|
335
|
-
|
|
336
|
-
```typescript
|
|
337
|
-
import { SlashCommandsConfig } from "@flogeez/angular-tiptap-editor";
|
|
338
|
-
|
|
339
|
-
slashCommands: SlashCommandsConfig = {
|
|
340
|
-
// Toggle native commands
|
|
341
|
-
heading1: true,
|
|
342
|
-
image: false,
|
|
343
|
-
// Add custom ones
|
|
344
|
-
custom: [
|
|
345
|
-
{
|
|
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
|
-
]
|
|
353
|
-
};
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
### 🖼️ Advanced Image Handling
|
|
357
|
-
|
|
358
|
-
Professional image management:
|
|
359
|
-
|
|
360
|
-
- **Drag & Drop**: Drag images directly into the editor
|
|
361
|
-
- **File Selection**: Click to select images from device
|
|
362
|
-
- **Auto-Compression**: Images automatically compressed (max 1920x1080)
|
|
363
|
-
- **Resizable**: Images can be resized with handles
|
|
364
|
-
- **Bubble Menu**: Context menu for image operations
|
|
365
|
-
- **Custom Upload Handler**: Upload images to your own server instead of base64
|
|
366
|
-
|
|
367
|
-
#### Custom Image Upload Handler
|
|
368
|
-
|
|
369
|
-
By default, images are converted to base64 and embedded directly in the HTML content. You can provide a custom upload handler to upload images to your own server (S3, Cloudinary, custom API, etc.) and use the returned URL instead.
|
|
370
|
-
|
|
371
|
-
The handler can return either an **Observable** or a **Promise**.
|
|
372
|
-
|
|
373
|
-
#### Using Observable (recommended for Angular)
|
|
374
|
-
|
|
375
|
-
```typescript
|
|
376
|
-
import { Component, inject } from '@angular/core';
|
|
377
|
-
import { HttpClient } from '@angular/common/http';
|
|
378
|
-
import { map } from 'rxjs/operators';
|
|
379
|
-
import {
|
|
380
|
-
AngularTiptapEditorComponent,
|
|
381
|
-
ImageUploadHandler
|
|
382
|
-
} from '@flogeez/angular-tiptap-editor';
|
|
383
|
-
|
|
384
|
-
@Component({
|
|
385
|
-
selector: 'app-custom-upload',
|
|
386
|
-
standalone: true,
|
|
387
|
-
imports: [AngularTiptapEditorComponent],
|
|
388
|
-
template: `
|
|
389
|
-
<angular-tiptap-editor
|
|
390
|
-
[content]="content"
|
|
391
|
-
[imageUploadHandler]="uploadHandler"
|
|
392
|
-
(contentChange)="onContentChange($event)"
|
|
393
|
-
/>
|
|
394
|
-
`
|
|
395
|
-
})
|
|
396
|
-
export class CustomUploadComponent {
|
|
397
|
-
private http = inject(HttpClient);
|
|
398
|
-
content = '';
|
|
399
|
-
|
|
400
|
-
uploadHandler: ImageUploadHandler = (ctx) => {
|
|
401
|
-
const formData = new FormData();
|
|
402
|
-
formData.append('image', ctx.file);
|
|
403
|
-
|
|
404
|
-
return this.http.post<{ url: string }>('/api/upload', formData).pipe(
|
|
405
|
-
map(result => ({ src: result.url }))
|
|
406
|
-
);
|
|
407
|
-
};
|
|
408
|
-
|
|
409
|
-
onContentChange(newContent: string) {
|
|
410
|
-
this.content = newContent;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
```
|
|
414
|
-
|
|
415
|
-
#### Using Promise (async/await)
|
|
416
|
-
|
|
417
|
-
```typescript
|
|
418
|
-
uploadHandler: ImageUploadHandler = async (ctx) => {
|
|
419
|
-
const formData = new FormData();
|
|
420
|
-
formData.append('image', ctx.file);
|
|
421
|
-
|
|
422
|
-
const result = await firstValueFrom(
|
|
423
|
-
this.http.post<{ url: string }>('/api/upload', formData)
|
|
424
|
-
);
|
|
425
|
-
|
|
426
|
-
return { src: result.url };
|
|
427
|
-
};
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
The `ImageUploadContext` provides:
|
|
431
|
-
- `file`: The original File object
|
|
432
|
-
- `width`: Processed image width
|
|
433
|
-
- `height`: Processed image height
|
|
434
|
-
- `type`: MIME type (e.g., 'image/jpeg')
|
|
435
|
-
- `base64`: Base64 data URL of the processed image (fallback)
|
|
436
|
-
|
|
437
|
-
The handler must return an `ImageUploadHandlerResult` with at least a `src` property containing the image URL.
|
|
438
|
-
|
|
439
|
-
---
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
### 📝 Word & Character Counting
|
|
443
|
-
|
|
444
|
-
Real-time content statistics:
|
|
445
|
-
|
|
446
|
-
- **Live Updates**: Counters update as you type
|
|
447
|
-
- **Proper Pluralization**: "1 word" vs "2 words"
|
|
448
|
-
- **Separate Counts**: Independent word and character counts
|
|
449
|
-
- **Configurable**: Show/hide individual counters
|
|
450
|
-
|
|
451
|
-
## 🎨 Demo
|
|
452
|
-
|
|
453
|
-
### 🌐 Live Demo
|
|
454
|
-
|
|
455
|
-
Try the interactive demo online: **[https://flogeez.github.io/angular-tiptap-editor/](https://flogeez.github.io/angular-tiptap-editor/)**
|
|
456
|
-
|
|
457
|
-
### 🖥️ Run Locally
|
|
458
|
-
|
|
459
|
-
```bash
|
|
460
|
-
git clone https://github.com/FloGeez/angular-tiptap-editor.git
|
|
461
|
-
cd angular-tiptap-editor
|
|
462
|
-
npm install
|
|
463
|
-
npm start
|
|
464
|
-
```
|
|
465
|
-
|
|
466
|
-
Open [http://localhost:4200](http://localhost:4200) to view the demo.
|
|
467
|
-
|
|
468
|
-
## 📖 Documentation
|
|
469
|
-
|
|
470
|
-
### API Reference
|
|
471
|
-
|
|
472
|
-
#### Inputs
|
|
473
|
-
|
|
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 |
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
#### Outputs
|
|
508
|
-
|
|
509
|
-
| Output | Type | Description |
|
|
510
|
-
| --------------- | ----------------- | ------------------------------- |
|
|
511
|
-
| `contentChange` | `string` | Emitted when content changes |
|
|
512
|
-
| `editorCreated` | `Editor` | Emitted when editor is created |
|
|
513
|
-
| `editorUpdate` | `{editor, trans}` | Emitted on every editor update |
|
|
514
|
-
| `editorFocus` | `{editor, event}` | Emitted when editor gains focus |
|
|
515
|
-
| `editorBlur` | `{editor, event}` | Emitted when editor loses focus |
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
## 🌍 Internationalization
|
|
519
|
-
|
|
520
|
-
The editor comes with built-in support for **English (en)** and **French (fr)**, featuring automatic browser language detection.
|
|
521
|
-
|
|
522
|
-
### Basic Usage
|
|
523
|
-
|
|
524
|
-
```typescript
|
|
525
|
-
// Force a specific language
|
|
526
|
-
<angular-tiptap-editor [locale]="'fr'" />
|
|
527
|
-
|
|
528
|
-
// Auto-detect (default)
|
|
529
|
-
<angular-tiptap-editor />
|
|
530
|
-
```
|
|
531
|
-
|
|
532
|
-
### Adding Custom Languages
|
|
533
|
-
|
|
534
|
-
You can easily extend the editor with new languages or override existing labels using the `TiptapI18nService`:
|
|
535
|
-
|
|
536
|
-
```typescript
|
|
537
|
-
import { TiptapI18nService } from "@flogeez/angular-tiptap-editor";
|
|
538
|
-
|
|
539
|
-
@Component({ ... })
|
|
540
|
-
export class MyComponent {
|
|
541
|
-
constructor(private i18nService: TiptapI18nService) {
|
|
542
|
-
// Add Spanish support
|
|
543
|
-
this.i18nService.addTranslations('es', {
|
|
544
|
-
toolbar: { bold: 'Negrita', italic: 'Cursiva', ... },
|
|
545
|
-
editor: { placeholder: 'Empieza a escribir...' }
|
|
546
|
-
});
|
|
547
|
-
|
|
548
|
-
// Switch to Spanish
|
|
549
|
-
this.i18nService.setLocale('es');
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
```
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
### 🎨 CSS Custom Properties
|
|
556
|
-
|
|
557
|
-
Customize the editor appearance using CSS variables with the `--ate-` prefix:
|
|
558
|
-
|
|
559
|
-
```css
|
|
560
|
-
/* In your global styles or component styles */
|
|
561
|
-
angular-tiptap-editor {
|
|
562
|
-
--ate-primary: #2563eb;
|
|
563
|
-
--ate-primary-contrast: #ffffff;
|
|
564
|
-
--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
|
-
|
|
568
|
-
--ate-surface: #ffffff;
|
|
569
|
-
--ate-surface-secondary: #f8f9fa;
|
|
570
|
-
--ate-surface-tertiary: #f1f5f9;
|
|
571
|
-
|
|
572
|
-
--ate-text: #2d3748;
|
|
573
|
-
--ate-text-secondary: #64748b;
|
|
574
|
-
--ate-text-muted: #a0aec0;
|
|
575
|
-
|
|
576
|
-
--ate-border: #e2e8f0;
|
|
577
|
-
|
|
578
|
-
/* And More... */
|
|
579
|
-
}
|
|
580
|
-
```
|
|
581
|
-
|
|
582
|
-
#### Dark Mode Support
|
|
583
|
-
|
|
584
|
-
The editor supports dark mode in two ways:
|
|
585
|
-
|
|
586
|
-
**1. With CSS Class**
|
|
587
|
-
|
|
588
|
-
```html
|
|
589
|
-
<angular-tiptap-editor [class.dark]="isDarkMode" />
|
|
590
|
-
```
|
|
591
|
-
|
|
592
|
-
**2. With Data Attribute**
|
|
593
|
-
|
|
594
|
-
```html
|
|
595
|
-
<angular-tiptap-editor [attr.data-theme]="isDarkMode ? 'dark' : null" />
|
|
596
|
-
```
|
|
597
|
-
|
|
598
|
-
#### Example: Custom Dark Theme
|
|
599
|
-
|
|
600
|
-
```css
|
|
601
|
-
angular-tiptap-editor.dark {
|
|
602
|
-
--ate-background: #1a1a2e;
|
|
603
|
-
--ate-border-color: #3d3d5c;
|
|
604
|
-
--ate-focus-color: #6366f1;
|
|
605
|
-
--ate-text-color: #e2e8f0;
|
|
606
|
-
--ate-placeholder-color: #64748b;
|
|
607
|
-
--ate-counter-background: #2d2d44;
|
|
608
|
-
--ate-counter-color: #94a3b8;
|
|
609
|
-
--ate-blockquote-background: #2d2d44;
|
|
610
|
-
--ate-code-background: #2d2d44;
|
|
611
|
-
}
|
|
612
|
-
```
|
|
613
|
-
|
|
614
|
-
#### Example: Custom Brand Colors
|
|
615
|
-
|
|
616
|
-
```css
|
|
617
|
-
angular-tiptap-editor {
|
|
618
|
-
--ate-focus-color: #8b5cf6;
|
|
619
|
-
--ate-image-selected-color: #8b5cf6;
|
|
620
|
-
--ate-border-radius: 12px;
|
|
621
|
-
}
|
|
622
|
-
```
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
### ⚡ Reactive State & OnPush
|
|
626
|
-
|
|
627
|
-
The library exposes a reactive `editorState` signal via the `EditorCommandsService`. This signal contains everything you need to build custom UIs around the editor:
|
|
628
|
-
|
|
629
|
-
- **Active State**: Check if `bold`, `italic`, or custom marks are active.
|
|
630
|
-
- **Commands Availability**: Check if `undo`, `redo`, or custom commands can be executed.
|
|
631
|
-
- **Structural Data**: Access table status, image attributes, or selection details.
|
|
632
|
-
|
|
633
|
-
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.
|
|
634
|
-
|
|
635
|
-
---
|
|
636
|
-
|
|
637
|
-
### 🧩 Custom Tiptap Extensions
|
|
638
|
-
|
|
639
|
-
You are not limited to the built-in extensions. Pass any Tiptap extension, mark, or node:
|
|
640
|
-
|
|
641
|
-
```html
|
|
642
|
-
<angular-tiptap-editor [tiptapExtensions]="[MyCustomExtension]" />
|
|
643
|
-
```
|
|
644
|
-
|
|
645
|
-
Any custom extension is automatically detected and its state (active/can) is added to the reactive `editorState` snapshot.
|
|
646
|
-
|
|
647
|
-
---
|
|
648
|
-
|
|
649
|
-
## 🏗️ Architecture
|
|
650
|
-
|
|
651
|
-
### Reactive State Management
|
|
652
|
-
|
|
653
|
-
The library uses a **Snapshot & Signal** pattern to bridge Tiptap and Angular.
|
|
654
|
-
|
|
655
|
-
1. **State Snapshot**: Every editor transaction triggers a set of "Calculators" that produce a single immutable state object.
|
|
656
|
-
2. **Specialized Calculators**: Logic is modularized into specialized functions (Marks, Table, Image, etc.) and a **Discovery Calculator** for automatic extension detection.
|
|
657
|
-
3. **Signals Integration**: This snapshot is stored in a single Angular Signal. Sub-components (toolbar, menus) consume this signal only where needed.
|
|
658
|
-
4. **Change Detection Optimization**: A custom equality check on the signal prevents unnecessary re-renders when the visual state of the editor hasn't changed.
|
|
659
|
-
|
|
660
|
-
### Core Services
|
|
661
|
-
|
|
662
|
-
- **`EditorCommandsService`**: Exposes the `editorState` signal and provides a centralized API for executing Tiptap commands.
|
|
663
|
-
- **`ImageService`**: Manages the image processing pipeline (selection, compression, and server-side upload handling).
|
|
664
|
-
- **`TiptapI18nService`**: Reactive translation service with support for browser locale auto-detection.
|
|
665
|
-
|
|
666
|
-
### Isolated Instances
|
|
667
|
-
|
|
668
|
-
Each component instance provides its own set of services (`EditorCommandsService`, `ImageService`, etc.) at the component level. This ensures that multiple editors on the same page maintain independent states and configurations without interference.
|
|
669
|
-
|
|
670
|
-
### Modern Angular Integration
|
|
671
|
-
|
|
672
|
-
- **Signals**: Native reactivity for efficient UI updates.
|
|
673
|
-
- **OnPush**: Designed for `ChangeDetectionStrategy.OnPush` throughout.
|
|
674
|
-
- **Typed State**: Fully typed interfaces for the editor state and configurations.
|
|
675
|
-
|
|
676
|
-
### Default Configurations
|
|
677
|
-
|
|
678
|
-
The library provides default configurations that can be imported and customized:
|
|
679
|
-
|
|
680
|
-
```typescript
|
|
681
|
-
import {
|
|
682
|
-
DEFAULT_TOOLBAR_CONFIG,
|
|
683
|
-
DEFAULT_BUBBLE_MENU_CONFIG,
|
|
684
|
-
DEFAULT_IMAGE_BUBBLE_MENU_CONFIG,
|
|
685
|
-
DEFAULT_TABLE_MENU_CONFIG,
|
|
686
|
-
SLASH_COMMAND_KEYS,
|
|
687
|
-
} from "@flogeez/angular-tiptap-editor";
|
|
688
|
-
```
|
|
689
|
-
|
|
690
|
-
## 🔧 Development
|
|
691
|
-
|
|
692
|
-
### Build Library
|
|
693
|
-
|
|
694
|
-
```bash
|
|
695
|
-
npm run build:lib
|
|
696
|
-
```
|
|
697
|
-
|
|
698
|
-
### Watch Mode (Development)
|
|
699
|
-
|
|
700
|
-
```bash
|
|
701
|
-
npm run dev
|
|
702
|
-
```
|
|
703
|
-
|
|
704
|
-
This runs the library in watch mode and starts the demo application.
|
|
705
|
-
|
|
706
|
-
### Available Scripts
|
|
707
|
-
|
|
708
|
-
- `npm start` - Start demo application
|
|
709
|
-
- `npm run build` - Build demo application
|
|
710
|
-
- `npm run build:lib` - Build library
|
|
711
|
-
- `npm run watch:lib` - Watch library changes
|
|
712
|
-
- `npm run dev` - Development mode (watch + serve)
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
## 📝 License
|
|
716
|
-
|
|
717
|
-
MIT License - see [LICENSE](LICENSE) file for details.
|
|
718
|
-
|
|
719
|
-
## 🤝 Contributing
|
|
720
|
-
|
|
721
|
-
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
722
|
-
|
|
723
|
-
## 🔗 Links
|
|
724
|
-
|
|
725
|
-
- 📖 [Tiptap Documentation](https://tiptap.dev/)
|
|
726
|
-
- 🅰️ [Angular Documentation](https://angular.dev/)
|
|
727
|
-
- 📦 [NPM Package](https://www.npmjs.com/package/@flogeez/angular-tiptap-editor)
|
|
728
|
-
- 📖 [Live Demo](https://flogeez.github.io/angular-tiptap-editor/)
|
|
729
|
-
- 🐛 [Report Issues](https://github.com/FloGeez/angular-tiptap-editor/issues)
|
|
730
|
-
- 💡 [Feature Requests](https://github.com/FloGeez/angular-tiptap-editor/issues)
|
|
731
|
-
|
|
732
|
-
## 🆕 What's New
|
|
733
|
-
|
|
734
|
-
### Latest Updates
|
|
735
|
-
|
|
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.
|
|
740
|
-
- ✅ **Refactored Link Management**: Dedicated link bubble menu with smart UI anchoring and real-time URL sync.
|
|
741
|
-
|
|
742
|
-
---
|
|
743
|
-
|
|
744
|
-
Made with ❤️ by [FloGeez](https://github.com/FloGeez)
|
|
1
|
+
# Angular Tiptap Editor
|
|
2
|
+
|
|
3
|
+
A modern, customizable rich-text editor for Angular applications, built with Tiptap and featuring complete internationalization support.
|
|
4
|
+
|
|
5
|
+
[](https://flogeez.github.io/angular-tiptap-editor/) [](https://stackblitz.com/edit/angular-tiptap-editor)
|
|
6
|
+
|
|
7
|
+
## 🚀 Features
|
|
8
|
+
|
|
9
|
+
- **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.
|
|
11
|
+
- **Modern UX (Notion-like)**: Intuitive slash commands and bubble menus for a keyboard-first experience.
|
|
12
|
+
- **Highly Customizable**: Easily configure toolbars, bubble menus, and slash command items.
|
|
13
|
+
- **Signal-Based Reactivity**: Pure Signal architecture natively compatible with `ChangeDetectionStrategy.OnPush`.
|
|
14
|
+
- **Advanced Table Support**: Full table management with cell selection and context-aware bubble menus.
|
|
15
|
+
- **Professional Media**: Advanced image handling with resizing, auto-compression, and custom uploaders.
|
|
16
|
+
- **Built-in i18n**: English & French support with a reactive, extensible locale system.
|
|
17
|
+
- **Word/Character Count**: Real-time statistics with proper pluralization support.
|
|
18
|
+
- **Office-Ready**: Cleaned-up pasting from Microsoft Word and Excel to maintain layout integrity.
|
|
19
|
+
- **Service Driven**: Deep programmatic control via `EditorCommandsService` and isolated instances.
|
|
20
|
+
- **A11y First**: Built with accessibility best practices and full keyboard navigation.
|
|
21
|
+
|
|
22
|
+
## 💎 Why this editor?
|
|
23
|
+
|
|
24
|
+
Most Angular wrappers for Tiptap provide a basic component but leave the heavy lifting to you. **Angular Tiptap Editor** is built to solve common production hurdles:
|
|
25
|
+
|
|
26
|
+
- **True Scalability**: Thanks to **isolated services** provided at the component level, you can host multiple independent editors with different configurations and languages on the same page without a single state leak.
|
|
27
|
+
- **OnPush by Default**: The entire UI (toolbar, menus) is powered by **Angular Signals**. The `editorState` snapshot logic ensures that your components only re-render when necessary, even in complex `OnPush` applications.
|
|
28
|
+
- **Deep i18n & Extensibility**: Not just English/French — you can inject **custom translations** and **custom Tiptap extensions**. Our `DiscoveryCalculator` automatically tracks any new mark or node you add, making them reactive without extra code.
|
|
29
|
+
- **Clean Office UX**: Professional-grade pasting from **Word and Excel** plus smart image handling (auto-compression, resizing handles) ensures a polished experience for end-users.
|
|
30
|
+
|
|
31
|
+
## 🛠️ Extensions included
|
|
32
|
+
|
|
33
|
+
The library comes with a pre-configured set of standard and custom extensions:
|
|
34
|
+
|
|
35
|
+
- **Nodes**: `StarterKit`, `Heading`, `Table`, `Image`, `HorizontalRule`, `CodeBlock`.
|
|
36
|
+
- **Marks**: `Bold`, `Italic`, `Underline`, `Strike`, `Code`, `Link`, `Highlight`, `TextStyle`, `Color`, `Superscript`, `Subscript`.
|
|
37
|
+
- **Utilities**: `Placeholder`, `CharacterCount`, `Typography`, `Focus`, `BubbleMenu`, `Gapcursor`, `Dropcursor`, `ResizableImage` (Custom).
|
|
38
|
+
|
|
39
|
+
## 📦 Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install @flogeez/angular-tiptap-editor
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### CSS Styles
|
|
46
|
+
|
|
47
|
+
Add the required CSS to your `angular.json` file in the `styles` array:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"styles": [
|
|
52
|
+
...
|
|
53
|
+
"node_modules/@fontsource/material-symbols-outlined/index.css",
|
|
54
|
+
"node_modules/@flogeez/angular-tiptap-editor/src/lib/styles/index.css",
|
|
55
|
+
...
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## 🎯 Quick Start
|
|
61
|
+
|
|
62
|
+
### 1. Basic Usage
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { Component } from "@angular/core";
|
|
66
|
+
import { AngularTiptapEditorComponent } from "@flogeez/angular-tiptap-editor";
|
|
67
|
+
|
|
68
|
+
@Component({
|
|
69
|
+
selector: "app-example",
|
|
70
|
+
standalone: true,
|
|
71
|
+
imports: [AngularTiptapEditorComponent],
|
|
72
|
+
template: `
|
|
73
|
+
<angular-tiptap-editor
|
|
74
|
+
[content]="content"
|
|
75
|
+
(contentChange)="onContentChange($event)"
|
|
76
|
+
/>
|
|
77
|
+
`,
|
|
78
|
+
})
|
|
79
|
+
export class ExampleComponent {
|
|
80
|
+
content = "<p>Hello <strong>World</strong>!</p>";
|
|
81
|
+
|
|
82
|
+
onContentChange(newContent: string) {
|
|
83
|
+
this.content = newContent;
|
|
84
|
+
console.log("Content updated:", newContent);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 2. With Custom Configuration
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { Component } from "@angular/core";
|
|
93
|
+
import {
|
|
94
|
+
AngularTiptapEditorComponent,
|
|
95
|
+
DEFAULT_TOOLBAR_CONFIG,
|
|
96
|
+
DEFAULT_BUBBLE_MENU_CONFIG,
|
|
97
|
+
} from "@flogeez/angular-tiptap-editor";
|
|
98
|
+
|
|
99
|
+
@Component({
|
|
100
|
+
selector: "app-advanced",
|
|
101
|
+
standalone: true,
|
|
102
|
+
imports: [AngularTiptapEditorComponent],
|
|
103
|
+
template: `
|
|
104
|
+
<angular-tiptap-editor
|
|
105
|
+
[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"
|
|
114
|
+
(contentChange)="onContentChange($event)"
|
|
115
|
+
/>
|
|
116
|
+
`,
|
|
117
|
+
})
|
|
118
|
+
export class AdvancedComponent {
|
|
119
|
+
content = "<h1>Welcome!</h1><p>Start editing...</p>";
|
|
120
|
+
|
|
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
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// Available keys: "heading1", "heading2", "heading3", "bulletList",
|
|
140
|
+
// "orderedList", "blockquote", "code", "image", "horizontalRule", "table"
|
|
141
|
+
|
|
142
|
+
onContentChange(newContent: string) {
|
|
143
|
+
this.content = newContent;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### 3. Registering Custom Extensions
|
|
149
|
+
|
|
150
|
+
Easily extend the editor with any standard Tiptap extension or your own custom marks/nodes via the `tiptapExtensions` input.
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
import { Component } from "@angular/core";
|
|
154
|
+
import { AngularTiptapEditorComponent } from "@flogeez/angular-tiptap-editor";
|
|
155
|
+
|
|
156
|
+
@Component({
|
|
157
|
+
selector: "app-custom-extensions",
|
|
158
|
+
standalone: true,
|
|
159
|
+
imports: [AngularTiptapEditorComponent],
|
|
160
|
+
template: `
|
|
161
|
+
<angular-tiptap-editor
|
|
162
|
+
[content]="content"
|
|
163
|
+
[tiptapExtensions]="extensions"
|
|
164
|
+
(contentChange)="content = $event"
|
|
165
|
+
/>
|
|
166
|
+
`,
|
|
167
|
+
})
|
|
168
|
+
export class CustomExtensionsComponent {
|
|
169
|
+
content = "<p>Custom extensions example</p>";
|
|
170
|
+
|
|
171
|
+
extensions = [
|
|
172
|
+
// Add your custom TipTap extensions here
|
|
173
|
+
// Example: Custom extension configuration
|
|
174
|
+
// MyCustomExtension.configure({ /* options */ })
|
|
175
|
+
];
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### 4. With Form Integration
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
import { Component } from "@angular/core";
|
|
183
|
+
import { FormControl, ReactiveFormsModule } from "@angular/forms";
|
|
184
|
+
import { AngularTiptapEditorComponent } from "@flogeez/angular-tiptap-editor";
|
|
185
|
+
|
|
186
|
+
@Component({
|
|
187
|
+
selector: "app-form",
|
|
188
|
+
standalone: true,
|
|
189
|
+
imports: [AngularTiptapEditorComponent, ReactiveFormsModule],
|
|
190
|
+
template: `
|
|
191
|
+
<form>
|
|
192
|
+
<angular-tiptap-editor
|
|
193
|
+
[formControl]="contentControl"
|
|
194
|
+
placeholder="Enter your content here..."
|
|
195
|
+
[showCharacterCount]="true"
|
|
196
|
+
[showWordCount]="true"
|
|
197
|
+
/>
|
|
198
|
+
<button type="submit">Submit</button>
|
|
199
|
+
</form>
|
|
200
|
+
`,
|
|
201
|
+
})
|
|
202
|
+
export class FormComponent {
|
|
203
|
+
contentControl = new FormControl("<p>Initial content</p>");
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### 5. Using EditorCommandsService
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
import { Component, inject } from "@angular/core";
|
|
211
|
+
import { EditorCommandsService } from "@flogeez/angular-tiptap-editor";
|
|
212
|
+
|
|
213
|
+
@Component({
|
|
214
|
+
selector: "app-commands",
|
|
215
|
+
standalone: true,
|
|
216
|
+
template: `
|
|
217
|
+
<div>
|
|
218
|
+
<div class="controls">
|
|
219
|
+
<button (click)="clearContent()">Clear Content</button>
|
|
220
|
+
<button (click)="focusEditor()">Focus Editor</button>
|
|
221
|
+
<button (click)="setContent()">Set Content</button>
|
|
222
|
+
</div>
|
|
223
|
+
|
|
224
|
+
<angular-tiptap-editor
|
|
225
|
+
(editorCreated)="onEditorCreated($event)"
|
|
226
|
+
/>
|
|
227
|
+
</div>
|
|
228
|
+
`,
|
|
229
|
+
})
|
|
230
|
+
export class CommandsComponent {
|
|
231
|
+
private editorCommandsService = inject(EditorCommandsService);
|
|
232
|
+
private editor: Editor | null = null;
|
|
233
|
+
|
|
234
|
+
onEditorCreated(editor: Editor) {
|
|
235
|
+
this.editor = editor;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
clearContent() {
|
|
239
|
+
if (this.editor) {
|
|
240
|
+
this.editorCommandsService.clearContent(this.editor);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
focusEditor() {
|
|
245
|
+
if (this.editor) {
|
|
246
|
+
this.editorCommandsService.focus(this.editor);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
setContent() {
|
|
251
|
+
if (this.editor) {
|
|
252
|
+
this.editorCommandsService.setContent(
|
|
253
|
+
this.editor,
|
|
254
|
+
"<h1>New Content</h1>"
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### 6. Extending Reactive Editor State
|
|
262
|
+
|
|
263
|
+
The editor features a dual-layer state architecture: **Automatic Tracking** for simple extensions and **Custom Calculators** for advanced needs.
|
|
264
|
+
|
|
265
|
+
#### A. Automatic Extension Tracking (Zero Config)
|
|
266
|
+
|
|
267
|
+
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
|
+
|
|
269
|
+
* **For Marks**: `state().marks.yourExtensionName` (boolean) and `state().can.toggleYourExtensionName` (boolean).
|
|
270
|
+
* **For Nodes**: `state().nodes.yourExtensionName` (boolean).
|
|
271
|
+
|
|
272
|
+
#### B. Custom State Calculators (Advanced)
|
|
273
|
+
|
|
274
|
+
If you need to extract complex data (like attributes, depth, or custom logic), you can provide a custom `StateCalculator`.
|
|
275
|
+
|
|
276
|
+
1. **Define a Calculator**:
|
|
277
|
+
```typescript
|
|
278
|
+
import { StateCalculator } from "@flogeez/angular-tiptap-editor";
|
|
279
|
+
|
|
280
|
+
// This function will be called on every editor update
|
|
281
|
+
export const MyCustomCalculator: StateCalculator = (editor) => {
|
|
282
|
+
return {
|
|
283
|
+
custom: {
|
|
284
|
+
hasHighPriority: editor.isActive('priority'),
|
|
285
|
+
selectionDepth: editor.state.selection.$from.depth,
|
|
286
|
+
// Any data you need...
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
};
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
2. **Register it in the Template**:
|
|
293
|
+
```html
|
|
294
|
+
<angular-tiptap-editor
|
|
295
|
+
[stateCalculators]="[MyCustomCalculator]"
|
|
296
|
+
/>
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
3. **Consume it anywhere**:
|
|
300
|
+
```typescript
|
|
301
|
+
@Component({ ... })
|
|
302
|
+
export class MyToolbarComponent {
|
|
303
|
+
private editorCommands = inject(EditorCommandsService);
|
|
304
|
+
|
|
305
|
+
// Access your custom data reactively!
|
|
306
|
+
isHighPriority = computed(() => this.editorCommands.editorState().custom?.hasHighPriority);
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## ✨ Key Features
|
|
311
|
+
|
|
312
|
+
### 📊 Table Management
|
|
313
|
+
|
|
314
|
+
Full table support with intuitive bubble menus:
|
|
315
|
+
|
|
316
|
+
- **Table Creation**: Insert tables via slash commands (`/table`)
|
|
317
|
+
- **Cell Selection**: Click and drag to select multiple cells
|
|
318
|
+
- **Bubble Menus**: Context-aware menus for table operations
|
|
319
|
+
- **Row/Column Management**: Add, remove, and merge cells
|
|
320
|
+
- **Styling**: Custom table styling with proper borders
|
|
321
|
+
|
|
322
|
+
### ⚡ Slash Commands
|
|
323
|
+
|
|
324
|
+
Quick content insertion with slash commands:
|
|
325
|
+
|
|
326
|
+
- **Headings**: `/h1`, `/h2`, `/h3`
|
|
327
|
+
- **Lists**: `/bullet`, `/numbered`
|
|
328
|
+
- **Blocks**: `/quote`, `/code`, `/line`
|
|
329
|
+
- **Media**: `/image`, `/table`
|
|
330
|
+
- **Fully Internationalized**: All commands translated
|
|
331
|
+
|
|
332
|
+
#### Custom Slash Commands
|
|
333
|
+
|
|
334
|
+
The `slashCommands` object also allows you to add completely custom command items:
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
import { SlashCommandsConfig } from "@flogeez/angular-tiptap-editor";
|
|
338
|
+
|
|
339
|
+
slashCommands: SlashCommandsConfig = {
|
|
340
|
+
// Toggle native commands
|
|
341
|
+
heading1: true,
|
|
342
|
+
image: false,
|
|
343
|
+
// Add custom ones
|
|
344
|
+
custom: [
|
|
345
|
+
{
|
|
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
|
+
]
|
|
353
|
+
};
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### 🖼️ Advanced Image Handling
|
|
357
|
+
|
|
358
|
+
Professional image management:
|
|
359
|
+
|
|
360
|
+
- **Drag & Drop**: Drag images directly into the editor
|
|
361
|
+
- **File Selection**: Click to select images from device
|
|
362
|
+
- **Auto-Compression**: Images automatically compressed (max 1920x1080)
|
|
363
|
+
- **Resizable**: Images can be resized with handles
|
|
364
|
+
- **Bubble Menu**: Context menu for image operations
|
|
365
|
+
- **Custom Upload Handler**: Upload images to your own server instead of base64
|
|
366
|
+
|
|
367
|
+
#### Custom Image Upload Handler
|
|
368
|
+
|
|
369
|
+
By default, images are converted to base64 and embedded directly in the HTML content. You can provide a custom upload handler to upload images to your own server (S3, Cloudinary, custom API, etc.) and use the returned URL instead.
|
|
370
|
+
|
|
371
|
+
The handler can return either an **Observable** or a **Promise**.
|
|
372
|
+
|
|
373
|
+
#### Using Observable (recommended for Angular)
|
|
374
|
+
|
|
375
|
+
```typescript
|
|
376
|
+
import { Component, inject } from '@angular/core';
|
|
377
|
+
import { HttpClient } from '@angular/common/http';
|
|
378
|
+
import { map } from 'rxjs/operators';
|
|
379
|
+
import {
|
|
380
|
+
AngularTiptapEditorComponent,
|
|
381
|
+
ImageUploadHandler
|
|
382
|
+
} from '@flogeez/angular-tiptap-editor';
|
|
383
|
+
|
|
384
|
+
@Component({
|
|
385
|
+
selector: 'app-custom-upload',
|
|
386
|
+
standalone: true,
|
|
387
|
+
imports: [AngularTiptapEditorComponent],
|
|
388
|
+
template: `
|
|
389
|
+
<angular-tiptap-editor
|
|
390
|
+
[content]="content"
|
|
391
|
+
[imageUploadHandler]="uploadHandler"
|
|
392
|
+
(contentChange)="onContentChange($event)"
|
|
393
|
+
/>
|
|
394
|
+
`
|
|
395
|
+
})
|
|
396
|
+
export class CustomUploadComponent {
|
|
397
|
+
private http = inject(HttpClient);
|
|
398
|
+
content = '';
|
|
399
|
+
|
|
400
|
+
uploadHandler: ImageUploadHandler = (ctx) => {
|
|
401
|
+
const formData = new FormData();
|
|
402
|
+
formData.append('image', ctx.file);
|
|
403
|
+
|
|
404
|
+
return this.http.post<{ url: string }>('/api/upload', formData).pipe(
|
|
405
|
+
map(result => ({ src: result.url }))
|
|
406
|
+
);
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
onContentChange(newContent: string) {
|
|
410
|
+
this.content = newContent;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
#### Using Promise (async/await)
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
uploadHandler: ImageUploadHandler = async (ctx) => {
|
|
419
|
+
const formData = new FormData();
|
|
420
|
+
formData.append('image', ctx.file);
|
|
421
|
+
|
|
422
|
+
const result = await firstValueFrom(
|
|
423
|
+
this.http.post<{ url: string }>('/api/upload', formData)
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
return { src: result.url };
|
|
427
|
+
};
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
The `ImageUploadContext` provides:
|
|
431
|
+
- `file`: The original File object
|
|
432
|
+
- `width`: Processed image width
|
|
433
|
+
- `height`: Processed image height
|
|
434
|
+
- `type`: MIME type (e.g., 'image/jpeg')
|
|
435
|
+
- `base64`: Base64 data URL of the processed image (fallback)
|
|
436
|
+
|
|
437
|
+
The handler must return an `ImageUploadHandlerResult` with at least a `src` property containing the image URL.
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
### 📝 Word & Character Counting
|
|
443
|
+
|
|
444
|
+
Real-time content statistics:
|
|
445
|
+
|
|
446
|
+
- **Live Updates**: Counters update as you type
|
|
447
|
+
- **Proper Pluralization**: "1 word" vs "2 words"
|
|
448
|
+
- **Separate Counts**: Independent word and character counts
|
|
449
|
+
- **Configurable**: Show/hide individual counters
|
|
450
|
+
|
|
451
|
+
## 🎨 Demo
|
|
452
|
+
|
|
453
|
+
### 🌐 Live Demo
|
|
454
|
+
|
|
455
|
+
Try the interactive demo online: **[https://flogeez.github.io/angular-tiptap-editor/](https://flogeez.github.io/angular-tiptap-editor/)**
|
|
456
|
+
|
|
457
|
+
### 🖥️ Run Locally
|
|
458
|
+
|
|
459
|
+
```bash
|
|
460
|
+
git clone https://github.com/FloGeez/angular-tiptap-editor.git
|
|
461
|
+
cd angular-tiptap-editor
|
|
462
|
+
npm install
|
|
463
|
+
npm start
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
Open [http://localhost:4200](http://localhost:4200) to view the demo.
|
|
467
|
+
|
|
468
|
+
## 📖 Documentation
|
|
469
|
+
|
|
470
|
+
### API Reference
|
|
471
|
+
|
|
472
|
+
#### Inputs
|
|
473
|
+
|
|
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 |
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
#### Outputs
|
|
508
|
+
|
|
509
|
+
| Output | Type | Description |
|
|
510
|
+
| --------------- | ----------------- | ------------------------------- |
|
|
511
|
+
| `contentChange` | `string` | Emitted when content changes |
|
|
512
|
+
| `editorCreated` | `Editor` | Emitted when editor is created |
|
|
513
|
+
| `editorUpdate` | `{editor, trans}` | Emitted on every editor update |
|
|
514
|
+
| `editorFocus` | `{editor, event}` | Emitted when editor gains focus |
|
|
515
|
+
| `editorBlur` | `{editor, event}` | Emitted when editor loses focus |
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
## 🌍 Internationalization
|
|
519
|
+
|
|
520
|
+
The editor comes with built-in support for **English (en)** and **French (fr)**, featuring automatic browser language detection.
|
|
521
|
+
|
|
522
|
+
### Basic Usage
|
|
523
|
+
|
|
524
|
+
```typescript
|
|
525
|
+
// Force a specific language
|
|
526
|
+
<angular-tiptap-editor [locale]="'fr'" />
|
|
527
|
+
|
|
528
|
+
// Auto-detect (default)
|
|
529
|
+
<angular-tiptap-editor />
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
### Adding Custom Languages
|
|
533
|
+
|
|
534
|
+
You can easily extend the editor with new languages or override existing labels using the `TiptapI18nService`:
|
|
535
|
+
|
|
536
|
+
```typescript
|
|
537
|
+
import { TiptapI18nService } from "@flogeez/angular-tiptap-editor";
|
|
538
|
+
|
|
539
|
+
@Component({ ... })
|
|
540
|
+
export class MyComponent {
|
|
541
|
+
constructor(private i18nService: TiptapI18nService) {
|
|
542
|
+
// Add Spanish support
|
|
543
|
+
this.i18nService.addTranslations('es', {
|
|
544
|
+
toolbar: { bold: 'Negrita', italic: 'Cursiva', ... },
|
|
545
|
+
editor: { placeholder: 'Empieza a escribir...' }
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
// Switch to Spanish
|
|
549
|
+
this.i18nService.setLocale('es');
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
### 🎨 CSS Custom Properties
|
|
556
|
+
|
|
557
|
+
Customize the editor appearance using CSS variables with the `--ate-` prefix:
|
|
558
|
+
|
|
559
|
+
```css
|
|
560
|
+
/* In your global styles or component styles */
|
|
561
|
+
angular-tiptap-editor {
|
|
562
|
+
--ate-primary: #2563eb;
|
|
563
|
+
--ate-primary-contrast: #ffffff;
|
|
564
|
+
--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
|
+
|
|
568
|
+
--ate-surface: #ffffff;
|
|
569
|
+
--ate-surface-secondary: #f8f9fa;
|
|
570
|
+
--ate-surface-tertiary: #f1f5f9;
|
|
571
|
+
|
|
572
|
+
--ate-text: #2d3748;
|
|
573
|
+
--ate-text-secondary: #64748b;
|
|
574
|
+
--ate-text-muted: #a0aec0;
|
|
575
|
+
|
|
576
|
+
--ate-border: #e2e8f0;
|
|
577
|
+
|
|
578
|
+
/* And More... */
|
|
579
|
+
}
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
#### Dark Mode Support
|
|
583
|
+
|
|
584
|
+
The editor supports dark mode in two ways:
|
|
585
|
+
|
|
586
|
+
**1. With CSS Class**
|
|
587
|
+
|
|
588
|
+
```html
|
|
589
|
+
<angular-tiptap-editor [class.dark]="isDarkMode" />
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
**2. With Data Attribute**
|
|
593
|
+
|
|
594
|
+
```html
|
|
595
|
+
<angular-tiptap-editor [attr.data-theme]="isDarkMode ? 'dark' : null" />
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
#### Example: Custom Dark Theme
|
|
599
|
+
|
|
600
|
+
```css
|
|
601
|
+
angular-tiptap-editor.dark {
|
|
602
|
+
--ate-background: #1a1a2e;
|
|
603
|
+
--ate-border-color: #3d3d5c;
|
|
604
|
+
--ate-focus-color: #6366f1;
|
|
605
|
+
--ate-text-color: #e2e8f0;
|
|
606
|
+
--ate-placeholder-color: #64748b;
|
|
607
|
+
--ate-counter-background: #2d2d44;
|
|
608
|
+
--ate-counter-color: #94a3b8;
|
|
609
|
+
--ate-blockquote-background: #2d2d44;
|
|
610
|
+
--ate-code-background: #2d2d44;
|
|
611
|
+
}
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
#### Example: Custom Brand Colors
|
|
615
|
+
|
|
616
|
+
```css
|
|
617
|
+
angular-tiptap-editor {
|
|
618
|
+
--ate-focus-color: #8b5cf6;
|
|
619
|
+
--ate-image-selected-color: #8b5cf6;
|
|
620
|
+
--ate-border-radius: 12px;
|
|
621
|
+
}
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
### ⚡ Reactive State & OnPush
|
|
626
|
+
|
|
627
|
+
The library exposes a reactive `editorState` signal via the `EditorCommandsService`. This signal contains everything you need to build custom UIs around the editor:
|
|
628
|
+
|
|
629
|
+
- **Active State**: Check if `bold`, `italic`, or custom marks are active.
|
|
630
|
+
- **Commands Availability**: Check if `undo`, `redo`, or custom commands can be executed.
|
|
631
|
+
- **Structural Data**: Access table status, image attributes, or selection details.
|
|
632
|
+
|
|
633
|
+
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.
|
|
634
|
+
|
|
635
|
+
---
|
|
636
|
+
|
|
637
|
+
### 🧩 Custom Tiptap Extensions
|
|
638
|
+
|
|
639
|
+
You are not limited to the built-in extensions. Pass any Tiptap extension, mark, or node:
|
|
640
|
+
|
|
641
|
+
```html
|
|
642
|
+
<angular-tiptap-editor [tiptapExtensions]="[MyCustomExtension]" />
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
Any custom extension is automatically detected and its state (active/can) is added to the reactive `editorState` snapshot.
|
|
646
|
+
|
|
647
|
+
---
|
|
648
|
+
|
|
649
|
+
## 🏗️ Architecture
|
|
650
|
+
|
|
651
|
+
### Reactive State Management
|
|
652
|
+
|
|
653
|
+
The library uses a **Snapshot & Signal** pattern to bridge Tiptap and Angular.
|
|
654
|
+
|
|
655
|
+
1. **State Snapshot**: Every editor transaction triggers a set of "Calculators" that produce a single immutable state object.
|
|
656
|
+
2. **Specialized Calculators**: Logic is modularized into specialized functions (Marks, Table, Image, etc.) and a **Discovery Calculator** for automatic extension detection.
|
|
657
|
+
3. **Signals Integration**: This snapshot is stored in a single Angular Signal. Sub-components (toolbar, menus) consume this signal only where needed.
|
|
658
|
+
4. **Change Detection Optimization**: A custom equality check on the signal prevents unnecessary re-renders when the visual state of the editor hasn't changed.
|
|
659
|
+
|
|
660
|
+
### Core Services
|
|
661
|
+
|
|
662
|
+
- **`EditorCommandsService`**: Exposes the `editorState` signal and provides a centralized API for executing Tiptap commands.
|
|
663
|
+
- **`ImageService`**: Manages the image processing pipeline (selection, compression, and server-side upload handling).
|
|
664
|
+
- **`TiptapI18nService`**: Reactive translation service with support for browser locale auto-detection.
|
|
665
|
+
|
|
666
|
+
### Isolated Instances
|
|
667
|
+
|
|
668
|
+
Each component instance provides its own set of services (`EditorCommandsService`, `ImageService`, etc.) at the component level. This ensures that multiple editors on the same page maintain independent states and configurations without interference.
|
|
669
|
+
|
|
670
|
+
### Modern Angular Integration
|
|
671
|
+
|
|
672
|
+
- **Signals**: Native reactivity for efficient UI updates.
|
|
673
|
+
- **OnPush**: Designed for `ChangeDetectionStrategy.OnPush` throughout.
|
|
674
|
+
- **Typed State**: Fully typed interfaces for the editor state and configurations.
|
|
675
|
+
|
|
676
|
+
### Default Configurations
|
|
677
|
+
|
|
678
|
+
The library provides default configurations that can be imported and customized:
|
|
679
|
+
|
|
680
|
+
```typescript
|
|
681
|
+
import {
|
|
682
|
+
DEFAULT_TOOLBAR_CONFIG,
|
|
683
|
+
DEFAULT_BUBBLE_MENU_CONFIG,
|
|
684
|
+
DEFAULT_IMAGE_BUBBLE_MENU_CONFIG,
|
|
685
|
+
DEFAULT_TABLE_MENU_CONFIG,
|
|
686
|
+
SLASH_COMMAND_KEYS,
|
|
687
|
+
} from "@flogeez/angular-tiptap-editor";
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
## 🔧 Development
|
|
691
|
+
|
|
692
|
+
### Build Library
|
|
693
|
+
|
|
694
|
+
```bash
|
|
695
|
+
npm run build:lib
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
### Watch Mode (Development)
|
|
699
|
+
|
|
700
|
+
```bash
|
|
701
|
+
npm run dev
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
This runs the library in watch mode and starts the demo application.
|
|
705
|
+
|
|
706
|
+
### Available Scripts
|
|
707
|
+
|
|
708
|
+
- `npm start` - Start demo application
|
|
709
|
+
- `npm run build` - Build demo application
|
|
710
|
+
- `npm run build:lib` - Build library
|
|
711
|
+
- `npm run watch:lib` - Watch library changes
|
|
712
|
+
- `npm run dev` - Development mode (watch + serve)
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
## 📝 License
|
|
716
|
+
|
|
717
|
+
MIT License - see [LICENSE](LICENSE) file for details.
|
|
718
|
+
|
|
719
|
+
## 🤝 Contributing
|
|
720
|
+
|
|
721
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
722
|
+
|
|
723
|
+
## 🔗 Links
|
|
724
|
+
|
|
725
|
+
- 📖 [Tiptap Documentation](https://tiptap.dev/)
|
|
726
|
+
- 🅰️ [Angular Documentation](https://angular.dev/)
|
|
727
|
+
- 📦 [NPM Package](https://www.npmjs.com/package/@flogeez/angular-tiptap-editor)
|
|
728
|
+
- 📖 [Live Demo](https://flogeez.github.io/angular-tiptap-editor/)
|
|
729
|
+
- 🐛 [Report Issues](https://github.com/FloGeez/angular-tiptap-editor/issues)
|
|
730
|
+
- 💡 [Feature Requests](https://github.com/FloGeez/angular-tiptap-editor/issues)
|
|
731
|
+
|
|
732
|
+
## 🆕 What's New
|
|
733
|
+
|
|
734
|
+
### Latest Updates
|
|
735
|
+
|
|
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.
|
|
740
|
+
- ✅ **Refactored Link Management**: Dedicated link bubble menu with smart UI anchoring and real-time URL sync.
|
|
741
|
+
|
|
742
|
+
---
|
|
743
|
+
|
|
744
|
+
Made with ❤️ by [FloGeez](https://github.com/FloGeez)
|