@storacha/md-merge 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/codec.d.ts +20 -0
- package/dist/codec.js +4 -4
- package/dist/diff.js +10 -5
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/rga-tree.js +4 -2
- package/package.json +1 -1
- package/src/codec.ts +6 -6
- package/src/diff.ts +10 -5
- package/src/index.ts +15 -0
- package/src/rga-tree.ts +4 -2
- package/test/rga-tree.test.ts +127 -1
package/dist/codec.d.ts
CHANGED
|
@@ -5,9 +5,29 @@
|
|
|
5
5
|
* adding recursive handling of nested RGA children in tree nodes.
|
|
6
6
|
*/
|
|
7
7
|
import { type RGAEvent, type EventComparator } from './crdt/rga.js';
|
|
8
|
+
import { type SerializedRGA, type SerializedNodeId } from './crdt/codec.js';
|
|
8
9
|
import type { RGATreeRoot } from './types.js';
|
|
9
10
|
import type { RGAChangeSet } from './types.js';
|
|
10
11
|
export { encodeRGA, decodeRGA } from './crdt/codec.js';
|
|
12
|
+
export declare function serializeTree<E extends RGAEvent>(root: RGATreeRoot<E>): unknown;
|
|
13
|
+
export declare function deserializeTree<E extends RGAEvent>(raw: {
|
|
14
|
+
type: string;
|
|
15
|
+
children: SerializedRGA;
|
|
16
|
+
}, parseEvent: (s: string) => E, compareEvents: EventComparator<E>): RGATreeRoot<E>;
|
|
17
|
+
export interface SerializedRGAChangeSet {
|
|
18
|
+
event: string;
|
|
19
|
+
changes: SerializedRGAChange[];
|
|
20
|
+
}
|
|
21
|
+
export interface SerializedRGAChange {
|
|
22
|
+
type: 'insert' | 'delete' | 'modify';
|
|
23
|
+
parentPath: SerializedNodeId[];
|
|
24
|
+
targetId?: SerializedNodeId | null;
|
|
25
|
+
afterId?: SerializedNodeId | null;
|
|
26
|
+
nodes?: unknown[];
|
|
27
|
+
before?: unknown[];
|
|
28
|
+
}
|
|
29
|
+
export declare function serializeChangeSet<E extends RGAEvent>(cs: RGAChangeSet<E>): SerializedRGAChangeSet;
|
|
30
|
+
export declare function deserializeChangeSet<E extends RGAEvent>(raw: SerializedRGAChangeSet, parseEvent: (s: string) => E): RGAChangeSet<E>;
|
|
11
31
|
export declare function encodeTree<E extends RGAEvent>(root: RGATreeRoot<E>): Promise<import("multiformats").BlockView<unknown, 113, 18, 1>>;
|
|
12
32
|
export declare function decodeTree<E extends RGAEvent>(block: {
|
|
13
33
|
bytes: Uint8Array;
|
package/dist/codec.js
CHANGED
|
@@ -31,7 +31,7 @@ function serializeTreeNodeValue(node) {
|
|
|
31
31
|
}
|
|
32
32
|
return node;
|
|
33
33
|
}
|
|
34
|
-
function serializeTree(root) {
|
|
34
|
+
export function serializeTree(root) {
|
|
35
35
|
return {
|
|
36
36
|
type: 'root',
|
|
37
37
|
children: serializeRGA(root.children, (n) => serializeTreeNodeValue(n)),
|
|
@@ -50,13 +50,13 @@ function deserializeTreeNodeValue(raw, parseEvent, compareEvents) {
|
|
|
50
50
|
function deserializeTreeRGA(raw, parseEvent, compareEvents) {
|
|
51
51
|
return deserializeRGA(raw, parseEvent, (v) => deserializeTreeNodeValue(v, parseEvent, compareEvents), compareEvents);
|
|
52
52
|
}
|
|
53
|
-
function deserializeTree(raw, parseEvent, compareEvents) {
|
|
53
|
+
export function deserializeTree(raw, parseEvent, compareEvents) {
|
|
54
54
|
return {
|
|
55
55
|
type: 'root',
|
|
56
56
|
children: deserializeTreeRGA(raw.children, parseEvent, compareEvents),
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
|
-
function serializeChangeSet(cs) {
|
|
59
|
+
export function serializeChangeSet(cs) {
|
|
60
60
|
return {
|
|
61
61
|
event: cs.event.toString(),
|
|
62
62
|
changes: cs.changes.map(c => ({
|
|
@@ -69,7 +69,7 @@ function serializeChangeSet(cs) {
|
|
|
69
69
|
})),
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
|
-
function deserializeChangeSet(raw, parseEvent) {
|
|
72
|
+
export function deserializeChangeSet(raw, parseEvent) {
|
|
73
73
|
return {
|
|
74
74
|
event: parseEvent(raw.event),
|
|
75
75
|
changes: raw.changes.map(c => {
|
package/dist/diff.js
CHANGED
|
@@ -109,14 +109,19 @@ function diffGap(oldNodes, newNodes, oldStartIdx, newStartIdx, pathPrefix) {
|
|
|
109
109
|
});
|
|
110
110
|
oi++;
|
|
111
111
|
}
|
|
112
|
-
// Remaining new = inserts
|
|
113
|
-
|
|
112
|
+
// Remaining new = inserts (batched into a single change)
|
|
113
|
+
if (ni < newNodes.length) {
|
|
114
|
+
const insertNodes = [];
|
|
115
|
+
const insertIdx = newStartIdx + ni;
|
|
116
|
+
while (ni < newNodes.length) {
|
|
117
|
+
insertNodes.push(newNodes[ni]);
|
|
118
|
+
ni++;
|
|
119
|
+
}
|
|
114
120
|
changes.push({
|
|
115
121
|
type: "insert",
|
|
116
|
-
path: [...pathPrefix,
|
|
117
|
-
nodes:
|
|
122
|
+
path: [...pathPrefix, insertIdx],
|
|
123
|
+
nodes: insertNodes,
|
|
118
124
|
});
|
|
119
|
-
ni++;
|
|
120
125
|
}
|
|
121
126
|
return changes;
|
|
122
127
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -9,7 +9,8 @@ import type { RGAEvent, EventComparator } from "./crdt/rga.js";
|
|
|
9
9
|
export { parse, stringify, stringifyNode, fingerprint } from "./parse.js";
|
|
10
10
|
export { diff, applyChangeSet } from "./diff.js";
|
|
11
11
|
export { toRGATree, toMdast, applyMdastToRGATree, generateRGAChangeSet, applyRGAChangeSet, mergeRGATrees, } from "./rga-tree.js";
|
|
12
|
-
export { encodeTree, decodeTree, encodeChangeSet, decodeChangeSet, encodeRGA, decodeRGA, } from "./codec.js";
|
|
12
|
+
export { encodeTree, decodeTree, encodeChangeSet, decodeChangeSet, encodeRGA, decodeRGA, serializeTree, deserializeTree, serializeChangeSet, deserializeChangeSet, type SerializedRGAChangeSet, type SerializedRGAChange, } from "./codec.js";
|
|
13
|
+
export { stripUndefined, serializeRGA, deserializeRGA, serializeNodeId, deserializeNodeId, type SerializedRGA, type SerializedNodeId, } from "./crdt/codec.js";
|
|
13
14
|
export type { ChangeSet, Change, RGAChangeSet, RGAChange, RGATreeRoot, RGATreeNode, RGAParentNode, RGALeafNode, } from "./types.js";
|
|
14
15
|
export { RGA, type RGAEvent, type RGANodeId, type EventComparator, } from "./types.js";
|
|
15
16
|
/**
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,8 @@ import { toRGATree, toMdast, generateRGAChangeSet, applyRGAChangeSet, } from "./
|
|
|
10
10
|
export { parse, stringify, stringifyNode, fingerprint } from "./parse.js";
|
|
11
11
|
export { diff, applyChangeSet } from "./diff.js";
|
|
12
12
|
export { toRGATree, toMdast, applyMdastToRGATree, generateRGAChangeSet, applyRGAChangeSet, mergeRGATrees, } from "./rga-tree.js";
|
|
13
|
-
export { encodeTree, decodeTree, encodeChangeSet, decodeChangeSet, encodeRGA, decodeRGA, } from "./codec.js";
|
|
13
|
+
export { encodeTree, decodeTree, encodeChangeSet, decodeChangeSet, encodeRGA, decodeRGA, serializeTree, deserializeTree, serializeChangeSet, deserializeChangeSet, } from "./codec.js";
|
|
14
|
+
export { stripUndefined, serializeRGA, deserializeRGA, serializeNodeId, deserializeNodeId, } from "./crdt/codec.js";
|
|
14
15
|
export { RGA, } from "./types.js";
|
|
15
16
|
/**
|
|
16
17
|
* Compute an RGA-addressed changeset between an existing RGA tree and new markdown.
|
package/dist/rga-tree.js
CHANGED
|
@@ -189,18 +189,20 @@ export function applyRGAChangeSet(root, changeset, compareEvents) {
|
|
|
189
189
|
break;
|
|
190
190
|
}
|
|
191
191
|
case "insert": {
|
|
192
|
+
let afterId = change.afterId;
|
|
192
193
|
for (const node of change.nodes ?? []) {
|
|
193
194
|
const rgaNode = convertNode(node, changeset.event, compareEvents);
|
|
194
|
-
currentRGA.insert(
|
|
195
|
+
afterId = currentRGA.insert(afterId, rgaNode, changeset.event);
|
|
195
196
|
}
|
|
196
197
|
break;
|
|
197
198
|
}
|
|
198
199
|
case "modify": {
|
|
199
200
|
if (change.targetId)
|
|
200
201
|
currentRGA.delete(change.targetId);
|
|
202
|
+
let afterId = change.afterId;
|
|
201
203
|
for (const node of change.nodes ?? []) {
|
|
202
204
|
const rgaNode = convertNode(node, changeset.event, compareEvents);
|
|
203
|
-
currentRGA.insert(
|
|
205
|
+
afterId = currentRGA.insert(afterId, rgaNode, changeset.event);
|
|
204
206
|
}
|
|
205
207
|
break;
|
|
206
208
|
}
|
package/package.json
CHANGED
package/src/codec.ts
CHANGED
|
@@ -51,7 +51,7 @@ function serializeTreeNodeValue<E extends RGAEvent>(node: RGATreeNode<E>): unkno
|
|
|
51
51
|
return node
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
function serializeTree<E extends RGAEvent>(root: RGATreeRoot<E>): unknown {
|
|
54
|
+
export function serializeTree<E extends RGAEvent>(root: RGATreeRoot<E>): unknown {
|
|
55
55
|
return {
|
|
56
56
|
type: 'root',
|
|
57
57
|
children: serializeRGA(root.children, (n: RGATreeNode<E>) => serializeTreeNodeValue(n)),
|
|
@@ -86,7 +86,7 @@ function deserializeTreeRGA<E extends RGAEvent>(
|
|
|
86
86
|
)
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
function deserializeTree<E extends RGAEvent>(
|
|
89
|
+
export function deserializeTree<E extends RGAEvent>(
|
|
90
90
|
raw: { type: string; children: SerializedRGA },
|
|
91
91
|
parseEvent: (s: string) => E,
|
|
92
92
|
compareEvents: EventComparator<E>,
|
|
@@ -99,12 +99,12 @@ function deserializeTree<E extends RGAEvent>(
|
|
|
99
99
|
|
|
100
100
|
// ---- RGAChangeSet serialization ----
|
|
101
101
|
|
|
102
|
-
interface SerializedRGAChangeSet {
|
|
102
|
+
export interface SerializedRGAChangeSet {
|
|
103
103
|
event: string
|
|
104
104
|
changes: SerializedRGAChange[]
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
interface SerializedRGAChange {
|
|
107
|
+
export interface SerializedRGAChange {
|
|
108
108
|
type: 'insert' | 'delete' | 'modify'
|
|
109
109
|
parentPath: SerializedNodeId[]
|
|
110
110
|
targetId?: SerializedNodeId | null
|
|
@@ -113,7 +113,7 @@ interface SerializedRGAChange {
|
|
|
113
113
|
before?: unknown[]
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
function serializeChangeSet<E extends RGAEvent>(cs: RGAChangeSet<E>): SerializedRGAChangeSet {
|
|
116
|
+
export function serializeChangeSet<E extends RGAEvent>(cs: RGAChangeSet<E>): SerializedRGAChangeSet {
|
|
117
117
|
return {
|
|
118
118
|
event: cs.event.toString(),
|
|
119
119
|
changes: cs.changes.map(c => ({
|
|
@@ -127,7 +127,7 @@ function serializeChangeSet<E extends RGAEvent>(cs: RGAChangeSet<E>): Serialized
|
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
function deserializeChangeSet<E extends RGAEvent>(
|
|
130
|
+
export function deserializeChangeSet<E extends RGAEvent>(
|
|
131
131
|
raw: SerializedRGAChangeSet,
|
|
132
132
|
parseEvent: (s: string) => E,
|
|
133
133
|
): RGAChangeSet<E> {
|
package/src/diff.ts
CHANGED
|
@@ -131,14 +131,19 @@ function diffGap(
|
|
|
131
131
|
});
|
|
132
132
|
oi++;
|
|
133
133
|
}
|
|
134
|
-
// Remaining new = inserts
|
|
135
|
-
|
|
134
|
+
// Remaining new = inserts (batched into a single change)
|
|
135
|
+
if (ni < newNodes.length) {
|
|
136
|
+
const insertNodes: RootContent[] = [];
|
|
137
|
+
const insertIdx = newStartIdx + ni;
|
|
138
|
+
while (ni < newNodes.length) {
|
|
139
|
+
insertNodes.push(newNodes[ni] as RootContent);
|
|
140
|
+
ni++;
|
|
141
|
+
}
|
|
136
142
|
changes.push({
|
|
137
143
|
type: "insert",
|
|
138
|
-
path: [...pathPrefix,
|
|
139
|
-
nodes:
|
|
144
|
+
path: [...pathPrefix, insertIdx],
|
|
145
|
+
nodes: insertNodes,
|
|
140
146
|
});
|
|
141
|
-
ni++;
|
|
142
147
|
}
|
|
143
148
|
return changes;
|
|
144
149
|
}
|
package/src/index.ts
CHANGED
|
@@ -33,7 +33,22 @@ export {
|
|
|
33
33
|
decodeChangeSet,
|
|
34
34
|
encodeRGA,
|
|
35
35
|
decodeRGA,
|
|
36
|
+
serializeTree,
|
|
37
|
+
deserializeTree,
|
|
38
|
+
serializeChangeSet,
|
|
39
|
+
deserializeChangeSet,
|
|
40
|
+
type SerializedRGAChangeSet,
|
|
41
|
+
type SerializedRGAChange,
|
|
36
42
|
} from "./codec.js";
|
|
43
|
+
export {
|
|
44
|
+
stripUndefined,
|
|
45
|
+
serializeRGA,
|
|
46
|
+
deserializeRGA,
|
|
47
|
+
serializeNodeId,
|
|
48
|
+
deserializeNodeId,
|
|
49
|
+
type SerializedRGA,
|
|
50
|
+
type SerializedNodeId,
|
|
51
|
+
} from "./crdt/codec.js";
|
|
37
52
|
export type {
|
|
38
53
|
ChangeSet,
|
|
39
54
|
Change,
|
package/src/rga-tree.ts
CHANGED
|
@@ -272,25 +272,27 @@ export function applyRGAChangeSet<E extends RGAEvent>(
|
|
|
272
272
|
break;
|
|
273
273
|
}
|
|
274
274
|
case "insert": {
|
|
275
|
+
let afterId = change.afterId;
|
|
275
276
|
for (const node of change.nodes ?? []) {
|
|
276
277
|
const rgaNode = convertNode(
|
|
277
278
|
node as Node,
|
|
278
279
|
changeset.event,
|
|
279
280
|
compareEvents,
|
|
280
281
|
);
|
|
281
|
-
currentRGA.insert(
|
|
282
|
+
afterId = currentRGA.insert(afterId, rgaNode, changeset.event);
|
|
282
283
|
}
|
|
283
284
|
break;
|
|
284
285
|
}
|
|
285
286
|
case "modify": {
|
|
286
287
|
if (change.targetId) currentRGA.delete(change.targetId);
|
|
288
|
+
let afterId = change.afterId;
|
|
287
289
|
for (const node of change.nodes ?? []) {
|
|
288
290
|
const rgaNode = convertNode(
|
|
289
291
|
node as Node,
|
|
290
292
|
changeset.event,
|
|
291
293
|
compareEvents,
|
|
292
294
|
);
|
|
293
|
-
currentRGA.insert(
|
|
295
|
+
afterId = currentRGA.insert(afterId, rgaNode, changeset.event);
|
|
294
296
|
}
|
|
295
297
|
break;
|
|
296
298
|
}
|
package/test/rga-tree.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
2
|
import { parse, stringify } from "../src/parse.js";
|
|
3
|
-
import { toRGATree, toMdast, applyMdastToRGATree, mergeRGATrees } from "../src/rga-tree.js";
|
|
3
|
+
import { toRGATree, toMdast, applyMdastToRGATree, mergeRGATrees, applyRGAChangeSet } from "../src/rga-tree.js";
|
|
4
4
|
import {
|
|
5
5
|
RGA,
|
|
6
6
|
type RGAEvent,
|
|
@@ -321,3 +321,129 @@ describe("mergeRGATrees", () => {
|
|
|
321
321
|
expect(tree2.children.nodes.size).toBe(origSize2);
|
|
322
322
|
});
|
|
323
323
|
});
|
|
324
|
+
|
|
325
|
+
describe("multi-append", () => {
|
|
326
|
+
it("handles appending multiple nodes at once", () => {
|
|
327
|
+
const oldMd = "# Hello\n\nOriginal.\n";
|
|
328
|
+
const newMd = "# Hello\n\nOriginal.\n\nSecond.\n\nThird.\n";
|
|
329
|
+
const oldRoot = parse(oldMd);
|
|
330
|
+
const newRoot = parse(newMd);
|
|
331
|
+
const tree = toRGATree(oldRoot, r1, cmp);
|
|
332
|
+
|
|
333
|
+
const r2 = new TestEvent("r2");
|
|
334
|
+
const updated = applyMdastToRGATree(tree, newRoot, r2, cmp);
|
|
335
|
+
const result = stringify(toMdast(updated));
|
|
336
|
+
|
|
337
|
+
expect(result).toContain("Original.");
|
|
338
|
+
expect(result).toContain("Second.");
|
|
339
|
+
expect(result).toContain("Third.");
|
|
340
|
+
// Verify order is preserved
|
|
341
|
+
const origIdx = result.indexOf("Original.");
|
|
342
|
+
const secIdx = result.indexOf("Second.");
|
|
343
|
+
const thirdIdx = result.indexOf("Third.");
|
|
344
|
+
expect(origIdx).toBeLessThan(secIdx);
|
|
345
|
+
expect(secIdx).toBeLessThan(thirdIdx);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it("handles appending to an empty children array", () => {
|
|
349
|
+
const oldMd = "";
|
|
350
|
+
const newMd = "# Hello\n\nWorld.\n";
|
|
351
|
+
const oldRoot = parse(oldMd);
|
|
352
|
+
const newRoot = parse(newMd);
|
|
353
|
+
const tree = toRGATree(oldRoot, r1, cmp);
|
|
354
|
+
|
|
355
|
+
const r2 = new TestEvent("r2");
|
|
356
|
+
const updated = applyMdastToRGATree(tree, newRoot, r2, cmp);
|
|
357
|
+
const result = stringify(toMdast(updated));
|
|
358
|
+
|
|
359
|
+
expect(result).toContain("Hello");
|
|
360
|
+
expect(result).toContain("World.");
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
describe("applyRGAChangeSet multi-node", () => {
|
|
365
|
+
const r2 = new TestEvent("r2");
|
|
366
|
+
|
|
367
|
+
it("insert with multiple nodes preserves order", () => {
|
|
368
|
+
const tree = toRGATree(parse("# Hello\n"), r1, cmp);
|
|
369
|
+
const afterId = tree.children.toNodes()[0].id; // after the heading
|
|
370
|
+
|
|
371
|
+
const changeset = {
|
|
372
|
+
event: r2,
|
|
373
|
+
changes: [{
|
|
374
|
+
type: "insert" as const,
|
|
375
|
+
parentPath: [],
|
|
376
|
+
afterId,
|
|
377
|
+
nodes: [
|
|
378
|
+
{ type: "paragraph", children: [{ type: "text", value: "First" }] },
|
|
379
|
+
{ type: "paragraph", children: [{ type: "text", value: "Second" }] },
|
|
380
|
+
{ type: "paragraph", children: [{ type: "text", value: "Third" }] },
|
|
381
|
+
] as any,
|
|
382
|
+
}],
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
const result = applyRGAChangeSet(tree, changeset, cmp);
|
|
386
|
+
const md = stringify(toMdast(result));
|
|
387
|
+
|
|
388
|
+
expect(md).toContain("First");
|
|
389
|
+
expect(md).toContain("Second");
|
|
390
|
+
expect(md).toContain("Third");
|
|
391
|
+
expect(md.indexOf("First")).toBeLessThan(md.indexOf("Second"));
|
|
392
|
+
expect(md.indexOf("Second")).toBeLessThan(md.indexOf("Third"));
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it("modify with multiple replacement nodes preserves order", () => {
|
|
396
|
+
const tree = toRGATree(parse("# Hello\n\nOriginal.\n"), r1, cmp);
|
|
397
|
+
const nodes = tree.children.toNodes();
|
|
398
|
+
const targetId = nodes[1].id; // the paragraph
|
|
399
|
+
const afterId = nodes[0].id; // after the heading
|
|
400
|
+
|
|
401
|
+
const changeset = {
|
|
402
|
+
event: r2,
|
|
403
|
+
changes: [{
|
|
404
|
+
type: "modify" as const,
|
|
405
|
+
parentPath: [],
|
|
406
|
+
targetId,
|
|
407
|
+
afterId,
|
|
408
|
+
nodes: [
|
|
409
|
+
{ type: "paragraph", children: [{ type: "text", value: "Replacement A" }] },
|
|
410
|
+
{ type: "paragraph", children: [{ type: "text", value: "Replacement B" }] },
|
|
411
|
+
] as any,
|
|
412
|
+
}],
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
const result = applyRGAChangeSet(tree, changeset, cmp);
|
|
416
|
+
const md = stringify(toMdast(result));
|
|
417
|
+
|
|
418
|
+
expect(md).not.toContain("Original.");
|
|
419
|
+
expect(md).toContain("Replacement A");
|
|
420
|
+
expect(md).toContain("Replacement B");
|
|
421
|
+
expect(md.indexOf("Replacement A")).toBeLessThan(md.indexOf("Replacement B"));
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it("insert at start (no afterId) with multiple nodes preserves relative order", () => {
|
|
425
|
+
// Insert two nodes at root with no afterId. RGA ordering means they
|
|
426
|
+
// appear after existing r1 nodes (r2 > r1), but their relative order
|
|
427
|
+
// (A before B) is preserved via chained afterIds.
|
|
428
|
+
const tree = toRGATree(parse("# Existing\n"), r1, cmp);
|
|
429
|
+
|
|
430
|
+
const changeset = {
|
|
431
|
+
event: r2,
|
|
432
|
+
changes: [{
|
|
433
|
+
type: "insert" as const,
|
|
434
|
+
parentPath: [],
|
|
435
|
+
afterId: undefined,
|
|
436
|
+
nodes: [
|
|
437
|
+
{ type: "paragraph", children: [{ type: "text", value: "Added A" }] },
|
|
438
|
+
{ type: "paragraph", children: [{ type: "text", value: "Added B" }] },
|
|
439
|
+
] as any,
|
|
440
|
+
}],
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
const result = applyRGAChangeSet(tree, changeset, cmp);
|
|
444
|
+
const md = stringify(toMdast(result));
|
|
445
|
+
|
|
446
|
+
// Relative order of the inserted nodes is preserved
|
|
447
|
+
expect(md.indexOf("Added A")).toBeLessThan(md.indexOf("Added B"));
|
|
448
|
+
});
|
|
449
|
+
});
|