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
```