@stll/folio-core 0.28.1 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +1 -2
  2. package/dist/layout-bridge/convert/toFlowBlocks.js +61 -95
  3. package/dist/markdown/renderParagraph.js +7 -2
  4. package/dist/prosemirror/attrs/index.js +2 -1
  5. package/dist/prosemirror/bookmarkBoundaryAttrs.d.ts +8 -0
  6. package/dist/prosemirror/bookmarkBoundaryAttrs.js +66 -0
  7. package/dist/prosemirror/conversion/fromProseDoc.js +170 -52
  8. package/dist/prosemirror/conversion/toProseDoc.js +169 -61
  9. package/dist/prosemirror/extensions/StarterKit.js +11 -3
  10. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +8 -4
  11. package/dist/prosemirror/extensions/features/PasteCleanupExtension.d.ts +4 -1
  12. package/dist/prosemirror/extensions/features/PasteCleanupExtension.js +9 -5
  13. package/dist/prosemirror/extensions/features/pasteCleanup.d.ts +10 -23
  14. package/dist/prosemirror/extensions/features/pasteCleanup.js +77 -22
  15. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.d.ts +9 -0
  16. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +67 -0
  17. package/dist/prosemirror/extensions/nodes/FieldExtension.d.ts +10 -4
  18. package/dist/prosemirror/extensions/nodes/FieldExtension.js +77 -59
  19. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.d.ts +4 -1
  20. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +4 -3
  21. package/dist/prosemirror/listMarker.d.ts +58 -0
  22. package/dist/prosemirror/listMarker.js +185 -0
  23. package/dist/prosemirror/numberedRefFields.d.ts +24 -0
  24. package/dist/prosemirror/numberedRefFields.js +276 -0
  25. package/dist/prosemirror/paraText.js +56 -17
  26. package/dist/prosemirror/plugins/anonymizationDecorations.js +1 -1
  27. package/dist/prosemirror/plugins/pmTextScan.d.ts +3 -1
  28. package/dist/prosemirror/plugins/pmTextScan.js +28 -7
  29. package/dist/prosemirror/plugins/templateDirectives.js +2 -2
  30. package/dist/prosemirror/schema/index.d.ts +2 -2
  31. package/dist/prosemirror/schema/nodes.d.ts +13 -1
  32. package/dist/prosemirror/validation.js +93 -0
  33. package/package.json +1 -1
@@ -4,7 +4,9 @@ import { OUTLINE_STYLE_CSS_ALIASES } from "../../types/documentEnumValues.js";
4
4
  import { pixelsToEmu } from "../../utils/units.js";
5
5
  import { expectBlockSdtAttrs, expectCharacterSpacingMarkAttrs, expectCharacterStyleMarkAttrs, expectCommentMarkAttrs, expectEmphasisMarkAttrs, expectFieldAttrs, expectFontFamilyMarkAttrs, expectFontSizeMarkAttrs, expectFootnoteRefMarkAttrs, expectHardBreakAttrs, expectHighlightMarkAttrs, expectHyperlinkMarkAttrs, expectImageAttrs, expectLanguageMarkAttrs, expectMathAttrs, expectParagraphAttrs, expectRunFormattingOverrideMarkAttrs, expectRunPropertyChangeMarkAttrs, expectRunShadingMarkAttrs, expectSdtAttrs, expectShapeAttrs, expectStrikeMarkAttrs, expectSymbolAttrs, expectTabAttrs, expectTableAttrs, expectTableCellAttrs, expectTableRowAttrs, expectTextBoxAttrs, expectTextColorMarkAttrs, expectTextEffectMarkAttrs, expectTrackedChangeMarkAttrs, expectUnderlineMarkAttrs } from "../attrs/index.js";
6
6
  import { autospacingMatchesBase, hasAutospacingBaseSide } from "../autospacingBase.js";
7
+ import { expectBookmarkBoundaryAttrs } from "../bookmarkBoundaryAttrs.js";
7
8
  import { applyRunFormattingOverrideAttrs } from "../extensions/marks/RunFormattingOverrideExtension.js";
9
+ import { resolveNumberedRefFields } from "../numberedRefFields.js";
8
10
  import { directionToBidi } from "../paragraphDirection.js";
9
11
  import { RUN_FORMATTING_MARK_NAMES } from "../runFormattingMarkNames.js";
10
12
  import { parseShapeGeometryAdjustments } from "../shapeGeometryAdjustments.js";
@@ -198,12 +200,33 @@ function mapSuggestionStrippedNode(node) {
198
200
  function stripSuggestedProvenance(doc) {
199
201
  return mapSuggestionStrippedNode(doc) ?? doc;
200
202
  }
201
- /**
202
- * Extract block content (paragraphs, tables, block SDTs) from a ProseMirror
203
- * document.
204
- */
205
- function extractBlocks(inputDoc) {
206
- const pmDoc = stripSuggestedProvenance(inputDoc);
203
+ function materializeNumberedRefValues(doc) {
204
+ const results = resolveNumberedRefFields(doc);
205
+ if (results.size === 0) return doc;
206
+ const visit = (node) => {
207
+ if (node.type.name === "field" || node.type.name === "structuredField") {
208
+ const displayText = results.get(node);
209
+ if (displayText !== void 0) return node.type.create({
210
+ ...node.attrs,
211
+ displayText
212
+ }, node.content, node.marks);
213
+ return node;
214
+ }
215
+ if (node.childCount === 0) return node;
216
+ const children = [];
217
+ let changed = false;
218
+ node.forEach((child) => {
219
+ const mappedChild = visit(child);
220
+ children.push(mappedChild);
221
+ changed ||= mappedChild !== child;
222
+ });
223
+ return changed ? node.copy(Fragment.fromArray(children)) : node;
224
+ };
225
+ return visit(doc);
226
+ }
227
+ function extractBlocks(inputDoc, refResolution = "resolve") {
228
+ const strippedDoc = stripSuggestedProvenance(inputDoc);
229
+ const pmDoc = refResolution === "resolve" ? materializeNumberedRefValues(strippedDoc) : strippedDoc;
207
230
  const blocks = [];
208
231
  const textBoxAnchorMarkers = /* @__PURE__ */ new Map();
209
232
  const documentCounts = buildDocumentTrackedChangeCounts(pmDoc);
@@ -271,7 +294,7 @@ function convertPMBlockSdt(node) {
271
294
  if (attrs.rawEndPropertiesXml) properties.rawEndPropertiesXml = attrs.rawEndPropertiesXml;
272
295
  if (attrs.rawSdtChildrenBeforeContent) properties.rawSdtChildrenBeforeContent = attrs.rawSdtChildrenBeforeContent;
273
296
  if (attrs.rawSdtChildrenAfterContent) properties.rawSdtChildrenAfterContent = attrs.rawSdtChildrenAfterContent;
274
- const extracted = extractBlocks(node.type.schema.node("doc", null, node.content));
297
+ const extracted = extractBlocks(node.type.schema.node("doc", null, node.content), "inherit");
275
298
  return {
276
299
  type: "blockSdt",
277
300
  properties,
@@ -391,6 +414,7 @@ function editTextBoxAnchorInContent(content, marker, replacement) {
391
414
  }
392
415
  let nestedContent;
393
416
  if (item?.type === "hyperlink") nestedContent = item.children;
417
+ else if (item?.type === "simpleField") nestedContent = item.content;
394
418
  else if (item?.type === "inlineSdt" || item?.type === "insertion" || item?.type === "deletion" || item?.type === "moveFrom" || item?.type === "moveTo") nestedContent = item.content;
395
419
  if (!nestedContent) continue;
396
420
  const hadContent = nestedContent.length > 0;
@@ -640,12 +664,28 @@ function paragraphAttrsToFormatting(attrs) {
640
664
  if (attrs.suppressAutoHyphens != null) f.suppressAutoHyphens = attrs.suppressAutoHyphens;
641
665
  return f;
642
666
  }
643
- /**
644
- * Extract paragraph content (runs, hyperlinks) from ProseMirror paragraph
645
- *
646
- * Coalesces consecutive text with the same marks into single Runs
647
- * for efficient DOCX representation.
648
- */
667
+ function createTrackedRunWrapper(type, info, child) {
668
+ if (type === "insertion") return {
669
+ type,
670
+ info,
671
+ content: [child]
672
+ };
673
+ if (type === "deletion") return {
674
+ type,
675
+ info,
676
+ content: [child]
677
+ };
678
+ if (type === "moveFrom") return {
679
+ type,
680
+ info,
681
+ content: [child]
682
+ };
683
+ return {
684
+ type,
685
+ info,
686
+ content: [child]
687
+ };
688
+ }
649
689
  function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, textBoxAnchorMarkers, skipLeadingRenderedPageBreak = false) {
650
690
  const content = [];
651
691
  const inheritedFormatting = paragraph.type.name === "paragraph" ? expectParagraphAttrs(paragraph).defaultTextFormatting ?? void 0 : void 0;
@@ -659,6 +699,7 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
659
699
  let currentMarksKey = null;
660
700
  let currentHyperlink = null;
661
701
  let currentHyperlinkKey = null;
702
+ let currentTrackedChange;
662
703
  const openedComments = /* @__PURE__ */ new Set();
663
704
  const commentLastOffset = /* @__PURE__ */ new Map();
664
705
  paragraph.forEach((node, offset) => {
@@ -687,6 +728,7 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
687
728
  for (const commentId of nodeCommentIds) if (!openedComments.has(commentId)) toOpen.push(commentId);
688
729
  if (toClose.length === 0 && toOpen.length === 0) return;
689
730
  flushCurrentInline();
731
+ currentTrackedChange = void 0;
690
732
  for (const commentId of toClose.toSorted((a, b) => a - b)) {
691
733
  content.push({
692
734
  type: "commentRangeEnd",
@@ -708,6 +750,7 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
708
750
  if (!item || item.attrs.offset > offset) break;
709
751
  nextEmptyHyperlink += 1;
710
752
  flushCurrentInline();
753
+ currentTrackedChange = void 0;
711
754
  content.push(createEmptyHyperlink(item.attrs));
712
755
  }
713
756
  };
@@ -722,8 +765,10 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
722
765
  const noteRefMark = node.marks.find((m) => m.type.name === "footnoteRef");
723
766
  const insertionMark = node.marks.find((m) => m.type.name === "insertion");
724
767
  const deletionMark = node.marks.find((m) => m.type.name === "deletion");
768
+ if (!insertionMark && !deletionMark) currentTrackedChange = void 0;
725
769
  if (node.type.name === "textBoxAnchor") {
726
770
  flushCurrentInline();
771
+ currentTrackedChange = void 0;
727
772
  if (!textBoxAnchorMarkers) return;
728
773
  const { anchorId } = expectTextBoxAnchorAttrs(node);
729
774
  const marker = {
@@ -766,42 +811,51 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
766
811
  if (!changeMark) return;
767
812
  const changeAttrs = expectTrackedChangeMarkAttrs(changeMark);
768
813
  const otherMarks = node.marks.filter((m) => m.type.name !== "insertion" && m.type.name !== "deletion");
769
- const run = createTrackedChangeRun({
770
- inheritedFormatting,
771
- marks: otherMarks,
772
- node
773
- });
774
- if (!run) return;
775
- const trackedContent = linkMark ? {
776
- ...createHyperlink(linkMark),
777
- children: [run]
778
- } : run;
779
814
  const info = {
780
815
  id: changeAttrs.revisionId,
781
816
  author: changeAttrs.author || "Unknown"
782
817
  };
783
818
  if (changeAttrs.date) info.date = changeAttrs.date;
784
819
  if (changeAttrs.initials) info.initials = changeAttrs.initials;
785
- if (insertionMark) if (changeAttrs.moveKind === "moveTo") content.push({
786
- type: "moveTo",
787
- info,
788
- content: [trackedContent]
789
- });
790
- else content.push({
791
- type: "insertion",
792
- info,
793
- content: [trackedContent]
794
- });
795
- else if (changeAttrs.moveKind === "moveFrom") content.push({
796
- type: "moveFrom",
797
- info,
798
- content: [trackedContent]
799
- });
800
- else content.push({
801
- type: "deletion",
802
- info,
803
- content: [trackedContent]
820
+ let type;
821
+ if (insertionMark) type = changeAttrs.moveKind === "moveTo" ? "moveTo" : "insertion";
822
+ else type = changeAttrs.moveKind === "moveFrom" ? "moveFrom" : "deletion";
823
+ const trackedChangeKey = `${type}:${JSON.stringify(info)}`;
824
+ if (linkMark) {
825
+ const linkKey = getLinkKey(linkMark);
826
+ if (!currentTrackedChange || currentTrackedChange.key !== trackedChangeKey || currentTrackedChange.hyperlinkKey !== linkKey) {
827
+ const hyperlink = createHyperlink(linkMark);
828
+ const wrapper = createTrackedRunWrapper(type, info, hyperlink);
829
+ content.push(wrapper);
830
+ currentTrackedChange = {
831
+ key: trackedChangeKey,
832
+ hyperlink,
833
+ hyperlinkKey: linkKey
834
+ };
835
+ }
836
+ if (node.type.name === "bookmarkBoundary") {
837
+ addNodeToHyperlink({
838
+ hyperlink: currentTrackedChange.hyperlink,
839
+ inheritedFormatting,
840
+ node
841
+ });
842
+ return;
843
+ }
844
+ const run = createTrackedChangeRun({
845
+ inheritedFormatting,
846
+ marks: otherMarks,
847
+ node
848
+ });
849
+ if (run) currentTrackedChange.hyperlink.children.push(run);
850
+ return;
851
+ }
852
+ currentTrackedChange = void 0;
853
+ const run = createTrackedChangeRun({
854
+ inheritedFormatting,
855
+ marks: otherMarks,
856
+ node
804
857
  });
858
+ if (run) content.push(createTrackedRunWrapper(type, info, run));
805
859
  return;
806
860
  }
807
861
  if (noteRefMark && !linkMark) {
@@ -824,7 +878,21 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
824
878
  return;
825
879
  }
826
880
  if (currentHyperlink) flushCurrentInline();
827
- if (node.isText) {
881
+ if (node.type.name === "bookmarkBoundary") {
882
+ flushCurrentInline();
883
+ const attrs = expectBookmarkBoundaryAttrs(node);
884
+ if (attrs.type === "start") content.push({
885
+ type: "bookmarkStart",
886
+ id: attrs.id,
887
+ name: attrs.name,
888
+ ...attrs.colFirst !== void 0 ? { colFirst: attrs.colFirst } : {},
889
+ ...attrs.colLast !== void 0 ? { colLast: attrs.colLast } : {}
890
+ });
891
+ else content.push({
892
+ type: "bookmarkEnd",
893
+ id: attrs.id
894
+ });
895
+ } else if (node.isText) {
828
896
  const marksKey = getMarksKey(node.marks);
829
897
  if (currentRun && currentMarksKey === marksKey) appendTextToRun(currentRun, node.text || "");
830
898
  else {
@@ -854,9 +922,12 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
854
922
  } else if (node.type.name === "renderedPageBreak") {
855
923
  flushCurrentInline();
856
924
  content.push(createRenderedPageBreakRun());
857
- } else if (node.type.name === "field") {
925
+ } else if (node.type.name === "field" || node.type.name === "structuredField") {
858
926
  flushCurrentInline();
859
- content.push(createFieldFromNode(node, node.marks));
927
+ content.push(createFieldFromNode(node, {
928
+ marks: node.marks,
929
+ textBoxAnchorMarkers
930
+ }));
860
931
  } else if (node.type.name === "sdt") {
861
932
  flushCurrentInline();
862
933
  content.push(createInlineSdtFromNode(node, textBoxAnchorMarkers));
@@ -1005,6 +1076,21 @@ function createHyperlink(linkMark) {
1005
1076
  return hyperlink;
1006
1077
  }
1007
1078
  function addNodeToHyperlink({ hyperlink, inheritedFormatting, node }) {
1079
+ if (node.type.name === "bookmarkBoundary") {
1080
+ const attrs = expectBookmarkBoundaryAttrs(node);
1081
+ if (attrs.type === "start") hyperlink.children.push({
1082
+ type: "bookmarkStart",
1083
+ id: attrs.id,
1084
+ name: attrs.name,
1085
+ ...attrs.colFirst !== void 0 ? { colFirst: attrs.colFirst } : {},
1086
+ ...attrs.colLast !== void 0 ? { colLast: attrs.colLast } : {}
1087
+ });
1088
+ else hyperlink.children.push({
1089
+ type: "bookmarkEnd",
1090
+ id: attrs.id
1091
+ });
1092
+ return;
1093
+ }
1008
1094
  const noteRefMark = node.marks.find((m) => m.type.name === "footnoteRef");
1009
1095
  if (noteRefMark) {
1010
1096
  hyperlink.children.push(createNoteReferenceRun(noteRefMark, node.marks));
@@ -1151,13 +1237,10 @@ function createTabRun(node, marks) {
1151
1237
  if (formatting) run.formatting = formatting;
1152
1238
  return run;
1153
1239
  }
1154
- /**
1155
- * Create a SimpleField or ComplexField from a PM field node
1156
- */
1157
- function createFieldFromNode(node, marks) {
1240
+ function createFieldFromNode(node, { marks, textBoxAnchorMarkers }) {
1158
1241
  const attrs = expectFieldAttrs(node);
1159
1242
  const formatting = marks && marks.length > 0 ? marksToTextFormatting(marks) : void 0;
1160
- let displayText = attrs.displayText || "";
1243
+ let displayText = attrs.displayText ?? "";
1161
1244
  if (!displayText) switch (attrs.fieldType) {
1162
1245
  case "PAGE":
1163
1246
  displayText = "1";
@@ -1177,13 +1260,15 @@ function createFieldFromNode(node, marks) {
1177
1260
  }],
1178
1261
  ...formatting && Object.keys(formatting).length > 0 ? { formatting } : {}
1179
1262
  };
1263
+ const extractedContent = extractParagraphContent(node, void 0, void 0, textBoxAnchorMarkers).filter((content) => content.type === "run" || content.type === "hyperlink");
1264
+ const fieldContent = extractedContent.length > 0 ? synchronizeFieldDisplayText(extractedContent, displayText, displayRun) : [];
1180
1265
  if (attrs.fieldKind === "complex") {
1181
1266
  const complex = {
1182
1267
  type: "complexField",
1183
1268
  instruction: attrs.instruction,
1184
1269
  fieldType: attrs.fieldType,
1185
1270
  fieldCode: [],
1186
- fieldResult: [displayRun]
1271
+ fieldResult: fieldContent.length > 0 ? fieldContent.filter((content) => content.type === "run") : [displayRun]
1187
1272
  };
1188
1273
  if (attrs.fldLock) complex.fldLock = true;
1189
1274
  if (attrs.dirty) complex.dirty = true;
@@ -1193,12 +1278,45 @@ function createFieldFromNode(node, marks) {
1193
1278
  type: "simpleField",
1194
1279
  instruction: attrs.instruction,
1195
1280
  fieldType: attrs.fieldType,
1196
- content: [displayRun]
1281
+ content: fieldContent.length > 0 ? fieldContent : [displayRun]
1197
1282
  };
1198
1283
  if (attrs.fldLock) simple.fldLock = true;
1199
1284
  if (attrs.dirty) simple.dirty = true;
1200
1285
  return simple;
1201
1286
  }
1287
+ const synchronizeFieldDisplayText = (content, displayText, fallbackRun) => {
1288
+ let currentText = "";
1289
+ const visitRuns = (visit) => {
1290
+ for (const child of content) {
1291
+ if (child.type === "run") {
1292
+ visit(child);
1293
+ continue;
1294
+ }
1295
+ for (const hyperlinkChild of child.children) if (hyperlinkChild.type === "run") visit(hyperlinkChild);
1296
+ }
1297
+ };
1298
+ visitRuns((run) => {
1299
+ for (const runContent of run.content) if (runContent.type === "text") currentText += runContent.text;
1300
+ });
1301
+ if (currentText === displayText) return content;
1302
+ let replacedText = false;
1303
+ visitRuns((run) => {
1304
+ for (const runContent of run.content) {
1305
+ if (runContent.type !== "text") continue;
1306
+ runContent.text = replacedText ? "" : displayText;
1307
+ replacedText = true;
1308
+ }
1309
+ });
1310
+ if (replacedText) return content;
1311
+ const hyperlink = content.find((child) => child.type === "hyperlink");
1312
+ if (!hyperlink) {
1313
+ content.push(fallbackRun);
1314
+ return content;
1315
+ }
1316
+ const firstEnd = hyperlink.children.findIndex((child) => child.type === "bookmarkEnd");
1317
+ hyperlink.children.splice(firstEnd < 0 ? hyperlink.children.length : firstEnd, 0, fallbackRun);
1318
+ return content;
1319
+ };
1202
1320
  /**
1203
1321
  * Create a MathEquation from a PM math node
1204
1322
  */