@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.
- package/dist/ai-edits/apply.js +415 -166
- package/dist/ai-edits/batch-claims.d.ts +21 -6
- package/dist/ai-edits/batch-claims.js +47 -16
- package/dist/ai-edits/headless.d.ts +20 -0
- package/dist/ai-edits/headless.js +140 -11
- package/dist/ai-edits/pending-suggestions.js +1 -1
- package/dist/ai-edits/read.d.ts +5 -3
- package/dist/ai-edits/read.js +97 -47
- package/dist/ai-edits/revisionStretches.d.ts +17 -0
- package/dist/ai-edits/revisionStretches.js +80 -0
- package/dist/ai-edits/snapshot.js +3 -2
- package/dist/ai-edits/table-cell-mutations.d.ts +3 -1
- package/dist/ai-edits/table-cell-mutations.js +2 -1
- package/dist/ai-edits/table-row-column-mutations.d.ts +6 -1
- package/dist/ai-edits/table-row-column-mutations.js +27 -10
- package/dist/compare/content-alignment.d.ts +5 -1
- package/dist/compare/content-alignment.js +39 -32
- package/dist/compare/inline-provenance.js +1 -1
- package/dist/controller/layoutPipeline.js +3 -2
- package/dist/docx/archiveInflation.d.ts +127 -0
- package/dist/docx/archiveInflation.js +181 -0
- package/dist/docx/metadataPrivacy.js +24 -1
- package/dist/docx/noteReferenceMark.d.ts +15 -0
- package/dist/docx/noteReferenceMark.js +48 -0
- package/dist/docx/paragraphParser.js +5 -3
- package/dist/docx/paragraphPropertySource.d.ts +1 -1
- package/dist/docx/paragraphPropertySource.js +4 -2
- package/dist/docx/parser.js +2 -2
- package/dist/docx/rezip.js +4 -2
- package/dist/docx/selectiveXmlPatch.d.ts +3 -1
- package/dist/docx/selectiveXmlPatch.js +81 -3
- package/dist/docx/server/boundedArchive.d.ts +8 -13
- package/dist/docx/server/boundedArchive.js +70 -59
- package/dist/docx/server/extractDocxText.d.ts +8 -2
- package/dist/docx/server/extractDocxText.js +2 -2
- package/dist/docx/server/validateDocxConformance.d.ts +1 -1
- package/dist/docx/server/validateDocxConformance.js +1 -0
- package/dist/docx/unzip.d.ts +19 -2
- package/dist/docx/unzip.js +135 -24
- package/dist/docx/verticalMergeProjection.d.ts +17 -0
- package/dist/docx/verticalMergeProjection.js +120 -0
- package/dist/fonts/embeddedFonts.js +1 -1
- package/dist/i18n/messages/catalogs.gen.d.ts +34 -34
- package/dist/i18n/messages/catalogs.gen.js +34 -34
- package/dist/i18n/messages/messages.gen.d.ts +2 -2
- package/dist/internal/wholeStoryRevisionResolution.js +95 -25
- package/dist/layout-engine/measure/measureParagraph.js +2 -2
- package/dist/layout-engine/measure/paragraphMeasureShared.d.ts +12 -1
- package/dist/layout-engine/measure/paragraphMeasureShared.js +14 -1
- package/dist/markdown/escape.d.ts +2 -2
- package/dist/markdown/escape.js +4 -5
- package/dist/markdown/renderBlock.js +91 -25
- package/dist/markdown/renderRuns.d.ts +10 -1
- package/dist/markdown/renderRuns.js +320 -59
- package/dist/prosemirror/clearRunColor.d.ts +6 -0
- package/dist/prosemirror/clearRunColor.js +46 -0
- package/dist/prosemirror/commands/comments.js +62 -22
- package/dist/prosemirror/commands/formatPainter.js +1 -1
- package/dist/prosemirror/commands/hyperlink.js +2 -1
- package/dist/prosemirror/commands/propertyChangeScope.d.ts +3 -2
- package/dist/prosemirror/commands/propertyChangeScope.js +28 -4
- package/dist/prosemirror/commands/resolveAllTableChanges.js +40 -41
- package/dist/prosemirror/commands/resolveParagraphProperties.js +33 -8
- package/dist/prosemirror/commands/tableCellMergeResolution.d.ts +7 -1
- package/dist/prosemirror/commands/tableCellMergeResolution.js +39 -20
- package/dist/prosemirror/commands/tableMergeFoldDecisions.d.ts +61 -0
- package/dist/prosemirror/commands/tableMergeFoldDecisions.js +79 -0
- package/dist/prosemirror/containerFinalParagraph.d.ts +11 -5
- package/dist/prosemirror/containerFinalParagraph.js +7 -6
- package/dist/prosemirror/conversion/fromProseDoc.d.ts +2 -13
- package/dist/prosemirror/conversion/fromProseDoc.js +12 -590
- package/dist/prosemirror/conversion/toProseDoc.d.ts +1 -13
- package/dist/prosemirror/conversion/toProseDoc.js +28 -262
- package/dist/prosemirror/documentSchema.d.ts +8 -0
- package/dist/prosemirror/documentSchema.js +18 -0
- package/dist/prosemirror/extensions/StarterKit.js +2 -0
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +104 -63
- package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +1 -1
- package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.js +24 -3
- package/dist/prosemirror/extensions/features/JoinedRunStyleExtension.d.ts +20 -0
- package/dist/prosemirror/extensions/features/JoinedRunStyleExtension.js +43 -0
- package/dist/prosemirror/extensions/features/ListExtension.js +10 -9
- package/dist/prosemirror/extensions/features/pastedHeadingStyles.js +1 -1
- package/dist/prosemirror/extensions/features/pastedHtmlLists.js +2 -4
- package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +14 -8
- package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +8 -4
- package/dist/prosemirror/extensions/marks/StrikeExtension.js +2 -2
- package/dist/prosemirror/extensions/marks/SubscriptExtension.js +2 -2
- package/dist/prosemirror/extensions/marks/SuperscriptExtension.js +2 -2
- package/dist/prosemirror/extensions/marks/TextColorExtension.js +4 -3
- package/dist/prosemirror/extensions/marks/markUtils.d.ts +8 -6
- package/dist/prosemirror/extensions/marks/markUtils.js +17 -8
- package/dist/prosemirror/extensions/marks/noteReferenceDeletion.d.ts +3 -1
- package/dist/prosemirror/extensions/marks/noteReferenceDeletion.js +12 -1
- package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.js +2 -2
- package/dist/prosemirror/extensions/nodes/HardBreakExtension.js +5 -3
- package/dist/prosemirror/extensions/nodes/TableExtension.js +104 -210
- package/dist/prosemirror/extensions/types.d.ts +7 -0
- package/dist/prosemirror/hyperlinkRemoval.d.ts +9 -0
- package/dist/prosemirror/hyperlinkRemoval.js +129 -0
- package/dist/prosemirror/index.d.ts +2 -1
- package/dist/prosemirror/index.js +2 -1
- package/dist/prosemirror/listNumbering.d.ts +3 -21
- package/dist/prosemirror/listNumbering.js +24 -39
- package/dist/prosemirror/listRendering.d.ts +29 -0
- package/dist/prosemirror/listRendering.js +26 -0
- package/dist/prosemirror/markupViewNotes.d.ts +8 -0
- package/dist/prosemirror/markupViewNotes.js +49 -0
- package/dist/prosemirror/markupViewProjection.d.ts +3 -1
- package/dist/prosemirror/markupViewProjection.js +7 -2
- package/dist/prosemirror/noteReferenceReview.d.ts +77 -0
- package/dist/prosemirror/noteReferenceReview.js +332 -0
- package/dist/prosemirror/paragraphIndentation.js +5 -2
- package/dist/prosemirror/paragraphMarkJoin.d.ts +6 -4
- package/dist/prosemirror/paragraphMarkJoin.js +21 -19
- package/dist/prosemirror/paragraphPropertyCarry.d.ts +64 -0
- package/dist/prosemirror/paragraphPropertyCarry.js +155 -0
- package/dist/prosemirror/plugins/documentStyleState.d.ts +26 -0
- package/dist/prosemirror/plugins/documentStyleState.js +32 -0
- package/dist/prosemirror/plugins/documentStyles.d.ts +2 -21
- package/dist/prosemirror/plugins/documentStyles.js +66 -29
- package/dist/prosemirror/plugins/index.d.ts +2 -1
- package/dist/prosemirror/plugins/index.js +2 -1
- package/dist/prosemirror/plugins/paragraphStyleResolution.d.ts +15 -0
- package/dist/prosemirror/plugins/paragraphStyleResolution.js +230 -0
- package/dist/prosemirror/plugins/suggestionMode.d.ts +12 -2
- package/dist/prosemirror/plugins/suggestionMode.js +387 -28
- package/dist/prosemirror/plugins/templateDirectives.d.ts +6 -0
- package/dist/prosemirror/plugins/templateDirectives.js +21 -7
- package/dist/prosemirror/plugins/templatePreviewValues.js +14 -21
- package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +22 -2
- package/dist/prosemirror/rebaseParagraphRunFormatting.js +39 -13
- package/dist/prosemirror/rebaseParagraphRuns.d.ts +36 -0
- package/dist/prosemirror/rebaseParagraphRuns.js +99 -0
- package/dist/prosemirror/runFormattingFromMarks.d.ts +18 -0
- package/dist/prosemirror/runFormattingFromMarks.js +567 -0
- package/dist/prosemirror/runFormattingReconciliation.js +10 -9
- package/dist/prosemirror/schema/nodes.d.ts +2 -0
- package/dist/prosemirror/styles/paragraphStyleCascade.d.ts +59 -0
- package/dist/prosemirror/styles/paragraphStyleCascade.js +145 -0
- package/dist/prosemirror/styles/resolvedStyleAttrs.d.ts +21 -2
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +38 -1
- package/dist/prosemirror/styles/tableStyleRegions.d.ts +31 -0
- package/dist/prosemirror/styles/tableStyleRegions.js +55 -0
- package/dist/prosemirror/tableCellPaste.d.ts +74 -0
- package/dist/prosemirror/tableCellPaste.js +525 -0
- package/dist/prosemirror/tableGridMutation.d.ts +53 -14
- package/dist/prosemirror/tableGridMutation.js +166 -18
- package/dist/prosemirror/tableRunIn.d.ts +51 -0
- package/dist/prosemirror/tableRunIn.js +165 -0
- package/dist/prosemirror/textInput.js +11 -2
- package/dist/prosemirror/trackedRevisionPath.d.ts +17 -0
- package/dist/prosemirror/trackedRevisionPath.js +32 -0
- package/dist/server.d.ts +2 -2
- 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 {
|
|
9
|
-
import {
|
|
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)
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
const
|
|
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 = (
|
|
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
|
|
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
|
|
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
|
|
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
|
}
|