or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

collaboration.mdcommands-and-editing.mdcursors-and-enhancements.mdhistory.mdindex.mdinput-and-keymaps.mdmarkdown.mdmenus-and-ui.mdmodel-and-schema.mdschema-definitions.mdstate-management.mdtables.mdtransformations.mdview-and-rendering.md

history.mddocs/

0

# History

1

2

The history system provides undo and redo functionality by tracking document changes and maintaining reversible state transitions. It intelligently groups related changes and manages the undo stack.

3

4

## Capabilities

5

6

### History Plugin

7

8

Create and configure the undo/redo system.

9

10

```typescript { .api }

11

/**

12

* Create a history plugin with configurable options

13

*/

14

function history(config?: HistoryOptions): Plugin;

15

16

/**

17

* History plugin configuration options

18

*/

19

interface HistoryOptions {

20

/**

21

* Maximum number of events in the history (default: 100)

22

*/

23

depth?: number;

24

25

/**

26

* Delay in milliseconds for grouping events (default: 500)

27

*/

28

newGroupDelay?: number;

29

}

30

```

31

32

### History Commands

33

34

Commands for navigating through the undo/redo stack.

35

36

```typescript { .api }

37

/**

38

* Undo the last change and scroll to the affected area

39

*/

40

function undo(state: EditorState, dispatch?: (tr: Transaction) => void): boolean;

41

42

/**

43

* Undo the last change without scrolling

44

*/

45

function undoNoScroll(state: EditorState, dispatch?: (tr: Transaction) => void): boolean;

46

47

/**

48

* Redo the last undone change and scroll to the affected area

49

*/

50

function redo(state: EditorState, dispatch?: (tr: Transaction) => void): boolean;

51

52

/**

53

* Redo the last undone change without scrolling

54

*/

55

function redoNoScroll(state: EditorState, dispatch?: (tr: Transaction) => void): boolean;

56

```

57

58

### History State Queries

59

60

Functions to inspect the current history state.

61

62

```typescript { .api }

63

/**

64

* Get the number of undoable events in the history

65

*/

66

function undoDepth(state: EditorState): number;

67

68

/**

69

* Get the number of redoable events in the history

70

*/

71

function redoDepth(state: EditorState): number;

72

```

73

74

### History Control

75

76

Functions to control history behavior and grouping.

77

78

```typescript { .api }

79

/**

80

* Prevent the next steps from being grouped with previous ones

81

*/

82

function closeHistory(tr: Transaction): Transaction;

83

```

84

85

**Usage Examples:**

86

87

```typescript

88

import {

89

history,

90

undo,

91

redo,

92

undoDepth,

93

redoDepth,

94

closeHistory

95

} from "@tiptap/pm/history";

96

import { keymap } from "@tiptap/pm/keymap";

97

98

// Basic history setup

99

const historyPlugin = history({

100

depth: 50, // Keep 50 events

101

newGroupDelay: 1000 // Group events within 1 second

102

});

103

104

// History keymap

105

const historyKeymap = keymap({

106

"Mod-z": undo,

107

"Mod-y": redo,

108

"Mod-Shift-z": redo // Alternative redo binding

109

});

110

111

// Create editor with history

112

const state = EditorState.create({

113

schema: mySchema,

114

plugins: [

115

historyPlugin,

116

historyKeymap

117

]

118

});

119

120

// Check history state

121

function canUndo(state: EditorState): boolean {

122

return undoDepth(state) > 0;

123

}

124

125

function canRedo(state: EditorState): boolean {

126

return redoDepth(state) > 0;

127

}

128

129

// Custom undo/redo with UI updates

130

function createHistoryActions(view: EditorView) {

131

return {

132

undo: () => {

133

if (canUndo(view.state)) {

134

undo(view.state, view.dispatch);

135

updateHistoryButtons(view);

136

}

137

},

138

139

redo: () => {

140

if (canRedo(view.state)) {

141

redo(view.state, view.dispatch);

142

updateHistoryButtons(view);

143

}

144

}

145

};

146

}

147

148

function updateHistoryButtons(view: EditorView) {

149

const undoButton = document.getElementById("undo-btn");

150

const redoButton = document.getElementById("redo-btn");

151

152

undoButton.disabled = !canUndo(view.state);

153

redoButton.disabled = !canRedo(view.state);

154

}

155

```

156

157

## Advanced History Management

158

159

### Manual History Grouping

160

161

Control when history events are grouped together.

162

163

```typescript

164

// Group related operations

165

function performComplexEdit(view: EditorView) {

166

let tr = view.state.tr;

167

168

// Start a new history group

169

tr = closeHistory(tr);

170

171

// Perform multiple related operations

172

tr = tr.insertText("New content at ", 10);

173

tr = tr.addMark(10, 25, view.state.schema.marks.strong.create());

174

tr = tr.insertText(" with formatting", 25);

175

176

// Dispatch as single undoable unit

177

view.dispatch(tr);

178

}

179

180

// Prevent grouping with previous edits

181

function insertTimestamp(view: EditorView) {

182

const tr = closeHistory(view.state.tr);

183

const timestamp = new Date().toLocaleString();

184

185

view.dispatch(

186

tr.insertText(`[${timestamp}] `)

187

);

188

}

189

```

190

191

### Custom History Behavior

192

193

Create specialized history handling for specific operations.

194

195

```typescript

196

// Auto-save with history preservation

197

class AutoSaveManager {

198

private saveTimeout: NodeJS.Timeout | null = null;

199

200

constructor(private view: EditorView, private saveInterval: number = 30000) {

201

this.setupAutoSave();

202

}

203

204

private setupAutoSave() {

205

const plugin = new Plugin({

206

state: {

207

init: () => null,

208

apply: (tr, value) => {

209

if (tr.docChanged) {

210

this.scheduleAutoSave();

211

}

212

return value;

213

}

214

}

215

});

216

217

// Add plugin to existing plugins

218

const newState = this.view.state.reconfigure({

219

plugins: this.view.state.plugins.concat(plugin)

220

});

221

this.view.updateState(newState);

222

}

223

224

private scheduleAutoSave() {

225

if (this.saveTimeout) {

226

clearTimeout(this.saveTimeout);

227

}

228

229

this.saveTimeout = setTimeout(() => {

230

this.performAutoSave();

231

}, this.saveInterval);

232

}

233

234

private async performAutoSave() {

235

try {

236

// Close history group before save

237

const tr = closeHistory(this.view.state.tr);

238

tr.setMeta("auto-save", true);

239

this.view.dispatch(tr);

240

241

// Perform save operation

242

await this.saveDocument();

243

244

// Clear undo history if save successful and history is deep

245

if (undoDepth(this.view.state) > 100) {

246

this.clearOldHistory();

247

}

248

} catch (error) {

249

console.error("Auto-save failed:", error);

250

}

251

}

252

253

private clearOldHistory() {

254

// Create new state with fresh history

255

const historyPlugin = history({ depth: 20 });

256

const newState = this.view.state.reconfigure({

257

plugins: this.view.state.plugins

258

.filter(p => p.spec !== history().spec)

259

.concat(historyPlugin)

260

});

261

this.view.updateState(newState);

262

}

263

264

private async saveDocument(): Promise<void> {

265

// Implement your save logic here

266

const doc = this.view.state.doc.toJSON();

267

// await api.save(doc);

268

}

269

}

270

```

271

272

### History Visualization

273

274

Create UI components that show history state.

275

276

```typescript

277

// History timeline component

278

class HistoryTimeline {

279

private element: HTMLElement;

280

281

constructor(private view: EditorView) {

282

this.element = this.createElement();

283

this.updateTimeline();

284

285

// Listen for state changes

286

this.view.setProps({

287

dispatchTransaction: (tr) => {

288

this.view.updateState(this.view.state.apply(tr));

289

this.updateTimeline();

290

}

291

});

292

}

293

294

private createElement(): HTMLElement {

295

const container = document.createElement("div");

296

container.className = "history-timeline";

297

return container;

298

}

299

300

private updateTimeline() {

301

const undoCount = undoDepth(this.view.state);

302

const redoCount = redoDepth(this.view.state);

303

304

this.element.innerHTML = "";

305

306

// Add undo items

307

for (let i = undoCount - 1; i >= 0; i--) {

308

const item = document.createElement("div");

309

item.className = "history-item undo-item";

310

item.textContent = `Undo ${i + 1}`;

311

item.onclick = () => this.undoToStep(i);

312

this.element.appendChild(item);

313

}

314

315

// Add current state marker

316

const current = document.createElement("div");

317

current.className = "history-item current-item";

318

current.textContent = "Current";

319

this.element.appendChild(current);

320

321

// Add redo items

322

for (let i = 0; i < redoCount; i++) {

323

const item = document.createElement("div");

324

item.className = "history-item redo-item";

325

item.textContent = `Redo ${i + 1}`;

326

item.onclick = () => this.redoToStep(i);

327

this.element.appendChild(item);

328

}

329

}

330

331

private undoToStep(step: number) {

332

for (let i = 0; i <= step; i++) {

333

if (canUndo(this.view.state)) {

334

undo(this.view.state, this.view.dispatch);

335

}

336

}

337

}

338

339

private redoToStep(step: number) {

340

for (let i = 0; i <= step; i++) {

341

if (canRedo(this.view.state)) {

342

redo(this.view.state, this.view.dispatch);

343

}

344

}

345

}

346

347

public getElement(): HTMLElement {

348

return this.element;

349

}

350

}

351

```

352

353

### Collaborative History

354

355

Handle history in collaborative editing scenarios.

356

357

```typescript

358

// History manager for collaborative editing

359

class CollaborativeHistory {

360

constructor(private view: EditorView) {

361

this.setupCollaborativeHistory();

362

}

363

364

private setupCollaborativeHistory() {

365

const plugin = new Plugin({

366

state: {

367

init: () => ({

368

localUndoDepth: 0,

369

remoteChanges: []

370

}),

371

372

apply: (tr, value) => {

373

// Track local vs remote changes

374

const isLocal = !tr.getMeta("remote");

375

const isUndo = tr.getMeta("history$") === "undo";

376

const isRedo = tr.getMeta("history$") === "redo";

377

378

if (isLocal && !isUndo && !isRedo) {

379

// Local change - can be undone

380

return {

381

...value,

382

localUndoDepth: value.localUndoDepth + 1

383

};

384

} else if (tr.getMeta("remote")) {

385

// Remote change - affects undo stack

386

return {

387

...value,

388

remoteChanges: [...value.remoteChanges, tr]

389

};

390

}

391

392

return value;

393

}

394

},

395

396

props: {

397

handleKeyDown: (view, event) => {

398

// Custom undo/redo for collaborative context

399

if (event.key === "z" && (event.metaKey || event.ctrlKey)) {

400

if (event.shiftKey) {

401

return this.collaborativeRedo(view);

402

} else {

403

return this.collaborativeUndo(view);

404

}

405

}

406

return false;

407

}

408

}

409

});

410

411

const newState = this.view.state.reconfigure({

412

plugins: this.view.state.plugins.concat(plugin)

413

});

414

this.view.updateState(newState);

415

}

416

417

private collaborativeUndo(view: EditorView): boolean {

418

// Only undo local changes

419

const state = view.state;

420

const pluginState = this.getPluginState(state);

421

422

if (pluginState.localUndoDepth > 0) {

423

return undo(state, view.dispatch);

424

}

425

426

return false;

427

}

428

429

private collaborativeRedo(view: EditorView): boolean {

430

// Only redo local changes

431

const state = view.state;

432

433

if (redoDepth(state) > 0) {

434

return redo(state, view.dispatch);

435

}

436

437

return false;

438

}

439

440

private getPluginState(state: EditorState) {

441

// Get plugin state helper

442

return state.plugins.find(p => p.spec.key === "collaborativeHistory")?.getState(state);

443

}

444

}

445

```

446

447

## Types

448

449

```typescript { .api }

450

/**

451

* History plugin configuration

452

*/

453

interface HistoryOptions {

454

depth?: number;

455

newGroupDelay?: number;

456

}

457

458

/**

459

* History metadata for transactions

460

*/

461

interface HistoryMeta {

462

"history$"?: "undo" | "redo";

463

addToHistory?: boolean;

464

preserveItems?: number;

465

}

466

```