@magic-marker/prosemirror-suggest-changes 0.3.3-wrap-unwrap.12 → 0.3.3-wrap-unwrap.14
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/features/joinOnDelete/index.js +11 -0
- package/dist/features/wrapUnwrap/__tests__/blockquoteStructure.playwright.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/splitDetection.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/buildMaterializedPaths.js +5 -2
- package/dist/features/wrapUnwrap/constants.d.ts +1 -1
- package/dist/features/wrapUnwrap/constants.js +1 -5
- package/dist/features/wrapUnwrap/revert/revertMoveOp.js +1 -1
- package/dist/features/wrapUnwrap/structureChangesPlugin.d.ts +10 -3
- package/dist/features/wrapUnwrap/structureChangesPlugin.js +103 -21
- package/dist/features/wrapUnwrap/types.d.ts +6 -3
- package/dist/features/wrapUnwrap/types.js +2 -1
- package/dist/index.d.ts +1 -1
- package/dist/testing/e2eTestSchema.d.ts +1 -1
- package/dist/testing/testBuilders.d.ts +1 -1
- package/dist/withSuggestChanges.d.ts +2 -0
- package/dist/withSuggestChanges.js +6 -4
- package/package.json +1 -1
|
@@ -140,9 +140,9 @@ export declare function assertReverted(finalState: EditorState, initialState: Ed
|
|
|
140
140
|
* This helper replaces the editor document with the provided JSON,
|
|
141
141
|
* clears transactions, and returns initial state.
|
|
142
142
|
*/
|
|
143
|
-
export declare function setupDocFromJSON(page: Page, docJSON:
|
|
143
|
+
export declare function setupDocFromJSON(page: Page, docJSON: object): Promise<{
|
|
144
144
|
initialState: EditorState;
|
|
145
|
-
initialDoc:
|
|
145
|
+
initialDoc: object;
|
|
146
146
|
}>;
|
|
147
147
|
/**
|
|
148
148
|
* Assert that document fully reverted to initial state.
|
|
@@ -2,6 +2,7 @@ import { Mark } from "prosemirror-model";
|
|
|
2
2
|
import { canJoin, Transform } from "prosemirror-transform";
|
|
3
3
|
import { ZWSP } from "../../constants.js";
|
|
4
4
|
import { getSuggestionMarks } from "../../utils.js";
|
|
5
|
+
import { guardStructureMarkAttrs } from "../wrapUnwrap/types.js";
|
|
5
6
|
export function isJoinMarkAttrs(attrs) {
|
|
6
7
|
if (attrs["type"] !== "join") return false;
|
|
7
8
|
if (attrs["data"] == null) return false;
|
|
@@ -94,7 +95,9 @@ export function maybeRevertJoinMark(tr, from, to, node, markType) {
|
|
|
94
95
|
attrs: $endOfNode.nodeAfter.attrs,
|
|
95
96
|
marks: $endOfNode.nodeAfter.marks.map((mark)=>mark.toJSON())
|
|
96
97
|
};
|
|
98
|
+
const shouldSuppressJoinMark = hasStructureAddMark($endOfNode.nodeBefore) || hasStructureAddMark($endOfNode.nodeAfter);
|
|
97
99
|
transform.join(mappedEndOfNode);
|
|
100
|
+
if (shouldSuppressJoinMark) return false;
|
|
98
101
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
99
102
|
const joinStep = transform.steps[transform.steps.length - 1];
|
|
100
103
|
const joinPos = joinStep.getMap().map(mappedEndOfNode);
|
|
@@ -111,6 +114,14 @@ export function maybeRevertJoinMark(tr, from, to, node, markType) {
|
|
|
111
114
|
});
|
|
112
115
|
return transform;
|
|
113
116
|
}
|
|
117
|
+
function hasStructureAddMark(node) {
|
|
118
|
+
const { structure } = getSuggestionMarks(node.type.schema);
|
|
119
|
+
return node.marks.some((mark)=>{
|
|
120
|
+
if (mark.type !== structure) return false;
|
|
121
|
+
if (!guardStructureMarkAttrs(mark.attrs)) return false;
|
|
122
|
+
return mark.attrs.data.op.op === "add";
|
|
123
|
+
});
|
|
124
|
+
}
|
|
114
125
|
/**
|
|
115
126
|
* Find ZWSP nodes marked as insertions and deletions with the same mark id
|
|
116
127
|
* Delete them from the given range
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getNodeId } from "./getNodeId.js";
|
|
2
|
+
import { DOC_NODE_ID } from "./constants.js";
|
|
2
3
|
const TRACE_ENABLED = true;
|
|
3
4
|
function trace(...args) {
|
|
4
5
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
@@ -17,8 +18,8 @@ export function buildMaterializedPaths(doc) {
|
|
|
17
18
|
const rightSibling = children[index + 1];
|
|
18
19
|
const rightSiblingId = rightSibling ? getNodeId(rightSibling) : null;
|
|
19
20
|
const parent = {
|
|
20
|
-
nodeId:
|
|
21
|
-
nodeType:
|
|
21
|
+
nodeId: DOC_NODE_ID,
|
|
22
|
+
nodeType: DOC_NODE_ID,
|
|
22
23
|
nodeAttrs: {},
|
|
23
24
|
nodeMarks: [],
|
|
24
25
|
childSiblingIds: [
|
|
@@ -29,6 +30,7 @@ export function buildMaterializedPaths(doc) {
|
|
|
29
30
|
};
|
|
30
31
|
paths.set(nodeId, {
|
|
31
32
|
nodeType: node.type.name,
|
|
33
|
+
node,
|
|
32
34
|
chain: [
|
|
33
35
|
parent
|
|
34
36
|
]
|
|
@@ -70,6 +72,7 @@ export function buildMaterializedPaths(doc) {
|
|
|
70
72
|
];
|
|
71
73
|
paths.set(nodeId, {
|
|
72
74
|
nodeType: node.type.name,
|
|
75
|
+
node,
|
|
73
76
|
chain
|
|
74
77
|
});
|
|
75
78
|
return true;
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
+
export const DOC_NODE_ID = "__doc__";
|
|
1
2
|
export const STRUCTURE_CHANGES_ADD_MARKS = "structure-changes-add-mark-type";
|
|
2
3
|
export const STRUCTURE_CHANGES_REVERT_MARKS = "structure-changes-revert-mark-type";
|
|
3
|
-
export const LIST_NODES = [
|
|
4
|
-
"orderedList",
|
|
5
|
-
"bulletList",
|
|
6
|
-
"listItem"
|
|
7
|
-
];
|
|
@@ -118,5 +118,5 @@ export function findInsertionPos(node, pos, parent, child) {
|
|
|
118
118
|
return leftSibling.pos + leftSibling.node.nodeSize;
|
|
119
119
|
}
|
|
120
120
|
// insert at end of node
|
|
121
|
-
return pos != null ? pos + node.nodeSize - 1 : node.
|
|
121
|
+
return pos != null ? pos + node.nodeSize - 1 : node.content.size;
|
|
122
122
|
}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { type Schema, type Node } from "prosemirror-model";
|
|
2
2
|
import { Plugin, PluginKey } from "prosemirror-state";
|
|
3
3
|
import { type SuggestionId } from "../../generateId.js";
|
|
4
|
+
import { type StructuralContextPath } from "./types.js";
|
|
4
5
|
import { Transform } from "prosemirror-transform";
|
|
5
6
|
export declare const structureChangesKey: PluginKey<any>;
|
|
6
|
-
export
|
|
7
|
-
export
|
|
7
|
+
export type SuggestStructureChangesReason = "split-derived-add";
|
|
8
|
+
export interface SuggestStructureChangesResult {
|
|
8
9
|
handled: boolean;
|
|
9
10
|
transform: Transform;
|
|
10
|
-
|
|
11
|
+
reason?: SuggestStructureChangesReason;
|
|
12
|
+
}
|
|
13
|
+
export declare function structureChangesPlugin(generateId?: (schema: Schema, doc?: Node) => SuggestionId, opts?: {
|
|
14
|
+
experimental_trackStructures?: StructuralContextPath[];
|
|
15
|
+
}): Plugin<any>;
|
|
16
|
+
export declare function suggestStructureChanges(docBefore: Node, docAfter: Node, structuralContextPaths: StructuralContextPath[], generateId?: (schema: Schema, doc?: Node) => SuggestionId): SuggestStructureChangesResult;
|
|
17
|
+
export declare function getRequiredStructuralContextPaths(structuralContextPaths: StructuralContextPath[] | undefined): StructuralContextPath[];
|
|
@@ -3,7 +3,7 @@ import { getSuggestionMarks } from "../../utils.js";
|
|
|
3
3
|
import { generateNextNumberId } from "../../generateId.js";
|
|
4
4
|
import { getNodeId } from "./getNodeId.js";
|
|
5
5
|
import { guardStructureMarkAttrs } from "./types.js";
|
|
6
|
-
import {
|
|
6
|
+
import { DOC_NODE_ID, STRUCTURE_CHANGES_ADD_MARKS } from "./constants.js";
|
|
7
7
|
import { Transform } from "prosemirror-transform";
|
|
8
8
|
import { isSuggestChangesEnabled, suggestChangesKey } from "../../plugin.js";
|
|
9
9
|
import { buildMaterializedPaths } from "./buildMaterializedPaths.js";
|
|
@@ -15,12 +15,8 @@ function trace(...args) {
|
|
|
15
15
|
console.log("[structureChanges]", ...args);
|
|
16
16
|
}
|
|
17
17
|
export const structureChangesKey = new PluginKey("@handlewithcare/prosemirror-suggest-changes-structure-changes");
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
// children: [{ type: [LIST_ITEM_NODE] }],
|
|
21
|
-
// };
|
|
22
|
-
// const structures = [listStructure];
|
|
23
|
-
export function structureChangesPlugin(generateId) {
|
|
18
|
+
export function structureChangesPlugin(generateId, opts) {
|
|
19
|
+
const structuralContextPaths = getRequiredStructuralContextPaths(opts?.experimental_trackStructures);
|
|
24
20
|
return new Plugin({
|
|
25
21
|
key: structureChangesKey,
|
|
26
22
|
appendTransaction (transactions, _oldState, newState) {
|
|
@@ -92,7 +88,7 @@ export function structureChangesPlugin(generateId) {
|
|
|
92
88
|
trace("structureChangesPlugin", "appendTransaction", [
|
|
93
89
|
...transactions
|
|
94
90
|
]);
|
|
95
|
-
const { transform } = suggestStructureChanges(oldDoc, newDoc, generateId);
|
|
91
|
+
const { transform } = suggestStructureChanges(oldDoc, newDoc, structuralContextPaths, generateId);
|
|
96
92
|
const tr = newState.tr;
|
|
97
93
|
transform.steps.forEach((step)=>{
|
|
98
94
|
tr.step(step);
|
|
@@ -108,7 +104,7 @@ function isEnabled(tr, editorState) {
|
|
|
108
104
|
const isEnabled = isSuggestChangesEnabled(editorState) && !tr.getMeta("history$") && !tr.getMeta("collab$") && !ySyncMeta.isUndoRedoOperation && !ySyncMeta.isChangeOrigin && !("skip" in (tr.getMeta(suggestChangesKey) ?? {}));
|
|
109
105
|
return isEnabled;
|
|
110
106
|
}
|
|
111
|
-
export function suggestStructureChanges(docBefore, docAfter, generateId) {
|
|
107
|
+
export function suggestStructureChanges(docBefore, docAfter, structuralContextPaths, generateId) {
|
|
112
108
|
const suggestionId = generateId ? generateId(docBefore.type.schema, docBefore) : generateNextNumberId(docBefore.type.schema, docBefore);
|
|
113
109
|
const pathsBefore = buildMaterializedPaths(docBefore);
|
|
114
110
|
const pathsAfter = buildMaterializedPaths(docAfter);
|
|
@@ -116,17 +112,31 @@ export function suggestStructureChanges(docBefore, docAfter, generateId) {
|
|
|
116
112
|
pathsBefore: Object.fromEntries(pathsBefore.entries()),
|
|
117
113
|
pathsAfter: Object.fromEntries(pathsAfter.entries())
|
|
118
114
|
});
|
|
119
|
-
const ops = getOps(pathsBefore, pathsAfter);
|
|
115
|
+
const { ops, reason } = getOps(pathsBefore, pathsAfter, structuralContextPaths);
|
|
120
116
|
trace("suggestStructureChanges", "ops", {
|
|
121
|
-
ops: Object.fromEntries(ops.entries())
|
|
117
|
+
ops: Object.fromEntries(ops.entries()),
|
|
118
|
+
reason
|
|
122
119
|
});
|
|
123
120
|
const transform = new Transform(docAfter);
|
|
121
|
+
if (reason) {
|
|
122
|
+
return {
|
|
123
|
+
handled: false,
|
|
124
|
+
transform,
|
|
125
|
+
reason
|
|
126
|
+
};
|
|
127
|
+
}
|
|
124
128
|
addMarks(ops, transform, suggestionId);
|
|
125
129
|
return {
|
|
126
130
|
handled: ops.size > 0,
|
|
127
131
|
transform
|
|
128
132
|
};
|
|
129
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
|
+
}
|
|
130
140
|
function addMarks(ops, tr, suggestionId) {
|
|
131
141
|
const perfAddMarks = performance.now();
|
|
132
142
|
const { structure } = getSuggestionMarks(tr.doc.type.schema);
|
|
@@ -172,20 +182,22 @@ function hasStructureAddMark(node) {
|
|
|
172
182
|
return mark.attrs.data.op.op === "add";
|
|
173
183
|
});
|
|
174
184
|
}
|
|
175
|
-
function getOps(beforePaths, afterPaths) {
|
|
185
|
+
function getOps(beforePaths, afterPaths, structuralContextPaths) {
|
|
176
186
|
const ops = new Map();
|
|
187
|
+
const contextNodeTypes = getStructuralContextNodeTypes(structuralContextPaths);
|
|
177
188
|
// first take care of nodes that exist in both
|
|
178
189
|
for (const [id, beforePath] of beforePaths){
|
|
179
|
-
if (
|
|
190
|
+
if (contextNodeTypes.has(beforePath.nodeType)) continue;
|
|
180
191
|
const afterPath = afterPaths.get(id);
|
|
181
192
|
// node was removed - do nothing
|
|
182
193
|
if (afterPath == null) continue;
|
|
194
|
+
if (contextNodeTypes.has(afterPath.nodeType)) continue;
|
|
183
195
|
const sameChain = sameParentChain(beforePath.chain, afterPath.chain);
|
|
184
196
|
// node did not move anywhere - do nothing
|
|
185
197
|
if (sameChain) continue;
|
|
186
|
-
const
|
|
187
|
-
// node is outside
|
|
188
|
-
if (!
|
|
198
|
+
const hasStructuralContext = chainHasStructuralContext(beforePath.chain, structuralContextPaths) || chainHasStructuralContext(afterPath.chain, structuralContextPaths);
|
|
199
|
+
// node is outside configured structural contexts
|
|
200
|
+
if (!hasStructuralContext) continue;
|
|
189
201
|
const op = {
|
|
190
202
|
op: "move",
|
|
191
203
|
from: beforePath.chain,
|
|
@@ -196,17 +208,87 @@ function getOps(beforePaths, afterPaths) {
|
|
|
196
208
|
// now take care of nodes that exist only in afterPaths
|
|
197
209
|
// (we don't care about nodes that exist only in beforePaths - they were deleted)
|
|
198
210
|
for (const [id, afterPath] of afterPaths){
|
|
199
|
-
if (
|
|
211
|
+
if (contextNodeTypes.has(afterPath.nodeType)) continue;
|
|
200
212
|
// ignore nodes that also exist in beforePaths - they are already handled
|
|
201
213
|
if (beforePaths.has(id)) continue;
|
|
202
|
-
const
|
|
203
|
-
// node is outside
|
|
204
|
-
if (!
|
|
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
|
+
}
|
|
205
224
|
// node was added
|
|
206
225
|
const op = {
|
|
207
226
|
op: "add"
|
|
208
227
|
};
|
|
209
228
|
ops.set(id, op);
|
|
210
229
|
}
|
|
211
|
-
return
|
|
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: __doc__->nodeTypeA->nodeTypeB->nodeTypeC->nodeTypeD
|
|
244
|
+
// contain a structural context path (a "sub chain") like nodeTypeB->nodeTypeC ?
|
|
245
|
+
function containsContiguousPath(chainNodeTypes, structuralContextPath) {
|
|
246
|
+
if (structuralContextPath.length > chainNodeTypes.length) return false;
|
|
247
|
+
for(let start = 0; start <= chainNodeTypes.length - structuralContextPath.length; start++){
|
|
248
|
+
const matches = structuralContextPath.every((nodeType, index)=>chainNodeTypes[start + index] === nodeType);
|
|
249
|
+
if (matches) return true;
|
|
250
|
+
}
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
// detect block split - try to look at the node above, concatenate two text contents,
|
|
254
|
+
// and see if it combines into a single node in the old document
|
|
255
|
+
function isSplitDerivedAdd(newNodeId, beforePaths, afterPaths, contextNodeTypes) {
|
|
256
|
+
const newNode = afterPaths.get(newNodeId)?.node;
|
|
257
|
+
if (!newNode || newNode.textContent === "") return false;
|
|
258
|
+
if (matchesSplitDerivedPair(newNodeId, newNode.textContent, beforePaths, afterPaths)) {
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
const afterPath = afterPaths.get(newNodeId);
|
|
262
|
+
if (!afterPath) return false;
|
|
263
|
+
for (const parent of afterPath.chain){
|
|
264
|
+
if (parent.nodeType === DOC_NODE_ID) continue;
|
|
265
|
+
if (!contextNodeTypes.has(parent.nodeType)) continue;
|
|
266
|
+
const parentNode = afterPaths.get(parent.nodeId)?.node;
|
|
267
|
+
if (!parentNode || parentNode.textContent === "") continue;
|
|
268
|
+
if (matchesSplitDerivedPair(parent.nodeId, parentNode.textContent, beforePaths, afterPaths)) {
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
function matchesSplitDerivedPair(rightNodeId, rightText, beforePaths, afterPaths) {
|
|
275
|
+
const rightPath = afterPaths.get(rightNodeId);
|
|
276
|
+
const previousSiblingId = rightPath?.chain[0]?.childSiblingIds[0];
|
|
277
|
+
if (!previousSiblingId) return false;
|
|
278
|
+
const previousBefore = beforePaths.has(previousSiblingId) ? beforePaths.get(previousSiblingId)?.node : null;
|
|
279
|
+
const previousAfter = afterPaths.has(previousSiblingId) ? afterPaths.get(previousSiblingId)?.node : null;
|
|
280
|
+
if (!previousBefore || !previousAfter) return false;
|
|
281
|
+
if (hasStructureAddMarkInSubtree(previousBefore)) return false;
|
|
282
|
+
return previousBefore.textContent === previousAfter.textContent + rightText;
|
|
283
|
+
}
|
|
284
|
+
function hasStructureAddMarkInSubtree(node) {
|
|
285
|
+
if (hasStructureAddMark(node)) return true;
|
|
286
|
+
let found = false;
|
|
287
|
+
node.descendants((descendant)=>{
|
|
288
|
+
if (descendant.isText) return false;
|
|
289
|
+
if (!hasStructureAddMark(descendant)) return true;
|
|
290
|
+
found = true;
|
|
291
|
+
return false;
|
|
292
|
+
});
|
|
293
|
+
return found;
|
|
212
294
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { type Attrs } from "prosemirror-model";
|
|
1
|
+
import { type Attrs, type Node } from "prosemirror-model";
|
|
2
2
|
import { type SuggestionId } from "../../generateId.js";
|
|
3
|
+
import { DOC_NODE_ID } from "./constants.js";
|
|
3
4
|
interface NodeParent {
|
|
4
5
|
nodeId: string;
|
|
5
6
|
nodeType: string;
|
|
@@ -14,9 +15,10 @@ export interface StructureMarkAttrs {
|
|
|
14
15
|
op: Op;
|
|
15
16
|
};
|
|
16
17
|
}
|
|
18
|
+
export type StructuralContextPath = readonly [string, ...string[]];
|
|
17
19
|
export interface DocParent extends Omit<NodeParent, "nodeId"> {
|
|
18
|
-
nodeId:
|
|
19
|
-
nodeType:
|
|
20
|
+
nodeId: typeof DOC_NODE_ID;
|
|
21
|
+
nodeType: typeof DOC_NODE_ID;
|
|
20
22
|
}
|
|
21
23
|
export type Parent = NodeParent | DocParent;
|
|
22
24
|
export interface MoveOp {
|
|
@@ -31,6 +33,7 @@ export type Op = MoveOp | AddOp;
|
|
|
31
33
|
export type MaterializedPaths = Map<string, {
|
|
32
34
|
chain: Parent[];
|
|
33
35
|
nodeType: string;
|
|
36
|
+
node: Node;
|
|
34
37
|
}>;
|
|
35
38
|
export declare function guardDocParent(parent: Parent | undefined): parent is DocParent;
|
|
36
39
|
export declare function guardStructureMarkAttrs(attrs: Attrs): attrs is StructureMarkAttrs;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { DOC_NODE_ID } from "./constants.js";
|
|
1
2
|
export function guardDocParent(parent) {
|
|
2
|
-
return parent != null && parent.nodeId ===
|
|
3
|
+
return parent != null && parent.nodeId === DOC_NODE_ID && parent.nodeType === DOC_NODE_ID;
|
|
3
4
|
}
|
|
4
5
|
export function guardStructureMarkAttrs(attrs) {
|
|
5
6
|
if (!("data" in attrs)) return false;
|
package/dist/index.d.ts
CHANGED
|
@@ -4,5 +4,5 @@ export { suggestChanges, suggestChangesKey, isSuggestChangesEnabled, } from "./p
|
|
|
4
4
|
export { withSuggestChanges, transformToSuggestionTransaction, } from "./withSuggestChanges.js";
|
|
5
5
|
export { ensureSelection as experimental_ensureSelection, ensureSelectionKey as experimental_ensureSelectionKey, isEnsureSelectionEnabled as experimental_isEnsureSelectionEnabled, } from "./ensureSelectionPlugin.js";
|
|
6
6
|
export { guardStructureMarkAttrs } from "./features/wrapUnwrap/types.js";
|
|
7
|
-
export type { Op as StructureOp, StructureMarkAttrs, } from "./features/wrapUnwrap/types.js";
|
|
7
|
+
export type { Op as StructureOp, StructureMarkAttrs, StructuralContextPath, } from "./features/wrapUnwrap/types.js";
|
|
8
8
|
export { wrappingInputRule as experimental_wrappingInputRule } from "./wrappingInputRule.js";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { Schema } from "prosemirror-model";
|
|
2
|
-
export declare function createSchema(deletionMarksVisibility?: "hidden" | "visible"): Schema<"blockquote" | "text" | "
|
|
2
|
+
export declare function createSchema(deletionMarksVisibility?: "hidden" | "visible"): Schema<"blockquote" | "text" | "doc" | "paragraph" | "horizontal_rule" | "heading" | "code_block" | "image" | "hard_break" | "orderedList" | "bulletList" | "listItem", "insertion" | "deletion" | "modification" | "structure" | "code" | "em" | "link" | "strong">;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Schema, type Node } from "prosemirror-model";
|
|
2
2
|
import { type MarkBuilder, type NodeBuilder } from "prosemirror-test-builder";
|
|
3
|
-
export declare const schema: Schema<"blockquote" | "text" | "
|
|
3
|
+
export declare const schema: Schema<"blockquote" | "text" | "doc" | "paragraph" | "horizontal_rule" | "heading" | "code_block" | "image" | "hard_break" | "orderedList" | "bulletList" | "listItem", "insertion" | "deletion" | "modification" | "structure" | "code" | "em" | "link" | "strong" | "difficulty">;
|
|
4
4
|
export declare const testBuilders: { [NodeTypeName in keyof (typeof schema)["nodes"]]: NodeBuilder; } & { [MarkTypeName in keyof (typeof schema)["marks"]]: MarkBuilder; } & {
|
|
5
5
|
schema: typeof schema;
|
|
6
6
|
};
|
|
@@ -3,6 +3,7 @@ import { type EditorState, type Transaction } from "prosemirror-state";
|
|
|
3
3
|
import { type Transform } from "prosemirror-transform";
|
|
4
4
|
import { type EditorView } from "prosemirror-view";
|
|
5
5
|
import { type SuggestionId } from "./generateId.js";
|
|
6
|
+
import { type StructuralContextPath } from "./features/wrapUnwrap/types.js";
|
|
6
7
|
/**
|
|
7
8
|
* Given a standard transaction from ProseMirror, produce
|
|
8
9
|
* a new transaction that tracks the changes from the original,
|
|
@@ -27,5 +28,6 @@ export declare function transformToSuggestionTransaction(originalTransaction: Tr
|
|
|
27
28
|
*/
|
|
28
29
|
export declare function withSuggestChanges(dispatchTransaction?: EditorView["dispatch"], generateId?: (schema: Schema, doc?: Node) => SuggestionId, opts?: {
|
|
29
30
|
experimental_trackStructureChanges?: boolean;
|
|
31
|
+
experimental_trackStructures?: StructuralContextPath[];
|
|
30
32
|
experimental_ensureUniqueNodeIds?: (transactions: Transaction[], oldDoc: Node, newDoc: Node) => Transform;
|
|
31
33
|
}): EditorView["dispatch"];
|
|
@@ -10,7 +10,7 @@ import { isSuggestChangesEnabled, suggestChangesKey } from "./plugin.js";
|
|
|
10
10
|
import { generateNextNumberId } from "./generateId.js";
|
|
11
11
|
import { getSuggestionMarks } from "./utils.js";
|
|
12
12
|
import { prependDeletionsWithZWSP } from "./prependDeletionsWithZWSP.js";
|
|
13
|
-
import { suggestStructureChanges } from "./features/wrapUnwrap/structureChangesPlugin.js";
|
|
13
|
+
import { getRequiredStructuralContextPaths, suggestStructureChanges } from "./features/wrapUnwrap/structureChangesPlugin.js";
|
|
14
14
|
const TRACE_ENABLED = true;
|
|
15
15
|
function trace(...args) {
|
|
16
16
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
@@ -121,13 +121,15 @@ function getStepHandler(step) {
|
|
|
121
121
|
if (isEnabled) {
|
|
122
122
|
let structureChangesResult = null;
|
|
123
123
|
const docBefore = transaction.docs[0];
|
|
124
|
-
|
|
124
|
+
const structuralContextPaths = opts?.experimental_trackStructureChanges ? getRequiredStructuralContextPaths(opts.experimental_trackStructures) : null;
|
|
125
|
+
const ensureUniqueNodeIds = opts?.experimental_ensureUniqueNodeIds;
|
|
126
|
+
if (transaction.docChanged && docBefore && structuralContextPaths && typeof ensureUniqueNodeIds === "function") {
|
|
125
127
|
trace("trying to track structure changes first...");
|
|
126
128
|
// after a transaction, some nodes may not yet have unique ids (they were just added, and the unique id plugin has not yet run)
|
|
127
129
|
// this hook allows to "post-process" the transaction and add the missing ids
|
|
128
130
|
// basically it allows to run the core logic of the unique ids plugin earlier
|
|
129
131
|
const perfUid = performance.now();
|
|
130
|
-
const uniqueNodeIdsTransform =
|
|
132
|
+
const uniqueNodeIdsTransform = ensureUniqueNodeIds([
|
|
131
133
|
transaction
|
|
132
134
|
], docBefore, transaction.doc);
|
|
133
135
|
trace("perf", "structure", "ensureUniqueNodsIds took", Number((performance.now() - perfUid).toFixed(2)), "ms");
|
|
@@ -137,7 +139,7 @@ function getStepHandler(step) {
|
|
|
137
139
|
// if handled, then ignore the main plugin
|
|
138
140
|
// otherwise use the main plugin
|
|
139
141
|
const perfStructure = performance.now();
|
|
140
|
-
structureChangesResult = suggestStructureChanges(docBefore, docAfter, generateId);
|
|
142
|
+
structureChangesResult = suggestStructureChanges(docBefore, docAfter, structuralContextPaths, generateId);
|
|
141
143
|
trace("perf", "structure", "suggestStructureChanges took", Number((performance.now() - perfStructure).toFixed(2)), "ms");
|
|
142
144
|
trace("structure changes transform completed", structureChangesResult.transform);
|
|
143
145
|
if (structureChangesResult.handled) {
|