@stll/folio-core 0.54.2 → 0.55.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 (199) hide show
  1. package/dist/ai-edits/apply.js +22 -1
  2. package/dist/ai-edits/headless.js +1 -0
  3. package/dist/compare/content-types.d.ts +3 -15
  4. package/dist/controller/canonicalComposition.d.ts +36 -0
  5. package/dist/controller/canonicalComposition.js +168 -0
  6. package/dist/controller/canonicalInput.d.ts +45 -0
  7. package/dist/controller/canonicalInput.js +338 -0
  8. package/dist/controller/canonicalOperations.d.ts +22 -0
  9. package/dist/controller/canonicalOperations.js +85 -0
  10. package/dist/controller/canonicalReview.d.ts +12 -0
  11. package/dist/controller/canonicalReview.js +19 -0
  12. package/dist/controller/canonicalSession.d.ts +149 -0
  13. package/dist/controller/canonicalSession.js +1038 -0
  14. package/dist/controller/canonicalStoryEditor.d.ts +34 -0
  15. package/dist/controller/canonicalStoryEditor.js +82 -0
  16. package/dist/controller/canonicalStructure.d.ts +16 -0
  17. package/dist/controller/canonicalStructure.js +269 -0
  18. package/dist/controller/folioEditor.d.ts +1 -1
  19. package/dist/controller/folioEditor.js +9 -0
  20. package/dist/controller/headerFooterEditorManager.d.ts +4 -0
  21. package/dist/controller/headerFooterEditorManager.js +71 -3
  22. package/dist/controller/hiddenEditorApi.d.ts +35 -0
  23. package/dist/controller/hiddenEditorApi.js +25 -8
  24. package/dist/controller/hiddenEditorManager.d.ts +11 -2
  25. package/dist/controller/hiddenEditorManager.js +456 -30
  26. package/dist/controller/layoutPipeline.d.ts +1 -1
  27. package/dist/controller/layoutPipeline.js +2 -0
  28. package/dist/controller/noteEditorManager.d.ts +4 -0
  29. package/dist/controller/noteEditorManager.js +113 -15
  30. package/dist/docx/attributeRemainder.js +1 -0
  31. package/dist/docx/canonicalSessionInput.d.ts +11 -0
  32. package/dist/docx/canonicalSessionInput.js +27 -0
  33. package/dist/docx/canonicalStoryRepack.d.ts +10 -0
  34. package/dist/docx/canonicalStoryRepack.js +41 -0
  35. package/dist/docx/contentTypeRegistry.d.ts +8 -0
  36. package/dist/docx/contentTypeRegistry.js +64 -0
  37. package/dist/docx/ensureParaIds.d.ts +7 -1
  38. package/dist/docx/ensureParaIds.js +163 -211
  39. package/dist/docx/foldedListNumberFields.d.ts +85 -0
  40. package/dist/docx/foldedListNumberFields.js +267 -0
  41. package/dist/docx/headerFooterVerbatim.d.ts +14 -1
  42. package/dist/docx/headerFooterVerbatim.js +95 -2
  43. package/dist/docx/noteIds.d.ts +5 -0
  44. package/dist/docx/noteIds.js +6 -2
  45. package/dist/docx/noteReferenceMark.d.ts +3 -1
  46. package/dist/docx/noteReferenceMark.js +15 -14
  47. package/dist/docx/numberingParser.d.ts +3 -1
  48. package/dist/docx/numberingParser.js +17 -3
  49. package/dist/docx/packageParts.d.ts +3 -1
  50. package/dist/docx/packageParts.js +65 -16
  51. package/dist/docx/paragraphMarkPropertyPresence.d.ts +9 -0
  52. package/dist/docx/paragraphMarkPropertyPresence.js +21 -0
  53. package/dist/docx/paragraphParser.js +101 -53
  54. package/dist/docx/paragraphPropertySource.d.ts +15 -1
  55. package/dist/docx/paragraphPropertySource.js +76 -22
  56. package/dist/docx/parser.js +2 -1
  57. package/dist/docx/reviewHistoryNamespace.d.ts +4 -0
  58. package/dist/docx/reviewHistoryNamespace.js +4 -0
  59. package/dist/docx/reviewResolutionProvenance.d.ts +25 -0
  60. package/dist/docx/reviewResolutionProvenance.js +153 -0
  61. package/dist/docx/rezip.d.ts +7 -3
  62. package/dist/docx/rezip.js +107 -45
  63. package/dist/docx/runConsolidator.d.ts +2 -58
  64. package/dist/docx/runConsolidator.js +3 -175
  65. package/dist/docx/runParser.d.ts +2 -1
  66. package/dist/docx/runParser.js +156 -116
  67. package/dist/docx/saveDiagnostics.d.ts +11 -0
  68. package/dist/docx/saveDiagnostics.js +0 -0
  69. package/dist/docx/selectiveSave.d.ts +2 -1
  70. package/dist/docx/selectiveSave.js +4 -1
  71. package/dist/docx/selectiveXmlPatch.d.ts +16 -8
  72. package/dist/docx/selectiveXmlPatch.js +74 -2
  73. package/dist/docx/serializer/headerFooterSerializer.d.ts +5 -1
  74. package/dist/docx/serializer/headerFooterSerializer.js +19 -11
  75. package/dist/docx/serializer/noteSerializer.d.ts +2 -2
  76. package/dist/docx/serializer/noteSerializer.js +24 -8
  77. package/dist/docx/serializer/paragraphSerializer.js +41 -4
  78. package/dist/docx/serializer/partNamespaces.js +2 -1
  79. package/dist/docx/serializer/runSerializer.js +3 -1
  80. package/dist/docx/settingsHeaderFooterUpdate.d.ts +5 -0
  81. package/dist/docx/settingsHeaderFooterUpdate.js +90 -0
  82. package/dist/docx/settingsParser.d.ts +1 -2
  83. package/dist/docx/settingsParser.js +5 -6
  84. package/dist/docx/storyBlockReplay.d.ts +11 -0
  85. package/dist/docx/storyBlockReplay.js +367 -0
  86. package/dist/docx/streamingXmlParser.d.ts +22 -1
  87. package/dist/docx/streamingXmlParser.js +49 -3
  88. package/dist/docx/structuralXmlPatch.js +8 -1
  89. package/dist/docx/verbatimCapture.d.ts +3 -1
  90. package/dist/docx/verbatimCapture.js +49 -6
  91. package/dist/i18n/messages/ar.gen.d.ts +5 -0
  92. package/dist/i18n/messages/ar.gen.js +656 -0
  93. package/dist/i18n/messages/cs.gen.d.ts +5 -0
  94. package/dist/i18n/messages/cs.gen.js +656 -0
  95. package/dist/i18n/messages/de.gen.d.ts +5 -0
  96. package/dist/i18n/messages/de.gen.js +656 -0
  97. package/dist/i18n/messages/en.gen.d.ts +5 -0
  98. package/dist/i18n/messages/en.gen.js +656 -0
  99. package/dist/i18n/messages/es.gen.d.ts +5 -0
  100. package/dist/i18n/messages/es.gen.js +656 -0
  101. package/dist/i18n/messages/et.gen.d.ts +5 -0
  102. package/dist/i18n/messages/et.gen.js +656 -0
  103. package/dist/i18n/messages/fr.gen.d.ts +5 -0
  104. package/dist/i18n/messages/fr.gen.js +656 -0
  105. package/dist/i18n/messages/he.gen.d.ts +5 -0
  106. package/dist/i18n/messages/he.gen.js +656 -0
  107. package/dist/i18n/messages/hi.gen.d.ts +5 -0
  108. package/dist/i18n/messages/hi.gen.js +656 -0
  109. package/dist/i18n/messages/hu.gen.d.ts +5 -0
  110. package/dist/i18n/messages/hu.gen.js +656 -0
  111. package/dist/i18n/messages/locales.d.ts +5 -0
  112. package/dist/i18n/messages/locales.js +22 -0
  113. package/dist/i18n/messages/lt.gen.d.ts +5 -0
  114. package/dist/i18n/messages/lt.gen.js +656 -0
  115. package/dist/i18n/messages/lv.gen.d.ts +5 -0
  116. package/dist/i18n/messages/lv.gen.js +656 -0
  117. package/dist/i18n/messages/pl.gen.d.ts +5 -0
  118. package/dist/i18n/messages/pl.gen.js +656 -0
  119. package/dist/i18n/messages/pt-BR.gen.d.ts +5 -0
  120. package/dist/i18n/messages/pt-BR.gen.js +656 -0
  121. package/dist/i18n/messages/sk.gen.d.ts +5 -0
  122. package/dist/i18n/messages/sk.gen.js +656 -0
  123. package/dist/i18n/messages/tr.gen.d.ts +5 -0
  124. package/dist/i18n/messages/tr.gen.js +656 -0
  125. package/dist/i18n/messages/zh-CN.gen.d.ts +5 -0
  126. package/dist/i18n/messages/zh-CN.gen.js +656 -0
  127. package/dist/i18n/messages.d.ts +2 -3
  128. package/dist/i18n/messages.js +1 -19
  129. package/dist/internal/noteMarkerAttrs.d.ts +8 -0
  130. package/dist/internal/noteMarkerAttrs.js +16 -0
  131. package/dist/internal/pageBreakRunSourceDescendantIndex.js +1 -0
  132. package/dist/internal/prosemirrorAttrBoundary.d.ts +21 -0
  133. package/dist/internal/prosemirrorAttrBoundary.js +50 -0
  134. package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
  135. package/dist/layout-bridge/convert/footnoteLayout.js +3 -3
  136. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
  137. package/dist/layout-bridge/convert/headerFooterLayout.js +1 -0
  138. package/dist/layout-bridge/convert/noteReferences.d.ts +1 -3
  139. package/dist/layout-bridge/convert/noteReferences.js +1 -6
  140. package/dist/layout-bridge/convert/paragraphRuns.js +2 -4
  141. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +2 -2
  142. package/dist/layout-bridge/convert/toFlowBlocks.js +2 -2
  143. package/dist/managers/DocumentLoaderManager.d.ts +1 -0
  144. package/dist/managers/DocumentLoaderManager.js +8 -1
  145. package/dist/markdown/renderBlock.js +15 -1
  146. package/dist/markdown/renderParagraph.js +9 -13
  147. package/dist/markdown/renderRuns.js +3 -2
  148. package/dist/markdown/renderTable.js +2 -0
  149. package/dist/markdown/types.d.ts +3 -9
  150. package/dist/prosemirror/attrs/index.d.ts +2 -12
  151. package/dist/prosemirror/attrs/index.js +22 -48
  152. package/dist/prosemirror/bookmarkBoundaryAttrs.d.ts +2 -1
  153. package/dist/prosemirror/canonicalCommands.d.ts +97 -0
  154. package/dist/prosemirror/canonicalCommands.js +28 -0
  155. package/dist/prosemirror/commands/pageBreak.js +15 -1
  156. package/dist/prosemirror/commentReferenceAttrs.d.ts +2 -1
  157. package/dist/prosemirror/conversion/fromProseDoc.js +91 -19
  158. package/dist/prosemirror/conversion/listRenderingDefinition.d.ts +6 -0
  159. package/dist/prosemirror/conversion/listRenderingDefinition.js +97 -0
  160. package/dist/prosemirror/conversion/toProseDoc.d.ts +4 -3
  161. package/dist/prosemirror/conversion/toProseDoc.js +51 -11
  162. package/dist/prosemirror/executeEditorCommand.d.ts +11 -0
  163. package/dist/prosemirror/executeEditorCommand.js +26 -0
  164. package/dist/prosemirror/extensions/StarterKit.js +2 -0
  165. package/dist/prosemirror/extensions/core/ParagraphExtension.js +50 -19
  166. package/dist/prosemirror/extensions/features/ListExtension.js +18 -5
  167. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +5 -2
  168. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +13 -1
  169. package/dist/prosemirror/extensions/marks/TextColorExtension.js +3 -2
  170. package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +2 -0
  171. package/dist/prosemirror/extensions/marks/markUtils.js +70 -8
  172. package/dist/prosemirror/extensions/nodes/NoteMarkerExtension.d.ts +6 -0
  173. package/dist/prosemirror/extensions/nodes/NoteMarkerExtension.js +36 -0
  174. package/dist/prosemirror/extensions/nodes/PreservedXmlExtension.js +4 -2
  175. package/dist/prosemirror/listMarker.d.ts +1 -0
  176. package/dist/prosemirror/listMarker.js +16 -7
  177. package/dist/prosemirror/listNumbering.js +26 -5
  178. package/dist/prosemirror/listRenderingAttrs.d.ts +1 -0
  179. package/dist/prosemirror/listRenderingAttrs.js +3 -0
  180. package/dist/prosemirror/markupViewNotes.js +3 -2
  181. package/dist/prosemirror/moveRangeBoundaryAttrs.d.ts +2 -1
  182. package/dist/prosemirror/noteReferenceReview.js +5 -3
  183. package/dist/prosemirror/plugins/suggestionMode.js +50 -2
  184. package/dist/prosemirror/rangeAnchorAttrs.d.ts +2 -1
  185. package/dist/prosemirror/runFormattingFromMarks.d.ts +4 -1
  186. package/dist/prosemirror/runFormattingFromMarks.js +15 -1
  187. package/dist/prosemirror/runFormattingInlineCarriers.d.ts +2 -1
  188. package/dist/prosemirror/runFormattingInlineCarriers.js +4 -1
  189. package/dist/prosemirror/schema/marks.d.ts +2 -0
  190. package/dist/prosemirror/schema/nodes.d.ts +10 -2
  191. package/dist/prosemirror/textBoxAnchorAttrs.d.ts +2 -1
  192. package/dist/prosemirror/textInput.js +5 -1
  193. package/dist/prosemirror/trackedRevisionPath.js +2 -1
  194. package/dist/prosemirror/trackedRunInlineAtoms.d.ts +1 -0
  195. package/dist/prosemirror/trackedRunInlineAtoms.js +1 -0
  196. package/dist/prosemirror/validation.js +4 -0
  197. package/dist/types/content.d.ts +2 -2
  198. package/dist/utils/createDocument.js +2 -2
  199. package/package.json +14 -2
@@ -0,0 +1,34 @@
1
+ import { HiddenEditorApi } from "./hiddenEditorApi.js";
2
+ import { Transaction } from "prosemirror-state";
3
+ import { EditorView } from "prosemirror-view";
4
+ import { OpStory } from "@stll/docx-core/ops";
5
+ //#region src/controller/canonicalStoryEditor.d.ts
6
+ type CanonicalStoryEditorOptions = {
7
+ story: OpStory;
8
+ getView: () => EditorView;
9
+ getApi: () => HiddenEditorApi | null;
10
+ enabled: () => boolean;
11
+ onSelectionChange: () => void;
12
+ onRefusal: ((reason: string) => void) | undefined;
13
+ };
14
+ /** Secondary views compile classified native input into the shared Document journal. */
15
+ declare const createCanonicalStoryEditor: ({ story, getView, getApi, enabled, onRefusal, onSelectionChange }: CanonicalStoryEditorOptions) => {
16
+ dispatch: (transaction: Transaction) => boolean;
17
+ props: {
18
+ handleKeyDown: (view: EditorView, event: KeyboardEvent) => boolean;
19
+ handleTextInput: (view: EditorView, from: number, to: number, text: string) => boolean;
20
+ handleDOMEvents: {
21
+ beforeinput: (view: EditorView, event: InputEvent) => boolean;
22
+ mousedown: (view: EditorView) => boolean;
23
+ compositionstart: (view: EditorView) => boolean;
24
+ compositionend: (view: EditorView) => boolean;
25
+ input: (view: EditorView) => boolean;
26
+ blur: (view: EditorView) => boolean;
27
+ paste: (view: EditorView, event: Event) => boolean;
28
+ cut: (view: EditorView, event: Event) => boolean;
29
+ drop: (view: EditorView, event: Event) => boolean;
30
+ };
31
+ };
32
+ };
33
+ //#endregion
34
+ export { createCanonicalStoryEditor };
@@ -0,0 +1,82 @@
1
+ import { createCanonicalInputBoundary } from "./canonicalInput.js";
2
+ import { CanonicalSessionError } from "./canonicalSession.js";
3
+ //#region src/controller/canonicalStoryEditor.ts
4
+ /** Secondary views compile classified native input into the shared Document journal. */
5
+ const createCanonicalStoryEditor = ({ story, getView, getApi, enabled, onRefusal, onSelectionChange }) => {
6
+ const refuse = (message) => {
7
+ if (onRefusal) onRefusal(message);
8
+ else throw new CanonicalSessionError({
9
+ message,
10
+ reason: "refused"
11
+ });
12
+ };
13
+ const history = (direction) => {
14
+ const view = getView();
15
+ const accepted = getApi()?.applyCanonicalStoryHistory({
16
+ view,
17
+ story,
18
+ direction
19
+ }) ?? false;
20
+ if (accepted && !view.isDestroyed) onSelectionChange();
21
+ return accepted;
22
+ };
23
+ const boundary = createCanonicalInputBoundary({
24
+ breakUndoGroup: () => {
25
+ getApi()?.updateCanonicalInputLifecycle("breakUndoGroup");
26
+ },
27
+ beginComposition: () => getApi()?.updateCanonicalInputLifecycle("beginComposition") ?? false,
28
+ endComposition: () => {
29
+ getApi()?.updateCanonicalInputLifecycle("endComposition");
30
+ },
31
+ replace: (intent) => {
32
+ const view = getView();
33
+ if (getApi()?.replaceCanonicalStoryText({
34
+ view,
35
+ story,
36
+ intent
37
+ }) && !view.isDestroyed) onSelectionChange();
38
+ },
39
+ undo: () => history("undo"),
40
+ redo: () => history("redo"),
41
+ refuse
42
+ });
43
+ return {
44
+ dispatch: (transaction) => {
45
+ if (!enabled()) return false;
46
+ const view = getView();
47
+ if (transaction.docChanged) {
48
+ if (boundary.acceptComposition(view, transaction)) return true;
49
+ if (!boundary.commitNativeProposal(view, transaction)) boundary.refuseNativeMutation(view);
50
+ view.updateState(view.state);
51
+ return true;
52
+ }
53
+ if (transaction.selectionSet && !boundary.isComposing) getApi()?.updateCanonicalInputLifecycle("breakUndoGroup");
54
+ const applied = view.state.applyTransaction(transaction);
55
+ if (!applied.state.doc.eq(view.state.doc)) {
56
+ refuse("A plugin attempted an unclassified canonical story mutation.");
57
+ view.updateState(view.state);
58
+ return true;
59
+ }
60
+ view.updateState(applied.state);
61
+ if (applied.transactions.some((step) => step.selectionSet)) onSelectionChange();
62
+ return true;
63
+ },
64
+ props: {
65
+ handleKeyDown: (view, event) => enabled() && boundary.handleKeyDown(view, event),
66
+ handleTextInput: (view, from, to, text) => enabled() && boundary.handleTextInput(view, from, to, text),
67
+ handleDOMEvents: {
68
+ beforeinput: (view, event) => enabled() && boundary.handleDOMEvents.beforeinput(view, event),
69
+ mousedown: (view) => enabled() && boundary.handleDOMEvents.mousedown(view),
70
+ compositionstart: (view) => enabled() && boundary.handleDOMEvents.compositionstart(view),
71
+ compositionend: (view) => enabled() && boundary.handleDOMEvents.compositionend(view),
72
+ input: (view) => enabled() && boundary.handleDOMEvents.input(view),
73
+ blur: (view) => enabled() && boundary.handleDOMEvents.blur(view),
74
+ paste: (view, event) => enabled() && boundary.handleDOMEvents.paste(view, event),
75
+ cut: (view, event) => enabled() && boundary.handleDOMEvents.cut(view, event),
76
+ drop: (view, event) => enabled() && boundary.handleDOMEvents.drop(view, event)
77
+ }
78
+ }
79
+ };
80
+ };
81
+ //#endregion
82
+ export { createCanonicalStoryEditor };
@@ -0,0 +1,16 @@
1
+ import { CanonicalCommit, CanonicalSession, CanonicalSessionError } from "./canonicalSession.js";
2
+ import { CanonicalCommandIntent } from "../prosemirror/canonicalCommands.js";
3
+ import { EditorState } from "prosemirror-state";
4
+ import { Result } from "better-result";
5
+ //#region src/controller/canonicalStructure.d.ts
6
+ /** Toolbar meanings enter the same operation journal as native input. */
7
+ declare const prepareCanonicalCommands: (session: CanonicalSession, state: EditorState, commands: readonly CanonicalCommandIntent[]) => Result<CanonicalCommit, CanonicalSessionError>;
8
+ type CanonicalAutoformatInput = {
9
+ from: number;
10
+ to: number;
11
+ text: string;
12
+ };
13
+ /** Rules compile their replacement and paragraph change as one atomic edit. */
14
+ declare const prepareCanonicalAutoformat: (session: CanonicalSession, state: EditorState, { from, to, text }: CanonicalAutoformatInput) => Result<CanonicalCommit, CanonicalSessionError> | undefined;
15
+ //#endregion
16
+ export { prepareCanonicalAutoformat, prepareCanonicalCommands };
@@ -0,0 +1,269 @@
1
+ import { getCachedNumberingMap, numberingLevelUsesBulletMarker } from "../docx/numberingParser.js";
2
+ import { listRequestsForMarker } from "../prosemirror/listAutoformatMarkers.js";
3
+ import { CanonicalSessionError } from "./canonicalSession.js";
4
+ import { Result } from "better-result";
5
+ import { paragraphNumberingReference } from "@stll/docx-core/model";
6
+ import { OP_STORIES } from "@stll/docx-core/ops";
7
+ //#region src/controller/canonicalStructure.ts
8
+ const refuse = (message) => Result.err(new CanonicalSessionError({
9
+ message,
10
+ reason: "refused"
11
+ }));
12
+ const selectedParagraphs = (session, state) => {
13
+ const ids = /* @__PURE__ */ new Set();
14
+ state.doc.nodesBetween(state.selection.from, state.selection.to, (node) => {
15
+ if (node.type.name === "paragraph" && typeof node.attrs["paraId"] === "string") ids.add(node.attrs["paraId"]);
16
+ });
17
+ return session.document.package.document.content.filter((paragraph) => paragraph.type === "paragraph" && ids.has(paragraph.paraId ?? ""));
18
+ };
19
+ const numberingIntent = (session, paragraphs, request) => {
20
+ const definitions = session.document.package.numbering;
21
+ const map = definitions === void 0 ? void 0 : getCachedNumberingMap(definitions);
22
+ const body = session.document.package.document.content;
23
+ const first = body.findIndex((item) => item === paragraphs.at(0));
24
+ const last = body.findIndex((item) => item === paragraphs.at(-1));
25
+ const neighbor = [first > 0 ? body.at(first - 1) : void 0, body.at(last + 1)].find((item) => {
26
+ if (item?.type !== "paragraph" || item.formatting?.numPr?.kind !== "reference" || item.formatting.numPrFromStyle !== void 0) return false;
27
+ const level = map?.getLevel(item.formatting.numPr.numId, item.formatting.numPr.ilvl ?? 0);
28
+ if (level == null || numberingLevelUsesBulletMarker(level) !== (request.kind === "bullet")) return false;
29
+ return request.format === void 0 || level.numFmt === request.format.numFmt && level.lvlText === request.format.lvlText;
30
+ });
31
+ let numId;
32
+ let ilvl = 0;
33
+ let target;
34
+ if (neighbor?.type === "paragraph" && neighbor.formatting?.numPr?.kind === "reference") {
35
+ numId = neighbor.formatting.numPr.numId;
36
+ ilvl = neighbor.formatting.numPr.ilvl ?? 0;
37
+ target = {
38
+ type: "existing",
39
+ numId
40
+ };
41
+ } else {
42
+ numId = Math.max(0, ...definitions?.nums.map((num) => num.numId) ?? []) + 1;
43
+ const abstractNumId = Math.max(-1, ...definitions?.abstractNums.map((num) => num.abstractNumId) ?? []) + 1;
44
+ const levels = Array.from({ length: 9 }, (_, level) => ({
45
+ ilvl: level,
46
+ start: level === 0 ? request.start ?? 1 : 1,
47
+ numFmt: request.kind === "bullet" ? "bullet" : request.format?.numFmt ?? "decimal",
48
+ lvlText: request.kind === "bullet" ? "•" : (request.format?.lvlText ?? "%1.").replaceAll("%1", `%${level + 1}`),
49
+ suffix: "tab",
50
+ pPr: {
51
+ indentLeft: (level + 1) * 720,
52
+ indentFirstLine: 360,
53
+ hangingIndent: true
54
+ }
55
+ }));
56
+ target = {
57
+ type: "new",
58
+ num: {
59
+ numId,
60
+ abstractNumId
61
+ },
62
+ abstractNum: {
63
+ abstractNumId,
64
+ multiLevelType: "multilevel",
65
+ levels
66
+ }
67
+ };
68
+ }
69
+ return {
70
+ type: "setList",
71
+ target,
72
+ items: paragraphs.map((paragraph) => ({
73
+ at: {
74
+ story: OP_STORIES.MAIN,
75
+ blockId: paragraph.paraId ?? "",
76
+ offset: 0
77
+ },
78
+ ilvl
79
+ }))
80
+ };
81
+ };
82
+ /** Toolbar meanings enter the same operation journal as native input. */
83
+ const prepareCanonicalCommands = (session, state, commands) => {
84
+ const intents = [];
85
+ const paragraphs = selectedParagraphs(session, state);
86
+ for (const command of commands) switch (command.type) {
87
+ case "insertBreak": {
88
+ const from = session.projection.addressAt(command.from);
89
+ if (from.isErr()) return from;
90
+ const to = session.projection.addressAt(command.to);
91
+ if (to.isErr()) return to;
92
+ intents.push({
93
+ type: "insertAtom",
94
+ from: from.value,
95
+ to: to.value,
96
+ atom: {
97
+ type: "break",
98
+ breakType: command.breakType
99
+ }
100
+ });
101
+ break;
102
+ }
103
+ case "formatRun": {
104
+ const from = session.projection.addressAt(command.from);
105
+ if (from.isErr()) return from;
106
+ const to = session.projection.addressAt(command.to);
107
+ if (to.isErr()) return to;
108
+ intents.push({
109
+ type: "formatRun",
110
+ from: from.value,
111
+ to: to.value,
112
+ patch: command.patch
113
+ });
114
+ break;
115
+ }
116
+ case "formatParagraph": {
117
+ const at = session.projection.addressAt(command.at);
118
+ if (at.isErr()) return at;
119
+ intents.push({
120
+ type: "formatParagraph",
121
+ at: at.value,
122
+ patch: command.patch
123
+ });
124
+ break;
125
+ }
126
+ case "toggleList": {
127
+ const definitions = session.document.package.numbering;
128
+ const map = definitions === void 0 ? void 0 : getCachedNumberingMap(definitions);
129
+ if (!(paragraphs.length > 0 && paragraphs.every(({ formatting }) => {
130
+ const numPr = formatting?.numPr;
131
+ return numPr?.kind === "reference" && numberingLevelUsesBulletMarker(map?.getLevel(numPr.numId, numPr.ilvl ?? 0)) === (command.kind === "bullet");
132
+ }))) {
133
+ intents.push(numberingIntent(session, paragraphs, { kind: command.kind === "bullet" ? "bullet" : "numbered" }));
134
+ break;
135
+ }
136
+ for (const paragraph of paragraphs) intents.push({
137
+ type: "formatParagraph",
138
+ at: {
139
+ story: OP_STORIES.MAIN,
140
+ blockId: paragraph.paraId ?? "",
141
+ offset: 0
142
+ },
143
+ patch: { numPr: { kind: "none" } }
144
+ });
145
+ break;
146
+ }
147
+ case "removeList":
148
+ for (const paragraph of paragraphs) intents.push({
149
+ type: "formatParagraph",
150
+ at: {
151
+ story: OP_STORIES.MAIN,
152
+ blockId: paragraph.paraId ?? "",
153
+ offset: 0
154
+ },
155
+ patch: { numPr: { kind: "none" } }
156
+ });
157
+ break;
158
+ case "changeListLevel":
159
+ for (const paragraph of paragraphs) {
160
+ const numPr = paragraph.formatting?.numPr;
161
+ if (numPr?.kind !== "reference") continue;
162
+ const at = {
163
+ story: OP_STORIES.MAIN,
164
+ blockId: paragraph.paraId ?? "",
165
+ offset: 0
166
+ };
167
+ const level = (numPr.ilvl ?? 0) + (command.direction === "increase" ? 1 : -1);
168
+ if (level > 8) return refuse("The list is already at its deepest level.");
169
+ intents.push({
170
+ type: "formatParagraph",
171
+ at,
172
+ patch: { numPr: level < 0 ? { kind: "none" } : paragraphNumberingReference({
173
+ numId: numPr.numId,
174
+ ilvl: level
175
+ }) }
176
+ });
177
+ }
178
+ break;
179
+ case "restartNumbering":
180
+ case "continueNumbering": {
181
+ const source = paragraphs.at(0);
182
+ const numPr = source?.formatting?.numPr;
183
+ const definitions = session.document.package.numbering;
184
+ if (source === void 0 || numPr?.kind !== "reference" || definitions === void 0) return refuse("Numbering changes require a list paragraph.");
185
+ const body = session.document.package.document.content;
186
+ const index = body.findIndex((item) => item === source);
187
+ const affected = body.slice(index).filter((item) => item.type === "paragraph" && item.formatting?.numPr?.kind === "reference" && item.formatting.numPr.numId === numPr.numId);
188
+ let targetId;
189
+ let target;
190
+ if (command.type === "continueNumbering") {
191
+ const map = getCachedNumberingMap(definitions);
192
+ const bullet = numberingLevelUsesBulletMarker(map.getLevel(numPr.numId, numPr.ilvl ?? 0));
193
+ 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);
194
+ if (earlier?.type !== "paragraph" || earlier.formatting?.numPr?.kind !== "reference") return refuse("There is no preceding compatible list.");
195
+ targetId = earlier.formatting.numPr.numId;
196
+ target = {
197
+ type: "existing",
198
+ numId: targetId
199
+ };
200
+ } else {
201
+ const start = command.start ?? 1;
202
+ if (!Number.isSafeInteger(start) || start < 0) return refuse("The numbering start must be a non-negative integer.");
203
+ const original = definitions.nums.find((num) => num.numId === numPr.numId);
204
+ if (original === void 0) return refuse("The numbering instance does not exist.");
205
+ targetId = Math.max(0, ...definitions.nums.map((num) => num.numId)) + 1;
206
+ const level = numPr.ilvl ?? 0;
207
+ target = {
208
+ type: "new",
209
+ num: {
210
+ ...original,
211
+ numId: targetId,
212
+ levelOverrides: [...(original.levelOverrides ?? []).filter(({ ilvl }) => ilvl !== level), {
213
+ ...original.levelOverrides?.find(({ ilvl }) => ilvl === level),
214
+ ilvl: level,
215
+ startOverride: start
216
+ }]
217
+ }
218
+ };
219
+ }
220
+ intents.push({
221
+ type: "setList",
222
+ target,
223
+ items: affected.map((paragraph) => ({
224
+ at: {
225
+ story: OP_STORIES.MAIN,
226
+ blockId: paragraph.paraId ?? "",
227
+ offset: 0
228
+ },
229
+ ilvl: paragraph.formatting?.numPr?.kind === "reference" ? paragraph.formatting.numPr.ilvl ?? 0 : 0
230
+ }))
231
+ });
232
+ break;
233
+ }
234
+ default: return command;
235
+ }
236
+ return session.prepareIntents(state, intents);
237
+ };
238
+ /** Rules compile their replacement and paragraph change as one atomic edit. */
239
+ const prepareCanonicalAutoformat = (session, state, { from, to, text }) => {
240
+ if (text !== " " || from !== to) return void 0;
241
+ const at = session.projection.addressAt(from);
242
+ if (at.isErr()) return void 0;
243
+ const address = session.projection.paragraph(at.value.blockId);
244
+ if (address === void 0 || address.source.formatting?.numPr?.kind === "reference") return void 0;
245
+ const source = address.source;
246
+ const prefix = address.text.slice(0, at.value.offset) + text;
247
+ const requests = listRequestsForMarker(prefix);
248
+ const heading = /^(?<hashes>#{1,6}) $/u.exec(prefix)?.groups?.["hashes"];
249
+ if (requests === null && heading === void 0) return void 0;
250
+ const start = {
251
+ ...at.value,
252
+ offset: 0
253
+ };
254
+ const request = requests?.at(0);
255
+ const styleId = `Heading${heading?.length ?? 1}`;
256
+ if (request === void 0 && !session.hasStyle(styleId)) return void 0;
257
+ return session.prepareIntents(state, [{
258
+ type: "replaceText",
259
+ from: start,
260
+ to: at.value,
261
+ text: ""
262
+ }, request === void 0 ? {
263
+ type: "formatParagraph",
264
+ at: start,
265
+ patch: { styleId }
266
+ } : numberingIntent(session, [source], request)]);
267
+ };
268
+ //#endregion
269
+ export { prepareCanonicalAutoformat, prepareCanonicalCommands };
@@ -1,8 +1,8 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
2
  import { DocxInput } from "../utils/docxInput.js";
3
3
  import { Layout } from "../layout-engine/types.js";
4
- import { HiddenEditorApi } from "./hiddenEditorApi.js";
5
4
  import { FolioEditorEmitter } from "./folioEditorEvents.js";
5
+ import { HiddenEditorApi } from "./hiddenEditorApi.js";
6
6
  import { LayoutRunOptions } from "./layoutRunOptions.js";
7
7
  import { EditorState } from "prosemirror-state";
8
8
  //#region src/controller/folioEditor.d.ts
@@ -15,6 +15,15 @@ const createFolioEditor = (deps) => {
15
15
  getState: () => deps.getEditorApi()?.getState() ?? null,
16
16
  getView: () => deps.getEditorApi()?.getView() ?? null,
17
17
  getDocument: () => deps.getEditorApi()?.getDocument() ?? null,
18
+ updateCanonicalInputLifecycle: (action) => deps.getEditorApi()?.updateCanonicalInputLifecycle(action) ?? false,
19
+ applyCanonicalStoryHistory: (options) => deps.getEditorApi()?.applyCanonicalStoryHistory(options) ?? false,
20
+ applyCanonicalOperations: (ops) => deps.getEditorApi()?.applyCanonicalOperations(ops) ?? false,
21
+ getCanonicalStorySelection: (story) => deps.getEditorApi()?.getCanonicalStorySelection(story) ?? null,
22
+ getCanonicalStoryProjection: (story) => deps.getEditorApi()?.getCanonicalStoryProjection(story) ?? null,
23
+ replaceCanonicalStoryText: (options) => deps.getEditorApi()?.replaceCanonicalStoryText(options) ?? false,
24
+ getCanonicalDocument: () => deps.getEditorApi()?.getCanonicalDocument() ?? null,
25
+ setCanonicalMode: (mode) => deps.getEditorApi()?.setCanonicalMode(mode) ?? false,
26
+ resolveCanonicalRevisions: (revisionIds, resolution) => deps.getEditorApi()?.resolveCanonicalRevisions(revisionIds, resolution) ?? false,
18
27
  focus: () => deps.getEditorApi()?.focus(),
19
28
  blur: () => deps.getEditorApi()?.blur(),
20
29
  isFocused: () => deps.getEditorApi()?.isFocused() ?? false,
@@ -1,4 +1,5 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
+ import { HiddenEditorApi } from "./hiddenEditorApi.js";
2
3
  import { DirectEditorProps, EditorView } from "prosemirror-view";
3
4
  //#region src/controller/headerFooterEditorManager.d.ts
4
5
  type HeaderFooterPartKind = "header" | "footer";
@@ -16,6 +17,9 @@ type HeaderFooterEditorTransaction = {
16
17
  type HeaderFooterEditorManagerDeps = {
17
18
  createView?: ((mountNode: HTMLElement, props: DirectEditorProps) => EditorView) | undefined;
18
19
  getDocument: () => document_d_exports.Document | null;
20
+ getCanonicalApi?: (() => HiddenEditorApi | null) | undefined;
21
+ getExperimentalSession?: (() => "canonical" | undefined) | undefined;
22
+ onSessionRefusal?: ((reason: string) => void) | undefined;
19
23
  getHost: () => HTMLElement | null;
20
24
  getStyles: () => document_d_exports.StyleDefinitions | null | undefined;
21
25
  getTheme: () => document_d_exports.Theme | null | undefined;
@@ -10,9 +10,19 @@ 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 { EditorState } from "prosemirror-state";
13
+ import { createCanonicalStoryEditor } from "./canonicalStoryEditor.js";
14
+ import { OP_STORIES } from "@stll/docx-core/ops";
15
+ import { EditorState, TextSelection } from "prosemirror-state";
14
16
  import { EditorView } from "prosemirror-view";
15
17
  //#region src/controller/headerFooterEditorManager.ts
18
+ /**
19
+ * Framework-neutral lifecycle owner for persistent header/footer EditorViews.
20
+ *
21
+ * Each distinct relationship id owns one off-screen ProseMirror view. Painted
22
+ * header/footer instances that share that relationship therefore share live
23
+ * editor state, and adapters only decide when to synchronize or destroy the
24
+ * manager.
25
+ */
16
26
  const createEditorView = (mountNode, props) => new EditorView(mountNode, props);
17
27
  const DETACHED_WATERMARK_HOST = Symbol.for("stll.detachedWatermarkHost");
18
28
  const headerFooterToProseDocWithDetachedWatermarkHost = (headerFooter, options) => {
@@ -27,6 +37,7 @@ const buildInitialState = (headerFooter, styles, theme, numbering, manager) => {
27
37
  const proseDocOptions = {};
28
38
  if (styles) proseDocOptions.styles = styles;
29
39
  if (theme !== void 0) proseDocOptions.theme = theme;
40
+ if (numbering != null) proseDocOptions.numbering = numbering;
30
41
  const document = headerFooter.watermarkBlockIndex === void 0 ? headerFooterToProseDoc(headerFooter.content, proseDocOptions) : headerFooterToProseDocWithDetachedWatermarkHost(headerFooter, proseDocOptions);
31
42
  return ensureBaseDirectionInState(ensureParaIdsInState(EditorState.create({
32
43
  doc: document,
@@ -76,7 +87,8 @@ const createHeaderFooterEditorManager = (deps) => {
76
87
  const sync = () => {
77
88
  const host = deps.getHost();
78
89
  if (!host) return;
79
- const document = deps.getDocument();
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();
80
92
  const styles = deps.getStyles();
81
93
  const theme = deps.getTheme();
82
94
  const numbering = document?.package.numbering;
@@ -92,6 +104,30 @@ const createHeaderFooterEditorManager = (deps) => {
92
104
  const existing = mounted.get(part.rId);
93
105
  if (existing) {
94
106
  if (existing.mountNode.parentElement !== host) host.append(existing.mountNode);
107
+ if (deps.getExperimentalSession?.() === "canonical") {
108
+ const projected = deps.getCanonicalApi?.()?.getCanonicalStoryProjection({
109
+ kind: part.kind,
110
+ rId: part.rId
111
+ });
112
+ if (projected && !existing.view.state.doc.eq(projected)) {
113
+ const transaction = existing.view.state.tr.replaceWith(0, existing.view.state.doc.content.size, projected.content);
114
+ const restored = deps.getCanonicalApi?.()?.getCanonicalStorySelection({
115
+ kind: part.kind,
116
+ rId: part.rId
117
+ });
118
+ const anchor = Math.min(existing.view.state.selection.anchor, projected.content.size);
119
+ transaction.setSelection(restored ? TextSelection.create(transaction.doc, restored.anchor, restored.head) : TextSelection.near(transaction.doc.resolve(anchor)));
120
+ transaction.setMeta("addToHistory", false);
121
+ existing.view.updateState(existing.view.state.apply(transaction));
122
+ }
123
+ existing.appliedHeaderFooter = headerFooter;
124
+ existing.appliedContent = headerFooter.content;
125
+ existing.appliedStyles = styles;
126
+ existing.appliedTheme = theme;
127
+ existing.appliedNumbering = numbering;
128
+ existing.dirty = false;
129
+ continue;
130
+ }
95
131
  const contentIsCurrent = existing.appliedHeaderFooter === headerFooter && existing.appliedContent === headerFooter.content;
96
132
  const contextIsCurrent = existing.appliedStyles === styles && existing.appliedTheme === theme && existing.appliedNumbering === numbering;
97
133
  if (contentIsCurrent && contextIsCurrent) continue;
@@ -111,9 +147,40 @@ const createHeaderFooterEditorManager = (deps) => {
111
147
  mountNode.dataset["hfRId"] = part.rId;
112
148
  mountNode.dataset["hfKind"] = part.kind;
113
149
  host.append(mountNode);
150
+ const canonical = createCanonicalStoryEditor({
151
+ story: {
152
+ kind: part.kind,
153
+ rId: part.rId
154
+ },
155
+ getView: () => view,
156
+ getApi: () => deps.getCanonicalApi?.() ?? null,
157
+ enabled: () => deps.getExperimentalSession?.() === "canonical",
158
+ onRefusal: deps.onSessionRefusal,
159
+ onSelectionChange: () => deps.onTransaction?.({
160
+ kind: part.kind,
161
+ rId: part.rId,
162
+ view,
163
+ docChanged: false,
164
+ selectionChanged: true
165
+ })
166
+ });
167
+ const canonicalProjection = deps.getExperimentalSession?.() === "canonical" ? deps.getCanonicalApi?.()?.getCanonicalStoryProjection({
168
+ kind: part.kind,
169
+ rId: part.rId
170
+ }) : null;
171
+ if (deps.getExperimentalSession?.() === "canonical" && !canonicalProjection) {
172
+ manager.destroy();
173
+ mountNode.remove();
174
+ continue;
175
+ }
114
176
  const view = (deps.createView ?? createEditorView)(mountNode, {
115
- state: buildInitialState(headerFooter, styles, theme, numbering, manager),
177
+ ...canonical.props,
178
+ state: canonicalProjection ? EditorState.create({
179
+ doc: canonicalProjection,
180
+ plugins: [createDocumentStylesPlugin(styles), createDocumentNumberingPlugin(numbering)]
181
+ }) : buildInitialState(headerFooter, styles, theme, numbering, manager),
116
182
  dispatchTransaction(transaction) {
183
+ if (canonical.dispatch(transaction)) return;
117
184
  const nextState = view.state.apply(transaction);
118
185
  view.updateState(nextState);
119
186
  const mountedPart = mounted.get(part.rId);
@@ -150,6 +217,7 @@ const createHeaderFooterEditorManager = (deps) => {
150
217
  rId
151
218
  })),
152
219
  snapshotDocument: (document) => {
220
+ if (deps.getExperimentalSession?.() === "canonical") return deps.getCanonicalApi?.()?.getCanonicalDocument() ?? document;
153
221
  let headers = document.package.headers;
154
222
  let footers = document.package.footers;
155
223
  let headersChanged = false;
@@ -1,7 +1,21 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
+ import { createCanonicalInputBoundary } from "./canonicalInput.js";
3
+ import { CanonicalSessionMode } from "./canonicalSession.js";
4
+ import { Node } from "prosemirror-model";
2
5
  import { Command, EditorState, Transaction } from "prosemirror-state";
3
6
  import { EditorView } from "prosemirror-view";
7
+ import { DocumentOp, OpStory } from "@stll/docx-core/ops";
4
8
  //#region src/controller/hiddenEditorApi.d.ts
9
+ type CanonicalStoryHistoryOptions = {
10
+ view: EditorView;
11
+ story: OpStory;
12
+ direction: "undo" | "redo";
13
+ };
14
+ type CanonicalStoryTextOptions = {
15
+ view: EditorView;
16
+ story: OpStory;
17
+ intent: Parameters<Parameters<typeof createCanonicalInputBoundary>[0]["replace"]>[0];
18
+ };
5
19
  type HiddenEditorApi = {
6
20
  /** Request the off-screen EditorView (idempotent; creates it when possible). */
7
21
  ensureView: () => void;
@@ -13,6 +27,21 @@ type HiddenEditorApi = {
13
27
  getView: () => EditorView | null;
14
28
  /** Get the current Document from PM state */
15
29
  getDocument: () => document_d_exports.Document | null;
30
+ /** Canonical snapshot, or null in the default session. */
31
+ getCanonicalDocument: () => document_d_exports.Document | null;
32
+ /** Set the explicit mode for an active canonical session. */
33
+ setCanonicalMode: (mode: CanonicalSessionMode) => boolean;
34
+ /** Resolve canonical revisions as one batch and journal its exact inverse. */
35
+ resolveCanonicalRevisions: (revisionIds: readonly number[], resolution: "accept" | "reject") => boolean;
36
+ updateCanonicalInputLifecycle: (action: "beginComposition" | "endComposition" | "breakUndoGroup") => boolean;
37
+ applyCanonicalStoryHistory: (options: CanonicalStoryHistoryOptions) => boolean;
38
+ applyCanonicalOperations: (ops: readonly DocumentOp[]) => boolean;
39
+ getCanonicalStorySelection: (story: OpStory) => {
40
+ anchor: number;
41
+ head: number;
42
+ } | null;
43
+ getCanonicalStoryProjection: (story: OpStory) => Node | null;
44
+ replaceCanonicalStoryText: (options: CanonicalStoryTextOptions) => boolean;
16
45
  /** Focus the hidden editor */
17
46
  focus: () => void;
18
47
  /** Blur the hidden editor */
@@ -43,6 +72,12 @@ type HiddenEditorApi = {
43
72
  type HiddenEditorApiDeps = {
44
73
  getView: () => EditorView | null;
45
74
  getDocumentContext: () => document_d_exports.Document | null;
75
+ getCanonicalDocument?: () => document_d_exports.Document | null;
76
+ setCanonicalMode?: HiddenEditorApi["setCanonicalMode"];
77
+ resolveCanonicalRevisions?: HiddenEditorApi["resolveCanonicalRevisions"];
78
+ executeCanonicalCommand?: (command: Command) => boolean | undefined;
79
+ getCanonicalHistory?: () => Pick<HiddenEditorApi, "undo" | "redo" | "canUndo" | "canRedo"> | null;
80
+ canonicalOperations?: Pick<HiddenEditorApi, "updateCanonicalInputLifecycle" | "applyCanonicalOperations" | "getCanonicalStoryProjection" | "replaceCanonicalStoryText" | "applyCanonicalStoryHistory" | "getCanonicalStorySelection">;
46
81
  isDestroying: () => boolean;
47
82
  ensureView: () => void;
48
83
  isViewRequested: () => boolean;