logo
  • Docs
  • Plugins
  • API Reference
    Overview
    JSON
    CSV
    Markdown
    ProseMirror
    Build your own
    Previous pageMarkdownNext pageBuild your own
    npm
    @lix-js/plugin-prosemirror
    GitHub
    View source code
    đź’»
    Example
    Try demo →

    #Lix Plugin ProseMirror

    Plugin for Lix that tracks changes in ProseMirror documents.

    Screenshot of the ProseMirror example app

    Try out the example app → • See the example directory for a local setup.

    #Installation

    npm install @lix-js/sdk @lix-js/plugin-prosemirror

    #Quick start

    1. Open Lix with the ProseMirror plugin:
    import { openLix } from "@lix-js/sdk";
    import { plugin as prosemirrorPlugin } from "@lix-js/plugin-prosemirror";
    
    export const lix = await openLix({ providePlugins: [prosemirrorPlugin] });
    1. Create a ProseMirror doc in Lix:
    const file = await lix.db
    	.insertInto("file")
    	.values({
    		path: "/doc.json",
    		data: new TextEncoder().encode(
    			JSON.stringify({ type: "doc", content: [] }),
    		),
    	})
    	.returningAll()
    	.executeTakeFirstOrThrow();
    1. Wire the editor:
    import { EditorState } from "prosemirror-state";
    import { EditorView } from "prosemirror-view";
    import { lixProsemirror, idPlugin } from "@lix-js/plugin-prosemirror";
    
    const state = EditorState.create({
    	schema,
    	doc: schema.nodeFromJSON({ type: "doc", content: [] }),
    	plugins: [
    		idPlugin(), // add stable ids if your schema doesn't provide them
    		lixProsemirror({ lix, fileId: file.id }),
    	],
    });
    
    const view = new EditorView(document.querySelector("#editor"), { state });

    #How it works

    • Node-per-entity: each node with an attrs.id becomes a stored entity. The document entity keeps the ordered list of child IDs.
    • Content vs. structure: leaf nodes track text/marks; container nodes track attributes and the order of their children. New child nodes are captured inside their parent snapshot the first time they appear.
    • Apply: applyChanges rebuilds the ProseMirror JSON by merging stored node snapshots and the document’s children_order.

    #Requirements and tips

    • Every node you want tracked must have a stable, unique ID in attrs.id. Use idPlugin() if your schema does not supply IDs.
    • Make sure your schema allows an id attribute on the node types you want to track.
    • Reordering children is captured via the document’s children_order. Inserting in the middle or reparenting nodes will appear as moves plus any node-level edits.
    • Text nodes themselves do not need IDs; their parent leaf node carries the content diff.

    #Adding IDs automatically

    If your nodes don’t already have IDs, add the bundled idPlugin() so the ProseMirror plugin can track them:

    {
      "type": "doc",
      "content": [
        {
          "attrs": {
    +       "id": "unique-id-1"
          },
          "type": "paragraph",
          "content": [
            {
              "type": "text",
              "text": "Hello World"
            }
          ]
        }
      ]
    }

    #Rendering diffs

    You can render review-friendly diffs with HTML Diff. Serialize the before/after ProseMirror JSON to HTML using your schema, then feed the two HTML strings to HTML Diff:

    import { renderHtmlDiff } from "@lix-js/html-diff";
    import { DOMSerializer, Schema } from "prosemirror-model";
    
    // Convert ProseMirror JSON to HTML
    const toHtml = (schema: Schema, json: any) => {
    	const doc = schema.nodeFromJSON(json);
    	const div = document.createElement("div");
    	const fragment = DOMSerializer.fromSchema(schema).serializeFragment(doc.content);
    	div.appendChild(fragment);
    	return div.innerHTML;
    };
    
    const beforeHtml = toHtml(schema, beforeJson);
    const afterHtml = toHtml(schema, afterJson);
    
    const diffHtml = await renderHtmlDiff({
    	beforeHtml,
    	afterHtml,
    	diffAttribute: "data-diff-key",
    });
    
    // Render it wherever you show reviews
    document.querySelector("#diff")!.innerHTML = diffHtml;