@ansonlai/docx-redline-js 0.8.2 → 0.8.3

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.
@@ -1,4 +1,4 @@
1
- // @ansonlai/docx-redline-js standalone CommonJS bundle v0.8.2 — https://github.com/AnsonLai/docx-redline-js
1
+ // @ansonlai/docx-redline-js standalone CommonJS bundle v0.8.3 — 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;
@@ -10590,7 +10590,37 @@ async function executeListGeneration(options) {
10590
10590
  }];
10591
10591
  }
10592
10592
  }
10593
- if (generateRedlines && deletionRuns.length > 0) {
10593
+ const sourceParagraphCount = (originalRunModel || []).filter((run) => run.kind === RunKind.PARAGRAPH_START).length;
10594
+ if (generateRedlines && sourceParagraphCount > 0 && (sourceParagraphCount > 1 || deletionRuns.length === 0)) {
10595
+ let segment = null;
10596
+ const flushSegment = () => {
10597
+ if (!segment) return;
10598
+ results.push(serializeToOoxml(segment, null, [], {
10599
+ author,
10600
+ generateRedlines,
10601
+ revisionIdAllocator
10602
+ }));
10603
+ segment = null;
10604
+ };
10605
+ for (const run of originalRunModel) {
10606
+ if (run.kind === RunKind.PARAGRAPH_START) {
10607
+ flushSegment();
10608
+ segment = [{
10609
+ ...run,
10610
+ pPrElement: null,
10611
+ pPrXml: addParagraphMarkRevision(
10612
+ run.pPrElement || run.pPrXml || null,
10613
+ "del",
10614
+ author,
10615
+ revisionIdAllocator
10616
+ )
10617
+ }];
10618
+ } else if (segment && (run.kind === "text" || run.kind === "run")) {
10619
+ segment.push({ ...run, kind: "deletion", author });
10620
+ }
10621
+ }
10622
+ flushSegment();
10623
+ } else if (generateRedlines && deletionRuns.length > 0) {
10594
10624
  const deletedPPr = addParagraphMarkRevision(sourcePPr, "del", author, revisionIdAllocator);
10595
10625
  const deletedParagraph = serializeToOoxml(deletionRuns, deletedPPr, [], {
10596
10626
  author,
@@ -16788,12 +16818,12 @@ function validateParagraphBoundaryMutation(targetParagraph, modifiedText, option
16788
16818
  }
16789
16819
 
16790
16820
  // core/list-targeting.js
16791
- function getFirstDescendantByLocalName(node, localName2) {
16792
- if (!node || typeof node.getElementsByTagNameNS !== "function") return null;
16793
- const namespaced = node.getElementsByTagNameNS(WORD_MAIN_NS, localName2);
16794
- if (namespaced.length > 0) return namespaced[0];
16795
- const anyNs = node.getElementsByTagNameNS("*", localName2);
16796
- return anyNs.length > 0 ? anyNs[0] : null;
16821
+ function getDirectChildByLocalName(node, localName2) {
16822
+ if (!node) return null;
16823
+ for (const child of Array.from(node.childNodes || [])) {
16824
+ if (child?.nodeType === 1 && child.localName === localName2) return child;
16825
+ }
16826
+ return null;
16797
16827
  }
16798
16828
  function readValAttribute(element) {
16799
16829
  if (!element) return null;
@@ -16910,15 +16940,15 @@ function buildListEntriesForInsertion(parsedItems, normalizedTargetText, anchorL
16910
16940
  }
16911
16941
  function getParagraphListInfo(paragraph) {
16912
16942
  if (!paragraph) return null;
16913
- const pPr = getFirstDescendantByLocalName(paragraph, "pPr");
16943
+ const pPr = getDirectChildByLocalName(paragraph, "pPr");
16914
16944
  if (!pPr) return null;
16915
- const numPr = getFirstDescendantByLocalName(pPr, "numPr");
16945
+ const numPr = getDirectChildByLocalName(pPr, "numPr");
16916
16946
  if (!numPr) return null;
16917
- const numIdEl = getFirstDescendantByLocalName(numPr, "numId");
16947
+ const numIdEl = getDirectChildByLocalName(numPr, "numId");
16918
16948
  if (!numIdEl) return null;
16919
16949
  const numId = readValAttribute(numIdEl);
16920
16950
  if (!numId || numId === "0") return null;
16921
- const ilvlEl = getFirstDescendantByLocalName(numPr, "ilvl");
16951
+ const ilvlEl = getDirectChildByLocalName(numPr, "ilvl");
16922
16952
  const ilvlRaw = readValAttribute(ilvlEl);
16923
16953
  const ilvl = Number.parseInt(ilvlRaw || "0", 10);
16924
16954
  return {
@@ -17328,8 +17358,13 @@ function enforceListBindingOnParagraphNodes(nodes, options = {}) {
17328
17358
  }
17329
17359
  return updated;
17330
17360
  }
17361
+ function parseOxmlFragment(oxml) {
17362
+ const text2 = String(oxml || "");
17363
+ const declared = /xmlns:w\s*=/.test(text2);
17364
+ return parseOoxmlSafe(declared ? text2 : `<w:root xmlns:w="${NS_W}">${text2}</w:root>`, "application/xml").doc;
17365
+ }
17331
17366
  function getFirstParagraphFromOxml(oxml) {
17332
- const doc = parseOoxmlSafe(oxml, "application/xml").doc;
17367
+ const doc = parseOxmlFragment(oxml);
17333
17368
  if (!doc) return null;
17334
17369
  const parseError = getXmlParseError(doc);
17335
17370
  if (parseError) return null;
@@ -17356,17 +17391,17 @@ function setElementVal(element, value) {
17356
17391
  element.setAttribute("w:val", String(value));
17357
17392
  }
17358
17393
  function extractFirstParagraphNumIdFromOxml(oxml) {
17359
- const doc = parseOoxmlSafe(oxml, "application/xml").doc;
17394
+ const doc = parseOxmlFragment(oxml);
17360
17395
  if (!doc) return null;
17361
17396
  const parseError = getXmlParseError(doc);
17362
17397
  if (parseError) return null;
17363
17398
  const paragraphs = getDocumentParagraphNodes(doc);
17364
- const firstParagraph = paragraphs[0] || null;
17365
- if (!firstParagraph) return null;
17366
- const numIdNodes = Array.from(firstParagraph.getElementsByTagNameNS("*", "numId"));
17367
- for (const numIdNode of numIdNodes) {
17368
- const numId = getElementId(numIdNode, ["w:val", "val"]);
17369
- if (numId != null) return String(numId);
17399
+ for (const paragraph of paragraphs) {
17400
+ const numIdNodes = Array.from(paragraph.getElementsByTagNameNS("*", "numId"));
17401
+ for (const numIdNode of numIdNodes) {
17402
+ const numId = getElementId(numIdNode, ["w:val", "val"]);
17403
+ if (numId != null) return String(numId);
17404
+ }
17370
17405
  }
17371
17406
  return null;
17372
17407
  }
@@ -17862,6 +17897,8 @@ function createDynamicNumberingIdState(numberingXml, options = {}) {
17862
17897
  const baseNumId = Math.max(minId, maxUsedNumId + 1);
17863
17898
  const baseAbstractNumId = Math.max(minId, maxUsedAbstractNumId + 1);
17864
17899
  return {
17900
+ // Kept so list edits can recognise which existing definition a source list uses and continue it.
17901
+ sourceNumberingXml: String(numberingXml || ""),
17865
17902
  nextNumId: nextAvailableId(baseNumId, usedNumIds, maxPreferred),
17866
17903
  nextAbstractNumId: nextAvailableId(baseAbstractNumId, usedAbstractNumIds, maxPreferred),
17867
17904
  usedNumIds,
@@ -18002,6 +18039,82 @@ function buildExplicitDecimalMultilevelNumberingXml(numId, abstractNumId, startA
18002
18039
  </w:num>
18003
18040
  </w:numbering>`.trim();
18004
18041
  }
18042
+ function isTrackedDeletedParagraph(node) {
18043
+ if (!node || node.nodeType !== 1 || node.localName !== "p") return false;
18044
+ const pPr = Array.from(node.childNodes || []).find((child) => isDirectWordChild(child, "pPr"));
18045
+ const rPr = pPr && Array.from(pPr.childNodes || []).find((child) => isDirectWordChild(child, "rPr"));
18046
+ return !!rPr && Array.from(rPr.childNodes || []).some((child) => isDirectWordChild(child, "del"));
18047
+ }
18048
+ function paragraphNumIdNodes(node) {
18049
+ return Array.from(node?.getElementsByTagNameNS?.("*", "numId") || []).filter((numIdNode) => numIdNode.parentNode?.localName === "numPr");
18050
+ }
18051
+ function levelFormats(abstractNum) {
18052
+ const formats = /* @__PURE__ */ new Map();
18053
+ for (const lvl of Array.from(abstractNum?.getElementsByTagNameNS?.("*", "lvl") || [])) {
18054
+ const ilvl = getElementId2(lvl, ["w:ilvl", "ilvl"]);
18055
+ const fmt = Array.from(lvl.getElementsByTagNameNS("*", "numFmt"))[0]?.getAttribute("w:val") || null;
18056
+ if (ilvl != null && fmt) formats.set(ilvl, fmt);
18057
+ }
18058
+ return formats;
18059
+ }
18060
+ function normalizeListFormat(format) {
18061
+ return format === "bullet" || format === "circle" || format === "square" ? "bullet" : format;
18062
+ }
18063
+ function abstractNumForNumId(numberingDoc, numId) {
18064
+ const num = Array.from(numberingDoc.getElementsByTagNameNS("*", "num")).find((candidate) => getElementId2(candidate, ["w:numId", "numId"]) === numId);
18065
+ const abstractId = num && getElementId2(Array.from(num.getElementsByTagNameNS("*", "abstractNumId"))[0], ["w:val", "val"]);
18066
+ if (abstractId == null) return null;
18067
+ return Array.from(numberingDoc.getElementsByTagNameNS("*", "abstractNum")).find((candidate) => getElementId2(candidate, ["w:abstractNumId", "abstractNumId"]) === abstractId) || null;
18068
+ }
18069
+ function reuseSourceListNumbering(numberingXml, replacementNodes, sourceParagraphs, numberingIdState) {
18070
+ const sourceXml = numberingIdState?.sourceNumberingXml;
18071
+ if (!numberingXml || !String(sourceXml || "").trim() || !Array.isArray(replacementNodes)) return null;
18072
+ const sourceNumIds = /* @__PURE__ */ new Set();
18073
+ for (const paragraph of sourceParagraphs || []) {
18074
+ for (const numIdNode of paragraphNumIdNodes(paragraph)) {
18075
+ const id = getElementId2(numIdNode, ["w:val", "val"]);
18076
+ if (id != null && id !== 0) sourceNumIds.add(id);
18077
+ }
18078
+ }
18079
+ if (sourceNumIds.size !== 1) return null;
18080
+ const [sourceNumId] = sourceNumIds;
18081
+ const sourceDoc = parseOoxml(sourceXml);
18082
+ const payloadDoc = parseOoxml(numberingXml);
18083
+ if (hasXmlParseError(sourceDoc) || hasXmlParseError(payloadDoc)) return null;
18084
+ const sourceAbstract = abstractNumForNumId(sourceDoc, sourceNumId);
18085
+ if (!sourceAbstract) return null;
18086
+ const sourceFormats = levelFormats(sourceAbstract);
18087
+ const newParagraphNumIds = [];
18088
+ for (const node of replacementNodes) {
18089
+ if (isTrackedDeletedParagraph(node)) continue;
18090
+ for (const numIdNode of paragraphNumIdNodes(node)) {
18091
+ const ilvlNode = Array.from(numIdNode.parentNode.childNodes || []).find((child) => child.localName === "ilvl");
18092
+ newParagraphNumIds.push({
18093
+ numId: getElementId2(numIdNode, ["w:val", "val"]),
18094
+ ilvl: ilvlNode ? getElementId2(ilvlNode, ["w:val", "val"]) ?? 0 : 0
18095
+ });
18096
+ }
18097
+ }
18098
+ if (newParagraphNumIds.length === 0) return null;
18099
+ const payloadFormats = /* @__PURE__ */ new Map();
18100
+ for (const { numId, ilvl } of newParagraphNumIds) {
18101
+ if (numId == null) return null;
18102
+ if (!payloadFormats.has(numId)) {
18103
+ const abstract = abstractNumForNumId(payloadDoc, numId);
18104
+ if (!abstract) return null;
18105
+ payloadFormats.set(numId, levelFormats(abstract));
18106
+ }
18107
+ const generated = payloadFormats.get(numId).get(ilvl);
18108
+ const existing = sourceFormats.get(ilvl);
18109
+ if (!generated || !existing || normalizeListFormat(generated) !== normalizeListFormat(existing)) return null;
18110
+ }
18111
+ const clonedNodes = replacementNodes.map((node) => node?.cloneNode ? node.cloneNode(true) : node);
18112
+ for (const node of clonedNodes) {
18113
+ if (isTrackedDeletedParagraph(node)) continue;
18114
+ for (const numIdNode of paragraphNumIdNodes(node)) setElementVal2(numIdNode, sourceNumId);
18115
+ }
18116
+ return { numId: sourceNumId, replacementNodes: clonedNodes };
18117
+ }
18005
18118
  function remapNumberingPayloadForDocument(numberingXml, replacementNodes, numberingIdState) {
18006
18119
  const numberingDoc = parseOoxml(numberingXml);
18007
18120
  if (hasXmlParseError(numberingDoc)) {
@@ -18039,6 +18152,7 @@ function remapNumberingPayloadForDocument(numberingXml, replacementNodes, number
18039
18152
  }
18040
18153
  const clonedNodes = Array.isArray(replacementNodes) ? replacementNodes.map((node) => node?.cloneNode ? node.cloneNode(true) : node) : [];
18041
18154
  for (const node of clonedNodes) {
18155
+ if (isTrackedDeletedParagraph(node)) continue;
18042
18156
  const numIdNodes = Array.from(node?.getElementsByTagNameNS?.("*", "numId") || []);
18043
18157
  for (const numIdNode of numIdNodes) {
18044
18158
  const oldNumRef = getElementId2(numIdNode, ["w:val", "val"]);
@@ -18859,6 +18973,8 @@ function areRevisionTokensEqual(a, b) {
18859
18973
  var attr2 = (node, name) => node?.getAttribute?.(`w:${name}`) || node?.getAttribute?.(name) || "";
18860
18974
  var descendants = (node, name) => Array.from(node?.getElementsByTagNameNS?.(NS_W, name) || []);
18861
18975
  var first = (node, name) => descendants(node, name)[0] || null;
18976
+ var directChild2 = (node, name) => Array.from(node?.childNodes || []).find((child) => child?.nodeType === 1 && child.localName === name && (!child.namespaceURI || child.namespaceURI === NS_W)) || null;
18977
+ var currentPPr = (paragraph) => directChild2(paragraph, "pPr");
18862
18978
  function hasAncestor(node, localName2) {
18863
18979
  let cursor = node?.parentNode;
18864
18980
  while (cursor) {
@@ -18874,11 +18990,11 @@ function parseXml2(xml, partName, required = false) {
18874
18990
  return { doc: parsed.doc, warnings: parsed.warnings || [] };
18875
18991
  }
18876
18992
  function paragraphListProperties(paragraph) {
18877
- const numPr = first(first(paragraph, "pPr"), "numPr");
18993
+ const numPr = directChild2(currentPPr(paragraph), "numPr");
18878
18994
  if (!numPr) return null;
18879
- const numId = attr2(first(numPr, "numId"), "val");
18995
+ const numId = attr2(directChild2(numPr, "numId"), "val");
18880
18996
  if (!numId || numId === "0") return null;
18881
- return { numId, level: Number.parseInt(attr2(first(numPr, "ilvl"), "val") || "0", 10) || 0 };
18997
+ return { numId, level: Number.parseInt(attr2(directChild2(numPr, "ilvl"), "val") || "0", 10) || 0 };
18882
18998
  }
18883
18999
  function parseNumbering(numberingDoc) {
18884
19000
  const abstracts = /* @__PURE__ */ new Map();
@@ -18963,11 +19079,11 @@ function createNumberingResolver(numberingDoc) {
18963
19079
  };
18964
19080
  }
18965
19081
  function headingLevel(paragraph) {
18966
- const pPr = first(paragraph, "pPr");
18967
- const style = attr2(first(pPr, "pStyle"), "val");
19082
+ const pPr = currentPPr(paragraph);
19083
+ const style = attr2(directChild2(pPr, "pStyle"), "val");
18968
19084
  const match = style.match(/^heading\s*([1-9])$/i);
18969
19085
  if (match) return Math.min(Number(match[1]), 6);
18970
- const outline = Number.parseInt(attr2(first(pPr, "outlineLvl"), "val"), 10);
19086
+ const outline = Number.parseInt(attr2(directChild2(pPr, "outlineLvl"), "val"), 10);
18971
19087
  return Number.isInteger(outline) ? Math.min(outline + 1, 6) : null;
18972
19088
  }
18973
19089
  function structuralContext(paragraph, text2) {
@@ -19146,7 +19262,7 @@ function inspectDocumentParts(parts, options = {}) {
19146
19262
  const ids = [...new Set([...descendants(paragraph, "commentRangeStart"), ...descendants(paragraph, "commentReference")].map((node) => attr2(node, "id")).filter(Boolean))];
19147
19263
  const authors = revisionAuthors(paragraph);
19148
19264
  const list = resolveNumbering(paragraphListProperties(paragraph));
19149
- const styleId = attr2(first(first(paragraph, "pPr"), "pStyle"), "val") || null;
19265
+ const styleId = attr2(directChild2(currentPPr(paragraph), "pStyle"), "val") || null;
19150
19266
  const structure = structuralContext(paragraph, text2);
19151
19267
  const index = zeroIndex + 1;
19152
19268
  const provision = list?.label && list.format !== "bullet" ? list.label : null;
@@ -24547,7 +24663,9 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
24547
24663
  paragraphText,
24548
24664
  generateRedlines ? createRevisionMetadata(author, xmlDoc) : null,
24549
24665
  author,
24550
- { generateRedlines }
24666
+ // A wholly inserted paragraph must track its own paragraph mark; otherwise Word's
24667
+ // Reject All removes the text but leaves an empty paragraph behind.
24668
+ { generateRedlines, trackParagraphMark: true }
24551
24669
  );
24552
24670
  const imported = xmlDoc.importNode(plainParagraph, true);
24553
24671
  ensureParagraphIdsOnImportedNode(imported, operationSession);
@@ -24678,6 +24796,14 @@ async function applyToParagraphByExactText(documentXml, targetText, modifiedText
24678
24796
  const extracted = extractReplacementNodes(result.oxml);
24679
24797
  let replacementNodes = removeListPackagingSentinel(extracted.replacementNodes, result.warnings);
24680
24798
  let numberingXml = extracted.numberingXml;
24799
+ if (numberingXml && runtimeContext?.numberingIdState && !useTableScope) {
24800
+ const sourceParagraphs = useListScope ? listScopeEdit.paragraphs : explicitRangeParagraphs || [targetParagraph];
24801
+ const continued = reuseSourceListNumbering(numberingXml, replacementNodes, sourceParagraphs, runtimeContext.numberingIdState);
24802
+ if (continued) {
24803
+ replacementNodes = continued.replacementNodes;
24804
+ numberingXml = null;
24805
+ }
24806
+ }
24681
24807
  if (numberingXml && runtimeContext?.numberingIdState) {
24682
24808
  const normalizedNumbering = remapNumberingPayloadForDocument(numberingXml, replacementNodes, runtimeContext.numberingIdState);
24683
24809
  replacementNodes = normalizedNumbering.replacementNodes;