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

internal-plugins.mddocs/

0

# Internal Plugins

1

2

Internal plugins provide the fundamental functionality required for editor operation. These plugins are automatically loaded during editor creation and form the foundation upon which all other features are built.

3

4

## Capabilities

5

6

### Core Internal Plugins

7

8

The essential plugins that provide basic editor functionality.

9

10

```typescript { .api }

11

/**

12

* Core internal plugins automatically loaded by the editor

13

*/

14

15

/** Creates the ProseMirror schema from registered nodes and marks */

16

const schema: MilkdownPlugin;

17

18

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

19

const parser: MilkdownPlugin;

20

21

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

22

const serializer: MilkdownPlugin;

23

24

/** Initializes the command management system */

25

const commands: MilkdownPlugin;

26

27

/** Initializes the keymap management system */

28

const keymap: MilkdownPlugin;

29

30

/** Creates the ProseMirror editor state */

31

const editorState: MilkdownPlugin;

32

33

/** Creates the ProseMirror editor view and mounts it to DOM */

34

const editorView: MilkdownPlugin;

35

```

36

37

**Usage:**

38

39

These plugins are automatically loaded by the editor. You typically don't need to use them directly, but they're available for inspection or custom editor setups:

40

41

```typescript

42

import { Editor, schema, parser, commands } from "@milkdown/core";

43

44

// These are loaded automatically

45

const editor = Editor.make().create();

46

47

// For custom setups, you could theoretically use them individually

48

// (though this is not recommended for normal usage)

49

const customPlugin: MilkdownPlugin = (ctx) => {

50

return async () => {

51

await ctx.wait(SchemaReady);

52

await ctx.wait(ParserReady);

53

await ctx.wait(CommandsReady);

54

55

// Your plugin logic here

56

};

57

};

58

```

59

60

### Configuration and Initialization Plugins

61

62

Plugins that handle editor configuration and initialization.

63

64

```typescript { .api }

65

/**

66

* Configuration and initialization plugin factories

67

*/

68

69

/**

70

* Creates a configuration plugin that executes user configuration

71

* @param configure - Configuration function to execute

72

* @returns MilkdownPlugin for configuration

73

*/

74

function config(configure: Config): MilkdownPlugin;

75

76

/**

77

* Creates the initialization plugin that prepares core slices

78

* @param editor - The editor instance to initialize

79

* @returns MilkdownPlugin for initialization

80

*/

81

function init(editor: Editor): MilkdownPlugin;

82

```

83

84

**Usage Examples:**

85

86

```typescript

87

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

88

89

// Create configuration plugin

90

const myConfigPlugin = config(async (ctx) => {

91

// Configure editor context

92

ctx.set(defaultValueCtx, '# Hello World');

93

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

94

95

// Async configuration

96

const theme = await loadUserTheme();

97

ctx.set(themeCtx, theme);

98

});

99

100

// The editor automatically creates and uses config plugins

101

// from functions passed to .config()

102

const editor = Editor.make()

103

.config((ctx) => {

104

// This creates a config plugin internally

105

ctx.set(defaultValueCtx, 'Initial content');

106

})

107

.create();

108

```

109

110

### Plugin Timing System

111

112

Timers that coordinate plugin loading order and dependencies.

113

114

```typescript { .api }

115

/**

116

* Plugin timing system - timers that resolve when plugins are ready

117

*/

118

119

/** Resolved when configuration plugin completes */

120

const ConfigReady: TimerType;

121

122

/** Resolved when initialization plugin completes */

123

const InitReady: TimerType;

124

125

/** Resolved when schema plugin completes */

126

const SchemaReady: TimerType;

127

128

/** Resolved when parser plugin completes */

129

const ParserReady: TimerType;

130

131

/** Resolved when serializer plugin completes */

132

const SerializerReady: TimerType;

133

134

/** Resolved when commands plugin completes */

135

const CommandsReady: TimerType;

136

137

/** Resolved when keymap plugin completes */

138

const KeymapReady: TimerType;

139

140

/** Resolved when editor state plugin completes */

141

const EditorStateReady: TimerType;

142

143

/** Resolved when editor view plugin completes */

144

const EditorViewReady: TimerType;

145

```

146

147

**Usage Examples:**

148

149

```typescript

150

import {

151

MilkdownPlugin,

152

SchemaReady,

153

ParserReady,

154

CommandsReady

155

} from "@milkdown/core";

156

157

const myPlugin: MilkdownPlugin = (ctx) => {

158

return async () => {

159

// Wait for required plugins to be ready

160

await ctx.wait(SchemaReady);

161

await ctx.wait(ParserReady);

162

await ctx.wait(CommandsReady);

163

164

// Now safe to use schema, parser, and commands

165

const schema = ctx.get(schemaCtx);

166

const parser = ctx.get(parserCtx);

167

const commands = ctx.get(commandsCtx);

168

169

// Plugin initialization logic

170

};

171

};

172

```

173

174

## Plugin Loading Order

175

176

Internal plugins load in a specific order to ensure proper dependencies:

177

178

1. **Config** - Executes user configuration

179

2. **Init** - Prepares core context slices

180

3. **Schema** - Creates ProseMirror schema from nodes/marks

181

4. **Parser** - Creates markdown-to-ProseMirror parser

182

5. **Serializer** - Creates ProseMirror-to-markdown serializer

183

6. **Commands** - Initializes command system

184

7. **Keymap** - Initializes keymap system

185

8. **Editor State** - Creates ProseMirror editor state

186

9. **Editor View** - Creates and mounts ProseMirror editor view

187

188

```typescript

189

import {

190

ConfigReady,

191

InitReady,

192

SchemaReady,

193

ParserReady,

194

SerializerReady,

195

CommandsReady,

196

KeymapReady,

197

EditorStateReady,

198

EditorViewReady

199

} from "@milkdown/core";

200

201

// Example plugin that waits for multiple stages

202

const advancedPlugin: MilkdownPlugin = (ctx) => {

203

return async () => {

204

// Wait for basic setup

205

await ctx.wait(InitReady);

206

console.log('Editor initialized');

207

208

// Wait for content processing

209

await ctx.wait(ParserReady);

210

await ctx.wait(SerializerReady);

211

console.log('Content processing ready');

212

213

// Wait for interaction systems

214

await ctx.wait(CommandsReady);

215

await ctx.wait(KeymapReady);

216

console.log('Interaction systems ready');

217

218

// Wait for final editor

219

await ctx.wait(EditorViewReady);

220

console.log('Editor fully ready');

221

};

222

};

223

```

224

225

## Advanced Usage

226

227

### Custom Plugin Dependencies

228

229

```typescript

230

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

231

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

232

233

// Create custom timer

234

const MyPluginReady = createTimer('MyPluginReady');

235

236

const myPlugin: MilkdownPlugin = (ctx) => {

237

ctx.record(MyPluginReady);

238

239

return async () => {

240

// Wait for dependencies

241

await ctx.wait(SchemaReady);

242

await ctx.wait(CommandsReady);

243

244

// Plugin logic

245

setupMyPlugin(ctx);

246

247

// Signal ready

248

ctx.done(MyPluginReady);

249

250

return () => {

251

// Cleanup

252

ctx.clearTimer(MyPluginReady);

253

};

254

};

255

};

256

257

// Another plugin can wait for your plugin

258

const dependentPlugin: MilkdownPlugin = (ctx) => {

259

return async () => {

260

await ctx.wait(MyPluginReady);

261

// This runs after myPlugin is ready

262

};

263

};

264

```

265

266

### Intercepting Internal Plugin Behavior

267

268

```typescript

269

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

270

271

// Plugin that modifies internal plugin timing

272

const schemaModifier: MilkdownPlugin = (ctx) => {

273

return async () => {

274

// Add additional dependency for schema plugin

275

ctx.update(schemaTimerCtx, (timers) => [

276

...timers,

277

MyCustomTimer

278

]);

279

280

// Schema plugin will now wait for MyCustomTimer too

281

};

282

};

283

```

284

285

### Accessing Internal Plugin State

286

287

```typescript

288

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

289

290

const inspectorPlugin: MilkdownPlugin = (ctx) => {

291

return async () => {

292

await ctx.wait(SchemaReady);

293

await ctx.wait(ParserReady);

294

await ctx.wait(SerializerReady);

295

296

// Access internal plugin state

297

const schema = ctx.get(schemaCtx);

298

const parser = ctx.get(parserCtx);

299

const serializer = ctx.get(serializerCtx);

300

301

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

302

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

303

304

// Test parser/serializer

305

const markdown = '# Hello World';

306

const doc = parser(markdown);

307

const backToMarkdown = serializer(doc);

308

console.log('Round trip:', { markdown, backToMarkdown });

309

};

310

};

311

```

312

313

## Configuration Types

314

315

### Configuration Function Type

316

317

```typescript { .api }

318

/**

319

* Type for configuration functions executed during editor initialization

320

* @param ctx - The editor context to configure

321

*/

322

type Config = (ctx: Ctx) => void | Promise<void>;

323

```

324

325

### Additional Types

326

327

```typescript { .api }

328

/** Editor view options type (ProseMirror DirectEditorProps without state) */

329

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

330

331

/** Root element types for editor mounting */

332

type RootType = Node | undefined | null | string;

333

334

/** Editor state creation options override function */

335

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

336

```

337

338

**Examples:**

339

340

```typescript

341

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

342

343

// Synchronous configuration

344

const syncConfig: Config = (ctx) => {

345

ctx.set(defaultValueCtx, '# Hello');

346

ctx.set(rootCtx, document.body);

347

};

348

349

// Asynchronous configuration

350

const asyncConfig: Config = async (ctx) => {

351

const content = await fetch('/api/content').then(r => r.text());

352

ctx.set(defaultValueCtx, content);

353

354

const userPrefs = await loadUserPreferences();

355

ctx.set(themeCtx, userPrefs.theme);

356

};

357

358

// Error handling in configuration

359

const safeConfig: Config = async (ctx) => {

360

try {

361

const content = await fetch('/api/content').then(r => r.text());

362

ctx.set(defaultValueCtx, content);

363

} catch (error) {

364

console.warn('Failed to load remote content, using default');

365

ctx.set(defaultValueCtx, '# Welcome');

366

}

367

};

368

```

369

370

## Plugin Metadata

371

372

Internal plugins include metadata for debugging and inspection:

373

374

```typescript

375

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

376

377

const editor = Editor.make()

378

.enableInspector(true)

379

.create();

380

381

// Get telemetry for internal plugins

382

const telemetry = editor.inspect();

383

telemetry.forEach(item => {

384

console.log(`Plugin: ${item.displayName}`);

385

console.log(`Package: ${item.package}`);

386

console.log(`Group: ${item.group}`);

387

});

388

389

// Internal plugins have metadata like:

390

// { displayName: 'Schema', package: '@milkdown/core', group: 'System' }

391

// { displayName: 'Parser', package: '@milkdown/core', group: 'System' }

392

// { displayName: 'Commands', package: '@milkdown/core', group: 'System' }

393

```

394

395

## Error Handling

396

397

### Plugin Loading Failures

398

399

```typescript

400

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

401

402

try {

403

const editor = await Editor.make()

404

.config((ctx) => {

405

// Potentially failing configuration

406

if (!document.getElementById('editor')) {

407

throw new Error('Editor mount point not found');

408

}

409

})

410

.create();

411

} catch (error) {

412

console.error('Editor creation failed:', error);

413

// Handle plugin loading failure

414

}

415

```

416

417

### Timer Resolution Issues

418

419

```typescript

420

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

421

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

422

423

const timeoutPlugin: MilkdownPlugin = (ctx) => {

424

return async () => {

425

try {

426

// Wait with timeout

427

await Promise.race([

428

ctx.wait(SchemaReady),

429

new Promise((_, reject) =>

430

setTimeout(() => reject(new Error('Schema timeout')), 5000)

431

)

432

]);

433

} catch (error) {

434

console.error('Plugin loading timed out:', error);

435

throw error;

436

}

437

};

438

};

439

```

440

441

## Best Practices

442

443

### Plugin Dependency Management

444

445

```typescript

446

// Good: Explicit dependency waiting

447

const myPlugin: MilkdownPlugin = (ctx) => {

448

return async () => {

449

await ctx.wait(SchemaReady);

450

await ctx.wait(CommandsReady);

451

// Safe to use schema and commands

452

};

453

};

454

455

// Avoid: Assuming plugins are ready

456

const badPlugin: MilkdownPlugin = (ctx) => {

457

return async () => {

458

const schema = ctx.get(schemaCtx); // May not be ready!

459

};

460

};

461

```

462

463

### Custom Timer Usage

464

465

```typescript

466

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

467

468

// Good: Descriptive timer names

469

const DataLoaderReady = createTimer('DataLoaderReady');

470

const ThemeManagerReady = createTimer('ThemeManagerReady');

471

472

// Good: Proper timer lifecycle

473

const myPlugin: MilkdownPlugin = (ctx) => {

474

ctx.record(DataLoaderReady);

475

476

return async () => {

477

// Plugin logic

478

ctx.done(DataLoaderReady);

479

480

return () => {

481

ctx.clearTimer(DataLoaderReady); // Important cleanup

482

};

483

};

484

};

485

```

486

487

### Configuration Organization

488

489

```typescript

490

// Good: Organized configuration

491

const editorConfig: Config = async (ctx) => {

492

// Content configuration

493

ctx.set(defaultValueCtx, await loadContent());

494

495

// UI configuration

496

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

497

ctx.set(rootAttrsCtx, { class: 'editor-theme' });

498

499

// Feature configuration

500

ctx.update(inputRulesCtx, (rules) => [...rules, ...customRules]);

501

};

502

503

// Avoid: Scattered configuration

504

const messyConfig: Config = (ctx) => {

505

ctx.set(defaultValueCtx, 'content');

506

ctx.update(inputRulesCtx, (rules) => [...rules, rule1]);

507

ctx.set(rootCtx, document.body);

508

ctx.update(inputRulesCtx, (rules) => [...rules, rule2]); // Redundant

509

};

510

```