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 121 122 | 10x 10x 623x 606x 606x 606x 83x 83x 523x 54x 54x 54x 54x 54x 54x 54x 54x 54x 54x 54x 469x 469x 469x 469x 469x 485x 485x 485x 26x 469x 469x 469x 465x 465x 465x 9x 469x 469x 469x 469x 469x 5x 5x 464x 464x 464x 464x 100x 100x 100x 185x 185x 100x 185x 185x 185x 184x 117x 364x 2854x 364x 3079x 364x 6x 464x | /**
* Granular ProseMirror document patching.
*
* Given an old and a new ProseMirror document, this module builds a minimal
* {@link Transaction} that transforms one into the other.
*/
import {Slice} from 'prosemirror-model';
import type {Node as PmNode} from 'prosemirror-model';
import type {Transaction} from 'prosemirror-state';
/**
* Build minimal transaction steps that transform `oldNode` into `newNode`.
*
* The `offset` parameter is the absolute document position of the node's
* opening tag. For the root `doc` node pass `-1` (default) because Prose-
* Mirror's doc node has no wrapper positions — its content starts at 0.
*
* @param tr The transaction to append steps to.
* @param oldNode The current (old) document / sub-tree root.
* @param newNode The desired (new) document / sub-tree root.
* @param offset Absolute position of this node's opening tag (-1 for doc).
*/
export const applyPatch = (tr: Transaction, oldNode: PmNode, newNode: PmNode, offset: number = -1): void => {
if (oldNode === newNode) return;
const oldContent = oldNode.content;
const newContent = newNode.content;
if (oldContent.eq(newContent)) {
if (offset >= 0 && !oldNode.sameMarkup(newNode)) tr.setNodeMarkup(offset, null, newNode.attrs, newNode.marks);
return;
}
if (oldNode.isTextblock) {
const start = oldContent.findDiffStart(newContent);
Eif (start !== null) {
let {a: endA, b: endB} = oldContent.findDiffEnd(newContent)!;
const overlap = start - Math.min(endA, endB);
Iif (overlap > 0) {
endA += overlap;
endB += overlap;
}
const from = offset + 1 + start;
const to = offset + 1 + endA;
const slice = newContent.cut(start, endB);
Eif (from !== to || slice.size > 0) tr.replace(from, to, new Slice(slice, 0, 0));
}
if (!oldNode.sameMarkup(newNode)) tr.setNodeMarkup(offset, null, newNode.attrs, newNode.marks);
return;
}
const oldCount = oldNode.childCount;
const newCount = newNode.childCount;
const minCount = Math.min(oldCount, newCount);
// Find longest common prefix. The `===` check covers cache hits; `.eq()`
// covers structurally-equal nodes that weren't cached.
let pfx = 0;
while (pfx < minCount) {
const oc = oldNode.child(pfx);
const nc = newNode.child(pfx);
if (oc !== nc && !oc.eq(nc)) break;
pfx++;
}
// All children match and counts are equal — only markup may have changed.
Iif (pfx === minCount && oldCount === newCount) {
if (offset >= 0 && !oldNode.sameMarkup(newNode)) tr.setNodeMarkup(offset, null, newNode.attrs, newNode.marks);
return;
}
// Find longest common suffix (avoid overlapping with prefix).
let sfx = 0;
while (sfx < minCount - pfx) {
const oc = oldNode.child(oldCount - 1 - sfx);
const nc = newNode.child(newCount - 1 - sfx);
if (oc !== nc && !oc.eq(nc)) break;
sfx++;
}
const oldEnd = oldCount - sfx;
const newEnd = newCount - sfx;
const oldChanged = oldEnd - pfx;
const newChanged = newEnd - pfx;
// When every child changed and we're not at the doc root, replace the whole
// node atomically. This avoids ProseMirror auto-filling required children
// (e.g. a list node that loses all its items via `tr.delete`).
if (offset >= 0 && pfx === 0 && sfx === 0) {
tr.replaceWith(offset, offset + oldNode.nodeSize, newNode);
return;
}
// Compute the base offset of the first changed child.
let childOffset = offset + 1;
for (let i = 0; i < pfx; i++) childOffset += oldNode.child(i).nodeSize;
const sameNumberOfChangedChildren = oldChanged === newChanged;
if (sameNumberOfChangedChildren) {
const offsets: number[] = new Array(oldChanged);
let off = childOffset;
for (let i = 0; i < oldChanged; i++) {
offsets[i] = off;
off += oldNode.child(pfx + i).nodeSize;
}
for (let i = oldChanged - 1; i >= 0; i--) {
const oc = oldNode.child(pfx + i);
const nc = newNode.child(pfx + i);
if (oc === nc || oc.eq(nc)) continue;
if (oc.type === nc.type) applyPatch(tr, oc, nc, offsets[i]);
else tr.replaceWith(offsets[i], offsets[i] + oc.nodeSize, nc);
}
} else {
let to = childOffset;
for (let i = 0; i < oldChanged; i++) to += oldNode.child(pfx + i).nodeSize;
const children: PmNode[] = [];
for (let i = pfx; i < newEnd; i++) children.push(newNode.child(i));
if (children.length) tr.replaceWith(childOffset, to, children);
else Eif (childOffset !== to) tr.delete(childOffset, to);
}
// After children have been reconciled, update wrapper markup if needed.
Iif (offset >= 0 && !oldNode.sameMarkup(newNode)) tr.setNodeMarkup(offset, null, newNode.attrs, newNode.marks);
};
|