- 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.
126 lines
4.2 KiB
Markdown
126 lines
4.2 KiB
Markdown
# 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:
|
||
|
||
```ts
|
||
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.
|
||
|
||
```ts
|
||
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:
|
||
|
||
```text
|
||
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 🚀.
|