@ansonlai/docx-redline-js 0.8.0 → 0.8.2
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 +553 -486
- package/README.md +331 -309
- package/dist/docx-redline-js.esm.js +1209 -168
- 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 +1209 -168
- package/dist/docx-redline.bundle.cjs.map +4 -4
- package/dist/docx-redline.bundle.js +1209 -168
- 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-mode.js +183 -54
- 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/pipeline/diff-engine.js +274 -258
- 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/run-tests.mjs +14 -2
- 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 bundle v0.8.
|
|
1
|
+
// @ansonlai/docx-redline-js standalone bundle v0.8.2 — 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;
|
|
@@ -9001,15 +9001,29 @@ function createDiffEngine(options = {}) {
|
|
|
9001
9001
|
engine.Diff_Timeout = timeout;
|
|
9002
9002
|
return engine;
|
|
9003
9003
|
}
|
|
9004
|
-
function tokenize(text2) {
|
|
9004
|
+
function tokenize(text2, atomicChars = null) {
|
|
9005
9005
|
const tokens = [];
|
|
9006
|
+
const pushWord = (word) => {
|
|
9007
|
+
if (!atomicChars || atomicChars.size === 0) {
|
|
9008
|
+
tokens.push(word);
|
|
9009
|
+
return;
|
|
9010
|
+
}
|
|
9011
|
+
let start = 0;
|
|
9012
|
+
for (let index = 0; index < word.length; index++) {
|
|
9013
|
+
if (!atomicChars.has(word[index])) continue;
|
|
9014
|
+
if (index > start) tokens.push(word.slice(start, index));
|
|
9015
|
+
tokens.push(word[index]);
|
|
9016
|
+
start = index + 1;
|
|
9017
|
+
}
|
|
9018
|
+
if (start < word.length) tokens.push(word.slice(start));
|
|
9019
|
+
};
|
|
9006
9020
|
const leading = text2.match(/^\s+/);
|
|
9007
9021
|
if (leading) tokens.push(leading[0]);
|
|
9008
9022
|
const regex = /(\S+)(\s*)/g;
|
|
9009
9023
|
regex.lastIndex = leading?.[0].length || 0;
|
|
9010
9024
|
let match;
|
|
9011
9025
|
while ((match = regex.exec(text2)) !== null) {
|
|
9012
|
-
if (match[1])
|
|
9026
|
+
if (match[1]) pushWord(match[1]);
|
|
9013
9027
|
if (match[2]) tokens.push(match[2]);
|
|
9014
9028
|
}
|
|
9015
9029
|
return tokens;
|
|
@@ -9037,8 +9051,8 @@ function wordsToChars(text1, text2, options = {}) {
|
|
|
9037
9051
|
}
|
|
9038
9052
|
return { chars, tokenIds };
|
|
9039
9053
|
}
|
|
9040
|
-
const tokens1 = tokenize(text1);
|
|
9041
|
-
const tokens2 = tokenize(text2);
|
|
9054
|
+
const tokens1 = tokenize(text1, options.atomicChars);
|
|
9055
|
+
const tokens2 = tokenize(text2, options.atomicChars);
|
|
9042
9056
|
const encoded1 = mapTokensToChars(tokens1);
|
|
9043
9057
|
const encoded2 = mapTokensToChars(tokens2);
|
|
9044
9058
|
return {
|
|
@@ -14196,6 +14210,19 @@ function applyReconstructionDiffs(xmlDoc, diffs, context, serializer, author, fo
|
|
|
14196
14210
|
pendingReplacementEvent = null;
|
|
14197
14211
|
}
|
|
14198
14212
|
}
|
|
14213
|
+
for (const offset of Array.from(sentinelMapByStart.keys()).sort((a, b) => a - b)) {
|
|
14214
|
+
for (const sentinel of sentinelMapByStart.get(offset)) {
|
|
14215
|
+
if (!sentinel.isCommentMarker || emittedCommentMarkers.has(sentinel.node)) continue;
|
|
14216
|
+
emittedCommentMarkers.add(sentinel.node);
|
|
14217
|
+
if (isWordElement(sentinel.node, "commentReference")) {
|
|
14218
|
+
const run = createWordElement(xmlDoc, "w:r");
|
|
14219
|
+
run.appendChild(sentinel.node.cloneNode(true));
|
|
14220
|
+
currentParagraph.appendChild(run);
|
|
14221
|
+
} else {
|
|
14222
|
+
currentParagraph.appendChild(sentinel.node.cloneNode(true));
|
|
14223
|
+
}
|
|
14224
|
+
}
|
|
14225
|
+
}
|
|
14199
14226
|
if (generateRedlines) {
|
|
14200
14227
|
containerFragments.forEach((fragment) => {
|
|
14201
14228
|
Array.from(fragment.childNodes).forEach((node) => {
|
|
@@ -14430,7 +14457,11 @@ function applyReconstructionMode(xmlDoc, originalText, modifiedText, serializer,
|
|
|
14430
14457
|
if (mapping.paragraphs.length === 0) {
|
|
14431
14458
|
return withOoxmlSourceType({ oxml: serializer.serializeToString(xmlDoc), hasChanges: false });
|
|
14432
14459
|
}
|
|
14433
|
-
const
|
|
14460
|
+
const wordDiffs = computeWordDiffs(mapping.originalFullText, mapping.processedModifiedText, {
|
|
14461
|
+
...diffOptions,
|
|
14462
|
+
atomicChars: new Set(mapping.referenceMap.keys())
|
|
14463
|
+
});
|
|
14464
|
+
const diffs = keepUnchangedTextOutsideWrappers(anchorSharedReferences(wordDiffs, mapping.referenceMap), mapping);
|
|
14434
14465
|
return withOoxmlSourceType(applyReconstructionDiffs(
|
|
14435
14466
|
xmlDoc,
|
|
14436
14467
|
diffs,
|
|
@@ -14442,6 +14473,109 @@ function applyReconstructionMode(xmlDoc, originalText, modifiedText, serializer,
|
|
|
14442
14473
|
options
|
|
14443
14474
|
));
|
|
14444
14475
|
}
|
|
14476
|
+
function forEachChangeGroup(diffs, onEqual, onGroup) {
|
|
14477
|
+
let index = 0;
|
|
14478
|
+
while (index < diffs.length) {
|
|
14479
|
+
if (diffs[index][0] === 0) {
|
|
14480
|
+
onEqual(diffs[index]);
|
|
14481
|
+
index++;
|
|
14482
|
+
continue;
|
|
14483
|
+
}
|
|
14484
|
+
const group = [];
|
|
14485
|
+
while (index < diffs.length && diffs[index][0] !== 0) group.push(diffs[index++]);
|
|
14486
|
+
onGroup(
|
|
14487
|
+
group,
|
|
14488
|
+
group.filter(([op]) => op === -1).map(([, text2]) => text2).join(""),
|
|
14489
|
+
group.filter(([op]) => op === 1).map(([, text2]) => text2).join("")
|
|
14490
|
+
);
|
|
14491
|
+
}
|
|
14492
|
+
}
|
|
14493
|
+
function pushChange(result, deleted, inserted) {
|
|
14494
|
+
if (deleted === inserted) {
|
|
14495
|
+
if (deleted) result.push([0, deleted]);
|
|
14496
|
+
return;
|
|
14497
|
+
}
|
|
14498
|
+
if (deleted) result.push([-1, deleted]);
|
|
14499
|
+
if (inserted) result.push([1, inserted]);
|
|
14500
|
+
}
|
|
14501
|
+
function anchorSharedReferences(diffs, referenceMap) {
|
|
14502
|
+
if (referenceMap.size === 0) return diffs;
|
|
14503
|
+
const result = [];
|
|
14504
|
+
forEachChangeGroup(diffs, (part) => result.push(part), (group, deleted, inserted) => {
|
|
14505
|
+
const deletedRefs = [...deleted].filter((char) => referenceMap.has(char) && inserted.includes(char));
|
|
14506
|
+
const insertedRefs = [...inserted].filter((char) => deletedRefs.includes(char));
|
|
14507
|
+
if (deletedRefs.length === 0 || deletedRefs.join("") !== insertedRefs.join("")) {
|
|
14508
|
+
result.push(...group);
|
|
14509
|
+
return;
|
|
14510
|
+
}
|
|
14511
|
+
let deletedStart = 0;
|
|
14512
|
+
let insertedStart = 0;
|
|
14513
|
+
for (const ref of deletedRefs) {
|
|
14514
|
+
const deletedAt = deleted.indexOf(ref, deletedStart);
|
|
14515
|
+
const insertedAt = inserted.indexOf(ref, insertedStart);
|
|
14516
|
+
pushChange(result, deleted.slice(deletedStart, deletedAt), inserted.slice(insertedStart, insertedAt));
|
|
14517
|
+
result.push([0, ref]);
|
|
14518
|
+
deletedStart = deletedAt + 1;
|
|
14519
|
+
insertedStart = insertedAt + 1;
|
|
14520
|
+
}
|
|
14521
|
+
pushChange(result, deleted.slice(deletedStart), inserted.slice(insertedStart));
|
|
14522
|
+
});
|
|
14523
|
+
return result;
|
|
14524
|
+
}
|
|
14525
|
+
function commonPrefixLength(left, right) {
|
|
14526
|
+
const limit = Math.min(left.length, right.length);
|
|
14527
|
+
let length = 0;
|
|
14528
|
+
while (length < limit && left[length] === right[length]) length++;
|
|
14529
|
+
return length;
|
|
14530
|
+
}
|
|
14531
|
+
function commonSuffixLength(left, right, limit) {
|
|
14532
|
+
let length = 0;
|
|
14533
|
+
while (length < limit && left[left.length - 1 - length] === right[right.length - 1 - length]) length++;
|
|
14534
|
+
return length;
|
|
14535
|
+
}
|
|
14536
|
+
function keepUnchangedTextOutsideWrappers(diffs, mapping) {
|
|
14537
|
+
const wrapperAt = (index) => mapping.getRunProperties(index).wrapper || null;
|
|
14538
|
+
const result = [];
|
|
14539
|
+
let originalOffset = 0;
|
|
14540
|
+
forEachChangeGroup(diffs, (part) => {
|
|
14541
|
+
result.push(part);
|
|
14542
|
+
originalOffset += part[1].length;
|
|
14543
|
+
}, (group, deleted, inserted) => {
|
|
14544
|
+
const start = originalOffset;
|
|
14545
|
+
const end = start + deleted.length;
|
|
14546
|
+
originalOffset = end;
|
|
14547
|
+
const isWrapperBoundary = (index) => index > start && index < end && wrapperAt(index - 1) !== wrapperAt(index);
|
|
14548
|
+
let prefix = 0;
|
|
14549
|
+
let suffix = 0;
|
|
14550
|
+
if (deleted && inserted) {
|
|
14551
|
+
const sharedSuffix = commonSuffixLength(deleted, inserted, Math.min(deleted.length, inserted.length));
|
|
14552
|
+
for (let length = sharedSuffix; length > 0; length--) {
|
|
14553
|
+
if (isWrapperBoundary(end - length)) {
|
|
14554
|
+
suffix = length;
|
|
14555
|
+
break;
|
|
14556
|
+
}
|
|
14557
|
+
}
|
|
14558
|
+
const sharedPrefix = Math.min(
|
|
14559
|
+
commonPrefixLength(deleted, inserted),
|
|
14560
|
+
Math.min(deleted.length, inserted.length) - suffix
|
|
14561
|
+
);
|
|
14562
|
+
for (let length = sharedPrefix; length > 0; length--) {
|
|
14563
|
+
if (isWrapperBoundary(start + length)) {
|
|
14564
|
+
prefix = length;
|
|
14565
|
+
break;
|
|
14566
|
+
}
|
|
14567
|
+
}
|
|
14568
|
+
}
|
|
14569
|
+
if (prefix === 0 && suffix === 0) {
|
|
14570
|
+
result.push(...group);
|
|
14571
|
+
return;
|
|
14572
|
+
}
|
|
14573
|
+
if (prefix > 0) result.push([0, deleted.slice(0, prefix)]);
|
|
14574
|
+
pushChange(result, deleted.slice(prefix, deleted.length - suffix), inserted.slice(prefix, inserted.length - suffix));
|
|
14575
|
+
if (suffix > 0) result.push([0, deleted.slice(deleted.length - suffix)]);
|
|
14576
|
+
});
|
|
14577
|
+
return result;
|
|
14578
|
+
}
|
|
14445
14579
|
|
|
14446
14580
|
// engine/table-mode.js
|
|
14447
14581
|
function noChanges(serializer, xmlDoc) {
|
|
@@ -17990,6 +18124,260 @@ function ingestWordOoxmlToMarkdownResult(ooxml) {
|
|
|
17990
18124
|
return { text: lines.join("\n\n").trim(), status: "ok", warnings: parsed.warnings };
|
|
17991
18125
|
}
|
|
17992
18126
|
|
|
18127
|
+
// services/comment-builders.js
|
|
18128
|
+
var NS_W14 = "http://schemas.microsoft.com/office/word/2010/wordml";
|
|
18129
|
+
var NS_W15 = "http://schemas.microsoft.com/office/word/2012/wordml";
|
|
18130
|
+
function createCommentParaId(commentId) {
|
|
18131
|
+
const numeric = Number.parseInt(String(commentId), 10);
|
|
18132
|
+
const value = Number.isFinite(numeric) ? 1879048192 + (numeric >>> 0) >>> 0 : 1879048192;
|
|
18133
|
+
return value.toString(16).toUpperCase().padStart(8, "0").slice(-8);
|
|
18134
|
+
}
|
|
18135
|
+
function buildCommentElement(commentId, author, content, date, paraId = createCommentParaId(commentId)) {
|
|
18136
|
+
const initials = author.split(" ").map((word) => word[0]).join("").toUpperCase() || "AI";
|
|
18137
|
+
const escapedContent = escapeXml(content);
|
|
18138
|
+
const escapedAuthor = escapeXml(author);
|
|
18139
|
+
return `<w:comment w:id="${commentId}" w:author="${escapedAuthor}" w:date="${date}" w:initials="${initials}">
|
|
18140
|
+
<w:p w14:paraId="${escapeXml(paraId)}" xmlns:w14="${NS_W14}">
|
|
18141
|
+
<w:r><w:t>${escapedContent}</w:t></w:r>
|
|
18142
|
+
</w:p>
|
|
18143
|
+
</w:comment>`;
|
|
18144
|
+
}
|
|
18145
|
+
function buildCommentsPartXml(comments) {
|
|
18146
|
+
if (!comments || comments.length === 0) {
|
|
18147
|
+
return `<w:comments xmlns:w="${NS_W}"></w:comments>`;
|
|
18148
|
+
}
|
|
18149
|
+
const commentElements = comments.map(
|
|
18150
|
+
(comment) => buildCommentElement(comment.id, comment.author, comment.content, comment.date, comment.paraId)
|
|
18151
|
+
).join("\n ");
|
|
18152
|
+
return `<w:comments xmlns:w="${NS_W}">
|
|
18153
|
+
${commentElements}
|
|
18154
|
+
</w:comments>`;
|
|
18155
|
+
}
|
|
18156
|
+
function buildCommentsExtendedPartXml(entries) {
|
|
18157
|
+
const body = (entries || []).map((entry) => {
|
|
18158
|
+
const parent = entry.paraIdParent ? ` w15:paraIdParent="${escapeXml(entry.paraIdParent)}"` : "";
|
|
18159
|
+
return `<w15:commentEx w15:paraId="${escapeXml(entry.paraId)}"${parent} w15:done="${entry.done ? "1" : "0"}"/>`;
|
|
18160
|
+
}).join("");
|
|
18161
|
+
return `<w15:commentsEx xmlns:w15="${NS_W15}">${body}</w15:commentsEx>`;
|
|
18162
|
+
}
|
|
18163
|
+
|
|
18164
|
+
// services/comment-thread-parts.js
|
|
18165
|
+
var attrOf = (node, qualified, local) => node?.getAttribute?.(qualified) || node?.getAttribute?.(local) || "";
|
|
18166
|
+
function commentThreadParagraph(commentElement) {
|
|
18167
|
+
const paragraphs = Array.from(commentElement?.getElementsByTagNameNS?.("*", "p") || []);
|
|
18168
|
+
return paragraphs.length ? paragraphs[paragraphs.length - 1] : null;
|
|
18169
|
+
}
|
|
18170
|
+
function commentParaId(commentElement) {
|
|
18171
|
+
return attrOf(commentThreadParagraph(commentElement), "w14:paraId", "paraId").toUpperCase();
|
|
18172
|
+
}
|
|
18173
|
+
function usedParaIds(commentsDoc, extendedDoc) {
|
|
18174
|
+
const ids = /* @__PURE__ */ new Set();
|
|
18175
|
+
for (const p of Array.from(commentsDoc?.getElementsByTagNameNS("*", "p") || [])) {
|
|
18176
|
+
const id = attrOf(p, "w14:paraId", "paraId");
|
|
18177
|
+
if (id) ids.add(id.toUpperCase());
|
|
18178
|
+
}
|
|
18179
|
+
for (const ex of Array.from(extendedDoc?.getElementsByTagNameNS("*", "commentEx") || [])) {
|
|
18180
|
+
const id = attrOf(ex, "w15:paraId", "paraId");
|
|
18181
|
+
if (id) ids.add(id.toUpperCase());
|
|
18182
|
+
}
|
|
18183
|
+
return ids;
|
|
18184
|
+
}
|
|
18185
|
+
function allocateParaId(commentId, occupied) {
|
|
18186
|
+
let candidate = createCommentParaId(commentId);
|
|
18187
|
+
let value = Number.parseInt(candidate, 16) >>> 0;
|
|
18188
|
+
while (occupied.has(candidate)) {
|
|
18189
|
+
value = value + 1 >>> 0;
|
|
18190
|
+
candidate = value.toString(16).toUpperCase().padStart(8, "0");
|
|
18191
|
+
}
|
|
18192
|
+
occupied.add(candidate);
|
|
18193
|
+
return candidate;
|
|
18194
|
+
}
|
|
18195
|
+
var parseDoc = (xml, partName) => {
|
|
18196
|
+
const parsed = parseOoxmlSafe(xml, "application/xml");
|
|
18197
|
+
if (!parsed.doc || parsed.error) return { error: { code: "PARSE_ERROR", message: `Could not parse ${partName}: ${parsed.error?.message || "invalid XML"}` } };
|
|
18198
|
+
return { doc: parsed.doc };
|
|
18199
|
+
};
|
|
18200
|
+
function applyThreadResolutionToParts({ commentsXml, commentsExtendedXml = null, commentId, resolved = true }) {
|
|
18201
|
+
if (!commentsXml) return { status: "error", error: { code: "COMMENTS_PART_MISSING", message: "Resolving a comment requires an existing word/comments.xml part." } };
|
|
18202
|
+
const commentsParsed = parseDoc(commentsXml, "word/comments.xml");
|
|
18203
|
+
if (commentsParsed.error) return { status: "error", error: commentsParsed.error };
|
|
18204
|
+
const commentsDoc = commentsParsed.doc;
|
|
18205
|
+
const idOf = (node) => attrOf(node, "w:id", "id");
|
|
18206
|
+
const comments = Array.from(commentsDoc.getElementsByTagNameNS("*", "comment"));
|
|
18207
|
+
const target = comments.find((node) => idOf(node) === String(commentId));
|
|
18208
|
+
if (!target) return { status: "error", error: { code: "COMMENT_NOT_FOUND", message: `Comment '${commentId}' was not found.` } };
|
|
18209
|
+
let extendedDoc;
|
|
18210
|
+
if (commentsExtendedXml) {
|
|
18211
|
+
const parsed = parseDoc(commentsExtendedXml, "word/commentsExtended.xml");
|
|
18212
|
+
if (parsed.error) return { status: "error", error: parsed.error };
|
|
18213
|
+
extendedDoc = parsed.doc;
|
|
18214
|
+
} else {
|
|
18215
|
+
extendedDoc = parseDoc(`<w15:commentsEx xmlns:w15="${NS_W15}"/>`, "word/commentsExtended.xml").doc;
|
|
18216
|
+
}
|
|
18217
|
+
const root = extendedDoc.documentElement;
|
|
18218
|
+
const entryFor = (paraId) => Array.from(root.getElementsByTagNameNS("*", "commentEx")).find((node) => attrOf(node, "w15:paraId", "paraId").toUpperCase() === paraId) || null;
|
|
18219
|
+
const occupied = usedParaIds(commentsDoc, extendedDoc);
|
|
18220
|
+
let commentsChanged = false;
|
|
18221
|
+
const keyOf = (node) => {
|
|
18222
|
+
let paraId = commentParaId(node);
|
|
18223
|
+
if (!paraId) {
|
|
18224
|
+
paraId = allocateParaId(idOf(node), occupied);
|
|
18225
|
+
commentThreadParagraph(node)?.setAttributeNS(NS_W14, "w14:paraId", paraId);
|
|
18226
|
+
commentsChanged = true;
|
|
18227
|
+
}
|
|
18228
|
+
return paraId;
|
|
18229
|
+
};
|
|
18230
|
+
const targetKey = keyOf(target);
|
|
18231
|
+
const targetEntry = entryFor(targetKey);
|
|
18232
|
+
const rootKey = targetEntry && attrOf(targetEntry, "w15:paraIdParent", "paraIdParent").toUpperCase() || targetKey;
|
|
18233
|
+
const threadKeys = /* @__PURE__ */ new Set([rootKey, targetKey]);
|
|
18234
|
+
for (const entry of Array.from(root.getElementsByTagNameNS("*", "commentEx"))) {
|
|
18235
|
+
if (attrOf(entry, "w15:paraIdParent", "paraIdParent").toUpperCase() === rootKey) threadKeys.add(attrOf(entry, "w15:paraId", "paraId").toUpperCase());
|
|
18236
|
+
}
|
|
18237
|
+
const doneValue = resolved ? "1" : "0";
|
|
18238
|
+
let extendedChanged = !commentsExtendedXml;
|
|
18239
|
+
for (const key of threadKeys) {
|
|
18240
|
+
let entry = entryFor(key);
|
|
18241
|
+
if (!entry) {
|
|
18242
|
+
entry = extendedDoc.createElementNS(NS_W15, "w15:commentEx");
|
|
18243
|
+
entry.setAttributeNS(NS_W15, "w15:paraId", key);
|
|
18244
|
+
if (key !== rootKey) entry.setAttributeNS(NS_W15, "w15:paraIdParent", rootKey);
|
|
18245
|
+
entry.setAttributeNS(NS_W15, "w15:done", doneValue);
|
|
18246
|
+
root.appendChild(entry);
|
|
18247
|
+
extendedChanged = true;
|
|
18248
|
+
} else if (attrOf(entry, "w15:done", "done") !== doneValue) {
|
|
18249
|
+
entry.setAttributeNS(NS_W15, "w15:done", doneValue);
|
|
18250
|
+
extendedChanged = true;
|
|
18251
|
+
}
|
|
18252
|
+
}
|
|
18253
|
+
const byKey = new Map(comments.map((node) => [commentParaId(node), node]));
|
|
18254
|
+
const commentIds = [...threadKeys].map((key) => byKey.get(key)).filter(Boolean).map(idOf);
|
|
18255
|
+
const rootComment = byKey.get(rootKey);
|
|
18256
|
+
const serializer = createSerializer();
|
|
18257
|
+
const hasChanges = extendedChanged || commentsChanged;
|
|
18258
|
+
return {
|
|
18259
|
+
status: "ok",
|
|
18260
|
+
hasChanges,
|
|
18261
|
+
resolved,
|
|
18262
|
+
threadRootId: rootComment ? idOf(rootComment) : String(commentId),
|
|
18263
|
+
commentIds,
|
|
18264
|
+
...hasChanges ? { commentsExtendedXml: serializer.serializeToString(extendedDoc) } : {},
|
|
18265
|
+
...commentsChanged ? { commentsXml: serializer.serializeToString(commentsDoc) } : {}
|
|
18266
|
+
};
|
|
18267
|
+
}
|
|
18268
|
+
var NS_W_MAIN = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
18269
|
+
function anchorReplyInDocument(xmlDoc, memberIds, newId) {
|
|
18270
|
+
const wanted = new Set(memberIds.map(String));
|
|
18271
|
+
const idOf = (node) => attrOf(node, "w:id", "id");
|
|
18272
|
+
const all = (name) => Array.from(xmlDoc.getElementsByTagNameNS("*", name)).filter((node) => wanted.has(idOf(node)));
|
|
18273
|
+
const starts = all("commentRangeStart");
|
|
18274
|
+
const references = all("commentReference");
|
|
18275
|
+
if (!starts.length || !references.length) return false;
|
|
18276
|
+
const lastStart = starts[starts.length - 1];
|
|
18277
|
+
const lastReference = references[references.length - 1];
|
|
18278
|
+
const referenceRun = lastReference.parentNode?.localName === "r" ? lastReference.parentNode : lastReference;
|
|
18279
|
+
const make = (name, parent) => {
|
|
18280
|
+
const element = xmlDoc.createElementNS(NS_W_MAIN, `w:${name}`);
|
|
18281
|
+
element.setAttributeNS(NS_W_MAIN, "w:id", String(newId));
|
|
18282
|
+
if (parent) parent.appendChild(element);
|
|
18283
|
+
return element;
|
|
18284
|
+
};
|
|
18285
|
+
const insertAfter = (anchor, node) => anchor.parentNode.insertBefore(node, anchor.nextSibling);
|
|
18286
|
+
insertAfter(lastStart, make("commentRangeStart"));
|
|
18287
|
+
const end = make("commentRangeEnd");
|
|
18288
|
+
const run = xmlDoc.createElementNS(NS_W_MAIN, "w:r");
|
|
18289
|
+
run.appendChild(xmlDoc.createElementNS(NS_W_MAIN, "w:rPr"));
|
|
18290
|
+
make("commentReference", run);
|
|
18291
|
+
insertAfter(referenceRun, end);
|
|
18292
|
+
insertAfter(end, run);
|
|
18293
|
+
return true;
|
|
18294
|
+
}
|
|
18295
|
+
var NS_W16CID = "http://schemas.microsoft.com/office/word/2016/wordml/cid";
|
|
18296
|
+
var NS_W16CEX = "http://schemas.microsoft.com/office/word/2018/wordml/cex";
|
|
18297
|
+
var durableIdFor = (paraId, taken) => {
|
|
18298
|
+
let value = (Number.parseInt(paraId, 16) ^ 625341585) & 2147483646;
|
|
18299
|
+
let candidate = value.toString(16).toUpperCase().padStart(8, "0");
|
|
18300
|
+
while (taken.has(candidate)) {
|
|
18301
|
+
value = value + 2 & 2147483646;
|
|
18302
|
+
candidate = value.toString(16).toUpperCase().padStart(8, "0");
|
|
18303
|
+
}
|
|
18304
|
+
taken.add(candidate);
|
|
18305
|
+
return candidate;
|
|
18306
|
+
};
|
|
18307
|
+
function reconcileCommentSiblingParts({ commentsXml, commentsIdsXml = null, commentsExtensibleXml = null }) {
|
|
18308
|
+
const out = {};
|
|
18309
|
+
if (!commentsXml || !commentsIdsXml && !commentsExtensibleXml) return out;
|
|
18310
|
+
const commentsParsed = parseOoxmlSafe(commentsXml, "application/xml");
|
|
18311
|
+
if (!commentsParsed.doc || commentsParsed.error) return out;
|
|
18312
|
+
const serializer = createSerializer();
|
|
18313
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
18314
|
+
for (const node of Array.from(commentsParsed.doc.getElementsByTagNameNS("*", "comment"))) {
|
|
18315
|
+
const key = commentParaId(node);
|
|
18316
|
+
if (key) byKey.set(key, node);
|
|
18317
|
+
}
|
|
18318
|
+
let idsDoc = null;
|
|
18319
|
+
const durableByKey = /* @__PURE__ */ new Map();
|
|
18320
|
+
const taken = /* @__PURE__ */ new Set();
|
|
18321
|
+
let idsChanged = false;
|
|
18322
|
+
if (commentsIdsXml) {
|
|
18323
|
+
const parsed = parseOoxmlSafe(commentsIdsXml, "application/xml");
|
|
18324
|
+
if (!parsed.doc || parsed.error) return out;
|
|
18325
|
+
idsDoc = parsed.doc;
|
|
18326
|
+
const root = idsDoc.documentElement;
|
|
18327
|
+
for (const entry of Array.from(root.getElementsByTagNameNS("*", "commentId"))) {
|
|
18328
|
+
const key = attrOf(entry, "w16cid:paraId", "paraId").toUpperCase();
|
|
18329
|
+
const durable = attrOf(entry, "w16cid:durableId", "durableId").toUpperCase();
|
|
18330
|
+
if (durable) taken.add(durable);
|
|
18331
|
+
if (!byKey.has(key)) {
|
|
18332
|
+
root.removeChild(entry);
|
|
18333
|
+
idsChanged = true;
|
|
18334
|
+
continue;
|
|
18335
|
+
}
|
|
18336
|
+
durableByKey.set(key, durable);
|
|
18337
|
+
}
|
|
18338
|
+
for (const key of byKey.keys()) {
|
|
18339
|
+
if (durableByKey.has(key)) continue;
|
|
18340
|
+
const durable = durableIdFor(key, taken);
|
|
18341
|
+
const entry = idsDoc.createElementNS(NS_W16CID, "w16cid:commentId");
|
|
18342
|
+
entry.setAttributeNS(NS_W16CID, "w16cid:paraId", key);
|
|
18343
|
+
entry.setAttributeNS(NS_W16CID, "w16cid:durableId", durable);
|
|
18344
|
+
root.appendChild(entry);
|
|
18345
|
+
durableByKey.set(key, durable);
|
|
18346
|
+
idsChanged = true;
|
|
18347
|
+
}
|
|
18348
|
+
}
|
|
18349
|
+
let extDoc = null;
|
|
18350
|
+
let extChanged = false;
|
|
18351
|
+
if (commentsExtensibleXml && idsDoc) {
|
|
18352
|
+
const parsed = parseOoxmlSafe(commentsExtensibleXml, "application/xml");
|
|
18353
|
+
if (parsed.doc && !parsed.error) {
|
|
18354
|
+
extDoc = parsed.doc;
|
|
18355
|
+
const root = extDoc.documentElement;
|
|
18356
|
+
const live = new Set(durableByKey.values());
|
|
18357
|
+
const present = /* @__PURE__ */ new Set();
|
|
18358
|
+
for (const entry of Array.from(root.getElementsByTagNameNS("*", "commentExtensible"))) {
|
|
18359
|
+
const durable = attrOf(entry, "w16cex:durableId", "durableId").toUpperCase();
|
|
18360
|
+
if (!live.has(durable)) {
|
|
18361
|
+
root.removeChild(entry);
|
|
18362
|
+
extChanged = true;
|
|
18363
|
+
} else present.add(durable);
|
|
18364
|
+
}
|
|
18365
|
+
for (const [key, durable] of durableByKey) {
|
|
18366
|
+
if (present.has(durable)) continue;
|
|
18367
|
+
const date = attrOf(byKey.get(key), "w:date", "date");
|
|
18368
|
+
const entry = extDoc.createElementNS(NS_W16CEX, "w16cex:commentExtensible");
|
|
18369
|
+
entry.setAttributeNS(NS_W16CEX, "w16cex:durableId", durable);
|
|
18370
|
+
if (date) entry.setAttributeNS(NS_W16CEX, "w16cex:dateUtc", date);
|
|
18371
|
+
root.appendChild(entry);
|
|
18372
|
+
extChanged = true;
|
|
18373
|
+
}
|
|
18374
|
+
}
|
|
18375
|
+
}
|
|
18376
|
+
if (idsChanged) out.commentsIdsXml = serializer.serializeToString(idsDoc);
|
|
18377
|
+
if (extChanged) out.commentsExtensibleXml = serializer.serializeToString(extDoc);
|
|
18378
|
+
return out;
|
|
18379
|
+
}
|
|
18380
|
+
|
|
17993
18381
|
// core/sha256.js
|
|
17994
18382
|
var K = new Uint32Array([
|
|
17995
18383
|
1116352408,
|
|
@@ -18482,7 +18870,7 @@ function readCommentDefinitions(commentsDoc) {
|
|
|
18482
18870
|
author: attr2(comment, "author") || null,
|
|
18483
18871
|
date: attr2(comment, "date") || null,
|
|
18484
18872
|
text: paragraphs.map((p) => extractCanonicalParagraphText(p)).join("\n"),
|
|
18485
|
-
paraId:
|
|
18873
|
+
paraId: commentParaId(comment) || null
|
|
18486
18874
|
});
|
|
18487
18875
|
}
|
|
18488
18876
|
return result;
|
|
@@ -18713,43 +19101,6 @@ function inspectDocumentParts(parts, options = {}) {
|
|
|
18713
19101
|
};
|
|
18714
19102
|
}
|
|
18715
19103
|
|
|
18716
|
-
// services/comment-builders.js
|
|
18717
|
-
var NS_W14 = "http://schemas.microsoft.com/office/word/2010/wordml";
|
|
18718
|
-
var NS_W15 = "http://schemas.microsoft.com/office/word/2012/wordml";
|
|
18719
|
-
function createCommentParaId(commentId) {
|
|
18720
|
-
const numeric = Number.parseInt(String(commentId), 10);
|
|
18721
|
-
const value = Number.isFinite(numeric) ? 1879048192 + (numeric >>> 0) >>> 0 : 1879048192;
|
|
18722
|
-
return value.toString(16).toUpperCase().padStart(8, "0").slice(-8);
|
|
18723
|
-
}
|
|
18724
|
-
function buildCommentElement(commentId, author, content, date, paraId = createCommentParaId(commentId)) {
|
|
18725
|
-
const initials = author.split(" ").map((word) => word[0]).join("").toUpperCase() || "AI";
|
|
18726
|
-
const escapedContent = escapeXml(content);
|
|
18727
|
-
const escapedAuthor = escapeXml(author);
|
|
18728
|
-
return `<w:comment w:id="${commentId}" w:author="${escapedAuthor}" w:date="${date}" w:initials="${initials}">
|
|
18729
|
-
<w:p w14:paraId="${escapeXml(paraId)}" xmlns:w14="${NS_W14}">
|
|
18730
|
-
<w:r><w:t>${escapedContent}</w:t></w:r>
|
|
18731
|
-
</w:p>
|
|
18732
|
-
</w:comment>`;
|
|
18733
|
-
}
|
|
18734
|
-
function buildCommentsPartXml(comments) {
|
|
18735
|
-
if (!comments || comments.length === 0) {
|
|
18736
|
-
return `<w:comments xmlns:w="${NS_W}"></w:comments>`;
|
|
18737
|
-
}
|
|
18738
|
-
const commentElements = comments.map(
|
|
18739
|
-
(comment) => buildCommentElement(comment.id, comment.author, comment.content, comment.date, comment.paraId)
|
|
18740
|
-
).join("\n ");
|
|
18741
|
-
return `<w:comments xmlns:w="${NS_W}">
|
|
18742
|
-
${commentElements}
|
|
18743
|
-
</w:comments>`;
|
|
18744
|
-
}
|
|
18745
|
-
function buildCommentsExtendedPartXml(entries) {
|
|
18746
|
-
const body = (entries || []).map((entry) => {
|
|
18747
|
-
const parent = entry.paraIdParent ? ` w15:paraIdParent="${escapeXml(entry.paraIdParent)}"` : "";
|
|
18748
|
-
return `<w15:commentEx w15:paraId="${escapeXml(entry.paraId)}"${parent} w15:done="${entry.done ? "1" : "0"}"/>`;
|
|
18749
|
-
}).join("");
|
|
18750
|
-
return `<w15:commentsEx xmlns:w15="${NS_W15}">${body}</w15:commentsEx>`;
|
|
18751
|
-
}
|
|
18752
|
-
|
|
18753
19104
|
// services/comment-locator.js
|
|
18754
19105
|
function createParagraphTextIndex(paragraph, options = {}) {
|
|
18755
19106
|
const revisionView = options.revisionView === "current" ? "accepted" : options.revisionView || "accepted";
|
|
@@ -19210,29 +19561,7 @@ function parseRequired(xml, partName) {
|
|
|
19210
19561
|
}
|
|
19211
19562
|
return { doc: parsed.doc };
|
|
19212
19563
|
}
|
|
19213
|
-
function
|
|
19214
|
-
const ids = /* @__PURE__ */ new Set();
|
|
19215
|
-
for (const p of Array.from(commentsDoc?.getElementsByTagNameNS("*", "p") || [])) {
|
|
19216
|
-
const id = attr3(p, "w14:paraId", "paraId");
|
|
19217
|
-
if (id) ids.add(id.toUpperCase());
|
|
19218
|
-
}
|
|
19219
|
-
for (const ex of Array.from(extendedDoc?.getElementsByTagNameNS("*", "commentEx") || [])) {
|
|
19220
|
-
const id = attr3(ex, "w15:paraId", "paraId");
|
|
19221
|
-
if (id) ids.add(id.toUpperCase());
|
|
19222
|
-
}
|
|
19223
|
-
return ids;
|
|
19224
|
-
}
|
|
19225
|
-
function allocateParaId(commentId, occupied) {
|
|
19226
|
-
let candidate = createCommentParaId(commentId);
|
|
19227
|
-
let value = Number.parseInt(candidate, 16) >>> 0;
|
|
19228
|
-
while (occupied.has(candidate)) {
|
|
19229
|
-
value = value + 1 >>> 0;
|
|
19230
|
-
candidate = value.toString(16).toUpperCase().padStart(8, "0");
|
|
19231
|
-
}
|
|
19232
|
-
occupied.add(candidate);
|
|
19233
|
-
return candidate;
|
|
19234
|
-
}
|
|
19235
|
-
function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, parentCommentId, commentId, commentContent, author, date = (/* @__PURE__ */ new Date()).toISOString() }) {
|
|
19564
|
+
function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, documentXml = null, parentCommentId, commentId, commentContent, author, date = (/* @__PURE__ */ new Date()).toISOString() }) {
|
|
19236
19565
|
if (!commentsXml) return { status: "error", error: { code: "COMMENTS_PART_MISSING", message: "A comment reply requires an existing word/comments.xml part." } };
|
|
19237
19566
|
const commentsParsed = parseRequired(commentsXml, "word/comments.xml");
|
|
19238
19567
|
if (commentsParsed.error) return { status: "error", error: commentsParsed.error };
|
|
@@ -19246,15 +19575,31 @@ function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, par
|
|
|
19246
19575
|
extendedDoc = parsed.doc;
|
|
19247
19576
|
}
|
|
19248
19577
|
const occupied = usedParaIds(commentsDoc, extendedDoc);
|
|
19249
|
-
const
|
|
19578
|
+
if (documentXml) for (const match of String(documentXml).matchAll(/w14:paraId="([0-9A-Fa-f]{8})"/g)) occupied.add(match[1].toUpperCase());
|
|
19579
|
+
const parentParagraph = commentThreadParagraph(parent);
|
|
19250
19580
|
if (!parentParagraph) return { status: "error", error: { code: "PARENT_COMMENT_INVALID", message: `Parent comment '${parentCommentId}' has no paragraph.` } };
|
|
19251
|
-
let parentParaId =
|
|
19581
|
+
let parentParaId = commentParaId(parent);
|
|
19252
19582
|
if (!parentParaId) {
|
|
19253
19583
|
parentParaId = allocateParaId(parentCommentId, occupied);
|
|
19254
19584
|
parentParagraph.setAttributeNS(NS_W14, "w14:paraId", parentParaId);
|
|
19255
19585
|
} else {
|
|
19256
19586
|
parentParaId = parentParaId.toUpperCase();
|
|
19257
19587
|
}
|
|
19588
|
+
for (const node of Array.from(extendedDoc?.getElementsByTagNameNS("*", "commentEx") || [])) {
|
|
19589
|
+
if (attr3(node, "w15:paraId", "paraId").toUpperCase() === parentParaId) {
|
|
19590
|
+
const root2 = attr3(node, "w15:paraIdParent", "paraIdParent").toUpperCase();
|
|
19591
|
+
if (root2) parentParaId = root2;
|
|
19592
|
+
break;
|
|
19593
|
+
}
|
|
19594
|
+
}
|
|
19595
|
+
const threadCommentIds = [];
|
|
19596
|
+
const commentNodes = Array.from(commentsDoc.getElementsByTagNameNS("*", "comment"));
|
|
19597
|
+
const parentOf = new Map(Array.from(extendedDoc?.getElementsByTagNameNS("*", "commentEx") || []).map((node) => [attr3(node, "w15:paraId", "paraId").toUpperCase(), attr3(node, "w15:paraIdParent", "paraIdParent").toUpperCase()]));
|
|
19598
|
+
for (const node of commentNodes) {
|
|
19599
|
+
const key = commentParaId(node);
|
|
19600
|
+
if (key === parentParaId || parentOf.get(key) === parentParaId) threadCommentIds.push(attr3(node, "w:id", "id"));
|
|
19601
|
+
}
|
|
19602
|
+
const threadRootCommentId = attr3(commentNodes.find((node) => commentParaId(node) === parentParaId), "w:id", "id") || String(parentCommentId);
|
|
19258
19603
|
const replyParaId = allocateParaId(commentId, occupied);
|
|
19259
19604
|
const replyParsed = parseRequired(`<w:comments xmlns:w="${NS_W6}" xmlns:w14="${NS_W14}">${buildCommentElement(commentId, author, commentContent, date, replyParaId)}</w:comments>`, "reply comment");
|
|
19260
19605
|
commentsDoc.documentElement.appendChild(commentsDoc.importNode(replyParsed.doc.documentElement.firstChild, true));
|
|
@@ -19263,16 +19608,18 @@ function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, par
|
|
|
19263
19608
|
}
|
|
19264
19609
|
const root = extendedDoc.documentElement;
|
|
19265
19610
|
const entries = Array.from(root.getElementsByTagNameNS("*", "commentEx"));
|
|
19266
|
-
|
|
19611
|
+
const rootEntry = entries.find((node) => attr3(node, "w15:paraId", "paraId").toUpperCase() === parentParaId);
|
|
19612
|
+
if (!rootEntry) {
|
|
19267
19613
|
const parentEx = extendedDoc.createElementNS(NS_W15, "w15:commentEx");
|
|
19268
19614
|
parentEx.setAttributeNS(NS_W15, "w15:paraId", parentParaId);
|
|
19269
19615
|
parentEx.setAttributeNS(NS_W15, "w15:done", "0");
|
|
19270
19616
|
root.appendChild(parentEx);
|
|
19271
19617
|
}
|
|
19618
|
+
const threadDone = attr3(rootEntry, "w15:done", "done") === "1" ? "1" : "0";
|
|
19272
19619
|
const replyEx = extendedDoc.createElementNS(NS_W15, "w15:commentEx");
|
|
19273
19620
|
replyEx.setAttributeNS(NS_W15, "w15:paraId", replyParaId);
|
|
19274
19621
|
replyEx.setAttributeNS(NS_W15, "w15:paraIdParent", parentParaId);
|
|
19275
|
-
replyEx.setAttributeNS(NS_W15, "w15:done",
|
|
19622
|
+
replyEx.setAttributeNS(NS_W15, "w15:done", threadDone);
|
|
19276
19623
|
root.appendChild(replyEx);
|
|
19277
19624
|
const serializer = createSerializer();
|
|
19278
19625
|
return {
|
|
@@ -19285,7 +19632,9 @@ function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, par
|
|
|
19285
19632
|
commentId,
|
|
19286
19633
|
parentCommentId: String(parentCommentId),
|
|
19287
19634
|
paraId: replyParaId,
|
|
19288
|
-
parentParaId
|
|
19635
|
+
parentParaId,
|
|
19636
|
+
threadRootCommentId,
|
|
19637
|
+
threadCommentIds: threadCommentIds.length ? threadCommentIds : [threadRootCommentId]
|
|
19289
19638
|
};
|
|
19290
19639
|
}
|
|
19291
19640
|
|
|
@@ -19350,19 +19699,19 @@ function applyFormattingRemovalToOoxml(ooxmlString, targetText, formatTypes) {
|
|
|
19350
19699
|
if (!targetText || !ooxmlString) return ooxmlString;
|
|
19351
19700
|
const doc = parseOoxml(ooxmlString);
|
|
19352
19701
|
if (!doc) return ooxmlString;
|
|
19353
|
-
const
|
|
19354
|
-
const runs = doc.getElementsByTagNameNS(
|
|
19355
|
-
const insertions = doc.getElementsByTagNameNS(
|
|
19702
|
+
const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
19703
|
+
const runs = doc.getElementsByTagNameNS(NS_W11, "r");
|
|
19704
|
+
const insertions = doc.getElementsByTagNameNS(NS_W11, "ins");
|
|
19356
19705
|
const allRuns = [...Array.from(runs)];
|
|
19357
19706
|
for (const ins of insertions) {
|
|
19358
|
-
const insideRuns = ins.getElementsByTagNameNS(
|
|
19707
|
+
const insideRuns = ins.getElementsByTagNameNS(NS_W11, "r");
|
|
19359
19708
|
allRuns.push(...Array.from(insideRuns));
|
|
19360
19709
|
}
|
|
19361
19710
|
for (const run of allRuns) {
|
|
19362
|
-
const textNodes = run.getElementsByTagNameNS(
|
|
19711
|
+
const textNodes = run.getElementsByTagNameNS(NS_W11, "t");
|
|
19363
19712
|
const runText = Array.from(textNodes).map((t) => t.textContent).join("");
|
|
19364
19713
|
if (runText.includes(targetText) || runText === targetText) {
|
|
19365
|
-
const rPrElements = run.getElementsByTagNameNS(
|
|
19714
|
+
const rPrElements = run.getElementsByTagNameNS(NS_W11, "rPr");
|
|
19366
19715
|
if (rPrElements.length > 0) {
|
|
19367
19716
|
const rPr = rPrElements[0];
|
|
19368
19717
|
const newRPr = removeFormattingFromRPr(rPr, formatTypes);
|
|
@@ -19397,7 +19746,7 @@ var HIGHLIGHT_COLOR_MAP = {
|
|
|
19397
19746
|
"white": "white"
|
|
19398
19747
|
};
|
|
19399
19748
|
function injectHighlightIntoRPr(doc, rPr, color = "yellow", options = {}) {
|
|
19400
|
-
const
|
|
19749
|
+
const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
19401
19750
|
const ooxmlColor = HIGHLIGHT_COLOR_MAP[color.toLowerCase()] || "yellow";
|
|
19402
19751
|
const generateRedlines = options?.generateRedlines ?? false;
|
|
19403
19752
|
const author = options?.author || getDefaultAuthor();
|
|
@@ -19416,10 +19765,10 @@ function injectHighlightIntoRPr(doc, rPr, color = "yellow", options = {}) {
|
|
|
19416
19765
|
}
|
|
19417
19766
|
});
|
|
19418
19767
|
}
|
|
19419
|
-
const existingHighlight = rPrElement.getElementsByTagNameNS(
|
|
19768
|
+
const existingHighlight = rPrElement.getElementsByTagNameNS(NS_W11, "highlight");
|
|
19420
19769
|
Array.from(existingHighlight).forEach(removeNode2);
|
|
19421
19770
|
const highlightEl = createWordElement(doc, "w:highlight");
|
|
19422
|
-
highlightEl.setAttributeNS(
|
|
19771
|
+
highlightEl.setAttributeNS(NS_W11, "w:val", ooxmlColor);
|
|
19423
19772
|
rPrElement.appendChild(highlightEl);
|
|
19424
19773
|
if (generateRedlines && previousRPrState) {
|
|
19425
19774
|
const rPrChange = createWordElement(doc, "w:rPrChange");
|
|
@@ -19428,7 +19777,7 @@ function injectHighlightIntoRPr(doc, rPr, color = "yellow", options = {}) {
|
|
|
19428
19777
|
rPrChange.setAttribute("w:author", metadata.author);
|
|
19429
19778
|
rPrChange.setAttribute("w:date", metadata.date);
|
|
19430
19779
|
rPrChange.appendChild(previousRPrState);
|
|
19431
|
-
const existingChange = rPrElement.getElementsByTagNameNS(
|
|
19780
|
+
const existingChange = rPrElement.getElementsByTagNameNS(NS_W11, "rPrChange");
|
|
19432
19781
|
Array.from(existingChange).forEach(removeNode2);
|
|
19433
19782
|
rPrElement.appendChild(rPrChange);
|
|
19434
19783
|
}
|
|
@@ -19445,12 +19794,12 @@ function applyHighlightToOoxml(ooxmlString, targetText, color = "yellow", option
|
|
|
19445
19794
|
} else {
|
|
19446
19795
|
revisionIdAllocator = createRevisionIdAllocator(doc);
|
|
19447
19796
|
}
|
|
19448
|
-
const
|
|
19797
|
+
const NS_W11 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
19449
19798
|
const getRunText = (run) => {
|
|
19450
|
-
const textNodes = run.getElementsByTagNameNS(
|
|
19799
|
+
const textNodes = run.getElementsByTagNameNS(NS_W11, "t");
|
|
19451
19800
|
return Array.from(textNodes).map((t) => t.textContent).join("");
|
|
19452
19801
|
};
|
|
19453
|
-
const allRuns = Array.from(doc.getElementsByTagNameNS(
|
|
19802
|
+
const allRuns = Array.from(doc.getElementsByTagNameNS(NS_W11, "r"));
|
|
19454
19803
|
const sourceRunsWithClaimedRevisionIds = /* @__PURE__ */ new WeakSet();
|
|
19455
19804
|
const cloneRunWithText2 = (sourceRun, text2, shouldHighlight) => {
|
|
19456
19805
|
const clonedRun = sourceRun.cloneNode(true);
|
|
@@ -19459,14 +19808,14 @@ function applyHighlightToOoxml(ooxmlString, targetText, color = "yellow", option
|
|
|
19459
19808
|
} else {
|
|
19460
19809
|
sourceRunsWithClaimedRevisionIds.add(sourceRun);
|
|
19461
19810
|
}
|
|
19462
|
-
const textNodes = clonedRun.getElementsByTagNameNS(
|
|
19811
|
+
const textNodes = clonedRun.getElementsByTagNameNS(NS_W11, "t");
|
|
19463
19812
|
Array.from(textNodes).forEach(removeNode2);
|
|
19464
19813
|
const newText = createWordElement(doc, "w:t");
|
|
19465
19814
|
newText.setAttribute("xml:space", "preserve");
|
|
19466
19815
|
newText.textContent = text2;
|
|
19467
19816
|
clonedRun.appendChild(newText);
|
|
19468
19817
|
if (shouldHighlight) {
|
|
19469
|
-
const rPrElements = clonedRun.getElementsByTagNameNS(
|
|
19818
|
+
const rPrElements = clonedRun.getElementsByTagNameNS(NS_W11, "rPr");
|
|
19470
19819
|
const existingRPr = rPrElements.length > 0 ? rPrElements[0] : null;
|
|
19471
19820
|
const newRPr = injectHighlightIntoRPr(doc, existingRPr, color, options);
|
|
19472
19821
|
if (existingRPr) {
|
|
@@ -19515,20 +19864,68 @@ function applyHighlightToOoxml(ooxmlString, targetText, color = "yellow", option
|
|
|
19515
19864
|
return serializeOoxml(doc);
|
|
19516
19865
|
}
|
|
19517
19866
|
|
|
19867
|
+
// services/package-parts.js
|
|
19868
|
+
var WORD_ML = "application/vnd.openxmlformats-officedocument.wordprocessingml";
|
|
19869
|
+
var REL_OFFICE_DOC = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
19870
|
+
var PART_SPECS = Object.freeze([
|
|
19871
|
+
{ id: "document", path: "word/document.xml", pathPattern: null, contentType: `${WORD_ML}.document.main+xml`, legacyContentTypes: [], relType: `${REL_OFFICE_DOC}/officeDocument`, relTarget: null, verified: true },
|
|
19872
|
+
{ id: "numbering", path: "word/numbering.xml", pathPattern: null, contentType: `${WORD_ML}.numbering+xml`, legacyContentTypes: [], relType: `${REL_OFFICE_DOC}/numbering`, relTarget: "numbering.xml", verified: false },
|
|
19873
|
+
{ id: "comments", path: "word/comments.xml", pathPattern: null, contentType: `${WORD_ML}.comments+xml`, legacyContentTypes: [], relType: `${REL_OFFICE_DOC}/comments`, relTarget: "comments.xml", verified: true },
|
|
19874
|
+
{
|
|
19875
|
+
id: "commentsExtended",
|
|
19876
|
+
path: "word/commentsExtended.xml",
|
|
19877
|
+
pathPattern: null,
|
|
19878
|
+
contentType: `${WORD_ML}.commentsExtended+xml`,
|
|
19879
|
+
// Written by this package through 0.8.0; Word does not recognize it and offers to repair the file.
|
|
19880
|
+
legacyContentTypes: ["application/vnd.ms-word.commentsExtended+xml"],
|
|
19881
|
+
relType: "http://schemas.microsoft.com/office/2011/relationships/commentsExtended",
|
|
19882
|
+
relTarget: "commentsExtended.xml",
|
|
19883
|
+
verified: true
|
|
19884
|
+
},
|
|
19885
|
+
{ 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 },
|
|
19886
|
+
{ 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 },
|
|
19887
|
+
// Not yet produced by any Word fixture (Word only writes people.xml for signed-in authors): unverified.
|
|
19888
|
+
{ 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 },
|
|
19889
|
+
{ 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 },
|
|
19890
|
+
{ 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 }
|
|
19891
|
+
].map((spec) => Object.freeze(spec)));
|
|
19892
|
+
var BY_ID = new Map(PART_SPECS.map((spec) => [spec.id, spec]));
|
|
19893
|
+
function getPartSpec(id) {
|
|
19894
|
+
const spec = BY_ID.get(id);
|
|
19895
|
+
if (!spec) throw new Error(`Unknown package part: ${id}`);
|
|
19896
|
+
return spec;
|
|
19897
|
+
}
|
|
19898
|
+
function toEntryName(partName) {
|
|
19899
|
+
return String(partName || "").replace(/^\/+/, "");
|
|
19900
|
+
}
|
|
19901
|
+
function findPartSpecByPartName(partName) {
|
|
19902
|
+
const name = toEntryName(partName);
|
|
19903
|
+
const lower = name.toLowerCase();
|
|
19904
|
+
return PART_SPECS.find((spec) => spec.path ? spec.path.toLowerCase() === lower : spec.pathPattern?.test(name)) || null;
|
|
19905
|
+
}
|
|
19906
|
+
function correctedContentTypeFor(partName, actualContentType) {
|
|
19907
|
+
const spec = findPartSpecByPartName(partName);
|
|
19908
|
+
if (!spec || !spec.legacyContentTypes.includes(actualContentType)) return null;
|
|
19909
|
+
return spec.contentType;
|
|
19910
|
+
}
|
|
19911
|
+
|
|
19518
19912
|
// services/standalone-docx-plumbing.js
|
|
19519
19913
|
var NS_W7 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
19520
19914
|
var NS_CT = "http://schemas.openxmlformats.org/package/2006/content-types";
|
|
19521
19915
|
var NS_RELS = "http://schemas.openxmlformats.org/package/2006/relationships";
|
|
19522
|
-
var
|
|
19523
|
-
var
|
|
19524
|
-
var
|
|
19525
|
-
var
|
|
19526
|
-
var
|
|
19527
|
-
var
|
|
19528
|
-
var
|
|
19529
|
-
var
|
|
19530
|
-
var
|
|
19531
|
-
var
|
|
19916
|
+
var NUMBERING = getPartSpec("numbering");
|
|
19917
|
+
var COMMENTS = getPartSpec("comments");
|
|
19918
|
+
var COMMENTS_EXTENDED = getPartSpec("commentsExtended");
|
|
19919
|
+
var NUMBERING_REL_TYPE = NUMBERING.relType;
|
|
19920
|
+
var NUMBERING_CONTENT_TYPE = NUMBERING.contentType;
|
|
19921
|
+
var COMMENTS_REL_TYPE = COMMENTS.relType;
|
|
19922
|
+
var COMMENTS_CONTENT_TYPE = COMMENTS.contentType;
|
|
19923
|
+
var COMMENTS_EXTENDED_REL_TYPE = COMMENTS_EXTENDED.relType;
|
|
19924
|
+
var COMMENTS_EXTENDED_CONTENT_TYPE = COMMENTS_EXTENDED.contentType;
|
|
19925
|
+
var DOCUMENT_PATH = getPartSpec("document").path;
|
|
19926
|
+
var NUMBERING_PATH = NUMBERING.path;
|
|
19927
|
+
var COMMENTS_PATH = COMMENTS.path;
|
|
19928
|
+
var COMMENTS_EXTENDED_PATH = COMMENTS_EXTENDED.path;
|
|
19532
19929
|
var CONTENT_TYPES_PATH = "[Content_Types].xml";
|
|
19533
19930
|
var DOCUMENT_RELS_PATH = "word/_rels/document.xml.rels";
|
|
19534
19931
|
function parseXmlStrictStandalone(xmlText, label = "xml") {
|
|
@@ -19816,6 +20213,22 @@ async function ensureCommentsExtendedArtifactsInZip(zip, commentsExtendedXml, op
|
|
|
19816
20213
|
}
|
|
19817
20214
|
}
|
|
19818
20215
|
}
|
|
20216
|
+
async function repairKnownContentTypes(zip) {
|
|
20217
|
+
const ctText = await readZipText(zip, CONTENT_TYPES_PATH);
|
|
20218
|
+
if (!ctText) return [];
|
|
20219
|
+
const ctDoc = parseXmlStrictStandalone(ctText, CONTENT_TYPES_PATH);
|
|
20220
|
+
const repairs = [];
|
|
20221
|
+
for (const override of Array.from(ctDoc.getElementsByTagNameNS("*", "Override"))) {
|
|
20222
|
+
const partName = override.getAttribute("PartName") || "";
|
|
20223
|
+
const actual = override.getAttribute("ContentType") || "";
|
|
20224
|
+
const expected = correctedContentTypeFor(partName, actual);
|
|
20225
|
+
if (!expected) continue;
|
|
20226
|
+
override.setAttribute("ContentType", expected);
|
|
20227
|
+
repairs.push({ partName, from: actual, to: expected });
|
|
20228
|
+
}
|
|
20229
|
+
if (repairs.length > 0) zip.file(CONTENT_TYPES_PATH, createSerializer().serializeToString(ctDoc));
|
|
20230
|
+
return repairs;
|
|
20231
|
+
}
|
|
19819
20232
|
async function validateDocxPackage(zip) {
|
|
19820
20233
|
const documentXml = await readZipText(zip, DOCUMENT_PATH);
|
|
19821
20234
|
if (!documentXml) {
|
|
@@ -19897,9 +20310,8 @@ async function validateDocxPackage(zip) {
|
|
|
19897
20310
|
const paraIdToCommentId = /* @__PURE__ */ new Map();
|
|
19898
20311
|
for (const comment of Array.from(commentsDoc.getElementsByTagNameNS("*", "comment"))) {
|
|
19899
20312
|
const id = comment.getAttribute("w:id") || comment.getAttribute("id");
|
|
19900
|
-
const
|
|
19901
|
-
|
|
19902
|
-
if (paraId) paraIdToCommentId.set(paraId.toUpperCase(), id);
|
|
20313
|
+
const paraId = commentParaId(comment);
|
|
20314
|
+
if (paraId) paraIdToCommentId.set(paraId, id);
|
|
19903
20315
|
}
|
|
19904
20316
|
const knownParaIds = new Set(paraIdToCommentId.keys());
|
|
19905
20317
|
const extendedParaIds = /* @__PURE__ */ new Set();
|
|
@@ -19960,10 +20372,39 @@ async function validateDocxPackage(zip) {
|
|
|
19960
20372
|
throw new Error("Validation failed: comments rel missing");
|
|
19961
20373
|
}
|
|
19962
20374
|
}
|
|
20375
|
+
{
|
|
20376
|
+
const relationshipById = /* @__PURE__ */ new Map();
|
|
20377
|
+
for (const rel of Array.from(relsDoc.getElementsByTagNameNS("*", "Relationship"))) {
|
|
20378
|
+
relationshipById.set(rel.getAttribute("Id") || "", { type: rel.getAttribute("Type") || "", target: rel.getAttribute("Target") || "" });
|
|
20379
|
+
}
|
|
20380
|
+
const headerSpec = getPartSpec("header");
|
|
20381
|
+
const footerSpec = getPartSpec("footer");
|
|
20382
|
+
const overrideTypeByPart = new Map(Array.from(ctDoc.getElementsByTagNameNS("*", "Override")).map((override) => [(override.getAttribute("PartName") || "").toLowerCase(), override.getAttribute("ContentType") || ""]));
|
|
20383
|
+
for (const reference of [
|
|
20384
|
+
...Array.from(documentDoc.getElementsByTagNameNS(NS_W7, "headerReference")),
|
|
20385
|
+
...Array.from(documentDoc.getElementsByTagNameNS(NS_W7, "footerReference"))
|
|
20386
|
+
]) {
|
|
20387
|
+
const relId = reference.getAttributeNS?.("http://schemas.openxmlformats.org/officeDocument/2006/relationships", "id") || reference.getAttribute("r:id") || "";
|
|
20388
|
+
const spec = reference.localName === "headerReference" ? headerSpec : footerSpec;
|
|
20389
|
+
const relationship = relationshipById.get(relId);
|
|
20390
|
+
if (!relationship) throw new Error(`Validation failed: ${reference.localName} ${relId || "(no r:id)"} has no document relationship`);
|
|
20391
|
+
if (relationship.type !== spec.relType) throw new Error(`Validation failed: ${reference.localName} ${relId} points at a relationship of the wrong type (${relationship.type})`);
|
|
20392
|
+
const partName = relationship.target.startsWith("/") ? relationship.target : `/word/${relationship.target}`;
|
|
20393
|
+
const partXml = await readZipText(zip, partName.slice(1));
|
|
20394
|
+
if (!partXml) throw new Error(`Validation failed: ${reference.localName} ${relId} targets a missing part: ${partName}`);
|
|
20395
|
+
parseXmlStrictStandalone(partXml, partName.slice(1));
|
|
20396
|
+
const contentType = overrideTypeByPart.get(partName.toLowerCase());
|
|
20397
|
+
if (contentType !== spec.contentType) throw new Error(`Validation failed: ${partName} content type is ${contentType ? `'${contentType}'` : "missing"}; Word expects '${spec.contentType}'`);
|
|
20398
|
+
}
|
|
20399
|
+
}
|
|
19963
20400
|
if (commentsExtendedXml) {
|
|
19964
|
-
const
|
|
19965
|
-
(override) => (override.getAttribute("PartName") || "").toLowerCase() === "/word/commentsextended.xml"
|
|
20401
|
+
const extendedOverride = Array.from(ctDoc.getElementsByTagNameNS("*", "Override")).find(
|
|
20402
|
+
(override) => (override.getAttribute("PartName") || "").toLowerCase() === "/word/commentsextended.xml"
|
|
19966
20403
|
);
|
|
20404
|
+
if (extendedOverride && extendedOverride.getAttribute("ContentType") !== COMMENTS_EXTENDED_CONTENT_TYPE) {
|
|
20405
|
+
throw new Error(`Validation failed: commentsExtended CT override has wrong content type '${extendedOverride.getAttribute("ContentType")}' (Word expects '${COMMENTS_EXTENDED_CONTENT_TYPE}')`);
|
|
20406
|
+
}
|
|
20407
|
+
const hasContentType = !!extendedOverride;
|
|
19967
20408
|
const hasRelationship = Array.from(relsDoc.getElementsByTagNameNS("*", "Relationship")).some(
|
|
19968
20409
|
(rel) => (rel.getAttribute("Type") || "") === COMMENTS_EXTENDED_REL_TYPE
|
|
19969
20410
|
);
|
|
@@ -20327,6 +20768,156 @@ function reconcileReceiptsAgainstOutput(parts, receipts) {
|
|
|
20327
20768
|
return { valid: true };
|
|
20328
20769
|
}
|
|
20329
20770
|
|
|
20771
|
+
// services/headers-footers.js
|
|
20772
|
+
var NS_W8 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
20773
|
+
var NS_R2 = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
20774
|
+
var REVISION_ELEMENTS = /* @__PURE__ */ new Set(["ins", "del", "moveFrom", "moveTo", "rPrChange", "pPrChange", "sectPrChange", "tblPrChange", "tblGridChange", "tcPrChange", "trPrChange", "numberingChange"]);
|
|
20775
|
+
var REVISION_CONTENT_ELEMENTS = /* @__PURE__ */ new Set(["ins", "del", "moveFrom", "moveTo"]);
|
|
20776
|
+
var attrOf2 = (node, qualified, local) => node?.getAttribute?.(qualified) || node?.getAttribute?.(local) || "";
|
|
20777
|
+
function resolveTarget(target) {
|
|
20778
|
+
const clean = String(target || "");
|
|
20779
|
+
if (clean.startsWith("/")) return clean.replace(/^\/+/, "");
|
|
20780
|
+
const parts = ["word"];
|
|
20781
|
+
for (const segment of clean.split("/")) {
|
|
20782
|
+
if (segment === "..") parts.pop();
|
|
20783
|
+
else if (segment && segment !== ".") parts.push(segment);
|
|
20784
|
+
}
|
|
20785
|
+
return parts.join("/");
|
|
20786
|
+
}
|
|
20787
|
+
function discoverHeaderFooterParts({ documentXml, relsXml = null, settingsXml = null }) {
|
|
20788
|
+
if (!documentXml || !relsXml) return [];
|
|
20789
|
+
const documentParsed = parseOoxmlSafe(documentXml, "application/xml");
|
|
20790
|
+
const relsParsed = parseOoxmlSafe(relsXml, "application/xml");
|
|
20791
|
+
if (!documentParsed.doc || documentParsed.error || !relsParsed.doc || relsParsed.error) return [];
|
|
20792
|
+
const targetByRelId = /* @__PURE__ */ new Map();
|
|
20793
|
+
for (const rel of Array.from(relsParsed.doc.getElementsByTagNameNS("*", "Relationship"))) {
|
|
20794
|
+
targetByRelId.set(rel.getAttribute("Id") || "", rel.getAttribute("Target") || "");
|
|
20795
|
+
}
|
|
20796
|
+
const evenAndOdd = !!settingsXml && /<w:evenAndOddHeaders\b(?![^>]*w:val="(?:0|false|off)")/.test(settingsXml);
|
|
20797
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
20798
|
+
const inForce = /* @__PURE__ */ new Map();
|
|
20799
|
+
const sectPrs = Array.from(documentParsed.doc.getElementsByTagNameNS(NS_W8, "sectPr"));
|
|
20800
|
+
sectPrs.forEach((sectPr, sectionIndex) => {
|
|
20801
|
+
const titlePg = Array.from(sectPr.childNodes || []).some((child) => child.localName === "titlePg" && !/^(0|false|off)$/i.test(attrOf2(child, "w:val", "val")));
|
|
20802
|
+
for (const ref of Array.from(sectPr.childNodes || [])) {
|
|
20803
|
+
if (ref.nodeType !== 1 || ref.localName !== "headerReference" && ref.localName !== "footerReference") continue;
|
|
20804
|
+
const relId = ref.getAttributeNS?.(NS_R2, "id") || ref.getAttribute("r:id") || "";
|
|
20805
|
+
const target = targetByRelId.get(relId);
|
|
20806
|
+
if (!target) continue;
|
|
20807
|
+
const path = resolveTarget(target);
|
|
20808
|
+
const kind = ref.localName === "headerReference" ? "header" : "footer";
|
|
20809
|
+
const type = attrOf2(ref, "w:type", "type") || "default";
|
|
20810
|
+
const entry = byPath.get(path) || { path, kind, type, sections: [], applies: [], titlePgSections: 0 };
|
|
20811
|
+
if (!entry.sections.includes(sectionIndex)) entry.sections.push(sectionIndex);
|
|
20812
|
+
if (titlePg) entry.titlePgSections += 1;
|
|
20813
|
+
byPath.set(path, entry);
|
|
20814
|
+
inForce.set(`${kind}:${type}`, path);
|
|
20815
|
+
}
|
|
20816
|
+
for (const path of inForce.values()) {
|
|
20817
|
+
const entry = byPath.get(path);
|
|
20818
|
+
if (entry && !entry.applies.includes(sectionIndex)) entry.applies.push(sectionIndex);
|
|
20819
|
+
}
|
|
20820
|
+
});
|
|
20821
|
+
return Array.from(byPath.values()).map((entry) => ({
|
|
20822
|
+
path: entry.path,
|
|
20823
|
+
kind: entry.kind,
|
|
20824
|
+
type: entry.type,
|
|
20825
|
+
sections: entry.sections,
|
|
20826
|
+
appliesToSections: entry.applies,
|
|
20827
|
+
sharedBySections: entry.applies.length > 1,
|
|
20828
|
+
// first-page parts only show with w:titlePg; even parts only with w:evenAndOddHeaders in settings.xml.
|
|
20829
|
+
active: entry.type === "first" ? entry.titlePgSections > 0 : entry.type === "even" ? evenAndOdd : true
|
|
20830
|
+
}));
|
|
20831
|
+
}
|
|
20832
|
+
function resolvePartSelector(parts, selector) {
|
|
20833
|
+
const describe = (part) => ({ path: part.path, kind: part.kind, type: part.type, sections: part.sections, appliesToSections: part.appliesToSections });
|
|
20834
|
+
if (typeof selector === "string") {
|
|
20835
|
+
const wanted = selector.replace(/^\/+/, "").toLowerCase();
|
|
20836
|
+
const match = parts.find((part) => part.path.toLowerCase() === wanted || part.path.toLowerCase() === `word/${wanted}`);
|
|
20837
|
+
if (match) return { part: match };
|
|
20838
|
+
return { error: { code: "PART_NOT_FOUND", message: `No header/footer part '${selector}' is referenced by the document.`, candidates: parts.map(describe) } };
|
|
20839
|
+
}
|
|
20840
|
+
if (!selector || typeof selector !== "object" || selector.kind !== "header" && selector.kind !== "footer") {
|
|
20841
|
+
return { error: { code: "INVALID_OPERATION", message: 'part must be a part path or { kind: "header"|"footer", type?: "default"|"first"|"even", section?: number }.' } };
|
|
20842
|
+
}
|
|
20843
|
+
const type = selector.type || "default";
|
|
20844
|
+
if (!["default", "first", "even"].includes(type)) {
|
|
20845
|
+
return { error: { code: "INVALID_OPERATION", message: `part.type must be default, first or even (got '${type}').` } };
|
|
20846
|
+
}
|
|
20847
|
+
let matches = parts.filter((part) => part.kind === selector.kind && part.type === type);
|
|
20848
|
+
if (Number.isInteger(selector.section)) matches = matches.filter((part) => part.appliesToSections.includes(selector.section));
|
|
20849
|
+
if (matches.length === 1) return { part: matches[0] };
|
|
20850
|
+
if (matches.length === 0) {
|
|
20851
|
+
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) } };
|
|
20852
|
+
}
|
|
20853
|
+
return { error: { code: "PART_AMBIGUOUS", message: `Several ${type} ${selector.kind} parts exist; add "section" or use the part path.`, candidates: matches.map(describe) } };
|
|
20854
|
+
}
|
|
20855
|
+
function isHeaderFooterPath(path) {
|
|
20856
|
+
const spec = findPartSpecByPartName(path);
|
|
20857
|
+
return spec?.id === "header" || spec?.id === "footer";
|
|
20858
|
+
}
|
|
20859
|
+
function countRevisionsInsideFields(xml) {
|
|
20860
|
+
const parsed = parseOoxmlSafe(xml, "application/xml");
|
|
20861
|
+
if (!parsed.doc || parsed.error) return 0;
|
|
20862
|
+
let depth = 0;
|
|
20863
|
+
let count = 0;
|
|
20864
|
+
const visit = (node) => {
|
|
20865
|
+
for (const child of Array.from(node.childNodes || [])) {
|
|
20866
|
+
if (child.nodeType !== 1) continue;
|
|
20867
|
+
const name = child.localName;
|
|
20868
|
+
if (name === "fldChar") {
|
|
20869
|
+
const type = attrOf2(child, "w:fldCharType", "fldCharType");
|
|
20870
|
+
if (type === "begin") depth += 1;
|
|
20871
|
+
else if (type === "end") depth = Math.max(0, depth - 1);
|
|
20872
|
+
continue;
|
|
20873
|
+
}
|
|
20874
|
+
if (REVISION_CONTENT_ELEMENTS.has(name) && depth > 0) count += 1;
|
|
20875
|
+
if (name === "fldSimple") {
|
|
20876
|
+
depth += 1;
|
|
20877
|
+
visit(child);
|
|
20878
|
+
depth -= 1;
|
|
20879
|
+
continue;
|
|
20880
|
+
}
|
|
20881
|
+
visit(child);
|
|
20882
|
+
}
|
|
20883
|
+
};
|
|
20884
|
+
visit(parsed.doc.documentElement);
|
|
20885
|
+
return count;
|
|
20886
|
+
}
|
|
20887
|
+
function collectRevisionIds(xml) {
|
|
20888
|
+
const ids = /* @__PURE__ */ new Set();
|
|
20889
|
+
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]);
|
|
20890
|
+
return ids;
|
|
20891
|
+
}
|
|
20892
|
+
function renumberCollidingRevisionIds(xml, taken) {
|
|
20893
|
+
const parsed = parseOoxmlSafe(xml, "application/xml");
|
|
20894
|
+
if (!parsed.doc || parsed.error) return { xml, renumbered: 0, idMap: /* @__PURE__ */ new Map() };
|
|
20895
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
20896
|
+
let next = 1 + Math.max(0, ...Array.from(taken, (id) => Number(id)).filter(Number.isFinite));
|
|
20897
|
+
const used = new Set(taken);
|
|
20898
|
+
let renumbered = 0;
|
|
20899
|
+
const visit = (node) => {
|
|
20900
|
+
for (const child of Array.from(node.childNodes || [])) {
|
|
20901
|
+
if (child.nodeType !== 1) continue;
|
|
20902
|
+
if (REVISION_ELEMENTS.has(child.localName)) {
|
|
20903
|
+
const id = attrOf2(child, "w:id", "id");
|
|
20904
|
+
if (id && used.has(id)) {
|
|
20905
|
+
while (used.has(String(next))) next += 1;
|
|
20906
|
+
child.setAttributeNS(NS_W8, "w:id", String(next));
|
|
20907
|
+
idMap.set(id, String(next));
|
|
20908
|
+
used.add(String(next));
|
|
20909
|
+
renumbered += 1;
|
|
20910
|
+
} else if (id) {
|
|
20911
|
+
used.add(id);
|
|
20912
|
+
}
|
|
20913
|
+
}
|
|
20914
|
+
visit(child);
|
|
20915
|
+
}
|
|
20916
|
+
};
|
|
20917
|
+
visit(parsed.doc.documentElement);
|
|
20918
|
+
return renumbered > 0 ? { xml: createSerializer().serializeToString(parsed.doc), renumbered, idMap } : { xml, renumbered: 0, idMap };
|
|
20919
|
+
}
|
|
20920
|
+
|
|
20330
20921
|
// services/error-recovery.js
|
|
20331
20922
|
var ERROR_RECOVERY_VERSION = 1;
|
|
20332
20923
|
var RULES = Object.freeze({
|
|
@@ -20364,6 +20955,12 @@ var RULES = Object.freeze({
|
|
|
20364
20955
|
STRUCTURED_CONTENT_INVALID: ["request", "request_fixable", "change_request"],
|
|
20365
20956
|
UNSUPPORTED_INSERTION_AFFINITY: ["request", "request_fixable", "change_request"],
|
|
20366
20957
|
EXISTING_REVISIONS: ["target-safety", "policy_choice_required", "set_option"],
|
|
20958
|
+
PART_NOT_FOUND: ["target-resolution", "target_refresh_required", "reinspect"],
|
|
20959
|
+
PART_AMBIGUOUS: ["target-resolution", "candidate_selection_required", "choose_candidate"],
|
|
20960
|
+
COMMENT_IN_HEADER_FOOTER: ["request", "request_fixable", "change_request"],
|
|
20961
|
+
FIELD_EDIT_REFUSED: ["target-safety", "request_fixable", "change_request"],
|
|
20962
|
+
COMMENT_NOT_FOUND: ["target-resolution", "target_refresh_required", "reinspect"],
|
|
20963
|
+
PARENT_ANCHOR_NOT_FOUND: ["target-resolution", "target_refresh_required", "reinspect"],
|
|
20367
20964
|
COMMENTED_CONTENT_MERGE: ["target-safety", "user_authorization_required", "resolve_comments"],
|
|
20368
20965
|
COMMENTED_CONTENT_DELETE: ["target-safety", "user_authorization_required", "resolve_comments"],
|
|
20369
20966
|
OVERLAPPING_SOURCE_TARGETS: ["batch-compilation", "source_conflict", "consolidate_operations"],
|
|
@@ -20816,6 +21413,7 @@ var SUPPORTED_OPERATION_TYPES = /* @__PURE__ */ new Set([
|
|
|
20816
21413
|
"delete",
|
|
20817
21414
|
"comment",
|
|
20818
21415
|
"comment_reply",
|
|
21416
|
+
"comment_resolve",
|
|
20819
21417
|
"highlight"
|
|
20820
21418
|
]);
|
|
20821
21419
|
var EXISTING_REVISIONS_POLICIES = Object.freeze([
|
|
@@ -20834,11 +21432,14 @@ function isRecord(value) {
|
|
|
20834
21432
|
function nonEmptyString(value) {
|
|
20835
21433
|
return typeof value === "string" && value.trim().length > 0;
|
|
20836
21434
|
}
|
|
21435
|
+
function isCommentThreadOperationKind(kind) {
|
|
21436
|
+
return kind === "comment_reply" || kind === "comment_resolve";
|
|
21437
|
+
}
|
|
20837
21438
|
function getCanonicalOperationType(operation) {
|
|
20838
21439
|
const type = operation?.type;
|
|
20839
21440
|
if (type === "insert" && operation?.target?.revisionView === "rejected") return "rejected-insert";
|
|
20840
21441
|
if (type === "restore") return "restore";
|
|
20841
|
-
if (type === "comment" || type === "comment_reply" || type === "highlight") return type;
|
|
21442
|
+
if (type === "comment" || type === "comment_reply" || type === "comment_resolve" || type === "highlight") return type;
|
|
20842
21443
|
if (type === "paragraph-format") return "paragraph-format";
|
|
20843
21444
|
if (type === "character-format" || type === "format" && (operation?.textToFormat != null || operation?.properties != null)) return "format";
|
|
20844
21445
|
return "redline";
|
|
@@ -20958,7 +21559,7 @@ function validateDocumentOperation(operation) {
|
|
|
20958
21559
|
};
|
|
20959
21560
|
}
|
|
20960
21561
|
const target = normalized.targetDescriptor;
|
|
20961
|
-
if (normalized.operationKind
|
|
21562
|
+
if (!isCommentThreadOperationKind(normalized.operationKind) && !nonEmptyString(target.text) && target.index == null && !target.paragraphId && !target.fingerprint && !target.captureRef) {
|
|
20962
21563
|
return {
|
|
20963
21564
|
valid: false,
|
|
20964
21565
|
error: {
|
|
@@ -21185,6 +21786,14 @@ function validateDocumentOperation(operation) {
|
|
|
21185
21786
|
return { valid: false, error: { code: "INVALID_OPERATION", message: 'Comment reply operations require a non-empty "commentContent" field.' } };
|
|
21186
21787
|
}
|
|
21187
21788
|
}
|
|
21789
|
+
if (normalized.operationKind === "comment_resolve") {
|
|
21790
|
+
if (!nonEmptyString(String(normalized.commentId ?? ""))) {
|
|
21791
|
+
return { valid: false, error: { code: "INVALID_OPERATION", message: 'Comment resolve operations require a "commentId".' } };
|
|
21792
|
+
}
|
|
21793
|
+
if (normalized.resolved != null && typeof normalized.resolved !== "boolean") {
|
|
21794
|
+
return { valid: false, error: { code: "INVALID_OPERATION", message: 'Comment resolve "resolved" must be a boolean when provided.' } };
|
|
21795
|
+
}
|
|
21796
|
+
}
|
|
21188
21797
|
if (normalized.operationKind === "highlight") {
|
|
21189
21798
|
if (!nonEmptyString(normalized.textToHighlight)) {
|
|
21190
21799
|
return {
|
|
@@ -21664,7 +22273,7 @@ function compileOperationBatch(xmlDoc, operations = [], options = {}) {
|
|
|
21664
22273
|
const dynamicOccurrence = operation.targetDescriptor?.occurrence != null && !operation.targetDescriptor?.paragraphId && !operation.targetDescriptor?.fingerprint;
|
|
21665
22274
|
const compositeTextTarget = typeof operation.targetDescriptor?.text === "string" && /\r|\n/.test(operation.targetDescriptor.text) && !operation.targetEndDescriptor;
|
|
21666
22275
|
if (dynamicOccurrence || compositeTextTarget) binding.dynamic = true;
|
|
21667
|
-
if (validation.valid && operation.operationKind !== "comment_reply" && !operation.targetDescriptor?.captureRef && !binding.dynamic) {
|
|
22276
|
+
if (validation.valid && operation.operationKind !== "comment_reply" && operation.operationKind !== "comment_resolve" && !operation.targetDescriptor?.captureRef && !binding.dynamic) {
|
|
21668
22277
|
try {
|
|
21669
22278
|
const start = resolveDescriptor(
|
|
21670
22279
|
xmlDoc,
|
|
@@ -22165,12 +22774,12 @@ function resolveTargetFromCapture(xmlDoc, session, targetDescriptor, _opType = "
|
|
|
22165
22774
|
}
|
|
22166
22775
|
|
|
22167
22776
|
// services/document-operation-mutations.js
|
|
22168
|
-
var
|
|
22777
|
+
var NS_W9 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
22169
22778
|
var NS_W142 = "http://schemas.microsoft.com/office/word/2010/wordml";
|
|
22170
22779
|
function getCommentIdsInElement(element) {
|
|
22171
22780
|
const ids = /* @__PURE__ */ new Set();
|
|
22172
22781
|
for (const localName2 of ["commentRangeStart", "commentRangeEnd", "commentReference"]) {
|
|
22173
|
-
for (const node of Array.from(element?.getElementsByTagNameNS?.(
|
|
22782
|
+
for (const node of Array.from(element?.getElementsByTagNameNS?.(NS_W9, localName2) || [])) {
|
|
22174
22783
|
const id = node.getAttribute("w:id") || node.getAttribute("id");
|
|
22175
22784
|
if (id !== "") ids.add(id);
|
|
22176
22785
|
}
|
|
@@ -22384,7 +22993,7 @@ function normalizeBodySectionOrder(xmlDoc) {
|
|
|
22384
22993
|
normalizeBodySectionOrderStandalone(xmlDoc);
|
|
22385
22994
|
}
|
|
22386
22995
|
function removeProofErrNodes(paragraph) {
|
|
22387
|
-
for (const node of Array.from(paragraph?.getElementsByTagNameNS?.(
|
|
22996
|
+
for (const node of Array.from(paragraph?.getElementsByTagNameNS?.(NS_W9, "proofErr") || [])) {
|
|
22388
22997
|
node.parentNode?.removeChild(node);
|
|
22389
22998
|
}
|
|
22390
22999
|
}
|
|
@@ -22395,12 +23004,12 @@ function preprocessRedlineTargetParagraph(targetParagraph) {
|
|
|
22395
23004
|
function getDirectWordChild4(element, localName2) {
|
|
22396
23005
|
if (!element) return null;
|
|
22397
23006
|
return Array.from(element.childNodes || []).find(
|
|
22398
|
-
(node) => node && node.nodeType === 1 && node.namespaceURI ===
|
|
23007
|
+
(node) => node && node.nodeType === 1 && node.namespaceURI === NS_W9 && node.localName === localName2
|
|
22399
23008
|
) || null;
|
|
22400
23009
|
}
|
|
22401
23010
|
function computeTableIndexInDocument(xmlDoc, tableElement) {
|
|
22402
23011
|
if (!xmlDoc || !tableElement) return null;
|
|
22403
|
-
const tables = Array.from(xmlDoc.getElementsByTagNameNS(
|
|
23012
|
+
const tables = Array.from(xmlDoc.getElementsByTagNameNS(NS_W9, "tbl"));
|
|
22404
23013
|
const idx = tables.indexOf(tableElement);
|
|
22405
23014
|
return idx >= 0 ? idx + 1 : null;
|
|
22406
23015
|
}
|
|
@@ -22450,7 +23059,7 @@ function buildInsertedListParagraph(xmlDoc, anchorParagraph, entry, revisionMeta
|
|
|
22450
23059
|
markParagraphMarkInserted(xmlDoc, paragraph, author);
|
|
22451
23060
|
}
|
|
22452
23061
|
const run = createWordElement(xmlDoc, "w:r");
|
|
22453
|
-
const anchorFirstRun = Array.from(anchorParagraph.getElementsByTagNameNS(
|
|
23062
|
+
const anchorFirstRun = Array.from(anchorParagraph.getElementsByTagNameNS(NS_W9, "r"))[0] || null;
|
|
22454
23063
|
const anchorRunPr = anchorFirstRun ? getDirectWordChild4(anchorFirstRun, "rPr") : null;
|
|
22455
23064
|
if (anchorRunPr) {
|
|
22456
23065
|
run.appendChild(clonePropertiesWithoutRevisionHistory(anchorRunPr));
|
|
@@ -22475,7 +23084,7 @@ function buildInsertedListParagraph(xmlDoc, anchorParagraph, entry, revisionMeta
|
|
|
22475
23084
|
}
|
|
22476
23085
|
function serializeParagraphRangeAsDocument(paragraphs, serializer) {
|
|
22477
23086
|
const paragraphXml = (paragraphs || []).map((paragraph) => serializer.serializeToString(paragraph)).join("");
|
|
22478
|
-
return `<w:document xmlns:w="${
|
|
23087
|
+
return `<w:document xmlns:w="${NS_W9}"><w:body>${paragraphXml}</w:body></w:document>`;
|
|
22479
23088
|
}
|
|
22480
23089
|
function applyExplicitRangeListInsertions({
|
|
22481
23090
|
xmlDoc,
|
|
@@ -22544,7 +23153,7 @@ function buildEmptyParagraphTemplateFromAnchor(xmlDoc, anchorParagraph) {
|
|
|
22544
23153
|
const anchorPPr = getDirectWordChild4(anchorParagraph, "pPr");
|
|
22545
23154
|
if (anchorPPr) paragraph.appendChild(clonePropertiesWithoutRevisionHistory(anchorPPr));
|
|
22546
23155
|
const run = createWordElement(xmlDoc, "w:r");
|
|
22547
|
-
const anchorFirstRun = Array.from(anchorParagraph.getElementsByTagNameNS(
|
|
23156
|
+
const anchorFirstRun = Array.from(anchorParagraph.getElementsByTagNameNS(NS_W9, "r"))[0] || null;
|
|
22548
23157
|
const anchorRunPr = anchorFirstRun ? getDirectWordChild4(anchorFirstRun, "rPr") : null;
|
|
22549
23158
|
if (anchorRunPr) run.appendChild(clonePropertiesWithoutRevisionHistory(anchorRunPr));
|
|
22550
23159
|
const textNode = createWordElement(xmlDoc, "w:t");
|
|
@@ -22567,7 +23176,7 @@ function wrapParagraphContentInInsertion(xmlDoc, paragraph, revisionMetadata, au
|
|
|
22567
23176
|
ins.setAttribute("w:author", metadata.author);
|
|
22568
23177
|
ins.setAttribute("w:date", metadata.date);
|
|
22569
23178
|
for (const child of Array.from(paragraph.childNodes || [])) {
|
|
22570
|
-
if (child?.nodeType === 1 && child.namespaceURI ===
|
|
23179
|
+
if (child?.nodeType === 1 && child.namespaceURI === NS_W9 && child.localName === "pPr") continue;
|
|
22571
23180
|
ins.appendChild(child.cloneNode(true));
|
|
22572
23181
|
}
|
|
22573
23182
|
wrappedParagraph.appendChild(ins);
|
|
@@ -22591,7 +23200,7 @@ async function buildInsertedPlainParagraph(xmlDoc, anchorParagraph, text2, revis
|
|
|
22591
23200
|
if (typeof markdownResult?.oxml === "string") {
|
|
22592
23201
|
const extracted = extractReplacementNodes(markdownResult.oxml);
|
|
22593
23202
|
sourceParagraph = (extracted.replacementNodes || []).find(
|
|
22594
|
-
(node) => node && node.nodeType === 1 && node.namespaceURI ===
|
|
23203
|
+
(node) => node && node.nodeType === 1 && node.namespaceURI === NS_W9 && node.localName === "p"
|
|
22595
23204
|
) || null;
|
|
22596
23205
|
}
|
|
22597
23206
|
if (!sourceParagraph) {
|
|
@@ -22623,7 +23232,7 @@ function collectNumberingIdsFromNodes(nodes) {
|
|
|
22623
23232
|
}
|
|
22624
23233
|
function directDeletionCarrierText(carrier) {
|
|
22625
23234
|
return Array.from(carrier?.childNodes || []).reduce((text2, child) => {
|
|
22626
|
-
return text2 + (child?.nodeType === 1 && child.namespaceURI ===
|
|
23235
|
+
return text2 + (child?.nodeType === 1 && child.namespaceURI === NS_W9 && child.localName === "r" ? getRunContentPieces(child).map((piece) => piece.text).join("") : "");
|
|
22627
23236
|
}, "");
|
|
22628
23237
|
}
|
|
22629
23238
|
function findOccurrenceOffset(text2, needle, occurrence) {
|
|
@@ -22665,9 +23274,9 @@ function hasUnsupportedDeletionSplitMarkup(paragraph, carrier) {
|
|
|
22665
23274
|
}
|
|
22666
23275
|
for (const child of Array.from(carrier?.childNodes || [])) {
|
|
22667
23276
|
if (child.nodeType !== 1) continue;
|
|
22668
|
-
if (!(child.namespaceURI ===
|
|
23277
|
+
if (!(child.namespaceURI === NS_W9 && child.localName === "r")) return true;
|
|
22669
23278
|
for (const runChild of Array.from(child.childNodes || [])) {
|
|
22670
|
-
if (runChild.nodeType !== 1 || runChild.namespaceURI !==
|
|
23279
|
+
if (runChild.nodeType !== 1 || runChild.namespaceURI !== NS_W9) continue;
|
|
22671
23280
|
if (!["rPr", "delText", "t", "tab", "br", "cr", "noBreakHyphen", "softHyphen"].includes(runChild.localName)) return true;
|
|
22672
23281
|
}
|
|
22673
23282
|
}
|
|
@@ -22676,7 +23285,7 @@ function hasUnsupportedDeletionSplitMarkup(paragraph, carrier) {
|
|
|
22676
23285
|
function insertedRunPropertiesAtDeletionOffset(carrier, localOffset) {
|
|
22677
23286
|
let offset = 0;
|
|
22678
23287
|
for (const child of Array.from(carrier?.childNodes || [])) {
|
|
22679
|
-
if (!(child?.nodeType === 1 && child.namespaceURI ===
|
|
23288
|
+
if (!(child?.nodeType === 1 && child.namespaceURI === NS_W9 && child.localName === "r")) continue;
|
|
22680
23289
|
const length = getRunTextLength(getRunContentPieces(child));
|
|
22681
23290
|
if (localOffset <= offset + length) {
|
|
22682
23291
|
return clonePropertiesWithoutRevisionHistory(getDirectWordChild4(child, "rPr"));
|
|
@@ -22706,7 +23315,7 @@ function normalizedAuthor2(author) {
|
|
|
22706
23315
|
function directWordParagraphSibling(paragraph, direction = "nextSibling") {
|
|
22707
23316
|
let cursor = paragraph?.[direction] || null;
|
|
22708
23317
|
while (cursor) {
|
|
22709
|
-
if (cursor.nodeType === 1 && cursor.namespaceURI ===
|
|
23318
|
+
if (cursor.nodeType === 1 && cursor.namespaceURI === NS_W9 && cursor.localName === "p") return cursor;
|
|
22710
23319
|
cursor = cursor[direction] || null;
|
|
22711
23320
|
}
|
|
22712
23321
|
return null;
|
|
@@ -22716,7 +23325,7 @@ function createSanitizedRestorationPPr(xmlDoc, sourceParagraph) {
|
|
|
22716
23325
|
const pPr = createWordElement(xmlDoc, "w:pPr");
|
|
22717
23326
|
if (!sourcePPr) return pPr;
|
|
22718
23327
|
for (const child of Array.from(sourcePPr.childNodes || [])) {
|
|
22719
|
-
if (child.nodeType !== 1 || child.namespaceURI !==
|
|
23328
|
+
if (child.nodeType !== 1 || child.namespaceURI !== NS_W9) continue;
|
|
22720
23329
|
if (!RESTORATION_PPR_ALLOWLIST.has(child.localName)) continue;
|
|
22721
23330
|
pPr.appendChild(xmlDoc.importNode(child, true));
|
|
22722
23331
|
}
|
|
@@ -22725,14 +23334,14 @@ function createSanitizedRestorationPPr(xmlDoc, sourceParagraph) {
|
|
|
22725
23334
|
function collectRestorationAnchorWarnings(sourceParagraph) {
|
|
22726
23335
|
const warnings = [];
|
|
22727
23336
|
const bookmarkNames = /* @__PURE__ */ new Set();
|
|
22728
|
-
for (const node of Array.from(sourceParagraph?.getElementsByTagNameNS?.(
|
|
23337
|
+
for (const node of Array.from(sourceParagraph?.getElementsByTagNameNS?.(NS_W9, "bookmarkStart") || [])) {
|
|
22729
23338
|
const name = wordAttribute4(node, "name") || "(unnamed)";
|
|
22730
23339
|
bookmarkNames.add(name);
|
|
22731
23340
|
}
|
|
22732
23341
|
for (const name of bookmarkNames) warnings.push(`RESTORATION_DROPPED_BOOKMARK:${name}`);
|
|
22733
23342
|
const commentIds = /* @__PURE__ */ new Set();
|
|
22734
23343
|
for (const localName2 of ["commentRangeStart", "commentRangeEnd", "commentReference"]) {
|
|
22735
|
-
for (const node of Array.from(sourceParagraph?.getElementsByTagNameNS?.(
|
|
23344
|
+
for (const node of Array.from(sourceParagraph?.getElementsByTagNameNS?.(NS_W9, localName2) || [])) {
|
|
22736
23345
|
const id = wordAttribute4(node, "id");
|
|
22737
23346
|
if (id !== "") commentIds.add(id);
|
|
22738
23347
|
}
|
|
@@ -22749,24 +23358,24 @@ function removeRestorationAnchors(paragraph) {
|
|
|
22749
23358
|
"commentReference"
|
|
22750
23359
|
];
|
|
22751
23360
|
for (const localName2 of anchorNames) {
|
|
22752
|
-
for (const node of Array.from(paragraph?.getElementsByTagNameNS?.(
|
|
23361
|
+
for (const node of Array.from(paragraph?.getElementsByTagNameNS?.(NS_W9, localName2) || [])) {
|
|
22753
23362
|
node.parentNode?.removeChild(node);
|
|
22754
23363
|
}
|
|
22755
23364
|
}
|
|
22756
|
-
for (const run of Array.from(paragraph?.getElementsByTagNameNS?.(
|
|
22757
|
-
const meaningful = Array.from(run.childNodes || []).some((child) => child.nodeType === 1 && child.namespaceURI ===
|
|
23365
|
+
for (const run of Array.from(paragraph?.getElementsByTagNameNS?.(NS_W9, "r") || [])) {
|
|
23366
|
+
const meaningful = Array.from(run.childNodes || []).some((child) => child.nodeType === 1 && child.namespaceURI === NS_W9 && child.localName !== "rPr");
|
|
22758
23367
|
if (!meaningful) run.parentNode?.removeChild(run);
|
|
22759
23368
|
}
|
|
22760
23369
|
}
|
|
22761
23370
|
function buildRejectedRestorationTemplate(xmlDoc, sourceParagraph, serializer) {
|
|
22762
23371
|
const rejected = rejectTrackedChangesInOoxml(serializer.serializeToString(sourceParagraph), { allAuthors: true });
|
|
22763
23372
|
const parsed = parseOoxmlSafe(rejected.oxml, "application/xml");
|
|
22764
|
-
const rejectedParagraph = parsed.doc?.getElementsByTagNameNS?.(
|
|
23373
|
+
const rejectedParagraph = parsed.doc?.getElementsByTagNameNS?.(NS_W9, "p")?.[0] || null;
|
|
22765
23374
|
if (!rejectedParagraph) return null;
|
|
22766
23375
|
const paragraph = createWordElement(xmlDoc, "w:p");
|
|
22767
23376
|
paragraph.appendChild(createSanitizedRestorationPPr(xmlDoc, sourceParagraph));
|
|
22768
23377
|
for (const child of Array.from(rejectedParagraph.childNodes || [])) {
|
|
22769
|
-
if (child.nodeType === 1 && child.namespaceURI ===
|
|
23378
|
+
if (child.nodeType === 1 && child.namespaceURI === NS_W9 && child.localName === "pPr") continue;
|
|
22770
23379
|
paragraph.appendChild(xmlDoc.importNode(child, true));
|
|
22771
23380
|
}
|
|
22772
23381
|
removeRestorationAnchors(paragraph);
|
|
@@ -22787,7 +23396,7 @@ async function editRestorationTemplate(xmlDoc, template, modifiedText, author, s
|
|
|
22787
23396
|
);
|
|
22788
23397
|
if (result?.status === "error" || typeof result?.oxml !== "string") return null;
|
|
22789
23398
|
const extracted = extractReplacementNodes(result.oxml);
|
|
22790
|
-
const paragraph = (extracted.replacementNodes || []).find((node) => node?.nodeType === 1 && node.namespaceURI ===
|
|
23399
|
+
const paragraph = (extracted.replacementNodes || []).find((node) => node?.nodeType === 1 && node.namespaceURI === NS_W9 && node.localName === "p");
|
|
22791
23400
|
return paragraph ? xmlDoc.importNode(paragraph, true) : null;
|
|
22792
23401
|
}
|
|
22793
23402
|
function allocateFreshParagraphId(xmlDoc, operationSession) {
|
|
@@ -22958,7 +23567,7 @@ async function insertIntoRejectedDeletedText(documentXml, targetText, anchor, mo
|
|
|
22958
23567
|
}
|
|
22959
23568
|
const insertionOffset = anchorStart + anchor.offset;
|
|
22960
23569
|
const carriers = Array.from(paragraph.childNodes || []).filter(
|
|
22961
|
-
(node) => node?.nodeType === 1 && node.namespaceURI ===
|
|
23570
|
+
(node) => node?.nodeType === 1 && node.namespaceURI === NS_W9 && node.localName === "del"
|
|
22962
23571
|
);
|
|
22963
23572
|
if (carriers.some((carrier) => hasUnsupportedDeletionSplitMarkup(paragraph, carrier))) {
|
|
22964
23573
|
return {
|
|
@@ -23615,7 +24224,7 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
23615
24224
|
};
|
|
23616
24225
|
}
|
|
23617
24226
|
if (!allSame) {
|
|
23618
|
-
const hasMove = targetParagraph.getElementsByTagNameNS(
|
|
24227
|
+
const hasMove = targetParagraph.getElementsByTagNameNS(NS_W9, "moveFrom").length > 0 || targetParagraph.getElementsByTagNameNS(NS_W9, "moveTo").length > 0;
|
|
23619
24228
|
if (hasMove) {
|
|
23620
24229
|
return {
|
|
23621
24230
|
documentXml,
|
|
@@ -23630,8 +24239,8 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
23630
24239
|
}
|
|
23631
24240
|
}
|
|
23632
24241
|
} else {
|
|
23633
|
-
const hasDel = targetParagraph.getElementsByTagNameNS(
|
|
23634
|
-
const hasMove = targetParagraph.getElementsByTagNameNS(
|
|
24242
|
+
const hasDel = targetParagraph.getElementsByTagNameNS(NS_W9, "del").length > 0;
|
|
24243
|
+
const hasMove = targetParagraph.getElementsByTagNameNS(NS_W9, "moveFrom").length > 0 || targetParagraph.getElementsByTagNameNS(NS_W9, "moveTo").length > 0;
|
|
23635
24244
|
if (hasDel) {
|
|
23636
24245
|
return {
|
|
23637
24246
|
documentXml,
|
|
@@ -24150,7 +24759,7 @@ async function applyFormattingToParagraphByExactText(documentXml, targetText, te
|
|
|
24150
24759
|
const targetParagraph = resolved.paragraph;
|
|
24151
24760
|
const collectVisibleRuns = () => {
|
|
24152
24761
|
const runEntries2 = [];
|
|
24153
|
-
const allRuns = Array.from(targetParagraph.getElementsByTagNameNS(
|
|
24762
|
+
const allRuns = Array.from(targetParagraph.getElementsByTagNameNS(NS_W9, "r"));
|
|
24154
24763
|
for (const run of allRuns) {
|
|
24155
24764
|
let parent = run.parentNode;
|
|
24156
24765
|
let isDel = false;
|
|
@@ -24242,7 +24851,7 @@ async function applyFormattingToParagraphByExactText(documentXml, targetText, te
|
|
|
24242
24851
|
const targetRuns = finalRuns.filter((span) => span.start >= matchStart && span.end <= matchEnd);
|
|
24243
24852
|
let anyChanged = false;
|
|
24244
24853
|
for (const entry of targetRuns) {
|
|
24245
|
-
const rPr = entry.run.getElementsByTagNameNS(
|
|
24854
|
+
const rPr = entry.run.getElementsByTagNameNS(NS_W9, "rPr")[0] || null;
|
|
24246
24855
|
if (checkRunPropertiesChanged(rPr, properties)) {
|
|
24247
24856
|
anyChanged = true;
|
|
24248
24857
|
break;
|
|
@@ -24253,7 +24862,7 @@ async function applyFormattingToParagraphByExactText(documentXml, targetText, te
|
|
|
24253
24862
|
}
|
|
24254
24863
|
for (const entry of targetRuns) {
|
|
24255
24864
|
const run = entry.run;
|
|
24256
|
-
let rPr = run.getElementsByTagNameNS(
|
|
24865
|
+
let rPr = run.getElementsByTagNameNS(NS_W9, "rPr")[0];
|
|
24257
24866
|
if (!rPr) {
|
|
24258
24867
|
rPr = createWordElement(xmlDoc, "w:rPr");
|
|
24259
24868
|
run.insertBefore(rPr, run.firstChild);
|
|
@@ -24262,7 +24871,7 @@ async function applyFormattingToParagraphByExactText(documentXml, targetText, te
|
|
|
24262
24871
|
let insideSameAuthorIns = false;
|
|
24263
24872
|
while (insAncestor && insAncestor !== targetParagraph) {
|
|
24264
24873
|
if (insAncestor.nodeType === 1 && (insAncestor.localName === "ins" || insAncestor.nodeName === "w:ins")) {
|
|
24265
|
-
const insAuthor = insAncestor.getAttribute("w:author") || insAncestor.getAttributeNS(
|
|
24874
|
+
const insAuthor = insAncestor.getAttribute("w:author") || insAncestor.getAttributeNS(NS_W9, "author");
|
|
24266
24875
|
if (insAuthor === (author || getDefaultAuthor())) {
|
|
24267
24876
|
insideSameAuthorIns = true;
|
|
24268
24877
|
}
|
|
@@ -24310,7 +24919,7 @@ async function applyParagraphFormatToParagraphByExactText(documentXml, targetTex
|
|
|
24310
24919
|
return { documentXml, hasChanges: false, status: "error", error: resolved?.error || { code: "TARGET_NOT_FOUND", message: "Target paragraph not found." } };
|
|
24311
24920
|
}
|
|
24312
24921
|
const targetParagraph = resolved.paragraph;
|
|
24313
|
-
let pPr = targetParagraph.getElementsByTagNameNS(
|
|
24922
|
+
let pPr = targetParagraph.getElementsByTagNameNS(NS_W9, "pPr")[0];
|
|
24314
24923
|
if (!pPr) {
|
|
24315
24924
|
pPr = createWordElement(xmlDoc, "w:pPr");
|
|
24316
24925
|
targetParagraph.insertBefore(pPr, targetParagraph.firstChild);
|
|
@@ -24358,7 +24967,7 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
24358
24967
|
}
|
|
24359
24968
|
let operation = validation.operation || normalizeDocumentOperation(op);
|
|
24360
24969
|
const authorUsed = resolveDocumentOperationAuthor(operation, author, getDefaultAuthor());
|
|
24361
|
-
if (operation.operationKind !== "comment_reply" && operation.operationKind !== "rejected-insert" && operation.operationKind !== "restore" && (operation.targetDescriptor?.revisionView === "rejected" || operation.targetEndDescriptor?.revisionView === "rejected")) {
|
|
24970
|
+
if (operation.operationKind !== "comment_reply" && operation.operationKind !== "comment_resolve" && operation.operationKind !== "rejected-insert" && operation.operationKind !== "restore" && (operation.targetDescriptor?.revisionView === "rejected" || operation.targetEndDescriptor?.revisionView === "rejected")) {
|
|
24362
24971
|
return {
|
|
24363
24972
|
documentXml,
|
|
24364
24973
|
hasChanges: false,
|
|
@@ -24525,6 +25134,7 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
24525
25134
|
result = applyCommentReplyToParts({
|
|
24526
25135
|
commentsXml: existingCommentsXml,
|
|
24527
25136
|
commentsExtendedXml: existingExtendedXml,
|
|
25137
|
+
documentXml,
|
|
24528
25138
|
parentCommentId: operation.parentCommentId,
|
|
24529
25139
|
commentId,
|
|
24530
25140
|
commentContent: operation.commentContent,
|
|
@@ -24532,8 +25142,30 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
24532
25142
|
date: operation.date || (/* @__PURE__ */ new Date()).toISOString()
|
|
24533
25143
|
});
|
|
24534
25144
|
result.documentXml = documentXml;
|
|
24535
|
-
if (result.
|
|
25145
|
+
if (result.status !== "error" && result.hasChanges) {
|
|
25146
|
+
if (!session.document || !anchorReplyInDocument(session.document, result.threadCommentIds, commentId)) {
|
|
25147
|
+
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.` } };
|
|
25148
|
+
} else if (session.receiptCollector) {
|
|
25149
|
+
session.receiptCollector.recordComment(commentId);
|
|
25150
|
+
}
|
|
25151
|
+
}
|
|
24536
25152
|
}
|
|
25153
|
+
} else if (operation.operationKind === "comment_resolve") {
|
|
25154
|
+
const resolution = applyThreadResolutionToParts({
|
|
25155
|
+
commentsXml: session.commentsXml || runtimeContext?.commentsXml || options.commentsXml,
|
|
25156
|
+
commentsExtendedXml: session.commentsExtendedXml || runtimeContext?.commentsExtendedXml || options.commentsExtendedXml,
|
|
25157
|
+
commentId: operation.commentId,
|
|
25158
|
+
resolved: operation.resolved !== false
|
|
25159
|
+
});
|
|
25160
|
+
result = resolution.status === "error" ? { documentXml, hasChanges: false, status: "error", error: resolution.error } : {
|
|
25161
|
+
documentXml,
|
|
25162
|
+
hasChanges: resolution.hasChanges,
|
|
25163
|
+
...resolution.commentsXml ? { commentsXml: resolution.commentsXml, commentsXmlMode: "replace" } : {},
|
|
25164
|
+
...resolution.commentsExtendedXml ? { commentsExtendedXml: resolution.commentsExtendedXml, commentsExtendedXmlMode: "replace" } : {},
|
|
25165
|
+
resolved: resolution.resolved,
|
|
25166
|
+
threadRootId: resolution.threadRootId,
|
|
25167
|
+
commentIds: resolution.commentIds
|
|
25168
|
+
};
|
|
24537
25169
|
} else if (operation.operationKind === "highlight") {
|
|
24538
25170
|
result = await applyHighlightToParagraphByExactText(
|
|
24539
25171
|
documentXml,
|
|
@@ -24674,7 +25306,7 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
24674
25306
|
...resolutionCapture
|
|
24675
25307
|
};
|
|
24676
25308
|
}
|
|
24677
|
-
session.markMutationCommitted(
|
|
25309
|
+
session.markMutationCommitted(true);
|
|
24678
25310
|
if (operation._compiledSourceId && session.sourceTargetRegistry) {
|
|
24679
25311
|
session.sourceTargetRegistry.commitMutation(
|
|
24680
25312
|
operation._compiledSourceId,
|
|
@@ -24787,9 +25419,9 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
24787
25419
|
}
|
|
24788
25420
|
|
|
24789
25421
|
// services/batch-operation-orchestrator.js
|
|
24790
|
-
var
|
|
25422
|
+
var NS_W10 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
24791
25423
|
function operationTargetPriority(op) {
|
|
24792
|
-
return op?.type === "comment" || op?.type === "comment_reply" ? 0 : 1;
|
|
25424
|
+
return op?.type === "comment" || op?.type === "comment_reply" || op?.type === "comment_resolve" ? 0 : 1;
|
|
24793
25425
|
}
|
|
24794
25426
|
function buildOperationDependencyPlan(operations = []) {
|
|
24795
25427
|
const list = Array.isArray(operations) ? operations : [];
|
|
@@ -24864,7 +25496,7 @@ function buildOperationDependencyPlan(operations = []) {
|
|
|
24864
25496
|
inDegrees[i2]++;
|
|
24865
25497
|
}
|
|
24866
25498
|
const kind = normalizeDocumentOperation(op).operationKind;
|
|
24867
|
-
if (kind !== "comment" && kind !== "comment_reply") {
|
|
25499
|
+
if (kind !== "comment" && kind !== "comment_reply" && kind !== "comment_resolve") {
|
|
24868
25500
|
if (!mutatingCaptureConsumers.has(ref)) mutatingCaptureConsumers.set(ref, []);
|
|
24869
25501
|
mutatingCaptureConsumers.get(ref).push({
|
|
24870
25502
|
index: i2,
|
|
@@ -24932,9 +25564,9 @@ function mergeCommentsXml(existingXml, incomingXml) {
|
|
|
24932
25564
|
const serializer = createSerializer();
|
|
24933
25565
|
const existingRoot = existingDoc.documentElement;
|
|
24934
25566
|
const existingIds = new Set(
|
|
24935
|
-
Array.from(existingDoc.getElementsByTagNameNS(
|
|
25567
|
+
Array.from(existingDoc.getElementsByTagNameNS(NS_W10, "comment")).map((node) => node.getAttribute("w:id") || node.getAttribute("id")).filter(Boolean)
|
|
24936
25568
|
);
|
|
24937
|
-
for (const comment of Array.from(incomingDoc.getElementsByTagNameNS(
|
|
25569
|
+
for (const comment of Array.from(incomingDoc.getElementsByTagNameNS(NS_W10, "comment"))) {
|
|
24938
25570
|
const id = comment.getAttribute("w:id") || comment.getAttribute("id");
|
|
24939
25571
|
if (id && existingIds.has(id)) continue;
|
|
24940
25572
|
existingRoot.appendChild(existingDoc.importNode(comment, true));
|
|
@@ -25095,7 +25727,7 @@ async function applyOperationsToDocumentXml(documentXml, operations, author, run
|
|
|
25095
25727
|
context.targetRefSnapshot = session.initialTargetReferenceSnapshot;
|
|
25096
25728
|
}
|
|
25097
25729
|
session.runtimeContext = context;
|
|
25098
|
-
if (sourceOperations.some((op) => op?.type === "comment_reply")) {
|
|
25730
|
+
if (sourceOperations.some((op) => op?.type === "comment_reply" || op?.type === "comment_resolve")) {
|
|
25099
25731
|
session.commentsXml = context.commentsXml || options.commentsXml || null;
|
|
25100
25732
|
session.commentsExtendedXml = context.commentsExtendedXml || options.commentsExtendedXml || null;
|
|
25101
25733
|
session.commentsXmlMode = "replace";
|
|
@@ -25168,6 +25800,7 @@ async function applyOperationsToDocumentXml(documentXml, operations, author, run
|
|
|
25168
25800
|
...result.resolvedTarget ? { resolvedTarget: result.resolvedTarget } : {},
|
|
25169
25801
|
...result.resolvedAnchor ? { resolvedAnchor: result.resolvedAnchor } : {},
|
|
25170
25802
|
...result.change ? { change: result.change } : {},
|
|
25803
|
+
...result.operationType === "comment_resolve" && !isError ? { resolved: result.resolved, threadRootId: result.threadRootId, commentIds: result.commentIds } : {},
|
|
25171
25804
|
...Array.isArray(result.warnings) && result.warnings.length > 0 ? { warnings: result.warnings } : {},
|
|
25172
25805
|
...result.error ? { error: result.error } : {},
|
|
25173
25806
|
...result.receipt ? { receipt: result.receipt } : {}
|
|
@@ -25444,6 +26077,26 @@ function preflightOperations(documentXml, operations, author, options = {}) {
|
|
|
25444
26077
|
}
|
|
25445
26078
|
commentsRequired = commentsRequired || operation.operationKind === "comment" || operation.operationKind === "comment_reply";
|
|
25446
26079
|
numberingRequired = numberingRequired || operationNeedsNumbering(operation);
|
|
26080
|
+
if (operation.operationKind === "comment_resolve") {
|
|
26081
|
+
const commentId = String(operation.commentId);
|
|
26082
|
+
results.push(options._existingCommentDetails?.[commentId] ? {
|
|
26083
|
+
index: index + 1,
|
|
26084
|
+
type: sourceOperation.type,
|
|
26085
|
+
operationType: operation.operationKind,
|
|
26086
|
+
status: "ready",
|
|
26087
|
+
authorUsed,
|
|
26088
|
+
resolvedBy: "comment_id",
|
|
26089
|
+
commentId
|
|
26090
|
+
} : {
|
|
26091
|
+
index: index + 1,
|
|
26092
|
+
type: sourceOperation.type,
|
|
26093
|
+
operationType: operation.operationKind,
|
|
26094
|
+
status: "error",
|
|
26095
|
+
authorUsed,
|
|
26096
|
+
error: { code: "COMMENT_NOT_FOUND", message: `Comment '${commentId}' was not found.` }
|
|
26097
|
+
});
|
|
26098
|
+
continue;
|
|
26099
|
+
}
|
|
25447
26100
|
if (operation.operationKind === "comment_reply") {
|
|
25448
26101
|
const parentId = String(operation.parentCommentId);
|
|
25449
26102
|
const parent = options._existingCommentDetails?.[parentId];
|
|
@@ -26595,6 +27248,15 @@ var text = (entries, path) => {
|
|
|
26595
27248
|
if (typeof entry === "string") return entry;
|
|
26596
27249
|
return textDecoder2.decode(entry);
|
|
26597
27250
|
};
|
|
27251
|
+
function reconcileCommentSiblings(working) {
|
|
27252
|
+
const updated = reconcileCommentSiblingParts({
|
|
27253
|
+
commentsXml: text(working, "word/comments.xml"),
|
|
27254
|
+
commentsIdsXml: text(working, "word/commentsIds.xml"),
|
|
27255
|
+
commentsExtensibleXml: text(working, "word/commentsExtensible.xml")
|
|
27256
|
+
});
|
|
27257
|
+
if (updated.commentsIdsXml) working.set("word/commentsIds.xml", textEncoder4.encode(updated.commentsIdsXml));
|
|
27258
|
+
if (updated.commentsExtensibleXml) working.set("word/commentsExtensible.xml", textEncoder4.encode(updated.commentsExtensibleXml));
|
|
27259
|
+
}
|
|
26598
27260
|
var cloneEntries = (entries) => new Map([...entries].map(([name, data]) => [name, new Uint8Array(data)]));
|
|
26599
27261
|
function toBufferCompatible(uint8Arr) {
|
|
26600
27262
|
if (typeof Buffer !== "undefined" && typeof Buffer.from === "function") {
|
|
@@ -26662,6 +27324,7 @@ function computePackageRevisionToken(input) {
|
|
|
26662
27324
|
entries
|
|
26663
27325
|
});
|
|
26664
27326
|
}
|
|
27327
|
+
var COMMENT_OPERATION_TYPES = /* @__PURE__ */ new Set(["comment", "comment_reply", "comment_resolve"]);
|
|
26665
27328
|
function nextCommentId(entries) {
|
|
26666
27329
|
const ids = `${text(entries, "word/document.xml") || ""} ${text(entries, "word/comments.xml") || ""}`.match(/(?:w:)?id=["'](\d+)["']/g) || [];
|
|
26667
27330
|
let next = ids.reduce((max2, token) => Math.max(max2, Number(token.match(/\d+/)?.[0] || 0)), 0) + 1;
|
|
@@ -26680,7 +27343,7 @@ function existingCommentDetails(entries) {
|
|
|
26680
27343
|
id,
|
|
26681
27344
|
author: comment.getAttribute("w:author") || comment.getAttribute("author") || "",
|
|
26682
27345
|
text: String(comment.textContent || "").trim(),
|
|
26683
|
-
paraId:
|
|
27346
|
+
paraId: commentParaId(comment) || null
|
|
26684
27347
|
};
|
|
26685
27348
|
}
|
|
26686
27349
|
return details;
|
|
@@ -26711,13 +27374,34 @@ var DocxDocument = class {
|
|
|
26711
27374
|
}
|
|
26712
27375
|
inspect(options = {}) {
|
|
26713
27376
|
const digestFn = options.digestFn || ((bytes) => sha256(bytes));
|
|
26714
|
-
|
|
27377
|
+
const inspection = inspectDocumentParts({
|
|
26715
27378
|
documentXml: text(this.entries, "word/document.xml"),
|
|
26716
27379
|
commentsXml: text(this.entries, "word/comments.xml"),
|
|
26717
27380
|
commentsExtendedXml: text(this.entries, "word/commentsExtended.xml"),
|
|
26718
27381
|
numberingXml: text(this.entries, "word/numbering.xml"),
|
|
26719
27382
|
stylesXml: text(this.entries, "word/styles.xml")
|
|
26720
27383
|
}, { ...options, digestFn });
|
|
27384
|
+
if (inspection.status === "error") return inspection;
|
|
27385
|
+
return { ...inspection, headersFooters: this.inspectHeadersFooters() };
|
|
27386
|
+
}
|
|
27387
|
+
/**
|
|
27388
|
+
* Header and footer parts the document uses, with their paragraph text, so operations can target them with
|
|
27389
|
+
* `part`. `hasFields` marks parts containing PAGE/NUMPAGES-style fields (edit around them, not inside).
|
|
27390
|
+
*/
|
|
27391
|
+
inspectHeadersFooters() {
|
|
27392
|
+
return discoverHeaderFooterParts({
|
|
27393
|
+
documentXml: text(this.entries, "word/document.xml"),
|
|
27394
|
+
relsXml: text(this.entries, "word/_rels/document.xml.rels"),
|
|
27395
|
+
settingsXml: text(this.entries, "word/settings.xml")
|
|
27396
|
+
}).map((part) => {
|
|
27397
|
+
const xml = text(this.entries, part.path) || "";
|
|
27398
|
+
const partInspection = xml ? inspectDocumentParts({ documentXml: xml }) : { paragraphs: [] };
|
|
27399
|
+
return {
|
|
27400
|
+
...part,
|
|
27401
|
+
hasFields: /<w:fldChar\b|<w:fldSimple\b/.test(xml),
|
|
27402
|
+
paragraphs: (partInspection.paragraphs || []).map((paragraph) => ({ index: paragraph.index, text: paragraph.text }))
|
|
27403
|
+
};
|
|
27404
|
+
});
|
|
26721
27405
|
}
|
|
26722
27406
|
getRevisionToken() {
|
|
26723
27407
|
return computePackageRevisionToken(this.entries);
|
|
@@ -26726,12 +27410,51 @@ var DocxDocument = class {
|
|
|
26726
27410
|
return this.getRevisionToken();
|
|
26727
27411
|
}
|
|
26728
27412
|
preflight(operations, author = getDefaultAuthor(), options = {}) {
|
|
26729
|
-
|
|
27413
|
+
const list = Array.isArray(operations) ? operations : [];
|
|
27414
|
+
const runBody = (ops) => preflightOperations(
|
|
26730
27415
|
text(this.entries, "word/document.xml"),
|
|
26731
|
-
|
|
27416
|
+
ops,
|
|
26732
27417
|
author || getDefaultAuthor(),
|
|
26733
27418
|
{ ...options, _existingCommentDetails: existingCommentDetails(this.entries) }
|
|
26734
27419
|
);
|
|
27420
|
+
if (!list.some((operation) => operation && typeof operation === "object" && operation.part != null)) return runBody(operations);
|
|
27421
|
+
const parts = this.inspectHeadersFooters();
|
|
27422
|
+
const bodyOps = [];
|
|
27423
|
+
const results = [];
|
|
27424
|
+
const conflicts = [];
|
|
27425
|
+
const authorsUsed = /* @__PURE__ */ new Set();
|
|
27426
|
+
let valid = true;
|
|
27427
|
+
list.forEach((operation, i2) => {
|
|
27428
|
+
if (!operation || typeof operation !== "object" || operation.part == null) {
|
|
27429
|
+
bodyOps.push({ operation, origIndex: i2 });
|
|
27430
|
+
return;
|
|
27431
|
+
}
|
|
27432
|
+
const resolved = resolvePartSelector(parts, operation.part);
|
|
27433
|
+
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;
|
|
27434
|
+
if (refusal) {
|
|
27435
|
+
valid = false;
|
|
27436
|
+
results.push({ index: i2 + 1, type: operation.type || "redline", status: "error", error: normalizeErrorWithRecovery({ ...refusal, operationIndex: i2 + 1 }) });
|
|
27437
|
+
return;
|
|
27438
|
+
}
|
|
27439
|
+
const { part: _selector, ...rest } = operation;
|
|
27440
|
+
const partResult = preflightOperations(text(this.entries, resolved.part.path), [rest], author || getDefaultAuthor(), { ...options, _existingCommentDetails: {} });
|
|
27441
|
+
valid = valid && partResult.valid;
|
|
27442
|
+
for (const result of partResult.results || []) results.push({ ...result, index: i2 + 1, part: resolved.part.path });
|
|
27443
|
+
conflicts.push(...partResult.conflicts || []);
|
|
27444
|
+
for (const name of partResult.authorsUsed || []) authorsUsed.add(name);
|
|
27445
|
+
});
|
|
27446
|
+
let requiredArtifacts = { comments: false, numbering: false };
|
|
27447
|
+
if (bodyOps.length) {
|
|
27448
|
+
const body = runBody(bodyOps.map((item) => item.operation));
|
|
27449
|
+
valid = valid && body.valid;
|
|
27450
|
+
for (const result of body.results || []) results.push({ ...result, index: bodyOps[result.index - 1].origIndex + 1 });
|
|
27451
|
+
conflicts.push(...body.conflicts || []);
|
|
27452
|
+
for (const name of body.authorsUsed || []) authorsUsed.add(name);
|
|
27453
|
+
requiredArtifacts = body.requiredArtifacts || requiredArtifacts;
|
|
27454
|
+
}
|
|
27455
|
+
results.sort((a, b) => a.index - b.index);
|
|
27456
|
+
const ok = valid && conflicts.length === 0;
|
|
27457
|
+
return { valid: ok, status: ok ? "ok" : "error", results, conflicts, authorsUsed: [...authorsUsed], requiredArtifacts };
|
|
26735
27458
|
}
|
|
26736
27459
|
toUint8Array() {
|
|
26737
27460
|
return zipDocx(this.entries);
|
|
@@ -26739,7 +27462,240 @@ var DocxDocument = class {
|
|
|
26739
27462
|
toBuffer() {
|
|
26740
27463
|
return toBufferCompatible(this.toUint8Array());
|
|
26741
27464
|
}
|
|
27465
|
+
/**
|
|
27466
|
+
* Applies operations to the document. Operations without `part` target the body (word/document.xml).
|
|
27467
|
+
* An operation with `part` targets one header or footer part:
|
|
27468
|
+
* `{ part: { kind: 'footer', type: 'default', section: 0 } | 'word/footer2.xml', target: { exactText }, ... }`.
|
|
27469
|
+
* Discover parts with `inspect().headersFooters`.
|
|
27470
|
+
*/
|
|
26742
27471
|
async applyOperations(operations, options = {}) {
|
|
27472
|
+
const list = Array.isArray(operations) ? operations : [];
|
|
27473
|
+
if (!list.some((operation) => operation && typeof operation === "object" && operation.part != null)) {
|
|
27474
|
+
return this.applyBodyOperations(operations, options);
|
|
27475
|
+
}
|
|
27476
|
+
return this.applyOperationsWithParts(list, options);
|
|
27477
|
+
}
|
|
27478
|
+
async applyOperationsWithParts(operations, options = {}) {
|
|
27479
|
+
const author = options.author || getDefaultAuthor();
|
|
27480
|
+
const atomic = options.atomic === true;
|
|
27481
|
+
const snapshotEntries = this.entries;
|
|
27482
|
+
const snapshotBytes = this.originalBytes;
|
|
27483
|
+
const total = operations.length;
|
|
27484
|
+
const restore = () => {
|
|
27485
|
+
this.entries = snapshotEntries;
|
|
27486
|
+
this.originalBytes = snapshotBytes;
|
|
27487
|
+
};
|
|
27488
|
+
const original = () => ({
|
|
27489
|
+
uint8Array: snapshotBytes,
|
|
27490
|
+
toUint8Array: () => new Uint8Array(snapshotBytes),
|
|
27491
|
+
buffer: toBufferCompatible(snapshotBytes),
|
|
27492
|
+
toBuffer: () => toBufferCompatible(snapshotBytes)
|
|
27493
|
+
});
|
|
27494
|
+
const refuse = (error2, results2 = [], receipts2 = [], extra = {}) => ({
|
|
27495
|
+
status: "error",
|
|
27496
|
+
hasChanges: false,
|
|
27497
|
+
written: false,
|
|
27498
|
+
rolledBack: true,
|
|
27499
|
+
results: results2,
|
|
27500
|
+
receipts: receipts2,
|
|
27501
|
+
executionOrder: [],
|
|
27502
|
+
authorsUsed: [],
|
|
27503
|
+
artifactsChanged: [],
|
|
27504
|
+
error: normalizeErrorWithRecovery(error2),
|
|
27505
|
+
retryPlan: createRetryPlan({ atomic: true, rolledBack: true, results: results2, receipts: receipts2, operationCount: total }),
|
|
27506
|
+
validation: { originalIssues: [], generatedIssues: [] },
|
|
27507
|
+
...extra,
|
|
27508
|
+
...original()
|
|
27509
|
+
});
|
|
27510
|
+
if (options.expectedRevision) {
|
|
27511
|
+
const precondition = await this.applyBodyOperations([], { expectedRevision: options.expectedRevision });
|
|
27512
|
+
if (precondition.status === "error") return precondition;
|
|
27513
|
+
}
|
|
27514
|
+
const parts = discoverHeaderFooterParts({
|
|
27515
|
+
documentXml: text(this.entries, "word/document.xml"),
|
|
27516
|
+
relsXml: text(this.entries, "word/_rels/document.xml.rels"),
|
|
27517
|
+
settingsXml: text(this.entries, "word/settings.xml")
|
|
27518
|
+
});
|
|
27519
|
+
const bodyOps = [];
|
|
27520
|
+
const partOps = [];
|
|
27521
|
+
for (let i2 = 0; i2 < operations.length; i2++) {
|
|
27522
|
+
const operation = operations[i2];
|
|
27523
|
+
if (!operation || typeof operation !== "object" || operation.part == null) {
|
|
27524
|
+
bodyOps.push({ operation, origIndex: i2 });
|
|
27525
|
+
continue;
|
|
27526
|
+
}
|
|
27527
|
+
if (COMMENT_OPERATION_TYPES.has(operation.type)) {
|
|
27528
|
+
return refuse({ code: "COMMENT_IN_HEADER_FOOTER", message: `Operation ${i2 + 1}: Word does not support comments in headers or footers.`, operationIndex: i2 + 1 });
|
|
27529
|
+
}
|
|
27530
|
+
const resolved = resolvePartSelector(parts, operation.part);
|
|
27531
|
+
if (resolved.error) return refuse({ ...resolved.error, message: `Operation ${i2 + 1}: ${resolved.error.message}`, operationIndex: i2 + 1 });
|
|
27532
|
+
const { part: _selector, ...rest } = operation;
|
|
27533
|
+
partOps.push({ operation: rest, origIndex: i2, part: resolved.part });
|
|
27534
|
+
}
|
|
27535
|
+
const results = [];
|
|
27536
|
+
const receipts = [];
|
|
27537
|
+
const executionOrder = [];
|
|
27538
|
+
const authorsUsed = /* @__PURE__ */ new Set();
|
|
27539
|
+
const originalIssues = [];
|
|
27540
|
+
let anyChange = false;
|
|
27541
|
+
let anyFailure = false;
|
|
27542
|
+
let bodyResult = null;
|
|
27543
|
+
if (bodyOps.length) {
|
|
27544
|
+
bodyResult = await this.applyBodyOperations(bodyOps.map((item) => item.operation), { ...options, expectedRevision: void 0 });
|
|
27545
|
+
const toOriginal = (index) => bodyOps[index - 1]?.origIndex + 1;
|
|
27546
|
+
for (const result of bodyResult.results || []) {
|
|
27547
|
+
results.push({
|
|
27548
|
+
...result,
|
|
27549
|
+
index: toOriginal(result.index),
|
|
27550
|
+
...result.receipt ? { receipt: { ...result.receipt, operationIndex: toOriginal(result.receipt.operationIndex) } } : {}
|
|
27551
|
+
});
|
|
27552
|
+
}
|
|
27553
|
+
for (const receipt of bodyResult.receipts || []) receipts.push({ ...receipt, operationIndex: toOriginal(receipt.operationIndex) });
|
|
27554
|
+
for (const index of bodyResult.executionOrder || []) executionOrder.push(toOriginal(index));
|
|
27555
|
+
for (const name of bodyResult.authorsUsed || []) authorsUsed.add(name);
|
|
27556
|
+
originalIssues.push(...bodyResult.validation?.originalIssues || []);
|
|
27557
|
+
if (bodyResult.status === "error" || bodyResult.rolledBack) {
|
|
27558
|
+
anyFailure = true;
|
|
27559
|
+
if (atomic) {
|
|
27560
|
+
restore();
|
|
27561
|
+
return { ...bodyResult, results, receipts, executionOrder, written: false, artifactsChanged: [], rolledBack: true, ...original() };
|
|
27562
|
+
}
|
|
27563
|
+
}
|
|
27564
|
+
anyChange = bodyResult.hasChanges === true;
|
|
27565
|
+
}
|
|
27566
|
+
const working = cloneEntries(this.entries);
|
|
27567
|
+
const zip = new MemoryZip(working);
|
|
27568
|
+
const changedParts = /* @__PURE__ */ new Set();
|
|
27569
|
+
const { expectedRevision: _ignored, ...runnerOptions } = options;
|
|
27570
|
+
const failPart = (item, error2) => {
|
|
27571
|
+
anyFailure = true;
|
|
27572
|
+
const failure = normalizeErrorWithRecovery({ ...error2, operationIndex: item.origIndex + 1 });
|
|
27573
|
+
results.push({ index: item.origIndex + 1, type: item.operation.type || "redline", status: "error", authorUsed: author, part: item.part.path, error: failure });
|
|
27574
|
+
};
|
|
27575
|
+
for (const item of partOps) {
|
|
27576
|
+
const path = item.part.path;
|
|
27577
|
+
const xml = text(working, path);
|
|
27578
|
+
if (!xml) {
|
|
27579
|
+
failPart(item, { code: "PART_NOT_FOUND", message: `Part '${path}' is referenced but missing from the package.` });
|
|
27580
|
+
if (atomic) break;
|
|
27581
|
+
continue;
|
|
27582
|
+
}
|
|
27583
|
+
const numberingText = text(working, "word/numbering.xml");
|
|
27584
|
+
const outcome = await applyOperationsToDocumentXml(xml, [item.operation], author, { numberingIdState: createDynamicNumberingIdState(numberingText || void 0) }, {
|
|
27585
|
+
...runnerOptions,
|
|
27586
|
+
atomic: true,
|
|
27587
|
+
strictTargets: options.strictTargets !== false,
|
|
27588
|
+
_existingCommentDetails: {}
|
|
27589
|
+
});
|
|
27590
|
+
const single = outcome.results?.[0];
|
|
27591
|
+
if (outcome.rolledBack || outcome.status === "error") {
|
|
27592
|
+
failPart(item, single?.error || outcome.error || { code: "OPERATION_FAILED", message: "Operation failed." });
|
|
27593
|
+
if (atomic) break;
|
|
27594
|
+
continue;
|
|
27595
|
+
}
|
|
27596
|
+
if (!outcome.hasChanges) {
|
|
27597
|
+
results.push({ ...single, index: item.origIndex + 1, part: path });
|
|
27598
|
+
continue;
|
|
27599
|
+
}
|
|
27600
|
+
if (countRevisionsInsideFields(outcome.documentXml) > countRevisionsInsideFields(xml)) {
|
|
27601
|
+
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.` });
|
|
27602
|
+
if (atomic) break;
|
|
27603
|
+
continue;
|
|
27604
|
+
}
|
|
27605
|
+
const taken = /* @__PURE__ */ new Set();
|
|
27606
|
+
for (const [name, data] of working) {
|
|
27607
|
+
if (name === path || !(name === "word/document.xml" || isHeaderFooterPath(name))) continue;
|
|
27608
|
+
for (const id of collectRevisionIds(textDecoder2.decode(data))) taken.add(id);
|
|
27609
|
+
}
|
|
27610
|
+
const renumbered = renumberCollidingRevisionIds(outcome.documentXml, taken);
|
|
27611
|
+
working.set(path, textEncoder4.encode(renumbered.xml));
|
|
27612
|
+
const describeReceipt = (receipt) => receipt?.revisionItems ? { ...receipt, revisionItems: receipt.revisionItems.map((revision) => ({ ...revision, id: renumbered.idMap.get(String(revision.id)) ?? revision.id, partName: path })) } : receipt;
|
|
27613
|
+
if (outcome.numberingXmlParts?.length) {
|
|
27614
|
+
await ensureNumberingArtifactsInZip(zip, outcome.numberingXmlParts, { mergeNumberingXml: mergeNumberingXmlBySchemaOrder });
|
|
27615
|
+
}
|
|
27616
|
+
changedParts.add(path);
|
|
27617
|
+
anyChange = true;
|
|
27618
|
+
results.push({
|
|
27619
|
+
...single,
|
|
27620
|
+
index: item.origIndex + 1,
|
|
27621
|
+
part: path,
|
|
27622
|
+
partSections: item.part.appliesToSections,
|
|
27623
|
+
...single?.receipt ? { receipt: { ...describeReceipt(single.receipt), operationIndex: item.origIndex + 1 } } : {}
|
|
27624
|
+
});
|
|
27625
|
+
if (outcome.receipts?.[0]) receipts.push({ ...describeReceipt(outcome.receipts[0]), operationIndex: item.origIndex + 1 });
|
|
27626
|
+
executionOrder.push(item.origIndex + 1);
|
|
27627
|
+
for (const name of outcome.authorsUsed || []) authorsUsed.add(name);
|
|
27628
|
+
}
|
|
27629
|
+
const generatedIssues = [];
|
|
27630
|
+
if (!(atomic && anyFailure)) {
|
|
27631
|
+
for (const path of changedParts) {
|
|
27632
|
+
const before = validateRedlineOoxml(text(this.entries, path)).issues.map((issue) => ({ source: path, ...issue }));
|
|
27633
|
+
const after = validateRedlineOoxml(text(working, path)).issues.map((issue) => ({ source: path, ...issue }));
|
|
27634
|
+
generatedIssues.push(...validationErrors(subtractValidationIssueMultiset(after, before)));
|
|
27635
|
+
}
|
|
27636
|
+
if (options.validate !== false && changedParts.size) {
|
|
27637
|
+
try {
|
|
27638
|
+
await validateDocxPackage(zip);
|
|
27639
|
+
} catch (error2) {
|
|
27640
|
+
generatedIssues.push({ source: "package", code: "PACKAGE_VALIDATION", severity: "error", message: error2.message });
|
|
27641
|
+
}
|
|
27642
|
+
}
|
|
27643
|
+
}
|
|
27644
|
+
if (atomic && anyFailure || generatedIssues.length) {
|
|
27645
|
+
restore();
|
|
27646
|
+
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." };
|
|
27647
|
+
const rolledBackResults = results.map((result) => result.receipt ? { ...result, receipt: { ...result.receipt, committed: false, finalDisposition: "rolled_back" } } : result).sort((a, b) => a.index - b.index);
|
|
27648
|
+
const rolledBackReceipts = receipts.map((receipt) => ({ ...receipt, committed: false, finalDisposition: "rolled_back" }));
|
|
27649
|
+
return refuse(error2, rolledBackResults, rolledBackReceipts, { validation: { originalIssues, generatedIssues }, issues: generatedIssues });
|
|
27650
|
+
}
|
|
27651
|
+
results.sort((a, b) => a.index - b.index);
|
|
27652
|
+
receipts.sort((a, b) => a.operationIndex - b.operationIndex);
|
|
27653
|
+
const status = !anyFailure ? "ok" : anyChange ? "partial" : "error";
|
|
27654
|
+
const failureError = anyFailure ? { error: normalizeErrorWithRecovery({ code: "BATCH_OPERATION_FAILED", message: "One or more operations failed; the others were applied." }) } : {};
|
|
27655
|
+
if (!changedParts.size) {
|
|
27656
|
+
const buf = toBufferCompatible(this.originalBytes);
|
|
27657
|
+
return {
|
|
27658
|
+
...bodyResult || {},
|
|
27659
|
+
results,
|
|
27660
|
+
receipts,
|
|
27661
|
+
executionOrder,
|
|
27662
|
+
authorsUsed: [...authorsUsed],
|
|
27663
|
+
status,
|
|
27664
|
+
hasChanges: anyChange,
|
|
27665
|
+
written: bodyResult?.written === true,
|
|
27666
|
+
artifactsChanged: bodyResult?.artifactsChanged || [],
|
|
27667
|
+
validation: { originalIssues, generatedIssues: [] },
|
|
27668
|
+
...failureError,
|
|
27669
|
+
uint8Array: this.originalBytes,
|
|
27670
|
+
toUint8Array: () => new Uint8Array(this.originalBytes),
|
|
27671
|
+
buffer: buf,
|
|
27672
|
+
toBuffer: () => toBufferCompatible(this.originalBytes)
|
|
27673
|
+
};
|
|
27674
|
+
}
|
|
27675
|
+
this.entries = working;
|
|
27676
|
+
const outputBytes = this.toUint8Array();
|
|
27677
|
+
this.originalBytes = outputBytes;
|
|
27678
|
+
const artifactsChanged = [...working].filter(([name, data]) => !snapshotEntries.has(name) || !areByteArraysEqual(data, snapshotEntries.get(name))).map(([name]) => name);
|
|
27679
|
+
return {
|
|
27680
|
+
...bodyResult || {},
|
|
27681
|
+
status,
|
|
27682
|
+
hasChanges: true,
|
|
27683
|
+
written: true,
|
|
27684
|
+
results,
|
|
27685
|
+
receipts,
|
|
27686
|
+
executionOrder,
|
|
27687
|
+
authorsUsed: [...authorsUsed],
|
|
27688
|
+
artifactsChanged,
|
|
27689
|
+
validation: { originalIssues, generatedIssues: [] },
|
|
27690
|
+
...failureError,
|
|
27691
|
+
uint8Array: outputBytes,
|
|
27692
|
+
toUint8Array: () => new Uint8Array(outputBytes),
|
|
27693
|
+
buffer: toBufferCompatible(outputBytes),
|
|
27694
|
+
inspection: this.inspect(),
|
|
27695
|
+
toBuffer: () => toBufferCompatible(outputBytes)
|
|
27696
|
+
};
|
|
27697
|
+
}
|
|
27698
|
+
async applyBodyOperations(operations, options = {}) {
|
|
26743
27699
|
const failedApply = (error2) => {
|
|
26744
27700
|
const results = [];
|
|
26745
27701
|
const receipts = [];
|
|
@@ -26850,18 +27806,21 @@ var DocxDocument = class {
|
|
|
26850
27806
|
toBuffer: () => toBufferCompatible(this.originalBytes)
|
|
26851
27807
|
};
|
|
26852
27808
|
}
|
|
26853
|
-
|
|
27809
|
+
const bodyChanged = (result.results || []).some((item) => item?.status !== "error" && item?.operationType !== "comment_resolve");
|
|
27810
|
+
if (bodyChanged) working.set("word/document.xml", textEncoder4.encode(result.documentXml));
|
|
26854
27811
|
await ensureNumberingArtifactsInZip(zip, result.numberingXmlParts, { mergeNumberingXml: mergeNumberingXmlBySchemaOrder });
|
|
26855
27812
|
const existingCommentsXml = text(working, "word/comments.xml");
|
|
26856
27813
|
const commentsXmlForPackaging = result.commentsXml || existingCommentsXml;
|
|
26857
27814
|
await ensureCommentsArtifactsInZip(zip, commentsXmlForPackaging, {
|
|
26858
27815
|
replaceExisting: result.commentsXmlMode === "replace" || !result.commentsXml && !!existingCommentsXml
|
|
26859
27816
|
});
|
|
26860
|
-
|
|
26861
|
-
|
|
26862
|
-
|
|
26863
|
-
|
|
26864
|
-
}
|
|
27817
|
+
if (result.commentsExtendedXml) {
|
|
27818
|
+
await ensureCommentsExtendedArtifactsInZip(zip, result.commentsExtendedXml, {
|
|
27819
|
+
replaceExisting: result.commentsExtendedXmlMode === "replace"
|
|
27820
|
+
});
|
|
27821
|
+
}
|
|
27822
|
+
await repairKnownContentTypes(zip);
|
|
27823
|
+
reconcileCommentSiblings(working);
|
|
26865
27824
|
if (options.validate !== false) {
|
|
26866
27825
|
const generated = validateRedlineOoxml(result.documentXml);
|
|
26867
27826
|
const outputIssues = generated.issues.map((issue) => ({ source: "word/document.xml", ...issue }));
|
|
@@ -26930,13 +27889,25 @@ var DocxDocument = class {
|
|
|
26930
27889
|
const transform = action === "accept" ? acceptTrackedChangesInOoxml : action === "reject" ? rejectTrackedChangesInOoxml : null;
|
|
26931
27890
|
if (!transform) return packageFailure(this.originalBytes, "INVALID_ACTION", `Unknown revision action: ${action}`);
|
|
26932
27891
|
const sourceBytes = this.originalBytes;
|
|
27892
|
+
const originalEntries = this.entries;
|
|
26933
27893
|
const working = cloneEntries(this.entries);
|
|
26934
27894
|
const zip = new MemoryZip(working);
|
|
26935
27895
|
const result = transform(text(working, "word/document.xml"), { author: options.author, allAuthors: options.allAuthors === true });
|
|
26936
27896
|
if (result.status === "error" || result.error) {
|
|
26937
27897
|
return packageFailure(sourceBytes, result.error?.code || "REVISION_OPERATION_FAILED", result.error?.message || "Revision operation failed.");
|
|
26938
27898
|
}
|
|
26939
|
-
|
|
27899
|
+
const partUpdates = /* @__PURE__ */ new Map();
|
|
27900
|
+
for (const part of this.inspectHeadersFooters()) {
|
|
27901
|
+
const partXml = text(working, part.path);
|
|
27902
|
+
if (!partXml) continue;
|
|
27903
|
+
const partResult = transform(partXml, { author: options.author, allAuthors: options.allAuthors === true });
|
|
27904
|
+
if (partResult.status === "error" || partResult.error) {
|
|
27905
|
+
return packageFailure(sourceBytes, partResult.error?.code || "REVISION_OPERATION_FAILED", `${part.path}: ${partResult.error?.message || "Revision operation failed."}`);
|
|
27906
|
+
}
|
|
27907
|
+
if (partResult.hasChanges) partUpdates.set(part.path, partResult.oxml);
|
|
27908
|
+
}
|
|
27909
|
+
const hasChanges = result.hasChanges || partUpdates.size > 0;
|
|
27910
|
+
if (!hasChanges) {
|
|
26940
27911
|
const buf = toBufferCompatible(sourceBytes);
|
|
26941
27912
|
return {
|
|
26942
27913
|
...result,
|
|
@@ -26949,8 +27920,10 @@ var DocxDocument = class {
|
|
|
26949
27920
|
toBuffer: () => toBufferCompatible(sourceBytes)
|
|
26950
27921
|
};
|
|
26951
27922
|
}
|
|
26952
|
-
working.set("word/document.xml", textEncoder4.encode(result.oxml));
|
|
27923
|
+
if (result.hasChanges) working.set("word/document.xml", textEncoder4.encode(result.oxml));
|
|
27924
|
+
for (const [path, xml] of partUpdates) working.set(path, textEncoder4.encode(xml));
|
|
26953
27925
|
try {
|
|
27926
|
+
await repairKnownContentTypes(zip);
|
|
26954
27927
|
if (options.validate !== false) await validateDocxPackage(zip);
|
|
26955
27928
|
} catch (error2) {
|
|
26956
27929
|
return packageFailure(sourceBytes, "PACKAGE_VALIDATION", error2.message);
|
|
@@ -26963,13 +27936,71 @@ var DocxDocument = class {
|
|
|
26963
27936
|
...result,
|
|
26964
27937
|
status: "ok",
|
|
26965
27938
|
written: true,
|
|
26966
|
-
|
|
27939
|
+
hasChanges: true,
|
|
27940
|
+
artifactsChanged: [...working].filter(([name, data]) => !originalEntries.has(name) || !areByteArraysEqual(data, originalEntries.get(name))).map(([name]) => name),
|
|
26967
27941
|
uint8Array: outputBytes,
|
|
26968
27942
|
toUint8Array: () => new Uint8Array(outputBytes),
|
|
26969
27943
|
buffer: outputBuf,
|
|
26970
27944
|
toBuffer: () => toBufferCompatible(outputBytes)
|
|
26971
27945
|
};
|
|
26972
27946
|
}
|
|
27947
|
+
/**
|
|
27948
|
+
* Resolves (or reopens) the comment thread containing `commentId`.
|
|
27949
|
+
* Resolved is a thread-level state in Word, so the root and every reply are updated together.
|
|
27950
|
+
*/
|
|
27951
|
+
async resolveComment(commentId, options = {}) {
|
|
27952
|
+
const sourceBytes = this.originalBytes;
|
|
27953
|
+
const working = cloneEntries(this.entries);
|
|
27954
|
+
const resolved = options.resolved !== false;
|
|
27955
|
+
const noChange = (extra) => {
|
|
27956
|
+
const buf = toBufferCompatible(sourceBytes);
|
|
27957
|
+
return {
|
|
27958
|
+
status: "ok",
|
|
27959
|
+
hasChanges: false,
|
|
27960
|
+
written: false,
|
|
27961
|
+
artifactsChanged: [],
|
|
27962
|
+
...extra,
|
|
27963
|
+
uint8Array: sourceBytes,
|
|
27964
|
+
toUint8Array: () => new Uint8Array(sourceBytes),
|
|
27965
|
+
buffer: buf,
|
|
27966
|
+
toBuffer: () => toBufferCompatible(sourceBytes)
|
|
27967
|
+
};
|
|
27968
|
+
};
|
|
27969
|
+
const result = applyThreadResolutionToParts({
|
|
27970
|
+
commentsXml: text(working, "word/comments.xml"),
|
|
27971
|
+
commentsExtendedXml: text(working, "word/commentsExtended.xml"),
|
|
27972
|
+
commentId,
|
|
27973
|
+
resolved
|
|
27974
|
+
});
|
|
27975
|
+
if (result.status === "error") return packageFailure(sourceBytes, result.error.code, result.error.message);
|
|
27976
|
+
if (!result.hasChanges) return noChange({ resolved, threadRootId: result.threadRootId, commentIds: result.commentIds });
|
|
27977
|
+
const zip = new MemoryZip(working);
|
|
27978
|
+
try {
|
|
27979
|
+
if (result.commentsXml) working.set("word/comments.xml", textEncoder4.encode(result.commentsXml));
|
|
27980
|
+
await ensureCommentsExtendedArtifactsInZip(zip, result.commentsExtendedXml, { replaceExisting: true });
|
|
27981
|
+
await repairKnownContentTypes(zip);
|
|
27982
|
+
if (options.validate !== false) await validateDocxPackage(zip);
|
|
27983
|
+
} catch (error2) {
|
|
27984
|
+
return packageFailure(sourceBytes, "PACKAGE_VALIDATION", error2.message);
|
|
27985
|
+
}
|
|
27986
|
+
const artifactsChanged = [...working].filter(([name, data]) => !this.entries.has(name) || !areByteArraysEqual(data, this.entries.get(name))).map(([name]) => name);
|
|
27987
|
+
this.entries = working;
|
|
27988
|
+
const outputBytes = this.toUint8Array();
|
|
27989
|
+
this.originalBytes = outputBytes;
|
|
27990
|
+
return {
|
|
27991
|
+
status: "ok",
|
|
27992
|
+
hasChanges: true,
|
|
27993
|
+
written: true,
|
|
27994
|
+
resolved,
|
|
27995
|
+
threadRootId: result.threadRootId,
|
|
27996
|
+
commentIds: result.commentIds,
|
|
27997
|
+
artifactsChanged,
|
|
27998
|
+
uint8Array: outputBytes,
|
|
27999
|
+
toUint8Array: () => new Uint8Array(outputBytes),
|
|
28000
|
+
buffer: toBufferCompatible(outputBytes),
|
|
28001
|
+
toBuffer: () => toBufferCompatible(outputBytes)
|
|
28002
|
+
};
|
|
28003
|
+
}
|
|
26973
28004
|
async deleteComments(options = {}) {
|
|
26974
28005
|
const sourceBytes = this.originalBytes;
|
|
26975
28006
|
const working = cloneEntries(this.entries);
|
|
@@ -26993,7 +28024,13 @@ var DocxDocument = class {
|
|
|
26993
28024
|
if (!parsed.doc || parsed.error) {
|
|
26994
28025
|
return packageFailure(sourceBytes, "PARSE_ERROR", parsed.error?.message || "Could not parse comments.xml.");
|
|
26995
28026
|
}
|
|
26996
|
-
const
|
|
28027
|
+
const requestedIds = Array.isArray(options.ids) ? new Set(options.ids.map(String)) : null;
|
|
28028
|
+
if (requestedIds) {
|
|
28029
|
+
const known = new Set(Array.from(parsed.doc.getElementsByTagNameNS("*", "comment")).map((node) => node.getAttribute("w:id") || node.getAttribute("id")));
|
|
28030
|
+
const missing = [...requestedIds].filter((id) => !known.has(id));
|
|
28031
|
+
if (missing.length) return packageFailure(sourceBytes, "COMMENT_NOT_FOUND", `Comment id(s) not found: ${missing.join(", ")}.`);
|
|
28032
|
+
}
|
|
28033
|
+
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;
|
|
26997
28034
|
const ids = new Set(
|
|
26998
28035
|
Array.from(parsed.doc.getElementsByTagNameNS("*", "comment")).filter(matches).map((node) => node.getAttribute("w:id") || node.getAttribute("id")).filter(Boolean)
|
|
26999
28036
|
);
|
|
@@ -27022,9 +28059,8 @@ var DocxDocument = class {
|
|
|
27022
28059
|
const idByParaId = /* @__PURE__ */ new Map();
|
|
27023
28060
|
for (const comment of Array.from(parsed.doc.getElementsByTagNameNS("*", "comment"))) {
|
|
27024
28061
|
const id = comment.getAttribute("w:id") || comment.getAttribute("id");
|
|
27025
|
-
const
|
|
27026
|
-
|
|
27027
|
-
if (paraId) idByParaId.set(paraId.toUpperCase(), id);
|
|
28062
|
+
const paraId = commentParaId(comment);
|
|
28063
|
+
if (paraId) idByParaId.set(paraId, id);
|
|
27028
28064
|
}
|
|
27029
28065
|
for (const [paraId, id] of idByParaId) {
|
|
27030
28066
|
if (ids.has(id)) removedParaIds.add(paraId);
|
|
@@ -27043,8 +28079,8 @@ var DocxDocument = class {
|
|
|
27043
28079
|
}
|
|
27044
28080
|
}
|
|
27045
28081
|
}
|
|
27046
|
-
const
|
|
27047
|
-
const remainingParsed = parseOoxmlSafe(
|
|
28082
|
+
const remainingXml = requestedIds ? commentsXml : deleteCommentsByAuthorInOoxml(commentsXml, { author: options.author, allAuthors: options.allAuthors === true }).oxml;
|
|
28083
|
+
const remainingParsed = parseOoxmlSafe(remainingXml, "application/xml");
|
|
27048
28084
|
if (!remainingParsed.doc || remainingParsed.error) {
|
|
27049
28085
|
return packageFailure(sourceBytes, "PARSE_ERROR", remainingParsed.error?.message || "Could not parse updated comments.xml.");
|
|
27050
28086
|
}
|
|
@@ -27080,10 +28116,14 @@ var DocxDocument = class {
|
|
|
27080
28116
|
}
|
|
27081
28117
|
working.set("word/document.xml", textEncoder4.encode(serializer.serializeToString(documentParsed.doc)));
|
|
27082
28118
|
try {
|
|
27083
|
-
|
|
28119
|
+
reconcileCommentSiblings(working);
|
|
28120
|
+
const finalZip = new MemoryZip(working);
|
|
28121
|
+
await repairKnownContentTypes(finalZip);
|
|
28122
|
+
if (options.validate !== false) await validateDocxPackage(finalZip);
|
|
27084
28123
|
} catch (error2) {
|
|
27085
28124
|
return packageFailure(sourceBytes, "PACKAGE_VALIDATION", error2.message);
|
|
27086
28125
|
}
|
|
28126
|
+
const artifactsChanged = [...working].filter(([name, data]) => !this.entries.has(name) || !areByteArraysEqual(data, this.entries.get(name))).map(([name]) => name);
|
|
27087
28127
|
this.entries = working;
|
|
27088
28128
|
const outputBytes = this.toUint8Array();
|
|
27089
28129
|
this.originalBytes = outputBytes;
|
|
@@ -27094,7 +28134,7 @@ var DocxDocument = class {
|
|
|
27094
28134
|
written: true,
|
|
27095
28135
|
commentsRemoved: ids.size,
|
|
27096
28136
|
referencesRemoved,
|
|
27097
|
-
artifactsChanged
|
|
28137
|
+
artifactsChanged,
|
|
27098
28138
|
uint8Array: outputBytes,
|
|
27099
28139
|
toUint8Array: () => new Uint8Array(outputBytes),
|
|
27100
28140
|
buffer: outputBuf,
|
|
@@ -27350,6 +28390,7 @@ export {
|
|
|
27350
28390
|
rejectTrackedChangesInOoxml,
|
|
27351
28391
|
remapNumberingPayloadForDocument,
|
|
27352
28392
|
removeFormattingFromRPr,
|
|
28393
|
+
repairKnownContentTypes,
|
|
27353
28394
|
reserveNextNumberingId,
|
|
27354
28395
|
reserveNextNumberingIdPair,
|
|
27355
28396
|
resolveParagraphRangeByRefs,
|