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 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | 5x 5x 5x 5x 1096x 1096x 565x 565x 5x 827x 827x 827x 827x 827x 827x 793x 793x 793x 793x 793x 793x 793x 793x 592x 604x 604x 604x 604x 604x 201x 201x 176x 176x 5x 262x 262x 262x 262x 262x 262x 524x 265x 259x 5x 604x 604x 604x 195x 195x 409x 147x 147x 147x 262x 3x 3x 262x 5x 195x 195x 315x 195x 44x 44x 44x 151x 151x 151x 151x 93x 105x | /**
* Given the current `editor.children` (old) and a freshly-converted Slate
* document (dst), this module applies the minimal set of Slate `Transforms`
* needed to bring the editor in sync with the dst document.
*/
import {Transforms, Editor} from 'slate';
import * as str from 'json-joy/lib/util/diff/str';
import {deepEqual} from '@jsonjoy.com/json-equal';
import type {SlateDescendantNode, SlateDocument, SlateElementNode, SlateTextNode} from '../types';
/**
* Apply minimal Slate transforms to make `editor.children` match `dst`.
*/
export const applyPatch = (editor: Editor, dst: SlateDocument): void => {
const oldDoc = editor.children as SlateDocument;
if (oldDoc === dst) return;
Editor.withoutNormalizing(editor, () => {
patchChildren(editor, [], oldDoc as SlateDescendantNode[], dst as SlateDescendantNode[]);
});
};
const patchChildren = (
editor: Editor,
basePath: number[],
src: SlateDescendantNode[],
dst: SlateDescendantNode[],
): void => {
const srcLen = src.length;
const dstLen = dst.length;
const minLen = Math.min(srcLen, dstLen);
let pfx = 0;
while (pfx < minLen && deepEqual(src[pfx], dst[pfx])) pfx++;
if (pfx === srcLen && pfx === dstLen) return;
let sfx = 0;
while (sfx < minLen - pfx && deepEqual(src[srcLen - 1 - sfx], dst[dstLen - 1 - sfx])) sfx++;
const srcEnd = srcLen - sfx;
const dstEnd = dstLen - sfx;
const srcChanged = srcEnd - pfx;
const dstChanged = dstEnd - pfx;
const sameCount = srcChanged === dstChanged;
if (sameCount) {
for (let i = srcChanged - 1; i >= 0; i--) {
const idx = pfx + i;
const oldNode = src[idx];
const newNode = dst[idx];
Iif (deepEqual(oldNode, newNode)) continue;
patchNode(editor, [...basePath, idx], oldNode, newNode);
}
} else {
// Different counts in the changed window.
// Delete old[pfx..oldEnd) in reverse order, then insert new[pfx..newEnd).
for (let i = srcEnd - 1; i >= pfx; i--) Transforms.removeNodes(editor, {at: [...basePath, i]});
if (dstChanged > 0) {
const toInsert = dst.slice(pfx, dstEnd) as any[];
Transforms.insertNodes(editor, toInsert, {at: [...basePath, pfx]});
}
}
};
const attrsEqual = (a: SlateElementNode, b: SlateElementNode): boolean => {
Iif (a === b) return true;
Iif (a.type !== b.type) return false;
const aKeys = Object.keys(a);
const bKeys = Object.keys(b);
Iif (aKeys.length !== bKeys.length) return false;
for (const key of aKeys) {
if (key === 'children') continue;
if (!deepEqual(a[key], b[key])) return false;
}
return true;
};
const patchNode = (editor: Editor, path: number[], src: SlateDescendantNode, dst: SlateDescendantNode): void => {
const srcIsText = 'text' in src;
const dstIsText = 'text' in dst;
if (srcIsText && dstIsText) {
patchTextNode(editor, path, src as any, dst as any);
return;
}
// Replace whole node.
if (srcIsText || dstIsText || src.type !== dst.type) {
Transforms.removeNodes(editor, {at: path});
Transforms.insertNodes(editor, dst as any, {at: path});
return;
}
// Patch attributes and recurse on children.
if (!attrsEqual(src, dst)) {
const {children: _, ...newAttrs} = dst;
Transforms.setNodes(editor, newAttrs as any, {at: path});
}
patchChildren(editor, path, src.children, dst.children);
};
const patchTextNode = (editor: Editor, path: number[], src: SlateTextNode, dst: SlateTextNode): void => {
// Check whether non-text mark properties changed.
const oldKeys = Object.keys(src);
const newKeys = Object.keys(dst);
const markChanged =
oldKeys.length !== newKeys.length || oldKeys.some((k) => k !== 'text' && !deepEqual(src[k], dst[k]));
if (markChanged) {
Transforms.removeNodes(editor, {at: path});
Transforms.insertNodes(editor, dst as any, {at: path});
return;
}
// Diff and apply text content changes.
const srcTxt = src.text;
const dstTxt = dst.text;
Iif (srcTxt === dstTxt) return;
str.apply(
str.diff(srcTxt, dstTxt),
srcTxt.length,
(pos, str) => Transforms.insertText(editor, str, {at: {path, offset: pos}}),
(pos, len) => Transforms.delete(editor, {at: {path, offset: pos}, distance: len, unit: 'character'}),
);
};
|