or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

command-system.mddocument-helpers.mdeditor-core.mdextension-system.mdindex.mdrule-systems.mdutilities.md

command-system.mddocs/

0

# Command System

1

2

The command system in @tiptap/core provides a powerful and flexible way to execute editor actions. Commands can be executed individually, chained together for complex operations, or validated before execution.

3

4

## Capabilities

5

6

### CommandManager

7

8

The CommandManager class handles command execution, chaining, and validation within the editor context.

9

10

```typescript { .api }

11

/**

12

* Manages command execution and validation for the editor

13

*/

14

class CommandManager {

15

/**

16

* Create a new CommandManager instance

17

* @param props - Configuration including editor and state

18

*/

19

constructor(props: {

20

editor: Editor;

21

state: EditorState

22

});

23

24

/** Direct access to individual commands */

25

readonly commands: SingleCommands;

26

27

/**

28

* Create a command chain for sequential execution

29

* @returns ChainedCommands instance for chaining operations

30

*/

31

chain(): ChainedCommands;

32

33

/**

34

* Create command validation interface

35

* @returns CanCommands instance for testing executability

36

*/

37

can(): CanCommands;

38

39

/**

40

* Create a custom command chain with specific transaction and dispatch behavior

41

* @param startTr - Optional starting transaction

42

* @param shouldDispatch - Whether commands should be dispatched immediately

43

* @returns ChainedCommands instance

44

*/

45

createChain(

46

startTr?: Transaction,

47

shouldDispatch?: boolean

48

): ChainedCommands;

49

50

/**

51

* Create a custom command validation instance

52

* @param startTr - Optional starting transaction

53

* @returns CanCommands instance

54

*/

55

createCan(startTr?: Transaction): CanCommands;

56

57

/**

58

* Build command properties for command execution

59

* @param tr - Transaction to build props for

60

* @param shouldDispatch - Whether to include dispatch function

61

* @returns CommandProps object

62

*/

63

buildProps(

64

tr: Transaction,

65

shouldDispatch?: boolean

66

): CommandProps;

67

}

68

```

69

70

### Single Commands

71

72

Interface for executing individual commands that return boolean success values.

73

74

```typescript { .api }

75

/**

76

* Interface for executing individual commands

77

*/

78

interface SingleCommands {

79

[commandName: string]: (attributes?: Record<string, any>) => boolean;

80

81

// Core commands available by default

82

insertContent(value: Content, options?: InsertContentOptions): boolean;

83

deleteSelection(): boolean;

84

deleteRange(range: { from: number; to: number }): boolean;

85

enter(): boolean;

86

focus(position?: FocusPosition, options?: { scrollIntoView?: boolean }): boolean;

87

blur(): boolean;

88

selectAll(): boolean;

89

selectTextblockStart(): boolean;

90

selectTextblockEnd(): boolean;

91

selectNodeForward(): boolean;

92

selectNodeBackward(): boolean;

93

selectParentNode(): boolean;

94

95

// Mark commands

96

setMark(typeOrName: string | MarkType, attributes?: Record<string, any>): boolean;

97

toggleMark(typeOrName: string | MarkType, attributes?: Record<string, any>): boolean;

98

unsetMark(typeOrName: string | MarkType, options?: { extendEmptyMarkRange?: boolean }): boolean;

99

100

// Node commands

101

setNode(typeOrName: string | NodeType, attributes?: Record<string, any>): boolean;

102

toggleNode(typeOrName: string | NodeType, toggleTypeOrName?: string | NodeType, attributes?: Record<string, any>): boolean;

103

wrapIn(typeOrName: string | NodeType, attributes?: Record<string, any>): boolean;

104

lift(typeOrName?: string | NodeType, attributes?: Record<string, any>): boolean;

105

liftEmptyBlock(): boolean;

106

splitBlock(options?: { keepMarks?: boolean }): boolean;

107

joinBackward(): boolean;

108

joinForward(): boolean;

109

110

// Custom commands added by extensions

111

[extensionCommand: string]: (attributes?: Record<string, any>) => boolean;

112

}

113

114

interface InsertContentOptions {

115

parseOptions?: ParseOptions;

116

updateSelection?: boolean;

117

}

118

119

type Content =

120

| string

121

| JSONContent

122

| JSONContent[]

123

| ProseMirrorNode

124

| ProseMirrorNode[]

125

| ProseMirrorFragment;

126

```

127

128

**Usage Examples:**

129

130

```typescript

131

// Execute individual commands

132

const success = editor.commands.insertContent('Hello World!');

133

134

if (editor.commands.focus()) {

135

editor.commands.selectAll();

136

}

137

138

// Mark commands

139

editor.commands.setMark('bold');

140

editor.commands.toggleMark('italic');

141

editor.commands.unsetMark('link');

142

143

// Node commands

144

editor.commands.setNode('heading', { level: 1 });

145

editor.commands.wrapIn('blockquote');

146

editor.commands.lift('listItem');

147

148

// Insert complex content

149

editor.commands.insertContent({

150

type: 'paragraph',

151

content: [

152

{

153

type: 'text',

154

text: 'Bold text',

155

marks: [{ type: 'bold' }]

156

}

157

]

158

});

159

160

// Delete and select commands

161

editor.commands.deleteSelection();

162

editor.commands.selectTextblockStart();

163

editor.commands.selectParentNode();

164

```

165

166

### Chained Commands

167

168

Interface for chaining multiple commands together for sequential execution.

169

170

```typescript { .api }

171

/**

172

* Interface for chaining commands together

173

*/

174

interface ChainedCommands {

175

[commandName: string]: (attributes?: Record<string, any>) => ChainedCommands;

176

177

/**

178

* Execute the entire command chain

179

* @returns Whether all commands in the chain succeeded

180

*/

181

run(): boolean;

182

183

// All single commands are available for chaining

184

insertContent(value: Content, options?: InsertContentOptions): ChainedCommands;

185

deleteSelection(): ChainedCommands;

186

deleteRange(range: { from: number; to: number }): ChainedCommands;

187

enter(): ChainedCommands;

188

focus(position?: FocusPosition, options?: { scrollIntoView?: boolean }): ChainedCommands;

189

blur(): ChainedCommands;

190

selectAll(): ChainedCommands;

191

192

setMark(typeOrName: string | MarkType, attributes?: Record<string, any>): ChainedCommands;

193

toggleMark(typeOrName: string | MarkType, attributes?: Record<string, any>): ChainedCommands;

194

unsetMark(typeOrName: string | MarkType, options?: { extendEmptyMarkRange?: boolean }): ChainedCommands;

195

196

setNode(typeOrName: string | NodeType, attributes?: Record<string, any>): ChainedCommands;

197

toggleNode(typeOrName: string | NodeType, toggleTypeOrName?: string | NodeType, attributes?: Record<string, any>): ChainedCommands;

198

wrapIn(typeOrName: string | NodeType, attributes?: Record<string, any>): ChainedCommands;

199

lift(typeOrName?: string | NodeType, attributes?: Record<string, any>): ChainedCommands;

200

201

// Custom commands added by extensions

202

[extensionCommand: string]: (attributes?: Record<string, any>) => ChainedCommands;

203

}

204

```

205

206

**Usage Examples:**

207

208

```typescript

209

// Basic command chaining

210

editor

211

.chain()

212

.focus()

213

.selectAll()

214

.deleteSelection()

215

.insertContent('New content')

216

.run();

217

218

// Complex formatting chain

219

editor

220

.chain()

221

.focus()

222

.toggleMark('bold')

223

.toggleMark('italic')

224

.insertContent('Bold and italic text')

225

.setMark('link', { href: 'https://example.com' })

226

.insertContent(' with a link')

227

.run();

228

229

// Node manipulation chain

230

editor

231

.chain()

232

.focus()

233

.selectAll()

234

.wrapIn('blockquote')

235

.setNode('heading', { level: 2 })

236

.insertContent('Quoted heading')

237

.run();

238

239

// Conditional chaining

240

const success = editor

241

.chain()

242

.focus()

243

.deleteSelection() // Only if something is selected

244

.insertContent('Replacement text')

245

.run();

246

247

if (success) {

248

console.log('Chain executed successfully');

249

}

250

251

// Chain with custom commands (from extensions)

252

editor

253

.chain()

254

.focus()

255

.setFontSize(16)

256

.setTextAlign('center')

257

.insertTable({ rows: 3, cols: 3 })

258

.run();

259

```

260

261

### Command Validation

262

263

Interface for testing whether commands can be executed without actually running them.

264

265

```typescript { .api }

266

/**

267

* Interface for validating command executability

268

*/

269

interface CanCommands {

270

[commandName: string]: (attributes?: Record<string, any>) => boolean;

271

272

// Core command validation

273

insertContent(value: Content, options?: InsertContentOptions): boolean;

274

deleteSelection(): boolean;

275

deleteRange(range: { from: number; to: number }): boolean;

276

enter(): boolean;

277

focus(position?: FocusPosition): boolean;

278

blur(): boolean;

279

selectAll(): boolean;

280

281

setMark(typeOrName: string | MarkType, attributes?: Record<string, any>): boolean;

282

toggleMark(typeOrName: string | MarkType, attributes?: Record<string, any>): boolean;

283

unsetMark(typeOrName: string | MarkType): boolean;

284

285

setNode(typeOrName: string | NodeType, attributes?: Record<string, any>): boolean;

286

toggleNode(typeOrName: string | NodeType, toggleTypeOrName?: string | NodeType): boolean;

287

wrapIn(typeOrName: string | NodeType, attributes?: Record<string, any>): boolean;

288

lift(typeOrName?: string | NodeType): boolean;

289

290

// Custom commands added by extensions

291

[extensionCommand: string]: (attributes?: Record<string, any>) => boolean;

292

}

293

```

294

295

**Usage Examples:**

296

297

```typescript

298

// Check if commands can be executed

299

if (editor.can().toggleMark('bold')) {

300

editor.commands.toggleMark('bold');

301

}

302

303

if (editor.can().wrapIn('blockquote')) {

304

editor.commands.wrapIn('blockquote');

305

}

306

307

// Conditional UI updates

308

function BoldButton() {

309

const canToggleBold = editor.can().toggleMark('bold');

310

const isBold = editor.isActive('bold');

311

312

return (

313

<button

314

disabled={!canToggleBold}

315

className={isBold ? 'active' : ''}

316

onClick={() => editor.commands.toggleMark('bold')}

317

>

318

Bold

319

</button>

320

);

321

}

322

323

// Check complex operations

324

const canInsertTable = editor.can().insertTable?.({ rows: 3, cols: 3 });

325

const canSetHeading = editor.can().setNode('heading', { level: 1 });

326

327

// Multiple validations

328

const formattingActions = [

329

{ name: 'bold', can: editor.can().toggleMark('bold') },

330

{ name: 'italic', can: editor.can().toggleMark('italic') },

331

{ name: 'code', can: editor.can().toggleMark('code') },

332

{ name: 'link', can: editor.can().setMark('link', { href: '' }) }

333

];

334

335

const availableActions = formattingActions.filter(action => action.can);

336

```

337

338

### Command Properties

339

340

The properties passed to command functions when they are executed.

341

342

```typescript { .api }

343

/**

344

* Properties passed to command functions during execution

345

*/

346

interface CommandProps {

347

/** The editor instance */

348

editor: Editor;

349

350

/** Current transaction (may be modified by commands) */

351

tr: Transaction;

352

353

/** Access to all single commands */

354

commands: SingleCommands;

355

356

/** Access to command validation */

357

can: CanCommands;

358

359

/** Create a new command chain */

360

chain: () => ChainedCommands;

361

362

/** Current editor state */

363

state: EditorState;

364

365

/** ProseMirror editor view */

366

view: EditorView;

367

368

/** Dispatch function (undefined in dry-run mode) */

369

dispatch: ((tr: Transaction) => void) | undefined;

370

}

371

372

/**

373

* Command function signature

374

*/

375

type CommandFunction = (props: CommandProps) => boolean;

376

```

377

378

### Creating Custom Commands

379

380

How to create custom commands in extensions.

381

382

```typescript { .api }

383

/**

384

* Commands configuration for extensions

385

*/

386

interface Commands {

387

[commandName: string]: (...args: any[]) => CommandFunction;

388

}

389

```

390

391

**Usage Examples:**

392

393

```typescript

394

import { Extension } from '@tiptap/core';

395

396

// Extension with custom commands

397

const CustomCommands = Extension.create({

398

name: 'customCommands',

399

400

addCommands() {

401

return {

402

// Simple command

403

insertDate: () => ({ commands }) => {

404

const date = new Date().toLocaleDateString();

405

return commands.insertContent(date);

406

},

407

408

// Command with parameters

409

insertHeading: (level: number, text: string) => ({ commands, chain }) => {

410

return chain()

411

.setNode('heading', { level })

412

.insertContent(text)

413

.run();

414

},

415

416

// Complex command using transaction

417

duplicateLine: () => ({ tr, state, dispatch }) => {

418

const { from, to } = state.selection;

419

const line = state.doc.textBetween(from, to);

420

421

if (!line) return false;

422

423

tr.insertText(`\n${line}`, to);

424

425

if (dispatch) {

426

dispatch(tr);

427

}

428

429

return true;

430

},

431

432

// Command that checks state

433

toggleHighlight: (color: string = 'yellow') => ({ commands, editor }) => {

434

const isActive = editor.isActive('highlight', { color });

435

436

if (isActive) {

437

return commands.unsetMark('highlight');

438

}

439

440

return commands.setMark('highlight', { color });

441

},

442

443

// Command with validation

444

wrapInCallout: (type: string = 'info') => ({ commands, can }) => {

445

// Only wrap if we can and aren't already in a callout

446

if (!can().wrapIn('callout') || editor.isActive('callout')) {

447

return false;

448

}

449

450

return commands.wrapIn('callout', { type });

451

}

452

};

453

}

454

});

455

456

// Using custom commands

457

editor.commands.insertDate();

458

editor.commands.insertHeading(1, 'Chapter Title');

459

editor.commands.duplicateLine();

460

461

// Chain custom commands

462

editor

463

.chain()

464

.focus()

465

.insertHeading(2, 'Section')

466

.insertDate()

467

.toggleHighlight('blue')

468

.run();

469

470

// Validate custom commands

471

if (editor.can().wrapInCallout('warning')) {

472

editor.commands.wrapInCallout('warning');

473

}

474

```

475

476

### Command Execution Flow

477

478

Understanding how commands are processed and executed.

479

480

```typescript { .api }

481

/**

482

* Command execution involves several phases:

483

* 1. Command function is called with CommandProps

484

* 2. Command modifies the transaction (tr)

485

* 3. Command returns boolean success value

486

* 4. Transaction is dispatched (if dispatch is provided)

487

* 5. Editor state is updated

488

*/

489

490

// Example command implementation

491

const exampleCommand = (text: string) => ({ tr, dispatch, state }) => {

492

// 1. Validate the command can run

493

if (!text || text.trim().length === 0) {

494

return false;

495

}

496

497

// 2. Modify the transaction

498

const { from } = state.selection;

499

tr.insertText(text, from);

500

501

// 3. Dispatch if available (not in validation mode)

502

if (dispatch) {

503

dispatch(tr);

504

}

505

506

// 4. Return success

507

return true;

508

};

509

510

// Command chaining execution

511

// Each command in a chain operates on the same transaction

512

// The transaction is only dispatched when .run() is called

513

editor

514

.chain()

515

.command1() // Modifies tr

516

.command2() // Modifies same tr

517

.command3() // Modifies same tr

518

.run(); // Dispatches tr with all modifications

519

```