rapier-markdown-kit 1.1.99 → 1.1.101
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 +10 -2
- package/README.md +42 -8
- package/dist/kit/export-fonts.mjs +403 -0
- package/dist/kit/ledger/data.mjs +1 -1
- package/dist/kit/ledger/format.mjs +12 -5
- package/dist/kit/ledger/hash.mjs +16 -22
- package/dist/kit/ledger/history.mjs +59 -276
- package/dist/kit/ledger/inverse-source.mjs +316 -0
- package/dist/kit/ledger/journal-records.mjs +116 -12
- package/dist/kit/ledger/merge.mjs +7 -4
- package/dist/kit/ledger/transport.mjs +123 -19
- package/dist/kit/pdf-glyphs.mjs +159 -0
- package/dist/kit/pdf-vectors.mjs +204 -0
- package/dist/kit/print-capitals.mjs +85 -0
- package/dist/kit/rapier-ledger.mjs +2 -2
- package/dist/kit/render-entry.mjs +1 -0
- package/dist/kit/render-ink.mjs +132 -0
- package/dist/kit/render-markdown.mjs +71 -17
- package/dist/kit/render-print.mjs +208 -60
- package/dist/kit/render-sanitize.mjs +23 -7
- package/dist/kit/render-styles.mjs +8 -12
- package/dist/kit/render.mjs +365 -133
- package/dist/layout/model.mjs +26 -20
- package/dist/layout/typography.mjs +364 -0
- package/dist/packages/rapier-diff/LICENSE +21 -0
- package/dist/packages/rapier-diff/index.mjs +610 -0
- package/dist/spec/document-fonts.mjs +244 -0
- package/dist/spec/document-settings.mjs +494 -0
- package/dist/spec/frontmatter.mjs +30 -0
- package/dist/spec/html-blocks.mjs +143 -0
- package/dist/spec/md-assets.mjs +77 -5
- package/dist/spec/md-layout.mjs +7 -3
- package/dist/spec/md-marks.mjs +114 -8
- package/package.json +1 -1
- package/style.css +110 -52
- package/dist/agent/diff.mjs +0 -52
package/dist/kit/ledger/hash.mjs
CHANGED
|
@@ -21,33 +21,27 @@ const K = new Uint32Array([
|
|
|
21
21
|
0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
|
|
22
22
|
0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2,
|
|
23
23
|
]);
|
|
24
|
-
|
|
25
|
-
// faster than through destructured arrays, with the same digest.
|
|
26
|
-
const W = new Int32Array(64);
|
|
24
|
+
const rotate = (x, n) => (x >>> n) | (x << (32 - n));
|
|
27
25
|
export function sha256(text) {
|
|
28
|
-
const bytes = new TextEncoder().encode(text)
|
|
29
|
-
const padded = new Uint8Array(Math.ceil((length + 9) / 64) * 64)
|
|
30
|
-
padded.set(bytes); padded[length] = 0x80;
|
|
31
|
-
const
|
|
32
|
-
padded
|
|
33
|
-
|
|
34
|
-
let
|
|
35
|
-
|
|
36
|
-
for (let offset = 0; offset < end; offset += 64) {
|
|
37
|
-
for (let i = 0, j = offset; i < 16; i++, j += 4) w[i] = padded[j] << 24 | padded[j + 1] << 16 | padded[j + 2] << 8 | padded[j + 3];
|
|
26
|
+
const bytes = new TextEncoder().encode(text);
|
|
27
|
+
const padded = new Uint8Array(Math.ceil((bytes.length + 9) / 64) * 64);
|
|
28
|
+
padded.set(bytes); padded[bytes.length] = 0x80;
|
|
29
|
+
const data = new DataView(padded.buffer), bits = bytes.length * 8;
|
|
30
|
+
data.setUint32(padded.length - 8, Math.floor(bits / 0x100000000)); data.setUint32(padded.length - 4, bits >>> 0);
|
|
31
|
+
const h = new Uint32Array([0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19]), w = new Uint32Array(64);
|
|
32
|
+
for (let offset = 0; offset < padded.length; offset += 64) {
|
|
33
|
+
for (let i = 0; i < 16; i++) w[i] = data.getUint32(offset + i * 4);
|
|
38
34
|
for (let i = 16; i < 64; i++) {
|
|
39
35
|
const x = w[i - 15], y = w[i - 2];
|
|
40
|
-
w[i] =
|
|
36
|
+
w[i] = w[i - 16] + (rotate(x, 7) ^ rotate(x, 18) ^ (x >>> 3)) + w[i - 7] + (rotate(y, 17) ^ rotate(y, 19) ^ (y >>> 10));
|
|
41
37
|
}
|
|
42
|
-
let a
|
|
38
|
+
let [a,b,c,d,e,f,g,k] = h;
|
|
43
39
|
for (let i = 0; i < 64; i++) {
|
|
44
|
-
const t1 = (k + ((e
|
|
45
|
-
const t2 = (((a
|
|
46
|
-
k = g; g = f; f = e; e = (d + t1)
|
|
40
|
+
const t1 = (k + (rotate(e, 6) ^ rotate(e, 11) ^ rotate(e, 25)) + ((e & f) ^ (~e & g)) + K[i] + w[i]) >>> 0;
|
|
41
|
+
const t2 = ((rotate(a, 2) ^ rotate(a, 13) ^ rotate(a, 22)) + ((a & b) ^ (a & c) ^ (b & c))) >>> 0;
|
|
42
|
+
k = g; g = f; f = e; e = (d + t1) >>> 0; d = c; c = b; b = a; a = (t1 + t2) >>> 0;
|
|
47
43
|
}
|
|
48
|
-
|
|
44
|
+
for (const [i, value] of [a,b,c,d,e,f,g,k].entries()) h[i] += value;
|
|
49
45
|
}
|
|
50
|
-
|
|
51
|
-
for (const x of [h0, h1, h2, h3, h4, h5, h6, h7]) out += (x >>> 0).toString(16).padStart(8, '0');
|
|
52
|
-
return out;
|
|
46
|
+
return [...h].map(x => x.toString(16).padStart(8, '0')).join('');
|
|
53
47
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
// Transient projections and selective inverses of the existing canonical journal.
|
|
3
3
|
import {_rapierTransformSplices, _rapierRecordSplices, _rapierRecordMetadata, _rapierValidMetadata, _rapierTransformMetadata, _rapierMetadataDelta, _rapierHistoryEffects, _rapierMetadataState, _rapierReplayMetadata} from './journal-records.mjs';
|
|
4
|
-
import {
|
|
4
|
+
import {projectInverseSource} from './inverse-source.mjs';
|
|
5
5
|
import {textRoot, rootAfter} from './hash.mjs';
|
|
6
6
|
import {authoredHistory} from './transport.mjs';
|
|
7
7
|
import {plainData} from './data.mjs';
|
|
@@ -41,6 +41,39 @@ export function groupHistoryActs(acts) {
|
|
|
41
41
|
return groups;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
// A picture's codec replacement travels with its original navigation step.
|
|
45
|
+
// Match every complete replacement exactly before moving any source bytes.
|
|
46
|
+
export function pictureHistorySplices({source, records}, target, redo) {
|
|
47
|
+
const id = target?.transaction?.id, at = records.indexOf(target);
|
|
48
|
+
if (!id || at < 0 || target.transaction.operation === 'document.finish-picture' || !Array.isArray(target.splices)) return null;
|
|
49
|
+
const later = records.slice(at + 1).filter(row => row?.transaction?.operation === 'document.finish-picture' && Array.isArray(row.splices) && row.splices.length);
|
|
50
|
+
if (!later.length) return null;
|
|
51
|
+
const own = new Set(later.filter(row => row.transaction.sourceTransactionId === id));
|
|
52
|
+
const step = redo ? target.splices : target.splices.slice().reverse().map(row => ({pos: row.pos, removed: row.inserted, inserted: row.removed}));
|
|
53
|
+
const plan = moveOthers => {
|
|
54
|
+
let text = source;
|
|
55
|
+
const rows = [], moved = new Set();
|
|
56
|
+
const only = needle => { const found = text.indexOf(needle); return found >= 0 && text.indexOf(needle, found + 1) < 0 ? found : -1; };
|
|
57
|
+
const apply = row => { const next = _rapierTransformSplices(text, [row]); if (next === null) throw Error('picture_target'); text = next; rows.push(row); };
|
|
58
|
+
const swap = (record, back) => {
|
|
59
|
+
const found = record.splices.map(splice => { const from = back ? splice.inserted : splice.removed; return from ? only(from) : -1; });
|
|
60
|
+
if (found.every(position => position < 0)) return false;
|
|
61
|
+
if (found.some(position => position < 0)) throw Error('partial_picture');
|
|
62
|
+
for (const splice of record.splices) {
|
|
63
|
+
const from = back ? splice.inserted : splice.removed, to = back ? splice.removed : splice.inserted;
|
|
64
|
+
apply({pos: only(from), removed: from, inserted: to});
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
67
|
+
};
|
|
68
|
+
for (const record of later.slice().reverse()) if ((moveOthers || own.has(record)) && swap(record, true)) moved.add(record);
|
|
69
|
+
for (const splice of step) apply(splice);
|
|
70
|
+
for (const record of later) if (own.has(record) ? redo : moved.has(record)) swap(record, false);
|
|
71
|
+
return rows;
|
|
72
|
+
};
|
|
73
|
+
try { const rows = plan(false); return !own.size && rows.length === step.length ? null : rows; } catch (_) {}
|
|
74
|
+
try { return plan(true); } catch (_) { return null; }
|
|
75
|
+
}
|
|
76
|
+
|
|
44
77
|
// Reading an admitted repair can recover pre-existing lone UTF-16 units. This is
|
|
45
78
|
// not a document mutation; the forward replay and roots still validate every row.
|
|
46
79
|
function historyPreviousSource(source, splices) {
|
|
@@ -118,7 +151,7 @@ export function replayHistory(input) {
|
|
|
118
151
|
const prepared = prepareHistory(input);
|
|
119
152
|
if (!prepared.ok) return prepared;
|
|
120
153
|
const {source, initialSource, metadata, initialMetadata, revision, earliestRevision, complete, rows, effects} = prepared;
|
|
121
|
-
const acts = rows.map(({record}) => historyAct(record, effects));
|
|
154
|
+
const acts = rows.filter(({record}) => !effects.pictures.has(record.transaction.id)).map(({record}) => historyAct(record, effects));
|
|
122
155
|
return {ok: true, source, initialSource, metadata, initialMetadata, revision, earliestRevision, complete, acts,
|
|
123
156
|
effectiveActIds: effects.effectiveActIds.slice(), groups: groupHistoryActs(acts)};
|
|
124
157
|
}
|
|
@@ -151,9 +184,7 @@ function historyRangeBefore(range, splice) {
|
|
|
151
184
|
touched: true};
|
|
152
185
|
}
|
|
153
186
|
|
|
154
|
-
|
|
155
|
-
const prepared = prepareHistory(input);
|
|
156
|
-
if (!prepared.ok) return prepared;
|
|
187
|
+
function projectHistoryRange(prepared, from, to, actId) {
|
|
157
188
|
if (!historyBoundary(prepared.source, from) || !historyBoundary(prepared.source, to) || to < from) return historyFailure('range_invalid');
|
|
158
189
|
let text = prepared.source, range = {from, to}, before = null;
|
|
159
190
|
const acts = [];
|
|
@@ -168,7 +199,7 @@ export function historyProjection(input, {from = 0, to = input?.source?.length,
|
|
|
168
199
|
if (text === null) return historyFailure('history_invalid');
|
|
169
200
|
const previous = {...range, source: text.slice(range.from, range.to)};
|
|
170
201
|
if (record.transaction.id === actId) before = previous;
|
|
171
|
-
if (touched || record.metadata || prepared.effects.references.get(record.transaction.id)?.length)
|
|
202
|
+
if (!prepared.effects.pictures.has(record.transaction.id) && (touched || record.metadata || prepared.effects.references.get(record.transaction.id)?.length))
|
|
172
203
|
acts.push({...historyAct(record, prepared.effects), before: previous, after});
|
|
173
204
|
}
|
|
174
205
|
if (actId != null && !before) return historyFailure('act_unavailable');
|
|
@@ -177,6 +208,21 @@ export function historyProjection(input, {from = 0, to = input?.source?.length,
|
|
|
177
208
|
earliestRevision: prepared.earliestRevision, complete: prepared.complete, acts, groups: groupHistoryActs(acts), ...(before ? {before} : {})};
|
|
178
209
|
}
|
|
179
210
|
|
|
211
|
+
export function historyProjection(input, {from = 0, to = input?.source?.length, actId} = {}) {
|
|
212
|
+
const prepared = prepareHistory(input);
|
|
213
|
+
if (!prepared.ok) return prepared;
|
|
214
|
+
return projectHistoryRange(prepared, from, to, actId);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Several ranges share one proved source snapshot for this call.
|
|
218
|
+
export function historyProjections(input, ranges) {
|
|
219
|
+
const prepared = prepareHistory(input);
|
|
220
|
+
if (!prepared.ok) return prepared;
|
|
221
|
+
if (!Array.isArray(ranges)) return historyFailure('range_invalid');
|
|
222
|
+
return {ok: true, projections: ranges.map(({from = 0, to = prepared.source.length, actId} = {}) =>
|
|
223
|
+
structuredClone(projectHistoryRange(prepared, from, to, actId)))};
|
|
224
|
+
}
|
|
225
|
+
|
|
180
226
|
// Project all reading places in one replay. Typing only invalidates this transient view.
|
|
181
227
|
export function historyPlaces(input, blocks) {
|
|
182
228
|
const prepared = prepareHistory(input);
|
|
@@ -198,11 +244,11 @@ export function historyPlaces(input, blocks) {
|
|
|
198
244
|
block.range = {from: mapped.from, to: mapped.to}; touched ||= mapped.touched;
|
|
199
245
|
}
|
|
200
246
|
const previous = {...block.range, source: before.slice(block.range.from, block.range.to)};
|
|
201
|
-
if (touched && previous.source !== after.source) block.acts.push({...historyAct(record), before: previous, after});
|
|
247
|
+
if (touched && previous.source !== after.source && !prepared.effects.pictures.has(record.transaction.id)) block.acts.push({...historyAct(record), before: previous, after});
|
|
202
248
|
}
|
|
203
249
|
text = before;
|
|
204
250
|
}
|
|
205
|
-
const acts = prepared.rows.map(({record}) => historyAct(record));
|
|
251
|
+
const acts = prepared.rows.filter(({record}) => !prepared.effects.pictures.has(record.transaction.id)).map(({record}) => historyAct(record));
|
|
206
252
|
const present = new Map(), effects = new Map();
|
|
207
253
|
for (const act of acts) {
|
|
208
254
|
const changes = [];
|
|
@@ -228,37 +274,6 @@ export function historyPlaces(input, blocks) {
|
|
|
228
274
|
reversed: acts.filter(act => present.get(act.id) !== true).map(act => act.id)};
|
|
229
275
|
}
|
|
230
276
|
|
|
231
|
-
function minimalHistorySplice(before, after, pos = 0) {
|
|
232
|
-
let first = 0, a = before.length, b = after.length;
|
|
233
|
-
while (first < a && first < b && before[first] === after[first]) first++;
|
|
234
|
-
if (!historyBoundary(before, first) || !historyBoundary(after, first)) first--;
|
|
235
|
-
while (a > first && b > first && before[a - 1] === after[b - 1]) { a--; b--; }
|
|
236
|
-
if (!historyBoundary(before, a) || !historyBoundary(after, b)) { a++; b++; }
|
|
237
|
-
return {pos: pos + first, removed: before.slice(first, a), inserted: after.slice(first, b)};
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Split only at byte-identical runs inside the recorded replacement, never by proportional character positions.
|
|
241
|
-
function historyHunks(row) {
|
|
242
|
-
if (row.removed === row.inserted) return [];
|
|
243
|
-
if (!row.removed || !row.inserted) return [{...row}];
|
|
244
|
-
const size = row.removed.length + row.inserted.length;
|
|
245
|
-
const parts = size <= 16384 ? diffChars(row.removed, row.inserted, {maxEditLength: 256})
|
|
246
|
-
: size <= 1024 * 1024 ? diffLines(row.removed, row.inserted, {maxEditLength: 128}) : null;
|
|
247
|
-
if (!parts) return [minimalHistorySplice(row.removed, row.inserted, row.pos)];
|
|
248
|
-
const hunks = [];
|
|
249
|
-
let offset = 0, start = 0, removed = '', inserted = '';
|
|
250
|
-
const flush = () => { if (removed || inserted) hunks.push({pos: row.pos + start, removed, inserted}); removed = ''; inserted = ''; };
|
|
251
|
-
for (const part of parts) {
|
|
252
|
-
if (part.added || part.removed) {
|
|
253
|
-
if (!removed && !inserted) start = offset;
|
|
254
|
-
if (part.added) inserted += part.value;
|
|
255
|
-
else { removed += part.value; offset += part.value.length; }
|
|
256
|
-
} else { flush(); offset += part.value.length; }
|
|
257
|
-
}
|
|
258
|
-
flush();
|
|
259
|
-
return hunks.length <= 64 ? hunks.reverse() : [minimalHistorySplice(row.removed, row.inserted, row.pos)];
|
|
260
|
-
}
|
|
261
|
-
|
|
262
277
|
// Runs retain only exact replacement ancestry. A later replacement owns an ambiguous hunk whole.
|
|
263
278
|
export function selectiveUndo(input, actIds, options = {}) {
|
|
264
279
|
const prepared = prepareHistory(input);
|
|
@@ -283,244 +298,12 @@ export function selectiveUndo(input, actIds, options = {}) {
|
|
|
283
298
|
sourceTransactionIds: [...selected], metadata, afterMetadata, effectChanged, unchanged: !metadata && !effectChanged,
|
|
284
299
|
superseded: [], overlaps: []};
|
|
285
300
|
try {
|
|
286
|
-
let pieces = prepared.initialSource ? [{text: prepared.initialSource, owner: null, lineage: []}] : [];
|
|
287
|
-
const all = [], byId = new Map(), byAct = new Map(), processed = [], keptInverseAncestors = new Set();
|
|
288
|
-
const textOf = rows => rows.map(row => row.text || '').join('');
|
|
289
|
-
const boundary = pos => {
|
|
290
|
-
let offset = 0;
|
|
291
|
-
for (let index = 0; index < pieces.length; index++) {
|
|
292
|
-
const row = pieces[index];
|
|
293
|
-
if (!row.text) continue;
|
|
294
|
-
const end = offset + row.text.length;
|
|
295
|
-
if (pos > offset && pos < end) {
|
|
296
|
-
const at = pos - offset;
|
|
297
|
-
pieces.splice(index, 1, {...row, text: row.text.slice(0, at)}, {...row, text: row.text.slice(at)});
|
|
298
|
-
break;
|
|
299
|
-
}
|
|
300
|
-
offset = end;
|
|
301
|
-
}
|
|
302
|
-
offset = 0;
|
|
303
|
-
let first = pieces.length, last = pieces.length;
|
|
304
|
-
for (let index = 0; index < pieces.length; index++) {
|
|
305
|
-
if (offset === pos) { first = index; last = index; while (last < pieces.length && !pieces[last].text) last++; return {first, last}; }
|
|
306
|
-
offset += pieces[index].text?.length || 0;
|
|
307
|
-
}
|
|
308
|
-
if (offset !== pos) throw Error('range');
|
|
309
|
-
return {first, last};
|
|
310
|
-
};
|
|
311
|
-
const owns = (piece, hunk) => piece === hunk.marker || piece.owner === hunk.id ||
|
|
312
|
-
piece.restoredBy?.includes(hunk.id) || hunk.restoredMarkers?.has(piece);
|
|
313
|
-
const effectiveBefore = (hunk, effects, visiting = new Set()) => {
|
|
314
|
-
if (visiting.has(hunk.id)) return null;
|
|
315
|
-
visiting.add(hunk.id); let text = '';
|
|
316
|
-
for (const piece of hunk.before) {
|
|
317
|
-
if (!piece.text) continue;
|
|
318
|
-
const owner = byId.get(piece.owner);
|
|
319
|
-
if (owner && !effects.active.has(owner.actId) && !owner.kept) {
|
|
320
|
-
if (piece.text !== owner.inserted) return null;
|
|
321
|
-
const prior = effectiveBefore(owner, effects, new Set(visiting));
|
|
322
|
-
if (prior === null) return null;
|
|
323
|
-
text += prior;
|
|
324
|
-
} else text += piece.text;
|
|
325
|
-
}
|
|
326
|
-
return text;
|
|
327
|
-
};
|
|
328
|
-
const reactivate = (actIds, effects) => {
|
|
329
|
-
for (const hunk of all) {
|
|
330
|
-
if (!actIds.has(hunk.actId) || hunk.derived || prepared.effects.references.get(hunk.actId)?.length) continue;
|
|
331
|
-
if (keptInverseAncestors.has(hunk.id)) continue;
|
|
332
|
-
const later = [...hunk.supersededBy].some(id => {
|
|
333
|
-
const next = byId.get(id);
|
|
334
|
-
return next && effects.active.has(next.actId) && !prepared.effects.references.get(next.actId)?.length;
|
|
335
|
-
});
|
|
336
|
-
if (later || pieces.some(piece => piece.text && piece.owner === hunk.id) || (!hunk.inserted && hunk.active)) continue;
|
|
337
|
-
const marker = pieces.indexOf(hunk.marker);
|
|
338
|
-
if (marker < 0) throw Error('reactivation');
|
|
339
|
-
const pos = textOf(pieces.slice(0, marker)).length, current = textOf(pieces);
|
|
340
|
-
const removed = effectiveBefore(hunk, effects);
|
|
341
|
-
if (removed === null || current.slice(pos, pos + removed.length) !== removed) throw Error('reactivation');
|
|
342
|
-
boundary(pos + removed.length);
|
|
343
|
-
const start = boundary(pos), end = boundary(pos + removed.length);
|
|
344
|
-
const at = start.last, count = Math.max(0, end.first - at);
|
|
345
|
-
if (textOf(pieces.slice(at, at + count)) !== removed) throw Error('reactivation');
|
|
346
|
-
const replacedOwners = new Set(pieces.slice(at, at + count).map(piece => piece.owner).filter(Boolean));
|
|
347
|
-
pieces.splice(at, count, ...(hunk.inserted ? [{text: hunk.inserted, owner: hunk.id, lineage: hunk.lineage.slice()}] : []));
|
|
348
|
-
for (const owner of replacedOwners) if (owner !== hunk.id && !pieces.some(piece => piece.text && piece.owner === owner)) {
|
|
349
|
-
const prior = byId.get(owner); if (prior) prior.active = false;
|
|
350
|
-
}
|
|
351
|
-
hunk.active = true; delete hunk.undoneBy;
|
|
352
|
-
}
|
|
353
|
-
};
|
|
354
301
|
const transported = records.some(row => row.authored) ? authoredHistory(prepared.initialSource, records, selected) : null;
|
|
355
|
-
const
|
|
302
|
+
const rows = transported ? transported.records.map(record => ({record, splices: record.splices,
|
|
356
303
|
sourceIndices: transported.sourceIndices.get(record.transaction.id)})) : prepared.rows;
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
if (references.size) {
|
|
362
|
-
// Exact inverse rows restore the previous run identities. Retain the inverse's
|
|
363
|
-
// own transient marker too, so reversing it restores that same ancestry again.
|
|
364
|
-
const targets = all.filter(hunk => hunk.active && references.has(hunk.actId));
|
|
365
|
-
let restored = targets.length ? pieces.slice() : null;
|
|
366
|
-
const inverses = [], priorSplices = options.keepHunk ? [] : null;
|
|
367
|
-
for (let index = targets.length - 1; restored && index >= 0; index--) {
|
|
368
|
-
const target = targets[index], first = restored.indexOf(target.marker);
|
|
369
|
-
if (first < 0) { restored = null; break; }
|
|
370
|
-
let last = first;
|
|
371
|
-
for (let at = first; at < restored.length; at++) if (owns(restored[at], target)) last = at;
|
|
372
|
-
const pos = textOf(restored.slice(0, first)).length;
|
|
373
|
-
const before = restored.slice(first, last + 1), replacement = [];
|
|
374
|
-
for (const piece of before) {
|
|
375
|
-
if (piece === target.marker) replacement.push(...target.before);
|
|
376
|
-
else if (!owns(piece, target)) replacement.push(piece);
|
|
377
|
-
}
|
|
378
|
-
const id = tx.id + '#inverse:' + index, lineage = new Set([id]);
|
|
379
|
-
for (const piece of before) for (const parent of piece.lineage || piece.mark?.lineage || []) lineage.add(parent);
|
|
380
|
-
const hunk = {id, actId: tx.id, removed: textOf(before), inserted: textOf(replacement), before, reversedHunk: target.id,
|
|
381
|
-
chosen: selected.has(tx.id), lineage: [...lineage], supersededBy: new Set(), active: true,
|
|
382
|
-
restoredMarkers: new Set(replacement.filter(piece => !piece.text))};
|
|
383
|
-
if (hunk.chosen && options.keepHunk) {
|
|
384
|
-
const spliceIndex = splices.findIndex(row => row.pos <= pos && row.pos + row.removed.length >= pos);
|
|
385
|
-
const keep = options.keepHunk({actId: tx.id, spliceIndex: Math.max(0, spliceIndex), hunkIndex: index,
|
|
386
|
-
splice: {pos, removed: hunk.removed, inserted: hunk.inserted}, beforeSource: textOf(restored), actSource, priorSplices});
|
|
387
|
-
if (typeof keep !== 'boolean') throw Error('hunk_selection');
|
|
388
|
-
hunk.chosen = !keep;
|
|
389
|
-
}
|
|
390
|
-
hunk.kept = selected.has(tx.id) && !hunk.chosen;
|
|
391
|
-
// A semantic owner retaining this inverse also retains the exact ancestors
|
|
392
|
-
// it restored; effect reactivation must not change them behind that owner.
|
|
393
|
-
if (hunk.kept) for (let prior = target; prior; prior = byId.get(prior.reversedHunk)) keptInverseAncestors.add(prior.id);
|
|
394
|
-
hunk.marker = {mark: hunk};
|
|
395
|
-
const insertion = replacement.map(piece => piece.text ? {...piece,
|
|
396
|
-
lineage: [...new Set([...(piece.lineage || []), id])],
|
|
397
|
-
restoredBy: [...(piece.restoredBy || []), id]} : piece);
|
|
398
|
-
restored.splice(first, last + 1 - first, hunk.marker, ...insertion);
|
|
399
|
-
inverses.push(hunk); priorSplices?.push({pos, removed: hunk.removed, inserted: hunk.inserted});
|
|
400
|
-
}
|
|
401
|
-
if (restored && textOf(restored) === _rapierTransformSplices(textOf(pieces), splices)) {
|
|
402
|
-
pieces = restored;
|
|
403
|
-
for (const target of targets) { target.active = false; target.undoneBy = tx.id; }
|
|
404
|
-
for (const piece of pieces) if (piece.mark) {
|
|
405
|
-
piece.mark.active = true; delete piece.mark.undoneBy;
|
|
406
|
-
}
|
|
407
|
-
for (const hunk of inverses) { all.push(hunk); byId.set(hunk.id, hunk); }
|
|
408
|
-
byAct.set(tx.id, inverses);
|
|
409
|
-
continue;
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
if (references.size) {
|
|
414
|
-
const previous = _rapierHistoryEffects(processed.slice(0, -1)), current = _rapierHistoryEffects(processed);
|
|
415
|
-
const activated = new Set(current.effectiveActIds.filter(id => !previous.active.has(id) &&
|
|
416
|
-
id !== tx.id && !current.references.get(id)?.length));
|
|
417
|
-
if (activated.size) {
|
|
418
|
-
const before = pieces, priorStates = all.map(hunk => [hunk, hunk.active, hunk.undoneBy]); pieces = pieces.slice();
|
|
419
|
-
try {
|
|
420
|
-
reactivate(activated, current);
|
|
421
|
-
if (textOf(pieces) === _rapierTransformSplices(textOf(before), splices)) continue;
|
|
422
|
-
} catch (_) {}
|
|
423
|
-
pieces = before;
|
|
424
|
-
for (const [hunk, active, undoneBy] of priorStates) { hunk.active = active; hunk.undoneBy = undoneBy; }
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
const priorSplices = options.keepHunk ? [] : null;
|
|
428
|
-
for (let rowIndex = 0; rowIndex < splices.length; rowIndex++) {
|
|
429
|
-
const original = splices[rowIndex];
|
|
430
|
-
let rows;
|
|
431
|
-
if (options.splitSplice) {
|
|
432
|
-
const beforeSource = textOf(pieces), parts = options.splitSplice({actId: tx.id, beforeSource, splice: original});
|
|
433
|
-
// Semantic boundaries only refine this act's exact bytes, never its history.
|
|
434
|
-
if (!Array.isArray(parts) || _rapierTransformSplices(beforeSource, parts) !== _rapierTransformSplices(beforeSource, [original]))
|
|
435
|
-
throw Error('hunk_projection');
|
|
436
|
-
rows = parts.flatMap(historyHunks);
|
|
437
|
-
} else rows = historyHunks(original);
|
|
438
|
-
for (let index = 0; index < rows.length; index++) {
|
|
439
|
-
const row = rows[index];
|
|
440
|
-
const derived = (sourceIndices?.[rowIndex] ?? rowIndex) === record.derivedCommentIndex;
|
|
441
|
-
let chosen = selected.has(tx.id) && !derived;
|
|
442
|
-
boundary(row.pos + row.removed.length);
|
|
443
|
-
const start = boundary(row.pos), end = boundary(row.pos + row.removed.length);
|
|
444
|
-
let at = start.last, count = Math.max(0, end.first - at);
|
|
445
|
-
if (!row.removed.length) {
|
|
446
|
-
// A new insertion at a replacement's left boundary stays before that replacement.
|
|
447
|
-
const marker = pieces.slice(start.first, start.last).findIndex(item => item.mark?.inserted.length);
|
|
448
|
-
at = marker < 0 ? start.last : start.first + marker; count = 0;
|
|
449
|
-
}
|
|
450
|
-
const removed = pieces.slice(at, at + count);
|
|
451
|
-
if (textOf(removed) !== row.removed) throw Error('replay');
|
|
452
|
-
// A semantic owner may retain a drawing hunk while prose in that same act undoes.
|
|
453
|
-
// The predicate only selects transient hunks; every original row still replays.
|
|
454
|
-
if (chosen && options.keepHunk) {
|
|
455
|
-
const keep = options.keepHunk({actId: tx.id, spliceIndex: rowIndex, hunkIndex: index,
|
|
456
|
-
splice: {pos: row.pos, removed: row.removed, inserted: row.inserted}, beforeSource: textOf(pieces), actSource, priorSplices});
|
|
457
|
-
if (typeof keep !== 'boolean') throw Error('hunk_selection');
|
|
458
|
-
chosen = !keep;
|
|
459
|
-
}
|
|
460
|
-
const id = tx.id + '#' + rowIndex + ':' + index;
|
|
461
|
-
const lineage = new Set([id]);
|
|
462
|
-
for (const piece of removed) for (const parent of piece.lineage || piece.mark?.lineage || []) lineage.add(parent);
|
|
463
|
-
const hunk = {id, actId: tx.id, removed: row.removed, inserted: row.inserted, before: removed, derived,
|
|
464
|
-
chosen, kept: selected.has(tx.id) && !chosen, lineage: [...lineage], supersededBy: new Set(), active: true};
|
|
465
|
-
hunk.marker = {mark: hunk};
|
|
466
|
-
all.push(hunk); byId.set(id, hunk);
|
|
467
|
-
if (!byAct.has(tx.id)) byAct.set(tx.id, []);
|
|
468
|
-
byAct.get(tx.id).push(hunk);
|
|
469
|
-
for (const parent of lineage) {
|
|
470
|
-
const earlier = byId.get(parent);
|
|
471
|
-
if (earlier && earlier !== hunk) earlier.supersededBy.add(id);
|
|
472
|
-
}
|
|
473
|
-
pieces.splice(at, count, hunk.marker, ...(row.inserted ? [{text: row.inserted, owner: id, lineage: [...lineage]}] : []));
|
|
474
|
-
priorSplices?.push({pos: row.pos, removed: row.removed, inserted: row.inserted});
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
if (textOf(pieces) !== prepared.source) throw Error('head');
|
|
479
|
-
const origins = new Map();
|
|
480
|
-
let offset = 0;
|
|
481
|
-
for (const piece of pieces) if (piece.text) { origins.set(piece, offset); offset += piece.text.length; }
|
|
482
|
-
const superseded = [];
|
|
483
|
-
for (let index = all.length - 1; index >= 0; index--) {
|
|
484
|
-
const hunk = all[index];
|
|
485
|
-
const inheritedInactive = !hunk.derived && !hunk.kept && !keptInverseAncestors.has(hunk.id) && !projectedEffects.active.has(hunk.actId) &&
|
|
486
|
-
!prepared.effects.references.get(hunk.actId)?.length;
|
|
487
|
-
if (!hunk.chosen && !inheritedInactive) continue;
|
|
488
|
-
hunk.active = prepared.effects.active.has(hunk.actId);
|
|
489
|
-
const laterActIds = [...new Set([...hunk.supersededBy].filter(id =>
|
|
490
|
-
(byId.get(id)?.kept || byId.get(id)?.active && projectedEffects.active.has(byId.get(id)?.actId))).map(id => byId.get(id).actId))];
|
|
491
|
-
const marker = pieces.indexOf(hunk.marker);
|
|
492
|
-
if ((!hunk.active && !inheritedInactive) || laterActIds.length || marker < 0) {
|
|
493
|
-
if (hunk.chosen) superseded.push({actId: hunk.actId, hunk: hunk.id, laterActIds: hunk.undoneBy ? [hunk.undoneBy] : laterActIds,
|
|
494
|
-
reason: hunk.active ? 'later_edit' : 'already_undone'});
|
|
495
|
-
continue;
|
|
496
|
-
}
|
|
497
|
-
for (let at = pieces.length - 1; at >= 0; at--) if (pieces[at] !== hunk.marker && owns(pieces[at], hunk)) pieces.splice(at, 1);
|
|
498
|
-
pieces.splice(pieces.indexOf(hunk.marker), 1, ...hunk.before);
|
|
499
|
-
// Restoring an inverse can restore a deletion marker without any owned
|
|
500
|
-
// text. That exact marker already represents the active deletion.
|
|
501
|
-
for (const piece of hunk.before) if (piece.mark) { piece.mark.active = true; delete piece.mark.undoneBy; }
|
|
502
|
-
}
|
|
503
|
-
// A hidden withdrawal can have no splice of its own. Reversing that withdrawal
|
|
504
|
-
// reactivates the original hunk at its retained marker, never at a text search.
|
|
505
|
-
reactivate(new Set(records.filter(record => !prepared.effects.active.has(record.transaction.id) &&
|
|
506
|
-
projectedEffects.active.has(record.transaction.id)).map(record => record.transaction.id)), projectedEffects);
|
|
507
|
-
const after = textOf(pieces), changes = [];
|
|
508
|
-
let cursor = 0, inserted = '';
|
|
509
|
-
for (const piece of pieces) {
|
|
510
|
-
if (!piece.text) continue;
|
|
511
|
-
const origin = origins.get(piece);
|
|
512
|
-
if (origin == null) { inserted += piece.text; continue; }
|
|
513
|
-
if (origin < cursor) throw Error('source_order');
|
|
514
|
-
const removed = prepared.source.slice(cursor, origin);
|
|
515
|
-
if (removed !== inserted) changes.push(minimalHistorySplice(removed, inserted, cursor));
|
|
516
|
-
inserted = ''; cursor = origin + piece.text.length;
|
|
517
|
-
}
|
|
518
|
-
const removed = prepared.source.slice(cursor);
|
|
519
|
-
if (removed !== inserted) changes.push(minimalHistorySplice(removed, inserted, cursor));
|
|
520
|
-
const splices = changes.length <= 64 ? changes.reverse() : [minimalHistorySplice(prepared.source, after)];
|
|
521
|
-
if (_rapierTransformSplices(prepared.source, splices) !== after) throw Error('plan');
|
|
522
|
-
return {ok: true, splices, after, actIds: [...requested], sourceTransactionIds: [...selected], metadata, afterMetadata,
|
|
523
|
-
effectChanged, unchanged: after === prepared.source && !metadata && !effectChanged, superseded,
|
|
524
|
-
overlaps: superseded.filter(row => row.reason === 'later_edit')};
|
|
304
|
+
const result = projectInverseSource({...prepared, rows}, selected, projectedEffects, options);
|
|
305
|
+
if (!result.ok) return result;
|
|
306
|
+
return {...result, actIds: [...requested], sourceTransactionIds: [...selected], metadata, afterMetadata,
|
|
307
|
+
effectChanged, unchanged: result.after === prepared.source && !metadata && !effectChanged};
|
|
525
308
|
} catch (_) { return historyFailure('history_invalid'); }
|
|
526
309
|
}
|