Files
llm-in-text/milkdown-docs/guide/interacting-with-editor.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

8.9 KiB

Interacting with Editor

This guide covers the essential ways to interact with the Milkdown editor, including initialization, content management, and editor lifecycle.

Using Crepe Editor


Crepe is a high-level wrapper around Milkdown that provides a simpler API for common editor operations. Here's how to use it:

import { Crepe } from "@milkdown/crepe";

// Create a new editor instance
const editor = new Crepe({
  // Optional: specify root element (DOM node or selector)
  root: "#editor",

  // Optional: set default content, supports markdown, json and dom.
  defaultValue: "# Hello Crepe!",
});

// Create the editor
await editor.create();

// Get markdown content
const markdown = editor.getMarkdown();

// Set readonly mode
editor.setReadonly(true);

// Register event listeners
editor.on((listener) => {
  listener.markdownUpdated((ctx, markdown) => {
    console.log("Content updated:", markdown);
  });

  listener.focus((ctx) => {
    console.log("Editor focused");
  });

  listener.blur((ctx) => {
    console.log("Editor blurred");
  });

  listener.selectionUpdated((ctx, selection, prevSelection) => {
    console.log("Selection updated:", selection);
  });

  listener.updated((ctx, doc, prevDoc) => {
    console.log("Document updated:", doc);
  });
});

// Destroy the editor when done
await editor.destroy();

Register to DOM


By default, milkdown will create editor on the document.body. Alternatively, you can also point out which dom node you want it to load into:

import { rootCtx } from "@milkdown/kit/core";

Editor.make().config((ctx) => {
  ctx.set(rootCtx, document.querySelector("#editor"));
});

It's also possible to just pass a selector to rootCtx:

The selector will be passed to document.querySelector to get the dom.

import { rootCtx } from "@milkdown/kit/core";

Editor.make().config((ctx) => {
  ctx.set(rootCtx, "#editor");
});

Setting Default Value


We support three types of default values:

  • Markdown strings
  • HTML DOM
  • Prosemirror documentation JSON

Markdown

You can set a markdown string as the default value of the editor.

import { defaultValueCtx } from "@milkdown/kit/core";

const defaultValue = "# Hello milkdown";
Editor.make().config((ctx) => {
  ctx.set(defaultValueCtx, defaultValue);
});

Dom

You can also use HTML as default value.

Let's assume that we have the following html snippets:

<div id="pre">
  <h1>Hello milkdown!</h1>
</div>

Then we can use it as a defaultValue with a type specification:

import { defaultValueCtx } from "@milkdown/kit/core";

const defaultValue = {
  type: "html",
  dom: document.querySelector("#pre"),
};
Editor.make().config((ctx) => {
  ctx.set(defaultValueCtx, defaultValue);
});

JSON

We can also use a JSON object as a default value.

This JSON object can be obtained by a listener through the listener-plugin, for example:

import { listener, listenerCtx } from "@milkdown/kit/plugin/listener";

let jsonOutput;

Editor.make()
  .config((ctx) => {
    ctx.get(listenerCtx).updated((ctx, doc, prevDoc) => {
      jsonOutput = doc.toJSON();
    });
  })
  .use(listener);

Then we can use this jsonOutput as default Value:

import { defaultValueCtx } from "@milkdown/kit/core";

const defaultValue = {
  type: "json",
  value: jsonOutput,
};
Editor.make().config((ctx) => {
  ctx.set(defaultValueCtx, defaultValue);
});

Inspecting Editor Status


You can inspect the editor's status through the status property.

import { Editor, EditorStatus } from "@milkdown/kit/core";

const editor = Editor.make().use(/* some plugins */);

assert(editor.status === EditorStatus.Idle);

editor.create().then(() => {
  assert(editor.status === EditorStatus.Created);
});

assert(editor.status === EditorStatus.OnCreate);

editor.destroy().then(() => {
  assert(editor.status === EditorStatus.Destroyed);
});

assert(editor.status === EditorStatus.OnDestroyed);

You can also listen to the status changes:

import { Editor, EditorStatus } from "@milkdown/kit/core";

const editor = Editor.make().use(/* some plugins */);

editor.onStatusChange((status: EditorStatus) => {
  console.log(status);
});

Status Lifecycle

  1. Idle: Initial state
  2. OnCreate: During creation
  3. Created: Successfully created
  4. OnDestroyed: During destruction
  5. Destroyed: Successfully destroyed

Adding Listeners


As mentioned above, you can add a listener to the editor, in order to get its value when needed. You can add as many listeners as you want, all the listeners will be triggered at once.

Markdown Listener

You can add markdown listener to get the editor's contents as a markdown string.

⚠️ Markdown listener will influence the performance for large documents, please use it carefully. If you have a large document, I suggest you to only parse and serialize the document when needed.

import { listener, listenerCtx } from "@milkdown/kit/plugin/listener";

let output = "";

Editor.make()
  .config((ctx) => {
    ctx.get(listenerCtx).markdownUpdated((ctx, markdown, prevMarkdown) => {
      output = markdown;
    });
  })
  .use(listener);

Doc Listener

You can also listen to the raw prosemirror document node, and do things you want from there.

import { listener, listenerCtx } from "@milkdown/kit/plugin/listener";

let jsonOutput;

Editor.make()
  .config((ctx) => {
    ctx.get(listenerCtx).updated((ctx, doc, prevDoc) => {
      jsonOutput = doc.toJSON();
    });
  })
  .use(listener);

Selection Listener

You can track changes to the editor's selection using the selectionUpdated event. This is useful for implementing features like:

  • Custom toolbars that update based on selection
  • Context menus
  • Selection-based formatting controls
import { listener, listenerCtx } from "@milkdown/kit/plugin/listener";
import { Selection, TextSelection } from "@milkdown/prose/state";

Editor.make()
  .config((ctx) => {
    ctx.get(listenerCtx).selectionUpdated((ctx, selection, prevSelection) => {
      if (selection instanceof TextSelection) {
        // Get selection range
        const { from, to } = selection;

        // Example: Update toolbar based on selection
        updateToolbar({
          hasSelection: from !== to,
          selectionStart: from,
          selectionEnd: to,
        });
      }
    });
  })
  .use(listener);

The selection listener will be triggered when the selection is changed. So you don't need to compare them manually.

For more details about listeners, please check Using Listeners.

Readonly Mode


You can set the editor to readonly mode by setting the editable property.

import { editorViewOptionsCtx } from "@milkdown/kit/core";

let readonly = false;

const editable = () => !readonly;

Editor.make().config((ctx) => {
  ctx.update(editorViewOptionsCtx, (prev) => ({
    ...prev,
    editable,
  }));
});

// set to readonly after 5 secs.
setTimeout(() => {
  readonly = true;
}, 5000);

Use Cases for Readonly Mode

  • Preview mode
  • Document review
  • Print-friendly views
  • Mobile device optimization

Using Actions


You can use an action to get the context value in a running editor on demand.

For example, to get the markdown string by running an action:

import { Editor, editorViewCtx, serializerCtx } from "@milkdown/kit/core";

async function playWithEditor() {
  const editor = await Editor.make().use(commonmark).create();

  const getMarkdown = () =>
    editor.action((ctx) => {
      const editorView = ctx.get(editorViewCtx);
      const serializer = ctx.get(serializerCtx);
      return serializer(editorView.state.doc);
    });

  // get markdown string:
  getMarkdown();
}

We provide some macros out of the box, you can use them as actions:

import { insert } from "@milkdown/kit/utils";

editor.action(insert("# Hello milkdown"));

Common Actions

  • Insert content
  • Get current selection
  • Apply formatting
  • Execute commands

For more details about macros, please check macros.

Destroying


You can call editor.destroy to destroy an existing editor. You can create a new editor again with editor.create.

await editor.destroy();

// Then create again
await editor.create();

If you just want to recreate the editor, you can use editor.create, it will destroy the old editor and create a new one.

await editor.create();

// This equals to call `editor.destroy` and `editor.create` again.
await editor.create();

If you want to clear the plugins and configs for the editor when calling editor.destroy, you can pass true to editor.destroy.

await editor.destroy(true);