@stll/folio-core 0.35.0 → 0.36.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/dist/ai-edits/apply.js +370 -118
- package/dist/ai-edits/headless.js +16 -10
- package/dist/ai-edits/index.d.ts +2 -2
- package/dist/ai-edits/index.js +2 -2
- package/dist/ai-edits/snapshot.js +6 -1
- package/dist/ai-edits/types.d.ts +19 -6
- package/dist/ai-edits/word-diff.d.ts +18 -3
- package/dist/ai-edits/word-diff.js +556 -67
- package/dist/compare/compare.js +7 -16
- package/dist/compare/plan.js +8 -5
- package/dist/compare/verification.d.ts +8 -9
- package/dist/compare/verification.js +51 -46
- package/dist/compat/eigenpal.d.ts +2 -2
- package/dist/compat/eigenpal.js +2 -2
- package/dist/controller/headerFooterEditorManager.js +5 -5
- package/dist/controller/hiddenEditorApi.js +1 -1
- package/dist/controller/noteEditorManager.js +2 -2
- package/dist/document-operations.d.ts +5 -3
- package/dist/document-operations.js +32 -5
- package/dist/docx/commentParser.js +2 -4
- package/dist/docx/commentRangeIntegrity.js +2 -4
- package/dist/docx/documentClone.d.ts +2 -0
- package/dist/docx/documentClone.js +2 -0
- package/dist/docx/headerFooterParser.js +2 -4
- package/dist/docx/normalizeBaseDirection.js +5 -7
- package/dist/docx/paragraphParser.js +27 -29
- package/dist/docx/paragraphPropertySource.d.ts +49 -0
- package/dist/docx/paragraphPropertySource.js +113 -0
- package/dist/docx/runConsolidator.js +3 -4
- package/dist/docx/runParser.js +2 -15
- package/dist/docx/sectionParser.js +1 -15
- package/dist/docx/serializer/paragraphSerializer.js +287 -30
- package/dist/docx/serializer/runSerializer.js +2 -10
- package/dist/docx/serializer/sectionPropertiesSerializer.js +2 -10
- package/dist/docx/serializer/tableSerializer.js +4 -23
- package/dist/docx/serializer/trackedChangeAttributes.d.ts +9 -0
- package/dist/docx/serializer/trackedChangeAttributes.js +21 -0
- package/dist/docx/server/createBilingualDocument.js +8 -10
- package/dist/docx/server/evaluateDocxXmlPatchProposal.js +22 -13
- package/dist/docx/server/validateDocxConformance.js +17 -13
- package/dist/docx/tableParser.js +1 -37
- package/dist/docx/trackedChangeInfo.d.ts +11 -0
- package/dist/docx/trackedChangeInfo.js +38 -0
- package/dist/docx/verbatimCapture.d.ts +20 -9
- package/dist/docx/verbatimCapture.js +208 -8
- package/dist/docx/xmlParser.d.ts +3 -1
- package/dist/docx/xmlParser.js +6 -1
- package/dist/docx/xmlSafety.d.ts +1 -1
- package/dist/docx/xmlSafety.js +157 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/layout-bridge/convert/headerFooterLayout.js +4 -4
- package/dist/layout-bridge/convert/toFlowBlocks.js +12 -7
- package/dist/markdown/renderBlock.js +7 -10
- package/dist/prosemirror/attrs/index.js +15 -0
- package/dist/prosemirror/commands/comments.js +365 -32
- package/dist/prosemirror/commands/pageBreak.js +1 -1
- package/dist/prosemirror/commands/propertyChangeScope.d.ts +26 -4
- package/dist/prosemirror/commands/propertyChangeScope.js +65 -3
- package/dist/prosemirror/conversion/fromProseDoc.d.ts +2 -4
- package/dist/prosemirror/conversion/fromProseDoc.js +130 -18
- package/dist/prosemirror/conversion/toProseDoc.js +27 -12
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +48 -4
- package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +7 -1
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +20 -7
- package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +2 -0
- package/dist/prosemirror/paragraphAlignment.d.ts +12 -0
- package/dist/prosemirror/paragraphAlignment.js +13 -0
- package/dist/prosemirror/plugins/suggestionMode.js +1 -1
- package/dist/prosemirror/schema/marks.d.ts +2 -0
- package/dist/prosemirror/schema/nodes.d.ts +12 -1
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -0
- package/dist/redline.js +4 -1
- package/dist/server.d.ts +2 -2
- package/dist/server.js +2 -2
- package/dist/utils/mergeDocumentContent.js +3 -3
- package/dist/utils/replaceText.js +2 -1
- package/dist/version-comparison.js +6 -4
- package/dist/watermark/index.js +2 -4
- package/package.json +7 -2
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { cloneParagraphWithPropertySource } from "./paragraphPropertySource.js";
|
|
1
2
|
//#region src/docx/commentRangeIntegrity.ts
|
|
2
3
|
const withoutOrphanCommentRanges = (doc) => {
|
|
3
4
|
const validCommentIds = new Set((doc.package.document.comments ?? []).map((comment) => comment.id));
|
|
@@ -176,10 +177,7 @@ const withoutOrphanParagraphMarkers = (paragraph, validCommentIds) => {
|
|
|
176
177
|
content.push(item);
|
|
177
178
|
}
|
|
178
179
|
if (!changed) return paragraph;
|
|
179
|
-
return {
|
|
180
|
-
...paragraph,
|
|
181
|
-
content
|
|
182
|
-
};
|
|
180
|
+
return cloneParagraphWithPropertySource(paragraph, { content });
|
|
183
181
|
};
|
|
184
182
|
const isCommentMarker = (content) => content.type === "commentRangeStart" || content.type === "commentRangeEnd" || content.type === "commentReference";
|
|
185
183
|
//#endregion
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { parseBlockContent } from "./blockContentParser.js";
|
|
2
2
|
import { parseFooterReference, parseFooterReferences, parseHeaderReference, parseHeaderReferences } from "./headerFooterRefParser.js";
|
|
3
3
|
import { assignHeaderFooterVerbatimXml } from "./headerFooterVerbatim.js";
|
|
4
|
+
import { cloneParagraphWithPropertySource } from "./paragraphPropertySource.js";
|
|
4
5
|
import { parseWatermark } from "./watermarkParser.js";
|
|
5
6
|
import { collectXmlnsDeclarations, parseXml } from "./xmlParser.js";
|
|
6
7
|
//#region src/docx/headerFooterParser.ts
|
|
@@ -37,10 +38,7 @@ function parseHeader(headerXml, hdrFtrType = "default", styles = null, theme = n
|
|
|
37
38
|
});
|
|
38
39
|
if (watermarkResult) {
|
|
39
40
|
const host = result.content.at(watermarkResult.blockIndex);
|
|
40
|
-
if (host?.type === "paragraph") result.content[watermarkResult.blockIndex] = {
|
|
41
|
-
...host,
|
|
42
|
-
content: []
|
|
43
|
-
};
|
|
41
|
+
if (host?.type === "paragraph") result.content[watermarkResult.blockIndex] = cloneParagraphWithPropertySource(host, { content: [] });
|
|
44
42
|
}
|
|
45
43
|
assignHeaderFooterVerbatimXml(result, headerXml);
|
|
46
44
|
return result;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createStyleResolver } from "../prosemirror/styles/styleResolver.js";
|
|
2
2
|
import { detectBaseDirection } from "../utils/baseDirection.js";
|
|
3
|
+
import { cloneParagraphWithoutPropertySource } from "./paragraphPropertySource.js";
|
|
3
4
|
//#region src/docx/normalizeBaseDirection.ts
|
|
4
5
|
const runText = (run) => {
|
|
5
6
|
let text = "";
|
|
@@ -32,13 +33,10 @@ const paragraphText = (paragraph) => paragraphContentText(paragraph.content);
|
|
|
32
33
|
const normalizeParagraph = (paragraph, styles) => {
|
|
33
34
|
if ((paragraph.formatting?.bidi ?? styles.resolveParagraphStyle(paragraph.formatting?.styleId).paragraphFormatting?.bidi) != null) return paragraph;
|
|
34
35
|
if (detectBaseDirection(paragraphText(paragraph)) !== "rtl") return paragraph;
|
|
35
|
-
return {
|
|
36
|
-
...paragraph,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
bidi: true
|
|
40
|
-
}
|
|
41
|
-
};
|
|
36
|
+
return cloneParagraphWithoutPropertySource(paragraph, { formatting: {
|
|
37
|
+
...paragraph.formatting,
|
|
38
|
+
bidi: true
|
|
39
|
+
} });
|
|
42
40
|
};
|
|
43
41
|
const normalizeTable = (table, styles) => ({
|
|
44
42
|
...table,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { canonicalJson } from "../utils/canonicalJson.js";
|
|
1
2
|
import { isValidHexColor } from "../utils/colorResolver.js";
|
|
2
3
|
import { isValidHexId } from "../utils/hexId.js";
|
|
3
4
|
import { parseBookmarkEnd as parseBookmarkEnd$1, parseBookmarkStart as parseBookmarkStart$1 } from "./bookmarkParser.js";
|
|
@@ -5,15 +6,17 @@ import { parseFieldType } from "./fieldParser.js";
|
|
|
5
6
|
import { parseHyperlink as parseHyperlink$1, parseHyperlinkChild } from "./hyperlinkParser.js";
|
|
6
7
|
import { markerFormattingFromLevel } from "./numberingParser.js";
|
|
7
8
|
import { paraIdInRange } from "./paraIdRangeNormalization.js";
|
|
9
|
+
import { assignParagraphPropertySource } from "./paragraphPropertySource.js";
|
|
8
10
|
import { BorderStyleSchema, FrameWrapSchema, FrameXAlignSchema, FrameYAlignSchema, LineSpacingRuleSchema, ParagraphAlignmentSchema, ShadingPatternSchema, TabLeaderSchema, TabStopAlignmentSchema, ThemeColorSlotSchema, narrowEnum } from "./parserEnums.js";
|
|
9
11
|
import { consolidateParagraphContent } from "./runConsolidator.js";
|
|
10
12
|
import { parseRun, parseRunProperties } from "./runParser.js";
|
|
11
13
|
import { parseSdtProperties } from "./sdtProperties.js";
|
|
12
14
|
import { parseSectionProperties } from "./sectionParser.js";
|
|
15
|
+
import { parsePropertyChangeInfo, parseTrackedChangeInfo } from "./trackedChangeInfo.js";
|
|
13
16
|
import { captureVerbatimXml } from "./verbatimCapture.js";
|
|
14
|
-
import { WORDPROCESSINGML_NAMESPACE_URIS, findChild, findChildByNamespaceUri, findChildren, getAttribute, getChildElements, getLocalName, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseNumberingLevelAttribute, parseNumericAttribute } from "./xmlParser.js";
|
|
17
|
+
import { WORDPROCESSINGML_NAMESPACE_URIS, cloneElement, findChild, findChildByNamespaceUri, findChildren, findChildrenByNamespaceUri, getAttribute, getChildElements, getLocalName, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseNumberingLevelAttribute, parseNumericAttribute } from "./xmlParser.js";
|
|
15
18
|
import { panic } from "better-result";
|
|
16
|
-
import { PARAGRAPH_MARK_CHANGE_KINDS
|
|
19
|
+
import { PARAGRAPH_MARK_CHANGE_KINDS } from "@stll/docx-core/model";
|
|
17
20
|
//#region src/docx/paragraphParser.ts
|
|
18
21
|
/**
|
|
19
22
|
* Extract plain text from a math element (recursive text content extraction)
|
|
@@ -373,6 +376,21 @@ function parseParagraphProperties(pPr, theme, styles) {
|
|
|
373
376
|
}
|
|
374
377
|
return Object.keys(formatting).length > 0 ? formatting : void 0;
|
|
375
378
|
}
|
|
379
|
+
/**
|
|
380
|
+
* Capture the authored paragraph properties separately from structural and
|
|
381
|
+
* revision children, which have their own model fields and lifecycles.
|
|
382
|
+
*/
|
|
383
|
+
const captureParagraphPropertySource = (pPr) => captureVerbatimXml(cloneElement(pPr, { elements: (pPr.elements ?? []).flatMap((child) => {
|
|
384
|
+
if (child.type !== "element") return [child];
|
|
385
|
+
const localName = getLocalName(child.name);
|
|
386
|
+
if (WORDPROCESSINGML_NAMESPACE_URIS.has(child.namespaceUri ?? "") && (localName === "sectPr" || localName === "pPrChange")) return [];
|
|
387
|
+
if (localName !== "rPr" || !WORDPROCESSINGML_NAMESPACE_URIS.has(child.namespaceUri ?? "")) return [child];
|
|
388
|
+
return [cloneElement(child, { elements: (child.elements ?? []).filter((runProperty) => {
|
|
389
|
+
if (runProperty.type !== "element") return true;
|
|
390
|
+
const runPropertyName = getLocalName(runProperty.name);
|
|
391
|
+
return !WORDPROCESSINGML_NAMESPACE_URIS.has(runProperty.namespaceUri ?? "") || !PARAGRAPH_MARK_CHANGE_KINDS.some((kind) => kind === runPropertyName);
|
|
392
|
+
}) })];
|
|
393
|
+
}) }));
|
|
376
394
|
const RENDERED_BREAK_INLINE_WRAPPERS = /* @__PURE__ */ new Set([
|
|
377
395
|
"hyperlink",
|
|
378
396
|
"smartTag",
|
|
@@ -482,34 +500,10 @@ function normalizeDeletionContentElement(node) {
|
|
|
482
500
|
if (node.elements) result.elements = node.elements.map(normalizeDeletionContentElement);
|
|
483
501
|
return result;
|
|
484
502
|
}
|
|
485
|
-
function parseTrackedChangeInfo(node) {
|
|
486
|
-
const rawId = getAttribute(node, "w", "id");
|
|
487
|
-
const parsedId = rawId ? Number.parseInt(rawId, 10) : 0;
|
|
488
|
-
const rawAuthor = getAttribute(node, "w", "author");
|
|
489
|
-
const rawDate = getAttribute(node, "w", "date");
|
|
490
|
-
const author = rawAuthor?.trim() ?? "";
|
|
491
|
-
const date = rawDate?.trim() ?? "";
|
|
492
|
-
const initials = (getAttribute(node, "w", "initials") ?? "").trim();
|
|
493
|
-
const info = {
|
|
494
|
-
id: normalizeRevisionId(parsedId),
|
|
495
|
-
author: author.length > 0 ? author : "Unknown"
|
|
496
|
-
};
|
|
497
|
-
if (date.length > 0) info.date = date;
|
|
498
|
-
if (initials.length > 0) info.initials = initials;
|
|
499
|
-
return info;
|
|
500
|
-
}
|
|
501
|
-
function parsePropertyChangeInfo(node) {
|
|
502
|
-
const base = parseTrackedChangeInfo(node);
|
|
503
|
-
const rsid = (getAttribute(node, "w", "rsid") ?? "").trim();
|
|
504
|
-
return rsid.length > 0 ? {
|
|
505
|
-
...base,
|
|
506
|
-
rsid
|
|
507
|
-
} : base;
|
|
508
|
-
}
|
|
509
503
|
function parseParagraphPropertyChanges(pPr, theme, styles, currentFormatting) {
|
|
510
504
|
if (!pPr) return;
|
|
511
|
-
const changes =
|
|
512
|
-
const previousFormatting = parseParagraphProperties(
|
|
505
|
+
const changes = findChildrenByNamespaceUri(pPr, WORDPROCESSINGML_NAMESPACE_URIS, "pPrChange").map((changeElement) => {
|
|
506
|
+
const previousFormatting = parseParagraphProperties(findChildByNamespaceUri(changeElement, WORDPROCESSINGML_NAMESPACE_URIS, "pPr"), theme, styles ?? void 0);
|
|
513
507
|
const change = {
|
|
514
508
|
type: "paragraphPropertyChange",
|
|
515
509
|
info: parsePropertyChangeInfo(changeElement)
|
|
@@ -528,7 +522,7 @@ function parseParagraphPropertyChanges(pPr, theme, styles, currentFormatting) {
|
|
|
528
522
|
*/
|
|
529
523
|
function parseParagraphMarkChange(pPr) {
|
|
530
524
|
if (!pPr) return;
|
|
531
|
-
const rPr =
|
|
525
|
+
const rPr = findChildByNamespaceUri(pPr, WORDPROCESSINGML_NAMESPACE_URIS, "rPr");
|
|
532
526
|
if (!rPr) return;
|
|
533
527
|
for (const kind of PARAGRAPH_MARK_CHANGE_KINDS) {
|
|
534
528
|
const element = findChildByNamespaceUri(rPr, WORDPROCESSINGML_NAMESPACE_URIS, kind);
|
|
@@ -1200,6 +1194,10 @@ function parseParagraph(node, styles, theme, numbering, rels = null, media = nul
|
|
|
1200
1194
|
}
|
|
1201
1195
|
}
|
|
1202
1196
|
}
|
|
1197
|
+
if (pPr) assignParagraphPropertySource(paragraph, {
|
|
1198
|
+
xml: captureParagraphPropertySource(pPr),
|
|
1199
|
+
formattingJson: canonicalJson(paragraph.formatting ?? {})
|
|
1200
|
+
});
|
|
1203
1201
|
return paragraph;
|
|
1204
1202
|
}
|
|
1205
1203
|
/**
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { document_d_exports } from "../types/document.js";
|
|
2
|
+
import { Transaction } from "prosemirror-state";
|
|
3
|
+
import { Fragment, Mark, Node } from "prosemirror-model";
|
|
4
|
+
//#region src/docx/paragraphPropertySource.d.ts
|
|
5
|
+
type ParagraphPropertySource = {
|
|
6
|
+
xml: string;
|
|
7
|
+
formattingJson: string;
|
|
8
|
+
};
|
|
9
|
+
declare const assignParagraphPropertySource: (paragraph: document_d_exports.Paragraph, source: ParagraphPropertySource) => void;
|
|
10
|
+
declare const getParagraphPropertySource: (paragraph: document_d_exports.Paragraph) => ParagraphPropertySource | undefined;
|
|
11
|
+
declare const copyParagraphPropertySource: (target: document_d_exports.Paragraph, source: document_d_exports.Paragraph) => void;
|
|
12
|
+
type ParagraphCloneOverrides = Omit<Partial<document_d_exports.Paragraph>, "type">;
|
|
13
|
+
/** Clone a paragraph while deliberately retaining its parsed `w:pPr` owner. */
|
|
14
|
+
declare const cloneParagraphWithPropertySource: (paragraph: document_d_exports.Paragraph, overrides: ParagraphCloneOverrides) => document_d_exports.Paragraph;
|
|
15
|
+
/** Clone a paragraph whose formatting provenance is deliberately no longer applicable. */
|
|
16
|
+
declare const cloneParagraphWithoutPropertySource: (paragraph: document_d_exports.Paragraph, overrides: ParagraphCloneOverrides) => document_d_exports.Paragraph;
|
|
17
|
+
/**
|
|
18
|
+
* Deep-clone a document and transfer each private paragraph capture across the
|
|
19
|
+
* exact graph clone. `structuredClone` preserves graph topology, so any count
|
|
20
|
+
* mismatch is an internal invariant failure rather than a position heuristic.
|
|
21
|
+
*/
|
|
22
|
+
declare const cloneDocumentWithParagraphPropertySources: (document: document_d_exports.Document) => document_d_exports.Document;
|
|
23
|
+
declare const linkProseParagraphPropertySource: (proseParagraph: Node, sourceParagraph: document_d_exports.Paragraph) => void;
|
|
24
|
+
type RecreateProseNodeOptions = {
|
|
25
|
+
attrs?: Node["attrs"];
|
|
26
|
+
content?: Fragment | Node | readonly Node[] | null;
|
|
27
|
+
marks?: readonly Mark[];
|
|
28
|
+
};
|
|
29
|
+
/** Rebuild a PM node and retain paragraph provenance when the node is one. */
|
|
30
|
+
declare const recreateProseNodeWithParagraphPropertySource: (source: Node, options?: RecreateProseNodeOptions) => Node;
|
|
31
|
+
type SetProseParagraphMarkupOptions = {
|
|
32
|
+
attrs: Node["attrs"];
|
|
33
|
+
ownership: "preserve" | "transfer-allocated-id";
|
|
34
|
+
pos: number;
|
|
35
|
+
transaction: Transaction;
|
|
36
|
+
};
|
|
37
|
+
/** Replace paragraph markup without losing its private parser-owner link. */
|
|
38
|
+
declare const setProseParagraphMarkupWithPropertySource: ({ attrs, ownership, pos, transaction }: SetProseParagraphMarkupOptions) => void;
|
|
39
|
+
/**
|
|
40
|
+
* Carry the parser-owned source identity across load-time paraId allocation.
|
|
41
|
+
* The generated id is safe to use later because it was assigned while the
|
|
42
|
+
* ProseMirror node still had an unambiguous source paragraph owner.
|
|
43
|
+
*/
|
|
44
|
+
declare const transferProseParagraphPropertySource: (target: Node, source: Node, paraId: string) => void;
|
|
45
|
+
declare const linkParagraphPropertySourceCandidate: (target: document_d_exports.Paragraph, source: Node) => void;
|
|
46
|
+
declare const getParagraphPropertySourceCandidate: (paragraph: document_d_exports.Paragraph) => document_d_exports.Paragraph | undefined;
|
|
47
|
+
declare const getParagraphPropertySourceTransferId: (paragraph: document_d_exports.Paragraph) => string | undefined;
|
|
48
|
+
//#endregion
|
|
49
|
+
export { assignParagraphPropertySource, cloneDocumentWithParagraphPropertySources, cloneParagraphWithPropertySource, cloneParagraphWithoutPropertySource, copyParagraphPropertySource, getParagraphPropertySource, getParagraphPropertySourceCandidate, getParagraphPropertySourceTransferId, linkParagraphPropertySourceCandidate, linkProseParagraphPropertySource, recreateProseNodeWithParagraphPropertySource, setProseParagraphMarkupWithPropertySource, transferProseParagraphPropertySource };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { visitDocxParagraphs } from "./paragraphTraversal.js";
|
|
2
|
+
import { panic } from "better-result";
|
|
3
|
+
//#region src/docx/paragraphPropertySource.ts
|
|
4
|
+
const paragraphPropertySources = /* @__PURE__ */ new WeakMap();
|
|
5
|
+
const paragraphPropertySourceOwners = /* @__PURE__ */ new WeakMap();
|
|
6
|
+
const proseParagraphSourceOwners = /* @__PURE__ */ new WeakMap();
|
|
7
|
+
const paragraphPropertySourceCandidates = /* @__PURE__ */ new WeakMap();
|
|
8
|
+
const paragraphPropertySourceTransferIds = /* @__PURE__ */ new WeakMap();
|
|
9
|
+
const assignParagraphPropertySource = (paragraph, source) => {
|
|
10
|
+
paragraphPropertySources.set(paragraph, source);
|
|
11
|
+
paragraphPropertySourceOwners.set(paragraph, paragraph);
|
|
12
|
+
};
|
|
13
|
+
const getParagraphPropertySource = (paragraph) => paragraphPropertySources.get(paragraph);
|
|
14
|
+
const copyParagraphPropertySource = (target, source) => {
|
|
15
|
+
const propertySource = paragraphPropertySources.get(source);
|
|
16
|
+
if (propertySource) {
|
|
17
|
+
paragraphPropertySources.set(target, { ...propertySource });
|
|
18
|
+
paragraphPropertySourceOwners.set(target, paragraphPropertySourceOwners.get(source) ?? source);
|
|
19
|
+
}
|
|
20
|
+
const transferId = paragraphPropertySourceTransferIds.get(source);
|
|
21
|
+
if (transferId) paragraphPropertySourceTransferIds.set(target, transferId);
|
|
22
|
+
const candidate = paragraphPropertySourceCandidates.get(source);
|
|
23
|
+
if (candidate) paragraphPropertySourceCandidates.set(target, candidate);
|
|
24
|
+
};
|
|
25
|
+
/** Clone a paragraph while deliberately retaining its parsed `w:pPr` owner. */
|
|
26
|
+
const cloneParagraphWithPropertySource = (paragraph, overrides) => {
|
|
27
|
+
const cloned = {
|
|
28
|
+
...paragraph,
|
|
29
|
+
...overrides
|
|
30
|
+
};
|
|
31
|
+
copyParagraphPropertySource(cloned, paragraph);
|
|
32
|
+
return cloned;
|
|
33
|
+
};
|
|
34
|
+
/** Clone a paragraph whose formatting provenance is deliberately no longer applicable. */
|
|
35
|
+
const cloneParagraphWithoutPropertySource = (paragraph, overrides) => ({
|
|
36
|
+
...paragraph,
|
|
37
|
+
...overrides
|
|
38
|
+
});
|
|
39
|
+
const paragraphsIn = (document) => {
|
|
40
|
+
const paragraphs = [];
|
|
41
|
+
visitDocxParagraphs({
|
|
42
|
+
documentBody: document.package.document,
|
|
43
|
+
headers: document.package.headers,
|
|
44
|
+
footers: document.package.footers,
|
|
45
|
+
footnotes: document.package.footnotes,
|
|
46
|
+
endnotes: document.package.endnotes
|
|
47
|
+
}, (paragraph) => paragraphs.push(paragraph));
|
|
48
|
+
return paragraphs;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Deep-clone a document and transfer each private paragraph capture across the
|
|
52
|
+
* exact graph clone. `structuredClone` preserves graph topology, so any count
|
|
53
|
+
* mismatch is an internal invariant failure rather than a position heuristic.
|
|
54
|
+
*/
|
|
55
|
+
const cloneDocumentWithParagraphPropertySources = (document) => {
|
|
56
|
+
const cloned = structuredClone(document);
|
|
57
|
+
const sources = paragraphsIn(document);
|
|
58
|
+
const targets = paragraphsIn(cloned);
|
|
59
|
+
if (sources.length !== targets.length) panic("The cloned document changed paragraph graph ownership.");
|
|
60
|
+
for (const [index, source] of sources.entries()) {
|
|
61
|
+
const target = targets.at(index);
|
|
62
|
+
if (!target) panic("The cloned document lost a paragraph owner.");
|
|
63
|
+
copyParagraphPropertySource(target, source);
|
|
64
|
+
}
|
|
65
|
+
return cloned;
|
|
66
|
+
};
|
|
67
|
+
const linkProseParagraphPropertySource = (proseParagraph, sourceParagraph) => {
|
|
68
|
+
if (paragraphPropertySources.has(sourceParagraph)) proseParagraphSourceOwners.set(proseParagraph, paragraphPropertySourceOwners.get(sourceParagraph) ?? sourceParagraph);
|
|
69
|
+
};
|
|
70
|
+
/** Carry a parser-linked paragraph owner across an immutable PM node rebuild. */
|
|
71
|
+
const copyProseParagraphPropertySource = (target, source) => {
|
|
72
|
+
if (target.type.name !== "paragraph" || source.type.name !== "paragraph") return;
|
|
73
|
+
const sourceOwner = proseParagraphSourceOwners.get(source);
|
|
74
|
+
if (sourceOwner) proseParagraphSourceOwners.set(target, sourceOwner);
|
|
75
|
+
};
|
|
76
|
+
/** Rebuild a PM node and retain paragraph provenance when the node is one. */
|
|
77
|
+
const recreateProseNodeWithParagraphPropertySource = (source, options = {}) => {
|
|
78
|
+
const target = source.type.create(options.attrs ?? source.attrs, options.content === void 0 ? source.content : options.content, options.marks ?? source.marks);
|
|
79
|
+
copyProseParagraphPropertySource(target, source);
|
|
80
|
+
return target;
|
|
81
|
+
};
|
|
82
|
+
/** Replace paragraph markup without losing its private parser-owner link. */
|
|
83
|
+
const setProseParagraphMarkupWithPropertySource = ({ attrs, ownership, pos, transaction }) => {
|
|
84
|
+
const source = transaction.doc.nodeAt(pos);
|
|
85
|
+
transaction.setNodeMarkup(pos, void 0, attrs);
|
|
86
|
+
const target = transaction.doc.nodeAt(pos);
|
|
87
|
+
if (!source || !target) return;
|
|
88
|
+
if (ownership === "preserve") {
|
|
89
|
+
copyProseParagraphPropertySource(target, source);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const paraId = target.attrs["paraId"];
|
|
93
|
+
if (typeof paraId === "string") transferProseParagraphPropertySource(target, source, paraId);
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Carry the parser-owned source identity across load-time paraId allocation.
|
|
97
|
+
* The generated id is safe to use later because it was assigned while the
|
|
98
|
+
* ProseMirror node still had an unambiguous source paragraph owner.
|
|
99
|
+
*/
|
|
100
|
+
const transferProseParagraphPropertySource = (target, source, paraId) => {
|
|
101
|
+
const sourceOwner = proseParagraphSourceOwners.get(source);
|
|
102
|
+
if (!sourceOwner) return;
|
|
103
|
+
proseParagraphSourceOwners.set(target, sourceOwner);
|
|
104
|
+
paragraphPropertySourceTransferIds.set(sourceOwner, paraId);
|
|
105
|
+
};
|
|
106
|
+
const linkParagraphPropertySourceCandidate = (target, source) => {
|
|
107
|
+
const sourceOwner = proseParagraphSourceOwners.get(source);
|
|
108
|
+
if (sourceOwner) paragraphPropertySourceCandidates.set(target, sourceOwner);
|
|
109
|
+
};
|
|
110
|
+
const getParagraphPropertySourceCandidate = (paragraph) => paragraphPropertySourceCandidates.get(paragraph);
|
|
111
|
+
const getParagraphPropertySourceTransferId = (paragraph) => paragraphPropertySourceTransferIds.get(paragraph);
|
|
112
|
+
//#endregion
|
|
113
|
+
export { assignParagraphPropertySource, cloneDocumentWithParagraphPropertySources, cloneParagraphWithPropertySource, cloneParagraphWithoutPropertySource, copyParagraphPropertySource, getParagraphPropertySource, getParagraphPropertySourceCandidate, getParagraphPropertySourceTransferId, linkParagraphPropertySourceCandidate, linkProseParagraphPropertySource, recreateProseNodeWithParagraphPropertySource, setProseParagraphMarkupWithPropertySource, transferProseParagraphPropertySource };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { cloneParagraphWithPropertySource } from "./paragraphPropertySource.js";
|
|
1
2
|
//#region src/docx/runConsolidator.ts
|
|
2
3
|
/**
|
|
3
4
|
* Check if two TextFormatting objects are equivalent
|
|
@@ -98,6 +99,7 @@ function isMergeableContent(content) {
|
|
|
98
99
|
* Runs with breaks, tabs, images, fields, etc. act as merge boundaries
|
|
99
100
|
*/
|
|
100
101
|
function canMergeRun(run) {
|
|
102
|
+
if (run.propertyChanges && run.propertyChanges.length > 0) return false;
|
|
101
103
|
if (run.content.length === 0) return true;
|
|
102
104
|
return run.content.every(isMergeableContent);
|
|
103
105
|
}
|
|
@@ -192,10 +194,7 @@ function consolidateParagraphContent(content) {
|
|
|
192
194
|
*/
|
|
193
195
|
function consolidateParagraph(paragraph) {
|
|
194
196
|
if (paragraph.content.length === 0) return paragraph;
|
|
195
|
-
return {
|
|
196
|
-
...paragraph,
|
|
197
|
-
content: consolidateParagraphContent(paragraph.content)
|
|
198
|
-
};
|
|
197
|
+
return cloneParagraphWithPropertySource(paragraph, { content: consolidateParagraphContent(paragraph.content) });
|
|
199
198
|
}
|
|
200
199
|
/**
|
|
201
200
|
* Get the number of runs in a paragraph (for debugging/metrics)
|
package/dist/docx/runParser.js
CHANGED
|
@@ -7,10 +7,11 @@ 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 { parsePropertyChangeInfo } from "./trackedChangeInfo.js";
|
|
10
11
|
import { captureVerbatimXml } from "./verbatimCapture.js";
|
|
11
12
|
import { parseVmlImageContent } from "./vmlImageParser.js";
|
|
12
13
|
import { cloneWithXmlnsDeclarations, findAllDeep, findChild, findChildren, getAttribute, getChildElements, getLocalName, getTextContent, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute } from "./xmlParser.js";
|
|
13
|
-
import { DRAWING_RAW_XML_MODES
|
|
14
|
+
import { DRAWING_RAW_XML_MODES } from "@stll/docx-core/model";
|
|
14
15
|
//#region src/docx/runParser.ts
|
|
15
16
|
/**
|
|
16
17
|
* Sanity cap on `w:lang` `@w:val`/`@w:eastAsia`/`@w:bidi` tag length. BCP-47
|
|
@@ -341,20 +342,6 @@ function parseRunProperties(rPr, theme, _styles) {
|
|
|
341
342
|
}
|
|
342
343
|
return Object.keys(formatting).length > 0 ? formatting : void 0;
|
|
343
344
|
}
|
|
344
|
-
function parsePropertyChangeInfo(changeElement) {
|
|
345
|
-
const rawId = getAttribute(changeElement, "w", "id");
|
|
346
|
-
const parsedId = rawId ? Number.parseInt(rawId, 10) : 0;
|
|
347
|
-
const author = (getAttribute(changeElement, "w", "author") ?? "").trim();
|
|
348
|
-
const date = (getAttribute(changeElement, "w", "date") ?? "").trim();
|
|
349
|
-
const rsid = (getAttribute(changeElement, "w", "rsid") ?? "").trim();
|
|
350
|
-
const info = {
|
|
351
|
-
id: normalizeRevisionId(parsedId),
|
|
352
|
-
author: author.length > 0 ? author : "Unknown"
|
|
353
|
-
};
|
|
354
|
-
if (date.length > 0) info.date = date;
|
|
355
|
-
if (rsid.length > 0) info.rsid = rsid;
|
|
356
|
-
return info;
|
|
357
|
-
}
|
|
358
345
|
function parseRunPropertyChanges(rPr, theme, styles, currentFormatting) {
|
|
359
346
|
if (!rPr) return;
|
|
360
347
|
const changes = findChildren(rPr, "w", "rPrChange").map((changeElement) => {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { parseFooterReference, parseHeaderReference } from "./headerFooterRefParser.js";
|
|
2
2
|
import { parseEndnoteProperties, parseFootnoteProperties } from "./notePropertiesParser.js";
|
|
3
3
|
import { BorderStyleSchema, NumberFormatSchema, ThemeColorSlotSchema, narrowEnum } from "./parserEnums.js";
|
|
4
|
+
import { parsePropertyChangeInfo } from "./trackedChangeInfo.js";
|
|
4
5
|
import { findChild, findChildren, getAttribute, getChildElements, getLocalName, parseBooleanElement, parseNumericAttribute } from "./xmlParser.js";
|
|
5
|
-
import { normalizeRevisionId } from "@stll/docx-core/model";
|
|
6
6
|
//#region src/docx/sectionParser.ts
|
|
7
7
|
/**
|
|
8
8
|
* Sanity cap on `w:cols/@w:num`. Word's column picker tops out well below
|
|
@@ -61,20 +61,6 @@ function parseColorValue(colorStr, themeColor, themeTint, themeShade) {
|
|
|
61
61
|
if (themeShade) color.themeShade = themeShade;
|
|
62
62
|
return Object.keys(color).length > 0 ? color : void 0;
|
|
63
63
|
}
|
|
64
|
-
function parsePropertyChangeInfo(node) {
|
|
65
|
-
const rawId = getAttribute(node, "w", "id");
|
|
66
|
-
const parsedId = rawId ? Number.parseInt(rawId, 10) : 0;
|
|
67
|
-
const author = (getAttribute(node, "w", "author") ?? "").trim();
|
|
68
|
-
const date = (getAttribute(node, "w", "date") ?? "").trim();
|
|
69
|
-
const rsid = (getAttribute(node, "w", "rsid") ?? "").trim();
|
|
70
|
-
const info = {
|
|
71
|
-
id: normalizeRevisionId(parsedId),
|
|
72
|
-
author: author.length > 0 ? author : "Unknown"
|
|
73
|
-
};
|
|
74
|
-
if (date.length > 0) info.date = date;
|
|
75
|
-
if (rsid.length > 0) info.rsid = rsid;
|
|
76
|
-
return info;
|
|
77
|
-
}
|
|
78
64
|
/**
|
|
79
65
|
* Parse a border element for page borders
|
|
80
66
|
*/
|