or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

delta-operations.mdeditor-core.mdformatting-system.mdindex.mdmodule-system.mdregistry-system.mdtheme-system.md

module-system.mddocs/

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.