Building a Markdown Rich Text Editor with ProseMirror and Remirror in React
This tutorial walks through creating a lightweight rich‑text editor using ProseMirror's core modules, enhancing it with plugins like history and input rules, and then simplifying development with the Remirror React library to support Markdown formatting and custom extensions.
In the previous article we set up an Electron development environment that loads a web page; now we add the main feature—Markdown editing—by leveraging ProseMirror to build a rich‑text editor.
ProseMirror Core Modules
ProseMirror consists of several essential packages:
prosemirror-model defines the document model describing editor content.
prosemirror-state provides a single data structure representing the full editor state, including selections and a transaction system for state updates.
prosemirror-view renders a given state as an editable DOM element and handles user interaction.
prosemirror-transform offers undo/redo‑able document modifications, building on the transaction functionality of prosemirror-state .
Additional frequently used modules include prosemirror-collab and prosemirror-gapcursor .
Simple Editor Example
import { EditorState } from "prosemirror-state");
import { EditorView } from "prosemirror-view";
import { Schema, DOMParser } from "prosemirror-model";
import { schema } from "prosemirror-schema-basic";
import { useEffect, useRef } from "react";
import React from "react";
export const Editor = () => {
const schemaIntance = new Schema({
nodes: schema.spec.nodes,
marks: schema.spec.marks,
});
const view = useRef
();
useEffect(() => {
view.current = new EditorView(document.getElementById("editor"), {
state: EditorState.create({
doc: DOMParser.fromSchema(schemaIntance).parse(document.getElementById("content")!),
schema: schemaIntance,
}),
});
}, []);
return (
Hello ProseMirror
);
};The editor is minimal and lacks common features such as handling the Enter key for new lines.
Enhancing with Plugins
We can add functionality using ProseMirror plugins like prosemirror-history (undo/redo), prosemirror-keymap (key bindings), and prosemirror-commands (basic editing commands).
import { undo, redo, history } from "prosemirror-history";
import { keymap } from "prosemirror-keymap";
import { baseKeymap } from "prosemirror-commands";
useEffect(() => {
view.current = new EditorView(document.getElementById("editor"), {
state: EditorState.create({
doc: DOMParser.fromSchema(schemaIntance).parse(document.getElementById("content")!),
schema: schemaIntance,
plugins: [
history(),
keymap({ "Mod-z": undo, "Mod-y": redo }),
keymap(baseKeymap),
],
}),
});
}, []);Now the editor supports undo and redo operations via keyboard shortcuts.
Input Rules for Markdown‑Style Formatting
The prosemirror-inputrules module lets us define patterns that automatically transform typed text. For example, typing hello (surrounded by double asterisks) can be turned into a bold node.
import { inputRules, InputRule } from "prosemirror-inputrules";
view.current = new EditorView(document.getElementById("editor"), {
state: EditorState.create({
doc: DOMParser.fromSchema(schemaIntance).parse(document.getElementById("content")!),
schema: schemaIntance,
plugins: [
inputRules({
rules: [
new InputRule(/\*\*[^*]{1,}\*\*$/,
(state, match, from, to) => {
const mark = state.schema.mark("strong");
const str = match[0].substring(2, match[0].length - 2);
const node = state.schema.text(str, [mark]);
return state.tr.replaceWith(from, to, node);
})
],
}),
],
}),
});Combined with history and keymap , the editor can undo the automatic transformation using CommandOrCtrl+z .
Remirror: A React Wrapper for ProseMirror
Remirror builds on ProseMirror to provide a React‑friendly API for constructing cross‑platform rich‑text editors. It is not a drop‑in solution like Draft.js but offers extensibility through extensions.
Installation
npm add remirror @remirror/react @remirror/pmCreating a Manager
Before rendering a Remirror editor, we create a manager that controls behavior. Extensions such as MarkdownExtension add markdown parsing support.
import React from "react";
import { MarkdownExtension } from "remirror/extensions";
import { Remirror, useRemirror } from "@remirror/react";
import "remirror/styles/all.css";
const Editor = () => {
const { manager, state } = useRemirror({
extensions: () => [new MarkdownExtension()],
content: "",
selection: "start",
stringHandler: "markdown",
});
return (
);
};
export default function App() { return
; }Adding Extensions
We can add built‑in extensions like BoldExtension to enable markdown‑style bold syntax.
import { MarkdownExtension, BoldExtension } from "remirror/extensions";
const { manager, state } = useRemirror({
extensions: () => [new MarkdownExtension(), new BoldExtension()],
content: "",
selection: "start",
stringHandler: "markdown",
});Typing **bold** automatically renders as bold text.
Custom Extension Example
The following shows a simplified implementation of a custom BoldExtension using Remirror's core decorators.
export interface BoldOptions { weight?: Static
; }
@extension
({
defaultOptions: { weight: undefined },
staticKeys: ['weight'],
})
export class BoldExtension extends MarkExtension
{
get name() { return 'bold' as const; }
createTags() { return [ExtensionTag.FormattingMark, ExtensionTag.FontStyle]; }
// ... (schema, input rules, commands, key bindings) ...
}The custom extension mirrors the functionality we achieved directly with ProseMirror modules.
Rendering the Editor and Toolbar
import { BoldExtension, HeadingExtension, ItalicExtension, MarkdownExtension, PlaceholderExtension } from "remirror/extensions";
const Menu = () =>
alert("button b")}>B
;
export const Markdown = () => {
const { manager, state } = useRemirror({
extensions: () => [
new PlaceholderExtension({ placeholder: "请输入文字" }),
new BoldExtension(),
new ItalicExtension(),
new MarkdownExtension(),
new HeadingExtension(),
],
content: "",
selection: "start",
stringHandler: "markdown",
});
return (
);
};When custom menus are needed, the EditorComponent can be used inside Remirror to render the editor while the developer supplies their own toolbar.
Conclusion
By following the patterns shown for ProseMirror plugins and Remirror extensions, developers can quickly assemble a markdown‑capable rich‑text editor in a React/Electron environment and extend it further according to project requirements.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.