@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 standalone bundle v0.
|
|
1
|
+
// @ansonlai/docx-redline-js standalone bundle 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;
|
|
@@ -8142,7 +8142,10 @@ var MARKDOWN_PATTERNS = [
|
|
|
8142
8142
|
{ regex: /\*\*\+\+(.+?)\+\+\*\*/, format: { bold: true, underline: true } },
|
|
8143
8143
|
// Bold: **text** or __text__
|
|
8144
8144
|
{ regex: /\*\*(.+?)\*\*/, format: { bold: true } },
|
|
8145
|
-
|
|
8145
|
+
// Underscore delimiters follow CommonMark flanking rules: not intraword, not part of a
|
|
8146
|
+
// longer underscore run, and never around whitespace. So signature lines ("_____")
|
|
8147
|
+
// and snake_case identifiers stay literal text.
|
|
8148
|
+
{ regex: /(?<![A-Za-z0-9_])__(?=[^_\s])(.+?)(?<=[^_\s])__(?![A-Za-z0-9_])/, format: { bold: true } },
|
|
8146
8149
|
// Underline: ++text++
|
|
8147
8150
|
{ regex: /\+\+(.+?)\+\+/, format: { underline: true } },
|
|
8148
8151
|
// Strikethrough: ~~text~~ or ~text~
|
|
@@ -8150,7 +8153,7 @@ var MARKDOWN_PATTERNS = [
|
|
|
8150
8153
|
{ regex: /~(.+?)~/, format: { strikethrough: true } },
|
|
8151
8154
|
// Italic: *text* or _text_ (using lookahead only for compatibility)
|
|
8152
8155
|
{ regex: /\*(?!\*)(.+?)\*(?!\*)/, format: { italic: true } },
|
|
8153
|
-
{ regex: /_(
|
|
8156
|
+
{ regex: /(?<![A-Za-z0-9_])_(?=[^_\s])(.+?)(?<=[^_\s])_(?![A-Za-z0-9_])/, format: { italic: true } }
|
|
8154
8157
|
];
|
|
8155
8158
|
function preprocessMarkdown(text2) {
|
|
8156
8159
|
if (!text2) {
|
|
@@ -10321,6 +10324,23 @@ function withOoxmlSourceType(result) {
|
|
|
10321
10324
|
}
|
|
10322
10325
|
return { ...result, sourceType: classifyOoxmlSourceType(result.oxml) };
|
|
10323
10326
|
}
|
|
10327
|
+
function hasUnattributedTrackedChange(node) {
|
|
10328
|
+
if (!node) return false;
|
|
10329
|
+
for (const localName4 of TRACKED_CHANGE_NAMES) {
|
|
10330
|
+
for (const change of wordElementsByLocalName(node, localName4)) {
|
|
10331
|
+
const author = change.getAttribute?.("w:author") || change.getAttribute?.("author") || (typeof change.getAttributeNS === "function" ? change.getAttributeNS(NS_W, "author") : null);
|
|
10332
|
+
if (!author || !String(author).trim()) return true;
|
|
10333
|
+
}
|
|
10334
|
+
}
|
|
10335
|
+
return false;
|
|
10336
|
+
}
|
|
10337
|
+
function resolveExistingRevisionsPolicy(policy, node, author) {
|
|
10338
|
+
if (policy !== "auto") return policy;
|
|
10339
|
+
const nodes = (Array.isArray(node) ? node : [node]).filter(Boolean);
|
|
10340
|
+
const own = String(author || "").trim().toLowerCase();
|
|
10341
|
+
const foreign = nodes.some((item) => containsTrackedChanges(item) && (hasUnattributedTrackedChange(item) || getTrackedChangeAuthors(item).some((name) => name.trim().toLowerCase() !== own)));
|
|
10342
|
+
return foreign ? "slice-cross-author" : "merge-same-author";
|
|
10343
|
+
}
|
|
10324
10344
|
|
|
10325
10345
|
// engine/paragraph-mark-revisions.js
|
|
10326
10346
|
function localNameOf(node) {
|
|
@@ -15831,7 +15851,7 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
15831
15851
|
const operationWarnings = [];
|
|
15832
15852
|
let normalizedExistingRevisions = false;
|
|
15833
15853
|
let preservedInsertedParagraphMark = null;
|
|
15834
|
-
|
|
15854
|
+
let existingRevisionsPolicy = options.existingRevisions || "merge-same-author";
|
|
15835
15855
|
const keepNormalizedNoOp = existingRevisionsPolicy === "accept-all-first-keep-normalized";
|
|
15836
15856
|
const finalize = (result) => {
|
|
15837
15857
|
const withStatus = { ...result };
|
|
@@ -15879,6 +15899,10 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
15879
15899
|
const parsed = parseOoxmlSafe(inputOoxml, "text/xml");
|
|
15880
15900
|
parseWarnings = parsed.warnings;
|
|
15881
15901
|
let xmlDoc = parsed.doc;
|
|
15902
|
+
if (existingRevisionsPolicy === "auto") {
|
|
15903
|
+
existingRevisionsPolicy = resolveExistingRevisionsPolicy("auto", xmlDoc, author);
|
|
15904
|
+
options = { ...options, existingRevisions: existingRevisionsPolicy };
|
|
15905
|
+
}
|
|
15882
15906
|
const parseError = xmlDoc ? getXmlParseError(xmlDoc) : null;
|
|
15883
15907
|
if (parsed.error || parseError) {
|
|
15884
15908
|
const message = parsed.error?.message || parseError?.textContent || "Could not parse OOXML input.";
|
|
@@ -15913,7 +15937,7 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
15913
15937
|
if (existingRevisionsPolicy === "merge-same-author" || existingRevisionsPolicy === "slice-cross-author") {
|
|
15914
15938
|
const authors = getTrackedChangeAuthors(xmlDoc);
|
|
15915
15939
|
const currentAuthor = String(author || "").trim().toLowerCase();
|
|
15916
|
-
const isSameAuthor = authors.length > 0 && authors.every((a) => a.trim().toLowerCase() === currentAuthor);
|
|
15940
|
+
const isSameAuthor = authors.length > 0 && !hasUnattributedTrackedChange(xmlDoc) && authors.every((a) => a.trim().toLowerCase() === currentAuthor);
|
|
15917
15941
|
if (isSameAuthor) {
|
|
15918
15942
|
const commentIds = getCommentIdsInOoxml(xmlDoc);
|
|
15919
15943
|
if (commentIds.length > 0) {
|
|
@@ -16070,7 +16094,7 @@ async function applyRedlineToOxml(oxml, originalText, modifiedText, options = {}
|
|
|
16070
16094
|
sanitizedText = structuredAnalysis.normalizedMarkdown;
|
|
16071
16095
|
}
|
|
16072
16096
|
}
|
|
16073
|
-
const { cleanText: cleanModifiedText, formatHints } = preprocessMarkdown(sanitizedText);
|
|
16097
|
+
const { cleanText: cleanModifiedText, formatHints } = options.plainTextContent === true ? { cleanText: sanitizedText, formatHints: [] } : preprocessMarkdown(sanitizedText);
|
|
16074
16098
|
const { existingFormatHints, textSpans, paragraphs } = extractFormattingFromOoxml(xmlDoc);
|
|
16075
16099
|
const hasLiteralTextChanges = existingRevisionsPolicy === "slice-cross-author" ? cleanModifiedText !== originalText : cleanModifiedText.trim() !== originalText.trim();
|
|
16076
16100
|
const hasListStructureChange = !hasLiteralTextChanges && options.explicitStructuredContent === true && isListTargetLoose(cleanModifiedText) && !sourceMatchesListShape(paragraphs, cleanModifiedText);
|
|
@@ -19902,6 +19926,70 @@ function inspectDocumentParts(parts, options = {}) {
|
|
|
19902
19926
|
};
|
|
19903
19927
|
}
|
|
19904
19928
|
|
|
19929
|
+
// services/inspection-comparison.js
|
|
19930
|
+
var commentIdentity = (comment) => [comment.author ?? null, comment.text ?? "", comment.anchoredText || ""];
|
|
19931
|
+
function reviewSegments(segments = []) {
|
|
19932
|
+
return segments.filter((segment) => segment.kind !== "baseline").reduce((result, segment) => {
|
|
19933
|
+
const next = {
|
|
19934
|
+
kind: segment.kind,
|
|
19935
|
+
author: segment.author || null,
|
|
19936
|
+
text: segment.text,
|
|
19937
|
+
acceptedStart: segment.acceptedStart ?? null,
|
|
19938
|
+
rejectedStart: segment.rejectedStart ?? null
|
|
19939
|
+
};
|
|
19940
|
+
const previous = result.at(-1);
|
|
19941
|
+
const adjacent = (key) => previous[key] == null && next[key] == null || previous[key] != null && previous[key] + previous.text.length === next[key];
|
|
19942
|
+
if (previous && previous.kind === next.kind && previous.author === next.author && adjacent("acceptedStart") && adjacent("rejectedStart")) previous.text += next.text;
|
|
19943
|
+
else result.push(next);
|
|
19944
|
+
return result;
|
|
19945
|
+
}, []);
|
|
19946
|
+
}
|
|
19947
|
+
function inspectionReviewShape(inspection) {
|
|
19948
|
+
const comments = inspection?.comments || [];
|
|
19949
|
+
return {
|
|
19950
|
+
paragraphs: (inspection?.paragraphs || []).map((paragraph) => ({
|
|
19951
|
+
text: paragraph.exactText,
|
|
19952
|
+
inTable: paragraph.inTable === true,
|
|
19953
|
+
table: paragraph.table ? [paragraph.table.rowIndex, paragraph.table.cellIndex] : null,
|
|
19954
|
+
list: paragraph.list ? [paragraph.list.format, paragraph.list.level, paragraph.list.label] : null,
|
|
19955
|
+
review: {
|
|
19956
|
+
present: paragraph.hasRevisions === true,
|
|
19957
|
+
authors: [...paragraph.revisionAuthors || []].sort(),
|
|
19958
|
+
segments: reviewSegments(paragraph.segments)
|
|
19959
|
+
}
|
|
19960
|
+
})),
|
|
19961
|
+
comments: comments.map((comment) => {
|
|
19962
|
+
const parent = comment.parentCommentId != null ? comments.find((item) => String(item.id) === String(comment.parentCommentId)) : null;
|
|
19963
|
+
return [
|
|
19964
|
+
...commentIdentity(comment),
|
|
19965
|
+
comment.done === true,
|
|
19966
|
+
parent ? commentIdentity(parent) : comment.parentCommentId != null || comment.parentParaId ? "unresolved parent" : null
|
|
19967
|
+
];
|
|
19968
|
+
}).map((item) => JSON.stringify(item)).sort()
|
|
19969
|
+
};
|
|
19970
|
+
}
|
|
19971
|
+
function compareDocumentInspections(expected, actual) {
|
|
19972
|
+
const left = inspectionReviewShape(expected);
|
|
19973
|
+
const right = inspectionReviewShape(actual);
|
|
19974
|
+
const differences = [];
|
|
19975
|
+
const note = (path, a, b) => {
|
|
19976
|
+
if (differences.length < 10 && JSON.stringify(a) !== JSON.stringify(b)) differences.push({ path, expected: a, actual: b });
|
|
19977
|
+
};
|
|
19978
|
+
note("paragraphs.length", left.paragraphs.length, right.paragraphs.length);
|
|
19979
|
+
const count = Math.max(left.paragraphs.length, right.paragraphs.length);
|
|
19980
|
+
for (let index = 0; index < count; index++) {
|
|
19981
|
+
const a = left.paragraphs[index] ?? null;
|
|
19982
|
+
const b = right.paragraphs[index] ?? null;
|
|
19983
|
+
if (!a || !b) {
|
|
19984
|
+
note(`paragraphs[${index}]`, a, b);
|
|
19985
|
+
continue;
|
|
19986
|
+
}
|
|
19987
|
+
for (const key of ["text", "inTable", "table", "list", "review"]) note(`paragraphs[${index}].${key}`, a[key], b[key]);
|
|
19988
|
+
}
|
|
19989
|
+
note("comments", left.comments, right.comments);
|
|
19990
|
+
return { matches: differences.length === 0, differences };
|
|
19991
|
+
}
|
|
19992
|
+
|
|
19905
19993
|
// services/comment-locator.js
|
|
19906
19994
|
function createParagraphTextIndex(paragraph, options = {}) {
|
|
19907
19995
|
const revisionView = options.revisionView === "current" ? "accepted" : options.revisionView || "accepted";
|
|
@@ -20376,7 +20464,7 @@ function applyCommentReplyToParts({ commentsXml, commentsExtendedXml = null, doc
|
|
|
20376
20464
|
extendedDoc = parsed.doc;
|
|
20377
20465
|
}
|
|
20378
20466
|
const occupied = usedParaIds(commentsDoc, extendedDoc);
|
|
20379
|
-
if (documentXml) for (const match of String(documentXml).matchAll(
|
|
20467
|
+
if (documentXml) for (const match of String(documentXml).matchAll(/\bw14:paraId="([0-9A-Fa-f]{8})"/g)) occupied.add(match[1].toUpperCase());
|
|
20380
20468
|
const parentParagraph = commentThreadParagraph(parent);
|
|
20381
20469
|
if (!parentParagraph) return { status: "error", error: { code: "PARENT_COMMENT_INVALID", message: `Parent comment '${parentCommentId}' has no paragraph.` } };
|
|
20382
20470
|
let parentParaId = commentParaId(parent);
|
|
@@ -22678,6 +22766,7 @@ var SUPPORTED_OPERATION_TYPES = /* @__PURE__ */ new Set([
|
|
|
22678
22766
|
"highlight"
|
|
22679
22767
|
]);
|
|
22680
22768
|
var EXISTING_REVISIONS_POLICIES = Object.freeze([
|
|
22769
|
+
"auto",
|
|
22681
22770
|
"merge-same-author",
|
|
22682
22771
|
"slice-cross-author",
|
|
22683
22772
|
"reject-input",
|
|
@@ -26732,13 +26821,17 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
26732
26821
|
}
|
|
26733
26822
|
};
|
|
26734
26823
|
}
|
|
26824
|
+
const autoExistingRevisions = options.existingRevisions === "auto";
|
|
26825
|
+
if (autoExistingRevisions) {
|
|
26826
|
+
options = { ...options, existingRevisions: resolveExistingRevisionsPolicy("auto", targetParagraph, author) };
|
|
26827
|
+
}
|
|
26735
26828
|
const hasRevisions = containsTrackedChanges(targetParagraph);
|
|
26736
26829
|
const existingPolicy = options.existingRevisions || "merge-same-author";
|
|
26737
26830
|
if (hasRevisions && existingPolicy !== "accept-all-first" && existingPolicy !== "accept-all-first-keep-normalized") {
|
|
26738
26831
|
if (existingPolicy === "merge-same-author" || existingPolicy === "slice-cross-author") {
|
|
26739
26832
|
const authors = getTrackedChangeAuthors(targetParagraph);
|
|
26740
26833
|
const opAuthor = String(author || "").trim().toLowerCase();
|
|
26741
|
-
const allSame = authors.length > 0 && authors.every((a) => a.trim().toLowerCase() === opAuthor);
|
|
26834
|
+
const allSame = authors.length > 0 && !hasUnattributedTrackedChange(targetParagraph) && authors.every((a) => a.trim().toLowerCase() === opAuthor);
|
|
26742
26835
|
if (!allSame && existingPolicy === "merge-same-author") {
|
|
26743
26836
|
return {
|
|
26744
26837
|
documentXml,
|
|
@@ -26747,7 +26840,7 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
26747
26840
|
status: "error",
|
|
26748
26841
|
error: {
|
|
26749
26842
|
code: "EXISTING_REVISIONS",
|
|
26750
|
-
message: `Target paragraph contains tracked changes from another author (${authors
|
|
26843
|
+
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.`,
|
|
26751
26844
|
revisionAuthors: authors,
|
|
26752
26845
|
currentPolicy: existingPolicy
|
|
26753
26846
|
}
|
|
@@ -26889,6 +26982,9 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
26889
26982
|
onWarn
|
|
26890
26983
|
}) : typeof targetText === "string" && /\r?\n/.test(targetText) ? findReconstructionParagraphRange(xmlDoc, targetText) : null;
|
|
26891
26984
|
const hasExplicitRangeScope = Array.isArray(explicitRangeParagraphs) && explicitRangeParagraphs.length > 0;
|
|
26985
|
+
if (autoExistingRevisions && hasExplicitRangeScope) {
|
|
26986
|
+
options = { ...options, existingRevisions: resolveExistingRevisionsPolicy("auto", explicitRangeParagraphs, author) };
|
|
26987
|
+
}
|
|
26892
26988
|
if (!useTableScope && hasExplicitRangeScope) {
|
|
26893
26989
|
const insertionEntries = buildExplicitRangeInsertionEntries(explicitRangeParagraphs, effectiveModifiedText);
|
|
26894
26990
|
if (insertionEntries && insertionEntries.length > 0) {
|
|
@@ -27214,6 +27310,7 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
|
|
|
27214
27310
|
pairReplacements: options.pairReplacements === true,
|
|
27215
27311
|
insertionAffinity: options.insertionAffinity || null,
|
|
27216
27312
|
literalTextEdit: options.literalTextEdit === true,
|
|
27313
|
+
plainTextContent: options.plainTextContent === true,
|
|
27217
27314
|
_revisionIdAllocator: revisionIdAllocator,
|
|
27218
27315
|
_isolatedTableCell: useTableScope
|
|
27219
27316
|
});
|
|
@@ -28224,7 +28321,31 @@ function verifyUntrackedLocality({ before, after, targetSourceIndex, beforeViews
|
|
|
28224
28321
|
}
|
|
28225
28322
|
return violations.length ? { ok: false, violations } : { ok: true };
|
|
28226
28323
|
}
|
|
28227
|
-
|
|
28324
|
+
var numberingDefinitionCache = /* @__PURE__ */ new Map();
|
|
28325
|
+
var NUMBERING_DEFINITION_CACHE_SIZE = 8;
|
|
28326
|
+
function parseNumberingDefinitions(text2) {
|
|
28327
|
+
const cached = numberingDefinitionCache.get(text2);
|
|
28328
|
+
if (cached) return cached;
|
|
28329
|
+
const abstractLevels = /* @__PURE__ */ new Map();
|
|
28330
|
+
for (const match of text2.matchAll(/<(?:\w+:)?abstractNum\b([^>]*)>([\s\S]*?)<\/(?:\w+:)?abstractNum>/g)) {
|
|
28331
|
+
const id = /abstractNumId="([^"]*)"/.exec(match[1])?.[1];
|
|
28332
|
+
if (id == null) continue;
|
|
28333
|
+
const levels = abstractLevels.get(id) || /* @__PURE__ */ new Set();
|
|
28334
|
+
for (const lvl of match[2].matchAll(/<(?:\w+:)?lvl\b[^>]*?ilvl="([^"]*)"/g)) levels.add(lvl[1]);
|
|
28335
|
+
abstractLevels.set(id, levels);
|
|
28336
|
+
}
|
|
28337
|
+
const numToAbstract = /* @__PURE__ */ new Map();
|
|
28338
|
+
for (const match of text2.matchAll(/<(?:\w+:)?num\b([^>]*)>([\s\S]*?)<\/(?:\w+:)?num>/g)) {
|
|
28339
|
+
const id = /numId="([^"]*)"/.exec(match[1])?.[1];
|
|
28340
|
+
const abstractId = /abstractNumId[^>]*?val="([^"]*)"/.exec(match[2])?.[1];
|
|
28341
|
+
if (id != null && abstractId != null) numToAbstract.set(id, abstractId);
|
|
28342
|
+
}
|
|
28343
|
+
const definitions = { abstractLevels, numToAbstract };
|
|
28344
|
+
if (numberingDefinitionCache.size >= NUMBERING_DEFINITION_CACHE_SIZE) numberingDefinitionCache.delete(numberingDefinitionCache.keys().next().value);
|
|
28345
|
+
numberingDefinitionCache.set(text2, definitions);
|
|
28346
|
+
return definitions;
|
|
28347
|
+
}
|
|
28348
|
+
function verifyNumberingReferences({ before, after, numberingXmls }) {
|
|
28228
28349
|
const references = (documentNode) => {
|
|
28229
28350
|
const found = /* @__PURE__ */ new Map();
|
|
28230
28351
|
for (const numIdNode of getAllReferencedNumIdElements(documentNode)) {
|
|
@@ -28240,21 +28361,7 @@ function verifyNumberingReferences({ before, after, numberingXmls, beforeViews =
|
|
|
28240
28361
|
const used = references(after);
|
|
28241
28362
|
if (used.size === 0) return { ok: true };
|
|
28242
28363
|
const known = references(before);
|
|
28243
|
-
const
|
|
28244
|
-
const abstractLevels = /* @__PURE__ */ new Map();
|
|
28245
|
-
for (const match of text2.matchAll(/<(?:\w+:)?abstractNum\b([^>]*)>([\s\S]*?)<\/(?:\w+:)?abstractNum>/g)) {
|
|
28246
|
-
const id = /abstractNumId="([^"]*)"/.exec(match[1])?.[1];
|
|
28247
|
-
if (id == null) continue;
|
|
28248
|
-
const levels = abstractLevels.get(id) || /* @__PURE__ */ new Set();
|
|
28249
|
-
for (const lvl of match[2].matchAll(/<(?:\w+:)?lvl\b[^>]*?ilvl="([^"]*)"/g)) levels.add(lvl[1]);
|
|
28250
|
-
abstractLevels.set(id, levels);
|
|
28251
|
-
}
|
|
28252
|
-
const numToAbstract = /* @__PURE__ */ new Map();
|
|
28253
|
-
for (const match of text2.matchAll(/<(?:\w+:)?num\b([^>]*)>([\s\S]*?)<\/(?:\w+:)?num>/g)) {
|
|
28254
|
-
const id = /numId="([^"]*)"/.exec(match[1])?.[1];
|
|
28255
|
-
const abstractId = /abstractNumId[^>]*?val="([^"]*)"/.exec(match[2])?.[1];
|
|
28256
|
-
if (id != null && abstractId != null) numToAbstract.set(id, abstractId);
|
|
28257
|
-
}
|
|
28364
|
+
const { abstractLevels, numToAbstract } = parseNumberingDefinitions((numberingXmls || []).filter(Boolean).join("\n"));
|
|
28258
28365
|
const violations = [];
|
|
28259
28366
|
for (const [numId, levels] of used) {
|
|
28260
28367
|
if (known.has(numId)) continue;
|
|
@@ -28603,6 +28710,7 @@ async function applyOperationToDocumentXml(documentXml, op, author, runtimeConte
|
|
|
28603
28710
|
...operation.existingRevisions ? { existingRevisions: operation.existingRevisions } : {},
|
|
28604
28711
|
structuredContent: typeof operation.structuredContent === "boolean" ? operation.structuredContent : options.structuredContent !== false,
|
|
28605
28712
|
explicitStructuredContent: operation.structuredContent === true,
|
|
28713
|
+
plainTextContent: operation.structuredContent === false || operation.structuredContent == null && options.structuredContent === false,
|
|
28606
28714
|
...operation._localizedReplacementCompilation && operation.structuredContent !== true ? { literalTextEdit: true } : {},
|
|
28607
28715
|
pairReplacements: typeof operation.pairReplacements === "boolean" ? operation.pairReplacements : options.pairReplacements !== false,
|
|
28608
28716
|
...operation.insertionAffinity ? { insertionAffinity: operation.insertionAffinity } : {},
|
|
@@ -29857,35 +29965,44 @@ function preflightOperations(documentXml, operations, author, options = {}) {
|
|
|
29857
29965
|
const anchor = operation.operationKind === "comment" ? operation.textToComment || paragraphText : operation.operationKind === "highlight" ? operation.textToHighlight : operation.operationKind === "format" ? operation.textToFormat : null;
|
|
29858
29966
|
const anchorResolution = operation.operationKind === "comment" && anchor != null ? resolveTextInParagraphIndex(createParagraphTextIndex(paragraph, { revisionView: targetView }), anchor) : null;
|
|
29859
29967
|
const anchorFound = anchor == null || (anchorResolution ? anchorResolution.found : paragraphText.includes(anchor));
|
|
29968
|
+
const rangeParagraphs = (opType) => {
|
|
29969
|
+
if (operation.targetEndRef) {
|
|
29970
|
+
return resolveParagraphRangeByRefs(xmlDoc, operation.targetRef, operation.targetEndRef, {
|
|
29971
|
+
opType,
|
|
29972
|
+
targetRefSnapshot: options.targetRefSnapshot || null,
|
|
29973
|
+
onInfo: options.onInfo,
|
|
29974
|
+
onWarn: options.onWarn
|
|
29975
|
+
});
|
|
29976
|
+
}
|
|
29977
|
+
if (!operation.targetEndDescriptor) return [paragraph];
|
|
29978
|
+
const endResolved = resolveTargetParagraph(xmlDoc, {
|
|
29979
|
+
targetText: operation.targetEndDescriptor.text,
|
|
29980
|
+
targetRef: operation.targetEndDescriptor.index,
|
|
29981
|
+
targetDescriptor: operation.targetEndDescriptor,
|
|
29982
|
+
opType,
|
|
29983
|
+
strictAmbiguity: strictTargets,
|
|
29984
|
+
paragraphMetadataIndex: currentMetadataIndex,
|
|
29985
|
+
metadataIndices,
|
|
29986
|
+
onInfo: options.onInfo,
|
|
29987
|
+
onWarn: options.onWarn
|
|
29988
|
+
});
|
|
29989
|
+
const allParagraphs = getDocumentParagraphNodes(xmlDoc);
|
|
29990
|
+
const startIndex = allParagraphs.indexOf(paragraph);
|
|
29991
|
+
const endIndex = allParagraphs.indexOf(endResolved?.paragraph);
|
|
29992
|
+
return startIndex >= 0 && endIndex >= startIndex ? allParagraphs.slice(startIndex, endIndex + 1) : null;
|
|
29993
|
+
};
|
|
29860
29994
|
const hasRevisions = containsTrackedChanges(paragraph);
|
|
29861
|
-
const
|
|
29995
|
+
const requestedPolicy = operation.existingRevisions || options.existingRevisions || "merge-same-author";
|
|
29996
|
+
const existingPolicy = resolveExistingRevisionsPolicy(
|
|
29997
|
+
requestedPolicy,
|
|
29998
|
+
requestedPolicy === "auto" && operation.operationKind === "redline" && (operation.targetEndRef || operation.targetEndDescriptor) ? rangeParagraphs("redline") || paragraph : paragraph,
|
|
29999
|
+
authorUsed
|
|
30000
|
+
);
|
|
29862
30001
|
const deletingWholeParagraph = operation.operationKind === "redline" && operation.modified === "";
|
|
29863
30002
|
const commentIds = deletingWholeParagraph ? getCommentIdsInParagraph(paragraph) : [];
|
|
29864
30003
|
let error2 = null;
|
|
29865
30004
|
if (operation.operationKind === "restore") {
|
|
29866
|
-
|
|
29867
|
-
opType: "restore",
|
|
29868
|
-
targetRefSnapshot: options.targetRefSnapshot || null,
|
|
29869
|
-
onInfo: options.onInfo,
|
|
29870
|
-
onWarn: options.onWarn
|
|
29871
|
-
}) : [paragraph];
|
|
29872
|
-
if (!operation.targetEndRef && operation.targetEndDescriptor) {
|
|
29873
|
-
const endResolved = resolveTargetParagraph(xmlDoc, {
|
|
29874
|
-
targetText: operation.targetEndDescriptor.text,
|
|
29875
|
-
targetRef: operation.targetEndDescriptor.index,
|
|
29876
|
-
targetDescriptor: operation.targetEndDescriptor,
|
|
29877
|
-
opType: "restore",
|
|
29878
|
-
strictAmbiguity: strictTargets,
|
|
29879
|
-
paragraphMetadataIndex: currentMetadataIndex,
|
|
29880
|
-
metadataIndices,
|
|
29881
|
-
onInfo: options.onInfo,
|
|
29882
|
-
onWarn: options.onWarn
|
|
29883
|
-
});
|
|
29884
|
-
const allParagraphs = getDocumentParagraphNodes(xmlDoc);
|
|
29885
|
-
const startIndex = allParagraphs.indexOf(paragraph);
|
|
29886
|
-
const endIndex = allParagraphs.indexOf(endResolved?.paragraph);
|
|
29887
|
-
restorationParagraphs = startIndex >= 0 && endIndex >= startIndex ? allParagraphs.slice(startIndex, endIndex + 1) : null;
|
|
29888
|
-
}
|
|
30005
|
+
const restorationParagraphs = rangeParagraphs("restore");
|
|
29889
30006
|
const replacements = Array.isArray(operation.modified) ? operation.modified : [operation.modified];
|
|
29890
30007
|
if (!restorationParagraphs || restorationParagraphs[0] !== paragraph || replacements.length !== restorationParagraphs.length) {
|
|
29891
30008
|
error2 = {
|
|
@@ -29932,11 +30049,11 @@ function preflightOperations(documentXml, operations, author, options = {}) {
|
|
|
29932
30049
|
if (existingPolicy === "merge-same-author" || existingPolicy === "slice-cross-author") {
|
|
29933
30050
|
const authors = getTrackedChangeAuthors(paragraph);
|
|
29934
30051
|
const opAuthor = String(authorUsed || "").trim().toLowerCase();
|
|
29935
|
-
const allSame = authors.length > 0 && authors.every((a) => a.trim().toLowerCase() === opAuthor);
|
|
30052
|
+
const allSame = authors.length > 0 && !hasUnattributedTrackedChange(paragraph) && authors.every((a) => a.trim().toLowerCase() === opAuthor);
|
|
29936
30053
|
if (!allSame && existingPolicy === "merge-same-author") {
|
|
29937
30054
|
error2 = {
|
|
29938
30055
|
code: "EXISTING_REVISIONS",
|
|
29939
|
-
message: `Target paragraph contains tracked changes from another author (${authors
|
|
30056
|
+
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.`,
|
|
29940
30057
|
revisionAuthors: authors,
|
|
29941
30058
|
currentPolicy: existingPolicy
|
|
29942
30059
|
};
|
|
@@ -32022,6 +32139,7 @@ export {
|
|
|
32022
32139
|
canonicalNumberingDefinition,
|
|
32023
32140
|
clearSingleLineListFallbackExplicitSequence,
|
|
32024
32141
|
collectContiguousListParagraphBlock,
|
|
32142
|
+
compareDocumentInspections,
|
|
32025
32143
|
computeDocumentPartsRevisionToken,
|
|
32026
32144
|
computePackageRevisionToken,
|
|
32027
32145
|
computeRevisionToken,
|
|
@@ -32073,6 +32191,7 @@ export {
|
|
|
32073
32191
|
injectCommentsIntoPackage2 as injectCommentsIntoPackage,
|
|
32074
32192
|
insertBodyElementBeforeSectPr,
|
|
32075
32193
|
inspectDocumentParts,
|
|
32194
|
+
inspectionReviewShape,
|
|
32076
32195
|
isLikelyStructuredTableSourceParagraph,
|
|
32077
32196
|
isMarkdownTableText,
|
|
32078
32197
|
isNodeVisibleInRevisionView,
|
|
@@ -32102,6 +32221,7 @@ export {
|
|
|
32102
32221
|
repairKnownContentTypes,
|
|
32103
32222
|
reserveNextNumberingId,
|
|
32104
32223
|
reserveNextNumberingIdPair,
|
|
32224
|
+
resolveExistingRevisionsPolicy,
|
|
32105
32225
|
resolveParagraphRangeByRefs,
|
|
32106
32226
|
resolveSingleLineListFallbackNumberingAction,
|
|
32107
32227
|
resolveTargetParagraph,
|