or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

command-system.mdcontext-system.mdeditor-management.mdindex.mdinternal-plugins.mdkeymap-management.md

context-system.mddocs/

0

# Context System

1

2

The context system is the foundational dependency injection and state management system that powers all Milkdown functionality. It provides type-safe access to shared state, configuration, and services throughout the editor lifecycle.

3

4

## Capabilities

5

6

### Core Context Slices

7

8

The primary context slices that manage essential editor state and functionality.

9

10

```typescript { .api }

11

/**

12

* Core editor state and view management slices

13

*/

14

15

/** Contains the ProseMirror editor view instance */

16

const editorViewCtx: SliceType<EditorView, 'editorView'>;

17

18

/** Contains the ProseMirror editor state */

19

const editorStateCtx: SliceType<EditorState, 'editorState'>;

20

21

/** Contains the main editor instance */

22

const editorCtx: SliceType<Editor, 'editor'>;

23

24

/** Contains the ProseMirror schema */

25

const schemaCtx: SliceType<Schema, 'schema'>;

26

27

/** Contains the markdown-to-ProseMirror parser */

28

const parserCtx: SliceType<Parser, 'parser'>;

29

30

/** Contains the ProseMirror-to-markdown serializer */

31

const serializerCtx: SliceType<Serializer, 'serializer'>;

32

33

/** Contains the command manager instance */

34

const commandsCtx: SliceType<CommandManager, 'commands'>;

35

36

/** Contains the keymap manager instance */

37

const keymapCtx: SliceType<KeymapManager, 'keymap'>;

38

```

39

40

**Usage Examples:**

41

42

```typescript

43

import { Editor, editorStateCtx, editorViewCtx, schemaCtx } from "@milkdown/core";

44

45

editor.action((ctx) => {

46

// Access core editor components

47

const state = ctx.get(editorStateCtx);

48

const view = ctx.get(editorViewCtx);

49

const schema = ctx.get(schemaCtx);

50

51

console.log('Current selection:', state.selection);

52

console.log('Editor DOM element:', view.dom);

53

console.log('Available nodes:', Object.keys(schema.nodes));

54

});

55

```

56

57

### Configuration Slices

58

59

Context slices for editor configuration and customization.

60

61

```typescript { .api }

62

/**

63

* Configuration and customization slices

64

*/

65

66

/** The default content for editor initialization (string, HTML, or JSON) */

67

const defaultValueCtx: SliceType<DefaultValue, 'defaultValue'>;

68

69

/** The root element or selector for editor mounting */

70

const rootCtx: SliceType<RootType, 'root'>;

71

72

/** Attributes to apply to the root container element */

73

const rootAttrsCtx: SliceType<Record<string, string>, 'rootAttrs'>;

74

75

/** The actual root DOM element after resolution */

76

const rootDOMCtx: SliceType<HTMLElement, 'rootDOM'>;

77

78

/** Options passed to the ProseMirror EditorView constructor */

79

const editorViewOptionsCtx: SliceType<Partial<EditorOptions>, 'editorViewOptions'>;

80

81

/** Function to override editor state creation options */

82

const editorStateOptionsCtx: SliceType<StateOptionsOverride, 'editorStateOptions'>;

83

```

84

85

**Usage Examples:**

86

87

```typescript

88

import { Editor, defaultValueCtx, rootCtx, rootAttrsCtx } from "@milkdown/core";

89

90

const editor = Editor.make()

91

.config((ctx) => {

92

// Set initial content

93

ctx.set(defaultValueCtx, '# Welcome\n\nStart writing...');

94

95

// Set root element

96

ctx.set(rootCtx, document.getElementById('editor'));

97

98

// Add CSS classes and attributes to root

99

ctx.set(rootAttrsCtx, {

100

'class': 'my-editor-theme dark-mode',

101

'data-testid': 'milkdown-editor'

102

});

103

});

104

```

105

106

### Plugin and Rule Storage Slices

107

108

Context slices that store various types of plugins and rules used by the editor.

109

110

```typescript { .api }

111

/**

112

* Plugin and rule storage slices

113

*/

114

115

/** Array of ProseMirror input rules for text transformations */

116

const inputRulesCtx: SliceType<InputRule[], 'inputRules'>;

117

118

/** Array of ProseMirror plugins for editor behavior */

119

const prosePluginsCtx: SliceType<Plugin[], 'prosePlugins'>;

120

121

/** Array of remark plugins for markdown processing */

122

const remarkPluginsCtx: SliceType<RemarkPlugin[], 'remarkPlugins'>;

123

124

/** Array of custom node view constructors */

125

const nodeViewCtx: SliceType<NodeView[], 'nodeView'>;

126

127

/** Array of custom mark view constructors */

128

const markViewCtx: SliceType<MarkView[], 'markView'>;

129

```

130

131

**Usage Examples:**

132

133

```typescript

134

import {

135

Editor,

136

inputRulesCtx,

137

prosePluginsCtx,

138

nodeViewCtx

139

} from "@milkdown/core";

140

import { Plugin } from "@milkdown/prose/state";

141

import { InputRule } from "@milkdown/prose/inputrules";

142

143

editor.config((ctx) => {

144

// Add input rules

145

ctx.update(inputRulesCtx, (rules) => [

146

...rules,

147

new InputRule(/--$/, '—'), // Convert -- to em dash

148

new InputRule(/\.\.\./, '…') // Convert ... to ellipsis

149

]);

150

151

// Add ProseMirror plugins

152

ctx.update(prosePluginsCtx, (plugins) => [

153

...plugins,

154

new Plugin({

155

key: new PluginKey('myCustomPlugin'),

156

// Plugin implementation

157

})

158

]);

159

160

// Add custom node views

161

ctx.update(nodeViewCtx, (views) => [

162

...views,

163

['image', MyImageNodeView],

164

['code_block', MyCodeBlockView]

165

]);

166

});

167

```

168

169

### Schema Definition Slices

170

171

Context slices for defining the editor's document schema.

172

173

```typescript { .api }

174

/**

175

* Schema definition slices

176

*/

177

178

/** Array of node specifications for the schema */

179

const nodesCtx: SliceType<Array<[string, NodeSchema]>, 'nodes'>;

180

181

/** Array of mark specifications for the schema */

182

const marksCtx: SliceType<Array<[string, MarkSchema]>, 'marks'>;

183

```

184

185

**Usage Examples:**

186

187

```typescript

188

import { Editor, nodesCtx, marksCtx } from "@milkdown/core";

189

190

editor.config((ctx) => {

191

// Add custom nodes

192

ctx.update(nodesCtx, (nodes) => [

193

...nodes,

194

['callout', {

195

content: 'block+',

196

group: 'block',

197

defining: true,

198

attrs: {

199

type: { default: 'info' }

200

},

201

parseDOM: [{ tag: 'div.callout' }],

202

toDOM: (node) => ['div', { class: `callout ${node.attrs.type}` }, 0]

203

}]

204

]);

205

206

// Add custom marks

207

ctx.update(marksCtx, (marks) => [

208

...marks,

209

['highlight', {

210

attrs: {

211

color: { default: 'yellow' }

212

},

213

parseDOM: [{ tag: 'mark' }],

214

toDOM: (mark) => ['mark', { style: `background-color: ${mark.attrs.color}` }]

215

}]

216

]);

217

});

218

```

219

220

### Remark Processing Slices

221

222

Context slices for markdown processing with remark.

223

224

```typescript { .api }

225

/**

226

* Remark processing slices

227

*/

228

229

/** The remark processor instance for markdown parsing/serialization */

230

const remarkCtx: SliceType<RemarkParser, 'remark'>;

231

232

/** Options for remark stringify operation */

233

const remarkStringifyOptionsCtx: SliceType<Options, 'remarkStringifyOptions'>;

234

```

235

236

**Usage Examples:**

237

238

```typescript

239

import { Editor, remarkCtx, remarkStringifyOptionsCtx } from "@milkdown/core";

240

import remarkGfm from 'remark-gfm';

241

import remarkMath from 'remark-math';

242

243

editor.config((ctx) => {

244

// Configure remark processor

245

ctx.update(remarkCtx, (remark) =>

246

remark.use(remarkGfm).use(remarkMath)

247

);

248

249

// Configure stringify options

250

ctx.update(remarkStringifyOptionsCtx, (options) => ({

251

...options,

252

bullet: '-', // Use - for bullets

253

emphasis: '*', // Use * for emphasis

254

strong: '**', // Use ** for strong

255

listItemIndent: 'one'

256

}));

257

});

258

```

259

260

### Timer Management Slices

261

262

Context slices that manage plugin loading order and dependencies.

263

264

```typescript { .api }

265

/**

266

* Timer management slices for plugin coordination

267

*/

268

269

/** Timers to wait for before initializing the init plugin */

270

const initTimerCtx: SliceType<TimerType[], 'initTimer'>;

271

272

/** Timers to wait for before initializing the schema plugin */

273

const schemaTimerCtx: SliceType<TimerType[], 'schemaTimer'>;

274

275

/** Timers to wait for before initializing the parser plugin */

276

const parserTimerCtx: SliceType<TimerType[], 'parserTimer'>;

277

278

/** Timers to wait for before initializing the serializer plugin */

279

const serializerTimerCtx: SliceType<TimerType[], 'serializerTimer'>;

280

281

/** Timers to wait for before initializing the commands plugin */

282

const commandsTimerCtx: SliceType<TimerType[], 'commandsTimer'>;

283

284

/** Timers to wait for before initializing the keymap plugin */

285

const keymapTimerCtx: SliceType<TimerType[], 'keymapTimer'>;

286

287

/** Timers to wait for before initializing the editor state plugin */

288

const editorStateTimerCtx: SliceType<TimerType[], 'editorStateTimer'>;

289

290

/** Timers to wait for before initializing the editor view plugin */

291

const editorViewTimerCtx: SliceType<TimerType[], 'editorViewTimer'>;

292

```

293

294

## Advanced Usage

295

296

### Custom Context Slices

297

298

```typescript

299

import { createSlice } from "@milkdown/ctx";

300

301

// Create custom context slices for your plugin

302

const myDataCtx = createSlice([], 'myData');

303

const myConfigCtx = createSlice({ enabled: true }, 'myConfig');

304

305

editor.config((ctx) => {

306

// Initialize custom slices

307

ctx.inject(myDataCtx, []);

308

ctx.inject(myConfigCtx, { enabled: true, theme: 'dark' });

309

310

// Use custom slices

311

const data = ctx.get(myDataCtx);

312

ctx.set(myConfigCtx, { ...ctx.get(myConfigCtx), theme: 'light' });

313

});

314

```

315

316

### Context State Management

317

318

```typescript

319

import { Editor, editorStateCtx } from "@milkdown/core";

320

321

editor.action((ctx) => {

322

// Get current state

323

const currentState = ctx.get(editorStateCtx);

324

325

// Create new state with modifications

326

const transaction = currentState.tr.insertText('Hello World');

327

const newState = currentState.apply(transaction);

328

329

// Update the context (usually done by plugins)

330

ctx.set(editorStateCtx, newState);

331

});

332

```

333

334

### Context Dependency Management

335

336

```typescript

337

import { MilkdownPlugin, createTimer } from "@milkdown/ctx";

338

import { SchemaReady, ParserReady } from "@milkdown/core";

339

340

const MyPluginReady = createTimer('MyPluginReady');

341

342

const myPlugin: MilkdownPlugin = (ctx) => {

343

ctx.record(MyPluginReady);

344

345

return async () => {

346

// Wait for dependencies

347

await ctx.wait(SchemaReady);

348

await ctx.wait(ParserReady);

349

350

// Plugin is ready

351

ctx.done(MyPluginReady);

352

353

return () => {

354

// Cleanup

355

ctx.clearTimer(MyPluginReady);

356

};

357

};

358

};

359

```

360

361

### Context-Aware Components

362

363

```typescript

364

import { Editor, editorViewCtx, schemaCtx } from "@milkdown/core";

365

366

function createContextAwareComponent() {

367

return editor.action((ctx) => {

368

const view = ctx.get(editorViewCtx);

369

const schema = ctx.get(schemaCtx);

370

371

// Create component that can interact with editor

372

return {

373

insertText: (text: string) => {

374

const transaction = view.state.tr.insertText(text);

375

view.dispatch(transaction);

376

},

377

378

getNodeTypes: () => Object.keys(schema.nodes),

379

380

focus: () => view.focus()

381

};

382

});

383

}

384

385

const component = createContextAwareComponent();

386

component.insertText('Hello from component!');

387

```

388

389

## Type Definitions

390

391

### Core Types

392

393

```typescript { .api }

394

/**

395

* Core context system types

396

*/

397

398

/** Default value types for editor initialization */

399

type DefaultValue =

400

| string

401

| { type: 'html'; dom: HTMLElement }

402

| { type: 'json'; value: JSONRecord };

403

404

/** Root element types */

405

type RootType = Node | undefined | null | string;

406

407

/** Editor view option types */

408

type EditorOptions = Omit<DirectEditorProps, 'state'>;

409

410

/** State creation override function */

411

type StateOptionsOverride = (prev: StateOptions) => StateOptions;

412

413

/** Node view tuple type */

414

type NodeView = [nodeId: string, view: NodeViewConstructor];

415

416

/** Mark view tuple type */

417

type MarkView = [markId: string, view: MarkViewConstructor];

418

```

419

420

## Error Handling

421

422

### Context Access Safety

423

424

```typescript

425

import { Editor, editorViewCtx } from "@milkdown/core";

426

import { ctxCallOutOfScope } from "@milkdown/exception";

427

428

function safeContextAccess() {

429

try {

430

editor.action((ctx) => {

431

const view = ctx.get(editorViewCtx);

432

// Safe to use view here

433

});

434

} catch (error) {

435

if (error === ctxCallOutOfScope()) {

436

console.error('Context accessed outside of valid scope');

437

}

438

}

439

}

440

```

441

442

### Context Slice Validation

443

444

```typescript

445

import { Editor, editorStateCtx } from "@milkdown/core";

446

447

editor.action((ctx) => {

448

try {

449

const state = ctx.get(editorStateCtx);

450

451

if (!state || !state.doc) {

452

throw new Error('Invalid editor state');

453

}

454

455

// Safe to use state

456

} catch (error) {

457

console.error('Context slice validation failed:', error);

458

}

459

});

460

```

461

462

## Best Practices

463

464

### Context Slice Naming

465

466

```typescript

467

import { createSlice } from "@milkdown/ctx";

468

469

// Good: Descriptive names with 'Ctx' suffix

470

const userPreferencesCtx = createSlice({}, 'userPreferences');

471

const syntaxHighlightCtx = createSlice(null, 'syntaxHighlight');

472

473

// Avoid: Generic or unclear names

474

const dataCtx = createSlice({}, 'data'); // Too generic

475

const ctx1 = createSlice({}, 'ctx1'); // Unclear purpose

476

```

477

478

### Context Update Patterns

479

480

```typescript

481

import { Editor, prosePluginsCtx } from "@milkdown/core";

482

483

editor.config((ctx) => {

484

// Good: Preserve existing state

485

ctx.update(prosePluginsCtx, (plugins) => [...plugins, newPlugin]);

486

487

// Avoid: Overwriting without preserving

488

ctx.set(prosePluginsCtx, [newPlugin]); // Lost existing plugins

489

});

490

```

491

492

### Context Cleanup

493

494

```typescript

495

import { MilkdownPlugin } from "@milkdown/ctx";

496

497

const myPlugin: MilkdownPlugin = (ctx) => {

498

const mySlice = createSlice([], 'mySlice');

499

ctx.inject(mySlice, []);

500

501

return async () => {

502

// Plugin initialization

503

504

return () => {

505

// Important: Clean up custom slices

506

ctx.remove(mySlice);

507

};

508

};

509

};

510

```