Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | 4x 393x 4x 10x 10x 10x 10x 10x 10x 358x 358x 10x 443x 443x 136x 443x 10x 4x 25x 25x 25x 4x 25x 25x 25x 1077x 1077x 1077x 1077x 25x 25x 555x 555x 1077x 555x 25x 580x 555x | import {createEditor, Transforms, type Editor} from 'slate';
import type {SlateDocument, SlateOperation} from '../../types';
export const clone = <T>(value: T): T => JSON.parse(JSON.stringify(value)) as T;
export interface SlateTrace {
start: SlateDocument;
operations: SlateOperation[];
/**
* High-level Slate.js operations are often composed of multiple low-level
* atomic operations. All the atomic operations are stored in the `operations`
* array, this array records the indices in `operations` where each high-level
* operation (most likely) begins. It records the index of the next operation
* when Slate `.normalize()` is called, which usually happens after each
* high-level operation.
*/
checkpoints: number[];
}
export class SlateTraceRecorder {
public static create(
initialValue: SlateDocument = [{type: 'paragraph', children: [{text: ''}]}],
): SlateTraceRecorder {
const editor = createEditor();
editor.children = clone(initialValue);
return new SlateTraceRecorder(editor);
}
public readonly editor: Editor;
public readonly start: SlateDocument;
public readonly operations: SlateOperation[] = [];
public readonly checkpoints: number[] = [];
constructor(editor: Editor) {
this.editor = editor;
this.start = clone(editor.children as SlateDocument);
const {apply, normalizeNode} = this.editor;
// 1. Capture every operation into our permanent log
this.editor.apply = (op: any) => {
this.operations.push(clone(op)); // Clone op to prevent reference issues
apply(op);
};
// 2. Mark high-level boundaries
this.editor.normalizeNode = (entry) => {
const [, path] = entry;
if (path.length === 0) {
// Record where the NEXT batch of operations will begin
this.checkpoints.push(this.operations.length);
}
normalizeNode(entry);
};
}
public getTrace(): SlateTrace {
return {
start: this.start,
operations: this.operations,
checkpoints: this.checkpoints,
};
}
}
export class SlateTraceRunner {
public readonly editor = createEditor();
public nextOpIdx: number = 0;
public nextCheckpointIdx: number = 0;
public static readonly from = (trace: SlateTrace): SlateTraceRunner => new SlateTraceRunner(trace);
constructor(readonly trace: SlateTrace) {
this.editor.children = clone(trace.start);
}
public readonly next = (): SlateOperation | undefined => {
const operation = this.trace.operations[this.nextOpIdx++];
Iif (!operation) return;
Transforms.transform(this.editor, operation);
return operation;
};
public runToEnd = (): void => {
while (this.next());
};
public readonly toNextCheckpoint = (): void => {
const checkpointIdx = this.trace.checkpoints[this.nextCheckpointIdx++];
Iif (checkpointIdx === undefined) return;
while (this.nextOpIdx < checkpointIdx) this.next();
this.editor.normalize();
};
public endReached = (): boolean => {
return this.nextCheckpointIdx >= this.trace.checkpoints.length;
};
public readonly state = (): SlateDocument => this.editor.children as SlateDocument;
}
|