@stll/folio-core 0.53.0 → 0.54.1

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 (155) hide show
  1. package/dist/ai-edits/apply.js +415 -166
  2. package/dist/ai-edits/batch-claims.d.ts +21 -6
  3. package/dist/ai-edits/batch-claims.js +47 -16
  4. package/dist/ai-edits/headless.d.ts +20 -0
  5. package/dist/ai-edits/headless.js +140 -11
  6. package/dist/ai-edits/pending-suggestions.js +1 -1
  7. package/dist/ai-edits/read.d.ts +5 -3
  8. package/dist/ai-edits/read.js +97 -47
  9. package/dist/ai-edits/revisionStretches.d.ts +17 -0
  10. package/dist/ai-edits/revisionStretches.js +80 -0
  11. package/dist/ai-edits/snapshot.js +3 -2
  12. package/dist/ai-edits/table-cell-mutations.d.ts +3 -1
  13. package/dist/ai-edits/table-cell-mutations.js +2 -1
  14. package/dist/ai-edits/table-row-column-mutations.d.ts +6 -1
  15. package/dist/ai-edits/table-row-column-mutations.js +27 -10
  16. package/dist/compare/content-alignment.d.ts +5 -1
  17. package/dist/compare/content-alignment.js +39 -32
  18. package/dist/compare/inline-provenance.js +1 -1
  19. package/dist/controller/layoutPipeline.js +3 -2
  20. package/dist/docx/archiveInflation.d.ts +127 -0
  21. package/dist/docx/archiveInflation.js +181 -0
  22. package/dist/docx/metadataPrivacy.js +24 -1
  23. package/dist/docx/noteReferenceMark.d.ts +15 -0
  24. package/dist/docx/noteReferenceMark.js +48 -0
  25. package/dist/docx/paragraphParser.js +5 -3
  26. package/dist/docx/paragraphPropertySource.d.ts +1 -1
  27. package/dist/docx/paragraphPropertySource.js +4 -2
  28. package/dist/docx/parser.js +2 -2
  29. package/dist/docx/rezip.js +4 -2
  30. package/dist/docx/selectiveXmlPatch.d.ts +3 -1
  31. package/dist/docx/selectiveXmlPatch.js +81 -3
  32. package/dist/docx/server/boundedArchive.d.ts +8 -13
  33. package/dist/docx/server/boundedArchive.js +70 -59
  34. package/dist/docx/server/extractDocxText.d.ts +8 -2
  35. package/dist/docx/server/extractDocxText.js +2 -2
  36. package/dist/docx/server/validateDocxConformance.d.ts +1 -1
  37. package/dist/docx/server/validateDocxConformance.js +1 -0
  38. package/dist/docx/unzip.d.ts +19 -2
  39. package/dist/docx/unzip.js +135 -24
  40. package/dist/docx/verticalMergeProjection.d.ts +17 -0
  41. package/dist/docx/verticalMergeProjection.js +120 -0
  42. package/dist/fonts/embeddedFonts.js +1 -1
  43. package/dist/i18n/messages/catalogs.gen.d.ts +34 -34
  44. package/dist/i18n/messages/catalogs.gen.js +34 -34
  45. package/dist/i18n/messages/messages.gen.d.ts +2 -2
  46. package/dist/internal/wholeStoryRevisionResolution.js +95 -25
  47. package/dist/layout-engine/measure/measureParagraph.js +2 -2
  48. package/dist/layout-engine/measure/paragraphMeasureShared.d.ts +12 -1
  49. package/dist/layout-engine/measure/paragraphMeasureShared.js +14 -1
  50. package/dist/markdown/escape.d.ts +2 -2
  51. package/dist/markdown/escape.js +4 -5
  52. package/dist/markdown/renderBlock.js +91 -25
  53. package/dist/markdown/renderRuns.d.ts +10 -1
  54. package/dist/markdown/renderRuns.js +320 -59
  55. package/dist/prosemirror/clearRunColor.d.ts +6 -0
  56. package/dist/prosemirror/clearRunColor.js +46 -0
  57. package/dist/prosemirror/commands/comments.js +62 -22
  58. package/dist/prosemirror/commands/formatPainter.js +1 -1
  59. package/dist/prosemirror/commands/hyperlink.js +2 -1
  60. package/dist/prosemirror/commands/propertyChangeScope.d.ts +3 -2
  61. package/dist/prosemirror/commands/propertyChangeScope.js +28 -4
  62. package/dist/prosemirror/commands/resolveAllTableChanges.js +40 -41
  63. package/dist/prosemirror/commands/resolveParagraphProperties.js +33 -8
  64. package/dist/prosemirror/commands/tableCellMergeResolution.d.ts +7 -1
  65. package/dist/prosemirror/commands/tableCellMergeResolution.js +39 -20
  66. package/dist/prosemirror/commands/tableMergeFoldDecisions.d.ts +61 -0
  67. package/dist/prosemirror/commands/tableMergeFoldDecisions.js +79 -0
  68. package/dist/prosemirror/containerFinalParagraph.d.ts +11 -5
  69. package/dist/prosemirror/containerFinalParagraph.js +7 -6
  70. package/dist/prosemirror/conversion/fromProseDoc.d.ts +2 -13
  71. package/dist/prosemirror/conversion/fromProseDoc.js +12 -590
  72. package/dist/prosemirror/conversion/toProseDoc.d.ts +1 -13
  73. package/dist/prosemirror/conversion/toProseDoc.js +28 -262
  74. package/dist/prosemirror/documentSchema.d.ts +8 -0
  75. package/dist/prosemirror/documentSchema.js +18 -0
  76. package/dist/prosemirror/extensions/StarterKit.js +2 -0
  77. package/dist/prosemirror/extensions/core/ParagraphExtension.js +104 -63
  78. package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +1 -1
  79. package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.js +24 -3
  80. package/dist/prosemirror/extensions/features/JoinedRunStyleExtension.d.ts +20 -0
  81. package/dist/prosemirror/extensions/features/JoinedRunStyleExtension.js +43 -0
  82. package/dist/prosemirror/extensions/features/ListExtension.js +10 -9
  83. package/dist/prosemirror/extensions/features/pastedHeadingStyles.js +1 -1
  84. package/dist/prosemirror/extensions/features/pastedHtmlLists.js +2 -4
  85. package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +14 -8
  86. package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +8 -4
  87. package/dist/prosemirror/extensions/marks/StrikeExtension.js +2 -2
  88. package/dist/prosemirror/extensions/marks/SubscriptExtension.js +2 -2
  89. package/dist/prosemirror/extensions/marks/SuperscriptExtension.js +2 -2
  90. package/dist/prosemirror/extensions/marks/TextColorExtension.js +4 -3
  91. package/dist/prosemirror/extensions/marks/markUtils.d.ts +8 -6
  92. package/dist/prosemirror/extensions/marks/markUtils.js +17 -8
  93. package/dist/prosemirror/extensions/marks/noteReferenceDeletion.d.ts +3 -1
  94. package/dist/prosemirror/extensions/marks/noteReferenceDeletion.js +12 -1
  95. package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.js +2 -2
  96. package/dist/prosemirror/extensions/nodes/HardBreakExtension.js +5 -3
  97. package/dist/prosemirror/extensions/nodes/TableExtension.js +104 -210
  98. package/dist/prosemirror/extensions/types.d.ts +7 -0
  99. package/dist/prosemirror/hyperlinkRemoval.d.ts +9 -0
  100. package/dist/prosemirror/hyperlinkRemoval.js +129 -0
  101. package/dist/prosemirror/index.d.ts +2 -1
  102. package/dist/prosemirror/index.js +2 -1
  103. package/dist/prosemirror/listNumbering.d.ts +3 -21
  104. package/dist/prosemirror/listNumbering.js +24 -39
  105. package/dist/prosemirror/listRendering.d.ts +29 -0
  106. package/dist/prosemirror/listRendering.js +26 -0
  107. package/dist/prosemirror/markupViewNotes.d.ts +8 -0
  108. package/dist/prosemirror/markupViewNotes.js +49 -0
  109. package/dist/prosemirror/markupViewProjection.d.ts +3 -1
  110. package/dist/prosemirror/markupViewProjection.js +7 -2
  111. package/dist/prosemirror/noteReferenceReview.d.ts +77 -0
  112. package/dist/prosemirror/noteReferenceReview.js +332 -0
  113. package/dist/prosemirror/paragraphIndentation.js +5 -2
  114. package/dist/prosemirror/paragraphMarkJoin.d.ts +6 -4
  115. package/dist/prosemirror/paragraphMarkJoin.js +21 -19
  116. package/dist/prosemirror/paragraphPropertyCarry.d.ts +64 -0
  117. package/dist/prosemirror/paragraphPropertyCarry.js +155 -0
  118. package/dist/prosemirror/plugins/documentStyleState.d.ts +26 -0
  119. package/dist/prosemirror/plugins/documentStyleState.js +32 -0
  120. package/dist/prosemirror/plugins/documentStyles.d.ts +2 -21
  121. package/dist/prosemirror/plugins/documentStyles.js +66 -29
  122. package/dist/prosemirror/plugins/index.d.ts +2 -1
  123. package/dist/prosemirror/plugins/index.js +2 -1
  124. package/dist/prosemirror/plugins/paragraphStyleResolution.d.ts +15 -0
  125. package/dist/prosemirror/plugins/paragraphStyleResolution.js +230 -0
  126. package/dist/prosemirror/plugins/suggestionMode.d.ts +12 -2
  127. package/dist/prosemirror/plugins/suggestionMode.js +387 -28
  128. package/dist/prosemirror/plugins/templateDirectives.d.ts +6 -0
  129. package/dist/prosemirror/plugins/templateDirectives.js +21 -7
  130. package/dist/prosemirror/plugins/templatePreviewValues.js +14 -21
  131. package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +22 -2
  132. package/dist/prosemirror/rebaseParagraphRunFormatting.js +39 -13
  133. package/dist/prosemirror/rebaseParagraphRuns.d.ts +36 -0
  134. package/dist/prosemirror/rebaseParagraphRuns.js +99 -0
  135. package/dist/prosemirror/runFormattingFromMarks.d.ts +18 -0
  136. package/dist/prosemirror/runFormattingFromMarks.js +567 -0
  137. package/dist/prosemirror/runFormattingReconciliation.js +10 -9
  138. package/dist/prosemirror/schema/nodes.d.ts +2 -0
  139. package/dist/prosemirror/styles/paragraphStyleCascade.d.ts +59 -0
  140. package/dist/prosemirror/styles/paragraphStyleCascade.js +145 -0
  141. package/dist/prosemirror/styles/resolvedStyleAttrs.d.ts +21 -2
  142. package/dist/prosemirror/styles/resolvedStyleAttrs.js +38 -1
  143. package/dist/prosemirror/styles/tableStyleRegions.d.ts +31 -0
  144. package/dist/prosemirror/styles/tableStyleRegions.js +55 -0
  145. package/dist/prosemirror/tableCellPaste.d.ts +74 -0
  146. package/dist/prosemirror/tableCellPaste.js +525 -0
  147. package/dist/prosemirror/tableGridMutation.d.ts +53 -14
  148. package/dist/prosemirror/tableGridMutation.js +166 -18
  149. package/dist/prosemirror/tableRunIn.d.ts +51 -0
  150. package/dist/prosemirror/tableRunIn.js +165 -0
  151. package/dist/prosemirror/textInput.js +11 -2
  152. package/dist/prosemirror/trackedRevisionPath.d.ts +17 -0
  153. package/dist/prosemirror/trackedRevisionPath.js +32 -0
  154. package/dist/server.d.ts +2 -2
  155. package/package.json +6 -6
@@ -1,12 +1,23 @@
1
- import { expectParagraphAttrs } from "../attrs/index.js";
1
+ import { expectParagraphAttrs, expectTrackedChangeMarkAttrs } from "../attrs/index.js";
2
+ import { paragraphPropertiesSnapshot } from "../commands/propertyChangeScope.js";
3
+ import { paragraphEndsItsContainer } from "../containerFinalParagraph.js";
2
4
  import { encloseWholeControls } from "../contentControlRevisions.js";
3
5
  import { splitBlockClearBorders } from "../extensions/features/BaseKeymapExtension.js";
6
+ import { JOINED_RUNS_RESTYLED_META } from "../extensions/features/JoinedRunStyleExtension.js";
4
7
  import { expandNoteReferenceDeletionRange } from "../extensions/marks/noteReferenceDeletion.js";
8
+ import { carryParagraphProperties, paragraphLeftAfter, recordReplacedParagraphProperties } from "../paragraphPropertyCarry.js";
9
+ import { rebaseParagraphRuns } from "../rebaseParagraphRunFormatting.js";
10
+ import { RUN_FORMATTING_MARK_NAMES } from "../runFormattingMarkNames.js";
11
+ import { paragraphRunStyleContext } from "../runStyleFormatting.js";
12
+ import { cellPasteRange, pasteTableCells } from "../tableCellPaste.js";
5
13
  import { handleEditorBeforeInput } from "../textInput.js";
6
14
  import { canCarryTrackedRunMark } from "../trackedRunInlineAtoms.js";
15
+ import { getDocumentNumbering } from "./documentNumbering.js";
16
+ import { getDocumentStyleResolver } from "./documentStyleState.js";
7
17
  import { mintRevisionId, seedRevisionIdsFromDoc } from "./revisionIds.js";
8
- import { Mapping } from "prosemirror-transform";
9
- import { Plugin, PluginKey, TextSelection } from "prosemirror-state";
18
+ import { Slice } from "prosemirror-model";
19
+ import { Mapping, ReplaceStep } from "prosemirror-transform";
20
+ import { AllSelection, Plugin, PluginKey, Selection, TextSelection } from "prosemirror-state";
10
21
  import { isHistoryTransaction } from "prosemirror-history";
11
22
  import { undoInputRule } from "prosemirror-inputrules";
12
23
  //#region src/prosemirror/plugins/suggestionMode.ts
@@ -48,6 +59,27 @@ function makeRevisionInfo(state) {
48
59
  date: attrs.date
49
60
  };
50
61
  }
62
+ /**
63
+ * A removed paragraph mark leaves the paragraph after it once accepted. An
64
+ * edit that reads as the paragraph before it (Backspace or Delete at the
65
+ * break, a deletion running from inside one paragraph into another) gives the
66
+ * paragraph left that paragraph's properties now, under the mark's revision:
67
+ * accepting reads as the edit made directly, rejecting restores its own.
68
+ */
69
+ function carryIntoParagraphLeft(state, tr, source, survivorPos, info) {
70
+ carryParagraphProperties({
71
+ tr,
72
+ position: survivorPos,
73
+ source,
74
+ styleResolver: getDocumentStyleResolver(state),
75
+ numbering: getDocumentNumbering(state),
76
+ revision: {
77
+ id: info.id,
78
+ author: info.author ?? "",
79
+ date: info.date ?? ""
80
+ }
81
+ });
82
+ }
51
83
  function makeParagraphMarkInfo(pluginState) {
52
84
  const attrs = makeMarkAttrs(pluginState);
53
85
  return {
@@ -82,7 +114,7 @@ function findAdjacentRevisionForRange(doc, from, to, markTypeName, author) {
82
114
  * Retract the current author's own inserted text.
83
115
  * Processes in reverse order to maintain position validity.
84
116
  */
85
- function markRangeAsDeleted(tr, doc, from, to, insertionType, deletionType, pluginState) {
117
+ function markRangeAsDeleted(tr, doc, from, to, insertionType, deletionType, pluginState, joinState) {
86
118
  const ranges = [];
87
119
  doc.nodesBetween(from, to, (node, pos) => {
88
120
  if (!canCarryTrackedRunMark(node)) return;
@@ -98,19 +130,38 @@ function markRangeAsDeleted(tr, doc, from, to, insertionType, deletionType, plug
98
130
  });
99
131
  });
100
132
  const delAttrs = findAdjacentRevisionForRange(doc, from, to, "deletion", pluginState.author) || makeMarkAttrs(pluginState);
133
+ let removedMark = false;
101
134
  doc.nodesBetween(from, to, (node, pos) => {
102
135
  if (node.type.name === "tableRow" && pos >= from && pos + node.nodeSize <= to && node.attrs["trIns"] == null && node.attrs["trDel"] == null) tr.setNodeAttribute(pos, "trDel", delAttrs);
103
136
  if (node.type.name !== "paragraph") return;
104
137
  const boundary = pos + node.nodeSize;
105
- if (boundary > from && boundary <= to && node.attrs["pPrMark"] == null) tr.setNodeAttribute(pos, "pPrMark", {
106
- kind: "del",
107
- info: {
108
- id: delAttrs.revisionId,
109
- author: delAttrs.author,
110
- date: delAttrs.date
111
- }
112
- });
138
+ if (boundary > from && boundary <= to && node.attrs["pPrMark"] == null) {
139
+ tr.setNodeAttribute(pos, "pPrMark", {
140
+ kind: "del",
141
+ info: {
142
+ id: delAttrs.revisionId,
143
+ author: delAttrs.author,
144
+ date: delAttrs.date
145
+ }
146
+ });
147
+ removedMark = true;
148
+ }
113
149
  });
150
+ const $first = doc.resolve(from);
151
+ const $last = doc.resolve(to);
152
+ if (joinState && removedMark && $first.parent.type.name === "paragraph" && $last.parent.type.name === "paragraph" && $first.parent !== $last.parent && from > $first.start()) {
153
+ const lastPos = doc === tr.doc ? $last.before() : tr.mapping.map($last.before());
154
+ const lastMark = tr.doc.nodeAt(lastPos)?.attrs["pPrMark"];
155
+ const survivorPos = lastMark?.kind === "del" || lastMark?.kind === "moveFrom" ? paragraphLeftAfter({
156
+ doc: tr.doc,
157
+ paragraphPos: lastPos
158
+ }) ?? lastPos : lastPos;
159
+ carryIntoParagraphLeft(joinState, tr, $first.parent, survivorPos, {
160
+ id: delAttrs.revisionId,
161
+ author: delAttrs.author,
162
+ date: delAttrs.date
163
+ });
164
+ }
114
165
  if (ranges.length === 0) return;
115
166
  encloseWholeControls({
116
167
  tr,
@@ -124,6 +175,23 @@ function markRangeAsDeleted(tr, doc, from, to, insertionType, deletionType, plug
124
175
  else tr.addMark(range.from, range.to, deletionType.create(delAttrs));
125
176
  }
126
177
  }
178
+ /** Preserve clipboard revisions inside the revision that inserted their content. */
179
+ function enclosePastedRunRevisions({ tr, from, to, revision: insertion }) {
180
+ tr.doc.nodesBetween(from, to, (node, pos) => {
181
+ if (!canCarryTrackedRunMark(node)) return;
182
+ const revision = node.marks.find(({ type }) => type.name === "insertion" || type.name === "deletion");
183
+ if (!revision) return;
184
+ const previous = expectTrackedChangeMarkAttrs(revision);
185
+ tr.addMark(Math.max(from, pos), Math.min(to, pos + node.nodeSize), revision.type.create({
186
+ ...revision.attrs,
187
+ _docxRevisionAncestors: [{
188
+ type: "insertion",
189
+ ...insertion,
190
+ outerWrapperCount: 0
191
+ }, ...previous._docxRevisionAncestors ?? []]
192
+ }));
193
+ });
194
+ }
127
195
  /**
128
196
  * Track every inserted run, paragraph break, and table row in `[from, to)`.
129
197
  * Content already carrying a revision keeps its attribution.
@@ -137,10 +205,13 @@ function markRangeAsInserted(tr, doc, from, to, insertionType, deletionType, att
137
205
  if (start >= end) return;
138
206
  tr.addMark(start, end, insertionType.create(attrs));
139
207
  });
208
+ const $end = doc.resolve(to);
209
+ const endsBetweenBlocks = !$end.parent.isTextblock && $end.nodeBefore !== null && $end.nodeBefore.type.name !== "paragraph";
140
210
  doc.nodesBetween(from, to, (node, pos) => {
141
211
  if (node.type.name === "paragraph") {
142
212
  const boundary = pos + node.nodeSize;
143
- if (boundary > from && boundary < to && node.attrs["pPrMark"] == null) tr.setNodeAttribute(pos, "pPrMark", {
213
+ const keepsOwnMark = endsBetweenBlocks && boundary - 1 === from;
214
+ if (boundary > from && boundary < to && !keepsOwnMark && node.attrs["pPrMark"] == null) tr.setNodeAttribute(pos, "pPrMark", {
144
215
  kind: "ins",
145
216
  info: {
146
217
  id: attrs.revisionId,
@@ -162,32 +233,217 @@ function markRangeAsInserted(tr, doc, from, to, insertionType, deletionType, att
162
233
  * With track changes on, pasting over a non-empty text selection marks the
163
234
  * replaced text as a tracked deletion and the pasted slice as a tracked
164
235
  * insertion (so they read as one replacement), matching typing over a
165
- * selection. Returns false for a collapsed cursor or non-text selection so the
236
+ * selection. Select-all (an `AllSelection`) counts as a text selection over the
237
+ * whole document. Returns false for a collapsed cursor or another selection so the
166
238
  * default paste + the `appendTransaction` catch-all marks a plain insertion
167
239
  * (eigenpal/docx-editor#784).
168
240
  */
169
241
  function handleSuggestionPaste(view, slice, pluginState) {
170
242
  const { selection } = view.state;
171
- if (!(selection instanceof TextSelection) || selection.empty) return false;
243
+ const selectsAll = selection instanceof AllSelection;
244
+ if (!(selection instanceof TextSelection || selectsAll) || selection.empty) return false;
172
245
  const insertionType = view.state.schema.marks["insertion"];
173
246
  const deletionType = view.state.schema.marks["deletion"];
174
247
  if (!insertionType || !deletionType) return false;
175
- const { from, to } = selection;
248
+ if (selectsAll && (slice.openStart > 0 || slice.openEnd > 0)) slice = new Slice(view.state.tr.replaceSelection(slice).doc.content, 0, 0);
249
+ const { from, to } = selectsAll ? {
250
+ from: Selection.atStart(view.state.doc).from,
251
+ to: Selection.atEnd(view.state.doc).to
252
+ } : selection;
176
253
  const tr = view.state.tr;
177
254
  tr.setMeta(SUGGESTION_META, true);
178
- markRangeAsDeleted(tr, view.state.doc, from, to, insertionType, deletionType, pluginState);
179
- const insertFrom = tr.mapping.map(to);
180
- const sizeBefore = tr.doc.content.size;
181
- tr.replaceRange(insertFrom, insertFrom, slice);
182
- const insertTo = insertFrom + (tr.doc.content.size - sizeBefore);
183
- tr.removeMark(insertFrom, insertTo, deletionType);
184
- const insertAttrs = findAdjacentRevision(view.state.doc, from, "insertion", pluginState.author) || makeMarkAttrs(pluginState);
255
+ const { doc } = view.state;
256
+ const $from = doc.resolve(from);
257
+ const $to = doc.resolve(to);
258
+ const closedBlocks = slice.openStart === 0 && slice.content.firstChild?.isBlock === true;
259
+ const beforeStruck = closedBlocks && $from.parent.isTextblock && $from.parentOffset === 0;
260
+ const wholeBlocks = beforeStruck && $to.parent.isTextblock && $to.parentOffset === $to.parent.content.size;
261
+ const endsContainer = wholeBlocks && paragraphEndsItsContainer(doc.resolve($to.before()), $to.parent.type.name);
262
+ const rotates = endsContainer && slice.content.firstChild?.type === $to.parent.type && slice.content.lastChild?.type === $to.parent.type;
263
+ const tableFirst = endsContainer && !rotates;
264
+ if (!tableFirst) markRangeAsDeleted(tr, doc, from, wholeBlocks && !endsContainer ? $to.after() : to, insertionType, deletionType, pluginState);
265
+ const carryAcross = () => {
266
+ if (selectsAll || $from.parent.type.name !== "paragraph" || $from.parent === $to.parent || from <= $from.start()) return;
267
+ const firstPos = tr.mapping.map($from.before());
268
+ const mark = tr.doc.nodeAt(firstPos)?.attrs["pPrMark"];
269
+ const survivorPos = paragraphLeftAfter({
270
+ doc: tr.doc,
271
+ paragraphPos: firstPos
272
+ });
273
+ if (mark?.kind === "del" && survivorPos !== null) carryIntoParagraphLeft(view.state, tr, $from.parent, survivorPos, mark.info);
274
+ };
275
+ const inlineSlice = slice.content.firstChild?.isInline === true;
276
+ if (inlineSlice) carryAcross();
277
+ let at = tr.mapping.map(to);
278
+ if (rotates) at = tr.mapping.map($to.after());
279
+ else if (tableFirst) at = $from.before();
280
+ else if (beforeStruck) at = tr.mapping.map(from, -1);
281
+ const firstStep = tr.steps.length;
282
+ tr.replaceRange(at, at, slice);
283
+ const placed = tr.steps.slice(firstStep).flatMap((step, offset) => {
284
+ const later = new Mapping(tr.mapping.maps.slice(firstStep + offset + 1));
285
+ const ranges = [];
286
+ step.getMap().forEach((_oldFrom, _oldTo, newFrom, newTo) => {
287
+ ranges.push({
288
+ from: later.map(newFrom, -1),
289
+ to: later.map(newTo, 1)
290
+ });
291
+ });
292
+ return ranges;
293
+ });
294
+ let insertFrom = placed.length === 0 ? at : Math.min(...placed.map((range) => range.from));
295
+ let insertTo = placed.length === 0 ? at : Math.max(...placed.map((range) => range.to));
296
+ if (tableFirst) {
297
+ const struck = tr.steps.length;
298
+ markRangeAsDeleted(tr, tr.doc, tr.mapping.map(from), tr.mapping.map(to), insertionType, deletionType, pluginState);
299
+ const since = tr.mapping.slice(struck);
300
+ insertFrom = since.map(insertFrom, -1);
301
+ insertTo = since.map(insertTo, -1);
302
+ }
303
+ const insertAttrs = findAdjacentRevision(doc, from, "insertion", pluginState.author) || makeMarkAttrs(pluginState);
304
+ enclosePastedRunRevisions({
305
+ tr,
306
+ from: insertFrom,
307
+ to: insertTo,
308
+ revision: insertAttrs
309
+ });
185
310
  markRangeAsInserted(tr, tr.doc, insertFrom, insertTo, insertionType, deletionType, insertAttrs);
311
+ if (!inlineSlice) carryAcross();
312
+ if (!closedBlocks && $to.parent.type.name === "paragraph") {
313
+ const $end = tr.doc.resolve(tr.mapping.map(to));
314
+ if ($end.parent.type.name === "paragraph" && $end.before() !== tr.mapping.map($from.before())) recordReplacedParagraphProperties({
315
+ tr,
316
+ position: $end.before(),
317
+ replaced: $to.parent,
318
+ revision: {
319
+ id: insertAttrs.revisionId,
320
+ author: insertAttrs.author,
321
+ date: insertAttrs.date
322
+ }
323
+ });
324
+ }
325
+ const firstPasted = slice.content.firstChild;
326
+ if (selectsAll && !closedBlocks && firstPasted?.type.name === "paragraph") {
327
+ const $first = tr.doc.resolve(insertFrom);
328
+ if ($first.parent.type.name === "paragraph") carryParagraphProperties({
329
+ tr,
330
+ position: $first.before(),
331
+ source: firstPasted,
332
+ revision: {
333
+ id: insertAttrs.revisionId,
334
+ author: insertAttrs.author,
335
+ date: insertAttrs.date
336
+ },
337
+ styleResolver: getDocumentStyleResolver(view.state),
338
+ numbering: getDocumentNumbering(view.state)
339
+ });
340
+ }
341
+ if (rotates) {
342
+ const rotation = tr.steps.length;
343
+ const joined = rotateIntoFinalParagraph(view.state, tr, tr.mapping.map($to.before()), insertAttrs);
344
+ insertFrom = Math.min(tr.mapping.slice(rotation).map(insertFrom, -1), joined);
345
+ insertTo = tr.mapping.slice(rotation).map(insertTo);
346
+ }
186
347
  tr.setSelection(TextSelection.near(tr.doc.resolve(insertTo)));
187
348
  view.dispatch(tr.scrollIntoView());
188
349
  return true;
189
350
  }
190
351
  /**
352
+ * With track changes on, a paste into a table's cells (a cell selection, or a
353
+ * block of copied cells) replaces each cell's content the way a paste over a
354
+ * text selection does: the old content struck through, the pasted content
355
+ * after it as an insertion. When the pasted content is whole paragraphs, the
356
+ * last one ends the cell, so its paragraph mark is tracked as added too:
357
+ * rejecting then drops it and accepting joins the struck paragraphs into it.
358
+ */
359
+ function handleSuggestionTableCellPaste(view, slice, pluginState) {
360
+ const insertionType = view.state.schema.marks["insertion"];
361
+ const deletionType = view.state.schema.marks["deletion"];
362
+ if (!insertionType || !deletionType) return false;
363
+ const revision = makeMarkAttrs(pluginState);
364
+ return pasteTableCells(view.state, slice, (tr) => view.dispatch(tr.setMeta(SUGGESTION_META, true)), {
365
+ revision,
366
+ replaceCellContent: (tr, cellPos, content) => {
367
+ const cell = tr.doc.nodeAt(cellPos);
368
+ if (!cell) return;
369
+ const { from, to } = cellPasteRange(cell, cellPos, content);
370
+ const mapFrom = tr.mapping.maps.length;
371
+ markRangeAsDeleted(tr, tr.doc, from, to, insertionType, deletionType, pluginState);
372
+ const insertFrom = tr.mapping.slice(mapFrom).map(to);
373
+ const sizeBefore = tr.doc.content.size;
374
+ tr.replaceRange(insertFrom, insertFrom, content);
375
+ const insertTo = insertFrom + (tr.doc.content.size - sizeBefore);
376
+ enclosePastedRunRevisions({
377
+ tr,
378
+ from: insertFrom,
379
+ to: insertTo,
380
+ revision
381
+ });
382
+ markRangeAsInserted(tr, tr.doc, insertFrom, insertTo, insertionType, deletionType, revision);
383
+ tr.doc.nodesBetween(insertFrom, insertTo, (node, pos) => {
384
+ if (node.type.name === "paragraph" && pos >= insertFrom && pos + node.nodeSize <= insertTo && node.attrs["pPrMark"] == null) tr.setNodeAttribute(pos, "pPrMark", {
385
+ kind: "ins",
386
+ info: {
387
+ id: revision.revisionId,
388
+ author: revision.author,
389
+ date: revision.date
390
+ }
391
+ });
392
+ });
393
+ }
394
+ });
395
+ }
396
+ /**
397
+ * Pasted paragraphs replaced a container's last paragraph. They were placed
398
+ * after it, and the paragraph was struck but keeps its mark, as a container's
399
+ * last paragraph must. Join the first pasted paragraph onto it, so the struck
400
+ * text and the first paste share one paragraph whose break is the inserted one,
401
+ * and record the replaced paragraph's formatting on the paragraph that now ends
402
+ * the container: accepting leaves only the paste, and rejecting closes every
403
+ * pasted break back into one paragraph that returns to its old formatting.
404
+ * Returns the position of the joined paragraph.
405
+ */
406
+ function rotateIntoFinalParagraph(state, tr, replacedPos, attrs) {
407
+ const replaced = tr.doc.nodeAt(replacedPos);
408
+ const first = replaced ? tr.doc.nodeAt(replacedPos + replaced.nodeSize) : null;
409
+ if (!replaced || !first) return replacedPos;
410
+ const previousFormatting = paragraphPropertiesSnapshot(replaced);
411
+ const { _sectionProperties: sectionProperties } = replaced.attrs;
412
+ tr.join(replacedPos + replaced.nodeSize);
413
+ tr.setNodeMarkup(replacedPos, void 0, first.attrs);
414
+ const styleResolver = getDocumentStyleResolver(state);
415
+ if (styleResolver && replaced.content.size > 0) rebaseParagraphRuns({
416
+ previousContext: paragraphRunStyleContext(replaced, styleResolver),
417
+ paragraphPosition: replacedPos,
418
+ range: {
419
+ from: 0,
420
+ to: replaced.content.size
421
+ },
422
+ styleResolver,
423
+ tr
424
+ });
425
+ const $joined = tr.doc.resolve(replacedPos);
426
+ const container = $joined.parent;
427
+ let finalPos = replacedPos;
428
+ for (let index = $joined.index(); index < container.childCount - 1; index += 1) finalPos += container.child(index).nodeSize;
429
+ const final = tr.doc.nodeAt(finalPos);
430
+ if (final) tr.setNodeMarkup(finalPos, void 0, {
431
+ ...final.attrs,
432
+ pPrMark: null,
433
+ ...sectionProperties == null ? {} : { _sectionProperties: sectionProperties },
434
+ _propertyChanges: [{
435
+ type: "paragraphPropertyChange",
436
+ info: {
437
+ id: attrs.revisionId,
438
+ author: attrs.author,
439
+ date: attrs.date
440
+ },
441
+ previousFormatting
442
+ }]
443
+ });
444
+ return replacedPos;
445
+ }
446
+ /**
191
447
  * Insert text as a tracked insertion, optionally marking replaced selection as deletion.
192
448
  */
193
449
  function applySuggestionInsert(view, from, to, text, pluginState) {
@@ -196,20 +452,65 @@ function applySuggestionInsert(view, from, to, text, pluginState) {
196
452
  const tr = view.state.tr;
197
453
  tr.setMeta(SUGGESTION_META, true);
198
454
  tr.setMeta(SUGGESTED_TEXT_INPUT_META, text);
455
+ const selectsAll = view.state.selection instanceof AllSelection;
456
+ if (selectsAll) {
457
+ from = Math.max(from, Selection.atStart(view.state.doc).from);
458
+ to = Math.min(to, Selection.atEnd(view.state.doc).to);
459
+ }
460
+ const replacedFormatting = selectsAll ? (view.state.doc.resolve(from).nodeAfter?.marks ?? []).filter(({ type }) => RUN_FORMATTING_MARK_NAMES.has(type.name)) : null;
199
461
  const insertAttrs = findAdjacentRevision(view.state.doc, from, "insertion", pluginState.author) || makeMarkAttrs(pluginState);
200
462
  if (from !== to) {
201
463
  const deletionType = view.state.schema.marks["deletion"];
202
- if (deletionType) markRangeAsDeleted(tr, view.state.doc, from, to, insertionType, deletionType, pluginState);
464
+ if (deletionType) markRangeAsDeleted(tr, view.state.doc, from, to, insertionType, deletionType, pluginState, view.state);
203
465
  }
204
466
  const insertAt = tr.mapping.map(to);
205
467
  tr.insertText(text, insertAt, insertAt);
206
468
  const deletionType = view.state.schema.marks["deletion"];
207
469
  if (deletionType) tr.removeMark(insertAt, insertAt + text.length, deletionType);
208
470
  tr.addMark(insertAt, insertAt + text.length, insertionType.create(insertAttrs));
471
+ if (replacedFormatting) {
472
+ for (const mark of tr.doc.resolve(insertAt).nodeAfter?.marks ?? []) if (RUN_FORMATTING_MARK_NAMES.has(mark.type.name)) tr.removeMark(insertAt, insertAt + text.length, mark.type);
473
+ for (const mark of replacedFormatting) tr.addMark(insertAt, insertAt + text.length, mark);
474
+ }
475
+ separateSplitStretch(tr, insertAt, insertAt + text.length, insertAttrs.revisionId);
476
+ if (view.state.selection instanceof AllSelection) tr.setSelection(TextSelection.create(tr.doc, insertAt + text.length));
209
477
  view.dispatch(tr.scrollIntoView());
210
478
  return true;
211
479
  }
212
480
  /**
481
+ * Text typed inside another revision leaves that revision in two stretches.
482
+ * A saved package writes each stretch as a wrapper of its own with its own
483
+ * `w:id`, so the stretch after the typed text gets a fresh id now: the editor
484
+ * lists, resolves and saves the same changes a reopened package does.
485
+ */
486
+ function separateSplitStretch(tr, from, to, typedId) {
487
+ const $from = tr.doc.resolve(from);
488
+ const $to = tr.doc.resolve(to);
489
+ const before = $from.nodeBefore;
490
+ if (!before || $to.parent !== $from.parent) return;
491
+ const parent = $to.parent;
492
+ const start = $to.start();
493
+ for (const mark of before.marks) {
494
+ if (mark.type.name !== "insertion" && mark.type.name !== "deletion") continue;
495
+ const revisionId = mark.attrs["revisionId"];
496
+ if (revisionId === typedId || typeof revisionId !== "number") continue;
497
+ let end = to;
498
+ let offset = to - start;
499
+ while (offset < parent.content.size) {
500
+ const child = parent.childAfter(offset).node;
501
+ if (!child?.marks.some((candidate) => candidate.eq(mark))) break;
502
+ offset += child.nodeSize;
503
+ end = start + offset;
504
+ }
505
+ if (end === to) continue;
506
+ tr.removeMark(to, end, mark);
507
+ tr.addMark(to, end, mark.type.create({
508
+ ...mark.attrs,
509
+ revisionId: mintRevisionId()
510
+ }));
511
+ }
512
+ }
513
+ /**
213
514
  * Remove `from`..`to` of `tr.doc` as a suggestion: text the current author
214
515
  * inserted is retracted, anything else is marked deleted. `false` when the
215
516
  * editor is not suggesting.
@@ -248,6 +549,13 @@ function handleSuggestionEnter(view, pluginState) {
248
549
  info: makeParagraphMarkInfo(pluginState)
249
550
  };
250
551
  tr.setNodeAttribute(sourcePos, "pPrMark", markInfo);
552
+ const $caret = tr.selection.$from;
553
+ if ($caret.parent.type.name === "paragraph" && $caret.before() !== sourcePos) recordReplacedParagraphProperties({
554
+ tr,
555
+ position: $caret.before(),
556
+ replaced: $from.parent,
557
+ revision: markInfo.info
558
+ });
251
559
  }
252
560
  }
253
561
  view.dispatch(tr.scrollIntoView());
@@ -301,9 +609,22 @@ function applyPPrDel(view, targetParagraphPos, pluginState) {
301
609
  const tr = view.state.tr;
302
610
  tr.setMeta(SUGGESTION_META, true);
303
611
  const joinPos = targetParagraphPos + targetNode.nodeSize;
612
+ const joined = view.state.doc.nodeAt(joinPos);
304
613
  try {
305
614
  tr.join(joinPos);
306
615
  tr.setNodeAttribute(targetParagraphPos, "pPrMark", null);
616
+ const styleResolver = getDocumentStyleResolver(view.state);
617
+ if (styleResolver && joined?.type === targetNode.type && joined.content.size > 0) rebaseParagraphRuns({
618
+ previousContext: paragraphRunStyleContext(joined, styleResolver),
619
+ paragraphPosition: targetParagraphPos,
620
+ range: {
621
+ from: targetNode.content.size,
622
+ to: targetNode.content.size + joined.content.size
623
+ },
624
+ styleResolver,
625
+ tr
626
+ });
627
+ tr.setMeta(JOINED_RUNS_RESTYLED_META, true);
307
628
  view.dispatch(tr.scrollIntoView());
308
629
  } catch {
309
630
  return true;
@@ -318,6 +639,10 @@ function applyPPrDel(view, targetParagraphPos, pluginState) {
318
639
  info: makeParagraphMarkInfo(pluginState)
319
640
  };
320
641
  tr.setNodeAttribute(targetParagraphPos, "pPrMark", markInfo);
642
+ carryIntoParagraphLeft(view.state, tr, targetNode, paragraphLeftAfter({
643
+ doc: tr.doc,
644
+ paragraphPos: targetParagraphPos
645
+ }) ?? targetParagraphPos + targetNode.nodeSize, markInfo.info);
321
646
  view.dispatch(tr.scrollIntoView());
322
647
  return true;
323
648
  }
@@ -345,7 +670,7 @@ function handleSuggestionDelete(state, dispatch, direction) {
345
670
  from: rangeFrom.pos,
346
671
  to: rangeTo.pos
347
672
  }).sort((a, b) => b.from - a.from);
348
- for (const { from, to } of ranges) markRangeAsDeleted(tr, state.doc, from, to, insertionType, deletionType, pluginState);
673
+ for (const { from, to } of ranges) markRangeAsDeleted(tr, state.doc, from, to, insertionType, deletionType, pluginState, state);
349
674
  const cursorPos = tr.mapping.map($to.pos);
350
675
  tr.setSelection(TextSelection.near(tr.doc.resolve(cursorPos)));
351
676
  dispatch(tr.scrollIntoView());
@@ -414,6 +739,33 @@ function markComposedAsInsertion(view, from, pluginState, replaced) {
414
739
  view.dispatch(tr);
415
740
  }
416
741
  /**
742
+ * Paragraphs inserted into one (a paste at the caret) split it: its break ends
743
+ * the last part, which reads as the last inserted paragraph. Rejecting the
744
+ * inserted breaks leaves that part, so it records what the paragraph read as.
745
+ */
746
+ function recordSplitParagraph(tr, before, step, insertedFrom, insertedTo, attrs) {
747
+ if (!(step instanceof ReplaceStep) || !before) return;
748
+ const $at = before.resolve(step.from);
749
+ if ($at.parent.type.name !== "paragraph" || step.to > $at.after() || before.textBetween(step.from, step.to) !== "") return;
750
+ const split = $at.parent;
751
+ const $first = tr.doc.resolve(insertedFrom);
752
+ const $last = tr.doc.resolve(insertedTo);
753
+ let lastPos = null;
754
+ if ($last.parent.type.name === "paragraph") lastPos = $last.before();
755
+ else if ($last.nodeBefore?.type.name === "paragraph") lastPos = insertedTo - $last.nodeBefore.nodeSize;
756
+ if ($first.parent.type.name !== "paragraph" || lastPos === null || lastPos === $first.before()) return;
757
+ recordReplacedParagraphProperties({
758
+ tr,
759
+ position: lastPos,
760
+ replaced: split,
761
+ revision: {
762
+ id: attrs.revisionId,
763
+ author: attrs.author,
764
+ date: attrs.date
765
+ }
766
+ });
767
+ }
768
+ /**
417
769
  * Create the suggestion mode plugin.
418
770
  * When active, text edits become tracked changes.
419
771
  */
@@ -490,7 +842,7 @@ function createSuggestionModePlugin(initialActive = false, author = "User") {
490
842
  handleKeyDown(view, event) {
491
843
  const pluginState = suggestionModeKey.getState(view.state);
492
844
  if (!pluginState?.active) return false;
493
- if (event.key === "Enter") {
845
+ if (event.key === "Enter" && !event.shiftKey && !event.metaKey && !event.ctrlKey && !event.altKey) {
494
846
  const { $from, empty } = view.state.selection;
495
847
  if (empty && $from.parent.type.name === "paragraph" && expectParagraphAttrs($from.parent).numPr?.kind === "reference") return false;
496
848
  return handleSuggestionEnter(view, pluginState);
@@ -515,7 +867,7 @@ function createSuggestionModePlugin(initialActive = false, author = "User") {
515
867
  handlePaste(view, _event, slice) {
516
868
  const pluginState = suggestionModeKey.getState(view.state);
517
869
  if (!pluginState?.active) return false;
518
- return handleSuggestionPaste(view, slice, pluginState);
870
+ return handleSuggestionTableCellPaste(view, slice, pluginState) || handleSuggestionPaste(view, slice, pluginState);
519
871
  }
520
872
  },
521
873
  appendTransaction(transactions, _oldState, newState) {
@@ -538,7 +890,14 @@ function createSuggestionModePlugin(initialActive = false, author = "User") {
538
890
  const newFrom = following.map(stepFrom, -1);
539
891
  const newTo = following.map(stepTo, 1);
540
892
  if (newTo > newFrom) {
893
+ if (userTr.getMeta("paste")) enclosePastedRunRevisions({
894
+ tr,
895
+ from: newFrom,
896
+ to: newTo,
897
+ revision: markAttrs
898
+ });
541
899
  if (deletionType) markRangeAsInserted(tr, newState.doc, newFrom, newTo, insertionType, deletionType, markAttrs);
900
+ recordSplitParagraph(tr, userTr.docs[stepIndex], step, newFrom, newTo, markAttrs);
542
901
  }
543
902
  });
544
903
  }
@@ -573,4 +932,4 @@ function isSuggestionModeActive(state) {
573
932
  return suggestionModeKey.getState(state)?.active ?? false;
574
933
  }
575
934
  //#endregion
576
- export { SUGGESTED_TEXT_INPUT_META, SUGGESTION_BYPASS_META, SUGGESTION_META, createSuggestionModePlugin, deleteSelectionAsSuggestion, handleSuggestionEnter, handleSuggestionPaste, isSuggestionModeActive, makeRevisionInfo, paragraphBoundaryTarget, setSuggestionMode, suggestRangeDeletion, suggestionModeKey, toggleSuggestionMode };
935
+ export { SUGGESTED_TEXT_INPUT_META, SUGGESTION_BYPASS_META, SUGGESTION_META, createSuggestionModePlugin, deleteSelectionAsSuggestion, handleSuggestionEnter, handleSuggestionPaste, handleSuggestionTableCellPaste, isSuggestionModeActive, makeRevisionInfo, paragraphBoundaryTarget, setSuggestionMode, suggestRangeDeletion, suggestionModeKey, toggleSuggestionMode };
@@ -14,6 +14,12 @@ type DirectiveRange = {
14
14
  * array path the loop iterates (`{% for row in items %}` ⇒ `items`).
15
15
  */
16
16
  expr: string;
17
+ /**
18
+ * For an `if` / `elif` whose body is one field path plus a filter chain
19
+ * (`{% if consented | checkbox | label("…") %}`), that path; `expr` keeps
20
+ * the body as authored. Unset for every other tag.
21
+ */
22
+ conditionPath?: string;
17
23
  /** Clause-slot version selector, e.g. "v3" or "latest". */
18
24
  clauseVersion?: string;
19
25
  /** Loop alias of a `for` marker (`{% for row in items %}` ⇒ `row`); unset
@@ -3,9 +3,17 @@ import { collectBlockChunks, joinChunks, offsetToDocPos } from "./pmTextScan.js"
3
3
  import { PluginKey } from "prosemirror-state";
4
4
  import { assertNever, isBlockDirectiveKind, scanMarkers } from "@stll/template-conditions";
5
5
  //#region src/prosemirror/plugins/templateDirectives.ts
6
+ /**
7
+ * The body of an `if` / `elif` tag as authored: its inner text after the
8
+ * keyword. The grammar reads a body that is one path plus a filter chain as
9
+ * that path, with the chain in `filters`; the document still holds the whole
10
+ * body, and that is what a host keys a condition by.
11
+ */
12
+ const conditionBody = ({ inner, meta }) => inner.slice(meta.kind.length).trim();
6
13
  /** The display expression for a marker (field path, clause name, key,
7
- * condition, loop array path, loop property). */
8
- const directiveExpr = (meta) => {
14
+ * condition as authored, loop array path, loop property). */
15
+ const directiveExpr = (marker) => {
16
+ const { meta } = marker;
9
17
  switch (meta.kind) {
10
18
  case "placeholder": return meta.expr;
11
19
  case "clause": return meta.name;
@@ -13,7 +21,7 @@ const directiveExpr = (meta) => {
13
21
  case "ref": return meta.key;
14
22
  case "loop": return meta.property;
15
23
  case "if":
16
- case "elif": return meta.expr;
24
+ case "elif": return conditionBody(marker);
17
25
  case "for": return meta.path;
18
26
  case "else":
19
27
  case "endif":
@@ -63,6 +71,8 @@ const computeBlockDepths = (ranges) => {
63
71
  };
64
72
  /** The loop alias a `for` marker binds, or undefined for every other kind. */
65
73
  const directiveAlias = (meta) => meta.kind === "for" ? meta.alias : void 0;
74
+ /** The path a condition's filter chain configures, or undefined without a chain. */
75
+ const directiveConditionPath = (meta) => (meta.kind === "if" || meta.kind === "elif") && meta.filters.length > 0 ? meta.expr : void 0;
66
76
  const scanDirectives = (doc) => {
67
77
  const ranges = [];
68
78
  for (const chunks of collectBlockChunks(doc)) {
@@ -73,27 +83,31 @@ const scanDirectives = (doc) => {
73
83
  if (sole && sole.raw === trimmed && isBlockDirectiveKind(sole.meta.kind)) {
74
84
  const last = chunks.at(-1);
75
85
  const alias = directiveAlias(sole.meta);
86
+ const conditionPath = directiveConditionPath(sole.meta);
76
87
  ranges.push({
77
88
  from: chunks[0]?.start ?? 0,
78
89
  to: last ? last.end ?? last.start + last.text.length : 0,
79
90
  kind: sole.meta.kind,
80
- expr: directiveExpr(sole.meta),
91
+ expr: directiveExpr(sole),
81
92
  block: true,
82
- ...alias !== void 0 ? { alias } : {}
93
+ ...alias !== void 0 ? { alias } : {},
94
+ ...conditionPath !== void 0 ? { conditionPath } : {}
83
95
  });
84
96
  continue;
85
97
  }
86
98
  for (const marker of scanMarkers(joined)) {
87
99
  const clauseVersion = marker.meta.kind === "clause" ? marker.meta.version : void 0;
88
100
  const alias = directiveAlias(marker.meta);
101
+ const conditionPath = directiveConditionPath(marker.meta);
89
102
  ranges.push({
90
103
  from: offsetToDocPos(chunks, marker.start),
91
104
  to: offsetToDocPos(chunks, marker.end, "end"),
92
105
  kind: marker.meta.kind,
93
- expr: directiveExpr(marker.meta),
106
+ expr: directiveExpr(marker),
94
107
  block: false,
95
108
  ...clauseVersion !== void 0 ? { clauseVersion } : {},
96
- ...alias !== void 0 ? { alias } : {}
109
+ ...alias !== void 0 ? { alias } : {},
110
+ ...conditionPath !== void 0 ? { conditionPath } : {}
97
111
  });
98
112
  }
99
113
  }