@stll/folio-core 0.55.0 → 0.56.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 (119) hide show
  1. package/dist/ai-edits/apply.js +169 -38
  2. package/dist/ai-edits/headless.js +2 -2
  3. package/dist/ai-edits/types.d.ts +7 -1
  4. package/dist/compare/compare.js +1 -0
  5. package/dist/compare/reproducible-package.d.ts +0 -17
  6. package/dist/compare/reproducible-package.js +12 -18
  7. package/dist/controller/canonicalClipboard.d.ts +23 -0
  8. package/dist/controller/canonicalClipboard.js +476 -0
  9. package/dist/controller/canonicalClipboardResources.d.ts +21 -0
  10. package/dist/controller/canonicalClipboardResources.js +382 -0
  11. package/dist/controller/canonicalComposition.d.ts +20 -3
  12. package/dist/controller/canonicalComposition.js +166 -38
  13. package/dist/controller/canonicalInlineProjection.d.ts +113 -0
  14. package/dist/controller/canonicalInlineProjection.js +393 -0
  15. package/dist/controller/canonicalInput.d.ts +13 -4
  16. package/dist/controller/canonicalInput.js +52 -13
  17. package/dist/controller/canonicalInputTimer.d.ts +7 -0
  18. package/dist/controller/canonicalInputTimer.js +11 -0
  19. package/dist/controller/canonicalPublicOperations.d.ts +48 -0
  20. package/dist/controller/canonicalPublicOperations.js +556 -0
  21. package/dist/controller/canonicalSession.d.ts +24 -6
  22. package/dist/controller/canonicalSession.js +259 -111
  23. package/dist/controller/canonicalStoryEditor.d.ts +5 -3
  24. package/dist/controller/canonicalStoryEditor.js +7 -2
  25. package/dist/controller/canonicalStructure.js +57 -4
  26. package/dist/controller/canonicalTocSelection.d.ts +11 -0
  27. package/dist/controller/canonicalTocSelection.js +43 -0
  28. package/dist/controller/folioEditor.d.ts +2 -9
  29. package/dist/controller/folioEditor.js +5 -4
  30. package/dist/controller/headerFooterEditorManager.d.ts +2 -1
  31. package/dist/controller/headerFooterEditorManager.js +8 -7
  32. package/dist/controller/hiddenEditorApi.d.ts +12 -2
  33. package/dist/controller/hiddenEditorApi.js +4 -0
  34. package/dist/controller/hiddenEditorManager.d.ts +3 -1
  35. package/dist/controller/hiddenEditorManager.js +216 -34
  36. package/dist/controller/noteEditorManager.d.ts +2 -1
  37. package/dist/controller/noteEditorManager.js +8 -7
  38. package/dist/document-operations.d.ts +8 -1
  39. package/dist/document-operations.js +6 -4
  40. package/dist/docx/blockPlainText.d.ts +1 -1
  41. package/dist/docx/blockPlainText.js +11 -3
  42. package/dist/docx/canonicalResourceSave.d.ts +16 -0
  43. package/dist/docx/canonicalResourceSave.js +27 -0
  44. package/dist/docx/canonicalSave.d.ts +29 -0
  45. package/dist/docx/canonicalSave.js +101 -0
  46. package/dist/docx/canonicalStoryRepack.d.ts +1 -1
  47. package/dist/docx/canonicalStoryRepack.js +38 -5
  48. package/dist/docx/documentParser.d.ts +1 -13
  49. package/dist/docx/documentParser.js +1 -47
  50. package/dist/docx/ensureParaIds.js +10 -4
  51. package/dist/docx/footnoteParser.d.ts +1 -11
  52. package/dist/docx/footnoteParser.js +1 -16
  53. package/dist/docx/headerFooterParser.d.ts +1 -9
  54. package/dist/docx/headerFooterParser.js +1 -12
  55. package/dist/docx/headerFooterVerbatim.d.ts +26 -1
  56. package/dist/docx/headerFooterVerbatim.js +81 -3
  57. package/dist/docx/imageParser.d.ts +13 -1
  58. package/dist/docx/imageParser.js +20 -11
  59. package/dist/docx/metadataPrivacy.js +6 -1
  60. package/dist/docx/packageParts.js +9 -3
  61. package/dist/docx/paragraphPropertySource.js +2 -1
  62. package/dist/docx/parser.js +2 -1
  63. package/dist/docx/removeHeaderFooterParts.js +17 -6
  64. package/dist/docx/rezip.d.ts +49 -5
  65. package/dist/docx/rezip.js +745 -229
  66. package/dist/docx/saveDiagnostics.d.ts +11 -0
  67. package/dist/docx/selectiveSave.d.ts +2 -1
  68. package/dist/docx/selectiveSave.js +32 -3
  69. package/dist/docx/selectiveXmlPatch.js +7 -5
  70. package/dist/docx/serializer/documentSerializer.d.ts +9 -2
  71. package/dist/docx/serializer/documentSerializer.js +29 -10
  72. package/dist/docx/server/applyDocxXmlPatchProposal.js +14 -9
  73. package/dist/docx/storyBlockReplay.d.ts +21 -1
  74. package/dist/docx/storyBlockReplay.js +113 -9
  75. package/dist/docx/storyPlainText.d.ts +20 -0
  76. package/dist/docx/storyPlainText.js +23 -0
  77. package/dist/docx/structuralXmlPatch.js +10 -22
  78. package/dist/docx/styleParser.d.ts +5 -1
  79. package/dist/docx/styleParser.js +1 -1
  80. package/dist/docx/tableParser.d.ts +1 -8
  81. package/dist/docx/tableParser.js +1 -28
  82. package/dist/docx/textBoxParser.d.ts +1 -5
  83. package/dist/docx/textBoxParser.js +1 -20
  84. package/dist/docx/unzip.js +6 -1
  85. package/dist/internal/acceptedBlockProjection.d.ts +12 -0
  86. package/dist/internal/acceptedBlockProjection.js +45 -0
  87. package/dist/managers/DocumentLoaderManager.js +2 -1
  88. package/dist/markdown/escape.d.ts +3 -1
  89. package/dist/markdown/escape.js +9 -2
  90. package/dist/markdown/renderBlock.js +5 -120
  91. package/dist/markdown/renderParagraph.js +4 -2
  92. package/dist/markdown/renderRuns.d.ts +7 -1
  93. package/dist/markdown/renderRuns.js +32 -13
  94. package/dist/prosemirror/canonicalCommands.d.ts +28 -4
  95. package/dist/prosemirror/canonicalCommands.js +11 -4
  96. package/dist/prosemirror/canonicalSelectionRange.d.ts +9 -0
  97. package/dist/prosemirror/canonicalSelectionRange.js +15 -0
  98. package/dist/prosemirror/clipboardIntent.d.ts +7 -0
  99. package/dist/prosemirror/clipboardIntent.js +9 -0
  100. package/dist/prosemirror/commands/pageBreak.js +2 -2
  101. package/dist/prosemirror/commands/pastePlainText.js +2 -1
  102. package/dist/prosemirror/conversion/hyphenTextCarriers.d.ts +8 -0
  103. package/dist/prosemirror/conversion/hyphenTextCarriers.js +8 -0
  104. package/dist/prosemirror/conversion/toProseDoc.d.ts +7 -1
  105. package/dist/prosemirror/conversion/toProseDoc.js +5 -4
  106. package/dist/prosemirror/extensions/core/ParagraphExtension.js +39 -30
  107. package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +56 -28
  108. package/dist/prosemirror/extensions/marks/markUtils.js +3 -1
  109. package/dist/prosemirror/insertOperations.js +2 -1
  110. package/dist/prosemirror/paragraphPropertyCarry.d.ts +7 -1
  111. package/dist/prosemirror/paragraphPropertyCarry.js +8 -3
  112. package/dist/prosemirror/plugins/suggestionMode.js +7 -5
  113. package/dist/types/canonicalCapabilities.d.ts +215 -0
  114. package/dist/types/canonicalCapabilities.js +212 -0
  115. package/dist/types/canonicalSave.d.ts +11 -0
  116. package/dist/types/canonicalSave.js +0 -0
  117. package/dist/types/docxSerialization.d.ts +14 -0
  118. package/dist/types/docxSerialization.js +7 -0
  119. package/package.json +4 -4
@@ -1,3 +1,4 @@
1
+ import { CanonicalGap } from "../types/canonicalCapabilities.js";
1
2
  import { HiddenEditorApi } from "./hiddenEditorApi.js";
2
3
  import { Transaction } from "prosemirror-state";
3
4
  import { EditorView } from "prosemirror-view";
@@ -9,7 +10,7 @@ type CanonicalStoryEditorOptions = {
9
10
  getApi: () => HiddenEditorApi | null;
10
11
  enabled: () => boolean;
11
12
  onSelectionChange: () => void;
12
- onRefusal: ((reason: string) => void) | undefined;
13
+ onRefusal: ((reason: string, gap: CanonicalGap) => void) | undefined;
13
14
  };
14
15
  /** Secondary views compile classified native input into the shared Document journal. */
15
16
  declare const createCanonicalStoryEditor: ({ story, getView, getApi, enabled, onRefusal, onSelectionChange }: CanonicalStoryEditorOptions) => {
@@ -18,14 +19,15 @@ declare const createCanonicalStoryEditor: ({ story, getView, getApi, enabled, on
18
19
  handleKeyDown: (view: EditorView, event: KeyboardEvent) => boolean;
19
20
  handleTextInput: (view: EditorView, from: number, to: number, text: string) => boolean;
20
21
  handleDOMEvents: {
22
+ keydown: (view: EditorView, event: KeyboardEvent) => boolean;
21
23
  beforeinput: (view: EditorView, event: InputEvent) => boolean;
22
24
  mousedown: (view: EditorView) => boolean;
23
25
  compositionstart: (view: EditorView) => boolean;
24
26
  compositionend: (view: EditorView) => boolean;
25
27
  input: (view: EditorView) => boolean;
26
28
  blur: (view: EditorView) => boolean;
27
- paste: (view: EditorView, event: Event) => boolean;
28
- cut: (view: EditorView, event: Event) => boolean;
29
+ paste: (view: EditorView, event: ClipboardEvent) => boolean;
30
+ cut: (view: EditorView, event: ClipboardEvent) => boolean;
29
31
  drop: (view: EditorView, event: Event) => boolean;
30
32
  };
31
33
  };
@@ -1,11 +1,15 @@
1
+ import { CANONICAL_GAP } from "../types/canonicalCapabilities.js";
1
2
  import { createCanonicalInputBoundary } from "./canonicalInput.js";
2
3
  import { CanonicalSessionError } from "./canonicalSession.js";
3
4
  //#region src/controller/canonicalStoryEditor.ts
4
5
  /** Secondary views compile classified native input into the shared Document journal. */
5
6
  const createCanonicalStoryEditor = ({ story, getView, getApi, enabled, onRefusal, onSelectionChange }) => {
6
- const refuse = (message) => {
7
- if (onRefusal) onRefusal(message);
7
+ const refuse = (reason) => {
8
+ const gap = CANONICAL_GAP.dispatch;
9
+ const message = reason;
10
+ if (onRefusal) onRefusal(message, gap);
8
11
  else throw new CanonicalSessionError({
12
+ gap,
9
13
  message,
10
14
  reason: "refused"
11
15
  });
@@ -65,6 +69,7 @@ const createCanonicalStoryEditor = ({ story, getView, getApi, enabled, onRefusal
65
69
  handleKeyDown: (view, event) => enabled() && boundary.handleKeyDown(view, event),
66
70
  handleTextInput: (view, from, to, text) => enabled() && boundary.handleTextInput(view, from, to, text),
67
71
  handleDOMEvents: {
72
+ keydown: (view, event) => enabled() && boundary.handleDOMEvents.keydown(view, event),
68
73
  beforeinput: (view, event) => enabled() && boundary.handleDOMEvents.beforeinput(view, event),
69
74
  mousedown: (view) => enabled() && boundary.handleDOMEvents.mousedown(view),
70
75
  compositionstart: (view) => enabled() && boundary.handleDOMEvents.compositionstart(view),
@@ -1,11 +1,17 @@
1
+ import { BUILT_IN_STYLE_NAME, createBuiltInStyleIndex } from "../docx/builtInStyles.js";
1
2
  import { getCachedNumberingMap, numberingLevelUsesBulletMarker } from "../docx/numberingParser.js";
2
3
  import { listRequestsForMarker } from "../prosemirror/listAutoformatMarkers.js";
4
+ import { CANONICAL_GAP } from "../types/canonicalCapabilities.js";
5
+ import { collectHeadings } from "../utils/headingCollector.js";
3
6
  import { CanonicalSessionError } from "./canonicalSession.js";
4
7
  import { Result } from "better-result";
5
- import { paragraphNumberingReference } from "@stll/docx-core/model";
8
+ import { headingOutlineLevel, paragraphNumberingReference } from "@stll/docx-core/model";
6
9
  import { OP_STORIES } from "@stll/docx-core/ops";
7
10
  //#region src/controller/canonicalStructure.ts
11
+ const DEFAULT_TOC_PAGE_WIDTH_TWIPS = 12240;
12
+ const DEFAULT_TOC_MARGIN_TWIPS = 1440;
8
13
  const refuse = (message) => Result.err(new CanonicalSessionError({
14
+ gap: CANONICAL_GAP.commands,
9
15
  message,
10
16
  reason: "refused"
11
17
  }));
@@ -84,6 +90,53 @@ const prepareCanonicalCommands = (session, state, commands) => {
84
90
  const intents = [];
85
91
  const paragraphs = selectedParagraphs(session, state);
86
92
  for (const command of commands) switch (command.type) {
93
+ case "generateTOC": {
94
+ const at = session.projection.addressAt(command.at);
95
+ if (at.isErr()) return at;
96
+ const definitions = session.document.package.styles;
97
+ const styles = createBuiltInStyleIndex(definitions?.styles ?? [], definitions?.docDefaults);
98
+ const headings = [];
99
+ for (const heading of collectHeadings(session.projection.doc, styles)) {
100
+ const address = session.projection.addressAt(heading.pmPos + 1);
101
+ if (address.isErr()) return address;
102
+ const outline = headingOutlineLevel(heading.level);
103
+ if (outline?.kind !== "heading") return refuse("A TOC heading has an invalid outline level.");
104
+ const styleId = styles.styleIdForTableOfContentsLevel(outline.level + 1);
105
+ headings.push({
106
+ blockId: address.value.blockId,
107
+ text: heading.text,
108
+ level: outline.level,
109
+ ...styleId === void 0 ? {} : { styleId }
110
+ });
111
+ }
112
+ if (headings.length === 0) break;
113
+ const titleStyleId = styles.styleIdForBuiltInName(BUILT_IN_STYLE_NAME.tocHeading);
114
+ const section = session.document.package.document.content.find((item) => item.type === "paragraph" && (item.sectionProperties?.pageWidth ?? 0) > 0);
115
+ const properties = section?.type === "paragraph" ? section.sectionProperties : void 0;
116
+ intents.push({
117
+ type: "generateTOC",
118
+ at: at.value,
119
+ title: command.title,
120
+ headings,
121
+ tabPosition: (properties?.pageWidth ?? DEFAULT_TOC_PAGE_WIDTH_TWIPS) - (properties?.marginLeft ?? DEFAULT_TOC_MARGIN_TWIPS) - (properties?.marginRight ?? DEFAULT_TOC_MARGIN_TWIPS),
122
+ ...titleStyleId === void 0 ? {} : { titleStyleId }
123
+ });
124
+ break;
125
+ }
126
+ case "setHyperlink":
127
+ case "removeHyperlink":
128
+ case "insertHyperlink": {
129
+ const from = session.projection.addressAt(command.from);
130
+ if (from.isErr()) return from;
131
+ const to = session.projection.addressAt(command.to);
132
+ if (to.isErr()) return to;
133
+ intents.push({
134
+ ...command,
135
+ from: from.value,
136
+ to: to.value
137
+ });
138
+ break;
139
+ }
87
140
  case "insertBreak": {
88
141
  const from = session.projection.addressAt(command.from);
89
142
  if (from.isErr()) return from;
@@ -233,7 +286,7 @@ const prepareCanonicalCommands = (session, state, commands) => {
233
286
  }
234
287
  default: return command;
235
288
  }
236
- return session.prepareIntents(state, intents);
289
+ return session.prepareIntents(state, { intents });
237
290
  };
238
291
  /** Rules compile their replacement and paragraph change as one atomic edit. */
239
292
  const prepareCanonicalAutoformat = (session, state, { from, to, text }) => {
@@ -254,7 +307,7 @@ const prepareCanonicalAutoformat = (session, state, { from, to, text }) => {
254
307
  const request = requests?.at(0);
255
308
  const styleId = `Heading${heading?.length ?? 1}`;
256
309
  if (request === void 0 && !session.hasStyle(styleId)) return void 0;
257
- return session.prepareIntents(state, [{
310
+ return session.prepareIntents(state, { intents: [{
258
311
  type: "replaceText",
259
312
  from: start,
260
313
  to: at.value,
@@ -263,7 +316,7 @@ const prepareCanonicalAutoformat = (session, state, { from, to, text }) => {
263
316
  type: "formatParagraph",
264
317
  at: start,
265
318
  patch: { styleId }
266
- } : numberingIntent(session, [source], request)]);
319
+ } : numberingIntent(session, [source], request)] });
267
320
  };
268
321
  //#endregion
269
322
  export { prepareCanonicalAutoformat, prepareCanonicalCommands };
@@ -0,0 +1,11 @@
1
+ import { DocumentOp, TextPosition } from "@stll/docx-core/ops";
2
+ //#region src/controller/canonicalTocSelection.d.ts
3
+ type TocSelectionMapping = {
4
+ at: TextPosition;
5
+ after: TextPosition;
6
+ ops: readonly DocumentOp[];
7
+ };
8
+ /** TOC insertion retains the selected text and maps its endpoints through the canonical split. */
9
+ declare const mapTocSelection: (point: TextPosition, { at, after, ops }: TocSelectionMapping) => TextPosition;
10
+ //#endregion
11
+ export { mapTocSelection };
@@ -0,0 +1,43 @@
1
+ import { panic } from "better-result";
2
+ import { DOCUMENT_OP_TYPES, SPLIT_HALVES, compareGaps, idKey, mapTocBookmarkPosition, sameStory } from "@stll/docx-core/ops";
3
+ //#region src/controller/canonicalTocSelection.ts
4
+ /** TOC insertion retains the selected text and maps its endpoints through the canonical split. */
5
+ const mapTocSelection = (point, { at, after, ops }) => {
6
+ if (!sameStory(point.story, at.story)) return point;
7
+ point = mapTocBookmarkPosition({
8
+ ...point,
9
+ zeroWidthBefore: point.zeroWidthBefore ?? 0
10
+ }, ops);
11
+ at = mapTocBookmarkPosition({
12
+ ...at,
13
+ zeroWidthBefore: at.zeroWidthBefore ?? 0
14
+ }, ops);
15
+ if (idKey(point.blockId) !== idKey(at.blockId)) return point;
16
+ const split = ops.find((op) => op.type === DOCUMENT_OP_TYPES.SPLIT_BLOCK);
17
+ if (split === void 0) return point;
18
+ if (point.offset === at.offset && (point.zeroWidthBefore ?? 0) === (at.zeroWidthBefore ?? 0)) return after;
19
+ if (split.type !== DOCUMENT_OP_TYPES.SPLIT_BLOCK || split.newHalf !== SPLIT_HALVES.FIRST) return panic("TOC insertion must retain its following paragraph identity.");
20
+ const pointGap = {
21
+ offset: point.offset,
22
+ zeroWidthBefore: point.zeroWidthBefore ?? 0
23
+ };
24
+ const splitGap = {
25
+ offset: split.at.offset,
26
+ zeroWidthBefore: split.at.zeroWidthBefore ?? 0
27
+ };
28
+ if (compareGaps(pointGap, splitGap) < 0) return {
29
+ ...point,
30
+ blockId: split.newBlockId
31
+ };
32
+ if (point.offset === split.at.offset) return {
33
+ ...point,
34
+ offset: 0,
35
+ zeroWidthBefore: pointGap.zeroWidthBefore - splitGap.zeroWidthBefore
36
+ };
37
+ return {
38
+ ...point,
39
+ offset: point.offset - split.at.offset
40
+ };
41
+ };
42
+ //#endregion
43
+ export { mapTocSelection };
@@ -1,5 +1,6 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
2
  import { DocxInput } from "../utils/docxInput.js";
3
+ import { FOLIO_DOCX_SERIALIZATION_MODE, FolioDocxSerializationMode, FolioGetDocxOptions } from "../types/docxSerialization.js";
3
4
  import { Layout } from "../layout-engine/types.js";
4
5
  import { FolioEditorEmitter } from "./folioEditorEvents.js";
5
6
  import { HiddenEditorApi } from "./hiddenEditorApi.js";
@@ -14,14 +15,6 @@ type FolioEditorDeps = {
14
15
  getDocumentIO?: () => FolioEditorDocumentIO;
15
16
  emitter: FolioEditorEmitter;
16
17
  };
17
- declare const FOLIO_DOCX_SERIALIZATION_MODE: Readonly<{
18
- readonly full: "full";
19
- readonly preferSelective: "prefer-selective";
20
- }>;
21
- type FolioDocxSerializationMode = (typeof FOLIO_DOCX_SERIALIZATION_MODE)[keyof typeof FOLIO_DOCX_SERIALIZATION_MODE];
22
- type FolioGetDocxOptions = {
23
- mode?: FolioDocxSerializationMode;
24
- };
25
18
  /**
26
19
  * Document lifecycle operations supplied by a host integration.
27
20
  *
@@ -45,4 +38,4 @@ type FolioEditor = HiddenEditorApi & {
45
38
  };
46
39
  declare const createFolioEditor: (deps: FolioEditorDeps) => FolioEditor;
47
40
  //#endregion
48
- export { FOLIO_DOCX_SERIALIZATION_MODE, FolioDocxSerializationMode, FolioEditor, FolioEditorDeps, FolioEditorDocumentIO, FolioGetDocxOptions, createFolioEditor };
41
+ export { FOLIO_DOCX_SERIALIZATION_MODE, type FolioDocxSerializationMode, FolioEditor, FolioEditorDeps, FolioEditorDocumentIO, type FolioGetDocxOptions, createFolioEditor };
@@ -1,9 +1,6 @@
1
+ import { FOLIO_DOCX_SERIALIZATION_MODE } from "../types/docxSerialization.js";
1
2
  import { panic } from "better-result";
2
3
  //#region src/controller/folioEditor.ts
3
- const FOLIO_DOCX_SERIALIZATION_MODE = Object.freeze({
4
- full: "full",
5
- preferSelective: "prefer-selective"
6
- });
7
4
  const createFolioEditor = (deps) => {
8
5
  const getDocumentIO = () => deps.getDocumentIO?.() ?? panic("FolioEditor document I/O requires a getDocumentIO dependency");
9
6
  return {
@@ -21,6 +18,10 @@ const createFolioEditor = (deps) => {
21
18
  getCanonicalStorySelection: (story) => deps.getEditorApi()?.getCanonicalStorySelection(story) ?? null,
22
19
  getCanonicalStoryProjection: (story) => deps.getEditorApi()?.getCanonicalStoryProjection(story) ?? null,
23
20
  replaceCanonicalStoryText: (options) => deps.getEditorApi()?.replaceCanonicalStoryText(options) ?? false,
21
+ isCanonicalSaveCurrent: (version) => deps.getEditorApi()?.isCanonicalSaveCurrent(version) ?? false,
22
+ captureCanonicalSave: () => deps.getEditorApi()?.captureCanonicalSave() ?? null,
23
+ applyCanonicalDocumentOperations: (options) => deps.getEditorApi()?.applyCanonicalDocumentOperations(options) ?? null,
24
+ undoCanonicalDocumentOperations: (handle) => deps.getEditorApi()?.undoCanonicalDocumentOperations(handle) ?? null,
24
25
  getCanonicalDocument: () => deps.getEditorApi()?.getCanonicalDocument() ?? null,
25
26
  setCanonicalMode: (mode) => deps.getEditorApi()?.setCanonicalMode(mode) ?? false,
26
27
  resolveCanonicalRevisions: (revisionIds, resolution) => deps.getEditorApi()?.resolveCanonicalRevisions(revisionIds, resolution) ?? false,
@@ -1,4 +1,5 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
+ import { CanonicalGap } from "../types/canonicalCapabilities.js";
2
3
  import { HiddenEditorApi } from "./hiddenEditorApi.js";
3
4
  import { DirectEditorProps, EditorView } from "prosemirror-view";
4
5
  //#region src/controller/headerFooterEditorManager.d.ts
@@ -19,7 +20,7 @@ type HeaderFooterEditorManagerDeps = {
19
20
  getDocument: () => document_d_exports.Document | null;
20
21
  getCanonicalApi?: (() => HiddenEditorApi | null) | undefined;
21
22
  getExperimentalSession?: (() => "canonical" | undefined) | undefined;
22
- onSessionRefusal?: ((reason: string) => void) | undefined;
23
+ onSessionRefusal?: ((reason: string, gap: CanonicalGap) => void) | undefined;
23
24
  getHost: () => HTMLElement | null;
24
25
  getStyles: () => document_d_exports.StyleDefinitions | null | undefined;
25
26
  getTheme: () => document_d_exports.Theme | null | undefined;
@@ -10,6 +10,7 @@ import { createDocumentNumberingPlugin } from "../prosemirror/plugins/documentNu
10
10
  import { createDocumentStylesPlugin } from "../prosemirror/plugins/documentStyles.js";
11
11
  import { schema } from "../prosemirror/schema/index.js";
12
12
  import { storyListNumbering } from "../prosemirror/storyListNumbering.js";
13
+ import { CANONICAL_GAP, usesCanonicalSession } from "../types/canonicalCapabilities.js";
13
14
  import { createCanonicalStoryEditor } from "./canonicalStoryEditor.js";
14
15
  import { OP_STORIES } from "@stll/docx-core/ops";
15
16
  import { EditorState, TextSelection } from "prosemirror-state";
@@ -87,8 +88,8 @@ const createHeaderFooterEditorManager = (deps) => {
87
88
  const sync = () => {
88
89
  const host = deps.getHost();
89
90
  if (!host) return;
90
- if (deps.getExperimentalSession?.() === "canonical" && !deps.getCanonicalApi?.()?.getCanonicalStoryProjection(OP_STORIES.MAIN)) return;
91
- const document = deps.getExperimentalSession?.() === "canonical" ? deps.getCanonicalApi?.()?.getCanonicalDocument() ?? deps.getDocument() : deps.getDocument();
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();
92
93
  const styles = deps.getStyles();
93
94
  const theme = deps.getTheme();
94
95
  const numbering = document?.package.numbering;
@@ -104,7 +105,7 @@ const createHeaderFooterEditorManager = (deps) => {
104
105
  const existing = mounted.get(part.rId);
105
106
  if (existing) {
106
107
  if (existing.mountNode.parentElement !== host) host.append(existing.mountNode);
107
- if (deps.getExperimentalSession?.() === "canonical") {
108
+ if (usesCanonicalSession(deps.getExperimentalSession?.(), CANONICAL_GAP.secondaryStories)) {
108
109
  const projected = deps.getCanonicalApi?.()?.getCanonicalStoryProjection({
109
110
  kind: part.kind,
110
111
  rId: part.rId
@@ -154,7 +155,7 @@ const createHeaderFooterEditorManager = (deps) => {
154
155
  },
155
156
  getView: () => view,
156
157
  getApi: () => deps.getCanonicalApi?.() ?? null,
157
- enabled: () => deps.getExperimentalSession?.() === "canonical",
158
+ enabled: () => usesCanonicalSession(deps.getExperimentalSession?.(), CANONICAL_GAP.secondaryStories),
158
159
  onRefusal: deps.onSessionRefusal,
159
160
  onSelectionChange: () => deps.onTransaction?.({
160
161
  kind: part.kind,
@@ -164,11 +165,11 @@ const createHeaderFooterEditorManager = (deps) => {
164
165
  selectionChanged: true
165
166
  })
166
167
  });
167
- const canonicalProjection = deps.getExperimentalSession?.() === "canonical" ? deps.getCanonicalApi?.()?.getCanonicalStoryProjection({
168
+ const canonicalProjection = usesCanonicalSession(deps.getExperimentalSession?.(), CANONICAL_GAP.secondaryStories) ? deps.getCanonicalApi?.()?.getCanonicalStoryProjection({
168
169
  kind: part.kind,
169
170
  rId: part.rId
170
171
  }) : null;
171
- if (deps.getExperimentalSession?.() === "canonical" && !canonicalProjection) {
172
+ if (usesCanonicalSession(deps.getExperimentalSession?.(), CANONICAL_GAP.secondaryStories) && !canonicalProjection) {
172
173
  manager.destroy();
173
174
  mountNode.remove();
174
175
  continue;
@@ -217,7 +218,7 @@ const createHeaderFooterEditorManager = (deps) => {
217
218
  rId
218
219
  })),
219
220
  snapshotDocument: (document) => {
220
- if (deps.getExperimentalSession?.() === "canonical") return deps.getCanonicalApi?.()?.getCanonicalDocument() ?? document;
221
+ if (usesCanonicalSession(deps.getExperimentalSession?.(), CANONICAL_GAP.secondaryStories)) return deps.getCanonicalApi?.()?.getCanonicalDocument() ?? document;
221
222
  let headers = document.package.headers;
222
223
  let footers = document.package.footers;
223
224
  let headersChanged = false;
@@ -1,6 +1,9 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
- import { createCanonicalInputBoundary } from "./canonicalInput.js";
2
+ import { FolioDocumentOperationResult, FolioDocumentOperationUndoHandle, FolioDocumentOperationUndoResult } from "../document-operations.js";
3
+ import { CanonicalSaveSnapshot } from "../types/canonicalSave.js";
3
4
  import { CanonicalSessionMode } from "./canonicalSession.js";
5
+ import { createCanonicalInputBoundary } from "./canonicalInput.js";
6
+ import { CanonicalPublicOperationOptions } from "./canonicalPublicOperations.js";
4
7
  import { Node } from "prosemirror-model";
5
8
  import { Command, EditorState, Transaction } from "prosemirror-state";
6
9
  import { EditorView } from "prosemirror-view";
@@ -29,6 +32,9 @@ type HiddenEditorApi = {
29
32
  getDocument: () => document_d_exports.Document | null;
30
33
  /** Canonical snapshot, or null in the default session. */
31
34
  getCanonicalDocument: () => document_d_exports.Document | null;
35
+ captureCanonicalSave: () => CanonicalSaveSnapshot | null;
36
+ /** A save cannot acknowledge newer edits or an unfinished composition. */
37
+ isCanonicalSaveCurrent: (version: number) => boolean;
32
38
  /** Set the explicit mode for an active canonical session. */
33
39
  setCanonicalMode: (mode: CanonicalSessionMode) => boolean;
34
40
  /** Resolve canonical revisions as one batch and journal its exact inverse. */
@@ -42,6 +48,8 @@ type HiddenEditorApi = {
42
48
  } | null;
43
49
  getCanonicalStoryProjection: (story: OpStory) => Node | null;
44
50
  replaceCanonicalStoryText: (options: CanonicalStoryTextOptions) => boolean;
51
+ applyCanonicalDocumentOperations: (options: CanonicalPublicOperationOptions) => FolioDocumentOperationResult | null;
52
+ undoCanonicalDocumentOperations: (handle: FolioDocumentOperationUndoHandle) => FolioDocumentOperationUndoResult | null;
45
53
  /** Focus the hidden editor */
46
54
  focus: () => void;
47
55
  /** Blur the hidden editor */
@@ -73,11 +81,13 @@ type HiddenEditorApiDeps = {
73
81
  getView: () => EditorView | null;
74
82
  getDocumentContext: () => document_d_exports.Document | null;
75
83
  getCanonicalDocument?: () => document_d_exports.Document | null;
84
+ captureCanonicalSave?: HiddenEditorApi["captureCanonicalSave"];
85
+ isCanonicalSaveCurrent?: HiddenEditorApi["isCanonicalSaveCurrent"];
76
86
  setCanonicalMode?: HiddenEditorApi["setCanonicalMode"];
77
87
  resolveCanonicalRevisions?: HiddenEditorApi["resolveCanonicalRevisions"];
78
88
  executeCanonicalCommand?: (command: Command) => boolean | undefined;
79
89
  getCanonicalHistory?: () => Pick<HiddenEditorApi, "undo" | "redo" | "canUndo" | "canRedo"> | null;
80
- canonicalOperations?: Pick<HiddenEditorApi, "updateCanonicalInputLifecycle" | "applyCanonicalOperations" | "getCanonicalStoryProjection" | "replaceCanonicalStoryText" | "applyCanonicalStoryHistory" | "getCanonicalStorySelection">;
90
+ canonicalOperations?: Pick<HiddenEditorApi, "updateCanonicalInputLifecycle" | "applyCanonicalOperations" | "getCanonicalStoryProjection" | "replaceCanonicalStoryText" | "applyCanonicalStoryHistory" | "getCanonicalStorySelection" | "applyCanonicalDocumentOperations" | "undoCanonicalDocumentOperations">;
81
91
  isDestroying: () => boolean;
82
92
  ensureView: () => void;
83
93
  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
+ captureCanonicalSave: () => deps.captureCanonicalSave?.() ?? null,
38
+ isCanonicalSaveCurrent: (version) => deps.isCanonicalSaveCurrent?.(version) ?? false,
37
39
  getCanonicalDocument: () => {
38
40
  const canonical = deps.getCanonicalDocument?.();
39
41
  return canonical ? cloneDocumentWithParagraphPropertySources(canonical) : null;
@@ -51,6 +53,8 @@ const createHiddenEditorApi = (deps) => {
51
53
  getCanonicalStorySelection: (story) => deps.canonicalOperations?.getCanonicalStorySelection(story) ?? null,
52
54
  getCanonicalStoryProjection: (story) => deps.canonicalOperations?.getCanonicalStoryProjection(story) ?? null,
53
55
  replaceCanonicalStoryText: (options) => deps.canonicalOperations?.replaceCanonicalStoryText(options) ?? false,
56
+ applyCanonicalDocumentOperations: (options) => deps.canonicalOperations?.applyCanonicalDocumentOperations(options) ?? null,
57
+ undoCanonicalDocumentOperations: (handle) => deps.canonicalOperations?.undoCanonicalDocumentOperations(handle) ?? null,
54
58
  focus: () => {
55
59
  const view = deps.getView();
56
60
  if (!view || view.hasFocus()) return;
@@ -1,4 +1,5 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
+ import { CanonicalGap } from "../types/canonicalCapabilities.js";
2
3
  import { HiddenEditorApi } from "./hiddenEditorApi.js";
3
4
  import { HiddenEditorStateReason } from "../layout-engine/layoutInstrumentation.js";
4
5
  import { ExtensionManager } from "../prosemirror/extensions/ExtensionManager.js";
@@ -12,6 +13,7 @@ import * as YProseMirror from "y-prosemirror";
12
13
  //#region src/controller/hiddenEditorManager.d.ts
13
14
  declare const CanonicalSessionRefusalError_base: import("better-result").TaggedErrorClass<"CanonicalSessionRefusalError">;
14
15
  declare class CanonicalSessionRefusalError extends CanonicalSessionRefusalError_base<{
16
+ gap: CanonicalGap;
15
17
  message: string;
16
18
  }> {}
17
19
  type YProseMirrorModule = typeof YProseMirror;
@@ -63,7 +65,7 @@ type HiddenEditorManagerDeps = {
63
65
  getExperimentalSession?: () => "canonical" | undefined;
64
66
  getEditingMode?: () => EditorMode;
65
67
  getSuggestionAuthor?: () => string;
66
- onSessionRefusal?: (reason: string) => void;
68
+ onSessionRefusal?: (reason: string, gap: CanonicalGap) => void;
67
69
  /**
68
70
  * Identity of the loaded document as tracked by the adapter's loader: the
69
71
  * same value across internal edits (so typing does not trigger an external