marshall-y-slate
Version:
Yjs bindings for Slate.
97 lines (90 loc) • 3.18 kB
text/typescript
import { UndoManager } from 'yjs';
import * as Y from 'yjs';
import { YjsEditor } from './yjs-editor';
import { absolutePositionToRelativePosition, relativePositionToAbsolutePosition } from '../cursor/utils';
import { BasePoint, Descendant, Editor, Path, Transforms } from 'slate';
export interface YjsUndoEditor extends YjsEditor {
undoManager: UndoManager;
undo: () => void;
redo: () => void;
}
export function withUndoManager<T extends YjsEditor>(
editor: T,
options = {
trackedOrigins: [],
captureTimeout: 300,
deleteFilter: () => true
}
): T & YjsUndoEditor {
const e = editor as T & YjsUndoEditor;
const { onChange } = e;
let previousSelection: { anchorRelative: Y.RelativePosition, focusRelative: Y.RelativePosition } | null;
const undoManager = new Y.UndoManager(e.sharedType, {
...options,
trackedOrigins: new Set([editor].concat(options.trackedOrigins))
});
e.onChange = () => {
onChange();
if (!YjsEditor.isRemote(e)) {
try {
const lastOperation = e.operations[e.operations.length - 1];
if (lastOperation && lastOperation.type === 'set_selection' && lastOperation.newProperties) {
const { anchor, focus } = lastOperation.newProperties as any;
const anchorRelative = anchor && absolutePositionToRelativePosition(e.sharedType, anchor);
const focusRelative = focus && absolutePositionToRelativePosition(e.sharedType, focus);
if (anchorRelative && focusRelative) {
previousSelection = {
anchorRelative,
focusRelative
};
}
}
} catch (error) {
const e: YjsEditor & {
onError: (errorData: {
code?: number,
name?: string,
nativeError?: any,
data?: Descendant[]
}) => void
} = editor as any;
if (e.onError) {
e.onError({ code: 10004, name: 'get previous relative', nativeError: error });
}
}
}
};
undoManager.on('stack-item-added', (event: any) => {
if (event.changedParentTypes.has(e.sharedType) && previousSelection) {
event.stackItem.meta.set(e, previousSelection);
previousSelection = null;
}
});
undoManager.on('stack-item-popped', (event: any) => {
const selection = event.stackItem.meta.get(e);
if (selection) {
const anchor = relativePositionToAbsolutePosition(e.sharedType, selection.anchorRelative);
const focus = relativePositionToAbsolutePosition(e.sharedType, selection.focusRelative);
if (anchor?.path && focus?.path && Editor.hasPath(e, anchor.path as Path) && Editor.hasPath(e, focus.path as Path)) {
Transforms.setSelection(e, {
anchor: anchor as BasePoint,
focus: focus as BasePoint
});
}
}
});
e.undoManager = undoManager;
e.undo = () => {
YjsEditor.asUndo(e, () => {
e.undoManager.undo();
});
Promise.resolve().then(() => Editor.normalize(editor));
};
e.redo = () => {
YjsEditor.asUndo(e, () => {
e.undoManager.redo();
});
Promise.resolve().then(() => Editor.normalize(editor));
};
return e;
}