@stll/folio-core 0.37.0 → 0.37.2
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 +157 -84
- package/dist/ai-edits/headless.d.ts +24 -1
- package/dist/ai-edits/headless.js +108 -71
- package/dist/ai-edits/read.d.ts +9 -1
- package/dist/ai-edits/read.js +34 -16
- package/dist/ai-edits/snapshot.d.ts +7 -5
- package/dist/ai-edits/snapshot.js +39 -12
- package/dist/ai-edits/types.d.ts +15 -0
- package/dist/compare/compare.d.ts +2 -3
- package/dist/compare/compare.js +41 -64
- package/dist/compare/content-alignment.js +0 -1
- package/dist/compare/plan.js +32 -2
- package/dist/compare/scenario.d.ts +11 -2
- package/dist/compare/scenario.js +6 -2
- package/dist/display-list/primitives.d.ts +1 -1
- package/dist/document-operations.d.ts +2 -2
- package/dist/document-operations.js +44 -14
- package/dist/internal/headlessRevisionResolution.d.ts +2 -2
- package/dist/internal/headlessRevisionResolution.js +30 -9
- package/dist/prosemirror/commands/comments.js +2 -25
- package/package.json +1 -1
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
import { FolioAIInlineFormattingPatch } from "../ai-edits/types.js";
|
|
1
|
+
import { FolioAIEditOperation, FolioAIInlineFormattingPatch } from "../ai-edits/types.js";
|
|
2
2
|
import { CompareDocxParseError, CompareDocxSerializeError } from "./types.js";
|
|
3
3
|
import { Result } from "better-result";
|
|
4
4
|
//#region src/compare/scenario.d.ts
|
|
5
|
+
type InsertTableRowOperation = Extract<FolioAIEditOperation, {
|
|
6
|
+
type: "insertTableRow";
|
|
7
|
+
}>;
|
|
8
|
+
type TableRowPosition = NonNullable<InsertTableRowOperation["position"]>;
|
|
9
|
+
declare const EDIT_SCRIPT_TABLE_ROW_POSITION: {
|
|
10
|
+
readonly after: "after";
|
|
11
|
+
readonly before: "before";
|
|
12
|
+
};
|
|
5
13
|
type EditScriptStep = {
|
|
6
14
|
type: "insertParagraphAfter";
|
|
7
15
|
blockIndex: number;
|
|
@@ -46,6 +54,7 @@ type EditScriptStep = {
|
|
|
46
54
|
} | {
|
|
47
55
|
type: "insertTableRow";
|
|
48
56
|
blockIndex: number;
|
|
57
|
+
position?: TableRowPosition;
|
|
49
58
|
cellTexts: readonly string[];
|
|
50
59
|
} | {
|
|
51
60
|
type: "deleteTableRow";
|
|
@@ -74,4 +83,4 @@ type EditScriptResult = {
|
|
|
74
83
|
*/
|
|
75
84
|
declare const applyEditScript: (base: ArrayBuffer, script: EditScript) => Promise<Result<EditScriptResult, CompareDocxParseError | CompareDocxSerializeError>>;
|
|
76
85
|
//#endregion
|
|
77
|
-
export { EDIT_SCRIPT_UNRESOLVED_REASONS, EditScript, EditScriptResult, EditScriptStep, EditScriptUnresolvedReason, EditScriptUnresolvedStep, applyEditScript };
|
|
86
|
+
export { EDIT_SCRIPT_TABLE_ROW_POSITION, EDIT_SCRIPT_UNRESOLVED_REASONS, EditScript, EditScriptResult, EditScriptStep, EditScriptUnresolvedReason, EditScriptUnresolvedStep, applyEditScript };
|
package/dist/compare/scenario.js
CHANGED
|
@@ -24,6 +24,10 @@ import { Result, panic } from "better-result";
|
|
|
24
24
|
* dropped: a script silently doing less than it says would weaken every
|
|
25
25
|
* property built on it.
|
|
26
26
|
*/
|
|
27
|
+
const EDIT_SCRIPT_TABLE_ROW_POSITION = {
|
|
28
|
+
after: "after",
|
|
29
|
+
before: "before"
|
|
30
|
+
};
|
|
27
31
|
const EDIT_SCRIPT_UNRESOLVED_REASONS = Object.freeze([
|
|
28
32
|
"block-out-of-range",
|
|
29
33
|
"block-not-in-table",
|
|
@@ -160,7 +164,7 @@ const planStep = ({ step, blocks, nextOperationId }) => {
|
|
|
160
164
|
id: nextOperationId(),
|
|
161
165
|
type: "insertTableRow",
|
|
162
166
|
blockId: block.id,
|
|
163
|
-
position:
|
|
167
|
+
position: step.position ?? EDIT_SCRIPT_TABLE_ROW_POSITION.after,
|
|
164
168
|
cellTexts: [...step.cellTexts]
|
|
165
169
|
}]
|
|
166
170
|
} : {
|
|
@@ -260,4 +264,4 @@ const applyEditScript = async (base, script) => {
|
|
|
260
264
|
});
|
|
261
265
|
};
|
|
262
266
|
//#endregion
|
|
263
|
-
export { EDIT_SCRIPT_UNRESOLVED_REASONS, applyEditScript };
|
|
267
|
+
export { EDIT_SCRIPT_TABLE_ROW_POSITION, EDIT_SCRIPT_UNRESOLVED_REASONS, applyEditScript };
|
|
@@ -15,7 +15,7 @@ import { hasComplexScript, isComplexScriptCodePoint } from "../utils/scriptSegme
|
|
|
15
15
|
* advances rightward from its origin whatever the paragraph does.
|
|
16
16
|
*/
|
|
17
17
|
declare const glyphCellOffsetsPx: (run: DisplayGlyphRun) => readonly number[];
|
|
18
|
-
declare const DISPLAY_PRIMITIVE_KINDS: ("
|
|
18
|
+
declare const DISPLAY_PRIMITIVE_KINDS: ("glyphRun" | "rect" | "line" | "image" | "clipGroup" | "rotateGroup" | "opacityGroup")[];
|
|
19
19
|
/**
|
|
20
20
|
* Dash geometry of each stroke pattern, as multiples of the stroke thickness.
|
|
21
21
|
*
|
|
@@ -101,8 +101,8 @@ declare const FOLIO_DOCUMENT_OPERATION_KEYS_BY_TYPE: Readonly<{
|
|
|
101
101
|
readonly insertBeforeBlock: readonly ["id", "type", "blockId", "severity", "area", "precondition", "suggestionId", "text", "inheritFormatting", "alignment", "spacing", "listLevel", "moveId", "pageBreakBefore", "styleId", "comment"];
|
|
102
102
|
readonly replaceBlock: readonly ["id", "type", "blockId", "severity", "area", "precondition", "suggestionId", "text", "preserveFormatting", "styleId", "comment"];
|
|
103
103
|
readonly deleteBlock: readonly ["id", "type", "blockId", "severity", "area", "precondition", "suggestionId", "moveId", "comment"];
|
|
104
|
-
readonly splitBlock: readonly ["id", "type", "blockId", "severity", "area", "precondition", "suggestionId", "offset", "separator"];
|
|
105
|
-
readonly mergeBlockWithNext: readonly ["id", "type", "blockId", "severity", "area", "precondition", "suggestionId", "separator"];
|
|
104
|
+
readonly splitBlock: readonly ["id", "type", "blockId", "severity", "area", "precondition", "suggestionId", "offset", "separator", "firstParagraphProperties", "secondParagraphProperties"];
|
|
105
|
+
readonly mergeBlockWithNext: readonly ["id", "type", "blockId", "severity", "area", "precondition", "suggestionId", "separator", "mergedParagraphProperties"];
|
|
106
106
|
readonly setBlockParagraphProperties: readonly ["id", "type", "blockId", "severity", "area", "precondition", "suggestionId", "properties"];
|
|
107
107
|
readonly insertTable: readonly ["id", "type", "blockId", "severity", "area", "precondition", "suggestionId", "position", "rows"];
|
|
108
108
|
readonly deleteTable: readonly ["id", "type", "blockId", "severity", "area", "precondition", "suggestionId"];
|
|
@@ -262,9 +262,9 @@ const readTableRows = (value, path) => {
|
|
|
262
262
|
if (parsed.some((row) => row.length !== width)) return invalidBatch(rowsPath, "expected every row to hold the same number of cells");
|
|
263
263
|
return parsed;
|
|
264
264
|
};
|
|
265
|
-
const readParagraphProperties = (value, path) => {
|
|
266
|
-
const candidate = value[
|
|
267
|
-
const propertiesPath = `${path}
|
|
265
|
+
const readParagraphProperties = ({ value, key, path }) => {
|
|
266
|
+
const candidate = value[key];
|
|
267
|
+
const propertiesPath = `${path}.${key}`;
|
|
268
268
|
if (!isPlainObject(candidate)) return invalidBatch(propertiesPath, "expected an object");
|
|
269
269
|
assertAllowedKeys(candidate, propertiesPath, [
|
|
270
270
|
"styleId",
|
|
@@ -463,9 +463,15 @@ const FOLIO_DOCUMENT_OPERATION_KEYS_BY_TYPE = Object.freeze({
|
|
|
463
463
|
splitBlock: [
|
|
464
464
|
...COMMON_OPERATION_KEYS,
|
|
465
465
|
"offset",
|
|
466
|
-
"separator"
|
|
466
|
+
"separator",
|
|
467
|
+
"firstParagraphProperties",
|
|
468
|
+
"secondParagraphProperties"
|
|
469
|
+
],
|
|
470
|
+
mergeBlockWithNext: [
|
|
471
|
+
...COMMON_OPERATION_KEYS,
|
|
472
|
+
"separator",
|
|
473
|
+
"mergedParagraphProperties"
|
|
467
474
|
],
|
|
468
|
-
mergeBlockWithNext: [...COMMON_OPERATION_KEYS, "separator"],
|
|
469
475
|
setBlockParagraphProperties: [...COMMON_OPERATION_KEYS, "properties"],
|
|
470
476
|
insertTable: [
|
|
471
477
|
...COMMON_OPERATION_KEYS,
|
|
@@ -588,24 +594,48 @@ const parseDocumentOperation = (value, index) => {
|
|
|
588
594
|
id,
|
|
589
595
|
type,
|
|
590
596
|
blockId,
|
|
591
|
-
properties: readParagraphProperties(
|
|
597
|
+
properties: readParagraphProperties({
|
|
598
|
+
value,
|
|
599
|
+
key: "properties",
|
|
600
|
+
path
|
|
601
|
+
})
|
|
592
602
|
};
|
|
593
603
|
if (type === "splitBlock" || type === "mergeBlockWithNext") {
|
|
594
604
|
const separator = readOptionalString(value, "separator", path);
|
|
595
|
-
if (type === "mergeBlockWithNext")
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
605
|
+
if (type === "mergeBlockWithNext") {
|
|
606
|
+
const mergedParagraphProperties = value["mergedParagraphProperties"] === void 0 ? void 0 : readParagraphProperties({
|
|
607
|
+
value,
|
|
608
|
+
key: "mergedParagraphProperties",
|
|
609
|
+
path
|
|
610
|
+
});
|
|
611
|
+
return {
|
|
612
|
+
...operationMeta,
|
|
613
|
+
id,
|
|
614
|
+
type,
|
|
615
|
+
blockId,
|
|
616
|
+
...separator !== void 0 && { separator },
|
|
617
|
+
...mergedParagraphProperties !== void 0 && { mergedParagraphProperties }
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
const firstParagraphProperties = value["firstParagraphProperties"] === void 0 ? void 0 : readParagraphProperties({
|
|
621
|
+
value,
|
|
622
|
+
key: "firstParagraphProperties",
|
|
623
|
+
path
|
|
624
|
+
});
|
|
625
|
+
const secondParagraphProperties = value["secondParagraphProperties"] === void 0 ? void 0 : readParagraphProperties({
|
|
626
|
+
value,
|
|
627
|
+
key: "secondParagraphProperties",
|
|
628
|
+
path
|
|
629
|
+
});
|
|
602
630
|
return {
|
|
603
631
|
...operationMeta,
|
|
604
632
|
id,
|
|
605
633
|
type,
|
|
606
634
|
blockId,
|
|
607
635
|
offset: readNonNegativeInteger(value, "offset", path),
|
|
608
|
-
...separator !== void 0 && { separator }
|
|
636
|
+
...separator !== void 0 && { separator },
|
|
637
|
+
...firstParagraphProperties !== void 0 && { firstParagraphProperties },
|
|
638
|
+
...secondParagraphProperties !== void 0 && { secondParagraphProperties }
|
|
609
639
|
};
|
|
610
640
|
}
|
|
611
641
|
if (type === "replaceInBlock") return {
|
|
@@ -3,12 +3,12 @@ import { Transaction } from "prosemirror-state";
|
|
|
3
3
|
import { MarkType } from "prosemirror-model";
|
|
4
4
|
//#region src/internal/headlessRevisionResolution.d.ts
|
|
5
5
|
type HeadlessRevisionResolutionMode = "accept" | "reject";
|
|
6
|
-
type
|
|
6
|
+
type HeadlessRange = {
|
|
7
7
|
from: number;
|
|
8
8
|
to: number;
|
|
9
9
|
};
|
|
10
10
|
type HeadlessInlineChangeTracking = {
|
|
11
|
-
ranges: readonly
|
|
11
|
+
ranges: readonly HeadlessRange[];
|
|
12
12
|
mappingFrom: number;
|
|
13
13
|
};
|
|
14
14
|
type AppendHeadlessInlineResolutionOptions = {
|
|
@@ -76,6 +76,7 @@ const resolveInlineNode = ({ node, context, paragraphScope }) => {
|
|
|
76
76
|
};
|
|
77
77
|
const resolveInlineContent = ({ node, position, context, inheritedParagraphScope }) => {
|
|
78
78
|
const paragraphScope = node.type.name === "paragraph" && context.mode === "reject" ? { paragraph: node } : inheritedParagraphScope;
|
|
79
|
+
const replacementRangeStart = context.replacementRanges.length;
|
|
79
80
|
let resolvedNode = node;
|
|
80
81
|
if (node.isInline) {
|
|
81
82
|
const resolved = resolveInlineNode({
|
|
@@ -84,9 +85,10 @@ const resolveInlineContent = ({ node, position, context, inheritedParagraphScope
|
|
|
84
85
|
paragraphScope
|
|
85
86
|
});
|
|
86
87
|
if (resolved === null) {
|
|
87
|
-
context.
|
|
88
|
+
context.replacementRanges.push({
|
|
88
89
|
from: position,
|
|
89
|
-
to: position + node.nodeSize
|
|
90
|
+
to: position + node.nodeSize,
|
|
91
|
+
newSize: 0
|
|
90
92
|
});
|
|
91
93
|
return null;
|
|
92
94
|
}
|
|
@@ -109,17 +111,36 @@ const resolveInlineContent = ({ node, position, context, inheritedParagraphScope
|
|
|
109
111
|
} else contentChanged = true;
|
|
110
112
|
});
|
|
111
113
|
if (!contentChanged) return resolvedNode;
|
|
114
|
+
let resolvedContent = Fragment.fromArray(children);
|
|
115
|
+
if (!resolvedNode.type.validContent(resolvedContent)) {
|
|
116
|
+
const fitted = resolvedNode.type.createAndFill(resolvedNode.attrs, resolvedContent, resolvedNode.marks);
|
|
117
|
+
if (!fitted || !resolvedNode.type.validContent(fitted.content)) {
|
|
118
|
+
if (!resolvedNode.isInline) return panic(`Headless inline resolution invalidated ${resolvedNode.type.name} content`);
|
|
119
|
+
context.replacementRanges.splice(replacementRangeStart, context.replacementRanges.length - replacementRangeStart, {
|
|
120
|
+
from: position,
|
|
121
|
+
to: position + node.nodeSize,
|
|
122
|
+
newSize: 0
|
|
123
|
+
});
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
context.replacementRanges.splice(replacementRangeStart, context.replacementRanges.length - replacementRangeStart, {
|
|
127
|
+
from: contentStart,
|
|
128
|
+
to: contentStart + node.content.size,
|
|
129
|
+
newSize: fitted.content.size
|
|
130
|
+
});
|
|
131
|
+
resolvedContent = fitted.content;
|
|
132
|
+
}
|
|
112
133
|
if (resolvedNode.type.name === "paragraph") context.changedParagraphRanges.push({
|
|
113
134
|
from: position,
|
|
114
135
|
to: position + resolvedNode.nodeSize
|
|
115
136
|
});
|
|
116
|
-
return rebuildNode(node, resolvedNode.attrs,
|
|
137
|
+
return rebuildNode(node, resolvedNode.attrs, resolvedContent, resolvedNode.marks);
|
|
117
138
|
};
|
|
118
|
-
const
|
|
139
|
+
const coalesceReplacementRanges = (replacementRanges) => {
|
|
119
140
|
const coalesced = [];
|
|
120
|
-
for (const range of
|
|
141
|
+
for (const range of replacementRanges) {
|
|
121
142
|
const previous = coalesced.at(-1);
|
|
122
|
-
if (previous && range.from <= previous.to) previous.to = Math.max(previous.to, range.to);
|
|
143
|
+
if (previous && previous.newSize === 0 && range.newSize === 0 && range.from <= previous.to) previous.to = Math.max(previous.to, range.to);
|
|
123
144
|
else coalesced.push({ ...range });
|
|
124
145
|
}
|
|
125
146
|
return coalesced;
|
|
@@ -129,7 +150,7 @@ const appendHeadlessInlineResolution = ({ tr, mode, keepType, removeType, styleR
|
|
|
129
150
|
mode,
|
|
130
151
|
keepType,
|
|
131
152
|
removeType,
|
|
132
|
-
|
|
153
|
+
replacementRanges: [],
|
|
133
154
|
changedParagraphRanges: [],
|
|
134
155
|
styleResolver
|
|
135
156
|
};
|
|
@@ -139,10 +160,10 @@ const appendHeadlessInlineResolution = ({ tr, mode, keepType, removeType, styleR
|
|
|
139
160
|
context
|
|
140
161
|
});
|
|
141
162
|
if (!resolved || resolved.eq(tr.doc)) return null;
|
|
142
|
-
const positionMap = new StepMap(
|
|
163
|
+
const positionMap = new StepMap(coalesceReplacementRanges(context.replacementRanges).flatMap(({ from, to, newSize }) => [
|
|
143
164
|
from,
|
|
144
165
|
to - from,
|
|
145
|
-
|
|
166
|
+
newSize
|
|
146
167
|
]));
|
|
147
168
|
const mappingFrom = tr.steps.length;
|
|
148
169
|
tr.step(new HeadlessInlineResolutionStep(0, tr.doc.content.size, new Slice(resolved.content, 0, 0), positionMap));
|
|
@@ -50,7 +50,6 @@ function removeCommentMark(commentId) {
|
|
|
50
50
|
return true;
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
|
-
const BULK_INLINE_RESOLUTION_THRESHOLD = 256;
|
|
54
53
|
/**
|
|
55
54
|
* Resolve a tracked change: accept or reject.
|
|
56
55
|
* - Accept: keep insertions (remove mark), delete deletions (remove text)
|
|
@@ -80,8 +79,6 @@ function resolveChange(from, to, mode, revisionIds, execution = "legacy") {
|
|
|
80
79
|
const pPrMarkOps = [];
|
|
81
80
|
const tableRowStructuralOps = [];
|
|
82
81
|
const tableCellStructuralOps = [];
|
|
83
|
-
const deferredRunPropertyChanges = [];
|
|
84
|
-
let bulkInlineCarrierCount = 0;
|
|
85
82
|
let removedSectionEndpointCount = 0;
|
|
86
83
|
const removedSectionReferences = [];
|
|
87
84
|
state.doc.nodesBetween(from, to, (node, pos) => {
|
|
@@ -156,31 +153,12 @@ function resolveChange(from, to, mode, revisionIds, execution = "legacy") {
|
|
|
156
153
|
return true;
|
|
157
154
|
}
|
|
158
155
|
if (!node.isInline) return true;
|
|
156
|
+
if (canBulkInlineResolution) return true;
|
|
159
157
|
const nodeEnd = pos + node.nodeSize;
|
|
160
158
|
const rangeFrom = Math.max(from, pos);
|
|
161
159
|
const rangeTo = Math.min(to, nodeEnd);
|
|
162
160
|
const runPropertyChangeMark = node.marks.find((mark) => mark.type.name === "runPropertyChange");
|
|
163
|
-
const resolvesRunPropertyChange = runPropertyChangeMark !== void 0 && expectRunPropertyChangeMarkAttrs(runPropertyChangeMark).changes.length > 0;
|
|
164
161
|
const removesNode = removeType !== void 0 && node.marks.some((mark) => mark.type === removeType && matchesRevision(mark));
|
|
165
|
-
const removesKeptMark = keepType !== void 0 && node.marks.some((mark) => mark.type === keepType && matchesRevision(mark));
|
|
166
|
-
if (canBulkInlineResolution) {
|
|
167
|
-
if (resolvesRunPropertyChange) deferredRunPropertyChanges.push({
|
|
168
|
-
tr,
|
|
169
|
-
node,
|
|
170
|
-
from: rangeFrom,
|
|
171
|
-
to: rangeTo,
|
|
172
|
-
mark: runPropertyChangeMark,
|
|
173
|
-
mode,
|
|
174
|
-
revisionSet,
|
|
175
|
-
styleResolver
|
|
176
|
-
});
|
|
177
|
-
if (removesNode) deleteRanges.push({
|
|
178
|
-
from: rangeFrom,
|
|
179
|
-
to: rangeTo
|
|
180
|
-
});
|
|
181
|
-
if (resolvesRunPropertyChange || removesNode || removesKeptMark) bulkInlineCarrierCount++;
|
|
182
|
-
return true;
|
|
183
|
-
}
|
|
184
162
|
if (runPropertyChangeMark) resolveRunPropertyChange({
|
|
185
163
|
tr,
|
|
186
164
|
node,
|
|
@@ -201,7 +179,7 @@ function resolveChange(from, to, mode, revisionIds, execution = "legacy") {
|
|
|
201
179
|
return true;
|
|
202
180
|
});
|
|
203
181
|
let bulkInlineChangeTracking = null;
|
|
204
|
-
if (canBulkInlineResolution
|
|
182
|
+
if (canBulkInlineResolution) bulkInlineChangeTracking = appendHeadlessInlineResolution({
|
|
205
183
|
tr,
|
|
206
184
|
mode,
|
|
207
185
|
keepType,
|
|
@@ -209,7 +187,6 @@ function resolveChange(from, to, mode, revisionIds, execution = "legacy") {
|
|
|
209
187
|
styleResolver
|
|
210
188
|
});
|
|
211
189
|
else {
|
|
212
|
-
for (const deferred of deferredRunPropertyChanges) resolveRunPropertyChange(deferred);
|
|
213
190
|
if (removeKeptMarksInBulk) tr.removeMark(from, to, keepType);
|
|
214
191
|
let rangesToDelete = deleteRanges;
|
|
215
192
|
if (revisionSet === null) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stll/folio-core",
|
|
3
|
-
"version": "0.37.
|
|
3
|
+
"version": "0.37.2",
|
|
4
4
|
"description": "Headless, framework-neutral core of folio: the OOXML (.docx) parser, document model, ProseMirror integration, and page-layout engine. No React.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"document-model",
|