@stll/folio-core 0.47.3 → 0.47.5
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.js +46 -11
- package/dist/compare/inline-atoms.js +218 -31
- package/dist/compare/section-boundary-properties.js +1 -0
- package/dist/content-controls/mutateContentControls.js +1 -3
- package/dist/docx/commentRangeIntegrity.js +7 -0
- package/dist/docx/documentParser.js +55 -3
- package/dist/docx/inlineWrapperContent.d.ts +18 -16
- package/dist/docx/inlineWrapperContent.js +18 -16
- package/dist/docx/normalizeBaseDirection.js +4 -0
- package/dist/docx/rezip.js +1 -0
- package/dist/docx/serializer/documentSerializer.js +16 -1
- package/dist/docx/serializer/paragraphSerializer.js +8 -0
- package/dist/docx/serializer/tableSerializer.js +5 -2
- package/dist/docx/server/createBilingualDocument.js +23 -14
- package/dist/docx/tableParser.js +101 -75
- package/dist/markdown/renderRuns.js +4 -0
- package/dist/markdown/renderTable.js +46 -12
- package/dist/prosemirror/attrs/index.js +67 -3
- package/dist/prosemirror/commentReferenceAttrs.js +2 -2
- package/dist/prosemirror/commentReferenceIntegrity.d.ts +7 -3
- package/dist/prosemirror/commentReferenceIntegrity.js +9 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +74 -18
- package/dist/prosemirror/conversion/toProseDoc.js +75 -42
- package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +13 -6
- package/dist/prosemirror/extensions/marks/InlineWrapperExtension.js +24 -6
- package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.js +9 -5
- package/dist/prosemirror/extensions/nodes/TableExtension.js +2 -2
- package/dist/prosemirror/schema/marks.d.ts +11 -0
- package/dist/prosemirror/schema/nodes.d.ts +6 -4
- package/dist/prosemirror/trackedRunInlineAtoms.d.ts +1 -1
- package/dist/prosemirror/trackedRunInlineAtoms.js +1 -1
- package/dist/prosemirror/validation.js +48 -7
- package/dist/types/content.d.ts +2 -2
- package/dist/types/index.d.ts +3 -1
- package/dist/utils/mergeDocumentContent.js +1 -7
- package/package.json +2 -2
|
@@ -868,6 +868,11 @@ const taggedLayerValidator = (label) => (layer, path, issues) => {
|
|
|
868
868
|
path: `${path}.${key}`,
|
|
869
869
|
message: "Expected a string."
|
|
870
870
|
});
|
|
871
|
+
const propertiesXml = layer["propertiesXml"];
|
|
872
|
+
if (typeof propertiesXml === "string" && !isSafePreservedChildXml(propertiesXml)) issues.push({
|
|
873
|
+
path: `${path}.propertiesXml`,
|
|
874
|
+
message: "Expected one bounded, well-formed XML element."
|
|
875
|
+
});
|
|
871
876
|
for (const key of Object.keys(layer)) if (!TAGGED_LAYER_KEYS.has(key)) issues.push({
|
|
872
877
|
path: `${path}.${key}`,
|
|
873
878
|
message: `Unexpected ${label} wrapper property.`
|
|
@@ -894,6 +899,19 @@ const INLINE_WRAPPER_LAYER_VALIDATORS = {
|
|
|
894
899
|
const inlineWrapperLayerValidator = (kind) => {
|
|
895
900
|
return typeof kind === "string" ? INLINE_WRAPPER_LAYER_VALIDATORS[kind] : void 0;
|
|
896
901
|
};
|
|
902
|
+
const inlineWrapperProperties = function* (layers) {
|
|
903
|
+
for (let index = 0; index < layers.length; index += 1) {
|
|
904
|
+
const layer = layers[index];
|
|
905
|
+
yield { xml: isRecord(layer) && typeof layer["propertiesXml"] === "string" ? layer["propertiesXml"] : "" };
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
const emptyHyperlinkWrapperProperties = function* (stacks) {
|
|
909
|
+
for (let stackIndex = 0; stackIndex < stacks.length; stackIndex += 1) {
|
|
910
|
+
const stack = stacks[stackIndex];
|
|
911
|
+
yield {};
|
|
912
|
+
if (Array.isArray(stack)) yield* inlineWrapperProperties(stack);
|
|
913
|
+
}
|
|
914
|
+
};
|
|
897
915
|
const validateInlineWrapperStack = (value, path, issues) => {
|
|
898
916
|
if (!Array.isArray(value)) {
|
|
899
917
|
issues.push({
|
|
@@ -909,6 +927,13 @@ const validateInlineWrapperStack = (value, path, issues) => {
|
|
|
909
927
|
});
|
|
910
928
|
return;
|
|
911
929
|
}
|
|
930
|
+
if (!isWithinPreservedMarkupBudget(inlineWrapperProperties(value))) {
|
|
931
|
+
issues.push({
|
|
932
|
+
path,
|
|
933
|
+
message: "Inline wrapper stack exceeds its resource budget."
|
|
934
|
+
});
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
912
937
|
for (const [index, layer] of value.entries()) {
|
|
913
938
|
const layerPath = `${path}[${index}]`;
|
|
914
939
|
if (!isRecord(layer)) {
|
|
@@ -950,6 +975,22 @@ const readInlineWrapperMarkAttrs = (mark) => {
|
|
|
950
975
|
const issues = [];
|
|
951
976
|
expectMarkType(mark, "inlineWrapper", issues);
|
|
952
977
|
validateInlineWrapperStack(attrs["stack"], "inlineWrapper.attrs.stack", issues);
|
|
978
|
+
const hyperlinkIndex = attrs["_docxHyperlinkIndex"];
|
|
979
|
+
const stackStart = attrs["_docxInsideHyperlinkStackStart"];
|
|
980
|
+
const hasHyperlinkIndex = hyperlinkIndex !== void 0 && hyperlinkIndex !== null;
|
|
981
|
+
const hasStackStart = stackStart !== void 0 && stackStart !== null;
|
|
982
|
+
if (hasHyperlinkIndex !== hasStackStart) issues.push({
|
|
983
|
+
path: "inlineWrapper.attrs",
|
|
984
|
+
message: "Hyperlink wrapper provenance requires both an index and stack start."
|
|
985
|
+
});
|
|
986
|
+
else if (hasHyperlinkIndex && hasStackStart) {
|
|
987
|
+
validateNonNegativeInteger(hyperlinkIndex, "inlineWrapper.attrs._docxHyperlinkIndex", issues);
|
|
988
|
+
validateNonNegativeInteger(stackStart, "inlineWrapper.attrs._docxInsideHyperlinkStackStart", issues);
|
|
989
|
+
if (typeof stackStart === "number" && Array.isArray(attrs["stack"]) && stackStart >= attrs["stack"].length) issues.push({
|
|
990
|
+
path: "inlineWrapper.attrs._docxInsideHyperlinkStackStart",
|
|
991
|
+
message: "Expected a stack position inside the wrapper stack."
|
|
992
|
+
});
|
|
993
|
+
}
|
|
953
994
|
return attrsResult(attrs, issues);
|
|
954
995
|
};
|
|
955
996
|
const expectInlineWrapperMarkAttrs = (mark) => expectCachedMarkAttrs(mark, inlineWrapperAttrsCache, readInlineWrapperMarkAttrs, "inline wrapper attrs");
|
|
@@ -1745,7 +1786,7 @@ const optionalPositionedBookmarks = (attrs, path, issues) => {
|
|
|
1745
1786
|
});
|
|
1746
1787
|
continue;
|
|
1747
1788
|
}
|
|
1748
|
-
|
|
1789
|
+
validateNonNegativeSafeInteger(entry["index"], `${entryPath}.index`, issues);
|
|
1749
1790
|
const marker = entry["marker"];
|
|
1750
1791
|
if (!isRecord(marker)) {
|
|
1751
1792
|
issues.push({
|
|
@@ -1762,7 +1803,7 @@ const optionalPositionedBookmarks = (attrs, path, issues) => {
|
|
|
1762
1803
|
});
|
|
1763
1804
|
continue;
|
|
1764
1805
|
}
|
|
1765
|
-
|
|
1806
|
+
validateNonNegativeSafeInteger(marker["id"], `${entryPath}.marker.id`, issues);
|
|
1766
1807
|
if (type === "bookmarkStart") requiredString(marker, "name", `${entryPath}.marker.name`, issues);
|
|
1767
1808
|
optionalContentControls(entry, `${entryPath}.contentControls`, issues);
|
|
1768
1809
|
}
|
|
@@ -2574,11 +2615,17 @@ const validateNonNegativeInteger = (value, path, issues) => {
|
|
|
2574
2615
|
});
|
|
2575
2616
|
return;
|
|
2576
2617
|
}
|
|
2577
|
-
if (!Number.
|
|
2618
|
+
if (!Number.isSafeInteger(value) || value < 0) issues.push({
|
|
2578
2619
|
path,
|
|
2579
2620
|
message: "Expected a non-negative integer."
|
|
2580
2621
|
});
|
|
2581
2622
|
};
|
|
2623
|
+
const validateNonNegativeSafeInteger = (value, path, issues) => {
|
|
2624
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) issues.push({
|
|
2625
|
+
path,
|
|
2626
|
+
message: "Expected a non-negative safe integer."
|
|
2627
|
+
});
|
|
2628
|
+
};
|
|
2582
2629
|
const optionalBookmarkArray = (value, issues) => {
|
|
2583
2630
|
if (value === void 0 || value === null) return;
|
|
2584
2631
|
if (!Array.isArray(value)) {
|
|
@@ -2633,6 +2680,23 @@ const optionalEmptyHyperlinkArray = (value, issues) => {
|
|
|
2633
2680
|
optionalString(item, "target", `${itemPath}.target`, issues);
|
|
2634
2681
|
optionalBoolean(item, "history", `${itemPath}.history`, issues);
|
|
2635
2682
|
optionalString(item, "docLocation", `${itemPath}.docLocation`, issues);
|
|
2683
|
+
const wrapperStacks = item["_docxEmptyWrapperStacks"];
|
|
2684
|
+
if (wrapperStacks === void 0 || wrapperStacks === null) continue;
|
|
2685
|
+
if (!Array.isArray(wrapperStacks)) {
|
|
2686
|
+
issues.push({
|
|
2687
|
+
path: `${itemPath}._docxEmptyWrapperStacks`,
|
|
2688
|
+
message: "Expected an array of inline wrapper stacks."
|
|
2689
|
+
});
|
|
2690
|
+
continue;
|
|
2691
|
+
}
|
|
2692
|
+
if (!isWithinPreservedMarkupBudget(emptyHyperlinkWrapperProperties(wrapperStacks))) {
|
|
2693
|
+
issues.push({
|
|
2694
|
+
path: `${itemPath}._docxEmptyWrapperStacks`,
|
|
2695
|
+
message: "Empty inline wrapper stacks exceed their aggregate resource budget."
|
|
2696
|
+
});
|
|
2697
|
+
continue;
|
|
2698
|
+
}
|
|
2699
|
+
for (const [stackIndex, stack] of wrapperStacks.entries()) validateInlineWrapperStack(stack, `${itemPath}._docxEmptyWrapperStacks[${stackIndex}]`, issues);
|
|
2636
2700
|
}
|
|
2637
2701
|
};
|
|
2638
2702
|
//#endregion
|
|
@@ -8,9 +8,9 @@ const readCommentReferenceAttrs = (node) => {
|
|
|
8
8
|
message: `Expected commentReference, got ${node.type.name}.`
|
|
9
9
|
});
|
|
10
10
|
const commentId = node.attrs["commentId"];
|
|
11
|
-
if (typeof commentId !== "number" || !Number.
|
|
11
|
+
if (typeof commentId !== "number" || !Number.isSafeInteger(commentId)) issues.push({
|
|
12
12
|
path: "commentReference.attrs.commentId",
|
|
13
|
-
message: "Expected a
|
|
13
|
+
message: "Expected a safe integer."
|
|
14
14
|
});
|
|
15
15
|
if (issues.length > 0 || typeof commentId !== "number") return {
|
|
16
16
|
ok: false,
|
|
@@ -10,13 +10,17 @@
|
|
|
10
10
|
* a comment mark creates none — so the repair is computed here and applied by
|
|
11
11
|
* the extension's `appendTransaction`, rather than left to each call site.
|
|
12
12
|
*/
|
|
13
|
-
type
|
|
14
|
-
commentId: number;
|
|
13
|
+
type PositionedReference = {
|
|
15
14
|
position: number;
|
|
16
15
|
/** Node size, so a caller can delete the node without re-reading it. */
|
|
17
16
|
size: number;
|
|
18
|
-
malformed: boolean;
|
|
19
17
|
};
|
|
18
|
+
type CommentReferenceOccurrence = (PositionedReference & {
|
|
19
|
+
status: "valid";
|
|
20
|
+
commentId: number;
|
|
21
|
+
}) | (PositionedReference & {
|
|
22
|
+
status: "malformed";
|
|
23
|
+
});
|
|
20
24
|
/** Where a comment's mark last covers content, and how far that content runs. */
|
|
21
25
|
type CommentMarkExtent = {
|
|
22
26
|
commentId: number;
|
|
@@ -10,7 +10,15 @@ const planCommentReferenceRepairs = ({ references, markExtents }) => {
|
|
|
10
10
|
const deletions = [];
|
|
11
11
|
const keptIds = /* @__PURE__ */ new Set();
|
|
12
12
|
for (const reference of references) {
|
|
13
|
-
if (reference.
|
|
13
|
+
if (reference.status === "malformed") {
|
|
14
|
+
deletions.push({
|
|
15
|
+
type: "delete",
|
|
16
|
+
position: reference.position,
|
|
17
|
+
size: reference.size
|
|
18
|
+
});
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (!markedIds.has(reference.commentId)) {
|
|
14
22
|
deletions.push({
|
|
15
23
|
type: "delete",
|
|
16
24
|
position: reference.position,
|
|
@@ -272,6 +272,7 @@ function fromProseDoc(pmDoc, baseDocument, { reuse = "none" } = {}) {
|
|
|
272
272
|
if (tokenSources) restoreParagraphPropertySourcesByToken(blocks, tokenSources);
|
|
273
273
|
else if (baseDocument) restoreParagraphPropertySources(blocks, baseDocument.package.document.content, linkedSources.targets, linkedSources.sources);
|
|
274
274
|
const documentBody = { content: blocks };
|
|
275
|
+
if (baseDocument?.package.document.background) documentBody.background = baseDocument.package.document.background;
|
|
275
276
|
if (baseDocument?.package.document.finalSectionProperties) documentBody.finalSectionProperties = baseDocument.package.document.finalSectionProperties;
|
|
276
277
|
if (baseDocument?.package.document.sections) documentBody.sections = baseDocument.package.document.sections;
|
|
277
278
|
if (baseDocument?.package.document.comments) documentBody.comments = baseDocument.package.document.comments;
|
|
@@ -1137,6 +1138,17 @@ const inlineWrapperStackOf = (node) => {
|
|
|
1137
1138
|
const mark = node.marks.find((candidate) => candidate.type.name === INLINE_WRAPPER_MARK_NAME);
|
|
1138
1139
|
return mark ? expectInlineWrapperMarkAttrs(mark).stack : [];
|
|
1139
1140
|
};
|
|
1141
|
+
/** The authored hyperlink that owns a suffix of the node's wrapper stack. */
|
|
1142
|
+
const inlineWrapperHyperlinkOriginOf = (node) => {
|
|
1143
|
+
const mark = node.marks.find((candidate) => candidate.type.name === INLINE_WRAPPER_MARK_NAME);
|
|
1144
|
+
if (!mark) return;
|
|
1145
|
+
const attrs = expectInlineWrapperMarkAttrs(mark);
|
|
1146
|
+
if (typeof attrs._docxHyperlinkIndex !== "number" || typeof attrs._docxInsideHyperlinkStackStart !== "number") return;
|
|
1147
|
+
return {
|
|
1148
|
+
hyperlinkIndex: attrs._docxHyperlinkIndex,
|
|
1149
|
+
stackStart: attrs._docxInsideHyperlinkStackStart
|
|
1150
|
+
};
|
|
1151
|
+
};
|
|
1140
1152
|
const isRevisionWrapper = (item) => item.type === "insertion" || item.type === "deletion" || item.type === "moveFrom" || item.type === "moveTo";
|
|
1141
1153
|
/** `stack` closed around `content`, outermost layer first. */
|
|
1142
1154
|
const nestInlineWrappers = (stack, content) => {
|
|
@@ -1160,7 +1172,20 @@ const nestInlineWrappers = (stack, content) => {
|
|
|
1160
1172
|
* A group with nothing in it writes no wrapper: an authored wrapper whose
|
|
1161
1173
|
* content was deleted or rejected is gone, and an empty one says nothing.
|
|
1162
1174
|
*/
|
|
1163
|
-
const
|
|
1175
|
+
const nestInsideHyperlink = (item, stack, hyperlinkIndex, indices) => {
|
|
1176
|
+
if (item.type === "hyperlink") {
|
|
1177
|
+
if (indices.get(item) !== hyperlinkIndex) return false;
|
|
1178
|
+
item.children = [nestInlineWrappers(stack, item.children)];
|
|
1179
|
+
return true;
|
|
1180
|
+
}
|
|
1181
|
+
if (isRevisionWrapper(item)) {
|
|
1182
|
+
let nested = false;
|
|
1183
|
+
for (const child of item.content) if (nestInsideHyperlink(child, stack, hyperlinkIndex, indices)) nested = true;
|
|
1184
|
+
return nested;
|
|
1185
|
+
}
|
|
1186
|
+
return false;
|
|
1187
|
+
};
|
|
1188
|
+
const nestInlineWrapperGroups = (items, groups, hyperlinkIndices) => {
|
|
1164
1189
|
const nested = [];
|
|
1165
1190
|
for (const [index, group] of groups.entries()) {
|
|
1166
1191
|
const slice = items.slice(group.start, groups[index + 1]?.start ?? items.length);
|
|
@@ -1168,10 +1193,22 @@ const nestInlineWrapperGroups = (items, groups) => {
|
|
|
1168
1193
|
nested.push(...slice);
|
|
1169
1194
|
continue;
|
|
1170
1195
|
}
|
|
1196
|
+
const origin = group.origin;
|
|
1197
|
+
let outerStack = group.stack;
|
|
1198
|
+
if (origin !== void 0) {
|
|
1199
|
+
const innerStack = group.stack.slice(origin.stackStart);
|
|
1200
|
+
let nestedInsideOrigin = false;
|
|
1201
|
+
for (const item of slice) if (nestInsideHyperlink(item, innerStack, origin.hyperlinkIndex, hyperlinkIndices)) nestedInsideOrigin = true;
|
|
1202
|
+
if (nestedInsideOrigin) outerStack = group.stack.slice(0, origin.stackStart);
|
|
1203
|
+
}
|
|
1204
|
+
if (outerStack.length === 0) {
|
|
1205
|
+
nested.push(...slice);
|
|
1206
|
+
continue;
|
|
1207
|
+
}
|
|
1171
1208
|
let pending = [];
|
|
1172
1209
|
const closeNest = () => {
|
|
1173
1210
|
if (pending.length === 0) return;
|
|
1174
|
-
nested.push(nestInlineWrappers(
|
|
1211
|
+
nested.push(nestInlineWrappers(outerStack, pending));
|
|
1175
1212
|
pending = [];
|
|
1176
1213
|
};
|
|
1177
1214
|
for (const item of slice) {
|
|
@@ -1180,7 +1217,7 @@ const nestInlineWrapperGroups = (items, groups) => {
|
|
|
1180
1217
|
continue;
|
|
1181
1218
|
}
|
|
1182
1219
|
closeNest();
|
|
1183
|
-
if (item.content.length > 0) item.content = [nestInlineWrappers(
|
|
1220
|
+
if (item.content.length > 0) item.content = [nestInlineWrappers(outerStack, item.content)];
|
|
1184
1221
|
nested.push(item);
|
|
1185
1222
|
}
|
|
1186
1223
|
closeNest();
|
|
@@ -1214,7 +1251,14 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
|
|
|
1214
1251
|
let currentHyperlinkKey = null;
|
|
1215
1252
|
let currentTrackedChange;
|
|
1216
1253
|
const sourceRunOwners = /* @__PURE__ */ new WeakMap();
|
|
1254
|
+
const sourceHyperlinkIndices = /* @__PURE__ */ new WeakMap();
|
|
1217
1255
|
const openedComments = /* @__PURE__ */ new Set();
|
|
1256
|
+
const createIndexedHyperlink = (mark) => {
|
|
1257
|
+
const hyperlink = createHyperlink(mark);
|
|
1258
|
+
const index = expectHyperlinkMarkAttrs(mark)._docxHyperlinkIndex;
|
|
1259
|
+
if (typeof index === "number") sourceHyperlinkIndices.set(hyperlink, index);
|
|
1260
|
+
return hyperlink;
|
|
1261
|
+
};
|
|
1218
1262
|
const commentLastOffset = /* @__PURE__ */ new Map();
|
|
1219
1263
|
paragraph.forEach((node, offset) => {
|
|
1220
1264
|
for (const commentId of getCommentMarkIds(node.marks)) commentLastOffset.set(commentId, offset);
|
|
@@ -1298,14 +1342,20 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
|
|
|
1298
1342
|
*/
|
|
1299
1343
|
const enterWrapperGroup = (node) => {
|
|
1300
1344
|
const stack = inlineWrapperStackOf(node);
|
|
1301
|
-
const
|
|
1345
|
+
const origin = inlineWrapperHyperlinkOriginOf(node);
|
|
1346
|
+
const key = [
|
|
1347
|
+
inlineWrapperStackKey(stack),
|
|
1348
|
+
origin?.hyperlinkIndex ?? "",
|
|
1349
|
+
origin?.stackStart ?? ""
|
|
1350
|
+
].join("\0");
|
|
1302
1351
|
if (key === wrapperGroupKey) return;
|
|
1303
1352
|
flushCurrentInline();
|
|
1304
1353
|
currentTrackedChange = void 0;
|
|
1305
1354
|
wrapperGroupKey = key;
|
|
1306
1355
|
wrapperGroups.push({
|
|
1307
1356
|
start: content.length,
|
|
1308
|
-
stack
|
|
1357
|
+
stack,
|
|
1358
|
+
...origin ? { origin } : {}
|
|
1309
1359
|
});
|
|
1310
1360
|
};
|
|
1311
1361
|
const processInlineNode = (node, offset) => {
|
|
@@ -1332,12 +1382,6 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
|
|
|
1332
1382
|
content.push({ ...start }, { ...end });
|
|
1333
1383
|
return;
|
|
1334
1384
|
}
|
|
1335
|
-
if (node.type.name === "moveRangeBoundary") {
|
|
1336
|
-
flushCurrentInline();
|
|
1337
|
-
currentTrackedChange = void 0;
|
|
1338
|
-
content.push({ ...expectMoveRangeBoundaryAttrs(node) });
|
|
1339
|
-
return;
|
|
1340
|
-
}
|
|
1341
1385
|
const linkMark = node.marks.find((m) => m.type.name === "hyperlink");
|
|
1342
1386
|
const noteRefMark = node.marks.find((m) => m.type.name === "footnoteRef");
|
|
1343
1387
|
const insertionMark = node.marks.find((m) => m.type.name === "insertion");
|
|
@@ -1354,10 +1398,8 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
|
|
|
1354
1398
|
};
|
|
1355
1399
|
if (textBoxAnchorMarkers.has(anchorId)) return;
|
|
1356
1400
|
textBoxAnchorMarkers.set(anchorId, marker);
|
|
1357
|
-
const anchoredContent = linkMark ?
|
|
1358
|
-
|
|
1359
|
-
children: [marker]
|
|
1360
|
-
} : marker;
|
|
1401
|
+
const anchoredContent = linkMark ? createIndexedHyperlink(linkMark) : marker;
|
|
1402
|
+
if (anchoredContent.type === "hyperlink") anchoredContent.children.push(marker);
|
|
1361
1403
|
const changeMark = insertionMark ?? deletionMark;
|
|
1362
1404
|
if (!changeMark) {
|
|
1363
1405
|
content.push(anchoredContent);
|
|
@@ -1409,7 +1451,7 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
|
|
|
1409
1451
|
if (linkMark) {
|
|
1410
1452
|
const linkKey = getLinkKey(linkMark);
|
|
1411
1453
|
if (!currentTrackedChange || currentTrackedChange.type !== "hyperlink" || currentTrackedChange.key !== trackedChangeKey || currentTrackedChange.hyperlinkKey !== linkKey) {
|
|
1412
|
-
const hyperlink =
|
|
1454
|
+
const hyperlink = createIndexedHyperlink(linkMark);
|
|
1413
1455
|
const wrapper = createTrackedRunWrapper(type, info, hyperlink);
|
|
1414
1456
|
content.push(wrapper);
|
|
1415
1457
|
currentTrackedChange = {
|
|
@@ -1455,6 +1497,10 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
|
|
|
1455
1497
|
currentTrackedChange.wrapper.content.push(bookmarkMarkerFromAttrs(expectBookmarkBoundaryAttrs(node)));
|
|
1456
1498
|
return;
|
|
1457
1499
|
}
|
|
1500
|
+
if (node.type.name === "moveRangeBoundary") {
|
|
1501
|
+
currentTrackedChange.wrapper.content.push({ ...expectMoveRangeBoundaryAttrs(node) });
|
|
1502
|
+
return;
|
|
1503
|
+
}
|
|
1458
1504
|
if (node.type.name === "field" || node.type.name === "structuredField") {
|
|
1459
1505
|
currentTrackedChange.wrapper.content.push(createFieldFromNode(node, {
|
|
1460
1506
|
...formattingContext,
|
|
@@ -1488,6 +1534,12 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
|
|
|
1488
1534
|
appendDirectRun(node, ownedRun, node.isText);
|
|
1489
1535
|
return;
|
|
1490
1536
|
}
|
|
1537
|
+
if (node.type.name === "moveRangeBoundary") {
|
|
1538
|
+
flushCurrentInline();
|
|
1539
|
+
currentTrackedChange = void 0;
|
|
1540
|
+
content.push({ ...expectMoveRangeBoundaryAttrs(node) });
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1491
1543
|
if (noteRefMark && !linkMark) {
|
|
1492
1544
|
flushCurrentInline();
|
|
1493
1545
|
content.push(createNoteReferenceRun(noteRefMark, node.marks, formattingContext));
|
|
@@ -1497,7 +1549,7 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
|
|
|
1497
1549
|
const linkKey = getLinkKey(linkMark);
|
|
1498
1550
|
if (currentHyperlink && currentHyperlinkKey === linkKey) {} else {
|
|
1499
1551
|
flushCurrentInline();
|
|
1500
|
-
currentHyperlink =
|
|
1552
|
+
currentHyperlink = createIndexedHyperlink(linkMark);
|
|
1501
1553
|
currentHyperlinkKey = linkKey;
|
|
1502
1554
|
}
|
|
1503
1555
|
addNodeToHyperlink({
|
|
@@ -1588,7 +1640,7 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
|
|
|
1588
1640
|
type: "commentRangeEnd",
|
|
1589
1641
|
id: commentId
|
|
1590
1642
|
});
|
|
1591
|
-
return nestInlineWrapperGroups(content, wrapperGroups);
|
|
1643
|
+
return nestInlineWrapperGroups(content, wrapperGroups, sourceHyperlinkIndices);
|
|
1592
1644
|
}
|
|
1593
1645
|
function createTrackedChangeRun({ baseParagraphFormatting, inheritedFormatting, marks, node, paragraphMarkFormatting, paragraphMarkPrecedesStyle, styleResolver }) {
|
|
1594
1646
|
const formattingContext = {
|
|
@@ -1646,6 +1698,7 @@ function createEmptyHyperlink(attrs) {
|
|
|
1646
1698
|
if (attrs.target !== void 0) hyperlink.target = attrs.target;
|
|
1647
1699
|
if (attrs.history !== void 0) hyperlink.history = attrs.history;
|
|
1648
1700
|
if (attrs.docLocation !== void 0) hyperlink.docLocation = attrs.docLocation;
|
|
1701
|
+
for (const stack of attrs._docxEmptyWrapperStacks ?? []) hyperlink.children.push(nestInlineWrappers(stack, []));
|
|
1649
1702
|
return hyperlink;
|
|
1650
1703
|
}
|
|
1651
1704
|
function getCommentMarkIds(marks) {
|
|
@@ -3298,6 +3351,9 @@ function convertPMTableCell(node, documentCounts, styleResolver = null) {
|
|
|
3298
3351
|
} else if (contentNode.type.name === "table") {
|
|
3299
3352
|
content.push(convertPMTable(contentNode, documentCounts, styleResolver));
|
|
3300
3353
|
previousStandaloneTextBox = null;
|
|
3354
|
+
} else if (contentNode.type.name === "blockSdt") {
|
|
3355
|
+
content.push(convertPMBlockSdt(contentNode, styleResolver));
|
|
3356
|
+
previousStandaloneTextBox = null;
|
|
3301
3357
|
} else if (contentNode.type.name === "textBox") previousStandaloneTextBox = appendTextBoxBlock(content, contentNode, {
|
|
3302
3358
|
pendingPageBreaks: 0,
|
|
3303
3359
|
previousStandaloneTextBox,
|
|
@@ -486,6 +486,7 @@ function convertParagraph(paragraph, styleResolver, context, extraRunFormatting,
|
|
|
486
486
|
nextRunIdentityId
|
|
487
487
|
});
|
|
488
488
|
if (linkNodes.length === 0) {
|
|
489
|
+
const emptyWrapperStacks = emptyInlineWrapperStacks(content.children);
|
|
489
490
|
emptyHyperlinks ??= [];
|
|
490
491
|
emptyHyperlinks.push({
|
|
491
492
|
offset: inlineOffset,
|
|
@@ -495,7 +496,8 @@ function convertParagraph(paragraph, styleResolver, context, extraRunFormatting,
|
|
|
495
496
|
...content.rId !== void 0 ? { rId: content.rId } : {},
|
|
496
497
|
...content.target !== void 0 ? { target: content.target } : {},
|
|
497
498
|
...content.history !== void 0 ? { history: content.history } : {},
|
|
498
|
-
...content.docLocation !== void 0 ? { docLocation: content.docLocation } : {}
|
|
499
|
+
...content.docLocation !== void 0 ? { docLocation: content.docLocation } : {},
|
|
500
|
+
...emptyWrapperStacks.length === 0 ? {} : { _docxEmptyWrapperStacks: emptyWrapperStacks }
|
|
499
501
|
});
|
|
500
502
|
break;
|
|
501
503
|
}
|
|
@@ -667,6 +669,7 @@ function convertTrackedChange(change, markType, nextHyperlinkInstanceIndex, next
|
|
|
667
669
|
id: item.id,
|
|
668
670
|
displacedByCustomXml: item.displacedByCustomXml
|
|
669
671
|
}));
|
|
672
|
+
else if (item.type === "moveFromRangeStart" || item.type === "moveFromRangeEnd" || item.type === "moveToRangeStart" || item.type === "moveToRangeEnd") itemNodes.push(schema.node(MOVE_RANGE_BOUNDARY_NODE_NAME, { marker: item }));
|
|
670
673
|
else if (item.type === "preservedInline") itemNodes.push(preservedInlineNode(item));
|
|
671
674
|
else panic(`Unsupported tracked-run content: ${JSON.stringify(item)}`);
|
|
672
675
|
nodes.push(...withInlineWrapperMark(itemNodes, stack));
|
|
@@ -1035,6 +1038,7 @@ function blockHasMeaningfulContent(block) {
|
|
|
1035
1038
|
if (block.type === "preservedBlock") return true;
|
|
1036
1039
|
if (block.type === "bookmarkStart" || block.type === "bookmarkEnd") return true;
|
|
1037
1040
|
if (block.type === "table") return block.rows.some((row) => row.cells.some((cell) => tableCellHasMeaningfulContent(cell)));
|
|
1041
|
+
if (block.type === "blockSdt") return true;
|
|
1038
1042
|
return block.content.some(paragraphContentHasMeaningfulContent);
|
|
1039
1043
|
}
|
|
1040
1044
|
function paragraphContentHasMeaningfulContent(content) {
|
|
@@ -1394,28 +1398,35 @@ function convertTableCell({ cell, styleResolver, context, isHeader, gridWidthPer
|
|
|
1394
1398
|
}
|
|
1395
1399
|
if (preserveVMergeRestart) attrs._preserveVMergeRestart = true;
|
|
1396
1400
|
if (vMergeContinuationCells && vMergeContinuationCells.length > 0) attrs._docxVMergeContinuationCells = transportTableCellsWithParagraphPropertySources(vMergeContinuationCells);
|
|
1397
|
-
const
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1401
|
+
const convertCellBlocks = (blocks) => {
|
|
1402
|
+
const nodes = [];
|
|
1403
|
+
for (const block of blocks) switch (block.type) {
|
|
1404
|
+
case "paragraph":
|
|
1405
|
+
nodes.push(...convertParagraphWithTextBoxes(block, styleResolver, {
|
|
1406
|
+
textBoxGroupId: context.nextTextBoxGroupId(),
|
|
1407
|
+
context,
|
|
1408
|
+
...conditionalStyle?.rPr !== void 0 ? { extraRunFormatting: conditionalStyle.rPr } : {},
|
|
1409
|
+
...conditionalStyle?.pPr !== void 0 ? { tableParagraphOverlay: conditionalStyle.pPr } : {}
|
|
1410
|
+
}));
|
|
1411
|
+
break;
|
|
1412
|
+
case "table":
|
|
1413
|
+
nodes.push(convertTable(block, styleResolver, context));
|
|
1414
|
+
break;
|
|
1415
|
+
case "blockSdt":
|
|
1416
|
+
nodes.push(convertBlockSdt(block, convertCellBlocks));
|
|
1417
|
+
break;
|
|
1418
|
+
case "preservedBlock":
|
|
1419
|
+
nodes.push(convertPreservedBlock(block));
|
|
1420
|
+
break;
|
|
1421
|
+
case "bookmarkStart":
|
|
1422
|
+
case "bookmarkEnd":
|
|
1423
|
+
nodes.push(convertBlockBookmarkBoundary(block));
|
|
1424
|
+
break;
|
|
1425
|
+
default: panic(`Unsupported table cell content: ${JSON.stringify(block)}`);
|
|
1426
|
+
}
|
|
1427
|
+
return nodes;
|
|
1428
|
+
};
|
|
1429
|
+
const contentNodes = convertCellBlocks(cell.content);
|
|
1419
1430
|
if (contentNodes.length === 0) contentNodes.push(schema.node("paragraph", {}, []));
|
|
1420
1431
|
const nodeType = isHeader ? "tableHeader" : "tableCell";
|
|
1421
1432
|
return schema.node(nodeType, attrs, contentNodes);
|
|
@@ -1527,27 +1538,32 @@ const withInlineWrapperStacks = (content, stack = []) => content.flatMap((item)
|
|
|
1527
1538
|
content: item,
|
|
1528
1539
|
stack
|
|
1529
1540
|
}]);
|
|
1530
|
-
/**
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
* wrapper would be the one lost.
|
|
1538
|
-
*
|
|
1539
|
-
* A node that holds its own leaves, a content control, is marked here as the
|
|
1540
|
-
* one node it is; its leaves were marked with the whole enclosing stack when
|
|
1541
|
-
* it was built, because the painter reads the wrapper off the leaf.
|
|
1542
|
-
*/
|
|
1543
|
-
const withInlineWrapperMark = (nodes, stack) => {
|
|
1541
|
+
/** Wrapper nests with no leaf to carry their stack as a ProseMirror mark. */
|
|
1542
|
+
const emptyInlineWrapperStacks = (content, stack = []) => content.flatMap((item) => {
|
|
1543
|
+
if (item.type !== "inlineWrapper") return [];
|
|
1544
|
+
const nested = [...stack, inlineWrapperLayer(item)];
|
|
1545
|
+
return item.content.length === 0 ? [nested] : emptyInlineWrapperStacks(item.content, nested);
|
|
1546
|
+
});
|
|
1547
|
+
const withInlineWrapperMark = (nodes, stack, origin) => {
|
|
1544
1548
|
const markType = schema.marks["inlineWrapper"];
|
|
1545
1549
|
if (stack.length === 0 || !markType) return nodes;
|
|
1546
1550
|
return nodes.map((node) => {
|
|
1547
|
-
if (!node.
|
|
1551
|
+
if (!node.isInline) return node;
|
|
1548
1552
|
const inner = node.marks.find((mark) => mark.type === markType);
|
|
1549
|
-
const
|
|
1550
|
-
|
|
1553
|
+
const innerAttrs = inner === void 0 ? void 0 : expectInlineWrapperMarkAttrs(inner);
|
|
1554
|
+
const layers = innerAttrs === void 0 ? stack : [...stack, ...innerAttrs.stack];
|
|
1555
|
+
const inheritedOrigin = typeof innerAttrs?._docxHyperlinkIndex !== "number" || typeof innerAttrs._docxInsideHyperlinkStackStart !== "number" ? void 0 : {
|
|
1556
|
+
hyperlinkIndex: innerAttrs._docxHyperlinkIndex,
|
|
1557
|
+
stackStart: stack.length + innerAttrs._docxInsideHyperlinkStackStart
|
|
1558
|
+
};
|
|
1559
|
+
const provenance = origin ?? inheritedOrigin;
|
|
1560
|
+
return node.mark(markType.create({
|
|
1561
|
+
stack: layers,
|
|
1562
|
+
...provenance === void 0 ? {} : {
|
|
1563
|
+
_docxHyperlinkIndex: provenance.hyperlinkIndex,
|
|
1564
|
+
_docxInsideHyperlinkStackStart: provenance.stackStart
|
|
1565
|
+
}
|
|
1566
|
+
}).addToSet(node.marks));
|
|
1551
1567
|
});
|
|
1552
1568
|
};
|
|
1553
1569
|
/**
|
|
@@ -1570,9 +1586,17 @@ function convertMathEquation(math) {
|
|
|
1570
1586
|
function convertInlineSdt(sdt, nextHyperlinkInstanceIndex, nextRunIdentityId, getInheritedRunFormatting, trackedRunFormattingResolvers, styleResolver, textBoxAnchors, wrappedBy = []) {
|
|
1571
1587
|
const props = sdt.properties;
|
|
1572
1588
|
const inlineNodes = [];
|
|
1573
|
-
|
|
1589
|
+
const stacked = withInlineWrapperStacks(sdt.content, wrappedBy);
|
|
1590
|
+
const emptyRanges = planEmptyRanges(stacked.map(({ content }) => content));
|
|
1591
|
+
for (const [index, { content, stack }] of stacked.entries()) {
|
|
1574
1592
|
if (!isInlineSdtContent(content)) continue;
|
|
1575
1593
|
const itemNodes = [];
|
|
1594
|
+
const anchored = emptyRanges.anchorAt.get(index);
|
|
1595
|
+
if (anchored) {
|
|
1596
|
+
inlineNodes.push(...withInlineWrapperMark([schema.node(RANGE_ANCHOR_NODE_NAME, anchored)], stack));
|
|
1597
|
+
continue;
|
|
1598
|
+
}
|
|
1599
|
+
if (emptyRanges.closedAt.has(index)) continue;
|
|
1576
1600
|
switch (content.type) {
|
|
1577
1601
|
case "run":
|
|
1578
1602
|
itemNodes.push(...convertRun(content, getInheritedRunFormatting(content.formatting), nextRunIdentityId, styleResolver, textBoxAnchors));
|
|
@@ -1633,6 +1657,12 @@ function convertInlineSdt(sdt, nextHyperlinkInstanceIndex, nextRunIdentityId, ge
|
|
|
1633
1657
|
displacedByCustomXml: content.displacedByCustomXml
|
|
1634
1658
|
}));
|
|
1635
1659
|
break;
|
|
1660
|
+
case "moveFromRangeStart":
|
|
1661
|
+
case "moveFromRangeEnd":
|
|
1662
|
+
case "moveToRangeStart":
|
|
1663
|
+
case "moveToRangeEnd":
|
|
1664
|
+
itemNodes.push(schema.node(MOVE_RANGE_BOUNDARY_NODE_NAME, { marker: content }));
|
|
1665
|
+
break;
|
|
1636
1666
|
case "preservedInline":
|
|
1637
1667
|
itemNodes.push(preservedInlineNode(content));
|
|
1638
1668
|
break;
|
|
@@ -2331,7 +2361,10 @@ function convertHyperlink(hyperlink, { getInheritedRunFormatting, styleResolver,
|
|
|
2331
2361
|
}
|
|
2332
2362
|
default: break;
|
|
2333
2363
|
}
|
|
2334
|
-
nodes.push(...withInlineWrapperMark(childNodes, stack
|
|
2364
|
+
nodes.push(...withInlineWrapperMark(childNodes, stack, {
|
|
2365
|
+
hyperlinkIndex,
|
|
2366
|
+
stackStart: 0
|
|
2367
|
+
}));
|
|
2335
2368
|
}
|
|
2336
2369
|
return nodes;
|
|
2337
2370
|
}
|
|
@@ -7,6 +7,7 @@ import { panic } from "better-result";
|
|
|
7
7
|
/**
|
|
8
8
|
* Hyperlink Mark Extension
|
|
9
9
|
*/
|
|
10
|
+
const DOCX_HYPERLINK_INDEX_ATTRIBUTE = "data-docx-hyperlink-index";
|
|
10
11
|
/**
|
|
11
12
|
* Sanitize an href arriving from (or being emitted to) the DOM: pasted HTML,
|
|
12
13
|
* clipboard content, or a mark attr already stored on the document. Only
|
|
@@ -90,19 +91,25 @@ const HyperlinkExtension = createMarkExtension({
|
|
|
90
91
|
inclusive: false,
|
|
91
92
|
parseDOM: [{
|
|
92
93
|
tag: "a[href]",
|
|
93
|
-
getAttrs: (dom) =>
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
94
|
+
getAttrs: (dom) => {
|
|
95
|
+
const index = dom.getAttribute(DOCX_HYPERLINK_INDEX_ATTRIBUTE);
|
|
96
|
+
const docxHyperlinkIndex = index !== null && /^\d+$/u.test(index) && Number.isSafeInteger(Number(index)) ? Number(index) : void 0;
|
|
97
|
+
return {
|
|
98
|
+
href: sanitizeStoredHref(dom.getAttribute("href") ?? void 0),
|
|
99
|
+
tooltip: dom.getAttribute("title") ?? void 0,
|
|
100
|
+
target: dom.getAttribute("target") ?? void 0,
|
|
101
|
+
...docxHyperlinkIndex === void 0 ? {} : { _docxHyperlinkIndex: docxHyperlinkIndex }
|
|
102
|
+
};
|
|
103
|
+
}
|
|
98
104
|
}],
|
|
99
105
|
toDOM(mark) {
|
|
100
|
-
const { href, target, tooltip } = expectHyperlinkMarkAttrs(mark);
|
|
106
|
+
const { href, target, tooltip, _docxHyperlinkIndex } = expectHyperlinkMarkAttrs(mark);
|
|
101
107
|
const domAttrs = {
|
|
102
108
|
href: sanitizeStoredHref(href),
|
|
103
109
|
...anchorTargetAttrs(target)
|
|
104
110
|
};
|
|
105
111
|
if (tooltip) domAttrs["title"] = tooltip;
|
|
112
|
+
if (typeof _docxHyperlinkIndex === "number") domAttrs[DOCX_HYPERLINK_INDEX_ATTRIBUTE] = String(_docxHyperlinkIndex);
|
|
106
113
|
return [
|
|
107
114
|
"a",
|
|
108
115
|
domAttrs,
|