@ansonlai/docx-redline-js 0.8.0 → 0.8.1
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/CHANGELOG.md +539 -486
- package/README.md +331 -309
- package/dist/docx-redline-js.esm.js +1083 -163
- package/dist/docx-redline-js.esm.js.map +4 -4
- package/dist/docx-redline-js.esm.min.js +107 -107
- package/dist/docx-redline-js.esm.min.js.map +4 -4
- package/dist/docx-redline.bundle.cjs +1083 -163
- package/dist/docx-redline.bundle.cjs.map +4 -4
- package/dist/docx-redline.bundle.js +1083 -163
- package/dist/docx-redline.bundle.js.map +4 -4
- package/docs/AGENT_KNOWLEDGE_BASE.md +69 -8
- package/docs/schemas/document-operations.schema.json +29 -0
- package/document/docx-document.js +416 -22
- package/engine/reconstruction-writer.js +18 -0
- package/index.d.ts +598 -575
- package/index.js +349 -348
- package/node/cli-help.js +5 -5
- package/node/cli.js +760 -754
- package/node/index.d.ts +1 -1
- package/package.json +2 -1
- package/scripts/generate-word-comment-edge-case-fixture.ps1 +58 -0
- package/scripts/generate-word-comment-thread-fixture.ps1 +36 -0
- package/scripts/generate-word-header-footer-fixture.ps1 +32 -0
- package/scripts/generate-word-multi-section-fixture.ps1 +27 -0
- package/scripts/generate-word-package-integrity-cases.mjs +141 -0
- package/scripts/generate-word-thread-state-fixtures.ps1 +50 -0
- package/scripts/word-com-package-integrity.ps1 +98 -0
- package/services/batch-operation-orchestrator.js +262 -259
- package/services/comment-replies.js +96 -95
- package/services/comment-thread-parts.js +280 -0
- package/services/document-inspection.js +366 -365
- package/services/document-operation-applier.js +230 -202
- package/services/document-operation-contract.js +17 -2
- package/services/error-recovery.js +6 -0
- package/services/headers-footers.js +206 -0
- package/services/operation-batch-compiler.js +1 -0
- package/services/operation-preflight.js +231 -218
- package/services/package-parts.js +80 -0
- package/services/standalone-docx-plumbing.js +631 -569
- package/services/standalone-operation-runner.d.ts +19 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @ansonlai/docx-redline-js standalone CommonJS bundle v0.8.
|
|
1
|
+
// @ansonlai/docx-redline-js standalone CommonJS bundle v0.8.1 — https://github.com/AnsonLai/docx-redline-js
|
|
2
2
|
var __create = Object.create;
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -8080,6 +8080,7 @@ __export(index_exports, {
|
|
|
8080
8080
|
rejectTrackedChangesInOoxml: () => rejectTrackedChangesInOoxml,
|
|
8081
8081
|
remapNumberingPayloadForDocument: () => remapNumberingPayloadForDocument,
|
|
8082
8082
|
removeFormattingFromRPr: () => removeFormattingFromRPr,
|
|
8083
|
+
repairKnownContentTypes: () => repairKnownContentTypes,
|
|
8083
8084
|
reserveNextNumberingId: () => reserveNextNumberingId,
|
|
8084
8085
|
reserveNextNumberingIdPair: () => reserveNextNumberingIdPair,
|
|
8085
8086
|
resolveParagraphRangeByRefs: () => resolveParagraphRangeByRefs,
|
|
@@ -14333,6 +14334,19 @@ function applyReconstructionDiffs(xmlDoc, diffs, context, serializer, author, fo
|
|
|
14333
14334
|
pendingReplacementEvent = null;
|
|
14334
14335
|
}
|
|
14335
14336
|
}
|
|
14337
|
+
for (const offset of Array.from(sentinelMapByStart.keys()).sort((a, b) => a - b)) {
|
|
14338
|
+
for (const sentinel of sentinelMapByStart.get(offset)) {
|
|
14339
|
+
if (!sentinel.isCommentMarker || emittedCommentMarkers.has(sentinel.node)) continue;
|
|
14340
|
+
emittedCommentMarkers.add(sentinel.node);
|
|
14341
|
+
if (isWordElement(sentinel.node, "commentReference")) {
|
|
14342
|
+
const run = createWordElement(xmlDoc, "w:r");
|
|
14343
|
+
run.appendChild(sentinel.node.cloneNode(true));
|
|
14344
|
+
currentParagraph.appendChild(run);
|
|
14345
|
+
} else {
|
|
14346
|
+
currentParagraph.appendChild(sentinel.node.cloneNode(true));
|
|
14347
|
+
}
|
|
14348
|
+
}
|
|
14349
|
+
}
|
|
14336
14350
|
if (generateRedlines) {
|
|
14337
14351
|
containerFragments.forEach((fragment) => {
|
|
14338
14352
|
Array.from(fragment.childNodes).forEach((node) => {
|
|
@@ -18127,6 +18141,260 @@ function ingestWordOoxmlToMarkdownResult(ooxml) {
|
|
|
18127
18141
|
return { text: lines.join("\n\n").trim(), status: "ok", warnings: parsed.warnings };
|
|
18128
18142
|
}
|
|
18129
18143
|
|
|
18144
|
+
// services/comment-builders.js
|
|
18145
|
+
var NS_W14 = "http://schemas.microsoft.com/office/word/2010/wordml";
|
|
18146
|
+
var NS_W15 = "http://schemas.microsoft.com/office/word/2012/wordml";
|
|
18147
|
+
function createCommentParaId(commentId) {
|
|
18148
|
+
const numeric = Number.parseInt(String(commentId), 10);
|
|
18149
|
+
const value = Number.isFinite(numeric) ? 1879048192 + (numeric >>> 0) >>> 0 : 1879048192;
|
|
18150
|
+
return value.toString(16).toUpperCase().padStart(8, "0").slice(-8);
|
|
18151
|
+
}
|
|
18152
|
+
function buildCommentElement(commentId, author, content, date, paraId = createCommentParaId(commentId)) {
|
|
18153
|
+
const initials = author.split(" ").map((word) => word[0]).join("").toUpperCase() || "AI";
|
|
18154
|
+
const escapedContent = escapeXml(content);
|
|
18155
|
+
const escapedAuthor = escapeXml(author);
|
|
18156
|
+
return `<w:comment w:id="${commentId}" w:author="${escapedAuthor}" w:date="${date}" w:initials="${initials}">
|
|
18157
|
+
<w:p w14:paraId="${escapeXml(paraId)}" xmlns:w14="${NS_W14}">
|
|
18158
|
+
<w:r><w:t>${escapedContent}</w:t></w:r>
|
|
18159
|
+
</w:p>
|
|
18160
|
+
</w:comment>`;
|
|
18161
|
+
}
|
|
18162
|
+
function buildCommentsPartXml(comments) {
|
|
18163
|
+
if (!comments || comments.length === 0) {
|
|
18164
|
+
return `<w:comments xmlns:w="${NS_W}"></w:comments>`;
|
|
18165
|
+
}
|
|
18166
|
+
const commentElements = comments.map(
|
|
18167
|
+
(comment) => buildCommentElement(comment.id, comment.author, comment.content, comment.date, comment.paraId)
|
|
18168
|
+
).join("\n ");
|
|
18169
|
+
return `<w:comments xmlns:w="${NS_W}">
|
|
18170
|
+
${commentElements}
|
|
18171
|
+
</w:comments>`;
|
|
18172
|
+
}
|
|
18173
|
+
function buildCommentsExtendedPartXml(entries) {
|
|
18174
|
+
const body = (entries || []).map((entry) => {
|
|
18175
|
+
const parent = entry.paraIdParent ? ` w15:paraIdParent="${escapeXml(entry.paraIdParent)}"` : "";
|
|
18176
|
+
return `<w15:commentEx w15:paraId="${escapeXml(entry.paraId)}"${parent} w15:done="${entry.done ? "1" : "0"}"/>`;
|
|
18177
|
+
}).join("");
|
|
18178
|
+
return `<w15:commentsEx xmlns:w15="${NS_W15}">${body}</w15:commentsEx>`;
|
|
18179
|
+
}
|
|
18180
|
+
|
|
18181
|
+
// services/comment-thread-parts.js
|
|
18182
|
+
var attrOf = (node, qualified, local) => node?.getAttribute?.(qualified) || node?.getAttribute?.(local) || "";
|
|
18183
|
+
function commentThreadParagraph(commentElement) {
|
|
18184
|
+
const paragraphs = Array.from(commentElement?.getElementsByTagNameNS?.("*", "p") || []);
|
|
18185
|
+
return paragraphs.length ? paragraphs[paragraphs.length - 1] : null;
|
|
18186
|
+
}
|
|
18187
|
+
function commentParaId(commentElement) {
|
|
18188
|
+
return attrOf(commentThreadParagraph(commentElement), "w14:paraId", "paraId").toUpperCase();
|
|
18189
|
+
}
|
|
18190
|
+
function usedParaIds(commentsDoc, extendedDoc) {
|
|
18191
|
+
const ids = /* @__PURE__ */ new Set();
|
|
18192
|
+
for (const p of Array.from(commentsDoc?.getElementsByTagNameNS("*", "p") || [])) {
|
|
18193
|
+
const id = attrOf(p, "w14:paraId", "paraId");
|
|
18194
|
+
if (id) ids.add(id.toUpperCase());
|
|
18195
|
+
}
|
|
18196
|
+
for (const ex of Array.from(extendedDoc?.getElementsByTagNameNS("*", "commentEx") || [])) {
|
|
18197
|
+
const id = attrOf(ex, "w15:paraId", "paraId");
|
|
18198
|
+
if (id) ids.add(id.toUpperCase());
|
|
18199
|
+
}
|
|
18200
|
+
return ids;
|
|
18201
|
+
}
|
|
18202
|
+
function allocateParaId(commentId, occupied) {
|
|
18203
|
+
let candidate = createCommentParaId(commentId);
|
|
18204
|
+
let value = Number.parseInt(candidate, 16) >>> 0;
|
|
18205
|
+
while (occupied.has(candidate)) {
|
|
18206
|
+
value = value + 1 >>> 0;
|
|
18207
|
+
candidate = value.toString(16).toUpperCase().padStart(8, "0");
|
|
18208
|
+
}
|
|
18209
|
+
occupied.add(candidate);
|
|
18210
|
+
return candidate;
|
|
18211
|
+
}
|
|
18212
|
+
var parseDoc = (xml, partName) => {
|
|
18213
|
+
const parsed = parseOoxmlSafe(xml, "application/xml");
|
|
18214
|
+
if (!parsed.doc || parsed.error) return { error: { code: "PARSE_ERROR", message: `Could not parse ${partName}: ${parsed.error?.message || "invalid XML"}` } };
|
|
18215
|
+
return { doc: parsed.doc };
|
|
18216
|
+
};
|
|
18217
|
+
function applyThreadResolutionToParts({ commentsXml, commentsExtendedXml = null, commentId, resolved = true }) {
|
|
18218
|
+
if (!commentsXml) return { status: "error", error: { code: "COMMENTS_PART_MISSING", message: "Resolving a comment requires an existing word/comments.xml part." } };
|
|
18219
|
+
const commentsParsed = parseDoc(commentsXml, "word/comments.xml");
|
|
18220
|
+
if (commentsParsed.error) return { status: "error", error: commentsParsed.error };
|
|
18221
|
+
const commentsDoc = commentsParsed.doc;
|
|
18222
|
+
const idOf = (node) => attrOf(node, "w:id", "id");
|
|
18223
|
+
const comments = Array.from(commentsDoc.getElementsByTagNameNS("*", "comment"));
|
|
18224
|
+
const target = comments.find((node) => idOf(node) === String(commentId));
|
|
18225
|
+
if (!target) return { status: "error", error: { code: "COMMENT_NOT_FOUND", message: `Comment '${commentId}' was not found.` } };
|
|
18226
|
+
let extendedDoc;
|
|
18227
|
+
if (commentsExtendedXml) {
|
|
18228
|
+
const parsed = parseDoc(commentsExtendedXml, "word/commentsExtended.xml");
|
|
18229
|
+
if (parsed.error) return { status: "error", error: parsed.error };
|
|
18230
|
+
extendedDoc = parsed.doc;
|
|
18231
|
+
} else {
|
|
18232
|
+
extendedDoc = parseDoc(`<w15:commentsEx xmlns:w15="${NS_W15}"/>`, "word/commentsExtended.xml").doc;
|
|
18233
|
+
}
|
|
18234
|
+
const root = extendedDoc.documentElement;
|
|
18235
|
+
const entryFor = (paraId) => Array.from(root.getElementsByTagNameNS("*", "commentEx")).find((node) => attrOf(node, "w15:paraId", "paraId").toUpperCase() === paraId) || null;
|
|
18236
|
+
const occupied = usedParaIds(commentsDoc, extendedDoc);
|
|
18237
|
+
let commentsChanged = false;
|
|
18238
|
+
const keyOf = (node) => {
|
|
18239
|
+
let paraId = commentParaId(node);
|
|
18240
|
+
if (!paraId) {
|
|
18241
|
+
paraId = allocateParaId(idOf(node), occupied);
|
|
18242
|
+
commentThreadParagraph(node)?.setAttributeNS(NS_W14, "w14:paraId", paraId);
|
|
18243
|
+
commentsChanged = true;
|
|
18244
|
+
}
|
|
18245
|
+
return paraId;
|
|
18246
|
+
};
|
|
18247
|
+
const targetKey = keyOf(target);
|
|
18248
|
+
const targetEntry = entryFor(targetKey);
|
|
18249
|
+
const rootKey = targetEntry && attrOf(targetEntry, "w15:paraIdParent", "paraIdParent").toUpperCase() || targetKey;
|
|
18250
|
+
const threadKeys = /* @__PURE__ */ new Set([rootKey, targetKey]);
|
|
18251
|
+
for (const entry of Array.from(root.getElementsByTagNameNS("*", "commentEx"))) {
|
|
18252
|
+
if (attrOf(entry, "w15:paraIdParent", "paraIdParent").toUpperCase() === rootKey) threadKeys.add(attrOf(entry, "w15:paraId", "paraId").toUpperCase());
|
|
18253
|
+
}
|
|
18254
|
+
const doneValue = resolved ? "1" : "0";
|
|
18255
|
+
let extendedChanged = !commentsExtendedXml;
|
|
18256
|
+
for (const key of threadKeys) {
|
|
18257
|
+
let entry = entryFor(key);
|
|
18258
|
+
if (!entry) {
|
|
18259
|
+
entry = extendedDoc.createElementNS(NS_W15, "w15:commentEx");
|
|
18260
|
+
entry.setAttributeNS(NS_W15, "w15:paraId", key);
|
|
18261
|
+
if (key !== rootKey) entry.setAttributeNS(NS_W15, "w15:paraIdParent", rootKey);
|
|
18262
|
+
entry.setAttributeNS(NS_W15, "w15:done", doneValue);
|
|
18263
|
+
root.appendChild(entry);
|
|
18264
|
+
extendedChanged = true;
|
|
18265
|
+
} else if (attrOf(entry, "w15:done", "done") !== doneValue) {
|
|
18266
|
+
entry.setAttributeNS(NS_W15, "w15:done", doneValue);
|
|
18267
|
+
extendedChanged = true;
|
|
18268
|
+
}
|
|
18269
|
+
}
|
|
18270
|
+
const byKey = new Map(comments.map((node) => [commentParaId(node), node]));
|
|
18271
|
+
const commentIds = [...threadKeys].map((key) => byKey.get(key)).filter(Boolean).map(idOf);
|
|
18272
|
+
const rootComment = byKey.get(rootKey);
|
|
18273
|
+
const serializer = createSerializer();
|
|
18274
|
+
const hasChanges = extendedChanged || commentsChanged;
|
|
18275
|
+
return {
|
|
18276
|
+
status: "ok",
|
|
18277
|
+
hasChanges,
|
|
18278
|
+
resolved,
|
|
18279
|
+
threadRootId: rootComment ? idOf(rootComment) : String(commentId),
|
|
18280
|
+
commentIds,
|
|
18281
|
+
...hasChanges ? { commentsExtendedXml: serializer.serializeToString(extendedDoc) } : {},
|
|
18282
|
+
...commentsChanged ? { commentsXml: serializer.serializeToString(commentsDoc) } : {}
|
|
18283
|
+
};
|
|
18284
|
+
}
|
|
18285
|
+
var NS_W_MAIN = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
18286
|
+
function anchorReplyInDocument(xmlDoc, memberIds, newId) {
|
|
18287
|
+
const wanted = new Set(memberIds.map(String));
|
|
18288
|
+
const idOf = (node) => attrOf(node, "w:id", "id");
|
|
18289
|
+
const all = (name) => Array.from(xmlDoc.getElementsByTagNameNS("*", name)).filter((node) => wanted.has(idOf(node)));
|
|
18290
|
+
const starts = all("commentRangeStart");
|
|
18291
|
+
const references = all("commentReference");
|
|
18292
|
+
if (!starts.length || !references.length) return false;
|
|
18293
|
+
const lastStart = starts[starts.length - 1];
|
|
18294
|
+
const lastReference = references[references.length - 1];
|
|
18295
|
+
const referenceRun = lastReference.parentNode?.localName === "r" ? lastReference.parentNode : lastReference;
|
|
18296
|
+
const make = (name, parent) => {
|
|
18297
|
+
const element = xmlDoc.createElementNS(NS_W_MAIN, `w:${name}`);
|
|
18298
|
+
element.setAttributeNS(NS_W_MAIN, "w:id", String(newId));
|
|
18299
|
+
if (parent) parent.appendChild(element);
|
|
18300
|
+
return element;
|
|
18301
|
+
};
|
|
18302
|
+
const insertAfter = (anchor, node) => anchor.parentNode.insertBefore(node, anchor.nextSibling);
|
|
18303
|
+
insertAfter(lastStart, make("commentRangeStart"));
|
|
18304
|
+
const end = make("commentRangeEnd");
|
|
18305
|
+
const run = xmlDoc.createElementNS(NS_W_MAIN, "w:r");
|
|
18306
|
+
run.appendChild(xmlDoc.createElementNS(NS_W_MAIN, "w:rPr"));
|
|
18307
|
+
make("commentReference", run);
|
|
18308
|
+
insertAfter(referenceRun, end);
|
|
18309
|
+
insertAfter(end, run);
|
|
18310
|
+
return true;
|
|
18311
|
+
}
|
|
18312
|
+
var NS_W16CID = "http://schemas.microsoft.com/office/word/2016/wordml/cid";
|
|
18313
|
+
var NS_W16CEX = "http://schemas.microsoft.com/office/word/2018/wordml/cex";
|
|
18314
|
+
var durableIdFor = (paraId, taken) => {
|
|
18315
|
+
let value = (Number.parseInt(paraId, 16) ^ 625341585) & 2147483646;
|
|
18316
|
+
let candidate = value.toString(16).toUpperCase().padStart(8, "0");
|
|
18317
|
+
while (taken.has(candidate)) {
|
|
18318
|
+
value = value + 2 & 2147483646;
|
|
18319
|
+
candidate = value.toString(16).toUpperCase().padStart(8, "0");
|
|
18320
|
+
}
|
|
18321
|
+
taken.add(candidate);
|
|
18322
|
+
return candidate;
|
|
18323
|
+
};
|
|
18324
|
+
function reconcileCommentSiblingParts({ commentsXml, commentsIdsXml = null, commentsExtensibleXml = null }) {
|
|
18325
|
+
const out = {};
|
|
18326
|
+
if (!commentsXml || !commentsIdsXml && !commentsExtensibleXml) return out;
|
|
18327
|
+
const commentsParsed = parseOoxmlSafe(commentsXml, "application/xml");
|
|
18328
|
+
if (!commentsParsed.doc || commentsParsed.error) return out;
|
|
18329
|
+
const serializer = createSerializer();
|
|
18330
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
18331
|
+
for (const node of Array.from(commentsParsed.doc.getElementsByTagNameNS("*", "comment"))) {
|
|
18332
|
+
const key = commentParaId(node);
|
|
18333
|
+
if (key) byKey.set(key, node);
|
|
18334
|
+
}
|
|
18335
|
+
let idsDoc = null;
|
|
18336
|
+
const durableByKey = /* @__PURE__ */ new Map();
|
|
18337
|
+
const taken = /* @__PURE__ */ new Set();
|
|
18338
|
+
let idsChanged = false;
|
|
18339
|
+
if (commentsIdsXml) {
|
|
18340
|
+
const parsed = parseOoxmlSafe(commentsIdsXml, "application/xml");
|
|
18341
|
+
if (!parsed.doc || parsed.error) return out;
|
|
18342
|
+
idsDoc = parsed.doc;
|
|
18343
|
+
const root = idsDoc.documentElement;
|
|
18344
|
+
for (const entry of Array.from(root.getElementsByTagNameNS("*", "commentId"))) {
|
|
18345
|
+
const key = attrOf(entry, "w16cid:paraId", "paraId").toUpperCase();
|
|
18346
|
+
const durable = attrOf(entry, "w16cid:durableId", "durableId").toUpperCase();
|
|
18347
|
+
if (durable) taken.add(durable);
|
|
18348
|
+
if (!byKey.has(key)) {
|
|
18349
|
+
root.removeChild(entry);
|
|
18350
|
+
idsChanged = true;
|
|
18351
|
+
continue;
|
|
18352
|
+
}
|
|
18353
|
+
durableByKey.set(key, durable);
|
|
18354
|
+
}
|
|
18355
|
+
for (const key of byKey.keys()) {
|
|
18356
|
+
if (durableByKey.has(key)) continue;
|
|
18357
|
+
const durable = durableIdFor(key, taken);
|
|
18358
|
+
const entry = idsDoc.createElementNS(NS_W16CID, "w16cid:commentId");
|
|
18359
|
+
entry.setAttributeNS(NS_W16CID, "w16cid:paraId", key);
|
|
18360
|
+
entry.setAttributeNS(NS_W16CID, "w16cid:durableId", durable);
|
|
18361
|
+
root.appendChild(entry);
|
|
18362
|
+
durableByKey.set(key, durable);
|
|
18363
|
+
idsChanged = true;
|
|
18364
|
+
}
|
|
18365
|
+
}
|
|
18366
|
+
let extDoc = null;
|
|
18367
|
+
let extChanged = false;
|
|
18368
|
+
if (commentsExtensibleXml && idsDoc) {
|
|
18369
|
+
const parsed = parseOoxmlSafe(commentsExtensibleXml, "application/xml");
|
|
18370
|
+
if (parsed.doc && !parsed.error) {
|
|
18371
|
+
extDoc = parsed.doc;
|
|
18372
|
+
const root = extDoc.documentElement;
|
|
18373
|
+
const live = new Set(durableByKey.values());
|
|
18374
|
+
const present = /* @__PURE__ */ new Set();
|
|
18375
|
+
for (const entry of Array.from(root.getElementsByTagNameNS("*", "commentExtensible"))) {
|
|
18376
|
+
const durable = attrOf(entry, "w16cex:durableId", "durableId").toUpperCase();
|
|
18377
|
+
if (!live.has(durable)) {
|
|
18378
|
+
root.removeChild(entry);
|
|
18379
|
+
extChanged = true;
|
|
18380
|
+
} else present.add(durable);
|
|
18381
|
+
}
|
|
18382
|
+
for (const [key, durable] of durableByKey) {
|
|
18383
|
+
if (present.has(durable)) continue;
|
|
18384
|
+
const date = attrOf(byKey.get(key), "w:date", "date");
|
|
18385
|
+
const entry = extDoc.createElementNS(NS_W16CEX, "w16cex:commentExtensible");
|
|
18386
|
+
entry.setAttributeNS(NS_W16CEX, "w16cex:durableId", durable);
|
|
18387
|
+
if (date) entry.setAttributeNS(NS_W16CEX, "w16cex:dateUtc", date);
|
|
18388
|
+
root.appendChild(entry);
|
|
18389
|
+
extChanged = true;
|
|
18390
|
+
}
|
|
18391
|
+
}
|
|
18392
|
+
}
|
|
18393
|
+
if (idsChanged) out.commentsIdsXml = serializer.serializeToString(idsDoc);
|
|
18394
|
+
if (extChanged) out.commentsExtensibleXml = serializer.serializeToString(extDoc);
|
|
18395
|
+
return out;
|
|
18396
|
+
}
|
|
18397
|
+
|
|
18130
18398
|
// core/sha256.js
|
|
18131
18399
|
var K = new Uint32Array([
|
|
18132
18400
|
1116352408,
|
|
@@ -18619,7 +18887,7 @@ function readCommentDefinitions(commentsDoc) {
|
|
|
18619
18887
|
author: attr2(comment, "author") || null,
|
|
18620
18888
|
date: attr2(comment, "date") || null,
|
|
18621
18889
|
text: paragraphs.map((p) => extractCanonicalParagraphText(p)).join("\n"),
|
|
18622
|
-
paraId:
|
|
18890
|
+
paraId: commentParaId(comment) || null
|
|
18623
18891
|
});
|
|
18624
18892
|
}
|
|
18625
18893
|
return result;
|
|
@@ -18850,43 +19118,6 @@ function inspectDocumentParts(parts, options = {}) {
|
|
|
18850
19118
|
};
|
|
18851
19119
|
}
|
|
18852
19120
|
|
|
18853
|
-
// services/comment-builders.js
|
|
18854
|
-
var NS_W14 = "http://schemas.microsoft.com/office/word/2010/wordml";
|
|
18855
|
-
var NS_W15 = "http://schemas.microsoft.com/office/word/2012/wordml";
|
|
18856
|
-
function createCommentParaId(commentId) {
|
|
18857
|
-
const numeric = Number.parseInt(String(commentId), 10);
|
|
18858
|
-
const value = Number.isFinite(numeric) ? 1879048192 + (numeric >>> 0) >>> 0 : 1879048192;
|
|
18859
|
-
return value.toString(16).toUpperCase().padStart(8, "0").slice(-8);
|
|
18860
|
-
}
|
|
18861
|
-
function buildCommentElement(commentId, author, content, date, paraId = createCommentParaId(commentId)) {
|
|
18862
|
-
const initials = author.split(" ").map((word) => word[0]).join("").toUpperCase() || "AI";
|
|
18863
|
-
const escapedContent = escapeXml(content);
|
|
18864
|
-
const escapedAuthor = escapeXml(author);
|
|
18865
|
-
return `<w:comment w:id="${commentId}" w:author="${escapedAuthor}" w:date="${date}" w:initials="${initials}">
|
|
18866
|
-
<w:p w14:paraId="${escapeXml(paraId)}" xmlns:w14="${NS_W14}">
|
|
18867
|
-
<w:r><w:t>${escapedContent}</w:t></w:r>
|
|
18868
|
-
</w:p>
|
|
18869
|
-
</w:comment>`;
|
|
18870
|
-
}
|
|
18871
|
-
function buildCommentsPartXml(comments) {
|
|
18872
|
-
if (!comments || comments.length === 0) {
|
|
18873
|
-
return `<w:comments xmlns:w="${NS_W}"></w:comments>`;
|
|
18874
|
-
}
|
|
18875
|
-
const commentElements = comments.map(
|
|
18876
|
-
(comment) => buildCommentElement(comment.id, comment.author, comment.content, comment.date, comment.paraId)
|
|
18877
|
-
).join("\n ");
|
|
18878
|
-
return `<w:comments xmlns:w="${NS_W}">
|
|
18879
|
-
${commentElements}
|
|
18880
|
-
</w:comments>`;
|
|
18881
|
-
}
|
|
18882
|
-
function buildCommentsExtendedPartXml(entries) {
|
|
18883
|
-
const body = (entries || []).map((entry) => {
|
|
18884
|
-
const parent = entry.paraIdParent ? ` w15:paraIdParent="${escapeXml(entry.paraIdParent)}"` : "";
|
|
18885
|
-
return `<w15:commentEx w15:paraId="${escapeXml(entry.paraId)}"${parent} w15:done="${entry.done ? "1" : "0"}"/>`;
|
|
18886
|
-
}).join("");
|
|
18887
|
-
return `<w15:commentsEx xmlns:w15="${NS_W15}">${body}</w15:commentsEx>`;
|
|
18888
|
-
}
|
|
18889
|
-
|
|
18890
19121
|
// services/comment-locator.js
|
|
18891
19122
|
function createParagraphTextIndex(paragraph, options = {}) {
|
|
18892
19123
|
const revisionView = options.revisionView === "current" ? "accepted" : options.revisionView || "accepted";
|
|
@@ -19347,29 +19578,7 @@ function parseRequired(xml, partName) {
|
|
|
19347
19578
|
}
|
|
19348
19579
|
return { doc: parsed.doc };
|
|
19349
19580
|
}
|
|
19350
|
-
function
|
|
19351
|
-
const ids = /* @__PURE__ */ new Set();
|
|
19352
|
-
for (const p of Array.from(commentsDoc?.getElementsByTagNameNS("*", "p") || [])) {
|
|
19353
|
-
const id = attr3(p, "w14:paraId", "paraId");
|
|
19354
|
-
if (id) ids.add(id.toUpperCase());
|
|
19355
|
-
}
|
|
19356
|
-
for (const ex of Array.from(extendedDoc?.getElementsByTagNameNS("*", "commentEx") || [])) {
|
|
19357
|
-
const id = attr3(ex, "w15:paraId", "paraId");
|
|
19358
|
-
if (id) ids.add(id.toUpperCase());
|
|
19359
|
-
}
|
|
19360
|
-
return ids;
|
|
19361
|
-
}
|
|
19362
|
-
function allocateParaId(commentId, occupied) {
|
|
19363
|
-
let candidate = createCommentParaId(commentId);
|
|
19364
|
-
let value = Number.parseInt(candidate, 16) >>> 0;
|
|
19365
|
-
while (occupied.has(candidate)) {
|
|
19366
|
-
value = value + 1 >>> 0;
|
|
19367
|
-
candidate = value.toString(16).toUpperCase().padStart(8, "0");
|
|
19368
|
-
}
|
|
19369
|
-
occupied.add(candidate);
|
|
19370
|
-
return candidate;
|
|
19371
|
-
}
|
|
19372
|
-
function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, parentCommentId, commentId, commentContent, author, date = (/* @__PURE__ */ new Date()).toISOString() }) {
|
|
19581
|
+
function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, documentXml = null, parentCommentId, commentId, commentContent, author, date = (/* @__PURE__ */ new Date()).toISOString() }) {
|
|
19373
19582
|
if (!commentsXml) return { status: "error", error: { code: "COMMENTS_PART_MISSING", message: "A comment reply requires an existing word/comments.xml part." } };
|
|
19374
19583
|
const commentsParsed = parseRequired(commentsXml, "word/comments.xml");
|
|
19375
19584
|
if (commentsParsed.error) return { status: "error", error: commentsParsed.error };
|
|
@@ -19383,15 +19592,31 @@ function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, par
|
|
|
19383
19592
|
extendedDoc = parsed.doc;
|
|
19384
19593
|
}
|
|
19385
19594
|
const occupied = usedParaIds(commentsDoc, extendedDoc);
|
|
19386
|
-
const
|
|
19595
|
+
if (documentXml) for (const match of String(documentXml).matchAll(/w14:paraId="([0-9A-Fa-f]{8})"/g)) occupied.add(match[1].toUpperCase());
|
|
19596
|
+
const parentParagraph = commentThreadParagraph(parent);
|
|
19387
19597
|
if (!parentParagraph) return { status: "error", error: { code: "PARENT_COMMENT_INVALID", message: `Parent comment '${parentCommentId}' has no paragraph.` } };
|
|
19388
|
-
let parentParaId =
|
|
19598
|
+
let parentParaId = commentParaId(parent);
|
|
19389
19599
|
if (!parentParaId) {
|
|
19390
19600
|
parentParaId = allocateParaId(parentCommentId, occupied);
|
|
19391
19601
|
parentParagraph.setAttributeNS(NS_W14, "w14:paraId", parentParaId);
|
|
19392
19602
|
} else {
|
|
19393
19603
|
parentParaId = parentParaId.toUpperCase();
|
|
19394
19604
|
}
|
|
19605
|
+
for (const node of Array.from(extendedDoc?.getElementsByTagNameNS("*", "commentEx") || [])) {
|
|
19606
|
+
if (attr3(node, "w15:paraId", "paraId").toUpperCase() === parentParaId) {
|
|
19607
|
+
const root2 = attr3(node, "w15:paraIdParent", "paraIdParent").toUpperCase();
|
|
19608
|
+
if (root2) parentParaId = root2;
|
|
19609
|
+
break;
|
|
19610
|
+
}
|
|
19611
|
+
}
|
|
19612
|
+
const threadCommentIds = [];
|
|
19613
|
+
const commentNodes = Array.from(commentsDoc.getElementsByTagNameNS("*", "comment"));
|
|
19614
|
+
const parentOf = new Map(Array.from(extendedDoc?.getElementsByTagNameNS("*", "commentEx") || []).map((node) => [attr3(node, "w15:paraId", "paraId").toUpperCase(), attr3(node, "w15:paraIdParent", "paraIdParent").toUpperCase()]));
|
|
19615
|
+
for (const node of commentNodes) {
|
|
19616
|
+
const key = commentParaId(node);
|
|
19617
|
+
if (key === parentParaId || parentOf.get(key) === parentParaId) threadCommentIds.push(attr3(node, "w:id", "id"));
|
|
19618
|
+
}
|
|
19619
|
+
const threadRootCommentId = attr3(commentNodes.find((node) => commentParaId(node) === parentParaId), "w:id", "id") || String(parentCommentId);
|
|
19395
19620
|
const replyParaId = allocateParaId(commentId, occupied);
|
|
19396
19621
|
const replyParsed = parseRequired(`<w:comments xmlns:w="${NS_W6}" xmlns:w14="${NS_W14}">${buildCommentElement(commentId, author, commentContent, date, replyParaId)}</w:comments>`, "reply comment");
|
|
19397
19622
|
commentsDoc.documentElement.appendChild(commentsDoc.importNode(replyParsed.doc.documentElement.firstChild, true));
|
|
@@ -19400,16 +19625,18 @@ function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, par
|
|
|
19400
19625
|
}
|
|
19401
19626
|
const root = extendedDoc.documentElement;
|
|
19402
19627
|
const entries = Array.from(root.getElementsByTagNameNS("*", "commentEx"));
|
|
19403
|
-
|
|
19628
|
+
const rootEntry = entries.find((node) => attr3(node, "w15:paraId", "paraId").toUpperCase() === parentParaId);
|
|
19629
|
+
if (!rootEntry) {
|
|
19404
19630
|
const parentEx = extendedDoc.createElementNS(NS_W15, "w15:commentEx");
|
|
19405
19631
|
parentEx.setAttributeNS(NS_W15, "w15:paraId", parentParaId);
|
|
19406
19632
|
parentEx.setAttributeNS(NS_W15, "w15:done", "0");
|
|
19407
19633
|
root.appendChild(parentEx);
|
|
19408
19634
|
}
|
|
19635
|
+
const threadDone = attr3(rootEntry, "w15:done", "done") === "1" ? "1" : "0";
|
|
19409
19636
|
const replyEx = extendedDoc.createElementNS(NS_W15, "w15:commentEx");
|
|
19410
19637
|
replyEx.setAttributeNS(NS_W15, "w15:paraId", replyParaId);
|
|
19411
19638
|
replyEx.setAttributeNS(NS_W15, "w15:paraIdParent", parentParaId);
|
|
19412
|
-
replyEx.setAttributeNS(NS_W15, "w15:done",
|
|
19639
|
+
replyEx.setAttributeNS(NS_W15, "w15:done", threadDone);
|
|
19413
19640
|
root.appendChild(replyEx);
|
|
19414
19641
|
const serializer = createSerializer();
|
|
19415
19642
|
return {
|
|
@@ -19422,7 +19649,9 @@ function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, par
|
|
|
19422
19649
|
commentId,
|
|
19423
19650
|
parentCommentId: String(parentCommentId),
|
|
19424
19651
|
paraId: replyParaId,
|
|
19425
|
-
parentParaId
|
|
19652
|
+
parentParaId,
|
|
19653
|
+
threadRootCommentId,
|
|
19654
|
+
threadCommentIds: threadCommentIds.length ? threadCommentIds : [threadRootCommentId]
|
|
19426
19655
|
};
|
|
19427
19656
|
}
|
|
19428
19657
|
|
|
@@ -19487,19 +19716,19 @@ function applyFormattingRemovalToOoxml(ooxmlString, targetText, formatTypes) {
|
|
|
19487
19716
|
if (!targetText || !ooxmlString) return ooxmlString;
|
|
19488
19717
|
const doc = parseOoxml(ooxmlString);
|
|
19489
19718
|
if (!doc) return ooxmlString;
|
|
19490
|
-
const
|
|
19491
|
-
const runs = doc.getElementsByTagNameNS(
|
|
19492
|
-
const insertions = doc.getElementsByTagNameNS(
|
|
19719
|
+
const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
19720
|
+
const runs = doc.getElementsByTagNameNS(NS_W11, "r");
|
|
19721
|
+
const insertions = doc.getElementsByTagNameNS(NS_W11, "ins");
|
|
19493
19722
|
const allRuns = [...Array.from(runs)];
|
|
19494
19723
|
for (const ins of insertions) {
|
|
19495
|
-
const insideRuns = ins.getElementsByTagNameNS(
|
|
19724
|
+
const insideRuns = ins.getElementsByTagNameNS(NS_W11, "r");
|
|
19496
19725
|
allRuns.push(...Array.from(insideRuns));
|
|
19497
19726
|
}
|
|
19498
19727
|
for (const run of allRuns) {
|
|
19499
|
-
const textNodes = run.getElementsByTagNameNS(
|
|
19728
|
+
const textNodes = run.getElementsByTagNameNS(NS_W11, "t");
|
|
19500
19729
|
const runText = Array.from(textNodes).map((t) => t.textContent).join("");
|
|
19501
19730
|
if (runText.includes(targetText) || runText === targetText) {
|
|
19502
|
-
const rPrElements = run.getElementsByTagNameNS(
|
|
19731
|
+
const rPrElements = run.getElementsByTagNameNS(NS_W11, "rPr");
|
|
19503
19732
|
if (rPrElements.length > 0) {
|
|
19504
19733
|
const rPr = rPrElements[0];
|
|
19505
19734
|
const newRPr = removeFormattingFromRPr(rPr, formatTypes);
|
|
@@ -19534,7 +19763,7 @@ var HIGHLIGHT_COLOR_MAP = {
|
|
|
19534
19763
|
"white": "white"
|
|
19535
19764
|
};
|
|
19536
19765
|
function injectHighlightIntoRPr(doc, rPr, color = "yellow", options = {}) {
|
|
19537
|
-
const
|
|
19766
|
+
const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
19538
19767
|
const ooxmlColor = HIGHLIGHT_COLOR_MAP[color.toLowerCase()] || "yellow";
|
|
19539
19768
|
const generateRedlines = options?.generateRedlines ?? false;
|
|
19540
19769
|
const author = options?.author || getDefaultAuthor();
|
|
@@ -19553,10 +19782,10 @@ function injectHighlightIntoRPr(doc, rPr, color = "yellow", options = {}) {
|
|
|
19553
19782
|
}
|
|
19554
19783
|
});
|
|
19555
19784
|
}
|
|
19556
|
-
const existingHighlight = rPrElement.getElementsByTagNameNS(
|
|
19785
|
+
const existingHighlight = rPrElement.getElementsByTagNameNS(NS_W11, "highlight");
|
|
19557
19786
|
Array.from(existingHighlight).forEach(removeNode2);
|
|
19558
19787
|
const highlightEl = createWordElement(doc, "w:highlight");
|
|
19559
|
-
highlightEl.setAttributeNS(
|
|
19788
|
+
highlightEl.setAttributeNS(NS_W11, "w:val", ooxmlColor);
|
|
19560
19789
|
rPrElement.appendChild(highlightEl);
|
|
19561
19790
|
if (generateRedlines && previousRPrState) {
|
|
19562
19791
|
const rPrChange = createWordElement(doc, "w:rPrChange");
|
|
@@ -19565,7 +19794,7 @@ function injectHighlightIntoRPr(doc, rPr, color = "yellow", options = {}) {
|
|
|
19565
19794
|
rPrChange.setAttribute("w:author", metadata.author);
|
|
19566
19795
|
rPrChange.setAttribute("w:date", metadata.date);
|
|
19567
19796
|
rPrChange.appendChild(previousRPrState);
|
|
19568
|
-
const existingChange = rPrElement.getElementsByTagNameNS(
|
|
19797
|
+
const existingChange = rPrElement.getElementsByTagNameNS(NS_W11, "rPrChange");
|
|
19569
19798
|
Array.from(existingChange).forEach(removeNode2);
|
|
19570
19799
|
rPrElement.appendChild(rPrChange);
|
|
19571
19800
|
}
|
|
@@ -19582,12 +19811,12 @@ function applyHighlightToOoxml(ooxmlString, targetText, color = "yellow", option
|
|
|
19582
19811
|
} else {
|
|
19583
19812
|
revisionIdAllocator = createRevisionIdAllocator(doc);
|
|
19584
19813
|
}
|
|
19585
|
-
const
|
|
19814
|
+
const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
19586
19815
|
const getRunText = (run) => {
|
|
19587
|
-
const textNodes = run.getElementsByTagNameNS(
|
|
19816
|
+
const textNodes = run.getElementsByTagNameNS(NS_W11, "t");
|
|
19588
19817
|
return Array.from(textNodes).map((t) => t.textContent).join("");
|
|
19589
19818
|
};
|
|
19590
|
-
const allRuns = Array.from(doc.getElementsByTagNameNS(
|
|
19819
|
+
const allRuns = Array.from(doc.getElementsByTagNameNS(NS_W11, "r"));
|
|
19591
19820
|
const sourceRunsWithClaimedRevisionIds = /* @__PURE__ */ new WeakSet();
|
|
19592
19821
|
const cloneRunWithText2 = (sourceRun, text2, shouldHighlight) => {
|
|
19593
19822
|
const clonedRun = sourceRun.cloneNode(true);
|
|
@@ -19596,14 +19825,14 @@ function applyHighlightToOoxml(ooxmlString, targetText, color = "yellow", option
|
|
|
19596
19825
|
} else {
|
|
19597
19826
|
sourceRunsWithClaimedRevisionIds.add(sourceRun);
|
|
19598
19827
|
}
|
|
19599
|
-
const textNodes = clonedRun.getElementsByTagNameNS(
|
|
19828
|
+
const textNodes = clonedRun.getElementsByTagNameNS(NS_W11, "t");
|
|
19600
19829
|
Array.from(textNodes).forEach(removeNode2);
|
|
19601
19830
|
const newText = createWordElement(doc, "w:t");
|
|
19602
19831
|
newText.setAttribute("xml:space", "preserve");
|
|
19603
19832
|
newText.textContent = text2;
|
|
19604
19833
|
clonedRun.appendChild(newText);
|
|
19605
19834
|
if (shouldHighlight) {
|
|
19606
|
-
const rPrElements = clonedRun.getElementsByTagNameNS(
|
|
19835
|
+
const rPrElements = clonedRun.getElementsByTagNameNS(NS_W11, "rPr");
|
|
19607
19836
|
const existingRPr = rPrElements.length > 0 ? rPrElements[0] : null;
|
|
19608
19837
|
const newRPr = injectHighlightIntoRPr(doc, existingRPr, color, options);
|
|
19609
19838
|
if (existingRPr) {
|
|
@@ -19652,20 +19881,68 @@ function applyHighlightToOoxml(ooxmlString, targetText, color = "yellow", option
|
|
|
19652
19881
|
return serializeOoxml(doc);
|
|
19653
19882
|
}
|
|
19654
19883
|
|
|
19884
|
+
// services/package-parts.js
|
|
19885
|
+
var WORD_ML = "application/vnd.openxmlformats-officedocument.wordprocessingml";
|
|
19886
|
+
var REL_OFFICE_DOC = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
19887
|
+
var PART_SPECS = Object.freeze([
|
|
19888
|
+
{ id: "document", path: "word/document.xml", pathPattern: null, contentType: `${WORD_ML}.document.main+xml`, legacyContentTypes: [], relType: `${REL_OFFICE_DOC}/officeDocument`, relTarget: null, verified: true },
|
|
19889
|
+
{ id: "numbering", path: "word/numbering.xml", pathPattern: null, contentType: `${WORD_ML}.numbering+xml`, legacyContentTypes: [], relType: `${REL_OFFICE_DOC}/numbering`, relTarget: "numbering.xml", verified: false },
|
|
19890
|
+
{ id: "comments", path: "word/comments.xml", pathPattern: null, contentType: `${WORD_ML}.comments+xml`, legacyContentTypes: [], relType: `${REL_OFFICE_DOC}/comments`, relTarget: "comments.xml", verified: true },
|
|
19891
|
+
{
|
|
19892
|
+
id: "commentsExtended",
|
|
19893
|
+
path: "word/commentsExtended.xml",
|
|
19894
|
+
pathPattern: null,
|
|
19895
|
+
contentType: `${WORD_ML}.commentsExtended+xml`,
|
|
19896
|
+
// Written by this package through 0.8.0; Word does not recognize it and offers to repair the file.
|
|
19897
|
+
legacyContentTypes: ["application/vnd.ms-word.commentsExtended+xml"],
|
|
19898
|
+
relType: "http://schemas.microsoft.com/office/2011/relationships/commentsExtended",
|
|
19899
|
+
relTarget: "commentsExtended.xml",
|
|
19900
|
+
verified: true
|
|
19901
|
+
},
|
|
19902
|
+
{ id: "commentsIds", path: "word/commentsIds.xml", pathPattern: null, contentType: `${WORD_ML}.commentsIds+xml`, legacyContentTypes: [], relType: "http://schemas.microsoft.com/office/2016/09/relationships/commentsIds", relTarget: "commentsIds.xml", verified: true },
|
|
19903
|
+
{ id: "commentsExtensible", path: "word/commentsExtensible.xml", pathPattern: null, contentType: `${WORD_ML}.commentsExtensible+xml`, legacyContentTypes: [], relType: "http://schemas.microsoft.com/office/2018/08/relationships/commentsExtensible", relTarget: "commentsExtensible.xml", verified: true },
|
|
19904
|
+
// Not yet produced by any Word fixture (Word only writes people.xml for signed-in authors): unverified.
|
|
19905
|
+
{ id: "people", path: "word/people.xml", pathPattern: null, contentType: `${WORD_ML}.people+xml`, legacyContentTypes: [], relType: "http://schemas.microsoft.com/office/2011/relationships/people", relTarget: "people.xml", verified: false },
|
|
19906
|
+
{ id: "header", path: null, pathPattern: /^word\/header\d+\.xml$/i, contentType: `${WORD_ML}.header+xml`, legacyContentTypes: [], relType: `${REL_OFFICE_DOC}/header`, relTarget: null, verified: true },
|
|
19907
|
+
{ id: "footer", path: null, pathPattern: /^word\/footer\d+\.xml$/i, contentType: `${WORD_ML}.footer+xml`, legacyContentTypes: [], relType: `${REL_OFFICE_DOC}/footer`, relTarget: null, verified: true }
|
|
19908
|
+
].map((spec) => Object.freeze(spec)));
|
|
19909
|
+
var BY_ID = new Map(PART_SPECS.map((spec) => [spec.id, spec]));
|
|
19910
|
+
function getPartSpec(id) {
|
|
19911
|
+
const spec = BY_ID.get(id);
|
|
19912
|
+
if (!spec) throw new Error(`Unknown package part: ${id}`);
|
|
19913
|
+
return spec;
|
|
19914
|
+
}
|
|
19915
|
+
function toEntryName(partName) {
|
|
19916
|
+
return String(partName || "").replace(/^\/+/, "");
|
|
19917
|
+
}
|
|
19918
|
+
function findPartSpecByPartName(partName) {
|
|
19919
|
+
const name = toEntryName(partName);
|
|
19920
|
+
const lower = name.toLowerCase();
|
|
19921
|
+
return PART_SPECS.find((spec) => spec.path ? spec.path.toLowerCase() === lower : spec.pathPattern?.test(name)) || null;
|
|
19922
|
+
}
|
|
19923
|
+
function correctedContentTypeFor(partName, actualContentType) {
|
|
19924
|
+
const spec = findPartSpecByPartName(partName);
|
|
19925
|
+
if (!spec || !spec.legacyContentTypes.includes(actualContentType)) return null;
|
|
19926
|
+
return spec.contentType;
|
|
19927
|
+
}
|
|
19928
|
+
|
|
19655
19929
|
// services/standalone-docx-plumbing.js
|
|
19656
19930
|
var NS_W7 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
19657
19931
|
var NS_CT = "http://schemas.openxmlformats.org/package/2006/content-types";
|
|
19658
19932
|
var NS_RELS = "http://schemas.openxmlformats.org/package/2006/relationships";
|
|
19659
|
-
var
|
|
19660
|
-
var
|
|
19661
|
-
var
|
|
19662
|
-
var
|
|
19663
|
-
var
|
|
19664
|
-
var
|
|
19665
|
-
var
|
|
19666
|
-
var
|
|
19667
|
-
var
|
|
19668
|
-
var
|
|
19933
|
+
var NUMBERING = getPartSpec("numbering");
|
|
19934
|
+
var COMMENTS = getPartSpec("comments");
|
|
19935
|
+
var COMMENTS_EXTENDED = getPartSpec("commentsExtended");
|
|
19936
|
+
var NUMBERING_REL_TYPE = NUMBERING.relType;
|
|
19937
|
+
var NUMBERING_CONTENT_TYPE = NUMBERING.contentType;
|
|
19938
|
+
var COMMENTS_REL_TYPE = COMMENTS.relType;
|
|
19939
|
+
var COMMENTS_CONTENT_TYPE = COMMENTS.contentType;
|
|
19940
|
+
var COMMENTS_EXTENDED_REL_TYPE = COMMENTS_EXTENDED.relType;
|
|
19941
|
+
var COMMENTS_EXTENDED_CONTENT_TYPE = COMMENTS_EXTENDED.contentType;
|
|
19942
|
+
var DOCUMENT_PATH = getPartSpec("document").path;
|
|
19943
|
+
var NUMBERING_PATH = NUMBERING.path;
|
|
19944
|
+
var COMMENTS_PATH = COMMENTS.path;
|
|
19945
|
+
var COMMENTS_EXTENDED_PATH = COMMENTS_EXTENDED.path;
|
|
19669
19946
|
var CONTENT_TYPES_PATH = "[Content_Types].xml";
|
|
19670
19947
|
var DOCUMENT_RELS_PATH = "word/_rels/document.xml.rels";
|
|
19671
19948
|
function parseXmlStrictStandalone(xmlText, label = "xml") {
|
|
@@ -19953,6 +20230,22 @@ async function ensureCommentsExtendedArtifactsInZip(zip, commentsExtendedXml, op
|
|
|
19953
20230
|
}
|
|
19954
20231
|
}
|
|
19955
20232
|
}
|
|
20233
|
+
async function repairKnownContentTypes(zip) {
|
|
20234
|
+
const ctText = await readZipText(zip, CONTENT_TYPES_PATH);
|
|
20235
|
+
if (!ctText) return [];
|
|
20236
|
+
const ctDoc = parseXmlStrictStandalone(ctText, CONTENT_TYPES_PATH);
|
|
20237
|
+
const repairs = [];
|
|
20238
|
+
for (const override of Array.from(ctDoc.getElementsByTagNameNS("*", "Override"))) {
|
|
20239
|
+
const partName = override.getAttribute("PartName") || "";
|
|
20240
|
+
const actual = override.getAttribute("ContentType") || "";
|
|
20241
|
+
const expected = correctedContentTypeFor(partName, actual);
|
|
20242
|
+
if (!expected) continue;
|
|
20243
|
+
override.setAttribute("ContentType", expected);
|
|
20244
|
+
repairs.push({ partName, from: actual, to: expected });
|
|
20245
|
+
}
|
|
20246
|
+
if (repairs.length > 0) zip.file(CONTENT_TYPES_PATH, createSerializer().serializeToString(ctDoc));
|
|
20247
|
+
return repairs;
|
|
20248
|
+
}
|
|
19956
20249
|
async function validateDocxPackage(zip) {
|
|
19957
20250
|
const documentXml = await readZipText(zip, DOCUMENT_PATH);
|
|
19958
20251
|
if (!documentXml) {
|
|
@@ -20034,9 +20327,8 @@ async function validateDocxPackage(zip) {
|
|
|
20034
20327
|
const paraIdToCommentId = /* @__PURE__ */ new Map();
|
|
20035
20328
|
for (const comment of Array.from(commentsDoc.getElementsByTagNameNS("*", "comment"))) {
|
|
20036
20329
|
const id = comment.getAttribute("w:id") || comment.getAttribute("id");
|
|
20037
|
-
const
|
|
20038
|
-
|
|
20039
|
-
if (paraId) paraIdToCommentId.set(paraId.toUpperCase(), id);
|
|
20330
|
+
const paraId = commentParaId(comment);
|
|
20331
|
+
if (paraId) paraIdToCommentId.set(paraId, id);
|
|
20040
20332
|
}
|
|
20041
20333
|
const knownParaIds = new Set(paraIdToCommentId.keys());
|
|
20042
20334
|
const extendedParaIds = /* @__PURE__ */ new Set();
|
|
@@ -20097,10 +20389,39 @@ async function validateDocxPackage(zip) {
|
|
|
20097
20389
|
throw new Error("Validation failed: comments rel missing");
|
|
20098
20390
|
}
|
|
20099
20391
|
}
|
|
20392
|
+
{
|
|
20393
|
+
const relationshipById = /* @__PURE__ */ new Map();
|
|
20394
|
+
for (const rel of Array.from(relsDoc.getElementsByTagNameNS("*", "Relationship"))) {
|
|
20395
|
+
relationshipById.set(rel.getAttribute("Id") || "", { type: rel.getAttribute("Type") || "", target: rel.getAttribute("Target") || "" });
|
|
20396
|
+
}
|
|
20397
|
+
const headerSpec = getPartSpec("header");
|
|
20398
|
+
const footerSpec = getPartSpec("footer");
|
|
20399
|
+
const overrideTypeByPart = new Map(Array.from(ctDoc.getElementsByTagNameNS("*", "Override")).map((override) => [(override.getAttribute("PartName") || "").toLowerCase(), override.getAttribute("ContentType") || ""]));
|
|
20400
|
+
for (const reference of [
|
|
20401
|
+
...Array.from(documentDoc.getElementsByTagNameNS(NS_W7, "headerReference")),
|
|
20402
|
+
...Array.from(documentDoc.getElementsByTagNameNS(NS_W7, "footerReference"))
|
|
20403
|
+
]) {
|
|
20404
|
+
const relId = reference.getAttributeNS?.("http://schemas.openxmlformats.org/officeDocument/2006/relationships", "id") || reference.getAttribute("r:id") || "";
|
|
20405
|
+
const spec = reference.localName === "headerReference" ? headerSpec : footerSpec;
|
|
20406
|
+
const relationship = relationshipById.get(relId);
|
|
20407
|
+
if (!relationship) throw new Error(`Validation failed: ${reference.localName} ${relId || "(no r:id)"} has no document relationship`);
|
|
20408
|
+
if (relationship.type !== spec.relType) throw new Error(`Validation failed: ${reference.localName} ${relId} points at a relationship of the wrong type (${relationship.type})`);
|
|
20409
|
+
const partName = relationship.target.startsWith("/") ? relationship.target : `/word/${relationship.target}`;
|
|
20410
|
+
const partXml = await readZipText(zip, partName.slice(1));
|
|
20411
|
+
if (!partXml) throw new Error(`Validation failed: ${reference.localName} ${relId} targets a missing part: ${partName}`);
|
|
20412
|
+
parseXmlStrictStandalone(partXml, partName.slice(1));
|
|
20413
|
+
const contentType = overrideTypeByPart.get(partName.toLowerCase());
|
|
20414
|
+
if (contentType !== spec.contentType) throw new Error(`Validation failed: ${partName} content type is ${contentType ? `'${contentType}'` : "missing"}; Word expects '${spec.contentType}'`);
|
|
20415
|
+
}
|
|
20416
|
+
}
|
|
20100
20417
|
if (commentsExtendedXml) {
|
|
20101
|
-
const
|
|
20102
|
-
(override) => (override.getAttribute("PartName") || "").toLowerCase() === "/word/commentsextended.xml"
|
|
20418
|
+
const extendedOverride = Array.from(ctDoc.getElementsByTagNameNS("*", "Override")).find(
|
|
20419
|
+
(override) => (override.getAttribute("PartName") || "").toLowerCase() === "/word/commentsextended.xml"
|
|
20103
20420
|
);
|
|
20421
|
+
if (extendedOverride && extendedOverride.getAttribute("ContentType") !== COMMENTS_EXTENDED_CONTENT_TYPE) {
|
|
20422
|
+
throw new Error(`Validation failed: commentsExtended CT override has wrong content type '${extendedOverride.getAttribute("ContentType")}' (Word expects '${COMMENTS_EXTENDED_CONTENT_TYPE}')`);
|
|
20423
|
+
}
|
|
20424
|
+
const hasContentType = !!extendedOverride;
|
|
20104
20425
|
const hasRelationship = Array.from(relsDoc.getElementsByTagNameNS("*", "Relationship")).some(
|
|
20105
20426
|
(rel) => (rel.getAttribute("Type") || "") === COMMENTS_EXTENDED_REL_TYPE
|
|
20106
20427
|
);
|
|
@@ -20464,6 +20785,156 @@ function reconcileReceiptsAgainstOutput(parts, receipts) {
|
|
|
20464
20785
|
return { valid: true };
|
|
20465
20786
|
}
|
|
20466
20787
|
|
|
20788
|
+
// services/headers-footers.js
|
|
20789
|
+
var NS_W8 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
20790
|
+
var NS_R2 = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
20791
|
+
var REVISION_ELEMENTS = /* @__PURE__ */ new Set(["ins", "del", "moveFrom", "moveTo", "rPrChange", "pPrChange", "sectPrChange", "tblPrChange", "tblGridChange", "tcPrChange", "trPrChange", "numberingChange"]);
|
|
20792
|
+
var REVISION_CONTENT_ELEMENTS = /* @__PURE__ */ new Set(["ins", "del", "moveFrom", "moveTo"]);
|
|
20793
|
+
var attrOf2 = (node, qualified, local) => node?.getAttribute?.(qualified) || node?.getAttribute?.(local) || "";
|
|
20794
|
+
function resolveTarget(target) {
|
|
20795
|
+
const clean = String(target || "");
|
|
20796
|
+
if (clean.startsWith("/")) return clean.replace(/^\/+/, "");
|
|
20797
|
+
const parts = ["word"];
|
|
20798
|
+
for (const segment of clean.split("/")) {
|
|
20799
|
+
if (segment === "..") parts.pop();
|
|
20800
|
+
else if (segment && segment !== ".") parts.push(segment);
|
|
20801
|
+
}
|
|
20802
|
+
return parts.join("/");
|
|
20803
|
+
}
|
|
20804
|
+
function discoverHeaderFooterParts({ documentXml, relsXml = null, settingsXml = null }) {
|
|
20805
|
+
if (!documentXml || !relsXml) return [];
|
|
20806
|
+
const documentParsed = parseOoxmlSafe(documentXml, "application/xml");
|
|
20807
|
+
const relsParsed = parseOoxmlSafe(relsXml, "application/xml");
|
|
20808
|
+
if (!documentParsed.doc || documentParsed.error || !relsParsed.doc || relsParsed.error) return [];
|
|
20809
|
+
const targetByRelId = /* @__PURE__ */ new Map();
|
|
20810
|
+
for (const rel of Array.from(relsParsed.doc.getElementsByTagNameNS("*", "Relationship"))) {
|
|
20811
|
+
targetByRelId.set(rel.getAttribute("Id") || "", rel.getAttribute("Target") || "");
|
|
20812
|
+
}
|
|
20813
|
+
const evenAndOdd = !!settingsXml && /<w:evenAndOddHeaders\b(?![^>]*w:val="(?:0|false|off)")/.test(settingsXml);
|
|
20814
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
20815
|
+
const inForce = /* @__PURE__ */ new Map();
|
|
20816
|
+
const sectPrs = Array.from(documentParsed.doc.getElementsByTagNameNS(NS_W8, "sectPr"));
|
|
20817
|
+
sectPrs.forEach((sectPr, sectionIndex) => {
|
|
20818
|
+
const titlePg = Array.from(sectPr.childNodes || []).some((child) => child.localName === "titlePg" && !/^(0|false|off)$/i.test(attrOf2(child, "w:val", "val")));
|
|
20819
|
+
for (const ref of Array.from(sectPr.childNodes || [])) {
|
|
20820
|
+
if (ref.nodeType !== 1 || ref.localName !== "headerReference" && ref.localName !== "footerReference") continue;
|
|
20821
|
+
const relId = ref.getAttributeNS?.(NS_R2, "id") || ref.getAttribute("r:id") || "";
|
|
20822
|
+
const target = targetByRelId.get(relId);
|
|
20823
|
+
if (!target) continue;
|
|
20824
|
+
const path = resolveTarget(target);
|
|
20825
|
+
const kind = ref.localName === "headerReference" ? "header" : "footer";
|
|
20826
|
+
const type = attrOf2(ref, "w:type", "type") || "default";
|
|
20827
|
+
const entry = byPath.get(path) || { path, kind, type, sections: [], applies: [], titlePgSections: 0 };
|
|
20828
|
+
if (!entry.sections.includes(sectionIndex)) entry.sections.push(sectionIndex);
|
|
20829
|
+
if (titlePg) entry.titlePgSections += 1;
|
|
20830
|
+
byPath.set(path, entry);
|
|
20831
|
+
inForce.set(`${kind}:${type}`, path);
|
|
20832
|
+
}
|
|
20833
|
+
for (const path of inForce.values()) {
|
|
20834
|
+
const entry = byPath.get(path);
|
|
20835
|
+
if (entry && !entry.applies.includes(sectionIndex)) entry.applies.push(sectionIndex);
|
|
20836
|
+
}
|
|
20837
|
+
});
|
|
20838
|
+
return Array.from(byPath.values()).map((entry) => ({
|
|
20839
|
+
path: entry.path,
|
|
20840
|
+
kind: entry.kind,
|
|
20841
|
+
type: entry.type,
|
|
20842
|
+
sections: entry.sections,
|
|
20843
|
+
appliesToSections: entry.applies,
|
|
20844
|
+
sharedBySections: entry.applies.length > 1,
|
|
20845
|
+
// first-page parts only show with w:titlePg; even parts only with w:evenAndOddHeaders in settings.xml.
|
|
20846
|
+
active: entry.type === "first" ? entry.titlePgSections > 0 : entry.type === "even" ? evenAndOdd : true
|
|
20847
|
+
}));
|
|
20848
|
+
}
|
|
20849
|
+
function resolvePartSelector(parts, selector) {
|
|
20850
|
+
const describe = (part) => ({ path: part.path, kind: part.kind, type: part.type, sections: part.sections, appliesToSections: part.appliesToSections });
|
|
20851
|
+
if (typeof selector === "string") {
|
|
20852
|
+
const wanted = selector.replace(/^\/+/, "").toLowerCase();
|
|
20853
|
+
const match = parts.find((part) => part.path.toLowerCase() === wanted || part.path.toLowerCase() === `word/${wanted}`);
|
|
20854
|
+
if (match) return { part: match };
|
|
20855
|
+
return { error: { code: "PART_NOT_FOUND", message: `No header/footer part '${selector}' is referenced by the document.`, candidates: parts.map(describe) } };
|
|
20856
|
+
}
|
|
20857
|
+
if (!selector || typeof selector !== "object" || selector.kind !== "header" && selector.kind !== "footer") {
|
|
20858
|
+
return { error: { code: "INVALID_OPERATION", message: 'part must be a part path or { kind: "header"|"footer", type?: "default"|"first"|"even", section?: number }.' } };
|
|
20859
|
+
}
|
|
20860
|
+
const type = selector.type || "default";
|
|
20861
|
+
if (!["default", "first", "even"].includes(type)) {
|
|
20862
|
+
return { error: { code: "INVALID_OPERATION", message: `part.type must be default, first or even (got '${type}').` } };
|
|
20863
|
+
}
|
|
20864
|
+
let matches = parts.filter((part) => part.kind === selector.kind && part.type === type);
|
|
20865
|
+
if (Number.isInteger(selector.section)) matches = matches.filter((part) => part.appliesToSections.includes(selector.section));
|
|
20866
|
+
if (matches.length === 1) return { part: matches[0] };
|
|
20867
|
+
if (matches.length === 0) {
|
|
20868
|
+
return { error: { code: "PART_NOT_FOUND", message: `The document has no ${type} ${selector.kind}${Number.isInteger(selector.section) ? ` for section ${selector.section}` : ""}.`, candidates: parts.map(describe) } };
|
|
20869
|
+
}
|
|
20870
|
+
return { error: { code: "PART_AMBIGUOUS", message: `Several ${type} ${selector.kind} parts exist; add "section" or use the part path.`, candidates: matches.map(describe) } };
|
|
20871
|
+
}
|
|
20872
|
+
function isHeaderFooterPath(path) {
|
|
20873
|
+
const spec = findPartSpecByPartName(path);
|
|
20874
|
+
return spec?.id === "header" || spec?.id === "footer";
|
|
20875
|
+
}
|
|
20876
|
+
function countRevisionsInsideFields(xml) {
|
|
20877
|
+
const parsed = parseOoxmlSafe(xml, "application/xml");
|
|
20878
|
+
if (!parsed.doc || parsed.error) return 0;
|
|
20879
|
+
let depth = 0;
|
|
20880
|
+
let count = 0;
|
|
20881
|
+
const visit = (node) => {
|
|
20882
|
+
for (const child of Array.from(node.childNodes || [])) {
|
|
20883
|
+
if (child.nodeType !== 1) continue;
|
|
20884
|
+
const name = child.localName;
|
|
20885
|
+
if (name === "fldChar") {
|
|
20886
|
+
const type = attrOf2(child, "w:fldCharType", "fldCharType");
|
|
20887
|
+
if (type === "begin") depth += 1;
|
|
20888
|
+
else if (type === "end") depth = Math.max(0, depth - 1);
|
|
20889
|
+
continue;
|
|
20890
|
+
}
|
|
20891
|
+
if (REVISION_CONTENT_ELEMENTS.has(name) && depth > 0) count += 1;
|
|
20892
|
+
if (name === "fldSimple") {
|
|
20893
|
+
depth += 1;
|
|
20894
|
+
visit(child);
|
|
20895
|
+
depth -= 1;
|
|
20896
|
+
continue;
|
|
20897
|
+
}
|
|
20898
|
+
visit(child);
|
|
20899
|
+
}
|
|
20900
|
+
};
|
|
20901
|
+
visit(parsed.doc.documentElement);
|
|
20902
|
+
return count;
|
|
20903
|
+
}
|
|
20904
|
+
function collectRevisionIds(xml) {
|
|
20905
|
+
const ids = /* @__PURE__ */ new Set();
|
|
20906
|
+
for (const match of String(xml || "").matchAll(/<w:(?:ins|del|moveFrom|moveTo|rPrChange|pPrChange|sectPrChange|tblPrChange|tblGridChange|tcPrChange|trPrChange|numberingChange)\b[^>]*?\bw:id="(\d+)"/g)) ids.add(match[1]);
|
|
20907
|
+
return ids;
|
|
20908
|
+
}
|
|
20909
|
+
function renumberCollidingRevisionIds(xml, taken) {
|
|
20910
|
+
const parsed = parseOoxmlSafe(xml, "application/xml");
|
|
20911
|
+
if (!parsed.doc || parsed.error) return { xml, renumbered: 0, idMap: /* @__PURE__ */ new Map() };
|
|
20912
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
20913
|
+
let next = 1 + Math.max(0, ...Array.from(taken, (id) => Number(id)).filter(Number.isFinite));
|
|
20914
|
+
const used = new Set(taken);
|
|
20915
|
+
let renumbered = 0;
|
|
20916
|
+
const visit = (node) => {
|
|
20917
|
+
for (const child of Array.from(node.childNodes || [])) {
|
|
20918
|
+
if (child.nodeType !== 1) continue;
|
|
20919
|
+
if (REVISION_ELEMENTS.has(child.localName)) {
|
|
20920
|
+
const id = attrOf2(child, "w:id", "id");
|
|
20921
|
+
if (id && used.has(id)) {
|
|
20922
|
+
while (used.has(String(next))) next += 1;
|
|
20923
|
+
child.setAttributeNS(NS_W8, "w:id", String(next));
|
|
20924
|
+
idMap.set(id, String(next));
|
|
20925
|
+
used.add(String(next));
|
|
20926
|
+
renumbered += 1;
|
|
20927
|
+
} else if (id) {
|
|
20928
|
+
used.add(id);
|
|
20929
|
+
}
|
|
20930
|
+
}
|
|
20931
|
+
visit(child);
|
|
20932
|
+
}
|
|
20933
|
+
};
|
|
20934
|
+
visit(parsed.doc.documentElement);
|
|
20935
|
+
return renumbered > 0 ? { xml: createSerializer().serializeToString(parsed.doc), renumbered, idMap } : { xml, renumbered: 0, idMap };
|
|
20936
|
+
}
|
|
20937
|
+
|
|
20467
20938
|
// services/error-recovery.js
|
|
20468
20939
|
var ERROR_RECOVERY_VERSION = 1;
|
|
20469
20940
|
var RULES = Object.freeze({
|
|
@@ -20501,6 +20972,12 @@ var RULES = Object.freeze({
|
|
|
20501
20972
|
STRUCTURED_CONTENT_INVALID: ["request", "request_fixable", "change_request"],
|
|
20502
20973
|
UNSUPPORTED_INSERTION_AFFINITY: ["request", "request_fixable", "change_request"],
|
|
20503
20974
|
EXISTING_REVISIONS: ["target-safety", "policy_choice_required", "set_option"],
|
|
20975
|
+
PART_NOT_FOUND: ["target-resolution", "target_refresh_required", "reinspect"],
|
|
20976
|
+
PART_AMBIGUOUS: ["target-resolution", "candidate_selection_required", "choose_candidate"],
|
|
20977
|
+
COMMENT_IN_HEADER_FOOTER: ["request", "request_fixable", "change_request"],
|
|
20978
|
+
FIELD_EDIT_REFUSED: ["target-safety", "request_fixable", "change_request"],
|
|
20979
|
+
COMMENT_NOT_FOUND: ["target-resolution", "target_refresh_required", "reinspect"],
|
|
20980
|
+
PARENT_ANCHOR_NOT_FOUND: ["target-resolution", "target_refresh_required", "reinspect"],
|
|
20504
20981
|
COMMENTED_CONTENT_MERGE: ["target-safety", "user_authorization_required", "resolve_comments"],
|
|
20505
20982
|
COMMENTED_CONTENT_DELETE: ["target-safety", "user_authorization_required", "resolve_comments"],
|
|
20506
20983
|
OVERLAPPING_SOURCE_TARGETS: ["batch-compilation", "source_conflict", "consolidate_operations"],
|
|
@@ -20953,6 +21430,7 @@ var SUPPORTED_OPERATION_TYPES = /* @__PURE__ */ new Set([
|
|
|
20953
21430
|
"delete",
|
|
20954
21431
|
"comment",
|
|
20955
21432
|
"comment_reply",
|
|
21433
|
+
"comment_resolve",
|
|
20956
21434
|
"highlight"
|
|
20957
21435
|
]);
|
|
20958
21436
|
var EXISTING_REVISIONS_POLICIES = Object.freeze([
|
|
@@ -20971,11 +21449,14 @@ function isRecord(value) {
|
|
|
20971
21449
|
function nonEmptyString(value) {
|
|
20972
21450
|
return typeof value === "string" && value.trim().length > 0;
|
|
20973
21451
|
}
|
|
21452
|
+
function isCommentThreadOperationKind(kind) {
|
|
21453
|
+
return kind === "comment_reply" || kind === "comment_resolve";
|
|
21454
|
+
}
|
|
20974
21455
|
function getCanonicalOperationType(operation) {
|
|
20975
21456
|
const type = operation?.type;
|
|
20976
21457
|
if (type === "insert" && operation?.target?.revisionView === "rejected") return "rejected-insert";
|
|
20977
21458
|
if (type === "restore") return "restore";
|
|
20978
|
-
if (type === "comment" || type === "comment_reply" || type === "highlight") return type;
|
|
21459
|
+
if (type === "comment" || type === "comment_reply" || type === "comment_resolve" || type === "highlight") return type;
|
|
20979
21460
|
if (type === "paragraph-format") return "paragraph-format";
|
|
20980
21461
|
if (type === "character-format" || type === "format" && (operation?.textToFormat != null || operation?.properties != null)) return "format";
|
|
20981
21462
|
return "redline";
|
|
@@ -21095,7 +21576,7 @@ function validateDocumentOperation(operation) {
|
|
|
21095
21576
|
};
|
|
21096
21577
|
}
|
|
21097
21578
|
const target = normalized.targetDescriptor;
|
|
21098
|
-
if (normalized.operationKind
|
|
21579
|
+
if (!isCommentThreadOperationKind(normalized.operationKind) && !nonEmptyString(target.text) && target.index == null && !target.paragraphId && !target.fingerprint && !target.captureRef) {
|
|
21099
21580
|
return {
|
|
21100
21581
|
valid: false,
|
|
21101
21582
|
error: {
|
|
@@ -21322,6 +21803,14 @@ function validateDocumentOperation(operation) {
|
|
|
21322
21803
|
return { valid: false, error: { code: "INVALID_OPERATION", message: 'Comment reply operations require a non-empty "commentContent" field.' } };
|
|
21323
21804
|
}
|
|
21324
21805
|
}
|
|
21806
|
+
if (normalized.operationKind === "comment_resolve") {
|
|
21807
|
+
if (!nonEmptyString(String(normalized.commentId ?? ""))) {
|
|
21808
|
+
return { valid: false, error: { code: "INVALID_OPERATION", message: 'Comment resolve operations require a "commentId".' } };
|
|
21809
|
+
}
|
|
21810
|
+
if (normalized.resolved != null && typeof normalized.resolved !== "boolean") {
|
|
21811
|
+
return { valid: false, error: { code: "INVALID_OPERATION", message: 'Comment resolve "resolved" must be a boolean when provided.' } };
|
|
21812
|
+
}
|
|
21813
|
+
}
|
|
21325
21814
|
if (normalized.operationKind === "highlight") {
|
|
21326
21815
|
if (!nonEmptyString(normalized.textToHighlight)) {
|
|
21327
21816
|
return {
|
|
@@ -21801,7 +22290,7 @@ function compileOperationBatch(xmlDoc, operations = [], options = {}) {
|
|
|
21801
22290
|
const dynamicOccurrence = operation.targetDescriptor?.occurrence != null && !operation.targetDescriptor?.paragraphId && !operation.targetDescriptor?.fingerprint;
|
|
21802
22291
|
const compositeTextTarget = typeof operation.targetDescriptor?.text === "string" && /\r|\n/.test(operation.targetDescriptor.text) && !operation.targetEndDescriptor;
|
|
21803
22292
|
if (dynamicOccurrence || compositeTextTarget) binding.dynamic = true;
|
|
21804
|
-
if (validation.valid && operation.operationKind !== "comment_reply" && !operation.targetDescriptor?.captureRef && !binding.dynamic) {
|
|
22293
|
+
if (validation.valid && operation.operationKind !== "comment_reply" && operation.operationKind !== "comment_resolve" && !operation.targetDescriptor?.captureRef && !binding.dynamic) {
|
|
21805
22294
|
try {
|
|
21806
22295
|
const start = resolveDescriptor(
|
|
21807
22296
|
xmlDoc,
|
|
@@ -22302,12 +22791,12 @@ function resolveTargetFromCapture(xmlDoc, session, targetDescriptor, _opType = "
|
|
|
22302
22791
|
}
|
|
22303
22792
|
|
|
22304
22793
|
// services/document-operation-mutations.js
|
|
22305
|
-
var
|
|
22794
|
+
var NS_W9 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
22306
22795
|
var NS_W142 = "http://schemas.microsoft.com/office/word/2010/wordml";
|
|
22307
22796
|
function getCommentIdsInElement(element) {
|
|
22308
22797
|
const ids = /* @__PURE__ */ new Set();
|
|
22309
22798
|
for (const localName2 of ["commentRangeStart", "commentRangeEnd", "commentReference"]) {
|
|
22310
|
-
for (const node of Array.from(element?.getElementsByTagNameNS?.(
|
|
22799
|
+
for (const node of Array.from(element?.getElementsByTagNameNS?.(NS_W9, localName2) || [])) {
|
|
22311
22800
|
const id = node.getAttribute("w:id") || node.getAttribute("id");
|
|
22312
22801
|
if (id !== "") ids.add(id);
|
|
22313
22802
|
}
|
|
@@ -22521,7 +23010,7 @@ function normalizeBodySectionOrder(xmlDoc) {
|
|
|
22521
23010
|
normalizeBodySectionOrderStandalone(xmlDoc);
|
|
22522
23011
|
}
|
|
22523
23012
|
function removeProofErrNodes(paragraph) {
|
|
22524
|
-
for (const node of Array.from(paragraph?.getElementsByTagNameNS?.(
|
|
23013
|
+
for (const node of Array.from(paragraph?.getElementsByTagNameNS?.(NS_W9, "proofErr") || [])) {
|
|
22525
23014
|
node.parentNode?.removeChild(node);
|
|
22526
23015
|
}
|
|
22527
23016
|
}
|
|
@@ -22532,12 +23021,12 @@ function preprocessRedlineTargetParagraph(targetParagraph) {
|
|
|
22532
23021
|
function getDirectWordChild4(element, localName2) {
|
|
22533
23022
|
if (!element) return null;
|
|
22534
23023
|
return Array.from(element.childNodes || []).find(
|
|
22535
|
-
(node) => node && node.nodeType === 1 && node.namespaceURI ===
|
|
23024
|
+
(node) => node && node.nodeType === 1 && node.namespaceURI === NS_W9 && node.localName === localName2
|
|
22536
23025
|
) || null;
|
|
22537
23026
|
}
|
|
22538
23027
|
function computeTableIndexInDocument(xmlDoc, tableElement) {
|
|
22539
23028
|
if (!xmlDoc || !tableElement) return null;
|
|
22540
|
-
const tables = Array.from(xmlDoc.getElementsByTagNameNS(
|
|
23029
|
+
const tables = Array.from(xmlDoc.getElementsByTagNameNS(NS_W9, "tbl"));
|
|
22541
23030
|
const idx = tables.indexOf(tableElement);
|
|
22542
23031
|
return idx >= 0 ? idx + 1 : null;
|
|
22543
23032
|
}
|
|
@@ -22587,7 +23076,7 @@ function buildInsertedListParagraph(xmlDoc, anchorParagraph, entry, revisionMeta
|
|
|
22587
23076
|
markParagraphMarkInserted(xmlDoc, paragraph, author);
|
|
22588
23077
|
}
|
|
22589
23078
|
const run = createWordElement(xmlDoc, "w:r");
|
|
22590
|
-
const anchorFirstRun = Array.from(anchorParagraph.getElementsByTagNameNS(
|
|
23079
|
+
const anchorFirstRun = Array.from(anchorParagraph.getElementsByTagNameNS(NS_W9, "r"))[0] || null;
|
|
22591
23080
|
const anchorRunPr = anchorFirstRun ? getDirectWordChild4(anchorFirstRun, "rPr") : null;
|
|
22592
23081
|
if (anchorRunPr) {
|
|
22593
23082
|
run.appendChild(clonePropertiesWithoutRevisionHistory(anchorRunPr));
|
|
@@ -22612,7 +23101,7 @@ function buildInsertedListParagraph(xmlDoc, anchorParagraph, entry, revisionMeta
|
|
|
22612
23101
|
}
|
|
22613
23102
|
function serializeParagraphRangeAsDocument(paragraphs, serializer) {
|
|
22614
23103
|
const paragraphXml = (paragraphs || []).map((paragraph) => serializer.serializeToString(paragraph)).join("");
|
|
22615
|
-
return `<w:document xmlns:w="${
|
|
23104
|
+
return `<w:document xmlns:w="${NS_W9}"><w:body>${paragraphXml}</w:body></w:document>`;
|
|
22616
23105
|
}
|
|
22617
23106
|
function applyExplicitRangeListInsertions({
|
|
22618
23107
|
xmlDoc,
|
|
@@ -22681,7 +23170,7 @@ function buildEmptyParagraphTemplateFromAnchor(xmlDoc, anchorParagraph) {
|
|
|
22681
23170
|
const anchorPPr = getDirectWordChild4(anchorParagraph, "pPr");
|
|
22682
23171
|
if (anchorPPr) paragraph.appendChild(clonePropertiesWithoutRevisionHistory(anchorPPr));
|
|
22683
23172
|
const run = createWordElement(xmlDoc, "w:r");
|
|
22684
|
-
const anchorFirstRun = Array.from(anchorParagraph.getElementsByTagNameNS(
|
|
23173
|
+
const anchorFirstRun = Array.from(anchorParagraph.getElementsByTagNameNS(NS_W9, "r"))[0] || null;
|
|
22685
23174
|
const anchorRunPr = anchorFirstRun ? getDirectWordChild4(anchorFirstRun, "rPr") : null;
|
|
22686
23175
|
if (anchorRunPr) run.appendChild(clonePropertiesWithoutRevisionHistory(anchorRunPr));
|
|
22687
23176
|
const textNode = createWordElement(xmlDoc, "w:t");
|
|
@@ -22704,7 +23193,7 @@ function wrapParagraphContentInInsertion(xmlDoc, paragraph, revisionMetadata, au
|
|
|
22704
23193
|
ins.setAttribute("w:author", metadata.author);
|
|
22705
23194
|
ins.setAttribute("w:date", metadata.date);
|
|
22706
23195
|
for (const child of Array.from(paragraph.childNodes || [])) {
|
|
22707
|
-
if (child?.nodeType === 1 && child.namespaceURI ===
|
|
23196
|
+
if (child?.nodeType === 1 && child.namespaceURI === NS_W9 && child.localName === "pPr") continue;
|
|
22708
23197
|
ins.appendChild(child.cloneNode(true));
|
|
22709
23198
|
}
|
|
22710
23199
|
wrappedParagraph.appendChild(ins);
|
|
@@ -22728,7 +23217,7 @@ async function buildInsertedPlainParagraph(xmlDoc, anchorParagraph, text2, revis
|
|
|
22728
23217
|
if (typeof markdownResult?.oxml === "string") {
|
|
22729
23218
|
const extracted = extractReplacementNodes(markdownResult.oxml);
|
|
22730
23219
|
sourceParagraph = (extracted.replacementNodes || []).find(
|
|
22731
|
-
(node) => node && node.nodeType === 1 && node.namespaceURI ===
|
|
23220
|
+
(node) => node && node.nodeType === 1 && node.namespaceURI === NS_W9 && node.localName === "p"
|
|
22732
23221
|
) || null;
|
|
22733
23222
|
}
|
|
22734
23223
|
if (!sourceParagraph) {
|
|
@@ -22760,7 +23249,7 @@ function collectNumberingIdsFromNodes(nodes) {
|
|
|
22760
23249
|
}
|
|
22761
23250
|
function directDeletionCarrierText(carrier) {
|
|
22762
23251
|
return Array.from(carrier?.childNodes || []).reduce((text2, child) => {
|
|
22763
|
-
return text2 + (child?.nodeType === 1 && child.namespaceURI ===
|
|
23252
|
+
return text2 + (child?.nodeType === 1 && child.namespaceURI === NS_W9 && child.localName === "r" ? getRunContentPieces(child).map((piece) => piece.text).join("") : "");
|
|
22764
23253
|
}, "");
|
|
22765
23254
|
}
|
|
22766
23255
|
function findOccurrenceOffset(text2, needle, occurrence) {
|
|
@@ -22802,9 +23291,9 @@ function hasUnsupportedDeletionSplitMarkup(paragraph, carrier) {
|
|
|
22802
23291
|
}
|
|
22803
23292
|
for (const child of Array.from(carrier?.childNodes || [])) {
|
|
22804
23293
|
if (child.nodeType !== 1) continue;
|
|
22805
|
-
if (!(child.namespaceURI ===
|
|
23294
|
+
if (!(child.namespaceURI === NS_W9 && child.localName === "r")) return true;
|
|
22806
23295
|
for (const runChild of Array.from(child.childNodes || [])) {
|
|
22807
|
-
if (runChild.nodeType !== 1 || runChild.namespaceURI !==
|
|
23296
|
+
if (runChild.nodeType !== 1 || runChild.namespaceURI !== NS_W9) continue;
|
|
22808
23297
|
if (!["rPr", "delText", "t", "tab", "br", "cr", "noBreakHyphen", "softHyphen"].includes(runChild.localName)) return true;
|
|
22809
23298
|
}
|
|
22810
23299
|
}
|
|
@@ -22813,7 +23302,7 @@ function hasUnsupportedDeletionSplitMarkup(paragraph, carrier) {
|
|
|
22813
23302
|
function insertedRunPropertiesAtDeletionOffset(carrier, localOffset) {
|
|
22814
23303
|
let offset = 0;
|
|
22815
23304
|
for (const child of Array.from(carrier?.childNodes || [])) {
|
|
22816
|
-
if (!(child?.nodeType === 1 && child.namespaceURI ===
|
|
23305
|
+
if (!(child?.nodeType === 1 && child.namespaceURI === NS_W9 && child.localName === "r")) continue;
|
|
22817
23306
|
const length = getRunTextLength(getRunContentPieces(child));
|
|
22818
23307
|
if (localOffset <= offset + length) {
|
|
22819
23308
|
return clonePropertiesWithoutRevisionHistory(getDirectWordChild4(child, "rPr"));
|
|
@@ -22843,7 +23332,7 @@ function normalizedAuthor2(author) {
|
|
|
22843
23332
|
function directWordParagraphSibling(paragraph, direction = "nextSibling") {
|
|
22844
23333
|
let cursor = paragraph?.[direction] || null;
|
|
22845
23334
|
while (cursor) {
|
|
22846
|
-
if (cursor.nodeType === 1 && cursor.namespaceURI ===
|
|
23335
|
+
if (cursor.nodeType === 1 && cursor.namespaceURI === NS_W9 && cursor.localName === "p") return cursor;
|
|
22847
23336
|
cursor = cursor[direction] || null;
|
|
22848
23337
|
}
|
|
22849
23338
|
return null;
|
|
@@ -22853,7 +23342,7 @@ function createSanitizedRestorationPPr(xmlDoc, sourceParagraph) {
|
|
|
22853
23342
|
const pPr = createWordElement(xmlDoc, "w:pPr");
|
|
22854
23343
|
if (!sourcePPr) return pPr;
|
|
22855
23344
|
for (const child of Array.from(sourcePPr.childNodes || [])) {
|
|
22856
|
-
if (child.nodeType !== 1 || child.namespaceURI !==
|
|
23345
|
+
if (child.nodeType !== 1 || child.namespaceURI !== NS_W9) continue;
|
|
22857
23346
|
if (!RESTORATION_PPR_ALLOWLIST.has(child.localName)) continue;
|
|
22858
23347
|
pPr.appendChild(xmlDoc.importNode(child, true));
|
|
22859
23348
|
}
|
|
@@ -22862,14 +23351,14 @@ function createSanitizedRestorationPPr(xmlDoc, sourceParagraph) {
|
|
|
22862
23351
|
function collectRestorationAnchorWarnings(sourceParagraph) {
|
|
22863
23352
|
const warnings = [];
|
|
22864
23353
|
const bookmarkNames = /* @__PURE__ */ new Set();
|
|
22865
|
-
for (const node of Array.from(sourceParagraph?.getElementsByTagNameNS?.(
|
|
23354
|
+
for (const node of Array.from(sourceParagraph?.getElementsByTagNameNS?.(NS_W9, "bookmarkStart") || [])) {
|
|
22866
23355
|
const name = wordAttribute4(node, "name") || "(unnamed)";
|
|
22867
23356
|
bookmarkNames.add(name);
|
|
22868
23357
|
}
|
|
22869
23358
|
for (const name of bookmarkNames) warnings.push(`RESTORATION_DROPPED_BOOKMARK:${name}`);
|
|
22870
23359
|
const commentIds = /* @__PURE__ */ new Set();
|
|
22871
23360
|
for (const localName2 of ["commentRangeStart", "commentRangeEnd", "commentReference"]) {
|
|
22872
|
-
for (const node of Array.from(sourceParagraph?.getElementsByTagNameNS?.(
|
|
23361
|
+
for (const node of Array.from(sourceParagraph?.getElementsByTagNameNS?.(NS_W9, localName2) || [])) {
|
|
22873
23362
|
const id = wordAttribute4(node, "id");
|
|
22874
23363
|
if (id !== "") commentIds.add(id);
|
|
22875
23364
|
}
|
|
@@ -22886,24 +23375,24 @@ function removeRestorationAnchors(paragraph) {
|
|
|
22886
23375
|
"commentReference"
|
|
22887
23376
|
];
|
|
22888
23377
|
for (const localName2 of anchorNames) {
|
|
22889
|
-
for (const node of Array.from(paragraph?.getElementsByTagNameNS?.(
|
|
23378
|
+
for (const node of Array.from(paragraph?.getElementsByTagNameNS?.(NS_W9, localName2) || [])) {
|
|
22890
23379
|
node.parentNode?.removeChild(node);
|
|
22891
23380
|
}
|
|
22892
23381
|
}
|
|
22893
|
-
for (const run of Array.from(paragraph?.getElementsByTagNameNS?.(
|
|
22894
|
-
const meaningful = Array.from(run.childNodes || []).some((child) => child.nodeType === 1 && child.namespaceURI ===
|
|
23382
|
+
for (const run of Array.from(paragraph?.getElementsByTagNameNS?.(NS_W9, "r") || [])) {
|
|
23383
|
+
const meaningful = Array.from(run.childNodes || []).some((child) => child.nodeType === 1 && child.namespaceURI === NS_W9 && child.localName !== "rPr");
|
|
22895
23384
|
if (!meaningful) run.parentNode?.removeChild(run);
|
|
22896
23385
|
}
|
|
22897
23386
|
}
|
|
22898
23387
|
function buildRejectedRestorationTemplate(xmlDoc, sourceParagraph, serializer) {
|
|
22899
23388
|
const rejected = rejectTrackedChangesInOoxml(serializer.serializeToString(sourceParagraph), { allAuthors: true });
|
|
22900
23389
|
const parsed = parseOoxmlSafe(rejected.oxml, "application/xml");
|
|
22901
|
-
const rejectedParagraph = parsed.doc?.getElementsByTagNameNS?.(
|
|
23390
|
+
const rejectedParagraph = parsed.doc?.getElementsByTagNameNS?.(NS_W9, "p")?.[0] || null;
|
|
22902
23391
|
if (!rejectedParagraph) return null;
|
|
22903
23392
|
const paragraph = createWordElement(xmlDoc, "w:p");
|
|
22904
23393
|
paragraph.appendChild(createSanitizedRestorationPPr(xmlDoc, sourceParagraph));
|
|
22905
23394
|
for (const child of Array.from(rejectedParagraph.childNodes || [])) {
|
|
22906
|
-
if (child.nodeType === 1 && child.namespaceURI ===
|
|
23395
|
+
if (child.nodeType === 1 && child.namespaceURI === NS_W9 && child.localName === "pPr") continue;
|
|
22907
23396
|
paragraph.appendChild(xmlDoc.importNode(child, true));
|
|
22908
23397
|
}
|
|
22909
23398
|
removeRestorationAnchors(paragraph);
|
|
@@ -22924,7 +23413,7 @@ async function editRestorationTemplate(xmlDoc, template, modifiedText, author, s
|
|
|
22924
23413
|
);
|
|
22925
23414
|
if (result?.status === "error" || typeof result?.oxml !== "string") return null;
|
|
22926
23415
|
const extracted = extractReplacementNodes(result.oxml);
|
|
22927
|
-
const paragraph = (extracted.replacementNodes || []).find((node) => node?.nodeType === 1 && node.namespaceURI ===
|
|
23416
|
+
const paragraph = (extracted.replacementNodes || []).find((node) => node?.nodeType === 1 && node.namespaceURI === NS_W9 && node.localName === "p");
|
|
22928
23417
|
return paragraph ? xmlDoc.importNode(paragraph, true) : null;
|
|
22929
23418
|
}
|
|
22930
23419
|
function allocateFreshParagraphId(xmlDoc, operationSession) {
|
|
@@ -23095,7 +23584,7 @@ async function insertIntoRejectedDeletedText(documentXml, targetText, anchor, mo
|
|
|
23095
23584
|
}
|
|
23096
23585
|
const insertionOffset = anchorStart + anchor.offset;
|
|
23097
23586
|
const carriers = Array.from(paragraph.childNodes || []).filter(
|
|
23098
|
-
(node) => node?.nodeType === 1 && node.namespaceURI ===
|
|
23587
|
+
(node) => node?.nodeType === 1 && node.namespaceURI === NS_W9 && node.localName === "del"
|
|
23099
23588
|
);
|
|
23100
23589
|
if (carriers.some((carrier) => hasUnsupportedDeletionSplitMarkup(paragraph, carrier))) {
|
|
23101
23590
|
return {
|
|
@@ -23752,7 +24241,7 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
23752
24241
|
};
|
|
23753
24242
|
}
|
|
23754
24243
|
if (!allSame) {
|
|
23755
|
-
const hasMove = targetParagraph.getElementsByTagNameNS(
|
|
24244
|
+
const hasMove = targetParagraph.getElementsByTagNameNS(NS_W9, "moveFrom").length > 0 || targetParagraph.getElementsByTagNameNS(NS_W9, "moveTo").length > 0;
|
|
23756
24245
|
if (hasMove) {
|
|
23757
24246
|
return {
|
|
23758
24247
|
documentXml,
|
|
@@ -23767,8 +24256,8 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
23767
24256
|
}
|
|
23768
24257
|
}
|
|
23769
24258
|
} else {
|
|
23770
|
-
const hasDel = targetParagraph.getElementsByTagNameNS(
|
|
23771
|
-
const hasMove = targetParagraph.getElementsByTagNameNS(
|
|
24259
|
+
const hasDel = targetParagraph.getElementsByTagNameNS(NS_W9, "del").length > 0;
|
|
24260
|
+
const hasMove = targetParagraph.getElementsByTagNameNS(NS_W9, "moveFrom").length > 0 || targetParagraph.getElementsByTagNameNS(NS_W9, "moveTo").length > 0;
|
|
23772
24261
|
if (hasDel) {
|
|
23773
24262
|
return {
|
|
23774
24263
|
documentXml,
|
|
@@ -24287,7 +24776,7 @@ async function applyFormattingToParagraphByExactText(documentXml, targetText, te
|
|
|
24287
24776
|
const targetParagraph = resolved.paragraph;
|
|
24288
24777
|
const collectVisibleRuns = () => {
|
|
24289
24778
|
const runEntries2 = [];
|
|
24290
|
-
const allRuns = Array.from(targetParagraph.getElementsByTagNameNS(
|
|
24779
|
+
const allRuns = Array.from(targetParagraph.getElementsByTagNameNS(NS_W9, "r"));
|
|
24291
24780
|
for (const run of allRuns) {
|
|
24292
24781
|
let parent = run.parentNode;
|
|
24293
24782
|
let isDel = false;
|
|
@@ -24379,7 +24868,7 @@ async function applyFormattingToParagraphByExactText(documentXml, targetText, te
|
|
|
24379
24868
|
const targetRuns = finalRuns.filter((span) => span.start >= matchStart && span.end <= matchEnd);
|
|
24380
24869
|
let anyChanged = false;
|
|
24381
24870
|
for (const entry of targetRuns) {
|
|
24382
|
-
const rPr = entry.run.getElementsByTagNameNS(
|
|
24871
|
+
const rPr = entry.run.getElementsByTagNameNS(NS_W9, "rPr")[0] || null;
|
|
24383
24872
|
if (checkRunPropertiesChanged(rPr, properties)) {
|
|
24384
24873
|
anyChanged = true;
|
|
24385
24874
|
break;
|
|
@@ -24390,7 +24879,7 @@ async function applyFormattingToParagraphByExactText(documentXml, targetText, te
|
|
|
24390
24879
|
}
|
|
24391
24880
|
for (const entry of targetRuns) {
|
|
24392
24881
|
const run = entry.run;
|
|
24393
|
-
let rPr = run.getElementsByTagNameNS(
|
|
24882
|
+
let rPr = run.getElementsByTagNameNS(NS_W9, "rPr")[0];
|
|
24394
24883
|
if (!rPr) {
|
|
24395
24884
|
rPr = createWordElement(xmlDoc, "w:rPr");
|
|
24396
24885
|
run.insertBefore(rPr, run.firstChild);
|
|
@@ -24399,7 +24888,7 @@ async function applyFormattingToParagraphByExactText(documentXml, targetText, te
|
|
|
24399
24888
|
let insideSameAuthorIns = false;
|
|
24400
24889
|
while (insAncestor && insAncestor !== targetParagraph) {
|
|
24401
24890
|
if (insAncestor.nodeType === 1 && (insAncestor.localName === "ins" || insAncestor.nodeName === "w:ins")) {
|
|
24402
|
-
const insAuthor = insAncestor.getAttribute("w:author") || insAncestor.getAttributeNS(
|
|
24891
|
+
const insAuthor = insAncestor.getAttribute("w:author") || insAncestor.getAttributeNS(NS_W9, "author");
|
|
24403
24892
|
if (insAuthor === (author || getDefaultAuthor())) {
|
|
24404
24893
|
insideSameAuthorIns = true;
|
|
24405
24894
|
}
|
|
@@ -24447,7 +24936,7 @@ async function applyParagraphFormatToParagraphByExactText(documentXml, targetTex
|
|
|
24447
24936
|
return { documentXml, hasChanges: false, status: "error", error: resolved?.error || { code: "TARGET_NOT_FOUND", message: "Target paragraph not found." } };
|
|
24448
24937
|
}
|
|
24449
24938
|
const targetParagraph = resolved.paragraph;
|
|
24450
|
-
let pPr = targetParagraph.getElementsByTagNameNS(
|
|
24939
|
+
let pPr = targetParagraph.getElementsByTagNameNS(NS_W9, "pPr")[0];
|
|
24451
24940
|
if (!pPr) {
|
|
24452
24941
|
pPr = createWordElement(xmlDoc, "w:pPr");
|
|
24453
24942
|
targetParagraph.insertBefore(pPr, targetParagraph.firstChild);
|
|
@@ -24495,7 +24984,7 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
24495
24984
|
}
|
|
24496
24985
|
let operation = validation.operation || normalizeDocumentOperation(op);
|
|
24497
24986
|
const authorUsed = resolveDocumentOperationAuthor(operation, author, getDefaultAuthor());
|
|
24498
|
-
if (operation.operationKind !== "comment_reply" && operation.operationKind !== "rejected-insert" && operation.operationKind !== "restore" && (operation.targetDescriptor?.revisionView === "rejected" || operation.targetEndDescriptor?.revisionView === "rejected")) {
|
|
24987
|
+
if (operation.operationKind !== "comment_reply" && operation.operationKind !== "comment_resolve" && operation.operationKind !== "rejected-insert" && operation.operationKind !== "restore" && (operation.targetDescriptor?.revisionView === "rejected" || operation.targetEndDescriptor?.revisionView === "rejected")) {
|
|
24499
24988
|
return {
|
|
24500
24989
|
documentXml,
|
|
24501
24990
|
hasChanges: false,
|
|
@@ -24662,6 +25151,7 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
24662
25151
|
result = applyCommentReplyToParts({
|
|
24663
25152
|
commentsXml: existingCommentsXml,
|
|
24664
25153
|
commentsExtendedXml: existingExtendedXml,
|
|
25154
|
+
documentXml,
|
|
24665
25155
|
parentCommentId: operation.parentCommentId,
|
|
24666
25156
|
commentId,
|
|
24667
25157
|
commentContent: operation.commentContent,
|
|
@@ -24669,8 +25159,30 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
24669
25159
|
date: operation.date || (/* @__PURE__ */ new Date()).toISOString()
|
|
24670
25160
|
});
|
|
24671
25161
|
result.documentXml = documentXml;
|
|
24672
|
-
if (result.
|
|
25162
|
+
if (result.status !== "error" && result.hasChanges) {
|
|
25163
|
+
if (!session.document || !anchorReplyInDocument(session.document, result.threadCommentIds, commentId)) {
|
|
25164
|
+
result = { documentXml, hasChanges: false, status: "error", error: { code: "PARENT_ANCHOR_NOT_FOUND", message: `Comment '${operation.parentCommentId}' has no anchor in the document body, so a reply cannot be placed.` } };
|
|
25165
|
+
} else if (session.receiptCollector) {
|
|
25166
|
+
session.receiptCollector.recordComment(commentId);
|
|
25167
|
+
}
|
|
25168
|
+
}
|
|
24673
25169
|
}
|
|
25170
|
+
} else if (operation.operationKind === "comment_resolve") {
|
|
25171
|
+
const resolution = applyThreadResolutionToParts({
|
|
25172
|
+
commentsXml: session.commentsXml || runtimeContext?.commentsXml || options.commentsXml,
|
|
25173
|
+
commentsExtendedXml: session.commentsExtendedXml || runtimeContext?.commentsExtendedXml || options.commentsExtendedXml,
|
|
25174
|
+
commentId: operation.commentId,
|
|
25175
|
+
resolved: operation.resolved !== false
|
|
25176
|
+
});
|
|
25177
|
+
result = resolution.status === "error" ? { documentXml, hasChanges: false, status: "error", error: resolution.error } : {
|
|
25178
|
+
documentXml,
|
|
25179
|
+
hasChanges: resolution.hasChanges,
|
|
25180
|
+
...resolution.commentsXml ? { commentsXml: resolution.commentsXml, commentsXmlMode: "replace" } : {},
|
|
25181
|
+
...resolution.commentsExtendedXml ? { commentsExtendedXml: resolution.commentsExtendedXml, commentsExtendedXmlMode: "replace" } : {},
|
|
25182
|
+
resolved: resolution.resolved,
|
|
25183
|
+
threadRootId: resolution.threadRootId,
|
|
25184
|
+
commentIds: resolution.commentIds
|
|
25185
|
+
};
|
|
24674
25186
|
} else if (operation.operationKind === "highlight") {
|
|
24675
25187
|
result = await applyHighlightToParagraphByExactText(
|
|
24676
25188
|
documentXml,
|
|
@@ -24811,7 +25323,7 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
24811
25323
|
...resolutionCapture
|
|
24812
25324
|
};
|
|
24813
25325
|
}
|
|
24814
|
-
session.markMutationCommitted(
|
|
25326
|
+
session.markMutationCommitted(true);
|
|
24815
25327
|
if (operation._compiledSourceId && session.sourceTargetRegistry) {
|
|
24816
25328
|
session.sourceTargetRegistry.commitMutation(
|
|
24817
25329
|
operation._compiledSourceId,
|
|
@@ -24924,9 +25436,9 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
24924
25436
|
}
|
|
24925
25437
|
|
|
24926
25438
|
// services/batch-operation-orchestrator.js
|
|
24927
|
-
var
|
|
25439
|
+
var NS_W10 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
24928
25440
|
function operationTargetPriority(op) {
|
|
24929
|
-
return op?.type === "comment" || op?.type === "comment_reply" ? 0 : 1;
|
|
25441
|
+
return op?.type === "comment" || op?.type === "comment_reply" || op?.type === "comment_resolve" ? 0 : 1;
|
|
24930
25442
|
}
|
|
24931
25443
|
function buildOperationDependencyPlan(operations = []) {
|
|
24932
25444
|
const list = Array.isArray(operations) ? operations : [];
|
|
@@ -25001,7 +25513,7 @@ function buildOperationDependencyPlan(operations = []) {
|
|
|
25001
25513
|
inDegrees[i2]++;
|
|
25002
25514
|
}
|
|
25003
25515
|
const kind = normalizeDocumentOperation(op).operationKind;
|
|
25004
|
-
if (kind !== "comment" && kind !== "comment_reply") {
|
|
25516
|
+
if (kind !== "comment" && kind !== "comment_reply" && kind !== "comment_resolve") {
|
|
25005
25517
|
if (!mutatingCaptureConsumers.has(ref)) mutatingCaptureConsumers.set(ref, []);
|
|
25006
25518
|
mutatingCaptureConsumers.get(ref).push({
|
|
25007
25519
|
index: i2,
|
|
@@ -25069,9 +25581,9 @@ function mergeCommentsXml(existingXml, incomingXml) {
|
|
|
25069
25581
|
const serializer = createSerializer();
|
|
25070
25582
|
const existingRoot = existingDoc.documentElement;
|
|
25071
25583
|
const existingIds = new Set(
|
|
25072
|
-
Array.from(existingDoc.getElementsByTagNameNS(
|
|
25584
|
+
Array.from(existingDoc.getElementsByTagNameNS(NS_W10, "comment")).map((node) => node.getAttribute("w:id") || node.getAttribute("id")).filter(Boolean)
|
|
25073
25585
|
);
|
|
25074
|
-
for (const comment of Array.from(incomingDoc.getElementsByTagNameNS(
|
|
25586
|
+
for (const comment of Array.from(incomingDoc.getElementsByTagNameNS(NS_W10, "comment"))) {
|
|
25075
25587
|
const id = comment.getAttribute("w:id") || comment.getAttribute("id");
|
|
25076
25588
|
if (id && existingIds.has(id)) continue;
|
|
25077
25589
|
existingRoot.appendChild(existingDoc.importNode(comment, true));
|
|
@@ -25232,7 +25744,7 @@ async function applyOperationsToDocumentXml(documentXml, operations, author, run
|
|
|
25232
25744
|
context.targetRefSnapshot = session.initialTargetReferenceSnapshot;
|
|
25233
25745
|
}
|
|
25234
25746
|
session.runtimeContext = context;
|
|
25235
|
-
if (sourceOperations.some((op) => op?.type === "comment_reply")) {
|
|
25747
|
+
if (sourceOperations.some((op) => op?.type === "comment_reply" || op?.type === "comment_resolve")) {
|
|
25236
25748
|
session.commentsXml = context.commentsXml || options.commentsXml || null;
|
|
25237
25749
|
session.commentsExtendedXml = context.commentsExtendedXml || options.commentsExtendedXml || null;
|
|
25238
25750
|
session.commentsXmlMode = "replace";
|
|
@@ -25305,6 +25817,7 @@ async function applyOperationsToDocumentXml(documentXml, operations, author, run
|
|
|
25305
25817
|
...result.resolvedTarget ? { resolvedTarget: result.resolvedTarget } : {},
|
|
25306
25818
|
...result.resolvedAnchor ? { resolvedAnchor: result.resolvedAnchor } : {},
|
|
25307
25819
|
...result.change ? { change: result.change } : {},
|
|
25820
|
+
...result.operationType === "comment_resolve" && !isError ? { resolved: result.resolved, threadRootId: result.threadRootId, commentIds: result.commentIds } : {},
|
|
25308
25821
|
...Array.isArray(result.warnings) && result.warnings.length > 0 ? { warnings: result.warnings } : {},
|
|
25309
25822
|
...result.error ? { error: result.error } : {},
|
|
25310
25823
|
...result.receipt ? { receipt: result.receipt } : {}
|
|
@@ -25581,6 +26094,26 @@ function preflightOperations(documentXml, operations, author, options = {}) {
|
|
|
25581
26094
|
}
|
|
25582
26095
|
commentsRequired = commentsRequired || operation.operationKind === "comment" || operation.operationKind === "comment_reply";
|
|
25583
26096
|
numberingRequired = numberingRequired || operationNeedsNumbering(operation);
|
|
26097
|
+
if (operation.operationKind === "comment_resolve") {
|
|
26098
|
+
const commentId = String(operation.commentId);
|
|
26099
|
+
results.push(options._existingCommentDetails?.[commentId] ? {
|
|
26100
|
+
index: index + 1,
|
|
26101
|
+
type: sourceOperation.type,
|
|
26102
|
+
operationType: operation.operationKind,
|
|
26103
|
+
status: "ready",
|
|
26104
|
+
authorUsed,
|
|
26105
|
+
resolvedBy: "comment_id",
|
|
26106
|
+
commentId
|
|
26107
|
+
} : {
|
|
26108
|
+
index: index + 1,
|
|
26109
|
+
type: sourceOperation.type,
|
|
26110
|
+
operationType: operation.operationKind,
|
|
26111
|
+
status: "error",
|
|
26112
|
+
authorUsed,
|
|
26113
|
+
error: { code: "COMMENT_NOT_FOUND", message: `Comment '${commentId}' was not found.` }
|
|
26114
|
+
});
|
|
26115
|
+
continue;
|
|
26116
|
+
}
|
|
25584
26117
|
if (operation.operationKind === "comment_reply") {
|
|
25585
26118
|
const parentId = String(operation.parentCommentId);
|
|
25586
26119
|
const parent = options._existingCommentDetails?.[parentId];
|
|
@@ -26732,6 +27265,15 @@ var text = (entries, path) => {
|
|
|
26732
27265
|
if (typeof entry === "string") return entry;
|
|
26733
27266
|
return textDecoder2.decode(entry);
|
|
26734
27267
|
};
|
|
27268
|
+
function reconcileCommentSiblings(working) {
|
|
27269
|
+
const updated = reconcileCommentSiblingParts({
|
|
27270
|
+
commentsXml: text(working, "word/comments.xml"),
|
|
27271
|
+
commentsIdsXml: text(working, "word/commentsIds.xml"),
|
|
27272
|
+
commentsExtensibleXml: text(working, "word/commentsExtensible.xml")
|
|
27273
|
+
});
|
|
27274
|
+
if (updated.commentsIdsXml) working.set("word/commentsIds.xml", textEncoder4.encode(updated.commentsIdsXml));
|
|
27275
|
+
if (updated.commentsExtensibleXml) working.set("word/commentsExtensible.xml", textEncoder4.encode(updated.commentsExtensibleXml));
|
|
27276
|
+
}
|
|
26735
27277
|
var cloneEntries = (entries) => new Map([...entries].map(([name, data]) => [name, new Uint8Array(data)]));
|
|
26736
27278
|
function toBufferCompatible(uint8Arr) {
|
|
26737
27279
|
if (typeof Buffer !== "undefined" && typeof Buffer.from === "function") {
|
|
@@ -26799,6 +27341,7 @@ function computePackageRevisionToken(input) {
|
|
|
26799
27341
|
entries
|
|
26800
27342
|
});
|
|
26801
27343
|
}
|
|
27344
|
+
var COMMENT_OPERATION_TYPES = /* @__PURE__ */ new Set(["comment", "comment_reply", "comment_resolve"]);
|
|
26802
27345
|
function nextCommentId(entries) {
|
|
26803
27346
|
const ids = `${text(entries, "word/document.xml") || ""} ${text(entries, "word/comments.xml") || ""}`.match(/(?:w:)?id=["'](\d+)["']/g) || [];
|
|
26804
27347
|
let next = ids.reduce((max2, token) => Math.max(max2, Number(token.match(/\d+/)?.[0] || 0)), 0) + 1;
|
|
@@ -26817,7 +27360,7 @@ function existingCommentDetails(entries) {
|
|
|
26817
27360
|
id,
|
|
26818
27361
|
author: comment.getAttribute("w:author") || comment.getAttribute("author") || "",
|
|
26819
27362
|
text: String(comment.textContent || "").trim(),
|
|
26820
|
-
paraId:
|
|
27363
|
+
paraId: commentParaId(comment) || null
|
|
26821
27364
|
};
|
|
26822
27365
|
}
|
|
26823
27366
|
return details;
|
|
@@ -26848,13 +27391,34 @@ var DocxDocument = class {
|
|
|
26848
27391
|
}
|
|
26849
27392
|
inspect(options = {}) {
|
|
26850
27393
|
const digestFn = options.digestFn || ((bytes) => sha256(bytes));
|
|
26851
|
-
|
|
27394
|
+
const inspection = inspectDocumentParts({
|
|
26852
27395
|
documentXml: text(this.entries, "word/document.xml"),
|
|
26853
27396
|
commentsXml: text(this.entries, "word/comments.xml"),
|
|
26854
27397
|
commentsExtendedXml: text(this.entries, "word/commentsExtended.xml"),
|
|
26855
27398
|
numberingXml: text(this.entries, "word/numbering.xml"),
|
|
26856
27399
|
stylesXml: text(this.entries, "word/styles.xml")
|
|
26857
27400
|
}, { ...options, digestFn });
|
|
27401
|
+
if (inspection.status === "error") return inspection;
|
|
27402
|
+
return { ...inspection, headersFooters: this.inspectHeadersFooters() };
|
|
27403
|
+
}
|
|
27404
|
+
/**
|
|
27405
|
+
* Header and footer parts the document uses, with their paragraph text, so operations can target them with
|
|
27406
|
+
* `part`. `hasFields` marks parts containing PAGE/NUMPAGES-style fields (edit around them, not inside).
|
|
27407
|
+
*/
|
|
27408
|
+
inspectHeadersFooters() {
|
|
27409
|
+
return discoverHeaderFooterParts({
|
|
27410
|
+
documentXml: text(this.entries, "word/document.xml"),
|
|
27411
|
+
relsXml: text(this.entries, "word/_rels/document.xml.rels"),
|
|
27412
|
+
settingsXml: text(this.entries, "word/settings.xml")
|
|
27413
|
+
}).map((part) => {
|
|
27414
|
+
const xml = text(this.entries, part.path) || "";
|
|
27415
|
+
const partInspection = xml ? inspectDocumentParts({ documentXml: xml }) : { paragraphs: [] };
|
|
27416
|
+
return {
|
|
27417
|
+
...part,
|
|
27418
|
+
hasFields: /<w:fldChar\b|<w:fldSimple\b/.test(xml),
|
|
27419
|
+
paragraphs: (partInspection.paragraphs || []).map((paragraph) => ({ index: paragraph.index, text: paragraph.text }))
|
|
27420
|
+
};
|
|
27421
|
+
});
|
|
26858
27422
|
}
|
|
26859
27423
|
getRevisionToken() {
|
|
26860
27424
|
return computePackageRevisionToken(this.entries);
|
|
@@ -26863,12 +27427,51 @@ var DocxDocument = class {
|
|
|
26863
27427
|
return this.getRevisionToken();
|
|
26864
27428
|
}
|
|
26865
27429
|
preflight(operations, author = getDefaultAuthor(), options = {}) {
|
|
26866
|
-
|
|
27430
|
+
const list = Array.isArray(operations) ? operations : [];
|
|
27431
|
+
const runBody = (ops) => preflightOperations(
|
|
26867
27432
|
text(this.entries, "word/document.xml"),
|
|
26868
|
-
|
|
27433
|
+
ops,
|
|
26869
27434
|
author || getDefaultAuthor(),
|
|
26870
27435
|
{ ...options, _existingCommentDetails: existingCommentDetails(this.entries) }
|
|
26871
27436
|
);
|
|
27437
|
+
if (!list.some((operation) => operation && typeof operation === "object" && operation.part != null)) return runBody(operations);
|
|
27438
|
+
const parts = this.inspectHeadersFooters();
|
|
27439
|
+
const bodyOps = [];
|
|
27440
|
+
const results = [];
|
|
27441
|
+
const conflicts = [];
|
|
27442
|
+
const authorsUsed = /* @__PURE__ */ new Set();
|
|
27443
|
+
let valid = true;
|
|
27444
|
+
list.forEach((operation, i2) => {
|
|
27445
|
+
if (!operation || typeof operation !== "object" || operation.part == null) {
|
|
27446
|
+
bodyOps.push({ operation, origIndex: i2 });
|
|
27447
|
+
return;
|
|
27448
|
+
}
|
|
27449
|
+
const resolved = resolvePartSelector(parts, operation.part);
|
|
27450
|
+
const refusal = COMMENT_OPERATION_TYPES.has(operation.type) ? { code: "COMMENT_IN_HEADER_FOOTER", message: "Word does not support comments in headers or footers." } : resolved.error;
|
|
27451
|
+
if (refusal) {
|
|
27452
|
+
valid = false;
|
|
27453
|
+
results.push({ index: i2 + 1, type: operation.type || "redline", status: "error", error: normalizeErrorWithRecovery({ ...refusal, operationIndex: i2 + 1 }) });
|
|
27454
|
+
return;
|
|
27455
|
+
}
|
|
27456
|
+
const { part: _selector, ...rest } = operation;
|
|
27457
|
+
const partResult = preflightOperations(text(this.entries, resolved.part.path), [rest], author || getDefaultAuthor(), { ...options, _existingCommentDetails: {} });
|
|
27458
|
+
valid = valid && partResult.valid;
|
|
27459
|
+
for (const result of partResult.results || []) results.push({ ...result, index: i2 + 1, part: resolved.part.path });
|
|
27460
|
+
conflicts.push(...partResult.conflicts || []);
|
|
27461
|
+
for (const name of partResult.authorsUsed || []) authorsUsed.add(name);
|
|
27462
|
+
});
|
|
27463
|
+
let requiredArtifacts = { comments: false, numbering: false };
|
|
27464
|
+
if (bodyOps.length) {
|
|
27465
|
+
const body = runBody(bodyOps.map((item) => item.operation));
|
|
27466
|
+
valid = valid && body.valid;
|
|
27467
|
+
for (const result of body.results || []) results.push({ ...result, index: bodyOps[result.index - 1].origIndex + 1 });
|
|
27468
|
+
conflicts.push(...body.conflicts || []);
|
|
27469
|
+
for (const name of body.authorsUsed || []) authorsUsed.add(name);
|
|
27470
|
+
requiredArtifacts = body.requiredArtifacts || requiredArtifacts;
|
|
27471
|
+
}
|
|
27472
|
+
results.sort((a, b) => a.index - b.index);
|
|
27473
|
+
const ok = valid && conflicts.length === 0;
|
|
27474
|
+
return { valid: ok, status: ok ? "ok" : "error", results, conflicts, authorsUsed: [...authorsUsed], requiredArtifacts };
|
|
26872
27475
|
}
|
|
26873
27476
|
toUint8Array() {
|
|
26874
27477
|
return zipDocx(this.entries);
|
|
@@ -26876,7 +27479,240 @@ var DocxDocument = class {
|
|
|
26876
27479
|
toBuffer() {
|
|
26877
27480
|
return toBufferCompatible(this.toUint8Array());
|
|
26878
27481
|
}
|
|
27482
|
+
/**
|
|
27483
|
+
* Applies operations to the document. Operations without `part` target the body (word/document.xml).
|
|
27484
|
+
* An operation with `part` targets one header or footer part:
|
|
27485
|
+
* `{ part: { kind: 'footer', type: 'default', section: 0 } | 'word/footer2.xml', target: { exactText }, ... }`.
|
|
27486
|
+
* Discover parts with `inspect().headersFooters`.
|
|
27487
|
+
*/
|
|
26879
27488
|
async applyOperations(operations, options = {}) {
|
|
27489
|
+
const list = Array.isArray(operations) ? operations : [];
|
|
27490
|
+
if (!list.some((operation) => operation && typeof operation === "object" && operation.part != null)) {
|
|
27491
|
+
return this.applyBodyOperations(operations, options);
|
|
27492
|
+
}
|
|
27493
|
+
return this.applyOperationsWithParts(list, options);
|
|
27494
|
+
}
|
|
27495
|
+
async applyOperationsWithParts(operations, options = {}) {
|
|
27496
|
+
const author = options.author || getDefaultAuthor();
|
|
27497
|
+
const atomic = options.atomic === true;
|
|
27498
|
+
const snapshotEntries = this.entries;
|
|
27499
|
+
const snapshotBytes = this.originalBytes;
|
|
27500
|
+
const total = operations.length;
|
|
27501
|
+
const restore = () => {
|
|
27502
|
+
this.entries = snapshotEntries;
|
|
27503
|
+
this.originalBytes = snapshotBytes;
|
|
27504
|
+
};
|
|
27505
|
+
const original = () => ({
|
|
27506
|
+
uint8Array: snapshotBytes,
|
|
27507
|
+
toUint8Array: () => new Uint8Array(snapshotBytes),
|
|
27508
|
+
buffer: toBufferCompatible(snapshotBytes),
|
|
27509
|
+
toBuffer: () => toBufferCompatible(snapshotBytes)
|
|
27510
|
+
});
|
|
27511
|
+
const refuse = (error2, results2 = [], receipts2 = [], extra = {}) => ({
|
|
27512
|
+
status: "error",
|
|
27513
|
+
hasChanges: false,
|
|
27514
|
+
written: false,
|
|
27515
|
+
rolledBack: true,
|
|
27516
|
+
results: results2,
|
|
27517
|
+
receipts: receipts2,
|
|
27518
|
+
executionOrder: [],
|
|
27519
|
+
authorsUsed: [],
|
|
27520
|
+
artifactsChanged: [],
|
|
27521
|
+
error: normalizeErrorWithRecovery(error2),
|
|
27522
|
+
retryPlan: createRetryPlan({ atomic: true, rolledBack: true, results: results2, receipts: receipts2, operationCount: total }),
|
|
27523
|
+
validation: { originalIssues: [], generatedIssues: [] },
|
|
27524
|
+
...extra,
|
|
27525
|
+
...original()
|
|
27526
|
+
});
|
|
27527
|
+
if (options.expectedRevision) {
|
|
27528
|
+
const precondition = await this.applyBodyOperations([], { expectedRevision: options.expectedRevision });
|
|
27529
|
+
if (precondition.status === "error") return precondition;
|
|
27530
|
+
}
|
|
27531
|
+
const parts = discoverHeaderFooterParts({
|
|
27532
|
+
documentXml: text(this.entries, "word/document.xml"),
|
|
27533
|
+
relsXml: text(this.entries, "word/_rels/document.xml.rels"),
|
|
27534
|
+
settingsXml: text(this.entries, "word/settings.xml")
|
|
27535
|
+
});
|
|
27536
|
+
const bodyOps = [];
|
|
27537
|
+
const partOps = [];
|
|
27538
|
+
for (let i2 = 0; i2 < operations.length; i2++) {
|
|
27539
|
+
const operation = operations[i2];
|
|
27540
|
+
if (!operation || typeof operation !== "object" || operation.part == null) {
|
|
27541
|
+
bodyOps.push({ operation, origIndex: i2 });
|
|
27542
|
+
continue;
|
|
27543
|
+
}
|
|
27544
|
+
if (COMMENT_OPERATION_TYPES.has(operation.type)) {
|
|
27545
|
+
return refuse({ code: "COMMENT_IN_HEADER_FOOTER", message: `Operation ${i2 + 1}: Word does not support comments in headers or footers.`, operationIndex: i2 + 1 });
|
|
27546
|
+
}
|
|
27547
|
+
const resolved = resolvePartSelector(parts, operation.part);
|
|
27548
|
+
if (resolved.error) return refuse({ ...resolved.error, message: `Operation ${i2 + 1}: ${resolved.error.message}`, operationIndex: i2 + 1 });
|
|
27549
|
+
const { part: _selector, ...rest } = operation;
|
|
27550
|
+
partOps.push({ operation: rest, origIndex: i2, part: resolved.part });
|
|
27551
|
+
}
|
|
27552
|
+
const results = [];
|
|
27553
|
+
const receipts = [];
|
|
27554
|
+
const executionOrder = [];
|
|
27555
|
+
const authorsUsed = /* @__PURE__ */ new Set();
|
|
27556
|
+
const originalIssues = [];
|
|
27557
|
+
let anyChange = false;
|
|
27558
|
+
let anyFailure = false;
|
|
27559
|
+
let bodyResult = null;
|
|
27560
|
+
if (bodyOps.length) {
|
|
27561
|
+
bodyResult = await this.applyBodyOperations(bodyOps.map((item) => item.operation), { ...options, expectedRevision: void 0 });
|
|
27562
|
+
const toOriginal = (index) => bodyOps[index - 1]?.origIndex + 1;
|
|
27563
|
+
for (const result of bodyResult.results || []) {
|
|
27564
|
+
results.push({
|
|
27565
|
+
...result,
|
|
27566
|
+
index: toOriginal(result.index),
|
|
27567
|
+
...result.receipt ? { receipt: { ...result.receipt, operationIndex: toOriginal(result.receipt.operationIndex) } } : {}
|
|
27568
|
+
});
|
|
27569
|
+
}
|
|
27570
|
+
for (const receipt of bodyResult.receipts || []) receipts.push({ ...receipt, operationIndex: toOriginal(receipt.operationIndex) });
|
|
27571
|
+
for (const index of bodyResult.executionOrder || []) executionOrder.push(toOriginal(index));
|
|
27572
|
+
for (const name of bodyResult.authorsUsed || []) authorsUsed.add(name);
|
|
27573
|
+
originalIssues.push(...bodyResult.validation?.originalIssues || []);
|
|
27574
|
+
if (bodyResult.status === "error" || bodyResult.rolledBack) {
|
|
27575
|
+
anyFailure = true;
|
|
27576
|
+
if (atomic) {
|
|
27577
|
+
restore();
|
|
27578
|
+
return { ...bodyResult, results, receipts, executionOrder, written: false, artifactsChanged: [], rolledBack: true, ...original() };
|
|
27579
|
+
}
|
|
27580
|
+
}
|
|
27581
|
+
anyChange = bodyResult.hasChanges === true;
|
|
27582
|
+
}
|
|
27583
|
+
const working = cloneEntries(this.entries);
|
|
27584
|
+
const zip = new MemoryZip(working);
|
|
27585
|
+
const changedParts = /* @__PURE__ */ new Set();
|
|
27586
|
+
const { expectedRevision: _ignored, ...runnerOptions } = options;
|
|
27587
|
+
const failPart = (item, error2) => {
|
|
27588
|
+
anyFailure = true;
|
|
27589
|
+
const failure = normalizeErrorWithRecovery({ ...error2, operationIndex: item.origIndex + 1 });
|
|
27590
|
+
results.push({ index: item.origIndex + 1, type: item.operation.type || "redline", status: "error", authorUsed: author, part: item.part.path, error: failure });
|
|
27591
|
+
};
|
|
27592
|
+
for (const item of partOps) {
|
|
27593
|
+
const path = item.part.path;
|
|
27594
|
+
const xml = text(working, path);
|
|
27595
|
+
if (!xml) {
|
|
27596
|
+
failPart(item, { code: "PART_NOT_FOUND", message: `Part '${path}' is referenced but missing from the package.` });
|
|
27597
|
+
if (atomic) break;
|
|
27598
|
+
continue;
|
|
27599
|
+
}
|
|
27600
|
+
const numberingText = text(working, "word/numbering.xml");
|
|
27601
|
+
const outcome = await applyOperationsToDocumentXml(xml, [item.operation], author, { numberingIdState: createDynamicNumberingIdState(numberingText || void 0) }, {
|
|
27602
|
+
...runnerOptions,
|
|
27603
|
+
atomic: true,
|
|
27604
|
+
strictTargets: options.strictTargets !== false,
|
|
27605
|
+
_existingCommentDetails: {}
|
|
27606
|
+
});
|
|
27607
|
+
const single = outcome.results?.[0];
|
|
27608
|
+
if (outcome.rolledBack || outcome.status === "error") {
|
|
27609
|
+
failPart(item, single?.error || outcome.error || { code: "OPERATION_FAILED", message: "Operation failed." });
|
|
27610
|
+
if (atomic) break;
|
|
27611
|
+
continue;
|
|
27612
|
+
}
|
|
27613
|
+
if (!outcome.hasChanges) {
|
|
27614
|
+
results.push({ ...single, index: item.origIndex + 1, part: path });
|
|
27615
|
+
continue;
|
|
27616
|
+
}
|
|
27617
|
+
if (countRevisionsInsideFields(outcome.documentXml) > countRevisionsInsideFields(xml)) {
|
|
27618
|
+
failPart(item, { code: "FIELD_EDIT_REFUSED", message: `The edit would change a field (for example PAGE or NUMPAGES) in ${path}. Word recomputes fields; edit the text around them instead.` });
|
|
27619
|
+
if (atomic) break;
|
|
27620
|
+
continue;
|
|
27621
|
+
}
|
|
27622
|
+
const taken = /* @__PURE__ */ new Set();
|
|
27623
|
+
for (const [name, data] of working) {
|
|
27624
|
+
if (name === path || !(name === "word/document.xml" || isHeaderFooterPath(name))) continue;
|
|
27625
|
+
for (const id of collectRevisionIds(textDecoder2.decode(data))) taken.add(id);
|
|
27626
|
+
}
|
|
27627
|
+
const renumbered = renumberCollidingRevisionIds(outcome.documentXml, taken);
|
|
27628
|
+
working.set(path, textEncoder4.encode(renumbered.xml));
|
|
27629
|
+
const describeReceipt = (receipt) => receipt?.revisionItems ? { ...receipt, revisionItems: receipt.revisionItems.map((revision) => ({ ...revision, id: renumbered.idMap.get(String(revision.id)) ?? revision.id, partName: path })) } : receipt;
|
|
27630
|
+
if (outcome.numberingXmlParts?.length) {
|
|
27631
|
+
await ensureNumberingArtifactsInZip(zip, outcome.numberingXmlParts, { mergeNumberingXml: mergeNumberingXmlBySchemaOrder });
|
|
27632
|
+
}
|
|
27633
|
+
changedParts.add(path);
|
|
27634
|
+
anyChange = true;
|
|
27635
|
+
results.push({
|
|
27636
|
+
...single,
|
|
27637
|
+
index: item.origIndex + 1,
|
|
27638
|
+
part: path,
|
|
27639
|
+
partSections: item.part.appliesToSections,
|
|
27640
|
+
...single?.receipt ? { receipt: { ...describeReceipt(single.receipt), operationIndex: item.origIndex + 1 } } : {}
|
|
27641
|
+
});
|
|
27642
|
+
if (outcome.receipts?.[0]) receipts.push({ ...describeReceipt(outcome.receipts[0]), operationIndex: item.origIndex + 1 });
|
|
27643
|
+
executionOrder.push(item.origIndex + 1);
|
|
27644
|
+
for (const name of outcome.authorsUsed || []) authorsUsed.add(name);
|
|
27645
|
+
}
|
|
27646
|
+
const generatedIssues = [];
|
|
27647
|
+
if (!(atomic && anyFailure)) {
|
|
27648
|
+
for (const path of changedParts) {
|
|
27649
|
+
const before = validateRedlineOoxml(text(this.entries, path)).issues.map((issue) => ({ source: path, ...issue }));
|
|
27650
|
+
const after = validateRedlineOoxml(text(working, path)).issues.map((issue) => ({ source: path, ...issue }));
|
|
27651
|
+
generatedIssues.push(...validationErrors(subtractValidationIssueMultiset(after, before)));
|
|
27652
|
+
}
|
|
27653
|
+
if (options.validate !== false && changedParts.size) {
|
|
27654
|
+
try {
|
|
27655
|
+
await validateDocxPackage(zip);
|
|
27656
|
+
} catch (error2) {
|
|
27657
|
+
generatedIssues.push({ source: "package", code: "PACKAGE_VALIDATION", severity: "error", message: error2.message });
|
|
27658
|
+
}
|
|
27659
|
+
}
|
|
27660
|
+
}
|
|
27661
|
+
if (atomic && anyFailure || generatedIssues.length) {
|
|
27662
|
+
restore();
|
|
27663
|
+
const error2 = generatedIssues.length ? { code: "PACKAGE_OPERATION_FAILED", message: `Applied operations introduced invalid markup (${[...new Set(generatedIssues.map((issue) => issue.code))].join(", ")}).`, issues: generatedIssues } : { code: "BATCH_OPERATION_FAILED", message: "Atomic batch rolled back because one or more operations failed." };
|
|
27664
|
+
const rolledBackResults = results.map((result) => result.receipt ? { ...result, receipt: { ...result.receipt, committed: false, finalDisposition: "rolled_back" } } : result).sort((a, b) => a.index - b.index);
|
|
27665
|
+
const rolledBackReceipts = receipts.map((receipt) => ({ ...receipt, committed: false, finalDisposition: "rolled_back" }));
|
|
27666
|
+
return refuse(error2, rolledBackResults, rolledBackReceipts, { validation: { originalIssues, generatedIssues }, issues: generatedIssues });
|
|
27667
|
+
}
|
|
27668
|
+
results.sort((a, b) => a.index - b.index);
|
|
27669
|
+
receipts.sort((a, b) => a.operationIndex - b.operationIndex);
|
|
27670
|
+
const status = !anyFailure ? "ok" : anyChange ? "partial" : "error";
|
|
27671
|
+
const failureError = anyFailure ? { error: normalizeErrorWithRecovery({ code: "BATCH_OPERATION_FAILED", message: "One or more operations failed; the others were applied." }) } : {};
|
|
27672
|
+
if (!changedParts.size) {
|
|
27673
|
+
const buf = toBufferCompatible(this.originalBytes);
|
|
27674
|
+
return {
|
|
27675
|
+
...bodyResult || {},
|
|
27676
|
+
results,
|
|
27677
|
+
receipts,
|
|
27678
|
+
executionOrder,
|
|
27679
|
+
authorsUsed: [...authorsUsed],
|
|
27680
|
+
status,
|
|
27681
|
+
hasChanges: anyChange,
|
|
27682
|
+
written: bodyResult?.written === true,
|
|
27683
|
+
artifactsChanged: bodyResult?.artifactsChanged || [],
|
|
27684
|
+
validation: { originalIssues, generatedIssues: [] },
|
|
27685
|
+
...failureError,
|
|
27686
|
+
uint8Array: this.originalBytes,
|
|
27687
|
+
toUint8Array: () => new Uint8Array(this.originalBytes),
|
|
27688
|
+
buffer: buf,
|
|
27689
|
+
toBuffer: () => toBufferCompatible(this.originalBytes)
|
|
27690
|
+
};
|
|
27691
|
+
}
|
|
27692
|
+
this.entries = working;
|
|
27693
|
+
const outputBytes = this.toUint8Array();
|
|
27694
|
+
this.originalBytes = outputBytes;
|
|
27695
|
+
const artifactsChanged = [...working].filter(([name, data]) => !snapshotEntries.has(name) || !areByteArraysEqual(data, snapshotEntries.get(name))).map(([name]) => name);
|
|
27696
|
+
return {
|
|
27697
|
+
...bodyResult || {},
|
|
27698
|
+
status,
|
|
27699
|
+
hasChanges: true,
|
|
27700
|
+
written: true,
|
|
27701
|
+
results,
|
|
27702
|
+
receipts,
|
|
27703
|
+
executionOrder,
|
|
27704
|
+
authorsUsed: [...authorsUsed],
|
|
27705
|
+
artifactsChanged,
|
|
27706
|
+
validation: { originalIssues, generatedIssues: [] },
|
|
27707
|
+
...failureError,
|
|
27708
|
+
uint8Array: outputBytes,
|
|
27709
|
+
toUint8Array: () => new Uint8Array(outputBytes),
|
|
27710
|
+
buffer: toBufferCompatible(outputBytes),
|
|
27711
|
+
inspection: this.inspect(),
|
|
27712
|
+
toBuffer: () => toBufferCompatible(outputBytes)
|
|
27713
|
+
};
|
|
27714
|
+
}
|
|
27715
|
+
async applyBodyOperations(operations, options = {}) {
|
|
26880
27716
|
const failedApply = (error2) => {
|
|
26881
27717
|
const results = [];
|
|
26882
27718
|
const receipts = [];
|
|
@@ -26987,18 +27823,21 @@ var DocxDocument = class {
|
|
|
26987
27823
|
toBuffer: () => toBufferCompatible(this.originalBytes)
|
|
26988
27824
|
};
|
|
26989
27825
|
}
|
|
26990
|
-
|
|
27826
|
+
const bodyChanged = (result.results || []).some((item) => item?.status !== "error" && item?.operationType !== "comment_resolve");
|
|
27827
|
+
if (bodyChanged) working.set("word/document.xml", textEncoder4.encode(result.documentXml));
|
|
26991
27828
|
await ensureNumberingArtifactsInZip(zip, result.numberingXmlParts, { mergeNumberingXml: mergeNumberingXmlBySchemaOrder });
|
|
26992
27829
|
const existingCommentsXml = text(working, "word/comments.xml");
|
|
26993
27830
|
const commentsXmlForPackaging = result.commentsXml || existingCommentsXml;
|
|
26994
27831
|
await ensureCommentsArtifactsInZip(zip, commentsXmlForPackaging, {
|
|
26995
27832
|
replaceExisting: result.commentsXmlMode === "replace" || !result.commentsXml && !!existingCommentsXml
|
|
26996
27833
|
});
|
|
26997
|
-
|
|
26998
|
-
|
|
26999
|
-
|
|
27000
|
-
|
|
27001
|
-
}
|
|
27834
|
+
if (result.commentsExtendedXml) {
|
|
27835
|
+
await ensureCommentsExtendedArtifactsInZip(zip, result.commentsExtendedXml, {
|
|
27836
|
+
replaceExisting: result.commentsExtendedXmlMode === "replace"
|
|
27837
|
+
});
|
|
27838
|
+
}
|
|
27839
|
+
await repairKnownContentTypes(zip);
|
|
27840
|
+
reconcileCommentSiblings(working);
|
|
27002
27841
|
if (options.validate !== false) {
|
|
27003
27842
|
const generated = validateRedlineOoxml(result.documentXml);
|
|
27004
27843
|
const outputIssues = generated.issues.map((issue) => ({ source: "word/document.xml", ...issue }));
|
|
@@ -27067,13 +27906,25 @@ var DocxDocument = class {
|
|
|
27067
27906
|
const transform = action === "accept" ? acceptTrackedChangesInOoxml : action === "reject" ? rejectTrackedChangesInOoxml : null;
|
|
27068
27907
|
if (!transform) return packageFailure(this.originalBytes, "INVALID_ACTION", `Unknown revision action: ${action}`);
|
|
27069
27908
|
const sourceBytes = this.originalBytes;
|
|
27909
|
+
const originalEntries = this.entries;
|
|
27070
27910
|
const working = cloneEntries(this.entries);
|
|
27071
27911
|
const zip = new MemoryZip(working);
|
|
27072
27912
|
const result = transform(text(working, "word/document.xml"), { author: options.author, allAuthors: options.allAuthors === true });
|
|
27073
27913
|
if (result.status === "error" || result.error) {
|
|
27074
27914
|
return packageFailure(sourceBytes, result.error?.code || "REVISION_OPERATION_FAILED", result.error?.message || "Revision operation failed.");
|
|
27075
27915
|
}
|
|
27076
|
-
|
|
27916
|
+
const partUpdates = /* @__PURE__ */ new Map();
|
|
27917
|
+
for (const part of this.inspectHeadersFooters()) {
|
|
27918
|
+
const partXml = text(working, part.path);
|
|
27919
|
+
if (!partXml) continue;
|
|
27920
|
+
const partResult = transform(partXml, { author: options.author, allAuthors: options.allAuthors === true });
|
|
27921
|
+
if (partResult.status === "error" || partResult.error) {
|
|
27922
|
+
return packageFailure(sourceBytes, partResult.error?.code || "REVISION_OPERATION_FAILED", `${part.path}: ${partResult.error?.message || "Revision operation failed."}`);
|
|
27923
|
+
}
|
|
27924
|
+
if (partResult.hasChanges) partUpdates.set(part.path, partResult.oxml);
|
|
27925
|
+
}
|
|
27926
|
+
const hasChanges = result.hasChanges || partUpdates.size > 0;
|
|
27927
|
+
if (!hasChanges) {
|
|
27077
27928
|
const buf = toBufferCompatible(sourceBytes);
|
|
27078
27929
|
return {
|
|
27079
27930
|
...result,
|
|
@@ -27086,8 +27937,10 @@ var DocxDocument = class {
|
|
|
27086
27937
|
toBuffer: () => toBufferCompatible(sourceBytes)
|
|
27087
27938
|
};
|
|
27088
27939
|
}
|
|
27089
|
-
working.set("word/document.xml", textEncoder4.encode(result.oxml));
|
|
27940
|
+
if (result.hasChanges) working.set("word/document.xml", textEncoder4.encode(result.oxml));
|
|
27941
|
+
for (const [path, xml] of partUpdates) working.set(path, textEncoder4.encode(xml));
|
|
27090
27942
|
try {
|
|
27943
|
+
await repairKnownContentTypes(zip);
|
|
27091
27944
|
if (options.validate !== false) await validateDocxPackage(zip);
|
|
27092
27945
|
} catch (error2) {
|
|
27093
27946
|
return packageFailure(sourceBytes, "PACKAGE_VALIDATION", error2.message);
|
|
@@ -27100,13 +27953,71 @@ var DocxDocument = class {
|
|
|
27100
27953
|
...result,
|
|
27101
27954
|
status: "ok",
|
|
27102
27955
|
written: true,
|
|
27103
|
-
|
|
27956
|
+
hasChanges: true,
|
|
27957
|
+
artifactsChanged: [...working].filter(([name, data]) => !originalEntries.has(name) || !areByteArraysEqual(data, originalEntries.get(name))).map(([name]) => name),
|
|
27104
27958
|
uint8Array: outputBytes,
|
|
27105
27959
|
toUint8Array: () => new Uint8Array(outputBytes),
|
|
27106
27960
|
buffer: outputBuf,
|
|
27107
27961
|
toBuffer: () => toBufferCompatible(outputBytes)
|
|
27108
27962
|
};
|
|
27109
27963
|
}
|
|
27964
|
+
/**
|
|
27965
|
+
* Resolves (or reopens) the comment thread containing `commentId`.
|
|
27966
|
+
* Resolved is a thread-level state in Word, so the root and every reply are updated together.
|
|
27967
|
+
*/
|
|
27968
|
+
async resolveComment(commentId, options = {}) {
|
|
27969
|
+
const sourceBytes = this.originalBytes;
|
|
27970
|
+
const working = cloneEntries(this.entries);
|
|
27971
|
+
const resolved = options.resolved !== false;
|
|
27972
|
+
const noChange = (extra) => {
|
|
27973
|
+
const buf = toBufferCompatible(sourceBytes);
|
|
27974
|
+
return {
|
|
27975
|
+
status: "ok",
|
|
27976
|
+
hasChanges: false,
|
|
27977
|
+
written: false,
|
|
27978
|
+
artifactsChanged: [],
|
|
27979
|
+
...extra,
|
|
27980
|
+
uint8Array: sourceBytes,
|
|
27981
|
+
toUint8Array: () => new Uint8Array(sourceBytes),
|
|
27982
|
+
buffer: buf,
|
|
27983
|
+
toBuffer: () => toBufferCompatible(sourceBytes)
|
|
27984
|
+
};
|
|
27985
|
+
};
|
|
27986
|
+
const result = applyThreadResolutionToParts({
|
|
27987
|
+
commentsXml: text(working, "word/comments.xml"),
|
|
27988
|
+
commentsExtendedXml: text(working, "word/commentsExtended.xml"),
|
|
27989
|
+
commentId,
|
|
27990
|
+
resolved
|
|
27991
|
+
});
|
|
27992
|
+
if (result.status === "error") return packageFailure(sourceBytes, result.error.code, result.error.message);
|
|
27993
|
+
if (!result.hasChanges) return noChange({ resolved, threadRootId: result.threadRootId, commentIds: result.commentIds });
|
|
27994
|
+
const zip = new MemoryZip(working);
|
|
27995
|
+
try {
|
|
27996
|
+
if (result.commentsXml) working.set("word/comments.xml", textEncoder4.encode(result.commentsXml));
|
|
27997
|
+
await ensureCommentsExtendedArtifactsInZip(zip, result.commentsExtendedXml, { replaceExisting: true });
|
|
27998
|
+
await repairKnownContentTypes(zip);
|
|
27999
|
+
if (options.validate !== false) await validateDocxPackage(zip);
|
|
28000
|
+
} catch (error2) {
|
|
28001
|
+
return packageFailure(sourceBytes, "PACKAGE_VALIDATION", error2.message);
|
|
28002
|
+
}
|
|
28003
|
+
const artifactsChanged = [...working].filter(([name, data]) => !this.entries.has(name) || !areByteArraysEqual(data, this.entries.get(name))).map(([name]) => name);
|
|
28004
|
+
this.entries = working;
|
|
28005
|
+
const outputBytes = this.toUint8Array();
|
|
28006
|
+
this.originalBytes = outputBytes;
|
|
28007
|
+
return {
|
|
28008
|
+
status: "ok",
|
|
28009
|
+
hasChanges: true,
|
|
28010
|
+
written: true,
|
|
28011
|
+
resolved,
|
|
28012
|
+
threadRootId: result.threadRootId,
|
|
28013
|
+
commentIds: result.commentIds,
|
|
28014
|
+
artifactsChanged,
|
|
28015
|
+
uint8Array: outputBytes,
|
|
28016
|
+
toUint8Array: () => new Uint8Array(outputBytes),
|
|
28017
|
+
buffer: toBufferCompatible(outputBytes),
|
|
28018
|
+
toBuffer: () => toBufferCompatible(outputBytes)
|
|
28019
|
+
};
|
|
28020
|
+
}
|
|
27110
28021
|
async deleteComments(options = {}) {
|
|
27111
28022
|
const sourceBytes = this.originalBytes;
|
|
27112
28023
|
const working = cloneEntries(this.entries);
|
|
@@ -27130,7 +28041,13 @@ var DocxDocument = class {
|
|
|
27130
28041
|
if (!parsed.doc || parsed.error) {
|
|
27131
28042
|
return packageFailure(sourceBytes, "PARSE_ERROR", parsed.error?.message || "Could not parse comments.xml.");
|
|
27132
28043
|
}
|
|
27133
|
-
const
|
|
28044
|
+
const requestedIds = Array.isArray(options.ids) ? new Set(options.ids.map(String)) : null;
|
|
28045
|
+
if (requestedIds) {
|
|
28046
|
+
const known = new Set(Array.from(parsed.doc.getElementsByTagNameNS("*", "comment")).map((node) => node.getAttribute("w:id") || node.getAttribute("id")));
|
|
28047
|
+
const missing = [...requestedIds].filter((id) => !known.has(id));
|
|
28048
|
+
if (missing.length) return packageFailure(sourceBytes, "COMMENT_NOT_FOUND", `Comment id(s) not found: ${missing.join(", ")}.`);
|
|
28049
|
+
}
|
|
28050
|
+
const matches = (comment) => requestedIds ? requestedIds.has(comment.getAttribute("w:id") || comment.getAttribute("id")) : options.allAuthors === true || (comment.getAttribute("w:author") || comment.getAttribute("author")) === options.author;
|
|
27134
28051
|
const ids = new Set(
|
|
27135
28052
|
Array.from(parsed.doc.getElementsByTagNameNS("*", "comment")).filter(matches).map((node) => node.getAttribute("w:id") || node.getAttribute("id")).filter(Boolean)
|
|
27136
28053
|
);
|
|
@@ -27159,9 +28076,8 @@ var DocxDocument = class {
|
|
|
27159
28076
|
const idByParaId = /* @__PURE__ */ new Map();
|
|
27160
28077
|
for (const comment of Array.from(parsed.doc.getElementsByTagNameNS("*", "comment"))) {
|
|
27161
28078
|
const id = comment.getAttribute("w:id") || comment.getAttribute("id");
|
|
27162
|
-
const
|
|
27163
|
-
|
|
27164
|
-
if (paraId) idByParaId.set(paraId.toUpperCase(), id);
|
|
28079
|
+
const paraId = commentParaId(comment);
|
|
28080
|
+
if (paraId) idByParaId.set(paraId, id);
|
|
27165
28081
|
}
|
|
27166
28082
|
for (const [paraId, id] of idByParaId) {
|
|
27167
28083
|
if (ids.has(id)) removedParaIds.add(paraId);
|
|
@@ -27180,8 +28096,8 @@ var DocxDocument = class {
|
|
|
27180
28096
|
}
|
|
27181
28097
|
}
|
|
27182
28098
|
}
|
|
27183
|
-
const
|
|
27184
|
-
const remainingParsed = parseOoxmlSafe(
|
|
28099
|
+
const remainingXml = requestedIds ? commentsXml : deleteCommentsByAuthorInOoxml(commentsXml, { author: options.author, allAuthors: options.allAuthors === true }).oxml;
|
|
28100
|
+
const remainingParsed = parseOoxmlSafe(remainingXml, "application/xml");
|
|
27185
28101
|
if (!remainingParsed.doc || remainingParsed.error) {
|
|
27186
28102
|
return packageFailure(sourceBytes, "PARSE_ERROR", remainingParsed.error?.message || "Could not parse updated comments.xml.");
|
|
27187
28103
|
}
|
|
@@ -27217,10 +28133,14 @@ var DocxDocument = class {
|
|
|
27217
28133
|
}
|
|
27218
28134
|
working.set("word/document.xml", textEncoder4.encode(serializer.serializeToString(documentParsed.doc)));
|
|
27219
28135
|
try {
|
|
27220
|
-
|
|
28136
|
+
reconcileCommentSiblings(working);
|
|
28137
|
+
const finalZip = new MemoryZip(working);
|
|
28138
|
+
await repairKnownContentTypes(finalZip);
|
|
28139
|
+
if (options.validate !== false) await validateDocxPackage(finalZip);
|
|
27221
28140
|
} catch (error2) {
|
|
27222
28141
|
return packageFailure(sourceBytes, "PACKAGE_VALIDATION", error2.message);
|
|
27223
28142
|
}
|
|
28143
|
+
const artifactsChanged = [...working].filter(([name, data]) => !this.entries.has(name) || !areByteArraysEqual(data, this.entries.get(name))).map(([name]) => name);
|
|
27224
28144
|
this.entries = working;
|
|
27225
28145
|
const outputBytes = this.toUint8Array();
|
|
27226
28146
|
this.originalBytes = outputBytes;
|
|
@@ -27231,7 +28151,7 @@ var DocxDocument = class {
|
|
|
27231
28151
|
written: true,
|
|
27232
28152
|
commentsRemoved: ids.size,
|
|
27233
28153
|
referencesRemoved,
|
|
27234
|
-
artifactsChanged
|
|
28154
|
+
artifactsChanged,
|
|
27235
28155
|
uint8Array: outputBytes,
|
|
27236
28156
|
toUint8Array: () => new Uint8Array(outputBytes),
|
|
27237
28157
|
buffer: outputBuf,
|