@stll/folio-core 0.55.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 (225) hide show
  1. package/dist/ai-edits/apply.js +214 -52
  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/headless.js +2 -2
  5. package/dist/ai-edits/table-row-column-mutations.js +17 -3
  6. package/dist/ai-edits/types.d.ts +7 -1
  7. package/dist/ai-suggestions/apply.js +20 -1
  8. package/dist/compare/compare.js +1 -0
  9. package/dist/compare/inline-atoms.js +40 -16
  10. package/dist/compare/inline-provenance.js +12 -5
  11. package/dist/compare/reproducible-package.d.ts +0 -17
  12. package/dist/compare/reproducible-package.js +12 -18
  13. package/dist/compat/eigenpal.d.ts +2 -1
  14. package/dist/compat/eigenpal.js +2 -1
  15. package/dist/controller/canonicalClipboard.d.ts +23 -0
  16. package/dist/controller/canonicalClipboard.js +482 -0
  17. package/dist/controller/canonicalClipboardResources.d.ts +21 -0
  18. package/dist/controller/canonicalClipboardResources.js +382 -0
  19. package/dist/controller/canonicalComments.d.ts +41 -0
  20. package/dist/controller/canonicalComments.js +152 -0
  21. package/dist/controller/canonicalComposition.d.ts +24 -3
  22. package/dist/controller/canonicalComposition.js +208 -48
  23. package/dist/controller/canonicalInlineProjection.d.ts +118 -0
  24. package/dist/controller/canonicalInlineProjection.js +476 -0
  25. package/dist/controller/canonicalInput.d.ts +13 -4
  26. package/dist/controller/canonicalInput.js +62 -14
  27. package/dist/controller/canonicalInputTimer.d.ts +16 -0
  28. package/dist/controller/canonicalInputTimer.js +34 -0
  29. package/dist/controller/canonicalPublicOperations.d.ts +48 -0
  30. package/dist/controller/canonicalPublicOperations.js +648 -0
  31. package/dist/controller/canonicalSelection.d.ts +18 -0
  32. package/dist/controller/canonicalSelection.js +32 -0
  33. package/dist/controller/canonicalSession.d.ts +27 -6
  34. package/dist/controller/canonicalSession.js +298 -116
  35. package/dist/controller/canonicalStoryEditor.d.ts +7 -3
  36. package/dist/controller/canonicalStoryEditor.js +27 -2
  37. package/dist/controller/canonicalStructure.js +57 -4
  38. package/dist/controller/canonicalTocSelection.d.ts +11 -0
  39. package/dist/controller/canonicalTocSelection.js +43 -0
  40. package/dist/controller/folioEditor.d.ts +2 -9
  41. package/dist/controller/folioEditor.js +18 -4
  42. package/dist/controller/headerFooterEditorManager.d.ts +2 -1
  43. package/dist/controller/headerFooterEditorManager.js +26 -11
  44. package/dist/controller/hiddenEditorApi.d.ts +24 -3
  45. package/dist/controller/hiddenEditorApi.js +8 -0
  46. package/dist/controller/hiddenEditorManager.d.ts +5 -1
  47. package/dist/controller/hiddenEditorManager.js +378 -43
  48. package/dist/controller/noteEditorManager.d.ts +2 -1
  49. package/dist/controller/noteEditorManager.js +29 -13
  50. package/dist/document-operations.d.ts +8 -1
  51. package/dist/document-operations.js +6 -4
  52. package/dist/docx/blockPlainText.d.ts +1 -1
  53. package/dist/docx/blockPlainText.js +11 -3
  54. package/dist/docx/builtInStyles.js +13 -6
  55. package/dist/docx/canonicalResourceSave.d.ts +16 -0
  56. package/dist/docx/canonicalResourceSave.js +27 -0
  57. package/dist/docx/canonicalSave.d.ts +29 -0
  58. package/dist/docx/canonicalSave.js +101 -0
  59. package/dist/docx/canonicalStoryRepack.d.ts +1 -1
  60. package/dist/docx/canonicalStoryRepack.js +38 -5
  61. package/dist/docx/containerChildren.gen.d.ts +3 -1
  62. package/dist/docx/containerChildren.gen.js +33 -1
  63. package/dist/docx/documentParser.d.ts +1 -13
  64. package/dist/docx/documentParser.js +1 -47
  65. package/dist/docx/ensureParaIds.js +10 -4
  66. package/dist/docx/fieldParser.js +14 -2
  67. package/dist/docx/footnoteParser.d.ts +1 -11
  68. package/dist/docx/footnoteParser.js +1 -16
  69. package/dist/docx/headerFooterParser.d.ts +1 -9
  70. package/dist/docx/headerFooterParser.js +1 -12
  71. package/dist/docx/headerFooterVerbatim.d.ts +26 -1
  72. package/dist/docx/headerFooterVerbatim.js +81 -3
  73. package/dist/docx/hyperlinkParser.d.ts +1 -1
  74. package/dist/docx/hyperlinkParser.js +20 -1
  75. package/dist/docx/imageParser.d.ts +13 -1
  76. package/dist/docx/imageParser.js +20 -11
  77. package/dist/docx/inlineWrapperContent.d.ts +4 -4
  78. package/dist/docx/inlineWrapperContent.js +4 -4
  79. package/dist/docx/linkedSdtPreservation.d.ts +10 -0
  80. package/dist/docx/linkedSdtPreservation.js +37 -0
  81. package/dist/docx/metadataPrivacy.js +6 -1
  82. package/dist/docx/packageParts.js +9 -3
  83. package/dist/docx/paragraphParser.js +88 -67
  84. package/dist/docx/paragraphPropertySource.d.ts +22 -1
  85. package/dist/docx/paragraphPropertySource.js +109 -2
  86. package/dist/docx/parser.js +2 -1
  87. package/dist/docx/preservedRunContent.js +2 -1
  88. package/dist/docx/relsParser.js +26 -2
  89. package/dist/docx/removeHeaderFooterParts.js +17 -6
  90. package/dist/docx/rezip.d.ts +49 -5
  91. package/dist/docx/rezip.js +754 -235
  92. package/dist/docx/saveDiagnostics.d.ts +11 -0
  93. package/dist/docx/selectiveSave.d.ts +2 -1
  94. package/dist/docx/selectiveSave.js +42 -11
  95. package/dist/docx/selectiveXmlPatch.js +7 -5
  96. package/dist/docx/serializer/commentSerializer.d.ts +4 -1
  97. package/dist/docx/serializer/commentSerializer.js +7 -3
  98. package/dist/docx/serializer/documentSerializer.d.ts +9 -2
  99. package/dist/docx/serializer/documentSerializer.js +29 -10
  100. package/dist/docx/serializer/paragraphSerializer.js +207 -94
  101. package/dist/docx/server/applyDocxXmlPatchProposal.js +14 -9
  102. package/dist/docx/server/materializeYjsDocx.js +14 -6
  103. package/dist/docx/server/migrateYjsAttrSchema.js +17 -5
  104. package/dist/docx/storyBlockReplay.d.ts +21 -1
  105. package/dist/docx/storyBlockReplay.js +113 -9
  106. package/dist/docx/storyPlainText.d.ts +20 -0
  107. package/dist/docx/storyPlainText.js +23 -0
  108. package/dist/docx/structuralXmlPatch.js +10 -22
  109. package/dist/docx/styleParser.d.ts +5 -1
  110. package/dist/docx/styleParser.js +1 -1
  111. package/dist/docx/tableParser.d.ts +1 -8
  112. package/dist/docx/tableParser.js +1 -28
  113. package/dist/docx/textBoxParser.d.ts +1 -5
  114. package/dist/docx/textBoxParser.js +1 -20
  115. package/dist/docx/unzip.js +6 -1
  116. package/dist/i18n/messages/ar.gen.js +4 -0
  117. package/dist/i18n/messages/catalogs.gen.d.ts +68 -0
  118. package/dist/i18n/messages/catalogs.gen.js +68 -0
  119. package/dist/i18n/messages/cs.gen.js +4 -0
  120. package/dist/i18n/messages/de.gen.js +4 -0
  121. package/dist/i18n/messages/en.gen.js +4 -0
  122. package/dist/i18n/messages/es.gen.js +4 -0
  123. package/dist/i18n/messages/et.gen.js +4 -0
  124. package/dist/i18n/messages/fr.gen.js +4 -0
  125. package/dist/i18n/messages/he.gen.js +4 -0
  126. package/dist/i18n/messages/hi.gen.js +4 -0
  127. package/dist/i18n/messages/hu.gen.js +4 -0
  128. package/dist/i18n/messages/lt.gen.js +4 -0
  129. package/dist/i18n/messages/lv.gen.js +4 -0
  130. package/dist/i18n/messages/messages.gen.d.ts +4 -0
  131. package/dist/i18n/messages/pl.gen.js +4 -0
  132. package/dist/i18n/messages/pt-BR.gen.js +4 -0
  133. package/dist/i18n/messages/sk.gen.js +4 -0
  134. package/dist/i18n/messages/tr.gen.js +4 -0
  135. package/dist/i18n/messages/zh-CN.gen.js +4 -0
  136. package/dist/index.d.ts +2 -1
  137. package/dist/index.js +2 -1
  138. package/dist/internal/acceptedBlockProjection.d.ts +12 -0
  139. package/dist/internal/acceptedBlockProjection.js +45 -0
  140. package/dist/internal/revisionResolutionInline.js +25 -3
  141. package/dist/managers/DocumentLoaderManager.js +2 -1
  142. package/dist/markdown/escape.d.ts +3 -1
  143. package/dist/markdown/escape.js +9 -2
  144. package/dist/markdown/renderBlock.js +5 -120
  145. package/dist/markdown/renderParagraph.js +4 -2
  146. package/dist/markdown/renderRuns.d.ts +7 -1
  147. package/dist/markdown/renderRuns.js +33 -24
  148. package/dist/panel-layout.d.ts +11 -9
  149. package/dist/panel-layout.js +13 -9
  150. package/dist/prosemirror/addTrackedDeletionMark.d.ts +18 -0
  151. package/dist/prosemirror/addTrackedDeletionMark.js +87 -0
  152. package/dist/prosemirror/attrs/index.js +5 -0
  153. package/dist/prosemirror/canonicalCommands.d.ts +28 -4
  154. package/dist/prosemirror/canonicalCommands.js +11 -4
  155. package/dist/prosemirror/canonicalSelectionRange.d.ts +9 -0
  156. package/dist/prosemirror/canonicalSelectionRange.js +15 -0
  157. package/dist/prosemirror/clipboardIntent.d.ts +7 -0
  158. package/dist/prosemirror/clipboardIntent.js +9 -0
  159. package/dist/prosemirror/commands/comments.js +60 -35
  160. package/dist/prosemirror/commands/pageBreak.js +2 -2
  161. package/dist/prosemirror/commands/pastePlainText.js +2 -1
  162. package/dist/prosemirror/commands/propertyChangeScope.js +3 -6
  163. package/dist/prosemirror/commands/resolveParagraphProperties.js +22 -13
  164. package/dist/prosemirror/commentOps.js +15 -1
  165. package/dist/prosemirror/conversion/fromProseDoc.js +22 -3
  166. package/dist/prosemirror/conversion/hyphenTextCarriers.d.ts +8 -0
  167. package/dist/prosemirror/conversion/hyphenTextCarriers.js +8 -0
  168. package/dist/prosemirror/conversion/toProseDoc.d.ts +7 -1
  169. package/dist/prosemirror/conversion/toProseDoc.js +124 -22
  170. package/dist/prosemirror/extensions/core/ParagraphExtension.js +61 -35
  171. package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +74 -25
  172. package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +11 -0
  173. package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +65 -29
  174. package/dist/prosemirror/extensions/marks/markUtils.js +11 -8
  175. package/dist/prosemirror/extensions/marks/noteReferenceDeletion.js +5 -2
  176. package/dist/prosemirror/fieldRepresentation.d.ts +8 -0
  177. package/dist/prosemirror/fieldRepresentation.js +18 -0
  178. package/dist/prosemirror/hyperlinkRemoval.d.ts +13 -1
  179. package/dist/prosemirror/hyperlinkRemoval.js +28 -9
  180. package/dist/prosemirror/insertOperations.js +2 -1
  181. package/dist/prosemirror/listNumbering.js +11 -5
  182. package/dist/prosemirror/noteReferenceOccurrenceId.d.ts +5 -0
  183. package/dist/prosemirror/noteReferenceOccurrenceId.js +5 -0
  184. package/dist/prosemirror/noteReferenceOccurrences.d.ts +21 -0
  185. package/dist/prosemirror/noteReferenceOccurrences.js +185 -0
  186. package/dist/prosemirror/noteReferenceReview.js +14 -1
  187. package/dist/prosemirror/paragraphPropertyCarry.d.ts +7 -1
  188. package/dist/prosemirror/paragraphPropertyCarry.js +8 -3
  189. package/dist/prosemirror/plugins/documentStyles.d.ts +4 -1
  190. package/dist/prosemirror/plugins/documentStyles.js +17 -10
  191. package/dist/prosemirror/plugins/suggestionMode.js +48 -48
  192. package/dist/prosemirror/preservedXmlReview.d.ts +6 -0
  193. package/dist/prosemirror/preservedXmlReview.js +35 -0
  194. package/dist/prosemirror/runFormattingFromMarks.js +2 -2
  195. package/dist/prosemirror/runFormattingReconciliation.js +5 -4
  196. package/dist/prosemirror/runPropertyChangeResolution.d.ts +8 -1
  197. package/dist/prosemirror/runPropertyChangeResolution.js +24 -2
  198. package/dist/prosemirror/runStyleFormatting.d.ts +3 -1
  199. package/dist/prosemirror/runStyleFormatting.js +3 -1
  200. package/dist/prosemirror/schema/marks.d.ts +1 -0
  201. package/dist/prosemirror/schema/nodes.d.ts +2 -0
  202. package/dist/prosemirror/styles/paragraphStyleCascade.js +4 -1
  203. package/dist/prosemirror/styles/resolvedStyleAttrs.d.ts +15 -15
  204. package/dist/prosemirror/styles/resolvedStyleAttrs.js +69 -28
  205. package/dist/prosemirror/textInput.d.ts +5 -1
  206. package/dist/prosemirror/textInput.js +3 -3
  207. package/dist/prosemirror/trackedRevisionPath.d.ts +5 -1
  208. package/dist/prosemirror/trackedRevisionPath.js +39 -1
  209. package/dist/prosemirror/validation.js +3 -5
  210. package/dist/prosemirror/yjsDocumentMetadata.d.ts +9 -3
  211. package/dist/prosemirror/yjsDocumentMetadata.js +37 -8
  212. package/dist/types/canonicalCapabilities.d.ts +201 -0
  213. package/dist/types/canonicalCapabilities.js +198 -0
  214. package/dist/types/canonicalComments.d.ts +51 -0
  215. package/dist/types/canonicalComments.js +0 -0
  216. package/dist/types/canonicalSave.d.ts +11 -0
  217. package/dist/types/canonicalSave.js +0 -0
  218. package/dist/types/canonicalSections.d.ts +12 -0
  219. package/dist/types/canonicalSections.js +0 -0
  220. package/dist/types/docxSerialization.d.ts +14 -0
  221. package/dist/types/docxSerialization.js +7 -0
  222. package/dist/utils/headingCollector.js +45 -4
  223. package/dist/utils/outlineDepth.d.ts +9 -0
  224. package/dist/utils/outlineDepth.js +9 -0
  225. package/package.json +4 -4
@@ -30,8 +30,15 @@ function escapeInline(text) {
30
30
  * line.
31
31
  */
32
32
  function escapeTableCell(text) {
33
- return text.replace(/\|/gu, "&#124;").replace(/\r?\n/gu, "<br>");
33
+ return inlineBreaksToHtml(text.replace(/\|/gu, "&#124;"));
34
34
  }
35
+ /** Single-line Markdown blocks express inline breaks without their two-space syntax. */
36
+ const inlineBreaksToHtml = (text) => text.split(/\r?\n/u).map((line, index, lines) => {
37
+ if (index === lines.length - 1) return line;
38
+ let end = line.length;
39
+ while (end > 0 && line.at(end - 1) === " ") end--;
40
+ return line.slice(0, end);
41
+ }).join("<br>");
35
42
  /**
36
43
  * Hyperlink URLs in inline form need parens balanced; we URL-encode the
37
44
  * problematic characters rather than escape, so the link still resolves.
@@ -55,4 +62,4 @@ function escapeAltText(text) {
55
62
  return text.replace(/(?<ch>[\\[\]])/gu, "\\$<ch>").replace(/\r?\n/gu, " ");
56
63
  }
57
64
  //#endregion
58
- export { escapeAltText, escapeInline, escapeLinkUrl, escapeTableCell };
65
+ export { escapeAltText, escapeInline, escapeLinkUrl, escapeTableCell, inlineBreaksToHtml };
@@ -1,6 +1,7 @@
1
1
  import { ALT_CHUNK_READER_DIAGNOSTIC, isAltChunkMarkup } from "../docx/altChunk.js";
2
2
  import { OPAQUE_REVISION_CARRIER_READER_DIAGNOSTIC, isOpaqueNestedRowMarkup, opaqueRevisionCarrierName } from "../docx/opaqueCarrier.js";
3
- import { cloneParagraphWithoutPropertySource } from "../docx/paragraphPropertySource.js";
3
+ import { projectAcceptedBlocks } from "../internal/acceptedBlockProjection.js";
4
+ import { pushWarning } from "./internals.js";
4
5
  import { renderParagraphBlock } from "./renderParagraph.js";
5
6
  import { renderTable } from "./renderTable.js";
6
7
  import { panic } from "better-result";
@@ -10,130 +11,14 @@ import { panic } from "better-result";
10
11
  * markdown for each block, suppressing redundant blank lines between list items
11
12
  * of the same list. Ported from eigenpal/docx-editor PR #595.
12
13
  */
13
- /**
14
- * In `trackedChanges: "clean"` mode every change is accepted, as the editor's
15
- * accept-all resolves the story (`internal/wholeStoryRevisionResolution.ts`).
16
- *
17
- * A paragraph whose mark is pending deletion loses its break on accept and
18
- * runs on into the paragraph after it, which is the paragraph left: its mark
19
- * ends the joined text, and a paragraph's properties (style, list) live on its
20
- * mark. A run of consecutive deletions collapses into that last paragraph.
21
- *
22
- * With a table right after it, its words run on into the table's first cell
23
- * and lead that cell's first paragraph (`prosemirror/tableRunIn.ts`), unless
24
- * it ends a section, keeps nothing once accepted, or that cell is pending.
25
- * Otherwise a paragraph that cannot join keeps its place, or goes when it
26
- * keeps nothing and something follows it.
27
- */
28
- function mergeAcceptedParagraphBreaks(blocks) {
29
- const reversed = [];
30
- let survivor = null;
31
- let tableFollows = false;
32
- let followed = false;
33
- const flush = () => {
34
- if (survivor) reversed.push(survivor);
35
- survivor = null;
36
- };
37
- for (let index = blocks.length - 1; index >= 0; index--) {
38
- const block = blocks[index];
39
- if (!block) continue;
40
- if (block.type !== "paragraph") {
41
- flush();
42
- reversed.push(block);
43
- tableFollows = block.type === "table";
44
- followed ||= tableFollows;
45
- continue;
46
- }
47
- const goes = block.pPrMark?.kind === "del" || block.pPrMark?.kind === "moveFrom";
48
- if (goes && survivor) {
49
- survivor = cloneParagraphWithoutPropertySource(survivor, { content: [...block.content, ...survivor.content] });
50
- continue;
51
- }
52
- const table = reversed.at(-1);
53
- if (goes && tableFollows && table?.type === "table") {
54
- const ranIn = tableWithParagraphRunIn(table, block);
55
- if (ranIn) {
56
- reversed[reversed.length - 1] = ranIn;
57
- continue;
58
- }
59
- }
60
- flush();
61
- tableFollows = false;
62
- if (goes && holdsNothingOnAccept(block) && followed && index + reversed.length > 0) continue;
63
- survivor = block;
64
- followed = true;
65
- }
66
- flush();
67
- return reversed.reverse();
68
- }
69
- /**
70
- * Whether the cell a paragraph's words would run into is itself pending (its
71
- * row or the cell, through nested tables' first cells), as
72
- * `prosemirror/tableRunIn.ts` reads it.
73
- */
74
- const runInCellPending = (table) => {
75
- const row = table.rows[0];
76
- const cell = row?.cells[0];
77
- if (!row || !cell) return false;
78
- if (row.structuralChange !== void 0 || cell.structuralChange !== void 0) return true;
79
- const first = cell.content[0];
80
- return first?.type === "table" && runInCellPending(first);
81
- };
82
- /**
83
- * `table` with `paragraph`'s words leading its first cell's first paragraph,
84
- * descending into nested tables, or null when the paragraph keeps its place.
85
- */
86
- const tableWithParagraphRunIn = (table, paragraph) => {
87
- if (paragraph.sectionProperties !== void 0 || holdsNothingOnAccept(paragraph)) return null;
88
- if (runInCellPending(table)) return null;
89
- const runIn = (current) => {
90
- const [row, ...rows] = current.rows;
91
- const [cell, ...cells] = row?.cells ?? [];
92
- const [first, ...rest] = cell?.content ?? [];
93
- if (!row || !cell || !first) return null;
94
- let led = null;
95
- if (first.type === "paragraph") led = cloneParagraphWithoutPropertySource(first, { content: [...paragraph.content, ...first.content] });
96
- else if (first.type === "table") led = runIn(first);
97
- if (!led) return null;
98
- return {
99
- ...current,
100
- rows: [{
101
- ...row,
102
- cells: [{
103
- ...cell,
104
- content: [led, ...rest]
105
- }, ...cells]
106
- }, ...rows]
107
- };
108
- };
109
- return runIn(table);
110
- };
111
- /** Paragraph content that shows nothing: range boundaries and anchors. */
112
- const ZERO_WIDTH_CONTENT = /* @__PURE__ */ new Set([
113
- "bookmarkStart",
114
- "bookmarkEnd",
115
- "commentRangeStart",
116
- "commentRangeEnd",
117
- "commentReference",
118
- "moveFromRangeStart",
119
- "moveFromRangeEnd",
120
- "moveToRangeStart",
121
- "moveToRangeEnd"
122
- ]);
123
- /** Whether an item shows nothing once every change is accepted. */
124
- const showsNothingOnAccept = (item) => {
125
- if (item.type === "deletion" || item.type === "moveFrom" || ZERO_WIDTH_CONTENT.has(item.type)) return true;
126
- if (item.type === "insertion" || item.type === "moveTo") return item.content.every(showsNothingOnAccept);
127
- return item.type === "run" && item.content.every((content) => content.type === "renderedPageBreak");
128
- };
129
- /** Whether a paragraph keeps no visible content once every change is accepted. */
130
- const holdsNothingOnAccept = (paragraph) => paragraph.content.every(showsNothingOnAccept);
131
14
  function renderBlocks(ctx, pkg, blocks) {
132
15
  const outerListIndentWidths = ctx.listIndentWidths;
133
16
  ctx.listIndentWidths = [];
134
17
  const out = [];
135
18
  let prevWasListItem = false;
136
- const ordered = ctx.opts.trackedChanges === "clean" ? mergeAcceptedParagraphBreaks(blocks) : blocks;
19
+ const projection = ctx.opts.trackedChanges === "clean" ? projectAcceptedBlocks(blocks, pkg) : null;
20
+ if (projection?.completeness === "partial") pushWarning(ctx, "Some tracked structural changes could not be resolved.");
21
+ const ordered = projection?.blocks ?? blocks;
137
22
  for (const block of ordered) switch (block.type) {
138
23
  case "paragraph": {
139
24
  const { markdown: md, isListItem } = renderParagraphBlock(ctx, pkg, block);
@@ -1,5 +1,6 @@
1
1
  import { isQuoteStyle, resolveHeadingLevel } from "../docx/builtInStyles.js";
2
2
  import { listLabelAttrsFromRendering } from "../prosemirror/listLabels.js";
3
+ import { inlineBreaksToHtml } from "./escape.js";
3
4
  import { renderParagraphInline } from "./renderRuns.js";
4
5
  //#region src/markdown/renderParagraph.ts
5
6
  /**
@@ -37,13 +38,14 @@ function renderParagraphBlock(ctx, pkg, para) {
37
38
  const headingLevel = markdownHeadingLevel(ctx, para);
38
39
  if (headingLevel !== void 0) {
39
40
  ctx.listIndentWidths = [];
40
- if (!inline) return {
41
+ if (!inline || !inline.trim() && label === void 0) return {
41
42
  markdown: "",
42
43
  isListItem: false
43
44
  };
44
45
  const hashes = "#".repeat(Math.min(MAX_MARKDOWN_HEADING_LEVEL, headingLevel + 1));
46
+ const headingInline = inlineBreaksToHtml(inline);
45
47
  return {
46
- markdown: label ? `${hashes} ${label} ${inline}` : `${hashes} ${inline}`,
48
+ markdown: label ? `${hashes} ${label} ${headingInline}` : `${hashes} ${headingInline}`,
47
49
  isListItem: false
48
50
  };
49
51
  }
@@ -1,6 +1,12 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
2
  import { RenderContext } from "./types.js";
3
3
  //#region src/markdown/renderRuns.d.ts
4
+ /**
5
+ * Inline marks we recognize. Order matters: we open from outermost to innermost
6
+ * so the output reads cleanly (`***bold italic***`), not the reverse.
7
+ */
8
+ type MarkKey = "bold" | "italic" | "code" | "strike";
9
+ declare function applyMarks(text: string, marks: readonly MarkKey[]): string;
4
10
  /**
5
11
  * The character beside a run's delimiters: `""` at the paragraph's edge,
6
12
  * `undefined` when it cannot be told (read as a letter, the strictest case).
@@ -16,4 +22,4 @@ type RunNeighbors = {
16
22
  */
17
23
  declare function renderParagraphInline(ctx: RenderContext, pkg: document_d_exports.DocxPackage | undefined, content: readonly document_d_exports.ParagraphContent[], paraId: string | undefined, edges?: RunNeighbors): string;
18
24
  //#endregion
19
- export { renderParagraphInline };
25
+ export { applyMarks, renderParagraphInline };
@@ -40,22 +40,32 @@ function marksFor(run) {
40
40
  if (ascii && MONOSPACE_FONTS.has(ascii)) out.push("code");
41
41
  return out;
42
42
  }
43
+ const splitEdgeWhitespace = (text) => {
44
+ const start = text.length - text.trimStart().length;
45
+ const end = text.trimEnd().length;
46
+ return {
47
+ leading: text.slice(0, start),
48
+ core: text.slice(start, Math.max(start, end)),
49
+ trailing: text.slice(Math.max(start, end))
50
+ };
51
+ };
43
52
  function applyMarks(text, marks) {
44
- if (!text) return text;
53
+ const { leading, core, trailing } = splitEdgeWhitespace(text);
54
+ if (!core || marks.length === 0) return text;
45
55
  if (marks.includes("code")) {
46
- const literal = text.replace(/\\(?<char>[\\`*[\]_<~])/gu, "$<char>");
56
+ const literal = core.replace(/\\(?<char>[\\`*[\]_<~])/gu, "$<char>");
47
57
  let longestRun = 0;
48
58
  for (const m of literal.matchAll(/`+/gu)) longestRun = Math.max(longestRun, m[0].length);
49
59
  const fence = "`".repeat(longestRun + 1);
50
60
  const pad = literal.startsWith("`") || literal.endsWith("`") ? " " : "";
51
- return `${fence}${pad}${literal}${pad}${fence}`;
61
+ return `${leading}${fence}${pad}${literal}${pad}${fence}${trailing}`;
52
62
  }
53
- let out = text;
63
+ let out = core;
54
64
  for (const m of marks) {
55
65
  const d = MARK_DELIMS[m];
56
66
  out = `${d}${out}${d}`;
57
67
  }
58
- return out;
68
+ return `${leading}${out}${trailing}`;
59
69
  }
60
70
  const UNKNOWN_NEIGHBORS = {
61
71
  before: void 0,
@@ -250,11 +260,8 @@ function pieceOf(ctx, pkg, run, paraId) {
250
260
  text: inner,
251
261
  marks: new Set(marks)
252
262
  };
253
- const leadLen = inner.length - inner.trimStart().length;
254
- const trailLen = inner.length - inner.trimEnd().length;
255
- const core = inner.slice(leadLen, inner.length - trailLen);
256
263
  return {
257
- text: core ? `${inner.slice(0, leadLen)}${applyMarks(core, marks)}${inner.slice(inner.length - trailLen)}` : inner,
264
+ text: inner.split("\n").map((line) => applyMarks(line, marks)).join("\n"),
258
265
  marks: /* @__PURE__ */ new Set()
259
266
  };
260
267
  }
@@ -346,6 +353,8 @@ function renderHyperlink(ctx, pkg, link, paraId) {
346
353
  after: "]"
347
354
  } : UNKNOWN_NEIGHBORS);
348
355
  if (!inner) return "";
356
+ const { leading, core, trailing } = splitEdgeWhitespace(inner);
357
+ if (!core) return inner;
349
358
  const href = link.href ?? (link.anchor ? `#${link.anchor}` : "");
350
359
  if (!href) {
351
360
  pushWarning(ctx, "hyperlink missing href and anchor; rendered as plain text");
@@ -357,9 +366,9 @@ function renderHyperlink(ctx, pkg, link, paraId) {
357
366
  href,
358
367
  refNumber
359
368
  });
360
- return `[${inner}][${refNumber}]`;
369
+ return `${leading}[${core}][${refNumber}]${trailing}`;
361
370
  }
362
- return `[${inner}](${escapeLinkUrl(href)})`;
371
+ return `${leading}[${core}](${escapeLinkUrl(href)})${trailing}`;
363
372
  }
364
373
  function renderTrackedWrapper(ctx, pkg, wrapper, paraId) {
365
374
  const renderChild = (child) => {
@@ -419,6 +428,16 @@ function renderTrackedWrapper(ctx, pkg, wrapper, paraId) {
419
428
  default: return wrapMoveTo(ctx, wrapper.info, inner);
420
429
  }
421
430
  }
431
+ /** Clean revisions have no inline boundary; group their runs with surrounding runs. */
432
+ const cleanRevisionContent = (content) => content.flatMap((item) => {
433
+ switch (item.type) {
434
+ case "insertion":
435
+ case "moveTo": return cleanRevisionContent(item.content);
436
+ case "deletion":
437
+ case "moveFrom": return [];
438
+ default: return [item];
439
+ }
440
+ });
422
441
  /**
423
442
  * Render the full inline content of a paragraph, tracking comment-range
424
443
  * boundaries to apply the configured wrapper.
@@ -429,7 +448,7 @@ function renderParagraphInline(ctx, pkg, content, paraId, edges = {
429
448
  }) {
430
449
  let out = "";
431
450
  const openComments = [];
432
- const items = content.filter((item) => !SILENT_MARKERS.has(item.type) && !(ctx.opts.comments === "strip" && COMMENT_MARKERS.has(item.type)));
451
+ const items = (ctx.opts.trackedChanges === "clean" ? cleanRevisionContent(content) : content).filter((item) => !SILENT_MARKERS.has(item.type) && !(ctx.opts.comments === "strip" && COMMENT_MARKERS.has(item.type)));
433
452
  const around = (index) => {
434
453
  const next = items[index + 1];
435
454
  return {
@@ -471,17 +490,7 @@ function renderParagraphInline(ctx, pkg, content, paraId, edges = {
471
490
  case "simpleField":
472
491
  case "complexField": {
473
492
  const runs = item.type === "simpleField" ? item.content : item.fieldResult;
474
- for (const child of runs) {
475
- if (child.type === "run") {
476
- out += renderRuns(ctx, pkg, [child], paraId);
477
- continue;
478
- }
479
- if (child.type === "hyperlink") {
480
- out += renderHyperlink(ctx, pkg, child, paraId);
481
- continue;
482
- }
483
- out += child.type === "inlineWrapper" ? renderParagraphInline(ctx, pkg, child.content, paraId, UNKNOWN_NEIGHBORS) : child.text;
484
- }
493
+ for (const child of runs) out += child.type === "preservedInline" ? child.text : renderParagraphInline(ctx, pkg, [child], paraId, UNKNOWN_NEIGHBORS);
485
494
  break;
486
495
  }
487
496
  case "inlineSdt":
@@ -557,4 +566,4 @@ function applyCommentWrapping(ctx, slot, current) {
557
566
  }, inner);
558
567
  }
559
568
  //#endregion
560
- export { renderParagraphInline };
569
+ export { applyMarks, renderParagraphInline };
@@ -5,17 +5,17 @@
5
5
  *
6
6
  * The editor measures the width it has once, and this module decides how
7
7
  * each panel is presented. Every track the page shares the row with is
8
- * counted here, so the page, the outline and the comments cannot overlap:
9
- * a panel either gets a track of its own or becomes a drawer the user opens
10
- * on purpose. A drawer takes no track.
8
+ * counted here, so the page, the outline and the comments cannot overlap.
9
+ * Explicitly opening the outline gives it a clamped track beside the viewport;
10
+ * comments keep their existing column-or-drawer behavior.
11
11
  *
12
12
  * Space goes to the page first, then the outline rail, then the comments
13
13
  * column, then the full outline column:
14
14
  *
15
15
  * - comments: a column when the page, the comments and the outline rail fit;
16
16
  * otherwise a drawer.
17
- * - outline: a column when it fits beside the page and the comments column;
18
- * otherwise a rail of heading ticks when that fits; otherwise a drawer.
17
+ * - outline: a column when it fits beside the page and comments; otherwise a
18
+ * rail of heading ticks when that fits, or a toolbar toggle for the opened track.
19
19
  *
20
20
  * The tier names the result: `wide` when every panel has its full column,
21
21
  * `medium` when the outline is a rail, `narrow` when a panel is a drawer.
@@ -29,6 +29,8 @@ declare const PANEL_METRICS: {
29
29
  readonly outlineColumnWidth: 256;
30
30
  /** The outline rail in the `medium` tier, border included. */
31
31
  readonly outlineRailWidth: 32;
32
+ /** Minimum width and height of a shared panel control. */
33
+ readonly controlMinimumSize: 32;
32
34
  /** A comment card column. */
33
35
  readonly commentsWidth: 280;
34
36
  /** Space between the page edge and the comment cards, and after them. */
@@ -41,8 +43,8 @@ declare const PANEL_METRICS: {
41
43
  /** Width the comments take beside the page: gap, cards, gap. */
42
44
  declare const COMMENTS_TRACK_WIDTH: number;
43
45
  type PanelLayoutTier = "wide" | "medium" | "narrow";
44
- /** How the outline is shown: a column, a rail of ticks, a drawer, or not at all. */
45
- type OutlinePresentation = "none" | "column" | "rail" | "drawer";
46
+ /** How the outline is shown: a column, tick rail, drawer, or explicitly opened track. */
47
+ type OutlinePresentation = "none" | "column" | "rail" | "drawer" | "expanded";
46
48
  /** How the comments are shown: a column beside the page, a drawer, or not at all. */
47
49
  type CommentsPresentation = "hidden" | "column" | "drawer";
48
50
  type PanelLayoutInput = {
@@ -50,8 +52,8 @@ type PanelLayoutInput = {
50
52
  availableWidth: number;
51
53
  /** Width of the widest page at the current zoom. */
52
54
  pageWidth: number;
53
- /** Whether the document has an outline to show. */
54
- outline: "absent" | "available";
55
+ /** Whether the document outline is absent, available, or explicitly opened. */
56
+ outline: "absent" | "available" | "expanded";
55
57
  /** Whether the user (or the auto-open) wants the comments shown. */
56
58
  comments: "closed" | "open";
57
59
  };
@@ -5,17 +5,17 @@
5
5
  *
6
6
  * The editor measures the width it has once, and this module decides how
7
7
  * each panel is presented. Every track the page shares the row with is
8
- * counted here, so the page, the outline and the comments cannot overlap:
9
- * a panel either gets a track of its own or becomes a drawer the user opens
10
- * on purpose. A drawer takes no track.
8
+ * counted here, so the page, the outline and the comments cannot overlap.
9
+ * Explicitly opening the outline gives it a clamped track beside the viewport;
10
+ * comments keep their existing column-or-drawer behavior.
11
11
  *
12
12
  * Space goes to the page first, then the outline rail, then the comments
13
13
  * column, then the full outline column:
14
14
  *
15
15
  * - comments: a column when the page, the comments and the outline rail fit;
16
16
  * otherwise a drawer.
17
- * - outline: a column when it fits beside the page and the comments column;
18
- * otherwise a rail of heading ticks when that fits; otherwise a drawer.
17
+ * - outline: a column when it fits beside the page and comments; otherwise a
18
+ * rail of heading ticks when that fits, or a toolbar toggle for the opened track.
19
19
  *
20
20
  * The tier names the result: `wide` when every panel has its full column,
21
21
  * `medium` when the outline is a rail, `narrow` when a panel is a drawer.
@@ -29,6 +29,8 @@ const PANEL_METRICS = {
29
29
  outlineColumnWidth: 256,
30
30
  /** The outline rail in the `medium` tier, border included. */
31
31
  outlineRailWidth: 32,
32
+ /** Minimum width and height of a shared panel control. */
33
+ controlMinimumSize: 32,
32
34
  /** A comment card column. */
33
35
  commentsWidth: 280,
34
36
  /** Space between the page edge and the comment cards, and after them. */
@@ -49,7 +51,7 @@ const pageTrackWidth = (pageWidth) => pageWidth + 2 * PANEL_METRICS.pageMargin;
49
51
  const panelLayoutThresholds = ({ pageWidth, outline, comments }) => {
50
52
  const page = pageTrackWidth(pageWidth);
51
53
  const commentsTrack = comments === "open" ? COMMENTS_TRACK_WIDTH : 0;
52
- const hasOutline = outline === "available";
54
+ const hasOutline = outline !== "absent";
53
55
  return {
54
56
  wide: page + commentsTrack + (hasOutline ? PANEL_METRICS.outlineColumnWidth : 0),
55
57
  medium: page + commentsTrack + (hasOutline ? PANEL_METRICS.outlineRailWidth : 0)
@@ -62,7 +64,7 @@ const OUTLINE_TRACK_WIDTH = {
62
64
  drawer: 0
63
65
  };
64
66
  const tierFor = (outline, comments) => {
65
- if (outline === "drawer" || comments === "drawer") return "narrow";
67
+ if (outline === "drawer" || outline === "expanded" || comments === "drawer") return "narrow";
66
68
  if (outline === "rail") return "medium";
67
69
  return "wide";
68
70
  };
@@ -73,11 +75,13 @@ const computePanelLayout = (input) => {
73
75
  const page = pageTrackWidth(input.pageWidth);
74
76
  const comments = (() => {
75
77
  if (input.comments === "closed") return "hidden";
76
- return availableWidth >= thresholds.medium ? "column" : "drawer";
78
+ const requiredWidth = input.outline === "expanded" ? thresholds.wide : thresholds.medium;
79
+ return availableWidth >= requiredWidth ? "column" : "drawer";
77
80
  })();
78
81
  const commentsGutter = comments === "column" ? COMMENTS_TRACK_WIDTH : 0;
79
82
  const outline = (() => {
80
83
  if (input.outline === "absent") return "none";
84
+ if (input.outline === "expanded") return "expanded";
81
85
  const beside = page + commentsGutter;
82
86
  if (availableWidth >= thresholds.wide) return "column";
83
87
  if (availableWidth >= beside + PANEL_METRICS.outlineRailWidth) return "rail";
@@ -87,7 +91,7 @@ const computePanelLayout = (input) => {
87
91
  tier: tierFor(outline, comments),
88
92
  outline,
89
93
  comments,
90
- outlineTrackWidth: OUTLINE_TRACK_WIDTH[outline],
94
+ outlineTrackWidth: outline === "expanded" ? Math.min(PANEL_METRICS.outlineColumnWidth, Math.max(PANEL_METRICS.controlMinimumSize, (availableWidth - commentsGutter) / 2)) : OUTLINE_TRACK_WIDTH[outline],
91
95
  commentsGutter,
92
96
  thresholds
93
97
  };
@@ -0,0 +1,18 @@
1
+ import { Mark } from "prosemirror-model";
2
+ import { Transform } from "prosemirror-transform";
3
+ //#region src/prosemirror/addTrackedDeletionMark.d.ts
4
+ type AddTrackedDeletionMarkOptions = {
5
+ tr: Transform;
6
+ from: number;
7
+ to: number;
8
+ mark: Mark;
9
+ insertionPolicy: "preserve-pending" | "retract-own";
10
+ };
11
+ /**
12
+ * Already deleted runs keep their revision identity and enclosing insertion path.
13
+ * An own insertion containing deleted descendants is marked rather than retracted:
14
+ * removing its carrier would also remove another pending revision.
15
+ */
16
+ declare const addTrackedDeletionMark: ({ tr, from, to, mark, insertionPolicy }: AddTrackedDeletionMarkOptions) => void;
17
+ //#endregion
18
+ export { addTrackedDeletionMark };
@@ -0,0 +1,87 @@
1
+ import { expectTrackedChangeMarkAttrs } from "./attrs/index.js";
2
+ import { trackedRevisionLayerOf } from "./trackedRevisionPath.js";
3
+ import { canCarryTrackedRunMark } from "./trackedRunInlineAtoms.js";
4
+ import { panic } from "better-result";
5
+ //#region src/prosemirror/addTrackedDeletionMark.ts
6
+ /**
7
+ * Already deleted runs keep their revision identity and enclosing insertion path.
8
+ * An own insertion containing deleted descendants is marked rather than retracted:
9
+ * removing its carrier would also remove another pending revision.
10
+ */
11
+ const addTrackedDeletionMark = ({ tr, from, to, mark, insertionPolicy }) => {
12
+ if (mark.type.name !== "deletion") panic("A tracked deletion requires a deletion mark");
13
+ const preservedDeletions = [];
14
+ const protectedCarriers = /* @__PURE__ */ new Set();
15
+ tr.doc.nodesBetween(from, to, (node, position) => {
16
+ const existing = node.marks.find(({ type }) => type.name === "deletion");
17
+ if (node.isInline && existing) {
18
+ const start = Math.max(from, position);
19
+ const end = Math.min(to, position + node.nodeSize);
20
+ if (start < end) preservedDeletions.push({
21
+ from: start,
22
+ to: end,
23
+ mark: existing
24
+ });
25
+ const at = tr.doc.resolve(position);
26
+ for (let depth = 1; depth <= at.depth; depth++) {
27
+ const ancestor = at.node(depth);
28
+ if (canCarryTrackedRunMark(ancestor)) protectedCarriers.add(ancestor);
29
+ }
30
+ }
31
+ return true;
32
+ });
33
+ const mapFrom = tr.mapping.maps.length;
34
+ const ranges = [];
35
+ const descendantDeletions = [];
36
+ const deletionFor = (node) => {
37
+ const insertion = node.marks.find(({ type }) => type.name === "insertion");
38
+ const ancestors = insertion ? expectTrackedChangeMarkAttrs(insertion)._docxRevisionAncestors : null;
39
+ return insertion && ancestors?.length ? mark.type.create({
40
+ ...mark.attrs,
41
+ _docxRevisionAncestors: [...ancestors, trackedRevisionLayerOf(insertion, node)]
42
+ }) : mark;
43
+ };
44
+ tr.doc.nodesBetween(from, to, (node, position) => {
45
+ if (!canCarryTrackedRunMark(node)) return true;
46
+ if (node.marks.some(({ type }) => type.name === "deletion")) return false;
47
+ const start = Math.max(from, position);
48
+ const end = Math.min(to, position + node.nodeSize);
49
+ if (start >= end) return false;
50
+ const insertion = node.marks.find(({ type }) => type.name === "insertion");
51
+ const ownInsertion = insertion !== void 0 && insertion.attrs["author"] === mark.attrs["author"];
52
+ const deletion = deletionFor(node);
53
+ const disposition = insertionPolicy === "retract-own" && ownInsertion && !protectedCarriers.has(node) ? "retract" : "mark";
54
+ const previous = ranges.at(-1);
55
+ if (previous?.to === start && previous.disposition === disposition && previous.mark.eq(deletion)) previous.to = end;
56
+ else ranges.push({
57
+ from: start,
58
+ to: end,
59
+ mark: deletion,
60
+ disposition
61
+ });
62
+ if (disposition === "mark") node.descendants((child, offset) => {
63
+ if (child.marks.some(({ type }) => type.name === "deletion")) return false;
64
+ const childDeletion = deletionFor(child);
65
+ if (childDeletion === mark) return true;
66
+ const childStart = Math.max(from, position + 1 + offset);
67
+ const childEnd = Math.min(to, position + 1 + offset + child.nodeSize);
68
+ if (childStart < childEnd) descendantDeletions.push({
69
+ from: childStart,
70
+ to: childEnd,
71
+ mark: childDeletion
72
+ });
73
+ return true;
74
+ });
75
+ return false;
76
+ });
77
+ for (const range of ranges.toReversed()) if (range.disposition === "retract") tr.delete(range.from, range.to);
78
+ else tr.addMark(range.from, range.to, range.mark);
79
+ const mapping = tr.mapping.slice(mapFrom);
80
+ for (const preserved of [...descendantDeletions, ...preservedDeletions]) {
81
+ const start = mapping.map(preserved.from);
82
+ const end = mapping.map(preserved.to);
83
+ if (start < end) tr.addMark(start, end, preserved.mark);
84
+ }
85
+ };
86
+ //#endregion
87
+ export { addTrackedDeletionMark };
@@ -8,6 +8,7 @@ import { attrsRecord, attrsResult, expectAttrs, expectCachedNodeAttrs, expectNod
8
8
  import { COUNTER_FORMAT_VALUES, EMPHASIS_MARK_VALUES, FIELD_TYPE_VALUES, FONT_HINT_VALUES, FONT_THEME_VALUES, HIGHLIGHT_COLOR_VALUES, IMAGE_HORIZONTAL_ALIGNMENT_VALUES, IMAGE_HORIZONTAL_RELATIVE_TO_VALUES, IMAGE_VERTICAL_ALIGNMENT_VALUES, IMAGE_VERTICAL_RELATIVE_TO_VALUES, IMAGE_WRAP_TEXT_VALUES, IMAGE_WRAP_TYPE_VALUES, LINE_SPACING_RULE_VALUES, OUTLINE_STYLE_ATTR_VALUES, PARAGRAPH_ALIGNMENT_VALUES, POSITIONAL_TAB_ALIGNMENT_VALUES, POSITIONAL_TAB_LEADER_VALUES, POSITIONAL_TAB_RELATIVE_TO_VALUES, SDT_LOCK_VALUES, SDT_TYPE_VALUES, SHADING_PATTERN_VALUES, TABLE_CELL_VERTICAL_ALIGNMENT_VALUES, TABLE_JUSTIFICATION_VALUES, TABLE_ROW_HEIGHT_RULE_VALUES, TABLE_WIDTH_TYPE_VALUES, TAB_LEADER_VALUES, TAB_STOP_ALIGNMENT_VALUES, TEXT_DIRECTION_VALUES, TEXT_EFFECT_VALUES, UNDERLINE_STYLE_VALUES } from "../../types/documentEnumValues.js";
9
9
  import { canonicalJson } from "../../utils/canonicalJson.js";
10
10
  import { normalizeHorizontalScalePercent } from "../../utils/horizontalScale.js";
11
+ import { isNoteReferenceOccurrenceId } from "../noteReferenceOccurrenceId.js";
11
12
  import { paragraphNumberingFromAttrValue } from "../numberingAttr.js";
12
13
  import { outlineLevelFromAttrValue } from "../outlineLevelAttr.js";
13
14
  import { isParagraphDirection } from "../paragraphDirection.js";
@@ -953,6 +954,10 @@ const readFootnoteRefMarkAttrs = (mark) => {
953
954
  const issues = [];
954
955
  expectMarkType(mark, "footnoteRef", issues);
955
956
  requiredStringOrNumber(attrs, "id", "footnoteRef.attrs.id", issues);
957
+ if (!isNoteReferenceOccurrenceId(attrs["occurrenceId"])) issues.push({
958
+ path: "footnoteRef.attrs.occurrenceId",
959
+ message: "Expected a nonblank occurrence identity."
960
+ });
956
961
  optionalOneOf(attrs, "noteType", "footnoteRef.attrs.noteType", issues, NOTE_TYPES);
957
962
  optionalOneOf(attrs, "vertAlign", "footnoteRef.attrs.vertAlign", issues, NOTE_REF_VERT_ALIGNS);
958
963
  optionalBoolean(attrs, "customMarkFollows", "footnoteRef.attrs.customMarkFollows", issues);
@@ -4,6 +4,28 @@ import { ParagraphPropsPatch, RunPropsPatch } from "@stll/docx-core/ops";
4
4
  //#region src/prosemirror/canonicalCommands.d.ts
5
5
  /** Command meaning before canonical positions are resolved by the session. */
6
6
  type CanonicalCommandIntent = {
7
+ type: "generateTOC";
8
+ at: number;
9
+ title: string;
10
+ } | {
11
+ type: "setHyperlink";
12
+ from: number;
13
+ to: number;
14
+ href: string;
15
+ tooltip?: string;
16
+ } | {
17
+ type: "removeHyperlink";
18
+ from: number;
19
+ to: number;
20
+ hyperlinkStyleId?: string;
21
+ } | {
22
+ type: "insertHyperlink";
23
+ from: number;
24
+ to: number;
25
+ text: string;
26
+ href: string;
27
+ tooltip?: string;
28
+ } | {
7
29
  type: "formatRun";
8
30
  from: number;
9
31
  to: number;
@@ -36,9 +58,6 @@ type CanonicalCommandDescriptor = (state: EditorState) => readonly CanonicalComm
36
58
  declare const withCanonicalCommand: (command: Command, descriptor: CanonicalCommandDescriptor) => Command;
37
59
  declare const getCanonicalCommandIntents: (command: Command, state: EditorState) => readonly CanonicalCommandIntent[] | undefined;
38
60
  declare const canonicalRunFormatting: (state: EditorState, patch: RunPropsPatch) => readonly [{
39
- readonly type: "formatRun";
40
- readonly from: number;
41
- readonly to: number;
42
61
  readonly patch: import("@stll/docx-core/ops").FormattingPatch<{
43
62
  bold?: boolean | undefined;
44
63
  boldCs?: boolean | undefined;
@@ -91,7 +110,12 @@ declare const canonicalRunFormatting: (state: EditorState, patch: RunPropsPatch)
91
110
  styleId?: string | undefined;
92
111
  preserved?: import("@stll/docx-core/model").PreservedMarkup | undefined;
93
112
  }>;
113
+ readonly from: number;
114
+ readonly to: number;
115
+ readonly type: "formatRun";
94
116
  }];
95
117
  declare const withCanonicalParagraphFormatting: (command: Command, patch: ParagraphPropsPatch | ((paragraph: Node) => ParagraphPropsPatch)) => Command;
118
+ /** Commands that read the selection start apply that same patch across selected paragraphs. */
119
+ declare const withCanonicalStartParagraphFormatting: (command: Command, patch: (paragraph: Node) => ParagraphPropsPatch) => Command;
96
120
  //#endregion
97
- export { CanonicalCommandIntent, canonicalRunFormatting, getCanonicalCommandIntents, withCanonicalCommand, withCanonicalParagraphFormatting };
121
+ export { CanonicalCommandIntent, canonicalRunFormatting, getCanonicalCommandIntents, withCanonicalCommand, withCanonicalParagraphFormatting, withCanonicalStartParagraphFormatting };