Files
llm-in-text/milkdown-docs/plugin/plugins-101.md
ydy0615 d9ab341223 Add documentation for using Milkdown with various frameworks
- Created a new document for using components in Milkdown.
- Added a guide for using plugins in Milkdown, including toggling plugins programmatically and listing official plugins.
- Introduced a recipe for integrating Milkdown with Angular, including installation steps and component creation.
- Added a recipe for using Milkdown with Next.js, detailing installation and component setup.
- Created a guide for integrating Milkdown with NuxtJS, including installation and component creation.
- Added a comprehensive guide for using Milkdown with React, covering both Crepe and core Milkdown usage.
- Introduced a recipe for SolidJS integration with Milkdown, including installation and component creation.
- Added a guide for using Milkdown with Svelte, detailing installation and component setup.
- Created a comprehensive guide for integrating Milkdown with Vue, covering both Crepe and core Milkdown usage.
- Added a recipe for using Milkdown with Vue2, including installation and component creation.
2026-01-17 14:18:08 +08:00

175 lines
5.5 KiB
Markdown

# Plugins 101
In this section we will show you the basic information of the plugin.
In most cases, you will not need to write plugins without helpers.
But it can help you understand the plugin system and what happens under the hood.
## Structure Overview
Generally speaking, a plugin will have following structure:
```typescript
import { MilkdownPlugin } from "@milkdown/kit/ctx";
const myPlugin: MilkdownPlugin = (ctx) => {
// #1 prepare plugin
return async () => {
// #2 run plugin
return async () => {
// #3 clean up plugin
};
};
};
```
Each plugin is composed by three parts:
1. _Prepare_: this part will be executed when plugin is registered in milkdown by `.use` method.
2. _Run_: this part will be executed when plugin is actually loaded.
3. _Post_: this part will be executed when plugin is removed by `.remove` method or editor is destroyed.
## Timer
Timer can be used to decide when to load the current plugin and how current plugin can influence other plugin's loading status.
You can use `ctx.wait` to wait a timer to finish.
```typescript
import { MilkdownPlugin, Complete } from "@milkdown/kit/core";
const myPlugin: MilkdownPlugin = (ctx) => {
return async () => {
const start = Date.now();
await ctx.wait(Complete);
const end = Date.now();
console.log("Milkdown load duration: ", end - start);
};
};
```
You can also create your own timer and influence other plugins load time.
For example, let's create a plugin that will fetch markdown content from remote server as editor's default value.
```typescript
import {
MilkdownPlugin,
editorStateTimerCtx,
defaultValueCtx,
createTimer,
} from "@milkdown/kit/core";
const RemoteTimer = createTimer("RemoteTimer");
const remotePlugin: MilkdownPlugin = (ctx) => {
// register timer
ctx.record(RemoteTimer);
return async () => {
// the editorState plugin will wait for this timer to finish before initialize editor state.
ctx.update(editorStateTimerCtx, (timers) => timers.concat(RemoteTimer));
const defaultMarkdown = await fetchMarkdownAPI();
ctx.set(defaultValueCtx, defaultMarkdown);
// mark timer as complete
ctx.done(RemoteTimer);
return async () => {
await SomeAPI();
// remove timer when plugin is removed
ctx.clearTimer(RemoteTimer);
};
};
};
```
It has following steps:
1. We use `createTimer` to create a timer, and use `pre.record` to register it into milkdown.
2. We update `editorStateTimerCtx` to tell the internal `editorState` plugin that before initialize editor state, it should wait our remote fetch process finished.
3. After we get value from `fetchMarkdownAPI`, we set it as `defaultValue` and use `ctx.done` to mark a timer as complete.
## Ctx
We have used `ctx` several times in the above example, now we can try to understand what it is.
Ctx is a data container which is shared in the entire editor instance. It's composed by a lot of slices. Every `slice` has a unique key and a value. You can change the value of a slice by `ctx.set` and `ctx.update`. And you can get the value of a slice by `ctx.get` with the slice key or name. Last but not least, you can remove a slice by `post.remove`.
```typescript
import { MilkdownPlugin, createSlice } from "@milkdown/kit/ctx";
const counterCtx = createSlice(0, "counter");
const counterPlugin: MilkdownPlugin = (ctx) => {
ctx.inject(counterCtx);
return () => {
// count is 0
const count0 = ctx.get(counterCtx);
// set count to 1
ctx.set(counterCtx, 1);
// now count is 1
const count1 = ctx.get(counterCtx);
// set count to n + 2
ctx.update(counterCtx, (prev) => prev + 2);
// now count is 3
const count2 = ctx.get(counterCtx);
// we can also get value by the slice name
const count3 = ctx.get("counter");
return () => {
// remove the slice
ctx.remove(counterCtx);
};
};
};
```
We can use `createSlice` to create a ctx, and use `pre.inject` to inject the ctx into the editor.
And when plugin processing, `ctx.get` can get the value of a ctx, `ctx.set` can set the value of a ctx, and `ctx.update` can update a ctx using callback function.
So, we can use `ctx` combine with `timer` to decide when should a plugin be processed.
```typescript
import {
MilkdownPlugin,
SchemaReady,
Timer,
createSlice,
} from "@milkdown/kit/core";
const examplePluginTimersCtx = createSlice<Timer[]>([], "example-timer");
const examplePlugin: MilkdownPlugin = (ctx) => {
ctx.inject(examplePluginTimersCtx, [SchemaReady]);
return async () => {
await Promise.all(
ctx.get(examplePluginTimersCtx).map((timer) => ctx.wait(timer)),
);
// or we can use a simplified syntax sugar
await ctx.waitTimers(examplePluginTimersCtx);
// do something
};
};
```
With this pattern, if other plugins want to delay the process of `examplePlugin`, all they need to do is just add a timer into `examplePluginTimersCtx` with `ctx.update`.
## Summary
Now let's go back to the plugin structure. Since we have the knowledge of `timer` and `ctx`, we can understand what we should do in each part of a plugin.
1. In `prepare` stage of the plugin, we can use `ctx.record` to register a timer, and use `ctx.inject` to inject a slice.
2. In `run` stage of the plugin, we can use `ctx.wait` to wait a timer to finish, and use `ctx.get` to get the value of a slice. We can also change values of slices by `ctx.set` and `ctx.update`. And we can use `ctx.done` to mark a timer as complete.
3. In `post` stage of the plugin, we can use `ctx.clearTimer` to clear a timer, and use `ctx.remove` to remove a slice.