@stll/folio-core 0.33.0 → 0.33.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/dist/ai-edits/apply.d.ts +15 -0
- package/dist/ai-edits/apply.js +135 -24
- package/dist/ai-edits/headless.d.ts +47 -3
- package/dist/ai-edits/headless.js +53 -3
- package/dist/ai-edits/index.d.ts +1 -1
- package/dist/ai-edits/snapshot.d.ts +19 -1
- package/dist/ai-edits/snapshot.js +25 -3
- package/dist/ai-edits/table-geometry.d.ts +56 -0
- package/dist/ai-edits/table-geometry.js +217 -0
- package/dist/ai-edits/table-row-column-mutations.d.ts +34 -16
- package/dist/ai-edits/table-row-column-mutations.js +12 -4
- package/dist/ai-edits/table-template.d.ts +60 -0
- package/dist/ai-edits/table-template.js +176 -0
- package/dist/ai-edits/types.d.ts +7 -0
- package/dist/compare/__fixtures__/body-sequence.d.ts +86 -4
- package/dist/compare/__fixtures__/body-sequence.js +169 -12
- package/dist/compare/compare.d.ts +15 -8
- package/dist/compare/compare.js +138 -34
- package/dist/compare/plan.d.ts +31 -1
- package/dist/compare/plan.js +277 -28
- package/dist/compare/reproducible-package.d.ts +7 -3
- package/dist/compare/reproducible-package.js +20 -3
- package/dist/compare/types.d.ts +20 -3
- package/dist/compare/types.js +13 -1
- package/dist/compare/verification.d.ts +49 -2
- package/dist/compare/verification.js +115 -1
- package/dist/compat/eigenpal.d.ts +5 -5
- package/dist/compat/eigenpal.js +3 -3
- package/dist/document-operations.d.ts +10 -1
- package/dist/document-operations.js +3 -2
- package/dist/docx/appVersionNormalization.d.ts +49 -0
- package/dist/docx/appVersionNormalization.js +74 -0
- package/dist/docx/blockContentParser.js +3 -2
- package/dist/docx/drawingUtils.js +4 -3
- package/dist/docx/hyperlinkParser.d.ts +9 -1
- package/dist/docx/hyperlinkParser.js +19 -13
- package/dist/docx/paraIdRangeNormalization.d.ts +40 -0
- package/dist/docx/paraIdRangeNormalization.js +64 -0
- package/dist/docx/paragraphParser.js +82 -6
- package/dist/docx/revisionIdNormalization.d.ts +15 -1
- package/dist/docx/revisionIdNormalization.js +22 -4
- package/dist/docx/rezip.d.ts +13 -1
- package/dist/docx/rezip.js +80 -21
- package/dist/docx/runParser.js +9 -8
- package/dist/docx/sdtProperties.js +4 -3
- package/dist/docx/selectiveSave.js +7 -6
- package/dist/docx/serializer/commentSerializer.d.ts +1 -1
- package/dist/docx/serializer/commentSerializer.js +43 -19
- package/dist/docx/serializer/documentSerializer.d.ts +3 -1
- package/dist/docx/serializer/documentSerializer.js +42 -78
- package/dist/docx/serializer/fontTableSerializer.js +8 -5
- package/dist/docx/serializer/headerFooterSerializer.d.ts +4 -1
- package/dist/docx/serializer/headerFooterSerializer.js +34 -29
- package/dist/docx/serializer/noteSerializer.js +34 -30
- package/dist/docx/serializer/numberingSerializer.js +8 -4
- package/dist/docx/serializer/paragraphSerializer.js +45 -21
- package/dist/docx/serializer/partNamespaces.d.ts +78 -0
- package/dist/docx/serializer/partNamespaces.js +324 -0
- package/dist/docx/serializer/runSerializer.js +1 -1
- package/dist/docx/serializer/settingsSerializer.js +8 -2
- package/dist/docx/serializer/stylesSerializer.js +8 -5
- package/dist/docx/serializer/tableSerializer.js +107 -27
- package/dist/docx/serializer/themeSerializer.js +10 -2
- package/dist/docx/server/build.d.ts +1 -1
- package/dist/docx/strictValueEncodings.gen.d.ts +15 -0
- package/dist/docx/strictValueEncodings.gen.js +275 -0
- package/dist/docx/tableParser.d.ts +0 -6
- package/dist/docx/tableParser.js +52 -7
- package/dist/docx/transitionalSpelling.d.ts +23 -0
- package/dist/docx/transitionalSpelling.js +36 -0
- package/dist/docx/universalMeasure.d.ts +20 -0
- package/dist/docx/universalMeasure.js +32 -0
- package/dist/docx/verbatimCapture.d.ts +20 -0
- package/dist/docx/verbatimCapture.js +131 -0
- package/dist/docx/vmlImageParser.js +4 -3
- package/dist/docx/watermarkParser.js +4 -3
- package/dist/docx/xmlParser.d.ts +23 -2
- package/dist/docx/xmlParser.js +43 -11
- package/dist/index.d.ts +5 -5
- package/dist/index.js +3 -3
- package/dist/model.d.ts +3 -3
- package/dist/model.js +2 -2
- package/dist/prosemirror/commands/comments.js +8 -2
- package/dist/prosemirror/containerFinalParagraph.d.ts +47 -0
- package/dist/prosemirror/containerFinalParagraph.js +92 -0
- package/dist/prosemirror/conversion/fromProseDoc.d.ts +8 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +15 -4
- package/dist/prosemirror/conversion/toProseDoc.js +12 -3
- package/dist/prosemirror/extensions/nodes/TableExtension.js +5 -0
- package/dist/prosemirror/schema/marks.d.ts +1 -1
- package/dist/prosemirror/schema/nodes.d.ts +22 -0
- package/dist/server.d.ts +1 -1
- package/dist/types/block-id.d.ts +12 -1
- package/dist/types/block-id.js +17 -1
- package/dist/utils/canonicalJson.d.ts +14 -0
- package/dist/utils/canonicalJson.js +19 -0
- package/dist/utils/formatToStyle.d.ts +1 -1
- package/package.json +2 -2
|
@@ -2,14 +2,16 @@ import { isValidHexColor } from "../utils/colorResolver.js";
|
|
|
2
2
|
import { isValidHexId } from "../utils/hexId.js";
|
|
3
3
|
import { parseBookmarkEnd as parseBookmarkEnd$1, parseBookmarkStart as parseBookmarkStart$1 } from "./bookmarkParser.js";
|
|
4
4
|
import { parseFieldType } from "./fieldParser.js";
|
|
5
|
-
import { parseHyperlink as parseHyperlink$1 } from "./hyperlinkParser.js";
|
|
5
|
+
import { parseHyperlink as parseHyperlink$1, parseHyperlinkChild } from "./hyperlinkParser.js";
|
|
6
6
|
import { markerFormattingFromLevel } from "./numberingParser.js";
|
|
7
|
+
import { paraIdInRange } from "./paraIdRangeNormalization.js";
|
|
7
8
|
import { BorderStyleSchema, FrameWrapSchema, FrameXAlignSchema, FrameYAlignSchema, LineSpacingRuleSchema, ParagraphAlignmentSchema, ShadingPatternSchema, TabLeaderSchema, TabStopAlignmentSchema, ThemeColorSlotSchema, narrowEnum } from "./parserEnums.js";
|
|
8
9
|
import { consolidateParagraphContent } from "./runConsolidator.js";
|
|
9
10
|
import { parseRun, parseRunProperties } from "./runParser.js";
|
|
10
11
|
import { parseSdtProperties } from "./sdtProperties.js";
|
|
11
12
|
import { parseSectionProperties } from "./sectionParser.js";
|
|
12
|
-
import {
|
|
13
|
+
import { captureVerbatimXml } from "./verbatimCapture.js";
|
|
14
|
+
import { WORDPROCESSINGML_NAMESPACE_URIS, findChild, findChildByNamespaceUri, findChildren, getAttribute, getChildElements, getLocalName, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseNumberingLevelAttribute, parseNumericAttribute } from "./xmlParser.js";
|
|
13
15
|
import { panic } from "better-result";
|
|
14
16
|
import { PARAGRAPH_MARK_CHANGE_KINDS, normalizeRevisionId } from "@stll/docx-core/model";
|
|
15
17
|
//#region src/docx/paragraphParser.ts
|
|
@@ -646,6 +648,80 @@ function pushInlineSdtSegments({ contents, properties, parsedContent }) {
|
|
|
646
648
|
function parseHyperlink(node, rels, styles, theme, media, rootXmlns = {}) {
|
|
647
649
|
return parseHyperlink$1(node, rels, styles, theme, media, rootXmlns);
|
|
648
650
|
}
|
|
651
|
+
/** The revision wrapper a `w:hyperlink` child is, when it is one. */
|
|
652
|
+
const hyperlinkRevisionWrapperType = (node) => {
|
|
653
|
+
switch (getLocalName(node.name)) {
|
|
654
|
+
case "ins": return "insertion";
|
|
655
|
+
case "del": return "deletion";
|
|
656
|
+
case "moveFrom": return "moveFrom";
|
|
657
|
+
case "moveTo": return "moveTo";
|
|
658
|
+
default: return;
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
const isHyperlinkChildContent = (content) => content.type === "run" || content.type === "bookmarkStart" || content.type === "bookmarkEnd";
|
|
662
|
+
/**
|
|
663
|
+
* A `w:hyperlink` as paragraph content, with any revision wrapper it holds
|
|
664
|
+
* hoisted around it.
|
|
665
|
+
*
|
|
666
|
+
* OOXML nests `w:ins`/`w:del` INSIDE `w:hyperlink`; the model nests the
|
|
667
|
+
* hyperlink inside the revision, because a revision is the unit a redline
|
|
668
|
+
* reads and a link that is half deleted is two links to it. This is the exact
|
|
669
|
+
* inverse of what the serializer writes, so a package survives the round trip.
|
|
670
|
+
*/
|
|
671
|
+
function parseHyperlinkParagraphContents(node, rels, styles, theme, media, rootXmlns) {
|
|
672
|
+
const children = getChildElements(node);
|
|
673
|
+
if (!children.some((child) => hyperlinkRevisionWrapperType(child) !== void 0)) return [parseHyperlink(node, rels, styles, theme, media, rootXmlns)];
|
|
674
|
+
const inScopeXmlns = mergeXmlnsDeclarations(rootXmlns, node);
|
|
675
|
+
const shell = parseHyperlink(node, rels, styles, theme, media, rootXmlns);
|
|
676
|
+
const linkOver = (linkChildren) => ({
|
|
677
|
+
...shell,
|
|
678
|
+
children: [...linkChildren]
|
|
679
|
+
});
|
|
680
|
+
const contents = [];
|
|
681
|
+
let plain = [];
|
|
682
|
+
const flushPlain = () => {
|
|
683
|
+
if (plain.length > 0) {
|
|
684
|
+
contents.push(linkOver(plain));
|
|
685
|
+
plain = [];
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
for (const child of children) {
|
|
689
|
+
const wrapperType = hyperlinkRevisionWrapperType(child);
|
|
690
|
+
if (wrapperType === void 0) {
|
|
691
|
+
const parsed = parseHyperlinkChild(child, styles, theme, rels, media, inScopeXmlns);
|
|
692
|
+
if (parsed) plain.push(parsed);
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
flushPlain();
|
|
696
|
+
const wrapped = parseParagraphContents(child, styles, theme, null, rels, media, wrapperType === "deletion" || wrapperType === "moveFrom" ? "deletion" : "default", inScopeXmlns);
|
|
697
|
+
const content = [];
|
|
698
|
+
let linked = [];
|
|
699
|
+
const flushLinked = () => {
|
|
700
|
+
if (linked.length > 0) {
|
|
701
|
+
content.push(linkOver(linked));
|
|
702
|
+
linked = [];
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
for (const item of wrapped) {
|
|
706
|
+
if (isHyperlinkChildContent(item)) {
|
|
707
|
+
linked.push(item);
|
|
708
|
+
continue;
|
|
709
|
+
}
|
|
710
|
+
flushLinked();
|
|
711
|
+
if (isTrackedChangeWrapperChild(item)) content.push(item);
|
|
712
|
+
}
|
|
713
|
+
flushLinked();
|
|
714
|
+
pushTrackedChangeWrapper({
|
|
715
|
+
contents,
|
|
716
|
+
type: wrapperType,
|
|
717
|
+
info: parseTrackedChangeInfo(child),
|
|
718
|
+
content,
|
|
719
|
+
preserveEmpty: true
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
flushPlain();
|
|
723
|
+
return contents;
|
|
724
|
+
}
|
|
649
725
|
/**
|
|
650
726
|
* Parse bookmark start (w:bookmarkStart)
|
|
651
727
|
* Delegates to bookmarkParser module.
|
|
@@ -798,7 +874,7 @@ function parseParagraphContents(paraElement, styles, theme, _numbering, rels, me
|
|
|
798
874
|
break;
|
|
799
875
|
}
|
|
800
876
|
case "hyperlink":
|
|
801
|
-
contents.push(
|
|
877
|
+
contents.push(...parseHyperlinkParagraphContents(child, rels, styles, theme, media, inScopeXmlns));
|
|
802
878
|
break;
|
|
803
879
|
case "bookmarkStart":
|
|
804
880
|
contents.push(parseBookmarkStart(child));
|
|
@@ -920,7 +996,7 @@ function parseParagraphContents(paraElement, styles, theme, _numbering, rels, me
|
|
|
920
996
|
case "oMath":
|
|
921
997
|
case "oMathPara": {
|
|
922
998
|
const isBlock = localName === "oMathPara";
|
|
923
|
-
const ommlXml =
|
|
999
|
+
const ommlXml = captureVerbatimXml(child);
|
|
924
1000
|
const plainText = extractMathText(child);
|
|
925
1001
|
const mathEq = {
|
|
926
1002
|
type: "mathEquation",
|
|
@@ -961,9 +1037,9 @@ function parseParagraph(node, styles, theme, numbering, rels = null, media = nul
|
|
|
961
1037
|
content: []
|
|
962
1038
|
};
|
|
963
1039
|
const paraId = getAttribute(node, "w14", "paraId") ?? getAttribute(node, "w", "paraId");
|
|
964
|
-
if (paraId && isValidHexId(paraId)) paragraph.paraId = paraId;
|
|
1040
|
+
if (paraId && isValidHexId(paraId)) paragraph.paraId = paraIdInRange(paraId);
|
|
965
1041
|
const textId = getAttribute(node, "w14", "textId") ?? getAttribute(node, "w", "textId");
|
|
966
|
-
if (textId && isValidHexId(textId)) paragraph.textId = textId;
|
|
1042
|
+
if (textId && isValidHexId(textId)) paragraph.textId = paraIdInRange(textId);
|
|
967
1043
|
if (!options?.inHeaderFooter && paragraphStartsWithRenderedPageBreak(node)) paragraph.renderedPageBreakBefore = true;
|
|
968
1044
|
const pPr = findChild(node, "w", "pPr");
|
|
969
1045
|
if (pPr) {
|
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
//#region src/docx/revisionIdNormalization.d.ts
|
|
2
|
+
declare const RevisionIdCollisionError_base: import("better-result").TaggedErrorClass<"RevisionIdCollisionError">;
|
|
3
|
+
/**
|
|
4
|
+
* Two revision elements in one package claimed the same `w:id`.
|
|
5
|
+
*
|
|
6
|
+
* `w:id` on a revision element is unique across the package, so a collision is
|
|
7
|
+
* a package a consumer may reject rather than a cosmetic detail. The
|
|
8
|
+
* normalization below hands every id it emits to one choke point, which throws
|
|
9
|
+
* this rather than letting the duplicate reach the ZIP.
|
|
10
|
+
*/
|
|
11
|
+
declare class RevisionIdCollisionError extends RevisionIdCollisionError_base<{
|
|
12
|
+
message: string;
|
|
13
|
+
revisionId: number;
|
|
14
|
+
part: string;
|
|
15
|
+
}> {}
|
|
2
16
|
declare const REVISION_ELEMENT_NAMES: Set<string>;
|
|
3
17
|
/**
|
|
4
18
|
* Keep physical tracked-change element ids unique across a package.
|
|
@@ -10,4 +24,4 @@ declare const REVISION_ELEMENT_NAMES: Set<string>;
|
|
|
10
24
|
*/
|
|
11
25
|
declare const normalizeRevisionIdsInXmlParts: (parts: ReadonlyMap<string, string>) => Map<string, string>;
|
|
12
26
|
//#endregion
|
|
13
|
-
export { REVISION_ELEMENT_NAMES, normalizeRevisionIdsInXmlParts };
|
|
27
|
+
export { REVISION_ELEMENT_NAMES, RevisionIdCollisionError, normalizeRevisionIdsInXmlParts };
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { rewriteStreamingXmlDecimalAttributes } from "./streamingXmlParser.js";
|
|
2
2
|
import { WORDPROCESSINGML_NAMESPACE_URIS, findAttributeByNamespaceUri, getLocalName, getNamespaceUri } from "./xmlParser.js";
|
|
3
3
|
import { XmlResourceLimitError, assertXmlResourceLimits } from "./xmlResourceLimits.js";
|
|
4
|
+
import { TaggedError } from "better-result";
|
|
4
5
|
//#region src/docx/revisionIdNormalization.ts
|
|
6
|
+
/**
|
|
7
|
+
* Two revision elements in one package claimed the same `w:id`.
|
|
8
|
+
*
|
|
9
|
+
* `w:id` on a revision element is unique across the package, so a collision is
|
|
10
|
+
* a package a consumer may reject rather than a cosmetic detail. The
|
|
11
|
+
* normalization below hands every id it emits to one choke point, which throws
|
|
12
|
+
* this rather than letting the duplicate reach the ZIP.
|
|
13
|
+
*/
|
|
14
|
+
var RevisionIdCollisionError = class extends TaggedError("RevisionIdCollisionError") {};
|
|
5
15
|
const REVISION_ELEMENT_NAMES = /* @__PURE__ */ new Set([
|
|
6
16
|
"cellDel",
|
|
7
17
|
"cellIns",
|
|
@@ -76,19 +86,27 @@ const normalizeRevisionIdsInXmlParts = (parts) => {
|
|
|
76
86
|
for (const [path, xml] of candidates) {
|
|
77
87
|
const ids = occurrencesByPath.get(path);
|
|
78
88
|
if (!ids) continue;
|
|
89
|
+
const claim = (id) => {
|
|
90
|
+
if (seen.has(id)) throw new RevisionIdCollisionError({
|
|
91
|
+
message: `Revision id ${String(id)} is claimed twice in ${path}`,
|
|
92
|
+
revisionId: id,
|
|
93
|
+
part: path
|
|
94
|
+
});
|
|
95
|
+
seen.add(id);
|
|
96
|
+
};
|
|
79
97
|
if (!repeatedPaths.has(path) && ids.every((id) => !seen.has(id))) {
|
|
80
|
-
for (const id of ids)
|
|
98
|
+
for (const id of ids) claim(id);
|
|
81
99
|
continue;
|
|
82
100
|
}
|
|
83
101
|
const rewritten = rewriteStreamingXmlDecimalAttributes(xml, (element) => {
|
|
84
102
|
const attribute = revisionAttribute(element);
|
|
85
103
|
if (!attribute) return null;
|
|
86
104
|
if (!seen.has(attribute.id)) {
|
|
87
|
-
|
|
105
|
+
claim(attribute.id);
|
|
88
106
|
return null;
|
|
89
107
|
}
|
|
90
108
|
const replacement = allocate();
|
|
91
|
-
|
|
109
|
+
claim(replacement);
|
|
92
110
|
return /* @__PURE__ */ new Map([[attribute.name, String(replacement)]]);
|
|
93
111
|
});
|
|
94
112
|
if (rewritten.status === "unsupported") throw new XmlResourceLimitError({
|
|
@@ -100,4 +118,4 @@ const normalizeRevisionIdsInXmlParts = (parts) => {
|
|
|
100
118
|
return normalized;
|
|
101
119
|
};
|
|
102
120
|
//#endregion
|
|
103
|
-
export { REVISION_ELEMENT_NAMES, normalizeRevisionIdsInXmlParts };
|
|
121
|
+
export { REVISION_ELEMENT_NAMES, RevisionIdCollisionError, normalizeRevisionIdsInXmlParts };
|
package/dist/docx/rezip.d.ts
CHANGED
|
@@ -105,6 +105,12 @@ declare function updateMultipleFiles(originalBuffer: ArrayBuffer, updates: Map<s
|
|
|
105
105
|
/**
|
|
106
106
|
* Apply file updates to an already-loaded JSZip instance and generate the output.
|
|
107
107
|
* Use this when the zip is already loaded to avoid a redundant decompression pass.
|
|
108
|
+
*
|
|
109
|
+
* This is the selective save's exit, so it owes the package the same passes
|
|
110
|
+
* {@link generateDocxZip} runs: a save that rewrites only the changed
|
|
111
|
+
* paragraphs still has to see the parts it left alone, both to know which
|
|
112
|
+
* revision ids are free and because an out-of-range paragraph id, or a
|
|
113
|
+
* malformed application version, can sit in a part it never touched.
|
|
108
114
|
*/
|
|
109
115
|
declare function applyUpdatesToZip(zip: JSZip, updates: Map<string, string | ArrayBuffer>, options?: RepackOptions): Promise<ArrayBuffer>;
|
|
110
116
|
/**
|
|
@@ -153,7 +159,13 @@ declare function hasUnmaterializedHeaderFooter(doc: document_d_exports.Document)
|
|
|
153
159
|
* have been set without yet resolving in that header's rels).
|
|
154
160
|
*/
|
|
155
161
|
declare function hasModelDrivenPictureWatermark(doc: document_d_exports.Document): boolean;
|
|
156
|
-
|
|
162
|
+
/**
|
|
163
|
+
* Re-serialize every header/footer the model still owns, keyed by part path.
|
|
164
|
+
*
|
|
165
|
+
* `sourceZip` supplies each part as it stands before the save so the rebuilt
|
|
166
|
+
* root can keep any prefix binding only the source document declared.
|
|
167
|
+
*/
|
|
168
|
+
declare function collectHeaderFooterUpdates(doc: document_d_exports.Document, sourceZip: JSZip): Promise<Map<string, string>>;
|
|
157
169
|
/** `word/Footnotes.xml` -> `word/_rels/Footnotes.xml.rels` (casing preserved). */
|
|
158
170
|
declare function notePartRelsPath(partPath: string): string;
|
|
159
171
|
/** Update existing core-property values without synthesizing absent metadata. */
|
package/dist/docx/rezip.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isAllowedExternalWatermarkImageUrl } from "../watermark/index.js";
|
|
2
|
+
import { normalizeAppVersionInExtendedProperties } from "./appVersionNormalization.js";
|
|
2
3
|
import { withoutOrphanCommentRanges } from "./commentRangeIntegrity.js";
|
|
3
4
|
import { applyReplyThreadMarkers } from "./commentReplyMarkers.js";
|
|
4
5
|
import { parseEndnotes, parseFootnotes } from "./footnoteParser.js";
|
|
@@ -6,6 +7,7 @@ import { assertValidFolioDocumentModel } from "./modelValidation.js";
|
|
|
6
7
|
import { isNewDataUrlDrawing } from "./newImage.js";
|
|
7
8
|
import { parseNumbering } from "./numberingParser.js";
|
|
8
9
|
import { isUnsafePackagePath, reconcilePackageReferences, removeUnsafeEntries } from "./packageParts.js";
|
|
10
|
+
import { normalizeParaIdRangeInXmlParts } from "./paraIdRangeNormalization.js";
|
|
9
11
|
import { RELATIONSHIP_TYPES, parseRelationships, resolveRelativePath } from "./relsParser.js";
|
|
10
12
|
import { normalizeRevisionIdsInXmlParts } from "./revisionIdNormalization.js";
|
|
11
13
|
import { appendNumberingDefs, buildPatchedNotePartXml, buildPatchedNumberingXml, collectAddedNumberingDefs, collectChangedNoteParaIds, collectChangedNumberingDefs, collectParaIds } from "./selectiveXmlPatch.js";
|
|
@@ -15,6 +17,7 @@ import { serializeFontTableXml } from "./serializer/fontTableSerializer.js";
|
|
|
15
17
|
import { serializeHeaderFooter } from "./serializer/headerFooterSerializer.js";
|
|
16
18
|
import { serializeEndnotes, serializeFootnotes, serializeNewEndnotesPart, serializeNewFootnotesPart } from "./serializer/noteSerializer.js";
|
|
17
19
|
import { serializeNumberingXml } from "./serializer/numberingSerializer.js";
|
|
20
|
+
import { readRootNamespaceBindings } from "./serializer/partNamespaces.js";
|
|
18
21
|
import { serializeSettingsXml } from "./serializer/settingsSerializer.js";
|
|
19
22
|
import { serializeStyle, serializeStylesXml } from "./serializer/stylesSerializer.js";
|
|
20
23
|
import { serializeThemeXml } from "./serializer/themeSerializer.js";
|
|
@@ -121,13 +124,12 @@ function assertDocumentPackageFidelity(originalDocumentXml, serializedDocumentXm
|
|
|
121
124
|
async function serializeCommentsToZip(doc, zip, compressionLevel) {
|
|
122
125
|
const comments = doc.package.document.comments ?? [];
|
|
123
126
|
const sourceCommentsFile = findZipEntryCaseInsensitive(zip, "word/comments.xml");
|
|
127
|
+
const sourceCommentsXml = sourceCommentsFile ? await sourceCommentsFile.async("text") : void 0;
|
|
124
128
|
if (comments.length === 0) {
|
|
125
|
-
if (!
|
|
126
|
-
const sourceCommentsXml = await sourceCommentsFile.async("text");
|
|
127
|
-
if (!hasCommentEntries(sourceCommentsXml)) return;
|
|
129
|
+
if (sourceCommentsXml === void 0 || !hasCommentEntries(sourceCommentsXml)) return;
|
|
128
130
|
}
|
|
129
131
|
ensureThreadedCommentParaIds(comments);
|
|
130
|
-
const commentsXml = serializeComments(comments);
|
|
132
|
+
const commentsXml = serializeComments(comments, sourceCommentsXml === void 0 ? void 0 : readRootNamespaceBindings(sourceCommentsXml));
|
|
131
133
|
zip.file(sourceCommentsFile?.name ?? "word/comments.xml", commentsXml, {
|
|
132
134
|
compression: "DEFLATE",
|
|
133
135
|
compressionOptions: { level: compressionLevel }
|
|
@@ -407,19 +409,58 @@ async function processNewHyperlinks(parts, zip, compressionLevel) {
|
|
|
407
409
|
}
|
|
408
410
|
}
|
|
409
411
|
/**
|
|
410
|
-
*
|
|
411
|
-
*
|
|
412
|
-
*
|
|
412
|
+
* Bring the ids a package addresses itself by inside the bounds the format
|
|
413
|
+
* gives them.
|
|
414
|
+
*
|
|
415
|
+
* Both bounds belong to the package rather than to a part, and neither is
|
|
416
|
+
* something one serializer can see on its own. A revision `w:id` is unique
|
|
417
|
+
* across the package, and one logical revision serializes as several physical
|
|
418
|
+
* wrappers — a word diff cut around unchanged words, a revision split around a
|
|
419
|
+
* hyperlink. A paragraph id is 31-bit, and a paragraph is referenced by id
|
|
420
|
+
* from parts other than the one it lives in. So the passes run at the exits,
|
|
421
|
+
* over every `word/*.xml` part including the ones a save left untouched.
|
|
413
422
|
*/
|
|
414
|
-
const
|
|
415
|
-
await reconcilePackageReferences(zip, compressionLevel);
|
|
423
|
+
const normalizePackageIdsInZip = async (zip, compressionLevel) => {
|
|
416
424
|
const xmlParts = /* @__PURE__ */ new Map();
|
|
417
425
|
for (const [path, file] of Object.entries(zip.files)) if (!file.dir && path.startsWith("word/") && path.endsWith(".xml")) xmlParts.set(path, await file.async("text"));
|
|
418
|
-
const normalizedParts = normalizeRevisionIdsInXmlParts(xmlParts);
|
|
426
|
+
const normalizedParts = normalizeParaIdRangeInXmlParts(normalizeRevisionIdsInXmlParts(xmlParts));
|
|
419
427
|
for (const [path, xml] of normalizedParts) if (xml !== xmlParts.get(path)) zip.file(path, xml, {
|
|
420
428
|
compression: "DEFLATE",
|
|
421
429
|
compressionOptions: { level: compressionLevel }
|
|
422
430
|
});
|
|
431
|
+
};
|
|
432
|
+
const EXTENDED_PROPERTIES_PATH = "docProps/app.xml";
|
|
433
|
+
/**
|
|
434
|
+
* Bring the application version the package states about itself into the form
|
|
435
|
+
* the schema gives it. A package that holds no extended properties keeps
|
|
436
|
+
* holding none.
|
|
437
|
+
*/
|
|
438
|
+
const normalizeAppVersionInZip = async (zip, compressionLevel) => {
|
|
439
|
+
const extendedProperties = zip.file(EXTENDED_PROPERTIES_PATH);
|
|
440
|
+
if (!extendedProperties) return;
|
|
441
|
+
const xml = await extendedProperties.async("text");
|
|
442
|
+
const normalized = normalizeAppVersionInExtendedProperties(xml);
|
|
443
|
+
if (normalized !== xml) zip.file(EXTENDED_PROPERTIES_PATH, normalized, {
|
|
444
|
+
compression: "DEFLATE",
|
|
445
|
+
compressionOptions: { level: compressionLevel }
|
|
446
|
+
});
|
|
447
|
+
};
|
|
448
|
+
/**
|
|
449
|
+
* Every pass a save owes the package as a whole, in one function so that the
|
|
450
|
+
* full repack and the selective save cannot drift into running different ones.
|
|
451
|
+
*/
|
|
452
|
+
const normalizePackageOnSave = async (zip, compressionLevel) => {
|
|
453
|
+
await normalizePackageIdsInZip(zip, compressionLevel);
|
|
454
|
+
await normalizeAppVersionInZip(zip, compressionLevel);
|
|
455
|
+
};
|
|
456
|
+
/**
|
|
457
|
+
* The single exit for a repacked package. Reconciliation runs here rather than
|
|
458
|
+
* at each caller so no save path can emit a package whose relationships or
|
|
459
|
+
* content types name a part it does not hold.
|
|
460
|
+
*/
|
|
461
|
+
const generateDocxZip = async (zip, compressionLevel) => {
|
|
462
|
+
await reconcilePackageReferences(zip, compressionLevel);
|
|
463
|
+
await normalizePackageOnSave(zip, compressionLevel);
|
|
423
464
|
return zip.generateAsync({
|
|
424
465
|
type: "arraybuffer",
|
|
425
466
|
compression: "DEFLATE",
|
|
@@ -454,14 +495,14 @@ const finishRepack = async ({ document, originalZip, outputZip, originalDocument
|
|
|
454
495
|
await processNewHyperlinks(parts, outputZip, compressionLevel);
|
|
455
496
|
assertValidFolioDocumentModel(document, "Cannot repack invalid DOCX document model");
|
|
456
497
|
applyReplyThreadMarkers(document);
|
|
457
|
-
const documentXml = serializeDocument(document);
|
|
498
|
+
const documentXml = serializeDocument(document, originalDocumentXml === void 0 ? void 0 : readRootNamespaceBindings(originalDocumentXml));
|
|
458
499
|
if (originalDocumentXml) assertDocumentPackageFidelity(originalDocumentXml, documentXml, document);
|
|
459
500
|
outputZip.file("word/document.xml", documentXml, {
|
|
460
501
|
compression: "DEFLATE",
|
|
461
502
|
compressionOptions: { level: compressionLevel }
|
|
462
503
|
});
|
|
463
504
|
await rebindWatermarkRelIds(document, outputZip, compressionLevel);
|
|
464
|
-
serializeHeadersFootersToZip(document, outputZip, compressionLevel);
|
|
505
|
+
await serializeHeadersFootersToZip(document, outputZip, compressionLevel);
|
|
465
506
|
await serializeNotesToZip({
|
|
466
507
|
doc: document,
|
|
467
508
|
originalZip,
|
|
@@ -543,14 +584,14 @@ async function repackDocxFromRaw(doc, rawContent, options = {}) {
|
|
|
543
584
|
await processNewHyperlinks(parts, newZip, compressionLevel);
|
|
544
585
|
assertValidFolioDocumentModel(exportDocument, "Cannot repack invalid DOCX document model");
|
|
545
586
|
applyReplyThreadMarkers(exportDocument);
|
|
546
|
-
const documentXml = serializeDocument(exportDocument);
|
|
587
|
+
const documentXml = serializeDocument(exportDocument, rawContent.documentXml ? readRootNamespaceBindings(rawContent.documentXml) : void 0);
|
|
547
588
|
if (rawContent.documentXml) assertDocumentPackageFidelity(rawContent.documentXml, documentXml, exportDocument);
|
|
548
589
|
newZip.file("word/document.xml", documentXml, {
|
|
549
590
|
compression: "DEFLATE",
|
|
550
591
|
compressionOptions: { level: compressionLevel }
|
|
551
592
|
});
|
|
552
593
|
await rebindWatermarkRelIds(exportDocument, newZip, compressionLevel);
|
|
553
|
-
serializeHeadersFootersToZip(exportDocument, newZip, compressionLevel);
|
|
594
|
+
await serializeHeadersFootersToZip(exportDocument, newZip, compressionLevel);
|
|
554
595
|
await serializeNotesToZip({
|
|
555
596
|
doc: exportDocument,
|
|
556
597
|
originalZip: rawContent.originalZip,
|
|
@@ -758,14 +799,21 @@ async function updateMultipleFiles(originalBuffer, updates, options = {}) {
|
|
|
758
799
|
/**
|
|
759
800
|
* Apply file updates to an already-loaded JSZip instance and generate the output.
|
|
760
801
|
* Use this when the zip is already loaded to avoid a redundant decompression pass.
|
|
802
|
+
*
|
|
803
|
+
* This is the selective save's exit, so it owes the package the same passes
|
|
804
|
+
* {@link generateDocxZip} runs: a save that rewrites only the changed
|
|
805
|
+
* paragraphs still has to see the parts it left alone, both to know which
|
|
806
|
+
* revision ids are free and because an out-of-range paragraph id, or a
|
|
807
|
+
* malformed application version, can sit in a part it never touched.
|
|
761
808
|
*/
|
|
762
|
-
function applyUpdatesToZip(zip, updates, options = {}) {
|
|
809
|
+
async function applyUpdatesToZip(zip, updates, options = {}) {
|
|
763
810
|
const { compressionLevel = 6 } = options;
|
|
764
811
|
for (const [path, content] of updates) zip.file(path, content, {
|
|
765
812
|
compression: "DEFLATE",
|
|
766
813
|
compressionOptions: { level: compressionLevel }
|
|
767
814
|
});
|
|
768
|
-
|
|
815
|
+
await normalizePackageOnSave(zip, compressionLevel);
|
|
816
|
+
return await zip.generateAsync({
|
|
769
817
|
type: "arraybuffer",
|
|
770
818
|
compression: "DEFLATE",
|
|
771
819
|
compressionOptions: { level: compressionLevel }
|
|
@@ -1067,7 +1115,13 @@ async function rebindWatermarkRelIds(doc, zip, compressionLevel) {
|
|
|
1067
1115
|
});
|
|
1068
1116
|
}
|
|
1069
1117
|
}
|
|
1070
|
-
|
|
1118
|
+
/**
|
|
1119
|
+
* Re-serialize every header/footer the model still owns, keyed by part path.
|
|
1120
|
+
*
|
|
1121
|
+
* `sourceZip` supplies each part as it stands before the save so the rebuilt
|
|
1122
|
+
* root can keep any prefix binding only the source document declared.
|
|
1123
|
+
*/
|
|
1124
|
+
async function collectHeaderFooterUpdates(doc, sourceZip) {
|
|
1071
1125
|
const updates = /* @__PURE__ */ new Map();
|
|
1072
1126
|
const rels = doc.package.relationships;
|
|
1073
1127
|
if (!rels) return updates;
|
|
@@ -1084,8 +1138,13 @@ function collectHeaderFooterUpdates(doc) {
|
|
|
1084
1138
|
for (const [rId, headerFooter] of map.entries()) {
|
|
1085
1139
|
const rel = rels.get(rId);
|
|
1086
1140
|
if (rel && rel.type === type && rel.target) {
|
|
1087
|
-
const
|
|
1088
|
-
|
|
1141
|
+
const path = resolveRelativePath(documentRelsPath, rel.target);
|
|
1142
|
+
const sourceFile = findZipEntryCaseInsensitive(sourceZip, path.toLowerCase());
|
|
1143
|
+
const bindings = sourceFile ? readRootNamespaceBindings(await sourceFile.async("text")) : /* @__PURE__ */ new Map();
|
|
1144
|
+
updates.set(path, serializeHeaderFooter(headerFooter, {
|
|
1145
|
+
path,
|
|
1146
|
+
bindings
|
|
1147
|
+
}));
|
|
1089
1148
|
}
|
|
1090
1149
|
}
|
|
1091
1150
|
}
|
|
@@ -1094,9 +1153,9 @@ function collectHeaderFooterUpdates(doc) {
|
|
|
1094
1153
|
/**
|
|
1095
1154
|
* Serialize modified headers and footers into the ZIP
|
|
1096
1155
|
*/
|
|
1097
|
-
function serializeHeadersFootersToZip(doc, zip, compressionLevel) {
|
|
1156
|
+
async function serializeHeadersFootersToZip(doc, zip, compressionLevel) {
|
|
1098
1157
|
const compressionOptions = { level: compressionLevel };
|
|
1099
|
-
for (const [filename, xml] of collectHeaderFooterUpdates(doc)) zip.file(filename, xml, {
|
|
1158
|
+
for (const [filename, xml] of await collectHeaderFooterUpdates(doc, zip)) zip.file(filename, xml, {
|
|
1100
1159
|
compression: "DEFLATE",
|
|
1101
1160
|
compressionOptions
|
|
1102
1161
|
});
|
package/dist/docx/runParser.js
CHANGED
|
@@ -7,8 +7,9 @@ import { parseShapeFromDrawing, shouldPreserveRawShapeDrawing } from "./shapePar
|
|
|
7
7
|
import { isTextBoxDrawing } from "./textBoxParser.js";
|
|
8
8
|
import { requiresXmlSpacePreserve } from "./textWhitespace.js";
|
|
9
9
|
import { resolveThemeFontRef } from "./themeParser.js";
|
|
10
|
+
import { captureVerbatimXml } from "./verbatimCapture.js";
|
|
10
11
|
import { parseVmlImageContent } from "./vmlImageParser.js";
|
|
11
|
-
import { cloneWithXmlnsDeclarations,
|
|
12
|
+
import { cloneWithXmlnsDeclarations, findAllDeep, findChild, findChildren, getAttribute, getChildElements, getLocalName, getTextContent, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute } from "./xmlParser.js";
|
|
12
13
|
import { DRAWING_RAW_XML_MODES, normalizeRevisionId } from "@stll/docx-core/model";
|
|
13
14
|
//#region src/docx/runParser.ts
|
|
14
15
|
/**
|
|
@@ -489,7 +490,7 @@ function parseDrawingContent(element, rels, media) {
|
|
|
489
490
|
if (groupImage) return {
|
|
490
491
|
type: "drawing",
|
|
491
492
|
image: groupImage,
|
|
492
|
-
rawXml:
|
|
493
|
+
rawXml: captureVerbatimXml(element)
|
|
493
494
|
};
|
|
494
495
|
if (shouldPreserveRawShapeDrawing(element)) return {
|
|
495
496
|
type: "drawing",
|
|
@@ -502,7 +503,7 @@ function parseDrawingContent(element, rels, media) {
|
|
|
502
503
|
},
|
|
503
504
|
wrap: { type: "inline" }
|
|
504
505
|
},
|
|
505
|
-
rawXml:
|
|
506
|
+
rawXml: captureVerbatimXml(element),
|
|
506
507
|
rawXmlMode: DRAWING_RAW_XML_MODES.PRESERVE_ONLY
|
|
507
508
|
};
|
|
508
509
|
const shape = parseShapeFromDrawing(element);
|
|
@@ -516,7 +517,7 @@ function parseDrawingContent(element, rels, media) {
|
|
|
516
517
|
type: "drawing",
|
|
517
518
|
image
|
|
518
519
|
};
|
|
519
|
-
if (!image.src) drawing.rawXml =
|
|
520
|
+
if (!image.src) drawing.rawXml = captureVerbatimXml(element);
|
|
520
521
|
return drawing;
|
|
521
522
|
}
|
|
522
523
|
/**
|
|
@@ -593,7 +594,7 @@ function parseRunContents(runElement, rels, media, rootXmlns = {}) {
|
|
|
593
594
|
if (groupedChoiceDrawing) {
|
|
594
595
|
const groupedDrawing = parseDrawingContent(groupedChoiceDrawing, rels, media);
|
|
595
596
|
if (groupedDrawing?.type === "drawing" && groupedDrawing.image.src) {
|
|
596
|
-
groupedDrawing.rawXml =
|
|
597
|
+
groupedDrawing.rawXml = captureVerbatimXml(child);
|
|
597
598
|
contents.push(groupedDrawing);
|
|
598
599
|
break;
|
|
599
600
|
}
|
|
@@ -601,7 +602,7 @@ function parseRunContents(runElement, rels, media, rootXmlns = {}) {
|
|
|
601
602
|
const fallbackPict = fallbackEl ? getChildElements(fallbackEl).find((el) => getLocalName(el.name) === "pict") : void 0;
|
|
602
603
|
const fallbackVml = fallbackPict && !choiceTextBoxDrawing ? parseVmlImageContent(fallbackPict, rels, media, rootXmlns) : null;
|
|
603
604
|
if (fallbackVml?.image.src) {
|
|
604
|
-
fallbackVml.rawXml =
|
|
605
|
+
fallbackVml.rawXml = captureVerbatimXml(cloneWithXmlnsDeclarations(child, rootXmlns));
|
|
605
606
|
contents.push(fallbackVml);
|
|
606
607
|
break;
|
|
607
608
|
}
|
|
@@ -611,13 +612,13 @@ function parseRunContents(runElement, rels, media, rootXmlns = {}) {
|
|
|
611
612
|
if (innerName === "drawing") {
|
|
612
613
|
const innerDrawing = parseDrawingContent(innerChild, rels, media);
|
|
613
614
|
if (innerDrawing) {
|
|
614
|
-
if (innerDrawing.type === "drawing" && (innerDrawing.rawXml !== void 0 || !innerDrawing.image.src)) innerDrawing.rawXml =
|
|
615
|
+
if (innerDrawing.type === "drawing" && (innerDrawing.rawXml !== void 0 || !innerDrawing.image.src)) innerDrawing.rawXml = captureVerbatimXml(child);
|
|
615
616
|
contents.push(innerDrawing);
|
|
616
617
|
}
|
|
617
618
|
} else if (innerName === "pict") {
|
|
618
619
|
const innerVml = parseVmlImageContent(innerChild, rels, media, rootXmlns);
|
|
619
620
|
if (innerVml) {
|
|
620
|
-
innerVml.rawXml =
|
|
621
|
+
innerVml.rawXml = captureVerbatimXml(cloneWithXmlnsDeclarations(child, rootXmlns));
|
|
621
622
|
contents.push(innerVml);
|
|
622
623
|
}
|
|
623
624
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SdtLockSchema, narrowEnum } from "./parserEnums.js";
|
|
2
|
-
import {
|
|
2
|
+
import { captureVerbatimXml } from "./verbatimCapture.js";
|
|
3
|
+
import { findChild, getAttributeAnyPrefix, getLocalName, parseBooleanElement } from "./xmlParser.js";
|
|
3
4
|
//#region src/docx/sdtProperties.ts
|
|
4
5
|
function parseListItems(el) {
|
|
5
6
|
const items = [];
|
|
@@ -201,7 +202,7 @@ function rewriteOpenTagAttrPrefixes(raw, canonical, local) {
|
|
|
201
202
|
function parseSdtProperties(sdtPr, sdtEndPr) {
|
|
202
203
|
const props = { sdtType: "richText" };
|
|
203
204
|
if (sdtPr) {
|
|
204
|
-
props.rawPropertiesXml = normalizeWordPrefix(
|
|
205
|
+
props.rawPropertiesXml = normalizeWordPrefix(captureVerbatimXml(sdtPr), sdtPr);
|
|
205
206
|
for (const el of sdtPr.elements ?? []) {
|
|
206
207
|
if (el.type !== "element") continue;
|
|
207
208
|
const name = el.name ? getLocalName(el.name) : "";
|
|
@@ -283,7 +284,7 @@ function parseSdtProperties(sdtPr, sdtEndPr) {
|
|
|
283
284
|
}
|
|
284
285
|
}
|
|
285
286
|
}
|
|
286
|
-
if (sdtEndPr) props.rawEndPropertiesXml = normalizeWordPrefix(
|
|
287
|
+
if (sdtEndPr) props.rawEndPropertiesXml = normalizeWordPrefix(captureVerbatimXml(sdtEndPr), sdtEndPr);
|
|
287
288
|
return props;
|
|
288
289
|
}
|
|
289
290
|
//#endregion
|
|
@@ -13,6 +13,7 @@ import { ensureThreadedCommentParaIds, serializeComments, serializeCommentsExten
|
|
|
13
13
|
import { serializeDocument } from "./serializer/documentSerializer.js";
|
|
14
14
|
import { serializeEndnotes, serializeFootnotes } from "./serializer/noteSerializer.js";
|
|
15
15
|
import { serializeNumberingXml } from "./serializer/numberingSerializer.js";
|
|
16
|
+
import { readRootNamespaceBindings } from "./serializer/partNamespaces.js";
|
|
16
17
|
//#region src/docx/selectiveSave.ts
|
|
17
18
|
/**
|
|
18
19
|
* Check if document content has new images (data: URL without rId) or
|
|
@@ -209,7 +210,6 @@ async function attemptSelectiveSave(doc, originalBuffer, options) {
|
|
|
209
210
|
if (!validateFolioDocumentModel(doc).valid) return null;
|
|
210
211
|
const comments = doc.package.document.comments ?? [];
|
|
211
212
|
const hasComments = comments.length > 0;
|
|
212
|
-
const headerFooterUpdates = collectHeaderFooterUpdates(doc);
|
|
213
213
|
try {
|
|
214
214
|
const zip = await (await import("jszip")).default.loadAsync(originalBuffer);
|
|
215
215
|
for (const [path, file] of Object.entries(zip.files)) if (!file.dir && isUnsafePackagePath(path)) return null;
|
|
@@ -228,15 +228,16 @@ async function attemptSelectiveSave(doc, originalBuffer, options) {
|
|
|
228
228
|
if (unrouted === null || unrouted.size > 0) return null;
|
|
229
229
|
}
|
|
230
230
|
if (bodyChangedIds.size > 0) {
|
|
231
|
-
const patchedDocXml = buildPatchedDocumentXml(originalDocXml, serializeDocument(doc), bodyChangedIds);
|
|
231
|
+
const patchedDocXml = buildPatchedDocumentXml(originalDocXml, serializeDocument(doc, readRootNamespaceBindings(originalDocXml)), bodyChangedIds);
|
|
232
232
|
if (!patchedDocXml) return null;
|
|
233
233
|
updates.set("word/document.xml", patchedDocXml);
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
|
-
const
|
|
237
|
-
if (hasComments ||
|
|
236
|
+
const sourceCommentsFile = zip.file("word/comments.xml");
|
|
237
|
+
if (hasComments || sourceCommentsFile) {
|
|
238
238
|
ensureThreadedCommentParaIds(comments);
|
|
239
|
-
|
|
239
|
+
const sourceBindings = sourceCommentsFile ? readRootNamespaceBindings(await sourceCommentsFile.async("text")) : void 0;
|
|
240
|
+
updates.set("word/comments.xml", serializeComments(comments, sourceBindings));
|
|
240
241
|
}
|
|
241
242
|
if (hasComments) {
|
|
242
243
|
const ctFile = zip.file("[Content_Types].xml");
|
|
@@ -256,7 +257,7 @@ async function attemptSelectiveSave(doc, originalBuffer, options) {
|
|
|
256
257
|
}
|
|
257
258
|
if (!await patchCommentsExtended(zip, comments, updates)) return null;
|
|
258
259
|
await patchNumberingPart(zip, doc, updates);
|
|
259
|
-
for (const [path, xml] of
|
|
260
|
+
for (const [path, xml] of await collectHeaderFooterUpdates(doc, zip)) updates.set(path, xml);
|
|
260
261
|
await queueSettingsUpdates(zip, updates);
|
|
261
262
|
const corePropsFile = zip.file("docProps/core.xml");
|
|
262
263
|
if (corePropsFile) {
|
|
@@ -7,7 +7,7 @@ import { Comment } from "../../types/content.js";
|
|
|
7
7
|
* comment — leaving the previous file in place would otherwise re-emit
|
|
8
8
|
* the orphaned comment threads on every save.
|
|
9
9
|
*/
|
|
10
|
-
declare function serializeComments(comments: Comment[]): string;
|
|
10
|
+
declare function serializeComments(comments: Comment[], sourceBindings?: ReadonlyMap<string, string>): string;
|
|
11
11
|
/**
|
|
12
12
|
* Assign a deterministic `w14:paraId` to the LAST paragraph of every comment
|
|
13
13
|
* that needs a commentsExtended entry (a reply, a reply's parent, or a resolved
|