@stll/folio-core 0.37.5 → 0.38.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 +275 -88
- package/dist/ai-edits/clean-text.js +12 -1
- package/dist/ai-edits/headless.d.ts +110 -0
- package/dist/ai-edits/headless.js +569 -40
- package/dist/ai-edits/snapshot.d.ts +24 -1
- package/dist/ai-edits/snapshot.js +206 -9
- package/dist/ai-edits/table-geometry.d.ts +7 -7
- package/dist/ai-edits/table-geometry.js +43 -27
- package/dist/ai-edits/table-template.d.ts +2 -1
- package/dist/ai-edits/table-template.js +9 -3
- package/dist/ai-edits/types.d.ts +37 -15
- package/dist/compare/__fixtures__/body-sequence.d.ts +4 -0
- package/dist/compare/__fixtures__/body-sequence.js +5 -3
- package/dist/compare/compare.d.ts +26 -2
- package/dist/compare/compare.js +444 -16
- package/dist/compare/content-alignment.js +3 -2
- package/dist/compare/content-types.d.ts +34 -2
- package/dist/compare/content.d.ts +13 -10
- package/dist/compare/content.js +69 -23
- package/dist/compare/formatting.d.ts +4 -22
- package/dist/compare/formatting.js +8 -112
- package/dist/compare/inline-atom-resources.d.ts +6 -0
- package/dist/compare/inline-atom-resources.js +26 -0
- package/dist/compare/inline-atoms.d.ts +35 -0
- package/dist/compare/inline-atoms.js +383 -0
- package/dist/compare/inline-provenance.d.ts +36 -0
- package/dist/compare/inline-provenance.js +334 -0
- package/dist/compare/plan.d.ts +4 -1
- package/dist/compare/plan.js +32 -4
- package/dist/compare/scenario.d.ts +4 -6
- package/dist/compare/section-boundary-properties.d.ts +63 -0
- package/dist/compare/section-boundary-properties.js +235 -0
- package/dist/compare/style-resources.d.ts +30 -0
- package/dist/compare/style-resources.js +404 -0
- package/dist/compare/types.d.ts +50 -11
- package/dist/compare/types.js +3 -1
- package/dist/compare/verification.d.ts +3 -5
- package/dist/compare/verification.js +8 -39
- package/dist/compat/eigenpal.d.ts +4 -4
- package/dist/compat/eigenpal.js +3 -3
- package/dist/document-operations.d.ts +5 -3
- package/dist/document-operations.js +112 -5
- package/dist/docx/blockContentParser.js +14 -2
- package/dist/docx/drawingIdNormalization.js +43 -2
- package/dist/docx/drawingRelationships.d.ts +10 -0
- package/dist/docx/drawingRelationships.js +28 -0
- package/dist/docx/fieldParser.js +1 -1
- package/dist/docx/hyperlinkParser.js +3 -5
- package/dist/docx/imageRawXml.d.ts +8 -0
- package/dist/docx/imageRawXml.js +13 -0
- package/dist/docx/newImage.d.ts +2 -2
- package/dist/docx/newImage.js +3 -3
- package/dist/docx/numberingParser.js +30 -27
- package/dist/docx/paragraphParser.js +11 -4
- package/dist/docx/removeHeaderFooterParts.d.ts +14 -0
- package/dist/docx/removeHeaderFooterParts.js +87 -0
- package/dist/docx/rezip.d.ts +3 -1
- package/dist/docx/rezip.js +143 -41
- package/dist/docx/runParser.js +16 -5
- package/dist/docx/sectionParser.js +3 -0
- package/dist/docx/sectionReferenceHistory.d.ts +17 -0
- package/dist/docx/sectionReferenceHistory.js +59 -0
- package/dist/docx/selectiveSave.js +11 -31
- package/dist/docx/selectiveXmlPatch.d.ts +8 -1
- package/dist/docx/selectiveXmlPatch.js +62 -2
- package/dist/docx/serializer/paragraphSerializer.js +2 -0
- package/dist/docx/serializer/partNamespaces.d.ts +1 -1
- package/dist/docx/serializer/partNamespaces.js +4 -0
- package/dist/docx/serializer/runSerializer.js +2 -1
- package/dist/docx/serializer/sectionPropertiesSerializer.js +5 -2
- package/dist/docx/serializer/textFormattingSerializer.d.ts +1 -1
- package/dist/docx/serializer/textFormattingSerializer.js +3 -1
- package/dist/docx/tableParser.js +21 -11
- package/dist/docx/xmlParser.d.ts +10 -1
- package/dist/docx/xmlParser.js +23 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3 -3
- package/dist/internal/compare/inline-presentation.d.ts +20 -0
- package/dist/internal/compare/inline-presentation.js +222 -0
- package/dist/internal/paragraphFormattingSerialization.d.ts +3 -1
- package/dist/internal/paragraphFormattingSerialization.js +8 -1
- package/dist/internal/sectionReferenceResolution.d.ts +21 -0
- package/dist/internal/sectionReferenceResolution.js +72 -0
- package/dist/layout-bridge/convert/toFlowBlocks.js +1 -0
- package/dist/markdown/renderRuns.js +1 -0
- package/dist/markdown/renderTable.js +1 -0
- package/dist/prosemirror/attrs/index.js +20 -1
- package/dist/prosemirror/commands/comments.d.ts +11 -1
- package/dist/prosemirror/commands/comments.js +68 -6
- package/dist/prosemirror/commands/propertyChangeScope.d.ts +9 -3
- package/dist/prosemirror/commands/propertyChangeScope.js +4 -10
- package/dist/prosemirror/conversion/fromProseDoc.js +16 -8
- package/dist/prosemirror/conversion/toProseDoc.js +16 -57
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +2 -0
- package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.js +3 -0
- package/dist/prosemirror/imageCommit.js +7 -9
- package/dist/prosemirror/paragraphIndentation.d.ts +21 -0
- package/dist/prosemirror/paragraphIndentation.js +72 -0
- package/dist/prosemirror/plugins/documentNumbering.d.ts +3 -1
- package/dist/prosemirror/plugins/documentNumbering.js +7 -1
- package/dist/prosemirror/plugins/documentStyles.d.ts +3 -1
- package/dist/prosemirror/plugins/documentStyles.js +7 -1
- package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +3 -1
- package/dist/prosemirror/rebaseParagraphRunFormatting.js +2 -1
- package/dist/prosemirror/runFormattingInlineCarriers.d.ts +15 -2
- package/dist/prosemirror/runFormattingInlineCarriers.js +33 -3
- package/dist/prosemirror/runStyleFormatting.d.ts +15 -2
- package/dist/prosemirror/runStyleFormatting.js +76 -2
- package/dist/prosemirror/schema/marks.d.ts +3 -1
- package/dist/prosemirror/schema/marks.js +2 -0
- package/dist/prosemirror/schema/nodes.d.ts +3 -0
- package/dist/prosemirror/tableCellRevisionVisibility.d.ts +6 -0
- package/dist/prosemirror/tableCellRevisionVisibility.js +14 -0
- package/dist/prosemirror/trackedRunInlineAtoms.d.ts +1 -1
- package/dist/prosemirror/trackedRunInlineAtoms.js +1 -1
- package/dist/redline.js +15 -2
- package/dist/server.d.ts +2 -2
- package/dist/server.js +2 -2
- package/dist/version-comparison.d.ts +2 -0
- package/dist/version-comparison.js +7 -2
- package/package.json +2 -2
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { sourceDocumentOf, styleResolverOf } from "../ai-edits/snapshot.js";
|
|
2
|
+
import { expectHyperlinkMarkAttrs, expectRunPropertyChangeMarkAttrs, expectTableCellAttrs } from "../prosemirror/attrs/index.js";
|
|
3
|
+
import { getDocumentStyleResolver } from "../prosemirror/plugins/documentStyles.js";
|
|
4
|
+
import { applyMarksToRunFormattingRepresentation, expandRunFormattingCarrier, runFormattingCarrierReviewText } from "../prosemirror/runFormattingInlineCarriers.js";
|
|
5
|
+
import { readAuthoredRunFormatting, reconcileRunFormattingMarks } from "../prosemirror/runFormattingReconciliation.js";
|
|
6
|
+
import { paragraphRunStyleContextAt } from "../prosemirror/runStyleFormatting.js";
|
|
7
|
+
import { isTableCellRetainedInReviewView } from "../prosemirror/tableCellRevisionVisibility.js";
|
|
8
|
+
import { canonicalJson } from "../utils/canonicalJson.js";
|
|
9
|
+
//#region src/compare/inline-provenance.ts
|
|
10
|
+
const isDeleted = (carrier) => carrier.representations.some(({ node }) => node.marks.some(({ type }) => type.name === "deletion"));
|
|
11
|
+
const hyperlinkOf = (node) => node.marks.find(({ type }) => type.name === "hyperlink");
|
|
12
|
+
const hyperlinkIdentity = (node) => {
|
|
13
|
+
const mark = hyperlinkOf(node);
|
|
14
|
+
if (!mark) return "null";
|
|
15
|
+
const { href, tooltip } = expectHyperlinkMarkAttrs(mark);
|
|
16
|
+
return canonicalJson({
|
|
17
|
+
href,
|
|
18
|
+
tooltip
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
const isInserted = (node) => node.marks.some(({ type }) => type.name === "insertion");
|
|
22
|
+
const carrierRepresentations = (carrier) => carrier.representations.map((representation) => ({
|
|
23
|
+
...representation,
|
|
24
|
+
from: representation.position,
|
|
25
|
+
to: representation.position + representation.node.nodeSize
|
|
26
|
+
}));
|
|
27
|
+
const targetBlockIdLookup = (anchors) => {
|
|
28
|
+
const ordered = Object.values(anchors).toSorted((left, right) => left.from - right.from || left.to - right.to);
|
|
29
|
+
const active = [];
|
|
30
|
+
let nextAnchor = 0;
|
|
31
|
+
return ({ position, nodeSize }) => {
|
|
32
|
+
while (nextAnchor < ordered.length) {
|
|
33
|
+
const anchor = ordered.at(nextAnchor);
|
|
34
|
+
if (!anchor || anchor.from > position) break;
|
|
35
|
+
active.push(anchor);
|
|
36
|
+
nextAnchor++;
|
|
37
|
+
}
|
|
38
|
+
const end = position + nodeSize;
|
|
39
|
+
let owner;
|
|
40
|
+
for (let index = active.length - 1; index >= 0; index--) {
|
|
41
|
+
const anchor = active[index];
|
|
42
|
+
if (!anchor) continue;
|
|
43
|
+
if (anchor.to < end) {
|
|
44
|
+
active.splice(index, 1);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (anchor.from <= position && end <= anchor.to && (owner === void 0 || anchor.to - anchor.from < owner.to - owner.from)) owner = anchor;
|
|
48
|
+
}
|
|
49
|
+
return owner?.id;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
const collectCarriers = ({ doc, targetSnapshot }) => {
|
|
53
|
+
const carriers = [];
|
|
54
|
+
const targetBlockIdAt = targetSnapshot ? targetBlockIdLookup(targetSnapshot.anchors) : void 0;
|
|
55
|
+
let unanchoredTargetCarrier = false;
|
|
56
|
+
doc.descendants((node, position) => {
|
|
57
|
+
if ((node.type.name === "tableCell" || node.type.name === "tableHeader") && !isTableCellRetainedInReviewView(expectTableCellAttrs(node).cellMarker?.kind, "final")) return false;
|
|
58
|
+
if (!node.isInline) return true;
|
|
59
|
+
const carrier = expandRunFormattingCarrier(node, position);
|
|
60
|
+
if (!carrier) return !node.isAtom;
|
|
61
|
+
if (isDeleted(carrier)) return false;
|
|
62
|
+
const targetBlockId = targetBlockIdAt?.({
|
|
63
|
+
position,
|
|
64
|
+
nodeSize: node.nodeSize
|
|
65
|
+
});
|
|
66
|
+
if (targetSnapshot && targetBlockId === void 0) {
|
|
67
|
+
unanchoredTargetCarrier = true;
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
carriers.push({
|
|
71
|
+
carrier,
|
|
72
|
+
text: runFormattingCarrierReviewText(carrier),
|
|
73
|
+
targetBlockId
|
|
74
|
+
});
|
|
75
|
+
return false;
|
|
76
|
+
});
|
|
77
|
+
return unanchoredTargetCarrier ? null : carriers;
|
|
78
|
+
};
|
|
79
|
+
const sameCarrierShape = (left, right) => {
|
|
80
|
+
if (left.carrier.disposition !== right.carrier.disposition || left.text !== right.text) return false;
|
|
81
|
+
const leftRepresentations = left.carrier.representations;
|
|
82
|
+
const rightRepresentations = right.carrier.representations;
|
|
83
|
+
return leftRepresentations.length === rightRepresentations.length && leftRepresentations.every((representation, index) => {
|
|
84
|
+
const target = rightRepresentations.at(index);
|
|
85
|
+
return target !== void 0 && representation.role === target.role && representation.node.type === target.node.type;
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Match whole formatting carriers while permitting only text runs to be cut at
|
|
90
|
+
* UTF-16 boundaries. Non-text carriers are serialization atoms and therefore
|
|
91
|
+
* must line up exactly on both sides.
|
|
92
|
+
*/
|
|
93
|
+
const matchCarrierStreams = ({ live, target }) => {
|
|
94
|
+
const matched = [];
|
|
95
|
+
let liveIndex = 0;
|
|
96
|
+
let targetIndex = 0;
|
|
97
|
+
let liveOffset = 0;
|
|
98
|
+
let targetOffset = 0;
|
|
99
|
+
while (liveIndex < live.length || targetIndex < target.length) {
|
|
100
|
+
const liveCarrier = live.at(liveIndex);
|
|
101
|
+
const targetCarrier = target.at(targetIndex);
|
|
102
|
+
if (!liveCarrier || !targetCarrier || targetCarrier.targetBlockId === void 0) return null;
|
|
103
|
+
const liveText = liveCarrier.text.slice(liveOffset);
|
|
104
|
+
const targetText = targetCarrier.text.slice(targetOffset);
|
|
105
|
+
const liveIsText = liveCarrier.carrier.disposition === "text-run";
|
|
106
|
+
const targetIsText = targetCarrier.carrier.disposition === "text-run";
|
|
107
|
+
if (!liveIsText || !targetIsText) {
|
|
108
|
+
if (liveOffset !== 0 || targetOffset !== 0 || !sameCarrierShape(liveCarrier, targetCarrier)) return null;
|
|
109
|
+
const liveRepresentations = carrierRepresentations(liveCarrier.carrier);
|
|
110
|
+
const targetRepresentations = carrierRepresentations(targetCarrier.carrier);
|
|
111
|
+
for (const [index, liveRepresentation] of liveRepresentations.entries()) {
|
|
112
|
+
const targetRepresentation = targetRepresentations.at(index);
|
|
113
|
+
if (!targetRepresentation) return null;
|
|
114
|
+
matched.push({
|
|
115
|
+
live: liveRepresentation,
|
|
116
|
+
target: targetRepresentation,
|
|
117
|
+
targetBlockId: targetCarrier.targetBlockId
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
liveIndex++;
|
|
121
|
+
targetIndex++;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const sharedLength = Math.min(liveText.length, targetText.length);
|
|
125
|
+
if (sharedLength === 0 || liveText.slice(0, sharedLength) !== targetText.slice(0, sharedLength)) return null;
|
|
126
|
+
const liveRepresentation = carrierRepresentations(liveCarrier.carrier).at(0);
|
|
127
|
+
const targetRepresentation = carrierRepresentations(targetCarrier.carrier).at(0);
|
|
128
|
+
if (!liveRepresentation || !targetRepresentation) return null;
|
|
129
|
+
matched.push({
|
|
130
|
+
live: {
|
|
131
|
+
...liveRepresentation,
|
|
132
|
+
from: liveRepresentation.from + liveOffset,
|
|
133
|
+
to: liveRepresentation.from + liveOffset + sharedLength
|
|
134
|
+
},
|
|
135
|
+
target: {
|
|
136
|
+
...targetRepresentation,
|
|
137
|
+
from: targetRepresentation.from + targetOffset,
|
|
138
|
+
to: targetRepresentation.from + targetOffset + sharedLength
|
|
139
|
+
},
|
|
140
|
+
targetBlockId: targetCarrier.targetBlockId
|
|
141
|
+
});
|
|
142
|
+
liveOffset += sharedLength;
|
|
143
|
+
targetOffset += sharedLength;
|
|
144
|
+
if (liveOffset === liveCarrier.text.length) {
|
|
145
|
+
liveIndex++;
|
|
146
|
+
liveOffset = 0;
|
|
147
|
+
}
|
|
148
|
+
if (targetOffset === targetCarrier.text.length) {
|
|
149
|
+
targetIndex++;
|
|
150
|
+
targetOffset = 0;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return matched;
|
|
154
|
+
};
|
|
155
|
+
const authoredFormattingAt = ({ doc, representation, styleResolver }) => readAuthoredRunFormatting({
|
|
156
|
+
context: paragraphRunStyleContextAt({
|
|
157
|
+
doc,
|
|
158
|
+
pos: representation.from,
|
|
159
|
+
styleResolver
|
|
160
|
+
}),
|
|
161
|
+
marks: representation.node.marks,
|
|
162
|
+
styleResolver
|
|
163
|
+
});
|
|
164
|
+
const changeWithCurrentFormatting = ({ change, formatting }) => ({
|
|
165
|
+
...change,
|
|
166
|
+
currentFormatting: formatting
|
|
167
|
+
});
|
|
168
|
+
/**
|
|
169
|
+
* Reconcile direct inline run formatting after the comparison plan has aligned
|
|
170
|
+
* text. It deliberately performs all stream, revision-slot, and range-budget
|
|
171
|
+
* checks before creating a transaction step.
|
|
172
|
+
*/
|
|
173
|
+
const matchInlineProvenance = ({ state, targetSnapshot, revisionStamp, originalRevisionIdSeed, author, maxRanges }) => {
|
|
174
|
+
const targetStyleResolver = styleResolverOf(targetSnapshot);
|
|
175
|
+
if (!Number.isSafeInteger(maxRanges) || maxRanges < 0) return { status: "budget-exceeded" };
|
|
176
|
+
const targetDocument = sourceDocumentOf(targetSnapshot);
|
|
177
|
+
const liveCarriers = collectCarriers({ doc: state.doc });
|
|
178
|
+
const targetCarriers = collectCarriers({
|
|
179
|
+
doc: targetDocument,
|
|
180
|
+
targetSnapshot
|
|
181
|
+
});
|
|
182
|
+
if (!liveCarriers || !targetCarriers) return { status: "unalignable" };
|
|
183
|
+
const matched = matchCarrierStreams({
|
|
184
|
+
live: liveCarriers,
|
|
185
|
+
target: targetCarriers
|
|
186
|
+
});
|
|
187
|
+
if (!matched) return { status: "unalignable" };
|
|
188
|
+
const propertyChangeType = state.schema.marks["runPropertyChange"];
|
|
189
|
+
const liveStyleResolver = getDocumentStyleResolver(state);
|
|
190
|
+
const planned = [];
|
|
191
|
+
for (const segment of matched) {
|
|
192
|
+
const liveFormatting = authoredFormattingAt({
|
|
193
|
+
doc: state.doc,
|
|
194
|
+
representation: segment.live,
|
|
195
|
+
styleResolver: liveStyleResolver
|
|
196
|
+
});
|
|
197
|
+
const targetFormatting = authoredFormattingAt({
|
|
198
|
+
doc: targetDocument,
|
|
199
|
+
representation: segment.target,
|
|
200
|
+
styleResolver: targetStyleResolver
|
|
201
|
+
});
|
|
202
|
+
const hyperlinkChange = hyperlinkIdentity(segment.live.node) === hyperlinkIdentity(segment.target.node) ? "unchanged" : "replace";
|
|
203
|
+
if (hyperlinkChange === "replace" && !segment.live.node.isText) return { status: "unalignable" };
|
|
204
|
+
if (hyperlinkChange === "unchanged" && canonicalJson(liveFormatting) === canonicalJson(targetFormatting)) continue;
|
|
205
|
+
const existingPropertyChange = propertyChangeType ? segment.live.node.marks.find((mark) => mark.type === propertyChangeType) : void 0;
|
|
206
|
+
const propertyChange = existingPropertyChange && expectRunPropertyChangeMarkAttrs(existingPropertyChange).changes.length > 0 ? existingPropertyChange : void 0;
|
|
207
|
+
if (propertyChange && expectRunPropertyChangeMarkAttrs(propertyChange).changes.some(({ info }) => info.id < originalRevisionIdSeed)) return { status: "unalignable" };
|
|
208
|
+
if (!isInserted(segment.live.node) && !propertyChangeType) return { status: "unalignable" };
|
|
209
|
+
planned.push({
|
|
210
|
+
...segment,
|
|
211
|
+
formatting: targetFormatting,
|
|
212
|
+
previousFormatting: liveFormatting,
|
|
213
|
+
inserted: isInserted(segment.live.node),
|
|
214
|
+
propertyChange,
|
|
215
|
+
hyperlinkChange
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
if (planned.length > maxRanges) return { status: "budget-exceeded" };
|
|
219
|
+
const transaction = state.tr;
|
|
220
|
+
let nextRevisionId = revisionStamp.idSeed;
|
|
221
|
+
const changedTargetBlockIds = /* @__PURE__ */ new Set();
|
|
222
|
+
for (const change of planned.toReversed()) {
|
|
223
|
+
const context = paragraphRunStyleContextAt({
|
|
224
|
+
doc: state.doc,
|
|
225
|
+
pos: change.live.from,
|
|
226
|
+
styleResolver: liveStyleResolver
|
|
227
|
+
});
|
|
228
|
+
const formattingMarks = reconcileRunFormattingMarks({
|
|
229
|
+
authoredFormatting: change.formatting,
|
|
230
|
+
context,
|
|
231
|
+
node: change.live.node,
|
|
232
|
+
styleResolver: liveStyleResolver
|
|
233
|
+
});
|
|
234
|
+
let marks = formattingMarks;
|
|
235
|
+
if (change.hyperlinkChange === "replace") {
|
|
236
|
+
const hyperlinkType = state.schema.marks["hyperlink"];
|
|
237
|
+
const insertionType = state.schema.marks["insertion"];
|
|
238
|
+
const deletionType = state.schema.marks["deletion"];
|
|
239
|
+
if (!hyperlinkType || !insertionType || !deletionType) return { status: "unalignable" };
|
|
240
|
+
marks = hyperlinkType.removeFromSet(marks);
|
|
241
|
+
const targetLink = hyperlinkOf(change.target.node);
|
|
242
|
+
if (targetLink) {
|
|
243
|
+
const { href, tooltip } = expectHyperlinkMarkAttrs(targetLink);
|
|
244
|
+
marks = hyperlinkType.create({
|
|
245
|
+
href,
|
|
246
|
+
tooltip
|
|
247
|
+
}).addToSet(marks);
|
|
248
|
+
}
|
|
249
|
+
if (!change.inserted) {
|
|
250
|
+
if (propertyChangeType) marks = propertyChangeType.removeFromSet(marks);
|
|
251
|
+
marks = insertionType.create({
|
|
252
|
+
revisionId: nextRevisionId++,
|
|
253
|
+
author,
|
|
254
|
+
date: revisionStamp.date
|
|
255
|
+
}).addToSet(marks);
|
|
256
|
+
const text = state.doc.textBetween(change.live.from, change.live.to);
|
|
257
|
+
transaction.addMark(change.live.from, change.live.to, deletionType.create({
|
|
258
|
+
revisionId: nextRevisionId++,
|
|
259
|
+
author,
|
|
260
|
+
date: revisionStamp.date
|
|
261
|
+
}));
|
|
262
|
+
transaction.insert(change.live.to, state.schema.text(text, marks));
|
|
263
|
+
changedTargetBlockIds.add(change.targetBlockId);
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (!change.inserted && propertyChangeType) if (change.propertyChange) {
|
|
268
|
+
const attrs = expectRunPropertyChangeMarkAttrs(change.propertyChange);
|
|
269
|
+
marks = propertyChangeType.create({
|
|
270
|
+
...change.propertyChange.attrs,
|
|
271
|
+
changes: attrs.changes.map((entry) => changeWithCurrentFormatting({
|
|
272
|
+
change: entry,
|
|
273
|
+
formatting: change.formatting
|
|
274
|
+
}))
|
|
275
|
+
}).addToSet(formattingMarks);
|
|
276
|
+
} else {
|
|
277
|
+
const propertyChange = {
|
|
278
|
+
type: "runPropertyChange",
|
|
279
|
+
info: {
|
|
280
|
+
id: nextRevisionId++,
|
|
281
|
+
author,
|
|
282
|
+
date: revisionStamp.date
|
|
283
|
+
},
|
|
284
|
+
...Object.keys(change.previousFormatting).length > 0 ? { previousFormatting: change.previousFormatting } : {}
|
|
285
|
+
};
|
|
286
|
+
marks = propertyChangeType.create({ changes: [propertyChange] }).addToSet(formattingMarks);
|
|
287
|
+
}
|
|
288
|
+
applyMarksToRunFormattingRepresentation({
|
|
289
|
+
tr: transaction,
|
|
290
|
+
representation: change.live,
|
|
291
|
+
marks
|
|
292
|
+
});
|
|
293
|
+
changedTargetBlockIds.add(change.targetBlockId);
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
status: "matched",
|
|
297
|
+
transaction,
|
|
298
|
+
nextRevisionId,
|
|
299
|
+
changedTargetBlockIds: [...changedTargetBlockIds],
|
|
300
|
+
rangeCount: planned.length
|
|
301
|
+
};
|
|
302
|
+
};
|
|
303
|
+
/** Compare full authored run-property provenance in two snapshot source documents. */
|
|
304
|
+
const sameAuthoredInlineProvenance = (baseSnapshot, targetSnapshot) => {
|
|
305
|
+
const baseDocument = sourceDocumentOf(baseSnapshot);
|
|
306
|
+
const targetDocument = sourceDocumentOf(targetSnapshot);
|
|
307
|
+
const baseCarriers = collectCarriers({
|
|
308
|
+
doc: baseDocument,
|
|
309
|
+
targetSnapshot: baseSnapshot
|
|
310
|
+
});
|
|
311
|
+
const targetCarriers = collectCarriers({
|
|
312
|
+
doc: targetDocument,
|
|
313
|
+
targetSnapshot
|
|
314
|
+
});
|
|
315
|
+
if (!baseCarriers || !targetCarriers) return false;
|
|
316
|
+
const matched = matchCarrierStreams({
|
|
317
|
+
live: baseCarriers,
|
|
318
|
+
target: targetCarriers
|
|
319
|
+
});
|
|
320
|
+
if (!matched) return false;
|
|
321
|
+
const baseStyleResolver = styleResolverOf(baseSnapshot);
|
|
322
|
+
const targetStyleResolver = styleResolverOf(targetSnapshot);
|
|
323
|
+
return matched.every(({ live, target }) => hyperlinkIdentity(live.node) === hyperlinkIdentity(target.node) && canonicalJson(authoredFormattingAt({
|
|
324
|
+
doc: baseDocument,
|
|
325
|
+
representation: live,
|
|
326
|
+
styleResolver: baseStyleResolver
|
|
327
|
+
})) === canonicalJson(authoredFormattingAt({
|
|
328
|
+
doc: targetDocument,
|
|
329
|
+
representation: target,
|
|
330
|
+
styleResolver: targetStyleResolver
|
|
331
|
+
})));
|
|
332
|
+
};
|
|
333
|
+
//#endregion
|
|
334
|
+
export { matchInlineProvenance, sameAuthoredInlineProvenance };
|
package/dist/compare/plan.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { FolioAIEditOperation, FolioAIEditSnapshot } from "../ai-edits/types.js";
|
|
2
2
|
import { TableGeometryPairing } from "../ai-edits/table-geometry.js";
|
|
3
3
|
import { FolioDocumentStoryHandle } from "../ai-edits/headless.js";
|
|
4
|
-
import {
|
|
4
|
+
import { CompareVerificationFailure } from "./verification.js";
|
|
5
5
|
import { CompareChange } from "./types.js";
|
|
6
|
+
import { FolioContentComparisonWorkSession } from "./content.js";
|
|
6
7
|
//#region src/compare/plan.d.ts
|
|
7
8
|
/**
|
|
8
9
|
* A table the target document already holds, which the operation naming it
|
|
@@ -23,6 +24,8 @@ type CompareTableTemplateRequest = {
|
|
|
23
24
|
type CompareStoryPlan = {
|
|
24
25
|
changes: CompareChange[];
|
|
25
26
|
operations: FolioAIEditOperation[];
|
|
27
|
+
/** Differences the operation grammar cannot safely project. */
|
|
28
|
+
verificationFailures: CompareVerificationFailure[];
|
|
26
29
|
/**
|
|
27
30
|
* Where an operation's table comes from. Kept beside the operations rather
|
|
28
31
|
* than inside them because a table node is not JSON, and the serialized
|
package/dist/compare/plan.js
CHANGED
|
@@ -221,8 +221,10 @@ const shareAContainer = (left, right) => {
|
|
|
221
221
|
const toFolioAIBlockParagraphProperties = (properties) => ({
|
|
222
222
|
...properties.styleId !== void 0 && { styleId: properties.styleId },
|
|
223
223
|
...properties.listLevel !== void 0 && { listLevel: properties.listLevel },
|
|
224
|
+
...properties.listReference !== void 0 && { numbering: properties.listReference },
|
|
224
225
|
...properties.alignment !== void 0 && { alignment: properties.alignment },
|
|
225
|
-
...properties.spacing !== void 0 && { spacing: properties.spacing }
|
|
226
|
+
...properties.spacing !== void 0 && { spacing: properties.spacing },
|
|
227
|
+
...properties.indentation !== void 0 && { indentation: properties.indentation }
|
|
226
228
|
});
|
|
227
229
|
/**
|
|
228
230
|
* For each step, the id of the next base block at or after it — the anchor a
|
|
@@ -484,6 +486,10 @@ const withTrailingDeletionRules = ({ baseSnapshot, targetSnapshot, operations, t
|
|
|
484
486
|
...insert.moveId !== void 0 && { moveId: insert.moveId },
|
|
485
487
|
styleId: insert.styleId ?? null,
|
|
486
488
|
listLevel: insert.listLevel ?? null,
|
|
489
|
+
numbering: insert.numbering ?? null,
|
|
490
|
+
indentation: insert.indentation ?? null,
|
|
491
|
+
...insert.lineBreakMode !== void 0 && { lineBreakMode: insert.lineBreakMode },
|
|
492
|
+
...insert.hardPageBreak !== void 0 && { hardPageBreak: insert.hardPageBreak },
|
|
487
493
|
alignment: insert.alignment ?? null,
|
|
488
494
|
spacing: insert.spacing ?? null
|
|
489
495
|
};
|
|
@@ -557,6 +563,7 @@ const planStoryCompare = ({ story, baseSnapshot, targetSnapshot, maxOperations,
|
|
|
557
563
|
const anchorIds = nextBaseBlockIdByStep(steps);
|
|
558
564
|
const changes = [];
|
|
559
565
|
const operations = [];
|
|
566
|
+
const verificationFailures = [];
|
|
560
567
|
const tableTemplates = [];
|
|
561
568
|
/**
|
|
562
569
|
* The anchor everything past the base document's content hangs from: its
|
|
@@ -582,13 +589,19 @@ const planStoryCompare = ({ story, baseSnapshot, targetSnapshot, maxOperations,
|
|
|
582
589
|
const moveIdOf = (baseBlockId) => `move-${baseBlockId}`;
|
|
583
590
|
const pushInsertOperation = (block, anchorId) => {
|
|
584
591
|
const moveSourceId = moveSourceByTargetBlockId.get(block.id);
|
|
592
|
+
const structuralBoundary = block.structuralBoundaries?.at(0);
|
|
593
|
+
const hardPageBreak = block.text.length === 0 && block.structuralBoundaries?.length === 1 && structuralBoundary?.type === "pageBreak" && structuralBoundary.offset === 0 ? { ...structuralBoundary.clear !== void 0 && { clear: structuralBoundary.clear } } : void 0;
|
|
585
594
|
const shared = {
|
|
586
595
|
text: block.text,
|
|
596
|
+
...(block.text.includes(" ") || block.text.includes("\n")) && { lineBreakMode: "inline" },
|
|
587
597
|
...moveSourceId !== void 0 && { moveId: moveIdOf(moveSourceId) },
|
|
598
|
+
...hardPageBreak !== void 0 && { hardPageBreak },
|
|
588
599
|
styleId: block.styleId ?? null,
|
|
589
600
|
listLevel: block.listLevel ?? null,
|
|
601
|
+
numbering: block.listReference ?? null,
|
|
590
602
|
alignment: block.directAlignment ?? null,
|
|
591
|
-
spacing: block.directSpacing ?? null
|
|
603
|
+
spacing: block.directSpacing ?? null,
|
|
604
|
+
indentation: block.directIndentation ?? null
|
|
592
605
|
};
|
|
593
606
|
if (anchorId !== null) {
|
|
594
607
|
operations.push({
|
|
@@ -712,12 +725,26 @@ const planStoryCompare = ({ story, baseSnapshot, targetSnapshot, maxOperations,
|
|
|
712
725
|
});
|
|
713
726
|
break;
|
|
714
727
|
}
|
|
715
|
-
const
|
|
728
|
+
const formattingComparison = inlineFormattingSegments({
|
|
716
729
|
baseBlock,
|
|
717
730
|
targetBlock,
|
|
718
731
|
maxSegments: maxOperations
|
|
719
732
|
});
|
|
720
|
-
|
|
733
|
+
switch (formattingComparison.status) {
|
|
734
|
+
case "compared": break;
|
|
735
|
+
case "budget-exceeded": return null;
|
|
736
|
+
case "unalignable":
|
|
737
|
+
verificationFailures.push({
|
|
738
|
+
invariant: "accept-reproduces-target",
|
|
739
|
+
cause: "inline-formatting",
|
|
740
|
+
story,
|
|
741
|
+
detail: `supported inline formatting could not be aligned (${formattingComparison.side})`
|
|
742
|
+
});
|
|
743
|
+
break;
|
|
744
|
+
default: panic("Unhandled inline formatting comparison result", { result: formattingComparison });
|
|
745
|
+
}
|
|
746
|
+
if (formattingComparison.status !== "compared") break;
|
|
747
|
+
const { segments } = formattingComparison;
|
|
721
748
|
if (segments.length === 0) break;
|
|
722
749
|
changes.push({
|
|
723
750
|
kind: "format",
|
|
@@ -935,6 +962,7 @@ const planStoryCompare = ({ story, baseSnapshot, targetSnapshot, maxOperations,
|
|
|
935
962
|
return planned.length > maxOperations ? null : {
|
|
936
963
|
changes,
|
|
937
964
|
operations: planned,
|
|
965
|
+
verificationFailures,
|
|
938
966
|
tableTemplates,
|
|
939
967
|
tableGeometryPairings: tableGeometryPairingsOf(steps)
|
|
940
968
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FolioContentFormatRange } from "./content-types.js";
|
|
2
|
+
import { FolioAIEditOperation } from "../ai-edits/types.js";
|
|
2
3
|
import { CompareDocxParseError, CompareDocxSerializeError } from "./types.js";
|
|
3
4
|
import { Result } from "better-result";
|
|
4
5
|
//#region src/compare/scenario.d.ts
|
|
@@ -45,13 +46,10 @@ type EditScriptStep = {
|
|
|
45
46
|
{
|
|
46
47
|
type: "mergeParagraphs";
|
|
47
48
|
blockIndex: number;
|
|
48
|
-
} | {
|
|
49
|
+
} | ({
|
|
49
50
|
type: "formatRange";
|
|
50
51
|
blockIndex: number;
|
|
51
|
-
|
|
52
|
-
endOffset: number;
|
|
53
|
-
formatting: FolioAIInlineFormattingPatch;
|
|
54
|
-
} | {
|
|
52
|
+
} & FolioContentFormatRange) | {
|
|
55
53
|
type: "insertTableRow";
|
|
56
54
|
blockIndex: number;
|
|
57
55
|
position?: TableRowPosition;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { document_d_exports } from "../types/document.js";
|
|
2
|
+
import { Node } from "prosemirror-model";
|
|
3
|
+
//#region src/compare/section-boundary-properties.d.ts
|
|
4
|
+
type SectionBoundaryPropertyChange = {
|
|
5
|
+
position: number;
|
|
6
|
+
current: document_d_exports.SectionProperties;
|
|
7
|
+
target: document_d_exports.SectionProperties;
|
|
8
|
+
};
|
|
9
|
+
type SectionBoundaryPropertiesComparison = {
|
|
10
|
+
status: "matched";
|
|
11
|
+
changes: readonly SectionBoundaryPropertyChange[];
|
|
12
|
+
} | {
|
|
13
|
+
status: "unalignable";
|
|
14
|
+
detail: string;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Compare section properties attached to retained top-level paragraph endpoints.
|
|
18
|
+
* A section endpoint cannot be added to or removed from a retained paragraph
|
|
19
|
+
* with `sectPrChange`: its previous payload is still a section endpoint.
|
|
20
|
+
*/
|
|
21
|
+
declare const compareSectionBoundaryProperties: ({ current, target, mapTargetProperties }: {
|
|
22
|
+
current: Node;
|
|
23
|
+
target: Node;
|
|
24
|
+
mapTargetProperties?: (properties: document_d_exports.SectionProperties) => document_d_exports.SectionProperties | null;
|
|
25
|
+
}) => SectionBoundaryPropertiesComparison;
|
|
26
|
+
type MappedSectionBoundaryProperties = {
|
|
27
|
+
kind: "inserted";
|
|
28
|
+
target: document_d_exports.SectionProperties;
|
|
29
|
+
} | {
|
|
30
|
+
kind: "retained";
|
|
31
|
+
previous: document_d_exports.SectionProperties;
|
|
32
|
+
target: document_d_exports.SectionProperties;
|
|
33
|
+
};
|
|
34
|
+
type StageSectionBoundaryPropertiesResult = {
|
|
35
|
+
status: "matched";
|
|
36
|
+
transaction: import("prosemirror-state").Transaction;
|
|
37
|
+
nextRevisionId: number;
|
|
38
|
+
rangeCount: number;
|
|
39
|
+
} | {
|
|
40
|
+
status: "unalignable";
|
|
41
|
+
detail: string;
|
|
42
|
+
} | {
|
|
43
|
+
status: "budget-exceeded";
|
|
44
|
+
};
|
|
45
|
+
/** Stage section deltas after document operations, against the accepted projection. */
|
|
46
|
+
declare const stageSectionBoundaryProperties: ({ state, target, originalRevisionIdSeed, revisionStamp, author, maxRanges, mapTargetProperties }: {
|
|
47
|
+
state: import("prosemirror-state").EditorState;
|
|
48
|
+
target: Node;
|
|
49
|
+
originalRevisionIdSeed: number;
|
|
50
|
+
revisionStamp: {
|
|
51
|
+
idSeed: number;
|
|
52
|
+
date: string;
|
|
53
|
+
};
|
|
54
|
+
author: string;
|
|
55
|
+
maxRanges: number;
|
|
56
|
+
mapTargetProperties: (args: {
|
|
57
|
+
kind: "inserted" | "retained";
|
|
58
|
+
current: document_d_exports.SectionProperties | undefined;
|
|
59
|
+
target: document_d_exports.SectionProperties;
|
|
60
|
+
}) => MappedSectionBoundaryProperties | null;
|
|
61
|
+
}) => StageSectionBoundaryPropertiesResult;
|
|
62
|
+
//#endregion
|
|
63
|
+
export { SectionBoundaryPropertiesComparison, SectionBoundaryPropertyChange, StageSectionBoundaryPropertiesResult, compareSectionBoundaryProperties, stageSectionBoundaryProperties };
|