prosemirror-slash-menu: Slash menu for ProseMirror
By Áron on Thursday, June 22, 2023

What's this about?

Release of ProseMirror slash menu plugin and the companion ProseMirror slash menu UI in react.

We are all familiar with the concept of a slash menu from Slack, Discord, Notion, etc. and it seems it would be a perfect fit for ProseMirror as well. With these two simple packages you can quickly add a slash menu to your editor and execute ProseMirror commands, or any other commands within your app for that matter.

How does it work?

The idea is for you to simply provide an array of MenuElements to the plugin, and it will take care of closing and opening, executing commands, navigation within menus and sub menus with the keyboard and filtering the items as the user is typing.

By using prosemirror-slash-menu-react you can also get a UI for the menu which can be styled to your liking just by overriding some CSS classes.

Why two packages?

So you can use it with any UI framework you want. Our thought process was: Let the plugin handle all the logic and the UI will be just a dumb display. Not to mention, we really don't want to bundle React into the package for those of you who will not use it.

The code for this post is here

How to use?

  1. Install the plugins: npm i -S prosemirror-slash-menu prosemirror-slash-menu-react
  2. Import SlashMenuReact, SlashMenuPlugin and defaultElements from prosemirror-slash-menu-react
  3. Add the plugin to your editor SlashMenuPlugin(defaultElements)
  4. Add the UI to your editor SlashMenuReact

In codespeak:

1import React, { useEffect, useRef, useState } from "react";
2import { exampleSetup } from "prosemirror-example-setup";
3import { EditorState } from "prosemirror-state";
4import { EditorView } from "prosemirror-view";
5import schema from "./schema";
6import { SlashMenuPlugin } from "prosemirror-slash-menu";
7import {
8  defaultElements,
9  defaultIcons,
10  Icons,
11  SlashMenuReact,
12} from "prosemirror-slash-menu-react";
13
14const ProseMirrorSlashMenu = () => {
15  const [pmState, setPmState] = useState<EditorState>();
16  const [editorView, setEditorView] = useState<EditorView>();
17  const editorRef = useRef<HTMLDivElement>(null);
18  useEffect(() => {
19    if (!editorRef.current) return;
20    const state = EditorState.create({
21      doc: schema.nodeFromJSON({
22        content: [
23          {
24            content: [
25              {
26                text: "Type '/' after a space to open the menu. ",
27                type: "text",
28              },
29            ],
30            type: "paragraph",
31          },
32        ],
33        type: "doc",
34      }),
35      plugins: [
36        SlashMenuPlugin(defaultElements),
37        ...exampleSetup({
38          schema,
39        }),
40      ],
41    });
42    const view: EditorView = new EditorView(editorRef.current, {
43      state,
44      dispatchTransaction: (tr) => {
45        try {
46          const newState = view.state.apply(tr);
47          view.updateState(newState);
48          setPmState(newState);
49        } catch (e) {}
50      },
51    });
52    setEditorView(view);
53    return () => {
54      view && view.destroy();
55    };
56  }, [editorRef]);
57  return (
58    <>
59      <div ref={editorRef} id="editor" />
60      {pmState && editorView && (
61        <SlashMenuReact
62          icons={{
63            [Icons.HeaderMenu]: defaultIcons.H1Icon,
64            [Icons.Level1]: defaultIcons.H1Icon,
65            [Icons.Level2]: defaultIcons.H2Icon,
66            [Icons.Level3]: defaultIcons.H3Icon,
67            [Icons.Bold]: defaultIcons.BoldIcon,
68            [Icons.Italic]: defaultIcons.ItalicIcon,
69            [Icons.Code]: defaultIcons.CodeIcon,
70            [Icons.Link]: defaultIcons.LinkIcon,
71          }}
72          editorState={pmState}
73          editorView={editorView}
74        />
75      )}
76    </>
77  );
78};

That's it, now you can override SlashMenuReacts CSS classes to your liking, pass in your own commands and have a slash menu in your editor. With this first version we aim to cover the most basic use cases, but we already have some customization options in mind. We are not mind readers yet though, only you know what you really need so feel free to contact us with your suggestions for improvement.

You can check out the docs below:

https://github.com/emergence-engineering/prosemirror-slash-menu

https://github.com/emergence-engineering/prosemirror-slash-menu-react

Did you like this article? Would you like to learn more?
Write to us at contact@emergence-engineering.com