docxodus 9.9.0 → 11.0.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/README.md +182 -4
- package/dist/canonical.d.ts +7 -0
- package/dist/canonical.d.ts.map +1 -0
- package/dist/canonical.js +56 -0
- package/dist/canonical.js.map +1 -0
- package/dist/docxodus.worker.d.ts +2 -1
- package/dist/docxodus.worker.d.ts.map +1 -1
- package/dist/docxodus.worker.js +250 -41
- package/dist/docxodus.worker.js.map +1 -1
- package/dist/editor.bundle.js +1980 -423
- package/dist/editor.d.ts +22 -2
- package/dist/editor.d.ts.map +1 -1
- package/dist/editor.js +94 -9
- package/dist/editor.js.map +1 -1
- package/dist/embed.bundle.js +2968 -597
- package/dist/embed.d.ts +1 -1
- package/dist/embed.iife.js +2969 -598
- package/dist/embed.js +1 -1
- package/dist/export-assets.json +277 -0
- package/dist/export-browser.bundle.js +7989 -0
- package/dist/export-browser.d.ts +266 -0
- package/dist/export-browser.d.ts.map +1 -0
- package/dist/export-browser.js +2915 -0
- package/dist/export-browser.js.map +1 -0
- package/dist/export-resource-limits-v1.json +57 -0
- package/dist/font-contract.d.ts +150 -0
- package/dist/font-contract.d.ts.map +1 -0
- package/dist/font-contract.js +59 -0
- package/dist/font-contract.js.map +1 -0
- package/dist/font-runtime.d.ts +33 -0
- package/dist/font-runtime.d.ts.map +1 -0
- package/dist/font-runtime.js +1100 -0
- package/dist/font-runtime.js.map +1 -0
- package/dist/index.d.ts +83 -65
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +194 -149
- package/dist/index.js.map +1 -1
- package/dist/page-geometry.d.ts +8 -1
- package/dist/page-geometry.d.ts.map +1 -1
- package/dist/page-geometry.js +37 -11
- package/dist/page-geometry.js.map +1 -1
- package/dist/pagination.bundle.js +1827 -410
- package/dist/pagination.d.ts +249 -15
- package/dist/pagination.d.ts.map +1 -1
- package/dist/pagination.js +1942 -453
- package/dist/pagination.js.map +1 -1
- package/dist/react.d.ts +13 -6
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +38 -5
- package/dist/react.js.map +1 -1
- package/dist/render-report-v2.schema.json +1581 -0
- package/dist/ribbon-chrome.d.ts +2 -2
- package/dist/ribbon-chrome.d.ts.map +1 -1
- package/dist/ribbon-chrome.js +72 -4
- package/dist/ribbon-chrome.js.map +1 -1
- package/dist/ribbon.js +119 -0
- package/dist/ribbon.js.map +1 -1
- package/dist/session.bundle.js +747 -29
- package/dist/session.d.ts +159 -19
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +593 -25
- package/dist/session.js.map +1 -1
- package/dist/types.d.ts +1287 -238
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +9 -75
- package/dist/types.js.map +1 -1
- package/dist/wasm/_framework/DocumentFormat.OpenXml.Framework.wasm +0 -0
- package/dist/wasm/_framework/DocumentFormat.OpenXml.Framework.wasm.br +0 -0
- package/dist/wasm/_framework/DocumentFormat.OpenXml.wasm +0 -0
- package/dist/wasm/_framework/DocumentFormat.OpenXml.wasm.br +0 -0
- package/dist/wasm/_framework/Docxodus.wasm +0 -0
- package/dist/wasm/_framework/Docxodus.wasm.br +0 -0
- package/dist/wasm/_framework/DocxodusWasm.wasm +0 -0
- package/dist/wasm/_framework/DocxodusWasm.wasm.br +0 -0
- package/dist/wasm/_framework/System.Collections.Concurrent.wasm +0 -0
- package/dist/wasm/_framework/System.Collections.Concurrent.wasm.br +0 -0
- package/dist/wasm/_framework/System.Collections.Immutable.wasm +0 -0
- package/dist/wasm/_framework/System.Collections.Immutable.wasm.br +0 -0
- package/dist/wasm/_framework/System.Collections.wasm +0 -0
- package/dist/wasm/_framework/System.Collections.wasm.br +0 -0
- package/dist/wasm/_framework/System.ComponentModel.Primitives.wasm +0 -0
- package/dist/wasm/_framework/System.ComponentModel.Primitives.wasm.br +0 -0
- package/dist/wasm/_framework/System.IO.Compression.wasm +0 -0
- package/dist/wasm/_framework/System.IO.Compression.wasm.br +0 -0
- package/dist/wasm/_framework/System.IO.Packaging.wasm +0 -0
- package/dist/wasm/_framework/System.IO.Packaging.wasm.br +0 -0
- package/dist/wasm/_framework/System.IO.Pipelines.wasm +0 -0
- package/dist/wasm/_framework/System.IO.Pipelines.wasm.br +0 -0
- package/dist/wasm/_framework/System.Linq.wasm +0 -0
- package/dist/wasm/_framework/System.Linq.wasm.br +0 -0
- package/dist/wasm/_framework/System.Private.CoreLib.wasm +0 -0
- package/dist/wasm/_framework/System.Private.CoreLib.wasm.br +0 -0
- package/dist/wasm/_framework/System.Private.Uri.wasm +0 -0
- package/dist/wasm/_framework/System.Private.Uri.wasm.br +0 -0
- package/dist/wasm/_framework/System.Private.Xml.Linq.wasm +0 -0
- package/dist/wasm/_framework/System.Private.Xml.Linq.wasm.br +0 -0
- package/dist/wasm/_framework/System.Private.Xml.wasm +0 -0
- package/dist/wasm/_framework/System.Private.Xml.wasm.br +0 -0
- package/dist/wasm/_framework/System.Runtime.InteropServices.JavaScript.wasm +0 -0
- package/dist/wasm/_framework/System.Runtime.InteropServices.JavaScript.wasm.br +0 -0
- package/dist/wasm/_framework/System.Runtime.wasm +0 -0
- package/dist/wasm/_framework/System.Runtime.wasm.br +0 -0
- package/dist/wasm/_framework/System.Security.Cryptography.wasm +0 -0
- package/dist/wasm/_framework/System.Security.Cryptography.wasm.br +0 -0
- package/dist/wasm/_framework/System.Text.Encodings.Web.wasm +0 -0
- package/dist/wasm/_framework/System.Text.Encodings.Web.wasm.br +0 -0
- package/dist/wasm/_framework/System.Text.Json.wasm +0 -0
- package/dist/wasm/_framework/System.Text.Json.wasm.br +0 -0
- package/dist/wasm/_framework/System.Text.RegularExpressions.wasm +0 -0
- package/dist/wasm/_framework/System.Text.RegularExpressions.wasm.br +0 -0
- package/dist/wasm/_framework/dotnet.boot.js +34 -40
- package/dist/wasm/_framework/dotnet.boot.js.br +0 -0
- package/dist/wasm/_framework/dotnet.native.js +39 -3
- package/dist/wasm/_framework/dotnet.native.js.br +0 -0
- package/dist/wasm/_framework/dotnet.native.wasm +0 -0
- package/dist/wasm/_framework/dotnet.native.wasm.br +0 -0
- package/dist/worker-proxy.bundle.js +198 -32
- package/dist/worker-proxy.d.ts +37 -4
- package/dist/worker-proxy.d.ts.map +1 -1
- package/dist/worker-proxy.js +187 -35
- package/dist/worker-proxy.js.map +1 -1
- package/package.json +22 -6
- package/dist/wasm/_framework/System.Diagnostics.Process.wasm +0 -0
- package/dist/wasm/_framework/System.Diagnostics.Process.wasm.br +0 -0
package/dist/session.js
CHANGED
|
@@ -1,6 +1,46 @@
|
|
|
1
1
|
// Copyright (c) Microsoft. All rights reserved.
|
|
2
2
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
-
import { ContextBoundary, DiffFormat, PlaceholderKinds, ProjectionDepth } from "./types.js";
|
|
3
|
+
import { ContextBoundary, DiffFormat, PlaceholderKinds, ProjectionDepth, ProjectionScopes } from "./types.js";
|
|
4
|
+
function mutationBatchChangeSet(before, after, key) {
|
|
5
|
+
const beforeGroups = new Map();
|
|
6
|
+
const afterGroups = new Map();
|
|
7
|
+
const group = (items, target) => {
|
|
8
|
+
items.forEach((item, index) => {
|
|
9
|
+
const identity = key(item);
|
|
10
|
+
const indices = target.get(identity) ?? [];
|
|
11
|
+
indices.push(index);
|
|
12
|
+
target.set(identity, indices);
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
group(before, beforeGroups);
|
|
16
|
+
group(after, afterGroups);
|
|
17
|
+
const beforeMatched = before.map(() => false);
|
|
18
|
+
const afterMatched = after.map(() => false);
|
|
19
|
+
const modified = after.map(() => false);
|
|
20
|
+
for (const [identity, afterIndices] of afterGroups) {
|
|
21
|
+
const beforeIndices = beforeGroups.get(identity) ?? [];
|
|
22
|
+
for (const afterIndex of afterIndices) {
|
|
23
|
+
const beforeIndex = beforeIndices.find(index => !beforeMatched[index] && JSON.stringify(before[index]) === JSON.stringify(after[afterIndex]));
|
|
24
|
+
if (beforeIndex === undefined)
|
|
25
|
+
continue;
|
|
26
|
+
beforeMatched[beforeIndex] = true;
|
|
27
|
+
afterMatched[afterIndex] = true;
|
|
28
|
+
}
|
|
29
|
+
const remainingBefore = beforeIndices.filter(index => !beforeMatched[index]);
|
|
30
|
+
const remainingAfter = afterIndices.filter(index => !afterMatched[index]);
|
|
31
|
+
const modifiedCount = Math.min(remainingBefore.length, remainingAfter.length);
|
|
32
|
+
for (let index = 0; index < modifiedCount; index++) {
|
|
33
|
+
beforeMatched[remainingBefore[index]] = true;
|
|
34
|
+
afterMatched[remainingAfter[index]] = true;
|
|
35
|
+
modified[remainingAfter[index]] = true;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
added: after.filter((_, index) => !afterMatched[index]),
|
|
40
|
+
removed: before.filter((_, index) => !beforeMatched[index]),
|
|
41
|
+
modified: after.filter((_, index) => modified[index]),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
4
44
|
/**
|
|
5
45
|
* Stateful in-memory DOCX editing session keyed by markdown-projection anchor ids.
|
|
6
46
|
* Mirror of the .NET `DocxSession` surface. See
|
|
@@ -26,6 +66,312 @@ export class DocxSession {
|
|
|
26
66
|
project() {
|
|
27
67
|
return JSON.parse(this.wasm.Project(this.handle));
|
|
28
68
|
}
|
|
69
|
+
/** Monotonic document version (0 at open; +1 per committed mutation/undo/redo). */
|
|
70
|
+
getVersion() {
|
|
71
|
+
return JSON.parse(this.wasm.GetVersion(this.handle)).version;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Verification manifest for the current logical checkpoint. Unsaved edits are included and
|
|
75
|
+
* the read does not mutate package bytes, caches, history, or the document version.
|
|
76
|
+
*/
|
|
77
|
+
getPackageManifest() {
|
|
78
|
+
return JSON.parse(this.wasm.GetPackageManifest(this.handle));
|
|
79
|
+
}
|
|
80
|
+
/** Register a browser-materialized PageMap without changing the document version. */
|
|
81
|
+
registerPageMap(pageMap, expectedRendererFingerprint) {
|
|
82
|
+
return JSON.parse(this.wasm.RegisterPageMap(this.handle, JSON.stringify(pageMap), expectedRendererFingerprint ?? ""));
|
|
83
|
+
}
|
|
84
|
+
getPageMapStatus(request) {
|
|
85
|
+
return JSON.parse(this.wasm.GetPageMapStatus(this.handle, request ? JSON.stringify(request) : ""));
|
|
86
|
+
}
|
|
87
|
+
getPageCitation(anchorId, request) {
|
|
88
|
+
return JSON.parse(this.wasm.GetPageCitation(this.handle, anchorId, JSON.stringify(request)));
|
|
89
|
+
}
|
|
90
|
+
/** Evaluate optimistic guards without mutating or advancing the version. */
|
|
91
|
+
checkPreconditions(preconditions) {
|
|
92
|
+
return JSON.parse(this.wasm.CheckPreconditions(this.handle, JSON.stringify(preconditions)));
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Guard any synchronous mutation. WASM calls are synchronous and single-threaded, so the
|
|
96
|
+
* check and callback form one uninterrupted client-side operation. Prefer a method's native
|
|
97
|
+
* `preconditions` option where it has one (notably replaceTextRange's match-count guard).
|
|
98
|
+
*/
|
|
99
|
+
runWithPreconditions(preconditions, mutation) {
|
|
100
|
+
const checked = this.checkPreconditions(preconditions);
|
|
101
|
+
return checked.success ? mutation() : checked;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Execute synchronous mutations atomically by default. Atomic success is one undo/version
|
|
105
|
+
* unit; any failed or thrown step restores the exact package and history checkpoint.
|
|
106
|
+
*/
|
|
107
|
+
executeBatch(steps, mode = "atomic") {
|
|
108
|
+
if (mode !== "atomic" && mode !== "best_effort") {
|
|
109
|
+
throw new RangeError(`unknown mutation batch mode: ${String(mode)}`);
|
|
110
|
+
}
|
|
111
|
+
const baseVersion = this.getVersion();
|
|
112
|
+
const observationWarnings = [];
|
|
113
|
+
const inspect = (label, read, fallback) => {
|
|
114
|
+
try {
|
|
115
|
+
return read();
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
observationWarnings.push(`${label} unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
119
|
+
return fallback;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
const beforeRevisions = inspect("Revision delta inspection", () => this.listRevisions(), []);
|
|
123
|
+
const beforeComments = inspect("Comment delta inspection", () => this.listComments(), []);
|
|
124
|
+
const beforeAnnotations = inspect("Annotation delta inspection", () => this.listAnnotations(), []);
|
|
125
|
+
const complete = (result) => {
|
|
126
|
+
try {
|
|
127
|
+
const revisionChanges = mutationBatchChangeSet(beforeRevisions, inspect("Revision delta inspection", () => this.listRevisions(), beforeRevisions), revision => revision.id);
|
|
128
|
+
const commentChanges = mutationBatchChangeSet(beforeComments, inspect("Comment delta inspection", () => this.listComments(), beforeComments), comment => comment.anchorId);
|
|
129
|
+
const annotationChanges = mutationBatchChangeSet(beforeAnnotations, inspect("Annotation delta inspection", () => this.listAnnotations(), beforeAnnotations), annotation => annotation.id ?? "");
|
|
130
|
+
const resultVersion = inspect("Result version inspection", () => this.getVersion(), baseVersion);
|
|
131
|
+
const warnings = [...observationWarnings];
|
|
132
|
+
if ([...revisionChanges.added, ...revisionChanges.modified]
|
|
133
|
+
.some(revision => revision.date !== undefined && revision.date !== null)) {
|
|
134
|
+
warnings.push("Tracked-revision date attributes may use the execution clock; compare revision ids, authors, types, text, and anchors across separate executions.");
|
|
135
|
+
}
|
|
136
|
+
if ([...commentChanges.added, ...commentChanges.modified]
|
|
137
|
+
.some(comment => comment.date !== undefined && comment.date !== null)) {
|
|
138
|
+
warnings.push("Comment date attributes may be generated from the execution clock; supply dates explicitly when byte-identical replay is required.");
|
|
139
|
+
}
|
|
140
|
+
// Same predicate as the .NET receipt (`annotation.Created.HasValue`): the warning is
|
|
141
|
+
// about an execution CLOCK, so an annotation added with no created timestamp is
|
|
142
|
+
// deterministic and must not raise it on one surface and not the other.
|
|
143
|
+
if (annotationChanges.added
|
|
144
|
+
.some(annotation => annotation.created !== undefined && annotation.created !== null)) {
|
|
145
|
+
warnings.push("Auto-generated annotation ids or creation timestamps are execution metadata; supply id and created explicitly when byte-identical replay is required.");
|
|
146
|
+
}
|
|
147
|
+
if (result.steps.some(step => step.results.some(edit => edit.created.length > 0))) {
|
|
148
|
+
warnings.push("Created anchors and related OOXML ids may be generated independently on replay; preview/apply equivalence is semantic and packageHash or anchor ids may differ.");
|
|
149
|
+
}
|
|
150
|
+
if (mode === "best_effort" && !result.success) {
|
|
151
|
+
warnings.push("Best-effort execution retains every successful step despite later failures.");
|
|
152
|
+
}
|
|
153
|
+
// null, never "": an absent hash must not compare equal to another absent hash, or a
|
|
154
|
+
// naive `preview.packageHash === applied.packageHash` replay assertion passes vacuously.
|
|
155
|
+
let packageHash = null;
|
|
156
|
+
if (!this.wasm.GetPackageContentHash) {
|
|
157
|
+
warnings.push("This WASM bundle predates package equivalence hashes; packageHash is unavailable.");
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
try {
|
|
161
|
+
packageHash = this.wasm.GetPackageContentHash(this.handle);
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
warnings.push(`Package equivalence hash unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
...result,
|
|
169
|
+
preview: false,
|
|
170
|
+
baseVersion,
|
|
171
|
+
resultVersion,
|
|
172
|
+
packageHash,
|
|
173
|
+
revisionChanges,
|
|
174
|
+
commentChanges,
|
|
175
|
+
annotationChanges,
|
|
176
|
+
warnings,
|
|
177
|
+
html: null,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
return {
|
|
182
|
+
...result,
|
|
183
|
+
preview: false,
|
|
184
|
+
baseVersion,
|
|
185
|
+
resultVersion: inspect("Result version inspection", () => this.getVersion(), baseVersion),
|
|
186
|
+
packageHash: null,
|
|
187
|
+
revisionChanges: { added: [], removed: [], modified: [] },
|
|
188
|
+
commentChanges: { added: [], removed: [], modified: [] },
|
|
189
|
+
annotationChanges: { added: [], removed: [], modified: [] },
|
|
190
|
+
warnings: [
|
|
191
|
+
...observationWarnings,
|
|
192
|
+
`Batch receipt enrichment unavailable: ${error instanceof Error ? error.message : String(error)}`,
|
|
193
|
+
],
|
|
194
|
+
html: null,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
const internalFailure = (value) => ({
|
|
199
|
+
success: false,
|
|
200
|
+
error: { code: "internal_error", message: value instanceof Error ? value.message : String(value) },
|
|
201
|
+
created: [], removed: [], modified: [],
|
|
202
|
+
});
|
|
203
|
+
const run = (step) => {
|
|
204
|
+
try {
|
|
205
|
+
const value = step.mutation();
|
|
206
|
+
const results = Array.isArray(value) ? value : [value];
|
|
207
|
+
if (results.some(result => result === null || typeof result !== "object" || typeof result.success !== "boolean")) {
|
|
208
|
+
return [internalFailure("batch mutation returned invalid edit results")];
|
|
209
|
+
}
|
|
210
|
+
// An empty list is a successful no-op step, matching the core contract.
|
|
211
|
+
return results;
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
return [internalFailure(error)];
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
const failureOf = (step, rolledBack) => ({
|
|
218
|
+
index: step.index,
|
|
219
|
+
tool: step.tool,
|
|
220
|
+
action: step.action,
|
|
221
|
+
error: step.results.find(result => !result.success)?.error
|
|
222
|
+
?? { code: "internal_error", message: "batch step failed without an error" },
|
|
223
|
+
rolledBack,
|
|
224
|
+
});
|
|
225
|
+
const preflightOne = (step) => {
|
|
226
|
+
try {
|
|
227
|
+
return step.preflight?.();
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
return internalFailure(error).error;
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
if (mode === "atomic") {
|
|
234
|
+
const preflight = steps.map(preflightOne);
|
|
235
|
+
const failedPreflight = preflight.findIndex(error => error !== undefined);
|
|
236
|
+
if (failedPreflight >= 0) {
|
|
237
|
+
const source = steps[failedPreflight];
|
|
238
|
+
const failed = {
|
|
239
|
+
index: failedPreflight, tool: source.tool, action: source.action,
|
|
240
|
+
success: false, rolledBack: true,
|
|
241
|
+
results: [{ success: false, error: preflight[failedPreflight], created: [], removed: [], modified: [] }],
|
|
242
|
+
};
|
|
243
|
+
return complete({ mode, status: "failed", success: false, rolledBack: true,
|
|
244
|
+
steps: [failed], failure: failureOf(failed, true) });
|
|
245
|
+
}
|
|
246
|
+
const transaction = this.wasm.BeginTransaction(this.handle);
|
|
247
|
+
const completed = [];
|
|
248
|
+
try {
|
|
249
|
+
for (let index = 0; index < steps.length; index++) {
|
|
250
|
+
const source = steps[index];
|
|
251
|
+
const results = run(source);
|
|
252
|
+
const step = {
|
|
253
|
+
index, tool: source.tool, action: source.action,
|
|
254
|
+
success: results.every(result => result.success), rolledBack: false, results,
|
|
255
|
+
};
|
|
256
|
+
completed.push(step);
|
|
257
|
+
if (!step.success) {
|
|
258
|
+
this.wasm.RollbackTransaction(transaction);
|
|
259
|
+
const rolledBack = completed.map(value => ({ ...value, rolledBack: true }));
|
|
260
|
+
const failed = rolledBack[rolledBack.length - 1];
|
|
261
|
+
return complete({ mode, status: "failed", success: false, rolledBack: true,
|
|
262
|
+
steps: rolledBack, failure: failureOf(failed, true) });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
this.wasm.CommitTransaction(transaction);
|
|
266
|
+
return complete({ mode, status: "ok", success: true, rolledBack: false, steps: completed });
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
try {
|
|
270
|
+
this.wasm.RollbackTransaction(transaction);
|
|
271
|
+
}
|
|
272
|
+
catch { /* preserve the original */ }
|
|
273
|
+
throw error;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// Preserve sequential best-effort semantics: a later preflight can observe state created by
|
|
277
|
+
// an earlier successful step, so run it immediately before that step rather than up front.
|
|
278
|
+
const completed = steps.map((source, index) => {
|
|
279
|
+
const preflight = preflightOne(source);
|
|
280
|
+
const results = preflight
|
|
281
|
+
? [{ success: false, error: preflight, created: [], removed: [], modified: [] }]
|
|
282
|
+
: run(source);
|
|
283
|
+
return {
|
|
284
|
+
index, tool: source.tool, action: source.action,
|
|
285
|
+
success: results.every(result => result.success), rolledBack: false, results,
|
|
286
|
+
};
|
|
287
|
+
});
|
|
288
|
+
const failed = completed.find(step => !step.success);
|
|
289
|
+
return complete({
|
|
290
|
+
mode,
|
|
291
|
+
status: failed ? (completed.some(step => step.success) ? "partial" : "failed") : "ok",
|
|
292
|
+
success: failed === undefined,
|
|
293
|
+
rolledBack: false,
|
|
294
|
+
steps: completed,
|
|
295
|
+
failure: failed ? failureOf(failed, false) : undefined,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Execute the same callback batch algorithm against a complete isolated package clone.
|
|
300
|
+
* Callbacks receive the shadow session explicitly; mutate that argument. The live session's
|
|
301
|
+
* package, caches, version, configuration, and undo/redo history are never execution targets.
|
|
302
|
+
*/
|
|
303
|
+
previewBatch(steps, mode = "atomic", options) {
|
|
304
|
+
if (mode !== "atomic" && mode !== "best_effort") {
|
|
305
|
+
throw new RangeError(`unknown mutation batch mode: ${String(mode)}`);
|
|
306
|
+
}
|
|
307
|
+
const htmlMode = options?.html ?? "none";
|
|
308
|
+
if (htmlMode !== "none" && htmlMode !== "scoped" && htmlMode !== "full") {
|
|
309
|
+
throw new RangeError(`unknown preview HTML mode: ${String(htmlMode)}`);
|
|
310
|
+
}
|
|
311
|
+
if (!this.wasm.OpenPreviewSession) {
|
|
312
|
+
throw new Error("This WASM bundle does not support isolated mutation previews.");
|
|
313
|
+
}
|
|
314
|
+
const shadow = new DocxSession(this.wasm.OpenPreviewSession(this.handle), this.wasm);
|
|
315
|
+
try {
|
|
316
|
+
const result = shadow.executeBatch(steps.map(step => ({
|
|
317
|
+
tool: step.tool,
|
|
318
|
+
action: step.action,
|
|
319
|
+
mutation: () => step.mutation(shadow),
|
|
320
|
+
preflight: step.preflight ? () => step.preflight(shadow) : undefined,
|
|
321
|
+
})), mode);
|
|
322
|
+
const warnings = [...result.warnings];
|
|
323
|
+
let html = null;
|
|
324
|
+
// A rendered document always starts with '<'; a leading '{' is the bridge's error object.
|
|
325
|
+
const unwrapRendered = (rendered) => {
|
|
326
|
+
if (rendered.trimStart().startsWith("{")) {
|
|
327
|
+
const envelope = JSON.parse(rendered);
|
|
328
|
+
if (envelope.error) {
|
|
329
|
+
warnings.push(`Preview HTML could not be generated: ${envelope.error}`);
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return rendered;
|
|
334
|
+
};
|
|
335
|
+
// Preview HTML MUST come from the façade's preview profile (DocxSessionOps.RenderPreview*),
|
|
336
|
+
// not the editor's authoring profile: the editor render hides comments, annotations and
|
|
337
|
+
// headers/footers, so routing a preview through it would show this surface a materially
|
|
338
|
+
// different document than the stdio/Python/MCP surfaces show for the identical batch.
|
|
339
|
+
const legacyProfileWarning = "This WASM bundle predates the shared preview HTML profile; preview HTML omits comments, " +
|
|
340
|
+
"annotations and headers/footers and may differ from other surfaces.";
|
|
341
|
+
try {
|
|
342
|
+
if (htmlMode === "scoped") {
|
|
343
|
+
if (!options?.htmlAnchorId) {
|
|
344
|
+
warnings.push("Scoped HTML was requested without htmlAnchorId; no HTML was generated.");
|
|
345
|
+
}
|
|
346
|
+
else if (this.wasm.RenderPreviewBlockHtml) {
|
|
347
|
+
html = unwrapRendered(this.wasm.RenderPreviewBlockHtml(shadow.handle, options.htmlAnchorId));
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
warnings.push(legacyProfileWarning);
|
|
351
|
+
html = shadow.renderBlock(options.htmlAnchorId);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
else if (htmlMode === "full") {
|
|
355
|
+
if (this.wasm.RenderPreviewHtml) {
|
|
356
|
+
html = unwrapRendered(this.wasm.RenderPreviewHtml(shadow.handle));
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
warnings.push(legacyProfileWarning);
|
|
360
|
+
html = unwrapRendered(this.wasm.RenderHtmlForReview
|
|
361
|
+
? this.wasm.RenderHtmlForReview(shadow.handle, "docx-", false, false, 1, true)
|
|
362
|
+
: this.wasm.RenderHtml(shadow.handle, "docx-", false, false, 1));
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
catch (error) {
|
|
367
|
+
warnings.push(`Preview HTML could not be generated: ${error instanceof Error ? error.message : String(error)}`);
|
|
368
|
+
}
|
|
369
|
+
return { ...result, preview: true, warnings, html };
|
|
370
|
+
}
|
|
371
|
+
finally {
|
|
372
|
+
shadow.close();
|
|
373
|
+
}
|
|
374
|
+
}
|
|
29
375
|
/**
|
|
30
376
|
* Project a slice of the document keyed off an anchor — useful for showing
|
|
31
377
|
* one section to an LLM at a time without paying the cost of projecting the
|
|
@@ -41,8 +387,10 @@ export class DocxSession {
|
|
|
41
387
|
*
|
|
42
388
|
* @see docs/architecture/docx_mutation_api.md
|
|
43
389
|
*/
|
|
44
|
-
projectAnchor(anchorId, depth = ProjectionDepth.SubtreeAndFollowingSiblings) {
|
|
45
|
-
return JSON.parse(
|
|
390
|
+
projectAnchor(anchorId, depth = ProjectionDepth.SubtreeAndFollowingSiblings, citation) {
|
|
391
|
+
return JSON.parse(citation
|
|
392
|
+
? this.wasm.ProjectAnchorWithCitations(this.handle, anchorId, depth, JSON.stringify(citation))
|
|
393
|
+
: this.wasm.ProjectAnchor(this.handle, anchorId, depth));
|
|
46
394
|
}
|
|
47
395
|
/**
|
|
48
396
|
* Render a single block to faithful HTML from the live session — the editor's
|
|
@@ -61,11 +409,17 @@ export class DocxSession {
|
|
|
61
409
|
return html;
|
|
62
410
|
}
|
|
63
411
|
// ─── Tier A: text CRUD ───────────────────────────────────────────────
|
|
64
|
-
replaceText(anchorId, markdown) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
412
|
+
replaceText(anchorId, markdown, preconditions) {
|
|
413
|
+
const apply = () => JSON.parse(this.wasm.ReplaceText(this.handle, anchorId, markdown));
|
|
414
|
+
return preconditions
|
|
415
|
+
? this.runWithPreconditions({ ...preconditions, anchorId: preconditions.anchorId ?? anchorId }, apply)
|
|
416
|
+
: apply();
|
|
417
|
+
}
|
|
418
|
+
deleteBlock(anchorId, preconditions) {
|
|
419
|
+
const apply = () => JSON.parse(this.wasm.DeleteBlock(this.handle, anchorId));
|
|
420
|
+
return preconditions
|
|
421
|
+
? this.runWithPreconditions({ ...preconditions, anchorId: preconditions.anchorId ?? anchorId }, apply)
|
|
422
|
+
: apply();
|
|
69
423
|
}
|
|
70
424
|
/** Reorder one top-level paragraph/heading/list/table block relative to another. */
|
|
71
425
|
moveBlock(sourceAnchorId, targetAnchorId, position) {
|
|
@@ -116,17 +470,29 @@ export class DocxSession {
|
|
|
116
470
|
}
|
|
117
471
|
/**
|
|
118
472
|
* Insert a `rows`×`cols` table before/after the block. `options` controls borders, row-major
|
|
119
|
-
* cell markdown, and cell alignment. The returned `EditResult.created` lists
|
|
473
|
+
* cell markdown, and cell alignment. The returned `EditResult.created` lists canonical `tc`
|
|
120
474
|
* anchors (row-major), so each cell can then be addressed to fill/format.
|
|
121
475
|
*/
|
|
122
476
|
insertTable(anchorId, position, rows, cols, options) {
|
|
123
477
|
const optionsJson = options ? JSON.stringify(options) : "";
|
|
124
478
|
return JSON.parse(this.wasm.InsertTable(this.handle, anchorId, position, rows, cols, optionsJson));
|
|
125
479
|
}
|
|
480
|
+
/** Resolve a canonical `tbl` anchor to explicit table/row/column/cell identities. */
|
|
481
|
+
getTableMetadata(tableAnchorId) {
|
|
482
|
+
return JSON.parse(this.wasm.GetTableMetadata(this.handle, tableAnchorId));
|
|
483
|
+
}
|
|
484
|
+
/** Resolve a canonical `tc` anchor to its zero-based table-grid coordinate and spans. */
|
|
485
|
+
resolveTableCellAnchor(cellAnchorId) {
|
|
486
|
+
return JSON.parse(this.wasm.ResolveTableCellAnchor(this.handle, cellAnchorId));
|
|
487
|
+
}
|
|
488
|
+
/** Resolve a zero-based table-grid coordinate to the physical `tc` covering it. */
|
|
489
|
+
resolveTableCellCoordinate(tableAnchorId, rowIndex, columnIndex) {
|
|
490
|
+
return JSON.parse(this.wasm.ResolveTableCellCoordinate(this.handle, tableAnchorId, rowIndex, columnIndex));
|
|
491
|
+
}
|
|
126
492
|
/**
|
|
127
|
-
* Table row/column editing, addressed by
|
|
128
|
-
* {@link insertTable}'s `created`
|
|
129
|
-
* empty (`created` lists
|
|
493
|
+
* Table row/column editing, addressed by the canonical `tc` anchor returned from
|
|
494
|
+
* {@link insertTable}'s `created` or table metadata. Insert clones the reference row/column's
|
|
495
|
+
* widths and starts empty (`created` lists new `tc` anchors); delete of the last row/column removes
|
|
130
496
|
* the whole table. All four are grid-aware: inserting across a merge extends it, deleting
|
|
131
497
|
* through one narrows it, and deleting a vertical merge's lead row promotes the next row to
|
|
132
498
|
* carry it — the grid is never left ragged.
|
|
@@ -164,7 +530,7 @@ export class DocxSession {
|
|
|
164
530
|
return JSON.parse(this.wasm.UnmergeCells(this.handle, cellAnchorId));
|
|
165
531
|
}
|
|
166
532
|
/**
|
|
167
|
-
* Table styling, addressed by a
|
|
533
|
+
* Table styling, addressed by a canonical `tc` anchor — the post-insert counterpart of
|
|
168
534
|
* {@link insertTable}'s options (issue #315 Stage A). `setColumnWidths` retunes `w:tblGrid` +
|
|
169
535
|
* every row's cell width (one positive twip value per column) and pins the table to fixed
|
|
170
536
|
* layout, exactly as inserting with explicit `columnWidths` would.
|
|
@@ -196,6 +562,10 @@ export class DocxSession {
|
|
|
196
562
|
setRepeatHeaderRow(cellAnchorId, repeat) {
|
|
197
563
|
return JSON.parse(this.wasm.SetRepeatHeaderRow(this.handle, cellAnchorId, repeat));
|
|
198
564
|
}
|
|
565
|
+
/** Apply row layout options to the row containing the canonical cell anchor. */
|
|
566
|
+
setTableRowOptions(cellAnchorId, options) {
|
|
567
|
+
return JSON.parse(this.wasm.SetTableRowOptions(this.handle, cellAnchorId, options.repeatHeader ?? null, options.allowBreakAcrossPages ?? null, options.heightTwips ?? null, options.heightRule ?? "atLeast"));
|
|
568
|
+
}
|
|
199
569
|
// ─── Headers / footers / page numbers ────────────────────────────────
|
|
200
570
|
/**
|
|
201
571
|
* Set the running header story for the section that owns `anchorId` (any body block in that
|
|
@@ -242,6 +612,37 @@ export class DocxSession {
|
|
|
242
612
|
setPageNumbering(anchorId, op) {
|
|
243
613
|
return JSON.parse(this.wasm.SetPageNumbering(this.handle, anchorId, JSON.stringify(op)));
|
|
244
614
|
}
|
|
615
|
+
/**
|
|
616
|
+
* Insert a **table of contents** before or after `anchorId` (issue #607). The field is written
|
|
617
|
+
* dirty and the document asks for a field update on open, so Word paginates and fills the table
|
|
618
|
+
* itself — nothing here ships a cached result that is stale the moment anything above it moves.
|
|
619
|
+
*
|
|
620
|
+
* The table is wrapped in the `w:sdt` content control Word puts around one, which is what gives
|
|
621
|
+
* it the *Update Table* control in Word's UI. Word's `TOCHeading` and `TOC1` styles are
|
|
622
|
+
* find-or-created; a document that already defines them keeps its own.
|
|
623
|
+
*
|
|
624
|
+
* Body anchors only, and refused under tracked-change recording: a generated table is regenerated
|
|
625
|
+
* wholesale on every field update, so there is no reversible way to redline it.
|
|
626
|
+
*/
|
|
627
|
+
insertTableOfContents(anchorId, pos = "before", options) {
|
|
628
|
+
return JSON.parse(this.wasm.InsertTableOfContents(this.handle, anchorId, pos, options ? JSON.stringify(options) : ""));
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Insert a **table of figures** — the captions carrying `options.captionLabel` and their page
|
|
632
|
+
* numbers. Same field mechanics as {@link insertTableOfContents}; Word writes this one as a bare
|
|
633
|
+
* paragraph rather than inside a content control, so this does too.
|
|
634
|
+
*/
|
|
635
|
+
insertTableOfFigures(anchorId, pos = "before", options) {
|
|
636
|
+
return JSON.parse(this.wasm.InsertTableOfFigures(this.handle, anchorId, pos, options ? JSON.stringify(options) : ""));
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Insert a **table of authorities** — the cases, statutes or other authorities marked in the
|
|
640
|
+
* document, grouped by `options.category`. The table lists entries the document has MARKED with
|
|
641
|
+
* `TA` fields; a document with no marked citations produces a table that is correct and empty.
|
|
642
|
+
*/
|
|
643
|
+
insertTableOfAuthorities(anchorId, pos = "before", options) {
|
|
644
|
+
return JSON.parse(this.wasm.InsertTableOfAuthorities(this.handle, anchorId, pos, options ? JSON.stringify(options) : ""));
|
|
645
|
+
}
|
|
245
646
|
/**
|
|
246
647
|
* Remove the section's page-numbering start/format: it reverts to continuing the previous
|
|
247
648
|
* section's numbering in Word's default `1, 2, 3`. Chapter-numbering attributes
|
|
@@ -284,6 +685,17 @@ export class DocxSession {
|
|
|
284
685
|
insertFootnote(anchorId, characterOffset, markdown) {
|
|
285
686
|
return JSON.parse(this.wasm.InsertFootnote(this.handle, anchorId, characterOffset, markdown));
|
|
286
687
|
}
|
|
688
|
+
/**
|
|
689
|
+
* Insert a Word-faithful internal cross-reference — a `REF` field targeting an existing
|
|
690
|
+
* bookmark — at a character offset (issue #545). The field carries a cached result run
|
|
691
|
+
* (the bookmarked text, or the target's auto-number under `referenceNumber`), so
|
|
692
|
+
* renderers that do not recompute fields show a faithful snapshot and Word updates it
|
|
693
|
+
* like a hand-authored cross-reference. A missing or incoherent bookmark fails with
|
|
694
|
+
* `missing_bookmark_target`.
|
|
695
|
+
*/
|
|
696
|
+
insertCrossReference(anchorId, characterOffset, bookmarkName, options) {
|
|
697
|
+
return JSON.parse(this.wasm.InsertCrossReference(this.handle, anchorId, characterOffset, bookmarkName, options ? JSON.stringify(options) : ""));
|
|
698
|
+
}
|
|
287
699
|
/** Create an endnote — see {@link insertFootnote}; writes the endnotes part and a
|
|
288
700
|
* `w:endnoteReference`, and the created definition anchor has kind `en`. */
|
|
289
701
|
insertEndnote(anchorId, characterOffset, markdown) {
|
|
@@ -343,6 +755,87 @@ export class DocxSession {
|
|
|
343
755
|
listComments() {
|
|
344
756
|
return JSON.parse(this.wasm.ListComments(this.handle));
|
|
345
757
|
}
|
|
758
|
+
listHyperlinks(scopes = ProjectionScopes.All) {
|
|
759
|
+
return JSON.parse(this.wasm.ListHyperlinks(this.handle, scopes));
|
|
760
|
+
}
|
|
761
|
+
addHyperlink(anchorId, span, kind, target) {
|
|
762
|
+
return JSON.parse(this.wasm.AddHyperlink(this.handle, anchorId, span.start, span.length, kind, target));
|
|
763
|
+
}
|
|
764
|
+
updateHyperlink(hyperlinkId, kind, target) {
|
|
765
|
+
return JSON.parse(this.wasm.UpdateHyperlink(this.handle, hyperlinkId, kind, target));
|
|
766
|
+
}
|
|
767
|
+
removeHyperlink(hyperlinkId) {
|
|
768
|
+
return JSON.parse(this.wasm.RemoveHyperlink(this.handle, hyperlinkId));
|
|
769
|
+
}
|
|
770
|
+
/** Versioned operational facts for native image inspection/mutation in this runtime. */
|
|
771
|
+
getImageCapabilities() {
|
|
772
|
+
return JSON.parse(this.wasm.GetImageCapabilities());
|
|
773
|
+
}
|
|
774
|
+
listImages(scopes = ProjectionScopes.All) {
|
|
775
|
+
return JSON.parse(this.wasm.ListImages(this.handle, scopes));
|
|
776
|
+
}
|
|
777
|
+
insertImage(anchorId, characterOffset, bytes, options = {}) {
|
|
778
|
+
return JSON.parse(this.wasm.InsertImage(this.handle, anchorId, characterOffset, imageBytesToBase64(bytes), JSON.stringify(options)));
|
|
779
|
+
}
|
|
780
|
+
replaceImage(imageId, bytes) {
|
|
781
|
+
return JSON.parse(this.wasm.ReplaceImage(this.handle, imageId, imageBytesToBase64(bytes)));
|
|
782
|
+
}
|
|
783
|
+
setImageDimensions(imageId, dimensions) {
|
|
784
|
+
return JSON.parse(this.wasm.SetImageDimensions(this.handle, imageId, JSON.stringify(dimensions)));
|
|
785
|
+
}
|
|
786
|
+
setImageMetadata(imageId, altText, title) {
|
|
787
|
+
return JSON.parse(this.wasm.SetImageMetadata(this.handle, imageId, altText, title));
|
|
788
|
+
}
|
|
789
|
+
setImageFloatingLayout(imageId, layout) {
|
|
790
|
+
return JSON.parse(this.wasm.SetImageFloatingLayout(this.handle, imageId, JSON.stringify(layout)));
|
|
791
|
+
}
|
|
792
|
+
removeImage(imageId) {
|
|
793
|
+
return JSON.parse(this.wasm.RemoveImage(this.handle, imageId));
|
|
794
|
+
}
|
|
795
|
+
/** Native Word structured-document tags, in outer-before-inner story order. */
|
|
796
|
+
listContentControls(scopes = ProjectionScopes.All) {
|
|
797
|
+
return JSON.parse(this.wasm.ListContentControls(this.handle, scopes));
|
|
798
|
+
}
|
|
799
|
+
fillContentControlText(anchorId, text, options = {}) {
|
|
800
|
+
return JSON.parse(this.wasm.FillContentControlText(this.handle, anchorId, text, JSON.stringify(options)));
|
|
801
|
+
}
|
|
802
|
+
fillContentControlRichText(anchorId, markdown, options = {}) {
|
|
803
|
+
return JSON.parse(this.wasm.FillContentControlRichText(this.handle, anchorId, markdown, JSON.stringify(options)));
|
|
804
|
+
}
|
|
805
|
+
setContentControlChecked(anchorId, isChecked, options = {}) {
|
|
806
|
+
return JSON.parse(this.wasm.SetContentControlChecked(this.handle, anchorId, isChecked, JSON.stringify(options)));
|
|
807
|
+
}
|
|
808
|
+
setContentControlDate(anchorId, value, displayText, options = {}) {
|
|
809
|
+
const timestamp = value instanceof Date ? value.toISOString() : value;
|
|
810
|
+
return JSON.parse(this.wasm.SetContentControlDate(this.handle, anchorId, timestamp, displayText ?? null, JSON.stringify(options)));
|
|
811
|
+
}
|
|
812
|
+
selectContentControlItem(anchorId, value, options = {}) {
|
|
813
|
+
return JSON.parse(this.wasm.SelectContentControlItem(this.handle, anchorId, value, JSON.stringify(options)));
|
|
814
|
+
}
|
|
815
|
+
fillContentControlPicture(anchorId, bytes, options = {}) {
|
|
816
|
+
return JSON.parse(this.wasm.FillContentControlPicture(this.handle, anchorId, imageBytesToBase64(bytes), JSON.stringify(options)));
|
|
817
|
+
}
|
|
818
|
+
addRepeatingSectionItem(sectionAnchorId, afterItemAnchorId, options = {}) {
|
|
819
|
+
return JSON.parse(this.wasm.AddRepeatingSectionItem(this.handle, sectionAnchorId, afterItemAnchorId ?? "", JSON.stringify(options)));
|
|
820
|
+
}
|
|
821
|
+
removeRepeatingSectionItem(itemAnchorId) {
|
|
822
|
+
return JSON.parse(this.wasm.RemoveRepeatingSectionItem(this.handle, itemAnchorId));
|
|
823
|
+
}
|
|
824
|
+
listBookmarks(scopes = ProjectionScopes.All) {
|
|
825
|
+
return JSON.parse(this.wasm.ListBookmarks(this.handle, scopes));
|
|
826
|
+
}
|
|
827
|
+
addBookmark(name, range) {
|
|
828
|
+
return JSON.parse(this.wasm.AddBookmark(this.handle, name, range.startAnchorId, range.startOffset, range.endAnchorId, range.endOffset));
|
|
829
|
+
}
|
|
830
|
+
renameBookmark(name, newName) {
|
|
831
|
+
return JSON.parse(this.wasm.RenameBookmark(this.handle, name, newName));
|
|
832
|
+
}
|
|
833
|
+
moveBookmark(name, range) {
|
|
834
|
+
return JSON.parse(this.wasm.MoveBookmark(this.handle, name, range.startAnchorId, range.startOffset, range.endAnchorId, range.endOffset));
|
|
835
|
+
}
|
|
836
|
+
removeBookmark(name) {
|
|
837
|
+
return JSON.parse(this.wasm.RemoveBookmark(this.handle, name));
|
|
838
|
+
}
|
|
346
839
|
// ─── Tracked revisions (issue #318) ──────────────────────────────────
|
|
347
840
|
/** Markup-native tracked-revision listing, in document order across body, headers,
|
|
348
841
|
* footers, footnotes, and endnotes. Ids are stable while the underlying markup
|
|
@@ -363,6 +856,22 @@ export class DocxSession {
|
|
|
363
856
|
rejectRevision(revisionId) {
|
|
364
857
|
return JSON.parse(this.wasm.RejectRevision(this.handle, revisionId));
|
|
365
858
|
}
|
|
859
|
+
/**
|
|
860
|
+
* Accept every live revision as one undoable session mutation.
|
|
861
|
+
*
|
|
862
|
+
* Fails closed: an unsupported, malformed, or ambiguous registry entry aborts the whole
|
|
863
|
+
* operation (`revisionUnsupported`/`revisionMalformed`/`revisionAmbiguous`) and nothing is
|
|
864
|
+
* mutated. There is no force mode — call {@link listRevisions} and read each entry's
|
|
865
|
+
* `diagnostic` to see what blocks it.
|
|
866
|
+
*/
|
|
867
|
+
acceptAllRevisions() {
|
|
868
|
+
return JSON.parse(this.wasm.AcceptAllRevisions(this.handle));
|
|
869
|
+
}
|
|
870
|
+
/** Reject every live revision as one undoable session mutation. Fails closed exactly like
|
|
871
|
+
* {@link acceptAllRevisions}. */
|
|
872
|
+
rejectAllRevisions() {
|
|
873
|
+
return JSON.parse(this.wasm.RejectAllRevisions(this.handle));
|
|
874
|
+
}
|
|
366
875
|
// ─── Tier C: formatting ──────────────────────────────────────────────
|
|
367
876
|
applyFormat(anchorId, span, op) {
|
|
368
877
|
const spanJson = span ? JSON.stringify(span) : "";
|
|
@@ -462,6 +971,10 @@ export class DocxSession {
|
|
|
462
971
|
* replaces it with `replace`, preserving the surrounding run formatting that
|
|
463
972
|
* the match didn't touch. Returns one `EditResult` per attempted match.
|
|
464
973
|
*
|
|
974
|
+
* A `find` that matches nothing fails with a single `text_not_found` result
|
|
975
|
+
* naming the anchor and the needle — never an empty array. Pass
|
|
976
|
+
* `expectedMatchCount: 0` to assert absence as a successful no-op instead.
|
|
977
|
+
*
|
|
465
978
|
* Run-formatting contract: the replacement text inherits the formatting of
|
|
466
979
|
* the FIRST run the match spanned. Middle/trailing runs keep their `w:rPr`
|
|
467
980
|
* but lose the slice of text the match consumed.
|
|
@@ -635,8 +1148,11 @@ export class DocxSession {
|
|
|
635
1148
|
*
|
|
636
1149
|
* @see docs/architecture/docx_mutation_api.md#findplaceholders
|
|
637
1150
|
*/
|
|
638
|
-
findPlaceholders(kinds = PlaceholderKinds.All, scope = 1, contextChars = 80, boundary = ContextBoundary.Char) {
|
|
639
|
-
|
|
1151
|
+
findPlaceholders(kinds = PlaceholderKinds.All, scope = 1, contextChars = 80, boundary = ContextBoundary.Char, citation) {
|
|
1152
|
+
const json = citation
|
|
1153
|
+
? this.wasm.FindPlaceholdersWithCitations(this.handle, kinds, scope, contextChars, boundary, JSON.stringify(citation))
|
|
1154
|
+
: this.wasm.FindPlaceholders(this.handle, kinds, scope, contextChars, boundary);
|
|
1155
|
+
return JSON.parse(json);
|
|
640
1156
|
}
|
|
641
1157
|
/**
|
|
642
1158
|
* Returns a snapshot of edit-state introspection signals — placeholder counts,
|
|
@@ -659,6 +1175,21 @@ export class DocxSession {
|
|
|
659
1175
|
}
|
|
660
1176
|
return raw;
|
|
661
1177
|
}
|
|
1178
|
+
/**
|
|
1179
|
+
* Return stable semantic changes from the package opened for this session to
|
|
1180
|
+
* its current state. Requires `captureInitialProjection` (enabled by default).
|
|
1181
|
+
*/
|
|
1182
|
+
getSemanticChanges() {
|
|
1183
|
+
return JSON.parse(this.wasm.GetSemanticChanges(this.handle));
|
|
1184
|
+
}
|
|
1185
|
+
/**
|
|
1186
|
+
* Run the default deliverable gate on this session's normal clean-save checkpoint.
|
|
1187
|
+
* With initial projection capture enabled (the default), exact opening bytes are the
|
|
1188
|
+
* baseline used for dispositions and semantic/package deltas.
|
|
1189
|
+
*/
|
|
1190
|
+
verifyDeliverable() {
|
|
1191
|
+
return JSON.parse(this.wasm.VerifyDeliverable(this.handle));
|
|
1192
|
+
}
|
|
662
1193
|
// ─── Annotation-based anchor discovery (#132) ────────────────────────
|
|
663
1194
|
/**
|
|
664
1195
|
* Resolves an annotation's range to the block-level markdown anchors covering
|
|
@@ -674,8 +1205,11 @@ export class DocxSession {
|
|
|
674
1205
|
*
|
|
675
1206
|
* @see docs/architecture/docx_mutation_api.md#findbyannotation
|
|
676
1207
|
*/
|
|
677
|
-
findByAnnotation(annotationId) {
|
|
678
|
-
|
|
1208
|
+
findByAnnotation(annotationId, citation) {
|
|
1209
|
+
const json = citation
|
|
1210
|
+
? this.wasm.FindByAnnotationWithCitations(this.handle, annotationId, JSON.stringify(citation))
|
|
1211
|
+
: this.wasm.FindByAnnotation(this.handle, annotationId);
|
|
1212
|
+
return JSON.parse(json);
|
|
679
1213
|
}
|
|
680
1214
|
/**
|
|
681
1215
|
* Finds every annotation whose `labelId` matches and resolves each of their
|
|
@@ -684,8 +1218,11 @@ export class DocxSession {
|
|
|
684
1218
|
* annotations on different paragraphs become three entries). Annotations
|
|
685
1219
|
* whose bookmark resolves to no anchors are omitted from the result.
|
|
686
1220
|
*/
|
|
687
|
-
findByLabel(labelId) {
|
|
688
|
-
|
|
1221
|
+
findByLabel(labelId, citation) {
|
|
1222
|
+
const json = citation
|
|
1223
|
+
? this.wasm.FindByLabelWithCitations(this.handle, labelId, JSON.stringify(citation))
|
|
1224
|
+
: this.wasm.FindByLabel(this.handle, labelId);
|
|
1225
|
+
return JSON.parse(json);
|
|
689
1226
|
}
|
|
690
1227
|
/**
|
|
691
1228
|
* Resolves any bookmark in the main document part (Docxodus-managed or
|
|
@@ -693,8 +1230,11 @@ export class DocxSession {
|
|
|
693
1230
|
* order. Empty when the bookmark name is unknown. Use this for raw bookmark
|
|
694
1231
|
* names that didn't come from the annotation system.
|
|
695
1232
|
*/
|
|
696
|
-
findByBookmark(bookmarkName) {
|
|
697
|
-
|
|
1233
|
+
findByBookmark(bookmarkName, citation) {
|
|
1234
|
+
const json = citation
|
|
1235
|
+
? this.wasm.FindByBookmarkWithCitations(this.handle, bookmarkName, JSON.stringify(citation))
|
|
1236
|
+
: this.wasm.FindByBookmark(this.handle, bookmarkName);
|
|
1237
|
+
return JSON.parse(json);
|
|
698
1238
|
}
|
|
699
1239
|
// ─── Text/kind-based anchor discovery (#171) ─────────────────────────
|
|
700
1240
|
/**
|
|
@@ -733,13 +1273,22 @@ export class DocxSession {
|
|
|
733
1273
|
return JSON.parse(this.wasm.FindByRegex(this.handle, pattern, regexOptions, options ? JSON.stringify(options) : ""));
|
|
734
1274
|
}
|
|
735
1275
|
/**
|
|
736
|
-
* Return every anchor of the given `kind`
|
|
737
|
-
* `"
|
|
1276
|
+
* Return every anchor of the given `kind` — one of `"p"`, `"h"`, `"li"`,
|
|
1277
|
+
* `"tbl"`, `"tr"`, `"tc"`, `"col"`, `"sdt"`, `"sec"`, `"fn"`, `"en"`,
|
|
1278
|
+
* `"cmt"`, `"unk"` — in document order. Matching is an exact string comparison;
|
|
1279
|
+
* in particular the row and cell tokens are `"tr"` and `"tc"`, never
|
|
1280
|
+
* `"row"`/`"cell"`. Unknown strings are not parsed or rejected: they simply
|
|
1281
|
+
* return an empty array. `"img"` and `"drw"` likewise return empty because the
|
|
1282
|
+
* projection never assigns those reserved kinds; address images through the
|
|
1283
|
+
* image surface instead. Reads the projection's anchor
|
|
738
1284
|
* index directly — no text scan. Pass `scope` (e.g. `"body"`) to restrict to
|
|
739
1285
|
* a single part; omit it to span all scopes.
|
|
740
1286
|
*/
|
|
741
|
-
findByKind(kind, scope) {
|
|
742
|
-
|
|
1287
|
+
findByKind(kind, scope, citation) {
|
|
1288
|
+
const json = citation
|
|
1289
|
+
? this.wasm.FindByKindWithCitations(this.handle, kind, scope ?? "", JSON.stringify(citation))
|
|
1290
|
+
: this.wasm.FindByKind(this.handle, kind, scope ?? "");
|
|
1291
|
+
return JSON.parse(json);
|
|
743
1292
|
}
|
|
744
1293
|
/**
|
|
745
1294
|
* Look up a single anchor's preview info — `{ id, kind, scope, textPreview }`.
|
|
@@ -793,6 +1342,18 @@ export class DocxSession {
|
|
|
793
1342
|
const raw = this.wasm.GetSectionInfo(this.handle, anchorId);
|
|
794
1343
|
return JSON.parse(raw);
|
|
795
1344
|
}
|
|
1345
|
+
/** Enumerate the document's explicit style catalog with resolved high-signal properties. */
|
|
1346
|
+
listStyles() {
|
|
1347
|
+
return JSON.parse(this.wasm.ListStyles(this.handle));
|
|
1348
|
+
}
|
|
1349
|
+
/** Inspect direct and effective paragraph/run formatting for one paragraph anchor. */
|
|
1350
|
+
getFormatting(anchorId) {
|
|
1351
|
+
return JSON.parse(this.wasm.GetFormatting(this.handle, anchorId));
|
|
1352
|
+
}
|
|
1353
|
+
/** Enumerate text-bearing runs as mutation-compatible anchor/span pairs. */
|
|
1354
|
+
listInlineSpans(anchorId) {
|
|
1355
|
+
return JSON.parse(this.wasm.ListInlineSpans(this.handle, anchorId));
|
|
1356
|
+
}
|
|
796
1357
|
/**
|
|
797
1358
|
* Enumerates every annotation persisted in the document. Lets an agent prime
|
|
798
1359
|
* itself with "here are the labeled regions you can target" before committing
|
|
@@ -851,6 +1412,13 @@ export class DocxSession {
|
|
|
851
1412
|
this.close();
|
|
852
1413
|
}
|
|
853
1414
|
}
|
|
1415
|
+
function imageBytesToBase64(bytes) {
|
|
1416
|
+
let binary = "";
|
|
1417
|
+
for (let offset = 0; offset < bytes.length; offset += 0x8000) {
|
|
1418
|
+
binary += String.fromCharCode(...bytes.subarray(offset, offset + 0x8000));
|
|
1419
|
+
}
|
|
1420
|
+
return globalThis.btoa(binary);
|
|
1421
|
+
}
|
|
854
1422
|
/**
|
|
855
1423
|
* Opens a new {@link DocxSession} over the supplied DOCX bytes.
|
|
856
1424
|
* The returned session holds its document in WASM memory until you call
|