- 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.
4.2 KiB
Example: Block Plugin
The block plugin adds a positional hook next to every top-level node (paragraphs, headings, lists, etc.). It is the foundation for features such as drag handles, quick-insert buttons or block toolbars.
In Milkdown this functionality lives in @milkdown/plugin-block and – consistent with tooltip & slash – consists of:
- a BlockProvider that deals with DOM positioning/lifecycle
- a blockFactory – implemented internally – exposed as two ctx slices:
blockSpec,blockPlugin
This guide covers:
- Understanding the provider/service architecture.
- Writing a vanilla TypeScript drag handle that lets you reorder blocks.
- Mounting custom UIs in React and Vue.
- Studying the production-ready Block Handle feature inside Crepe.
1. Anatomy of a Block Plugin
Unlike tooltip/slash, @milkdown/plugin-block ships its factory slices directly:
import { blockSpec, blockPlugin } from "@milkdown/plugin-block";
You normally interact with BlockProvider which talks to an internal BlockService: the service listens to mouse / drag events, figures out which node is active and sends show / hide messages to the provider.
Your job is to decide how to render a UI for that active node.
Key APIs:
new BlockProvider({ ctx, content, ... })– similar to Tooltip/Slash.provider.active– info about the currently focused block (node,pos,el).- Optional callbacks:
getOffset,getPlacement,getPositionfor fine-grained positioning.
2. Minimal Vanilla Drag Handle
Below we build a small drag handle that appears on hover and lets you drag-n-drop any block.
import { block, blockPlugin } from "@milkdown/plugin-block";
import { BlockProvider } from "@milkdown/plugin-block/block-provider"; // path depending on bundler
import { Editor } from "@milkdown/kit/core";
import { commonmark } from "@milkdown/kit/preset/commonmark";
// 1️⃣ Create DOM element for the handle
const handle = document.createElement("div");
handle.className = "drag-handle";
handle.innerHTML = "≡";
handle.style.cssText = `
width:20px;height:20px;display:flex;align-items:center;justify-content:center;
cursor:grab;border-radius:4px;background:#f2f3f5;color:#555;user-select:none;
`;
// 2️⃣ Build provider – show only when mouse is over a block
const provider = (ctx: Ctx) => {
const provider = new BlockProvider({
ctx,
content: handle,
getOffset: () => 8,
});
return {
update: provider.update,
destroy: provider.destroy,
};
};
// 3️⃣ Wire provider to Milkdown
const blockConfig = (ctx: Ctx) => {
ctx.set(blockSpec.key, {
view: provider(ctx),
});
};
Editor.make().config(blockConfig).use(commonmark).use(block).create();
Drag & Drop:
The HTML element has cursor:grab. The internal BlockService automatically sets draggable and wires ProseMirror's drag-events so you can reorder blocks without extra code 👉 nice!
3. Framework Examples
React
::iframe{src="https://stackblitz.com/github/Milkdown/examples/tree/main/react-block"}
The React demo renders a <BlockHandle/> component, keeps drag state in hooks and feeds the root element to BlockProvider.
Vue
::iframe{src="https://stackblitz.com/github/Milkdown/examples/tree/main/vue-block"}
Vue's <BlockHandle> uses Teleport and reactive refs exactly like the tooltip/slash examples.
4. Real-world Feature – Crepe Block Handle
Crepe brings all the pieces together to create a block edit experience that combines a drag handle and a plus-button to open the slash menu:
packages/crepe/src/feature/block-edit/handle/
Things worth exploring:
- Dynamic placement via
getPlacement(centred vs top-aligned depending on node height). - Filtering nodes with
blockConfig.filterNodesso handles do not appear inside tables / math / blockquotes. - Programmatically showing the slash menu after pressing the "+" button.
5. Summary & Next Steps
@milkdown/plugin-block is the Swiss-army knife for any block-level UI: drag handles, add-buttons, side toolbars…
Combine it with tooltip/slash to build sophisticated editors.
Hack on the examples, tweak positioning callbacks, and ship your own block goodies 🚀.