@openpresentation/opf 0.11.4 → 0.12.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/README.md +16 -0
- package/dist/audit.d.ts +156 -0
- package/dist/audit.js +7 -0
- package/dist/{catalogs-CUClB3nl.d.ts → catalogs-BnI7JKcn.d.ts} +3 -3
- package/dist/catalogs.d.ts +2 -2
- package/dist/catalogs.js +2 -1
- package/dist/{chunk-KJKASEPE.js → chunk-4LAM3N6S.js} +753 -42
- package/dist/chunk-6LZK2IZB.js +1690 -0
- package/dist/{chunk-NSVVQ52O.js → chunk-AUM5MXBR.js} +8 -6
- package/dist/{chunk-AEUSVEUY.js → chunk-E34DJ5LL.js} +50 -14
- package/dist/chunk-FYLJVF7X.js +18448 -0
- package/dist/{chunk-HJL64ETN.js → chunk-GJKJL4YY.js} +14 -0
- package/dist/{chunk-3H2U7FDR.js → chunk-HOBSL7AJ.js} +1867 -247
- package/dist/chunk-JKVKYVWL.js +1332 -0
- package/dist/{chunk-3OJN7QAW.js → chunk-MWPMJE6P.js} +18 -4
- package/dist/chunk-RUC7L5DJ.js +261 -0
- package/dist/chunk-S5W34SIJ.js +1 -0
- package/dist/chunk-V2537ENL.js +198 -0
- package/dist/{chunk-RXNFDGPC.js → chunk-WXSPETQ6.js} +311 -40
- package/dist/{composition-Dl-d9FRj.d.ts → composition-DWbCiMwF.d.ts} +559 -10
- package/dist/composition.d.ts +1 -1
- package/dist/composition.js +2 -1
- package/dist/convert.d.ts +261 -0
- package/dist/convert.js +1107 -0
- package/dist/diff.d.ts +145 -0
- package/dist/diff.js +604 -0
- package/dist/docs.js +72 -18
- package/dist/examples.d.ts +1 -1
- package/dist/examples.js +112 -112
- package/dist/font-policy.d.ts +15 -0
- package/dist/font-policy.js +1 -1
- package/dist/format.d.ts +20 -0
- package/dist/format.js +83 -0
- package/dist/index.d.ts +173 -24
- package/dist/index.js +926 -262
- package/dist/lint.d.ts +4 -4
- package/dist/lint.js +6 -5
- package/dist/markdown.d.ts +74 -0
- package/dist/markdown.js +1941 -0
- package/dist/pagination.d.ts +1 -1
- package/dist/pagination.js +6 -5
- package/dist/patch.d.ts +99 -0
- package/dist/patch.js +6 -0
- package/dist/{presentation-j5PVrqfV.d.ts → presentation-DcF-MZcz.d.ts} +452 -17
- package/dist/repo-readme.js +1 -1
- package/dist/{schemas-X_NniU4A.d.ts → schemas-BSjlG8-R.d.ts} +28 -0
- package/dist/schemas.d.ts +1 -1
- package/dist/schemas.js +1 -1
- package/dist/spec/catalogs/audiences/customers.json +5 -1
- package/dist/spec/catalogs/audiences/executives.json +5 -1
- package/dist/spec/catalogs/audiences/index.json +20 -8
- package/dist/spec/catalogs/audiences/investors.json +5 -1
- package/dist/spec/catalogs/audiences/marketing-team.json +5 -1
- package/dist/spec/catalogs/audiences/regulators.json +5 -1
- package/dist/spec/catalogs/audiences/sales-team.json +5 -1
- package/dist/spec/catalogs/manifest.json +2 -2
- package/dist/spec/reference/engine-defaults.json +1 -1
- package/dist/spec/reference/font-policy.json +311 -40
- package/dist/spec/reference/font-policy.schema.json +40 -0
- package/dist/spec/reference/symbol-font-encodings.json +18354 -0
- package/dist/spec/reference/symbol-font-encodings.schema.json +156 -0
- package/dist/spec/schemas/audience.schema.json +7 -7
- package/dist/spec/schemas/narrative.schema.json +1 -1
- package/dist/spec/schemas/opf.schema.json +597 -28
- package/dist/spec-files.d.ts +1 -1
- package/dist/spec-files.js +1 -1
- package/dist/symbol-font-encodings.d.ts +128 -0
- package/dist/symbol-font-encodings.js +1 -0
- package/dist/types.d.ts +4 -4
- package/dist/validator-DfG9yxba.d.ts +172 -0
- package/dist/validator.d.ts +4 -36
- package/dist/validator.js +5 -4
- package/package.json +32 -3
- package/dist/chunk-FDCWQHWO.js +0 -1366
package/dist/diff.js
ADDED
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
import { jsonEqual, formatPointer } from './chunk-RUC7L5DJ.js';
|
|
2
|
+
import './chunk-JKVKYVWL.js';
|
|
3
|
+
import './chunk-HOBSL7AJ.js';
|
|
4
|
+
import './chunk-S5W34SIJ.js';
|
|
5
|
+
import './chunk-E34DJ5LL.js';
|
|
6
|
+
import './chunk-4LAM3N6S.js';
|
|
7
|
+
|
|
8
|
+
// src/diff-match.ts
|
|
9
|
+
var DEFAULT_MATCH_THRESHOLD = 0.5;
|
|
10
|
+
var DIFFERENT_ID_THRESHOLD = 0.8;
|
|
11
|
+
var MAX_SIMILARITY_PAIRS = 25e4;
|
|
12
|
+
var MAX_LCS_CELLS = 4e6;
|
|
13
|
+
var isObject = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
14
|
+
var isComposite = (value) => !!value && typeof value === "object";
|
|
15
|
+
var idOf = (value) => isObject(value) && typeof value.id === "string" && value.id !== "" ? value.id : void 0;
|
|
16
|
+
var canonCache = /* @__PURE__ */ new WeakMap();
|
|
17
|
+
function canonical(value) {
|
|
18
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "undefined";
|
|
19
|
+
const cached = canonCache.get(value);
|
|
20
|
+
if (cached !== void 0) return cached;
|
|
21
|
+
const text = Array.isArray(value) ? `[${value.map(canonical).join(",")}]` : `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}`;
|
|
22
|
+
canonCache.set(value, text);
|
|
23
|
+
return text;
|
|
24
|
+
}
|
|
25
|
+
var leafCache = /* @__PURE__ */ new WeakMap();
|
|
26
|
+
function leaves(value) {
|
|
27
|
+
if (!isComposite(value)) return /* @__PURE__ */ new Map();
|
|
28
|
+
const cached = leafCache.get(value);
|
|
29
|
+
if (cached) return cached;
|
|
30
|
+
const out = /* @__PURE__ */ new Map();
|
|
31
|
+
const walk = (node, path) => {
|
|
32
|
+
if (Array.isArray(node)) {
|
|
33
|
+
for (const [i, item] of node.entries()) walk(item, `${path}/${i}`);
|
|
34
|
+
} else if (node && typeof node === "object") for (const key of Object.keys(node)) walk(node[key], `${path}/${key}`);
|
|
35
|
+
else out.set(path, node);
|
|
36
|
+
};
|
|
37
|
+
walk(value, "");
|
|
38
|
+
leafCache.set(value, out);
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
var words = (text) => new Set(text.toLowerCase().split(/\s+/u).filter(Boolean));
|
|
42
|
+
function wordOverlap(a, b) {
|
|
43
|
+
const left = words(a), right = words(b);
|
|
44
|
+
if (!left.size || !right.size) return 0;
|
|
45
|
+
let shared = 0;
|
|
46
|
+
for (const word of left) if (right.has(word)) shared++;
|
|
47
|
+
return shared / (left.size + right.size - shared);
|
|
48
|
+
}
|
|
49
|
+
function similarity(a, b) {
|
|
50
|
+
if (canonical(a) === canonical(b)) return 1;
|
|
51
|
+
const left = leaves(a), right = leaves(b);
|
|
52
|
+
const size = Math.max(left.size, right.size);
|
|
53
|
+
if (!size) return 0;
|
|
54
|
+
let score = 0;
|
|
55
|
+
for (const [path, value] of left) {
|
|
56
|
+
if (!right.has(path)) continue;
|
|
57
|
+
const other = right.get(path);
|
|
58
|
+
if (value === other) score += 1;
|
|
59
|
+
else if (typeof value === "string" && typeof other === "string") score += 0.5 * wordOverlap(value, other);
|
|
60
|
+
}
|
|
61
|
+
return score / size;
|
|
62
|
+
}
|
|
63
|
+
function longestIncreasing(values) {
|
|
64
|
+
const tails = [], previous = new Array(values.length).fill(-1);
|
|
65
|
+
for (let i = 0; i < values.length; i++) {
|
|
66
|
+
let lo = 0, hi = tails.length;
|
|
67
|
+
while (lo < hi) {
|
|
68
|
+
const mid = lo + hi >> 1;
|
|
69
|
+
if (values[tails[mid]] < values[i]) lo = mid + 1;
|
|
70
|
+
else hi = mid;
|
|
71
|
+
}
|
|
72
|
+
if (lo > 0) previous[i] = tails[lo - 1];
|
|
73
|
+
tails[lo] = i;
|
|
74
|
+
}
|
|
75
|
+
const chain = [];
|
|
76
|
+
for (let at = tails.length ? tails[tails.length - 1] : -1; at >= 0; at = previous[at]) chain.push(at);
|
|
77
|
+
return chain.reverse();
|
|
78
|
+
}
|
|
79
|
+
function lcsPairs(keysA, keysB) {
|
|
80
|
+
const n = keysA.length, m = keysB.length;
|
|
81
|
+
if (!n || !m) return [];
|
|
82
|
+
if (n * m > MAX_LCS_CELLS) {
|
|
83
|
+
const pairs2 = [];
|
|
84
|
+
let head = 0;
|
|
85
|
+
while (head < n && head < m && keysA[head] === keysB[head]) {
|
|
86
|
+
pairs2.push([head, head]);
|
|
87
|
+
head++;
|
|
88
|
+
}
|
|
89
|
+
let tail = 0;
|
|
90
|
+
while (tail < n - head && tail < m - head && keysA[n - 1 - tail] === keysB[m - 1 - tail]) tail++;
|
|
91
|
+
for (let k = tail; k > 0; k--) pairs2.push([n - k, m - k]);
|
|
92
|
+
return pairs2;
|
|
93
|
+
}
|
|
94
|
+
const width = m + 1, table = new Uint32Array((n + 1) * width);
|
|
95
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
96
|
+
for (let j = m - 1; j >= 0; j--) {
|
|
97
|
+
table[i * width + j] = keysA[i] === keysB[j] ? table[(i + 1) * width + j + 1] + 1 : Math.max(table[(i + 1) * width + j], table[i * width + j + 1]);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const pairs = [];
|
|
101
|
+
for (let i = 0, j = 0; i < n && j < m; ) {
|
|
102
|
+
if (keysA[i] === keysB[j]) {
|
|
103
|
+
pairs.push([i, j]);
|
|
104
|
+
i++;
|
|
105
|
+
j++;
|
|
106
|
+
} else if (table[(i + 1) * width + j] >= table[i * width + j + 1]) i++;
|
|
107
|
+
else j++;
|
|
108
|
+
}
|
|
109
|
+
return pairs;
|
|
110
|
+
}
|
|
111
|
+
function matchArrays(a, b, options = {}) {
|
|
112
|
+
const threshold = options.threshold ?? DEFAULT_MATCH_THRESHOLD;
|
|
113
|
+
const matchA = new Array(a.length).fill(-1), matchB = new Array(b.length).fill(-1);
|
|
114
|
+
const pairs = [];
|
|
115
|
+
const link = (i, j, by, score) => {
|
|
116
|
+
matchA[i] = j;
|
|
117
|
+
matchB[j] = i;
|
|
118
|
+
pairs.push(score === void 0 ? { a: i, b: j, by } : { a: i, b: j, by, similarity: score });
|
|
119
|
+
};
|
|
120
|
+
const free = (side) => side.map((value, index) => value < 0 ? index : -1).filter((index) => index >= 0);
|
|
121
|
+
const idsB = /* @__PURE__ */ new Map();
|
|
122
|
+
b.forEach((item, j) => {
|
|
123
|
+
const id = idOf(item);
|
|
124
|
+
if (id !== void 0) (idsB.get(id) ?? idsB.set(id, []).get(id)).push(j);
|
|
125
|
+
});
|
|
126
|
+
const used = /* @__PURE__ */ new Map();
|
|
127
|
+
a.forEach((item, i) => {
|
|
128
|
+
const id = idOf(item);
|
|
129
|
+
if (id === void 0) return;
|
|
130
|
+
const list = idsB.get(id), at = used.get(id) ?? 0;
|
|
131
|
+
if (list && at < list.length) {
|
|
132
|
+
link(i, list[at], "id");
|
|
133
|
+
used.set(id, at + 1);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
let freeA = free(matchA), freeB = free(matchB);
|
|
137
|
+
const keysA = freeA.map((i) => canonical(a[i])), keysB = freeB.map((j) => canonical(b[j]));
|
|
138
|
+
for (const [x, y] of lcsPairs(keysA, keysB)) link(freeA[x], freeB[y], "equal");
|
|
139
|
+
freeA = free(matchA);
|
|
140
|
+
freeB = free(matchB);
|
|
141
|
+
const pool = /* @__PURE__ */ new Map();
|
|
142
|
+
freeB.forEach((j) => {
|
|
143
|
+
const key = canonical(b[j]);
|
|
144
|
+
(pool.get(key) ?? pool.set(key, []).get(key)).push(j);
|
|
145
|
+
});
|
|
146
|
+
for (const i of freeA) {
|
|
147
|
+
const list = pool.get(canonical(a[i]));
|
|
148
|
+
if (list?.length) link(i, list.shift(), "equal");
|
|
149
|
+
}
|
|
150
|
+
freeA = free(matchA).filter((i) => isComposite(a[i]));
|
|
151
|
+
freeB = free(matchB).filter((j) => isComposite(b[j]));
|
|
152
|
+
if (freeA.length && freeB.length && freeA.length * freeB.length <= MAX_SIMILARITY_PAIRS) {
|
|
153
|
+
const scored = [];
|
|
154
|
+
for (const i of freeA) for (const j of freeB) {
|
|
155
|
+
const idA = idOf(a[i]), idB = idOf(b[j]);
|
|
156
|
+
const needed = idA !== void 0 && idB !== void 0 && idA !== idB ? Math.max(threshold, DIFFERENT_ID_THRESHOLD) : threshold;
|
|
157
|
+
const score = similarity(a[i], b[j]);
|
|
158
|
+
if (score >= needed && score > 0) scored.push({ i, j, score });
|
|
159
|
+
}
|
|
160
|
+
scored.sort((x, y) => y.score - x.score || x.i - y.i || x.j - y.j);
|
|
161
|
+
for (const { i, j, score } of scored) if (matchA[i] < 0 && matchB[j] < 0) link(i, j, "similar", Math.round(score * 1e3) / 1e3);
|
|
162
|
+
}
|
|
163
|
+
pairs.sort((x, y) => x.a - y.a);
|
|
164
|
+
const chain = longestIncreasing(pairs.map((pair) => pair.b)).map((index) => pairs[index]);
|
|
165
|
+
const anchors = [[-1, -1], ...chain.map((pair) => [pair.a, pair.b]), [a.length, b.length]];
|
|
166
|
+
for (let k = 0; k + 1 < anchors.length; k++) {
|
|
167
|
+
const [a0, b0] = anchors[k], [a1, b1] = anchors[k + 1];
|
|
168
|
+
const gapA = [], gapB = [];
|
|
169
|
+
for (let i = a0 + 1; i < a1; i++) if (matchA[i] < 0 && !isComposite(a[i])) gapA.push(i);
|
|
170
|
+
for (let j = b0 + 1; j < b1; j++) if (matchB[j] < 0 && !isComposite(b[j])) gapB.push(j);
|
|
171
|
+
for (let n = 0; n < Math.min(gapA.length, gapB.length); n++) link(gapA[n], gapB[n], "position");
|
|
172
|
+
}
|
|
173
|
+
pairs.sort((x, y) => x.a - y.a);
|
|
174
|
+
const inChain = new Set(longestIncreasing(pairs.map((pair) => pair.b)));
|
|
175
|
+
const moved = /* @__PURE__ */ new Set();
|
|
176
|
+
pairs.forEach((pair, index) => {
|
|
177
|
+
if (!inChain.has(index)) moved.add(pair.a);
|
|
178
|
+
});
|
|
179
|
+
return { pairs, removed: free(matchA), added: free(matchB), moved };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/merge.ts
|
|
183
|
+
var ABSENT = /* @__PURE__ */ Symbol("absent");
|
|
184
|
+
var isObject2 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
185
|
+
var has = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
|
|
186
|
+
var same = (x, y) => x === ABSENT || y === ABSENT ? x === y : jsonEqual(x, y);
|
|
187
|
+
var MESSAGES = {
|
|
188
|
+
"modify-modify": "Both sides changed this value differently.",
|
|
189
|
+
"modify-delete": "We changed this value; they deleted it.",
|
|
190
|
+
"delete-modify": "We deleted this value; they changed it.",
|
|
191
|
+
"add-add": "Both sides added different values here.",
|
|
192
|
+
"move-move": "Both sides moved this element to different positions."
|
|
193
|
+
};
|
|
194
|
+
function conflict(ctx, kind, path, base, ours, theirs) {
|
|
195
|
+
const entry = { kind, path: formatPointer(path), resolution: ctx.prefer, message: MESSAGES[kind] };
|
|
196
|
+
if (base !== ABSENT) entry.base = structuredClone(base);
|
|
197
|
+
if (ours !== ABSENT) entry.ours = structuredClone(ours);
|
|
198
|
+
if (theirs !== ABSENT) entry.theirs = structuredClone(theirs);
|
|
199
|
+
if (kind === "modify-delete") entry.deletedBy = "theirs";
|
|
200
|
+
if (kind === "delete-modify") entry.deletedBy = "ours";
|
|
201
|
+
if (ctx.slide) entry.slide = { ...ctx.slide };
|
|
202
|
+
ctx.conflicts.push(entry);
|
|
203
|
+
return ctx.prefer === "ours" ? ours : theirs;
|
|
204
|
+
}
|
|
205
|
+
function mergeValue(base, ours, theirs, path, ctx) {
|
|
206
|
+
if (same(ours, theirs)) {
|
|
207
|
+
if (!same(ours, base)) ctx.applied.both++;
|
|
208
|
+
return ours;
|
|
209
|
+
}
|
|
210
|
+
if (same(ours, base)) {
|
|
211
|
+
ctx.applied.theirs++;
|
|
212
|
+
return theirs;
|
|
213
|
+
}
|
|
214
|
+
if (same(theirs, base)) {
|
|
215
|
+
ctx.applied.ours++;
|
|
216
|
+
return ours;
|
|
217
|
+
}
|
|
218
|
+
if (base !== ABSENT) {
|
|
219
|
+
if (ours === ABSENT) return conflict(ctx, "delete-modify", path, base, ours, theirs);
|
|
220
|
+
if (theirs === ABSENT) return conflict(ctx, "modify-delete", path, base, ours, theirs);
|
|
221
|
+
if (isObject2(base) && isObject2(ours) && isObject2(theirs)) return mergeObject(base, ours, theirs, path, ctx);
|
|
222
|
+
if (Array.isArray(base) && Array.isArray(ours) && Array.isArray(theirs)) return mergeArray(base, ours, theirs, path, ctx);
|
|
223
|
+
return conflict(ctx, "modify-modify", path, base, ours, theirs);
|
|
224
|
+
}
|
|
225
|
+
if (isObject2(ours) && isObject2(theirs)) return mergeObject({}, ours, theirs, path, ctx);
|
|
226
|
+
if (Array.isArray(ours) && Array.isArray(theirs)) return mergeArray([], ours, theirs, path, ctx);
|
|
227
|
+
return conflict(ctx, "add-add", path, base, ours, theirs);
|
|
228
|
+
}
|
|
229
|
+
function mergeObject(base, ours, theirs, path, ctx) {
|
|
230
|
+
const keys = [];
|
|
231
|
+
const seen = /* @__PURE__ */ new Set();
|
|
232
|
+
for (const source of [base, ours, theirs]) for (const key of Object.keys(source)) if (!seen.has(key)) {
|
|
233
|
+
seen.add(key);
|
|
234
|
+
keys.push(key);
|
|
235
|
+
}
|
|
236
|
+
const out = {};
|
|
237
|
+
for (const key of keys) {
|
|
238
|
+
const value = mergeValue(has(base, key) ? base[key] : ABSENT, has(ours, key) ? ours[key] : ABSENT, has(theirs, key) ? theirs[key] : ABSENT, [...path, key], ctx);
|
|
239
|
+
if (value !== ABSENT) Object.defineProperty(out, key, { value, enumerable: true, configurable: true, writable: true });
|
|
240
|
+
}
|
|
241
|
+
return out;
|
|
242
|
+
}
|
|
243
|
+
function mergeArray(base, ours, theirs, path, ctx) {
|
|
244
|
+
const mo = matchArrays(base, ours, ctx.match), mt = matchArrays(base, theirs, ctx.match);
|
|
245
|
+
const oOf = new Array(base.length).fill(-1), tOf = new Array(base.length).fill(-1);
|
|
246
|
+
const baseOfO = new Array(ours.length).fill(-1), baseOfT = new Array(theirs.length).fill(-1);
|
|
247
|
+
for (const pair of mo.pairs) {
|
|
248
|
+
oOf[pair.a] = pair.b;
|
|
249
|
+
baseOfO[pair.b] = pair.a;
|
|
250
|
+
}
|
|
251
|
+
for (const pair of mt.pairs) {
|
|
252
|
+
tOf[pair.a] = pair.b;
|
|
253
|
+
baseOfT[pair.b] = pair.a;
|
|
254
|
+
}
|
|
255
|
+
const spine = ours.map((_, o) => {
|
|
256
|
+
const b = baseOfO[o];
|
|
257
|
+
return { b, o, t: b >= 0 ? tOf[b] : -1 };
|
|
258
|
+
});
|
|
259
|
+
const oursAddedOnly = (node) => !!node && node.b < 0 && node.o >= 0 && node.t < 0;
|
|
260
|
+
const tNode = /* @__PURE__ */ new Map();
|
|
261
|
+
for (const node of spine) if (node.t >= 0) tNode.set(node.t, node);
|
|
262
|
+
const insertAfterPredecessor = (ti, node) => {
|
|
263
|
+
let anchor;
|
|
264
|
+
for (let p = ti - 1; p >= 0 && !anchor; p--) anchor = tNode.get(p);
|
|
265
|
+
let at = anchor ? spine.indexOf(anchor) + 1 : 0;
|
|
266
|
+
while (oursAddedOnly(spine[at])) at++;
|
|
267
|
+
spine.splice(at, 0, node);
|
|
268
|
+
};
|
|
269
|
+
for (let ti = 0; ti < theirs.length; ti++) {
|
|
270
|
+
const bi = baseOfT[ti];
|
|
271
|
+
if (bi >= 0) {
|
|
272
|
+
if (oOf[bi] >= 0) continue;
|
|
273
|
+
if (same(base[bi], theirs[ti])) {
|
|
274
|
+
ctx.applied.ours++;
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
const node2 = { b: bi, o: -1, t: ti };
|
|
278
|
+
tNode.set(ti, node2);
|
|
279
|
+
insertAfterPredecessor(ti, node2);
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
const id = idOf(theirs[ti]);
|
|
283
|
+
let twin = id !== void 0 ? spine.find((node2) => oursAddedOnly(node2) && idOf(ours[node2.o]) === id) : void 0;
|
|
284
|
+
if (!twin) {
|
|
285
|
+
let anchor;
|
|
286
|
+
for (let p = ti - 1; p >= 0 && !anchor; p--) anchor = tNode.get(p);
|
|
287
|
+
for (let at = anchor ? spine.indexOf(anchor) + 1 : 0; oursAddedOnly(spine[at]); at++) {
|
|
288
|
+
if (jsonEqual(ours[spine[at].o], theirs[ti])) {
|
|
289
|
+
twin = spine[at];
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (twin) {
|
|
295
|
+
twin.t = ti;
|
|
296
|
+
tNode.set(ti, twin);
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
const node = { b: -1, o: -1, t: ti };
|
|
300
|
+
tNode.set(ti, node);
|
|
301
|
+
insertAfterPredecessor(ti, node);
|
|
302
|
+
}
|
|
303
|
+
const basePredecessor = (list, index) => {
|
|
304
|
+
for (let p = index - 1; p >= 0; p--) if (list[p] >= 0) return list[p];
|
|
305
|
+
return -1;
|
|
306
|
+
};
|
|
307
|
+
const movedByTheirs = mt.pairs.filter((pair) => mt.moved.has(pair.a)).sort((x, y) => x.b - y.b);
|
|
308
|
+
for (const pair of movedByTheirs) {
|
|
309
|
+
const node = spine.find((candidate) => candidate.b === pair.a && candidate.o >= 0 && candidate.t === pair.b);
|
|
310
|
+
if (!node) continue;
|
|
311
|
+
if (mo.moved.has(pair.a)) {
|
|
312
|
+
if (basePredecessor(baseOfO, node.o) === basePredecessor(baseOfT, pair.b)) continue;
|
|
313
|
+
node.moveConflict = true;
|
|
314
|
+
if (ctx.prefer === "ours") continue;
|
|
315
|
+
}
|
|
316
|
+
spine.splice(spine.indexOf(node), 1);
|
|
317
|
+
insertAfterPredecessor(pair.b, node);
|
|
318
|
+
}
|
|
319
|
+
const out = [];
|
|
320
|
+
const isSlides = path.length === 1 && path[0] === "slides";
|
|
321
|
+
for (const node of spine) {
|
|
322
|
+
const at = [...path, out.length];
|
|
323
|
+
const savedSlide = ctx.slide;
|
|
324
|
+
if (isSlides) {
|
|
325
|
+
const sample = node.o >= 0 ? ours[node.o] : node.t >= 0 ? theirs[node.t] : base[node.b];
|
|
326
|
+
ctx.slide = { index: out.length, ...idOf(sample) !== void 0 ? { id: idOf(sample) } : {}, ...isObject2(sample) && typeof sample.title === "string" ? { title: sample.title } : {} };
|
|
327
|
+
}
|
|
328
|
+
try {
|
|
329
|
+
const b = node.b >= 0 ? base[node.b] : ABSENT, o = node.o >= 0 ? ours[node.o] : ABSENT, t = node.t >= 0 ? theirs[node.t] : ABSENT;
|
|
330
|
+
if (node.b >= 0) {
|
|
331
|
+
if (o !== ABSENT && t !== ABSENT) {
|
|
332
|
+
const merged = mergeValue(b, o, t, at, ctx);
|
|
333
|
+
if (node.moveConflict) {
|
|
334
|
+
ctx.conflicts.push({ kind: "move-move", path: formatPointer(at), base: structuredClone(b), resolution: ctx.prefer, message: MESSAGES["move-move"], ...ctx.slide ? { slide: { ...ctx.slide } } : {} });
|
|
335
|
+
}
|
|
336
|
+
out.push(merged);
|
|
337
|
+
} else if (o !== ABSENT) {
|
|
338
|
+
if (same(b, o)) ctx.applied.theirs++;
|
|
339
|
+
else {
|
|
340
|
+
const kept = conflict(ctx, "modify-delete", at, b, o, ABSENT);
|
|
341
|
+
if (kept !== ABSENT) out.push(kept);
|
|
342
|
+
}
|
|
343
|
+
} else if (t !== ABSENT) {
|
|
344
|
+
const kept = conflict(ctx, "delete-modify", at, b, ABSENT, t);
|
|
345
|
+
if (kept !== ABSENT) out.push(kept);
|
|
346
|
+
}
|
|
347
|
+
} else if (o !== ABSENT && t !== ABSENT) {
|
|
348
|
+
out.push(mergeValue(ABSENT, o, t, at, ctx));
|
|
349
|
+
} else if (o !== ABSENT) {
|
|
350
|
+
ctx.applied.ours++;
|
|
351
|
+
out.push(o);
|
|
352
|
+
} else {
|
|
353
|
+
ctx.applied.theirs++;
|
|
354
|
+
out.push(t);
|
|
355
|
+
}
|
|
356
|
+
} finally {
|
|
357
|
+
ctx.slide = savedSlide;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return out;
|
|
361
|
+
}
|
|
362
|
+
function mergePresentations(base, ours, theirs, options = {}) {
|
|
363
|
+
const ctx = {
|
|
364
|
+
prefer: options.prefer === "theirs" ? "theirs" : "ours",
|
|
365
|
+
match: { threshold: options.threshold ?? DEFAULT_MATCH_THRESHOLD },
|
|
366
|
+
conflicts: [],
|
|
367
|
+
applied: { ours: 0, theirs: 0, both: 0 }
|
|
368
|
+
};
|
|
369
|
+
const merged = mergeValue(structuredClone(base), structuredClone(ours), structuredClone(theirs), [], ctx);
|
|
370
|
+
return { merged: merged === ABSENT ? void 0 : merged, conflicts: ctx.conflicts, clean: ctx.conflicts.length === 0, applied: ctx.applied };
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// src/diff.ts
|
|
374
|
+
var isObject3 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
375
|
+
var REGION_KEY = /^(top|middle|bottom|left|center|right)([:+].*)?$/u;
|
|
376
|
+
var ROOT_CATEGORIES = { design: "design", assets: "assets", catalogs: "catalogs", variables: "variables", narrative: "narrative", extensions: "extensions" };
|
|
377
|
+
function categoryOf(tokens) {
|
|
378
|
+
const [root] = tokens;
|
|
379
|
+
if (root === void 0) return "metadata";
|
|
380
|
+
if (root === "slides") {
|
|
381
|
+
if (tokens.length <= 2) return "slide";
|
|
382
|
+
if (tokens[2] === "design") return "design";
|
|
383
|
+
const last = tokens[tokens.length - 1] ?? "", parent = tokens[tokens.length - 2];
|
|
384
|
+
if (parent === "blocks" && /^\d+$/u.test(last)) return "block";
|
|
385
|
+
if (REGION_KEY.test(last)) return "block";
|
|
386
|
+
return "field";
|
|
387
|
+
}
|
|
388
|
+
return ROOT_CATEGORIES[root] ?? "metadata";
|
|
389
|
+
}
|
|
390
|
+
var pointer = (tokens) => formatPointer(tokens);
|
|
391
|
+
var titleOf = (slide) => isObject3(slide) && typeof slide.title === "string" ? slide.title : void 0;
|
|
392
|
+
var slideRef = (slide, aIndex, bIndex) => {
|
|
393
|
+
const ref = {};
|
|
394
|
+
const id = idOf(slide), title = titleOf(slide);
|
|
395
|
+
if (id !== void 0) ref.id = id;
|
|
396
|
+
if (title !== void 0) ref.title = title;
|
|
397
|
+
if (aIndex !== void 0) ref.aIndex = aIndex;
|
|
398
|
+
if (bIndex !== void 0) ref.bIndex = bIndex;
|
|
399
|
+
return ref;
|
|
400
|
+
};
|
|
401
|
+
function record(ctx, change) {
|
|
402
|
+
if (ctx.slide) change.slide = { ...ctx.slide };
|
|
403
|
+
ctx.changes.push(change);
|
|
404
|
+
}
|
|
405
|
+
function diffValue(a, b, aPath, bPath, ctx) {
|
|
406
|
+
if (jsonEqual(a, b)) return;
|
|
407
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
408
|
+
diffArray(a, b, aPath, bPath, ctx);
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
if (isObject3(a) && isObject3(b)) {
|
|
412
|
+
diffObject(a, b, aPath, bPath, ctx);
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
ctx.patch.push({ op: "replace", path: pointer(bPath), value: structuredClone(b) });
|
|
416
|
+
record(ctx, { type: "changed", category: categoryOf(bPath.map(String)), aPath: pointer(aPath), bPath: pointer(bPath), before: structuredClone(a), after: structuredClone(b) });
|
|
417
|
+
}
|
|
418
|
+
function diffObject(a, b, aPath, bPath, ctx) {
|
|
419
|
+
const own = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
|
|
420
|
+
for (const key of Object.keys(a)) {
|
|
421
|
+
if (own(b, key)) continue;
|
|
422
|
+
ctx.patch.push({ op: "remove", path: pointer([...bPath, key]) });
|
|
423
|
+
record(ctx, { type: "removed", category: categoryOf([...aPath, key].map(String)), aPath: pointer([...aPath, key]), before: structuredClone(a[key]) });
|
|
424
|
+
}
|
|
425
|
+
for (const key of Object.keys(b)) {
|
|
426
|
+
if (!own(a, key)) {
|
|
427
|
+
ctx.patch.push({ op: "add", path: pointer([...bPath, key]), value: structuredClone(b[key]) });
|
|
428
|
+
record(ctx, { type: "added", category: categoryOf([...bPath, key].map(String)), bPath: pointer([...bPath, key]), after: structuredClone(b[key]) });
|
|
429
|
+
} else diffValue(a[key], b[key], [...aPath, key], [...bPath, key], ctx);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
function diffArray(a, b, aPath, bPath, ctx) {
|
|
433
|
+
const match = matchArrays(a, b, ctx.options);
|
|
434
|
+
const isSlides = aPath.length === 1 && aPath[0] === "slides" && bPath.length === 1 && bPath[0] === "slides";
|
|
435
|
+
const category = (path) => categoryOf(path.map(String));
|
|
436
|
+
if (isSlides) {
|
|
437
|
+
const changedPairs = /* @__PURE__ */ new Set();
|
|
438
|
+
for (const pair of match.pairs) if (!jsonEqual(a[pair.a], b[pair.b])) changedPairs.add(pair.a);
|
|
439
|
+
const infos = [];
|
|
440
|
+
for (const pair of match.pairs) {
|
|
441
|
+
const moved = match.moved.has(pair.a), changed = changedPairs.has(pair.a);
|
|
442
|
+
infos.push({ ...slideRef(b[pair.b], pair.a, pair.b), matchedBy: pair.by, ...pair.similarity !== void 0 ? { similarity: pair.similarity } : {}, status: moved ? changed ? "moved-modified" : "moved" : changed ? "modified" : "unchanged" });
|
|
443
|
+
}
|
|
444
|
+
for (const i of match.removed) infos.push({ ...slideRef(a[i], i), status: "removed" });
|
|
445
|
+
for (const j of match.added) infos.push({ ...slideRef(b[j], void 0, j), status: "added" });
|
|
446
|
+
infos.sort((x, y) => (x.bIndex ?? Infinity) - (y.bIndex ?? Infinity) || (x.aIndex ?? 0) - (y.aIndex ?? 0));
|
|
447
|
+
ctx.slides.push(...infos);
|
|
448
|
+
}
|
|
449
|
+
const withSlide = (fn, aIndex, bIndex) => {
|
|
450
|
+
if (!isSlides) return fn();
|
|
451
|
+
const saved = ctx.slide;
|
|
452
|
+
ctx.slide = slideRef(bIndex !== void 0 ? b[bIndex] : a[aIndex], aIndex, bIndex);
|
|
453
|
+
try {
|
|
454
|
+
fn();
|
|
455
|
+
} finally {
|
|
456
|
+
ctx.slide = saved;
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
for (const i of [...match.removed].reverse()) ctx.patch.push({ op: "remove", path: pointer([...bPath, i]) });
|
|
460
|
+
for (const i of match.removed) withSlide(() => record(ctx, { type: "removed", category: category([...aPath, i]), aPath: pointer([...aPath, i]), before: structuredClone(a[i]) }), i, void 0);
|
|
461
|
+
const order = match.pairs.slice().sort((x, y) => x.b - y.b);
|
|
462
|
+
const current = match.pairs.map((pair) => pair.a);
|
|
463
|
+
order.forEach((pair, t) => {
|
|
464
|
+
if (!match.moved.has(pair.a)) return;
|
|
465
|
+
const from = current.indexOf(pair.a);
|
|
466
|
+
current.splice(from, 1);
|
|
467
|
+
const to = t === 0 ? 0 : current.indexOf(order[t - 1].a) + 1;
|
|
468
|
+
current.splice(to, 0, pair.a);
|
|
469
|
+
if (from !== to) ctx.patch.push({ op: "move", from: pointer([...bPath, from]), path: pointer([...bPath, to]) });
|
|
470
|
+
withSlide(() => record(ctx, { type: "moved", category: category([...bPath, pair.b]), aPath: pointer([...aPath, pair.a]), bPath: pointer([...bPath, pair.b]) }), pair.a, pair.b);
|
|
471
|
+
});
|
|
472
|
+
for (const j of match.added) {
|
|
473
|
+
ctx.patch.push({ op: "add", path: pointer([...bPath, j]), value: structuredClone(b[j]) });
|
|
474
|
+
withSlide(() => record(ctx, { type: "added", category: category([...bPath, j]), bPath: pointer([...bPath, j]), after: structuredClone(b[j]) }), void 0, j);
|
|
475
|
+
}
|
|
476
|
+
for (const pair of order) {
|
|
477
|
+
withSlide(() => diffValue(a[pair.a], b[pair.b], [...aPath, pair.a], [...bPath, pair.b], ctx), pair.a, pair.b);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
function summarise(changes, slides) {
|
|
481
|
+
const summary = { total: changes.length, byCategory: {}, byType: {}, slides: { added: 0, removed: 0, moved: 0, modified: 0, unchanged: 0 } };
|
|
482
|
+
for (const change of changes) {
|
|
483
|
+
summary.byCategory[change.category] = (summary.byCategory[change.category] ?? 0) + 1;
|
|
484
|
+
summary.byType[change.type] = (summary.byType[change.type] ?? 0) + 1;
|
|
485
|
+
}
|
|
486
|
+
for (const slide of slides) {
|
|
487
|
+
if (slide.status === "added") summary.slides.added++;
|
|
488
|
+
else if (slide.status === "removed") summary.slides.removed++;
|
|
489
|
+
else if (slide.status === "unchanged") summary.slides.unchanged++;
|
|
490
|
+
else {
|
|
491
|
+
if (slide.status === "moved" || slide.status === "moved-modified") summary.slides.moved++;
|
|
492
|
+
if (slide.status === "modified" || slide.status === "moved-modified") summary.slides.modified++;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return summary;
|
|
496
|
+
}
|
|
497
|
+
function diffPresentations(a, b, options = {}) {
|
|
498
|
+
const ctx = { options: { threshold: options.threshold ?? DEFAULT_MATCH_THRESHOLD }, changes: [], patch: [], slides: [] };
|
|
499
|
+
diffValue(a, b, [], [], ctx);
|
|
500
|
+
return { equal: ctx.patch.length === 0, changes: ctx.changes, patch: ctx.patch, slides: ctx.slides, summary: summarise(ctx.changes, ctx.slides) };
|
|
501
|
+
}
|
|
502
|
+
var CONTENT_KINDS = ["blocks", "chart", "table", "code", "metric", "quote", "timeline", "image", "video", "items", "bullets", "text"];
|
|
503
|
+
function shorten(text, max = 60) {
|
|
504
|
+
const flat = text.replace(/\s+/gu, " ");
|
|
505
|
+
return flat.length > max ? `${flat.slice(0, max - 1)}\u2026` : flat;
|
|
506
|
+
}
|
|
507
|
+
function describeValue(value) {
|
|
508
|
+
if (value === void 0) return "";
|
|
509
|
+
if (typeof value === "string") return JSON.stringify(shorten(value));
|
|
510
|
+
if (value === null || typeof value !== "object") return String(value);
|
|
511
|
+
if (Array.isArray(value)) return `[${value.length} item${value.length === 1 ? "" : "s"}]`;
|
|
512
|
+
const keys = Object.keys(value);
|
|
513
|
+
const kind = CONTENT_KINDS.find((key) => key in value);
|
|
514
|
+
const record2 = value;
|
|
515
|
+
if (kind) {
|
|
516
|
+
const text = typeof record2.text === "string" ? ` ${JSON.stringify(shorten(record2.text, 40))}` : typeof record2.title === "string" ? ` ${JSON.stringify(shorten(record2.title, 40))}` : "";
|
|
517
|
+
return `${kind}${text}`;
|
|
518
|
+
}
|
|
519
|
+
if (typeof record2.id === "string") return `{id ${JSON.stringify(record2.id)}}`;
|
|
520
|
+
return `{${keys.slice(0, 3).join(", ")}${keys.length > 3 ? ", \u2026" : ""}}`;
|
|
521
|
+
}
|
|
522
|
+
function displayPath(pointerText, prefixLength = 0) {
|
|
523
|
+
if (pointerText === void 0) return "";
|
|
524
|
+
const tokens = pointerText.split("/").slice(1).map((token) => token.replaceAll("~1", "/").replaceAll("~0", "~")).slice(prefixLength);
|
|
525
|
+
if (!tokens.length) return "(whole value)";
|
|
526
|
+
return tokens.map((token, i) => /^\d+$/u.test(token) ? `[${token}]` : /^[A-Za-z_$][\w$-]*$/u.test(token) ? i ? `.${token}` : token : `[${JSON.stringify(token)}]`).join("");
|
|
527
|
+
}
|
|
528
|
+
function slideLabel(slide, side = "b") {
|
|
529
|
+
const index = side === "b" ? slide.bIndex ?? slide.aIndex : slide.aIndex ?? slide.bIndex;
|
|
530
|
+
const parts = [`#${(index ?? 0) + 1}`];
|
|
531
|
+
if (slide.title) parts.push(JSON.stringify(shorten(slide.title, 40)));
|
|
532
|
+
if (slide.id) parts.push(`(id ${slide.id})`);
|
|
533
|
+
return parts.join(" ");
|
|
534
|
+
}
|
|
535
|
+
function changeLine(change, prefixLength) {
|
|
536
|
+
switch (change.type) {
|
|
537
|
+
case "added":
|
|
538
|
+
return `+ ${displayPath(change.bPath, prefixLength)} ${describeValue(change.after)}`.trimEnd();
|
|
539
|
+
case "removed":
|
|
540
|
+
return `- ${displayPath(change.aPath, prefixLength)} ${describeValue(change.before)}`.trimEnd();
|
|
541
|
+
case "moved":
|
|
542
|
+
return `> ${displayPath(change.aPath, prefixLength)} -> ${displayPath(change.bPath, prefixLength)}`;
|
|
543
|
+
default:
|
|
544
|
+
return `~ ${displayPath(change.bPath, prefixLength)} ${describeValue(change.before)} -> ${describeValue(change.after)}`;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
var SECTION_TITLES = {
|
|
548
|
+
metadata: "Metadata",
|
|
549
|
+
design: "Design",
|
|
550
|
+
variables: "Variables",
|
|
551
|
+
narrative: "Narrative",
|
|
552
|
+
assets: "Assets",
|
|
553
|
+
catalogs: "Catalogs",
|
|
554
|
+
extensions: "Extensions",
|
|
555
|
+
slide: "Slides",
|
|
556
|
+
block: "Slides",
|
|
557
|
+
field: "Slides"
|
|
558
|
+
};
|
|
559
|
+
function formatDiffReport(diff) {
|
|
560
|
+
if (diff.equal) return "No differences.\n";
|
|
561
|
+
const lines = [];
|
|
562
|
+
const s = diff.summary.slides;
|
|
563
|
+
const headline = [];
|
|
564
|
+
const slideParts = [[s.added, "added"], [s.removed, "removed"], [s.moved, "moved"], [s.modified, "modified"]].filter(([n]) => n > 0).map(([n, label]) => `${n} ${label}`);
|
|
565
|
+
if (slideParts.length) headline.push(`slides: ${slideParts.join(", ")}`);
|
|
566
|
+
for (const category of ["metadata", "design", "variables", "narrative", "assets", "catalogs", "extensions"]) {
|
|
567
|
+
const count = diff.summary.byCategory[category];
|
|
568
|
+
if (count) headline.push(`${category}: ${count}`);
|
|
569
|
+
}
|
|
570
|
+
lines.push(`${diff.summary.total} change${diff.summary.total === 1 ? "" : "s"} (${headline.join("; ")})`, "");
|
|
571
|
+
for (const category of ["metadata", "design", "variables", "narrative", "assets", "catalogs", "extensions"]) {
|
|
572
|
+
const section = diff.changes.filter((change) => change.category === category);
|
|
573
|
+
if (!section.length) continue;
|
|
574
|
+
lines.push(SECTION_TITLES[category]);
|
|
575
|
+
for (const change of section) lines.push(` ${changeLine(change, 0)}`);
|
|
576
|
+
lines.push("");
|
|
577
|
+
}
|
|
578
|
+
const slideChanges = diff.changes.filter((change) => change.category === "slide" || change.category === "block" || change.category === "field" || change.slide && change.category === "design");
|
|
579
|
+
if (slideChanges.length || diff.slides.some((slide) => slide.status !== "unchanged")) {
|
|
580
|
+
lines.push("Slides");
|
|
581
|
+
const whole = (change) => change.category === "slide" && !!change.slide && (change.bPath ?? change.aPath)?.split("/").length === 3;
|
|
582
|
+
for (const info of diff.slides) {
|
|
583
|
+
if (info.status === "unchanged") continue;
|
|
584
|
+
if (info.status === "added") {
|
|
585
|
+
lines.push(` + slide ${slideLabel(info)}`);
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
if (info.status === "removed") {
|
|
589
|
+
lines.push(` - slide ${slideLabel(info, "a")}`);
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
const moveNote = info.status === "moved" || info.status === "moved-modified" ? ` (moved from #${(info.aIndex ?? 0) + 1})` : "";
|
|
593
|
+
lines.push(` ~ slide ${slideLabel(info)}${moveNote}${info.matchedBy === "similar" ? " (matched by content)" : ""}`);
|
|
594
|
+
const inner = slideChanges.filter((change) => !whole(change) && change.slide && change.slide.bIndex === info.bIndex && change.slide.aIndex === info.aIndex);
|
|
595
|
+
for (const change of inner) lines.push(` ${changeLine(change, 2)}`);
|
|
596
|
+
}
|
|
597
|
+
for (const change of slideChanges) if (!change.slide) lines.push(` ${changeLine(change, 0)}`);
|
|
598
|
+
lines.push("");
|
|
599
|
+
}
|
|
600
|
+
return `${lines.join("\n").replace(/\n+$/u, "")}
|
|
601
|
+
`;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
export { categoryOf, describeValue, diffPresentations, formatDiffReport, matchArrays, mergePresentations, similarity };
|