ProseMirror bindings for Yjs that enable real-time collaborative editing with synchronization, cursors, and undo/redo
Y-ProseMirror provides three essential ProseMirror plugins that enable collaborative editing: synchronization, cursor tracking, and collaborative undo/redo functionality.
Maintains bidirectional synchronization between a Yjs XML fragment and ProseMirror document, ensuring all users see consistent content.
/**
* Creates a ProseMirror plugin that synchronizes with a Yjs XML fragment
* @param yXmlFragment - The Yjs XML fragment to bind to
* @param opts - Optional configuration object with destructured options
* @returns ProseMirror Plugin instance
*/
function ySyncPlugin(yXmlFragment: Y.XmlFragment, opts?: {
/** Array of color definitions for user highlighting */
colors?: Array<ColorDef>;
/** Mapping of users to specific colors */
colorMapping?: Map<string, ColorDef>;
/** Persistent user data for the session */
permanentUserData?: Y.PermanentUserData | null;
/** Pre-existing mapping between Yjs and ProseMirror nodes */
mapping?: ProsemirrorMapping;
/** Callback fired when content initially renders */
onFirstRender?: () => void;
}): Plugin;
interface ColorDef {
light: string;
dark: string;
}Usage Example:
import * as Y from "yjs";
import { ySyncPlugin } from "y-prosemirror";
const ydoc = new Y.Doc();
const yXmlFragment = ydoc.getXmlFragment("prosemirror");
const syncPlugin = ySyncPlugin(yXmlFragment, {
colors: [
{ light: "#ffeb3b", dark: "#f57f17" },
{ light: "#4caf50", dark: "#2e7d32" }
],
onFirstRender: () => console.log("Content rendered")
});Displays collaborative cursors and selections from other editing users using Yjs awareness protocol.
/**
* Creates a ProseMirror plugin for displaying collaborative cursors
* @param awareness - Yjs awareness instance for cursor tracking
* @param opts - Optional configuration object with destructured options
* @param cursorStateField - Awareness field name for cursor data (default: 'cursor')
* @returns ProseMirror Plugin instance
*/
function yCursorPlugin(
awareness: Awareness,
opts?: {
/** Filter function for awareness states */
awarenessStateFilter?: (currentClientId: number, userClientId: number, user: any) => boolean;
/** Custom cursor element builder */
cursorBuilder?: (user: any, clientId: number) => HTMLElement;
/** Custom selection attributes builder */
selectionBuilder?: (user: any, clientId: number) => DecorationAttrs;
/** Custom selection getter */
getSelection?: (state: EditorState) => Selection;
},
cursorStateField?: string
): Plugin;Usage Example:
import { Awareness } from "y-protocols/awareness";
import { yCursorPlugin } from "y-prosemirror";
const awareness = new Awareness(ydoc);
const cursorPlugin = yCursorPlugin(awareness, {
cursorBuilder: (user, clientId) => {
const cursor = document.createElement("div");
cursor.className = "user-cursor";
cursor.style.borderColor = user.color;
cursor.textContent = user.name;
return cursor;
},
awarenessStateFilter: (currentId, userId, user) => {
return currentId !== userId && user.cursor;
}
});Provides collaborative undo/redo functionality with per-user history tracking that respects collaborative document state.
/**
* Creates a ProseMirror plugin for collaborative undo/redo operations
* @param options - Optional configuration object with destructured options
* @returns ProseMirror Plugin instance
*/
function yUndoPlugin(options?: {
/** Set of node types that cannot be deleted during undo */
protectedNodes?: Set<string>;
/** Origins to track for undo operations */
trackedOrigins?: any[];
/** Custom undo manager instance */
undoManager?: UndoManager | null;
}): Plugin;Usage Example:
import { yUndoPlugin } from "y-prosemirror";
const undoPlugin = yUndoPlugin({
protectedNodes: new Set(["doc", "paragraph"]),
trackedOrigins: ["user-input", "paste"]
});Unique identifiers for each plugin, useful for accessing plugin state or configuration.
/** Plugin key for synchronization plugin */
const ySyncPluginKey: PluginKey;
/** Plugin key for cursor plugin */
const yCursorPluginKey: PluginKey;
/** Plugin key for undo plugin */
const yUndoPluginKey: PluginKey;Direct command functions for implementing undo/redo in your editor interface.
/**
* Perform undo operation on editor state
* @param state - Current editor state
* @returns true if undo was performed
*/
function undo(state: EditorState): boolean;
/**
* Perform redo operation on editor state
* @param state - Current editor state
* @returns true if redo was performed
*/
function redo(state: EditorState): boolean;
/**
* ProseMirror command for undo operation
* @param state - Current editor state
* @param dispatch - Dispatch function for state changes
* @returns true if command was handled
*/
function undoCommand(state: EditorState, dispatch?: (tr: Transaction) => void): boolean;
/**
* ProseMirror command for redo operation
* @param state - Current editor state
* @param dispatch - Dispatch function for state changes
* @returns true if command was handled
*/
function redoCommand(state: EditorState, dispatch?: (tr: Transaction) => void): boolean;Core binding class that manages the synchronization lifecycle between Yjs and ProseMirror.
/**
* Main binding class managing synchronization between Yjs and ProseMirror
*/
class ProsemirrorBinding {
/** The bound Yjs XML fragment */
type: Y.XmlFragment;
/** The ProseMirror editor view */
prosemirrorView: EditorView | null;
/** Mapping between Yjs and ProseMirror nodes */
mapping: ProsemirrorMapping;
/** The Yjs document */
doc: Y.Doc;
/** Mutex for preventing concurrent operations */
mux: Mutex;
/** Map tracking overlapping marks */
isOMark: Map<MarkType, boolean>;
/** Whether this binding has been destroyed */
isDestroyed: boolean;
/** Current selection as relative positions in the Yjs model */
beforeTransactionSelection: any;
/**
* Create a new ProseMirror binding
* @param yXmlFragment - Yjs XML fragment to bind
* @param mapping - Optional initial mapping
*/
constructor(yXmlFragment: Y.XmlFragment, mapping?: Map);
/**
* Initialize the binding with a ProseMirror view
* @param prosemirrorView - ProseMirror editor view
*/
initView(prosemirrorView: EditorView): void;
/**
* Clean up the binding and remove event listeners
*/
destroy(): void;
/**
* Render a specific document snapshot
* @param snapshot - Document snapshot to render
* @param prevSnapshot - Previous snapshot for comparison
*/
renderSnapshot(snapshot: Snapshot, prevSnapshot?: Snapshot): void;
/**
* Remove snapshot rendering and return to current state
*/
unrenderSnapshot(): void;
/**
* Get current transaction with addToHistory set to false
* @returns Transaction for internal operations
*/
get _tr(): Transaction;
/**
* Check if local cursor is visible in view
* @returns true if local cursor is in viewport
*/
_isLocalCursorInView(): boolean;
/**
* Force re-render of the binding mapping
*/
_forceRerender(): void;
}Default implementations for cursor display and awareness filtering that can be used or customized.
/**
* Default cursor element builder
* @param user - User information from awareness
* @param clientId - Client ID of the user
* @returns HTML element for cursor display
*/
function defaultCursorBuilder(user: any, clientId: number): HTMLElement;
/**
* Default selection attributes builder
* @param user - User information from awareness
* @param clientId - Client ID of the user
* @returns Decoration attributes for selection display
*/
function defaultSelectionBuilder(user: any, clientId: number): DecorationAttrs;
/**
* Default awareness state filter
* @param currentClientId - Current user's client ID
* @param userClientId - Other user's client ID
* @param user - User data from awareness
* @returns true if user should be displayed
*/
function defaultAwarenessStateFilter(
currentClientId: number,
userClientId: number,
user: any
): boolean;
/**
* Default filter for undo delete operations
* @param item - Yjs item being deleted
* @param protectedNodes - Set of protected node types
* @returns true if item should be deleted
*/
function defaultDeleteFilter(item: any, protectedNodes: Set<string>): boolean;
/** Default set of protected node types for undo operations */
const defaultProtectedNodes: Set<string>;Functions that are exported but primarily intended for internal use or advanced scenarios.
/**
* Update Yjs fragment from ProseMirror node
* @param y - Yjs document
* @param yDomFragment - Yjs fragment to update
* @param pNode - ProseMirror node
* @param meta - Binding metadata
*/
function updateYFragment(y: Y.Doc, yDomFragment: Y.XmlFragment, pNode: Node, meta: BindingMetadata): void;
/**
* Create ProseMirror node from Yjs element
* @param yElement - Yjs element
* @param schema - ProseMirror schema
* @param meta - Binding metadata
* @returns ProseMirror node
*/
function createNodeFromYElement(yElement: Y.XmlElement, schema: Schema, meta: BindingMetadata): Node;
/**
* Check if Yjs item is visible in snapshot
* @param item - Yjs item to check
* @param snapshot - Snapshot to check against
* @returns true if item is visible
*/
function isVisible(item: any, snapshot?: Snapshot): boolean;
/**
* Create empty binding metadata
* @returns Empty binding metadata object
*/
function createEmptyMeta(): BindingMetadata;
/**
* Get current selection as relative positions for collaboration
* @param pmbinding - ProseMirror binding instance
* @param state - Current editor state
* @returns Selection object with relative positions
*/
function getRelativeSelection(pmbinding: ProsemirrorBinding, state: EditorState): {
type: string;
anchor: Y.RelativePosition;
head: Y.RelativePosition;
};
/**
* Convert Yjs attribute name to ProseMirror mark name
* @param attrName - Yjs attribute name
* @returns ProseMirror mark name
*/
function yattr2markname(attrName: string): string;
/**
* Convert attributes to ProseMirror marks
* @param attrs - Attribute object
* @param schema - ProseMirror schema
* @returns Array of ProseMirror marks
*/
function attributesToMarks(attrs: object, schema: Schema): Mark[];
/**
* Create cursor and selection decorations
* @param awarenessStates - Map of awareness states
* @param cursorBuilder - Function to build cursor elements
* @param selectionBuilder - Function to build selection attributes
* @param schema - ProseMirror schema
* @returns DecorationSet with cursor decorations
*/
function createDecorations(
awarenessStates: Map<number, any>,
cursorBuilder: (user: any, clientId: number) => HTMLElement,
selectionBuilder: (user: any, clientId: number) => DecorationAttrs,
schema: Schema
): DecorationSet;Install with Tessl CLI
npx tessl i tessl/npm-y-prosemirror