@stll/folio-core 0.56.0 → 0.57.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 (142) hide show
  1. package/dist/ai-edits/apply.js +45 -14
  2. package/dist/ai-edits/batch-claims.d.ts +3 -1
  3. package/dist/ai-edits/batch-claims.js +25 -1
  4. package/dist/ai-edits/table-row-column-mutations.js +17 -3
  5. package/dist/ai-suggestions/apply.js +20 -1
  6. package/dist/compare/inline-atoms.js +40 -16
  7. package/dist/compare/inline-provenance.js +12 -5
  8. package/dist/compat/eigenpal.d.ts +2 -1
  9. package/dist/compat/eigenpal.js +2 -1
  10. package/dist/controller/canonicalClipboard.js +7 -1
  11. package/dist/controller/canonicalComments.d.ts +41 -0
  12. package/dist/controller/canonicalComments.js +152 -0
  13. package/dist/controller/canonicalComposition.d.ts +4 -0
  14. package/dist/controller/canonicalComposition.js +43 -11
  15. package/dist/controller/canonicalInlineProjection.d.ts +6 -1
  16. package/dist/controller/canonicalInlineProjection.js +86 -3
  17. package/dist/controller/canonicalInput.js +10 -1
  18. package/dist/controller/canonicalInputTimer.d.ts +10 -1
  19. package/dist/controller/canonicalInputTimer.js +24 -1
  20. package/dist/controller/canonicalPublicOperations.d.ts +2 -2
  21. package/dist/controller/canonicalPublicOperations.js +107 -15
  22. package/dist/controller/canonicalSelection.d.ts +18 -0
  23. package/dist/controller/canonicalSelection.js +32 -0
  24. package/dist/controller/canonicalSession.d.ts +3 -0
  25. package/dist/controller/canonicalSession.js +58 -24
  26. package/dist/controller/canonicalStoryEditor.d.ts +2 -0
  27. package/dist/controller/canonicalStoryEditor.js +20 -0
  28. package/dist/controller/folioEditor.js +13 -0
  29. package/dist/controller/headerFooterEditorManager.d.ts +1 -1
  30. package/dist/controller/headerFooterEditorManager.js +25 -11
  31. package/dist/controller/hiddenEditorApi.d.ts +13 -2
  32. package/dist/controller/hiddenEditorApi.js +4 -0
  33. package/dist/controller/hiddenEditorManager.d.ts +3 -1
  34. package/dist/controller/hiddenEditorManager.js +166 -13
  35. package/dist/controller/noteEditorManager.d.ts +1 -1
  36. package/dist/controller/noteEditorManager.js +28 -13
  37. package/dist/docx/builtInStyles.js +13 -6
  38. package/dist/docx/containerChildren.gen.d.ts +3 -1
  39. package/dist/docx/containerChildren.gen.js +33 -1
  40. package/dist/docx/fieldParser.js +14 -2
  41. package/dist/docx/hyperlinkParser.d.ts +1 -1
  42. package/dist/docx/hyperlinkParser.js +20 -1
  43. package/dist/docx/inlineWrapperContent.d.ts +4 -4
  44. package/dist/docx/inlineWrapperContent.js +4 -4
  45. package/dist/docx/linkedSdtPreservation.d.ts +10 -0
  46. package/dist/docx/linkedSdtPreservation.js +37 -0
  47. package/dist/docx/paragraphParser.js +88 -67
  48. package/dist/docx/paragraphPropertySource.d.ts +22 -1
  49. package/dist/docx/paragraphPropertySource.js +107 -1
  50. package/dist/docx/preservedRunContent.js +2 -1
  51. package/dist/docx/relsParser.js +26 -2
  52. package/dist/docx/rezip.js +9 -6
  53. package/dist/docx/selectiveSave.js +10 -8
  54. package/dist/docx/serializer/commentSerializer.d.ts +4 -1
  55. package/dist/docx/serializer/commentSerializer.js +7 -3
  56. package/dist/docx/serializer/paragraphSerializer.js +207 -94
  57. package/dist/docx/server/materializeYjsDocx.js +14 -6
  58. package/dist/docx/server/migrateYjsAttrSchema.js +17 -5
  59. package/dist/i18n/messages/ar.gen.js +4 -0
  60. package/dist/i18n/messages/catalogs.gen.d.ts +68 -0
  61. package/dist/i18n/messages/catalogs.gen.js +68 -0
  62. package/dist/i18n/messages/cs.gen.js +4 -0
  63. package/dist/i18n/messages/de.gen.js +4 -0
  64. package/dist/i18n/messages/en.gen.js +4 -0
  65. package/dist/i18n/messages/es.gen.js +4 -0
  66. package/dist/i18n/messages/et.gen.js +4 -0
  67. package/dist/i18n/messages/fr.gen.js +4 -0
  68. package/dist/i18n/messages/he.gen.js +4 -0
  69. package/dist/i18n/messages/hi.gen.js +4 -0
  70. package/dist/i18n/messages/hu.gen.js +4 -0
  71. package/dist/i18n/messages/lt.gen.js +4 -0
  72. package/dist/i18n/messages/lv.gen.js +4 -0
  73. package/dist/i18n/messages/messages.gen.d.ts +4 -0
  74. package/dist/i18n/messages/pl.gen.js +4 -0
  75. package/dist/i18n/messages/pt-BR.gen.js +4 -0
  76. package/dist/i18n/messages/sk.gen.js +4 -0
  77. package/dist/i18n/messages/tr.gen.js +4 -0
  78. package/dist/i18n/messages/zh-CN.gen.js +4 -0
  79. package/dist/index.d.ts +2 -1
  80. package/dist/index.js +2 -1
  81. package/dist/internal/revisionResolutionInline.js +25 -3
  82. package/dist/markdown/renderRuns.js +1 -11
  83. package/dist/panel-layout.d.ts +11 -9
  84. package/dist/panel-layout.js +13 -9
  85. package/dist/prosemirror/addTrackedDeletionMark.d.ts +18 -0
  86. package/dist/prosemirror/addTrackedDeletionMark.js +87 -0
  87. package/dist/prosemirror/attrs/index.js +5 -0
  88. package/dist/prosemirror/commands/comments.js +60 -35
  89. package/dist/prosemirror/commands/propertyChangeScope.js +3 -6
  90. package/dist/prosemirror/commands/resolveParagraphProperties.js +22 -13
  91. package/dist/prosemirror/commentOps.js +15 -1
  92. package/dist/prosemirror/conversion/fromProseDoc.js +22 -3
  93. package/dist/prosemirror/conversion/toProseDoc.js +119 -18
  94. package/dist/prosemirror/extensions/core/ParagraphExtension.js +22 -5
  95. package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +74 -25
  96. package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +11 -0
  97. package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +22 -14
  98. package/dist/prosemirror/extensions/marks/markUtils.js +8 -7
  99. package/dist/prosemirror/extensions/marks/noteReferenceDeletion.js +5 -2
  100. package/dist/prosemirror/fieldRepresentation.d.ts +8 -0
  101. package/dist/prosemirror/fieldRepresentation.js +18 -0
  102. package/dist/prosemirror/hyperlinkRemoval.d.ts +13 -1
  103. package/dist/prosemirror/hyperlinkRemoval.js +28 -9
  104. package/dist/prosemirror/listNumbering.js +11 -5
  105. package/dist/prosemirror/noteReferenceOccurrenceId.d.ts +5 -0
  106. package/dist/prosemirror/noteReferenceOccurrenceId.js +5 -0
  107. package/dist/prosemirror/noteReferenceOccurrences.d.ts +21 -0
  108. package/dist/prosemirror/noteReferenceOccurrences.js +185 -0
  109. package/dist/prosemirror/noteReferenceReview.js +14 -1
  110. package/dist/prosemirror/plugins/documentStyles.d.ts +4 -1
  111. package/dist/prosemirror/plugins/documentStyles.js +17 -10
  112. package/dist/prosemirror/plugins/suggestionMode.js +47 -49
  113. package/dist/prosemirror/preservedXmlReview.d.ts +6 -0
  114. package/dist/prosemirror/preservedXmlReview.js +35 -0
  115. package/dist/prosemirror/runFormattingFromMarks.js +2 -2
  116. package/dist/prosemirror/runFormattingReconciliation.js +5 -4
  117. package/dist/prosemirror/runPropertyChangeResolution.d.ts +8 -1
  118. package/dist/prosemirror/runPropertyChangeResolution.js +24 -2
  119. package/dist/prosemirror/runStyleFormatting.d.ts +3 -1
  120. package/dist/prosemirror/runStyleFormatting.js +3 -1
  121. package/dist/prosemirror/schema/marks.d.ts +1 -0
  122. package/dist/prosemirror/schema/nodes.d.ts +2 -0
  123. package/dist/prosemirror/styles/paragraphStyleCascade.js +4 -1
  124. package/dist/prosemirror/styles/resolvedStyleAttrs.d.ts +15 -15
  125. package/dist/prosemirror/styles/resolvedStyleAttrs.js +69 -28
  126. package/dist/prosemirror/textInput.d.ts +5 -1
  127. package/dist/prosemirror/textInput.js +3 -3
  128. package/dist/prosemirror/trackedRevisionPath.d.ts +5 -1
  129. package/dist/prosemirror/trackedRevisionPath.js +39 -1
  130. package/dist/prosemirror/validation.js +3 -5
  131. package/dist/prosemirror/yjsDocumentMetadata.d.ts +9 -3
  132. package/dist/prosemirror/yjsDocumentMetadata.js +37 -8
  133. package/dist/types/canonicalCapabilities.d.ts +6 -20
  134. package/dist/types/canonicalCapabilities.js +6 -20
  135. package/dist/types/canonicalComments.d.ts +51 -0
  136. package/dist/types/canonicalComments.js +0 -0
  137. package/dist/types/canonicalSections.d.ts +12 -0
  138. package/dist/types/canonicalSections.js +0 -0
  139. package/dist/utils/headingCollector.js +45 -4
  140. package/dist/utils/outlineDepth.d.ts +9 -0
  141. package/dist/utils/outlineDepth.js +9 -0
  142. package/package.json +2 -2
@@ -19,7 +19,7 @@ const dispatchEditorTextInput = (view, input) => {
19
19
  };
20
20
  if (!view.someProp("handleTextInput", (handler) => handler(view, from, to, text, defaultTransaction))) view.dispatch(defaultTransaction());
21
21
  };
22
- const replacementSelection = (view, event) => {
22
+ const getEditorInputTargetRange = (view, event) => {
23
23
  if (typeof event.getTargetRanges !== "function") return null;
24
24
  const ranges = event.getTargetRanges();
25
25
  if (ranges.length !== 1) return null;
@@ -39,7 +39,7 @@ const handleEditorBeforeInput = (view, event) => {
39
39
  if (event.inputType !== "insertText" && event.inputType !== "insertReplacementText") return false;
40
40
  const text = event.data ?? (event.dataTransfer?.types.includes("text/plain") ? event.dataTransfer.getData("text/plain") : null);
41
41
  if (text === null) return false;
42
- const selection = event.inputType === "insertReplacementText" ? replacementSelection(view, event) : view.state.selection;
42
+ const selection = event.inputType === "insertReplacementText" ? getEditorInputTargetRange(view, event) : view.state.selection;
43
43
  if (!selection) return false;
44
44
  event.preventDefault();
45
45
  dispatchEditorTextInput(view, {
@@ -78,4 +78,4 @@ const createTextInputPlugin = () => {
78
78
  } } });
79
79
  };
80
80
  //#endregion
81
- export { afterNativeCompositionFlush, createTextInputPlugin, dispatchEditorTextInput, handleEditorBeforeInput };
81
+ export { afterNativeCompositionFlush, createTextInputPlugin, dispatchEditorTextInput, getEditorInputTargetRange, handleEditorBeforeInput };
@@ -13,5 +13,9 @@ type EnclosingInsertionAncestorsOptions = {
13
13
  };
14
14
  /** The deletion's ancestor path, with the insertion it sits in as the outermost layer. */
15
15
  declare const enclosingInsertionAncestors: ({ insertionMark, ancestors, node }: EnclosingInsertionAncestorsOptions) => readonly TrackedRevisionAncestor[];
16
+ /** The authored revision layers already owned by a mark, outermost first. */
17
+ declare const trackedRevisionPathOf: (mark: Mark) => readonly TrackedRevisionAncestor[];
18
+ /** Promote a surviving existing layer; callers cannot supply a fresh identity. */
19
+ declare const retainedTrackedRevisionMark: (mark: Mark, keepLayer: (layer: TrackedRevisionAncestor) => boolean) => Mark | null;
16
20
  //#endregion
17
- export { enclosingInsertionAncestors, inlineWrapperStackOf, trackedRevisionLayerOf };
21
+ export { enclosingInsertionAncestors, inlineWrapperStackOf, retainedTrackedRevisionMark, trackedRevisionLayerOf, trackedRevisionPathOf };
@@ -1,5 +1,6 @@
1
1
  import { expectInlineWrapperMarkAttrs, expectTrackedChangeMarkAttrs } from "./attrs/index.js";
2
2
  import { INLINE_WRAPPER_MARK_NAME } from "./extensions/marks/InlineWrapperExtension.js";
3
+ import { panic } from "better-result";
3
4
  //#region src/prosemirror/trackedRevisionPath.ts
4
5
  /** The wrappers `node` sits inside, outermost first; empty when it sits in none. */
5
6
  const inlineWrapperStackOf = (node) => {
@@ -29,5 +30,42 @@ const enclosingInsertionAncestors = ({ insertionMark, ancestors, node }) => {
29
30
  if (ancestors.some(({ revisionId }) => revisionId === attrs.revisionId)) return ancestors;
30
31
  return [trackedRevisionLayerOf(insertionMark, node), ...ancestors];
31
32
  };
33
+ const revisionLayerOf = (mark) => {
34
+ const attrs = expectTrackedChangeMarkAttrs(mark);
35
+ let type;
36
+ if (mark.type.name === "insertion") type = attrs.moveKind === "moveTo" ? "moveTo" : "insertion";
37
+ else type = attrs.moveKind === "moveFrom" ? "moveFrom" : "deletion";
38
+ return {
39
+ type,
40
+ revisionId: attrs.revisionId,
41
+ author: attrs.author,
42
+ ...attrs.date ? { date: attrs.date } : {},
43
+ ...attrs.utcDate ? { utcDate: attrs.utcDate } : {},
44
+ ...attrs.initials ? { initials: attrs.initials } : {},
45
+ outerWrapperCount: attrs._docxOuterWrapperCount ?? 0
46
+ };
47
+ };
48
+ /** The authored revision layers already owned by a mark, outermost first. */
49
+ const trackedRevisionPathOf = (mark) => [...expectTrackedChangeMarkAttrs(mark)._docxRevisionAncestors ?? [], revisionLayerOf(mark)];
50
+ /** Promote a surviving existing layer; callers cannot supply a fresh identity. */
51
+ const retainedTrackedRevisionMark = (mark, keepLayer) => {
52
+ const remaining = trackedRevisionPathOf(mark).filter(keepLayer);
53
+ const active = remaining.at(-1);
54
+ if (!active) return null;
55
+ const name = active.type === "insertion" || active.type === "moveTo" ? "insertion" : "deletion";
56
+ const type = mark.type.schema.marks[name];
57
+ if (!type) panic("A nested tracked revision has no editor mark type");
58
+ return type.create({
59
+ revisionId: active.revisionId,
60
+ author: active.author,
61
+ date: active.date ?? null,
62
+ utcDate: active.utcDate ?? null,
63
+ initials: active.initials ?? null,
64
+ moveKind: active.type === "moveTo" || active.type === "moveFrom" ? active.type : null,
65
+ _historicalFormatting: active.type === "deletion" || active.type === "moveFrom" ? true : null,
66
+ _docxOuterWrapperCount: active.outerWrapperCount,
67
+ _docxRevisionAncestors: remaining.length > 1 ? remaining.slice(0, -1) : null
68
+ });
69
+ };
32
70
  //#endregion
33
- export { enclosingInsertionAncestors, inlineWrapperStackOf, trackedRevisionLayerOf };
71
+ export { enclosingInsertionAncestors, inlineWrapperStackOf, retainedTrackedRevisionMark, trackedRevisionLayerOf, trackedRevisionPathOf };
@@ -2,7 +2,7 @@ import { readNoteMarkerAttrs } from "../internal/noteMarkerAttrs.js";
2
2
  import { readBlockCustomXmlAttrs, readBlockSdtAttrs, readCharacterSpacingMarkAttrs, readCharacterStyleMarkAttrs, readCommentMarkAttrs, readEmphasisMarkAttrs, readFieldAttrs, readFontFamilyMarkAttrs, readFontSizeMarkAttrs, readFootnoteRefMarkAttrs, readHardBreakAttrs, readHighlightMarkAttrs, readHyperlinkMarkAttrs, readImageAttrs, readInlineWrapperMarkAttrs, readLanguageMarkAttrs, readMathAttrs, readPageBreakRunAttrs, readParagraphAttrs, readPreservedBlockAttrs, readPreservedXmlAttrs, readRunFormattingOverrideMarkAttrs, readRunIdentityMarkAttrs, readRunPropertyChangeMarkAttrs, readRunShadingMarkAttrs, readSdtAttrs, readShapeAttrs, readStrikeMarkAttrs, readSymbolAttrs, readTabAttrs, readTableAttrs, readTableCellAttrs, readTableRowAttrs, readTextBoxAttrs, readTextColorMarkAttrs, readTextEffectMarkAttrs, readTrackedChangeMarkAttrs, readUnderlineMarkAttrs } from "./attrs/index.js";
3
3
  import { readBookmarkBoundaryAttrs } from "./bookmarkBoundaryAttrs.js";
4
4
  import { readCommentReferenceAttrs } from "./commentReferenceAttrs.js";
5
- import { INLINE_WRAPPER_MARK_NAME } from "./extensions/marks/InlineWrapperExtension.js";
5
+ import { fieldRequiresStructuredContent } from "./fieldRepresentation.js";
6
6
  import { readMoveRangeBoundaryAttrs } from "./moveRangeBoundaryAttrs.js";
7
7
  import { readRangeAnchorAttrs } from "./rangeAnchorAttrs.js";
8
8
  import { readTextBoxAnchorAttrs } from "./textBoxAnchorAttrs.js";
@@ -249,8 +249,6 @@ const validateNodeAttrs = (node, path, issues) => {
249
249
  if (fieldAttrs.ok) {
250
250
  const hasStructuredHyperlink = node.content.content.some((child) => child.marks.some((mark) => mark.type.name === "hyperlink"));
251
251
  const hasPageBreakCarrier = node.content.content.some((child) => child.type.name === "pageBreakRun");
252
- const hasPreservedCapture = node.content.content.some((child) => child.type.name === "preservedXml");
253
- const hasInlineWrapper = node.content.content.some((child) => child.marks.some((mark) => mark.type.name === INLINE_WRAPPER_MARK_NAME));
254
252
  if (fieldAttrs.value.fieldKind === "complex" && !hasPageBreakCarrier) issues.push({
255
253
  path: `${path}.content`,
256
254
  message: "Complex fields cannot contain structured result children."
@@ -259,9 +257,9 @@ const validateNodeAttrs = (node, path, issues) => {
259
257
  path: `${path}.content`,
260
258
  message: "Complex field results cannot contain hyperlink content."
261
259
  });
262
- else if (!hasStructuredHyperlink && !hasPageBreakCarrier && !hasPreservedCapture && !hasInlineWrapper) issues.push({
260
+ else if (!fieldRequiresStructuredContent(node.content.content)) issues.push({
263
261
  path: `${path}.content`,
264
- message: "Structured simple fields require hyperlink, page-break, preserved or wrapper content."
262
+ message: "Structured simple fields require non-text, hyperlink, wrapper or tracked content."
265
263
  });
266
264
  node.forEach((child, _offset, index) => {
267
265
  const childPath = `${path}.content[${index}]`;
@@ -8,14 +8,20 @@ import * as Y from "yjs";
8
8
  */
9
9
  type AttrSchemaMigrationStep = (fragment: Y.XmlFragment) => number;
10
10
  /** Every attr-schema version this build reads, oldest first, with no gaps. */
11
- declare const FOLIO_YJS_ATTR_SCHEMA_VERSIONS: readonly [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
11
+ declare const FOLIO_YJS_ATTR_SCHEMA_VERSIONS: readonly [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
12
12
  /** An attr-schema version this build can read. */
13
13
  type FolioYjsAttrSchemaVersion = (typeof FOLIO_YJS_ATTR_SCHEMA_VERSIONS)[number];
14
14
  /**
15
15
  * The attr-schema version this build writes. Derived against the migration map
16
16
  * so the constant and the map cannot disagree.
17
17
  */
18
- declare const FOLIO_YJS_ATTR_SCHEMA_VERSION = 11;
18
+ declare const FOLIO_YJS_ATTR_SCHEMA_VERSION = 12;
19
+ declare const FolioYjsNoteReferenceSchemaError_base: import("better-result").TaggedErrorClass<"FolioYjsNoteReferenceSchemaError">;
20
+ declare class FolioYjsNoteReferenceSchemaError extends FolioYjsNoteReferenceSchemaError_base<{
21
+ message: string;
22
+ schemaVersion: FolioYjsAttrSchemaVersion;
23
+ requiredSchemaVersion: FolioYjsAttrSchemaVersion;
24
+ }> {}
19
25
  /**
20
26
  * The steps that carry a snapshot written under `fromVersion` up to
21
27
  * {@link FOLIO_YJS_ATTR_SCHEMA_VERSION}, in order. Empty when the snapshot is
@@ -70,4 +76,4 @@ declare const writeYjsDocumentMetadata: (ydoc: Y.Doc, document: Node) => void;
70
76
  declare const readYjsParagraphSourceContract: (ydoc: Y.Doc) => string | null;
71
77
  declare const withParagraphSourceContract: (document: Node, contract: string) => Node;
72
78
  //#endregion
73
- export { FOLIO_YJS_ATTR_SCHEMA_VERSION, FolioYjsAttrSchemaVersion, FolioYjsAttrSchemaVersionError, applyAttrSchemaMigrations, attrSchemaMigrationSteps, proseDocumentParagraphSourceContract, readYjsAttrSchemaVersion, readYjsParagraphSourceContract, withParagraphSourceContract, writeYjsAttrSchemaVersion, writeYjsDocumentMetadata };
79
+ export { FOLIO_YJS_ATTR_SCHEMA_VERSION, FolioYjsAttrSchemaVersion, FolioYjsAttrSchemaVersionError, FolioYjsNoteReferenceSchemaError, applyAttrSchemaMigrations, attrSchemaMigrationSteps, proseDocumentParagraphSourceContract, readYjsAttrSchemaVersion, readYjsParagraphSourceContract, withParagraphSourceContract, writeYjsAttrSchemaVersion, writeYjsDocumentMetadata };
@@ -1,4 +1,5 @@
1
1
  import { PROSE_PARAGRAPH_SOURCE_CONTRACT_ATTR, getProseDocumentParagraphPropertySourceContract } from "../docx/paragraphPropertySource.js";
2
+ import { isNoteReferenceOccurrenceId } from "./noteReferenceOccurrenceId.js";
2
3
  import { mintSectionProperties, parseSectionBreakType } from "./sectionCarrier.js";
3
4
  import { Result, TaggedError, panic } from "better-result";
4
5
  import { DRAWING_RAW_XML_MODES, outlineLevelFromStatedValue, paragraphNumberingFromSlots } from "@stll/docx-core/model";
@@ -473,7 +474,8 @@ const FOLIO_YJS_ATTR_SCHEMA_VERSIONS = [
473
474
  8,
474
475
  9,
475
476
  10,
476
- 11
477
+ 11,
478
+ 12
477
479
  ];
478
480
  /**
479
481
  * The one place a persisted attr-shape change is decided.
@@ -497,13 +499,39 @@ const ATTR_SCHEMA_MIGRATIONS = {
497
499
  8: mintSectionPropertiesFromBreakType,
498
500
  9: dropUnstatedPlaceholderFlags,
499
501
  10: renamePageBreakRunOwnerMarkAttr,
500
- 11: "current"
502
+ 11: stampMarkerOnly,
503
+ 12: "current"
501
504
  };
502
505
  /**
503
506
  * The attr-schema version this build writes. Derived against the migration map
504
507
  * so the constant and the map cannot disagree.
505
508
  */
506
- const FOLIO_YJS_ATTR_SCHEMA_VERSION = 11;
509
+ const FOLIO_YJS_ATTR_SCHEMA_VERSION = 12;
510
+ var FolioYjsNoteReferenceSchemaError = class extends TaggedError("FolioYjsNoteReferenceSchemaError") {};
511
+ /** Check before any migration mutates the snapshot or any PM builder can drop text. */
512
+ const requireNoteOccurrenceAttribution = (fragment, schemaVersion) => {
513
+ const visit = (node) => {
514
+ for (const child of node.toArray()) {
515
+ if (typeof child === "string") continue;
516
+ if (!isXmlText(child)) {
517
+ if ("toArray" in child) visit(child);
518
+ continue;
519
+ }
520
+ for (const op of child.toDelta()) {
521
+ const attributes = op.attributes;
522
+ if (!isRecord(attributes) || attributes["footnoteRef"] == null) continue;
523
+ const reference = attributes["footnoteRef"];
524
+ if (isRecord(reference) && isNoteReferenceOccurrenceId(reference["occurrenceId"])) continue;
525
+ throw new FolioYjsNoteReferenceSchemaError({
526
+ schemaVersion,
527
+ requiredSchemaVersion: 12,
528
+ message: `Yjs schema ${schemaVersion} has unattributed note references; schema 12 requires occurrence identities. Rebuild the collaboration state from the saved DOCX.`
529
+ });
530
+ }
531
+ }
532
+ };
533
+ visit(fragment);
534
+ };
507
535
  /**
508
536
  * The steps that carry a snapshot written under `fromVersion` up to
509
537
  * {@link FOLIO_YJS_ATTR_SCHEMA_VERSION}, in order. Empty when the snapshot is
@@ -528,7 +556,8 @@ const attrSchemaMigrationSteps = (fromVersion) => FOLIO_YJS_ATTR_SCHEMA_VERSIONS
528
556
  * Returns how many elements the steps rewrote.
529
557
  */
530
558
  const applyAttrSchemaMigrations = (ydoc, fragment, fromVersion) => {
531
- if (fromVersion === 11) return 0;
559
+ requireNoteOccurrenceAttribution(fragment, fromVersion);
560
+ if (fromVersion === 12) return 0;
532
561
  let rewritten = 0;
533
562
  ydoc.transact(() => {
534
563
  for (const step of attrSchemaMigrationSteps(fromVersion)) rewritten += step(fragment);
@@ -538,7 +567,7 @@ const applyAttrSchemaMigrations = (ydoc, fragment, fromVersion) => {
538
567
  };
539
568
  /** Raised when a snapshot's attr-schema marker is ahead of the running code. */
540
569
  var FolioYjsAttrSchemaVersionError = class extends TaggedError("FolioYjsAttrSchemaVersionError") {};
541
- const isReadableVersion = (marker) => typeof marker === "number" && Number.isInteger(marker) && marker >= 0 && marker <= 11;
570
+ const isReadableVersion = (marker) => typeof marker === "number" && Number.isInteger(marker) && marker >= 0 && marker <= 12;
542
571
  /**
543
572
  * The attr-schema version a collaboration document was written under.
544
573
  *
@@ -556,7 +585,7 @@ const readYjsAttrSchemaVersion = (ydoc) => {
556
585
  return Result.err(new FolioYjsAttrSchemaVersionError({
557
586
  marker,
558
587
  message: "The collaboration snapshot was written by a newer Folio attr schema than this build reads.",
559
- supportedVersion: 11
588
+ supportedVersion: 12
560
589
  }));
561
590
  };
562
591
  const proseDocumentParagraphSourceContract = (document) => {
@@ -564,7 +593,7 @@ const proseDocumentParagraphSourceContract = (document) => {
564
593
  };
565
594
  /** Stamp the attr-schema version of the build that wrote the current fragment. */
566
595
  const writeYjsAttrSchemaVersion = (ydoc) => {
567
- ydoc.getMap(FOLIO_YJS_METADATA_MAP_NAME).set(ATTR_SCHEMA_VERSION_KEY, 11);
596
+ ydoc.getMap(FOLIO_YJS_METADATA_MAP_NAME).set(ATTR_SCHEMA_VERSION_KEY, 12);
568
597
  };
569
598
  /**
570
599
  * Write both metadata bindings for a freshly seeded collaboration document.
@@ -589,4 +618,4 @@ const withParagraphSourceContract = (document, contract) => {
589
618
  }, document.content, document.marks);
590
619
  };
591
620
  //#endregion
592
- export { FOLIO_YJS_ATTR_SCHEMA_VERSION, FolioYjsAttrSchemaVersionError, applyAttrSchemaMigrations, attrSchemaMigrationSteps, proseDocumentParagraphSourceContract, readYjsAttrSchemaVersion, readYjsParagraphSourceContract, withParagraphSourceContract, writeYjsAttrSchemaVersion, writeYjsDocumentMetadata };
621
+ export { FOLIO_YJS_ATTR_SCHEMA_VERSION, FolioYjsAttrSchemaVersionError, FolioYjsNoteReferenceSchemaError, applyAttrSchemaMigrations, attrSchemaMigrationSteps, proseDocumentParagraphSourceContract, readYjsAttrSchemaVersion, readYjsParagraphSourceContract, withParagraphSourceContract, writeYjsAttrSchemaVersion, writeYjsDocumentMetadata };
@@ -3,13 +3,11 @@
3
3
  declare const CANONICAL_GAP: {
4
4
  readonly authorityRouting: "authority-routing";
5
5
  readonly commands: "command-descriptors";
6
- readonly suggesting: "adapter-suggesting";
7
6
  readonly trackedHyperlinkResolution: "tracked-hyperlink-resolution";
8
7
  readonly comments: "comment-model-edits";
9
8
  readonly modelEdits: "direct-model-edits";
10
9
  readonly sectionProperties: "section-properties";
11
10
  readonly watermark: "watermark-model-edits";
12
- readonly secondaryStories: "secondary-story-routing";
13
11
  readonly collaboration: "collaboration-session";
14
12
  readonly dispatch: "unclassified-transactions";
15
13
  readonly save: "pm-save-projection";
@@ -52,17 +50,11 @@ declare const CANONICAL_CAPABILITIES: {
52
50
  readonly adapters: readonly ["react", "vue"];
53
51
  readonly summary: "Hyperlink and TOC suggestions require serializable wrapper review provenance.";
54
52
  };
55
- readonly "adapter-suggesting": {
56
- readonly owner: "adapters";
57
- readonly kind: "refusal";
58
- readonly adapters: readonly ["react", "vue"];
59
- readonly summary: "Adapter suggesting controls and tracked secondary-story edits remain gated.";
60
- };
61
53
  readonly "comment-model-edits": {
62
54
  readonly owner: "adapters";
63
- readonly kind: "refusal";
55
+ readonly kind: "routing";
64
56
  readonly adapters: readonly ["react", "vue"];
65
- readonly summary: "Comment mutations still use direct model changes.";
57
+ readonly summary: "Default sessions retain comment model edits; canonical comments use semantic operations.";
66
58
  };
67
59
  readonly "direct-model-edits": {
68
60
  readonly owner: "adapters";
@@ -71,10 +63,10 @@ declare const CANONICAL_CAPABILITIES: {
71
63
  readonly summary: "Direct model and pending-suggestion snapshots bypass the canonical journal.";
72
64
  };
73
65
  readonly "section-properties": {
74
- readonly owner: "adapters";
66
+ readonly owner: "controller";
75
67
  readonly kind: "routing";
76
68
  readonly adapters: readonly ["react", "vue"];
77
- readonly summary: "Section properties select canonical operations or legacy model changes.";
69
+ readonly summary: "The shared controller journals section properties; default sessions keep model changes.";
78
70
  };
79
71
  readonly "watermark-model-edits": {
80
72
  readonly owner: "adapters";
@@ -82,12 +74,6 @@ declare const CANONICAL_CAPABILITIES: {
82
74
  readonly adapters: readonly ["vue"];
83
75
  readonly summary: "Vue watermark mutations still change the model directly.";
84
76
  };
85
- readonly "secondary-story-routing": {
86
- readonly owner: "controller";
87
- readonly kind: "routing";
88
- readonly adapters: readonly ["react", "vue"];
89
- readonly summary: "Secondary-story creation, views and history still select their session authority.";
90
- };
91
77
  readonly "collaboration-session": {
92
78
  readonly owner: "controller";
93
79
  readonly kind: "refusal";
@@ -152,9 +138,9 @@ declare const CANONICAL_CAPABILITIES: {
152
138
  readonly "publicOps.comments": {
153
139
  readonly owner: "document-operations";
154
140
  readonly defaultSessionMutation: "pm-public-operations";
155
- readonly kind: "refusal";
141
+ readonly kind: "routing";
156
142
  readonly adapters: readonly ["react", "vue"];
157
- readonly summary: "Public comment operations refuse in canonical sessions; legacy sessions retain PM comments.";
143
+ readonly summary: "Canonical public comments compile to semantic operations; default sessions retain PM comments.";
158
144
  };
159
145
  readonly "publicOps.suggestedMode": {
160
146
  readonly owner: "document-operations";
@@ -3,13 +3,11 @@
3
3
  const CANONICAL_GAP = {
4
4
  authorityRouting: "authority-routing",
5
5
  commands: "command-descriptors",
6
- suggesting: "adapter-suggesting",
7
6
  trackedHyperlinkResolution: "tracked-hyperlink-resolution",
8
7
  comments: "comment-model-edits",
9
8
  modelEdits: "direct-model-edits",
10
9
  sectionProperties: "section-properties",
11
10
  watermark: "watermark-model-edits",
12
- secondaryStories: "secondary-story-routing",
13
11
  collaboration: "collaboration-session",
14
12
  dispatch: "unclassified-transactions",
15
13
  save: "pm-save-projection",
@@ -49,17 +47,11 @@ const CANONICAL_CAPABILITIES = {
49
47
  adapters: ["react", "vue"],
50
48
  summary: "Hyperlink and TOC suggestions require serializable wrapper review provenance."
51
49
  },
52
- [CANONICAL_GAP.suggesting]: {
53
- owner: "adapters",
54
- kind: "refusal",
55
- adapters: ["react", "vue"],
56
- summary: "Adapter suggesting controls and tracked secondary-story edits remain gated."
57
- },
58
50
  [CANONICAL_GAP.comments]: {
59
51
  owner: "adapters",
60
- kind: "refusal",
52
+ kind: "routing",
61
53
  adapters: ["react", "vue"],
62
- summary: "Comment mutations still use direct model changes."
54
+ summary: "Default sessions retain comment model edits; canonical comments use semantic operations."
63
55
  },
64
56
  [CANONICAL_GAP.modelEdits]: {
65
57
  owner: "adapters",
@@ -68,10 +60,10 @@ const CANONICAL_CAPABILITIES = {
68
60
  summary: "Direct model and pending-suggestion snapshots bypass the canonical journal."
69
61
  },
70
62
  [CANONICAL_GAP.sectionProperties]: {
71
- owner: "adapters",
63
+ owner: "controller",
72
64
  kind: "routing",
73
65
  adapters: ["react", "vue"],
74
- summary: "Section properties select canonical operations or legacy model changes."
66
+ summary: "The shared controller journals section properties; default sessions keep model changes."
75
67
  },
76
68
  [CANONICAL_GAP.watermark]: {
77
69
  owner: "adapters",
@@ -79,12 +71,6 @@ const CANONICAL_CAPABILITIES = {
79
71
  adapters: ["vue"],
80
72
  summary: "Vue watermark mutations still change the model directly."
81
73
  },
82
- [CANONICAL_GAP.secondaryStories]: {
83
- owner: "controller",
84
- kind: "routing",
85
- adapters: ["react", "vue"],
86
- summary: "Secondary-story creation, views and history still select their session authority."
87
- },
88
74
  [CANONICAL_GAP.collaboration]: {
89
75
  owner: "controller",
90
76
  kind: "refusal",
@@ -149,9 +135,9 @@ const CANONICAL_CAPABILITIES = {
149
135
  [CANONICAL_GAP.publicComments]: {
150
136
  owner: "document-operations",
151
137
  defaultSessionMutation: "pm-public-operations",
152
- kind: "refusal",
138
+ kind: "routing",
153
139
  adapters: ["react", "vue"],
154
- summary: "Public comment operations refuse in canonical sessions; legacy sessions retain PM comments."
140
+ summary: "Canonical public comments compile to semantic operations; default sessions retain PM comments."
155
141
  },
156
142
  [CANONICAL_GAP.publicSuggestedMode]: {
157
143
  owner: "document-operations",
@@ -0,0 +1,51 @@
1
+ import { CanonicalGap } from "./canonicalCapabilities.js";
2
+ import { Comment } from "./content.js";
3
+ import { OpStory } from "@stll/docx-core/ops";
4
+ //#region src/types/canonicalComments.d.ts
5
+ /** Requests name their anchor; comment metadata never creates an unanchored root. */
6
+ type CanonicalCommentRequest = {
7
+ type: "create";
8
+ text: string;
9
+ author: string;
10
+ date?: string;
11
+ anchor: {
12
+ kind: "selection";
13
+ from: number;
14
+ to: number;
15
+ story: OpStory;
16
+ } | {
17
+ kind: "reply";
18
+ parentId: number;
19
+ } | {
20
+ kind: "revision";
21
+ story: OpStory;
22
+ revisionId: number;
23
+ };
24
+ } | {
25
+ type: "update";
26
+ id: number;
27
+ content: Comment["content"];
28
+ } | {
29
+ type: "resolve";
30
+ id: number;
31
+ status: "open" | "resolved";
32
+ } | {
33
+ type: "delete";
34
+ id: number;
35
+ } | {
36
+ type: "replace";
37
+ comments: readonly Comment[];
38
+ };
39
+ type CanonicalCommentResult = {
40
+ status: "applied";
41
+ comments: Comment[];
42
+ commentId?: number;
43
+ } | {
44
+ status: "refused";
45
+ gap: CanonicalGap;
46
+ message: string;
47
+ /** Whether the same request can succeed once a pending composition settles. */
48
+ retry: "afterComposition" | "never";
49
+ };
50
+ //#endregion
51
+ export { CanonicalCommentRequest, CanonicalCommentResult };
File without changes
@@ -0,0 +1,12 @@
1
+ import { CANONICAL_GAP } from "./canonicalCapabilities.js";
2
+ //#region src/types/canonicalSections.d.ts
3
+ type CanonicalSectionPropertiesResult = {
4
+ status: "applied";
5
+ version: number;
6
+ } | {
7
+ status: "refused";
8
+ gap: typeof CANONICAL_GAP.sectionProperties;
9
+ message: string;
10
+ };
11
+ //#endregion
12
+ export { CanonicalSectionPropertiesResult };
File without changes
@@ -1,6 +1,50 @@
1
1
  import { resolveHeadingLevel } from "../docx/builtInStyles.js";
2
2
  import { readOutlineLevelAttr } from "../prosemirror/outlineLevelAttr.js";
3
3
  //#region src/utils/headingCollector.ts
4
+ const RUN_IN_TITLE_MARKS = /* @__PURE__ */ new Set([
5
+ "bold",
6
+ "italic",
7
+ "underline"
8
+ ]);
9
+ const runInTitleFormat = (child) => {
10
+ const marks = child.marks.map(({ type }) => type.name).filter((name) => RUN_IN_TITLE_MARKS.has(name)).sort();
11
+ return marks.length > 0 ? marks.join("+") : void 0;
12
+ };
13
+ const headingText = (paragraph) => {
14
+ let text = "";
15
+ let capture = { status: "searching" };
16
+ for (let childIndex = 0; childIndex < paragraph.childCount; childIndex += 1) {
17
+ const child = paragraph.child(childIndex);
18
+ if (!child.isText) {
19
+ if (capture.status === "collecting") capture = {
20
+ status: "complete",
21
+ text: capture.text
22
+ };
23
+ continue;
24
+ }
25
+ const childText = child.text || "";
26
+ text += childText;
27
+ const format = runInTitleFormat(child);
28
+ if (capture.status === "searching" && format !== void 0) {
29
+ capture = {
30
+ status: "collecting",
31
+ format,
32
+ text: childText
33
+ };
34
+ continue;
35
+ }
36
+ if (capture.status !== "collecting") continue;
37
+ capture = format === capture.format ? {
38
+ status: "collecting",
39
+ format: capture.format,
40
+ text: capture.text + childText
41
+ } : {
42
+ status: "complete",
43
+ text: capture.text
44
+ };
45
+ }
46
+ return ((capture.status === "searching" ? "" : capture.text.trim()) || text).trim();
47
+ };
4
48
  /**
5
49
  * Collect all headings from a ProseMirror document, in document order.
6
50
  *
@@ -19,10 +63,7 @@ function collectHeadings(doc, styles) {
19
63
  styleId: typeof node.attrs["styleId"] === "string" ? node.attrs["styleId"] : null
20
64
  }, styles);
21
65
  if (level === void 0) return;
22
- let text = "";
23
- node.forEach((child) => {
24
- if (child.isText) text += child.text || "";
25
- });
66
+ const text = headingText(node);
26
67
  if (text.trim()) headings.push({
27
68
  text: text.trim(),
28
69
  level,
@@ -0,0 +1,9 @@
1
+ import { HeadingInfo } from "./headingCollector.js";
2
+ //#region src/utils/outlineDepth.d.ts
3
+ /** Maximum heading depth displayed in the document outline. */
4
+ type OutlineDepth = 2 | 3 | "all";
5
+ declare const DEFAULT_OUTLINE_DEPTH = 2;
6
+ /** Filter headings for the outline without changing their document order. */
7
+ declare const filterHeadingsByDepth: (headings: readonly HeadingInfo[], depth: OutlineDepth) => HeadingInfo[];
8
+ //#endregion
9
+ export { DEFAULT_OUTLINE_DEPTH, OutlineDepth, filterHeadingsByDepth };
@@ -0,0 +1,9 @@
1
+ //#region src/utils/outlineDepth.ts
2
+ const DEFAULT_OUTLINE_DEPTH = 2;
3
+ /** Filter headings for the outline without changing their document order. */
4
+ const filterHeadingsByDepth = (headings, depth) => {
5
+ if (depth === "all") return [...headings];
6
+ return headings.filter((heading) => heading.level < depth);
7
+ };
8
+ //#endregion
9
+ export { DEFAULT_OUTLINE_DEPTH, filterHeadingsByDepth };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stll/folio-core",
3
- "version": "0.56.0",
3
+ "version": "0.57.0",
4
4
  "description": "Headless, framework-neutral core of folio: the OOXML (.docx) parser, document model, ProseMirror integration, and page-layout engine. No React.",
5
5
  "keywords": [
6
6
  "document-model",
@@ -146,7 +146,7 @@
146
146
  "perf": "bun scripts/profile-editor.ts"
147
147
  },
148
148
  "dependencies": {
149
- "@stll/docx-core": "^0.32.0",
149
+ "@stll/docx-core": "^0.33.0",
150
150
  "@stll/docx-utils": "^0.1.0",
151
151
  "@stll/template-conditions": "^0.6.2",
152
152
  "better-result": "3.0.1",