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.bundle.js
CHANGED
|
@@ -2038,6 +2038,288 @@ 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 equalHead = (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 (!equalHead(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
|
+
|
|
2041
2323
|
// src/page-number-format.ts
|
|
2042
2324
|
var ROMAN_ONES = ["", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix"];
|
|
2043
2325
|
var ROMAN_TENS = ["", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc"];
|
|
@@ -5487,11 +5769,11 @@ var HeaderFooterRegion = class {
|
|
|
5487
5769
|
if (!this.partUriFor(which, "default")) this.seedStory(which, "default");
|
|
5488
5770
|
if (!this.partUriFor(which, kind)) this.seedStory(which, kind);
|
|
5489
5771
|
}
|
|
5490
|
-
const res =
|
|
5772
|
+
const res = parseResult2(this.bridge.EnsureHeaderFooterVisible(this.handle, this.bodyAnchorId, kind));
|
|
5491
5773
|
if (!res.success) return false;
|
|
5492
5774
|
} else {
|
|
5493
5775
|
if (!this.bridge.SetHeaderFooterKindEnabled) return false;
|
|
5494
|
-
const res =
|
|
5776
|
+
const res = parseResult2(this.bridge.SetHeaderFooterKindEnabled(this.handle, this.bodyAnchorId, kind, false));
|
|
5495
5777
|
if (!res.success) return false;
|
|
5496
5778
|
for (const which of ["header", "footer"]) {
|
|
5497
5779
|
if (this.kinds[which] === kind) this.kinds[which] = "default";
|
|
@@ -5516,7 +5798,7 @@ var HeaderFooterRegion = class {
|
|
|
5516
5798
|
* Deliberately a PLAIN field (no `\*` switch), exactly as Word inserts one, so it follows the
|
|
5517
5799
|
* section's page-number format — see {@link setPageNumbering}. */
|
|
5518
5800
|
insertPageNumber(which, anchorId, field) {
|
|
5519
|
-
const res =
|
|
5801
|
+
const res = parseResult2(this.bridge.InsertPageNumberField(this.handle, anchorId, field, ""));
|
|
5520
5802
|
if (!res.success) return false;
|
|
5521
5803
|
this.refresh(which);
|
|
5522
5804
|
return true;
|
|
@@ -5539,7 +5821,7 @@ var HeaderFooterRegion = class {
|
|
|
5539
5821
|
/** Set this section's page numbering (`w:pgNumType`) — Word's *Format Page Numbers…*. */
|
|
5540
5822
|
setPageNumbering(op) {
|
|
5541
5823
|
if (!this.bodyAnchorId) return false;
|
|
5542
|
-
const res =
|
|
5824
|
+
const res = parseResult2(this.bridge.SetPageNumbering(this.handle, this.bodyAnchorId, JSON.stringify(op)));
|
|
5543
5825
|
if (!res.success) return false;
|
|
5544
5826
|
this.reloadSection();
|
|
5545
5827
|
return true;
|
|
@@ -5551,7 +5833,7 @@ var HeaderFooterRegion = class {
|
|
|
5551
5833
|
/** Remove this section's page-numbering start/format. */
|
|
5552
5834
|
clearPageNumbering() {
|
|
5553
5835
|
if (!this.bodyAnchorId) return false;
|
|
5554
|
-
const res =
|
|
5836
|
+
const res = parseResult2(this.bridge.ClearPageNumbering(this.handle, this.bodyAnchorId));
|
|
5555
5837
|
if (!res.success) return false;
|
|
5556
5838
|
this.reloadSection();
|
|
5557
5839
|
return true;
|
|
@@ -5862,7 +6144,7 @@ var HeaderFooterRegion = class {
|
|
|
5862
6144
|
seedStory(which, kind) {
|
|
5863
6145
|
if (!this.bodyAnchorId) return;
|
|
5864
6146
|
const set = which === "header" ? this.bridge.SetHeaderText : this.bridge.SetFooterText;
|
|
5865
|
-
const res =
|
|
6147
|
+
const res = parseResult2(set(this.handle, this.bodyAnchorId, kind, ""));
|
|
5866
6148
|
if (!res.success) return;
|
|
5867
6149
|
this.callbacks.refreshAnchorMap();
|
|
5868
6150
|
this.sectionInfo = this.readSectionInfo(this.bodyAnchorId);
|
|
@@ -6018,7 +6300,7 @@ function caretFromPoint(doc, x, y) {
|
|
|
6018
6300
|
const range = d.caretRangeFromPoint?.(x, y);
|
|
6019
6301
|
return range ? { node: range.startContainer, offset: range.startOffset } : null;
|
|
6020
6302
|
}
|
|
6021
|
-
function
|
|
6303
|
+
function parseResult2(json) {
|
|
6022
6304
|
try {
|
|
6023
6305
|
return JSON.parse(json);
|
|
6024
6306
|
} catch {
|
|
@@ -8251,7 +8533,7 @@ function addToRegistry(args) {
|
|
|
8251
8533
|
};
|
|
8252
8534
|
}
|
|
8253
8535
|
var honeyPotFix = makeHoneyPotFix();
|
|
8254
|
-
var
|
|
8536
|
+
var adapter2 = makeAdapter({
|
|
8255
8537
|
typeKey: "element",
|
|
8256
8538
|
defaultDropEffect: "move",
|
|
8257
8539
|
mount: function mount(api2) {
|
|
@@ -8340,8 +8622,8 @@ var adapter = makeAdapter({
|
|
|
8340
8622
|
},
|
|
8341
8623
|
onPostDispatch: honeyPotFix.getOnPostDispatch()
|
|
8342
8624
|
});
|
|
8343
|
-
var dropTargetForElements =
|
|
8344
|
-
var monitorForElements =
|
|
8625
|
+
var dropTargetForElements = adapter2.dropTarget;
|
|
8626
|
+
var monitorForElements = adapter2.monitor;
|
|
8345
8627
|
function draggable(args) {
|
|
8346
8628
|
if (true) {
|
|
8347
8629
|
if (args.dragHandle && !args.element.contains(args.dragHandle)) {
|
|
@@ -8365,7 +8647,7 @@ function draggable(args) {
|
|
|
8365
8647
|
// this is because you *must* have a draggable element to start a drag
|
|
8366
8648
|
// but you _might_ not have any drop targets immediately
|
|
8367
8649
|
// (You might create drop targets async)
|
|
8368
|
-
|
|
8650
|
+
adapter2.registerUsage(),
|
|
8369
8651
|
addToRegistry(args),
|
|
8370
8652
|
addAttribute(args.element, {
|
|
8371
8653
|
attribute: "draggable",
|
|
@@ -9448,6 +9730,12 @@ function blockPreviewText(unit) {
|
|
|
9448
9730
|
const text = (unit.textContent ?? "").trim().replace(/\s+/g, " ");
|
|
9449
9731
|
return text.length > 48 ? `${text.slice(0, 48)}\u2026` : text;
|
|
9450
9732
|
}
|
|
9733
|
+
var BLOCK_HANDLE_WIDTH = 26;
|
|
9734
|
+
var BLOCK_HANDLE_HEIGHT = 28;
|
|
9735
|
+
var BLOCK_HANDLE_GAP = 6;
|
|
9736
|
+
function clampToRange(value, low, high) {
|
|
9737
|
+
return Math.max(low, Math.min(value, high));
|
|
9738
|
+
}
|
|
9451
9739
|
function ensureBlockDragStyles(doc) {
|
|
9452
9740
|
if (blockDragStyledDocuments.has(doc)) return;
|
|
9453
9741
|
blockDragStyledDocuments.add(doc);
|
|
@@ -9806,22 +10094,55 @@ function selectionSpanIn(block) {
|
|
|
9806
10094
|
const span = trimmedSpan(block, { start: Math.min(start2, end), length: Math.abs(end - start2) });
|
|
9807
10095
|
return span.length > 0 ? span : null;
|
|
9808
10096
|
}
|
|
9809
|
-
function
|
|
9810
|
-
|
|
9811
|
-
|
|
9812
|
-
|
|
9813
|
-
const range = document.createRange();
|
|
10097
|
+
function contentRangeIn(el, start2, length) {
|
|
10098
|
+
if (!el.isConnected) return null;
|
|
10099
|
+
const doc = el.ownerDocument;
|
|
10100
|
+
const range = doc.createRange();
|
|
9814
10101
|
const from = contentPositionIn(el, start2);
|
|
9815
10102
|
const to = contentPositionIn(el, start2 + length);
|
|
9816
10103
|
try {
|
|
9817
10104
|
range.setStart(from.node, from.offset);
|
|
9818
10105
|
range.setEnd(to.node, to.offset);
|
|
9819
10106
|
} catch {
|
|
9820
|
-
return;
|
|
10107
|
+
return null;
|
|
9821
10108
|
}
|
|
10109
|
+
return range;
|
|
10110
|
+
}
|
|
10111
|
+
function selectRange(el, start2, length) {
|
|
10112
|
+
const sel = typeof window !== "undefined" ? window.getSelection() : null;
|
|
10113
|
+
if (!sel || !el.isConnected) return;
|
|
10114
|
+
el.focus();
|
|
10115
|
+
const range = contentRangeIn(el, start2, length);
|
|
10116
|
+
if (!range) return;
|
|
9822
10117
|
sel.removeAllRanges();
|
|
9823
10118
|
sel.addRange(range);
|
|
9824
10119
|
}
|
|
10120
|
+
var FIND_HIGHLIGHT = "docxodus-find";
|
|
10121
|
+
var FIND_HIGHLIGHT_ACTIVE = "docxodus-find-active";
|
|
10122
|
+
var FIND_PAINT_LIMIT = 500;
|
|
10123
|
+
var findHighlightStyledDocuments = /* @__PURE__ */ new WeakSet();
|
|
10124
|
+
var findPaintOwner = null;
|
|
10125
|
+
function ensureFindHighlightStyles(doc) {
|
|
10126
|
+
if (findHighlightStyledDocuments.has(doc)) return;
|
|
10127
|
+
findHighlightStyledDocuments.add(doc);
|
|
10128
|
+
const style = doc.createElement("style");
|
|
10129
|
+
style.dataset.docxodusFindHighlight = "true";
|
|
10130
|
+
style.textContent = `
|
|
10131
|
+
::highlight(${FIND_HIGHLIGHT}) { background-color: rgba(15, 118, 110, .16); }
|
|
10132
|
+
::highlight(${FIND_HIGHLIGHT_ACTIVE}) { background-color: rgba(15, 118, 110, .38); }
|
|
10133
|
+
`;
|
|
10134
|
+
(doc.head ?? doc.documentElement).appendChild(style);
|
|
10135
|
+
}
|
|
10136
|
+
function highlightRegistry() {
|
|
10137
|
+
if (typeof CSS === "undefined") return null;
|
|
10138
|
+
const registry = CSS.highlights;
|
|
10139
|
+
const ctor = globalThis.Highlight;
|
|
10140
|
+
return registry && typeof ctor === "function" ? registry : null;
|
|
10141
|
+
}
|
|
10142
|
+
function makeHighlight(ranges) {
|
|
10143
|
+
const ctor = globalThis.Highlight;
|
|
10144
|
+
return new ctor(...ranges);
|
|
10145
|
+
}
|
|
9825
10146
|
function inBorderWrapper(el) {
|
|
9826
10147
|
const style = el.parentElement?.getAttribute("style") ?? "";
|
|
9827
10148
|
return /border-(top|bottom|left|right):\s*(?!none)[^;]+/i.test(style);
|
|
@@ -9910,6 +10231,10 @@ var DocxEditor = class _DocxEditor {
|
|
|
9910
10231
|
this.blockMoveMenu = null;
|
|
9911
10232
|
this.blockMoveLive = null;
|
|
9912
10233
|
this.blockDragSource = null;
|
|
10234
|
+
/** Whether hover or focus currently wants a handle for `blockDragSource`.
|
|
10235
|
+
* Tracked apart from the handle's `display`, which the viewport clip also drives: a handle
|
|
10236
|
+
* withdrawn because its block scrolled out of view has to come back when it scrolls in. */
|
|
10237
|
+
this.blockHandleWanted = false;
|
|
9913
10238
|
this.blockDragCleanup = [];
|
|
9914
10239
|
/** Block boxes measured at drag start — see `BlockDropZone`. Empty when no drag is in flight. */
|
|
9915
10240
|
this.dropZones = [];
|
|
@@ -10188,6 +10513,7 @@ var DocxEditor = class _DocxEditor {
|
|
|
10188
10513
|
document.removeEventListener("mouseup", this.onMouseUp, true);
|
|
10189
10514
|
}
|
|
10190
10515
|
this.clearDragSelection();
|
|
10516
|
+
this.clearFindMatches();
|
|
10191
10517
|
this.teardownBlockDrag();
|
|
10192
10518
|
this.gutter?.dispose();
|
|
10193
10519
|
this.gutter = null;
|
|
@@ -10306,11 +10632,47 @@ var DocxEditor = class _DocxEditor {
|
|
|
10306
10632
|
}
|
|
10307
10633
|
return null;
|
|
10308
10634
|
}
|
|
10635
|
+
/**
|
|
10636
|
+
* The rectangle the floating handle may occupy: the window, narrowed by every ancestor that
|
|
10637
|
+
* clips its overflow.
|
|
10638
|
+
*
|
|
10639
|
+
* The handle is `position: fixed`, so it is placed in viewport coordinates and no ancestor's
|
|
10640
|
+
* overflow clips it for us. But the editor is routinely mounted inside a bounded scroller —
|
|
10641
|
+
* the ribbon puts its surface in one, inside a clipped card — and a block scrolled out of that
|
|
10642
|
+
* scroller has to take its handle with it rather than leave it parked over the host's chrome.
|
|
10643
|
+
*
|
|
10644
|
+
* Gated on the computed `overflow` rather than on whether an element scrolls right now, so the
|
|
10645
|
+
* answer does not depend on how much content happens to be loaded.
|
|
10646
|
+
*/
|
|
10647
|
+
blockHandleClipRect() {
|
|
10648
|
+
const view = this.container.ownerDocument.defaultView;
|
|
10649
|
+
const clip = {
|
|
10650
|
+
top: 0,
|
|
10651
|
+
left: 0,
|
|
10652
|
+
right: view?.innerWidth ?? Number.MAX_SAFE_INTEGER,
|
|
10653
|
+
bottom: view?.innerHeight ?? Number.MAX_SAFE_INTEGER
|
|
10654
|
+
};
|
|
10655
|
+
for (let el = this.editRoot; el; el = el.parentElement) {
|
|
10656
|
+
const style = view?.getComputedStyle(el);
|
|
10657
|
+
if (!style || style.overflowX === "visible" && style.overflowY === "visible") continue;
|
|
10658
|
+
const box = el.getBoundingClientRect();
|
|
10659
|
+
if (style.overflowY !== "visible") {
|
|
10660
|
+
clip.top = Math.max(clip.top, box.top);
|
|
10661
|
+
clip.bottom = Math.min(clip.bottom, box.bottom);
|
|
10662
|
+
}
|
|
10663
|
+
if (style.overflowX !== "visible") {
|
|
10664
|
+
clip.left = Math.max(clip.left, box.left);
|
|
10665
|
+
clip.right = Math.min(clip.right, box.right);
|
|
10666
|
+
}
|
|
10667
|
+
}
|
|
10668
|
+
return clip;
|
|
10669
|
+
}
|
|
10309
10670
|
showBlockHandle(unit) {
|
|
10310
10671
|
const handle = this.blockDragHandle;
|
|
10311
10672
|
if (!handle || !this.isMovableBlockUnit(unit)) return;
|
|
10312
10673
|
const changed = unit !== this.blockDragSource;
|
|
10313
10674
|
this.blockDragSource = unit;
|
|
10675
|
+
this.blockHandleWanted = true;
|
|
10314
10676
|
const known = this.blockMoveTargetsFor(unit, { cachedOnly: true });
|
|
10315
10677
|
if (known?.size === 0) {
|
|
10316
10678
|
handle.style.display = "none";
|
|
@@ -10318,19 +10680,31 @@ var DocxEditor = class _DocxEditor {
|
|
|
10318
10680
|
}
|
|
10319
10681
|
if (changed && known === void 0) this.prefetchBlockMoveTargets(unit);
|
|
10320
10682
|
const rect = unit.getBoundingClientRect();
|
|
10683
|
+
const clip = this.blockHandleClipRect();
|
|
10684
|
+
if (rect.bottom <= clip.top || rect.top >= clip.bottom || rect.right <= clip.left || rect.left >= clip.right) {
|
|
10685
|
+
if (!this.blockDragging && this.blockMoveMenu?.style.display !== "block")
|
|
10686
|
+
handle.style.display = "none";
|
|
10687
|
+
return;
|
|
10688
|
+
}
|
|
10321
10689
|
handle.style.display = "flex";
|
|
10322
|
-
|
|
10323
|
-
handle.style.
|
|
10690
|
+
const top2 = rect.top + (unit.tagName === "TABLE" ? 6 : Math.max(0, (rect.height - BLOCK_HANDLE_HEIGHT) / 2));
|
|
10691
|
+
handle.style.left = `${clampToRange(
|
|
10692
|
+
rect.left - (BLOCK_HANDLE_WIDTH + BLOCK_HANDLE_GAP),
|
|
10693
|
+
clip.left,
|
|
10694
|
+
clip.right - BLOCK_HANDLE_WIDTH
|
|
10695
|
+
)}px`;
|
|
10696
|
+
handle.style.top = `${clampToRange(top2, clip.top, clip.bottom - BLOCK_HANDLE_HEIGHT)}px`;
|
|
10324
10697
|
const preview = blockPreviewText(unit);
|
|
10325
10698
|
handle.setAttribute("aria-label", preview ? `Move block: ${preview}` : "Move block");
|
|
10326
10699
|
}
|
|
10327
10700
|
hideBlockHandle() {
|
|
10328
10701
|
if (this.blockDragging || this.blockMoveMenu?.style.display === "block") return;
|
|
10702
|
+
this.blockHandleWanted = false;
|
|
10329
10703
|
if (this.blockDragHandle) this.blockDragHandle.style.display = "none";
|
|
10330
10704
|
}
|
|
10331
10705
|
positionBlockHandle() {
|
|
10332
10706
|
const source = this.currentBlockDragSource();
|
|
10333
|
-
if (source && this.
|
|
10707
|
+
if (source && this.blockHandleWanted) this.showBlockHandle(source);
|
|
10334
10708
|
}
|
|
10335
10709
|
/** Draw the drop line on `zone`'s requested edge, or take it away when there is no target. */
|
|
10336
10710
|
paintDropIndicator(data) {
|
|
@@ -10768,6 +11142,7 @@ var DocxEditor = class _DocxEditor {
|
|
|
10768
11142
|
this.blockMoveMenu = null;
|
|
10769
11143
|
this.blockMoveLive = null;
|
|
10770
11144
|
this.blockDragSource = null;
|
|
11145
|
+
this.blockHandleWanted = false;
|
|
10771
11146
|
this.blockDragging = false;
|
|
10772
11147
|
this.blockDragPointerDown = false;
|
|
10773
11148
|
}
|
|
@@ -12537,15 +12912,65 @@ var DocxEditor = class _DocxEditor {
|
|
|
12537
12912
|
}
|
|
12538
12913
|
return out;
|
|
12539
12914
|
}
|
|
12540
|
-
/**
|
|
12915
|
+
/**
|
|
12916
|
+
* Select a match, focus its block and scroll it into view — the caret lands on the hit, so
|
|
12917
|
+
* typing continues in the document. A find box that is still being typed into wants
|
|
12918
|
+
* `showFindMatches` instead; this is the commit step (closing the bar, jumping in to edit).
|
|
12919
|
+
*/
|
|
12541
12920
|
selectMatch(match) {
|
|
12542
12921
|
if (!match.block.isConnected) return;
|
|
12922
|
+
this.clearFindMatches();
|
|
12543
12923
|
this.activeBlock = match.block;
|
|
12544
12924
|
selectRange(match.block, match.start, match.length);
|
|
12545
12925
|
match.block.scrollIntoView({ block: "center", behavior: "smooth" });
|
|
12546
12926
|
}
|
|
12547
|
-
/**
|
|
12548
|
-
|
|
12927
|
+
/**
|
|
12928
|
+
* Paint `matches` and scroll the one at `activeIndex` into view WITHOUT moving focus or the
|
|
12929
|
+
* caret. This is what a find field calls on every keystroke: the document shows where the hits
|
|
12930
|
+
* are and rides to the current one, while the keyboard stays in the search box.
|
|
12931
|
+
*
|
|
12932
|
+
* Falls back to the document selection where the CSS Custom Highlight API is missing — still
|
|
12933
|
+
* without focusing the block, so the next character typed goes to the search field either way.
|
|
12934
|
+
*/
|
|
12935
|
+
showFindMatches(matches, activeIndex) {
|
|
12936
|
+
const active = matches[activeIndex];
|
|
12937
|
+
this.clearFindMatches();
|
|
12938
|
+
if (!active || !active.block.isConnected) return;
|
|
12939
|
+
this.activeBlock = active.block;
|
|
12940
|
+
const activeRange = contentRangeIn(active.block, active.start, active.length);
|
|
12941
|
+
const registry = highlightRegistry();
|
|
12942
|
+
if (registry && activeRange) {
|
|
12943
|
+
ensureFindHighlightStyles(this.container.ownerDocument);
|
|
12944
|
+
const rest = [];
|
|
12945
|
+
for (let i = 0; i < matches.length && rest.length < FIND_PAINT_LIMIT; i++) {
|
|
12946
|
+
if (i === activeIndex) continue;
|
|
12947
|
+
const range = contentRangeIn(matches[i].block, matches[i].start, matches[i].length);
|
|
12948
|
+
if (range) rest.push(range);
|
|
12949
|
+
}
|
|
12950
|
+
if (rest.length > 0) registry.set(FIND_HIGHLIGHT, makeHighlight(rest));
|
|
12951
|
+
registry.set(FIND_HIGHLIGHT_ACTIVE, makeHighlight([activeRange]));
|
|
12952
|
+
findPaintOwner = this;
|
|
12953
|
+
} else if (activeRange) {
|
|
12954
|
+
const sel = this.container.ownerDocument.defaultView?.getSelection();
|
|
12955
|
+
sel?.removeAllRanges();
|
|
12956
|
+
sel?.addRange(activeRange);
|
|
12957
|
+
}
|
|
12958
|
+
active.block.scrollIntoView({ block: "center", behavior: "smooth" });
|
|
12959
|
+
}
|
|
12960
|
+
/** Drop the find painting (closing the find bar, or committing a match to the caret). */
|
|
12961
|
+
clearFindMatches() {
|
|
12962
|
+
if (findPaintOwner !== this) return;
|
|
12963
|
+
const registry = highlightRegistry();
|
|
12964
|
+
registry?.delete(FIND_HIGHLIGHT);
|
|
12965
|
+
registry?.delete(FIND_HIGHLIGHT_ACTIVE);
|
|
12966
|
+
findPaintOwner = null;
|
|
12967
|
+
}
|
|
12968
|
+
/**
|
|
12969
|
+
* Replace one match's text (formatting of the surrounding run is inherited). `focus: false`
|
|
12970
|
+
* leaves the keyboard where it is — a Replace button pressed from the find bar must not drag
|
|
12971
|
+
* the caret into the document mid-search.
|
|
12972
|
+
*/
|
|
12973
|
+
replaceMatch(match, replacement, options = {}) {
|
|
12549
12974
|
const block = match.block;
|
|
12550
12975
|
if (this.closed || !block.isConnected) return false;
|
|
12551
12976
|
const unid = block.getAttribute("data-anchor");
|
|
@@ -12566,7 +12991,7 @@ var DocxEditor = class _DocxEditor {
|
|
|
12566
12991
|
);
|
|
12567
12992
|
if (!res.success) return false;
|
|
12568
12993
|
const fresh = this.swapBlock(block, unid, res.modified?.[0]);
|
|
12569
|
-
if (fresh) selectRange(fresh, span.start, replacement.length);
|
|
12994
|
+
if (fresh && options.focus !== false) selectRange(fresh, span.start, replacement.length);
|
|
12570
12995
|
return true;
|
|
12571
12996
|
}
|
|
12572
12997
|
/** Replace every occurrence; returns how many were replaced. */
|
|
@@ -12585,7 +13010,11 @@ var DocxEditor = class _DocxEditor {
|
|
|
12585
13010
|
for (const m of list.slice().sort((a, b) => b.start - a.start)) {
|
|
12586
13011
|
if (!current.isConnected) break;
|
|
12587
13012
|
const before = current;
|
|
12588
|
-
if (this.replaceMatch(
|
|
13013
|
+
if (this.replaceMatch(
|
|
13014
|
+
{ block: current, start: m.start, length: m.length },
|
|
13015
|
+
replacement,
|
|
13016
|
+
{ focus: options.focus }
|
|
13017
|
+
)) {
|
|
12589
13018
|
count++;
|
|
12590
13019
|
current = this.activeBlock && this.activeBlock !== before ? this.activeBlock : current;
|
|
12591
13020
|
}
|
|
@@ -13267,7 +13696,7 @@ function ensureCommentGutterStyles(doc) {
|
|
|
13267
13696
|
}
|
|
13268
13697
|
|
|
13269
13698
|
// src/ribbon-chrome.ts
|
|
13270
|
-
var RIBBON_STYLE_VERSION = "
|
|
13699
|
+
var RIBBON_STYLE_VERSION = "11";
|
|
13271
13700
|
var RIBBON_STYLE_ATTR = "data-docxodus-ribbon-styles";
|
|
13272
13701
|
var COMMENT_GUTTER_WIDTH = 264;
|
|
13273
13702
|
var RIBBON_CSS = `
|
|
@@ -13700,9 +14129,11 @@ var RIBBON_CSS = `
|
|
|
13700
14129
|
background: linear-gradient(to top, var(--dxr-desk), transparent);
|
|
13701
14130
|
}
|
|
13702
14131
|
.dxr[data-chrome] .dxr-surface { position: relative; margin: 26px auto; padding: 0 16px 96px; }
|
|
13703
|
-
/*
|
|
13704
|
-
|
|
13705
|
-
.dxr[data-chrome="full"] .dxr-surface[data-comments="on"]
|
|
14132
|
+
/* Reserve the markup area only while a visible comment or draft needs it. The gutter owns
|
|
14133
|
+
its empty/hidden state, so ordinary documents stay centered without disabling comments. */
|
|
14134
|
+
.dxr[data-chrome="full"] .dxr-surface[data-comments="on"]:has(> .docx-comment-gutter:not([data-empty]):not([hidden])) {
|
|
14135
|
+
padding-right: calc(var(--dxr-gutter) + 8px);
|
|
14136
|
+
}
|
|
13706
14137
|
.dxr-surface [contenteditable="true"]:focus {
|
|
13707
14138
|
outline: 2px solid var(--dxr-accent);
|
|
13708
14139
|
outline-offset: 2px;
|
|
@@ -15853,11 +16284,18 @@ var RibbonSurface = class {
|
|
|
15853
16284
|
if (!this.live || !text.value) return;
|
|
15854
16285
|
let count = 0;
|
|
15855
16286
|
this.run("replace all", () => {
|
|
15856
|
-
count = this.live.replaceAll(text.value, replace.value, {
|
|
16287
|
+
count = this.live.replaceAll(text.value, replace.value, {
|
|
16288
|
+
matchCase: matchCase.checked,
|
|
16289
|
+
focus: false
|
|
16290
|
+
});
|
|
15857
16291
|
});
|
|
15858
16292
|
this.setStatus(`Replaced ${count} occurrence${count === 1 ? "" : "s"}`);
|
|
15859
16293
|
this.refreshFind(true);
|
|
15860
16294
|
});
|
|
16295
|
+
for (const id of ["findprev", "findnext", "replaceone", "replaceall"]) {
|
|
16296
|
+
const button = this.control(id);
|
|
16297
|
+
if (button) this.keepSelection(button);
|
|
16298
|
+
}
|
|
15861
16299
|
bar.hidden = true;
|
|
15862
16300
|
}
|
|
15863
16301
|
openFindBar(withReplace) {
|
|
@@ -15868,28 +16306,44 @@ var RibbonSurface = class {
|
|
|
15868
16306
|
const text = this.require("findtext");
|
|
15869
16307
|
text.focus();
|
|
15870
16308
|
text.select();
|
|
15871
|
-
this.refreshFind(
|
|
16309
|
+
this.refreshFind(true);
|
|
15872
16310
|
}
|
|
16311
|
+
/**
|
|
16312
|
+
* Close the bar and hand the current hit over to the caret: the document is where the user is
|
|
16313
|
+
* going next, and Word leaves the insertion point on the match it stopped at. Until this runs
|
|
16314
|
+
* the match is only PAINTED (see `showFindMatches`), so the search field keeps the keyboard for
|
|
16315
|
+
* as long as the bar is open.
|
|
16316
|
+
*/
|
|
15873
16317
|
closeFindBar() {
|
|
15874
16318
|
const bar = this.control("findbar");
|
|
15875
16319
|
if (bar) bar.hidden = true;
|
|
16320
|
+
const match = this.findMatches[this.findIndex];
|
|
16321
|
+
if (match) this.live?.selectMatch(match);
|
|
16322
|
+
else this.live?.clearFindMatches();
|
|
15876
16323
|
this.findMatches = [];
|
|
15877
16324
|
this.findIndex = -1;
|
|
15878
16325
|
}
|
|
15879
|
-
|
|
16326
|
+
/**
|
|
16327
|
+
* Re-scan for the current query. `show` paints the hits and rides to the first one — it never
|
|
16328
|
+
* focuses the document, so typing the second character of a query that already matched goes on
|
|
16329
|
+
* refining the search instead of being typed into the document.
|
|
16330
|
+
*/
|
|
16331
|
+
refreshFind(show) {
|
|
15880
16332
|
const text = this.require("findtext");
|
|
15881
16333
|
const matchCase = this.require("findcase").checked;
|
|
15882
16334
|
this.findMatches = this.live && text.value ? this.live.find(text.value, { matchCase }) : [];
|
|
15883
16335
|
this.findIndex = this.findMatches.length > 0 ? 0 : -1;
|
|
15884
16336
|
this.updateFindCount();
|
|
15885
|
-
if (
|
|
16337
|
+
if (!show) return;
|
|
16338
|
+
if (this.findIndex >= 0) this.live?.showFindMatches(this.findMatches, this.findIndex);
|
|
16339
|
+
else this.live?.clearFindMatches();
|
|
15886
16340
|
}
|
|
15887
16341
|
stepFind(direction) {
|
|
15888
16342
|
if (!this.live) return;
|
|
15889
16343
|
if (this.findMatches.length === 0) this.refreshFind(false);
|
|
15890
16344
|
if (this.findMatches.length === 0) return;
|
|
15891
16345
|
this.findIndex = (this.findIndex + direction + this.findMatches.length) % this.findMatches.length;
|
|
15892
|
-
this.live.
|
|
16346
|
+
this.live.showFindMatches(this.findMatches, this.findIndex);
|
|
15893
16347
|
this.updateFindCount();
|
|
15894
16348
|
}
|
|
15895
16349
|
replaceCurrent() {
|
|
@@ -15898,12 +16352,12 @@ var RibbonSurface = class {
|
|
|
15898
16352
|
const match = this.findMatches[this.findIndex];
|
|
15899
16353
|
if (!match) return;
|
|
15900
16354
|
const replacement = this.require("replacetext").value;
|
|
15901
|
-
this.run("replace", () => this.live.replaceMatch(match, replacement));
|
|
16355
|
+
this.run("replace", () => this.live.replaceMatch(match, replacement, { focus: false }));
|
|
15902
16356
|
const keep = this.findIndex;
|
|
15903
16357
|
this.refreshFind(false);
|
|
15904
16358
|
if (this.findMatches.length > 0) {
|
|
15905
16359
|
this.findIndex = Math.min(keep, this.findMatches.length - 1);
|
|
15906
|
-
this.live.
|
|
16360
|
+
this.live.showFindMatches(this.findMatches, this.findIndex);
|
|
15907
16361
|
this.updateFindCount();
|
|
15908
16362
|
}
|
|
15909
16363
|
}
|
|
@@ -16334,6 +16788,16 @@ var RibbonSurface = class {
|
|
|
16334
16788
|
};
|
|
16335
16789
|
|
|
16336
16790
|
// src/index.ts
|
|
16791
|
+
function openDocxHistory(storage) {
|
|
16792
|
+
const bridge = ensureInitialized().HistoryBridge;
|
|
16793
|
+
if (!bridge) throw new Error("This WASM build does not include history bindings.");
|
|
16794
|
+
return new DocxHistoryClient(bridge, storage);
|
|
16795
|
+
}
|
|
16796
|
+
function openDocxHistoryArchive(bytes) {
|
|
16797
|
+
const bridge = ensureInitialized().HistoryBridge;
|
|
16798
|
+
if (!bridge) throw new Error("This WASM build does not include history bindings.");
|
|
16799
|
+
return DocxHistoryArchive.open(bridge, bytes);
|
|
16800
|
+
}
|
|
16337
16801
|
function openDocxSession2(bytes, settings) {
|
|
16338
16802
|
const wasm = ensureInitialized();
|
|
16339
16803
|
return openDocxSession(bytes, wasm, settings);
|
|
@@ -16388,14 +16852,16 @@ async function tryLoadFromPath(basePath) {
|
|
|
16388
16852
|
/* @vite-ignore */
|
|
16389
16853
|
dotnetPath
|
|
16390
16854
|
);
|
|
16391
|
-
const { getAssemblyExports, getConfig } = await dotnet.withDiagnosticTracing(false).create();
|
|
16855
|
+
const { getAssemblyExports, getConfig, setModuleImports } = await dotnet.withDiagnosticTracing(false).create();
|
|
16856
|
+
installHistoryStorageImports(setModuleImports);
|
|
16392
16857
|
const config = getConfig();
|
|
16393
16858
|
const exports = await getAssemblyExports(config.mainAssemblyName);
|
|
16394
16859
|
wasmExports = {
|
|
16395
16860
|
DocumentConverter: exports.DocxodusWasm.DocumentConverter,
|
|
16396
16861
|
DocumentComparer: exports.DocxodusWasm.DocumentComparer,
|
|
16397
16862
|
DocxDiffBridge: exports.DocxodusWasm.DocxDiffBridge,
|
|
16398
|
-
DocxSessionBridge: exports.DocxodusWasm.DocxSessionBridge
|
|
16863
|
+
DocxSessionBridge: exports.DocxodusWasm.DocxSessionBridge,
|
|
16864
|
+
HistoryBridge: exports.DocxodusWasm.HistoryBridge
|
|
16399
16865
|
};
|
|
16400
16866
|
return true;
|
|
16401
16867
|
} catch {
|
|
@@ -17769,6 +18235,20 @@ async function toDocumentBytes(source) {
|
|
|
17769
18235
|
}
|
|
17770
18236
|
throw new Error("Docxodus embed: unsupported document source");
|
|
17771
18237
|
}
|
|
18238
|
+
function scopedDocumentFragment(mount2, html) {
|
|
18239
|
+
const parsed = new DOMParser().parseFromString(html, "text/html");
|
|
18240
|
+
const fragment = document.createDocumentFragment();
|
|
18241
|
+
parsed.querySelectorAll("style").forEach((sourceStyle) => {
|
|
18242
|
+
const style = document.createElement("style");
|
|
18243
|
+
style.textContent = sourceStyle.textContent;
|
|
18244
|
+
scopeStyleElement(style, mount2.selector);
|
|
18245
|
+
fragment.appendChild(style);
|
|
18246
|
+
});
|
|
18247
|
+
const body = document.createElement("template");
|
|
18248
|
+
body.innerHTML = parsed.body.innerHTML;
|
|
18249
|
+
fragment.appendChild(body.content);
|
|
18250
|
+
return fragment;
|
|
18251
|
+
}
|
|
17772
18252
|
async function createViewer(container, source, options = {}) {
|
|
17773
18253
|
const el = resolveContainer2(container);
|
|
17774
18254
|
const { wasmBasePath: wasmBasePath2, ...conversion } = options;
|
|
@@ -17781,18 +18261,7 @@ async function createViewer(container, source, options = {}) {
|
|
|
17781
18261
|
renderFootnotesAndEndnotes: true,
|
|
17782
18262
|
...opts
|
|
17783
18263
|
});
|
|
17784
|
-
|
|
17785
|
-
const fragment = document.createDocumentFragment();
|
|
17786
|
-
parsed.querySelectorAll("style").forEach((sourceStyle) => {
|
|
17787
|
-
const style = document.createElement("style");
|
|
17788
|
-
style.textContent = sourceStyle.textContent;
|
|
17789
|
-
scopeStyleElement(style, mount2.selector);
|
|
17790
|
-
fragment.appendChild(style);
|
|
17791
|
-
});
|
|
17792
|
-
const body = document.createElement("template");
|
|
17793
|
-
body.innerHTML = parsed.body.innerHTML;
|
|
17794
|
-
fragment.appendChild(body.content);
|
|
17795
|
-
mount2.root.replaceChildren(fragment);
|
|
18264
|
+
mount2.root.replaceChildren(scopedDocumentFragment(mount2, lastHtml));
|
|
17796
18265
|
};
|
|
17797
18266
|
await render(source, conversion);
|
|
17798
18267
|
return {
|
|
@@ -17806,6 +18275,111 @@ async function createViewer(container, source, options = {}) {
|
|
|
17806
18275
|
}
|
|
17807
18276
|
};
|
|
17808
18277
|
}
|
|
18278
|
+
async function createHistoryViewer(container, history, documentId, options = {}) {
|
|
18279
|
+
const el = resolveContainer2(container);
|
|
18280
|
+
const { wasmBasePath: wasmBasePath2, maxEntriesToScan = 1e4, ...conversion } = options;
|
|
18281
|
+
if (!Number.isSafeInteger(maxEntriesToScan) || maxEntriesToScan <= 0 || maxEntriesToScan > 2147483647)
|
|
18282
|
+
throw new RangeError("Invalid history scan budget.");
|
|
18283
|
+
await ensureWasm(wasmBasePath2);
|
|
18284
|
+
const mount2 = createScopedMount(el);
|
|
18285
|
+
let accepted = null;
|
|
18286
|
+
let acceptedBytes = null;
|
|
18287
|
+
let displayedBytes = null;
|
|
18288
|
+
let shownSequence = "0";
|
|
18289
|
+
let following = true;
|
|
18290
|
+
let closed = false;
|
|
18291
|
+
let html = "";
|
|
18292
|
+
let queue = Promise.resolve();
|
|
18293
|
+
const clone = (value) => JSON.parse(JSON.stringify(value));
|
|
18294
|
+
const assertOpen = () => {
|
|
18295
|
+
if (closed) throw new DocxHistoryError("Closed", "History viewer is destroyed.");
|
|
18296
|
+
};
|
|
18297
|
+
const enqueue = (action) => {
|
|
18298
|
+
const result = queue.then(() => {
|
|
18299
|
+
assertOpen();
|
|
18300
|
+
return action();
|
|
18301
|
+
});
|
|
18302
|
+
queue = result.then(() => void 0, () => void 0);
|
|
18303
|
+
return result;
|
|
18304
|
+
};
|
|
18305
|
+
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;
|
|
18306
|
+
const paint = async (bytes, sequence) => {
|
|
18307
|
+
const nextHtml = await convertDocxToHtml(bytes, { renderFootnotesAndEndnotes: true, ...conversion });
|
|
18308
|
+
assertOpen();
|
|
18309
|
+
const fragment = scopedDocumentFragment(mount2, nextHtml);
|
|
18310
|
+
mount2.root.replaceChildren(fragment);
|
|
18311
|
+
html = nextHtml;
|
|
18312
|
+
displayedBytes = bytes;
|
|
18313
|
+
shownSequence = sequence;
|
|
18314
|
+
};
|
|
18315
|
+
const refresh = async (resume = false) => {
|
|
18316
|
+
const update = await history.readChangesSince(documentId, accepted?.view.head ?? null, maxEntriesToScan);
|
|
18317
|
+
assertOpen();
|
|
18318
|
+
const duplicate = accepted !== null && sameHead(accepted.view.head, update.view.head);
|
|
18319
|
+
const bytes = duplicate ? acceptedBytes : await history.exportVersion(documentId, update.view.version.id);
|
|
18320
|
+
assertOpen();
|
|
18321
|
+
const wantLive = following || resume;
|
|
18322
|
+
const changedContent = accepted === null || accepted.view.state.snapshot.contentDigest.value !== update.view.state.snapshot.contentDigest.value || accepted.view.state.epoch !== update.view.state.epoch;
|
|
18323
|
+
if (wantLive && (changedContent || !following)) await paint(bytes, update.view.state.sequence);
|
|
18324
|
+
else if (wantLive) {
|
|
18325
|
+
displayedBytes = bytes;
|
|
18326
|
+
shownSequence = update.view.state.sequence;
|
|
18327
|
+
}
|
|
18328
|
+
assertOpen();
|
|
18329
|
+
accepted = update;
|
|
18330
|
+
acceptedBytes = bytes;
|
|
18331
|
+
if (wantLive) following = true;
|
|
18332
|
+
return clone(update);
|
|
18333
|
+
};
|
|
18334
|
+
const showSequence = async (sequence) => {
|
|
18335
|
+
const bytes = await history.materialize(documentId, sequence, maxEntriesToScan);
|
|
18336
|
+
assertOpen();
|
|
18337
|
+
await paint(bytes, sequence);
|
|
18338
|
+
following = false;
|
|
18339
|
+
};
|
|
18340
|
+
const viewer = {
|
|
18341
|
+
element: el,
|
|
18342
|
+
get html() {
|
|
18343
|
+
return html;
|
|
18344
|
+
},
|
|
18345
|
+
get head() {
|
|
18346
|
+
assertOpen();
|
|
18347
|
+
return clone(accepted.view.head);
|
|
18348
|
+
},
|
|
18349
|
+
get sequence() {
|
|
18350
|
+
return shownSequence;
|
|
18351
|
+
},
|
|
18352
|
+
get following() {
|
|
18353
|
+
return following;
|
|
18354
|
+
},
|
|
18355
|
+
refresh: () => enqueue(() => refresh()),
|
|
18356
|
+
showSequence: (sequence) => enqueue(() => showSequence(sequence)),
|
|
18357
|
+
showTime: (cutoff) => enqueue(async () => {
|
|
18358
|
+
const sequence = await history.resolveSequenceAtTime(documentId, cutoff, maxEntriesToScan);
|
|
18359
|
+
assertOpen();
|
|
18360
|
+
await showSequence(sequence);
|
|
18361
|
+
}),
|
|
18362
|
+
resume: () => enqueue(() => refresh(true)),
|
|
18363
|
+
exportDisplayed: () => {
|
|
18364
|
+
assertOpen();
|
|
18365
|
+
return displayedBytes.slice();
|
|
18366
|
+
},
|
|
18367
|
+
destroy: () => {
|
|
18368
|
+
if (closed) return;
|
|
18369
|
+
closed = true;
|
|
18370
|
+
acceptedBytes = null;
|
|
18371
|
+
displayedBytes = null;
|
|
18372
|
+
mount2.root.remove();
|
|
18373
|
+
}
|
|
18374
|
+
};
|
|
18375
|
+
try {
|
|
18376
|
+
await viewer.refresh();
|
|
18377
|
+
return viewer;
|
|
18378
|
+
} catch (error) {
|
|
18379
|
+
viewer.destroy();
|
|
18380
|
+
throw error;
|
|
18381
|
+
}
|
|
18382
|
+
}
|
|
17809
18383
|
async function createEditor(container, source, options = {}) {
|
|
17810
18384
|
const el = resolveContainer2(container);
|
|
17811
18385
|
const { wasmBasePath: wasmBasePath2, ...editorOptions } = options;
|
|
@@ -17887,8 +18461,14 @@ export {
|
|
|
17887
18461
|
DocxDiffFormatComparison,
|
|
17888
18462
|
DocxDiffRevisionGranularity,
|
|
17889
18463
|
DocxEditor,
|
|
18464
|
+
DocxHistoryArchive,
|
|
18465
|
+
DocxHistoryClient,
|
|
18466
|
+
DocxHistoryDocument,
|
|
18467
|
+
DocxHistoryError,
|
|
18468
|
+
DocxHistoryReader,
|
|
17890
18469
|
DocxSession,
|
|
17891
18470
|
EmptyParagraphMode,
|
|
18471
|
+
MAX_HISTORY_ARCHIVE_BYTES,
|
|
17892
18472
|
PaginationEngine,
|
|
17893
18473
|
PaginationMode,
|
|
17894
18474
|
PlaceholderKinds,
|
|
@@ -17912,6 +18492,8 @@ export {
|
|
|
17912
18492
|
createBlankDocx,
|
|
17913
18493
|
createEditor,
|
|
17914
18494
|
createExternalAnnotationSet,
|
|
18495
|
+
createHistoryViewer,
|
|
18496
|
+
createMemoryHistoryStorage,
|
|
17915
18497
|
createRibbonEditor,
|
|
17916
18498
|
createUnavailablePageMap,
|
|
17917
18499
|
createViewer,
|
|
@@ -17946,6 +18528,7 @@ export {
|
|
|
17946
18528
|
getWasmExports,
|
|
17947
18529
|
hasAnnotations,
|
|
17948
18530
|
initialize,
|
|
18531
|
+
installHistoryStorageImports,
|
|
17949
18532
|
isDeletion,
|
|
17950
18533
|
isFormatChange,
|
|
17951
18534
|
isInitialized,
|
|
@@ -17953,6 +18536,8 @@ export {
|
|
|
17953
18536
|
isMove,
|
|
17954
18537
|
mountRibbon,
|
|
17955
18538
|
navigateToPageCitation,
|
|
18539
|
+
openDocxHistory,
|
|
18540
|
+
openDocxHistoryArchive,
|
|
17956
18541
|
openDocxSession2 as openDocxSession,
|
|
17957
18542
|
paginateHtml,
|
|
17958
18543
|
parseSectionDimensions,
|