@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
@@ -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,11 +7,12 @@ 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";
15
+ import { DOCUMENT_OP_TYPES, OP_STORIES, applyDocumentOps, combineEdits, commentDocumentIssue, compileEditorIntent, createEditorIntentIdAllocator, documentStories, editorParagraphGroups, findStoryBody, normalizeForOps, packageParagraphIds, sameStory, validateOpsDocument } from "@stll/docx-core/ops";
14
16
  import { AllSelection, TextSelection } from "prosemirror-state";
15
17
  //#region src/controller/canonicalSession.ts
16
18
  var CanonicalSessionError = class extends TaggedError("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:
@@ -219,6 +243,8 @@ const preservePropertySources = ({ target, source, stories = documentStories(sou
219
243
  copyDocumentParagraphPropertySourceContract(target, source);
220
244
  };
221
245
  const unsupportedSeedReason = (document, stories = documentStories(document)) => {
246
+ const commentIssue = commentDocumentIssue(document, "all");
247
+ if (commentIssue !== void 0) return commentIssue;
222
248
  for (const story of stories) {
223
249
  const body = findStoryBody(document, story);
224
250
  if (story !== OP_STORIES.MAIN && (story.kind === "footnote" || story.kind === "endnote")) {
@@ -257,6 +283,14 @@ const project = ({ document, styles, story = OP_STORIES.MAIN }) => {
257
283
  });
258
284
  if (converted.isErr()) return converted;
259
285
  const pairedBookmarkIds = collectPairedBookmarkIds(body.content);
286
+ const commentContext = {
287
+ rangedIds: rangedCommentIds(body.content),
288
+ pointIds: /* @__PURE__ */ new Set(),
289
+ openIds: /* @__PURE__ */ new Set()
290
+ };
291
+ visitCommentMarkers(body.content, ({ item }) => {
292
+ if (item.type === "commentReference" && !commentContext.rangedIds.has(item.id)) commentContext.pointIds.add(item.id);
293
+ });
260
294
  const paragraphs = [];
261
295
  let failure;
262
296
  converted.value.forEach((node, offset, index) => {
@@ -280,7 +314,8 @@ const project = ({ document, styles, story = OP_STORIES.MAIN }) => {
280
314
  const inline = projectCanonicalInline({
281
315
  source,
282
316
  paragraph: node,
283
- pairedBookmarkIds
317
+ pairedBookmarkIds,
318
+ commentContext
284
319
  });
285
320
  if (inline.isErr()) {
286
321
  failure = new CanonicalSessionError({
@@ -297,6 +332,10 @@ const project = ({ document, styles, story = OP_STORIES.MAIN }) => {
297
332
  node,
298
333
  source
299
334
  });
335
+ visitCommentMarkers([source], ({ item }) => {
336
+ if (item.type === "commentRangeStart") commentContext.openIds.add(item.id);
337
+ if (item.type === "commentRangeEnd") commentContext.openIds.delete(item.id);
338
+ });
300
339
  });
301
340
  if (failure !== void 0) return Result.err(failure);
302
341
  if (paragraphs.length !== body.content.length) return refuse("The projection changed paragraph structure.");
@@ -405,6 +444,10 @@ var CanonicalSession = class {
405
444
  this.styles = styles == null ? styles : structuredClone(styles);
406
445
  this.rememberSources(document);
407
446
  }
447
+ /** Render only the committed comment projection while provisional IME blocks document snapshots. */
448
+ getCommittedComments() {
449
+ return structuredClone(this.currentDocument.package.document.comments ?? []);
450
+ }
408
451
  setMode(mode) {
409
452
  if (this.mode.type !== mode.type || this.mode.type === "suggesting" && mode.type === "suggesting" && this.mode.author !== mode.author) this.breakUndoGroup();
410
453
  this.mode = mode.type === "editing" ? { type: "editing" } : {
@@ -994,26 +1037,15 @@ var CanonicalSession = class {
994
1037
  transaction.replaceWith(from, to, Fragment.fromArray(replacement));
995
1038
  }
996
1039
  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}`);
1014
- }
1040
+ if (sameStory(selection.anchor.story, projectedStory) && sameStory(selection.head.story, projectedStory)) restoreCanonicalSelection({
1041
+ transaction,
1042
+ projection: projected.value,
1043
+ selection,
1044
+ unavailable: {
1045
+ type: "near",
1046
+ anchor: state.selection.anchor
1015
1047
  }
1016
- }
1048
+ });
1017
1049
  transaction.setMeta(CANONICAL_PROJECTION_META, {
1018
1050
  type: "canonical",
1019
1051
  origin,
@@ -1033,6 +1065,7 @@ var CanonicalSession = class {
1033
1065
  document: applied.value.document,
1034
1066
  projection: projected.value,
1035
1067
  bodyProjection: bodyProjection.value,
1068
+ selection,
1036
1069
  touched: applied.value.touched,
1037
1070
  version: baseVersion + 1,
1038
1071
  origin,
@@ -1061,7 +1094,8 @@ var CanonicalSession = class {
1061
1094
  applied.value.touched.inserted,
1062
1095
  applied.value.touched.removed
1063
1096
  ]) 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";
1097
+ const bodyOps = ops.filter((op) => !COMMENT_OPERATION_TYPES.has(op.type));
1098
+ 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
1099
  this.currentVersion = baseVersion + 1;
1066
1100
  onPublish(applied.value.inverse, this.currentVersion);
1067
1101
  return Result.ok(void 0);
@@ -20,6 +20,8 @@ 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;
@@ -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,6 +82,14 @@ 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),
@@ -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;
@@ -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). */