Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/benrbray/prosemirror-math

Schema and plugins for "first-class" math support in ProseMirror!
https://github.com/benrbray/prosemirror-math

katex math prosemirror wysiwyg

Last synced: about 2 months ago
JSON representation

Schema and plugins for "first-class" math support in ProseMirror!

Awesome Lists containing this project

README

        

# `prosemirror-math`

[![](https://img.shields.io/npm/v/@benrbray/prosemirror-math?style=flat-square)](https://www.npmjs.com/package/@benrbray/prosemirror-math)
![license](https://img.shields.io/github/license/benrbray/prosemirror-math?style=flat-square)
![types](https://img.shields.io/npm/types/@benrbray/prosemirror-math?style=flat-square)

> Please don't hesitate to report issues or make feature requests! Contributions welcome!

## Overview

The `prosemirror-math` package provides schema and plugins for comfortably writing mathematics with [ProseMirror](https://prosemirror.net/). Written in TypeScript, with math rendering handled by [KaTeX](https://katex.org/). You can install the [npm package](https://www.npmjs.com/package/@benrbray/prosemirror-math) or use this repository as a starting point for your own plugin. The important files in this project are:

* `lib/math-schema.ts`: A minimal ProseMirror schema supporting inline and display math nodes.
* `lib/math-nodeview.ts`: A `NodeView` responsible for rendering and editing math nodes.
* `public/prosemirror-math.css`: Contains all necessary styling for math nodes to display correctly. This file can easily be modified to achieve your desired appearance.

## Basic Usage ([try it yourself!](http://benrbray.com/prosemirror-math/))

Unlike other editors, this plugin **treats math as part of the text itself**, rather than as an "atom" that can only be edited through a dialog box. For example, inline math nodes can be edited directly by bringing the cursor inside of them:

![edit inline math](img/prosemirror-math_inline.gif)

Display math supports multiline editing, as shown below:

![edit display math](img/prosemirror-math_display.gif)

To create a new math expression, simply enclose LaTeX math notation in dollar signs, like `$x+y=5$`. When you finish typing, a new math node will be automatically created:

![create inline math](img/prosemirror-math_insert-inline.gif)

To start a display math block, create a blank line and type `$$` followed by a space. A multiline editor will appear. To exit the block, press `Ctrl-Enter` or navigate away the mouse or arrow keys.

![create display math](img/prosemirror-math_insert-display.gif)

Math nodes behave like regular text when using the arrow keys or `Backspace`. You can select, copy, and paste math nodes just like regular text! From within a math node, press `Ctrl-Backspace` to delete the entire node.

> **TIP:** You can define your own commands with `\providecommand{\cmd}{...}`!

See the KaTeX documentation for a list of [supported LaTeX commands](https://katex.org/docs/supported.html). In the future, `prosemirror-math` will also accept a custom callback that can be used to invoke alternative renderers like MathJax.

## Installation & Setup

Note that `prosemirror-math` is built on top of [ProseMirror](https://prosemirror.net/), which itself has a steep learning curve. At the very least, you will need to understand [`Schema`](https://prosemirror.net/docs/ref/#model.Document_Schema) and [`Plugins`](https://prosemirror.net/docs/ref/#state.Plugin_System) to integrate `prosemirror-math` into your project. Start by installing the `npm` package:

```
npm install @benrbray/prosemirror-math
```

### CSS

First, make sure you include the CSS files for `prosemirror-math` and `katex` on any pages that will need them. They can be found at the following paths:

```
node_modules/katex/dist/katex.min.css
node_modules/@benrbray/prosemirror-math/dist/prosemirror-math.css
```

If you are using a bundler like `vite` or `webpack`, you may be able to include the CSS files like this:

```
import "@benrbray/prosemirror-math/dist/prosemirror-math.css";
import "katex/dist/katex.min.css";
```

### Schema

Add `math_inline` and `math_display` nodes to your `prosemirror` document schema. The names are important! If you modify the schema, be careful not to change any of the values marked `important!` below, or you might run into unexpected behavior!

```typescript
import { Schema } from "prosemirror-model";

let schema = new Schema({
nodes: {
doc: {
content: "block+"
},
paragraph: {
content: "inline*",
group: "block",
parseDOM: [{ tag: "p" }],
toDOM() { return ["p", 0]; }
},
math_inline: { // important!
group: "inline math",
content: "text*", // important!
inline: true, // important!
atom: true, // important!
toDOM: () => ["math-inline", { class: "math-node" }, 0],
parseDOM: [{
tag: "math-inline" // important!
}]
},
math_display: { // important!
group: "block math",
content: "text*", // important!
atom: true, // important!
code: true, // important!
toDOM: () => ["math-display", { class: "math-node" }, 0],
parseDOM: [{
tag: "math-display" // important!
}]
},
text: {
group: "inline"
}
}
});
```

### Input Rules

If you want the user to be able to easily add new math nodes by typing `$...$` for inline math or `$$` followed by a space for block math, you need to create [`InputRule`](https://prosemirror.net/docs/ref/#inputrules.InputRule) instances. You can write your own, or use the helper functions provided by `prosemirror-math`.

> **CAUTION:** Make sure the `NodeType`s you provide to each input rule belong to the same schema instance that you pass to your ProseMirror `EditorView` instance. Otherwise, you'll see strange errors in the console!

```typescript
import {
makeBlockMathInputRule, makeInlineMathInputRule,
REGEX_INLINE_MATH_DOLLARS, REGEX_BLOCK_MATH_DOLLARS
} from "@benrbray/prosemirror-math";

// create input rules (using default regex)
let inlineMathInputRule = makeInlineMathInputRule(REGEX_INLINE_MATH_DOLLARS, editorSchema.nodes.math_inline);
let blockMathInputRule = makeBlockMathInputRule(REGEX_BLOCK_MATH_DOLLARS, editorSchema.nodes.math_display);
```

### Plugins

Choose which plugins you need from the following list, and pass them to your `EditorState` instance, along with the input rules you created.

* `mathPlugin` **(required)** Provides the core functionality of `prosemirror-math`.
* `mathBackspaceCmd` *(recommended)* When included in your [keymap](https://prosemirror.net/docs/ref/#keymap.keymap) for the `"Backspace"` key, pressing backspace on the right boundary of a math node will place the cursor inside the math node, rather than deleting it.
* `insertMathCmd(nodeType: NodeType)` *(optional)* Helper function for creating a command which can be used to insert a math node at the current document position.
* `mathSerializer` *(recommended)* Attach to the `clipboardTextSerializer` prop of your EditorView. When pasting a selection from a `prosemirror-math` editor to a plain text editor, ensures that the pasted math expressions will be properly delimited by `$...$` and `$$...$$`.

Here is the recommended setup:

```typescript
import { mathPlugin, mathBackspaceCmd, insertMathCmd, mathSerializer } from "@benrbray/prosemirror-math";

// prosemirror imports
import { EditorView } from "prosemirror-view";
import { EditorState, Plugin } from "prosemirror-state";
import { chainCommands, deleteSelection, selectNodeBackward, joinBackward, Command } from "prosemirror-commands";
import { keymap } from "prosemirror-keymap";
import { inputRules } from "prosemirror-inputrules";

// plugins (order matters)
let plugins:Plugin[] = [
mathPlugin,
keymap({
"Mod-Space" : insertMathCmd(schema.nodes.math_inline),
// modify the default keymap chain for backspace
"Backspace": chainCommands(deleteSelection, mathBackspaceCmd, joinBackward, selectNodeBackward),
}),
inputRules({ rules: [ inlineMathInputRule, blockMathInputRule ] })
];

// create prosemirror state
let state = EditorState.create({
schema: editorSchema,
plugins: plugins,
doc: /* ... */
})

// create prosemirror view
let view = new EditorView(editorElt, {
state,
clipboardTextSerializer: (slice) => { return mathSerializer.serializeSlice(slice) },
})
```

## Development

Clone this repository and install the necessary dependencies:

```
git clone [email protected]:benrbray/prosemirror-math.git
cd prosemirror-math
npm install
```

From the root directory, you can run the scripts in `package.json`.

* Use `npm run build` to build the `prosemirror-math` package
* Use `npm run build:site` to generate the static demo website
* Use `npm run dev` to start a local development server

## Release

> (this section is to help me remember the steps required to publish a new release)

To make a prerelease version for testing:

```bash
# begin a prerelease
npm version premajor --preid=rc # from 1.0.0 to 2.0.0-rc.0
npm version preminor --preid=rc # from 1.0.0 to 1.1.0-rc.0
npm version prepatch --preid=rc # from 1.0.0 to 1.0.1-rc.0

# increment the prerelease version number
npm version prerelease # from 2.0.0-rc.0 to 2.0.0-rc.1

# promote the prerelease version
npm version major # from 2.0.0-rc.1 to 2.0.0
npm version minor # from 1.1.0-beta.0 to 1.1.0
npm version patch # from 1.0.1-alpha.0 to 1.0.1
```