xnl-collab-core 0.1.1
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/LICENSE +21 -0
- package/dist/index.cjs +573 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +16 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +565 -0
- package/dist/index.js.map +1 -0
- package/package.json +35 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 kongweixian
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var xnlCore = require('xnl-core');
|
|
4
|
+
var diff = require('diff');
|
|
5
|
+
|
|
6
|
+
// src/id.ts
|
|
7
|
+
var ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
8
|
+
function encodeTime(timeMs) {
|
|
9
|
+
let t = Math.floor(timeMs);
|
|
10
|
+
let out = "";
|
|
11
|
+
for (let i = 0; i < 10; i++) {
|
|
12
|
+
out = ENCODING[t % 32] + out;
|
|
13
|
+
t = Math.floor(t / 32);
|
|
14
|
+
}
|
|
15
|
+
return out;
|
|
16
|
+
}
|
|
17
|
+
function fillRandom(bytes) {
|
|
18
|
+
const c = globalThis.crypto;
|
|
19
|
+
if (c && typeof c.getRandomValues === "function") {
|
|
20
|
+
c.getRandomValues(bytes);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
24
|
+
bytes[i] = Math.floor(Math.random() * 256);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function encodeRandom(length) {
|
|
28
|
+
const bytes = new Uint8Array(length);
|
|
29
|
+
fillRandom(bytes);
|
|
30
|
+
let out = "";
|
|
31
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
32
|
+
out += ENCODING[bytes[i] & 31];
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
function ulid(nowMs = Date.now()) {
|
|
37
|
+
return encodeTime(nowMs) + encodeRandom(16);
|
|
38
|
+
}
|
|
39
|
+
function makeId(prefix) {
|
|
40
|
+
return `${prefix}${ulid()}`;
|
|
41
|
+
}
|
|
42
|
+
function assertAtMostOneRoot(nodes, context) {
|
|
43
|
+
if (nodes.length <= 1) return;
|
|
44
|
+
throw new Error(`${context}: expected 0 or 1 root node, got ${nodes.length}`);
|
|
45
|
+
}
|
|
46
|
+
function lastOrUndefined(nodes) {
|
|
47
|
+
return nodes.length ? nodes[nodes.length - 1] : void 0;
|
|
48
|
+
}
|
|
49
|
+
function mergeRootNodes3(baseNodes, leftNodes, rightNodes) {
|
|
50
|
+
const b = lastOrUndefined(baseNodes);
|
|
51
|
+
const l = lastOrUndefined(leftNodes);
|
|
52
|
+
const r = lastOrUndefined(rightNodes);
|
|
53
|
+
const bid = b ? readStableId(b) : void 0;
|
|
54
|
+
const lid = l ? readStableId(l) : void 0;
|
|
55
|
+
const rid = r ? readStableId(r) : void 0;
|
|
56
|
+
if (!b) {
|
|
57
|
+
if (!l && !r) return [];
|
|
58
|
+
if (!l) return [r];
|
|
59
|
+
if (!r) return [l];
|
|
60
|
+
if (lid && rid && lid === rid) {
|
|
61
|
+
const merged = mergeAny(void 0, l, r);
|
|
62
|
+
return merged === void 0 ? [] : [merged];
|
|
63
|
+
}
|
|
64
|
+
return [r] ;
|
|
65
|
+
}
|
|
66
|
+
if (!l && !r) return [];
|
|
67
|
+
if (!l && r) {
|
|
68
|
+
if (bid && rid && bid === rid) return [];
|
|
69
|
+
return [r];
|
|
70
|
+
}
|
|
71
|
+
if (l && !r) {
|
|
72
|
+
if (bid && lid && bid === lid) return [];
|
|
73
|
+
return [] ;
|
|
74
|
+
}
|
|
75
|
+
if (lid && rid && lid === rid) {
|
|
76
|
+
const merged = bid && bid === lid ? mergeAny(b, l, r) : mergeAny(void 0, l, r);
|
|
77
|
+
return merged === void 0 ? [] : [merged];
|
|
78
|
+
}
|
|
79
|
+
if (bid && lid && bid === lid && (!rid || bid !== rid)) {
|
|
80
|
+
return [r];
|
|
81
|
+
}
|
|
82
|
+
if (bid && rid && bid === rid && (!lid || bid !== lid)) {
|
|
83
|
+
return [l];
|
|
84
|
+
}
|
|
85
|
+
return [r] ;
|
|
86
|
+
}
|
|
87
|
+
function isPlainObject(value) {
|
|
88
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) && value.kind === void 0;
|
|
89
|
+
}
|
|
90
|
+
function isDataElement(node) {
|
|
91
|
+
return node && node.kind === "DataElement";
|
|
92
|
+
}
|
|
93
|
+
function isTextElement(node) {
|
|
94
|
+
return node && node.kind === "TextElement";
|
|
95
|
+
}
|
|
96
|
+
function cloneJson(v) {
|
|
97
|
+
return JSON.parse(JSON.stringify(v));
|
|
98
|
+
}
|
|
99
|
+
function readNodeId(node) {
|
|
100
|
+
if (!isDataElement(node) && !isTextElement(node)) return void 0;
|
|
101
|
+
return xnlCore.wordToString(node.id);
|
|
102
|
+
}
|
|
103
|
+
function readMetaId(node) {
|
|
104
|
+
if (!isDataElement(node) && !isTextElement(node)) return void 0;
|
|
105
|
+
const raw = node.metadata?.id;
|
|
106
|
+
if (typeof raw === "string") return raw;
|
|
107
|
+
if (xnlCore.isWord(raw)) return xnlCore.wordToString(raw) ?? void 0;
|
|
108
|
+
return void 0;
|
|
109
|
+
}
|
|
110
|
+
function readStableId(node) {
|
|
111
|
+
return readMetaId(node) ?? readNodeId(node);
|
|
112
|
+
}
|
|
113
|
+
function readTag(node) {
|
|
114
|
+
if (!isDataElement(node) && !isTextElement(node)) return void 0;
|
|
115
|
+
return node.tag;
|
|
116
|
+
}
|
|
117
|
+
function setMetaId(node, id) {
|
|
118
|
+
if (!isDataElement(node) && !isTextElement(node)) return;
|
|
119
|
+
if (!node.metadata || typeof node.metadata !== "object" || Array.isArray(node.metadata)) {
|
|
120
|
+
node.metadata = {};
|
|
121
|
+
}
|
|
122
|
+
node.metadata.id = id;
|
|
123
|
+
}
|
|
124
|
+
function makeMetaId(prefix) {
|
|
125
|
+
return makeId(prefix);
|
|
126
|
+
}
|
|
127
|
+
function ensureMetadataIds(root, prefix) {
|
|
128
|
+
const visit = (node) => {
|
|
129
|
+
if (isDataElement(node) || isTextElement(node)) {
|
|
130
|
+
const existing = readMetaId(node);
|
|
131
|
+
if (!existing) {
|
|
132
|
+
setMetaId(node, makeMetaId(prefix));
|
|
133
|
+
} else if (node.metadata?.id && typeof node.metadata.id !== "string") {
|
|
134
|
+
setMetaId(node, existing);
|
|
135
|
+
}
|
|
136
|
+
if (isDataElement(node)) {
|
|
137
|
+
if (node.body) for (const child of node.body) visit(child);
|
|
138
|
+
if (node.extend) {
|
|
139
|
+
for (const tag of node.extend.order) {
|
|
140
|
+
const child = node.extend.children[tag];
|
|
141
|
+
if (child) visit(child);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (node.attributes) {
|
|
145
|
+
for (const v of Object.values(node.attributes)) visit(v);
|
|
146
|
+
}
|
|
147
|
+
for (const v of Object.values(node.metadata ?? {})) visit(v);
|
|
148
|
+
} else {
|
|
149
|
+
if (node.attributes) {
|
|
150
|
+
for (const v of Object.values(node.attributes)) visit(v);
|
|
151
|
+
}
|
|
152
|
+
for (const v of Object.values(node.metadata ?? {})) visit(v);
|
|
153
|
+
}
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (Array.isArray(node)) {
|
|
157
|
+
for (const child of node) visit(child);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (isPlainObject(node)) {
|
|
161
|
+
for (const key of Object.keys(node)) visit(node[key]);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
if (root.nodes && Array.isArray(root.nodes)) {
|
|
165
|
+
for (const n of root.nodes) visit(n);
|
|
166
|
+
} else {
|
|
167
|
+
visit(root);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function mergePrimitive(base, left, right, key) {
|
|
171
|
+
if (left === right) return left;
|
|
172
|
+
if (left === base) return right;
|
|
173
|
+
if (right === base) return left;
|
|
174
|
+
return right ;
|
|
175
|
+
}
|
|
176
|
+
function normalizeEdits(edits) {
|
|
177
|
+
const sorted = edits.slice().sort((a, b) => a.start !== b.start ? a.start - b.start : a.end - b.end);
|
|
178
|
+
const out = [];
|
|
179
|
+
for (const e of sorted) {
|
|
180
|
+
const prev = out[out.length - 1];
|
|
181
|
+
if (!prev) {
|
|
182
|
+
out.push(e);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (prev.end === e.start && prev.end === prev.start && e.start === e.end) {
|
|
186
|
+
prev.text += e.text;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (prev.end === e.start && prev.text.length === 0 && e.text.length === 0) {
|
|
190
|
+
prev.end = e.end;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
out.push(e);
|
|
194
|
+
}
|
|
195
|
+
return out;
|
|
196
|
+
}
|
|
197
|
+
function diffToEdits(base, other) {
|
|
198
|
+
const changes = diff.diffChars(base, other);
|
|
199
|
+
let basePos = 0;
|
|
200
|
+
const edits = [];
|
|
201
|
+
for (let i = 0; i < changes.length; i++) {
|
|
202
|
+
const c = changes[i];
|
|
203
|
+
if (c.removed) {
|
|
204
|
+
const start = basePos;
|
|
205
|
+
const end = basePos + c.value.length;
|
|
206
|
+
basePos = end;
|
|
207
|
+
let inserted = "";
|
|
208
|
+
while (i + 1 < changes.length && changes[i + 1].added) {
|
|
209
|
+
inserted += changes[i + 1].value;
|
|
210
|
+
i++;
|
|
211
|
+
}
|
|
212
|
+
edits.push({ start, end, text: inserted });
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
if (c.added) {
|
|
216
|
+
edits.push({ start: basePos, end: basePos, text: c.value });
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
basePos += c.value.length;
|
|
220
|
+
}
|
|
221
|
+
return normalizeEdits(edits);
|
|
222
|
+
}
|
|
223
|
+
function editsOverlap(a, b) {
|
|
224
|
+
const aIsIns = a.start === a.end;
|
|
225
|
+
const bIsIns = b.start === b.end;
|
|
226
|
+
if (aIsIns && bIsIns) {
|
|
227
|
+
return a.start === b.start;
|
|
228
|
+
}
|
|
229
|
+
if (aIsIns && !bIsIns) {
|
|
230
|
+
return b.start <= a.start && a.start <= b.end;
|
|
231
|
+
}
|
|
232
|
+
if (!aIsIns && bIsIns) {
|
|
233
|
+
return a.start <= b.start && b.start <= a.end;
|
|
234
|
+
}
|
|
235
|
+
return Math.max(a.start, b.start) < Math.min(a.end, b.end);
|
|
236
|
+
}
|
|
237
|
+
function hasOverlappingEdits(left, right) {
|
|
238
|
+
let i = 0;
|
|
239
|
+
let j = 0;
|
|
240
|
+
const l = left.slice().sort((a, b) => a.start - b.start);
|
|
241
|
+
const r = right.slice().sort((a, b) => a.start - b.start);
|
|
242
|
+
while (i < l.length && j < r.length) {
|
|
243
|
+
const le = l[i];
|
|
244
|
+
const re = r[j];
|
|
245
|
+
if (editsOverlap(le, re)) return true;
|
|
246
|
+
const leEnd = le.end;
|
|
247
|
+
const reEnd = re.end;
|
|
248
|
+
if (leEnd < re.start || le.end === le.start && le.start < re.start) {
|
|
249
|
+
i++;
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
if (reEnd < le.start || re.end === re.start && re.start < le.start) {
|
|
253
|
+
j++;
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (le.start <= re.start) i++;
|
|
257
|
+
else j++;
|
|
258
|
+
}
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
function applyEdits(base, edits) {
|
|
262
|
+
const sorted = edits.slice().sort((a, b) => a.start !== b.start ? a.start - b.start : a.end - b.end);
|
|
263
|
+
let out = "";
|
|
264
|
+
let cursor = 0;
|
|
265
|
+
for (const e of sorted) {
|
|
266
|
+
out += base.slice(cursor, e.start);
|
|
267
|
+
out += e.text;
|
|
268
|
+
cursor = e.end;
|
|
269
|
+
}
|
|
270
|
+
out += base.slice(cursor);
|
|
271
|
+
return out;
|
|
272
|
+
}
|
|
273
|
+
function mergeText3(base, left, right) {
|
|
274
|
+
if (left === right) return left;
|
|
275
|
+
if (left === base) return right;
|
|
276
|
+
if (right === base) return left;
|
|
277
|
+
const editsL = diffToEdits(base, left);
|
|
278
|
+
const editsR = diffToEdits(base, right);
|
|
279
|
+
if (hasOverlappingEdits(editsL, editsR)) {
|
|
280
|
+
return right ;
|
|
281
|
+
}
|
|
282
|
+
return applyEdits(base, [...editsL, ...editsR]);
|
|
283
|
+
}
|
|
284
|
+
function mergePlainObject(base, left, right) {
|
|
285
|
+
const out = {};
|
|
286
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(base || {}), ...Object.keys(left || {}), ...Object.keys(right || {})]);
|
|
287
|
+
for (const key of keys) {
|
|
288
|
+
const b = (base || {})[key];
|
|
289
|
+
const l = (left || {})[key];
|
|
290
|
+
const r = (right || {})[key];
|
|
291
|
+
if (l === void 0 && r === void 0) continue;
|
|
292
|
+
out[key] = mergeAny(b, l, r);
|
|
293
|
+
}
|
|
294
|
+
return out;
|
|
295
|
+
}
|
|
296
|
+
function stableUnique(values) {
|
|
297
|
+
const out = [];
|
|
298
|
+
const seen = /* @__PURE__ */ new Set();
|
|
299
|
+
for (const v of values) {
|
|
300
|
+
if (seen.has(v)) continue;
|
|
301
|
+
seen.add(v);
|
|
302
|
+
out.push(v);
|
|
303
|
+
}
|
|
304
|
+
return out;
|
|
305
|
+
}
|
|
306
|
+
function mergeIdOrder(baseIds, leftIds, rightIds) {
|
|
307
|
+
const present = /* @__PURE__ */ new Set([...leftIds, ...rightIds]);
|
|
308
|
+
const nodes = Array.from(present);
|
|
309
|
+
const edges = /* @__PURE__ */ new Map();
|
|
310
|
+
const indeg = /* @__PURE__ */ new Map();
|
|
311
|
+
for (const id of nodes) {
|
|
312
|
+
edges.set(id, /* @__PURE__ */ new Set());
|
|
313
|
+
indeg.set(id, 0);
|
|
314
|
+
}
|
|
315
|
+
const addEdge = (from, to) => {
|
|
316
|
+
if (!present.has(from) || !present.has(to)) return;
|
|
317
|
+
if (from === to) return;
|
|
318
|
+
const s = edges.get(from);
|
|
319
|
+
if (s.has(to)) return;
|
|
320
|
+
s.add(to);
|
|
321
|
+
indeg.set(to, (indeg.get(to) ?? 0) + 1);
|
|
322
|
+
};
|
|
323
|
+
const addEdgesFromOrder = (order) => {
|
|
324
|
+
for (let i = 0; i + 1 < order.length; i++) addEdge(order[i], order[i + 1]);
|
|
325
|
+
};
|
|
326
|
+
addEdgesFromOrder(leftIds);
|
|
327
|
+
addEdgesFromOrder(rightIds);
|
|
328
|
+
const basePos = /* @__PURE__ */ new Map();
|
|
329
|
+
baseIds.forEach((id, idx) => basePos.set(id, idx));
|
|
330
|
+
const leftPos = /* @__PURE__ */ new Map();
|
|
331
|
+
leftIds.forEach((id, idx) => leftPos.set(id, idx));
|
|
332
|
+
const rightPos = /* @__PURE__ */ new Map();
|
|
333
|
+
rightIds.forEach((id, idx) => rightPos.set(id, idx));
|
|
334
|
+
const rank = (id) => {
|
|
335
|
+
return [basePos.get(id) ?? 1e9, leftPos.get(id) ?? 1e9, rightPos.get(id) ?? 1e9, id];
|
|
336
|
+
};
|
|
337
|
+
const compare = (a, b) => {
|
|
338
|
+
const ra = rank(a);
|
|
339
|
+
const rb = rank(b);
|
|
340
|
+
for (let i = 0; i < ra.length; i++) {
|
|
341
|
+
if (ra[i] < rb[i]) return -1;
|
|
342
|
+
if (ra[i] > rb[i]) return 1;
|
|
343
|
+
}
|
|
344
|
+
return 0;
|
|
345
|
+
};
|
|
346
|
+
const zeros = nodes.filter((id) => (indeg.get(id) ?? 0) === 0).sort(compare);
|
|
347
|
+
const out = [];
|
|
348
|
+
while (zeros.length) {
|
|
349
|
+
const id = zeros.shift();
|
|
350
|
+
out.push(id);
|
|
351
|
+
for (const to of edges.get(id) ?? []) {
|
|
352
|
+
indeg.set(to, (indeg.get(to) ?? 0) - 1);
|
|
353
|
+
if ((indeg.get(to) ?? 0) === 0) {
|
|
354
|
+
zeros.push(to);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
zeros.sort(compare);
|
|
358
|
+
}
|
|
359
|
+
if (out.length !== nodes.length) {
|
|
360
|
+
return stableUnique([...leftIds, ...rightIds]);
|
|
361
|
+
}
|
|
362
|
+
return out;
|
|
363
|
+
}
|
|
364
|
+
function mergeNodeList(base, left, right) {
|
|
365
|
+
const baseById = /* @__PURE__ */ new Map();
|
|
366
|
+
const leftById = /* @__PURE__ */ new Map();
|
|
367
|
+
const rightById = /* @__PURE__ */ new Map();
|
|
368
|
+
const baseIds = [];
|
|
369
|
+
const leftIds = [];
|
|
370
|
+
const rightIds = [];
|
|
371
|
+
const collect = (arr, ids, map) => {
|
|
372
|
+
for (const n of arr) {
|
|
373
|
+
const id = readStableId(n);
|
|
374
|
+
if (!id) continue;
|
|
375
|
+
ids.push(id);
|
|
376
|
+
if (!map.has(id)) map.set(id, n);
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
collect(base, baseIds, baseById);
|
|
380
|
+
collect(left, leftIds, leftById);
|
|
381
|
+
collect(right, rightIds, rightById);
|
|
382
|
+
const order = mergeIdOrder(baseIds, leftIds, rightIds);
|
|
383
|
+
const merged = [];
|
|
384
|
+
for (const id of order) {
|
|
385
|
+
const b = baseById.get(id);
|
|
386
|
+
const l = leftById.get(id);
|
|
387
|
+
const r = rightById.get(id);
|
|
388
|
+
const m = mergeAny(b, l, r);
|
|
389
|
+
if (m !== void 0) merged.push(m);
|
|
390
|
+
}
|
|
391
|
+
const seenAnon = /* @__PURE__ */ new Set();
|
|
392
|
+
const pushAnon = (n) => {
|
|
393
|
+
const key = JSON.stringify(n);
|
|
394
|
+
if (seenAnon.has(key)) return;
|
|
395
|
+
seenAnon.add(key);
|
|
396
|
+
merged.push(n);
|
|
397
|
+
};
|
|
398
|
+
for (const n of left) {
|
|
399
|
+
if (readStableId(n)) continue;
|
|
400
|
+
pushAnon(n);
|
|
401
|
+
}
|
|
402
|
+
for (const n of right) {
|
|
403
|
+
if (readStableId(n)) continue;
|
|
404
|
+
pushAnon(n);
|
|
405
|
+
}
|
|
406
|
+
return merged;
|
|
407
|
+
}
|
|
408
|
+
function mergeExtend(base, left, right) {
|
|
409
|
+
if (!base && !left && !right) return void 0;
|
|
410
|
+
const b = base ?? { order: [], children: {} };
|
|
411
|
+
const l = left ?? { order: [], children: {} };
|
|
412
|
+
const r = right ?? { order: [], children: {} };
|
|
413
|
+
const mergedChildren = {};
|
|
414
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(b.children), ...Object.keys(l.children), ...Object.keys(r.children)]);
|
|
415
|
+
for (const k of keys) {
|
|
416
|
+
const bv = b.children[k];
|
|
417
|
+
const lv = l.children[k];
|
|
418
|
+
const rv = r.children[k];
|
|
419
|
+
if (lv === void 0 && rv === void 0) continue;
|
|
420
|
+
const merged = mergeAny(bv, lv, rv);
|
|
421
|
+
if (merged !== void 0) mergedChildren[k] = merged;
|
|
422
|
+
}
|
|
423
|
+
const present = new Set(Object.keys(mergedChildren));
|
|
424
|
+
const order = stableUnique([...l.order, ...r.order, ...b.order]).filter((t) => present.has(t));
|
|
425
|
+
return { order, children: mergedChildren };
|
|
426
|
+
}
|
|
427
|
+
function mergeTextElement(base, left, right) {
|
|
428
|
+
const tag = mergePrimitive(base.tag, left.tag, right.tag);
|
|
429
|
+
const id = mergePrimitive(base.id, left.id, right.id);
|
|
430
|
+
const metadata = mergePlainObject(base.metadata ?? {}, left.metadata ?? {}, right.metadata ?? {});
|
|
431
|
+
const attributes = mergePlainObject(base.attributes ?? {}, left.attributes ?? {}, right.attributes ?? {});
|
|
432
|
+
const baseText = base.text ?? "";
|
|
433
|
+
const leftText = left.text ?? "";
|
|
434
|
+
const rightText = right.text ?? "";
|
|
435
|
+
const text = mergeText3(baseText, leftText, rightText);
|
|
436
|
+
const baseMarker = base.textMarker ?? "";
|
|
437
|
+
const leftMarker = left.textMarker ?? "";
|
|
438
|
+
const rightMarker = right.textMarker ?? "";
|
|
439
|
+
const textMarker = mergeText3(baseMarker, leftMarker, rightMarker);
|
|
440
|
+
const out = {
|
|
441
|
+
kind: "TextElement",
|
|
442
|
+
tag,
|
|
443
|
+
id,
|
|
444
|
+
metadata,
|
|
445
|
+
attributes: Object.keys(attributes).length ? attributes : void 0,
|
|
446
|
+
text: text.length ? text : void 0,
|
|
447
|
+
textMarker: textMarker.length ? textMarker : void 0
|
|
448
|
+
};
|
|
449
|
+
const sid = readMetaId(left) ?? readMetaId(right) ?? readMetaId(base);
|
|
450
|
+
if (sid) setMetaId(out, sid);
|
|
451
|
+
return out;
|
|
452
|
+
}
|
|
453
|
+
function mergeDataElement(base, left, right) {
|
|
454
|
+
const tag = mergePrimitive(base.tag, left.tag, right.tag);
|
|
455
|
+
const id = mergePrimitive(base.id, left.id, right.id);
|
|
456
|
+
const metadata = mergePlainObject(base.metadata ?? {}, left.metadata ?? {}, right.metadata ?? {});
|
|
457
|
+
const attributes = mergePlainObject(base.attributes ?? {}, left.attributes ?? {}, right.attributes ?? {});
|
|
458
|
+
const body = mergeNodeList(base.body ?? [], left.body ?? [], right.body ?? []);
|
|
459
|
+
const extend = mergeExtend(base.extend, left.extend, right.extend);
|
|
460
|
+
const out = {
|
|
461
|
+
kind: "DataElement",
|
|
462
|
+
tag,
|
|
463
|
+
id,
|
|
464
|
+
metadata,
|
|
465
|
+
attributes: Object.keys(attributes).length ? attributes : void 0,
|
|
466
|
+
body: body.length ? body : void 0,
|
|
467
|
+
extend
|
|
468
|
+
};
|
|
469
|
+
const sid = readMetaId(left) ?? readMetaId(right) ?? readMetaId(base);
|
|
470
|
+
if (sid) setMetaId(out, sid);
|
|
471
|
+
return out;
|
|
472
|
+
}
|
|
473
|
+
function mergeAny(base, left, right, key) {
|
|
474
|
+
if (left === void 0 && right === void 0) return void 0;
|
|
475
|
+
if (left === void 0) return right;
|
|
476
|
+
if (right === void 0) return left;
|
|
477
|
+
if (left === right) return left;
|
|
478
|
+
if (base === void 0) {
|
|
479
|
+
if (isDataElement(left) && isDataElement(right)) {
|
|
480
|
+
return mergeDataElement(left, left, right);
|
|
481
|
+
}
|
|
482
|
+
if (isTextElement(left) && isTextElement(right)) {
|
|
483
|
+
return mergeTextElement(left, left, right);
|
|
484
|
+
}
|
|
485
|
+
if (Array.isArray(left) && Array.isArray(right)) {
|
|
486
|
+
return mergeNodeList([], left, right);
|
|
487
|
+
}
|
|
488
|
+
if (isPlainObject(left) && isPlainObject(right)) {
|
|
489
|
+
return mergePlainObject({}, left, right);
|
|
490
|
+
}
|
|
491
|
+
return right ;
|
|
492
|
+
}
|
|
493
|
+
if (typeof base === "string" || typeof base === "number" || typeof base === "boolean" || base === null) {
|
|
494
|
+
return mergePrimitive(base, left, right);
|
|
495
|
+
}
|
|
496
|
+
if (Array.isArray(base) && Array.isArray(left) && Array.isArray(right)) {
|
|
497
|
+
return mergeNodeList(base, left, right);
|
|
498
|
+
}
|
|
499
|
+
if (isPlainObject(base) && isPlainObject(left) && isPlainObject(right)) {
|
|
500
|
+
return mergePlainObject(base, left, right);
|
|
501
|
+
}
|
|
502
|
+
if (isTextElement(base) && isTextElement(left) && isTextElement(right)) {
|
|
503
|
+
return mergeTextElement(base, left, right);
|
|
504
|
+
}
|
|
505
|
+
if (isDataElement(base) && isDataElement(left) && isDataElement(right)) {
|
|
506
|
+
return mergeDataElement(base, left, right);
|
|
507
|
+
}
|
|
508
|
+
return mergePrimitive(base, left, right);
|
|
509
|
+
}
|
|
510
|
+
function mergeDocuments3(baseText, leftText, rightText) {
|
|
511
|
+
const base = xnlCore.parseXnl(baseText);
|
|
512
|
+
const left = xnlCore.parseXnl(leftText);
|
|
513
|
+
const right = xnlCore.parseXnl(rightText);
|
|
514
|
+
const mergedNodes = mergeRootNodes3(base.nodes, left.nodes, right.nodes);
|
|
515
|
+
ensureMetadataIds({ nodes: mergedNodes }, "m_");
|
|
516
|
+
const mergedText = xnlCore.XNL.stringify({ nodes: mergedNodes });
|
|
517
|
+
const canonical = xnlCore.parseXnl(mergedText);
|
|
518
|
+
assertAtMostOneRoot(canonical.nodes, "mergeDocuments3");
|
|
519
|
+
const canonicalText = xnlCore.XNL.stringify(canonical);
|
|
520
|
+
return { text: canonicalText, nodes: canonical.nodes };
|
|
521
|
+
}
|
|
522
|
+
function canonicalizeText(text, prefix) {
|
|
523
|
+
const parsed = xnlCore.parseXnl(text);
|
|
524
|
+
assertAtMostOneRoot(parsed.nodes, "canonicalizeText");
|
|
525
|
+
const nodes = cloneJson(parsed.nodes);
|
|
526
|
+
ensureMetadataIds({ nodes }, prefix);
|
|
527
|
+
const withIdsText = xnlCore.XNL.stringify({ nodes });
|
|
528
|
+
const canonical = xnlCore.parseXnl(withIdsText);
|
|
529
|
+
assertAtMostOneRoot(canonical.nodes, "canonicalizeText");
|
|
530
|
+
const canonicalText = xnlCore.XNL.stringify(canonical);
|
|
531
|
+
return { text: canonicalText, nodes: canonical.nodes };
|
|
532
|
+
}
|
|
533
|
+
function applyIntent(baseText, mutations) {
|
|
534
|
+
const base = canonicalizeText(baseText, "s_");
|
|
535
|
+
assertAtMostOneRoot(base.nodes, "applyIntent");
|
|
536
|
+
const baseRoot = base.nodes[0];
|
|
537
|
+
const baseTag = baseRoot ? readTag(baseRoot) : void 0;
|
|
538
|
+
const root = cloneJson(base.nodes);
|
|
539
|
+
const next = xnlCore.XNL.mutation.apply(root, mutations, { metadataIdMode: "identity" });
|
|
540
|
+
if (!Array.isArray(next)) {
|
|
541
|
+
throw new Error("Root must remain array");
|
|
542
|
+
}
|
|
543
|
+
assertAtMostOneRoot(next, "applyIntent");
|
|
544
|
+
if (baseTag) {
|
|
545
|
+
const nextRoot = next[0];
|
|
546
|
+
const nextTag = nextRoot ? readTag(nextRoot) : void 0;
|
|
547
|
+
if (!nextTag) {
|
|
548
|
+
throw new Error("applyIntent: root node required");
|
|
549
|
+
}
|
|
550
|
+
if (nextTag !== baseTag) {
|
|
551
|
+
throw new Error(`applyIntent: root tag locked to <${baseTag}>`);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
ensureMetadataIds({ nodes: next }, "s_");
|
|
555
|
+
const nextText = xnlCore.XNL.stringify({ nodes: next });
|
|
556
|
+
const canonical = xnlCore.parseXnl(nextText);
|
|
557
|
+
assertAtMostOneRoot(canonical.nodes, "applyIntent");
|
|
558
|
+
const canonicalText = xnlCore.XNL.stringify(canonical);
|
|
559
|
+
return { text: canonicalText, nodes: canonical.nodes };
|
|
560
|
+
}
|
|
561
|
+
function mergeExample(baseText, leftText, rightText) {
|
|
562
|
+
return mergeDocuments3(baseText, leftText, rightText).text;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
exports.applyIntent = applyIntent;
|
|
566
|
+
exports.canonicalizeText = canonicalizeText;
|
|
567
|
+
exports.ensureMetadataIds = ensureMetadataIds;
|
|
568
|
+
exports.makeId = makeId;
|
|
569
|
+
exports.mergeDocuments3 = mergeDocuments3;
|
|
570
|
+
exports.mergeExample = mergeExample;
|
|
571
|
+
exports.ulid = ulid;
|
|
572
|
+
//# sourceMappingURL=index.cjs.map
|
|
573
|
+
//# sourceMappingURL=index.cjs.map
|