docxodus 12.0.1 → 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.
- package/README.md +9 -4
- package/dist/editor.bundle.js +197 -25
- package/dist/editor.d.ts +42 -3
- package/dist/editor.d.ts.map +1 -1
- package/dist/editor.js +204 -16
- package/dist/editor.js.map +1 -1
- package/dist/embed.bundle.js +635 -50
- package/dist/embed.d.ts +30 -1
- package/dist/embed.d.ts.map +1 -1
- package/dist/embed.iife.js +635 -50
- package/dist/embed.js +124 -13
- package/dist/embed.js.map +1 -1
- package/dist/export-assets.json +33 -33
- package/dist/history.d.ts +265 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +307 -0
- package/dist/history.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -2
- package/dist/index.js.map +1 -1
- package/dist/ribbon-chrome.d.ts +2 -2
- package/dist/ribbon-chrome.d.ts.map +1 -1
- package/dist/ribbon-chrome.js +6 -4
- package/dist/ribbon-chrome.js.map +1 -1
- package/dist/ribbon.js +39 -8
- package/dist/ribbon.js.map +1 -1
- package/dist/session.js +1 -1
- package/dist/session.js.map +1 -1
- package/dist/types.d.ts +2 -0
- 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.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.Expressions.wasm +0 -0
- package/dist/wasm/_framework/System.Linq.Expressions.wasm.br +0 -0
- package/dist/wasm/_framework/System.Memory.wasm +0 -0
- package/dist/wasm/_framework/System.Memory.wasm.br +0 -0
- package/dist/wasm/_framework/System.Net.Http.wasm +0 -0
- package/dist/wasm/_framework/System.Net.Http.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.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/package.json +4 -3
package/dist/embed.iife.js
CHANGED
|
@@ -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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
8467
|
-
var monitorForElements =
|
|
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
|
-
|
|
8784
|
+
adapter2.registerUsage(),
|
|
8492
8785
|
addToRegistry(args),
|
|
8493
8786
|
addAttribute(args.element, {
|
|
8494
8787
|
attribute: "draggable",
|
|
@@ -9571,6 +9864,12 @@ span.comment-highlight.docx-comment-active { background: hsl(var(--docx-comment-
|
|
|
9571
9864
|
const text = (unit.textContent ?? "").trim().replace(/\s+/g, " ");
|
|
9572
9865
|
return text.length > 48 ? `${text.slice(0, 48)}\u2026` : text;
|
|
9573
9866
|
}
|
|
9867
|
+
var BLOCK_HANDLE_WIDTH = 26;
|
|
9868
|
+
var BLOCK_HANDLE_HEIGHT = 28;
|
|
9869
|
+
var BLOCK_HANDLE_GAP = 6;
|
|
9870
|
+
function clampToRange(value, low, high) {
|
|
9871
|
+
return Math.max(low, Math.min(value, high));
|
|
9872
|
+
}
|
|
9574
9873
|
function ensureBlockDragStyles(doc) {
|
|
9575
9874
|
if (blockDragStyledDocuments.has(doc)) return;
|
|
9576
9875
|
blockDragStyledDocuments.add(doc);
|
|
@@ -9929,22 +10228,55 @@ span.comment-highlight.docx-comment-active { background: hsl(var(--docx-comment-
|
|
|
9929
10228
|
const span = trimmedSpan(block, { start: Math.min(start2, end), length: Math.abs(end - start2) });
|
|
9930
10229
|
return span.length > 0 ? span : null;
|
|
9931
10230
|
}
|
|
9932
|
-
function
|
|
9933
|
-
|
|
9934
|
-
|
|
9935
|
-
|
|
9936
|
-
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();
|
|
9937
10235
|
const from = contentPositionIn(el, start2);
|
|
9938
10236
|
const to = contentPositionIn(el, start2 + length);
|
|
9939
10237
|
try {
|
|
9940
10238
|
range.setStart(from.node, from.offset);
|
|
9941
10239
|
range.setEnd(to.node, to.offset);
|
|
9942
10240
|
} catch {
|
|
9943
|
-
return;
|
|
10241
|
+
return null;
|
|
9944
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;
|
|
9945
10251
|
sel.removeAllRanges();
|
|
9946
10252
|
sel.addRange(range);
|
|
9947
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
|
+
}
|
|
9948
10280
|
function inBorderWrapper(el) {
|
|
9949
10281
|
const style = el.parentElement?.getAttribute("style") ?? "";
|
|
9950
10282
|
return /border-(top|bottom|left|right):\s*(?!none)[^;]+/i.test(style);
|
|
@@ -10033,6 +10365,10 @@ span.comment-highlight.docx-comment-active { background: hsl(var(--docx-comment-
|
|
|
10033
10365
|
this.blockMoveMenu = null;
|
|
10034
10366
|
this.blockMoveLive = null;
|
|
10035
10367
|
this.blockDragSource = null;
|
|
10368
|
+
/** Whether hover or focus currently wants a handle for `blockDragSource`.
|
|
10369
|
+
* Tracked apart from the handle's `display`, which the viewport clip also drives: a handle
|
|
10370
|
+
* withdrawn because its block scrolled out of view has to come back when it scrolls in. */
|
|
10371
|
+
this.blockHandleWanted = false;
|
|
10036
10372
|
this.blockDragCleanup = [];
|
|
10037
10373
|
/** Block boxes measured at drag start — see `BlockDropZone`. Empty when no drag is in flight. */
|
|
10038
10374
|
this.dropZones = [];
|
|
@@ -10311,6 +10647,7 @@ span.comment-highlight.docx-comment-active { background: hsl(var(--docx-comment-
|
|
|
10311
10647
|
document.removeEventListener("mouseup", this.onMouseUp, true);
|
|
10312
10648
|
}
|
|
10313
10649
|
this.clearDragSelection();
|
|
10650
|
+
this.clearFindMatches();
|
|
10314
10651
|
this.teardownBlockDrag();
|
|
10315
10652
|
this.gutter?.dispose();
|
|
10316
10653
|
this.gutter = null;
|
|
@@ -10429,11 +10766,47 @@ span.comment-highlight.docx-comment-active { background: hsl(var(--docx-comment-
|
|
|
10429
10766
|
}
|
|
10430
10767
|
return null;
|
|
10431
10768
|
}
|
|
10769
|
+
/**
|
|
10770
|
+
* The rectangle the floating handle may occupy: the window, narrowed by every ancestor that
|
|
10771
|
+
* clips its overflow.
|
|
10772
|
+
*
|
|
10773
|
+
* The handle is `position: fixed`, so it is placed in viewport coordinates and no ancestor's
|
|
10774
|
+
* overflow clips it for us. But the editor is routinely mounted inside a bounded scroller —
|
|
10775
|
+
* the ribbon puts its surface in one, inside a clipped card — and a block scrolled out of that
|
|
10776
|
+
* scroller has to take its handle with it rather than leave it parked over the host's chrome.
|
|
10777
|
+
*
|
|
10778
|
+
* Gated on the computed `overflow` rather than on whether an element scrolls right now, so the
|
|
10779
|
+
* answer does not depend on how much content happens to be loaded.
|
|
10780
|
+
*/
|
|
10781
|
+
blockHandleClipRect() {
|
|
10782
|
+
const view = this.container.ownerDocument.defaultView;
|
|
10783
|
+
const clip = {
|
|
10784
|
+
top: 0,
|
|
10785
|
+
left: 0,
|
|
10786
|
+
right: view?.innerWidth ?? Number.MAX_SAFE_INTEGER,
|
|
10787
|
+
bottom: view?.innerHeight ?? Number.MAX_SAFE_INTEGER
|
|
10788
|
+
};
|
|
10789
|
+
for (let el = this.editRoot; el; el = el.parentElement) {
|
|
10790
|
+
const style = view?.getComputedStyle(el);
|
|
10791
|
+
if (!style || style.overflowX === "visible" && style.overflowY === "visible") continue;
|
|
10792
|
+
const box = el.getBoundingClientRect();
|
|
10793
|
+
if (style.overflowY !== "visible") {
|
|
10794
|
+
clip.top = Math.max(clip.top, box.top);
|
|
10795
|
+
clip.bottom = Math.min(clip.bottom, box.bottom);
|
|
10796
|
+
}
|
|
10797
|
+
if (style.overflowX !== "visible") {
|
|
10798
|
+
clip.left = Math.max(clip.left, box.left);
|
|
10799
|
+
clip.right = Math.min(clip.right, box.right);
|
|
10800
|
+
}
|
|
10801
|
+
}
|
|
10802
|
+
return clip;
|
|
10803
|
+
}
|
|
10432
10804
|
showBlockHandle(unit) {
|
|
10433
10805
|
const handle = this.blockDragHandle;
|
|
10434
10806
|
if (!handle || !this.isMovableBlockUnit(unit)) return;
|
|
10435
10807
|
const changed = unit !== this.blockDragSource;
|
|
10436
10808
|
this.blockDragSource = unit;
|
|
10809
|
+
this.blockHandleWanted = true;
|
|
10437
10810
|
const known = this.blockMoveTargetsFor(unit, { cachedOnly: true });
|
|
10438
10811
|
if (known?.size === 0) {
|
|
10439
10812
|
handle.style.display = "none";
|
|
@@ -10441,19 +10814,31 @@ span.comment-highlight.docx-comment-active { background: hsl(var(--docx-comment-
|
|
|
10441
10814
|
}
|
|
10442
10815
|
if (changed && known === void 0) this.prefetchBlockMoveTargets(unit);
|
|
10443
10816
|
const rect = unit.getBoundingClientRect();
|
|
10817
|
+
const clip = this.blockHandleClipRect();
|
|
10818
|
+
if (rect.bottom <= clip.top || rect.top >= clip.bottom || rect.right <= clip.left || rect.left >= clip.right) {
|
|
10819
|
+
if (!this.blockDragging && this.blockMoveMenu?.style.display !== "block")
|
|
10820
|
+
handle.style.display = "none";
|
|
10821
|
+
return;
|
|
10822
|
+
}
|
|
10444
10823
|
handle.style.display = "flex";
|
|
10445
|
-
|
|
10446
|
-
handle.style.
|
|
10824
|
+
const top2 = rect.top + (unit.tagName === "TABLE" ? 6 : Math.max(0, (rect.height - BLOCK_HANDLE_HEIGHT) / 2));
|
|
10825
|
+
handle.style.left = `${clampToRange(
|
|
10826
|
+
rect.left - (BLOCK_HANDLE_WIDTH + BLOCK_HANDLE_GAP),
|
|
10827
|
+
clip.left,
|
|
10828
|
+
clip.right - BLOCK_HANDLE_WIDTH
|
|
10829
|
+
)}px`;
|
|
10830
|
+
handle.style.top = `${clampToRange(top2, clip.top, clip.bottom - BLOCK_HANDLE_HEIGHT)}px`;
|
|
10447
10831
|
const preview = blockPreviewText(unit);
|
|
10448
10832
|
handle.setAttribute("aria-label", preview ? `Move block: ${preview}` : "Move block");
|
|
10449
10833
|
}
|
|
10450
10834
|
hideBlockHandle() {
|
|
10451
10835
|
if (this.blockDragging || this.blockMoveMenu?.style.display === "block") return;
|
|
10836
|
+
this.blockHandleWanted = false;
|
|
10452
10837
|
if (this.blockDragHandle) this.blockDragHandle.style.display = "none";
|
|
10453
10838
|
}
|
|
10454
10839
|
positionBlockHandle() {
|
|
10455
10840
|
const source = this.currentBlockDragSource();
|
|
10456
|
-
if (source && this.
|
|
10841
|
+
if (source && this.blockHandleWanted) this.showBlockHandle(source);
|
|
10457
10842
|
}
|
|
10458
10843
|
/** Draw the drop line on `zone`'s requested edge, or take it away when there is no target. */
|
|
10459
10844
|
paintDropIndicator(data) {
|
|
@@ -10891,6 +11276,7 @@ span.comment-highlight.docx-comment-active { background: hsl(var(--docx-comment-
|
|
|
10891
11276
|
this.blockMoveMenu = null;
|
|
10892
11277
|
this.blockMoveLive = null;
|
|
10893
11278
|
this.blockDragSource = null;
|
|
11279
|
+
this.blockHandleWanted = false;
|
|
10894
11280
|
this.blockDragging = false;
|
|
10895
11281
|
this.blockDragPointerDown = false;
|
|
10896
11282
|
}
|
|
@@ -12660,15 +13046,65 @@ span.comment-highlight.docx-comment-active { background: hsl(var(--docx-comment-
|
|
|
12660
13046
|
}
|
|
12661
13047
|
return out;
|
|
12662
13048
|
}
|
|
12663
|
-
/**
|
|
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
|
+
*/
|
|
12664
13054
|
selectMatch(match) {
|
|
12665
13055
|
if (!match.block.isConnected) return;
|
|
13056
|
+
this.clearFindMatches();
|
|
12666
13057
|
this.activeBlock = match.block;
|
|
12667
13058
|
selectRange(match.block, match.start, match.length);
|
|
12668
13059
|
match.block.scrollIntoView({ block: "center", behavior: "smooth" });
|
|
12669
13060
|
}
|
|
12670
|
-
/**
|
|
12671
|
-
|
|
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 = {}) {
|
|
12672
13108
|
const block = match.block;
|
|
12673
13109
|
if (this.closed || !block.isConnected) return false;
|
|
12674
13110
|
const unid = block.getAttribute("data-anchor");
|
|
@@ -12689,7 +13125,7 @@ span.comment-highlight.docx-comment-active { background: hsl(var(--docx-comment-
|
|
|
12689
13125
|
);
|
|
12690
13126
|
if (!res.success) return false;
|
|
12691
13127
|
const fresh = this.swapBlock(block, unid, res.modified?.[0]);
|
|
12692
|
-
if (fresh) selectRange(fresh, span.start, replacement.length);
|
|
13128
|
+
if (fresh && options.focus !== false) selectRange(fresh, span.start, replacement.length);
|
|
12693
13129
|
return true;
|
|
12694
13130
|
}
|
|
12695
13131
|
/** Replace every occurrence; returns how many were replaced. */
|
|
@@ -12708,7 +13144,11 @@ span.comment-highlight.docx-comment-active { background: hsl(var(--docx-comment-
|
|
|
12708
13144
|
for (const m of list.slice().sort((a, b) => b.start - a.start)) {
|
|
12709
13145
|
if (!current.isConnected) break;
|
|
12710
13146
|
const before = current;
|
|
12711
|
-
if (this.replaceMatch(
|
|
13147
|
+
if (this.replaceMatch(
|
|
13148
|
+
{ block: current, start: m.start, length: m.length },
|
|
13149
|
+
replacement,
|
|
13150
|
+
{ focus: options.focus }
|
|
13151
|
+
)) {
|
|
12712
13152
|
count++;
|
|
12713
13153
|
current = this.activeBlock && this.activeBlock !== before ? this.activeBlock : current;
|
|
12714
13154
|
}
|
|
@@ -13390,7 +13830,7 @@ span.comment-highlight.docx-comment-active { background: hsl(var(--docx-comment-
|
|
|
13390
13830
|
}
|
|
13391
13831
|
|
|
13392
13832
|
// src/ribbon-chrome.ts
|
|
13393
|
-
var RIBBON_STYLE_VERSION = "
|
|
13833
|
+
var RIBBON_STYLE_VERSION = "11";
|
|
13394
13834
|
var RIBBON_STYLE_ATTR = "data-docxodus-ribbon-styles";
|
|
13395
13835
|
var COMMENT_GUTTER_WIDTH = 264;
|
|
13396
13836
|
var RIBBON_CSS = `
|
|
@@ -13823,9 +14263,11 @@ span.comment-highlight.docx-comment-active { background: hsl(var(--docx-comment-
|
|
|
13823
14263
|
background: linear-gradient(to top, var(--dxr-desk), transparent);
|
|
13824
14264
|
}
|
|
13825
14265
|
.dxr[data-chrome] .dxr-surface { position: relative; margin: 26px auto; padding: 0 16px 96px; }
|
|
13826
|
-
/*
|
|
13827
|
-
|
|
13828
|
-
.dxr[data-chrome="full"] .dxr-surface[data-comments="on"]
|
|
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
|
+
}
|
|
13829
14271
|
.dxr-surface [contenteditable="true"]:focus {
|
|
13830
14272
|
outline: 2px solid var(--dxr-accent);
|
|
13831
14273
|
outline-offset: 2px;
|
|
@@ -15976,11 +16418,18 @@ span.comment-highlight.docx-comment-active { background: hsl(var(--docx-comment-
|
|
|
15976
16418
|
if (!this.live || !text.value) return;
|
|
15977
16419
|
let count = 0;
|
|
15978
16420
|
this.run("replace all", () => {
|
|
15979
|
-
count = this.live.replaceAll(text.value, replace.value, {
|
|
16421
|
+
count = this.live.replaceAll(text.value, replace.value, {
|
|
16422
|
+
matchCase: matchCase.checked,
|
|
16423
|
+
focus: false
|
|
16424
|
+
});
|
|
15980
16425
|
});
|
|
15981
16426
|
this.setStatus(`Replaced ${count} occurrence${count === 1 ? "" : "s"}`);
|
|
15982
16427
|
this.refreshFind(true);
|
|
15983
16428
|
});
|
|
16429
|
+
for (const id of ["findprev", "findnext", "replaceone", "replaceall"]) {
|
|
16430
|
+
const button = this.control(id);
|
|
16431
|
+
if (button) this.keepSelection(button);
|
|
16432
|
+
}
|
|
15984
16433
|
bar.hidden = true;
|
|
15985
16434
|
}
|
|
15986
16435
|
openFindBar(withReplace) {
|
|
@@ -15991,28 +16440,44 @@ span.comment-highlight.docx-comment-active { background: hsl(var(--docx-comment-
|
|
|
15991
16440
|
const text = this.require("findtext");
|
|
15992
16441
|
text.focus();
|
|
15993
16442
|
text.select();
|
|
15994
|
-
this.refreshFind(
|
|
16443
|
+
this.refreshFind(true);
|
|
15995
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
|
+
*/
|
|
15996
16451
|
closeFindBar() {
|
|
15997
16452
|
const bar = this.control("findbar");
|
|
15998
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();
|
|
15999
16457
|
this.findMatches = [];
|
|
16000
16458
|
this.findIndex = -1;
|
|
16001
16459
|
}
|
|
16002
|
-
|
|
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) {
|
|
16003
16466
|
const text = this.require("findtext");
|
|
16004
16467
|
const matchCase = this.require("findcase").checked;
|
|
16005
16468
|
this.findMatches = this.live && text.value ? this.live.find(text.value, { matchCase }) : [];
|
|
16006
16469
|
this.findIndex = this.findMatches.length > 0 ? 0 : -1;
|
|
16007
16470
|
this.updateFindCount();
|
|
16008
|
-
if (
|
|
16471
|
+
if (!show) return;
|
|
16472
|
+
if (this.findIndex >= 0) this.live?.showFindMatches(this.findMatches, this.findIndex);
|
|
16473
|
+
else this.live?.clearFindMatches();
|
|
16009
16474
|
}
|
|
16010
16475
|
stepFind(direction) {
|
|
16011
16476
|
if (!this.live) return;
|
|
16012
16477
|
if (this.findMatches.length === 0) this.refreshFind(false);
|
|
16013
16478
|
if (this.findMatches.length === 0) return;
|
|
16014
16479
|
this.findIndex = (this.findIndex + direction + this.findMatches.length) % this.findMatches.length;
|
|
16015
|
-
this.live.
|
|
16480
|
+
this.live.showFindMatches(this.findMatches, this.findIndex);
|
|
16016
16481
|
this.updateFindCount();
|
|
16017
16482
|
}
|
|
16018
16483
|
replaceCurrent() {
|
|
@@ -16021,12 +16486,12 @@ span.comment-highlight.docx-comment-active { background: hsl(var(--docx-comment-
|
|
|
16021
16486
|
const match = this.findMatches[this.findIndex];
|
|
16022
16487
|
if (!match) return;
|
|
16023
16488
|
const replacement = this.require("replacetext").value;
|
|
16024
|
-
this.run("replace", () => this.live.replaceMatch(match, replacement));
|
|
16489
|
+
this.run("replace", () => this.live.replaceMatch(match, replacement, { focus: false }));
|
|
16025
16490
|
const keep = this.findIndex;
|
|
16026
16491
|
this.refreshFind(false);
|
|
16027
16492
|
if (this.findMatches.length > 0) {
|
|
16028
16493
|
this.findIndex = Math.min(keep, this.findMatches.length - 1);
|
|
16029
|
-
this.live.
|
|
16494
|
+
this.live.showFindMatches(this.findMatches, this.findIndex);
|
|
16030
16495
|
this.updateFindCount();
|
|
16031
16496
|
}
|
|
16032
16497
|
}
|
|
@@ -16458,6 +16923,16 @@ span.comment-highlight.docx-comment-active { background: hsl(var(--docx-comment-
|
|
|
16458
16923
|
|
|
16459
16924
|
// src/index.ts
|
|
16460
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
|
+
}
|
|
16461
16936
|
function openDocxSession2(bytes, settings) {
|
|
16462
16937
|
const wasm = ensureInitialized();
|
|
16463
16938
|
return openDocxSession(bytes, wasm, settings);
|
|
@@ -16512,14 +16987,16 @@ span.comment-highlight.docx-comment-active { background: hsl(var(--docx-comment-
|
|
|
16512
16987
|
/* @vite-ignore */
|
|
16513
16988
|
dotnetPath
|
|
16514
16989
|
);
|
|
16515
|
-
const { getAssemblyExports, getConfig } = await dotnet.withDiagnosticTracing(false).create();
|
|
16990
|
+
const { getAssemblyExports, getConfig, setModuleImports } = await dotnet.withDiagnosticTracing(false).create();
|
|
16991
|
+
installHistoryStorageImports(setModuleImports);
|
|
16516
16992
|
const config = getConfig();
|
|
16517
16993
|
const exports = await getAssemblyExports(config.mainAssemblyName);
|
|
16518
16994
|
wasmExports = {
|
|
16519
16995
|
DocumentConverter: exports.DocxodusWasm.DocumentConverter,
|
|
16520
16996
|
DocumentComparer: exports.DocxodusWasm.DocumentComparer,
|
|
16521
16997
|
DocxDiffBridge: exports.DocxodusWasm.DocxDiffBridge,
|
|
16522
|
-
DocxSessionBridge: exports.DocxodusWasm.DocxSessionBridge
|
|
16998
|
+
DocxSessionBridge: exports.DocxodusWasm.DocxSessionBridge,
|
|
16999
|
+
HistoryBridge: exports.DocxodusWasm.HistoryBridge
|
|
16523
17000
|
};
|
|
16524
17001
|
return true;
|
|
16525
17002
|
} catch {
|
|
@@ -17894,6 +18371,20 @@ ${parsed.documentElement.outerHTML}`;
|
|
|
17894
18371
|
}
|
|
17895
18372
|
throw new Error("Docxodus embed: unsupported document source");
|
|
17896
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
|
+
}
|
|
17897
18388
|
async function createViewer(container, source, options = {}) {
|
|
17898
18389
|
const el = resolveContainer2(container);
|
|
17899
18390
|
const { wasmBasePath: wasmBasePath2, ...conversion } = options;
|
|
@@ -17906,18 +18397,7 @@ ${parsed.documentElement.outerHTML}`;
|
|
|
17906
18397
|
renderFootnotesAndEndnotes: true,
|
|
17907
18398
|
...opts
|
|
17908
18399
|
});
|
|
17909
|
-
|
|
17910
|
-
const fragment = document.createDocumentFragment();
|
|
17911
|
-
parsed.querySelectorAll("style").forEach((sourceStyle) => {
|
|
17912
|
-
const style = document.createElement("style");
|
|
17913
|
-
style.textContent = sourceStyle.textContent;
|
|
17914
|
-
scopeStyleElement(style, mount2.selector);
|
|
17915
|
-
fragment.appendChild(style);
|
|
17916
|
-
});
|
|
17917
|
-
const body = document.createElement("template");
|
|
17918
|
-
body.innerHTML = parsed.body.innerHTML;
|
|
17919
|
-
fragment.appendChild(body.content);
|
|
17920
|
-
mount2.root.replaceChildren(fragment);
|
|
18400
|
+
mount2.root.replaceChildren(scopedDocumentFragment(mount2, lastHtml));
|
|
17921
18401
|
};
|
|
17922
18402
|
await render(source, conversion);
|
|
17923
18403
|
return {
|
|
@@ -17931,6 +18411,111 @@ ${parsed.documentElement.outerHTML}`;
|
|
|
17931
18411
|
}
|
|
17932
18412
|
};
|
|
17933
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
|
+
}
|
|
17934
18519
|
async function createEditor(container, source, options = {}) {
|
|
17935
18520
|
const el = resolveContainer2(container);
|
|
17936
18521
|
const { wasmBasePath: wasmBasePath2, ...editorOptions } = options;
|