@stll/folio-core 0.47.4 → 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/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
|
@@ -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,
|
|
@@ -16,6 +16,7 @@ import { panic } from "better-result";
|
|
|
16
16
|
* extend the wrapper over text the author wrote outside it.
|
|
17
17
|
*/
|
|
18
18
|
const INLINE_WRAPPER_MARK_NAME = "inlineWrapper";
|
|
19
|
+
const INLINE_WRAPPER_HYPERLINK_ORIGIN_ATTRIBUTE = "data-inline-wrapper-hyperlink-origin";
|
|
19
20
|
/** What a tagged layer's element name is spelled as on the rendered span. */
|
|
20
21
|
const TAGGED_ELEMENT_ATTRIBUTES = {
|
|
21
22
|
smartTag: "data-smart-tag-element",
|
|
@@ -54,22 +55,39 @@ const InlineWrapperExtension = createMarkExtension({
|
|
|
54
55
|
name: INLINE_WRAPPER_MARK_NAME,
|
|
55
56
|
schemaMarkName: INLINE_WRAPPER_MARK_NAME,
|
|
56
57
|
markSpec: {
|
|
57
|
-
attrs: {
|
|
58
|
-
|
|
59
|
-
stack: { default: null }
|
|
58
|
+
attrs: {
|
|
59
|
+
/** Outermost first. `null` is no wrapper, which is the absence of the mark. */
|
|
60
|
+
stack: { default: null },
|
|
61
|
+
_docxHyperlinkIndex: { default: null },
|
|
62
|
+
_docxInsideHyperlinkStackStart: { default: null }
|
|
63
|
+
},
|
|
60
64
|
inclusive: false,
|
|
61
65
|
parseDOM: [{
|
|
62
66
|
tag: `[${INLINE_WRAPPER_STACK_ATTRIBUTE}]`,
|
|
63
67
|
getAttrs(dom) {
|
|
64
68
|
const stack = parseInlineWrapperStack(dom.getAttribute(INLINE_WRAPPER_STACK_ATTRIBUTE));
|
|
65
|
-
|
|
69
|
+
if (stack === null) return false;
|
|
70
|
+
const origin = dom.getAttribute(INLINE_WRAPPER_HYPERLINK_ORIGIN_ATTRIBUTE);
|
|
71
|
+
if (origin === null) return { stack };
|
|
72
|
+
const match = /^(\d+):(\d+)$/u.exec(origin);
|
|
73
|
+
if (match === null) return false;
|
|
74
|
+
const hyperlinkIndex = Number(match[1]);
|
|
75
|
+
const stackStart = Number(match[2]);
|
|
76
|
+
if (!Number.isSafeInteger(hyperlinkIndex) || !Number.isSafeInteger(stackStart) || stackStart >= stack.length) return false;
|
|
77
|
+
return {
|
|
78
|
+
stack,
|
|
79
|
+
_docxHyperlinkIndex: hyperlinkIndex,
|
|
80
|
+
_docxInsideHyperlinkStackStart: stackStart
|
|
81
|
+
};
|
|
66
82
|
}
|
|
67
83
|
}],
|
|
68
84
|
toDOM(mark) {
|
|
69
|
-
const { stack } = expectInlineWrapperMarkAttrs(mark);
|
|
85
|
+
const { stack, _docxHyperlinkIndex, _docxInsideHyperlinkStackStart } = expectInlineWrapperMarkAttrs(mark);
|
|
70
86
|
const innermost = stack.at(-1);
|
|
71
87
|
if (innermost === void 0) panic("An inline wrapper mark carries at least one layer");
|
|
72
|
-
|
|
88
|
+
const attributes = { [INLINE_WRAPPER_STACK_ATTRIBUTE]: serializeInlineWrapperStack(stack) };
|
|
89
|
+
if (typeof _docxHyperlinkIndex === "number" && typeof _docxInsideHyperlinkStackStart === "number") attributes[INLINE_WRAPPER_HYPERLINK_ORIGIN_ATTRIBUTE] = `${_docxHyperlinkIndex}:${_docxInsideHyperlinkStackStart}`;
|
|
90
|
+
return layerElement(innermost, attributes);
|
|
73
91
|
}
|
|
74
92
|
}
|
|
75
93
|
});
|
|
@@ -13,11 +13,15 @@ const surveyCommentReferences = (doc) => {
|
|
|
13
13
|
doc.descendants((node, position) => {
|
|
14
14
|
if (node.type.name === "commentReference") {
|
|
15
15
|
const attrs = readCommentReferenceAttrs(node);
|
|
16
|
-
references.push({
|
|
17
|
-
|
|
16
|
+
references.push(attrs.ok ? {
|
|
17
|
+
status: "valid",
|
|
18
|
+
commentId: attrs.value.commentId,
|
|
18
19
|
position,
|
|
19
|
-
size: node.nodeSize
|
|
20
|
-
|
|
20
|
+
size: node.nodeSize
|
|
21
|
+
} : {
|
|
22
|
+
status: "malformed",
|
|
23
|
+
position,
|
|
24
|
+
size: node.nodeSize
|
|
21
25
|
});
|
|
22
26
|
return false;
|
|
23
27
|
}
|
|
@@ -60,7 +64,7 @@ const CommentReferenceExtension = createNodeExtension({
|
|
|
60
64
|
tag: "span[data-docx-comment-reference]",
|
|
61
65
|
getAttrs(dom) {
|
|
62
66
|
const raw = dom.getAttribute("data-docx-comment-reference");
|
|
63
|
-
if (raw === null ||
|
|
67
|
+
if (raw === null || !/^-?(?:0|[1-9]\d*)$/.test(raw)) return false;
|
|
64
68
|
const commentId = Number(raw);
|
|
65
69
|
return Number.isSafeInteger(commentId) ? { commentId } : false;
|
|
66
70
|
}
|
|
@@ -367,7 +367,7 @@ const statedCellWidth = (value, type) => ({
|
|
|
367
367
|
}
|
|
368
368
|
});
|
|
369
369
|
const tableCellSpec = {
|
|
370
|
-
content: "(paragraph | table | textBox | preservedBlock | blockBookmarkBoundary)+",
|
|
370
|
+
content: "(paragraph | table | textBox | blockSdt | preservedBlock | blockBookmarkBoundary)+",
|
|
371
371
|
tableRole: "cell",
|
|
372
372
|
isolating: true,
|
|
373
373
|
attrs: {
|
|
@@ -436,7 +436,7 @@ const tableCellSpec = {
|
|
|
436
436
|
}
|
|
437
437
|
};
|
|
438
438
|
const tableHeaderSpec = {
|
|
439
|
-
content: "(paragraph | table | textBox | preservedBlock | blockBookmarkBoundary)+",
|
|
439
|
+
content: "(paragraph | table | textBox | blockSdt | preservedBlock | blockBookmarkBoundary)+",
|
|
440
440
|
tableRole: "header_cell",
|
|
441
441
|
isolating: true,
|
|
442
442
|
attrs: {
|
|
@@ -170,6 +170,17 @@ type InlineWrapperKind = InlineWrapperLayer["kind"];
|
|
|
170
170
|
*/
|
|
171
171
|
type InlineWrapperAttrs = {
|
|
172
172
|
stack: readonly InlineWrapperLayer[];
|
|
173
|
+
/**
|
|
174
|
+
* The imported hyperlink whose children the suffix of `stack` came from.
|
|
175
|
+
*
|
|
176
|
+
* ProseMirror stores marks as an unordered set, so a link mark and a wrapper
|
|
177
|
+
* mark otherwise cannot say whether `<w:bdo>` surrounded `<w:hyperlink>` or
|
|
178
|
+
* was one of its children. This is parser provenance, not editable wrapper
|
|
179
|
+
* data: it is meaningful only alongside the matching hyperlink mark.
|
|
180
|
+
*/
|
|
181
|
+
_docxHyperlinkIndex?: number;
|
|
182
|
+
/** The first stack layer authored inside `_docxHyperlinkIndex`. */
|
|
183
|
+
_docxInsideHyperlinkStackStart?: number;
|
|
173
184
|
};
|
|
174
185
|
declare const COMPLEX_SCRIPT_RUN_PROPERTY_KEYS: readonly ["boldCs", "italicCs", "fontSizeCs"];
|
|
175
186
|
type ComplexScriptRunPropertyKey = (typeof COMPLEX_SCRIPT_RUN_PROPERTY_KEYS)[number];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { document_d_exports } from "../../types/document.js";
|
|
2
|
-
import { TrackedChangeProvenance } from "./marks.js";
|
|
2
|
+
import { InlineWrapperLayer, TrackedChangeProvenance } from "./marks.js";
|
|
3
3
|
import "../../types/index.js";
|
|
4
4
|
import { OutlineStyleAttr } from "../../types/documentEnumValues.js";
|
|
5
5
|
import { SpacingExplicit } from "../../types/formatting.js";
|
|
@@ -212,9 +212,9 @@ type ParagraphAttrs = {
|
|
|
212
212
|
id: number;
|
|
213
213
|
name: string;
|
|
214
214
|
}[];
|
|
215
|
-
/**
|
|
216
|
-
* Preserve
|
|
217
|
-
*
|
|
215
|
+
/** A `w:hyperlink` with no projectable leaf cannot be represented as a text mark.
|
|
216
|
+
* Preserve its metadata and any empty transparent-wrapper children at the
|
|
217
|
+
* paragraph boundary so an editor round trip does not drop either element. */
|
|
218
218
|
_emptyHyperlinks?: {
|
|
219
219
|
/** ProseMirror inline offset where the zero-width hyperlink appeared. */
|
|
220
220
|
offset: number;
|
|
@@ -225,6 +225,8 @@ type ParagraphAttrs = {
|
|
|
225
225
|
target?: document_d_exports.Hyperlink["target"];
|
|
226
226
|
history?: document_d_exports.Hyperlink["history"];
|
|
227
227
|
docLocation?: document_d_exports.Hyperlink["docLocation"];
|
|
228
|
+
/** Empty transparent-wrapper nests authored as children of this link. */
|
|
229
|
+
_docxEmptyWrapperStacks?: readonly (readonly InlineWrapperLayer[])[];
|
|
228
230
|
}[];
|
|
229
231
|
/**
|
|
230
232
|
* Run-in heading flag: this paragraph's mark carries
|
|
@@ -16,7 +16,7 @@ declare const TRACKED_RUN_INLINE_ATOM_DISPOSITIONS: Readonly<{
|
|
|
16
16
|
readonly hardBreak: "carry";
|
|
17
17
|
readonly image: "carry";
|
|
18
18
|
readonly math: "carry";
|
|
19
|
-
readonly moveRangeBoundary: "
|
|
19
|
+
readonly moveRangeBoundary: "carry";
|
|
20
20
|
readonly pageBreakRun: "page-break-carrier";
|
|
21
21
|
readonly preservedXml: "carry";
|
|
22
22
|
readonly rangeAnchor: "transparent";
|
|
@@ -15,7 +15,7 @@ const TRACKED_RUN_INLINE_ATOM_DISPOSITIONS = Object.freeze({
|
|
|
15
15
|
hardBreak: "carry",
|
|
16
16
|
image: "carry",
|
|
17
17
|
math: "carry",
|
|
18
|
-
moveRangeBoundary: "
|
|
18
|
+
moveRangeBoundary: "carry",
|
|
19
19
|
pageBreakRun: "page-break-carrier",
|
|
20
20
|
preservedXml: "carry",
|
|
21
21
|
rangeAnchor: "transparent",
|