Lix Plugin ProseMirror
Plugin for Lix that tracks changes in ProseMirror documents.
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
- 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] });
- 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();
- 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.idbecomes 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:
applyChangesrebuilds the ProseMirror JSON by merging stored node snapshots and the document’schildren_order.
Requirements and tips
- Every node you want tracked must have a stable, unique ID in
attrs.id. UseidPlugin()if your schema does not supply IDs. - Make sure your schema allows an
idattribute 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;
