@magic-marker/prosemirror-suggest-changes 0.4.0 → 0.4.1-wrap-unwrap.2
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/__tests__/playwrightHelpers.d.ts +2 -2
- package/dist/__tests__/playwrightPage.d.ts +53 -2
- package/dist/commands.js +222 -43
- package/dist/{ensureSelectionPlugin.js → features/ensureValidSelection/ensureSelectionPlugin.js} +44 -77
- package/dist/features/ensureValidSelection/ensureSelectionPlugin.test.d.ts +1 -0
- package/dist/features/ensureValidSelection/ensureSelectionPlugin.test.js +112 -0
- package/dist/features/ensureValidSelection/selectionPosition.d.ts +3 -0
- package/dist/features/ensureValidSelection/selectionPosition.js +50 -0
- package/dist/features/joinOnDelete/__tests__/joinOnDeleteInLists.playwright.test.d.ts +1 -0
- package/dist/features/joinOnDelete/__tests__/joinOnDeleteInListsTipTapStyle.playwright.test.d.ts +1 -0
- package/dist/features/joinOnDelete/__tests__/listWithJoinsAndStructureMarks.playwright.test.d.ts +1 -0
- package/dist/features/joinOnDelete/index.d.ts +4 -19
- package/dist/features/joinOnDelete/index.js +166 -53
- package/dist/features/joinOnDelete/normalizeJoinNodesMetadata.d.ts +6 -0
- package/dist/features/joinOnDelete/normalizeJoinNodesMetadata.js +24 -0
- package/dist/features/joinOnDelete/types.d.ts +36 -0
- package/dist/features/joinOnDelete/types.js +23 -0
- package/dist/features/transactionShaping/detectSpecialTransactionShape.d.ts +3 -0
- package/dist/features/transactionShaping/detectSpecialTransactionShape.js +4 -0
- package/dist/features/transactionShaping/index.d.ts +3 -0
- package/dist/features/transactionShaping/index.js +11 -0
- package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/detectTipTapParagraphIntoListJoin.d.ts +3 -0
- package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/detectTipTapParagraphIntoListJoin.js +48 -0
- package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/detectTipTapParagraphIntoListJoin.test.d.ts +1 -0
- package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/detectTipTapParagraphIntoListJoin.test.js +188 -0
- package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/handleTipTapParagraphIntoListJoin.d.ts +3 -0
- package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/handleTipTapParagraphIntoListJoin.js +69 -0
- package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/index.d.ts +2 -0
- package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/index.js +2 -0
- package/dist/features/transactionShaping/types.d.ts +20 -0
- package/dist/features/transactionShaping/types.js +1 -0
- package/dist/features/wrapUnwrap/__tests__/blockquoteStructure.playwright.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/buildMaterializedPaths.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/listStructure.playwright.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/listStructureTextEdits.playwright.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/splitDetection.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/addIdAttr.d.ts +2 -0
- package/dist/features/wrapUnwrap/addIdAttr.js +60 -0
- package/dist/features/wrapUnwrap/apply/applyStructureSuggestions.d.ts +10 -0
- package/dist/features/wrapUnwrap/apply/applyStructureSuggestions.js +41 -0
- package/dist/features/wrapUnwrap/apply/index.d.ts +5 -0
- package/dist/features/wrapUnwrap/apply/index.js +21 -0
- package/dist/features/wrapUnwrap/areEquivalentStructureMarks.d.ts +2 -0
- package/dist/features/wrapUnwrap/areEquivalentStructureMarks.js +17 -0
- package/dist/features/wrapUnwrap/buildMaterializedPaths.d.ts +3 -0
- package/dist/features/wrapUnwrap/buildMaterializedPaths.js +82 -0
- package/dist/features/wrapUnwrap/constants.d.ts +3 -0
- package/dist/features/wrapUnwrap/constants.js +3 -0
- package/dist/features/wrapUnwrap/generateUniqueNodeId.d.ts +1 -0
- package/dist/features/wrapUnwrap/generateUniqueNodeId.js +6 -0
- package/dist/features/wrapUnwrap/getNodeId.d.ts +2 -0
- package/dist/features/wrapUnwrap/getNodeId.js +5 -0
- package/dist/features/wrapUnwrap/revert/deleteNodeUpwards.d.ts +3 -0
- package/dist/features/wrapUnwrap/revert/deleteNodeUpwards.js +37 -0
- package/dist/features/wrapUnwrap/revert/index.d.ts +5 -0
- package/dist/features/wrapUnwrap/revert/index.js +19 -0
- package/dist/features/wrapUnwrap/revert/revertAddOp.d.ts +4 -0
- package/dist/features/wrapUnwrap/revert/revertAddOp.js +4 -0
- package/dist/features/wrapUnwrap/revert/revertMoveOp.d.ts +16 -0
- package/dist/features/wrapUnwrap/revert/revertMoveOp.js +122 -0
- package/dist/features/wrapUnwrap/revert/revertStructureSuggestions.d.ts +10 -0
- package/dist/features/wrapUnwrap/revert/revertStructureSuggestions.js +236 -0
- package/dist/features/wrapUnwrap/sameParentChain.d.ts +2 -0
- package/dist/features/wrapUnwrap/sameParentChain.js +4 -0
- package/dist/features/wrapUnwrap/structureChangesPlugin.d.ts +17 -0
- package/dist/features/wrapUnwrap/structureChangesPlugin.js +299 -0
- package/dist/features/wrapUnwrap/types.d.ts +54 -0
- package/dist/features/wrapUnwrap/types.js +23 -0
- package/dist/features/wrapUnwrap/uniqueNodeIdsPlugin.d.ts +17 -0
- package/dist/features/wrapUnwrap/uniqueNodeIdsPlugin.js +106 -0
- package/dist/generateId.js +31 -4
- package/dist/index.d.ts +6 -3
- package/dist/index.js +5 -3
- package/dist/listInputRules.d.ts +2 -0
- package/dist/listInputRules.js +14 -0
- package/dist/rebaseStep.d.ts +9 -0
- package/dist/rebaseStep.js +11 -0
- package/dist/replaceStep.d.ts +1 -1
- package/dist/replaceStep.js +1 -0
- package/dist/schema.d.ts +2 -1
- package/dist/schema.js +37 -1
- package/dist/testing/e2eTestSchema.d.ts +2 -0
- package/dist/testing/testBuilders.d.ts +1 -2
- package/dist/transformToSuggestionTransaction.d.ts +22 -0
- package/dist/transformToSuggestionTransaction.js +101 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +6 -2
- package/dist/withSuggestChanges.d.ts +11 -14
- package/dist/withSuggestChanges.js +64 -94
- package/dist/wrappingInputRule.d.ts +4 -0
- package/dist/wrappingInputRule.js +28 -0
- package/package.json +1 -1
- package/src/features/joinOnDelete/README.md +0 -8
- /package/dist/{ensureSelectionPlugin.d.ts → features/ensureValidSelection/ensureSelectionPlugin.d.ts} +0 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { Plugin, PluginKey } from "prosemirror-state";
|
|
2
|
+
import { getSuggestionMarks } from "../../utils.js";
|
|
3
|
+
import { generateNextNumberId } from "../../generateId.js";
|
|
4
|
+
import { getNodeId } from "./getNodeId.js";
|
|
5
|
+
import { guardStructureMarkAttrs } from "./types.js";
|
|
6
|
+
import { DOC_NODE_ID, STRUCTURE_CHANGES_ADD_MARKS } from "./constants.js";
|
|
7
|
+
import { Transform } from "prosemirror-transform";
|
|
8
|
+
import { isSuggestChangesEnabled, suggestChangesKey } from "../../plugin.js";
|
|
9
|
+
import { buildMaterializedPaths } from "./buildMaterializedPaths.js";
|
|
10
|
+
import { sameParentChain } from "./sameParentChain.js";
|
|
11
|
+
const TRACE_ENABLED = false;
|
|
12
|
+
function trace(...args) {
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
14
|
+
if (!TRACE_ENABLED) return;
|
|
15
|
+
console.log("[structureChanges]", ...args);
|
|
16
|
+
}
|
|
17
|
+
export const structureChangesKey = new PluginKey("@handlewithcare/prosemirror-suggest-changes-structure-changes");
|
|
18
|
+
export function structureChangesPlugin(generateId, opts) {
|
|
19
|
+
const structuralContextPaths = getRequiredStructuralContextPaths(opts?.experimental_trackStructures);
|
|
20
|
+
return new Plugin({
|
|
21
|
+
key: structureChangesKey,
|
|
22
|
+
appendTransaction (transactions, _oldState, newState) {
|
|
23
|
+
if (transactions.some((tr)=>!isEnabled(tr, newState))) {
|
|
24
|
+
console.warn("structureChangesPlugin", "tracking changes is disabled, skipping structure changes plugin", [
|
|
25
|
+
...transactions
|
|
26
|
+
]);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
// do nothing if doc hasn't changed
|
|
30
|
+
const docChanged = transactions.some((transaction)=>transaction.docChanged);
|
|
31
|
+
if (!docChanged) {
|
|
32
|
+
console.warn("structureChangesPlugin", "doc not changed, skipping", [
|
|
33
|
+
...transactions
|
|
34
|
+
]);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const firstTr = transactions[0];
|
|
38
|
+
const lastTr = transactions[transactions.length - 1];
|
|
39
|
+
const oldDoc = firstTr?.docs[0];
|
|
40
|
+
const newDoc = lastTr?.doc;
|
|
41
|
+
// do nothing if there isn't a pair of docs to compare
|
|
42
|
+
if (!oldDoc || !newDoc) {
|
|
43
|
+
console.warn("structureChangesPlugin", "old or new doc is missing, skipping", {
|
|
44
|
+
oldDoc,
|
|
45
|
+
newDoc,
|
|
46
|
+
transactions: [
|
|
47
|
+
...transactions
|
|
48
|
+
]
|
|
49
|
+
});
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
let idsSettled = true;
|
|
53
|
+
newDoc.descendants((node, pos)=>{
|
|
54
|
+
if (node.isText) return true;
|
|
55
|
+
const nodeId = getNodeId(node);
|
|
56
|
+
if (nodeId == null) {
|
|
57
|
+
console.warn("structureChangesPlugin", "node", node.type.name, "at pos", pos, "is missing a unique id", {
|
|
58
|
+
id: node.attrs["id"]
|
|
59
|
+
});
|
|
60
|
+
idsSettled = false;
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
return true;
|
|
64
|
+
});
|
|
65
|
+
oldDoc.descendants((node, pos)=>{
|
|
66
|
+
if (node.isText) return true;
|
|
67
|
+
const nodeId = getNodeId(node);
|
|
68
|
+
if (nodeId == null) {
|
|
69
|
+
console.warn("structureChangesPlugin", "node", node.type.name, "at pos", pos, "is missing a unique id", {
|
|
70
|
+
id: node.attrs["id"]
|
|
71
|
+
});
|
|
72
|
+
idsSettled = false;
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
return true;
|
|
76
|
+
});
|
|
77
|
+
// do nothing if some nodes are missing unique ids - the diff is not possible then
|
|
78
|
+
if (!idsSettled) {
|
|
79
|
+
console.warn("structureChangesPlugin", "ids not settled, skipping", {
|
|
80
|
+
oldDoc,
|
|
81
|
+
newDoc,
|
|
82
|
+
transactions: [
|
|
83
|
+
...transactions
|
|
84
|
+
]
|
|
85
|
+
});
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
trace("structureChangesPlugin", "appendTransaction", [
|
|
89
|
+
...transactions
|
|
90
|
+
]);
|
|
91
|
+
const { transform } = suggestStructureChanges(oldDoc, newDoc, structuralContextPaths, generateId);
|
|
92
|
+
const tr = newState.tr;
|
|
93
|
+
transform.steps.forEach((step)=>{
|
|
94
|
+
tr.step(step);
|
|
95
|
+
});
|
|
96
|
+
if (!tr.steps.length) return;
|
|
97
|
+
tr.setMeta(structureChangesKey, STRUCTURE_CHANGES_ADD_MARKS);
|
|
98
|
+
return tr;
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function isEnabled(tr, editorState) {
|
|
103
|
+
const ySyncMeta = tr.getMeta("y-sync$") ?? {};
|
|
104
|
+
const isEnabled = isSuggestChangesEnabled(editorState) && !tr.getMeta("history$") && !tr.getMeta("collab$") && !ySyncMeta.isUndoRedoOperation && !ySyncMeta.isChangeOrigin && !("skip" in (tr.getMeta(suggestChangesKey) ?? {}));
|
|
105
|
+
return isEnabled;
|
|
106
|
+
}
|
|
107
|
+
export function suggestStructureChanges(docBefore, docAfter, structuralContextPaths, generateId) {
|
|
108
|
+
const suggestionId = generateId ? generateId(docBefore.type.schema, docBefore) : generateNextNumberId(docBefore.type.schema, docBefore);
|
|
109
|
+
const pathsBefore = buildMaterializedPaths(docBefore);
|
|
110
|
+
const pathsAfter = buildMaterializedPaths(docAfter);
|
|
111
|
+
trace("suggestStructureChanges", "materialized paths", {
|
|
112
|
+
pathsBefore: Object.fromEntries(pathsBefore.entries()),
|
|
113
|
+
pathsAfter: Object.fromEntries(pathsAfter.entries())
|
|
114
|
+
});
|
|
115
|
+
const { ops, reason } = getOps(pathsBefore, pathsAfter, structuralContextPaths);
|
|
116
|
+
trace("suggestStructureChanges", "ops", {
|
|
117
|
+
ops: Object.fromEntries(ops.entries()),
|
|
118
|
+
reason
|
|
119
|
+
});
|
|
120
|
+
const transform = new Transform(docAfter);
|
|
121
|
+
if (reason) {
|
|
122
|
+
return {
|
|
123
|
+
handled: false,
|
|
124
|
+
transform,
|
|
125
|
+
reason
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
addMarks(ops, transform, suggestionId);
|
|
129
|
+
return {
|
|
130
|
+
handled: ops.size > 0,
|
|
131
|
+
transform
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
export function getRequiredStructuralContextPaths(structuralContextPaths) {
|
|
135
|
+
if (!structuralContextPaths?.length) {
|
|
136
|
+
throw new Error("experimental_trackStructures must be provided when structure tracking is enabled");
|
|
137
|
+
}
|
|
138
|
+
return structuralContextPaths;
|
|
139
|
+
}
|
|
140
|
+
function addMarks(ops, tr, suggestionId) {
|
|
141
|
+
const perfAddMarks = performance.now();
|
|
142
|
+
const { structure } = getSuggestionMarks(tr.doc.type.schema);
|
|
143
|
+
tr.doc.descendants((node, pos)=>{
|
|
144
|
+
if (node.isText) return true;
|
|
145
|
+
const nodeId = getNodeId(node);
|
|
146
|
+
if (nodeId == null) return true;
|
|
147
|
+
const op = ops.get(nodeId);
|
|
148
|
+
if (op == null) return true;
|
|
149
|
+
if (op.op === "move") {
|
|
150
|
+
if (hasStructureAddMark(node)) return true;
|
|
151
|
+
const inverseMoveMark = findInverseStructureMoveMark(node, op);
|
|
152
|
+
if (inverseMoveMark) {
|
|
153
|
+
tr.removeNodeMark(pos, inverseMoveMark);
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
tr.addNodeMark(pos, structure.create({
|
|
158
|
+
id: suggestionId,
|
|
159
|
+
data: {
|
|
160
|
+
op
|
|
161
|
+
}
|
|
162
|
+
}));
|
|
163
|
+
return true;
|
|
164
|
+
});
|
|
165
|
+
trace("perf", "addMarks", "took", Number((performance.now() - perfAddMarks).toFixed(2)), "ms");
|
|
166
|
+
}
|
|
167
|
+
function findInverseStructureMoveMark(node, op) {
|
|
168
|
+
const { structure } = getSuggestionMarks(node.type.schema);
|
|
169
|
+
return node.marks.find((mark)=>{
|
|
170
|
+
if (mark.type !== structure) return false;
|
|
171
|
+
if (!guardStructureMarkAttrs(mark.attrs)) return false;
|
|
172
|
+
const existingOp = mark.attrs.data.op;
|
|
173
|
+
if (existingOp.op !== "move") return false;
|
|
174
|
+
return sameParentChain(existingOp.from, op.to) && sameParentChain(existingOp.to, op.from);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
function hasStructureAddMark(node) {
|
|
178
|
+
const { structure } = getSuggestionMarks(node.type.schema);
|
|
179
|
+
return node.marks.some((mark)=>{
|
|
180
|
+
if (mark.type !== structure) return false;
|
|
181
|
+
if (!guardStructureMarkAttrs(mark.attrs)) return false;
|
|
182
|
+
return mark.attrs.data.op.op === "add";
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
function getOps(beforePaths, afterPaths, structuralContextPaths) {
|
|
186
|
+
const ops = new Map();
|
|
187
|
+
const contextNodeTypes = getStructuralContextNodeTypes(structuralContextPaths);
|
|
188
|
+
// first take care of nodes that exist in both
|
|
189
|
+
for (const [id, beforePath] of beforePaths){
|
|
190
|
+
if (contextNodeTypes.has(beforePath.nodeType)) continue;
|
|
191
|
+
const afterPath = afterPaths.get(id);
|
|
192
|
+
// node was removed - do nothing
|
|
193
|
+
if (afterPath == null) continue;
|
|
194
|
+
if (contextNodeTypes.has(afterPath.nodeType)) continue;
|
|
195
|
+
const sameChain = sameParentChain(beforePath.chain, afterPath.chain);
|
|
196
|
+
// node did not move anywhere - do nothing
|
|
197
|
+
if (sameChain) continue;
|
|
198
|
+
const hasStructuralContext = chainHasStructuralContext(beforePath.chain, structuralContextPaths) || chainHasStructuralContext(afterPath.chain, structuralContextPaths);
|
|
199
|
+
// node is outside configured structural contexts
|
|
200
|
+
if (!hasStructuralContext) continue;
|
|
201
|
+
const op = {
|
|
202
|
+
op: "move",
|
|
203
|
+
from: beforePath.chain,
|
|
204
|
+
to: afterPath.chain
|
|
205
|
+
};
|
|
206
|
+
ops.set(id, op);
|
|
207
|
+
}
|
|
208
|
+
// now take care of nodes that exist only in afterPaths
|
|
209
|
+
// (we don't care about nodes that exist only in beforePaths - they were deleted)
|
|
210
|
+
for (const [id, afterPath] of afterPaths){
|
|
211
|
+
if (contextNodeTypes.has(afterPath.nodeType)) continue;
|
|
212
|
+
// ignore nodes that also exist in beforePaths - they are already handled
|
|
213
|
+
if (beforePaths.has(id)) continue;
|
|
214
|
+
const hasStructuralContext = chainHasStructuralContext(afterPath.chain, structuralContextPaths);
|
|
215
|
+
// node is outside configured structural contexts
|
|
216
|
+
if (!hasStructuralContext) continue;
|
|
217
|
+
// detect block split - if detected, bail out, and let the main plugin handle it
|
|
218
|
+
if (isSplitDerivedAdd(id, beforePaths, afterPaths, contextNodeTypes)) {
|
|
219
|
+
return {
|
|
220
|
+
ops: new Map(),
|
|
221
|
+
reason: "split-derived-add"
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
// node was added
|
|
225
|
+
const op = {
|
|
226
|
+
op: "add"
|
|
227
|
+
};
|
|
228
|
+
ops.set(id, op);
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
ops
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
function getStructuralContextNodeTypes(structuralContextPaths) {
|
|
235
|
+
return new Set(structuralContextPaths.flat());
|
|
236
|
+
}
|
|
237
|
+
function chainHasStructuralContext(chain, structuralContextPaths) {
|
|
238
|
+
const topDownChain = [
|
|
239
|
+
...chain
|
|
240
|
+
].reverse().filter((parent)=>parent.nodeType !== DOC_NODE_ID).map((parent)=>parent.nodeType);
|
|
241
|
+
return structuralContextPaths.some((path)=>containsContiguousPath(topDownChain, path));
|
|
242
|
+
}
|
|
243
|
+
// does a chain like: nodeTypeA->nodeTypeB->nodeTypeC
|
|
244
|
+
// end with a structural context path like nodeTypeB->nodeTypeC ?
|
|
245
|
+
function containsContiguousPath(chainNodeTypes, structuralContextPath) {
|
|
246
|
+
if (structuralContextPath.length > chainNodeTypes.length) return false;
|
|
247
|
+
let structuralPathPointer = structuralContextPath.length - 1;
|
|
248
|
+
let chainPointer = chainNodeTypes.length - 1;
|
|
249
|
+
while(structuralPathPointer >= 0){
|
|
250
|
+
if (structuralContextPath[structuralPathPointer] !== chainNodeTypes[chainPointer]) {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
structuralPathPointer--;
|
|
254
|
+
chainPointer--;
|
|
255
|
+
}
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
// detect block split - try to look at the node above, concatenate two text contents,
|
|
259
|
+
// and see if it combines into a single node in the old document
|
|
260
|
+
function isSplitDerivedAdd(newNodeId, beforePaths, afterPaths, contextNodeTypes) {
|
|
261
|
+
const newNode = afterPaths.get(newNodeId)?.node;
|
|
262
|
+
if (!newNode || newNode.textContent === "") return false;
|
|
263
|
+
if (matchesSplitDerivedPair(newNodeId, newNode.textContent, beforePaths, afterPaths)) {
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
const afterPath = afterPaths.get(newNodeId);
|
|
267
|
+
if (!afterPath) return false;
|
|
268
|
+
for (const parent of afterPath.chain){
|
|
269
|
+
if (parent.nodeType === DOC_NODE_ID) continue;
|
|
270
|
+
if (!contextNodeTypes.has(parent.nodeType)) continue;
|
|
271
|
+
const parentNode = afterPaths.get(parent.nodeId)?.node;
|
|
272
|
+
if (!parentNode || parentNode.textContent === "") continue;
|
|
273
|
+
if (matchesSplitDerivedPair(parent.nodeId, parentNode.textContent, beforePaths, afterPaths)) {
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
function matchesSplitDerivedPair(rightNodeId, rightText, beforePaths, afterPaths) {
|
|
280
|
+
const rightPath = afterPaths.get(rightNodeId);
|
|
281
|
+
const previousSiblingId = rightPath?.chain[0]?.childSiblingIds[0];
|
|
282
|
+
if (!previousSiblingId) return false;
|
|
283
|
+
const previousBefore = beforePaths.has(previousSiblingId) ? beforePaths.get(previousSiblingId)?.node : null;
|
|
284
|
+
const previousAfter = afterPaths.has(previousSiblingId) ? afterPaths.get(previousSiblingId)?.node : null;
|
|
285
|
+
if (!previousBefore || !previousAfter) return false;
|
|
286
|
+
if (hasStructureAddMarkInSubtree(previousBefore)) return false;
|
|
287
|
+
return previousBefore.textContent === previousAfter.textContent + rightText;
|
|
288
|
+
}
|
|
289
|
+
function hasStructureAddMarkInSubtree(node) {
|
|
290
|
+
if (hasStructureAddMark(node)) return true;
|
|
291
|
+
let found = false;
|
|
292
|
+
node.descendants((descendant)=>{
|
|
293
|
+
if (descendant.isText) return false;
|
|
294
|
+
if (!hasStructureAddMark(descendant)) return true;
|
|
295
|
+
found = true;
|
|
296
|
+
return false;
|
|
297
|
+
});
|
|
298
|
+
return found;
|
|
299
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type Attrs, type Node } from "prosemirror-model";
|
|
2
|
+
import { type SuggestionId } from "../../generateId.js";
|
|
3
|
+
import { DOC_NODE_ID } from "./constants.js";
|
|
4
|
+
interface NodeParent {
|
|
5
|
+
nodeId: string;
|
|
6
|
+
nodeType: string;
|
|
7
|
+
nodeAttrs: object;
|
|
8
|
+
nodeMarks: object[];
|
|
9
|
+
childSiblingIds: [string | null, string | null];
|
|
10
|
+
childIndex: number;
|
|
11
|
+
}
|
|
12
|
+
interface StructureMoveMarkAttrs {
|
|
13
|
+
id: SuggestionId;
|
|
14
|
+
data: {
|
|
15
|
+
op: MoveOp;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
interface StructureAddMarkAttrs {
|
|
19
|
+
id: SuggestionId;
|
|
20
|
+
data: {
|
|
21
|
+
op: AddOp;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export type StructureMarkAttrs = StructureMoveMarkAttrs | StructureAddMarkAttrs;
|
|
25
|
+
export type StructuralContextPath = readonly [string, ...string[]];
|
|
26
|
+
export interface DocParent extends Omit<NodeParent, "nodeId"> {
|
|
27
|
+
nodeId: typeof DOC_NODE_ID;
|
|
28
|
+
nodeType: typeof DOC_NODE_ID;
|
|
29
|
+
}
|
|
30
|
+
export type Parent = NodeParent | DocParent;
|
|
31
|
+
export interface MoveOp {
|
|
32
|
+
op: "move";
|
|
33
|
+
from: Parent[];
|
|
34
|
+
to: Parent[];
|
|
35
|
+
}
|
|
36
|
+
export interface AddOp {
|
|
37
|
+
op: "add";
|
|
38
|
+
}
|
|
39
|
+
export type Op = MoveOp | AddOp;
|
|
40
|
+
export type MaterializedPaths = Map<string, {
|
|
41
|
+
chain: Parent[];
|
|
42
|
+
nodeType: string;
|
|
43
|
+
node: Node;
|
|
44
|
+
}>;
|
|
45
|
+
export declare function guardDocParent(parent: Parent | undefined): parent is DocParent;
|
|
46
|
+
export declare function guardStructureMarkAttrs(attrs: Attrs): attrs is StructureMarkAttrs;
|
|
47
|
+
export declare function guardStructureMoveMarkAttrs(attrs: Attrs): attrs is StructureMoveMarkAttrs;
|
|
48
|
+
export declare function guardStructureAddMarkAttrs(attrs: Attrs): attrs is StructureAddMarkAttrs;
|
|
49
|
+
export interface StructureMarkObject {
|
|
50
|
+
type: "structure";
|
|
51
|
+
attrs: StructureMarkAttrs;
|
|
52
|
+
}
|
|
53
|
+
export declare function guardStructureMarkObject(mark: unknown): mark is StructureMarkObject;
|
|
54
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { DOC_NODE_ID } from "./constants.js";
|
|
2
|
+
export function guardDocParent(parent) {
|
|
3
|
+
return parent != null && parent.nodeId === DOC_NODE_ID && parent.nodeType === DOC_NODE_ID;
|
|
4
|
+
}
|
|
5
|
+
export function guardStructureMarkAttrs(attrs) {
|
|
6
|
+
if (!("data" in attrs)) return false;
|
|
7
|
+
const data = attrs["data"];
|
|
8
|
+
if (data === null || typeof data !== "object") return false;
|
|
9
|
+
if (!("op" in data)) return false;
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
export function guardStructureMoveMarkAttrs(attrs) {
|
|
13
|
+
return guardStructureMarkAttrs(attrs) && attrs.data.op.op === "move";
|
|
14
|
+
}
|
|
15
|
+
export function guardStructureAddMarkAttrs(attrs) {
|
|
16
|
+
return guardStructureMarkAttrs(attrs) && attrs.data.op.op === "add";
|
|
17
|
+
}
|
|
18
|
+
export function guardStructureMarkObject(mark) {
|
|
19
|
+
if (mark === null || typeof mark !== "object") return false;
|
|
20
|
+
if (!("type" in mark) || mark.type !== "structure") return false;
|
|
21
|
+
if (!("attrs" in mark)) return false;
|
|
22
|
+
return guardStructureMarkAttrs(mark.attrs);
|
|
23
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type Node } from "prosemirror-model";
|
|
2
|
+
import { Plugin, PluginKey, type Transaction } from "prosemirror-state";
|
|
3
|
+
import { Transform } from "prosemirror-transform";
|
|
4
|
+
export declare const uniqueNodeIdsPluginKey: PluginKey<{
|
|
5
|
+
completedInitialRun: boolean;
|
|
6
|
+
}>;
|
|
7
|
+
export declare const UNIQUE_NODE_IDS_PLUGIN_META = "unique-node-ids-plugin";
|
|
8
|
+
export declare function uniqueNodeIdsPlugin({ attributeName, generateID, }: {
|
|
9
|
+
attributeName: string;
|
|
10
|
+
generateID: () => string;
|
|
11
|
+
}): Plugin<{
|
|
12
|
+
completedInitialRun: boolean;
|
|
13
|
+
}>;
|
|
14
|
+
export declare function ensureUniqueNodeIds(_transactions: Transaction[], _oldDoc: Node, newDoc: Node, options: {
|
|
15
|
+
attributeName: string;
|
|
16
|
+
generateID: () => string;
|
|
17
|
+
}): Transform;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Plugin, PluginKey } from "prosemirror-state";
|
|
2
|
+
import { getNodeId } from "./getNodeId.js";
|
|
3
|
+
import { Transform } from "prosemirror-transform";
|
|
4
|
+
// unique ids plugin
|
|
5
|
+
// https://discuss.prosemirror.net/t/how-to-avoid-copying-attributes-to-new-paragraph/4568/2
|
|
6
|
+
// (also checks and fix duplicates that inevitably appear)
|
|
7
|
+
export const uniqueNodeIdsPluginKey = new PluginKey("@handlewithcare/prosemirror-suggest-changes-unique-node-ids");
|
|
8
|
+
export const UNIQUE_NODE_IDS_PLUGIN_META = "unique-node-ids-plugin";
|
|
9
|
+
const TRACE_ENABLED = false;
|
|
10
|
+
function trace(...args) {
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
12
|
+
if (!TRACE_ENABLED) return;
|
|
13
|
+
console.log("[uniqueNodeIdsPlugin]", ...args);
|
|
14
|
+
}
|
|
15
|
+
export function uniqueNodeIdsPlugin({ attributeName, generateID }) {
|
|
16
|
+
return new Plugin({
|
|
17
|
+
key: uniqueNodeIdsPluginKey,
|
|
18
|
+
appendTransaction (transactions, oldState, newState) {
|
|
19
|
+
trace("appendTransaction");
|
|
20
|
+
const pluginState = uniqueNodeIdsPluginKey.getState(newState);
|
|
21
|
+
// do nothing if doc hasn't changed (but make sure it runs initially)
|
|
22
|
+
const docChanged = transactions.some((transaction)=>transaction.docChanged);
|
|
23
|
+
if (!docChanged && pluginState?.completedInitialRun) {
|
|
24
|
+
trace("doc not changed, skipping", [
|
|
25
|
+
...transactions
|
|
26
|
+
]);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
trace("appendTransaction", [
|
|
30
|
+
...transactions
|
|
31
|
+
]);
|
|
32
|
+
const tr = newState.tr;
|
|
33
|
+
const transform = ensureUniqueNodeIds(transactions, oldState.doc, newState.doc, {
|
|
34
|
+
attributeName,
|
|
35
|
+
generateID
|
|
36
|
+
});
|
|
37
|
+
transform.steps.forEach((step)=>{
|
|
38
|
+
tr.step(step);
|
|
39
|
+
});
|
|
40
|
+
trace("tr steps", tr.steps);
|
|
41
|
+
if (!tr.steps.length) return;
|
|
42
|
+
tr.setMeta(uniqueNodeIdsPluginKey, UNIQUE_NODE_IDS_PLUGIN_META);
|
|
43
|
+
return tr;
|
|
44
|
+
},
|
|
45
|
+
state: {
|
|
46
|
+
init () {
|
|
47
|
+
return {
|
|
48
|
+
completedInitialRun: false
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
apply (tr, value) {
|
|
52
|
+
const meta = tr.getMeta(uniqueNodeIdsPluginKey);
|
|
53
|
+
if (meta === UNIQUE_NODE_IDS_PLUGIN_META && !value.completedInitialRun) {
|
|
54
|
+
return {
|
|
55
|
+
completedInitialRun: true
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
export function ensureUniqueNodeIds(_transactions, _oldDoc, newDoc, options) {
|
|
64
|
+
const tr = new Transform(newDoc);
|
|
65
|
+
const nodeIds = new Set();
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
67
|
+
if (TRACE_ENABLED) console.groupCollapsed("ensureUniqueNodeIds");
|
|
68
|
+
tr.doc.descendants((node, pos)=>{
|
|
69
|
+
if (node.isText) return false;
|
|
70
|
+
const nodeId = getNodeId(node);
|
|
71
|
+
// nodeId is set and is not duplicated
|
|
72
|
+
if (nodeId != null && !nodeIds.has(nodeId)) {
|
|
73
|
+
nodeIds.add(nodeId);
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
// nodeId is set and it is duplicated
|
|
77
|
+
if (nodeId != null && nodeIds.has(nodeId)) {
|
|
78
|
+
const id = options.generateID();
|
|
79
|
+
nodeIds.add(id);
|
|
80
|
+
tr.setNodeMarkup(pos, node.type, {
|
|
81
|
+
...node.attrs,
|
|
82
|
+
[options.attributeName]: id
|
|
83
|
+
}, node.marks);
|
|
84
|
+
trace("fixed duplicate id", id, "for node", node.type.name, "at pos", pos, {
|
|
85
|
+
was: nodeId,
|
|
86
|
+
is: id
|
|
87
|
+
});
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
// node id is not set
|
|
91
|
+
if (nodeId == null) {
|
|
92
|
+
const id = options.generateID();
|
|
93
|
+
nodeIds.add(id);
|
|
94
|
+
tr.setNodeMarkup(pos, node.type, {
|
|
95
|
+
...node.attrs,
|
|
96
|
+
[options.attributeName]: id
|
|
97
|
+
}, node.marks);
|
|
98
|
+
trace("set unique id", id, "for node", node.type.name, "at pos", pos);
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
return true;
|
|
102
|
+
});
|
|
103
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
104
|
+
if (TRACE_ENABLED) console.groupEnd();
|
|
105
|
+
return tr;
|
|
106
|
+
}
|
package/dist/generateId.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { getSuggestionMarks } from "./utils.js";
|
|
2
|
+
import { isJoinMark } from "./features/joinOnDelete/types.js";
|
|
3
|
+
import { normalizeJoinNodesMetadata } from "./features/joinOnDelete/normalizeJoinNodesMetadata.js";
|
|
2
4
|
export const suggestionIdValidate = "number|string";
|
|
3
5
|
export function parseSuggestionId(id) {
|
|
4
6
|
const parsed = parseInt(id, 10);
|
|
@@ -8,14 +10,39 @@ export function parseSuggestionId(id) {
|
|
|
8
10
|
return parsed;
|
|
9
11
|
}
|
|
10
12
|
export function generateNextNumberId(schema, doc) {
|
|
11
|
-
const { deletion, insertion, modification } = getSuggestionMarks(schema);
|
|
13
|
+
const { deletion, insertion, modification, structure } = getSuggestionMarks(schema);
|
|
12
14
|
// Find the highest change id in the document so far,
|
|
13
15
|
// and use that as the starting point for new changes
|
|
14
16
|
let suggestionId = 0;
|
|
15
17
|
doc?.descendants((node)=>{
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
// find max suggestion id across all suggestion marks and across all suggestion marks that are serialized into metadata of the join marks
|
|
19
|
+
const marks = node.marks.filter((mark)=>mark.type === insertion || mark.type === deletion || mark.type === modification || mark.type === structure);
|
|
20
|
+
const markIds = marks.map((mark)=>mark.attrs["id"]);
|
|
21
|
+
// collect suggestion ids of marks that are serialized into join marks metadata
|
|
22
|
+
const joinMarks = marks.filter((mark)=>isJoinMark(mark));
|
|
23
|
+
const joinMetadataMarkIds = [];
|
|
24
|
+
joinMarks.forEach((mark)=>{
|
|
25
|
+
const joinMetadata = normalizeJoinNodesMetadata(mark.attrs);
|
|
26
|
+
if (!joinMetadata) return;
|
|
27
|
+
joinMetadata.leftNodes.forEach((node)=>{
|
|
28
|
+
node.marks.forEach((mark)=>{
|
|
29
|
+
if (!mark.attrs["id"]) return;
|
|
30
|
+
joinMetadataMarkIds.push(mark.attrs["id"]);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
joinMetadata.rightNodes.forEach((node)=>{
|
|
34
|
+
node.marks.forEach((mark)=>{
|
|
35
|
+
if (!mark.attrs["id"]) return;
|
|
36
|
+
joinMetadataMarkIds.push(mark.attrs["id"]);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
const allMarkIds = [
|
|
41
|
+
...markIds,
|
|
42
|
+
...joinMetadataMarkIds
|
|
43
|
+
];
|
|
44
|
+
if (allMarkIds.length > 0) {
|
|
45
|
+
suggestionId = Math.max(suggestionId, ...allMarkIds);
|
|
19
46
|
return false;
|
|
20
47
|
}
|
|
21
48
|
return true;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
export { addSuggestionMarks, insertion, deletion, modification, hiddenDeletion, } from "./schema.js";
|
|
1
|
+
export { addSuggestionMarks, insertion, deletion, modification, hiddenDeletion, structure, } from "./schema.js";
|
|
2
2
|
export { selectSuggestion, revertSuggestion, revertSuggestions, applySuggestion, applySuggestions, enableSuggestChanges, disableSuggestChanges, toggleSuggestChanges, } from "./commands.js";
|
|
3
3
|
export { suggestChanges, suggestChangesKey, isSuggestChangesEnabled, } from "./plugin.js";
|
|
4
|
-
export { withSuggestChanges, transformToSuggestionTransaction, } from "./withSuggestChanges.js";
|
|
5
|
-
export { ensureSelection as experimental_ensureSelection, ensureSelectionKey as experimental_ensureSelectionKey, isEnsureSelectionEnabled as experimental_isEnsureSelectionEnabled, } from "./ensureSelectionPlugin.js";
|
|
4
|
+
export { withSuggestChanges, transformToSuggestionTransaction, suggestStructureChanges as experimental_suggestStructureChanges, } from "./withSuggestChanges.js";
|
|
5
|
+
export { ensureSelection as experimental_ensureSelection, ensureSelectionKey as experimental_ensureSelectionKey, isEnsureSelectionEnabled as experimental_isEnsureSelectionEnabled, } from "./features/ensureValidSelection/ensureSelectionPlugin.js";
|
|
6
|
+
export { guardStructureMarkAttrs } from "./features/wrapUnwrap/types.js";
|
|
7
|
+
export type { Op as StructureOp, StructureMarkAttrs, StructuralContextPath, } from "./features/wrapUnwrap/types.js";
|
|
8
|
+
export { wrappingInputRule as experimental_wrappingInputRule } from "./wrappingInputRule.js";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
export { addSuggestionMarks, insertion, deletion, modification, hiddenDeletion } from "./schema.js";
|
|
1
|
+
export { addSuggestionMarks, insertion, deletion, modification, hiddenDeletion, structure } from "./schema.js";
|
|
2
2
|
export { selectSuggestion, revertSuggestion, revertSuggestions, applySuggestion, applySuggestions, enableSuggestChanges, disableSuggestChanges, toggleSuggestChanges } from "./commands.js";
|
|
3
3
|
export { suggestChanges, suggestChangesKey, isSuggestChangesEnabled } from "./plugin.js";
|
|
4
|
-
export { withSuggestChanges, transformToSuggestionTransaction } from "./withSuggestChanges.js";
|
|
5
|
-
export { ensureSelection as experimental_ensureSelection, ensureSelectionKey as experimental_ensureSelectionKey, isEnsureSelectionEnabled as experimental_isEnsureSelectionEnabled } from "./ensureSelectionPlugin.js";
|
|
4
|
+
export { withSuggestChanges, transformToSuggestionTransaction, suggestStructureChanges as experimental_suggestStructureChanges } from "./withSuggestChanges.js";
|
|
5
|
+
export { ensureSelection as experimental_ensureSelection, ensureSelectionKey as experimental_ensureSelectionKey, isEnsureSelectionEnabled as experimental_isEnsureSelectionEnabled } from "./features/ensureValidSelection/ensureSelectionPlugin.js";
|
|
6
|
+
export { guardStructureMarkAttrs } from "./features/wrapUnwrap/types.js";
|
|
7
|
+
export { wrappingInputRule as experimental_wrappingInputRule } from "./wrappingInputRule.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { wrappingInputRule } from "./wrappingInputRule.js";
|
|
2
|
+
import { ZWSP } from "./constants.js";
|
|
3
|
+
export function listInputRules(bulletListNodeType, orderedListNodeType) {
|
|
4
|
+
const bulletListInputRule = wrappingInputRule(// ^ string start, [${ZWSP}\\s]* zero or more ZWSP or whitespace, ([-+*]) one of -+* , \\s one whitespace, $ end of string
|
|
5
|
+
// "u" flag treats \u as unicode code points instead of literal "u"
|
|
6
|
+
new RegExp(`^[${ZWSP}\\s]*([-+*])\\s$`, "u"), bulletListNodeType);
|
|
7
|
+
// ^ string start, [${ZWSP}\\s]* zero or more ZWSP or whitespace, ([0-9]+\\.) digit followed by dot, \\s one whitespace, $ end of string
|
|
8
|
+
// "u" flag treats \u as unicode code points instead of literal "u"
|
|
9
|
+
const orderedListInputRule = wrappingInputRule(new RegExp(`^[${ZWSP}\\s]*([0-9]+\\.)\\s$`, "u"), orderedListNodeType);
|
|
10
|
+
return [
|
|
11
|
+
bulletListInputRule,
|
|
12
|
+
orderedListInputRule
|
|
13
|
+
];
|
|
14
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type Step } from "prosemirror-transform";
|
|
2
|
+
/**
|
|
3
|
+
* Rebase a step onto a new lineage of steps
|
|
4
|
+
*
|
|
5
|
+
* @param step The step to rebase
|
|
6
|
+
* @param back The old steps to undo, in the order they were originally applied
|
|
7
|
+
* @param forth The new steps to map through
|
|
8
|
+
*/
|
|
9
|
+
export declare function rebaseStep(step: Step, back: Step[], forth: Step[]): Step | null;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rebase a step onto a new lineage of steps
|
|
3
|
+
*
|
|
4
|
+
* @param step The step to rebase
|
|
5
|
+
* @param back The old steps to undo, in the order they were originally applied
|
|
6
|
+
* @param forth The new steps to map through
|
|
7
|
+
*/ export function rebaseStep(step, back, forth) {
|
|
8
|
+
const reset = back.slice().reverse().reduce((acc, step)=>acc?.map(step.getMap().invert()) ?? null, step);
|
|
9
|
+
const rebased = forth.reduce((acc, step)=>acc?.map(step.getMap()) ?? null, reset);
|
|
10
|
+
return rebased;
|
|
11
|
+
}
|
package/dist/replaceStep.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Node } from "prosemirror-model";
|
|
2
2
|
import { type EditorState, type Transaction } from "prosemirror-state";
|
|
3
|
-
import { type
|
|
3
|
+
import { type ReplaceStep, type Step } from "prosemirror-transform";
|
|
4
4
|
import { type SuggestionId } from "./generateId.js";
|
|
5
5
|
/**
|
|
6
6
|
* Transform a replace step into its equivalent tracked steps.
|
package/dist/replaceStep.js
CHANGED
|
@@ -5,6 +5,7 @@ import { getSuggestionMarks } from "./utils.js";
|
|
|
5
5
|
import { joinBlocks } from "./features/joinBlocks/index.js";
|
|
6
6
|
import { collapseZWSPNodes, findJoinMark, joinNodesAndMarkJoinPoints, removeZWSPDeletions } from "./features/joinOnDelete/index.js";
|
|
7
7
|
import { adjustForStartToStartTextblockDeletion } from "./features/startToStartTextblockDeletion/index.js";
|
|
8
|
+
// import { rebaseStep } from "./rebaseStep.js";
|
|
8
9
|
/**
|
|
9
10
|
* Transform a replace step into its equivalent tracked steps.
|
|
10
11
|
*
|
package/dist/schema.d.ts
CHANGED
|
@@ -3,10 +3,11 @@ export declare const deletion: MarkSpec;
|
|
|
3
3
|
export declare const hiddenDeletion: MarkSpec;
|
|
4
4
|
export declare const insertion: MarkSpec;
|
|
5
5
|
export declare const modification: MarkSpec;
|
|
6
|
+
export declare const structure: MarkSpec;
|
|
6
7
|
/**
|
|
7
8
|
* Add the deletion, insertion, and modification marks to
|
|
8
9
|
* the provided MarkSpec map.
|
|
9
10
|
*/
|
|
10
11
|
export declare function addSuggestionMarks<Marks extends string>(marks: Record<Marks, MarkSpec>, opts?: {
|
|
11
12
|
experimental_deletions?: "hidden" | "visible";
|
|
12
|
-
}): Record<Marks | "deletion" | "insertion" | "modification", MarkSpec>;
|
|
13
|
+
}): Record<Marks | "deletion" | "insertion" | "modification" | "structure", MarkSpec>;
|