@stll/folio-core 0.56.0 → 0.58.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 (158) hide show
  1. package/dist/ai-edits/apply.js +122 -22
  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.d.ts +3 -1
  11. package/dist/controller/canonicalClipboard.js +31 -13
  12. package/dist/controller/canonicalComments.d.ts +41 -0
  13. package/dist/controller/canonicalComments.js +152 -0
  14. package/dist/controller/canonicalComposition.d.ts +17 -4
  15. package/dist/controller/canonicalComposition.js +125 -20
  16. package/dist/controller/canonicalInlineProjection.d.ts +6 -1
  17. package/dist/controller/canonicalInlineProjection.js +86 -3
  18. package/dist/controller/canonicalInput.d.ts +1 -1
  19. package/dist/controller/canonicalInput.js +14 -4
  20. package/dist/controller/canonicalInputTimer.d.ts +10 -1
  21. package/dist/controller/canonicalInputTimer.js +24 -1
  22. package/dist/controller/canonicalPublicOperations.d.ts +2 -2
  23. package/dist/controller/canonicalPublicOperations.js +107 -15
  24. package/dist/controller/canonicalSelection.d.ts +18 -0
  25. package/dist/controller/canonicalSelection.js +41 -0
  26. package/dist/controller/canonicalSession.d.ts +4 -1
  27. package/dist/controller/canonicalSession.js +106 -36
  28. package/dist/controller/canonicalStoryEditor.d.ts +3 -1
  29. package/dist/controller/canonicalStoryEditor.js +21 -1
  30. package/dist/controller/canonicalStructure.js +8 -3
  31. package/dist/controller/folioEditor.js +13 -0
  32. package/dist/controller/headerFooterEditorManager.d.ts +1 -1
  33. package/dist/controller/headerFooterEditorManager.js +25 -11
  34. package/dist/controller/hiddenEditorApi.d.ts +13 -2
  35. package/dist/controller/hiddenEditorApi.js +5 -1
  36. package/dist/controller/hiddenEditorManager.d.ts +3 -1
  37. package/dist/controller/hiddenEditorManager.js +167 -14
  38. package/dist/controller/noteEditorManager.d.ts +1 -1
  39. package/dist/controller/noteEditorManager.js +28 -13
  40. package/dist/docx/builtInStyles.js +13 -6
  41. package/dist/docx/containerChildren.gen.d.ts +3 -1
  42. package/dist/docx/containerChildren.gen.js +33 -1
  43. package/dist/docx/fieldParser.js +14 -2
  44. package/dist/docx/hyperlinkParser.d.ts +1 -1
  45. package/dist/docx/hyperlinkParser.js +20 -1
  46. package/dist/docx/inlineWrapperContent.d.ts +4 -4
  47. package/dist/docx/inlineWrapperContent.js +4 -4
  48. package/dist/docx/linkedSdtPreservation.d.ts +10 -0
  49. package/dist/docx/linkedSdtPreservation.js +37 -0
  50. package/dist/docx/listRendering.d.ts +18 -0
  51. package/dist/{prosemirror/conversion/listRenderingDefinition.js → docx/listRendering.js} +17 -3
  52. package/dist/docx/paragraphParser.js +88 -67
  53. package/dist/docx/paragraphPropertySource.d.ts +22 -1
  54. package/dist/docx/paragraphPropertySource.js +107 -1
  55. package/dist/docx/preservedRunContent.js +2 -1
  56. package/dist/docx/relsParser.js +26 -2
  57. package/dist/docx/rezip.js +9 -6
  58. package/dist/docx/selectiveSave.js +10 -8
  59. package/dist/docx/serializer/commentSerializer.d.ts +4 -1
  60. package/dist/docx/serializer/commentSerializer.js +7 -3
  61. package/dist/docx/serializer/paragraphSerializer.js +207 -94
  62. package/dist/docx/server/materializeYjsDocx.js +15 -7
  63. package/dist/docx/server/migrateYjsAttrSchema.js +17 -5
  64. package/dist/i18n/messages/ar.gen.js +4 -0
  65. package/dist/i18n/messages/catalogs.gen.d.ts +68 -0
  66. package/dist/i18n/messages/catalogs.gen.js +68 -0
  67. package/dist/i18n/messages/cs.gen.js +4 -0
  68. package/dist/i18n/messages/de.gen.js +4 -0
  69. package/dist/i18n/messages/en.gen.js +4 -0
  70. package/dist/i18n/messages/es.gen.js +4 -0
  71. package/dist/i18n/messages/et.gen.js +4 -0
  72. package/dist/i18n/messages/fr.gen.js +4 -0
  73. package/dist/i18n/messages/he.gen.js +4 -0
  74. package/dist/i18n/messages/hi.gen.js +4 -0
  75. package/dist/i18n/messages/hu.gen.js +4 -0
  76. package/dist/i18n/messages/lt.gen.js +4 -0
  77. package/dist/i18n/messages/lv.gen.js +4 -0
  78. package/dist/i18n/messages/messages.gen.d.ts +4 -0
  79. package/dist/i18n/messages/pl.gen.js +4 -0
  80. package/dist/i18n/messages/pt-BR.gen.js +4 -0
  81. package/dist/i18n/messages/sk.gen.js +4 -0
  82. package/dist/i18n/messages/tr.gen.js +4 -0
  83. package/dist/i18n/messages/zh-CN.gen.js +4 -0
  84. package/dist/index.d.ts +2 -1
  85. package/dist/index.js +2 -1
  86. package/dist/internal/revisionResolutionInline.js +25 -3
  87. package/dist/markdown/internals.js +7 -0
  88. package/dist/markdown/renderParagraph.js +11 -12
  89. package/dist/markdown/renderRuns.js +3 -13
  90. package/dist/markdown/types.d.ts +3 -0
  91. package/dist/panel-layout.d.ts +11 -9
  92. package/dist/panel-layout.js +13 -9
  93. package/dist/prosemirror/addTrackedDeletionMark.d.ts +18 -0
  94. package/dist/prosemirror/addTrackedDeletionMark.js +87 -0
  95. package/dist/prosemirror/attrs/index.js +9 -0
  96. package/dist/prosemirror/commands/comments.js +60 -35
  97. package/dist/prosemirror/commands/propertyChangeScope.js +3 -6
  98. package/dist/prosemirror/commands/resolveParagraphProperties.js +22 -13
  99. package/dist/prosemirror/commentOps.js +15 -1
  100. package/dist/prosemirror/conversion/fromProseDoc.d.ts +8 -1
  101. package/dist/prosemirror/conversion/fromProseDoc.js +150 -30
  102. package/dist/prosemirror/conversion/toProseDoc.js +128 -40
  103. package/dist/prosemirror/extensions/core/ParagraphExtension.js +22 -5
  104. package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +74 -25
  105. package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +11 -0
  106. package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +22 -14
  107. package/dist/prosemirror/extensions/marks/markUtils.js +8 -7
  108. package/dist/prosemirror/extensions/marks/noteReferenceDeletion.js +5 -2
  109. package/dist/prosemirror/extensions/nodes/ImageExtension.js +8 -1
  110. package/dist/prosemirror/fieldRepresentation.d.ts +8 -0
  111. package/dist/prosemirror/fieldRepresentation.js +18 -0
  112. package/dist/prosemirror/hyperlinkRemoval.d.ts +13 -1
  113. package/dist/prosemirror/hyperlinkRemoval.js +28 -9
  114. package/dist/prosemirror/imagePictureNames.d.ts +12 -0
  115. package/dist/prosemirror/imagePictureNames.js +20 -0
  116. package/dist/prosemirror/listNumbering.js +11 -5
  117. package/dist/prosemirror/noteReferenceOccurrenceId.d.ts +5 -0
  118. package/dist/prosemirror/noteReferenceOccurrenceId.js +5 -0
  119. package/dist/prosemirror/noteReferenceOccurrences.d.ts +21 -0
  120. package/dist/prosemirror/noteReferenceOccurrences.js +185 -0
  121. package/dist/prosemirror/noteReferenceReview.js +14 -1
  122. package/dist/prosemirror/paragraphFormattingProvenance.js +1 -0
  123. package/dist/prosemirror/plugins/documentStyles.d.ts +4 -1
  124. package/dist/prosemirror/plugins/documentStyles.js +17 -10
  125. package/dist/prosemirror/plugins/suggestionMode.js +47 -49
  126. package/dist/prosemirror/preservedXmlReview.d.ts +6 -0
  127. package/dist/prosemirror/preservedXmlReview.js +35 -0
  128. package/dist/prosemirror/runFormattingFromMarks.js +2 -2
  129. package/dist/prosemirror/runFormattingReconciliation.js +5 -4
  130. package/dist/prosemirror/runPropertyChangeResolution.d.ts +8 -1
  131. package/dist/prosemirror/runPropertyChangeResolution.js +24 -2
  132. package/dist/prosemirror/runStyleFormatting.d.ts +74 -2
  133. package/dist/prosemirror/runStyleFormatting.js +20 -1
  134. package/dist/prosemirror/schema/marks.d.ts +1 -0
  135. package/dist/prosemirror/schema/nodes.d.ts +3 -0
  136. package/dist/prosemirror/styles/paragraphStyleCascade.js +4 -1
  137. package/dist/prosemirror/styles/resolvedStyleAttrs.d.ts +15 -15
  138. package/dist/prosemirror/styles/resolvedStyleAttrs.js +69 -28
  139. package/dist/prosemirror/styles/styleResolver.d.ts +4 -1
  140. package/dist/prosemirror/styles/styleResolver.js +32 -11
  141. package/dist/prosemirror/textInput.d.ts +5 -1
  142. package/dist/prosemirror/textInput.js +3 -3
  143. package/dist/prosemirror/trackedRevisionPath.d.ts +5 -1
  144. package/dist/prosemirror/trackedRevisionPath.js +39 -1
  145. package/dist/prosemirror/validation.js +3 -5
  146. package/dist/prosemirror/yjsDocumentMetadata.d.ts +9 -3
  147. package/dist/prosemirror/yjsDocumentMetadata.js +37 -8
  148. package/dist/types/canonicalCapabilities.d.ts +18 -18
  149. package/dist/types/canonicalCapabilities.js +18 -18
  150. package/dist/types/canonicalComments.d.ts +51 -0
  151. package/dist/types/canonicalComments.js +0 -0
  152. package/dist/types/canonicalSections.d.ts +12 -0
  153. package/dist/types/canonicalSections.js +0 -0
  154. package/dist/utils/headingCollector.js +45 -4
  155. package/dist/utils/outlineDepth.d.ts +9 -0
  156. package/dist/utils/outlineDepth.js +9 -0
  157. package/package.json +2 -2
  158. package/dist/prosemirror/conversion/listRenderingDefinition.d.ts +0 -6
@@ -1,4 +1,5 @@
1
1
  import { splitsGraphemeCluster, splitsSurrogatePair } from "../ai-edits/character-boundaries.js";
2
+ import { rangedCommentIds, visitCommentMarkers } from "../docx/commentAnchorIndex.js";
2
3
  import { cloneDocumentWithParagraphPropertySources, copyDocumentParagraphPropertySourceContract, copyParagraphPropertySource } from "../docx/paragraphPropertySource.js";
3
4
  import { canonicalSelectionRange } from "../prosemirror/canonicalSelectionRange.js";
4
5
  import { collectPairedBookmarkIds, footnoteToProseDoc, headerFooterToProseDoc, toProseDoc } from "../prosemirror/conversion/toProseDoc.js";
@@ -6,12 +7,13 @@ import { markPackageChange } from "../prosemirror/extensions/features/ParagraphC
6
7
  import { runFormattingPatchFromMarks } from "../prosemirror/runFormattingFromMarks.js";
7
8
  import { CANONICAL_GAP } from "../types/canonicalCapabilities.js";
8
9
  import { canonicalInlineSourceIssue, canonicalNativeCells, projectCanonicalInline } from "./canonicalInlineProjection.js";
10
+ import { restoreCanonicalSelection } from "./canonicalSelection.js";
9
11
  import { mapTocSelection } from "./canonicalTocSelection.js";
10
12
  import { Result, TaggedError, panic } from "better-result";
11
13
  import { hasIllegalXmlCharacters } from "@stll/docx-core";
12
14
  import { Fragment } from "prosemirror-model";
13
- import { DOCUMENT_OP_TYPES, OP_STORIES, applyDocumentOps, combineEdits, compileEditorIntent, createEditorIntentIdAllocator, documentStories, editorParagraphGroups, findStoryBody, normalizeForOps, packageParagraphIds, sameStory, validateOpsDocument } from "@stll/docx-core/ops";
14
- import { AllSelection, TextSelection } from "prosemirror-state";
15
+ import { DOCUMENT_OP_REFUSAL_REASONS, DOCUMENT_OP_TYPES, OP_STORIES, applyDocumentOps, combineEdits, commentDocumentIssue, compileEditorIntent, createEditorIntentIdAllocator, documentStories, editorParagraphGroups, findStoryBody, normalizeForOps, packageParagraphIds, sameStory, validateOpsDocument } from "@stll/docx-core/ops";
16
+ import { AllSelection, NodeSelection, TextSelection } from "prosemirror-state";
15
17
  //#region src/controller/canonicalSession.ts
16
18
  var CanonicalSessionError = class extends TaggedError("CanonicalSessionError") {};
17
19
  const refuse = (message) => Result.err(new CanonicalSessionError({
@@ -24,8 +26,20 @@ const noChange = (message) => Result.err(new CanonicalSessionError({
24
26
  message,
25
27
  reason: "noChange"
26
28
  }));
29
+ const COMMENT_OPERATION_TYPES = /* @__PURE__ */ new Set([
30
+ DOCUMENT_OP_TYPES.CREATE_COMMENT,
31
+ DOCUMENT_OP_TYPES.UPDATE_COMMENT_CONTENT,
32
+ DOCUMENT_OP_TYPES.SET_COMMENT_RESOLUTION,
33
+ DOCUMENT_OP_TYPES.DELETE_COMMENT,
34
+ DOCUMENT_OP_TYPES.RESTORE_COMMENT_STATE
35
+ ]);
27
36
  const operationChangesPackage = (op) => {
28
37
  switch (op.type) {
38
+ case DOCUMENT_OP_TYPES.CREATE_COMMENT:
39
+ case DOCUMENT_OP_TYPES.UPDATE_COMMENT_CONTENT:
40
+ case DOCUMENT_OP_TYPES.SET_COMMENT_RESOLUTION:
41
+ case DOCUMENT_OP_TYPES.DELETE_COMMENT:
42
+ case DOCUMENT_OP_TYPES.RESTORE_COMMENT_STATE:
29
43
  case DOCUMENT_OP_TYPES.CREATE_HEADER_FOOTER:
30
44
  case DOCUMENT_OP_TYPES.REMOVE_HEADER_FOOTER:
31
45
  case DOCUMENT_OP_TYPES.ADD_NOTE:
@@ -71,7 +85,7 @@ const operationChangesStructure = (op) => {
71
85
  case DOCUMENT_OP_TYPES.SET_PACKAGE_RESOURCES:
72
86
  case DOCUMENT_OP_TYPES.CREATE_NUMBERING_INSTANCE:
73
87
  case DOCUMENT_OP_TYPES.DELETE_NUMBERING_INSTANCE:
74
- case DOCUMENT_OP_TYPES.SET_SECTION_ENDPOINT:
88
+ case DOCUMENT_OP_TYPES.SET_SECTION_ENDPOINT: return true;
75
89
  case DOCUMENT_OP_TYPES.INSERT_TEXT:
76
90
  case DOCUMENT_OP_TYPES.INSERT_CONTENT:
77
91
  case DOCUMENT_OP_TYPES.DELETE_RANGE:
@@ -84,12 +98,22 @@ const operationChangesStructure = (op) => {
84
98
  case DOCUMENT_OP_TYPES.SET_TABLE_GRID:
85
99
  case DOCUMENT_OP_TYPES.SET_CELL_PROPS:
86
100
  case DOCUMENT_OP_TYPES.SET_ROW_PROPS:
87
- case DOCUMENT_OP_TYPES.SET_TABLE_PROPS: return false;
101
+ case DOCUMENT_OP_TYPES.SET_TABLE_PROPS:
102
+ case DOCUMENT_OP_TYPES.CREATE_COMMENT:
103
+ case DOCUMENT_OP_TYPES.UPDATE_COMMENT_CONTENT:
104
+ case DOCUMENT_OP_TYPES.SET_COMMENT_RESOLUTION:
105
+ case DOCUMENT_OP_TYPES.DELETE_COMMENT:
106
+ case DOCUMENT_OP_TYPES.RESTORE_COMMENT_STATE: return false;
88
107
  default: return op;
89
108
  }
90
109
  };
91
110
  const operationChangesBodyProjection = (op) => {
92
111
  switch (op.type) {
112
+ case DOCUMENT_OP_TYPES.CREATE_COMMENT:
113
+ case DOCUMENT_OP_TYPES.UPDATE_COMMENT_CONTENT:
114
+ case DOCUMENT_OP_TYPES.SET_COMMENT_RESOLUTION:
115
+ case DOCUMENT_OP_TYPES.DELETE_COMMENT:
116
+ case DOCUMENT_OP_TYPES.RESTORE_COMMENT_STATE:
93
117
  case DOCUMENT_OP_TYPES.CREATE_HEADER_FOOTER:
94
118
  case DOCUMENT_OP_TYPES.REMOVE_HEADER_FOOTER:
95
119
  case DOCUMENT_OP_TYPES.ADD_NOTE:
@@ -159,12 +183,21 @@ var CanonicalProjection = class {
159
183
  return Result.ok(paragraph.start + gap.position);
160
184
  }
161
185
  selectionAt(state) {
162
- if (!(state.selection instanceof TextSelection) && !(state.selection instanceof AllSelection)) return refuse("Canonical input requires a text selection.");
163
- const range = canonicalSelectionRange(state);
164
- const type = state.selection instanceof AllSelection ? "all" : "text";
165
- const anchor = this.addressAt(type === "all" ? range.from : state.selection.anchor);
186
+ const selection = state.selection;
187
+ let type;
188
+ let anchorPosition = selection.anchor;
189
+ let headPosition = selection.head;
190
+ if (selection instanceof TextSelection) type = "text";
191
+ else if (selection instanceof NodeSelection && selection.node.isInline && selection.node.isAtom && selection.node.type.name !== "noteMarker") type = "inlineNode";
192
+ else if (selection instanceof AllSelection) {
193
+ type = "all";
194
+ const range = canonicalSelectionRange(state);
195
+ anchorPosition = range.from;
196
+ headPosition = range.to;
197
+ } else return refuse("Canonical input requires text, an inline atom, or the whole document selection.");
198
+ const anchor = this.addressAt(anchorPosition);
166
199
  if (anchor.isErr()) return anchor;
167
- const head = this.addressAt(type === "all" ? range.to : state.selection.head);
200
+ const head = this.addressAt(headPosition);
168
201
  if (head.isErr()) return head;
169
202
  return Result.ok({
170
203
  type,
@@ -219,6 +252,8 @@ const preservePropertySources = ({ target, source, stories = documentStories(sou
219
252
  copyDocumentParagraphPropertySourceContract(target, source);
220
253
  };
221
254
  const unsupportedSeedReason = (document, stories = documentStories(document)) => {
255
+ const commentIssue = commentDocumentIssue(document, "all");
256
+ if (commentIssue !== void 0) return commentIssue;
222
257
  for (const story of stories) {
223
258
  const body = findStoryBody(document, story);
224
259
  if (story !== OP_STORIES.MAIN && (story.kind === "footnote" || story.kind === "endnote")) {
@@ -257,9 +292,18 @@ const project = ({ document, styles, story = OP_STORIES.MAIN }) => {
257
292
  });
258
293
  if (converted.isErr()) return converted;
259
294
  const pairedBookmarkIds = collectPairedBookmarkIds(body.content);
295
+ const commentContext = {
296
+ rangedIds: rangedCommentIds(body.content),
297
+ pointIds: /* @__PURE__ */ new Set(),
298
+ openIds: /* @__PURE__ */ new Set()
299
+ };
300
+ visitCommentMarkers(body.content, ({ item }) => {
301
+ if (item.type === "commentReference" && !commentContext.rangedIds.has(item.id)) commentContext.pointIds.add(item.id);
302
+ });
260
303
  const paragraphs = [];
261
304
  let failure;
262
305
  converted.value.forEach((node, offset, index) => {
306
+ if (failure !== void 0) return;
263
307
  const source = body.content.at(index);
264
308
  if (source?.type !== "paragraph" || source.paraId === void 0) {
265
309
  failure = new CanonicalSessionError({
@@ -271,7 +315,7 @@ const project = ({ document, styles, story = OP_STORIES.MAIN }) => {
271
315
  }
272
316
  if (node.type.name !== "paragraph" || node.attrs["paraId"] !== source.paraId) {
273
317
  failure = new CanonicalSessionError({
274
- gap: CANONICAL_GAP.dispatch,
318
+ gap: CANONICAL_GAP.storyContentProjection,
275
319
  reason: "refused",
276
320
  message: "The projection changed paragraph structure."
277
321
  });
@@ -280,7 +324,8 @@ const project = ({ document, styles, story = OP_STORIES.MAIN }) => {
280
324
  const inline = projectCanonicalInline({
281
325
  source,
282
326
  paragraph: node,
283
- pairedBookmarkIds
327
+ pairedBookmarkIds,
328
+ commentContext
284
329
  });
285
330
  if (inline.isErr()) {
286
331
  failure = new CanonicalSessionError({
@@ -297,6 +342,10 @@ const project = ({ document, styles, story = OP_STORIES.MAIN }) => {
297
342
  node,
298
343
  source
299
344
  });
345
+ visitCommentMarkers([source], ({ item }) => {
346
+ if (item.type === "commentRangeStart") commentContext.openIds.add(item.id);
347
+ if (item.type === "commentRangeEnd") commentContext.openIds.delete(item.id);
348
+ });
300
349
  });
301
350
  if (failure !== void 0) return Result.err(failure);
302
351
  if (paragraphs.length !== body.content.length) return refuse("The projection changed paragraph structure.");
@@ -405,6 +454,10 @@ var CanonicalSession = class {
405
454
  this.styles = styles == null ? styles : structuredClone(styles);
406
455
  this.rememberSources(document);
407
456
  }
457
+ /** Render only the committed comment projection while provisional IME blocks document snapshots. */
458
+ getCommittedComments() {
459
+ return structuredClone(this.currentDocument.package.document.comments ?? []);
460
+ }
408
461
  setMode(mode) {
409
462
  if (this.mode.type !== mode.type || this.mode.type === "suggesting" && mode.type === "suggesting" && this.mode.author !== mode.author) this.breakUndoGroup();
410
463
  this.mode = mode.type === "editing" ? { type: "editing" } : {
@@ -552,9 +605,13 @@ var CanonicalSession = class {
552
605
  if (!Number.isFinite(time)) return refuse("The input time is invalid.");
553
606
  if (from > to || hasIllegalXmlCharacters(text) || /[\t\r\n]/u.test(text)) return refuse("Canonical input accepts a well-formed text replacement.");
554
607
  if (from === to && text.length === 0) return noChange("The input makes no text change.");
555
- const start = projection.inputAddressAt(from);
608
+ const replacesAll = state.selection instanceof AllSelection && from === state.selection.from && to === state.selection.to;
609
+ const range = canonicalSelectionRange(state);
610
+ const inputFrom = replacesAll ? range.from : from;
611
+ const inputTo = replacesAll ? range.to : to;
612
+ const start = projection.inputAddressAt(inputFrom);
556
613
  if (start.isErr()) return start;
557
- const end = projection.inputAddressAt(to);
614
+ const end = projection.inputAddressAt(inputTo);
558
615
  if (end.isErr()) return end;
559
616
  const preSelection = projection.selectionAt(state);
560
617
  if (preSelection.isErr()) return preSelection;
@@ -652,7 +709,14 @@ var CanonicalSession = class {
652
709
  mode: this.intentMode(document, intent),
653
710
  firstBlockId: this.nextBlockId
654
711
  });
655
- if (compiled.isErr()) return refuse(compiled.error.message);
712
+ if (compiled.isErr()) {
713
+ if (compiled.error.reason === DOCUMENT_OP_REFUSAL_REASONS.UNTRACKABLE && (intent.type === "setHyperlink" || intent.type === "removeHyperlink" || intent.type === "insertHyperlink")) return Result.err(new CanonicalSessionError({
714
+ gap: CANONICAL_GAP.trackedHyperlinkResolution,
715
+ reason: "refused",
716
+ message: compiled.error.message
717
+ }));
718
+ return refuse(compiled.error.message);
719
+ }
656
720
  const applied = applyDocumentOps(document, compiled.value.ops);
657
721
  if (applied.isErr()) return refuse(applied.error.message);
658
722
  document = applied.value.document;
@@ -938,8 +1002,11 @@ var CanonicalSession = class {
938
1002
  document: applied.value.document,
939
1003
  previous: this.currentDocument
940
1004
  });
941
- const unsupported = unsupportedSeedReason(applied.value.document, changed);
942
- if (unsupported !== null) return refuse(unsupported);
1005
+ if (unsupportedSeedReason(applied.value.document, changed) !== null) return Result.err(new CanonicalSessionError({
1006
+ gap: CANONICAL_GAP.storyContentProjection,
1007
+ reason: "refused",
1008
+ message: "The operations produce unsupported canonical story content."
1009
+ }));
943
1010
  preservePropertySources({
944
1011
  target: applied.value.document,
945
1012
  source: this.currentDocument,
@@ -994,25 +1061,16 @@ var CanonicalSession = class {
994
1061
  transaction.replaceWith(from, to, Fragment.fromArray(replacement));
995
1062
  }
996
1063
  if (!transaction.doc.eq(projected.value.doc)) transaction.replaceWith(0, transaction.doc.content.size, projected.value.doc.content);
997
- if (sameStory(selection.anchor.story, story) && sameStory(selection.head.story, story)) {
998
- const anchor = projected.value.positionAt(selection.anchor);
999
- const head = projected.value.positionAt(selection.head);
1000
- switch (selection.type) {
1001
- case "all":
1002
- transaction.setSelection(new AllSelection(transaction.doc));
1003
- break;
1004
- case "text":
1005
- if (anchor.isOk() && head.isOk()) {
1006
- transaction.setSelection(TextSelection.create(transaction.doc, anchor.value, head.value));
1007
- break;
1008
- }
1009
- transaction.setSelection(TextSelection.near(transaction.doc.resolve(Math.min(state.selection.anchor, transaction.doc.content.size))));
1010
- break;
1011
- default: {
1012
- const exhaustive = selection.type;
1013
- return panic(`Unknown canonical selection type: ${exhaustive}`);
1064
+ if (sameStory(selection.anchor.story, projectedStory) && sameStory(selection.head.story, projectedStory)) {
1065
+ if (!restoreCanonicalSelection({
1066
+ transaction,
1067
+ projection: projected.value,
1068
+ selection,
1069
+ unavailable: {
1070
+ type: "near",
1071
+ anchor: state.selection.anchor
1014
1072
  }
1015
- }
1073
+ })) return refuse("Canonical history cannot restore the selected inline atom.");
1016
1074
  }
1017
1075
  transaction.setMeta(CANONICAL_PROJECTION_META, {
1018
1076
  type: "canonical",
@@ -1033,6 +1091,7 @@ var CanonicalSession = class {
1033
1091
  document: applied.value.document,
1034
1092
  projection: projected.value,
1035
1093
  bodyProjection: bodyProjection.value,
1094
+ selection,
1036
1095
  touched: applied.value.touched,
1037
1096
  version: baseVersion + 1,
1038
1097
  origin,
@@ -1061,7 +1120,8 @@ var CanonicalSession = class {
1061
1120
  applied.value.touched.inserted,
1062
1121
  applied.value.touched.removed
1063
1122
  ]) for (const id of ids) this.saveTouched.add(id);
1064
- if (applied.value.touched.inserted.length > 0 || applied.value.touched.removed.length > 0 || ops.some(operationChangesPackage) || ops.some(operationChangesStructure)) this.saveStructure = "changed";
1123
+ const bodyOps = ops.filter((op) => !COMMENT_OPERATION_TYPES.has(op.type));
1124
+ if (bodyOps.some(operationChangesStructure) || bodyOps.some(operationChangesPackage) || bodyOps.length > 0 && (applied.value.touched.inserted.length > 0 || applied.value.touched.removed.length > 0)) this.saveStructure = "changed";
1065
1125
  this.currentVersion = baseVersion + 1;
1066
1126
  onPublish(applied.value.inverse, this.currentVersion);
1067
1127
  return Result.ok(void 0);
@@ -1070,6 +1130,11 @@ var CanonicalSession = class {
1070
1130
  }
1071
1131
  };
1072
1132
  const createCanonicalSession = (document, styles) => {
1133
+ if (documentStories(document).some((story) => findStoryBody(document, story)?.content.some((block) => block.type === "table"))) return Result.err(new CanonicalSessionError({
1134
+ gap: CANONICAL_GAP.tableActivation,
1135
+ reason: "refused",
1136
+ message: "Canonical sessions cannot activate documents containing tables."
1137
+ }));
1073
1138
  const unsupported = unsupportedSeedReason(document);
1074
1139
  if (unsupported !== null) return refuse(unsupported);
1075
1140
  const owned = cloneDocumentWithParagraphPropertySources(document);
@@ -1158,7 +1223,12 @@ const isCanonicalJoinBoundary = (state, direction) => {
1158
1223
  /** Native character deletion consumes one whole visible grapheme. */
1159
1224
  const deletionRange = (state, direction) => {
1160
1225
  const { selection } = state;
1161
- if (!(selection instanceof TextSelection)) return refuse("Canonical deletion requires a text selection.");
1226
+ if (selection instanceof NodeSelection && selection.node.isInline && selection.node.isAtom && selection.node.type.name !== "noteMarker") return Result.ok({
1227
+ from: selection.from,
1228
+ to: selection.to
1229
+ });
1230
+ if (selection instanceof AllSelection) return Result.ok(canonicalSelectionRange(state));
1231
+ if (!(selection instanceof TextSelection)) return refuse("Canonical deletion requires text, an inline atom, or the whole document selection.");
1162
1232
  if (!selection.empty) return Result.ok({
1163
1233
  from: selection.from,
1164
1234
  to: selection.to
@@ -20,10 +20,12 @@ declare const createCanonicalStoryEditor: ({ story, getView, getApi, enabled, on
20
20
  handleTextInput: (view: EditorView, from: number, to: number, text: string) => boolean;
21
21
  handleDOMEvents: {
22
22
  keydown: (view: EditorView, event: KeyboardEvent) => boolean;
23
+ keyup: (view: EditorView, event: KeyboardEvent) => boolean;
24
+ mouseup: (view: EditorView) => boolean;
23
25
  beforeinput: (view: EditorView, event: InputEvent) => boolean;
24
26
  mousedown: (view: EditorView) => boolean;
25
27
  compositionstart: (view: EditorView) => boolean;
26
- compositionend: (view: EditorView) => boolean;
28
+ compositionend: (view: EditorView, event: Event) => boolean;
27
29
  input: (view: EditorView) => boolean;
28
30
  blur: (view: EditorView) => boolean;
29
31
  paste: (view: EditorView, event: ClipboardEvent) => boolean;
@@ -1,6 +1,7 @@
1
1
  import { CANONICAL_GAP } from "../types/canonicalCapabilities.js";
2
2
  import { createCanonicalInputBoundary } from "./canonicalInput.js";
3
3
  import { CanonicalSessionError } from "./canonicalSession.js";
4
+ import { AllSelection, TextSelection } from "prosemirror-state";
4
5
  //#region src/controller/canonicalStoryEditor.ts
5
6
  /** Secondary views compile classified native input into the shared Document journal. */
6
7
  const createCanonicalStoryEditor = ({ story, getView, getApi, enabled, onRefusal, onSelectionChange }) => {
@@ -24,6 +25,17 @@ const createCanonicalStoryEditor = ({ story, getView, getApi, enabled, onRefusal
24
25
  if (accepted && !view.isDestroyed) onSelectionChange();
25
26
  return accepted;
26
27
  };
28
+ const syncNativeSelection = (view) => {
29
+ if (boundary.isComposing || view.composing) return;
30
+ const selection = view.dom.ownerDocument.getSelection();
31
+ if (!selection?.anchorNode || !selection.focusNode || !view.dom.contains(selection.anchorNode) || !view.dom.contains(selection.focusNode)) return;
32
+ const anchor = view.posAtDOM(selection.anchorNode, selection.anchorOffset);
33
+ const head = view.posAtDOM(selection.focusNode, selection.focusOffset);
34
+ if (view.state.selection instanceof AllSelection && Math.min(anchor, head) === 0 && Math.max(anchor, head) === view.state.doc.content.size) return;
35
+ if (anchor === view.state.selection.anchor && head === view.state.selection.head) return;
36
+ if (!view.state.doc.resolve(anchor).parent.inlineContent || !view.state.doc.resolve(head).parent.inlineContent) return;
37
+ view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, anchor, head)));
38
+ };
27
39
  const boundary = createCanonicalInputBoundary({
28
40
  breakUndoGroup: () => {
29
41
  getApi()?.updateCanonicalInputLifecycle("breakUndoGroup");
@@ -70,10 +82,18 @@ const createCanonicalStoryEditor = ({ story, getView, getApi, enabled, onRefusal
70
82
  handleTextInput: (view, from, to, text) => enabled() && boundary.handleTextInput(view, from, to, text),
71
83
  handleDOMEvents: {
72
84
  keydown: (view, event) => enabled() && boundary.handleDOMEvents.keydown(view, event),
85
+ keyup: (view, event) => {
86
+ if (enabled() && /^(?:Arrow|Home$|End$|Page)/u.test(event.key)) syncNativeSelection(view);
87
+ return false;
88
+ },
89
+ mouseup: (view) => {
90
+ if (enabled()) syncNativeSelection(view);
91
+ return false;
92
+ },
73
93
  beforeinput: (view, event) => enabled() && boundary.handleDOMEvents.beforeinput(view, event),
74
94
  mousedown: (view) => enabled() && boundary.handleDOMEvents.mousedown(view),
75
95
  compositionstart: (view) => enabled() && boundary.handleDOMEvents.compositionstart(view),
76
- compositionend: (view) => enabled() && boundary.handleDOMEvents.compositionend(view),
96
+ compositionend: (view, event) => enabled() && boundary.handleDOMEvents.compositionend(view, event),
77
97
  input: (view) => enabled() && boundary.handleDOMEvents.input(view),
78
98
  blur: (view) => enabled() && boundary.handleDOMEvents.blur(view),
79
99
  paste: (view, event) => enabled() && boundary.handleDOMEvents.paste(view, event),
@@ -15,6 +15,11 @@ const refuse = (message) => Result.err(new CanonicalSessionError({
15
15
  message,
16
16
  reason: "refused"
17
17
  }));
18
+ const noChange = (message) => Result.err(new CanonicalSessionError({
19
+ gap: CANONICAL_GAP.commands,
20
+ message,
21
+ reason: "noChange"
22
+ }));
18
23
  const selectedParagraphs = (session, state) => {
19
24
  const ids = /* @__PURE__ */ new Set();
20
25
  state.doc.nodesBetween(state.selection.from, state.selection.to, (node) => {
@@ -218,7 +223,7 @@ const prepareCanonicalCommands = (session, state, commands) => {
218
223
  offset: 0
219
224
  };
220
225
  const level = (numPr.ilvl ?? 0) + (command.direction === "increase" ? 1 : -1);
221
- if (level > 8) return refuse("The list is already at its deepest level.");
226
+ if (level > 8) return noChange("The list is already at its deepest level.");
222
227
  intents.push({
223
228
  type: "formatParagraph",
224
229
  at,
@@ -234,7 +239,7 @@ const prepareCanonicalCommands = (session, state, commands) => {
234
239
  const source = paragraphs.at(0);
235
240
  const numPr = source?.formatting?.numPr;
236
241
  const definitions = session.document.package.numbering;
237
- if (source === void 0 || numPr?.kind !== "reference" || definitions === void 0) return refuse("Numbering changes require a list paragraph.");
242
+ if (source === void 0 || numPr?.kind !== "reference" || definitions === void 0) return noChange("Numbering changes require a list paragraph.");
238
243
  const body = session.document.package.document.content;
239
244
  const index = body.findIndex((item) => item === source);
240
245
  const affected = body.slice(index).filter((item) => item.type === "paragraph" && item.formatting?.numPr?.kind === "reference" && item.formatting.numPr.numId === numPr.numId);
@@ -244,7 +249,7 @@ const prepareCanonicalCommands = (session, state, commands) => {
244
249
  const map = getCachedNumberingMap(definitions);
245
250
  const bullet = numberingLevelUsesBulletMarker(map.getLevel(numPr.numId, numPr.ilvl ?? 0));
246
251
  const earlier = body.slice(0, index).findLast((item) => item.type === "paragraph" && item.formatting?.numPr?.kind === "reference" && item.formatting.numPr.numId !== numPr.numId && numberingLevelUsesBulletMarker(map.getLevel(item.formatting.numPr.numId, item.formatting.numPr.ilvl ?? 0)) === bullet);
247
- if (earlier?.type !== "paragraph" || earlier.formatting?.numPr?.kind !== "reference") return refuse("There is no preceding compatible list.");
252
+ if (earlier?.type !== "paragraph" || earlier.formatting?.numPr?.kind !== "reference") return noChange("There is no preceding compatible list.");
248
253
  targetId = earlier.formatting.numPr.numId;
249
254
  target = {
250
255
  type: "existing",
@@ -1,3 +1,4 @@
1
+ import { CANONICAL_GAP } from "../types/canonicalCapabilities.js";
1
2
  import { FOLIO_DOCX_SERIALIZATION_MODE } from "../types/docxSerialization.js";
2
3
  import { panic } from "better-result";
3
4
  //#region src/controller/folioEditor.ts
@@ -14,6 +15,16 @@ const createFolioEditor = (deps) => {
14
15
  getDocument: () => deps.getEditorApi()?.getDocument() ?? null,
15
16
  updateCanonicalInputLifecycle: (action) => deps.getEditorApi()?.updateCanonicalInputLifecycle(action) ?? false,
16
17
  applyCanonicalStoryHistory: (options) => deps.getEditorApi()?.applyCanonicalStoryHistory(options) ?? false,
18
+ applyCanonicalComment: (request) => deps.getEditorApi()?.applyCanonicalComment(request) ?? null,
19
+ applyCanonicalSectionProperties: (patch) => {
20
+ const api = deps.getEditorApi();
21
+ if (!api) return {
22
+ status: "refused",
23
+ gap: CANONICAL_GAP.sectionProperties,
24
+ message: "The editor is not ready for section changes."
25
+ };
26
+ return api.applyCanonicalSectionProperties(patch);
27
+ },
17
28
  applyCanonicalOperations: (ops) => deps.getEditorApi()?.applyCanonicalOperations(ops) ?? false,
18
29
  getCanonicalStorySelection: (story) => deps.getEditorApi()?.getCanonicalStorySelection(story) ?? null,
19
30
  getCanonicalStoryProjection: (story) => deps.getEditorApi()?.getCanonicalStoryProjection(story) ?? null,
@@ -22,6 +33,8 @@ const createFolioEditor = (deps) => {
22
33
  captureCanonicalSave: () => deps.getEditorApi()?.captureCanonicalSave() ?? null,
23
34
  applyCanonicalDocumentOperations: (options) => deps.getEditorApi()?.applyCanonicalDocumentOperations(options) ?? null,
24
35
  undoCanonicalDocumentOperations: (handle) => deps.getEditorApi()?.undoCanonicalDocumentOperations(handle) ?? null,
36
+ getCanonicalComments: () => deps.getEditorApi()?.getCanonicalComments() ?? null,
37
+ getCanonicalCommittedVersion: () => deps.getEditorApi()?.getCanonicalCommittedVersion() ?? null,
25
38
  getCanonicalDocument: () => deps.getEditorApi()?.getCanonicalDocument() ?? null,
26
39
  setCanonicalMode: (mode) => deps.getEditorApi()?.setCanonicalMode(mode) ?? false,
27
40
  resolveCanonicalRevisions: (revisionIds, resolution) => deps.getEditorApi()?.resolveCanonicalRevisions(revisionIds, resolution) ?? false,
@@ -20,7 +20,7 @@ type HeaderFooterEditorManagerDeps = {
20
20
  getDocument: () => document_d_exports.Document | null;
21
21
  getCanonicalApi?: (() => HiddenEditorApi | null) | undefined;
22
22
  getExperimentalSession?: (() => "canonical" | undefined) | undefined;
23
- onSessionRefusal?: ((reason: string, gap: CanonicalGap) => void) | undefined;
23
+ onSessionRefusal?: ((reason: string, gap: CanonicalGap, error?: Error) => void) | undefined;
24
24
  getHost: () => HTMLElement | null;
25
25
  getStyles: () => document_d_exports.StyleDefinitions | null | undefined;
26
26
  getTheme: () => document_d_exports.Theme | null | undefined;
@@ -6,11 +6,13 @@ import { ExtensionManager } from "../prosemirror/extensions/ExtensionManager.js"
6
6
  import { createStarterKit } from "../prosemirror/extensions/StarterKit.js";
7
7
  import { ensureBaseDirectionInState } from "../prosemirror/extensions/features/AutoBidiDetectionExtension.js";
8
8
  import { ensureParaIdsInState } from "../prosemirror/extensions/features/ParaIdAllocatorExtension.js";
9
+ import { noteReferenceTransactionIssue } from "../prosemirror/noteReferenceOccurrences.js";
9
10
  import { createDocumentNumberingPlugin } from "../prosemirror/plugins/documentNumbering.js";
10
- import { createDocumentStylesPlugin } from "../prosemirror/plugins/documentStyles.js";
11
+ import { createDocumentStyleContextPlugin, createDocumentStylesPlugin } from "../prosemirror/plugins/documentStyles.js";
11
12
  import { schema } from "../prosemirror/schema/index.js";
12
13
  import { storyListNumbering } from "../prosemirror/storyListNumbering.js";
13
14
  import { CANONICAL_GAP, usesCanonicalSession } from "../types/canonicalCapabilities.js";
15
+ import { CanonicalSessionError } from "./canonicalSession.js";
14
16
  import { createCanonicalStoryEditor } from "./canonicalStoryEditor.js";
15
17
  import { OP_STORIES } from "@stll/docx-core/ops";
16
18
  import { EditorState, TextSelection } from "prosemirror-state";
@@ -85,11 +87,12 @@ const createHeaderFooterEditorManager = (deps) => {
85
87
  for (const part of mounted.values()) destroyMounted(part);
86
88
  mounted.clear();
87
89
  };
90
+ const getOwnedDocument = () => usesCanonicalSession(deps.getExperimentalSession?.(), CANONICAL_GAP.authorityRouting) ? deps.getCanonicalApi?.()?.getCanonicalDocument() ?? null : deps.getDocument();
88
91
  const sync = () => {
89
92
  const host = deps.getHost();
90
93
  if (!host) return;
91
- if (usesCanonicalSession(deps.getExperimentalSession?.(), CANONICAL_GAP.secondaryStories) && !deps.getCanonicalApi?.()?.getCanonicalStoryProjection(OP_STORIES.MAIN)) return;
92
- const document = usesCanonicalSession(deps.getExperimentalSession?.(), CANONICAL_GAP.secondaryStories) ? deps.getCanonicalApi?.()?.getCanonicalDocument() ?? deps.getDocument() : deps.getDocument();
94
+ if (usesCanonicalSession(deps.getExperimentalSession?.(), CANONICAL_GAP.authorityRouting) && !deps.getCanonicalApi?.()?.getCanonicalStoryProjection(OP_STORIES.MAIN)) return;
95
+ const document = getOwnedDocument();
93
96
  const styles = deps.getStyles();
94
97
  const theme = deps.getTheme();
95
98
  const numbering = document?.package.numbering;
@@ -105,7 +108,7 @@ const createHeaderFooterEditorManager = (deps) => {
105
108
  const existing = mounted.get(part.rId);
106
109
  if (existing) {
107
110
  if (existing.mountNode.parentElement !== host) host.append(existing.mountNode);
108
- if (usesCanonicalSession(deps.getExperimentalSession?.(), CANONICAL_GAP.secondaryStories)) {
111
+ if (usesCanonicalSession(deps.getExperimentalSession?.(), CANONICAL_GAP.authorityRouting)) {
109
112
  const projected = deps.getCanonicalApi?.()?.getCanonicalStoryProjection({
110
113
  kind: part.kind,
111
114
  rId: part.rId
@@ -155,7 +158,7 @@ const createHeaderFooterEditorManager = (deps) => {
155
158
  },
156
159
  getView: () => view,
157
160
  getApi: () => deps.getCanonicalApi?.() ?? null,
158
- enabled: () => usesCanonicalSession(deps.getExperimentalSession?.(), CANONICAL_GAP.secondaryStories),
161
+ enabled: () => usesCanonicalSession(deps.getExperimentalSession?.(), CANONICAL_GAP.authorityRouting),
159
162
  onRefusal: deps.onSessionRefusal,
160
163
  onSelectionChange: () => deps.onTransaction?.({
161
164
  kind: part.kind,
@@ -165,11 +168,11 @@ const createHeaderFooterEditorManager = (deps) => {
165
168
  selectionChanged: true
166
169
  })
167
170
  });
168
- const canonicalProjection = usesCanonicalSession(deps.getExperimentalSession?.(), CANONICAL_GAP.secondaryStories) ? deps.getCanonicalApi?.()?.getCanonicalStoryProjection({
171
+ const canonicalProjection = usesCanonicalSession(deps.getExperimentalSession?.(), CANONICAL_GAP.authorityRouting) ? deps.getCanonicalApi?.()?.getCanonicalStoryProjection({
169
172
  kind: part.kind,
170
173
  rId: part.rId
171
174
  }) : null;
172
- if (usesCanonicalSession(deps.getExperimentalSession?.(), CANONICAL_GAP.secondaryStories) && !canonicalProjection) {
175
+ if (usesCanonicalSession(deps.getExperimentalSession?.(), CANONICAL_GAP.authorityRouting) && !canonicalProjection) {
173
176
  manager.destroy();
174
177
  mountNode.remove();
175
178
  continue;
@@ -178,16 +181,19 @@ const createHeaderFooterEditorManager = (deps) => {
178
181
  ...canonical.props,
179
182
  state: canonicalProjection ? EditorState.create({
180
183
  doc: canonicalProjection,
181
- plugins: [createDocumentStylesPlugin(styles), createDocumentNumberingPlugin(numbering)]
184
+ plugins: [createDocumentStyleContextPlugin(styles), createDocumentNumberingPlugin(numbering)]
182
185
  }) : buildInitialState(headerFooter, styles, theme, numbering, manager),
183
186
  dispatchTransaction(transaction) {
184
187
  if (canonical.dispatch(transaction)) return;
185
188
  const nextState = view.state.apply(transaction);
189
+ const docChanged = !nextState.doc.eq(view.state.doc);
190
+ const noteIssue = noteReferenceTransactionIssue(transaction);
191
+ if (noteIssue) deps.onSessionRefusal?.(noteIssue.message, CANONICAL_GAP.dispatch, noteIssue);
186
192
  view.updateState(nextState);
187
193
  const mountedPart = mounted.get(part.rId);
188
- if (mountedPart && transaction.docChanged) mountedPart.dirty = true;
194
+ if (mountedPart && docChanged) mountedPart.dirty = true;
189
195
  deps.onTransaction?.({
190
- docChanged: transaction.docChanged,
196
+ docChanged,
191
197
  kind: part.kind,
192
198
  rId: part.rId,
193
199
  selectionChanged: transaction.selectionSet,
@@ -218,7 +224,15 @@ const createHeaderFooterEditorManager = (deps) => {
218
224
  rId
219
225
  })),
220
226
  snapshotDocument: (document) => {
221
- if (usesCanonicalSession(deps.getExperimentalSession?.(), CANONICAL_GAP.secondaryStories)) return deps.getCanonicalApi?.()?.getCanonicalDocument() ?? document;
227
+ if (usesCanonicalSession(deps.getExperimentalSession?.(), CANONICAL_GAP.authorityRouting)) {
228
+ const canonical = deps.getCanonicalApi?.()?.getCanonicalDocument();
229
+ if (canonical) return canonical;
230
+ throw new CanonicalSessionError({
231
+ gap: CANONICAL_GAP.authorityRouting,
232
+ reason: "refused",
233
+ message: "Canonical story snapshot is unavailable."
234
+ });
235
+ }
222
236
  let headers = document.package.headers;
223
237
  let footers = document.package.footers;
224
238
  let headersChanged = false;
@@ -1,13 +1,16 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
2
  import { FolioDocumentOperationResult, FolioDocumentOperationUndoHandle, FolioDocumentOperationUndoResult } from "../document-operations.js";
3
+ import { Comment } from "../types/content.js";
3
4
  import { CanonicalSaveSnapshot } from "../types/canonicalSave.js";
4
5
  import { CanonicalSessionMode } from "./canonicalSession.js";
5
6
  import { createCanonicalInputBoundary } from "./canonicalInput.js";
6
7
  import { CanonicalPublicOperationOptions } from "./canonicalPublicOperations.js";
8
+ import { CanonicalSectionPropertiesResult } from "../types/canonicalSections.js";
9
+ import { CanonicalCommentRequest, CanonicalCommentResult } from "../types/canonicalComments.js";
7
10
  import { Node } from "prosemirror-model";
8
11
  import { Command, EditorState, Transaction } from "prosemirror-state";
9
12
  import { EditorView } from "prosemirror-view";
10
- import { DocumentOp, OpStory } from "@stll/docx-core/ops";
13
+ import { DocumentOp, FormattingPatch, OpStory } from "@stll/docx-core/ops";
11
14
  //#region src/controller/hiddenEditorApi.d.ts
12
15
  type CanonicalStoryHistoryOptions = {
13
16
  view: EditorView;
@@ -31,6 +34,9 @@ type HiddenEditorApi = {
31
34
  /** Get the current Document from PM state */
32
35
  getDocument: () => document_d_exports.Document | null;
33
36
  /** Canonical snapshot, or null in the default session. */
37
+ getCanonicalComments: () => Comment[] | null;
38
+ /** Committed session/version identity; safe during provisional composition. */
39
+ getCanonicalCommittedVersion: () => string | null;
34
40
  getCanonicalDocument: () => document_d_exports.Document | null;
35
41
  captureCanonicalSave: () => CanonicalSaveSnapshot | null;
36
42
  /** A save cannot acknowledge newer edits or an unfinished composition. */
@@ -42,6 +48,9 @@ type HiddenEditorApi = {
42
48
  updateCanonicalInputLifecycle: (action: "beginComposition" | "endComposition" | "breakUndoGroup") => boolean;
43
49
  applyCanonicalStoryHistory: (options: CanonicalStoryHistoryOptions) => boolean;
44
50
  applyCanonicalOperations: (ops: readonly DocumentOp[]) => boolean;
51
+ applyCanonicalComment: (request: CanonicalCommentRequest) => CanonicalCommentResult | null;
52
+ /** Apply a final-section patch; null is reserved for default sessions. */
53
+ applyCanonicalSectionProperties: (patch: FormattingPatch<document_d_exports.SectionProperties>) => CanonicalSectionPropertiesResult | null;
45
54
  getCanonicalStorySelection: (story: OpStory) => {
46
55
  anchor: number;
47
56
  head: number;
@@ -80,6 +89,8 @@ type HiddenEditorApi = {
80
89
  type HiddenEditorApiDeps = {
81
90
  getView: () => EditorView | null;
82
91
  getDocumentContext: () => document_d_exports.Document | null;
92
+ getCanonicalComments?: () => Comment[] | null;
93
+ getCanonicalCommittedVersion?: HiddenEditorApi["getCanonicalCommittedVersion"];
83
94
  getCanonicalDocument?: () => document_d_exports.Document | null;
84
95
  captureCanonicalSave?: HiddenEditorApi["captureCanonicalSave"];
85
96
  isCanonicalSaveCurrent?: HiddenEditorApi["isCanonicalSaveCurrent"];
@@ -87,7 +98,7 @@ type HiddenEditorApiDeps = {
87
98
  resolveCanonicalRevisions?: HiddenEditorApi["resolveCanonicalRevisions"];
88
99
  executeCanonicalCommand?: (command: Command) => boolean | undefined;
89
100
  getCanonicalHistory?: () => Pick<HiddenEditorApi, "undo" | "redo" | "canUndo" | "canRedo"> | null;
90
- canonicalOperations?: Pick<HiddenEditorApi, "updateCanonicalInputLifecycle" | "applyCanonicalOperations" | "getCanonicalStoryProjection" | "replaceCanonicalStoryText" | "applyCanonicalStoryHistory" | "getCanonicalStorySelection" | "applyCanonicalDocumentOperations" | "undoCanonicalDocumentOperations">;
101
+ canonicalOperations?: Pick<HiddenEditorApi, "updateCanonicalInputLifecycle" | "applyCanonicalOperations" | "applyCanonicalComment" | "applyCanonicalSectionProperties" | "getCanonicalStoryProjection" | "replaceCanonicalStoryText" | "applyCanonicalStoryHistory" | "getCanonicalStorySelection" | "applyCanonicalDocumentOperations" | "undoCanonicalDocumentOperations">;
91
102
  isDestroying: () => boolean;
92
103
  ensureView: () => void;
93
104
  isViewRequested: () => boolean;
@@ -9,7 +9,7 @@ import { redo, undo } from "prosemirror-history";
9
9
  */
10
10
  const stateToDocument = (state, originalDoc) => {
11
11
  if (!originalDoc) return null;
12
- return fromProseDoc(state.doc, originalDoc);
12
+ return fromProseDoc(state.doc, originalDoc, { stylesheetSource: { type: "package" } });
13
13
  };
14
14
  const createHiddenEditorApi = (deps) => {
15
15
  const dispatchTr = (view, tr) => {
@@ -34,6 +34,8 @@ const createHiddenEditorApi = (deps) => {
34
34
  getView: () => deps.getView() ?? null,
35
35
  setCanonicalMode: (mode) => !deps.isDestroying() && (deps.setCanonicalMode?.(mode) ?? false),
36
36
  resolveCanonicalRevisions: (revisionIds, resolution) => !deps.isDestroying() && (deps.resolveCanonicalRevisions?.(revisionIds, resolution) ?? false),
37
+ getCanonicalComments: () => deps.getCanonicalComments?.() ?? null,
38
+ getCanonicalCommittedVersion: () => deps.getCanonicalCommittedVersion?.() ?? null,
37
39
  captureCanonicalSave: () => deps.captureCanonicalSave?.() ?? null,
38
40
  isCanonicalSaveCurrent: (version) => deps.isCanonicalSaveCurrent?.(version) ?? false,
39
41
  getCanonicalDocument: () => {
@@ -49,6 +51,8 @@ const createHiddenEditorApi = (deps) => {
49
51
  },
50
52
  updateCanonicalInputLifecycle: (action) => deps.canonicalOperations?.updateCanonicalInputLifecycle(action) ?? false,
51
53
  applyCanonicalStoryHistory: (options) => deps.canonicalOperations?.applyCanonicalStoryHistory(options) ?? false,
54
+ applyCanonicalComment: (request) => deps.canonicalOperations?.applyCanonicalComment(request) ?? null,
55
+ applyCanonicalSectionProperties: (patch) => deps.canonicalOperations?.applyCanonicalSectionProperties(patch) ?? null,
52
56
  applyCanonicalOperations: (ops) => deps.canonicalOperations?.applyCanonicalOperations(ops) ?? false,
53
57
  getCanonicalStorySelection: (story) => deps.canonicalOperations?.getCanonicalStorySelection(story) ?? null,
54
58
  getCanonicalStoryProjection: (story) => deps.canonicalOperations?.getCanonicalStoryProjection(story) ?? null,
@@ -65,7 +65,7 @@ type HiddenEditorManagerDeps = {
65
65
  getExperimentalSession?: () => "canonical" | undefined;
66
66
  getEditingMode?: () => EditorMode;
67
67
  getSuggestionAuthor?: () => string;
68
- onSessionRefusal?: (reason: string, gap: CanonicalGap) => void;
68
+ onSessionRefusal?: (reason: string, gap: CanonicalGap, error?: Error) => void;
69
69
  /**
70
70
  * Identity of the loaded document as tracked by the adapter's loader: the
71
71
  * same value across internal edits (so typing does not trigger an external
@@ -99,6 +99,8 @@ declare const createHiddenEditorClipboardHandlers: (deps: Pick<HiddenEditorManag
99
99
  paste: (_view: unknown, event: PreventableDomEvent) => boolean;
100
100
  };
101
101
  type HiddenEditorManager = {
102
+ /** Whether the canonical owner still holds a provisional composition. */
103
+ isCanonicalComposing: () => boolean;
102
104
  /** Request the view (sets the requested flag, then attempts creation). */
103
105
  ensureView: () => void;
104
106
  /** Re-attempt a previously-requested-but-deferred creation (no-op otherwise). */