docxodus 12.1.0 → 12.2.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.
Files changed (75) hide show
  1. package/README.md +9 -4
  2. package/dist/editor.bundle.js +135 -22
  3. package/dist/editor.d.ts +25 -3
  4. package/dist/editor.d.ts.map +1 -1
  5. package/dist/editor.js +132 -13
  6. package/dist/editor.js.map +1 -1
  7. package/dist/embed.bundle.js +573 -47
  8. package/dist/embed.d.ts +30 -1
  9. package/dist/embed.d.ts.map +1 -1
  10. package/dist/embed.iife.js +573 -47
  11. package/dist/embed.js +124 -13
  12. package/dist/embed.js.map +1 -1
  13. package/dist/export-assets.json +33 -33
  14. package/dist/history.d.ts +265 -0
  15. package/dist/history.d.ts.map +1 -0
  16. package/dist/history.js +307 -0
  17. package/dist/history.js.map +1 -0
  18. package/dist/index.d.ts +7 -0
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +20 -2
  21. package/dist/index.js.map +1 -1
  22. package/dist/ribbon-chrome.d.ts +2 -2
  23. package/dist/ribbon-chrome.d.ts.map +1 -1
  24. package/dist/ribbon-chrome.js +6 -4
  25. package/dist/ribbon-chrome.js.map +1 -1
  26. package/dist/ribbon.js +39 -8
  27. package/dist/ribbon.js.map +1 -1
  28. package/dist/session.js +1 -1
  29. package/dist/session.js.map +1 -1
  30. package/dist/types.d.ts +2 -0
  31. package/dist/types.d.ts.map +1 -1
  32. package/dist/types.js.map +1 -1
  33. package/dist/wasm/_framework/DocumentFormat.OpenXml.Framework.wasm +0 -0
  34. package/dist/wasm/_framework/DocumentFormat.OpenXml.Framework.wasm.br +0 -0
  35. package/dist/wasm/_framework/DocumentFormat.OpenXml.wasm +0 -0
  36. package/dist/wasm/_framework/DocumentFormat.OpenXml.wasm.br +0 -0
  37. package/dist/wasm/_framework/Docxodus.wasm +0 -0
  38. package/dist/wasm/_framework/Docxodus.wasm.br +0 -0
  39. package/dist/wasm/_framework/DocxodusWasm.wasm +0 -0
  40. package/dist/wasm/_framework/DocxodusWasm.wasm.br +0 -0
  41. package/dist/wasm/_framework/System.Collections.Concurrent.wasm +0 -0
  42. package/dist/wasm/_framework/System.Collections.Concurrent.wasm.br +0 -0
  43. package/dist/wasm/_framework/System.Collections.wasm +0 -0
  44. package/dist/wasm/_framework/System.Collections.wasm.br +0 -0
  45. package/dist/wasm/_framework/System.IO.Compression.wasm +0 -0
  46. package/dist/wasm/_framework/System.IO.Compression.wasm.br +0 -0
  47. package/dist/wasm/_framework/System.IO.Packaging.wasm +0 -0
  48. package/dist/wasm/_framework/System.IO.Packaging.wasm.br +0 -0
  49. package/dist/wasm/_framework/System.Linq.Expressions.wasm +0 -0
  50. package/dist/wasm/_framework/System.Linq.Expressions.wasm.br +0 -0
  51. package/dist/wasm/_framework/System.Memory.wasm +0 -0
  52. package/dist/wasm/_framework/System.Memory.wasm.br +0 -0
  53. package/dist/wasm/_framework/System.Net.Http.wasm +0 -0
  54. package/dist/wasm/_framework/System.Net.Http.wasm.br +0 -0
  55. package/dist/wasm/_framework/System.Private.CoreLib.wasm +0 -0
  56. package/dist/wasm/_framework/System.Private.CoreLib.wasm.br +0 -0
  57. package/dist/wasm/_framework/System.Private.Xml.Linq.wasm +0 -0
  58. package/dist/wasm/_framework/System.Private.Xml.Linq.wasm.br +0 -0
  59. package/dist/wasm/_framework/System.Private.Xml.wasm +0 -0
  60. package/dist/wasm/_framework/System.Private.Xml.wasm.br +0 -0
  61. package/dist/wasm/_framework/System.Runtime.InteropServices.JavaScript.wasm +0 -0
  62. package/dist/wasm/_framework/System.Runtime.InteropServices.JavaScript.wasm.br +0 -0
  63. package/dist/wasm/_framework/System.Runtime.wasm +0 -0
  64. package/dist/wasm/_framework/System.Runtime.wasm.br +0 -0
  65. package/dist/wasm/_framework/System.Security.Cryptography.wasm +0 -0
  66. package/dist/wasm/_framework/System.Security.Cryptography.wasm.br +0 -0
  67. package/dist/wasm/_framework/System.Text.Json.wasm +0 -0
  68. package/dist/wasm/_framework/System.Text.Json.wasm.br +0 -0
  69. package/dist/wasm/_framework/System.Text.RegularExpressions.wasm +0 -0
  70. package/dist/wasm/_framework/System.Text.RegularExpressions.wasm.br +0 -0
  71. package/dist/wasm/_framework/dotnet.boot.js +21 -21
  72. package/dist/wasm/_framework/dotnet.boot.js.br +0 -0
  73. package/dist/wasm/_framework/dotnet.native.wasm +0 -0
  74. package/dist/wasm/_framework/dotnet.native.wasm.br +0 -0
  75. package/package.json +4 -3
@@ -141,8 +141,14 @@ var Docxodus = (() => {
141
141
  DocxDiffFormatComparison: () => DocxDiffFormatComparison,
142
142
  DocxDiffRevisionGranularity: () => DocxDiffRevisionGranularity,
143
143
  DocxEditor: () => DocxEditor,
144
+ DocxHistoryArchive: () => DocxHistoryArchive,
145
+ DocxHistoryClient: () => DocxHistoryClient,
146
+ DocxHistoryDocument: () => DocxHistoryDocument,
147
+ DocxHistoryError: () => DocxHistoryError,
148
+ DocxHistoryReader: () => DocxHistoryReader,
144
149
  DocxSession: () => DocxSession,
145
150
  EmptyParagraphMode: () => EmptyParagraphMode,
151
+ MAX_HISTORY_ARCHIVE_BYTES: () => MAX_HISTORY_ARCHIVE_BYTES,
146
152
  PaginationEngine: () => PaginationEngine,
147
153
  PaginationMode: () => PaginationMode,
148
154
  PlaceholderKinds: () => PlaceholderKinds,
@@ -166,6 +172,8 @@ var Docxodus = (() => {
166
172
  createBlankDocx: () => createBlankDocx,
167
173
  createEditor: () => createEditor,
168
174
  createExternalAnnotationSet: () => createExternalAnnotationSet,
175
+ createHistoryViewer: () => createHistoryViewer,
176
+ createMemoryHistoryStorage: () => createMemoryHistoryStorage,
169
177
  createRibbonEditor: () => createRibbonEditor,
170
178
  createUnavailablePageMap: () => createUnavailablePageMap,
171
179
  createViewer: () => createViewer,
@@ -200,6 +208,7 @@ var Docxodus = (() => {
200
208
  getWasmExports: () => getWasmExports,
201
209
  hasAnnotations: () => hasAnnotations,
202
210
  initialize: () => initialize,
211
+ installHistoryStorageImports: () => installHistoryStorageImports,
203
212
  isDeletion: () => isDeletion,
204
213
  isFormatChange: () => isFormatChange,
205
214
  isInitialized: () => isInitialized,
@@ -207,6 +216,8 @@ var Docxodus = (() => {
207
216
  isMove: () => isMove,
208
217
  mountRibbon: () => mountRibbon,
209
218
  navigateToPageCitation: () => navigateToPageCitation,
219
+ openDocxHistory: () => openDocxHistory,
220
+ openDocxHistoryArchive: () => openDocxHistoryArchive,
210
221
  openDocxSession: () => openDocxSession2,
211
222
  paginateHtml: () => paginateHtml,
212
223
  parseSectionDimensions: () => parseSectionDimensions,
@@ -2161,6 +2172,288 @@ var Docxodus = (() => {
2161
2172
  return new DocxSession(handle, bridge);
2162
2173
  }
2163
2174
 
2175
+ // src/history.ts
2176
+ var MAX_HISTORY_ARCHIVE_BYTES = 64 * 1024 * 1024;
2177
+ var DocxHistoryError = class extends Error {
2178
+ constructor(code, message) {
2179
+ super(message);
2180
+ this.code = code;
2181
+ this.name = "DocxHistoryError";
2182
+ }
2183
+ };
2184
+ var adapters = /* @__PURE__ */ new Map();
2185
+ var nextAdapter = 0;
2186
+ function adapter(id) {
2187
+ const store = adapters.get(id);
2188
+ if (!store) throw new DocxHistoryError("Closed", "History storage adapter is closed.");
2189
+ return store;
2190
+ }
2191
+ function toBase64(bytes) {
2192
+ let binary = "";
2193
+ for (let offset = 0; offset < bytes.length; offset += 32768)
2194
+ binary += String.fromCharCode(...bytes.subarray(offset, offset + 32768));
2195
+ return btoa(binary);
2196
+ }
2197
+ function fromBase64(encoded) {
2198
+ const binary = atob(encoded);
2199
+ return Uint8Array.from(binary, (c) => c.charCodeAt(0));
2200
+ }
2201
+ function installHistoryStorageImports(setModuleImports) {
2202
+ setModuleImports("docxodus.history", {
2203
+ readBlob: async (id, json) => {
2204
+ const reference = JSON.parse(json);
2205
+ const bytes = await adapter(id).readBlob(reference);
2206
+ if (bytes === null) return "";
2207
+ if (bytes.length !== reference.length) throw new DocxHistoryError("PayloadMismatch", "Stored blob length differs from its reference.");
2208
+ return bytes.length === 0 ? "=" : toBase64(bytes);
2209
+ },
2210
+ putBlob: async (id, json, bytes) => adapter(id).putBlob(JSON.parse(json), bytes.slice()),
2211
+ readHead: async (id, documentId) => JSON.stringify(await adapter(id).readHead(documentId)),
2212
+ advanceHead: async (id, documentId, expected, state) => JSON.stringify(await adapter(id).advanceHead(documentId, JSON.parse(expected), JSON.parse(state))),
2213
+ initializeHead: async (id, documentId, head) => {
2214
+ const storage = adapter(id);
2215
+ if (!storage.initializeHead) throw new DocxHistoryError("InitializationUnsupported", "Storage cannot import an exact head.");
2216
+ return JSON.stringify(await storage.initializeHead(documentId, JSON.parse(head)));
2217
+ }
2218
+ });
2219
+ }
2220
+ var DocxHistoryClient = class {
2221
+ constructor(bridge, storage) {
2222
+ this.bridge = bridge;
2223
+ this.closed = false;
2224
+ if (nextAdapter >= 2147483647) throw new Error("History adapter identifiers exhausted.");
2225
+ this.adapterId = ++nextAdapter;
2226
+ adapters.set(this.adapterId, storage);
2227
+ try {
2228
+ this.handle = storage.initializeHead && bridge.OpenWithInitialization ? bridge.OpenWithInitialization(this.adapterId) : bridge.Open(this.adapterId);
2229
+ } catch (error) {
2230
+ adapters.delete(this.adapterId);
2231
+ throw error;
2232
+ }
2233
+ }
2234
+ close() {
2235
+ if (this.closed) return;
2236
+ this.bridge.Close(this.handle);
2237
+ adapters.delete(this.adapterId);
2238
+ this.closed = true;
2239
+ }
2240
+ /** Bind an identity without reading, creating a version, or taking ownership of this client. */
2241
+ document(documentId) {
2242
+ return new DocxHistoryDocument(documentId, (operation, fields, bytes) => this.call(operation, documentId, fields, bytes));
2243
+ }
2244
+ async exportHistoryArchive(documentId) {
2245
+ return fromBase64((await this.call("exportArchive", documentId)).bytes);
2246
+ }
2247
+ async importHistoryArchive(bytes) {
2248
+ checkArchiveSize(bytes);
2249
+ return (await this.call("importArchive", "", {}, bytes)).import;
2250
+ }
2251
+ async read(documentId) {
2252
+ return (await this.call("read", documentId)).view;
2253
+ }
2254
+ /** Host-triggered validated metadata tail; first join starts at the latest checkpoint. */
2255
+ async readChangesSince(documentId, after, maxEntriesToScan = 1e4) {
2256
+ return (await this.call("updates", documentId, { expectedHead: after, maxEntriesToScan })).update;
2257
+ }
2258
+ async readOperationsSince(documentId, after, maxEntriesToScan = 1e4) {
2259
+ return (await this.call("operations", documentId, { expectedHead: after, maxEntriesToScan })).operationUpdate;
2260
+ }
2261
+ async getOperation(documentId, operationId) {
2262
+ return (await this.call("getOperation", documentId, { operationId })).operation;
2263
+ }
2264
+ async exportOperationProposal(documentId, operationId) {
2265
+ return fromBase64((await this.call("exportOperationProposal", documentId, { operationId })).bytes);
2266
+ }
2267
+ async compareVersions(documentId, beforeVersionId, afterVersionId) {
2268
+ return fromBase64((await this.call("compare", documentId, { beforeVersionId, afterVersionId })).bytes);
2269
+ }
2270
+ async createVersion(documentId, expectedHead, bytes, metadata, requestId) {
2271
+ return (await this.call("create", documentId, { expectedHead, metadata, requestId }, bytes)).view;
2272
+ }
2273
+ async listVersions(documentId, cursor = null, limit = 25) {
2274
+ return (await this.call("list", documentId, { versionId: cursor, limit })).page;
2275
+ }
2276
+ async getVersion(documentId, versionId) {
2277
+ return (await this.call("get", documentId, { versionId })).version;
2278
+ }
2279
+ async exportVersion(documentId, versionId) {
2280
+ return fromBase64((await this.call("export", documentId, { versionId })).bytes);
2281
+ }
2282
+ async materialize(documentId, sequence, maxEntriesToScan = 1e4) {
2283
+ return fromBase64((await this.call("materialize", documentId, { sequence, maxEntriesToScan })).bytes);
2284
+ }
2285
+ async replay(documentId, sequence, maxEntriesToScan = 1e4) {
2286
+ return fromBase64((await this.call("replay", documentId, { sequence, maxEntriesToScan })).bytes);
2287
+ }
2288
+ async resolveSequenceAtTime(documentId, cutoff, maxEntriesToScan = 1e4) {
2289
+ return (await this.call("resolveTime", documentId, { cutoff, maxEntriesToScan })).sequence;
2290
+ }
2291
+ async restoreVersion(documentId, expectedHead, versionId, metadata, requestId) {
2292
+ return (await this.call("restore", documentId, { expectedHead, versionId, metadata, requestId })).view;
2293
+ }
2294
+ async call(operation, documentId, fields = {}, bytes = new Uint8Array()) {
2295
+ if (this.closed) throw new DocxHistoryError("Closed", "History client is closed.");
2296
+ return parseResult(await this.bridge.Invoke(
2297
+ this.handle,
2298
+ JSON.stringify({ schemaVersion: 1, operation, documentId, ...fields }),
2299
+ bytes
2300
+ ));
2301
+ }
2302
+ };
2303
+ var DocxHistoryReader = class {
2304
+ /** @internal Obtain through client.document() or openDocxHistoryArchive(). */
2305
+ constructor(documentId, call) {
2306
+ this.call = call;
2307
+ this.#documentId = documentId;
2308
+ }
2309
+ #documentId;
2310
+ get documentId() {
2311
+ return this.#documentId;
2312
+ }
2313
+ async read() {
2314
+ return (await this.call("read")).view;
2315
+ }
2316
+ async listVersions(cursor = null, limit = 25) {
2317
+ return (await this.call("list", { versionId: cursor, limit })).page;
2318
+ }
2319
+ async getVersion(versionId) {
2320
+ return (await this.call("get", { versionId })).version;
2321
+ }
2322
+ /** Omit versionId for latest; pass an ID to keep a UI operation pinned to a captured version. */
2323
+ async exportDocx(versionId = null) {
2324
+ return fromBase64((await this.call("exportDocx", { versionId })).bytes);
2325
+ }
2326
+ async exportHistoryArchive() {
2327
+ return fromBase64((await this.call("exportArchive")).bytes);
2328
+ }
2329
+ async materialize(sequence, maxEntriesToScan = 1e4) {
2330
+ return fromBase64((await this.call("materialize", { sequence, maxEntriesToScan })).bytes);
2331
+ }
2332
+ async replay(sequence, maxEntriesToScan = 1e4) {
2333
+ return fromBase64((await this.call("replay", { sequence, maxEntriesToScan })).bytes);
2334
+ }
2335
+ async resolveSequenceAtTime(cutoff, maxEntriesToScan = 1e4) {
2336
+ return (await this.call("resolveTime", { cutoff, maxEntriesToScan })).sequence;
2337
+ }
2338
+ async readChangesSince(after, maxEntriesToScan = 1e4) {
2339
+ return (await this.call("updates", { expectedHead: after, maxEntriesToScan })).update;
2340
+ }
2341
+ async readOperationsSince(after, maxEntriesToScan = 1e4) {
2342
+ return (await this.call("operations", { expectedHead: after, maxEntriesToScan })).operationUpdate;
2343
+ }
2344
+ async getOperation(operationId) {
2345
+ return (await this.call("getOperation", { operationId })).operation;
2346
+ }
2347
+ async exportOperationProposal(operationId) {
2348
+ return fromBase64((await this.call("exportOperationProposal", { operationId })).bytes);
2349
+ }
2350
+ /** Redlined DOCX through DocxCompare's existing accepted-input revision policy. */
2351
+ async compareVersions(beforeVersionId, afterVersionId) {
2352
+ return fromBase64((await this.call("compare", { beforeVersionId, afterVersionId })).bytes);
2353
+ }
2354
+ };
2355
+ var DocxHistoryDocument = class extends DocxHistoryReader {
2356
+ async createVersion(expectedHead, bytes, metadata, requestId) {
2357
+ requireRequestId(requestId);
2358
+ return (await this.call("create", { expectedHead, metadata, requestId }, bytes)).view;
2359
+ }
2360
+ async restoreVersion(expectedHead, versionId, metadata, requestId) {
2361
+ requireRequestId(requestId);
2362
+ return (await this.call("restore", { expectedHead, versionId, metadata, requestId })).view;
2363
+ }
2364
+ };
2365
+ var DocxHistoryArchive = class _DocxHistoryArchive extends DocxHistoryReader {
2366
+ constructor(bridge, handle, info) {
2367
+ super(info.documentId, async (operation, fields = {}, bytes = new Uint8Array()) => {
2368
+ if (this.closed) throw new DocxHistoryError("Closed", "History archive is closed.");
2369
+ return parseResult(await bridge.Invoke(
2370
+ handle,
2371
+ JSON.stringify({ schemaVersion: 1, operation, documentId: this.documentId, ...fields }),
2372
+ bytes
2373
+ ));
2374
+ });
2375
+ this.bridge = bridge;
2376
+ this.handle = handle;
2377
+ this.info = info;
2378
+ this.closed = false;
2379
+ }
2380
+ /** For custom loaders; normal callers use openDocxHistoryArchive(). */
2381
+ static async open(bridge, bytes) {
2382
+ checkArchiveSize(bytes);
2383
+ if (!bridge.OpenArchive) throw new Error("This WASM build does not include portable history.");
2384
+ const result = parseResult(await bridge.OpenArchive(bytes));
2385
+ return new _DocxHistoryArchive(bridge, result.handle, result.archive);
2386
+ }
2387
+ close() {
2388
+ if (this.closed) return;
2389
+ this.bridge.Close(this.handle);
2390
+ this.closed = true;
2391
+ }
2392
+ };
2393
+ function parseResult(json) {
2394
+ const result = JSON.parse(json);
2395
+ if (!result.success) throw new DocxHistoryError(result.errorCode, result.message);
2396
+ return result;
2397
+ }
2398
+ function checkArchiveSize(bytes) {
2399
+ if (bytes.length > MAX_HISTORY_ARCHIVE_BYTES) throw new DocxHistoryError("ResourceLimit", "History archive exceeds 64 MiB.");
2400
+ }
2401
+ function requireRequestId(requestId) {
2402
+ if (typeof requestId !== "string" || !requestId.trim()) throw new DocxHistoryError("InvalidRequest", "A durable requestId is required.");
2403
+ }
2404
+ function createMemoryHistoryStorage(maxBlobBytes = 256 * 1024 * 1024) {
2405
+ if (!Number.isSafeInteger(maxBlobBytes) || maxBlobBytes <= 0) throw new RangeError("Invalid blob byte limit.");
2406
+ const blobs = /* @__PURE__ */ new Map();
2407
+ const heads = /* @__PURE__ */ new Map();
2408
+ const copy = (value) => JSON.parse(JSON.stringify(value));
2409
+ const key = (reference) => {
2410
+ if (reference.digest.algorithm !== "SHA-256" || !/^[a-f0-9]{64}$/.test(reference.digest.value) || !Number.isSafeInteger(reference.length) || reference.length < 0 || reference.length > maxBlobBytes)
2411
+ throw new DocxHistoryError("InvalidRequest", "Invalid blob reference or byte limit.");
2412
+ return `${reference.digest.value}:${reference.length}`;
2413
+ };
2414
+ const equalReference = (a, b) => a.length === b.length && a.digest.algorithm === b.digest.algorithm && a.digest.value === b.digest.value;
2415
+ const equalHead = (a, b) => a === null || b === null ? a === b : a.revision === b.revision && equalReference(a.state, b.state);
2416
+ return {
2417
+ async readBlob(reference) {
2418
+ return blobs.get(key(reference))?.slice() ?? null;
2419
+ },
2420
+ async putBlob(reference, bytes) {
2421
+ const blobKey = key(reference);
2422
+ const captured = bytes.slice();
2423
+ if (captured.length !== reference.length) throw new DocxHistoryError("PayloadMismatch", "Blob length mismatch.");
2424
+ const digest = Array.from(
2425
+ new Uint8Array(await crypto.subtle.digest("SHA-256", captured)),
2426
+ (n) => n.toString(16).padStart(2, "0")
2427
+ ).join("");
2428
+ if (digest !== blobKey.split(":")[0]) throw new DocxHistoryError("PayloadMismatch", "Blob digest mismatch.");
2429
+ if (!blobs.has(blobKey)) blobs.set(blobKey, captured);
2430
+ },
2431
+ async readHead(documentId) {
2432
+ return copy(heads.get(documentId) ?? null);
2433
+ },
2434
+ async advanceHead(documentId, expected, state) {
2435
+ key(state);
2436
+ const current = heads.get(documentId) ?? null;
2437
+ if (!equalHead(current, expected)) return null;
2438
+ const revision = BigInt(current?.revision ?? "0") + 1n;
2439
+ if (revision > 9223372036854775807n) throw new RangeError("History revision exhausted.");
2440
+ const head = { revision: String(revision), state: copy(state) };
2441
+ heads.set(documentId, head);
2442
+ return copy(head);
2443
+ },
2444
+ async initializeHead(documentId, head) {
2445
+ key(head.state);
2446
+ if (typeof head.revision !== "string" || !/^[1-9][0-9]*$/.test(head.revision) || BigInt(head.revision) > 9223372036854775807n)
2447
+ throw new DocxHistoryError("InvalidRequest", "Imported head revision must be a positive Int64 string.");
2448
+ const existing = heads.get(documentId);
2449
+ if (existing) return { initialized: false, head: copy(existing) };
2450
+ const captured = copy(head);
2451
+ heads.set(documentId, captured);
2452
+ return { initialized: true, head: copy(captured) };
2453
+ }
2454
+ };
2455
+ }
2456
+
2164
2457
  // src/page-number-format.ts
2165
2458
  var ROMAN_ONES = ["", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix"];
2166
2459
  var ROMAN_TENS = ["", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc"];
@@ -5610,11 +5903,11 @@ var Docxodus = (() => {
5610
5903
  if (!this.partUriFor(which, "default")) this.seedStory(which, "default");
5611
5904
  if (!this.partUriFor(which, kind)) this.seedStory(which, kind);
5612
5905
  }
5613
- const res = parseResult(this.bridge.EnsureHeaderFooterVisible(this.handle, this.bodyAnchorId, kind));
5906
+ const res = parseResult2(this.bridge.EnsureHeaderFooterVisible(this.handle, this.bodyAnchorId, kind));
5614
5907
  if (!res.success) return false;
5615
5908
  } else {
5616
5909
  if (!this.bridge.SetHeaderFooterKindEnabled) return false;
5617
- const res = parseResult(this.bridge.SetHeaderFooterKindEnabled(this.handle, this.bodyAnchorId, kind, false));
5910
+ const res = parseResult2(this.bridge.SetHeaderFooterKindEnabled(this.handle, this.bodyAnchorId, kind, false));
5618
5911
  if (!res.success) return false;
5619
5912
  for (const which of ["header", "footer"]) {
5620
5913
  if (this.kinds[which] === kind) this.kinds[which] = "default";
@@ -5639,7 +5932,7 @@ var Docxodus = (() => {
5639
5932
  * Deliberately a PLAIN field (no `\*` switch), exactly as Word inserts one, so it follows the
5640
5933
  * section's page-number format — see {@link setPageNumbering}. */
5641
5934
  insertPageNumber(which, anchorId, field) {
5642
- const res = parseResult(this.bridge.InsertPageNumberField(this.handle, anchorId, field, ""));
5935
+ const res = parseResult2(this.bridge.InsertPageNumberField(this.handle, anchorId, field, ""));
5643
5936
  if (!res.success) return false;
5644
5937
  this.refresh(which);
5645
5938
  return true;
@@ -5662,7 +5955,7 @@ var Docxodus = (() => {
5662
5955
  /** Set this section's page numbering (`w:pgNumType`) — Word's *Format Page Numbers…*. */
5663
5956
  setPageNumbering(op) {
5664
5957
  if (!this.bodyAnchorId) return false;
5665
- const res = parseResult(this.bridge.SetPageNumbering(this.handle, this.bodyAnchorId, JSON.stringify(op)));
5958
+ const res = parseResult2(this.bridge.SetPageNumbering(this.handle, this.bodyAnchorId, JSON.stringify(op)));
5666
5959
  if (!res.success) return false;
5667
5960
  this.reloadSection();
5668
5961
  return true;
@@ -5674,7 +5967,7 @@ var Docxodus = (() => {
5674
5967
  /** Remove this section's page-numbering start/format. */
5675
5968
  clearPageNumbering() {
5676
5969
  if (!this.bodyAnchorId) return false;
5677
- const res = parseResult(this.bridge.ClearPageNumbering(this.handle, this.bodyAnchorId));
5970
+ const res = parseResult2(this.bridge.ClearPageNumbering(this.handle, this.bodyAnchorId));
5678
5971
  if (!res.success) return false;
5679
5972
  this.reloadSection();
5680
5973
  return true;
@@ -5985,7 +6278,7 @@ var Docxodus = (() => {
5985
6278
  seedStory(which, kind) {
5986
6279
  if (!this.bodyAnchorId) return;
5987
6280
  const set = which === "header" ? this.bridge.SetHeaderText : this.bridge.SetFooterText;
5988
- const res = parseResult(set(this.handle, this.bodyAnchorId, kind, ""));
6281
+ const res = parseResult2(set(this.handle, this.bodyAnchorId, kind, ""));
5989
6282
  if (!res.success) return;
5990
6283
  this.callbacks.refreshAnchorMap();
5991
6284
  this.sectionInfo = this.readSectionInfo(this.bodyAnchorId);
@@ -6141,7 +6434,7 @@ var Docxodus = (() => {
6141
6434
  const range = d.caretRangeFromPoint?.(x, y);
6142
6435
  return range ? { node: range.startContainer, offset: range.startOffset } : null;
6143
6436
  }
6144
- function parseResult(json) {
6437
+ function parseResult2(json) {
6145
6438
  try {
6146
6439
  return JSON.parse(json);
6147
6440
  } catch {
@@ -8374,7 +8667,7 @@ span.comment-highlight.docx-comment-active { background: hsl(var(--docx-comment-
8374
8667
  };
8375
8668
  }
8376
8669
  var honeyPotFix = makeHoneyPotFix();
8377
- var adapter = makeAdapter({
8670
+ var adapter2 = makeAdapter({
8378
8671
  typeKey: "element",
8379
8672
  defaultDropEffect: "move",
8380
8673
  mount: function mount(api2) {
@@ -8463,8 +8756,8 @@ span.comment-highlight.docx-comment-active { background: hsl(var(--docx-comment-
8463
8756
  },
8464
8757
  onPostDispatch: honeyPotFix.getOnPostDispatch()
8465
8758
  });
8466
- var dropTargetForElements = adapter.dropTarget;
8467
- var monitorForElements = adapter.monitor;
8759
+ var dropTargetForElements = adapter2.dropTarget;
8760
+ var monitorForElements = adapter2.monitor;
8468
8761
  function draggable(args) {
8469
8762
  if (true) {
8470
8763
  if (args.dragHandle && !args.element.contains(args.dragHandle)) {
@@ -8488,7 +8781,7 @@ span.comment-highlight.docx-comment-active { background: hsl(var(--docx-comment-
8488
8781
  // this is because you *must* have a draggable element to start a drag
8489
8782
  // but you _might_ not have any drop targets immediately
8490
8783
  // (You might create drop targets async)
8491
- adapter.registerUsage(),
8784
+ adapter2.registerUsage(),
8492
8785
  addToRegistry(args),
8493
8786
  addAttribute(args.element, {
8494
8787
  attribute: "draggable",
@@ -9935,22 +10228,55 @@ span.comment-highlight.docx-comment-active { background: hsl(var(--docx-comment-
9935
10228
  const span = trimmedSpan(block, { start: Math.min(start2, end), length: Math.abs(end - start2) });
9936
10229
  return span.length > 0 ? span : null;
9937
10230
  }
9938
- function selectRange(el, start2, length) {
9939
- const sel = typeof window !== "undefined" ? window.getSelection() : null;
9940
- if (!sel || !el.isConnected) return;
9941
- el.focus();
9942
- const range = document.createRange();
10231
+ function contentRangeIn(el, start2, length) {
10232
+ if (!el.isConnected) return null;
10233
+ const doc = el.ownerDocument;
10234
+ const range = doc.createRange();
9943
10235
  const from = contentPositionIn(el, start2);
9944
10236
  const to = contentPositionIn(el, start2 + length);
9945
10237
  try {
9946
10238
  range.setStart(from.node, from.offset);
9947
10239
  range.setEnd(to.node, to.offset);
9948
10240
  } catch {
9949
- return;
10241
+ return null;
9950
10242
  }
10243
+ return range;
10244
+ }
10245
+ function selectRange(el, start2, length) {
10246
+ const sel = typeof window !== "undefined" ? window.getSelection() : null;
10247
+ if (!sel || !el.isConnected) return;
10248
+ el.focus();
10249
+ const range = contentRangeIn(el, start2, length);
10250
+ if (!range) return;
9951
10251
  sel.removeAllRanges();
9952
10252
  sel.addRange(range);
9953
10253
  }
10254
+ var FIND_HIGHLIGHT = "docxodus-find";
10255
+ var FIND_HIGHLIGHT_ACTIVE = "docxodus-find-active";
10256
+ var FIND_PAINT_LIMIT = 500;
10257
+ var findHighlightStyledDocuments = /* @__PURE__ */ new WeakSet();
10258
+ var findPaintOwner = null;
10259
+ function ensureFindHighlightStyles(doc) {
10260
+ if (findHighlightStyledDocuments.has(doc)) return;
10261
+ findHighlightStyledDocuments.add(doc);
10262
+ const style = doc.createElement("style");
10263
+ style.dataset.docxodusFindHighlight = "true";
10264
+ style.textContent = `
10265
+ ::highlight(${FIND_HIGHLIGHT}) { background-color: rgba(15, 118, 110, .16); }
10266
+ ::highlight(${FIND_HIGHLIGHT_ACTIVE}) { background-color: rgba(15, 118, 110, .38); }
10267
+ `;
10268
+ (doc.head ?? doc.documentElement).appendChild(style);
10269
+ }
10270
+ function highlightRegistry() {
10271
+ if (typeof CSS === "undefined") return null;
10272
+ const registry = CSS.highlights;
10273
+ const ctor = globalThis.Highlight;
10274
+ return registry && typeof ctor === "function" ? registry : null;
10275
+ }
10276
+ function makeHighlight(ranges) {
10277
+ const ctor = globalThis.Highlight;
10278
+ return new ctor(...ranges);
10279
+ }
9954
10280
  function inBorderWrapper(el) {
9955
10281
  const style = el.parentElement?.getAttribute("style") ?? "";
9956
10282
  return /border-(top|bottom|left|right):\s*(?!none)[^;]+/i.test(style);
@@ -10321,6 +10647,7 @@ span.comment-highlight.docx-comment-active { background: hsl(var(--docx-comment-
10321
10647
  document.removeEventListener("mouseup", this.onMouseUp, true);
10322
10648
  }
10323
10649
  this.clearDragSelection();
10650
+ this.clearFindMatches();
10324
10651
  this.teardownBlockDrag();
10325
10652
  this.gutter?.dispose();
10326
10653
  this.gutter = null;
@@ -12719,15 +13046,65 @@ span.comment-highlight.docx-comment-active { background: hsl(var(--docx-comment-
12719
13046
  }
12720
13047
  return out;
12721
13048
  }
12722
- /** Select a match and scroll it into view. */
13049
+ /**
13050
+ * Select a match, focus its block and scroll it into view — the caret lands on the hit, so
13051
+ * typing continues in the document. A find box that is still being typed into wants
13052
+ * `showFindMatches` instead; this is the commit step (closing the bar, jumping in to edit).
13053
+ */
12723
13054
  selectMatch(match) {
12724
13055
  if (!match.block.isConnected) return;
13056
+ this.clearFindMatches();
12725
13057
  this.activeBlock = match.block;
12726
13058
  selectRange(match.block, match.start, match.length);
12727
13059
  match.block.scrollIntoView({ block: "center", behavior: "smooth" });
12728
13060
  }
12729
- /** Replace one match's text (formatting of the surrounding run is inherited). */
12730
- replaceMatch(match, replacement) {
13061
+ /**
13062
+ * Paint `matches` and scroll the one at `activeIndex` into view WITHOUT moving focus or the
13063
+ * caret. This is what a find field calls on every keystroke: the document shows where the hits
13064
+ * are and rides to the current one, while the keyboard stays in the search box.
13065
+ *
13066
+ * Falls back to the document selection where the CSS Custom Highlight API is missing — still
13067
+ * without focusing the block, so the next character typed goes to the search field either way.
13068
+ */
13069
+ showFindMatches(matches, activeIndex) {
13070
+ const active = matches[activeIndex];
13071
+ this.clearFindMatches();
13072
+ if (!active || !active.block.isConnected) return;
13073
+ this.activeBlock = active.block;
13074
+ const activeRange = contentRangeIn(active.block, active.start, active.length);
13075
+ const registry = highlightRegistry();
13076
+ if (registry && activeRange) {
13077
+ ensureFindHighlightStyles(this.container.ownerDocument);
13078
+ const rest = [];
13079
+ for (let i = 0; i < matches.length && rest.length < FIND_PAINT_LIMIT; i++) {
13080
+ if (i === activeIndex) continue;
13081
+ const range = contentRangeIn(matches[i].block, matches[i].start, matches[i].length);
13082
+ if (range) rest.push(range);
13083
+ }
13084
+ if (rest.length > 0) registry.set(FIND_HIGHLIGHT, makeHighlight(rest));
13085
+ registry.set(FIND_HIGHLIGHT_ACTIVE, makeHighlight([activeRange]));
13086
+ findPaintOwner = this;
13087
+ } else if (activeRange) {
13088
+ const sel = this.container.ownerDocument.defaultView?.getSelection();
13089
+ sel?.removeAllRanges();
13090
+ sel?.addRange(activeRange);
13091
+ }
13092
+ active.block.scrollIntoView({ block: "center", behavior: "smooth" });
13093
+ }
13094
+ /** Drop the find painting (closing the find bar, or committing a match to the caret). */
13095
+ clearFindMatches() {
13096
+ if (findPaintOwner !== this) return;
13097
+ const registry = highlightRegistry();
13098
+ registry?.delete(FIND_HIGHLIGHT);
13099
+ registry?.delete(FIND_HIGHLIGHT_ACTIVE);
13100
+ findPaintOwner = null;
13101
+ }
13102
+ /**
13103
+ * Replace one match's text (formatting of the surrounding run is inherited). `focus: false`
13104
+ * leaves the keyboard where it is — a Replace button pressed from the find bar must not drag
13105
+ * the caret into the document mid-search.
13106
+ */
13107
+ replaceMatch(match, replacement, options = {}) {
12731
13108
  const block = match.block;
12732
13109
  if (this.closed || !block.isConnected) return false;
12733
13110
  const unid = block.getAttribute("data-anchor");
@@ -12748,7 +13125,7 @@ span.comment-highlight.docx-comment-active { background: hsl(var(--docx-comment-
12748
13125
  );
12749
13126
  if (!res.success) return false;
12750
13127
  const fresh = this.swapBlock(block, unid, res.modified?.[0]);
12751
- if (fresh) selectRange(fresh, span.start, replacement.length);
13128
+ if (fresh && options.focus !== false) selectRange(fresh, span.start, replacement.length);
12752
13129
  return true;
12753
13130
  }
12754
13131
  /** Replace every occurrence; returns how many were replaced. */
@@ -12767,7 +13144,11 @@ span.comment-highlight.docx-comment-active { background: hsl(var(--docx-comment-
12767
13144
  for (const m of list.slice().sort((a, b) => b.start - a.start)) {
12768
13145
  if (!current.isConnected) break;
12769
13146
  const before = current;
12770
- if (this.replaceMatch({ block: current, start: m.start, length: m.length }, replacement)) {
13147
+ if (this.replaceMatch(
13148
+ { block: current, start: m.start, length: m.length },
13149
+ replacement,
13150
+ { focus: options.focus }
13151
+ )) {
12771
13152
  count++;
12772
13153
  current = this.activeBlock && this.activeBlock !== before ? this.activeBlock : current;
12773
13154
  }
@@ -13449,7 +13830,7 @@ span.comment-highlight.docx-comment-active { background: hsl(var(--docx-comment-
13449
13830
  }
13450
13831
 
13451
13832
  // src/ribbon-chrome.ts
13452
- var RIBBON_STYLE_VERSION = "10";
13833
+ var RIBBON_STYLE_VERSION = "11";
13453
13834
  var RIBBON_STYLE_ATTR = "data-docxodus-ribbon-styles";
13454
13835
  var COMMENT_GUTTER_WIDTH = 264;
13455
13836
  var RIBBON_CSS = `
@@ -13882,9 +14263,11 @@ span.comment-highlight.docx-comment-active { background: hsl(var(--docx-comment-
13882
14263
  background: linear-gradient(to top, var(--dxr-desk), transparent);
13883
14264
  }
13884
14265
  .dxr[data-chrome] .dxr-surface { position: relative; margin: 26px auto; padding: 0 16px 96px; }
13885
- /* The comment gutter sits to the right of the sheet; the surface reserves its width so the
13886
- page shifts left rather than the bubbles covering it \u2014 Word's markup area. */
13887
- .dxr[data-chrome="full"] .dxr-surface[data-comments="on"] { padding-right: calc(var(--dxr-gutter) + 8px); }
14266
+ /* Reserve the markup area only while a visible comment or draft needs it. The gutter owns
14267
+ its empty/hidden state, so ordinary documents stay centered without disabling comments. */
14268
+ .dxr[data-chrome="full"] .dxr-surface[data-comments="on"]:has(> .docx-comment-gutter:not([data-empty]):not([hidden])) {
14269
+ padding-right: calc(var(--dxr-gutter) + 8px);
14270
+ }
13888
14271
  .dxr-surface [contenteditable="true"]:focus {
13889
14272
  outline: 2px solid var(--dxr-accent);
13890
14273
  outline-offset: 2px;
@@ -16035,11 +16418,18 @@ span.comment-highlight.docx-comment-active { background: hsl(var(--docx-comment-
16035
16418
  if (!this.live || !text.value) return;
16036
16419
  let count = 0;
16037
16420
  this.run("replace all", () => {
16038
- count = this.live.replaceAll(text.value, replace.value, { matchCase: matchCase.checked });
16421
+ count = this.live.replaceAll(text.value, replace.value, {
16422
+ matchCase: matchCase.checked,
16423
+ focus: false
16424
+ });
16039
16425
  });
16040
16426
  this.setStatus(`Replaced ${count} occurrence${count === 1 ? "" : "s"}`);
16041
16427
  this.refreshFind(true);
16042
16428
  });
16429
+ for (const id of ["findprev", "findnext", "replaceone", "replaceall"]) {
16430
+ const button = this.control(id);
16431
+ if (button) this.keepSelection(button);
16432
+ }
16043
16433
  bar.hidden = true;
16044
16434
  }
16045
16435
  openFindBar(withReplace) {
@@ -16050,28 +16440,44 @@ span.comment-highlight.docx-comment-active { background: hsl(var(--docx-comment-
16050
16440
  const text = this.require("findtext");
16051
16441
  text.focus();
16052
16442
  text.select();
16053
- this.refreshFind(false);
16443
+ this.refreshFind(true);
16054
16444
  }
16445
+ /**
16446
+ * Close the bar and hand the current hit over to the caret: the document is where the user is
16447
+ * going next, and Word leaves the insertion point on the match it stopped at. Until this runs
16448
+ * the match is only PAINTED (see `showFindMatches`), so the search field keeps the keyboard for
16449
+ * as long as the bar is open.
16450
+ */
16055
16451
  closeFindBar() {
16056
16452
  const bar = this.control("findbar");
16057
16453
  if (bar) bar.hidden = true;
16454
+ const match = this.findMatches[this.findIndex];
16455
+ if (match) this.live?.selectMatch(match);
16456
+ else this.live?.clearFindMatches();
16058
16457
  this.findMatches = [];
16059
16458
  this.findIndex = -1;
16060
16459
  }
16061
- refreshFind(select) {
16460
+ /**
16461
+ * Re-scan for the current query. `show` paints the hits and rides to the first one — it never
16462
+ * focuses the document, so typing the second character of a query that already matched goes on
16463
+ * refining the search instead of being typed into the document.
16464
+ */
16465
+ refreshFind(show) {
16062
16466
  const text = this.require("findtext");
16063
16467
  const matchCase = this.require("findcase").checked;
16064
16468
  this.findMatches = this.live && text.value ? this.live.find(text.value, { matchCase }) : [];
16065
16469
  this.findIndex = this.findMatches.length > 0 ? 0 : -1;
16066
16470
  this.updateFindCount();
16067
- if (select && this.findIndex >= 0) this.live?.selectMatch(this.findMatches[this.findIndex]);
16471
+ if (!show) return;
16472
+ if (this.findIndex >= 0) this.live?.showFindMatches(this.findMatches, this.findIndex);
16473
+ else this.live?.clearFindMatches();
16068
16474
  }
16069
16475
  stepFind(direction) {
16070
16476
  if (!this.live) return;
16071
16477
  if (this.findMatches.length === 0) this.refreshFind(false);
16072
16478
  if (this.findMatches.length === 0) return;
16073
16479
  this.findIndex = (this.findIndex + direction + this.findMatches.length) % this.findMatches.length;
16074
- this.live.selectMatch(this.findMatches[this.findIndex]);
16480
+ this.live.showFindMatches(this.findMatches, this.findIndex);
16075
16481
  this.updateFindCount();
16076
16482
  }
16077
16483
  replaceCurrent() {
@@ -16080,12 +16486,12 @@ span.comment-highlight.docx-comment-active { background: hsl(var(--docx-comment-
16080
16486
  const match = this.findMatches[this.findIndex];
16081
16487
  if (!match) return;
16082
16488
  const replacement = this.require("replacetext").value;
16083
- this.run("replace", () => this.live.replaceMatch(match, replacement));
16489
+ this.run("replace", () => this.live.replaceMatch(match, replacement, { focus: false }));
16084
16490
  const keep = this.findIndex;
16085
16491
  this.refreshFind(false);
16086
16492
  if (this.findMatches.length > 0) {
16087
16493
  this.findIndex = Math.min(keep, this.findMatches.length - 1);
16088
- this.live.selectMatch(this.findMatches[this.findIndex]);
16494
+ this.live.showFindMatches(this.findMatches, this.findIndex);
16089
16495
  this.updateFindCount();
16090
16496
  }
16091
16497
  }
@@ -16517,6 +16923,16 @@ span.comment-highlight.docx-comment-active { background: hsl(var(--docx-comment-
16517
16923
 
16518
16924
  // src/index.ts
16519
16925
  var import_meta = {};
16926
+ function openDocxHistory(storage) {
16927
+ const bridge = ensureInitialized().HistoryBridge;
16928
+ if (!bridge) throw new Error("This WASM build does not include history bindings.");
16929
+ return new DocxHistoryClient(bridge, storage);
16930
+ }
16931
+ function openDocxHistoryArchive(bytes) {
16932
+ const bridge = ensureInitialized().HistoryBridge;
16933
+ if (!bridge) throw new Error("This WASM build does not include history bindings.");
16934
+ return DocxHistoryArchive.open(bridge, bytes);
16935
+ }
16520
16936
  function openDocxSession2(bytes, settings) {
16521
16937
  const wasm = ensureInitialized();
16522
16938
  return openDocxSession(bytes, wasm, settings);
@@ -16571,14 +16987,16 @@ span.comment-highlight.docx-comment-active { background: hsl(var(--docx-comment-
16571
16987
  /* @vite-ignore */
16572
16988
  dotnetPath
16573
16989
  );
16574
- const { getAssemblyExports, getConfig } = await dotnet.withDiagnosticTracing(false).create();
16990
+ const { getAssemblyExports, getConfig, setModuleImports } = await dotnet.withDiagnosticTracing(false).create();
16991
+ installHistoryStorageImports(setModuleImports);
16575
16992
  const config = getConfig();
16576
16993
  const exports = await getAssemblyExports(config.mainAssemblyName);
16577
16994
  wasmExports = {
16578
16995
  DocumentConverter: exports.DocxodusWasm.DocumentConverter,
16579
16996
  DocumentComparer: exports.DocxodusWasm.DocumentComparer,
16580
16997
  DocxDiffBridge: exports.DocxodusWasm.DocxDiffBridge,
16581
- DocxSessionBridge: exports.DocxodusWasm.DocxSessionBridge
16998
+ DocxSessionBridge: exports.DocxodusWasm.DocxSessionBridge,
16999
+ HistoryBridge: exports.DocxodusWasm.HistoryBridge
16582
17000
  };
16583
17001
  return true;
16584
17002
  } catch {
@@ -17953,6 +18371,20 @@ ${parsed.documentElement.outerHTML}`;
17953
18371
  }
17954
18372
  throw new Error("Docxodus embed: unsupported document source");
17955
18373
  }
18374
+ function scopedDocumentFragment(mount2, html) {
18375
+ const parsed = new DOMParser().parseFromString(html, "text/html");
18376
+ const fragment = document.createDocumentFragment();
18377
+ parsed.querySelectorAll("style").forEach((sourceStyle) => {
18378
+ const style = document.createElement("style");
18379
+ style.textContent = sourceStyle.textContent;
18380
+ scopeStyleElement(style, mount2.selector);
18381
+ fragment.appendChild(style);
18382
+ });
18383
+ const body = document.createElement("template");
18384
+ body.innerHTML = parsed.body.innerHTML;
18385
+ fragment.appendChild(body.content);
18386
+ return fragment;
18387
+ }
17956
18388
  async function createViewer(container, source, options = {}) {
17957
18389
  const el = resolveContainer2(container);
17958
18390
  const { wasmBasePath: wasmBasePath2, ...conversion } = options;
@@ -17965,18 +18397,7 @@ ${parsed.documentElement.outerHTML}`;
17965
18397
  renderFootnotesAndEndnotes: true,
17966
18398
  ...opts
17967
18399
  });
17968
- const parsed = new DOMParser().parseFromString(lastHtml, "text/html");
17969
- const fragment = document.createDocumentFragment();
17970
- parsed.querySelectorAll("style").forEach((sourceStyle) => {
17971
- const style = document.createElement("style");
17972
- style.textContent = sourceStyle.textContent;
17973
- scopeStyleElement(style, mount2.selector);
17974
- fragment.appendChild(style);
17975
- });
17976
- const body = document.createElement("template");
17977
- body.innerHTML = parsed.body.innerHTML;
17978
- fragment.appendChild(body.content);
17979
- mount2.root.replaceChildren(fragment);
18400
+ mount2.root.replaceChildren(scopedDocumentFragment(mount2, lastHtml));
17980
18401
  };
17981
18402
  await render(source, conversion);
17982
18403
  return {
@@ -17990,6 +18411,111 @@ ${parsed.documentElement.outerHTML}`;
17990
18411
  }
17991
18412
  };
17992
18413
  }
18414
+ async function createHistoryViewer(container, history, documentId, options = {}) {
18415
+ const el = resolveContainer2(container);
18416
+ const { wasmBasePath: wasmBasePath2, maxEntriesToScan = 1e4, ...conversion } = options;
18417
+ if (!Number.isSafeInteger(maxEntriesToScan) || maxEntriesToScan <= 0 || maxEntriesToScan > 2147483647)
18418
+ throw new RangeError("Invalid history scan budget.");
18419
+ await ensureWasm(wasmBasePath2);
18420
+ const mount2 = createScopedMount(el);
18421
+ let accepted = null;
18422
+ let acceptedBytes = null;
18423
+ let displayedBytes = null;
18424
+ let shownSequence = "0";
18425
+ let following = true;
18426
+ let closed = false;
18427
+ let html = "";
18428
+ let queue = Promise.resolve();
18429
+ const clone = (value) => JSON.parse(JSON.stringify(value));
18430
+ const assertOpen = () => {
18431
+ if (closed) throw new DocxHistoryError("Closed", "History viewer is destroyed.");
18432
+ };
18433
+ const enqueue = (action) => {
18434
+ const result = queue.then(() => {
18435
+ assertOpen();
18436
+ return action();
18437
+ });
18438
+ queue = result.then(() => void 0, () => void 0);
18439
+ return result;
18440
+ };
18441
+ const sameHead = (a, b) => a.revision === b.revision && a.state.length === b.state.length && a.state.digest.algorithm === b.state.digest.algorithm && a.state.digest.value === b.state.digest.value;
18442
+ const paint = async (bytes, sequence) => {
18443
+ const nextHtml = await convertDocxToHtml(bytes, { renderFootnotesAndEndnotes: true, ...conversion });
18444
+ assertOpen();
18445
+ const fragment = scopedDocumentFragment(mount2, nextHtml);
18446
+ mount2.root.replaceChildren(fragment);
18447
+ html = nextHtml;
18448
+ displayedBytes = bytes;
18449
+ shownSequence = sequence;
18450
+ };
18451
+ const refresh = async (resume = false) => {
18452
+ const update = await history.readChangesSince(documentId, accepted?.view.head ?? null, maxEntriesToScan);
18453
+ assertOpen();
18454
+ const duplicate = accepted !== null && sameHead(accepted.view.head, update.view.head);
18455
+ const bytes = duplicate ? acceptedBytes : await history.exportVersion(documentId, update.view.version.id);
18456
+ assertOpen();
18457
+ const wantLive = following || resume;
18458
+ const changedContent = accepted === null || accepted.view.state.snapshot.contentDigest.value !== update.view.state.snapshot.contentDigest.value || accepted.view.state.epoch !== update.view.state.epoch;
18459
+ if (wantLive && (changedContent || !following)) await paint(bytes, update.view.state.sequence);
18460
+ else if (wantLive) {
18461
+ displayedBytes = bytes;
18462
+ shownSequence = update.view.state.sequence;
18463
+ }
18464
+ assertOpen();
18465
+ accepted = update;
18466
+ acceptedBytes = bytes;
18467
+ if (wantLive) following = true;
18468
+ return clone(update);
18469
+ };
18470
+ const showSequence = async (sequence) => {
18471
+ const bytes = await history.materialize(documentId, sequence, maxEntriesToScan);
18472
+ assertOpen();
18473
+ await paint(bytes, sequence);
18474
+ following = false;
18475
+ };
18476
+ const viewer = {
18477
+ element: el,
18478
+ get html() {
18479
+ return html;
18480
+ },
18481
+ get head() {
18482
+ assertOpen();
18483
+ return clone(accepted.view.head);
18484
+ },
18485
+ get sequence() {
18486
+ return shownSequence;
18487
+ },
18488
+ get following() {
18489
+ return following;
18490
+ },
18491
+ refresh: () => enqueue(() => refresh()),
18492
+ showSequence: (sequence) => enqueue(() => showSequence(sequence)),
18493
+ showTime: (cutoff) => enqueue(async () => {
18494
+ const sequence = await history.resolveSequenceAtTime(documentId, cutoff, maxEntriesToScan);
18495
+ assertOpen();
18496
+ await showSequence(sequence);
18497
+ }),
18498
+ resume: () => enqueue(() => refresh(true)),
18499
+ exportDisplayed: () => {
18500
+ assertOpen();
18501
+ return displayedBytes.slice();
18502
+ },
18503
+ destroy: () => {
18504
+ if (closed) return;
18505
+ closed = true;
18506
+ acceptedBytes = null;
18507
+ displayedBytes = null;
18508
+ mount2.root.remove();
18509
+ }
18510
+ };
18511
+ try {
18512
+ await viewer.refresh();
18513
+ return viewer;
18514
+ } catch (error) {
18515
+ viewer.destroy();
18516
+ throw error;
18517
+ }
18518
+ }
17993
18519
  async function createEditor(container, source, options = {}) {
17994
18520
  const el = resolveContainer2(container);
17995
18521
  const { wasmBasePath: wasmBasePath2, ...editorOptions } = options;