docxodus 9.9.0 → 10.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 +178 -0
- 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 +244 -3
- package/dist/docxodus.worker.js.map +1 -1
- package/dist/editor.bundle.js +1914 -419
- 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 +2803 -469
- package/dist/embed.iife.js +2801 -467
- package/dist/export-assets.json +283 -0
- package/dist/export-browser.bundle.js +7990 -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 +62 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +130 -16
- 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 +11 -4
- 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 +1 -1
- package/dist/ribbon-chrome.d.ts.map +1 -1
- package/dist/ribbon-chrome.js +45 -0
- package/dist/ribbon-chrome.js.map +1 -1
- package/dist/ribbon.js +70 -0
- package/dist/ribbon.js.map +1 -1
- package/dist/session.bundle.js +695 -29
- package/dist/session.d.ts +134 -19
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +562 -25
- package/dist/session.js.map +1 -1
- package/dist/types.d.ts +1207 -16
- package/dist/types.d.ts.map +1 -1
- 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.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.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.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 +21 -21
- package/dist/wasm/_framework/dotnet.boot.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 +196 -29
- package/dist/worker-proxy.d.ts +36 -3
- package/dist/worker-proxy.d.ts.map +1 -1
- package/dist/worker-proxy.js +186 -33
- package/dist/worker-proxy.js.map +1 -1
- package/package.json +22 -6
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
|
|
@@ -284,6 +654,17 @@ export class DocxSession {
|
|
|
284
654
|
insertFootnote(anchorId, characterOffset, markdown) {
|
|
285
655
|
return JSON.parse(this.wasm.InsertFootnote(this.handle, anchorId, characterOffset, markdown));
|
|
286
656
|
}
|
|
657
|
+
/**
|
|
658
|
+
* Insert a Word-faithful internal cross-reference — a `REF` field targeting an existing
|
|
659
|
+
* bookmark — at a character offset (issue #545). The field carries a cached result run
|
|
660
|
+
* (the bookmarked text, or the target's auto-number under `referenceNumber`), so
|
|
661
|
+
* renderers that do not recompute fields show a faithful snapshot and Word updates it
|
|
662
|
+
* like a hand-authored cross-reference. A missing or incoherent bookmark fails with
|
|
663
|
+
* `missing_bookmark_target`.
|
|
664
|
+
*/
|
|
665
|
+
insertCrossReference(anchorId, characterOffset, bookmarkName, options) {
|
|
666
|
+
return JSON.parse(this.wasm.InsertCrossReference(this.handle, anchorId, characterOffset, bookmarkName, options ? JSON.stringify(options) : ""));
|
|
667
|
+
}
|
|
287
668
|
/** Create an endnote — see {@link insertFootnote}; writes the endnotes part and a
|
|
288
669
|
* `w:endnoteReference`, and the created definition anchor has kind `en`. */
|
|
289
670
|
insertEndnote(anchorId, characterOffset, markdown) {
|
|
@@ -343,6 +724,87 @@ export class DocxSession {
|
|
|
343
724
|
listComments() {
|
|
344
725
|
return JSON.parse(this.wasm.ListComments(this.handle));
|
|
345
726
|
}
|
|
727
|
+
listHyperlinks(scopes = ProjectionScopes.All) {
|
|
728
|
+
return JSON.parse(this.wasm.ListHyperlinks(this.handle, scopes));
|
|
729
|
+
}
|
|
730
|
+
addHyperlink(anchorId, span, kind, target) {
|
|
731
|
+
return JSON.parse(this.wasm.AddHyperlink(this.handle, anchorId, span.start, span.length, kind, target));
|
|
732
|
+
}
|
|
733
|
+
updateHyperlink(hyperlinkId, kind, target) {
|
|
734
|
+
return JSON.parse(this.wasm.UpdateHyperlink(this.handle, hyperlinkId, kind, target));
|
|
735
|
+
}
|
|
736
|
+
removeHyperlink(hyperlinkId) {
|
|
737
|
+
return JSON.parse(this.wasm.RemoveHyperlink(this.handle, hyperlinkId));
|
|
738
|
+
}
|
|
739
|
+
/** Versioned operational facts for native image inspection/mutation in this runtime. */
|
|
740
|
+
getImageCapabilities() {
|
|
741
|
+
return JSON.parse(this.wasm.GetImageCapabilities());
|
|
742
|
+
}
|
|
743
|
+
listImages(scopes = ProjectionScopes.All) {
|
|
744
|
+
return JSON.parse(this.wasm.ListImages(this.handle, scopes));
|
|
745
|
+
}
|
|
746
|
+
insertImage(anchorId, characterOffset, bytes, options = {}) {
|
|
747
|
+
return JSON.parse(this.wasm.InsertImage(this.handle, anchorId, characterOffset, imageBytesToBase64(bytes), JSON.stringify(options)));
|
|
748
|
+
}
|
|
749
|
+
replaceImage(imageId, bytes) {
|
|
750
|
+
return JSON.parse(this.wasm.ReplaceImage(this.handle, imageId, imageBytesToBase64(bytes)));
|
|
751
|
+
}
|
|
752
|
+
setImageDimensions(imageId, dimensions) {
|
|
753
|
+
return JSON.parse(this.wasm.SetImageDimensions(this.handle, imageId, JSON.stringify(dimensions)));
|
|
754
|
+
}
|
|
755
|
+
setImageMetadata(imageId, altText, title) {
|
|
756
|
+
return JSON.parse(this.wasm.SetImageMetadata(this.handle, imageId, altText, title));
|
|
757
|
+
}
|
|
758
|
+
setImageFloatingLayout(imageId, layout) {
|
|
759
|
+
return JSON.parse(this.wasm.SetImageFloatingLayout(this.handle, imageId, JSON.stringify(layout)));
|
|
760
|
+
}
|
|
761
|
+
removeImage(imageId) {
|
|
762
|
+
return JSON.parse(this.wasm.RemoveImage(this.handle, imageId));
|
|
763
|
+
}
|
|
764
|
+
/** Native Word structured-document tags, in outer-before-inner story order. */
|
|
765
|
+
listContentControls(scopes = ProjectionScopes.All) {
|
|
766
|
+
return JSON.parse(this.wasm.ListContentControls(this.handle, scopes));
|
|
767
|
+
}
|
|
768
|
+
fillContentControlText(anchorId, text, options = {}) {
|
|
769
|
+
return JSON.parse(this.wasm.FillContentControlText(this.handle, anchorId, text, JSON.stringify(options)));
|
|
770
|
+
}
|
|
771
|
+
fillContentControlRichText(anchorId, markdown, options = {}) {
|
|
772
|
+
return JSON.parse(this.wasm.FillContentControlRichText(this.handle, anchorId, markdown, JSON.stringify(options)));
|
|
773
|
+
}
|
|
774
|
+
setContentControlChecked(anchorId, isChecked, options = {}) {
|
|
775
|
+
return JSON.parse(this.wasm.SetContentControlChecked(this.handle, anchorId, isChecked, JSON.stringify(options)));
|
|
776
|
+
}
|
|
777
|
+
setContentControlDate(anchorId, value, displayText, options = {}) {
|
|
778
|
+
const timestamp = value instanceof Date ? value.toISOString() : value;
|
|
779
|
+
return JSON.parse(this.wasm.SetContentControlDate(this.handle, anchorId, timestamp, displayText ?? null, JSON.stringify(options)));
|
|
780
|
+
}
|
|
781
|
+
selectContentControlItem(anchorId, value, options = {}) {
|
|
782
|
+
return JSON.parse(this.wasm.SelectContentControlItem(this.handle, anchorId, value, JSON.stringify(options)));
|
|
783
|
+
}
|
|
784
|
+
fillContentControlPicture(anchorId, bytes, options = {}) {
|
|
785
|
+
return JSON.parse(this.wasm.FillContentControlPicture(this.handle, anchorId, imageBytesToBase64(bytes), JSON.stringify(options)));
|
|
786
|
+
}
|
|
787
|
+
addRepeatingSectionItem(sectionAnchorId, afterItemAnchorId, options = {}) {
|
|
788
|
+
return JSON.parse(this.wasm.AddRepeatingSectionItem(this.handle, sectionAnchorId, afterItemAnchorId ?? "", JSON.stringify(options)));
|
|
789
|
+
}
|
|
790
|
+
removeRepeatingSectionItem(itemAnchorId) {
|
|
791
|
+
return JSON.parse(this.wasm.RemoveRepeatingSectionItem(this.handle, itemAnchorId));
|
|
792
|
+
}
|
|
793
|
+
listBookmarks(scopes = ProjectionScopes.All) {
|
|
794
|
+
return JSON.parse(this.wasm.ListBookmarks(this.handle, scopes));
|
|
795
|
+
}
|
|
796
|
+
addBookmark(name, range) {
|
|
797
|
+
return JSON.parse(this.wasm.AddBookmark(this.handle, name, range.startAnchorId, range.startOffset, range.endAnchorId, range.endOffset));
|
|
798
|
+
}
|
|
799
|
+
renameBookmark(name, newName) {
|
|
800
|
+
return JSON.parse(this.wasm.RenameBookmark(this.handle, name, newName));
|
|
801
|
+
}
|
|
802
|
+
moveBookmark(name, range) {
|
|
803
|
+
return JSON.parse(this.wasm.MoveBookmark(this.handle, name, range.startAnchorId, range.startOffset, range.endAnchorId, range.endOffset));
|
|
804
|
+
}
|
|
805
|
+
removeBookmark(name) {
|
|
806
|
+
return JSON.parse(this.wasm.RemoveBookmark(this.handle, name));
|
|
807
|
+
}
|
|
346
808
|
// ─── Tracked revisions (issue #318) ──────────────────────────────────
|
|
347
809
|
/** Markup-native tracked-revision listing, in document order across body, headers,
|
|
348
810
|
* footers, footnotes, and endnotes. Ids are stable while the underlying markup
|
|
@@ -363,6 +825,22 @@ export class DocxSession {
|
|
|
363
825
|
rejectRevision(revisionId) {
|
|
364
826
|
return JSON.parse(this.wasm.RejectRevision(this.handle, revisionId));
|
|
365
827
|
}
|
|
828
|
+
/**
|
|
829
|
+
* Accept every live revision as one undoable session mutation.
|
|
830
|
+
*
|
|
831
|
+
* Fails closed: an unsupported, malformed, or ambiguous registry entry aborts the whole
|
|
832
|
+
* operation (`revisionUnsupported`/`revisionMalformed`/`revisionAmbiguous`) and nothing is
|
|
833
|
+
* mutated. There is no force mode — call {@link listRevisions} and read each entry's
|
|
834
|
+
* `diagnostic` to see what blocks it.
|
|
835
|
+
*/
|
|
836
|
+
acceptAllRevisions() {
|
|
837
|
+
return JSON.parse(this.wasm.AcceptAllRevisions(this.handle));
|
|
838
|
+
}
|
|
839
|
+
/** Reject every live revision as one undoable session mutation. Fails closed exactly like
|
|
840
|
+
* {@link acceptAllRevisions}. */
|
|
841
|
+
rejectAllRevisions() {
|
|
842
|
+
return JSON.parse(this.wasm.RejectAllRevisions(this.handle));
|
|
843
|
+
}
|
|
366
844
|
// ─── Tier C: formatting ──────────────────────────────────────────────
|
|
367
845
|
applyFormat(anchorId, span, op) {
|
|
368
846
|
const spanJson = span ? JSON.stringify(span) : "";
|
|
@@ -462,6 +940,10 @@ export class DocxSession {
|
|
|
462
940
|
* replaces it with `replace`, preserving the surrounding run formatting that
|
|
463
941
|
* the match didn't touch. Returns one `EditResult` per attempted match.
|
|
464
942
|
*
|
|
943
|
+
* A `find` that matches nothing fails with a single `text_not_found` result
|
|
944
|
+
* naming the anchor and the needle — never an empty array. Pass
|
|
945
|
+
* `expectedMatchCount: 0` to assert absence as a successful no-op instead.
|
|
946
|
+
*
|
|
465
947
|
* Run-formatting contract: the replacement text inherits the formatting of
|
|
466
948
|
* the FIRST run the match spanned. Middle/trailing runs keep their `w:rPr`
|
|
467
949
|
* but lose the slice of text the match consumed.
|
|
@@ -635,8 +1117,11 @@ export class DocxSession {
|
|
|
635
1117
|
*
|
|
636
1118
|
* @see docs/architecture/docx_mutation_api.md#findplaceholders
|
|
637
1119
|
*/
|
|
638
|
-
findPlaceholders(kinds = PlaceholderKinds.All, scope = 1, contextChars = 80, boundary = ContextBoundary.Char) {
|
|
639
|
-
|
|
1120
|
+
findPlaceholders(kinds = PlaceholderKinds.All, scope = 1, contextChars = 80, boundary = ContextBoundary.Char, citation) {
|
|
1121
|
+
const json = citation
|
|
1122
|
+
? this.wasm.FindPlaceholdersWithCitations(this.handle, kinds, scope, contextChars, boundary, JSON.stringify(citation))
|
|
1123
|
+
: this.wasm.FindPlaceholders(this.handle, kinds, scope, contextChars, boundary);
|
|
1124
|
+
return JSON.parse(json);
|
|
640
1125
|
}
|
|
641
1126
|
/**
|
|
642
1127
|
* Returns a snapshot of edit-state introspection signals — placeholder counts,
|
|
@@ -659,6 +1144,21 @@ export class DocxSession {
|
|
|
659
1144
|
}
|
|
660
1145
|
return raw;
|
|
661
1146
|
}
|
|
1147
|
+
/**
|
|
1148
|
+
* Return stable semantic changes from the package opened for this session to
|
|
1149
|
+
* its current state. Requires `captureInitialProjection` (enabled by default).
|
|
1150
|
+
*/
|
|
1151
|
+
getSemanticChanges() {
|
|
1152
|
+
return JSON.parse(this.wasm.GetSemanticChanges(this.handle));
|
|
1153
|
+
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Run the default deliverable gate on this session's normal clean-save checkpoint.
|
|
1156
|
+
* With initial projection capture enabled (the default), exact opening bytes are the
|
|
1157
|
+
* baseline used for dispositions and semantic/package deltas.
|
|
1158
|
+
*/
|
|
1159
|
+
verifyDeliverable() {
|
|
1160
|
+
return JSON.parse(this.wasm.VerifyDeliverable(this.handle));
|
|
1161
|
+
}
|
|
662
1162
|
// ─── Annotation-based anchor discovery (#132) ────────────────────────
|
|
663
1163
|
/**
|
|
664
1164
|
* Resolves an annotation's range to the block-level markdown anchors covering
|
|
@@ -674,8 +1174,11 @@ export class DocxSession {
|
|
|
674
1174
|
*
|
|
675
1175
|
* @see docs/architecture/docx_mutation_api.md#findbyannotation
|
|
676
1176
|
*/
|
|
677
|
-
findByAnnotation(annotationId) {
|
|
678
|
-
|
|
1177
|
+
findByAnnotation(annotationId, citation) {
|
|
1178
|
+
const json = citation
|
|
1179
|
+
? this.wasm.FindByAnnotationWithCitations(this.handle, annotationId, JSON.stringify(citation))
|
|
1180
|
+
: this.wasm.FindByAnnotation(this.handle, annotationId);
|
|
1181
|
+
return JSON.parse(json);
|
|
679
1182
|
}
|
|
680
1183
|
/**
|
|
681
1184
|
* Finds every annotation whose `labelId` matches and resolves each of their
|
|
@@ -684,8 +1187,11 @@ export class DocxSession {
|
|
|
684
1187
|
* annotations on different paragraphs become three entries). Annotations
|
|
685
1188
|
* whose bookmark resolves to no anchors are omitted from the result.
|
|
686
1189
|
*/
|
|
687
|
-
findByLabel(labelId) {
|
|
688
|
-
|
|
1190
|
+
findByLabel(labelId, citation) {
|
|
1191
|
+
const json = citation
|
|
1192
|
+
? this.wasm.FindByLabelWithCitations(this.handle, labelId, JSON.stringify(citation))
|
|
1193
|
+
: this.wasm.FindByLabel(this.handle, labelId);
|
|
1194
|
+
return JSON.parse(json);
|
|
689
1195
|
}
|
|
690
1196
|
/**
|
|
691
1197
|
* Resolves any bookmark in the main document part (Docxodus-managed or
|
|
@@ -693,8 +1199,11 @@ export class DocxSession {
|
|
|
693
1199
|
* order. Empty when the bookmark name is unknown. Use this for raw bookmark
|
|
694
1200
|
* names that didn't come from the annotation system.
|
|
695
1201
|
*/
|
|
696
|
-
findByBookmark(bookmarkName) {
|
|
697
|
-
|
|
1202
|
+
findByBookmark(bookmarkName, citation) {
|
|
1203
|
+
const json = citation
|
|
1204
|
+
? this.wasm.FindByBookmarkWithCitations(this.handle, bookmarkName, JSON.stringify(citation))
|
|
1205
|
+
: this.wasm.FindByBookmark(this.handle, bookmarkName);
|
|
1206
|
+
return JSON.parse(json);
|
|
698
1207
|
}
|
|
699
1208
|
// ─── Text/kind-based anchor discovery (#171) ─────────────────────────
|
|
700
1209
|
/**
|
|
@@ -733,13 +1242,22 @@ export class DocxSession {
|
|
|
733
1242
|
return JSON.parse(this.wasm.FindByRegex(this.handle, pattern, regexOptions, options ? JSON.stringify(options) : ""));
|
|
734
1243
|
}
|
|
735
1244
|
/**
|
|
736
|
-
* Return every anchor of the given `kind`
|
|
737
|
-
* `"
|
|
1245
|
+
* Return every anchor of the given `kind` — one of `"p"`, `"h"`, `"li"`,
|
|
1246
|
+
* `"tbl"`, `"tr"`, `"tc"`, `"col"`, `"sdt"`, `"sec"`, `"fn"`, `"en"`,
|
|
1247
|
+
* `"cmt"`, `"unk"` — in document order. Matching is an exact string comparison;
|
|
1248
|
+
* in particular the row and cell tokens are `"tr"` and `"tc"`, never
|
|
1249
|
+
* `"row"`/`"cell"`. Unknown strings are not parsed or rejected: they simply
|
|
1250
|
+
* return an empty array. `"img"` and `"drw"` likewise return empty because the
|
|
1251
|
+
* projection never assigns those reserved kinds; address images through the
|
|
1252
|
+
* image surface instead. Reads the projection's anchor
|
|
738
1253
|
* index directly — no text scan. Pass `scope` (e.g. `"body"`) to restrict to
|
|
739
1254
|
* a single part; omit it to span all scopes.
|
|
740
1255
|
*/
|
|
741
|
-
findByKind(kind, scope) {
|
|
742
|
-
|
|
1256
|
+
findByKind(kind, scope, citation) {
|
|
1257
|
+
const json = citation
|
|
1258
|
+
? this.wasm.FindByKindWithCitations(this.handle, kind, scope ?? "", JSON.stringify(citation))
|
|
1259
|
+
: this.wasm.FindByKind(this.handle, kind, scope ?? "");
|
|
1260
|
+
return JSON.parse(json);
|
|
743
1261
|
}
|
|
744
1262
|
/**
|
|
745
1263
|
* Look up a single anchor's preview info — `{ id, kind, scope, textPreview }`.
|
|
@@ -793,6 +1311,18 @@ export class DocxSession {
|
|
|
793
1311
|
const raw = this.wasm.GetSectionInfo(this.handle, anchorId);
|
|
794
1312
|
return JSON.parse(raw);
|
|
795
1313
|
}
|
|
1314
|
+
/** Enumerate the document's explicit style catalog with resolved high-signal properties. */
|
|
1315
|
+
listStyles() {
|
|
1316
|
+
return JSON.parse(this.wasm.ListStyles(this.handle));
|
|
1317
|
+
}
|
|
1318
|
+
/** Inspect direct and effective paragraph/run formatting for one paragraph anchor. */
|
|
1319
|
+
getFormatting(anchorId) {
|
|
1320
|
+
return JSON.parse(this.wasm.GetFormatting(this.handle, anchorId));
|
|
1321
|
+
}
|
|
1322
|
+
/** Enumerate text-bearing runs as mutation-compatible anchor/span pairs. */
|
|
1323
|
+
listInlineSpans(anchorId) {
|
|
1324
|
+
return JSON.parse(this.wasm.ListInlineSpans(this.handle, anchorId));
|
|
1325
|
+
}
|
|
796
1326
|
/**
|
|
797
1327
|
* Enumerates every annotation persisted in the document. Lets an agent prime
|
|
798
1328
|
* itself with "here are the labeled regions you can target" before committing
|
|
@@ -851,6 +1381,13 @@ export class DocxSession {
|
|
|
851
1381
|
this.close();
|
|
852
1382
|
}
|
|
853
1383
|
}
|
|
1384
|
+
function imageBytesToBase64(bytes) {
|
|
1385
|
+
let binary = "";
|
|
1386
|
+
for (let offset = 0; offset < bytes.length; offset += 0x8000) {
|
|
1387
|
+
binary += String.fromCharCode(...bytes.subarray(offset, offset + 0x8000));
|
|
1388
|
+
}
|
|
1389
|
+
return globalThis.btoa(binary);
|
|
1390
|
+
}
|
|
854
1391
|
/**
|
|
855
1392
|
* Opens a new {@link DocxSession} over the supplied DOCX bytes.
|
|
856
1393
|
* The returned session holds its document in WASM memory until you call
|