@stll/folio-core 0.37.1 → 0.37.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.
- package/dist/ai-edits/headless.d.ts +24 -1
- package/dist/ai-edits/headless.js +108 -71
- package/dist/ai-edits/read.d.ts +9 -1
- package/dist/ai-edits/read.js +34 -16
- package/dist/ai-edits/snapshot.d.ts +7 -5
- package/dist/ai-edits/snapshot.js +39 -12
- package/dist/ai-edits/table-cell-mutations.js +10 -5
- package/dist/ai-edits/table-template.js +19 -5
- package/dist/compare/compare.d.ts +2 -3
- package/dist/compare/compare.js +41 -64
- package/dist/compare/content-alignment.js +0 -1
- package/dist/compare/scenario.d.ts +11 -2
- package/dist/compare/scenario.js +6 -2
- package/dist/controller/hiddenEditorManager.js +11 -0
- package/dist/controller/layoutPipeline.js +1 -1
- package/dist/display-list/build/textBoxPrimitives.js +34 -1
- package/dist/display-list/dom/renderDisplayListToDom.js +5 -2
- package/dist/display-list/primitives.d.ts +1 -1
- package/dist/display-list/types.d.ts +8 -4
- package/dist/docx/paragraphPropertySource.d.ts +82 -2
- package/dist/docx/paragraphPropertySource.js +569 -3
- package/dist/docx/parser.js +9 -0
- package/dist/docx/server/materializeYjsDocx.d.ts +1 -1
- package/dist/docx/server/materializeYjsDocx.js +21 -2
- package/dist/headless-layout.js +1 -1
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +7 -1
- package/dist/layout-bridge/convert/headerFooterLayout.js +20 -3
- package/dist/layout-bridge/convert/toFlowBlocks.js +43 -12
- package/dist/layout-engine/index.js +20 -10
- package/dist/layout-engine/measure/measureBlocks.d.ts +7 -3
- package/dist/layout-engine/measure/measureBlocks.js +10 -7
- package/dist/layout-engine/measure/measureParagraph.d.ts +2 -0
- package/dist/layout-engine/measure/measureParagraph.js +1 -1
- package/dist/layout-engine/paginator.d.ts +1 -1
- package/dist/layout-engine/paginator.js +41 -4
- package/dist/layout-engine/textBoxFlow.d.ts +2 -0
- package/dist/layout-engine/textBoxFlow.js +9 -5
- package/dist/layout-engine/types.d.ts +6 -0
- package/dist/layout-painter/documentColors.d.ts +10 -1
- package/dist/layout-painter/documentColors.js +16 -1
- package/dist/layout-painter/renderParagraph.js +9 -24
- package/dist/layout-painter/renderTable.js +3 -3
- package/dist/layout-painter/renderTextBox.js +2 -1
- package/dist/pdf/pageSpace.d.ts +12 -1
- package/dist/pdf/pageSpace.js +24 -1
- package/dist/pdf/paint.js +2 -2
- package/dist/prosemirror/attrs/index.js +19 -8
- package/dist/prosemirror/commands/comments.js +12 -3
- package/dist/prosemirror/commands/tableCellMergeResolution.js +18 -11
- package/dist/prosemirror/conversion/fromProseDoc.js +94 -21
- package/dist/prosemirror/conversion/toProseDoc.js +100 -8
- package/dist/prosemirror/extensions/core/DocExtension.js +2 -0
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +2 -0
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +5 -1
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +132 -41
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +2 -0
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +7 -0
- package/dist/prosemirror/schema/nodes.d.ts +3 -1
- package/dist/prosemirror/yjsParagraphSourceContract.d.ts +9 -0
- package/dist/prosemirror/yjsParagraphSourceContract.js +26 -0
- package/dist/utils/rotationBoundingBox.d.ts +5 -1
- package/dist/utils/rotationBoundingBox.js +9 -1
- package/package.json +2 -2
|
@@ -1,27 +1,151 @@
|
|
|
1
1
|
import { visitDocxParagraphs } from "./paragraphTraversal.js";
|
|
2
|
-
import { panic } from "better-result";
|
|
2
|
+
import { TaggedError, panic } from "better-result";
|
|
3
|
+
import { PluginKey } from "prosemirror-state";
|
|
3
4
|
//#region src/docx/paragraphPropertySource.ts
|
|
4
5
|
const paragraphPropertySources = /* @__PURE__ */ new WeakMap();
|
|
5
6
|
const paragraphPropertySourceOwners = /* @__PURE__ */ new WeakMap();
|
|
6
7
|
const proseParagraphSourceOwners = /* @__PURE__ */ new WeakMap();
|
|
7
8
|
const paragraphPropertySourceCandidates = /* @__PURE__ */ new WeakMap();
|
|
8
9
|
const paragraphPropertySourceTransferIds = /* @__PURE__ */ new WeakMap();
|
|
10
|
+
const paragraphPropertySourceTokens = /* @__PURE__ */ new WeakMap();
|
|
11
|
+
const documentParagraphPropertySourceBindingBrand = Symbol("documentParagraphPropertySourceBinding");
|
|
12
|
+
const documentParagraphPropertySourceBinding = Symbol("paragraphPropertySourceBinding");
|
|
13
|
+
const PROSE_PARAGRAPH_SOURCE_TOKEN_ATTR = "_docxParagraphSourceToken";
|
|
14
|
+
const PROSE_PARAGRAPH_SOURCE_CONTRACT_ATTR = "_docxParagraphSourceContract";
|
|
15
|
+
/** Durable identity nested inside opaque collapsed-cell ProseMirror attrs; stripped on restore. */
|
|
16
|
+
const TABLE_CELL_PARAGRAPH_SOURCE_BINDING_ATTR = "_docxParagraphSourceBinding";
|
|
17
|
+
const PARAGRAPH_SOURCE_TOKEN_VERSION = "folio-ppr-v1";
|
|
18
|
+
const PARAGRAPH_SOURCE_TOKEN_PREFIX = "p1d";
|
|
19
|
+
const SHA256_HEX = /^[0-9a-f]{64}$/;
|
|
20
|
+
const SOURCE_TOKEN = /^p1d:([0-9a-f]{32}):(0|[1-9a-z][0-9a-z]*)$/;
|
|
21
|
+
const PARAGRAPH_PROPERTY_SOURCE_VALIDATION_CODES = [
|
|
22
|
+
"contract_mismatch",
|
|
23
|
+
"duplicate_token",
|
|
24
|
+
"invalid_token",
|
|
25
|
+
"unknown_token"
|
|
26
|
+
];
|
|
27
|
+
const TABLE_CELL_PARAGRAPH_SOURCE_PAYLOAD_ERROR_CLASSIFICATIONS = [
|
|
28
|
+
"complexity_limit",
|
|
29
|
+
"cyclic_or_aliased_graph",
|
|
30
|
+
"depth_limit",
|
|
31
|
+
"expected_array",
|
|
32
|
+
"expected_block",
|
|
33
|
+
"expected_cell",
|
|
34
|
+
"expected_inline_content",
|
|
35
|
+
"expected_paragraph",
|
|
36
|
+
"expected_record",
|
|
37
|
+
"expected_row",
|
|
38
|
+
"invalid_binding",
|
|
39
|
+
"invalid_block_type",
|
|
40
|
+
"invalid_cell_type",
|
|
41
|
+
"invalid_graph_value",
|
|
42
|
+
"invalid_inline_content_type",
|
|
43
|
+
"invalid_paragraph_type",
|
|
44
|
+
"invalid_row_type",
|
|
45
|
+
"malformed_source_token"
|
|
46
|
+
];
|
|
47
|
+
var ParagraphPropertySourceValidationError = class extends TaggedError("ParagraphPropertySourceValidationError") {};
|
|
48
|
+
const contractForDigest = (sourceDigest) => {
|
|
49
|
+
if (!SHA256_HEX.test(sourceDigest)) panic("Paragraph-property source digest must be lowercase SHA-256 hex");
|
|
50
|
+
return `${PARAGRAPH_SOURCE_TOKEN_VERSION}:${sourceDigest}`;
|
|
51
|
+
};
|
|
52
|
+
const fingerprintFromContract = (contract) => {
|
|
53
|
+
const prefix = `${PARAGRAPH_SOURCE_TOKEN_VERSION}:`;
|
|
54
|
+
const digest = contract.startsWith(prefix) ? contract.slice(prefix.length) : "";
|
|
55
|
+
return SHA256_HEX.test(digest) ? digest.slice(0, 32) : null;
|
|
56
|
+
};
|
|
57
|
+
const tokenForOrdinal = (contract, ordinal) => {
|
|
58
|
+
const fingerprint = fingerprintFromContract(contract);
|
|
59
|
+
if (!fingerprint) panic("Cannot mint a paragraph-property token for an invalid source contract");
|
|
60
|
+
return `${PARAGRAPH_SOURCE_TOKEN_PREFIX}:${fingerprint}:${ordinal.toString(36)}`;
|
|
61
|
+
};
|
|
62
|
+
const isDocumentParagraphPropertySourceBinding = (value) => typeof value === "object" && value !== null && documentParagraphPropertySourceBindingBrand in value && value[documentParagraphPropertySourceBindingBrand] === true && "contract" in value && typeof value.contract === "string" && "sourceOwners" in value && value.sourceOwners instanceof Set && "sources" in value && value.sources instanceof Map;
|
|
63
|
+
const setDocumentParagraphPropertySourceBinding = (document, binding) => {
|
|
64
|
+
if (!fingerprintFromContract(binding.contract)) panic("Cannot attach an invalid paragraph-property source contract");
|
|
65
|
+
if ((Object.hasOwn(document, documentParagraphPropertySourceBinding) ? Reflect.get(document, documentParagraphPropertySourceBinding) : void 0) === binding) return;
|
|
66
|
+
if (!Reflect.defineProperty(document, documentParagraphPropertySourceBinding, {
|
|
67
|
+
enumerable: true,
|
|
68
|
+
value: binding
|
|
69
|
+
})) panic("Cannot attach the paragraph-property source contract to the document");
|
|
70
|
+
};
|
|
71
|
+
const isParagraphPropertySourceToken = (token) => typeof token === "string" && SOURCE_TOKEN.test(token);
|
|
72
|
+
/** True only for a body-story token derived from the exact source contract. */
|
|
73
|
+
const paragraphPropertySourceTokenMatchesContract = (token, contract) => {
|
|
74
|
+
if (!isParagraphPropertySourceToken(token)) return false;
|
|
75
|
+
const match = SOURCE_TOKEN.exec(token);
|
|
76
|
+
const fingerprint = fingerprintFromContract(contract);
|
|
77
|
+
return match !== null && fingerprint !== null && match[1] === fingerprint;
|
|
78
|
+
};
|
|
79
|
+
/** Visit the v1 provenance scope in canonical OOXML body-story order. */
|
|
80
|
+
const visitDocumentStoryParagraphs = (content, visit) => {
|
|
81
|
+
visitDocxParagraphs({ documentBody: { content } }, visit);
|
|
82
|
+
};
|
|
9
83
|
const assignParagraphPropertySource = (paragraph, source) => {
|
|
10
84
|
paragraphPropertySources.set(paragraph, source);
|
|
11
85
|
paragraphPropertySourceOwners.set(paragraph, paragraph);
|
|
12
86
|
};
|
|
13
87
|
const getParagraphPropertySource = (paragraph) => paragraphPropertySources.get(paragraph);
|
|
14
|
-
|
|
88
|
+
/** Copy the captured `w:pPr` without claiming the source paragraph's durable identity. */
|
|
89
|
+
const copyParagraphPropertyCapture = (target, source) => {
|
|
15
90
|
const propertySource = paragraphPropertySources.get(source);
|
|
16
91
|
if (propertySource) {
|
|
17
92
|
paragraphPropertySources.set(target, { ...propertySource });
|
|
18
93
|
paragraphPropertySourceOwners.set(target, paragraphPropertySourceOwners.get(source) ?? source);
|
|
19
94
|
}
|
|
95
|
+
};
|
|
96
|
+
const copyParagraphPropertySource = (target, source) => {
|
|
97
|
+
copyParagraphPropertyCapture(target, source);
|
|
20
98
|
const transferId = paragraphPropertySourceTransferIds.get(source);
|
|
21
99
|
if (transferId) paragraphPropertySourceTransferIds.set(target, transferId);
|
|
22
100
|
const candidate = paragraphPropertySourceCandidates.get(source);
|
|
23
101
|
if (candidate) paragraphPropertySourceCandidates.set(target, candidate);
|
|
102
|
+
const token = paragraphPropertySourceTokens.get(source);
|
|
103
|
+
if (token) paragraphPropertySourceTokens.set(target, token);
|
|
24
104
|
};
|
|
105
|
+
/** Bind parsed body paragraphs to one exact source package. */
|
|
106
|
+
const assignDocumentParagraphPropertySourceContract = (document, sourceDigest) => {
|
|
107
|
+
const contract = contractForDigest(sourceDigest);
|
|
108
|
+
const sources = /* @__PURE__ */ new Map();
|
|
109
|
+
let ordinal = 0;
|
|
110
|
+
visitDocumentStoryParagraphs(document.package.document.content, (paragraph) => {
|
|
111
|
+
const token = tokenForOrdinal(contract, ordinal);
|
|
112
|
+
paragraphPropertySourceTokens.set(paragraph, token);
|
|
113
|
+
sources.set(token, paragraph);
|
|
114
|
+
ordinal += 1;
|
|
115
|
+
});
|
|
116
|
+
setDocumentParagraphPropertySourceBinding(document, Object.freeze({
|
|
117
|
+
[documentParagraphPropertySourceBindingBrand]: true,
|
|
118
|
+
contract,
|
|
119
|
+
sourceOwners: new Set(sources.values()),
|
|
120
|
+
sources
|
|
121
|
+
}));
|
|
122
|
+
};
|
|
123
|
+
const getDocumentParagraphPropertySourceBinding = (document) => {
|
|
124
|
+
if (!Object.hasOwn(document, documentParagraphPropertySourceBinding)) return;
|
|
125
|
+
const binding = Reflect.get(document, documentParagraphPropertySourceBinding);
|
|
126
|
+
if (!isDocumentParagraphPropertySourceBinding(binding) || !fingerprintFromContract(binding.contract)) panic("The document carries an invalid paragraph-property source contract");
|
|
127
|
+
return binding;
|
|
128
|
+
};
|
|
129
|
+
const getDocumentParagraphPropertySourceContract = (document) => getDocumentParagraphPropertySourceBinding(document)?.contract;
|
|
130
|
+
const copyDocumentParagraphPropertySources = (document) => {
|
|
131
|
+
const sources = getDocumentParagraphPropertySourceBinding(document)?.sources;
|
|
132
|
+
return sources ? new Map(sources) : void 0;
|
|
133
|
+
};
|
|
134
|
+
const paragraphPropertySourceBelongsToDocument = (paragraph, document) => {
|
|
135
|
+
const owner = paragraphPropertySourceOwners.get(paragraph);
|
|
136
|
+
const binding = getDocumentParagraphPropertySourceBinding(document);
|
|
137
|
+
return owner !== void 0 && binding !== void 0 && binding.sourceOwners.has(owner);
|
|
138
|
+
};
|
|
139
|
+
const getProseDocumentParagraphPropertySourceContract = (document) => {
|
|
140
|
+
const contract = document.attrs[PROSE_PARAGRAPH_SOURCE_CONTRACT_ATTR];
|
|
141
|
+
return typeof contract === "string" && fingerprintFromContract(contract) ? contract : null;
|
|
142
|
+
};
|
|
143
|
+
const getProseParagraphPropertySourceToken = (paragraph) => paragraph.attrs[PROSE_PARAGRAPH_SOURCE_TOKEN_ATTR];
|
|
144
|
+
const copyDocumentParagraphPropertySourceContract = (target, source) => {
|
|
145
|
+
const binding = getDocumentParagraphPropertySourceBinding(source);
|
|
146
|
+
if (binding) setDocumentParagraphPropertySourceBinding(target, binding);
|
|
147
|
+
};
|
|
148
|
+
const getParagraphPropertySourceToken = (paragraph) => paragraphPropertySourceTokens.get(paragraph);
|
|
25
149
|
/** Clone a paragraph while deliberately retaining its parsed `w:pPr` owner. */
|
|
26
150
|
const cloneParagraphWithPropertySource = (paragraph, overrides) => {
|
|
27
151
|
const cloned = {
|
|
@@ -62,11 +186,436 @@ const cloneDocumentWithParagraphPropertySources = (document) => {
|
|
|
62
186
|
if (!target) panic("The cloned document lost a paragraph owner.");
|
|
63
187
|
copyParagraphPropertySource(target, source);
|
|
64
188
|
}
|
|
189
|
+
copyDocumentParagraphPropertySourceContract(cloned, document);
|
|
190
|
+
return cloned;
|
|
191
|
+
};
|
|
192
|
+
const paragraphsInTableCells = (cells) => {
|
|
193
|
+
const paragraphs = [];
|
|
194
|
+
for (const cell of cells) visitDocxParagraphs({ documentBody: { content: cell.content } }, (paragraph) => paragraphs.push(paragraph));
|
|
195
|
+
return paragraphs;
|
|
196
|
+
};
|
|
197
|
+
const isPropertySourceBindingRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
198
|
+
const inspectTableCellParagraphPropertySourceBinding = (paragraph) => {
|
|
199
|
+
if (!Object.hasOwn(paragraph, "_docxParagraphSourceBinding")) return { status: "absent" };
|
|
200
|
+
const value = Reflect.get(paragraph, TABLE_CELL_PARAGRAPH_SOURCE_BINDING_ATTR);
|
|
201
|
+
if (!isPropertySourceBindingRecord(value)) return { status: "invalid" };
|
|
202
|
+
const keys = Reflect.ownKeys(value);
|
|
203
|
+
if (keys.length === 1 && keys[0] === "type" && Object.hasOwn(value, "type") && value["type"] === "authored") return {
|
|
204
|
+
binding: { type: "authored" },
|
|
205
|
+
status: "authored"
|
|
206
|
+
};
|
|
207
|
+
if (keys.length === 2 && keys.includes("type") && keys.includes("token") && Object.hasOwn(value, "type") && value["type"] === "source" && Object.hasOwn(value, "token") && typeof value["token"] === "string") return {
|
|
208
|
+
binding: {
|
|
209
|
+
token: value["token"],
|
|
210
|
+
type: "source"
|
|
211
|
+
},
|
|
212
|
+
status: "source"
|
|
213
|
+
};
|
|
214
|
+
return { status: "invalid" };
|
|
215
|
+
};
|
|
216
|
+
const TABLE_CELL_PARAGRAPH_SOURCE_MAX_DEPTH = 128;
|
|
217
|
+
const TABLE_CELL_PARAGRAPH_SOURCE_MAX_VALUES = 1e5;
|
|
218
|
+
const ARRAY_INDEX = /^(?:0|[1-9][0-9]*)$/;
|
|
219
|
+
const tableCellParagraphSourcePayloadMessages = {
|
|
220
|
+
complexity_limit: "The hidden table-cell payload exceeds its complexity limit.",
|
|
221
|
+
cyclic_or_aliased_graph: "The hidden table-cell payload contains a cyclic or aliased graph.",
|
|
222
|
+
depth_limit: "The hidden table-cell payload exceeds its nesting limit.",
|
|
223
|
+
expected_array: "The hidden table-cell payload field must be an array.",
|
|
224
|
+
expected_block: "The hidden table-cell payload contains a malformed block.",
|
|
225
|
+
expected_cell: "The hidden table-cell payload contains a malformed cell.",
|
|
226
|
+
expected_inline_content: "The hidden table-cell payload contains malformed inline content.",
|
|
227
|
+
expected_paragraph: "The hidden table-cell payload contains a malformed paragraph.",
|
|
228
|
+
expected_record: "The hidden table-cell payload contains a malformed object.",
|
|
229
|
+
expected_row: "The hidden table-cell payload contains a malformed row.",
|
|
230
|
+
invalid_binding: "A hidden table-cell paragraph has an invalid source binding.",
|
|
231
|
+
invalid_block_type: "The hidden table-cell payload contains an unsupported block type.",
|
|
232
|
+
invalid_cell_type: "The hidden table-cell payload contains an invalid cell type.",
|
|
233
|
+
invalid_graph_value: "The hidden table-cell payload contains an invalid graph value.",
|
|
234
|
+
invalid_inline_content_type: "The hidden table-cell payload contains an unsupported inline content type.",
|
|
235
|
+
invalid_paragraph_type: "The hidden table-cell payload contains an invalid paragraph type.",
|
|
236
|
+
invalid_row_type: "The hidden table-cell payload contains an invalid row type.",
|
|
237
|
+
malformed_source_token: "A hidden table-cell paragraph has a malformed source token."
|
|
238
|
+
};
|
|
239
|
+
const tableCellParagraphSourceValidationErrors = /* @__PURE__ */ new WeakSet();
|
|
240
|
+
const invalidTableCellParagraphSourcePayload = (classification, path) => {
|
|
241
|
+
const error = new ParagraphPropertySourceValidationError({
|
|
242
|
+
code: "invalid_token",
|
|
243
|
+
classification,
|
|
244
|
+
message: tableCellParagraphSourcePayloadMessages[classification],
|
|
245
|
+
path
|
|
246
|
+
});
|
|
247
|
+
tableCellParagraphSourceValidationErrors.add(error);
|
|
248
|
+
throw error;
|
|
249
|
+
};
|
|
250
|
+
const tableCellBlockTraversalByType = {
|
|
251
|
+
blockSdt: "blockSdt",
|
|
252
|
+
paragraph: "paragraph",
|
|
253
|
+
table: "table"
|
|
254
|
+
};
|
|
255
|
+
const tableCellParagraphContentTraversalByType = {
|
|
256
|
+
bookmarkEnd: "leaf",
|
|
257
|
+
bookmarkStart: "leaf",
|
|
258
|
+
commentRangeEnd: "leaf",
|
|
259
|
+
commentRangeStart: "leaf",
|
|
260
|
+
commentReference: "leaf",
|
|
261
|
+
complexField: "complexField",
|
|
262
|
+
deletion: "content",
|
|
263
|
+
hyperlink: "children",
|
|
264
|
+
inlineSdt: "content",
|
|
265
|
+
insertion: "content",
|
|
266
|
+
mathEquation: "leaf",
|
|
267
|
+
moveFrom: "content",
|
|
268
|
+
moveFromRangeEnd: "leaf",
|
|
269
|
+
moveFromRangeStart: "leaf",
|
|
270
|
+
moveTo: "content",
|
|
271
|
+
moveToRangeEnd: "leaf",
|
|
272
|
+
moveToRangeStart: "leaf",
|
|
273
|
+
run: "run",
|
|
274
|
+
simpleField: "content"
|
|
275
|
+
};
|
|
276
|
+
const tableCellRunContentTraversalByType = {
|
|
277
|
+
break: "leaf",
|
|
278
|
+
drawing: "leaf",
|
|
279
|
+
endnoteRef: "leaf",
|
|
280
|
+
fieldChar: "leaf",
|
|
281
|
+
footnoteRef: "leaf",
|
|
282
|
+
instrText: "leaf",
|
|
283
|
+
noBreakHyphen: "leaf",
|
|
284
|
+
renderedPageBreak: "leaf",
|
|
285
|
+
shape: "shape",
|
|
286
|
+
softHyphen: "leaf",
|
|
287
|
+
symbol: "leaf",
|
|
288
|
+
tab: "leaf",
|
|
289
|
+
text: "leaf"
|
|
290
|
+
};
|
|
291
|
+
const isTableCellBlockContentType = (value) => typeof value === "string" && Object.hasOwn(tableCellBlockTraversalByType, value);
|
|
292
|
+
const isTableCellParagraphContentType = (value) => typeof value === "string" && Object.hasOwn(tableCellParagraphContentTraversalByType, value);
|
|
293
|
+
const isTableCellRunContentType = (value) => typeof value === "string" && Object.hasOwn(tableCellRunContentTraversalByType, value);
|
|
294
|
+
const inspectTableCellParagraphSourceGraph = (value, path, depth, context) => {
|
|
295
|
+
if (depth > TABLE_CELL_PARAGRAPH_SOURCE_MAX_DEPTH) invalidTableCellParagraphSourcePayload("depth_limit", path);
|
|
296
|
+
context.values += 1;
|
|
297
|
+
if (context.values > TABLE_CELL_PARAGRAPH_SOURCE_MAX_VALUES) invalidTableCellParagraphSourcePayload("complexity_limit", path);
|
|
298
|
+
if (value === null || value === void 0 || typeof value === "boolean" || typeof value === "number" || typeof value === "string") return;
|
|
299
|
+
if (typeof value !== "object") return invalidTableCellParagraphSourcePayload("invalid_graph_value", path);
|
|
300
|
+
if (context.seen.has(value)) return invalidTableCellParagraphSourcePayload("cyclic_or_aliased_graph", path);
|
|
301
|
+
context.seen.add(value);
|
|
302
|
+
context.objects.push(value);
|
|
303
|
+
const prototype = Object.getPrototypeOf(value);
|
|
304
|
+
if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) return invalidTableCellParagraphSourcePayload("invalid_graph_value", path);
|
|
305
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
306
|
+
if (Array.isArray(value) && key === "length") {
|
|
307
|
+
if (value.length > TABLE_CELL_PARAGRAPH_SOURCE_MAX_VALUES) invalidTableCellParagraphSourcePayload("complexity_limit", path);
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
let childPath = `${path}.*`;
|
|
311
|
+
if (Array.isArray(value)) {
|
|
312
|
+
if (typeof key !== "string" || !ARRAY_INDEX.test(key)) return invalidTableCellParagraphSourcePayload("invalid_graph_value", `${path}[*]`);
|
|
313
|
+
childPath = `${path}[${key}]`;
|
|
314
|
+
}
|
|
315
|
+
if (typeof key !== "string") return invalidTableCellParagraphSourcePayload("invalid_graph_value", childPath);
|
|
316
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
317
|
+
if (descriptor === void 0 || !descriptor.enumerable || !("value" in descriptor)) return invalidTableCellParagraphSourcePayload("invalid_graph_value", childPath);
|
|
318
|
+
inspectTableCellParagraphSourceGraph(descriptor.value, childPath, depth + 1, context);
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
const tableCellParagraphSourceArray = (value, path) => {
|
|
322
|
+
if (!Array.isArray(value)) return invalidTableCellParagraphSourcePayload("expected_array", path);
|
|
323
|
+
return value;
|
|
324
|
+
};
|
|
325
|
+
const tableCellParagraphSourceRecord = (value, path, classification) => {
|
|
326
|
+
if (!isPropertySourceBindingRecord(value)) return invalidTableCellParagraphSourcePayload(classification, path);
|
|
327
|
+
return value;
|
|
328
|
+
};
|
|
329
|
+
const isDecodedTableCellParagraph = (value) => isPropertySourceBindingRecord(value) && value["type"] === "paragraph" && Array.isArray(value["content"]);
|
|
330
|
+
const isDecodedTableCell = (value) => isPropertySourceBindingRecord(value) && value["type"] === "tableCell" && Array.isArray(value["content"]);
|
|
331
|
+
const visitDecodedTableCellRunContent = (value, path, context) => {
|
|
332
|
+
const content = tableCellParagraphSourceRecord(value, path, "expected_inline_content");
|
|
333
|
+
const contentType = content["type"];
|
|
334
|
+
if (!isTableCellRunContentType(contentType)) return invalidTableCellParagraphSourcePayload("invalid_inline_content_type", path);
|
|
335
|
+
const traversal = tableCellRunContentTraversalByType[contentType];
|
|
336
|
+
switch (traversal) {
|
|
337
|
+
case "shape": {
|
|
338
|
+
const shapePath = `${path}.shape`;
|
|
339
|
+
const rawTextBody = tableCellParagraphSourceRecord(content["shape"], shapePath, "expected_record")["textBody"];
|
|
340
|
+
if (rawTextBody === void 0 || rawTextBody === null) return;
|
|
341
|
+
const textBodyPath = `${shapePath}.textBody`;
|
|
342
|
+
const textBody = tableCellParagraphSourceRecord(rawTextBody, textBodyPath, "expected_record");
|
|
343
|
+
const blocks = tableCellParagraphSourceArray(textBody["content"], `${textBodyPath}.content`);
|
|
344
|
+
for (const [index, block] of blocks.entries()) visitDecodedTableCellBlock(block, `${textBodyPath}.content[${index}]`, context);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
case "leaf": return;
|
|
348
|
+
default: return traversal;
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
const visitDecodedTableCellRun = (run, path, context) => {
|
|
352
|
+
const content = tableCellParagraphSourceArray(run["content"], `${path}.content`);
|
|
353
|
+
for (const [index, item] of content.entries()) visitDecodedTableCellRunContent(item, `${path}.content[${index}]`, context);
|
|
354
|
+
};
|
|
355
|
+
const visitDecodedTableCellInlineContent = (value, path, context) => {
|
|
356
|
+
const content = tableCellParagraphSourceRecord(value, path, "expected_inline_content");
|
|
357
|
+
const contentType = content["type"];
|
|
358
|
+
if (!isTableCellParagraphContentType(contentType)) return invalidTableCellParagraphSourcePayload("invalid_inline_content_type", path);
|
|
359
|
+
const traversal = tableCellParagraphContentTraversalByType[contentType];
|
|
360
|
+
switch (traversal) {
|
|
361
|
+
case "run":
|
|
362
|
+
visitDecodedTableCellRun(content, path, context);
|
|
363
|
+
return;
|
|
364
|
+
case "children": {
|
|
365
|
+
const children = tableCellParagraphSourceArray(content["children"], `${path}.children`);
|
|
366
|
+
for (const [index, child] of children.entries()) visitDecodedTableCellInlineContent(child, `${path}.children[${index}]`, context);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
case "content": {
|
|
370
|
+
const children = tableCellParagraphSourceArray(content["content"], `${path}.content`);
|
|
371
|
+
for (const [index, child] of children.entries()) visitDecodedTableCellInlineContent(child, `${path}.content[${index}]`, context);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
case "complexField":
|
|
375
|
+
for (const field of ["fieldCode", "fieldResult"]) {
|
|
376
|
+
const runs = tableCellParagraphSourceArray(content[field], `${path}.${field}`);
|
|
377
|
+
for (const [index, runValue] of runs.entries()) {
|
|
378
|
+
const runPath = `${path}.${field}[${index}]`;
|
|
379
|
+
const run = tableCellParagraphSourceRecord(runValue, runPath, "expected_inline_content");
|
|
380
|
+
if (run["type"] !== "run") return invalidTableCellParagraphSourcePayload("invalid_inline_content_type", runPath);
|
|
381
|
+
visitDecodedTableCellRun(run, runPath, context);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return;
|
|
385
|
+
case "leaf": return;
|
|
386
|
+
default: return traversal;
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
const visitDecodedTableCellParagraph = (paragraph, path, context) => {
|
|
390
|
+
if (paragraph["type"] !== "paragraph") return invalidTableCellParagraphSourcePayload("invalid_paragraph_type", path);
|
|
391
|
+
if (!isDecodedTableCellParagraph(paragraph)) return invalidTableCellParagraphSourcePayload("expected_paragraph", path);
|
|
392
|
+
const inspection = inspectTableCellParagraphPropertySourceBinding(paragraph);
|
|
393
|
+
switch (inspection.status) {
|
|
394
|
+
case "authored":
|
|
395
|
+
case "source":
|
|
396
|
+
if (inspection.status === "source" && !isParagraphPropertySourceToken(inspection.binding.token)) invalidTableCellParagraphSourcePayload("malformed_source_token", `${path}.${TABLE_CELL_PARAGRAPH_SOURCE_BINDING_ATTR}.token`);
|
|
397
|
+
context.paragraphs.push({
|
|
398
|
+
binding: inspection.binding,
|
|
399
|
+
paragraph,
|
|
400
|
+
path
|
|
401
|
+
});
|
|
402
|
+
break;
|
|
403
|
+
case "absent":
|
|
404
|
+
case "invalid": return invalidTableCellParagraphSourcePayload("invalid_binding", `${path}.${TABLE_CELL_PARAGRAPH_SOURCE_BINDING_ATTR}`);
|
|
405
|
+
default: return inspection;
|
|
406
|
+
}
|
|
407
|
+
const content = tableCellParagraphSourceArray(paragraph.content, `${path}.content`);
|
|
408
|
+
for (const [index, item] of content.entries()) visitDecodedTableCellInlineContent(item, `${path}.content[${index}]`, context);
|
|
409
|
+
};
|
|
410
|
+
const visitDecodedTableCell = (value, path, context) => {
|
|
411
|
+
const cell = tableCellParagraphSourceRecord(value, path, "expected_cell");
|
|
412
|
+
if (cell["type"] !== "tableCell") return invalidTableCellParagraphSourcePayload("invalid_cell_type", path);
|
|
413
|
+
const content = tableCellParagraphSourceArray(cell["content"], `${path}.content`);
|
|
414
|
+
for (const [index, block] of content.entries()) visitDecodedTableCellBlock(block, `${path}.content[${index}]`, context);
|
|
415
|
+
if (!isDecodedTableCell(cell)) return invalidTableCellParagraphSourcePayload("expected_cell", path);
|
|
416
|
+
return cell;
|
|
417
|
+
};
|
|
418
|
+
const visitDecodedTableCellRow = (value, path, context) => {
|
|
419
|
+
const row = tableCellParagraphSourceRecord(value, path, "expected_row");
|
|
420
|
+
if (row["type"] !== "tableRow") return invalidTableCellParagraphSourcePayload("invalid_row_type", path);
|
|
421
|
+
const cells = tableCellParagraphSourceArray(row["cells"], `${path}.cells`);
|
|
422
|
+
for (const [index, cell] of cells.entries()) visitDecodedTableCell(cell, `${path}.cells[${index}]`, context);
|
|
423
|
+
};
|
|
424
|
+
function visitDecodedTableCellBlock(value, path, context) {
|
|
425
|
+
const block = tableCellParagraphSourceRecord(value, path, "expected_block");
|
|
426
|
+
const blockType = block["type"];
|
|
427
|
+
if (!isTableCellBlockContentType(blockType)) return invalidTableCellParagraphSourcePayload("invalid_block_type", path);
|
|
428
|
+
const traversal = tableCellBlockTraversalByType[blockType];
|
|
429
|
+
switch (traversal) {
|
|
430
|
+
case "paragraph":
|
|
431
|
+
visitDecodedTableCellParagraph(block, path, context);
|
|
432
|
+
return;
|
|
433
|
+
case "table": {
|
|
434
|
+
const rows = tableCellParagraphSourceArray(block["rows"], `${path}.rows`);
|
|
435
|
+
for (const [index, row] of rows.entries()) visitDecodedTableCellRow(row, `${path}.rows[${index}]`, context);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
case "blockSdt": {
|
|
439
|
+
const content = tableCellParagraphSourceArray(block["content"], `${path}.content`);
|
|
440
|
+
for (const [index, child] of content.entries()) visitDecodedTableCellBlock(child, `${path}.content[${index}]`, context);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
default: return traversal;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
const decodedTableCellParagraphSourcePayloadBrand = Symbol("decodedTableCellParagraphSourcePayload");
|
|
447
|
+
const decodedTableCellParagraphSourcePayloadStates = /* @__PURE__ */ new WeakMap();
|
|
448
|
+
const decodedTableCellParagraphSourcePayloads = /* @__PURE__ */ new WeakMap();
|
|
449
|
+
const decodedTableCellParagraphSourcePayloadState = (payload) => {
|
|
450
|
+
const state = decodedTableCellParagraphSourcePayloadStates.get(payload);
|
|
451
|
+
if (!state) panic("A collapsed-cell paragraph source payload bypassed its decoder.");
|
|
452
|
+
return state;
|
|
453
|
+
};
|
|
454
|
+
const decodeTableCellParagraphSourcePayloadUnchecked = (value) => {
|
|
455
|
+
const graphContext = {
|
|
456
|
+
objects: [],
|
|
457
|
+
seen: /* @__PURE__ */ new WeakSet(),
|
|
458
|
+
values: 0
|
|
459
|
+
};
|
|
460
|
+
inspectTableCellParagraphSourceGraph(value, "continuationCells", 0, graphContext);
|
|
461
|
+
const context = { paragraphs: [] };
|
|
462
|
+
const rawCells = tableCellParagraphSourceArray(value, "continuationCells");
|
|
463
|
+
const cells = [];
|
|
464
|
+
for (const [index, rawCell] of rawCells.entries()) cells.push(visitDecodedTableCell(rawCell, `continuationCells[${index}]`, context));
|
|
465
|
+
let object = graphContext.objects.pop();
|
|
466
|
+
while (object) {
|
|
467
|
+
Object.freeze(object);
|
|
468
|
+
object = graphContext.objects.pop();
|
|
469
|
+
}
|
|
470
|
+
const payload = Object.freeze({
|
|
471
|
+
[decodedTableCellParagraphSourcePayloadBrand]: true,
|
|
472
|
+
cells: Object.freeze(cells)
|
|
473
|
+
});
|
|
474
|
+
decodedTableCellParagraphSourcePayloadStates.set(payload, Object.freeze({ paragraphs: Object.freeze(context.paragraphs.map((entry) => Object.freeze(entry))) }));
|
|
475
|
+
decodedTableCellParagraphSourcePayloads.set(payload.cells, payload);
|
|
476
|
+
return payload;
|
|
477
|
+
};
|
|
478
|
+
/** Decode and freeze the opaque collapsed-cell wire payload before typed use. */
|
|
479
|
+
const decodeTableCellParagraphSourcePayload = (value) => {
|
|
480
|
+
if (typeof value === "object" && value !== null) {
|
|
481
|
+
const decoded = decodedTableCellParagraphSourcePayloads.get(value);
|
|
482
|
+
if (decoded) return decoded;
|
|
483
|
+
}
|
|
484
|
+
try {
|
|
485
|
+
const decoded = decodeTableCellParagraphSourcePayloadUnchecked(value);
|
|
486
|
+
if (typeof value === "object" && value !== null) decodedTableCellParagraphSourcePayloads.set(value, decoded);
|
|
487
|
+
return decoded;
|
|
488
|
+
} catch (error) {
|
|
489
|
+
if (error instanceof ParagraphPropertySourceValidationError && tableCellParagraphSourceValidationErrors.has(error)) throw error;
|
|
490
|
+
return invalidTableCellParagraphSourcePayload("invalid_graph_value", "continuationCells");
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
const setTableCellParagraphPropertySourceBinding = (paragraph, binding) => {
|
|
494
|
+
if (!Reflect.set(paragraph, "_docxParagraphSourceBinding", Object.freeze(binding))) panic("Cannot attach paragraph-property transport identity to a table cell.");
|
|
495
|
+
};
|
|
496
|
+
const tableCellParagraphPropertySourceBindingForTransport = (paragraph, inspection) => {
|
|
497
|
+
const directToken = paragraphPropertySourceTokens.get(paragraph);
|
|
498
|
+
if (directToken !== void 0) {
|
|
499
|
+
if (!isParagraphPropertySourceToken(directToken)) panic("Cannot transport a malformed private paragraph-property source token.");
|
|
500
|
+
switch (inspection.status) {
|
|
501
|
+
case "absent": return {
|
|
502
|
+
token: directToken,
|
|
503
|
+
type: "source"
|
|
504
|
+
};
|
|
505
|
+
case "authored": panic("A hidden paragraph-property source binding conflicts with its private owner.");
|
|
506
|
+
case "source":
|
|
507
|
+
if (inspection.binding.token !== directToken) panic("A hidden paragraph-property source binding conflicts with its private owner.");
|
|
508
|
+
return inspection.binding;
|
|
509
|
+
case "invalid": panic("Cannot transport a malformed hidden paragraph-property source binding.");
|
|
510
|
+
default: return inspection;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
switch (inspection.status) {
|
|
514
|
+
case "absent": return { type: "authored" };
|
|
515
|
+
case "authored": return inspection.binding;
|
|
516
|
+
case "source":
|
|
517
|
+
if (!isParagraphPropertySourceToken(inspection.binding.token)) panic("Cannot transport a malformed hidden paragraph-property source token.");
|
|
518
|
+
return inspection.binding;
|
|
519
|
+
case "invalid": panic("Cannot transport a malformed hidden paragraph-property source binding.");
|
|
520
|
+
default: return inspection;
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
/** Prepare opaque continuation cells for ProseMirror and Yjs transport. */
|
|
524
|
+
const transportTableCellsWithParagraphPropertySources = (cells) => {
|
|
525
|
+
const cloned = structuredClone(cells);
|
|
526
|
+
const sources = paragraphsInTableCells(cells);
|
|
527
|
+
const targets = paragraphsInTableCells(cloned);
|
|
528
|
+
if (sources.length !== targets.length) panic("The cloned table cells changed paragraph graph ownership.");
|
|
529
|
+
for (const [index, source] of sources.entries()) {
|
|
530
|
+
const target = targets.at(index);
|
|
531
|
+
if (!target) panic("The cloned table cells lost a paragraph owner.");
|
|
532
|
+
copyParagraphPropertyCapture(target, source);
|
|
533
|
+
const inspection = inspectTableCellParagraphPropertySourceBinding(source);
|
|
534
|
+
setTableCellParagraphPropertySourceBinding(target, tableCellParagraphPropertySourceBindingForTransport(source, inspection));
|
|
535
|
+
}
|
|
536
|
+
return cloned;
|
|
537
|
+
};
|
|
538
|
+
/** Restore decoded identities privately and remove them from the document model. */
|
|
539
|
+
const restoreTableCellsWithParagraphPropertySources = (payload) => {
|
|
540
|
+
const sourceEntries = decodedTableCellParagraphSourcePayloadState(payload).paragraphs;
|
|
541
|
+
const cloned = structuredClone([...payload.cells]);
|
|
542
|
+
const targets = paragraphsInTableCells(cloned);
|
|
543
|
+
if (sourceEntries.length !== targets.length) panic("The cloned table cells changed paragraph graph ownership.");
|
|
544
|
+
for (const [index, sourceEntry] of sourceEntries.entries()) {
|
|
545
|
+
const target = targets.at(index);
|
|
546
|
+
if (!target) panic("The cloned table cells lost a paragraph owner.");
|
|
547
|
+
const { paragraph: source } = sourceEntry;
|
|
548
|
+
copyParagraphPropertyCapture(target, source);
|
|
549
|
+
if (!Reflect.deleteProperty(target, "_docxParagraphSourceBinding")) panic("Cannot detach paragraph-property transport identity from a table cell.");
|
|
550
|
+
switch (sourceEntry.binding.type) {
|
|
551
|
+
case "authored": break;
|
|
552
|
+
case "source":
|
|
553
|
+
paragraphPropertySourceTokens.set(target, sourceEntry.binding.token);
|
|
554
|
+
break;
|
|
555
|
+
default: return sourceEntry.binding;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return cloned;
|
|
559
|
+
};
|
|
560
|
+
/** Visit every transported hidden paragraph in canonical cell-story order. */
|
|
561
|
+
const visitTableCellParagraphPropertySourceBindings = (payload, visit) => {
|
|
562
|
+
for (const { binding, path } of decodedTableCellParagraphSourcePayloadState(payload).paragraphs) visit(binding, path);
|
|
563
|
+
};
|
|
564
|
+
/**
|
|
565
|
+
* Clone package-crossing vertical-merge payloads with their captured `w:pPr`,
|
|
566
|
+
* but without the durable paragraph tokens owned by the source package.
|
|
567
|
+
*/
|
|
568
|
+
const cloneTableCellsWithParagraphPropertyCaptures = (payload) => {
|
|
569
|
+
const sourceEntries = decodedTableCellParagraphSourcePayloadState(payload).paragraphs;
|
|
570
|
+
const cloned = structuredClone([...payload.cells]);
|
|
571
|
+
const targets = paragraphsInTableCells(cloned);
|
|
572
|
+
if (sourceEntries.length !== targets.length) panic("The cloned table cells changed paragraph graph ownership.");
|
|
573
|
+
for (const [index, sourceEntry] of sourceEntries.entries()) {
|
|
574
|
+
const target = targets.at(index);
|
|
575
|
+
if (!target) panic("The cloned table cells lost a paragraph owner.");
|
|
576
|
+
copyParagraphPropertyCapture(target, sourceEntry.paragraph);
|
|
577
|
+
setTableCellParagraphPropertySourceBinding(target, { type: "authored" });
|
|
578
|
+
}
|
|
65
579
|
return cloned;
|
|
66
580
|
};
|
|
67
581
|
const linkProseParagraphPropertySource = (proseParagraph, sourceParagraph) => {
|
|
68
582
|
if (paragraphPropertySources.has(sourceParagraph)) proseParagraphSourceOwners.set(proseParagraph, paragraphPropertySourceOwners.get(sourceParagraph) ?? sourceParagraph);
|
|
69
583
|
};
|
|
584
|
+
/** Create a parsed paragraph with both its same-process owner and durable body token. */
|
|
585
|
+
const createProseParagraphWithPropertySource = (nodeType, sourceParagraph, options = {}) => {
|
|
586
|
+
if (!nodeType || nodeType.name !== "paragraph") panic("Paragraph-property provenance can only seed a paragraph node");
|
|
587
|
+
const paragraph = nodeType.create({
|
|
588
|
+
...options.attrs,
|
|
589
|
+
[PROSE_PARAGRAPH_SOURCE_TOKEN_ATTR]: paragraphPropertySourceTokens.get(sourceParagraph) ?? null
|
|
590
|
+
}, options.content, options.marks);
|
|
591
|
+
linkProseParagraphPropertySource(paragraph, sourceParagraph);
|
|
592
|
+
return paragraph;
|
|
593
|
+
};
|
|
594
|
+
const paragraphPropertySourceTransfersKey = new PluginKey("paragraphPropertySourceTransfers");
|
|
595
|
+
/** Join adjacent paragraphs when revision semantics deliberately select the right owner. */
|
|
596
|
+
const joinProseParagraphsWithRightPropertySource = ({ attrs, pos, transaction }) => {
|
|
597
|
+
const $pos = transaction.doc.resolve(pos);
|
|
598
|
+
const left = $pos.nodeBefore;
|
|
599
|
+
const right = $pos.nodeAfter;
|
|
600
|
+
if (!left || !right || left.type.name !== "paragraph" || right.type.name !== "paragraph") panic("Paragraph-property ownership can only join adjacent paragraphs");
|
|
601
|
+
const leftToken = getProseParagraphPropertySourceToken(left);
|
|
602
|
+
const rightToken = getProseParagraphPropertySourceToken(right);
|
|
603
|
+
const leftPos = pos - left.nodeSize;
|
|
604
|
+
transaction.join(pos);
|
|
605
|
+
const joined = transaction.doc.nodeAt(leftPos);
|
|
606
|
+
if (!joined || joined.type.name !== "paragraph") panic("Joining paragraphs did not produce a paragraph");
|
|
607
|
+
transaction.setNodeMarkup(leftPos, void 0, {
|
|
608
|
+
...attrs,
|
|
609
|
+
[PROSE_PARAGRAPH_SOURCE_TOKEN_ATTR]: rightToken ?? null
|
|
610
|
+
}, joined.marks);
|
|
611
|
+
const transfers = transaction.getMeta(paragraphPropertySourceTransfersKey) ?? [];
|
|
612
|
+
transaction.setMeta(paragraphPropertySourceTransfersKey, [...transfers, {
|
|
613
|
+
displacedToken: typeof leftToken === "string" ? leftToken : null,
|
|
614
|
+
selectedToken: typeof rightToken === "string" ? rightToken : null
|
|
615
|
+
}]);
|
|
616
|
+
return transaction;
|
|
617
|
+
};
|
|
618
|
+
const getExplicitParagraphPropertySourceTransfers = (transaction) => transaction.getMeta(paragraphPropertySourceTransfersKey) ?? [];
|
|
70
619
|
/** Carry a parser-linked paragraph owner across an immutable PM node rebuild. */
|
|
71
620
|
const copyProseParagraphPropertySource = (target, source) => {
|
|
72
621
|
if (target.type.name !== "paragraph" || source.type.name !== "paragraph") return;
|
|
@@ -79,6 +628,21 @@ const recreateProseNodeWithParagraphPropertySource = (source, options = {}) => {
|
|
|
79
628
|
copyProseParagraphPropertySource(target, source);
|
|
80
629
|
return target;
|
|
81
630
|
};
|
|
631
|
+
/**
|
|
632
|
+
* Rebuild a PM node that is crossing from another package: retain its
|
|
633
|
+
* same-process paragraph-property capture, but detach the durable token that
|
|
634
|
+
* can only name a paragraph in the package it came from.
|
|
635
|
+
*/
|
|
636
|
+
const recreateProseNodeWithDetachedParagraphPropertySource = (source, options = {}) => {
|
|
637
|
+
const attrs = options.attrs ?? source.attrs;
|
|
638
|
+
return recreateProseNodeWithParagraphPropertySource(source, {
|
|
639
|
+
...options,
|
|
640
|
+
attrs: source.type.name === "paragraph" ? {
|
|
641
|
+
...attrs,
|
|
642
|
+
[PROSE_PARAGRAPH_SOURCE_TOKEN_ATTR]: null
|
|
643
|
+
} : attrs
|
|
644
|
+
});
|
|
645
|
+
};
|
|
82
646
|
/** Replace paragraph markup without losing its private parser-owner link. */
|
|
83
647
|
const setProseParagraphMarkupWithPropertySource = ({ attrs, ownership, pos, transaction }) => {
|
|
84
648
|
const source = transaction.doc.nodeAt(pos);
|
|
@@ -104,10 +668,12 @@ const transferProseParagraphPropertySource = (target, source, paraId) => {
|
|
|
104
668
|
paragraphPropertySourceTransferIds.set(sourceOwner, paraId);
|
|
105
669
|
};
|
|
106
670
|
const linkParagraphPropertySourceCandidate = (target, source) => {
|
|
671
|
+
const token = source.attrs[PROSE_PARAGRAPH_SOURCE_TOKEN_ATTR];
|
|
672
|
+
if (typeof token === "string") paragraphPropertySourceTokens.set(target, token);
|
|
107
673
|
const sourceOwner = proseParagraphSourceOwners.get(source);
|
|
108
674
|
if (sourceOwner) paragraphPropertySourceCandidates.set(target, sourceOwner);
|
|
109
675
|
};
|
|
110
676
|
const getParagraphPropertySourceCandidate = (paragraph) => paragraphPropertySourceCandidates.get(paragraph);
|
|
111
677
|
const getParagraphPropertySourceTransferId = (paragraph) => paragraphPropertySourceTransferIds.get(paragraph);
|
|
112
678
|
//#endregion
|
|
113
|
-
export { assignParagraphPropertySource, cloneDocumentWithParagraphPropertySources, cloneParagraphWithPropertySource, cloneParagraphWithoutPropertySource, copyParagraphPropertySource, getParagraphPropertySource, getParagraphPropertySourceCandidate, getParagraphPropertySourceTransferId, linkParagraphPropertySourceCandidate, linkProseParagraphPropertySource, recreateProseNodeWithParagraphPropertySource, setProseParagraphMarkupWithPropertySource, transferProseParagraphPropertySource };
|
|
679
|
+
export { PARAGRAPH_PROPERTY_SOURCE_VALIDATION_CODES, PROSE_PARAGRAPH_SOURCE_CONTRACT_ATTR, PROSE_PARAGRAPH_SOURCE_TOKEN_ATTR, ParagraphPropertySourceValidationError, TABLE_CELL_PARAGRAPH_SOURCE_BINDING_ATTR, TABLE_CELL_PARAGRAPH_SOURCE_PAYLOAD_ERROR_CLASSIFICATIONS, assignDocumentParagraphPropertySourceContract, assignParagraphPropertySource, cloneDocumentWithParagraphPropertySources, cloneParagraphWithPropertySource, cloneParagraphWithoutPropertySource, cloneTableCellsWithParagraphPropertyCaptures, copyDocumentParagraphPropertySourceContract, copyDocumentParagraphPropertySources, copyParagraphPropertyCapture, copyParagraphPropertySource, createProseParagraphWithPropertySource, decodeTableCellParagraphSourcePayload, getDocumentParagraphPropertySourceContract, getExplicitParagraphPropertySourceTransfers, getParagraphPropertySource, getParagraphPropertySourceCandidate, getParagraphPropertySourceToken, getParagraphPropertySourceTransferId, getProseDocumentParagraphPropertySourceContract, getProseParagraphPropertySourceToken, isParagraphPropertySourceToken, joinProseParagraphsWithRightPropertySource, linkParagraphPropertySourceCandidate, linkProseParagraphPropertySource, paragraphPropertySourceBelongsToDocument, paragraphPropertySourceTokenMatchesContract, recreateProseNodeWithDetachedParagraphPropertySource, recreateProseNodeWithParagraphPropertySource, restoreTableCellsWithParagraphPropertySources, setProseParagraphMarkupWithPropertySource, transferProseParagraphPropertySource, transportTableCellsWithParagraphPropertySources, visitDocumentStoryParagraphs, visitTableCellParagraphPropertySourceBindings };
|
package/dist/docx/parser.js
CHANGED
|
@@ -18,6 +18,7 @@ import { renderEmfSvg } from "./metafileSvg.js";
|
|
|
18
18
|
import { DocxModelValidationError, formatDocumentModelIssues, validateFolioDocumentModel } from "./modelValidation.js";
|
|
19
19
|
import { parseNumbering } from "./numberingParser.js";
|
|
20
20
|
import { normalizeNumberingReferences } from "./numberingReferenceNormalization.js";
|
|
21
|
+
import { assignDocumentParagraphPropertySourceContract } from "./paragraphPropertySource.js";
|
|
21
22
|
import { RELATIONSHIP_TYPES, parseRelationships, resolveRelativePath } from "./relsParser.js";
|
|
22
23
|
import { normalizeRenderedPageBreakHints } from "./renderedPageBreakNormalization.js";
|
|
23
24
|
import { parseSettings } from "./settingsParser.js";
|
|
@@ -50,6 +51,12 @@ import { TaggedError } from "better-result";
|
|
|
50
51
|
const EMF_MIME_TYPE = "image/x-emf";
|
|
51
52
|
const SVG_MIME_TYPE = "image/svg+xml";
|
|
52
53
|
const MAX_PACKAGE_EMF_PREVIEW_BYTES = 8 * 1024 * 1024;
|
|
54
|
+
const sha256Hex = async (buffer) => {
|
|
55
|
+
const bytes = new Uint8Array(await crypto.subtle.digest("SHA-256", buffer));
|
|
56
|
+
let digest = "";
|
|
57
|
+
for (const byte of bytes) digest += byte.toString(16).padStart(2, "0");
|
|
58
|
+
return digest;
|
|
59
|
+
};
|
|
53
60
|
/**
|
|
54
61
|
* Parse a DOCX file into a complete Document model
|
|
55
62
|
*
|
|
@@ -65,6 +72,7 @@ async function parseDocx(input, options = {}) {
|
|
|
65
72
|
try {
|
|
66
73
|
const timeStage = (_name, fn) => fn();
|
|
67
74
|
const timeStageAsync = async (_name, fn) => await fn();
|
|
75
|
+
const paragraphPropertySourceDigest = sha256Hex(buffer);
|
|
68
76
|
onProgress("Extracting DOCX...", 0);
|
|
69
77
|
const raw = await timeStageAsync("unzip", () => unzipDocx(buffer, {
|
|
70
78
|
...unzipLimits,
|
|
@@ -216,6 +224,7 @@ async function parseDocx(input, options = {}) {
|
|
|
216
224
|
...templateVariables !== void 0 ? { templateVariables } : {},
|
|
217
225
|
...requiredFonts.length > 0 ? { requiredFonts } : {}
|
|
218
226
|
};
|
|
227
|
+
assignDocumentParagraphPropertySourceContract(document, await paragraphPropertySourceDigest);
|
|
219
228
|
enforcePackageVmlPreviewBudget(document.package);
|
|
220
229
|
const validation = validateFolioDocumentModel(document);
|
|
221
230
|
const parsedCompleteModel = parseHeadersFooters && parseNotes;
|