docxodus 12.1.0 → 12.3.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 +14 -4
- package/dist/editor.bundle.js +137 -23
- package/dist/editor.d.ts +25 -3
- package/dist/editor.d.ts.map +1 -1
- package/dist/editor.js +132 -13
- package/dist/editor.js.map +1 -1
- package/dist/embed.bundle.js +1163 -48
- package/dist/embed.d.ts +30 -1
- package/dist/embed.d.ts.map +1 -1
- package/dist/embed.iife.js +1163 -48
- package/dist/embed.js +124 -13
- package/dist/embed.js.map +1 -1
- package/dist/export-assets.json +33 -33
- package/dist/history-checkpoints.d.ts +48 -0
- package/dist/history-checkpoints.d.ts.map +1 -0
- package/dist/history-checkpoints.js +100 -0
- package/dist/history-checkpoints.js.map +1 -0
- package/dist/history-controls.d.ts +30 -0
- package/dist/history-controls.d.ts.map +1 -0
- package/dist/history-controls.js +333 -0
- package/dist/history-controls.js.map +1 -0
- package/dist/history-indexeddb.d.ts +12 -0
- package/dist/history-indexeddb.d.ts.map +1 -0
- package/dist/history-indexeddb.js +154 -0
- package/dist/history-indexeddb.js.map +1 -0
- 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 +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -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 +42 -9
- 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 +6 -3
package/dist/embed.bundle.js
CHANGED
|
@@ -2038,6 +2038,872 @@ function openDocxSession(bytes, wasmExports2, settings) {
|
|
|
2038
2038
|
return new DocxSession(handle, bridge);
|
|
2039
2039
|
}
|
|
2040
2040
|
|
|
2041
|
+
// src/history.ts
|
|
2042
|
+
var MAX_HISTORY_ARCHIVE_BYTES = 64 * 1024 * 1024;
|
|
2043
|
+
var DocxHistoryError = class extends Error {
|
|
2044
|
+
constructor(code, message) {
|
|
2045
|
+
super(message);
|
|
2046
|
+
this.code = code;
|
|
2047
|
+
this.name = "DocxHistoryError";
|
|
2048
|
+
}
|
|
2049
|
+
};
|
|
2050
|
+
var adapters = /* @__PURE__ */ new Map();
|
|
2051
|
+
var nextAdapter = 0;
|
|
2052
|
+
function adapter(id) {
|
|
2053
|
+
const store = adapters.get(id);
|
|
2054
|
+
if (!store) throw new DocxHistoryError("Closed", "History storage adapter is closed.");
|
|
2055
|
+
return store;
|
|
2056
|
+
}
|
|
2057
|
+
function toBase64(bytes) {
|
|
2058
|
+
let binary = "";
|
|
2059
|
+
for (let offset = 0; offset < bytes.length; offset += 32768)
|
|
2060
|
+
binary += String.fromCharCode(...bytes.subarray(offset, offset + 32768));
|
|
2061
|
+
return btoa(binary);
|
|
2062
|
+
}
|
|
2063
|
+
function fromBase64(encoded) {
|
|
2064
|
+
const binary = atob(encoded);
|
|
2065
|
+
return Uint8Array.from(binary, (c) => c.charCodeAt(0));
|
|
2066
|
+
}
|
|
2067
|
+
function installHistoryStorageImports(setModuleImports) {
|
|
2068
|
+
setModuleImports("docxodus.history", {
|
|
2069
|
+
readBlob: async (id, json) => {
|
|
2070
|
+
const reference = JSON.parse(json);
|
|
2071
|
+
const bytes = await adapter(id).readBlob(reference);
|
|
2072
|
+
if (bytes === null) return "";
|
|
2073
|
+
if (bytes.length !== reference.length) throw new DocxHistoryError("PayloadMismatch", "Stored blob length differs from its reference.");
|
|
2074
|
+
return bytes.length === 0 ? "=" : toBase64(bytes);
|
|
2075
|
+
},
|
|
2076
|
+
putBlob: async (id, json, bytes) => adapter(id).putBlob(JSON.parse(json), bytes.slice()),
|
|
2077
|
+
readHead: async (id, documentId) => JSON.stringify(await adapter(id).readHead(documentId)),
|
|
2078
|
+
advanceHead: async (id, documentId, expected, state) => JSON.stringify(await adapter(id).advanceHead(documentId, JSON.parse(expected), JSON.parse(state))),
|
|
2079
|
+
initializeHead: async (id, documentId, head) => {
|
|
2080
|
+
const storage = adapter(id);
|
|
2081
|
+
if (!storage.initializeHead) throw new DocxHistoryError("InitializationUnsupported", "Storage cannot import an exact head.");
|
|
2082
|
+
return JSON.stringify(await storage.initializeHead(documentId, JSON.parse(head)));
|
|
2083
|
+
}
|
|
2084
|
+
});
|
|
2085
|
+
}
|
|
2086
|
+
var DocxHistoryClient = class {
|
|
2087
|
+
constructor(bridge, storage) {
|
|
2088
|
+
this.bridge = bridge;
|
|
2089
|
+
this.closed = false;
|
|
2090
|
+
if (nextAdapter >= 2147483647) throw new Error("History adapter identifiers exhausted.");
|
|
2091
|
+
this.adapterId = ++nextAdapter;
|
|
2092
|
+
adapters.set(this.adapterId, storage);
|
|
2093
|
+
try {
|
|
2094
|
+
this.handle = storage.initializeHead && bridge.OpenWithInitialization ? bridge.OpenWithInitialization(this.adapterId) : bridge.Open(this.adapterId);
|
|
2095
|
+
} catch (error) {
|
|
2096
|
+
adapters.delete(this.adapterId);
|
|
2097
|
+
throw error;
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
close() {
|
|
2101
|
+
if (this.closed) return;
|
|
2102
|
+
this.bridge.Close(this.handle);
|
|
2103
|
+
adapters.delete(this.adapterId);
|
|
2104
|
+
this.closed = true;
|
|
2105
|
+
}
|
|
2106
|
+
/** Bind an identity without reading, creating a version, or taking ownership of this client. */
|
|
2107
|
+
document(documentId) {
|
|
2108
|
+
return new DocxHistoryDocument(documentId, (operation, fields, bytes) => this.call(operation, documentId, fields, bytes));
|
|
2109
|
+
}
|
|
2110
|
+
async exportHistoryArchive(documentId) {
|
|
2111
|
+
return fromBase64((await this.call("exportArchive", documentId)).bytes);
|
|
2112
|
+
}
|
|
2113
|
+
async importHistoryArchive(bytes) {
|
|
2114
|
+
checkArchiveSize(bytes);
|
|
2115
|
+
return (await this.call("importArchive", "", {}, bytes)).import;
|
|
2116
|
+
}
|
|
2117
|
+
async read(documentId) {
|
|
2118
|
+
return (await this.call("read", documentId)).view;
|
|
2119
|
+
}
|
|
2120
|
+
/** Host-triggered validated metadata tail; first join starts at the latest checkpoint. */
|
|
2121
|
+
async readChangesSince(documentId, after, maxEntriesToScan = 1e4) {
|
|
2122
|
+
return (await this.call("updates", documentId, { expectedHead: after, maxEntriesToScan })).update;
|
|
2123
|
+
}
|
|
2124
|
+
async readOperationsSince(documentId, after, maxEntriesToScan = 1e4) {
|
|
2125
|
+
return (await this.call("operations", documentId, { expectedHead: after, maxEntriesToScan })).operationUpdate;
|
|
2126
|
+
}
|
|
2127
|
+
async getOperation(documentId, operationId) {
|
|
2128
|
+
return (await this.call("getOperation", documentId, { operationId })).operation;
|
|
2129
|
+
}
|
|
2130
|
+
async exportOperationProposal(documentId, operationId) {
|
|
2131
|
+
return fromBase64((await this.call("exportOperationProposal", documentId, { operationId })).bytes);
|
|
2132
|
+
}
|
|
2133
|
+
async compareVersions(documentId, beforeVersionId, afterVersionId) {
|
|
2134
|
+
return fromBase64((await this.call("compare", documentId, { beforeVersionId, afterVersionId })).bytes);
|
|
2135
|
+
}
|
|
2136
|
+
async createVersion(documentId, expectedHead, bytes, metadata, requestId) {
|
|
2137
|
+
return (await this.call("create", documentId, { expectedHead, metadata, requestId }, bytes)).view;
|
|
2138
|
+
}
|
|
2139
|
+
async listVersions(documentId, cursor = null, limit = 25) {
|
|
2140
|
+
return (await this.call("list", documentId, { versionId: cursor, limit })).page;
|
|
2141
|
+
}
|
|
2142
|
+
async getVersion(documentId, versionId) {
|
|
2143
|
+
return (await this.call("get", documentId, { versionId })).version;
|
|
2144
|
+
}
|
|
2145
|
+
async exportVersion(documentId, versionId) {
|
|
2146
|
+
return fromBase64((await this.call("export", documentId, { versionId })).bytes);
|
|
2147
|
+
}
|
|
2148
|
+
async materialize(documentId, sequence, maxEntriesToScan = 1e4) {
|
|
2149
|
+
return fromBase64((await this.call("materialize", documentId, { sequence, maxEntriesToScan })).bytes);
|
|
2150
|
+
}
|
|
2151
|
+
async replay(documentId, sequence, maxEntriesToScan = 1e4) {
|
|
2152
|
+
return fromBase64((await this.call("replay", documentId, { sequence, maxEntriesToScan })).bytes);
|
|
2153
|
+
}
|
|
2154
|
+
async resolveSequenceAtTime(documentId, cutoff, maxEntriesToScan = 1e4) {
|
|
2155
|
+
return (await this.call("resolveTime", documentId, { cutoff, maxEntriesToScan })).sequence;
|
|
2156
|
+
}
|
|
2157
|
+
async restoreVersion(documentId, expectedHead, versionId, metadata, requestId) {
|
|
2158
|
+
return (await this.call("restore", documentId, { expectedHead, versionId, metadata, requestId })).view;
|
|
2159
|
+
}
|
|
2160
|
+
async call(operation, documentId, fields = {}, bytes = new Uint8Array()) {
|
|
2161
|
+
if (this.closed) throw new DocxHistoryError("Closed", "History client is closed.");
|
|
2162
|
+
return parseResult(await this.bridge.Invoke(
|
|
2163
|
+
this.handle,
|
|
2164
|
+
JSON.stringify({ schemaVersion: 1, operation, documentId, ...fields }),
|
|
2165
|
+
bytes
|
|
2166
|
+
));
|
|
2167
|
+
}
|
|
2168
|
+
};
|
|
2169
|
+
var DocxHistoryReader = class {
|
|
2170
|
+
/** @internal Obtain through client.document() or openDocxHistoryArchive(). */
|
|
2171
|
+
constructor(documentId, call) {
|
|
2172
|
+
this.call = call;
|
|
2173
|
+
this.#documentId = documentId;
|
|
2174
|
+
}
|
|
2175
|
+
#documentId;
|
|
2176
|
+
get documentId() {
|
|
2177
|
+
return this.#documentId;
|
|
2178
|
+
}
|
|
2179
|
+
async read() {
|
|
2180
|
+
return (await this.call("read")).view;
|
|
2181
|
+
}
|
|
2182
|
+
async listVersions(cursor = null, limit = 25) {
|
|
2183
|
+
return (await this.call("list", { versionId: cursor, limit })).page;
|
|
2184
|
+
}
|
|
2185
|
+
async getVersion(versionId) {
|
|
2186
|
+
return (await this.call("get", { versionId })).version;
|
|
2187
|
+
}
|
|
2188
|
+
/** Omit versionId for latest; pass an ID to keep a UI operation pinned to a captured version. */
|
|
2189
|
+
async exportDocx(versionId = null) {
|
|
2190
|
+
return fromBase64((await this.call("exportDocx", { versionId })).bytes);
|
|
2191
|
+
}
|
|
2192
|
+
async exportHistoryArchive() {
|
|
2193
|
+
return fromBase64((await this.call("exportArchive")).bytes);
|
|
2194
|
+
}
|
|
2195
|
+
async materialize(sequence, maxEntriesToScan = 1e4) {
|
|
2196
|
+
return fromBase64((await this.call("materialize", { sequence, maxEntriesToScan })).bytes);
|
|
2197
|
+
}
|
|
2198
|
+
async replay(sequence, maxEntriesToScan = 1e4) {
|
|
2199
|
+
return fromBase64((await this.call("replay", { sequence, maxEntriesToScan })).bytes);
|
|
2200
|
+
}
|
|
2201
|
+
async resolveSequenceAtTime(cutoff, maxEntriesToScan = 1e4) {
|
|
2202
|
+
return (await this.call("resolveTime", { cutoff, maxEntriesToScan })).sequence;
|
|
2203
|
+
}
|
|
2204
|
+
async readChangesSince(after, maxEntriesToScan = 1e4) {
|
|
2205
|
+
return (await this.call("updates", { expectedHead: after, maxEntriesToScan })).update;
|
|
2206
|
+
}
|
|
2207
|
+
async readOperationsSince(after, maxEntriesToScan = 1e4) {
|
|
2208
|
+
return (await this.call("operations", { expectedHead: after, maxEntriesToScan })).operationUpdate;
|
|
2209
|
+
}
|
|
2210
|
+
async getOperation(operationId) {
|
|
2211
|
+
return (await this.call("getOperation", { operationId })).operation;
|
|
2212
|
+
}
|
|
2213
|
+
async exportOperationProposal(operationId) {
|
|
2214
|
+
return fromBase64((await this.call("exportOperationProposal", { operationId })).bytes);
|
|
2215
|
+
}
|
|
2216
|
+
/** Redlined DOCX through DocxCompare's existing accepted-input revision policy. */
|
|
2217
|
+
async compareVersions(beforeVersionId, afterVersionId) {
|
|
2218
|
+
return fromBase64((await this.call("compare", { beforeVersionId, afterVersionId })).bytes);
|
|
2219
|
+
}
|
|
2220
|
+
};
|
|
2221
|
+
var DocxHistoryDocument = class extends DocxHistoryReader {
|
|
2222
|
+
async createVersion(expectedHead, bytes, metadata, requestId) {
|
|
2223
|
+
requireRequestId(requestId);
|
|
2224
|
+
return (await this.call("create", { expectedHead, metadata, requestId }, bytes)).view;
|
|
2225
|
+
}
|
|
2226
|
+
async restoreVersion(expectedHead, versionId, metadata, requestId) {
|
|
2227
|
+
requireRequestId(requestId);
|
|
2228
|
+
return (await this.call("restore", { expectedHead, versionId, metadata, requestId })).view;
|
|
2229
|
+
}
|
|
2230
|
+
};
|
|
2231
|
+
var DocxHistoryArchive = class _DocxHistoryArchive extends DocxHistoryReader {
|
|
2232
|
+
constructor(bridge, handle, info) {
|
|
2233
|
+
super(info.documentId, async (operation, fields = {}, bytes = new Uint8Array()) => {
|
|
2234
|
+
if (this.closed) throw new DocxHistoryError("Closed", "History archive is closed.");
|
|
2235
|
+
return parseResult(await bridge.Invoke(
|
|
2236
|
+
handle,
|
|
2237
|
+
JSON.stringify({ schemaVersion: 1, operation, documentId: this.documentId, ...fields }),
|
|
2238
|
+
bytes
|
|
2239
|
+
));
|
|
2240
|
+
});
|
|
2241
|
+
this.bridge = bridge;
|
|
2242
|
+
this.handle = handle;
|
|
2243
|
+
this.info = info;
|
|
2244
|
+
this.closed = false;
|
|
2245
|
+
}
|
|
2246
|
+
/** For custom loaders; normal callers use openDocxHistoryArchive(). */
|
|
2247
|
+
static async open(bridge, bytes) {
|
|
2248
|
+
checkArchiveSize(bytes);
|
|
2249
|
+
if (!bridge.OpenArchive) throw new Error("This WASM build does not include portable history.");
|
|
2250
|
+
const result = parseResult(await bridge.OpenArchive(bytes));
|
|
2251
|
+
return new _DocxHistoryArchive(bridge, result.handle, result.archive);
|
|
2252
|
+
}
|
|
2253
|
+
close() {
|
|
2254
|
+
if (this.closed) return;
|
|
2255
|
+
this.bridge.Close(this.handle);
|
|
2256
|
+
this.closed = true;
|
|
2257
|
+
}
|
|
2258
|
+
};
|
|
2259
|
+
function parseResult(json) {
|
|
2260
|
+
const result = JSON.parse(json);
|
|
2261
|
+
if (!result.success) throw new DocxHistoryError(result.errorCode, result.message);
|
|
2262
|
+
return result;
|
|
2263
|
+
}
|
|
2264
|
+
function checkArchiveSize(bytes) {
|
|
2265
|
+
if (bytes.length > MAX_HISTORY_ARCHIVE_BYTES) throw new DocxHistoryError("ResourceLimit", "History archive exceeds 64 MiB.");
|
|
2266
|
+
}
|
|
2267
|
+
function requireRequestId(requestId) {
|
|
2268
|
+
if (typeof requestId !== "string" || !requestId.trim()) throw new DocxHistoryError("InvalidRequest", "A durable requestId is required.");
|
|
2269
|
+
}
|
|
2270
|
+
function createMemoryHistoryStorage(maxBlobBytes = 256 * 1024 * 1024) {
|
|
2271
|
+
if (!Number.isSafeInteger(maxBlobBytes) || maxBlobBytes <= 0) throw new RangeError("Invalid blob byte limit.");
|
|
2272
|
+
const blobs = /* @__PURE__ */ new Map();
|
|
2273
|
+
const heads = /* @__PURE__ */ new Map();
|
|
2274
|
+
const copy = (value) => JSON.parse(JSON.stringify(value));
|
|
2275
|
+
const key = (reference) => {
|
|
2276
|
+
if (reference.digest.algorithm !== "SHA-256" || !/^[a-f0-9]{64}$/.test(reference.digest.value) || !Number.isSafeInteger(reference.length) || reference.length < 0 || reference.length > maxBlobBytes)
|
|
2277
|
+
throw new DocxHistoryError("InvalidRequest", "Invalid blob reference or byte limit.");
|
|
2278
|
+
return `${reference.digest.value}:${reference.length}`;
|
|
2279
|
+
};
|
|
2280
|
+
const equalReference = (a, b) => a.length === b.length && a.digest.algorithm === b.digest.algorithm && a.digest.value === b.digest.value;
|
|
2281
|
+
const equalHead2 = (a, b) => a === null || b === null ? a === b : a.revision === b.revision && equalReference(a.state, b.state);
|
|
2282
|
+
return {
|
|
2283
|
+
async readBlob(reference) {
|
|
2284
|
+
return blobs.get(key(reference))?.slice() ?? null;
|
|
2285
|
+
},
|
|
2286
|
+
async putBlob(reference, bytes) {
|
|
2287
|
+
const blobKey = key(reference);
|
|
2288
|
+
const captured = bytes.slice();
|
|
2289
|
+
if (captured.length !== reference.length) throw new DocxHistoryError("PayloadMismatch", "Blob length mismatch.");
|
|
2290
|
+
const digest = Array.from(
|
|
2291
|
+
new Uint8Array(await crypto.subtle.digest("SHA-256", captured)),
|
|
2292
|
+
(n) => n.toString(16).padStart(2, "0")
|
|
2293
|
+
).join("");
|
|
2294
|
+
if (digest !== blobKey.split(":")[0]) throw new DocxHistoryError("PayloadMismatch", "Blob digest mismatch.");
|
|
2295
|
+
if (!blobs.has(blobKey)) blobs.set(blobKey, captured);
|
|
2296
|
+
},
|
|
2297
|
+
async readHead(documentId) {
|
|
2298
|
+
return copy(heads.get(documentId) ?? null);
|
|
2299
|
+
},
|
|
2300
|
+
async advanceHead(documentId, expected, state) {
|
|
2301
|
+
key(state);
|
|
2302
|
+
const current = heads.get(documentId) ?? null;
|
|
2303
|
+
if (!equalHead2(current, expected)) return null;
|
|
2304
|
+
const revision = BigInt(current?.revision ?? "0") + 1n;
|
|
2305
|
+
if (revision > 9223372036854775807n) throw new RangeError("History revision exhausted.");
|
|
2306
|
+
const head = { revision: String(revision), state: copy(state) };
|
|
2307
|
+
heads.set(documentId, head);
|
|
2308
|
+
return copy(head);
|
|
2309
|
+
},
|
|
2310
|
+
async initializeHead(documentId, head) {
|
|
2311
|
+
key(head.state);
|
|
2312
|
+
if (typeof head.revision !== "string" || !/^[1-9][0-9]*$/.test(head.revision) || BigInt(head.revision) > 9223372036854775807n)
|
|
2313
|
+
throw new DocxHistoryError("InvalidRequest", "Imported head revision must be a positive Int64 string.");
|
|
2314
|
+
const existing = heads.get(documentId);
|
|
2315
|
+
if (existing) return { initialized: false, head: copy(existing) };
|
|
2316
|
+
const captured = copy(head);
|
|
2317
|
+
heads.set(documentId, captured);
|
|
2318
|
+
return { initialized: true, head: copy(captured) };
|
|
2319
|
+
}
|
|
2320
|
+
};
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
// src/history-checkpoints.ts
|
|
2324
|
+
var HistoryCheckpoints = class _HistoryCheckpoints {
|
|
2325
|
+
constructor(document2, journal) {
|
|
2326
|
+
this.document = document2;
|
|
2327
|
+
this.journal = journal;
|
|
2328
|
+
this.current = null;
|
|
2329
|
+
this.request = null;
|
|
2330
|
+
this.running = false;
|
|
2331
|
+
this.stale = false;
|
|
2332
|
+
}
|
|
2333
|
+
/** Pass an already captured view when opening its exact version in an editor (for example, after import). */
|
|
2334
|
+
static async open(document2, journal, view) {
|
|
2335
|
+
const controls = new _HistoryCheckpoints(document2, journal);
|
|
2336
|
+
controls.request = structuredClone(await journal.read());
|
|
2337
|
+
if (controls.request && controls.request.documentId !== document2.documentId)
|
|
2338
|
+
throw new DocxHistoryError("InvalidRequest", "The pending checkpoint belongs to another document.");
|
|
2339
|
+
controls.current = view === void 0 ? await document2.read() : structuredClone(view);
|
|
2340
|
+
if (controls.current && controls.current.state.documentId !== document2.documentId)
|
|
2341
|
+
throw new DocxHistoryError("InvalidRequest", "The captured view belongs to another document.");
|
|
2342
|
+
return controls;
|
|
2343
|
+
}
|
|
2344
|
+
get view() {
|
|
2345
|
+
return structuredClone(this.current);
|
|
2346
|
+
}
|
|
2347
|
+
get hasPending() {
|
|
2348
|
+
return this.request !== null;
|
|
2349
|
+
}
|
|
2350
|
+
get needsRefresh() {
|
|
2351
|
+
return this.stale;
|
|
2352
|
+
}
|
|
2353
|
+
async refresh() {
|
|
2354
|
+
return this.exclusive(async () => {
|
|
2355
|
+
this.current = await this.document.read();
|
|
2356
|
+
this.stale = false;
|
|
2357
|
+
return this.view;
|
|
2358
|
+
});
|
|
2359
|
+
}
|
|
2360
|
+
async save(bytes, metadata) {
|
|
2361
|
+
return this.start({
|
|
2362
|
+
kind: "save",
|
|
2363
|
+
head: this.current?.head ?? null,
|
|
2364
|
+
bytes,
|
|
2365
|
+
id: crypto.randomUUID(),
|
|
2366
|
+
documentId: this.document.documentId,
|
|
2367
|
+
metadata
|
|
2368
|
+
});
|
|
2369
|
+
}
|
|
2370
|
+
async restore(target, metadata) {
|
|
2371
|
+
if (!this.current) throw new DocxHistoryError("NotFound", "Save a checkpoint before restoring a version.");
|
|
2372
|
+
return this.start({
|
|
2373
|
+
kind: "restore",
|
|
2374
|
+
head: this.current.head,
|
|
2375
|
+
target,
|
|
2376
|
+
id: crypto.randomUUID(),
|
|
2377
|
+
documentId: this.document.documentId,
|
|
2378
|
+
metadata
|
|
2379
|
+
});
|
|
2380
|
+
}
|
|
2381
|
+
async retry() {
|
|
2382
|
+
return this.exclusive(async () => {
|
|
2383
|
+
if (!this.request) throw new DocxHistoryError("InvalidRequest", "There is no pending checkpoint.");
|
|
2384
|
+
return this.publish();
|
|
2385
|
+
});
|
|
2386
|
+
}
|
|
2387
|
+
async start(request) {
|
|
2388
|
+
return this.exclusive(async () => {
|
|
2389
|
+
if (this.request) throw new DocxHistoryError("PendingRequest", "Retry the pending checkpoint first.");
|
|
2390
|
+
if (this.stale) throw new DocxHistoryError("StaleHead", "Refresh history before saving again.");
|
|
2391
|
+
this.request = structuredClone(request);
|
|
2392
|
+
return this.publish();
|
|
2393
|
+
});
|
|
2394
|
+
}
|
|
2395
|
+
async publish() {
|
|
2396
|
+
const intended = this.request;
|
|
2397
|
+
const request = await this.journal.put(structuredClone(intended));
|
|
2398
|
+
this.request = structuredClone(request);
|
|
2399
|
+
if (request.id !== intended.id)
|
|
2400
|
+
throw new DocxHistoryError("PendingRequest", "Another tab has a pending checkpoint. Retry it before saving your draft.");
|
|
2401
|
+
let view;
|
|
2402
|
+
try {
|
|
2403
|
+
view = request.kind === "save" ? await this.document.createVersion(request.head, request.bytes, request.metadata, request.id) : await this.document.restoreVersion(request.head, request.target, request.metadata, request.id);
|
|
2404
|
+
} catch (error) {
|
|
2405
|
+
if (error instanceof DocxHistoryError && error.code === "StaleHead") {
|
|
2406
|
+
this.stale = true;
|
|
2407
|
+
await this.journal.remove(request.id);
|
|
2408
|
+
this.request = null;
|
|
2409
|
+
}
|
|
2410
|
+
throw error;
|
|
2411
|
+
}
|
|
2412
|
+
this.current = view;
|
|
2413
|
+
await this.journal.remove(request.id);
|
|
2414
|
+
this.request = null;
|
|
2415
|
+
return this.view;
|
|
2416
|
+
}
|
|
2417
|
+
async exclusive(action) {
|
|
2418
|
+
if (this.running) throw new DocxHistoryError("Busy", "A history command is still running.");
|
|
2419
|
+
this.running = true;
|
|
2420
|
+
try {
|
|
2421
|
+
return await action();
|
|
2422
|
+
} finally {
|
|
2423
|
+
this.running = false;
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
};
|
|
2427
|
+
|
|
2428
|
+
// src/history-indexeddb.ts
|
|
2429
|
+
async function openIndexedDbHistoryStore(name) {
|
|
2430
|
+
const db = await new Promise((resolve, reject) => {
|
|
2431
|
+
let blocked = false;
|
|
2432
|
+
const request = indexedDB.open(name, 1);
|
|
2433
|
+
request.onupgradeneeded = () => {
|
|
2434
|
+
for (const store of ["blobs", "heads", "requests"]) request.result.createObjectStore(store);
|
|
2435
|
+
};
|
|
2436
|
+
request.onsuccess = () => {
|
|
2437
|
+
if (blocked) request.result.close();
|
|
2438
|
+
else resolve(request.result);
|
|
2439
|
+
};
|
|
2440
|
+
request.onerror = () => reject(request.error);
|
|
2441
|
+
request.onblocked = () => {
|
|
2442
|
+
blocked = true;
|
|
2443
|
+
reject(new Error("Close other tabs using this history store, then try again."));
|
|
2444
|
+
};
|
|
2445
|
+
});
|
|
2446
|
+
db.onversionchange = () => db.close();
|
|
2447
|
+
function transaction(name2, mode, action) {
|
|
2448
|
+
return new Promise((resolve, reject) => {
|
|
2449
|
+
const tx = db.transaction(name2, mode);
|
|
2450
|
+
let value;
|
|
2451
|
+
tx.oncomplete = () => resolve(value);
|
|
2452
|
+
tx.onabort = () => reject(tx.error ?? new Error("History storage transaction was aborted."));
|
|
2453
|
+
try {
|
|
2454
|
+
action(tx.objectStore(name2), (result) => {
|
|
2455
|
+
value = result;
|
|
2456
|
+
});
|
|
2457
|
+
} catch (error) {
|
|
2458
|
+
tx.abort();
|
|
2459
|
+
reject(error);
|
|
2460
|
+
}
|
|
2461
|
+
});
|
|
2462
|
+
}
|
|
2463
|
+
const storage = {
|
|
2464
|
+
readBlob(reference) {
|
|
2465
|
+
const key = referenceKey(reference);
|
|
2466
|
+
return transaction("blobs", "readonly", (store, result) => {
|
|
2467
|
+
store.get(key).onsuccess = (event) => result(event.target.result ?? null);
|
|
2468
|
+
});
|
|
2469
|
+
},
|
|
2470
|
+
async putBlob(reference, bytes) {
|
|
2471
|
+
const key = referenceKey(reference);
|
|
2472
|
+
const captured = bytes.slice();
|
|
2473
|
+
if (captured.length !== reference.length) throw new DocxHistoryError("PayloadMismatch", "Stored document length does not match its reference.");
|
|
2474
|
+
const hash = Array.from(
|
|
2475
|
+
new Uint8Array(await crypto.subtle.digest("SHA-256", captured)),
|
|
2476
|
+
(byte) => byte.toString(16).padStart(2, "0")
|
|
2477
|
+
).join("");
|
|
2478
|
+
if (hash !== key.split(":")[0])
|
|
2479
|
+
throw new DocxHistoryError("PayloadMismatch", "Stored document bytes do not match their reference.");
|
|
2480
|
+
await transaction("blobs", "readwrite", (store, result) => {
|
|
2481
|
+
store.put(captured, key);
|
|
2482
|
+
result();
|
|
2483
|
+
});
|
|
2484
|
+
},
|
|
2485
|
+
readHead(documentId) {
|
|
2486
|
+
return transaction("heads", "readonly", (store, result) => {
|
|
2487
|
+
store.get(documentId).onsuccess = (event) => result(event.target.result ?? null);
|
|
2488
|
+
});
|
|
2489
|
+
},
|
|
2490
|
+
advanceHead(documentId, expected, state) {
|
|
2491
|
+
referenceKey(state);
|
|
2492
|
+
const captured = structuredClone({ expected, state });
|
|
2493
|
+
if (expected) validateHead(expected);
|
|
2494
|
+
const revision = BigInt(expected?.revision ?? "0") + 1n;
|
|
2495
|
+
if (revision > 9223372036854775807n) throw new RangeError("History revision exhausted.");
|
|
2496
|
+
return transaction("heads", "readwrite", (store, result) => {
|
|
2497
|
+
store.get(documentId).onsuccess = (event) => {
|
|
2498
|
+
const current = event.target.result ?? null;
|
|
2499
|
+
if (!equalHead(current, captured.expected)) {
|
|
2500
|
+
result(null);
|
|
2501
|
+
return;
|
|
2502
|
+
}
|
|
2503
|
+
const head = { revision: String(revision), state: captured.state };
|
|
2504
|
+
store.put(head, documentId);
|
|
2505
|
+
result(head);
|
|
2506
|
+
};
|
|
2507
|
+
});
|
|
2508
|
+
},
|
|
2509
|
+
initializeHead(documentId, head) {
|
|
2510
|
+
validateHead(head);
|
|
2511
|
+
const captured = structuredClone(head);
|
|
2512
|
+
return transaction("heads", "readwrite", (store, result) => {
|
|
2513
|
+
store.get(documentId).onsuccess = (event) => {
|
|
2514
|
+
const existing = event.target.result;
|
|
2515
|
+
if (existing) {
|
|
2516
|
+
result({ initialized: false, head: existing });
|
|
2517
|
+
return;
|
|
2518
|
+
}
|
|
2519
|
+
store.put(captured, documentId);
|
|
2520
|
+
result({ initialized: true, head: captured });
|
|
2521
|
+
};
|
|
2522
|
+
});
|
|
2523
|
+
}
|
|
2524
|
+
};
|
|
2525
|
+
return {
|
|
2526
|
+
storage,
|
|
2527
|
+
journal(documentId) {
|
|
2528
|
+
return {
|
|
2529
|
+
read: () => transaction("requests", "readonly", (store, result) => {
|
|
2530
|
+
store.get(documentId).onsuccess = (event) => result(event.target.result ?? null);
|
|
2531
|
+
}),
|
|
2532
|
+
put(request) {
|
|
2533
|
+
if (request.documentId !== documentId) throw new Error("Checkpoint document identity does not match.");
|
|
2534
|
+
const captured = structuredClone(request);
|
|
2535
|
+
return transaction("requests", "readwrite", (store, result) => {
|
|
2536
|
+
store.get(documentId).onsuccess = (event) => {
|
|
2537
|
+
const existing = event.target.result;
|
|
2538
|
+
if (existing) {
|
|
2539
|
+
result(existing);
|
|
2540
|
+
return;
|
|
2541
|
+
}
|
|
2542
|
+
store.put(captured, documentId);
|
|
2543
|
+
result(captured);
|
|
2544
|
+
};
|
|
2545
|
+
});
|
|
2546
|
+
},
|
|
2547
|
+
remove(requestId) {
|
|
2548
|
+
return transaction("requests", "readwrite", (store, result) => {
|
|
2549
|
+
store.get(documentId).onsuccess = (event) => {
|
|
2550
|
+
if (event.target.result?.id === requestId) store.delete(documentId);
|
|
2551
|
+
result();
|
|
2552
|
+
};
|
|
2553
|
+
});
|
|
2554
|
+
}
|
|
2555
|
+
};
|
|
2556
|
+
},
|
|
2557
|
+
close: () => db.close()
|
|
2558
|
+
};
|
|
2559
|
+
}
|
|
2560
|
+
function referenceKey(reference) {
|
|
2561
|
+
if (reference.digest.algorithm !== "SHA-256" || !/^[a-f0-9]{64}$/.test(reference.digest.value) || !Number.isSafeInteger(reference.length) || reference.length < 0)
|
|
2562
|
+
throw new DocxHistoryError("InvalidRequest", "Invalid history blob reference.");
|
|
2563
|
+
return `${reference.digest.value}:${reference.length}`;
|
|
2564
|
+
}
|
|
2565
|
+
function validateHead(head) {
|
|
2566
|
+
referenceKey(head.state);
|
|
2567
|
+
if (typeof head.revision !== "string" || !/^[1-9][0-9]*$/.test(head.revision) || BigInt(head.revision) > 9223372036854775807n)
|
|
2568
|
+
throw new DocxHistoryError("InvalidRequest", "Invalid history revision.");
|
|
2569
|
+
}
|
|
2570
|
+
function equalHead(a, b) {
|
|
2571
|
+
return a === null || b === null ? 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;
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2574
|
+
// src/history-controls.ts
|
|
2575
|
+
function mountHistoryControls(container, options) {
|
|
2576
|
+
const pageSize = options.pageSize ?? 25;
|
|
2577
|
+
if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > 100) throw new RangeError("History page size must be 1\u2013100.");
|
|
2578
|
+
if (options.checkpoints && options.checkpoints.document !== options.reader)
|
|
2579
|
+
throw new Error("History controls and checkpoints must use the same document.");
|
|
2580
|
+
return new HistoryPanel(container, options, pageSize);
|
|
2581
|
+
}
|
|
2582
|
+
var HistoryPanel = class {
|
|
2583
|
+
constructor(container, options, pageSize) {
|
|
2584
|
+
this.options = options;
|
|
2585
|
+
this.pageSize = pageSize;
|
|
2586
|
+
this.actions = /* @__PURE__ */ new Map();
|
|
2587
|
+
this.events = new AbortController();
|
|
2588
|
+
this.records = [];
|
|
2589
|
+
this.next = null;
|
|
2590
|
+
this.view = null;
|
|
2591
|
+
this.active = null;
|
|
2592
|
+
this.destroyed = false;
|
|
2593
|
+
const doc = container.ownerDocument;
|
|
2594
|
+
this.element = doc.createElement("section");
|
|
2595
|
+
this.element.className = "dx-history";
|
|
2596
|
+
this.element.setAttribute("aria-label", "Document history");
|
|
2597
|
+
const style = doc.createElement("style");
|
|
2598
|
+
style.textContent = HISTORY_CSS;
|
|
2599
|
+
const title = doc.createElement("h2");
|
|
2600
|
+
title.textContent = "Document history";
|
|
2601
|
+
this.status = doc.createElement("p");
|
|
2602
|
+
this.status.setAttribute("role", "status");
|
|
2603
|
+
this.status.setAttribute("aria-live", "polite");
|
|
2604
|
+
this.fieldset = doc.createElement("fieldset");
|
|
2605
|
+
const legend = doc.createElement("legend");
|
|
2606
|
+
legend.textContent = "Versions and checkpoints";
|
|
2607
|
+
this.fieldset.append(legend);
|
|
2608
|
+
this.element.append(style, title, this.status, this.fieldset);
|
|
2609
|
+
container.append(this.element);
|
|
2610
|
+
const note = doc.createElement("p");
|
|
2611
|
+
note.textContent = options.checkpoints ? "Browse saved versions without changing your draft." : "Read-only history. Preview or download any saved version.";
|
|
2612
|
+
this.fieldset.append(note);
|
|
2613
|
+
this.button("refresh", "Refresh history", () => this.load(true));
|
|
2614
|
+
this.button("latest", "Open latest", async () => this.preview(await options.reader.exportDocx(), "Latest saved version"));
|
|
2615
|
+
this.versions = this.select("Version");
|
|
2616
|
+
this.versions.size = 6;
|
|
2617
|
+
this.detail = doc.createElement("p");
|
|
2618
|
+
this.fieldset.append(this.detail);
|
|
2619
|
+
this.versions.addEventListener("change", () => this.update(), { signal: this.events.signal });
|
|
2620
|
+
this.button("preview", "Preview selected", async () => this.preview(await options.reader.exportDocx(this.selected().id), versionTitle(this.selected())));
|
|
2621
|
+
this.button("download", "Download selected", async () => this.download(await options.reader.exportDocx(this.selected().id), `version-${this.selected().record.sequence}.docx`));
|
|
2622
|
+
this.before = this.select("Compare from");
|
|
2623
|
+
this.before.addEventListener("change", () => this.update(), { signal: this.events.signal });
|
|
2624
|
+
const compareNote = doc.createElement("p");
|
|
2625
|
+
compareNote.textContent = "Compare from this version to the selected version above.";
|
|
2626
|
+
this.fieldset.append(compareNote);
|
|
2627
|
+
this.button("compare", "Compare versions", async () => {
|
|
2628
|
+
const before = this.records[Number(this.before.value)];
|
|
2629
|
+
await this.preview(await options.reader.compareVersions(before.id, this.selected().id), "Comparison with tracked changes");
|
|
2630
|
+
});
|
|
2631
|
+
this.button("more", "Load older versions", async () => {
|
|
2632
|
+
if (this.next) await this.page(this.next, false);
|
|
2633
|
+
});
|
|
2634
|
+
this.author = this.input("Your name", "text", options.author ?? "You");
|
|
2635
|
+
this.label = this.input("Checkpoint name (optional)", "text");
|
|
2636
|
+
this.author.parentElement.hidden = this.label.parentElement.hidden = !options.checkpoints;
|
|
2637
|
+
this.button("save", "Save checkpoint", async () => {
|
|
2638
|
+
const view = await options.checkpoints.save(await options.capture(), this.metadata());
|
|
2639
|
+
await options.onCheckpoint?.(view, "save");
|
|
2640
|
+
await this.load(false);
|
|
2641
|
+
this.label.value = "";
|
|
2642
|
+
this.status.textContent = "Checkpoint saved. Your draft remains open.";
|
|
2643
|
+
});
|
|
2644
|
+
this.button("restore", "Restore selected", async () => {
|
|
2645
|
+
const version = this.selected();
|
|
2646
|
+
if (!doc.defaultView?.confirm(`Restore ${versionTitle(version)} as a new checkpoint?
|
|
2647
|
+
|
|
2648
|
+
Your current draft and all later versions will be kept.`)) {
|
|
2649
|
+
this.status.textContent = "Restore canceled. Your draft and history are unchanged.";
|
|
2650
|
+
return;
|
|
2651
|
+
}
|
|
2652
|
+
const view = await options.checkpoints.restore(version.id, this.metadata());
|
|
2653
|
+
await options.onCheckpoint?.(view, "restore");
|
|
2654
|
+
await this.load(false);
|
|
2655
|
+
this.status.textContent = "Restored as a new checkpoint. Your draft and later versions are kept.";
|
|
2656
|
+
});
|
|
2657
|
+
const restoreNote = doc.createElement("p");
|
|
2658
|
+
restoreNote.hidden = !options.checkpoints;
|
|
2659
|
+
restoreNote.textContent = "Restore creates a new checkpoint. Open latest to preview it; your draft stays open.";
|
|
2660
|
+
this.fieldset.append(restoreNote);
|
|
2661
|
+
this.button("retry", "Retry checkpoint", async () => {
|
|
2662
|
+
const view = await options.checkpoints.retry();
|
|
2663
|
+
await options.onCheckpoint?.(view, "retry");
|
|
2664
|
+
await this.load(false);
|
|
2665
|
+
this.status.textContent = "Checkpoint recovered. Your draft remains open. Refresh history to check for newer versions.";
|
|
2666
|
+
});
|
|
2667
|
+
const sharing = this.disclosure("Download with history");
|
|
2668
|
+
const sharingNote = doc.createElement("p");
|
|
2669
|
+
sharingNote.textContent = "Includes retained drafts and collaboration proposals. Share this file only when you want to include that history. A DOCX download keeps existing Word comments and revisions, without external history.";
|
|
2670
|
+
sharing.append(sharingNote);
|
|
2671
|
+
this.button("archive", "Download .docxhistory", async () => this.download(await options.reader.exportHistoryArchive(), "docxhistory"), sharing);
|
|
2672
|
+
const time = this.disclosure("Find a version by time");
|
|
2673
|
+
const cutoff = this.input("Saved at or before (local time)", "datetime-local", "", time);
|
|
2674
|
+
this.button("time", "Preview at time", async () => {
|
|
2675
|
+
if (!cutoff.value) throw new DocxHistoryError("InvalidRequest", "Choose a date and time first.");
|
|
2676
|
+
const sequence = await options.reader.resolveSequenceAtTime(new Date(cutoff.value).toISOString());
|
|
2677
|
+
await this.preview(await options.reader.materialize(sequence), "Version at selected time");
|
|
2678
|
+
}, time);
|
|
2679
|
+
const activity = this.disclosure("Recorded collaboration");
|
|
2680
|
+
const decisions = doc.createElement("ol");
|
|
2681
|
+
let showOlderActivity = () => {
|
|
2682
|
+
};
|
|
2683
|
+
this.button("activity", "Load activity", async () => {
|
|
2684
|
+
const { operations } = await options.reader.readOperationsSince(null);
|
|
2685
|
+
const resolved = new Set(operations.filter((op) => op.record.status === "accepted").map((op) => op.input.request.resolves?.digest.value));
|
|
2686
|
+
let shown = 0;
|
|
2687
|
+
showOlderActivity = () => {
|
|
2688
|
+
const page = operations.slice(Math.max(0, operations.length - shown - this.pageSize), operations.length - shown).reverse();
|
|
2689
|
+
decisions.append(...page.map((op) => this.activityItem(op, resolved.has(op.id.digest.value))));
|
|
2690
|
+
shown += page.length;
|
|
2691
|
+
this.actions.get("activity-more").hidden = shown >= operations.length;
|
|
2692
|
+
};
|
|
2693
|
+
decisions.replaceChildren();
|
|
2694
|
+
showOlderActivity();
|
|
2695
|
+
this.status.textContent = operations.length ? "Recorded activity loaded." : "No recorded collaboration.";
|
|
2696
|
+
}, activity);
|
|
2697
|
+
activity.append(decisions);
|
|
2698
|
+
this.button("activity-more", "Load older activity", async () => showOlderActivity(), activity);
|
|
2699
|
+
this.actions.get("activity-more").hidden = true;
|
|
2700
|
+
this.ready = this.run("Loading history", () => this.load(false));
|
|
2701
|
+
}
|
|
2702
|
+
refresh() {
|
|
2703
|
+
return this.run("Loading history", () => this.load(true));
|
|
2704
|
+
}
|
|
2705
|
+
async destroy() {
|
|
2706
|
+
this.destroyed = true;
|
|
2707
|
+
this.events.abort();
|
|
2708
|
+
this.element.remove();
|
|
2709
|
+
await this.active?.catch(() => {
|
|
2710
|
+
});
|
|
2711
|
+
}
|
|
2712
|
+
async load(refresh) {
|
|
2713
|
+
this.view = this.options.checkpoints ? refresh ? await this.options.checkpoints.refresh() : this.options.checkpoints.view : await this.options.reader.read();
|
|
2714
|
+
if (this.view) await this.page(this.view.version.id, true);
|
|
2715
|
+
else {
|
|
2716
|
+
this.records = [];
|
|
2717
|
+
this.next = null;
|
|
2718
|
+
this.versions.replaceChildren();
|
|
2719
|
+
this.before.replaceChildren();
|
|
2720
|
+
}
|
|
2721
|
+
this.status.textContent = this.options.checkpoints?.hasPending ? "A checkpoint needs recovery. Retry it before saving more changes." : this.view ? "History loaded. Select a version to preview, download or restore." : "No checkpoints yet. Save your first checkpoint when you are ready.";
|
|
2722
|
+
}
|
|
2723
|
+
async page(cursor, reset) {
|
|
2724
|
+
const page = await this.options.reader.listVersions(cursor, this.pageSize);
|
|
2725
|
+
if (reset) {
|
|
2726
|
+
this.records = [];
|
|
2727
|
+
this.versions.replaceChildren();
|
|
2728
|
+
this.before.replaceChildren();
|
|
2729
|
+
}
|
|
2730
|
+
const start2 = this.records.length;
|
|
2731
|
+
this.records.push(...page.versions);
|
|
2732
|
+
this.next = page.next;
|
|
2733
|
+
for (let index = start2; index < this.records.length; index++) {
|
|
2734
|
+
for (const select of [this.versions, this.before]) {
|
|
2735
|
+
const option = select.ownerDocument.createElement("option");
|
|
2736
|
+
option.value = String(index);
|
|
2737
|
+
option.textContent = option.title = versionTitle(this.records[index]);
|
|
2738
|
+
select.append(option);
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
if (reset) {
|
|
2742
|
+
this.versions.value = "0";
|
|
2743
|
+
this.before.value = this.records.length > 1 ? "1" : "0";
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
update() {
|
|
2747
|
+
const hasVersion = this.records.length > 0;
|
|
2748
|
+
const pending = this.options.checkpoints?.hasPending ?? false;
|
|
2749
|
+
const stale = this.options.checkpoints?.needsRefresh ?? false;
|
|
2750
|
+
for (const key of ["latest", "preview", "download", "archive", "time", "activity"]) this.actions.get(key).disabled = !hasVersion;
|
|
2751
|
+
this.actions.get("more").hidden = !this.next;
|
|
2752
|
+
this.actions.get("compare").disabled = !hasVersion || this.before.value === this.versions.value;
|
|
2753
|
+
this.actions.get("save").hidden = !this.options.checkpoints || !this.options.capture;
|
|
2754
|
+
this.actions.get("save").disabled = pending || stale;
|
|
2755
|
+
this.actions.get("restore").hidden = !this.options.checkpoints;
|
|
2756
|
+
this.actions.get("restore").disabled = !hasVersion || pending || stale;
|
|
2757
|
+
this.actions.get("retry").hidden = !pending;
|
|
2758
|
+
this.detail.textContent = hasVersion ? [
|
|
2759
|
+
versionTitle(this.selected()),
|
|
2760
|
+
this.selected().record.metadata.message,
|
|
2761
|
+
this.selected().record.restoredFrom ? "Restored from an earlier version." : ""
|
|
2762
|
+
].filter(Boolean).join(" \u2014 ") : "";
|
|
2763
|
+
}
|
|
2764
|
+
selected() {
|
|
2765
|
+
return this.records[Number(this.versions.value)];
|
|
2766
|
+
}
|
|
2767
|
+
activityItem(op, resolved) {
|
|
2768
|
+
const doc = this.element.ownerDocument;
|
|
2769
|
+
const item = doc.createElement("li");
|
|
2770
|
+
const state = op.record.status === "accepted" ? "Accepted" : resolved ? "Conflict resolved" : "Conflict needs review";
|
|
2771
|
+
const label = doc.createElement("p");
|
|
2772
|
+
label.textContent = `${op.input.request.metadata.author} \xB7 ${state} \xB7 ${formatTime(op.input.request.metadata.createdAt)}`;
|
|
2773
|
+
item.append(label);
|
|
2774
|
+
const description = doc.createElement("p");
|
|
2775
|
+
this.commandButton("View decision", async () => {
|
|
2776
|
+
const decision = await this.options.reader.getOperation(op.id);
|
|
2777
|
+
const { kind, metadata } = decision.input.request;
|
|
2778
|
+
description.textContent = [
|
|
2779
|
+
{ text: "Text edit", package: "Document edit", discard: "Discarded proposal" }[kind],
|
|
2780
|
+
metadata.label,
|
|
2781
|
+
metadata.message,
|
|
2782
|
+
decision.record.conflict
|
|
2783
|
+
].filter(Boolean).join(" \u2014 ");
|
|
2784
|
+
}, item);
|
|
2785
|
+
item.append(description);
|
|
2786
|
+
this.commandButton("Download proposal", async () => {
|
|
2787
|
+
await this.download(await this.options.reader.exportOperationProposal(op.id), `proposal-${op.record.revision}.docx`);
|
|
2788
|
+
}, item);
|
|
2789
|
+
return item;
|
|
2790
|
+
}
|
|
2791
|
+
metadata() {
|
|
2792
|
+
return { author: this.author.value.trim() || "You", createdAt: (/* @__PURE__ */ new Date()).toISOString(), label: this.label.value.trim() || void 0 };
|
|
2793
|
+
}
|
|
2794
|
+
async preview(bytes, title) {
|
|
2795
|
+
if (!this.destroyed) await this.options.preview(bytes, title);
|
|
2796
|
+
}
|
|
2797
|
+
async download(bytes, suffix) {
|
|
2798
|
+
if (this.destroyed) return;
|
|
2799
|
+
const stem = (this.options.documentName ?? "document").replace(/\.(docx|docxhistory)$/i, "");
|
|
2800
|
+
const name = suffix === "docxhistory" ? `${stem}.docxhistory` : `${stem}-${suffix}`;
|
|
2801
|
+
if (this.options.download) {
|
|
2802
|
+
await this.options.download(bytes, name);
|
|
2803
|
+
return;
|
|
2804
|
+
}
|
|
2805
|
+
const url = URL.createObjectURL(new Blob([bytes.slice()], { type: suffix === "docxhistory" ? "application/octet-stream" : "application/vnd.openxmlformats-officedocument.wordprocessingml.document" }));
|
|
2806
|
+
const link = this.element.ownerDocument.createElement("a");
|
|
2807
|
+
link.href = url;
|
|
2808
|
+
link.download = name;
|
|
2809
|
+
link.click();
|
|
2810
|
+
setTimeout(() => URL.revokeObjectURL(url), 1e3);
|
|
2811
|
+
}
|
|
2812
|
+
async run(label, action) {
|
|
2813
|
+
if (this.destroyed) throw new DocxHistoryError("Closed", "History controls are closed.");
|
|
2814
|
+
if (this.active) throw new DocxHistoryError("Busy", "A history command is still running.");
|
|
2815
|
+
this.fieldset.disabled = true;
|
|
2816
|
+
this.element.setAttribute("aria-busy", "true");
|
|
2817
|
+
this.status.textContent = `${label}\u2026`;
|
|
2818
|
+
const work = Promise.resolve().then(action);
|
|
2819
|
+
this.active = work;
|
|
2820
|
+
try {
|
|
2821
|
+
await work;
|
|
2822
|
+
if (this.status.textContent === `${label}\u2026`) this.status.textContent = "Ready.";
|
|
2823
|
+
} catch (error) {
|
|
2824
|
+
if (!this.destroyed) this.status.textContent = historyControlError(error, this.options.checkpoints?.hasPending);
|
|
2825
|
+
throw error;
|
|
2826
|
+
} finally {
|
|
2827
|
+
this.active = null;
|
|
2828
|
+
if (!this.destroyed) {
|
|
2829
|
+
this.fieldset.disabled = false;
|
|
2830
|
+
this.element.setAttribute("aria-busy", "false");
|
|
2831
|
+
this.update();
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
button(key, title, action, parent = this.fieldset) {
|
|
2836
|
+
this.actions.set(key, this.commandButton(title, action, parent));
|
|
2837
|
+
}
|
|
2838
|
+
commandButton(title, action, parent) {
|
|
2839
|
+
const button = parent.ownerDocument.createElement("button");
|
|
2840
|
+
button.type = "button";
|
|
2841
|
+
button.textContent = title;
|
|
2842
|
+
button.addEventListener("click", () => {
|
|
2843
|
+
void this.run(title, action).catch(() => {
|
|
2844
|
+
});
|
|
2845
|
+
}, { signal: this.events.signal });
|
|
2846
|
+
parent.append(button);
|
|
2847
|
+
return button;
|
|
2848
|
+
}
|
|
2849
|
+
select(title) {
|
|
2850
|
+
const label = this.fieldset.ownerDocument.createElement("label");
|
|
2851
|
+
label.textContent = title;
|
|
2852
|
+
const select = label.ownerDocument.createElement("select");
|
|
2853
|
+
select.setAttribute("aria-label", title);
|
|
2854
|
+
label.append(select);
|
|
2855
|
+
this.fieldset.append(label);
|
|
2856
|
+
return select;
|
|
2857
|
+
}
|
|
2858
|
+
input(title, type, value = "", parent = this.fieldset) {
|
|
2859
|
+
const label = parent.ownerDocument.createElement("label");
|
|
2860
|
+
label.textContent = title;
|
|
2861
|
+
const input = label.ownerDocument.createElement("input");
|
|
2862
|
+
input.type = type;
|
|
2863
|
+
input.value = value;
|
|
2864
|
+
label.append(input);
|
|
2865
|
+
parent.append(label);
|
|
2866
|
+
return input;
|
|
2867
|
+
}
|
|
2868
|
+
disclosure(title) {
|
|
2869
|
+
const details = this.fieldset.ownerDocument.createElement("details");
|
|
2870
|
+
const summary = details.ownerDocument.createElement("summary");
|
|
2871
|
+
summary.textContent = title;
|
|
2872
|
+
details.append(summary);
|
|
2873
|
+
this.fieldset.append(details);
|
|
2874
|
+
return details;
|
|
2875
|
+
}
|
|
2876
|
+
};
|
|
2877
|
+
function historyControlError(error, pending = false) {
|
|
2878
|
+
const code = error instanceof DocxHistoryError ? error.code : "";
|
|
2879
|
+
if (code === "StaleHead") return "A newer checkpoint exists. Your draft is safe. Refresh history, review the newer version, then save again.";
|
|
2880
|
+
if (code === "ImportConflict") return "A different local history already exists. Open this file read-only to explore it.";
|
|
2881
|
+
if (code === "InitializationUnsupported") return "This storage cannot import history. Open read-only or choose storage that supports importing.";
|
|
2882
|
+
if (pending) return "The checkpoint could not be confirmed. Your draft is safe. Retry checkpoint to recover the original request.";
|
|
2883
|
+
if (code === "ResourceLimit") return "This history file exceeds browser processing limits, which can apply even below 64 MiB. Your document is unchanged.";
|
|
2884
|
+
if (code === "UnsupportedVersion") return "This history file uses an unsupported version. Open it with a newer app. Your document is unchanged.";
|
|
2885
|
+
if (code === "InvalidManifest") return "This history file is damaged or incomplete. Choose another copy. Your document is unchanged.";
|
|
2886
|
+
return `History could not be loaded. Your document is unchanged. ${error instanceof Error ? error.message : "Please try again."}`;
|
|
2887
|
+
}
|
|
2888
|
+
function versionTitle(version) {
|
|
2889
|
+
const { metadata, sequence } = version.record;
|
|
2890
|
+
return `${metadata.label || `Version ${BigInt(sequence) + 1n}`} \xB7 ${metadata.author} \xB7 ${formatTime(metadata.createdAt)}`;
|
|
2891
|
+
}
|
|
2892
|
+
function formatTime(value) {
|
|
2893
|
+
const time = new Date(value);
|
|
2894
|
+
return Number.isNaN(time.getTime()) ? value : time.toLocaleString();
|
|
2895
|
+
}
|
|
2896
|
+
var HISTORY_CSS = `
|
|
2897
|
+
.dx-history{font:14px/1.5 system-ui,sans-serif;color:#203047;background:#fff;border:1px solid #cbd5e1;border-radius:12px;padding:18px;min-width:0}
|
|
2898
|
+
.dx-history *{box-sizing:border-box}.dx-history h2{font-size:20px;margin:0 0 8px}.dx-history p{margin:8px 0;overflow-wrap:anywhere}
|
|
2899
|
+
.dx-history fieldset{border:0;padding:0;margin:0;min-width:0}.dx-history legend{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}
|
|
2900
|
+
.dx-history label{display:block;font-weight:600;margin:12px 0 6px}.dx-history input,.dx-history select{display:block;width:100%;max-width:100%;margin-top:4px;font:inherit;color:inherit;border:1px solid #94a3b8;border-radius:6px;padding:8px;background:#fff}
|
|
2901
|
+
.dx-history button{min-height:40px;margin:4px 6px 4px 0;padding:7px 12px;border:1px solid #94a3b8;border-radius:6px;background:#f8fafc;color:inherit;font:inherit;cursor:pointer}
|
|
2902
|
+
.dx-history button:disabled{opacity:.5;cursor:wait}.dx-history :focus-visible{outline:3px solid #2563eb;outline-offset:2px}.dx-history [hidden]{display:none}
|
|
2903
|
+
.dx-history details{margin-top:16px;border-top:1px solid #e2e8f0;padding-top:12px}.dx-history summary{cursor:pointer;font-weight:600;padding:4px 0}
|
|
2904
|
+
.dx-history [role=status]{min-height:42px}.dx-history ol{padding-left:22px}.dx-history option{padding:6px}
|
|
2905
|
+
`;
|
|
2906
|
+
|
|
2041
2907
|
// src/page-number-format.ts
|
|
2042
2908
|
var ROMAN_ONES = ["", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix"];
|
|
2043
2909
|
var ROMAN_TENS = ["", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc"];
|
|
@@ -5487,11 +6353,11 @@ var HeaderFooterRegion = class {
|
|
|
5487
6353
|
if (!this.partUriFor(which, "default")) this.seedStory(which, "default");
|
|
5488
6354
|
if (!this.partUriFor(which, kind)) this.seedStory(which, kind);
|
|
5489
6355
|
}
|
|
5490
|
-
const res =
|
|
6356
|
+
const res = parseResult2(this.bridge.EnsureHeaderFooterVisible(this.handle, this.bodyAnchorId, kind));
|
|
5491
6357
|
if (!res.success) return false;
|
|
5492
6358
|
} else {
|
|
5493
6359
|
if (!this.bridge.SetHeaderFooterKindEnabled) return false;
|
|
5494
|
-
const res =
|
|
6360
|
+
const res = parseResult2(this.bridge.SetHeaderFooterKindEnabled(this.handle, this.bodyAnchorId, kind, false));
|
|
5495
6361
|
if (!res.success) return false;
|
|
5496
6362
|
for (const which of ["header", "footer"]) {
|
|
5497
6363
|
if (this.kinds[which] === kind) this.kinds[which] = "default";
|
|
@@ -5516,7 +6382,7 @@ var HeaderFooterRegion = class {
|
|
|
5516
6382
|
* Deliberately a PLAIN field (no `\*` switch), exactly as Word inserts one, so it follows the
|
|
5517
6383
|
* section's page-number format — see {@link setPageNumbering}. */
|
|
5518
6384
|
insertPageNumber(which, anchorId, field) {
|
|
5519
|
-
const res =
|
|
6385
|
+
const res = parseResult2(this.bridge.InsertPageNumberField(this.handle, anchorId, field, ""));
|
|
5520
6386
|
if (!res.success) return false;
|
|
5521
6387
|
this.refresh(which);
|
|
5522
6388
|
return true;
|
|
@@ -5539,7 +6405,7 @@ var HeaderFooterRegion = class {
|
|
|
5539
6405
|
/** Set this section's page numbering (`w:pgNumType`) — Word's *Format Page Numbers…*. */
|
|
5540
6406
|
setPageNumbering(op) {
|
|
5541
6407
|
if (!this.bodyAnchorId) return false;
|
|
5542
|
-
const res =
|
|
6408
|
+
const res = parseResult2(this.bridge.SetPageNumbering(this.handle, this.bodyAnchorId, JSON.stringify(op)));
|
|
5543
6409
|
if (!res.success) return false;
|
|
5544
6410
|
this.reloadSection();
|
|
5545
6411
|
return true;
|
|
@@ -5551,7 +6417,7 @@ var HeaderFooterRegion = class {
|
|
|
5551
6417
|
/** Remove this section's page-numbering start/format. */
|
|
5552
6418
|
clearPageNumbering() {
|
|
5553
6419
|
if (!this.bodyAnchorId) return false;
|
|
5554
|
-
const res =
|
|
6420
|
+
const res = parseResult2(this.bridge.ClearPageNumbering(this.handle, this.bodyAnchorId));
|
|
5555
6421
|
if (!res.success) return false;
|
|
5556
6422
|
this.reloadSection();
|
|
5557
6423
|
return true;
|
|
@@ -5862,7 +6728,7 @@ var HeaderFooterRegion = class {
|
|
|
5862
6728
|
seedStory(which, kind) {
|
|
5863
6729
|
if (!this.bodyAnchorId) return;
|
|
5864
6730
|
const set = which === "header" ? this.bridge.SetHeaderText : this.bridge.SetFooterText;
|
|
5865
|
-
const res =
|
|
6731
|
+
const res = parseResult2(set(this.handle, this.bodyAnchorId, kind, ""));
|
|
5866
6732
|
if (!res.success) return;
|
|
5867
6733
|
this.callbacks.refreshAnchorMap();
|
|
5868
6734
|
this.sectionInfo = this.readSectionInfo(this.bodyAnchorId);
|
|
@@ -6018,7 +6884,7 @@ function caretFromPoint(doc, x, y) {
|
|
|
6018
6884
|
const range = d.caretRangeFromPoint?.(x, y);
|
|
6019
6885
|
return range ? { node: range.startContainer, offset: range.startOffset } : null;
|
|
6020
6886
|
}
|
|
6021
|
-
function
|
|
6887
|
+
function parseResult2(json) {
|
|
6022
6888
|
try {
|
|
6023
6889
|
return JSON.parse(json);
|
|
6024
6890
|
} catch {
|
|
@@ -8251,7 +9117,7 @@ function addToRegistry(args) {
|
|
|
8251
9117
|
};
|
|
8252
9118
|
}
|
|
8253
9119
|
var honeyPotFix = makeHoneyPotFix();
|
|
8254
|
-
var
|
|
9120
|
+
var adapter2 = makeAdapter({
|
|
8255
9121
|
typeKey: "element",
|
|
8256
9122
|
defaultDropEffect: "move",
|
|
8257
9123
|
mount: function mount(api2) {
|
|
@@ -8340,8 +9206,8 @@ var adapter = makeAdapter({
|
|
|
8340
9206
|
},
|
|
8341
9207
|
onPostDispatch: honeyPotFix.getOnPostDispatch()
|
|
8342
9208
|
});
|
|
8343
|
-
var dropTargetForElements =
|
|
8344
|
-
var monitorForElements =
|
|
9209
|
+
var dropTargetForElements = adapter2.dropTarget;
|
|
9210
|
+
var monitorForElements = adapter2.monitor;
|
|
8345
9211
|
function draggable(args) {
|
|
8346
9212
|
if (true) {
|
|
8347
9213
|
if (args.dragHandle && !args.element.contains(args.dragHandle)) {
|
|
@@ -8365,7 +9231,7 @@ function draggable(args) {
|
|
|
8365
9231
|
// this is because you *must* have a draggable element to start a drag
|
|
8366
9232
|
// but you _might_ not have any drop targets immediately
|
|
8367
9233
|
// (You might create drop targets async)
|
|
8368
|
-
|
|
9234
|
+
adapter2.registerUsage(),
|
|
8369
9235
|
addToRegistry(args),
|
|
8370
9236
|
addAttribute(args.element, {
|
|
8371
9237
|
attribute: "draggable",
|
|
@@ -9812,22 +10678,55 @@ function selectionSpanIn(block) {
|
|
|
9812
10678
|
const span = trimmedSpan(block, { start: Math.min(start2, end), length: Math.abs(end - start2) });
|
|
9813
10679
|
return span.length > 0 ? span : null;
|
|
9814
10680
|
}
|
|
9815
|
-
function
|
|
9816
|
-
|
|
9817
|
-
|
|
9818
|
-
|
|
9819
|
-
const range = document.createRange();
|
|
10681
|
+
function contentRangeIn(el, start2, length) {
|
|
10682
|
+
if (!el.isConnected) return null;
|
|
10683
|
+
const doc = el.ownerDocument;
|
|
10684
|
+
const range = doc.createRange();
|
|
9820
10685
|
const from = contentPositionIn(el, start2);
|
|
9821
10686
|
const to = contentPositionIn(el, start2 + length);
|
|
9822
10687
|
try {
|
|
9823
10688
|
range.setStart(from.node, from.offset);
|
|
9824
10689
|
range.setEnd(to.node, to.offset);
|
|
9825
10690
|
} catch {
|
|
9826
|
-
return;
|
|
10691
|
+
return null;
|
|
9827
10692
|
}
|
|
10693
|
+
return range;
|
|
10694
|
+
}
|
|
10695
|
+
function selectRange(el, start2, length) {
|
|
10696
|
+
const sel = typeof window !== "undefined" ? window.getSelection() : null;
|
|
10697
|
+
if (!sel || !el.isConnected) return;
|
|
10698
|
+
el.focus();
|
|
10699
|
+
const range = contentRangeIn(el, start2, length);
|
|
10700
|
+
if (!range) return;
|
|
9828
10701
|
sel.removeAllRanges();
|
|
9829
10702
|
sel.addRange(range);
|
|
9830
10703
|
}
|
|
10704
|
+
var FIND_HIGHLIGHT = "docxodus-find";
|
|
10705
|
+
var FIND_HIGHLIGHT_ACTIVE = "docxodus-find-active";
|
|
10706
|
+
var FIND_PAINT_LIMIT = 500;
|
|
10707
|
+
var findHighlightStyledDocuments = /* @__PURE__ */ new WeakSet();
|
|
10708
|
+
var findPaintOwner = null;
|
|
10709
|
+
function ensureFindHighlightStyles(doc) {
|
|
10710
|
+
if (findHighlightStyledDocuments.has(doc)) return;
|
|
10711
|
+
findHighlightStyledDocuments.add(doc);
|
|
10712
|
+
const style = doc.createElement("style");
|
|
10713
|
+
style.dataset.docxodusFindHighlight = "true";
|
|
10714
|
+
style.textContent = `
|
|
10715
|
+
::highlight(${FIND_HIGHLIGHT}) { background-color: rgba(15, 118, 110, .16); }
|
|
10716
|
+
::highlight(${FIND_HIGHLIGHT_ACTIVE}) { background-color: rgba(15, 118, 110, .38); }
|
|
10717
|
+
`;
|
|
10718
|
+
(doc.head ?? doc.documentElement).appendChild(style);
|
|
10719
|
+
}
|
|
10720
|
+
function highlightRegistry() {
|
|
10721
|
+
if (typeof CSS === "undefined") return null;
|
|
10722
|
+
const registry = CSS.highlights;
|
|
10723
|
+
const ctor = globalThis.Highlight;
|
|
10724
|
+
return registry && typeof ctor === "function" ? registry : null;
|
|
10725
|
+
}
|
|
10726
|
+
function makeHighlight(ranges) {
|
|
10727
|
+
const ctor = globalThis.Highlight;
|
|
10728
|
+
return new ctor(...ranges);
|
|
10729
|
+
}
|
|
9831
10730
|
function inBorderWrapper(el) {
|
|
9832
10731
|
const style = el.parentElement?.getAttribute("style") ?? "";
|
|
9833
10732
|
return /border-(top|bottom|left|right):\s*(?!none)[^;]+/i.test(style);
|
|
@@ -10198,6 +11097,7 @@ var DocxEditor = class _DocxEditor {
|
|
|
10198
11097
|
document.removeEventListener("mouseup", this.onMouseUp, true);
|
|
10199
11098
|
}
|
|
10200
11099
|
this.clearDragSelection();
|
|
11100
|
+
this.clearFindMatches();
|
|
10201
11101
|
this.teardownBlockDrag();
|
|
10202
11102
|
this.gutter?.dispose();
|
|
10203
11103
|
this.gutter = null;
|
|
@@ -12596,15 +13496,65 @@ var DocxEditor = class _DocxEditor {
|
|
|
12596
13496
|
}
|
|
12597
13497
|
return out;
|
|
12598
13498
|
}
|
|
12599
|
-
/**
|
|
13499
|
+
/**
|
|
13500
|
+
* Select a match, focus its block and scroll it into view — the caret lands on the hit, so
|
|
13501
|
+
* typing continues in the document. A find box that is still being typed into wants
|
|
13502
|
+
* `showFindMatches` instead; this is the commit step (closing the bar, jumping in to edit).
|
|
13503
|
+
*/
|
|
12600
13504
|
selectMatch(match) {
|
|
12601
13505
|
if (!match.block.isConnected) return;
|
|
13506
|
+
this.clearFindMatches();
|
|
12602
13507
|
this.activeBlock = match.block;
|
|
12603
13508
|
selectRange(match.block, match.start, match.length);
|
|
12604
13509
|
match.block.scrollIntoView({ block: "center", behavior: "smooth" });
|
|
12605
13510
|
}
|
|
12606
|
-
/**
|
|
12607
|
-
|
|
13511
|
+
/**
|
|
13512
|
+
* Paint `matches` and scroll the one at `activeIndex` into view WITHOUT moving focus or the
|
|
13513
|
+
* caret. This is what a find field calls on every keystroke: the document shows where the hits
|
|
13514
|
+
* are and rides to the current one, while the keyboard stays in the search box.
|
|
13515
|
+
*
|
|
13516
|
+
* Falls back to the document selection where the CSS Custom Highlight API is missing — still
|
|
13517
|
+
* without focusing the block, so the next character typed goes to the search field either way.
|
|
13518
|
+
*/
|
|
13519
|
+
showFindMatches(matches, activeIndex) {
|
|
13520
|
+
const active = matches[activeIndex];
|
|
13521
|
+
this.clearFindMatches();
|
|
13522
|
+
if (!active || !active.block.isConnected) return;
|
|
13523
|
+
this.activeBlock = active.block;
|
|
13524
|
+
const activeRange = contentRangeIn(active.block, active.start, active.length);
|
|
13525
|
+
const registry = highlightRegistry();
|
|
13526
|
+
if (registry && activeRange) {
|
|
13527
|
+
ensureFindHighlightStyles(this.container.ownerDocument);
|
|
13528
|
+
const rest = [];
|
|
13529
|
+
for (let i = 0; i < matches.length && rest.length < FIND_PAINT_LIMIT; i++) {
|
|
13530
|
+
if (i === activeIndex) continue;
|
|
13531
|
+
const range = contentRangeIn(matches[i].block, matches[i].start, matches[i].length);
|
|
13532
|
+
if (range) rest.push(range);
|
|
13533
|
+
}
|
|
13534
|
+
if (rest.length > 0) registry.set(FIND_HIGHLIGHT, makeHighlight(rest));
|
|
13535
|
+
registry.set(FIND_HIGHLIGHT_ACTIVE, makeHighlight([activeRange]));
|
|
13536
|
+
findPaintOwner = this;
|
|
13537
|
+
} else if (activeRange) {
|
|
13538
|
+
const sel = this.container.ownerDocument.defaultView?.getSelection();
|
|
13539
|
+
sel?.removeAllRanges();
|
|
13540
|
+
sel?.addRange(activeRange);
|
|
13541
|
+
}
|
|
13542
|
+
active.block.scrollIntoView({ block: "center", behavior: "smooth" });
|
|
13543
|
+
}
|
|
13544
|
+
/** Drop the find painting (closing the find bar, or committing a match to the caret). */
|
|
13545
|
+
clearFindMatches() {
|
|
13546
|
+
if (findPaintOwner !== this) return;
|
|
13547
|
+
const registry = highlightRegistry();
|
|
13548
|
+
registry?.delete(FIND_HIGHLIGHT);
|
|
13549
|
+
registry?.delete(FIND_HIGHLIGHT_ACTIVE);
|
|
13550
|
+
findPaintOwner = null;
|
|
13551
|
+
}
|
|
13552
|
+
/**
|
|
13553
|
+
* Replace one match's text (formatting of the surrounding run is inherited). `focus: false`
|
|
13554
|
+
* leaves the keyboard where it is — a Replace button pressed from the find bar must not drag
|
|
13555
|
+
* the caret into the document mid-search.
|
|
13556
|
+
*/
|
|
13557
|
+
replaceMatch(match, replacement, options = {}) {
|
|
12608
13558
|
const block = match.block;
|
|
12609
13559
|
if (this.closed || !block.isConnected) return false;
|
|
12610
13560
|
const unid = block.getAttribute("data-anchor");
|
|
@@ -12625,7 +13575,7 @@ var DocxEditor = class _DocxEditor {
|
|
|
12625
13575
|
);
|
|
12626
13576
|
if (!res.success) return false;
|
|
12627
13577
|
const fresh = this.swapBlock(block, unid, res.modified?.[0]);
|
|
12628
|
-
if (fresh) selectRange(fresh, span.start, replacement.length);
|
|
13578
|
+
if (fresh && options.focus !== false) selectRange(fresh, span.start, replacement.length);
|
|
12629
13579
|
return true;
|
|
12630
13580
|
}
|
|
12631
13581
|
/** Replace every occurrence; returns how many were replaced. */
|
|
@@ -12644,7 +13594,11 @@ var DocxEditor = class _DocxEditor {
|
|
|
12644
13594
|
for (const m of list.slice().sort((a, b) => b.start - a.start)) {
|
|
12645
13595
|
if (!current.isConnected) break;
|
|
12646
13596
|
const before = current;
|
|
12647
|
-
if (this.replaceMatch(
|
|
13597
|
+
if (this.replaceMatch(
|
|
13598
|
+
{ block: current, start: m.start, length: m.length },
|
|
13599
|
+
replacement,
|
|
13600
|
+
{ focus: options.focus }
|
|
13601
|
+
)) {
|
|
12648
13602
|
count++;
|
|
12649
13603
|
current = this.activeBlock && this.activeBlock !== before ? this.activeBlock : current;
|
|
12650
13604
|
}
|
|
@@ -13326,7 +14280,7 @@ function ensureCommentGutterStyles(doc) {
|
|
|
13326
14280
|
}
|
|
13327
14281
|
|
|
13328
14282
|
// src/ribbon-chrome.ts
|
|
13329
|
-
var RIBBON_STYLE_VERSION = "
|
|
14283
|
+
var RIBBON_STYLE_VERSION = "11";
|
|
13330
14284
|
var RIBBON_STYLE_ATTR = "data-docxodus-ribbon-styles";
|
|
13331
14285
|
var COMMENT_GUTTER_WIDTH = 264;
|
|
13332
14286
|
var RIBBON_CSS = `
|
|
@@ -13759,9 +14713,11 @@ var RIBBON_CSS = `
|
|
|
13759
14713
|
background: linear-gradient(to top, var(--dxr-desk), transparent);
|
|
13760
14714
|
}
|
|
13761
14715
|
.dxr[data-chrome] .dxr-surface { position: relative; margin: 26px auto; padding: 0 16px 96px; }
|
|
13762
|
-
/*
|
|
13763
|
-
|
|
13764
|
-
.dxr[data-chrome="full"] .dxr-surface[data-comments="on"]
|
|
14716
|
+
/* Reserve the markup area only while a visible comment or draft needs it. The gutter owns
|
|
14717
|
+
its empty/hidden state, so ordinary documents stay centered without disabling comments. */
|
|
14718
|
+
.dxr[data-chrome="full"] .dxr-surface[data-comments="on"]:has(> .docx-comment-gutter:not([data-empty]):not([hidden])) {
|
|
14719
|
+
padding-right: calc(var(--dxr-gutter) + 8px);
|
|
14720
|
+
}
|
|
13765
14721
|
.dxr-surface [contenteditable="true"]:focus {
|
|
13766
14722
|
outline: 2px solid var(--dxr-accent);
|
|
13767
14723
|
outline-offset: 2px;
|
|
@@ -15277,7 +16233,8 @@ var RibbonSurface = class {
|
|
|
15277
16233
|
comments: this.options.comments,
|
|
15278
16234
|
commentAuthor: this.author
|
|
15279
16235
|
});
|
|
15280
|
-
this.
|
|
16236
|
+
const saveButton = this.control("save");
|
|
16237
|
+
if (saveButton) saveButton.disabled = false;
|
|
15281
16238
|
this.require("ribbon").setAttribute("aria-disabled", "false");
|
|
15282
16239
|
this.setState("ready");
|
|
15283
16240
|
this.setStatus(`Rendered in ${Math.round(performance.now() - started)} ms`);
|
|
@@ -15912,11 +16869,18 @@ var RibbonSurface = class {
|
|
|
15912
16869
|
if (!this.live || !text.value) return;
|
|
15913
16870
|
let count = 0;
|
|
15914
16871
|
this.run("replace all", () => {
|
|
15915
|
-
count = this.live.replaceAll(text.value, replace.value, {
|
|
16872
|
+
count = this.live.replaceAll(text.value, replace.value, {
|
|
16873
|
+
matchCase: matchCase.checked,
|
|
16874
|
+
focus: false
|
|
16875
|
+
});
|
|
15916
16876
|
});
|
|
15917
16877
|
this.setStatus(`Replaced ${count} occurrence${count === 1 ? "" : "s"}`);
|
|
15918
16878
|
this.refreshFind(true);
|
|
15919
16879
|
});
|
|
16880
|
+
for (const id of ["findprev", "findnext", "replaceone", "replaceall"]) {
|
|
16881
|
+
const button = this.control(id);
|
|
16882
|
+
if (button) this.keepSelection(button);
|
|
16883
|
+
}
|
|
15920
16884
|
bar.hidden = true;
|
|
15921
16885
|
}
|
|
15922
16886
|
openFindBar(withReplace) {
|
|
@@ -15927,28 +16891,44 @@ var RibbonSurface = class {
|
|
|
15927
16891
|
const text = this.require("findtext");
|
|
15928
16892
|
text.focus();
|
|
15929
16893
|
text.select();
|
|
15930
|
-
this.refreshFind(
|
|
16894
|
+
this.refreshFind(true);
|
|
15931
16895
|
}
|
|
16896
|
+
/**
|
|
16897
|
+
* Close the bar and hand the current hit over to the caret: the document is where the user is
|
|
16898
|
+
* going next, and Word leaves the insertion point on the match it stopped at. Until this runs
|
|
16899
|
+
* the match is only PAINTED (see `showFindMatches`), so the search field keeps the keyboard for
|
|
16900
|
+
* as long as the bar is open.
|
|
16901
|
+
*/
|
|
15932
16902
|
closeFindBar() {
|
|
15933
16903
|
const bar = this.control("findbar");
|
|
15934
16904
|
if (bar) bar.hidden = true;
|
|
16905
|
+
const match = this.findMatches[this.findIndex];
|
|
16906
|
+
if (match) this.live?.selectMatch(match);
|
|
16907
|
+
else this.live?.clearFindMatches();
|
|
15935
16908
|
this.findMatches = [];
|
|
15936
16909
|
this.findIndex = -1;
|
|
15937
16910
|
}
|
|
15938
|
-
|
|
16911
|
+
/**
|
|
16912
|
+
* Re-scan for the current query. `show` paints the hits and rides to the first one — it never
|
|
16913
|
+
* focuses the document, so typing the second character of a query that already matched goes on
|
|
16914
|
+
* refining the search instead of being typed into the document.
|
|
16915
|
+
*/
|
|
16916
|
+
refreshFind(show) {
|
|
15939
16917
|
const text = this.require("findtext");
|
|
15940
16918
|
const matchCase = this.require("findcase").checked;
|
|
15941
16919
|
this.findMatches = this.live && text.value ? this.live.find(text.value, { matchCase }) : [];
|
|
15942
16920
|
this.findIndex = this.findMatches.length > 0 ? 0 : -1;
|
|
15943
16921
|
this.updateFindCount();
|
|
15944
|
-
if (
|
|
16922
|
+
if (!show) return;
|
|
16923
|
+
if (this.findIndex >= 0) this.live?.showFindMatches(this.findMatches, this.findIndex);
|
|
16924
|
+
else this.live?.clearFindMatches();
|
|
15945
16925
|
}
|
|
15946
16926
|
stepFind(direction) {
|
|
15947
16927
|
if (!this.live) return;
|
|
15948
16928
|
if (this.findMatches.length === 0) this.refreshFind(false);
|
|
15949
16929
|
if (this.findMatches.length === 0) return;
|
|
15950
16930
|
this.findIndex = (this.findIndex + direction + this.findMatches.length) % this.findMatches.length;
|
|
15951
|
-
this.live.
|
|
16931
|
+
this.live.showFindMatches(this.findMatches, this.findIndex);
|
|
15952
16932
|
this.updateFindCount();
|
|
15953
16933
|
}
|
|
15954
16934
|
replaceCurrent() {
|
|
@@ -15957,12 +16937,12 @@ var RibbonSurface = class {
|
|
|
15957
16937
|
const match = this.findMatches[this.findIndex];
|
|
15958
16938
|
if (!match) return;
|
|
15959
16939
|
const replacement = this.require("replacetext").value;
|
|
15960
|
-
this.run("replace", () => this.live.replaceMatch(match, replacement));
|
|
16940
|
+
this.run("replace", () => this.live.replaceMatch(match, replacement, { focus: false }));
|
|
15961
16941
|
const keep = this.findIndex;
|
|
15962
16942
|
this.refreshFind(false);
|
|
15963
16943
|
if (this.findMatches.length > 0) {
|
|
15964
16944
|
this.findIndex = Math.min(keep, this.findMatches.length - 1);
|
|
15965
|
-
this.live.
|
|
16945
|
+
this.live.showFindMatches(this.findMatches, this.findIndex);
|
|
15966
16946
|
this.updateFindCount();
|
|
15967
16947
|
}
|
|
15968
16948
|
}
|
|
@@ -16393,6 +17373,16 @@ var RibbonSurface = class {
|
|
|
16393
17373
|
};
|
|
16394
17374
|
|
|
16395
17375
|
// src/index.ts
|
|
17376
|
+
function openDocxHistory(storage) {
|
|
17377
|
+
const bridge = ensureInitialized().HistoryBridge;
|
|
17378
|
+
if (!bridge) throw new Error("This WASM build does not include history bindings.");
|
|
17379
|
+
return new DocxHistoryClient(bridge, storage);
|
|
17380
|
+
}
|
|
17381
|
+
function openDocxHistoryArchive(bytes) {
|
|
17382
|
+
const bridge = ensureInitialized().HistoryBridge;
|
|
17383
|
+
if (!bridge) throw new Error("This WASM build does not include history bindings.");
|
|
17384
|
+
return DocxHistoryArchive.open(bridge, bytes);
|
|
17385
|
+
}
|
|
16396
17386
|
function openDocxSession2(bytes, settings) {
|
|
16397
17387
|
const wasm = ensureInitialized();
|
|
16398
17388
|
return openDocxSession(bytes, wasm, settings);
|
|
@@ -16447,14 +17437,16 @@ async function tryLoadFromPath(basePath) {
|
|
|
16447
17437
|
/* @vite-ignore */
|
|
16448
17438
|
dotnetPath
|
|
16449
17439
|
);
|
|
16450
|
-
const { getAssemblyExports, getConfig } = await dotnet.withDiagnosticTracing(false).create();
|
|
17440
|
+
const { getAssemblyExports, getConfig, setModuleImports } = await dotnet.withDiagnosticTracing(false).create();
|
|
17441
|
+
installHistoryStorageImports(setModuleImports);
|
|
16451
17442
|
const config = getConfig();
|
|
16452
17443
|
const exports = await getAssemblyExports(config.mainAssemblyName);
|
|
16453
17444
|
wasmExports = {
|
|
16454
17445
|
DocumentConverter: exports.DocxodusWasm.DocumentConverter,
|
|
16455
17446
|
DocumentComparer: exports.DocxodusWasm.DocumentComparer,
|
|
16456
17447
|
DocxDiffBridge: exports.DocxodusWasm.DocxDiffBridge,
|
|
16457
|
-
DocxSessionBridge: exports.DocxodusWasm.DocxSessionBridge
|
|
17448
|
+
DocxSessionBridge: exports.DocxodusWasm.DocxSessionBridge,
|
|
17449
|
+
HistoryBridge: exports.DocxodusWasm.HistoryBridge
|
|
16458
17450
|
};
|
|
16459
17451
|
return true;
|
|
16460
17452
|
} catch {
|
|
@@ -17828,6 +18820,20 @@ async function toDocumentBytes(source) {
|
|
|
17828
18820
|
}
|
|
17829
18821
|
throw new Error("Docxodus embed: unsupported document source");
|
|
17830
18822
|
}
|
|
18823
|
+
function scopedDocumentFragment(mount2, html) {
|
|
18824
|
+
const parsed = new DOMParser().parseFromString(html, "text/html");
|
|
18825
|
+
const fragment = document.createDocumentFragment();
|
|
18826
|
+
parsed.querySelectorAll("style").forEach((sourceStyle) => {
|
|
18827
|
+
const style = document.createElement("style");
|
|
18828
|
+
style.textContent = sourceStyle.textContent;
|
|
18829
|
+
scopeStyleElement(style, mount2.selector);
|
|
18830
|
+
fragment.appendChild(style);
|
|
18831
|
+
});
|
|
18832
|
+
const body = document.createElement("template");
|
|
18833
|
+
body.innerHTML = parsed.body.innerHTML;
|
|
18834
|
+
fragment.appendChild(body.content);
|
|
18835
|
+
return fragment;
|
|
18836
|
+
}
|
|
17831
18837
|
async function createViewer(container, source, options = {}) {
|
|
17832
18838
|
const el = resolveContainer2(container);
|
|
17833
18839
|
const { wasmBasePath: wasmBasePath2, ...conversion } = options;
|
|
@@ -17840,18 +18846,7 @@ async function createViewer(container, source, options = {}) {
|
|
|
17840
18846
|
renderFootnotesAndEndnotes: true,
|
|
17841
18847
|
...opts
|
|
17842
18848
|
});
|
|
17843
|
-
|
|
17844
|
-
const fragment = document.createDocumentFragment();
|
|
17845
|
-
parsed.querySelectorAll("style").forEach((sourceStyle) => {
|
|
17846
|
-
const style = document.createElement("style");
|
|
17847
|
-
style.textContent = sourceStyle.textContent;
|
|
17848
|
-
scopeStyleElement(style, mount2.selector);
|
|
17849
|
-
fragment.appendChild(style);
|
|
17850
|
-
});
|
|
17851
|
-
const body = document.createElement("template");
|
|
17852
|
-
body.innerHTML = parsed.body.innerHTML;
|
|
17853
|
-
fragment.appendChild(body.content);
|
|
17854
|
-
mount2.root.replaceChildren(fragment);
|
|
18849
|
+
mount2.root.replaceChildren(scopedDocumentFragment(mount2, lastHtml));
|
|
17855
18850
|
};
|
|
17856
18851
|
await render(source, conversion);
|
|
17857
18852
|
return {
|
|
@@ -17865,6 +18860,111 @@ async function createViewer(container, source, options = {}) {
|
|
|
17865
18860
|
}
|
|
17866
18861
|
};
|
|
17867
18862
|
}
|
|
18863
|
+
async function createHistoryViewer(container, history, documentId, options = {}) {
|
|
18864
|
+
const el = resolveContainer2(container);
|
|
18865
|
+
const { wasmBasePath: wasmBasePath2, maxEntriesToScan = 1e4, ...conversion } = options;
|
|
18866
|
+
if (!Number.isSafeInteger(maxEntriesToScan) || maxEntriesToScan <= 0 || maxEntriesToScan > 2147483647)
|
|
18867
|
+
throw new RangeError("Invalid history scan budget.");
|
|
18868
|
+
await ensureWasm(wasmBasePath2);
|
|
18869
|
+
const mount2 = createScopedMount(el);
|
|
18870
|
+
let accepted = null;
|
|
18871
|
+
let acceptedBytes = null;
|
|
18872
|
+
let displayedBytes = null;
|
|
18873
|
+
let shownSequence = "0";
|
|
18874
|
+
let following = true;
|
|
18875
|
+
let closed = false;
|
|
18876
|
+
let html = "";
|
|
18877
|
+
let queue = Promise.resolve();
|
|
18878
|
+
const clone = (value) => JSON.parse(JSON.stringify(value));
|
|
18879
|
+
const assertOpen = () => {
|
|
18880
|
+
if (closed) throw new DocxHistoryError("Closed", "History viewer is destroyed.");
|
|
18881
|
+
};
|
|
18882
|
+
const enqueue = (action) => {
|
|
18883
|
+
const result = queue.then(() => {
|
|
18884
|
+
assertOpen();
|
|
18885
|
+
return action();
|
|
18886
|
+
});
|
|
18887
|
+
queue = result.then(() => void 0, () => void 0);
|
|
18888
|
+
return result;
|
|
18889
|
+
};
|
|
18890
|
+
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;
|
|
18891
|
+
const paint = async (bytes, sequence) => {
|
|
18892
|
+
const nextHtml = await convertDocxToHtml(bytes, { renderFootnotesAndEndnotes: true, ...conversion });
|
|
18893
|
+
assertOpen();
|
|
18894
|
+
const fragment = scopedDocumentFragment(mount2, nextHtml);
|
|
18895
|
+
mount2.root.replaceChildren(fragment);
|
|
18896
|
+
html = nextHtml;
|
|
18897
|
+
displayedBytes = bytes;
|
|
18898
|
+
shownSequence = sequence;
|
|
18899
|
+
};
|
|
18900
|
+
const refresh = async (resume = false) => {
|
|
18901
|
+
const update = await history.readChangesSince(documentId, accepted?.view.head ?? null, maxEntriesToScan);
|
|
18902
|
+
assertOpen();
|
|
18903
|
+
const duplicate = accepted !== null && sameHead(accepted.view.head, update.view.head);
|
|
18904
|
+
const bytes = duplicate ? acceptedBytes : await history.exportVersion(documentId, update.view.version.id);
|
|
18905
|
+
assertOpen();
|
|
18906
|
+
const wantLive = following || resume;
|
|
18907
|
+
const changedContent = accepted === null || accepted.view.state.snapshot.contentDigest.value !== update.view.state.snapshot.contentDigest.value || accepted.view.state.epoch !== update.view.state.epoch;
|
|
18908
|
+
if (wantLive && (changedContent || !following)) await paint(bytes, update.view.state.sequence);
|
|
18909
|
+
else if (wantLive) {
|
|
18910
|
+
displayedBytes = bytes;
|
|
18911
|
+
shownSequence = update.view.state.sequence;
|
|
18912
|
+
}
|
|
18913
|
+
assertOpen();
|
|
18914
|
+
accepted = update;
|
|
18915
|
+
acceptedBytes = bytes;
|
|
18916
|
+
if (wantLive) following = true;
|
|
18917
|
+
return clone(update);
|
|
18918
|
+
};
|
|
18919
|
+
const showSequence = async (sequence) => {
|
|
18920
|
+
const bytes = await history.materialize(documentId, sequence, maxEntriesToScan);
|
|
18921
|
+
assertOpen();
|
|
18922
|
+
await paint(bytes, sequence);
|
|
18923
|
+
following = false;
|
|
18924
|
+
};
|
|
18925
|
+
const viewer = {
|
|
18926
|
+
element: el,
|
|
18927
|
+
get html() {
|
|
18928
|
+
return html;
|
|
18929
|
+
},
|
|
18930
|
+
get head() {
|
|
18931
|
+
assertOpen();
|
|
18932
|
+
return clone(accepted.view.head);
|
|
18933
|
+
},
|
|
18934
|
+
get sequence() {
|
|
18935
|
+
return shownSequence;
|
|
18936
|
+
},
|
|
18937
|
+
get following() {
|
|
18938
|
+
return following;
|
|
18939
|
+
},
|
|
18940
|
+
refresh: () => enqueue(() => refresh()),
|
|
18941
|
+
showSequence: (sequence) => enqueue(() => showSequence(sequence)),
|
|
18942
|
+
showTime: (cutoff) => enqueue(async () => {
|
|
18943
|
+
const sequence = await history.resolveSequenceAtTime(documentId, cutoff, maxEntriesToScan);
|
|
18944
|
+
assertOpen();
|
|
18945
|
+
await showSequence(sequence);
|
|
18946
|
+
}),
|
|
18947
|
+
resume: () => enqueue(() => refresh(true)),
|
|
18948
|
+
exportDisplayed: () => {
|
|
18949
|
+
assertOpen();
|
|
18950
|
+
return displayedBytes.slice();
|
|
18951
|
+
},
|
|
18952
|
+
destroy: () => {
|
|
18953
|
+
if (closed) return;
|
|
18954
|
+
closed = true;
|
|
18955
|
+
acceptedBytes = null;
|
|
18956
|
+
displayedBytes = null;
|
|
18957
|
+
mount2.root.remove();
|
|
18958
|
+
}
|
|
18959
|
+
};
|
|
18960
|
+
try {
|
|
18961
|
+
await viewer.refresh();
|
|
18962
|
+
return viewer;
|
|
18963
|
+
} catch (error) {
|
|
18964
|
+
viewer.destroy();
|
|
18965
|
+
throw error;
|
|
18966
|
+
}
|
|
18967
|
+
}
|
|
17868
18968
|
async function createEditor(container, source, options = {}) {
|
|
17869
18969
|
const el = resolveContainer2(container);
|
|
17870
18970
|
const { wasmBasePath: wasmBasePath2, ...editorOptions } = options;
|
|
@@ -17946,8 +19046,15 @@ export {
|
|
|
17946
19046
|
DocxDiffFormatComparison,
|
|
17947
19047
|
DocxDiffRevisionGranularity,
|
|
17948
19048
|
DocxEditor,
|
|
19049
|
+
DocxHistoryArchive,
|
|
19050
|
+
DocxHistoryClient,
|
|
19051
|
+
DocxHistoryDocument,
|
|
19052
|
+
DocxHistoryError,
|
|
19053
|
+
DocxHistoryReader,
|
|
17949
19054
|
DocxSession,
|
|
17950
19055
|
EmptyParagraphMode,
|
|
19056
|
+
HistoryCheckpoints,
|
|
19057
|
+
MAX_HISTORY_ARCHIVE_BYTES,
|
|
17951
19058
|
PaginationEngine,
|
|
17952
19059
|
PaginationMode,
|
|
17953
19060
|
PlaceholderKinds,
|
|
@@ -17971,6 +19078,8 @@ export {
|
|
|
17971
19078
|
createBlankDocx,
|
|
17972
19079
|
createEditor,
|
|
17973
19080
|
createExternalAnnotationSet,
|
|
19081
|
+
createHistoryViewer,
|
|
19082
|
+
createMemoryHistoryStorage,
|
|
17974
19083
|
createRibbonEditor,
|
|
17975
19084
|
createUnavailablePageMap,
|
|
17976
19085
|
createViewer,
|
|
@@ -18004,15 +19113,21 @@ export {
|
|
|
18004
19113
|
getVersion,
|
|
18005
19114
|
getWasmExports,
|
|
18006
19115
|
hasAnnotations,
|
|
19116
|
+
historyControlError,
|
|
18007
19117
|
initialize,
|
|
19118
|
+
installHistoryStorageImports,
|
|
18008
19119
|
isDeletion,
|
|
18009
19120
|
isFormatChange,
|
|
18010
19121
|
isInitialized,
|
|
18011
19122
|
isInsertion,
|
|
18012
19123
|
isMove,
|
|
19124
|
+
mountHistoryControls,
|
|
18013
19125
|
mountRibbon,
|
|
18014
19126
|
navigateToPageCitation,
|
|
19127
|
+
openDocxHistory,
|
|
19128
|
+
openDocxHistoryArchive,
|
|
18015
19129
|
openDocxSession2 as openDocxSession,
|
|
19130
|
+
openIndexedDbHistoryStore,
|
|
18016
19131
|
paginateHtml,
|
|
18017
19132
|
parseSectionDimensions,
|
|
18018
19133
|
projectAnnotationsOntoHtml,
|