- 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.
5.5 KiB
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:
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:
- Prepare: this part will be executed when plugin is registered in milkdown by
.usemethod. - Run: this part will be executed when plugin is actually loaded.
- Post: this part will be executed when plugin is removed by
.removemethod 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.
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.
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:
- We use
createTimerto create a timer, and usepre.recordto register it into milkdown. - We update
editorStateTimerCtxto tell the internaleditorStateplugin that before initialize editor state, it should wait our remote fetch process finished. - After we get value from
fetchMarkdownAPI, we set it asdefaultValueand usectx.doneto 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.
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.
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.
- In
preparestage of the plugin, we can usectx.recordto register a timer, and usectx.injectto inject a slice. - In
runstage of the plugin, we can usectx.waitto wait a timer to finish, and usectx.getto get the value of a slice. We can also change values of slices byctx.setandctx.update. And we can usectx.doneto mark a timer as complete. - In
poststage of the plugin, we can usectx.clearTimerto clear a timer, and usectx.removeto remove a slice.