Files
llm-in-text/milkdown-docs/plugin/example-block-plugin.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

4.2 KiB
Raw Permalink Blame History

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, getPosition for 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:

  1. Dynamic placement via getPlacement (centred vs top-aligned depending on node height).
  2. Filtering nodes with blockConfig.filterNodes so handles do not appear inside tables / math / blockquotes.
  3. 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 🚀.