0
# Module System
1
2
Extensible module architecture with core modules for history, keyboard, clipboard, toolbar, and upload functionality. Modules provide specialized functionality and can be configured, extended, or replaced to customize editor behavior.
3
4
## Capabilities
5
6
### Base Module Class
7
8
Abstract base class that all Quill modules extend, providing common initialization patterns.
9
10
```typescript { .api }
11
/**
12
* Base class for all Quill modules
13
*/
14
abstract class Module {
15
/** Default configuration options for the module */
16
static DEFAULTS: Record<string, unknown>;
17
18
/** Quill instance this module belongs to */
19
quill: Quill;
20
21
/** Module configuration options */
22
options: Record<string, unknown>;
23
24
/**
25
* Create module instance
26
* @param quill - Quill editor instance
27
* @param options - Module configuration options
28
*/
29
constructor(quill: Quill, options?: Record<string, unknown>);
30
}
31
```
32
33
**Usage Examples:**
34
35
```typescript
36
// Define custom module
37
class CustomModule extends Module {
38
static DEFAULTS = {
39
option1: 'default-value',
40
option2: true
41
};
42
43
constructor(quill, options) {
44
super(quill, options);
45
this.setupEventListeners();
46
}
47
48
setupEventListeners() {
49
this.quill.on('text-change', this.handleTextChange.bind(this));
50
}
51
52
handleTextChange(delta, oldDelta, source) {
53
// Custom logic here
54
}
55
}
56
57
// Register and use
58
Quill.register('modules/custom', CustomModule);
59
60
const quill = new Quill('#editor', {
61
modules: {
62
custom: {
63
option1: 'custom-value',
64
option2: false
65
}
66
}
67
});
68
```
69
70
### History Module
71
72
Undo/redo functionality with configurable stack size and change tracking.
73
74
```typescript { .api }
75
class History extends Module {
76
static DEFAULTS: {
77
/** Delay in milliseconds before creating new history entry */
78
delay: number;
79
/** Maximum number of changes in history stack */
80
maxStack: number;
81
/** Only track user changes, not API changes */
82
userOnly: boolean;
83
};
84
85
/** Current history stack */
86
stack: {
87
undo: HistoryRecord[];
88
redo: HistoryRecord[];
89
};
90
91
/** Timestamp of last recorded change */
92
lastRecorded: number;
93
94
/** Whether to ignore current change */
95
ignoreChange: boolean;
96
97
/** Current selection range for restoration */
98
currentRange: Range | null;
99
100
/**
101
* Clear all history
102
*/
103
clear(): void;
104
105
/**
106
* Force cutoff point in history (start new change group)
107
*/
108
cutoff(): void;
109
110
/**
111
* Undo last change
112
*/
113
undo(): void;
114
115
/**
116
* Redo last undone change
117
*/
118
redo(): void;
119
120
/**
121
* Record change in history
122
* @param changeDelta - Delta representing the change
123
* @param oldDelta - Previous document state
124
* @param source - Source of the change
125
*/
126
record(changeDelta: Delta, oldDelta: Delta): void;
127
128
/**
129
* Transform history stack against delta
130
* @param delta - Delta to transform against
131
* @param priority - Transform priority
132
*/
133
transform(delta: Delta): void;
134
}
135
136
interface HistoryRecord {
137
delta: Delta;
138
range: Range | null;
139
}
140
141
interface HistoryOptions {
142
delay?: number;
143
maxStack?: number;
144
userOnly?: boolean;
145
}
146
```
147
148
**Usage Examples:**
149
150
```typescript
151
// Configure history module
152
const quill = new Quill('#editor', {
153
modules: {
154
history: {
155
delay: 2000, // 2 second delay before new history entry
156
maxStack: 200, // Keep 200 changes in history
157
userOnly: true // Only track user changes
158
}
159
}
160
});
161
162
// Programmatic history operations
163
const history = quill.getModule('history');
164
165
// Clear history
166
history.clear();
167
168
// Create cutoff point
169
history.cutoff();
170
171
// Undo/redo
172
history.undo();
173
history.redo();
174
175
// Keyboard shortcuts (automatically bound)
176
// Ctrl+Z / Cmd+Z: Undo
177
// Ctrl+Y / Cmd+Shift+Z: Redo
178
```
179
180
### Keyboard Module
181
182
Keyboard input handling and customizable key bindings.
183
184
```typescript { .api }
185
class Keyboard extends Module {
186
static DEFAULTS: {
187
bindings: Record<string, KeyboardBinding | KeyboardBinding[]>;
188
};
189
190
/** Current keyboard bindings */
191
bindings: Record<string, KeyboardBinding[]>;
192
193
/** Composition tracker */
194
composition: Composition;
195
196
/**
197
* Add custom key binding
198
* @param binding - Key binding configuration
199
* @param handler - Handler function
200
*/
201
addBinding(binding: KeyboardBinding, handler: KeyboardHandler): void;
202
203
/**
204
* Add key binding with shortcut key
205
* @param key - Key specification
206
* @param handler - Handler function
207
*/
208
addBinding(key: string | KeyboardStatic, handler: KeyboardHandler): void;
209
210
/**
211
* Add key binding with options
212
* @param key - Key specification
213
* @param context - Context requirements
214
* @param handler - Handler function
215
*/
216
addBinding(key: string | KeyboardStatic, context: ContextObject, handler: KeyboardHandler): void;
217
218
/**
219
* Listen for keyboard events
220
* @param event - Keyboard event
221
*/
222
listen(): void;
223
224
/**
225
* Handle keyboard event
226
* @param event - Keyboard event
227
*/
228
handleKeyboard(event: KeyboardEvent): void;
229
}
230
231
interface KeyboardBinding {
232
key: string | number;
233
altKey?: boolean;
234
ctrlKey?: boolean;
235
metaKey?: boolean;
236
shiftKey?: boolean;
237
shortKey?: boolean; // Ctrl on PC, Cmd on Mac
238
handler: KeyboardHandler;
239
context?: ContextObject;
240
}
241
242
interface KeyboardStatic {
243
key: string | number;
244
altKey?: boolean;
245
ctrlKey?: boolean;
246
metaKey?: boolean;
247
shiftKey?: boolean;
248
shortKey?: boolean;
249
}
250
251
interface ContextObject {
252
collapsed?: boolean;
253
empty?: boolean;
254
format?: Record<string, any>;
255
list?: boolean;
256
offset?: number;
257
prefix?: RegExp | string;
258
suffix?: RegExp | string;
259
}
260
261
interface Context {
262
collapsed: boolean;
263
empty: boolean;
264
format: Record<string, any>;
265
line: Blot;
266
list: boolean;
267
offset: number;
268
prefix: string;
269
suffix: string;
270
event: KeyboardEvent;
271
}
272
273
type KeyboardHandler = (range: Range, context: Context) => boolean | void;
274
```
275
276
**Usage Examples:**
277
278
```typescript
279
// Get keyboard module
280
const keyboard = quill.getModule('keyboard');
281
282
// Add custom key binding
283
keyboard.addBinding({
284
key: 'Enter',
285
shiftKey: true
286
}, (range, context) => {
287
// Custom Shift+Enter behavior
288
quill.insertText(range.index, '\n');
289
return false; // Prevent default
290
});
291
292
// Add binding with context
293
keyboard.addBinding({
294
key: 'Tab'
295
}, {
296
format: ['code-block']
297
}, (range, context) => {
298
// Custom Tab behavior in code blocks
299
quill.insertText(range.index, ' '); // Insert 2 spaces
300
return false;
301
});
302
303
// Add shortcut key (Ctrl/Cmd)
304
keyboard.addBinding({
305
key: 'B',
306
shortKey: true
307
}, (range, context) => {
308
// Custom Ctrl+B / Cmd+B behavior
309
const format = quill.getFormat(range);
310
quill.format('bold', !format.bold);
311
});
312
313
// Add binding with prefix matching
314
keyboard.addBinding({
315
key: ' '
316
}, {
317
prefix: /^(#{1,6})$/
318
}, (range, context) => {
319
// Convert # prefix to headers
320
const level = context.prefix.length;
321
quill.formatLine(range.index - level, 1, 'header', level);
322
quill.deleteText(range.index - level, level);
323
});
324
```
325
326
### Clipboard Module
327
328
Clipboard operations with paste filtering and content conversion.
329
330
```typescript { .api }
331
class Clipboard extends Module {
332
static DEFAULTS: {
333
matchers: ClipboardMatcher[];
334
};
335
336
/** Content matchers for filtering pasted content */
337
matchers: ClipboardMatcher[];
338
339
/**
340
* Add content matcher for filtering pastes
341
* @param selector - CSS selector or node name to match
342
* @param matcher - Function to process matched content
343
*/
344
addMatcher(selector: string, matcher: ClipboardMatcherFunction): void;
345
346
/**
347
* Add content matcher with priority
348
* @param selector - CSS selector or node name
349
* @param priority - Matcher priority (higher runs first)
350
* @param matcher - Function to process matched content
351
*/
352
addMatcher(selector: string, priority: number, matcher: ClipboardMatcherFunction): void;
353
354
/**
355
* Paste HTML content directly (bypasses matchers)
356
* @param html - HTML string to paste
357
* @param source - Source of the paste operation
358
*/
359
dangerouslyPasteHTML(html: string, source?: EmitterSource): void;
360
361
/**
362
* Paste HTML at specific index
363
* @param index - Position to paste at
364
* @param html - HTML string to paste
365
* @param source - Source of the paste operation
366
*/
367
dangerouslyPasteHTML(index: number, html: string, source?: EmitterSource): void;
368
369
/**
370
* Convert clipboard content to Delta
371
* @param clipboard - Clipboard data with html and/or text
372
* @returns Delta representing the clipboard content
373
*/
374
convert(clipboard: { html?: string; text?: string }): Delta;
375
376
/**
377
* Handle paste event
378
* @param event - Paste event
379
*/
380
onPaste(event: ClipboardEvent): void;
381
382
/**
383
* Handle copy/cut events
384
* @param event - Copy or cut event
385
*/
386
onCopy(event: ClipboardEvent): void;
387
}
388
389
interface ClipboardMatcher {
390
selector: string;
391
priority: number;
392
matcher: ClipboardMatcherFunction;
393
}
394
395
type ClipboardMatcherFunction = (node: Node, delta: Delta, scroll: Scroll) => Delta;
396
```
397
398
**Usage Examples:**
399
400
```typescript
401
// Get clipboard module
402
const clipboard = quill.getModule('clipboard');
403
404
// Add matcher to handle pasted images
405
clipboard.addMatcher('IMG', (node, delta) => {
406
const image = node as HTMLImageElement;
407
return new Delta().insert({ image: image.src });
408
});
409
410
// Add matcher for custom elements
411
clipboard.addMatcher('.custom-element', (node, delta) => {
412
const text = node.textContent || '';
413
return new Delta().insert(text, { 'custom-format': true });
414
});
415
416
// Add high-priority matcher
417
clipboard.addMatcher('STRONG', 10, (node, delta) => {
418
// Higher priority than default bold matcher
419
const text = node.textContent || '';
420
return new Delta().insert(text, { bold: true, 'custom-bold': true });
421
});
422
423
// Paste HTML directly
424
clipboard.dangerouslyPasteHTML('<p><strong>Bold text</strong></p>');
425
426
// Paste at specific position
427
clipboard.dangerouslyPasteHTML(10, '<em>Italic text</em>');
428
429
// Convert HTML to Delta
430
const delta = clipboard.convert({
431
html: '<p>Hello <strong>world</strong></p>',
432
text: 'Hello world'
433
});
434
```
435
436
### Toolbar Module
437
438
Toolbar UI with customizable controls and handlers.
439
440
```typescript { .api }
441
class Toolbar extends Module {
442
static DEFAULTS: {
443
/** Toolbar container selector or element */
444
container?: string | HTMLElement | ToolbarConfig;
445
/** Custom format handlers */
446
handlers?: Record<string, ToolbarHandler>;
447
};
448
449
/** Toolbar container element */
450
container: HTMLElement;
451
452
/** Array of [format, element] control pairs */
453
controls: [string, HTMLElement][];
454
455
/** Format handler functions */
456
handlers: Record<string, ToolbarHandler>;
457
458
/**
459
* Add custom format handler
460
* @param format - Format name
461
* @param handler - Handler function
462
*/
463
addHandler(format: string, handler: ToolbarHandler): void;
464
465
/**
466
* Attach toolbar to input element
467
* @param input - Input element to attach
468
*/
469
attach(input: HTMLElement): void;
470
471
/**
472
* Update toolbar state based on current selection
473
* @param range - Current selection range
474
*/
475
update(range: Range | null): void;
476
477
/**
478
* Update control state
479
* @param format - Format name
480
* @param value - Current format value
481
*/
482
updateControl(format: string, value: any): void;
483
}
484
485
type ToolbarConfig = (string | Record<string, any>)[];
486
487
type ToolbarHandler = (value: any) => void;
488
```
489
490
**Usage Examples:**
491
492
```typescript
493
// Configure toolbar
494
const quill = new Quill('#editor', {
495
modules: {
496
toolbar: [
497
['bold', 'italic', 'underline'],
498
[{ 'header': [1, 2, 3, false] }],
499
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
500
['link', 'image'],
501
['clean']
502
]
503
}
504
});
505
506
// Get toolbar module
507
const toolbar = quill.getModule('toolbar');
508
509
// Add custom handler
510
toolbar.addHandler('image', () => {
511
const input = document.createElement('input');
512
input.type = 'file';
513
input.accept = 'image/*';
514
input.onchange = () => {
515
const file = input.files[0];
516
if (file) {
517
const reader = new FileReader();
518
reader.onload = (e) => {
519
const range = quill.getSelection();
520
quill.insertEmbed(range.index, 'image', e.target.result);
521
};
522
reader.readAsDataURL(file);
523
}
524
};
525
input.click();
526
});
527
528
// Custom format handler
529
toolbar.addHandler('mention', (value) => {
530
if (value) {
531
const range = quill.getSelection();
532
quill.insertText(range.index, `@${value} `);
533
quill.setSelection(range.index + value.length + 2);
534
}
535
});
536
537
// HTML toolbar configuration
538
const quill2 = new Quill('#editor2', {
539
modules: {
540
toolbar: {
541
container: '#toolbar',
542
handlers: {
543
'custom-button': customButtonHandler
544
}
545
}
546
}
547
});
548
```
549
550
### Uploader Module
551
552
File upload handling with customizable upload logic.
553
554
```typescript { .api }
555
class Uploader extends Module {
556
static DEFAULTS: {
557
/** Allowed MIME types */
558
mimetypes?: string[];
559
/** Handler function for upload */
560
handler?: UploaderHandler;
561
};
562
563
/**
564
* Upload files
565
* @param range - Current selection range
566
* @param files - Files to upload
567
*/
568
upload(range: Range, files: File[] | FileList): void;
569
570
/**
571
* Handle file upload
572
* @param file - File to upload
573
* @param handler - Upload completion handler
574
*/
575
handler(file: File, handler: (url: string) => void): void;
576
}
577
578
type UploaderHandler = (file: File, callback: (url: string) => void) => void;
579
```
580
581
**Usage Examples:**
582
583
```typescript
584
// Configure uploader
585
const quill = new Quill('#editor', {
586
modules: {
587
uploader: {
588
mimetypes: ['image/png', 'image/jpeg'],
589
handler: (file, callback) => {
590
// Custom upload logic
591
const formData = new FormData();
592
formData.append('image', file);
593
594
fetch('/upload', {
595
method: 'POST',
596
body: formData
597
})
598
.then(response => response.json())
599
.then(data => {
600
callback(data.url);
601
})
602
.catch(err => {
603
console.error('Upload failed:', err);
604
});
605
}
606
}
607
}
608
});
609
610
// Get uploader module
611
const uploader = quill.getModule('uploader');
612
613
// Manually trigger upload
614
const fileInput = document.createElement('input');
615
fileInput.type = 'file';
616
fileInput.accept = 'image/*';
617
fileInput.onchange = () => {
618
const range = quill.getSelection();
619
uploader.upload(range, fileInput.files);
620
};
621
```
622
623
### Input Module
624
625
Input method editor (IME) and text input handling.
626
627
```typescript { .api }
628
class Input extends Module {
629
/**
630
* Handle text input events
631
* @param event - Input event
632
*/
633
onInput(event: InputEvent): void;
634
635
/**
636
* Handle composition start
637
* @param event - Composition event
638
*/
639
onCompositionStart(event: CompositionEvent): void;
640
641
/**
642
* Handle composition update
643
* @param event - Composition event
644
*/
645
onCompositionUpdate(event: CompositionEvent): void;
646
647
/**
648
* Handle composition end
649
* @param event - Composition event
650
*/
651
onCompositionEnd(event: CompositionEvent): void;
652
}
653
```
654
655
### Syntax Module
656
657
Syntax highlighting for code blocks using highlight.js.
658
659
```typescript { .api }
660
class Syntax extends Module {
661
static DEFAULTS: {
662
/** Highlight.js instance */
663
hljs?: any;
664
/** Highlight interval in milliseconds */
665
interval?: number;
666
/** Languages to support */
667
languages?: string[];
668
};
669
670
/** Highlight.js instance */
671
hljs: any;
672
673
/** Highlight timer */
674
timer: number | null;
675
676
/**
677
* Highlight code blocks
678
*/
679
highlight(): void;
680
681
/**
682
* Initialize syntax highlighting
683
*/
684
init(): void;
685
}
686
```
687
688
**Usage Examples:**
689
690
```typescript
691
// Configure syntax highlighting
692
const quill = new Quill('#editor', {
693
modules: {
694
syntax: {
695
hljs: window.hljs, // Include highlight.js library
696
languages: ['javascript', 'python', 'java', 'css']
697
},
698
toolbar: [
699
[{ 'code-block': 'Code' }]
700
]
701
}
702
});
703
704
// Code blocks will be automatically highlighted
705
```
706
707
### Custom Module Creation
708
709
Create custom modules to extend Quill functionality.
710
711
```typescript { .api }
712
class CustomModule extends Module {
713
static DEFAULTS = {
714
// Default options
715
};
716
717
constructor(quill, options) {
718
super(quill, options);
719
// Initialize module
720
}
721
}
722
723
// Register module
724
Quill.register('modules/custom', CustomModule);
725
```
726
727
**Usage Examples:**
728
729
```typescript
730
// Word count module
731
class WordCount extends Module {
732
static DEFAULTS = {
733
container: null,
734
unit: 'word'
735
};
736
737
constructor(quill, options) {
738
super(quill, options);
739
this.container = document.querySelector(options.container);
740
this.quill.on('text-change', this.update.bind(this));
741
this.update(); // Initial count
742
}
743
744
calculate() {
745
const text = this.quill.getText();
746
if (this.options.unit === 'word') {
747
return text.split(/\s+/).filter(word => word.length > 0).length;
748
} else {
749
return text.length;
750
}
751
}
752
753
update() {
754
const count = this.calculate();
755
if (this.container) {
756
this.container.textContent = `${count} ${this.options.unit}s`;
757
}
758
}
759
}
760
761
// Register and use
762
Quill.register('modules/wordCount', WordCount);
763
764
const quill = new Quill('#editor', {
765
modules: {
766
wordCount: {
767
container: '#word-count',
768
unit: 'word'
769
}
770
}
771
});
772
```
773
774
## Table Module
775
776
Advanced table management module providing comprehensive table manipulation functionality including insertion, deletion, and structural modifications.
777
778
### Core Table Operations
779
780
```typescript { .api }
781
class Table extends Module {
782
static register(): void;
783
784
// Table structure manipulation
785
insertTable(rows: number, columns: number): void;
786
deleteTable(): void;
787
balanceTables(): void;
788
789
// Row operations
790
insertRowAbove(): void;
791
insertRowBelow(): void;
792
deleteRow(): void;
793
794
// Column operations
795
insertColumnLeft(): void;
796
insertColumnRight(): void;
797
deleteColumn(): void;
798
799
// Table state and utilities
800
getTable(range?: Range): [TableContainer | null, TableRow | null, TableCell | null, number];
801
}
802
```
803
804
### Table Module Options
805
806
```typescript { .api }
807
interface TableOptions {
808
// Currently no specific options for Table module
809
}
810
```
811
812
**Usage Examples:**
813
814
```typescript
815
import Quill from 'quill';
816
817
// Enable table module
818
const quill = new Quill('#editor', {
819
modules: {
820
table: true
821
}
822
});
823
824
// Get table module instance
825
const tableModule = quill.getModule('table');
826
827
// Insert a 3x4 table
828
tableModule.insertTable(3, 4);
829
830
// Table manipulation within existing table
831
// (requires cursor to be in a table)
832
tableModule.insertRowAbove();
833
tableModule.insertColumnRight();
834
tableModule.deleteRow();
835
tableModule.deleteColumn();
836
837
// Remove entire table
838
tableModule.deleteTable();
839
840
// Balance all table cells (ensure consistent structure)
841
tableModule.balanceTables();
842
```
843
844
The Table module automatically registers all table-related formats (TableContainer, TableBody, TableRow, TableCell) and provides a complete API for programmatic table manipulation.