Ecosyste.ms: Awesome

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

Awesome Lists | Featured Topics | Projects

https://github.com/marekdedic/prosemirror-unified

ProseMirror Unified integration
https://github.com/marekdedic/prosemirror-unified

Last synced: 18 days ago
JSON representation

ProseMirror Unified integration

Awesome Lists containing this project

README

        

# prosemirror-unified

[![NPM Version](https://img.shields.io/npm/v/prosemirror-unified?logo=npm)](https://www.npmjs.com/package/prosemirror-unified)
[![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/marekdedic/prosemirror-unified/CI.yml?branch=master&logo=github)](https://github.com/marekdedic/prosemirror-unified/actions/workflows/CI.yml)
[![Coveralls](https://img.shields.io/coverallsCoverage/github/marekdedic/prosemirror-unified?branch=master&logo=coveralls)](https://coveralls.io/github/marekdedic/prosemirror-unified)
[![NPM Downloads](https://img.shields.io/npm/dm/prosemirror-unified?logo=npm)](https://www.npmjs.com/package/prosemirror-unified)
[![NPM License](https://img.shields.io/npm/l/prosemirror-unified)](https://github.com/marekdedic/prosemirror-unified/blob/master/LICENSE)

This package provides support for using the [unified](https://github.com/unifiedjs/unified) ecosystem of parsers and other packages (for example, [remark](https://github.com/remarkjs/remark), the markdown parser) in [ProseMirror](https://prosemirror.net/).

## Using prosemirror-unified with an existing extension

In the same way as unified provides a general framework for parsing and serializing that has to be extended to be used with a particular syntax (for example using remark to parse and serialize markdown), prosemirror-unified is a general package for connecting unified with ProseMirror that has to be extended to support a particular syntax (for example using prosemirror-remark to support markdown).

Currently, there is only the [prosemirror-remark](https://github.com/marekdedic/prosemirror-remark) package to support markdown parsing and serialization. See the next section if you want to add support for other unified plugins.

### Example

```ts
import { MarkdownExtension } from "prosemirror-remark";
import { EditorState } from "prosemirror-state";
import { ProseMirrorUnified } from "prosemirror-unified";
import { EditorView } from "prosemirror-view";

const sourceMarkdown = "**Bold text**";
const pmu = new ProseMirrorUnified([new MarkdownExtension()]);

const view = new EditorView(
// The element to use for the editor
document.querySelector("#editor")!,
{
state: EditorState.create({
// Set the initial content of the editor from sourceMarkdown
doc: pmu.parse(sourceMarkdown),
plugins: [pmu.inputRulesPlugin(), pmu.keymapPlugin()],
schema: pmu.schema(),
}),
// Add interactive elements (task lists etc.)
nodeViews: adapter.nodeViews(),
// Log (in the browser console) the current content in markdown on every update
dispatchTransaction: (tr): void => {
view.updateState(view.state.apply(tr));
console.log(pmu.serialize(view.state.doc));
},
}
);
```

### The `ProseMirrorUnified` class

This class represents the adapter between ProseMirror and unified. To use the package with existing extensions, you only need the `ProseMirrorUnified` class.

#### Methods

##### `constructor(extensions: Array)`

The constructor of `ProseMirrorUnified` takes only one parameter, a list of prosemirror-unified extensions to use.

##### `parse(source: string): ProseMirrorNode`

Parses a source from a string with unified (so, for example, the source would contain markdown when using with prosemirror-remark) and returns the root node of the ProseMirror AST. This is useful when trying to set the contents of the editor, most notably when initializing it - see the example above.

##### `serialize(doc: ProseMirrorNode): string`

Serializes a ProseMirror node (incl. all children) to a string, i.e., the inverse of `parse`. This is useful when you want to get the contents out of the editor, for example to save them.

##### `schema(): Schema`

Returns the ProseMirror schema to use for the editor. This schema is constructed so that it supports all available extensions.

##### `inputRulesPlugin(): Plugin`

Input rules are rules that govern how ProseMirror replaces what users write with ProseMirror nodes - e.g. for markdown, when a user writes `**Some text**`, then after entering the last \*, the whole text would be replaced by a ProseMirror bold text. This allows users to write markdown, but still get the benefits of ProseMirror. The `inputRulesPlugin()` function returns a ProseMirror plugin that adds all extension input rules to the editor.

##### `keymapPlugin(): Plugin`

A ProseMirror keymap specifiec all the keyboard shortcuts users can use in the editor, for example Ctrl-b for bold text. This function returns a ProseMirror plugin that adds all extension keyboard shortcuts to the editor.

##### `nodeViews(): Record`

A list of Node views to use in the prose mirror editor. Node views are a way to provide more complex and often interactive elements in the editor, such as clickable task lists. This function returns a list of ProseMirror node views registered by all available extensions.

## Creating your own extensions

prosemirror-unified provides several utilities for creating your own extension to support custom unified syntax. Please note that `prosemirror-unified` doesn't aim to extend unified itself, so you either need to take an existing unified plugin (such as remark for GitHub or rehype for HTML) or create your own. prosemirror-unified provides the tools to translate between the unified syntax (called unist) and the ProseMirror syntax.

Documents in unist are represented by an abstract syntax tree (AST) of nodes, starting with a root node representing the whole document. In ProseMirror, things work mostly the same way, with one important difference - ProseMirror has a concept of *marks* that can be applied to a node in the AST. These represent things such as bold text, which in ProseMirror is just a text node with a mark to make it bold. In unist, there are no marks and bold text is represented by a bold node, which contains a text node.

To make up for this difference, prosemirror-unified provides two basic types of extensions:

- A `NodeExtension` is used when translating between a unist node and a ProseMirror node - for example a paragraph. The extension provides functions to translate both ways.
- A `MarkExtension` is used to translate between a unist node and a ProseMirror mark - for example bold text. This type of extension also provides fnctions to translate both ways.

### Translating from unist to ProseMirror

prosemirror-unified traverses an existing unist AST and creates a matching ProseMirror AST from the leaf nodes to the root. For each node, all extensions are checked to find one that can translate this type of node. Once an applicable extension is found, all the children of the node are translated first. Only after that is the actual node translated, so that it can use the already-prepared children and incorporate them in the ProseMirror tree. This process works the same for `NodeExtension`s and `MarkExtension`s as the extension can decide what the output node will look like and what marks it will have.

As some extensions need to add information after the whole document is parsed, there is a global context that any extension can modify when translating a node. Additionally, a post-translation hook can be added to any extension.

### Translating from ProseMirror to unist

prosemirror-unified traverses the existing unist AST and creates a matching unist AST from the leaf nodes to the root. For each node, all extensions are searched to find a `NodeExtension` that can translate this node. Once an applicable `NodeExtension` is found, all the children of the node are translated first. Only after that is the actual node translated, so that it can use the already-prepared children and incorporate then in the unist tree. If the original ProseMirror node had any marks, then for each mark a matching `MarkExtension` is found and that extension can post-process the already-translated unist node. For multiple marks, the order in which they are processes will is not guaranteed.

#### Example

Bold text is represented by a Text node with a Bold mark in ProseMirror. As such, when translating to unist, first a hypothetical `TextExtension` (which is a `NodeExtension`) is called, which translates the node into a unist Text node. This unist node is then post-processed by a `BoldExtension` (which is a `MarkExtension`) and changed into a unist Bold node (which contains the original unist Text node).

### The `Extension` class

This is the root class for all prosemirror-unified extensions. By itself, it cannot do much, except for two things.

#### Methods

##### `dependencies(): Array`

By overriding the `dependencies` method, you can specify other extensions that must be present for this extension to work. One example where this can be useful is when creating an extension to handle lists of items, you can specify an extension which handles an individual list item as a dependency. Another example is the `MarkdownExtension` from the prosemirror-remark package, which is a wrapper for all the individual extensions that add various parts of the markdown syntax.

By default returns `[]`.

##### `unifiedInitializationHook(processor: Processor): Processor`

This method is called when creating the unified instance to add support for various unified plugins. For example, to add remark, you would override this method as

```ts
import { Extension } from "prosemirror-unified";
import remarkParse from "remark-parse";
import remarkStringify from "remark-stringify";
import type { Processor } from "unified";
import type { Node as UnistNode } from "unist";

class MarkdownExtension extends Extension {
public unifiedInitializationHook(
processor: Processor
): Processor {
return processor
.use(remarkParse)
.use(remarkStringify);
}
}
```

By default, returns the parameter `processor` as-is.

### The `SyntaxExtension` class

The abstract class `SyntaxExtension` extends the `Extension` class. You should probably never need to extend this class directly. However, as much of the functionality of `NodeExtension` and `MarkExtension` is shared, it is contained in the `SyntaxExtension` class.

#### Generic parameters

If you are using TypeScript, the `SyntaxExtension` class has two generic parameters that you need to specify.

##### `UNode extends UnistNode`

This specifies the unist node type that the extension handles.

##### `UnistToProseMirrorContext extends Record`

This specifies the type of global context (shared across all extensions) that this extension expects. Default `Record`.

#### Methods

##### `abstract unistNodeName(): UNode["type"]`

This method should return the type of the unist node this extension translates.

##### `unistToProseMirrorTest(node: UnistNode): boolean`

This method is used to check whether the extension can translate a given unist node to a ProseMirror node. By default, it checks whether the node type matches `this.unistNodeName()`.

##### `abstract unistNodeToProseMirrorNode(node: UNode, proseMirrorSchema: Schema, convertedChildren: Array, context: Partial): Array`

This method handles the translation from a unist node to a ProseMirror node. It receives the original unist node, the built ProseMirror schema, the already-translated children and the global translation context that it can modify. It should return an array of ProseMirror nodes (usually only one, but you can theoretically convert one unist node into multiple ProseMirror nodes).

##### `postUnistToProseMirrorHook(context: Partial): void`

This method is called during translation from unist to ProseMirror after the whole document has been translated. By default does nothing.

##### `proseMirrorInputRules(proseMirrorSchema: Schema): Array`

Override this method to add input rules to the ProseMirror editor. The built ProseMirror schema is provided as a parameter.

By default returns `[]`.

##### `proseMirrorKeymap(proseMirrorSchema: Schema): Record`

Override this method to add keyboard shortcuts to the ProseMirror editor. Returns an object where the keys are keyboard shortcuts and values are commands to run. The built ProseMirror schema is provided as a parameter.

By default returns `{}`.

### The `NodeExtension` class

The abstract class `NodeExtension` extends the `SyntaxExtension` class. You should extend this class to provide support for custom nodes.

#### Generic parameters

If you are using TypeScript, the `NodeExtension` class has two generic parameters that you need to specify.

##### `UNode extends UnistNode`

This specifies the unist node type that the extension handles.

##### `UnistToProseMirrorContext extends Record`

This specifies the type of global context (shared across all extensions) that this extension expects. Default `Record`.

#### Methods

##### `proseMirrorToUnistTest(node: ProseMirrorNode): boolean`

This method is used to check whether the extension can translate a given ProseMirror node to a unist node. By default, it checks whether the node name matches `this.proseMirrorNodeName()`.

##### `proseMirrorNodeView(): NodeViewConstructor | null`

This method should return a constructor of a node view associated with the ProseMirror node this extension translates or `null` if it doesn't provide one. By default, it returns `null`.

##### `abstract proseMirrorNodeName(): string | null`

This method should return the type of the ProseMirror node this extension translates or `null` if it doesn't produce any ProseMirror nodes.

##### `abstract proseMirrorNodeSpec(): NodeSpec | null`

This method should return a ProseMirror node spec for the ProseMirror node it produces or `null` if it doesn't produce any ProseMirror nodes.

##### `abstract proseMirrorNodeToUnistNodes(node: ProseMirrorNode, convertedChildren: Array): Array`

This method handles the translation from a ProseMirror node to a unist node. It receives the original ProseMirror node and the already-translated children. It should return an array of unist nodes (usually only one, but you can theoretically convert one ProseMirror node into multiple unist nodes).

### The `MarkExtension` class

The abstract class `MarkExtension` extends the `SyntaxExtension` class. You should extend this class to provide support for custom ProseMirror marks.

#### Generic parameters

If you are using TypeScript, the `MarkExtension` class has two generic parameters that you need to specify.

##### `UNode extends UnistNode`

This specifies the unist node type that the extension handles.

##### `UnistToProseMirrorContext extends Record`

This specifies the type of global context (shared across all extensions) that this extension expects. Default `Record`.

#### Methods

##### `proseMirrorToUnistTest(node: UnistNode, mark: Mark): boolean`

This method is used to check whether the extension can post-process a given unist node with a ProseMirror mark. By default, it checks whether the node is a text node and the mark type matches `this.proseMirrorMarkName()`.

##### `abstract proseMirrorMarkName(): string | null`

This method should return the type of the ProseMirror mark this extension handles or `null` if it doesn't produce any ProseMirror marks.

##### `abstract proseMirrorMarkSpec(): MarkSpec | null`

This method should return a ProseMirror mark spec for the ProseMirror mark it produces or `null` if it doesn't produce any ProseMirror marks.

##### `abstract processConvertedUnistNode(convertedNode: UnistNode, originalMark: Mark): UNode`

This method is called when converting from ProseMirror to unist. The ProseMirror node has already been translated using an appropriate `NodeExtension` and the resulting unist node is passed to this method as a parameter, together with the mark that was applied to the original ProseMirror node. This function should post-process the unist node to produce the correct result for the given mark.

### The `MarkInputRule` class

This class extends the `InputRule` class provided by ProseMirror and should be used to create input rules that add marks to the document.

#### Methods

##### `constructor(matcher: RegExp, markType: MarkType)`

Creates a new input rule that adds the mark specified by `markType` if the user input matches the provided `matcher`.

### `createProseMirrorNode(nodeName: string | null, schema: Schema, children: Array, attrs: Attrs = {}): Array`

A helper function that creates a ProseMirror node based on `nodeName` with the specified children and attributes. Returns `[]` if `nodeName` is `null`. This function is useful when creating `NodeExtension`s