@ansonlai/docx-redline-js 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +679 -669
- package/CHANGELOG.md +32 -1
- package/README.md +29 -6
- package/core/word-xml.js +160 -118
- package/dist/docx-redline-js.esm.js +171 -51
- package/dist/docx-redline-js.esm.js.map +4 -4
- package/dist/docx-redline-js.esm.min.js +93 -93
- package/dist/docx-redline-js.esm.min.js.map +4 -4
- package/dist/docx-redline.bundle.cjs +171 -51
- package/dist/docx-redline.bundle.cjs.map +4 -4
- package/dist/docx-redline.bundle.js +171 -51
- package/dist/docx-redline.bundle.js.map +4 -4
- package/docs/AGENT_KNOWLEDGE_BASE.md +30 -4
- package/docs/SKILL_AUTHORING.md +5 -1
- package/docs/TESTING.md +4 -0
- package/docs/schemas/document-operations.schema.json +1 -1
- package/engine/oxml-engine.js +748 -736
- package/index.d.ts +6 -1
- package/index.js +2 -0
- package/node/cli.js +993 -992
- package/package.json +1 -1
- package/pipeline/markdown-processor.js +5 -2
- package/services/comment-replies.js +1 -1
- package/services/document-operation-applier.js +1 -0
- package/services/document-operation-contract.js +1 -0
- package/services/document-operation-mutations.js +3358 -3346
- package/services/inspection-comparison.js +82 -0
- package/services/operation-preflight.js +42 -33
- package/services/revision-fidelity-verifier.js +5 -19
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @ansonlai/docx-redline-js v0.
|
|
1
|
+
// @ansonlai/docx-redline-js v0.10.0 — 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;
|
|
@@ -1663,7 +1663,10 @@ var MARKDOWN_PATTERNS = [
|
|
|
1663
1663
|
{ regex: /\*\*\+\+(.+?)\+\+\*\*/, format: { bold: true, underline: true } },
|
|
1664
1664
|
// Bold: **text** or __text__
|
|
1665
1665
|
{ regex: /\*\*(.+?)\*\*/, format: { bold: true } },
|
|
1666
|
-
|
|
1666
|
+
// Underscore delimiters follow CommonMark flanking rules: not intraword, not part of a
|
|
1667
|
+
// longer underscore run, and never around whitespace. So signature lines ("_____")
|
|
1668
|
+
// and snake_case identifiers stay literal text.
|
|
1669
|
+
{ regex: /(?<![A-Za-z0-9_])__(?=[^_\s])(.+?)(?<=[^_\s])__(?![A-Za-z0-9_])/, format: { bold: true } },
|
|
1667
1670
|
// Underline: ++text++
|
|
1668
1671
|
{ regex: /\+\+(.+?)\+\+/, format: { underline: true } },
|
|
1669
1672
|
// Strikethrough: ~~text~~ or ~text~
|
|
@@ -1671,7 +1674,7 @@ var MARKDOWN_PATTERNS = [
|
|
|
1671
1674
|
{ regex: /~(.+?)~/, format: { strikethrough: true } },
|
|
1672
1675
|
// Italic: *text* or _text_ (using lookahead only for compatibility)
|
|
1673
1676
|
{ regex: /\*(?!\*)(.+?)\*(?!\*)/, format: { italic: true } },
|
|
1674
|
-
{ regex: /_(
|
|
1677
|
+
{ regex: /(?<![A-Za-z0-9_])_(?=[^_\s])(.+?)(?<=[^_\s])_(?![A-Za-z0-9_])/, format: { italic: true } }
|
|
1675
1678
|
];
|
|
1676
1679
|
function preprocessMarkdown(text2) {
|
|
1677
1680
|
if (!text2) {
|
|
@@ -3842,6 +3845,23 @@ function withOoxmlSourceType(result) {
|
|
|
3842
3845
|
}
|
|
3843
3846
|
return { ...result, sourceType: classifyOoxmlSourceType(result.oxml) };
|
|
3844
3847
|
}
|
|
3848
|
+
function hasUnattributedTrackedChange(node) {
|
|
3849
|
+
if (!node) return false;
|
|
3850
|
+
for (const localName4 of TRACKED_CHANGE_NAMES) {
|
|
3851
|
+
for (const change of wordElementsByLocalName(node, localName4)) {
|
|
3852
|
+
const author = change.getAttribute?.("w:author") || change.getAttribute?.("author") || (typeof change.getAttributeNS === "function" ? change.getAttributeNS(NS_W, "author") : null);
|
|
3853
|
+
if (!author || !String(author).trim()) return true;
|
|
3854
|
+
}
|
|
3855
|
+
}
|
|
3856
|
+
return false;
|
|
3857
|
+
}
|
|
3858
|
+
function resolveExistingRevisionsPolicy(policy, node, author) {
|
|
3859
|
+
if (policy !== "auto") return policy;
|
|
3860
|
+
const nodes = (Array.isArray(node) ? node : [node]).filter(Boolean);
|
|
3861
|
+
const own = String(author || "").trim().toLowerCase();
|
|
3862
|
+
const foreign = nodes.some((item) => containsTrackedChanges(item) && (hasUnattributedTrackedChange(item) || getTrackedChangeAuthors(item).some((name) => name.trim().toLowerCase() !== own)));
|
|
3863
|
+
return foreign ? "slice-cross-author" : "merge-same-author";
|
|
3864
|
+
}
|
|
3845
3865
|
|
|
3846
3866
|
// engine/paragraph-mark-revisions.js
|
|
3847
3867
|
function localNameOf(node) {
|
|
@@ -9352,7 +9372,7 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
9352
9372
|
const operationWarnings = [];
|
|
9353
9373
|
let normalizedExistingRevisions = false;
|
|
9354
9374
|
let preservedInsertedParagraphMark = null;
|
|
9355
|
-
|
|
9375
|
+
let existingRevisionsPolicy = options.existingRevisions || "merge-same-author";
|
|
9356
9376
|
const keepNormalizedNoOp = existingRevisionsPolicy === "accept-all-first-keep-normalized";
|
|
9357
9377
|
const finalize = (result) => {
|
|
9358
9378
|
const withStatus = { ...result };
|
|
@@ -9400,6 +9420,10 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
9400
9420
|
const parsed = parseOoxmlSafe(inputOoxml, "text/xml");
|
|
9401
9421
|
parseWarnings = parsed.warnings;
|
|
9402
9422
|
let xmlDoc = parsed.doc;
|
|
9423
|
+
if (existingRevisionsPolicy === "auto") {
|
|
9424
|
+
existingRevisionsPolicy = resolveExistingRevisionsPolicy("auto", xmlDoc, author);
|
|
9425
|
+
options = { ...options, existingRevisions: existingRevisionsPolicy };
|
|
9426
|
+
}
|
|
9403
9427
|
const parseError = xmlDoc ? getXmlParseError(xmlDoc) : null;
|
|
9404
9428
|
if (parsed.error || parseError) {
|
|
9405
9429
|
const message = parsed.error?.message || parseError?.textContent || "Could not parse OOXML input.";
|
|
@@ -9434,7 +9458,7 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
9434
9458
|
if (existingRevisionsPolicy === "merge-same-author" || existingRevisionsPolicy === "slice-cross-author") {
|
|
9435
9459
|
const authors = getTrackedChangeAuthors(xmlDoc);
|
|
9436
9460
|
const currentAuthor = String(author || "").trim().toLowerCase();
|
|
9437
|
-
const isSameAuthor = authors.length > 0 && authors.every((a) => a.trim().toLowerCase() === currentAuthor);
|
|
9461
|
+
const isSameAuthor = authors.length > 0 && !hasUnattributedTrackedChange(xmlDoc) && authors.every((a) => a.trim().toLowerCase() === currentAuthor);
|
|
9438
9462
|
if (isSameAuthor) {
|
|
9439
9463
|
const commentIds = getCommentIdsInOoxml(xmlDoc);
|
|
9440
9464
|
if (commentIds.length > 0) {
|
|
@@ -9591,7 +9615,7 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
9591
9615
|
sanitizedText = structuredAnalysis.normalizedMarkdown;
|
|
9592
9616
|
}
|
|
9593
9617
|
}
|
|
9594
|
-
const { cleanText: cleanModifiedText, formatHints } = preprocessMarkdown(sanitizedText);
|
|
9618
|
+
const { cleanText: cleanModifiedText, formatHints } = options.plainTextContent === true ? { cleanText: sanitizedText, formatHints: [] } : preprocessMarkdown(sanitizedText);
|
|
9595
9619
|
const { existingFormatHints, textSpans, paragraphs } = extractFormattingFromOoxml(xmlDoc);
|
|
9596
9620
|
const hasLiteralTextChanges = existingRevisionsPolicy === "slice-cross-author" ? cleanModifiedText !== originalText : cleanModifiedText.trim() !== originalText.trim();
|
|
9597
9621
|
const hasListStructureChange = !hasLiteralTextChanges && options.explicitStructuredContent === true && isListTargetLoose(cleanModifiedText) && !sourceMatchesListShape(paragraphs, cleanModifiedText);
|
|
@@ -13423,6 +13447,70 @@ function inspectDocumentParts(parts, options = {}) {
|
|
|
13423
13447
|
};
|
|
13424
13448
|
}
|
|
13425
13449
|
|
|
13450
|
+
// services/inspection-comparison.js
|
|
13451
|
+
var commentIdentity = (comment) => [comment.author ?? null, comment.text ?? "", comment.anchoredText || ""];
|
|
13452
|
+
function reviewSegments(segments = []) {
|
|
13453
|
+
return segments.filter((segment) => segment.kind !== "baseline").reduce((result, segment) => {
|
|
13454
|
+
const next = {
|
|
13455
|
+
kind: segment.kind,
|
|
13456
|
+
author: segment.author || null,
|
|
13457
|
+
text: segment.text,
|
|
13458
|
+
acceptedStart: segment.acceptedStart ?? null,
|
|
13459
|
+
rejectedStart: segment.rejectedStart ?? null
|
|
13460
|
+
};
|
|
13461
|
+
const previous = result.at(-1);
|
|
13462
|
+
const adjacent = (key) => previous[key] == null && next[key] == null || previous[key] != null && previous[key] + previous.text.length === next[key];
|
|
13463
|
+
if (previous && previous.kind === next.kind && previous.author === next.author && adjacent("acceptedStart") && adjacent("rejectedStart")) previous.text += next.text;
|
|
13464
|
+
else result.push(next);
|
|
13465
|
+
return result;
|
|
13466
|
+
}, []);
|
|
13467
|
+
}
|
|
13468
|
+
function inspectionReviewShape(inspection) {
|
|
13469
|
+
const comments = inspection?.comments || [];
|
|
13470
|
+
return {
|
|
13471
|
+
paragraphs: (inspection?.paragraphs || []).map((paragraph) => ({
|
|
13472
|
+
text: paragraph.exactText,
|
|
13473
|
+
inTable: paragraph.inTable === true,
|
|
13474
|
+
table: paragraph.table ? [paragraph.table.rowIndex, paragraph.table.cellIndex] : null,
|
|
13475
|
+
list: paragraph.list ? [paragraph.list.format, paragraph.list.level, paragraph.list.label] : null,
|
|
13476
|
+
review: {
|
|
13477
|
+
present: paragraph.hasRevisions === true,
|
|
13478
|
+
authors: [...paragraph.revisionAuthors || []].sort(),
|
|
13479
|
+
segments: reviewSegments(paragraph.segments)
|
|
13480
|
+
}
|
|
13481
|
+
})),
|
|
13482
|
+
comments: comments.map((comment) => {
|
|
13483
|
+
const parent = comment.parentCommentId != null ? comments.find((item) => String(item.id) === String(comment.parentCommentId)) : null;
|
|
13484
|
+
return [
|
|
13485
|
+
...commentIdentity(comment),
|
|
13486
|
+
comment.done === true,
|
|
13487
|
+
parent ? commentIdentity(parent) : comment.parentCommentId != null || comment.parentParaId ? "unresolved parent" : null
|
|
13488
|
+
];
|
|
13489
|
+
}).map((item) => JSON.stringify(item)).sort()
|
|
13490
|
+
};
|
|
13491
|
+
}
|
|
13492
|
+
function compareDocumentInspections(expected, actual) {
|
|
13493
|
+
const left = inspectionReviewShape(expected);
|
|
13494
|
+
const right = inspectionReviewShape(actual);
|
|
13495
|
+
const differences = [];
|
|
13496
|
+
const note = (path, a, b) => {
|
|
13497
|
+
if (differences.length < 10 && JSON.stringify(a) !== JSON.stringify(b)) differences.push({ path, expected: a, actual: b });
|
|
13498
|
+
};
|
|
13499
|
+
note("paragraphs.length", left.paragraphs.length, right.paragraphs.length);
|
|
13500
|
+
const count = Math.max(left.paragraphs.length, right.paragraphs.length);
|
|
13501
|
+
for (let index = 0; index < count; index++) {
|
|
13502
|
+
const a = left.paragraphs[index] ?? null;
|
|
13503
|
+
const b = right.paragraphs[index] ?? null;
|
|
13504
|
+
if (!a || !b) {
|
|
13505
|
+
note(`paragraphs[${index}]`, a, b);
|
|
13506
|
+
continue;
|
|
13507
|
+
}
|
|
13508
|
+
for (const key of ["text", "inTable", "table", "list", "review"]) note(`paragraphs[${index}].${key}`, a[key], b[key]);
|
|
13509
|
+
}
|
|
13510
|
+
note("comments", left.comments, right.comments);
|
|
13511
|
+
return { matches: differences.length === 0, differences };
|
|
13512
|
+
}
|
|
13513
|
+
|
|
13426
13514
|
// services/comment-locator.js
|
|
13427
13515
|
function createParagraphTextIndex(paragraph, options = {}) {
|
|
13428
13516
|
const revisionView = options.revisionView === "current" ? "accepted" : options.revisionView || "accepted";
|
|
@@ -13897,7 +13985,7 @@ function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, doc
|
|
|
13897
13985
|
extendedDoc = parsed.doc;
|
|
13898
13986
|
}
|
|
13899
13987
|
const occupied = usedParaIds(commentsDoc, extendedDoc);
|
|
13900
|
-
if (documentXml) for (const match of String(documentXml).matchAll(
|
|
13988
|
+
if (documentXml) for (const match of String(documentXml).matchAll(/\bw14:paraId="([0-9A-Fa-f]{8})"/g)) occupied.add(match[1].toUpperCase());
|
|
13901
13989
|
const parentParagraph = commentThreadParagraph(parent);
|
|
13902
13990
|
if (!parentParagraph) return { status: "error", error: { code: "PARENT_COMMENT_INVALID", message: `Parent comment '${parentCommentId}' has no paragraph.` } };
|
|
13903
13991
|
let parentParaId = commentParaId(parent);
|
|
@@ -16199,6 +16287,7 @@ var SUPPORTED_OPERATION_TYPES = /* @__PURE__ */ new Set([
|
|
|
16199
16287
|
"highlight"
|
|
16200
16288
|
]);
|
|
16201
16289
|
var EXISTING_REVISIONS_POLICIES = Object.freeze([
|
|
16290
|
+
"auto",
|
|
16202
16291
|
"merge-same-author",
|
|
16203
16292
|
"slice-cross-author",
|
|
16204
16293
|
"reject-input",
|
|
@@ -20253,13 +20342,17 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
20253
20342
|
}
|
|
20254
20343
|
};
|
|
20255
20344
|
}
|
|
20345
|
+
const autoExistingRevisions = options.existingRevisions === "auto";
|
|
20346
|
+
if (autoExistingRevisions) {
|
|
20347
|
+
options = { ...options, existingRevisions: resolveExistingRevisionsPolicy("auto", targetParagraph, author) };
|
|
20348
|
+
}
|
|
20256
20349
|
const hasRevisions = containsTrackedChanges(targetParagraph);
|
|
20257
20350
|
const existingPolicy = options.existingRevisions || "merge-same-author";
|
|
20258
20351
|
if (hasRevisions && existingPolicy !== "accept-all-first" && existingPolicy !== "accept-all-first-keep-normalized") {
|
|
20259
20352
|
if (existingPolicy === "merge-same-author" || existingPolicy === "slice-cross-author") {
|
|
20260
20353
|
const authors = getTrackedChangeAuthors(targetParagraph);
|
|
20261
20354
|
const opAuthor = String(author || "").trim().toLowerCase();
|
|
20262
|
-
const allSame = authors.length > 0 && authors.every((a) => a.trim().toLowerCase() === opAuthor);
|
|
20355
|
+
const allSame = authors.length > 0 && !hasUnattributedTrackedChange(targetParagraph) && authors.every((a) => a.trim().toLowerCase() === opAuthor);
|
|
20263
20356
|
if (!allSame && existingPolicy === "merge-same-author") {
|
|
20264
20357
|
return {
|
|
20265
20358
|
documentXml,
|
|
@@ -20268,7 +20361,7 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
20268
20361
|
status: "error",
|
|
20269
20362
|
error: {
|
|
20270
20363
|
code: "EXISTING_REVISIONS",
|
|
20271
|
-
message: `Target paragraph contains tracked changes from another author (${authors
|
|
20364
|
+
message: `Target paragraph contains tracked changes from another author (${[...authors, ...hasUnattributedTrackedChange(targetParagraph) ? ["unattributed"] : []].join(", ")}). Use existingRevisions: "slice-cross-author" for a surgical edit that preserves reviewer history; accepting or rejecting revisions requires separate authorization.`,
|
|
20272
20365
|
revisionAuthors: authors,
|
|
20273
20366
|
currentPolicy: existingPolicy
|
|
20274
20367
|
}
|
|
@@ -20410,6 +20503,9 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
20410
20503
|
onWarn
|
|
20411
20504
|
}) : typeof targetText === "string" && /\r?\n/.test(targetText) ? findReconstructionParagraphRange(xmlDoc, targetText) : null;
|
|
20412
20505
|
const hasExplicitRangeScope = Array.isArray(explicitRangeParagraphs) && explicitRangeParagraphs.length > 0;
|
|
20506
|
+
if (autoExistingRevisions && hasExplicitRangeScope) {
|
|
20507
|
+
options = { ...options, existingRevisions: resolveExistingRevisionsPolicy("auto", explicitRangeParagraphs, author) };
|
|
20508
|
+
}
|
|
20413
20509
|
if (!useTableScope && hasExplicitRangeScope) {
|
|
20414
20510
|
const insertionEntries = buildExplicitRangeInsertionEntries(explicitRangeParagraphs, effectiveModifiedText);
|
|
20415
20511
|
if (insertionEntries && insertionEntries.length > 0) {
|
|
@@ -20735,6 +20831,7 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
20735
20831
|
pairReplacements: options.pairReplacements === true,
|
|
20736
20832
|
insertionAffinity: options.insertionAffinity || null,
|
|
20737
20833
|
literalTextEdit: options.literalTextEdit === true,
|
|
20834
|
+
plainTextContent: options.plainTextContent === true,
|
|
20738
20835
|
_revisionIdAllocator: revisionIdAllocator,
|
|
20739
20836
|
_isolatedTableCell: useTableScope
|
|
20740
20837
|
});
|
|
@@ -21745,7 +21842,31 @@ function verifyUntrackedLocality({ before, after, targetSourceIndex, beforeViews
|
|
|
21745
21842
|
}
|
|
21746
21843
|
return violations.length ? { ok: false, violations } : { ok: true };
|
|
21747
21844
|
}
|
|
21748
|
-
|
|
21845
|
+
var numberingDefinitionCache = /* @__PURE__ */ new Map();
|
|
21846
|
+
var NUMBERING_DEFINITION_CACHE_SIZE = 8;
|
|
21847
|
+
function parseNumberingDefinitions(text2) {
|
|
21848
|
+
const cached = numberingDefinitionCache.get(text2);
|
|
21849
|
+
if (cached) return cached;
|
|
21850
|
+
const abstractLevels = /* @__PURE__ */ new Map();
|
|
21851
|
+
for (const match of text2.matchAll(/<(?:\w+:)?abstractNum\b([^>]*)>([\s\S]*?)<\/(?:\w+:)?abstractNum>/g)) {
|
|
21852
|
+
const id = /abstractNumId="([^"]*)"/.exec(match[1])?.[1];
|
|
21853
|
+
if (id == null) continue;
|
|
21854
|
+
const levels = abstractLevels.get(id) || /* @__PURE__ */ new Set();
|
|
21855
|
+
for (const lvl of match[2].matchAll(/<(?:\w+:)?lvl\b[^>]*?ilvl="([^"]*)"/g)) levels.add(lvl[1]);
|
|
21856
|
+
abstractLevels.set(id, levels);
|
|
21857
|
+
}
|
|
21858
|
+
const numToAbstract = /* @__PURE__ */ new Map();
|
|
21859
|
+
for (const match of text2.matchAll(/<(?:\w+:)?num\b([^>]*)>([\s\S]*?)<\/(?:\w+:)?num>/g)) {
|
|
21860
|
+
const id = /numId="([^"]*)"/.exec(match[1])?.[1];
|
|
21861
|
+
const abstractId = /abstractNumId[^>]*?val="([^"]*)"/.exec(match[2])?.[1];
|
|
21862
|
+
if (id != null && abstractId != null) numToAbstract.set(id, abstractId);
|
|
21863
|
+
}
|
|
21864
|
+
const definitions = { abstractLevels, numToAbstract };
|
|
21865
|
+
if (numberingDefinitionCache.size >= NUMBERING_DEFINITION_CACHE_SIZE) numberingDefinitionCache.delete(numberingDefinitionCache.keys().next().value);
|
|
21866
|
+
numberingDefinitionCache.set(text2, definitions);
|
|
21867
|
+
return definitions;
|
|
21868
|
+
}
|
|
21869
|
+
function verifyNumberingReferences({ before, after, numberingXmls }) {
|
|
21749
21870
|
const references = (documentNode) => {
|
|
21750
21871
|
const found = /* @__PURE__ */ new Map();
|
|
21751
21872
|
for (const numIdNode of getAllReferencedNumIdElements(documentNode)) {
|
|
@@ -21761,21 +21882,7 @@ function verifyNumberingReferences({ before, after, numberingXmls, beforeViews =
|
|
|
21761
21882
|
const used = references(after);
|
|
21762
21883
|
if (used.size === 0) return { ok: true };
|
|
21763
21884
|
const known = references(before);
|
|
21764
|
-
const
|
|
21765
|
-
const abstractLevels = /* @__PURE__ */ new Map();
|
|
21766
|
-
for (const match of text2.matchAll(/<(?:\w+:)?abstractNum\b([^>]*)>([\s\S]*?)<\/(?:\w+:)?abstractNum>/g)) {
|
|
21767
|
-
const id = /abstractNumId="([^"]*)"/.exec(match[1])?.[1];
|
|
21768
|
-
if (id == null) continue;
|
|
21769
|
-
const levels = abstractLevels.get(id) || /* @__PURE__ */ new Set();
|
|
21770
|
-
for (const lvl of match[2].matchAll(/<(?:\w+:)?lvl\b[^>]*?ilvl="([^"]*)"/g)) levels.add(lvl[1]);
|
|
21771
|
-
abstractLevels.set(id, levels);
|
|
21772
|
-
}
|
|
21773
|
-
const numToAbstract = /* @__PURE__ */ new Map();
|
|
21774
|
-
for (const match of text2.matchAll(/<(?:\w+:)?num\b([^>]*)>([\s\S]*?)<\/(?:\w+:)?num>/g)) {
|
|
21775
|
-
const id = /numId="([^"]*)"/.exec(match[1])?.[1];
|
|
21776
|
-
const abstractId = /abstractNumId[^>]*?val="([^"]*)"/.exec(match[2])?.[1];
|
|
21777
|
-
if (id != null && abstractId != null) numToAbstract.set(id, abstractId);
|
|
21778
|
-
}
|
|
21885
|
+
const { abstractLevels, numToAbstract } = parseNumberingDefinitions((numberingXmls || []).filter(Boolean).join("\n"));
|
|
21779
21886
|
const violations = [];
|
|
21780
21887
|
for (const [numId, levels] of used) {
|
|
21781
21888
|
if (known.has(numId)) continue;
|
|
@@ -22124,6 +22231,7 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
22124
22231
|
...operation.existingRevisions ? { existingRevisions: operation.existingRevisions } : {},
|
|
22125
22232
|
structuredContent: typeof operation.structuredContent === "boolean" ? operation.structuredContent : options.structuredContent !== false,
|
|
22126
22233
|
explicitStructuredContent: operation.structuredContent === true,
|
|
22234
|
+
plainTextContent: operation.structuredContent === false || operation.structuredContent == null && options.structuredContent === false,
|
|
22127
22235
|
...operation._localizedReplacementCompilation && operation.structuredContent !== true ? { literalTextEdit: true } : {},
|
|
22128
22236
|
pairReplacements: typeof operation.pairReplacements === "boolean" ? operation.pairReplacements : options.pairReplacements !== false,
|
|
22129
22237
|
...operation.insertionAffinity ? { insertionAffinity: operation.insertionAffinity } : {},
|
|
@@ -23378,35 +23486,44 @@ function preflightOperations(documentXml, operations, author, options = {}) {
|
|
|
23378
23486
|
const anchor = operation.operationKind === "comment" ? operation.textToComment || paragraphText : operation.operationKind === "highlight" ? operation.textToHighlight : operation.operationKind === "format" ? operation.textToFormat : null;
|
|
23379
23487
|
const anchorResolution = operation.operationKind === "comment" && anchor != null ? resolveTextInParagraphIndex(createParagraphTextIndex(paragraph, { revisionView: targetView }), anchor) : null;
|
|
23380
23488
|
const anchorFound = anchor == null || (anchorResolution ? anchorResolution.found : paragraphText.includes(anchor));
|
|
23489
|
+
const rangeParagraphs = (opType) => {
|
|
23490
|
+
if (operation.targetEndRef) {
|
|
23491
|
+
return resolveParagraphRangeByRefs(xmlDoc, operation.targetRef, operation.targetEndRef, {
|
|
23492
|
+
opType,
|
|
23493
|
+
targetRefSnapshot: options.targetRefSnapshot || null,
|
|
23494
|
+
onInfo: options.onInfo,
|
|
23495
|
+
onWarn: options.onWarn
|
|
23496
|
+
});
|
|
23497
|
+
}
|
|
23498
|
+
if (!operation.targetEndDescriptor) return [paragraph];
|
|
23499
|
+
const endResolved = resolveTargetParagraph(xmlDoc, {
|
|
23500
|
+
targetText: operation.targetEndDescriptor.text,
|
|
23501
|
+
targetRef: operation.targetEndDescriptor.index,
|
|
23502
|
+
targetDescriptor: operation.targetEndDescriptor,
|
|
23503
|
+
opType,
|
|
23504
|
+
strictAmbiguity: strictTargets,
|
|
23505
|
+
paragraphMetadataIndex: currentMetadataIndex,
|
|
23506
|
+
metadataIndices,
|
|
23507
|
+
onInfo: options.onInfo,
|
|
23508
|
+
onWarn: options.onWarn
|
|
23509
|
+
});
|
|
23510
|
+
const allParagraphs = getDocumentParagraphNodes(xmlDoc);
|
|
23511
|
+
const startIndex = allParagraphs.indexOf(paragraph);
|
|
23512
|
+
const endIndex = allParagraphs.indexOf(endResolved?.paragraph);
|
|
23513
|
+
return startIndex >= 0 && endIndex >= startIndex ? allParagraphs.slice(startIndex, endIndex + 1) : null;
|
|
23514
|
+
};
|
|
23381
23515
|
const hasRevisions = containsTrackedChanges(paragraph);
|
|
23382
|
-
const
|
|
23516
|
+
const requestedPolicy = operation.existingRevisions || options.existingRevisions || "merge-same-author";
|
|
23517
|
+
const existingPolicy = resolveExistingRevisionsPolicy(
|
|
23518
|
+
requestedPolicy,
|
|
23519
|
+
requestedPolicy === "auto" && operation.operationKind === "redline" && (operation.targetEndRef || operation.targetEndDescriptor) ? rangeParagraphs("redline") || paragraph : paragraph,
|
|
23520
|
+
authorUsed
|
|
23521
|
+
);
|
|
23383
23522
|
const deletingWholeParagraph = operation.operationKind === "redline" && operation.modified === "";
|
|
23384
23523
|
const commentIds = deletingWholeParagraph ? getCommentIdsInParagraph(paragraph) : [];
|
|
23385
23524
|
let error2 = null;
|
|
23386
23525
|
if (operation.operationKind === "restore") {
|
|
23387
|
-
|
|
23388
|
-
opType: "restore",
|
|
23389
|
-
targetRefSnapshot: options.targetRefSnapshot || null,
|
|
23390
|
-
onInfo: options.onInfo,
|
|
23391
|
-
onWarn: options.onWarn
|
|
23392
|
-
}) : [paragraph];
|
|
23393
|
-
if (!operation.targetEndRef && operation.targetEndDescriptor) {
|
|
23394
|
-
const endResolved = resolveTargetParagraph(xmlDoc, {
|
|
23395
|
-
targetText: operation.targetEndDescriptor.text,
|
|
23396
|
-
targetRef: operation.targetEndDescriptor.index,
|
|
23397
|
-
targetDescriptor: operation.targetEndDescriptor,
|
|
23398
|
-
opType: "restore",
|
|
23399
|
-
strictAmbiguity: strictTargets,
|
|
23400
|
-
paragraphMetadataIndex: currentMetadataIndex,
|
|
23401
|
-
metadataIndices,
|
|
23402
|
-
onInfo: options.onInfo,
|
|
23403
|
-
onWarn: options.onWarn
|
|
23404
|
-
});
|
|
23405
|
-
const allParagraphs = getDocumentParagraphNodes(xmlDoc);
|
|
23406
|
-
const startIndex = allParagraphs.indexOf(paragraph);
|
|
23407
|
-
const endIndex = allParagraphs.indexOf(endResolved?.paragraph);
|
|
23408
|
-
restorationParagraphs = startIndex >= 0 && endIndex >= startIndex ? allParagraphs.slice(startIndex, endIndex + 1) : null;
|
|
23409
|
-
}
|
|
23526
|
+
const restorationParagraphs = rangeParagraphs("restore");
|
|
23410
23527
|
const replacements = Array.isArray(operation.modified) ? operation.modified : [operation.modified];
|
|
23411
23528
|
if (!restorationParagraphs || restorationParagraphs[0] !== paragraph || replacements.length !== restorationParagraphs.length) {
|
|
23412
23529
|
error2 = {
|
|
@@ -23453,11 +23570,11 @@ function preflightOperations(documentXml, operations, author, options = {}) {
|
|
|
23453
23570
|
if (existingPolicy === "merge-same-author" || existingPolicy === "slice-cross-author") {
|
|
23454
23571
|
const authors = getTrackedChangeAuthors(paragraph);
|
|
23455
23572
|
const opAuthor = String(authorUsed || "").trim().toLowerCase();
|
|
23456
|
-
const allSame = authors.length > 0 && authors.every((a) => a.trim().toLowerCase() === opAuthor);
|
|
23573
|
+
const allSame = authors.length > 0 && !hasUnattributedTrackedChange(paragraph) && authors.every((a) => a.trim().toLowerCase() === opAuthor);
|
|
23457
23574
|
if (!allSame && existingPolicy === "merge-same-author") {
|
|
23458
23575
|
error2 = {
|
|
23459
23576
|
code: "EXISTING_REVISIONS",
|
|
23460
|
-
message: `Target paragraph contains tracked changes from another author (${authors
|
|
23577
|
+
message: `Target paragraph contains tracked changes from another author (${[...authors, ...hasUnattributedTrackedChange(paragraph) ? ["unattributed"] : []].join(", ")}). Use existingRevisions: "slice-cross-author" for a surgical edit that preserves reviewer history; accepting or rejecting revisions requires separate authorization.`,
|
|
23461
23578
|
revisionAuthors: authors,
|
|
23462
23579
|
currentPolicy: existingPolicy
|
|
23463
23580
|
};
|
|
@@ -25543,6 +25660,7 @@ export {
|
|
|
25543
25660
|
canonicalNumberingDefinition,
|
|
25544
25661
|
clearSingleLineListFallbackExplicitSequence,
|
|
25545
25662
|
collectContiguousListParagraphBlock,
|
|
25663
|
+
compareDocumentInspections,
|
|
25546
25664
|
computeDocumentPartsRevisionToken,
|
|
25547
25665
|
computePackageRevisionToken,
|
|
25548
25666
|
computeRevisionToken,
|
|
@@ -25594,6 +25712,7 @@ export {
|
|
|
25594
25712
|
injectCommentsIntoPackage2 as injectCommentsIntoPackage,
|
|
25595
25713
|
insertBodyElementBeforeSectPr,
|
|
25596
25714
|
inspectDocumentParts,
|
|
25715
|
+
inspectionReviewShape,
|
|
25597
25716
|
isLikelyStructuredTableSourceParagraph,
|
|
25598
25717
|
isMarkdownTableText,
|
|
25599
25718
|
isNodeVisibleInRevisionView,
|
|
@@ -25623,6 +25742,7 @@ export {
|
|
|
25623
25742
|
repairKnownContentTypes,
|
|
25624
25743
|
reserveNextNumberingId,
|
|
25625
25744
|
reserveNextNumberingIdPair,
|
|
25745
|
+
resolveExistingRevisionsPolicy,
|
|
25626
25746
|
resolveParagraphRangeByRefs,
|
|
25627
25747
|
resolveSingleLineListFallbackNumberingAction,
|
|
25628
25748
|
resolveTargetParagraph,
|