@stll/folio-core 0.4.0 → 0.6.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.d.ts +3 -9
- package/dist/ai-edits/apply.js +94 -14
- package/dist/ai-edits/blockRange.d.ts +33 -0
- package/dist/ai-edits/blockRange.js +58 -0
- package/dist/ai-edits/headless.d.ts +37 -4
- package/dist/ai-edits/headless.js +134 -16
- package/dist/ai-edits/index.d.ts +5 -3
- package/dist/ai-edits/index.js +3 -2
- package/dist/ai-edits/snapshot.d.ts +14 -2
- package/dist/ai-edits/snapshot.js +12 -1
- package/dist/ai-edits/types.d.ts +42 -3
- package/dist/compat/eigenpal.d.ts +3 -2
- package/dist/compat/eigenpal.js +6 -5
- package/dist/controller/layoutPipeline.d.ts +1 -0
- package/dist/controller/layoutPipeline.js +35 -137
- package/dist/controller/layoutScheduler.d.ts +2 -1
- package/dist/controller/layoutScheduler.js +6 -0
- package/dist/document-operations.d.ts +139 -0
- package/dist/document-operations.js +578 -0
- package/dist/docx/blockContentParser.js +17 -7
- package/dist/docx/encryption/agileDecryption.js +2 -1
- package/dist/docx/encryption/compoundFile.js +4 -2
- package/dist/docx/footnoteParser.d.ts +1 -17
- package/dist/docx/footnoteParser.js +1 -27
- package/dist/docx/groupDrawingParser.d.ts +8 -0
- package/dist/docx/groupDrawingParser.js +131 -0
- package/dist/docx/numberingParser.d.ts +3 -1
- package/dist/docx/numberingParser.js +8 -2
- package/dist/docx/paragraphParser.js +9 -3
- package/dist/docx/runParser.js +8 -1
- package/dist/docx/selectiveXmlPatch.js +4 -10
- package/dist/docx/serializer/numberingSerializer.js +3 -1
- package/dist/docx/settingsParser.d.ts +6 -3
- package/dist/docx/settingsParser.js +2 -0
- package/dist/docx/xmlParser.js +11 -4
- package/dist/i18n/messages/catalogs.gen.d.ts +1054 -34
- package/dist/i18n/messages/catalogs.gen.js +1122 -68
- package/dist/i18n/messages/messages.gen.d.ts +62 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +6 -5
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +37 -1
- package/dist/layout-bridge/convert/footnoteLayout.js +138 -14
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +5 -7
- package/dist/layout-bridge/convert/headerFooterLayout.js +5 -7
- package/dist/layout-bridge/convert/toFlowBlocks.js +56 -19
- package/dist/layout-engine/index.d.ts +6 -5
- package/dist/layout-engine/index.js +143 -30
- package/dist/layout-engine/keep-together.d.ts +13 -8
- package/dist/layout-engine/keep-together.js +40 -25
- package/dist/layout-engine/layoutInstrumentation.d.ts +11 -1
- package/dist/layout-engine/layoutInstrumentation.js +10 -1
- package/dist/layout-engine/measure/measureBlocks.js +50 -18
- package/dist/layout-engine/measure/measureParagraph.js +61 -15
- package/dist/layout-engine/paginator.d.ts +2 -1
- package/dist/layout-engine/paginator.js +9 -3
- package/dist/layout-engine/textBoxGroup.d.ts +7 -0
- package/dist/layout-engine/textBoxGroup.js +8 -0
- package/dist/layout-engine/types.d.ts +30 -9
- package/dist/layout-engine/types.js +20 -2
- package/dist/layout-painter/renderPage.d.ts +15 -1
- package/dist/layout-painter/renderPage.js +27 -11
- package/dist/layout-painter/renderParagraph.js +18 -8
- package/dist/layout-painter/renderTable.js +10 -7
- package/dist/managers/DocumentLoaderManager.js +12 -5
- package/dist/model.js +1 -1
- package/dist/paged-layout/pageText.d.ts +14 -0
- package/dist/paged-layout/pageText.js +21 -0
- package/dist/prosemirror/attrs/index.js +2 -0
- package/dist/prosemirror/commands/comments.js +75 -5
- package/dist/prosemirror/commands/hyperlink.js +4 -2
- package/dist/prosemirror/commands/image.js +2 -1
- package/dist/prosemirror/commands/propertyChangeScope.d.ts +68 -0
- package/dist/prosemirror/commands/propertyChangeScope.js +242 -0
- package/dist/prosemirror/conversion/fromProseDoc.js +14 -3
- package/dist/prosemirror/conversion/toProseDoc.js +29 -8
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +2 -0
- package/dist/prosemirror/extensions/features/ListExtension.js +6 -0
- package/dist/prosemirror/extensions/nodes/TableExtension.js +10 -11
- package/dist/prosemirror/findReplaceSelection.js +2 -1
- package/dist/prosemirror/schema/nodes.d.ts +10 -5
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -0
- package/dist/prosemirror/utils/tabCalculator.js +4 -2
- package/dist/redline.d.ts +20 -0
- package/dist/redline.js +127 -0
- package/dist/server.d.ts +7 -3
- package/dist/server.js +6 -2
- package/dist/symbols.d.ts +35 -0
- package/dist/symbols.js +512 -0
- package/dist/utils/clipboard.js +3 -3
- package/dist/utils/fontLoader.js +3 -0
- package/dist/utils/fontResolver.js +48 -1
- package/dist/utils/zoom.d.ts +21 -0
- package/dist/utils/zoom.js +21 -0
- package/dist/version-comparison.d.ts +92 -0
- package/dist/version-comparison.js +436 -0
- package/package.json +3 -3
- package/dist/paged-layout/headerFooterMargins.d.ts +0 -75
- package/dist/paged-layout/headerFooterMargins.js +0 -122
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
import { applyFolioAIEditOperations, previewFolioAIEditOperations } from "./ai-edits/apply.js";
|
|
2
|
+
import { TaggedError } from "better-result";
|
|
3
|
+
//#region src/document-operations.ts
|
|
4
|
+
const FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION = 1;
|
|
5
|
+
const FOLIO_DOCUMENT_OPERATION_TYPES = Object.freeze([
|
|
6
|
+
"replaceInBlock",
|
|
7
|
+
"replaceRange",
|
|
8
|
+
"commentOnRange",
|
|
9
|
+
"formatRange",
|
|
10
|
+
"insertAfterBlock",
|
|
11
|
+
"insertBeforeBlock",
|
|
12
|
+
"replaceBlock",
|
|
13
|
+
"deleteBlock",
|
|
14
|
+
"commentOnBlock",
|
|
15
|
+
"insertSignatureTable"
|
|
16
|
+
]);
|
|
17
|
+
const FOLIO_DOCUMENT_OPERATION_MODES = Object.freeze(["direct", "tracked-changes"]);
|
|
18
|
+
const FOLIO_DOCUMENT_OPERATION_STORIES = Object.freeze(["main"]);
|
|
19
|
+
const FOLIO_DOCUMENT_OPERATION_PRECONDITIONS = Object.freeze(["blockTextHash"]);
|
|
20
|
+
const FOLIO_DOCUMENT_OPERATION_BATCH_MODES = Object.freeze(["best-effort", "atomic"]);
|
|
21
|
+
const DIRECT_AND_TRACKED_MODES = FOLIO_DOCUMENT_OPERATION_MODES;
|
|
22
|
+
const DIRECT_ONLY_MODES = Object.freeze(["direct"]);
|
|
23
|
+
const FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE = Object.freeze({
|
|
24
|
+
replaceInBlock: DIRECT_AND_TRACKED_MODES,
|
|
25
|
+
replaceRange: DIRECT_AND_TRACKED_MODES,
|
|
26
|
+
commentOnRange: DIRECT_AND_TRACKED_MODES,
|
|
27
|
+
formatRange: DIRECT_ONLY_MODES,
|
|
28
|
+
insertAfterBlock: DIRECT_AND_TRACKED_MODES,
|
|
29
|
+
insertBeforeBlock: DIRECT_AND_TRACKED_MODES,
|
|
30
|
+
replaceBlock: DIRECT_AND_TRACKED_MODES,
|
|
31
|
+
deleteBlock: DIRECT_AND_TRACKED_MODES,
|
|
32
|
+
commentOnBlock: DIRECT_AND_TRACKED_MODES,
|
|
33
|
+
insertSignatureTable: DIRECT_ONLY_MODES
|
|
34
|
+
});
|
|
35
|
+
const DOCUMENT_OPERATION_CAPABILITIES = Object.freeze({
|
|
36
|
+
version: 1,
|
|
37
|
+
operationTypes: FOLIO_DOCUMENT_OPERATION_TYPES,
|
|
38
|
+
modes: FOLIO_DOCUMENT_OPERATION_MODES,
|
|
39
|
+
modesByOperationType: FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE,
|
|
40
|
+
batchModes: FOLIO_DOCUMENT_OPERATION_BATCH_MODES,
|
|
41
|
+
dryRun: true,
|
|
42
|
+
preconditions: FOLIO_DOCUMENT_OPERATION_PRECONDITIONS,
|
|
43
|
+
stories: FOLIO_DOCUMENT_OPERATION_STORIES
|
|
44
|
+
});
|
|
45
|
+
const getFolioDocumentOperationCapabilities = () => DOCUMENT_OPERATION_CAPABILITIES;
|
|
46
|
+
const includesDocumentOperationMode = (supportedModes, mode) => supportedModes.includes(mode);
|
|
47
|
+
const isFolioDocumentOperationModeSupported = (operationType, mode) => {
|
|
48
|
+
const supportedModes = FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE[operationType];
|
|
49
|
+
if (supportedModes === void 0) return false;
|
|
50
|
+
return includesDocumentOperationMode(supportedModes, mode);
|
|
51
|
+
};
|
|
52
|
+
const isSupportedFolioDocumentOperationVersion = (value) => value === 1;
|
|
53
|
+
var UnsupportedFolioDocumentOperationVersionError = class extends TaggedError("UnsupportedFolioDocumentOperationVersionError")() {};
|
|
54
|
+
var InvalidFolioDocumentOperationBatchError = class extends TaggedError("InvalidFolioDocumentOperationBatchError")() {};
|
|
55
|
+
const assertSupportedFolioDocumentOperationVersion = (value) => {
|
|
56
|
+
if (isSupportedFolioDocumentOperationVersion(value)) return value;
|
|
57
|
+
throw new UnsupportedFolioDocumentOperationVersionError({
|
|
58
|
+
message: "Unsupported document operation contract version.",
|
|
59
|
+
receivedVersion: value
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
63
|
+
const invalidBatch = (path, reason) => {
|
|
64
|
+
throw new InvalidFolioDocumentOperationBatchError({
|
|
65
|
+
message: `Invalid document operation batch at ${path}: ${reason}.`,
|
|
66
|
+
path,
|
|
67
|
+
reason
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
const assertAllowedKeys = (value, path, allowedKeys) => {
|
|
71
|
+
const unexpected = Object.keys(value).find((key) => !allowedKeys.includes(key));
|
|
72
|
+
if (unexpected !== void 0) invalidBatch(`${path}.${unexpected}`, "unexpected property");
|
|
73
|
+
};
|
|
74
|
+
const readString = (value, key, path) => {
|
|
75
|
+
const candidate = value[key];
|
|
76
|
+
if (typeof candidate === "string") return candidate;
|
|
77
|
+
return invalidBatch(`${path}.${key}`, "expected a string");
|
|
78
|
+
};
|
|
79
|
+
const readOptionalString = (value, key, path) => {
|
|
80
|
+
const candidate = value[key];
|
|
81
|
+
if (candidate === void 0) return;
|
|
82
|
+
if (typeof candidate === "string") return candidate;
|
|
83
|
+
return invalidBatch(`${path}.${key}`, "expected a string when provided");
|
|
84
|
+
};
|
|
85
|
+
const readOptionalBoolean = (value, key, path) => {
|
|
86
|
+
const candidate = value[key];
|
|
87
|
+
if (candidate === void 0) return;
|
|
88
|
+
if (typeof candidate === "boolean") return candidate;
|
|
89
|
+
return invalidBatch(`${path}.${key}`, "expected a boolean when provided");
|
|
90
|
+
};
|
|
91
|
+
const readNonNegativeInteger = (value, key, path) => {
|
|
92
|
+
const candidate = value[key];
|
|
93
|
+
if (typeof candidate === "number" && Number.isInteger(candidate) && candidate >= 0) return candidate;
|
|
94
|
+
return invalidBatch(`${path}.${key}`, "expected a non-negative integer");
|
|
95
|
+
};
|
|
96
|
+
const readTextRange = (value, path) => {
|
|
97
|
+
const candidate = value["range"];
|
|
98
|
+
const rangePath = `${path}.range`;
|
|
99
|
+
if (!isPlainObject(candidate)) return invalidBatch(rangePath, "expected an object");
|
|
100
|
+
assertAllowedKeys(candidate, rangePath, [
|
|
101
|
+
"type",
|
|
102
|
+
"story",
|
|
103
|
+
"blockId",
|
|
104
|
+
"startOffset",
|
|
105
|
+
"endOffset",
|
|
106
|
+
"selectedTextHash"
|
|
107
|
+
]);
|
|
108
|
+
if (candidate["type"] !== "textRange") return invalidBatch(`${rangePath}.type`, "expected \"textRange\"");
|
|
109
|
+
if (candidate["story"] !== "main") return invalidBatch(`${rangePath}.story`, "expected \"main\"");
|
|
110
|
+
const blockId = readString(candidate, "blockId", rangePath);
|
|
111
|
+
if (blockId.length === 0) return invalidBatch(`${rangePath}.blockId`, "expected a non-empty string");
|
|
112
|
+
const startOffset = readNonNegativeInteger(candidate, "startOffset", rangePath);
|
|
113
|
+
const endOffset = readNonNegativeInteger(candidate, "endOffset", rangePath);
|
|
114
|
+
if (endOffset <= startOffset) return invalidBatch(`${rangePath}.endOffset`, "expected a value greater than startOffset");
|
|
115
|
+
const selectedTextHash = readString(candidate, "selectedTextHash", rangePath);
|
|
116
|
+
if (!/^h[0-9a-z]+$/.test(selectedTextHash)) return invalidBatch(`${rangePath}.selectedTextHash`, "expected a normalized text hash");
|
|
117
|
+
return {
|
|
118
|
+
type: "textRange",
|
|
119
|
+
story: "main",
|
|
120
|
+
blockId,
|
|
121
|
+
startOffset,
|
|
122
|
+
endOffset,
|
|
123
|
+
selectedTextHash
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
const readInlineFormatting = (value, path) => {
|
|
127
|
+
const candidate = value["formatting"];
|
|
128
|
+
const formattingPath = `${path}.formatting`;
|
|
129
|
+
if (!isPlainObject(candidate)) return invalidBatch(formattingPath, "expected an object");
|
|
130
|
+
assertAllowedKeys(candidate, formattingPath, [
|
|
131
|
+
"bold",
|
|
132
|
+
"italic",
|
|
133
|
+
"underline"
|
|
134
|
+
]);
|
|
135
|
+
const bold = readOptionalBoolean(candidate, "bold", formattingPath);
|
|
136
|
+
const italic = readOptionalBoolean(candidate, "italic", formattingPath);
|
|
137
|
+
const underline = readOptionalBoolean(candidate, "underline", formattingPath);
|
|
138
|
+
if (bold === void 0 && italic === void 0 && underline === void 0) return invalidBatch(formattingPath, "expected at least one formatting property");
|
|
139
|
+
return {
|
|
140
|
+
...bold !== void 0 && { bold },
|
|
141
|
+
...italic !== void 0 && { italic },
|
|
142
|
+
...underline !== void 0 && { underline }
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
const readOptionalComment = (value, path) => {
|
|
146
|
+
const candidate = value["comment"];
|
|
147
|
+
if (candidate === void 0) return;
|
|
148
|
+
if (isPlainObject(candidate)) {
|
|
149
|
+
assertAllowedKeys(candidate, `${path}.comment`, ["text"]);
|
|
150
|
+
return { text: readString(candidate, "text", `${path}.comment`) };
|
|
151
|
+
}
|
|
152
|
+
return invalidBatch(`${path}.comment`, "expected an object when provided");
|
|
153
|
+
};
|
|
154
|
+
const readOptionalPrecondition = (value, path) => {
|
|
155
|
+
const candidate = value["precondition"];
|
|
156
|
+
if (candidate === void 0) return;
|
|
157
|
+
if (!isPlainObject(candidate)) return invalidBatch(`${path}.precondition`, "expected an object when provided");
|
|
158
|
+
const preconditionPath = `${path}.precondition`;
|
|
159
|
+
assertAllowedKeys(candidate, preconditionPath, ["blockTextHash"]);
|
|
160
|
+
const blockTextHash = readString(candidate, "blockTextHash", preconditionPath);
|
|
161
|
+
if (!/^h[0-9a-z]+$/.test(blockTextHash)) return invalidBatch(`${preconditionPath}.blockTextHash`, "expected a normalized block text hash");
|
|
162
|
+
return { blockTextHash };
|
|
163
|
+
};
|
|
164
|
+
const isReviewSeverity = (value) => value === "low" || value === "medium" || value === "high";
|
|
165
|
+
const readReviewMeta = (value, path) => {
|
|
166
|
+
const severity = value["severity"];
|
|
167
|
+
if (severity !== void 0 && !isReviewSeverity(severity)) return invalidBatch(`${path}.severity`, "expected \"low\", \"medium\", or \"high\" when provided");
|
|
168
|
+
const area = readOptionalString(value, "area", path);
|
|
169
|
+
return {
|
|
170
|
+
...severity !== void 0 && { severity },
|
|
171
|
+
...area !== void 0 && { area }
|
|
172
|
+
};
|
|
173
|
+
};
|
|
174
|
+
const COMMON_OPERATION_KEYS = [
|
|
175
|
+
"id",
|
|
176
|
+
"type",
|
|
177
|
+
"blockId",
|
|
178
|
+
"severity",
|
|
179
|
+
"area",
|
|
180
|
+
"precondition"
|
|
181
|
+
];
|
|
182
|
+
const parseSignatureParties = (value, path) => {
|
|
183
|
+
const parties = value["parties"];
|
|
184
|
+
if (!Array.isArray(parties)) return invalidBatch(`${path}.parties`, "expected an array");
|
|
185
|
+
return parties.map((party, index) => {
|
|
186
|
+
const partyPath = `${path}.parties[${index}]`;
|
|
187
|
+
if (!isPlainObject(party)) return invalidBatch(partyPath, "expected an object");
|
|
188
|
+
assertAllowedKeys(party, partyPath, [
|
|
189
|
+
"name",
|
|
190
|
+
"signatory",
|
|
191
|
+
"title"
|
|
192
|
+
]);
|
|
193
|
+
const signatory = readOptionalString(party, "signatory", partyPath);
|
|
194
|
+
const title = readOptionalString(party, "title", partyPath);
|
|
195
|
+
const parsedParty = { name: readString(party, "name", partyPath) };
|
|
196
|
+
if (signatory !== void 0) parsedParty.signatory = signatory;
|
|
197
|
+
if (title !== void 0) parsedParty.title = title;
|
|
198
|
+
return parsedParty;
|
|
199
|
+
});
|
|
200
|
+
};
|
|
201
|
+
const parseDocumentOperation = (value, index) => {
|
|
202
|
+
const path = `$.operations[${index}]`;
|
|
203
|
+
if (!isPlainObject(value)) return invalidBatch(path, "expected an object");
|
|
204
|
+
const id = readString(value, "id", path);
|
|
205
|
+
const type = readString(value, "type", path);
|
|
206
|
+
const reviewMeta = readReviewMeta(value, path);
|
|
207
|
+
const precondition = readOptionalPrecondition(value, path);
|
|
208
|
+
const comment = readOptionalComment(value, path);
|
|
209
|
+
const operationMeta = {
|
|
210
|
+
...reviewMeta,
|
|
211
|
+
...precondition !== void 0 && { precondition }
|
|
212
|
+
};
|
|
213
|
+
if (type === "replaceRange") {
|
|
214
|
+
assertAllowedKeys(value, path, [
|
|
215
|
+
"id",
|
|
216
|
+
"type",
|
|
217
|
+
"range",
|
|
218
|
+
"replace",
|
|
219
|
+
"comment",
|
|
220
|
+
"severity",
|
|
221
|
+
"area",
|
|
222
|
+
"precondition"
|
|
223
|
+
]);
|
|
224
|
+
return {
|
|
225
|
+
...operationMeta,
|
|
226
|
+
id,
|
|
227
|
+
type,
|
|
228
|
+
range: readTextRange(value, path),
|
|
229
|
+
replace: readString(value, "replace", path),
|
|
230
|
+
...comment !== void 0 && { comment }
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
if (type === "commentOnRange") {
|
|
234
|
+
assertAllowedKeys(value, path, [
|
|
235
|
+
"id",
|
|
236
|
+
"type",
|
|
237
|
+
"range",
|
|
238
|
+
"comment",
|
|
239
|
+
"severity",
|
|
240
|
+
"area",
|
|
241
|
+
"precondition"
|
|
242
|
+
]);
|
|
243
|
+
if (comment === void 0) return invalidBatch(`${path}.comment`, "expected an object");
|
|
244
|
+
return {
|
|
245
|
+
...operationMeta,
|
|
246
|
+
id,
|
|
247
|
+
type,
|
|
248
|
+
range: readTextRange(value, path),
|
|
249
|
+
comment
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
if (type === "formatRange") {
|
|
253
|
+
assertAllowedKeys(value, path, [
|
|
254
|
+
"id",
|
|
255
|
+
"type",
|
|
256
|
+
"range",
|
|
257
|
+
"formatting",
|
|
258
|
+
"severity",
|
|
259
|
+
"area",
|
|
260
|
+
"precondition"
|
|
261
|
+
]);
|
|
262
|
+
return {
|
|
263
|
+
...operationMeta,
|
|
264
|
+
id,
|
|
265
|
+
type,
|
|
266
|
+
range: readTextRange(value, path),
|
|
267
|
+
formatting: readInlineFormatting(value, path)
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
const blockId = readString(value, "blockId", path);
|
|
271
|
+
if (type === "replaceInBlock") {
|
|
272
|
+
assertAllowedKeys(value, path, [
|
|
273
|
+
...COMMON_OPERATION_KEYS,
|
|
274
|
+
"find",
|
|
275
|
+
"replace",
|
|
276
|
+
"comment"
|
|
277
|
+
]);
|
|
278
|
+
return {
|
|
279
|
+
...operationMeta,
|
|
280
|
+
id,
|
|
281
|
+
type,
|
|
282
|
+
blockId,
|
|
283
|
+
find: readString(value, "find", path),
|
|
284
|
+
replace: readString(value, "replace", path),
|
|
285
|
+
...comment !== void 0 && { comment }
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
if (type === "insertAfterBlock" || type === "insertBeforeBlock") {
|
|
289
|
+
assertAllowedKeys(value, path, [
|
|
290
|
+
...COMMON_OPERATION_KEYS,
|
|
291
|
+
"text",
|
|
292
|
+
"inheritFormatting",
|
|
293
|
+
"pageBreakBefore",
|
|
294
|
+
"styleId",
|
|
295
|
+
"comment"
|
|
296
|
+
]);
|
|
297
|
+
const inheritFormatting = readOptionalBoolean(value, "inheritFormatting", path);
|
|
298
|
+
const pageBreakBefore = readOptionalBoolean(value, "pageBreakBefore", path);
|
|
299
|
+
const styleId = readOptionalString(value, "styleId", path);
|
|
300
|
+
return {
|
|
301
|
+
...operationMeta,
|
|
302
|
+
id,
|
|
303
|
+
type,
|
|
304
|
+
blockId,
|
|
305
|
+
text: readString(value, "text", path),
|
|
306
|
+
...inheritFormatting !== void 0 && { inheritFormatting },
|
|
307
|
+
...pageBreakBefore !== void 0 && { pageBreakBefore },
|
|
308
|
+
...styleId !== void 0 && { styleId },
|
|
309
|
+
...comment !== void 0 && { comment }
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
if (type === "replaceBlock") {
|
|
313
|
+
assertAllowedKeys(value, path, [
|
|
314
|
+
...COMMON_OPERATION_KEYS,
|
|
315
|
+
"text",
|
|
316
|
+
"preserveFormatting",
|
|
317
|
+
"styleId",
|
|
318
|
+
"comment"
|
|
319
|
+
]);
|
|
320
|
+
const preserveFormatting = readOptionalBoolean(value, "preserveFormatting", path);
|
|
321
|
+
const styleId = readOptionalString(value, "styleId", path);
|
|
322
|
+
return {
|
|
323
|
+
...operationMeta,
|
|
324
|
+
id,
|
|
325
|
+
type,
|
|
326
|
+
blockId,
|
|
327
|
+
text: readString(value, "text", path),
|
|
328
|
+
...preserveFormatting !== void 0 && { preserveFormatting },
|
|
329
|
+
...styleId !== void 0 && { styleId },
|
|
330
|
+
...comment !== void 0 && { comment }
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
if (type === "deleteBlock") {
|
|
334
|
+
assertAllowedKeys(value, path, [...COMMON_OPERATION_KEYS, "comment"]);
|
|
335
|
+
return {
|
|
336
|
+
...operationMeta,
|
|
337
|
+
id,
|
|
338
|
+
type,
|
|
339
|
+
blockId,
|
|
340
|
+
...comment !== void 0 && { comment }
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
if (type === "commentOnBlock") {
|
|
344
|
+
assertAllowedKeys(value, path, [
|
|
345
|
+
...COMMON_OPERATION_KEYS,
|
|
346
|
+
"quote",
|
|
347
|
+
"comment"
|
|
348
|
+
]);
|
|
349
|
+
if (comment === void 0) return invalidBatch(`${path}.comment`, "expected an object");
|
|
350
|
+
const quote = readOptionalString(value, "quote", path);
|
|
351
|
+
return {
|
|
352
|
+
...operationMeta,
|
|
353
|
+
id,
|
|
354
|
+
type,
|
|
355
|
+
blockId,
|
|
356
|
+
...quote !== void 0 && { quote },
|
|
357
|
+
comment
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
if (type === "insertSignatureTable") {
|
|
361
|
+
assertAllowedKeys(value, path, [
|
|
362
|
+
...COMMON_OPERATION_KEYS,
|
|
363
|
+
"position",
|
|
364
|
+
"parties",
|
|
365
|
+
"comment"
|
|
366
|
+
]);
|
|
367
|
+
const position = value["position"];
|
|
368
|
+
if (position !== void 0 && position !== "after" && position !== "before") return invalidBatch(`${path}.position`, "expected \"after\" or \"before\" when provided");
|
|
369
|
+
return {
|
|
370
|
+
...operationMeta,
|
|
371
|
+
id,
|
|
372
|
+
type,
|
|
373
|
+
blockId,
|
|
374
|
+
...position !== void 0 && { position },
|
|
375
|
+
parties: parseSignatureParties(value, path),
|
|
376
|
+
...comment !== void 0 && { comment }
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
return invalidBatch(`${path}.type`, `unsupported operation type "${type}"`);
|
|
380
|
+
};
|
|
381
|
+
const parseFolioDocumentOperationBatch = (value) => {
|
|
382
|
+
if (!isPlainObject(value)) return invalidBatch("$", "expected an object");
|
|
383
|
+
assertAllowedKeys(value, "$", [
|
|
384
|
+
"version",
|
|
385
|
+
"operations",
|
|
386
|
+
"mode",
|
|
387
|
+
"atomic",
|
|
388
|
+
"dryRun"
|
|
389
|
+
]);
|
|
390
|
+
const version = assertSupportedFolioDocumentOperationVersion(value["version"]);
|
|
391
|
+
const operations = value["operations"];
|
|
392
|
+
if (!Array.isArray(operations)) return invalidBatch("$.operations", "expected an array");
|
|
393
|
+
const mode = value["mode"];
|
|
394
|
+
if (mode !== void 0 && mode !== "direct" && mode !== "tracked-changes") return invalidBatch("$.mode", "expected \"direct\" or \"tracked-changes\" when provided");
|
|
395
|
+
const atomic = readOptionalBoolean(value, "atomic", "$");
|
|
396
|
+
const dryRun = readOptionalBoolean(value, "dryRun", "$");
|
|
397
|
+
const parsedOperations = operations.map(parseDocumentOperation);
|
|
398
|
+
const operationIds = /* @__PURE__ */ new Set();
|
|
399
|
+
for (const [index, operation] of parsedOperations.entries()) {
|
|
400
|
+
if (operationIds.has(operation.id)) return invalidBatch(`$.operations[${index}].id`, "expected a unique operation id");
|
|
401
|
+
operationIds.add(operation.id);
|
|
402
|
+
}
|
|
403
|
+
return {
|
|
404
|
+
version,
|
|
405
|
+
operations: parsedOperations,
|
|
406
|
+
...mode !== void 0 && { mode },
|
|
407
|
+
...atomic !== void 0 && { atomic },
|
|
408
|
+
...dryRun !== void 0 && { dryRun }
|
|
409
|
+
};
|
|
410
|
+
};
|
|
411
|
+
const recoveryByReason = {
|
|
412
|
+
missingBlock: "refreshDocument",
|
|
413
|
+
changedBlock: "refreshDocument",
|
|
414
|
+
ambiguousFind: "narrowMatch",
|
|
415
|
+
missingFind: "refreshDocument",
|
|
416
|
+
unsupportedBlock: "changeTarget",
|
|
417
|
+
unsupportedMode: "changeMode",
|
|
418
|
+
atomicBatchRejected: "inspectBatch",
|
|
419
|
+
preconditionFailed: "refreshDocument",
|
|
420
|
+
staleRange: "refreshDocument",
|
|
421
|
+
emptyOperation: "removeOperation",
|
|
422
|
+
noopOperation: "removeOperation"
|
|
423
|
+
};
|
|
424
|
+
const getFolioDocumentOperationIssues = (operations, skipped) => {
|
|
425
|
+
const indexById = /* @__PURE__ */ new Map();
|
|
426
|
+
operations.forEach(({ id }, index) => {
|
|
427
|
+
indexById.set(id, index);
|
|
428
|
+
});
|
|
429
|
+
return skipped.map(({ id, reason }) => {
|
|
430
|
+
const operationIndex = indexById.get(id) ?? -1;
|
|
431
|
+
return {
|
|
432
|
+
operationId: id,
|
|
433
|
+
operationIndex,
|
|
434
|
+
path: `$.operations[${operationIndex}]`,
|
|
435
|
+
code: reason,
|
|
436
|
+
retryable: reason !== "emptyOperation" && reason !== "noopOperation",
|
|
437
|
+
recovery: recoveryByReason[reason]
|
|
438
|
+
};
|
|
439
|
+
});
|
|
440
|
+
};
|
|
441
|
+
const getPrimaryAffectedTarget = (operation) => {
|
|
442
|
+
switch (operation.type) {
|
|
443
|
+
case "replaceInBlock":
|
|
444
|
+
case "replaceBlock": return {
|
|
445
|
+
type: "block",
|
|
446
|
+
story: "main",
|
|
447
|
+
blockId: operation.blockId,
|
|
448
|
+
effect: "updated"
|
|
449
|
+
};
|
|
450
|
+
case "replaceRange": return {
|
|
451
|
+
type: "block",
|
|
452
|
+
story: "main",
|
|
453
|
+
blockId: operation.range.blockId,
|
|
454
|
+
effect: "updated"
|
|
455
|
+
};
|
|
456
|
+
case "commentOnRange": return {
|
|
457
|
+
type: "textRange",
|
|
458
|
+
range: operation.range,
|
|
459
|
+
effect: "commented"
|
|
460
|
+
};
|
|
461
|
+
case "formatRange": return {
|
|
462
|
+
type: "textRange",
|
|
463
|
+
range: operation.range,
|
|
464
|
+
effect: "formatted"
|
|
465
|
+
};
|
|
466
|
+
case "insertAfterBlock":
|
|
467
|
+
case "insertBeforeBlock": return {
|
|
468
|
+
type: "insertion",
|
|
469
|
+
story: "main",
|
|
470
|
+
anchorBlockId: operation.blockId,
|
|
471
|
+
position: operation.type === "insertBeforeBlock" ? "before" : "after",
|
|
472
|
+
content: "block"
|
|
473
|
+
};
|
|
474
|
+
case "deleteBlock": return {
|
|
475
|
+
type: "block",
|
|
476
|
+
story: "main",
|
|
477
|
+
blockId: operation.blockId,
|
|
478
|
+
effect: "deleted"
|
|
479
|
+
};
|
|
480
|
+
case "commentOnBlock": return {
|
|
481
|
+
type: "block",
|
|
482
|
+
story: "main",
|
|
483
|
+
blockId: operation.blockId,
|
|
484
|
+
effect: "commented"
|
|
485
|
+
};
|
|
486
|
+
case "insertSignatureTable": return {
|
|
487
|
+
type: "insertion",
|
|
488
|
+
story: "main",
|
|
489
|
+
anchorBlockId: operation.blockId,
|
|
490
|
+
position: operation.position ?? "after",
|
|
491
|
+
content: "signatureTable"
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
/** Build deterministic affected-target receipts from operations and their applied entries. */
|
|
496
|
+
const getFolioDocumentOperationReceipts = (operations, applied) => {
|
|
497
|
+
const appliedById = /* @__PURE__ */ new Map();
|
|
498
|
+
applied.forEach((operation) => {
|
|
499
|
+
appliedById.set(operation.id, operation);
|
|
500
|
+
});
|
|
501
|
+
const receipts = [];
|
|
502
|
+
operations.forEach((operation, operationIndex) => {
|
|
503
|
+
const appliedOperation = appliedById.get(operation.id);
|
|
504
|
+
if (!appliedOperation) return;
|
|
505
|
+
const affected = [getPrimaryAffectedTarget(operation)];
|
|
506
|
+
if (appliedOperation.commentId !== void 0) affected.push({
|
|
507
|
+
type: "comment",
|
|
508
|
+
commentId: appliedOperation.commentId
|
|
509
|
+
});
|
|
510
|
+
receipts.push({
|
|
511
|
+
operationId: operation.id,
|
|
512
|
+
operationIndex,
|
|
513
|
+
affected
|
|
514
|
+
});
|
|
515
|
+
});
|
|
516
|
+
return receipts;
|
|
517
|
+
};
|
|
518
|
+
const applyFolioDocumentOperations = ({ view, snapshot, batch, author, createCommentId, createUndoHandle }) => {
|
|
519
|
+
const parsedBatch = parseFolioDocumentOperationBatch(batch);
|
|
520
|
+
const apply = ({ targetView, targetCreateCommentId = createCommentId, preview = false }) => {
|
|
521
|
+
return (preview ? previewFolioAIEditOperations : applyFolioAIEditOperations)({
|
|
522
|
+
view: targetView,
|
|
523
|
+
snapshot,
|
|
524
|
+
operations: parsedBatch.operations,
|
|
525
|
+
mode: parsedBatch.mode ?? "tracked-changes",
|
|
526
|
+
...author !== void 0 && { author },
|
|
527
|
+
...targetCreateCommentId !== void 0 && { createCommentId: targetCreateCommentId }
|
|
528
|
+
});
|
|
529
|
+
};
|
|
530
|
+
const preview = () => apply({
|
|
531
|
+
targetView: view,
|
|
532
|
+
preview: true
|
|
533
|
+
});
|
|
534
|
+
const atomicResult = (previewResult, status) => {
|
|
535
|
+
const skippedById = new Map(previewResult.skipped.map((operation) => [operation.id, operation]));
|
|
536
|
+
const skipped = parsedBatch.operations.map(({ id }) => skippedById.get(id) ?? {
|
|
537
|
+
id,
|
|
538
|
+
reason: "atomicBatchRejected"
|
|
539
|
+
});
|
|
540
|
+
return {
|
|
541
|
+
version: 1,
|
|
542
|
+
status,
|
|
543
|
+
applied: [],
|
|
544
|
+
skipped,
|
|
545
|
+
issues: getFolioDocumentOperationIssues(parsedBatch.operations, skipped),
|
|
546
|
+
receipts: [],
|
|
547
|
+
undoHandle: null
|
|
548
|
+
};
|
|
549
|
+
};
|
|
550
|
+
if (parsedBatch.dryRun === true) {
|
|
551
|
+
const previewResult = preview();
|
|
552
|
+
if (parsedBatch.atomic === true && previewResult.skipped.length > 0) return atomicResult(previewResult, "previewed");
|
|
553
|
+
return {
|
|
554
|
+
version: 1,
|
|
555
|
+
status: "previewed",
|
|
556
|
+
applied: previewResult.applied.map(({ id }) => ({ id })),
|
|
557
|
+
skipped: previewResult.skipped,
|
|
558
|
+
issues: getFolioDocumentOperationIssues(parsedBatch.operations, previewResult.skipped),
|
|
559
|
+
receipts: getFolioDocumentOperationReceipts(parsedBatch.operations, previewResult.applied),
|
|
560
|
+
undoHandle: null
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
if (parsedBatch.atomic === true) {
|
|
564
|
+
const previewResult = preview();
|
|
565
|
+
if (previewResult.skipped.length > 0) return atomicResult(previewResult, "rejected");
|
|
566
|
+
}
|
|
567
|
+
const result = apply({ targetView: view });
|
|
568
|
+
return {
|
|
569
|
+
version: 1,
|
|
570
|
+
status: "committed",
|
|
571
|
+
...result,
|
|
572
|
+
issues: getFolioDocumentOperationIssues(parsedBatch.operations, result.skipped),
|
|
573
|
+
receipts: getFolioDocumentOperationReceipts(parsedBatch.operations, result.applied),
|
|
574
|
+
undoHandle: result.applied.length > 0 && createUndoHandle !== void 0 ? createUndoHandle() : null
|
|
575
|
+
};
|
|
576
|
+
};
|
|
577
|
+
//#endregion
|
|
578
|
+
export { FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch };
|
|
@@ -57,19 +57,30 @@ const computeListMarker = (paragraph, numbering, listCounters, abstractCounters)
|
|
|
57
57
|
if (!listRendering || !numbering) return;
|
|
58
58
|
const { numId, level } = listRendering;
|
|
59
59
|
if (numId === 0) return;
|
|
60
|
-
if (!listCounters.has(numId)) listCounters.set(numId, Array.from({ length: 9 }).fill(
|
|
60
|
+
if (!listCounters.has(numId)) listCounters.set(numId, Array.from({ length: 9 }).fill(NaN));
|
|
61
61
|
const counters = listCounters.get(numId);
|
|
62
62
|
if (!counters) return;
|
|
63
63
|
const abstractNumId = numbering.getAbstractNumId(numId);
|
|
64
|
+
const styleNumbering = paragraph.formatting?.numPrFromStyle;
|
|
65
|
+
if (abstractNumId !== null && styleNumbering) {
|
|
66
|
+
const latestAbstractCounters = abstractCounters.get(abstractNumId);
|
|
67
|
+
if (latestAbstractCounters) for (let i = 0; i < counters.length; i += 1) counters[i] = latestAbstractCounters[i] ?? NaN;
|
|
68
|
+
}
|
|
64
69
|
if (abstractNumId !== null && level > 0) {
|
|
65
70
|
const latestAbstractCounters = abstractCounters.get(abstractNumId);
|
|
66
|
-
|
|
67
|
-
|
|
71
|
+
if (counters.slice(0, level).every(Number.isNaN)) for (let i = 0; i < level; i += 1) {
|
|
72
|
+
const latestCounter = latestAbstractCounters?.[i];
|
|
73
|
+
counters[i] = latestCounter !== void 0 && !Number.isNaN(latestCounter) ? latestCounter : numbering.getLevel(numId, i)?.start ?? 1;
|
|
74
|
+
}
|
|
68
75
|
}
|
|
69
|
-
counters[level]
|
|
70
|
-
|
|
76
|
+
if (Number.isNaN(counters[level])) counters[level] = (numbering.getLevel(numId, level)?.start ?? 1) - 1;
|
|
77
|
+
counters[level] = (counters[level] ?? 0) + 1;
|
|
78
|
+
for (let i = level + 1; i < counters.length; i += 1) counters[i] = NaN;
|
|
71
79
|
const childAdvances = listRendering.implicitChildLevelAdvances ?? 0;
|
|
72
|
-
if (childAdvances > 0 && level + 1 < counters.length)
|
|
80
|
+
if (childAdvances > 0 && level + 1 < counters.length) {
|
|
81
|
+
const childCounter = counters[level + 1];
|
|
82
|
+
counters[level + 1] = (childCounter === void 0 || Number.isNaN(childCounter) ? 0 : childCounter) + childAdvances;
|
|
83
|
+
}
|
|
73
84
|
if (abstractNumId !== null) abstractCounters.set(abstractNumId, [...counters]);
|
|
74
85
|
const pattern = listRendering.marker;
|
|
75
86
|
if (listRendering.isBullet) {
|
|
@@ -90,7 +101,6 @@ const computeListMarker = (paragraph, numbering, listCounters, abstractCounters)
|
|
|
90
101
|
listRendering.marker = computedMarker;
|
|
91
102
|
};
|
|
92
103
|
const enrichParagraphTextBoxes = (paragraph, paraXml, styles, theme, numbering, rels, media) => {
|
|
93
|
-
if (paragraph.content.length === 0) return;
|
|
94
104
|
const xmlChildren = getChildElements(paraXml);
|
|
95
105
|
let parsedIndex = 0;
|
|
96
106
|
let lastConsumedRun;
|
|
@@ -124,7 +124,8 @@ const hashPassword = async (password, salt, iterations, hashName) => {
|
|
|
124
124
|
};
|
|
125
125
|
/** [MS-OFFCRYPTO] 2.3.4.9 — finalize a derived key with a block key suffix. */
|
|
126
126
|
const finalizeDerivedKey = async (passwordHash, blockKey, hashName, keyBits) => {
|
|
127
|
-
|
|
127
|
+
const algorithm = webHashName(hashName);
|
|
128
|
+
return (await digest(algorithm, joinBytes([passwordHash, blockKey]))).subarray(0, keyBits / 8);
|
|
128
129
|
};
|
|
129
130
|
const segmentIv = async (packageSalt, segmentIndex, hashName, blockBytes) => {
|
|
130
131
|
return (await digest(webHashName(hashName), joinBytes([packageSalt, writeUint32Le(segmentIndex)]))).subarray(0, blockBytes);
|
|
@@ -86,7 +86,8 @@ const loadFatSectorIds = (file, header) => {
|
|
|
86
86
|
let chain = header.difatStart;
|
|
87
87
|
for (let step = 0; step < header.difatSectorCount; step++) {
|
|
88
88
|
if (chain === SECTOR_END || chain === SECTOR_FREE) break;
|
|
89
|
-
const
|
|
89
|
+
const sector = loadSector(file, header, chain);
|
|
90
|
+
const view = viewOf(sector);
|
|
90
91
|
const slotsPerSector = header.sectorBytes / 4 - 1;
|
|
91
92
|
for (let slot = 0; slot < slotsPerSector; slot++) {
|
|
92
93
|
const id = u32(view, slot * 4);
|
|
@@ -100,7 +101,8 @@ const loadFatSectorIds = (file, header) => {
|
|
|
100
101
|
const loadFat = (file, header) => {
|
|
101
102
|
const table = [];
|
|
102
103
|
for (const fatSectorId of loadFatSectorIds(file, header)) {
|
|
103
|
-
const
|
|
104
|
+
const sector = loadSector(file, header, fatSectorId);
|
|
105
|
+
const view = viewOf(sector);
|
|
104
106
|
const entries = header.sectorBytes / 4;
|
|
105
107
|
for (let i = 0; i < entries; i++) table.push(u32(view, i * 4));
|
|
106
108
|
}
|
|
@@ -73,22 +73,6 @@ declare function isSeparatorFootnote(footnote: document_d_exports.Footnote): boo
|
|
|
73
73
|
* Check if an endnote is a separator (not regular content)
|
|
74
74
|
*/
|
|
75
75
|
declare function isSeparatorEndnote(endnote: document_d_exports.Endnote): boolean;
|
|
76
|
-
/**
|
|
77
|
-
* Get footnote number for display (excluding separators)
|
|
78
|
-
* @param footnote - The footnote to get the number for
|
|
79
|
-
* @param footnoteMap - The footnote map
|
|
80
|
-
* @param startNumber - Starting number (default 1)
|
|
81
|
-
* @returns The display number, or null for separator footnotes
|
|
82
|
-
*/
|
|
83
|
-
declare function getFootnoteDisplayNumber(footnote: document_d_exports.Footnote, footnoteMap: FootnoteMap, startNumber?: number): number | null;
|
|
84
|
-
/**
|
|
85
|
-
* Get endnote number for display (excluding separators)
|
|
86
|
-
* @param endnote - The endnote to get the number for
|
|
87
|
-
* @param endnoteMap - The endnote map
|
|
88
|
-
* @param startNumber - Starting number (default 1)
|
|
89
|
-
* @returns The display number, or null for separator endnotes
|
|
90
|
-
*/
|
|
91
|
-
declare function getEndnoteDisplayNumber(endnote: document_d_exports.Endnote, endnoteMap: EndnoteMap, startNumber?: number): number | null;
|
|
92
76
|
/**
|
|
93
77
|
* Create an empty footnote map
|
|
94
78
|
*/
|
|
@@ -106,4 +90,4 @@ declare function mergeFootnoteMaps(...maps: FootnoteMap[]): FootnoteMap;
|
|
|
106
90
|
*/
|
|
107
91
|
declare function mergeEndnoteMaps(...maps: EndnoteMap[]): EndnoteMap;
|
|
108
92
|
//#endregion
|
|
109
|
-
export { EndnoteMap, FootnoteMap, createEmptyEndnoteMap, createEmptyFootnoteMap,
|
|
93
|
+
export { EndnoteMap, FootnoteMap, createEmptyEndnoteMap, createEmptyFootnoteMap, getEndnoteText, getFootnoteText, isSeparatorEndnote, isSeparatorFootnote, mergeEndnoteMaps, mergeFootnoteMaps, parseEndnoteProperties, parseEndnotes, parseFootnoteProperties, parseFootnotes };
|