@stll/folio-core 0.53.0 → 0.54.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-edits/apply.js +390 -166
- package/dist/ai-edits/batch-claims.d.ts +21 -6
- package/dist/ai-edits/batch-claims.js +47 -16
- package/dist/ai-edits/headless.d.ts +20 -0
- package/dist/ai-edits/headless.js +140 -11
- package/dist/ai-edits/pending-suggestions.js +1 -1
- package/dist/ai-edits/read.d.ts +5 -3
- package/dist/ai-edits/read.js +97 -47
- package/dist/ai-edits/revisionStretches.d.ts +17 -0
- package/dist/ai-edits/revisionStretches.js +80 -0
- package/dist/ai-edits/snapshot.js +3 -2
- package/dist/ai-edits/table-cell-mutations.d.ts +3 -1
- package/dist/ai-edits/table-cell-mutations.js +2 -1
- package/dist/ai-edits/table-row-column-mutations.d.ts +6 -1
- package/dist/ai-edits/table-row-column-mutations.js +27 -10
- package/dist/compare/content-alignment.d.ts +5 -1
- package/dist/compare/content-alignment.js +39 -32
- package/dist/compare/inline-provenance.js +1 -1
- package/dist/controller/layoutPipeline.js +3 -2
- package/dist/docx/archiveInflation.d.ts +127 -0
- package/dist/docx/archiveInflation.js +181 -0
- package/dist/docx/metadataPrivacy.js +24 -1
- package/dist/docx/noteReferenceMark.d.ts +15 -0
- package/dist/docx/noteReferenceMark.js +48 -0
- package/dist/docx/paragraphParser.js +5 -3
- package/dist/docx/paragraphPropertySource.d.ts +1 -1
- package/dist/docx/paragraphPropertySource.js +3 -1
- package/dist/docx/parser.js +2 -2
- package/dist/docx/rezip.js +4 -2
- package/dist/docx/selectiveXmlPatch.d.ts +3 -1
- package/dist/docx/selectiveXmlPatch.js +81 -3
- package/dist/docx/server/boundedArchive.d.ts +8 -13
- package/dist/docx/server/boundedArchive.js +70 -59
- package/dist/docx/server/extractDocxText.d.ts +8 -2
- package/dist/docx/server/extractDocxText.js +2 -2
- package/dist/docx/server/validateDocxConformance.d.ts +1 -1
- package/dist/docx/server/validateDocxConformance.js +1 -0
- package/dist/docx/unzip.d.ts +19 -2
- package/dist/docx/unzip.js +135 -24
- package/dist/fonts/embeddedFonts.js +1 -1
- package/dist/i18n/messages/catalogs.gen.d.ts +34 -34
- package/dist/i18n/messages/catalogs.gen.js +34 -34
- package/dist/i18n/messages/messages.gen.d.ts +2 -2
- package/dist/internal/wholeStoryRevisionResolution.js +95 -25
- package/dist/layout-engine/measure/measureParagraph.js +2 -2
- package/dist/layout-engine/measure/paragraphMeasureShared.d.ts +12 -1
- package/dist/layout-engine/measure/paragraphMeasureShared.js +14 -1
- package/dist/markdown/escape.d.ts +2 -2
- package/dist/markdown/escape.js +4 -5
- package/dist/markdown/renderBlock.js +91 -25
- package/dist/markdown/renderRuns.d.ts +10 -1
- package/dist/markdown/renderRuns.js +320 -59
- package/dist/prosemirror/clearRunColor.d.ts +6 -0
- package/dist/prosemirror/clearRunColor.js +46 -0
- package/dist/prosemirror/commands/comments.js +56 -21
- package/dist/prosemirror/commands/formatPainter.js +1 -1
- package/dist/prosemirror/commands/hyperlink.js +2 -1
- package/dist/prosemirror/commands/propertyChangeScope.d.ts +3 -2
- package/dist/prosemirror/commands/propertyChangeScope.js +24 -3
- package/dist/prosemirror/commands/resolveAllTableChanges.js +46 -41
- package/dist/prosemirror/commands/resolveParagraphProperties.js +20 -6
- package/dist/prosemirror/commands/tableCellMergeResolution.d.ts +8 -1
- package/dist/prosemirror/commands/tableCellMergeResolution.js +23 -7
- package/dist/prosemirror/containerFinalParagraph.d.ts +11 -5
- package/dist/prosemirror/containerFinalParagraph.js +7 -6
- package/dist/prosemirror/conversion/fromProseDoc.d.ts +2 -13
- package/dist/prosemirror/conversion/fromProseDoc.js +12 -590
- package/dist/prosemirror/conversion/toProseDoc.d.ts +4 -11
- package/dist/prosemirror/conversion/toProseDoc.js +31 -150
- package/dist/prosemirror/documentSchema.d.ts +8 -0
- package/dist/prosemirror/documentSchema.js +18 -0
- package/dist/prosemirror/extensions/StarterKit.js +2 -0
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +104 -63
- package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +1 -1
- package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.js +24 -3
- package/dist/prosemirror/extensions/features/JoinedRunStyleExtension.d.ts +20 -0
- package/dist/prosemirror/extensions/features/JoinedRunStyleExtension.js +43 -0
- package/dist/prosemirror/extensions/features/ListExtension.js +10 -9
- package/dist/prosemirror/extensions/features/pastedHeadingStyles.js +1 -1
- package/dist/prosemirror/extensions/features/pastedHtmlLists.js +2 -4
- package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +14 -8
- package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +8 -4
- package/dist/prosemirror/extensions/marks/StrikeExtension.js +2 -2
- package/dist/prosemirror/extensions/marks/SubscriptExtension.js +2 -2
- package/dist/prosemirror/extensions/marks/SuperscriptExtension.js +2 -2
- package/dist/prosemirror/extensions/marks/TextColorExtension.js +4 -3
- package/dist/prosemirror/extensions/marks/markUtils.d.ts +8 -6
- package/dist/prosemirror/extensions/marks/markUtils.js +17 -8
- package/dist/prosemirror/extensions/marks/noteReferenceDeletion.d.ts +3 -1
- package/dist/prosemirror/extensions/marks/noteReferenceDeletion.js +12 -1
- package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.js +2 -2
- package/dist/prosemirror/extensions/nodes/HardBreakExtension.js +5 -3
- package/dist/prosemirror/extensions/nodes/TableExtension.js +104 -210
- package/dist/prosemirror/extensions/types.d.ts +7 -0
- package/dist/prosemirror/hyperlinkRemoval.d.ts +9 -0
- package/dist/prosemirror/hyperlinkRemoval.js +129 -0
- package/dist/prosemirror/index.d.ts +2 -1
- package/dist/prosemirror/index.js +2 -1
- package/dist/prosemirror/listNumbering.d.ts +3 -21
- package/dist/prosemirror/listNumbering.js +10 -25
- package/dist/prosemirror/listRendering.d.ts +27 -0
- package/dist/prosemirror/listRendering.js +27 -0
- package/dist/prosemirror/markupViewNotes.d.ts +8 -0
- package/dist/prosemirror/markupViewNotes.js +49 -0
- package/dist/prosemirror/markupViewProjection.d.ts +3 -1
- package/dist/prosemirror/markupViewProjection.js +7 -2
- package/dist/prosemirror/noteReferenceReview.d.ts +77 -0
- package/dist/prosemirror/noteReferenceReview.js +332 -0
- package/dist/prosemirror/paragraphIndentation.js +5 -2
- package/dist/prosemirror/paragraphMarkJoin.d.ts +6 -4
- package/dist/prosemirror/paragraphMarkJoin.js +21 -19
- package/dist/prosemirror/paragraphPropertyCarry.d.ts +64 -0
- package/dist/prosemirror/paragraphPropertyCarry.js +158 -0
- package/dist/prosemirror/plugins/documentStyleState.d.ts +26 -0
- package/dist/prosemirror/plugins/documentStyleState.js +32 -0
- package/dist/prosemirror/plugins/documentStyles.d.ts +2 -21
- package/dist/prosemirror/plugins/documentStyles.js +66 -29
- package/dist/prosemirror/plugins/index.d.ts +2 -1
- package/dist/prosemirror/plugins/index.js +2 -1
- package/dist/prosemirror/plugins/paragraphStyleResolution.d.ts +15 -0
- package/dist/prosemirror/plugins/paragraphStyleResolution.js +230 -0
- package/dist/prosemirror/plugins/suggestionMode.d.ts +12 -2
- package/dist/prosemirror/plugins/suggestionMode.js +391 -28
- package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +22 -2
- package/dist/prosemirror/rebaseParagraphRunFormatting.js +39 -13
- package/dist/prosemirror/rebaseParagraphRuns.d.ts +36 -0
- package/dist/prosemirror/rebaseParagraphRuns.js +99 -0
- package/dist/prosemirror/runFormattingFromMarks.d.ts +18 -0
- package/dist/prosemirror/runFormattingFromMarks.js +567 -0
- package/dist/prosemirror/runFormattingReconciliation.js +10 -9
- package/dist/prosemirror/schema/nodes.d.ts +2 -0
- package/dist/prosemirror/styles/paragraphStyleCascade.d.ts +59 -0
- package/dist/prosemirror/styles/paragraphStyleCascade.js +145 -0
- package/dist/prosemirror/styles/resolvedStyleAttrs.d.ts +21 -2
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +38 -1
- package/dist/prosemirror/styles/tableStyleRegions.d.ts +31 -0
- package/dist/prosemirror/styles/tableStyleRegions.js +55 -0
- package/dist/prosemirror/tableCellPaste.d.ts +74 -0
- package/dist/prosemirror/tableCellPaste.js +525 -0
- package/dist/prosemirror/tableGridMutation.d.ts +53 -14
- package/dist/prosemirror/tableGridMutation.js +166 -18
- package/dist/prosemirror/tableRunIn.d.ts +51 -0
- package/dist/prosemirror/tableRunIn.js +165 -0
- package/dist/prosemirror/textInput.js +11 -2
- package/dist/prosemirror/trackedRevisionPath.d.ts +17 -0
- package/dist/prosemirror/trackedRevisionPath.js +32 -0
- package/dist/server.d.ts +2 -2
- package/package.json +5 -5
package/dist/ai-edits/read.js
CHANGED
|
@@ -2,6 +2,7 @@ import { expectRunPropertyChangeMarkAttrs, expectTrackedChangeMarkAttrs } from "
|
|
|
2
2
|
import { getFolioNodeRevisionCarriers } from "../prosemirror/revisionCarriers.js";
|
|
3
3
|
import { expandRunFormattingCarrier, runFormattingCarrierReviewText, runFormattingInlineAtomDisposition } from "../prosemirror/runFormattingInlineCarriers.js";
|
|
4
4
|
import { getTableCellMergeChange } from "../prosemirror/tableCellMergeRevision.js";
|
|
5
|
+
import { enclosingInsertionAncestors, trackedRevisionLayerOf } from "../prosemirror/trackedRevisionPath.js";
|
|
5
6
|
import { readerTextOf } from "./note-references.js";
|
|
6
7
|
import { collectNoteReferenceLabels, createFolioAIEditSnapshot, sourceDocumentOf } from "./snapshot.js";
|
|
7
8
|
//#region src/ai-edits/read.ts
|
|
@@ -53,16 +54,29 @@ const firstBlockIdWithin = ({ node, nodePos, blockStarts }) => {
|
|
|
53
54
|
};
|
|
54
55
|
const belongsToRowRevision = (scope, revision) => revision.id === scope.change.id || revision.author === scope.change.author && revision.date === scope.change.date;
|
|
55
56
|
/** Shared revision interpreter; a null block map omits unrelated block-id projection. */
|
|
56
|
-
const
|
|
57
|
+
const getTrackedChangeGroupsFromProjectedDoc = (doc, blockStarts) => {
|
|
57
58
|
const insertionType = doc.type.schema.marks["insertion"];
|
|
58
59
|
const deletionType = doc.type.schema.marks["deletion"];
|
|
59
60
|
const grouped = /* @__PURE__ */ new Map();
|
|
61
|
+
const inlineRanges = /* @__PURE__ */ new Map();
|
|
62
|
+
const lastInlineGroupByPath = /* @__PURE__ */ new Map();
|
|
63
|
+
const transparentInlineEnds = /* @__PURE__ */ new Map();
|
|
64
|
+
const transparentGap = (start, end) => {
|
|
65
|
+
let pos = start;
|
|
66
|
+
while (pos < end) {
|
|
67
|
+
const next = transparentInlineEnds.get(pos);
|
|
68
|
+
if (next === void 0) return false;
|
|
69
|
+
pos = next;
|
|
70
|
+
}
|
|
71
|
+
return pos === end;
|
|
72
|
+
};
|
|
60
73
|
const structuralChangeCellPositions = /* @__PURE__ */ new Map();
|
|
61
74
|
const rowRevisionScopes = [];
|
|
62
75
|
const derivedFormattingRepresentationPositions = /* @__PURE__ */ new Set();
|
|
63
76
|
const noteReferences = collectNoteReferenceLabels(doc);
|
|
64
77
|
let currentBlockId = null;
|
|
65
78
|
doc.descendants((node, pos) => {
|
|
79
|
+
if (node.type.name === "commentReference") transparentInlineEnds.set(pos, pos + node.nodeSize);
|
|
66
80
|
while (pos >= (rowRevisionScopes.at(-1)?.end ?? Number.POSITIVE_INFINITY)) rowRevisionScopes.pop();
|
|
67
81
|
const nodeRevisionCarriers = getFolioNodeRevisionCarriers(node, pos);
|
|
68
82
|
if (nodeRevisionCarriers.length > 0) {
|
|
@@ -204,70 +218,106 @@ const getTrackedChangesFromProjectedDoc = (doc, blockStarts) => {
|
|
|
204
218
|
}
|
|
205
219
|
}
|
|
206
220
|
const text = readerTextOf(node, noteReferences);
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
221
|
+
const insertionMark = node.marks.find((mark) => mark.type === insertionType);
|
|
222
|
+
const deletionMark = node.marks.find((mark) => mark.type === deletionType);
|
|
223
|
+
const mark = deletionMark ?? insertionMark;
|
|
224
|
+
if (!mark || typeof mark.attrs["revisionId"] !== "number") return;
|
|
225
|
+
const attrs = expectTrackedChangeMarkAttrs(mark);
|
|
226
|
+
const pathLayers = [...enclosingInsertionAncestors({
|
|
227
|
+
insertionMark: deletionMark ? insertionMark : void 0,
|
|
228
|
+
ancestors: attrs._docxRevisionAncestors ?? [],
|
|
229
|
+
node
|
|
230
|
+
}), trackedRevisionLayerOf(mark, node)];
|
|
231
|
+
const layers = pathLayers.map((layer) => ({
|
|
232
|
+
kind: layer.type === "insertion" || layer.type === "moveTo" ? "insertion" : "deletion",
|
|
233
|
+
revision: {
|
|
234
|
+
id: layer.revisionId,
|
|
235
|
+
author: layer.author,
|
|
236
|
+
date: layer.date ?? null
|
|
237
|
+
}
|
|
238
|
+
}));
|
|
239
|
+
for (const [layerIndex, { kind: layerKind, revision }] of layers.entries()) {
|
|
240
|
+
const rowScope = rowRevisionScopes.at(-1);
|
|
241
|
+
if (rowScope?.kind === layerKind && belongsToRowRevision(rowScope, revision)) continue;
|
|
242
|
+
const path = `${currentBlockId ?? ""}:${JSON.stringify(pathLayers.slice(0, layerIndex + 1))}`;
|
|
243
|
+
const previousKey = lastInlineGroupByPath.get(path);
|
|
244
|
+
const previousRange = previousKey === void 0 ? void 0 : inlineRanges.get(previousKey);
|
|
245
|
+
const key = previousKey !== void 0 && previousRange && transparentGap(previousRange.end, pos) ? previousKey : `${path}:${pos}`;
|
|
246
|
+
lastInlineGroupByPath.set(path, key);
|
|
247
|
+
const hyperlink = node.marks.find((candidate) => candidate.type.name === "hyperlink");
|
|
248
|
+
const commentId = node.marks.find((candidate) => candidate.type.name === "comment")?.attrs["commentId"];
|
|
249
|
+
const boundary = {
|
|
250
|
+
hyperlink: hyperlink ? String(hyperlink.attrs["href"] ?? "") : null,
|
|
251
|
+
comment: typeof commentId === "number" ? commentId : null,
|
|
252
|
+
revisionParents: JSON.stringify(pathLayers.slice(0, layerIndex))
|
|
253
|
+
};
|
|
254
|
+
const range = inlineRanges.get(key);
|
|
255
|
+
if (range) {
|
|
256
|
+
range.end = pos + node.nodeSize;
|
|
257
|
+
range.lastBoundary = boundary;
|
|
258
|
+
} else inlineRanges.set(key, {
|
|
259
|
+
start: pos,
|
|
260
|
+
end: pos + node.nodeSize,
|
|
261
|
+
firstBoundary: boundary,
|
|
262
|
+
lastBoundary: boundary
|
|
263
|
+
});
|
|
264
|
+
const existing = grouped.get(key);
|
|
265
|
+
if (existing) {
|
|
266
|
+
existing.text += text;
|
|
267
|
+
continue;
|
|
246
268
|
}
|
|
269
|
+
grouped.set(key, {
|
|
270
|
+
...revision,
|
|
271
|
+
type: layerKind,
|
|
272
|
+
text,
|
|
273
|
+
blockId: currentBlockId
|
|
274
|
+
});
|
|
247
275
|
}
|
|
248
276
|
});
|
|
249
|
-
|
|
277
|
+
const groups = [];
|
|
278
|
+
let previousKey = null;
|
|
279
|
+
for (const [key, change] of grouped) {
|
|
280
|
+
const range = inlineRanges.get(key);
|
|
281
|
+
const previous = groups.at(-1);
|
|
282
|
+
const previousRange = previousKey === null ? void 0 : inlineRanges.get(previousKey);
|
|
283
|
+
if (previous && range && previousRange && previousRange.end <= range.start && transparentGap(previousRange.end, range.start) && previousRange.lastBoundary.revisionParents === range.firstBoundary.revisionParents && (previousRange.lastBoundary.hyperlink !== range.firstBoundary.hyperlink || previousRange.lastBoundary.comment !== range.firstBoundary.comment || previousRange.end !== range.start) && (change.type === "insertion" || change.type === "deletion") && previous.change.type === change.type && previous.change.author === change.author && previous.change.date === change.date && previous.change.blockId === change.blockId) {
|
|
284
|
+
previous.change.text += change.text;
|
|
285
|
+
previous.ids.push(change.id);
|
|
286
|
+
previousRange.end = range.end;
|
|
287
|
+
previousRange.lastBoundary = range.lastBoundary;
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
groups.push({
|
|
291
|
+
change: { ...change },
|
|
292
|
+
ids: [change.id]
|
|
293
|
+
});
|
|
294
|
+
previousKey = key;
|
|
295
|
+
}
|
|
296
|
+
return groups;
|
|
250
297
|
};
|
|
298
|
+
const getTrackedChangesFromProjectedDoc = (doc, blockStarts) => getTrackedChangeGroupsFromProjectedDoc(doc, blockStarts).map(({ change }) => change);
|
|
251
299
|
/** Read revisions from the exact immutable document that produced a snapshot. */
|
|
252
300
|
const getTrackedChangesFromSnapshot = (snapshot) => getTrackedChangesFromProjectedDoc(sourceDocumentOf(snapshot), blockStartIdsFromSnapshot(snapshot));
|
|
253
301
|
/**
|
|
254
302
|
* The tracked changes present in the body, read from inline marks and
|
|
255
|
-
* structural node attributes.
|
|
256
|
-
* into
|
|
303
|
+
* structural node attributes. Adjacent runs sharing a revision wrapper path
|
|
304
|
+
* fold into one entry; interrupted or differently nested wrappers stay separate.
|
|
257
305
|
*
|
|
258
306
|
* A tracked row insertion or deletion marks the row AND every run in its
|
|
259
307
|
* cells, the way Word writes it. Both halves are one change, so the run marks
|
|
260
308
|
* fold into the row's entry rather than reporting a second time.
|
|
261
309
|
*/
|
|
262
310
|
const getTrackedChangesFromDoc = (doc) => getTrackedChangesFromProjectedDoc(doc, blockStartIdsFromDoc(doc));
|
|
311
|
+
/** Physical revision ids behind an inline change split by markup. */
|
|
312
|
+
const getTrackedChangeGroupIdsFromDoc = (doc, id) => getTrackedChangeGroupsFromProjectedDoc(doc, blockStartIdsFromDoc(doc)).find(({ ids }) => ids.includes(id))?.ids ?? [id];
|
|
263
313
|
/** @internal Read revision statistics without paying for an unrelated block projection. */
|
|
264
314
|
const getTrackedChangeStatsFromDoc = (doc) => {
|
|
265
315
|
let highestId = 0;
|
|
266
|
-
const
|
|
267
|
-
for (const
|
|
316
|
+
const groups = getTrackedChangeGroupsFromProjectedDoc(doc, null);
|
|
317
|
+
for (const { ids } of groups) for (const id of ids) highestId = Math.max(highestId, id);
|
|
268
318
|
return {
|
|
269
319
|
highestId,
|
|
270
|
-
present:
|
|
320
|
+
present: groups.length > 0
|
|
271
321
|
};
|
|
272
322
|
};
|
|
273
323
|
/**
|
|
@@ -313,4 +363,4 @@ const getCommentAnchorsFromDoc = (doc) => {
|
|
|
313
363
|
}));
|
|
314
364
|
};
|
|
315
365
|
//#endregion
|
|
316
|
-
export { FOLIO_REVIEW_CHANGE_KINDS, getCommentAnchorsFromDoc, getTrackedChangeStatsFromDoc, getTrackedChangesFromDoc, getTrackedChangesFromSnapshot };
|
|
366
|
+
export { FOLIO_REVIEW_CHANGE_KINDS, getCommentAnchorsFromDoc, getTrackedChangeGroupIdsFromDoc, getTrackedChangeStatsFromDoc, getTrackedChangesFromDoc, getTrackedChangesFromSnapshot };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Transaction } from "prosemirror-state";
|
|
2
|
+
//#region src/ai-edits/revisionStretches.d.ts
|
|
3
|
+
type SeparateRevisionStretchesOptions = {
|
|
4
|
+
tr: Transaction;
|
|
5
|
+
revisionSeed: number;
|
|
6
|
+
};
|
|
7
|
+
type SeparatedRevisionStretches = {
|
|
8
|
+
nextRevisionId: number;
|
|
9
|
+
/** Each fresh id, with the revision whose stretch it now names. */
|
|
10
|
+
minted: {
|
|
11
|
+
revisionId: number;
|
|
12
|
+
from: number;
|
|
13
|
+
}[];
|
|
14
|
+
};
|
|
15
|
+
declare const separateRevisionStretches: ({ tr, revisionSeed }: SeparateRevisionStretchesOptions) => SeparatedRevisionStretches;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { SeparatedRevisionStretches, separateRevisionStretches };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { expectTrackedChangeMarkAttrs } from "../prosemirror/attrs/index.js";
|
|
2
|
+
//#region src/ai-edits/revisionStretches.ts
|
|
3
|
+
const INLINE_REVISION_MARKS = /* @__PURE__ */ new Set(["insertion", "deletion"]);
|
|
4
|
+
const separateRevisionStretches = ({ tr, revisionSeed }) => {
|
|
5
|
+
let nextRevisionId = revisionSeed;
|
|
6
|
+
const minted = [];
|
|
7
|
+
const retargets = [];
|
|
8
|
+
let stretches = /* @__PURE__ */ new Map();
|
|
9
|
+
tr.doc.descendants((node, pos) => {
|
|
10
|
+
if (node.isTextblock) {
|
|
11
|
+
stretches = /* @__PURE__ */ new Map();
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
if (!node.isInline || !(node.isLeaf || node.isText)) return true;
|
|
15
|
+
const layersOf = (mark) => {
|
|
16
|
+
const attrs = expectTrackedChangeMarkAttrs(mark);
|
|
17
|
+
return [...(attrs._docxRevisionAncestors ?? []).flatMap((ancestor) => ancestor.type === "insertion" || ancestor.type === "deletion" ? [{
|
|
18
|
+
kind: ancestor.type,
|
|
19
|
+
revisionId: ancestor.revisionId
|
|
20
|
+
}] : []), {
|
|
21
|
+
kind: mark.type.name,
|
|
22
|
+
revisionId: attrs.revisionId
|
|
23
|
+
}];
|
|
24
|
+
};
|
|
25
|
+
const revisionMarks = node.marks.filter((mark) => INLINE_REVISION_MARKS.has(mark.type.name) && expectTrackedChangeMarkAttrs(mark).moveKind === void 0);
|
|
26
|
+
const carried = new Set(revisionMarks.flatMap((mark) => layersOf(mark).map(({ kind, revisionId }) => `${kind}:${String(revisionId)}`)));
|
|
27
|
+
if (node.type.name !== "commentReference") {
|
|
28
|
+
for (const [key, stretch] of stretches) if (!carried.has(key)) stretch.interrupted = true;
|
|
29
|
+
}
|
|
30
|
+
for (const mark of revisionMarks) {
|
|
31
|
+
const ids = /* @__PURE__ */ new Map();
|
|
32
|
+
for (const { kind, revisionId } of layersOf(mark)) {
|
|
33
|
+
const key = `${kind}:${String(revisionId)}`;
|
|
34
|
+
let stretch = stretches.get(key);
|
|
35
|
+
if (!stretch) {
|
|
36
|
+
stretch = {
|
|
37
|
+
id: revisionId,
|
|
38
|
+
interrupted: false
|
|
39
|
+
};
|
|
40
|
+
stretches.set(key, stretch);
|
|
41
|
+
} else if (stretch.interrupted) {
|
|
42
|
+
const fresh = nextRevisionId++;
|
|
43
|
+
minted.push({
|
|
44
|
+
revisionId: fresh,
|
|
45
|
+
from: revisionId
|
|
46
|
+
});
|
|
47
|
+
stretch = {
|
|
48
|
+
id: fresh,
|
|
49
|
+
interrupted: false
|
|
50
|
+
};
|
|
51
|
+
stretches.set(key, stretch);
|
|
52
|
+
}
|
|
53
|
+
if (stretch.id !== revisionId) ids.set(revisionId, stretch.id);
|
|
54
|
+
}
|
|
55
|
+
if (ids.size > 0) retargets.push({
|
|
56
|
+
from: pos,
|
|
57
|
+
to: pos + node.nodeSize,
|
|
58
|
+
mark,
|
|
59
|
+
ids
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return true;
|
|
63
|
+
});
|
|
64
|
+
for (const { from, to, mark, ids } of retargets) {
|
|
65
|
+
const attrs = expectTrackedChangeMarkAttrs(mark);
|
|
66
|
+
const ancestors = attrs._docxRevisionAncestors;
|
|
67
|
+
tr.removeMark(from, to, mark);
|
|
68
|
+
tr.addMark(from, to, mark.type.create({
|
|
69
|
+
...mark.attrs,
|
|
70
|
+
revisionId: ids.get(attrs.revisionId) ?? attrs.revisionId,
|
|
71
|
+
...ancestors && { _docxRevisionAncestors: ancestors.map((ancestor) => Object.assign(structuredClone(ancestor), { revisionId: ids.get(ancestor.revisionId) ?? ancestor.revisionId })) }
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
nextRevisionId,
|
|
76
|
+
minted
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
//#endregion
|
|
80
|
+
export { separateRevisionStretches };
|
|
@@ -4,13 +4,13 @@ import { resolveParagraphNumbering } from "../docx/numberingReference.js";
|
|
|
4
4
|
import { OPAQUE_REVISION_CARRIER_READER_DIAGNOSTIC, isOpaqueNestedRowMarkup, opaqueRevisionCarrierName } from "../docx/opaqueCarrier.js";
|
|
5
5
|
import { recreateProseNodeWithParagraphPropertySource } from "../docx/paragraphPropertySource.js";
|
|
6
6
|
import { expectCharacterStyleMarkAttrs, expectHyperlinkMarkAttrs, expectParagraphAttrs, expectRunFormattingOverrideMarkAttrs } from "../prosemirror/attrs/index.js";
|
|
7
|
-
import { marksToTextFormatting } from "../prosemirror/conversion/fromProseDoc.js";
|
|
8
7
|
import { createListLabelCounter } from "../prosemirror/listLabels.js";
|
|
9
8
|
import { paragraphNumberingAttr, readParagraphNumberingAttr } from "../prosemirror/numberingAttr.js";
|
|
10
9
|
import { readOutlineLevelAttr } from "../prosemirror/outlineLevelAttr.js";
|
|
11
10
|
import { directParagraphAlignment } from "../prosemirror/paragraphAlignment.js";
|
|
12
11
|
import { directParagraphIndentation } from "../prosemirror/paragraphIndentation.js";
|
|
13
12
|
import { directParagraphSpacing } from "../prosemirror/paragraphSpacing.js";
|
|
13
|
+
import { marksToTextFormatting } from "../prosemirror/runFormattingFromMarks.js";
|
|
14
14
|
import { runFormattingInlineAtomCleanText } from "../prosemirror/runFormattingInlineCarriers.js";
|
|
15
15
|
import { authoredRunFormattingFromAttrs } from "../prosemirror/runFormattingProvenance.js";
|
|
16
16
|
import { readAuthoredRunFormatting, reconcileRunFormattingMarks } from "../prosemirror/runFormattingReconciliation.js";
|
|
@@ -748,7 +748,8 @@ const getDefaultPreviewRunStyle = (node, styleResolver) => {
|
|
|
748
748
|
};
|
|
749
749
|
};
|
|
750
750
|
const getPreviewRunStyle = (marks, defaultStyle) => {
|
|
751
|
-
const
|
|
751
|
+
const { bold: _bold, italic: _italic, underline: _underline, strike: _strike, ...inherited } = defaultStyle;
|
|
752
|
+
const style = { ...inherited };
|
|
752
753
|
for (const mark of marks) switch (mark.type.name) {
|
|
753
754
|
case "bold":
|
|
754
755
|
style.bold = true;
|
|
@@ -17,5 +17,7 @@ declare const mergeTableRectangle: ({ tr, tablePosition, table, rectangle }: Tab
|
|
|
17
17
|
declare const mergeTrackedVerticalTableCells: ({ tr, tablePosition, table, rectangle, revisionId, author, date }: TrackedTableCellRectangleMutationOptions) => Transaction | null;
|
|
18
18
|
declare const splitTableRectangle: ({ tr, tablePosition, table, rectangle }: TableCellRectangleMutationOptions) => Transaction | null;
|
|
19
19
|
declare const splitTrackedVerticalTableCell: ({ tr, tablePosition, table, rectangle, revisionId, author, date }: TrackedTableCellRectangleMutationOptions) => Transaction | null;
|
|
20
|
+
/** A cell holding one empty paragraph: all a tracked merge may fold away. */
|
|
21
|
+
declare const isEmptyTableCell: (cell: Node) => boolean;
|
|
20
22
|
//#endregion
|
|
21
|
-
export { mergeTableRectangle, mergeTrackedVerticalTableCells, splitTableRectangle, splitTrackedVerticalTableCell };
|
|
23
|
+
export { isEmptyTableCell, mergeTableRectangle, mergeTrackedVerticalTableCells, splitTableRectangle, splitTrackedVerticalTableCell };
|
|
@@ -259,6 +259,7 @@ const splitTrackedVerticalTableCell = ({ tr, tablePosition, table, rectangle, re
|
|
|
259
259
|
markStructuralChange(tr);
|
|
260
260
|
return tr;
|
|
261
261
|
};
|
|
262
|
+
/** A cell holding one empty paragraph: all a tracked merge may fold away. */
|
|
262
263
|
const isEmptyTableCell = (cell) => cell.childCount === 1 && cell.firstChild?.isTextblock === true && cell.firstChild.childCount === 0;
|
|
263
264
|
const trackedSplitCellFromStoredSource = ({ origin, source, marker }) => {
|
|
264
265
|
if (source.formatting?.gridSpan !== void 0 && source.formatting.gridSpan > 1) return null;
|
|
@@ -286,4 +287,4 @@ const formattingWithoutVerticalMerge = (formatting) => {
|
|
|
286
287
|
return next;
|
|
287
288
|
};
|
|
288
289
|
//#endregion
|
|
289
|
-
export { mergeTableRectangle, mergeTrackedVerticalTableCells, splitTableRectangle, splitTrackedVerticalTableCell };
|
|
290
|
+
export { isEmptyTableCell, mergeTableRectangle, mergeTrackedVerticalTableCells, splitTableRectangle, splitTrackedVerticalTableCell };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { TableStructureRevision } from "./table-template.js";
|
|
2
|
+
import { RowsEmptiedInBatch } from "../prosemirror/tableGridMutation.js";
|
|
2
3
|
import { TableRowTarget } from "./table-targets.js";
|
|
3
4
|
import { Node } from "prosemirror-model";
|
|
4
5
|
import { Transaction } from "prosemirror-state";
|
|
@@ -30,6 +31,8 @@ type TableRowColumnMutationResult = {
|
|
|
30
31
|
type: "applied";
|
|
31
32
|
transaction: Transaction;
|
|
32
33
|
revisionId: number | null;
|
|
34
|
+
/** Every revision written: a column change writes one per cell. */
|
|
35
|
+
revisionIds: readonly number[];
|
|
33
36
|
} | {
|
|
34
37
|
type: "unsupported";
|
|
35
38
|
};
|
|
@@ -117,8 +120,10 @@ type ApplyTableColumnDeletionOptions = {
|
|
|
117
120
|
deletion: TableColumnDeletion;
|
|
118
121
|
insertedColumnCount: number;
|
|
119
122
|
revision: TableStructureRevision | null;
|
|
123
|
+
/** Where to defer closing rows the deletion leaves without cells, when it runs in a batch. */
|
|
124
|
+
emptiedRows?: RowsEmptiedInBatch;
|
|
120
125
|
};
|
|
121
|
-
declare const applyTableColumnDeletion: ({ tr, deletion, insertedColumnCount, revision }: ApplyTableColumnDeletionOptions) => TableRowColumnMutationResult;
|
|
126
|
+
declare const applyTableColumnDeletion: ({ tr, deletion, insertedColumnCount, revision, emptiedRows }: ApplyTableColumnDeletionOptions) => TableRowColumnMutationResult;
|
|
122
127
|
type ApplyTableRowDeletionOptions = {
|
|
123
128
|
tr: Transaction;
|
|
124
129
|
deletion: TableRowDeletion;
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import { markStructuralChange } from "../prosemirror/extensions/features/ParagraphChangeTrackerExtension.js";
|
|
2
|
-
import { reconcileTableGridAfterColumnInsertion, reconcileTableGridAfterColumnRemoval, removeRowsWithoutCells, removeTableRow } from "../prosemirror/tableGridMutation.js";
|
|
2
|
+
import { markRowsEmptiedInBatch, reconcileTableGridAfterColumnInsertion, reconcileTableGridAfterColumnRemoval, removeRowsWithoutCells, removeTableRow } from "../prosemirror/tableGridMutation.js";
|
|
3
3
|
import { stripBlockIdentityAttrs } from "./block-identity.js";
|
|
4
4
|
import { findEnclosingTableCell, findEnclosingTableRow } from "./table-targets.js";
|
|
5
5
|
import { tableRowFromTemplate } from "./table-template.js";
|
|
6
6
|
import { TableMap, columnIsHeader, removeColumn, rowIsHeader, tableNodeTypes } from "prosemirror-tables";
|
|
7
7
|
//#region src/ai-edits/table-row-column-mutations.ts
|
|
8
|
+
/**
|
|
9
|
+
* The revision of the `index`th cell a column change marks. The save writes
|
|
10
|
+
* each cell's marker as a revision of its own, and each is accepted or
|
|
11
|
+
* rejected on its own; the reviewer's revisions are those from the start.
|
|
12
|
+
*/
|
|
13
|
+
const cellRevision = (revision, index) => ({
|
|
14
|
+
...revision,
|
|
15
|
+
revisionId: revision.revisionId + index
|
|
16
|
+
});
|
|
8
17
|
const UNPLACED_TEXT_PREVIEW_LENGTH = 40;
|
|
9
18
|
const previewCellText = (text) => JSON.stringify(text.length > UNPLACED_TEXT_PREVIEW_LENGTH ? `${text.slice(0, UNPLACED_TEXT_PREVIEW_LENGTH)}…` : text);
|
|
10
19
|
/**
|
|
@@ -165,6 +174,7 @@ const applyTableColumnInsertion = ({ tr, insertion, cellTexts, revision }) => {
|
|
|
165
174
|
});
|
|
166
175
|
if (!actions || revision && actions.some(({ type }) => type === "setColspan")) return { type: "unsupported" };
|
|
167
176
|
const mapFrom = tr.mapping.maps.length;
|
|
177
|
+
let markedCells = 0;
|
|
168
178
|
for (const action of actions) {
|
|
169
179
|
const position = tr.mapping.slice(mapFrom).map(tablePosition + 1 + action.position);
|
|
170
180
|
if (action.type === "setColspan") {
|
|
@@ -175,7 +185,7 @@ const applyTableColumnInsertion = ({ tr, insertion, cellTexts, revision }) => {
|
|
|
175
185
|
...action.cell.attrs,
|
|
176
186
|
cellMarker: {
|
|
177
187
|
kind: "ins",
|
|
178
|
-
info: revision
|
|
188
|
+
info: cellRevision(revision, markedCells++)
|
|
179
189
|
}
|
|
180
190
|
}, action.cell.content) : action.cell;
|
|
181
191
|
tr.insert(position, cell);
|
|
@@ -186,9 +196,9 @@ const applyTableColumnInsertion = ({ tr, insertion, cellTexts, revision }) => {
|
|
|
186
196
|
previousTable: table,
|
|
187
197
|
insertedColumn: insertion.columnIndex
|
|
188
198
|
});
|
|
189
|
-
return applied(tr, revision);
|
|
199
|
+
return applied(tr, revision, markedCells);
|
|
190
200
|
};
|
|
191
|
-
const applyTableColumnDeletion = ({ tr, deletion, insertedColumnCount, revision }) => {
|
|
201
|
+
const applyTableColumnDeletion = ({ tr, deletion, insertedColumnCount, revision, emptiedRows }) => {
|
|
192
202
|
const tablePosition = tr.mapping.map(deletion.tablePosition, 1);
|
|
193
203
|
const table = tr.doc.nodeAt(tablePosition);
|
|
194
204
|
if (!table || table.type.spec["tableRole"] !== "table") return { type: "unsupported" };
|
|
@@ -202,12 +212,12 @@ const applyTableColumnDeletion = ({ tr, deletion, insertedColumnCount, revision
|
|
|
202
212
|
columnIndex
|
|
203
213
|
});
|
|
204
214
|
if (!cellPositions) return { type: "unsupported" };
|
|
205
|
-
for (const cellPosition of cellPositions) tr.setNodeAttribute(tablePosition + 1 + cellPosition, "cellMarker", {
|
|
215
|
+
for (const [index, cellPosition] of cellPositions.entries()) tr.setNodeAttribute(tablePosition + 1 + cellPosition, "cellMarker", {
|
|
206
216
|
kind: "del",
|
|
207
|
-
info: revision
|
|
217
|
+
info: cellRevision(revision, index)
|
|
208
218
|
});
|
|
209
219
|
markStructuralChange(tr);
|
|
210
|
-
return applied(tr, revision);
|
|
220
|
+
return applied(tr, revision, cellPositions.length);
|
|
211
221
|
}
|
|
212
222
|
if (map.width === 1) {
|
|
213
223
|
const transaction = deleteTableNode({
|
|
@@ -232,7 +242,13 @@ const applyTableColumnDeletion = ({ tr, deletion, insertedColumnCount, revision
|
|
|
232
242
|
previousTable: table,
|
|
233
243
|
removedColumn: columnIndex
|
|
234
244
|
});
|
|
235
|
-
|
|
245
|
+
if (emptiedRows) markRowsEmptiedInBatch({
|
|
246
|
+
tr,
|
|
247
|
+
tablePosition,
|
|
248
|
+
previousTable: table,
|
|
249
|
+
emptied: emptiedRows
|
|
250
|
+
});
|
|
251
|
+
else removeRowsWithoutCells(tr, tablePosition);
|
|
236
252
|
return applied(tr, null);
|
|
237
253
|
};
|
|
238
254
|
const applyTableRowDeletion = ({ tr, deletion, revision }) => {
|
|
@@ -269,10 +285,11 @@ const applyTableRowDeletion = ({ tr, deletion, revision }) => {
|
|
|
269
285
|
}, at.index());
|
|
270
286
|
return applied(tr, null);
|
|
271
287
|
};
|
|
272
|
-
const applied = (transaction, revision) => ({
|
|
288
|
+
const applied = (transaction, revision, revisionCount = 1) => ({
|
|
273
289
|
type: "applied",
|
|
274
290
|
transaction,
|
|
275
|
-
revisionId: revision?.revisionId ?? null
|
|
291
|
+
revisionId: revision?.revisionId ?? null,
|
|
292
|
+
revisionIds: revision ? Array.from({ length: revisionCount }, (_, index) => revision.revisionId + index) : []
|
|
276
293
|
});
|
|
277
294
|
const deleteTableNode = ({ tr, tablePosition, table }) => {
|
|
278
295
|
const tableEnd = tablePosition + table.nodeSize;
|
|
@@ -17,6 +17,10 @@ type FolioContentBlockPair = {
|
|
|
17
17
|
baseIndex: number;
|
|
18
18
|
revisedIndex: number;
|
|
19
19
|
};
|
|
20
|
+
type IncreasingPairOptions = {
|
|
21
|
+
priority?: (pair: FolioContentBlockPair) => number;
|
|
22
|
+
tieBreak?: "earliest" | "minimum-tail";
|
|
23
|
+
};
|
|
20
24
|
/**
|
|
21
25
|
* Longest strictly increasing subsequence by revised index.
|
|
22
26
|
*
|
|
@@ -24,7 +28,7 @@ type FolioContentBlockPair = {
|
|
|
24
28
|
* preserving the historical deterministic tie break while reducing the
|
|
25
29
|
* stable-anchor pass from quadratic to O(n log n).
|
|
26
30
|
*/
|
|
27
|
-
declare const longestIncreasingFolioContentPairs: (pairs: readonly FolioContentBlockPair[]) => FolioContentBlockPair[];
|
|
31
|
+
declare const longestIncreasingFolioContentPairs: (pairs: readonly FolioContentBlockPair[], options?: IncreasingPairOptions) => FolioContentBlockPair[];
|
|
28
32
|
type FolioContentAlignedBlockEvent<Block extends FolioContentBlock = FolioContentBlock> = {
|
|
29
33
|
type: "pair";
|
|
30
34
|
baseBlock: Block;
|
|
@@ -45,15 +45,29 @@ const folioContentIdStability = (block) => block.idStability ?? "stable";
|
|
|
45
45
|
* preserving the historical deterministic tie break while reducing the
|
|
46
46
|
* stable-anchor pass from quadratic to O(n log n).
|
|
47
47
|
*/
|
|
48
|
-
const longestIncreasingFolioContentPairs = (pairs) => {
|
|
48
|
+
const longestIncreasingFolioContentPairs = (pairs, options = {}) => {
|
|
49
49
|
if (pairs.length === 0) return [];
|
|
50
50
|
const coordinates = [...new Set(pairs.map(({ revisedIndex }) => revisedIndex))].toSorted((left, right) => left - right);
|
|
51
51
|
const rankByCoordinate = new Map(coordinates.map((coordinate, index) => [coordinate, index + 1]));
|
|
52
|
-
const treeLengths = new Int32Array(coordinates.length + 1);
|
|
53
52
|
const treeIndexes = new Int32Array(coordinates.length + 1).fill(-1);
|
|
54
53
|
const lengths = new Int32Array(pairs.length).fill(1);
|
|
55
54
|
const predecessors = new Int32Array(pairs.length).fill(-1);
|
|
56
|
-
const
|
|
55
|
+
const priorities = new Int32Array(pairs.length);
|
|
56
|
+
const earlier = ({ leftIndex, rightIndex }) => {
|
|
57
|
+
const leftLength = lengths[leftIndex] ?? 0;
|
|
58
|
+
const rightLength = lengths[rightIndex] ?? 0;
|
|
59
|
+
if (leftLength !== rightLength) return leftLength > rightLength;
|
|
60
|
+
if (leftLength === 0) return false;
|
|
61
|
+
const leftPriority = priorities[leftIndex] ?? 0;
|
|
62
|
+
const rightPriority = priorities[rightIndex] ?? 0;
|
|
63
|
+
if (leftPriority !== rightPriority) return leftPriority > rightPriority;
|
|
64
|
+
if (options.tieBreak === "minimum-tail") {
|
|
65
|
+
const leftTail = pairs[leftIndex]?.revisedIndex ?? Number.POSITIVE_INFINITY;
|
|
66
|
+
const rightTail = pairs[rightIndex]?.revisedIndex ?? Number.POSITIVE_INFINITY;
|
|
67
|
+
if (leftTail !== rightTail) return leftTail < rightTail;
|
|
68
|
+
}
|
|
69
|
+
return leftIndex < rightIndex;
|
|
70
|
+
};
|
|
57
71
|
let bestEnd = 0;
|
|
58
72
|
let groupStart = 0;
|
|
59
73
|
while (groupStart < pairs.length) {
|
|
@@ -63,32 +77,38 @@ const longestIncreasingFolioContentPairs = (pairs) => {
|
|
|
63
77
|
for (let pairIndex = groupStart; pairIndex < groupEnd; pairIndex++) {
|
|
64
78
|
const pair = pairs[pairIndex];
|
|
65
79
|
const rank = pair === void 0 ? void 0 : rankByCoordinate.get(pair.revisedIndex);
|
|
66
|
-
if (rank === void 0) continue;
|
|
80
|
+
if (rank === void 0 || pair === void 0) continue;
|
|
67
81
|
let predecessorLength = 0;
|
|
68
82
|
let predecessorIndex = -1;
|
|
69
83
|
for (let cursor = rank - 1; cursor > 0; cursor -= cursor & -cursor) {
|
|
70
|
-
const candidateLength = treeLengths[cursor] ?? 0;
|
|
71
84
|
const candidateIndex = treeIndexes[cursor] ?? -1;
|
|
72
|
-
|
|
85
|
+
const candidateLength = lengths[candidateIndex] ?? 0;
|
|
86
|
+
if (earlier({
|
|
87
|
+
leftIndex: candidateIndex,
|
|
88
|
+
rightIndex: predecessorIndex
|
|
89
|
+
})) {
|
|
73
90
|
predecessorLength = candidateLength;
|
|
74
91
|
predecessorIndex = candidateIndex;
|
|
75
92
|
}
|
|
76
93
|
}
|
|
77
94
|
lengths[pairIndex] = predecessorLength + 1;
|
|
78
95
|
predecessors[pairIndex] = predecessorIndex;
|
|
79
|
-
|
|
96
|
+
priorities[pairIndex] = (priorities[predecessorIndex] ?? 0) + (options.priority?.(pair) ?? 0);
|
|
97
|
+
if (earlier({
|
|
98
|
+
leftIndex: pairIndex,
|
|
99
|
+
rightIndex: bestEnd
|
|
100
|
+
})) bestEnd = pairIndex;
|
|
80
101
|
}
|
|
81
102
|
for (let pairIndex = groupStart; pairIndex < groupEnd; pairIndex++) {
|
|
82
103
|
const pair = pairs[pairIndex];
|
|
83
104
|
const rank = pair === void 0 ? void 0 : rankByCoordinate.get(pair.revisedIndex);
|
|
84
105
|
if (rank === void 0) continue;
|
|
85
|
-
for (let cursor = rank; cursor <
|
|
86
|
-
const currentLength = treeLengths[cursor] ?? 0;
|
|
106
|
+
for (let cursor = rank; cursor < treeIndexes.length; cursor += cursor & -cursor) {
|
|
87
107
|
const currentIndex = treeIndexes[cursor] ?? -1;
|
|
88
|
-
if (earlier(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
108
|
+
if (earlier({
|
|
109
|
+
leftIndex: pairIndex,
|
|
110
|
+
rightIndex: currentIndex
|
|
111
|
+
})) treeIndexes[cursor] = pairIndex;
|
|
92
112
|
}
|
|
93
113
|
}
|
|
94
114
|
groupStart = groupEnd;
|
|
@@ -238,27 +258,14 @@ const pairByUniqueExactText = ({ base, revised, baseFrom, baseTo, revisedFrom, r
|
|
|
238
258
|
}
|
|
239
259
|
const orderedCandidates = candidates.toSorted((left, right) => left.baseIndex - right.baseIndex || left.revisedIndex - right.revisedIndex);
|
|
240
260
|
if (orderedCandidates.length === 0) return [];
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
while (lower < upper) {
|
|
248
|
-
const middle = lower + Math.floor((upper - lower) / 2);
|
|
249
|
-
if ((tailRevisedIndexes[middle] ?? Number.POSITIVE_INFINITY) < candidate.revisedIndex) lower = middle + 1;
|
|
250
|
-
else upper = middle;
|
|
261
|
+
return longestIncreasingFolioContentPairs(orderedCandidates, {
|
|
262
|
+
tieBreak: "minimum-tail",
|
|
263
|
+
priority: ({ baseIndex, revisedIndex }) => {
|
|
264
|
+
const baseBlock = base[baseIndex];
|
|
265
|
+
const revisedBlock = revised[revisedIndex];
|
|
266
|
+
return baseBlock && revisedBlock && baseBlock.pairingFacts.id === revisedBlock.pairingFacts.id && baseBlock.pairingFacts.idStability !== revisedBlock.pairingFacts.idStability ? 1 : 0;
|
|
251
267
|
}
|
|
252
|
-
predecessors[candidateIndex] = lower === 0 ? -1 : tailCandidateIndexes[lower - 1] ?? -1;
|
|
253
|
-
tailRevisedIndexes[lower] = candidate.revisedIndex;
|
|
254
|
-
tailCandidateIndexes[lower] = candidateIndex;
|
|
255
268
|
});
|
|
256
|
-
const pairs = [];
|
|
257
|
-
for (let candidateIndex = tailCandidateIndexes.at(-1) ?? -1; candidateIndex !== -1; candidateIndex = predecessors[candidateIndex] ?? -1) {
|
|
258
|
-
const candidate = orderedCandidates[candidateIndex];
|
|
259
|
-
if (candidate) pairs.push(candidate);
|
|
260
|
-
}
|
|
261
|
-
return pairs.toReversed();
|
|
262
269
|
};
|
|
263
270
|
/**
|
|
264
271
|
* Unique exact text anchors, then again inside every sub-gap they leave:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { sourceDocumentOf, styleResolverOf } from "../ai-edits/snapshot.js";
|
|
2
2
|
import { expectHyperlinkMarkAttrs, expectRunPropertyChangeMarkAttrs, expectTableCellAttrs } from "../prosemirror/attrs/index.js";
|
|
3
|
-
import { getDocumentStyleResolver } from "../prosemirror/plugins/
|
|
3
|
+
import { getDocumentStyleResolver } from "../prosemirror/plugins/documentStyleState.js";
|
|
4
4
|
import { applyMarksToRunFormattingRepresentation, expandRunFormattingCarrier, runFormattingCarrierReviewText } from "../prosemirror/runFormattingInlineCarriers.js";
|
|
5
5
|
import { readAuthoredRunFormatting, reconcileRunFormattingMarks } from "../prosemirror/runFormattingReconciliation.js";
|
|
6
6
|
import { nearestParagraphRunStyleContext } from "../prosemirror/runStyleFormatting.js";
|
|
@@ -30,6 +30,7 @@ import { renderPages } from "../layout-painter/renderPage.js";
|
|
|
30
30
|
import { tryBuildIncrementalMeasures } from "../paged-layout/incrementalMeasure.js";
|
|
31
31
|
import { computePerBlockMeasureInputs } from "../paged-layout/sectionBlockWidths.js";
|
|
32
32
|
import { getMargins, getPageNumbering, getPageSize, twipsToPixels } from "../paged-layout/sectionGeometry.js";
|
|
33
|
+
import { markupViewNotes } from "../prosemirror/markupViewNotes.js";
|
|
33
34
|
import { projectMarkupView } from "../prosemirror/markupViewProjection.js";
|
|
34
35
|
import { templatePreviewValuesKey } from "../prosemirror/plugins/templatePreviewValues.js";
|
|
35
36
|
import { getDocumentWatermark } from "../watermark/index.js";
|
|
@@ -215,10 +216,10 @@ function runLayoutPipelineMeasured(deps, state, options) {
|
|
|
215
216
|
recordPhaseDuration("flow-blocks", phaseStartedAt);
|
|
216
217
|
phaseStartedAt = performance.now();
|
|
217
218
|
const footnoteRefs = collectFootnoteRefs(newBlocks);
|
|
218
|
-
const documentFootnotes = document?.package.footnotes;
|
|
219
|
+
const documentFootnotes = markupViewNotes(document?.package.footnotes, markupView, state, document);
|
|
219
220
|
const hasFootnotes = footnoteRefs.length > 0 && documentFootnotes !== void 0;
|
|
220
221
|
const footnoteDisplayNumbers = documentFootnotes ? computeNoteDisplayNumbers(documentFootnotes, footnoteRefs.map((ref) => ref.footnoteId)) : void 0;
|
|
221
|
-
const documentEndnotes = document?.package.endnotes;
|
|
222
|
+
const documentEndnotes = markupViewNotes(document?.package.endnotes, markupView, state, document);
|
|
222
223
|
const endnoteDisplayNumbers = documentEndnotes ? computeNoteDisplayNumbers(documentEndnotes, collectEndnoteRefs(newBlocks).map((ref) => ref.endnoteId)) : void 0;
|
|
223
224
|
const endnoteNumberFormat = document?.package.document.sections?.at(-1)?.properties.endnotePr?.numFmt ?? document?.package.settings?.endnotePr?.numFmt ?? "lowerRoman";
|
|
224
225
|
const endnoteTexts = endnoteDisplayNumbers ? formatEndnoteTexts(endnoteDisplayNumbers, (displayNumber) => formatOoxmlCounter(displayNumber, endnoteNumberFormat)) : void 0;
|