@magic-marker/prosemirror-suggest-changes 0.3.3-wrap-unwrap.13 → 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__/splitDetection.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/buildMaterializedPaths.js +2 -0
- package/dist/features/wrapUnwrap/structureChangesPlugin.d.ts +7 -4
- package/dist/features/wrapUnwrap/structureChangesPlugin.js +62 -3
- package/dist/features/wrapUnwrap/types.d.ts +2 -1
- 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 {};
|
|
@@ -30,6 +30,7 @@ export function buildMaterializedPaths(doc) {
|
|
|
30
30
|
};
|
|
31
31
|
paths.set(nodeId, {
|
|
32
32
|
nodeType: node.type.name,
|
|
33
|
+
node,
|
|
33
34
|
chain: [
|
|
34
35
|
parent
|
|
35
36
|
]
|
|
@@ -71,6 +72,7 @@ export function buildMaterializedPaths(doc) {
|
|
|
71
72
|
];
|
|
72
73
|
paths.set(nodeId, {
|
|
73
74
|
nodeType: node.type.name,
|
|
75
|
+
node,
|
|
74
76
|
chain
|
|
75
77
|
});
|
|
76
78
|
return true;
|
|
@@ -4,11 +4,14 @@ import { type SuggestionId } from "../../generateId.js";
|
|
|
4
4
|
import { type StructuralContextPath } from "./types.js";
|
|
5
5
|
import { Transform } from "prosemirror-transform";
|
|
6
6
|
export declare const structureChangesKey: PluginKey<any>;
|
|
7
|
+
export type SuggestStructureChangesReason = "split-derived-add";
|
|
8
|
+
export interface SuggestStructureChangesResult {
|
|
9
|
+
handled: boolean;
|
|
10
|
+
transform: Transform;
|
|
11
|
+
reason?: SuggestStructureChangesReason;
|
|
12
|
+
}
|
|
7
13
|
export declare function structureChangesPlugin(generateId?: (schema: Schema, doc?: Node) => SuggestionId, opts?: {
|
|
8
14
|
experimental_trackStructures?: StructuralContextPath[];
|
|
9
15
|
}): Plugin<any>;
|
|
10
|
-
export declare function suggestStructureChanges(docBefore: Node, docAfter: Node, structuralContextPaths: StructuralContextPath[], generateId?: (schema: Schema, doc?: Node) => SuggestionId):
|
|
11
|
-
handled: boolean;
|
|
12
|
-
transform: Transform;
|
|
13
|
-
};
|
|
16
|
+
export declare function suggestStructureChanges(docBefore: Node, docAfter: Node, structuralContextPaths: StructuralContextPath[], generateId?: (schema: Schema, doc?: Node) => SuggestionId): SuggestStructureChangesResult;
|
|
14
17
|
export declare function getRequiredStructuralContextPaths(structuralContextPaths: StructuralContextPath[] | undefined): StructuralContextPath[];
|
|
@@ -112,11 +112,19 @@ export function suggestStructureChanges(docBefore, docAfter, structuralContextPa
|
|
|
112
112
|
pathsBefore: Object.fromEntries(pathsBefore.entries()),
|
|
113
113
|
pathsAfter: Object.fromEntries(pathsAfter.entries())
|
|
114
114
|
});
|
|
115
|
-
const ops = getOps(pathsBefore, pathsAfter, structuralContextPaths);
|
|
115
|
+
const { ops, reason } = getOps(pathsBefore, pathsAfter, structuralContextPaths);
|
|
116
116
|
trace("suggestStructureChanges", "ops", {
|
|
117
|
-
ops: Object.fromEntries(ops.entries())
|
|
117
|
+
ops: Object.fromEntries(ops.entries()),
|
|
118
|
+
reason
|
|
118
119
|
});
|
|
119
120
|
const transform = new Transform(docAfter);
|
|
121
|
+
if (reason) {
|
|
122
|
+
return {
|
|
123
|
+
handled: false,
|
|
124
|
+
transform,
|
|
125
|
+
reason
|
|
126
|
+
};
|
|
127
|
+
}
|
|
120
128
|
addMarks(ops, transform, suggestionId);
|
|
121
129
|
return {
|
|
122
130
|
handled: ops.size > 0,
|
|
@@ -206,13 +214,22 @@ function getOps(beforePaths, afterPaths, structuralContextPaths) {
|
|
|
206
214
|
const hasStructuralContext = chainHasStructuralContext(afterPath.chain, structuralContextPaths);
|
|
207
215
|
// node is outside configured structural contexts
|
|
208
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
|
+
}
|
|
209
224
|
// node was added
|
|
210
225
|
const op = {
|
|
211
226
|
op: "add"
|
|
212
227
|
};
|
|
213
228
|
ops.set(id, op);
|
|
214
229
|
}
|
|
215
|
-
return
|
|
230
|
+
return {
|
|
231
|
+
ops
|
|
232
|
+
};
|
|
216
233
|
}
|
|
217
234
|
function getStructuralContextNodeTypes(structuralContextPaths) {
|
|
218
235
|
return new Set(structuralContextPaths.flat());
|
|
@@ -233,3 +250,45 @@ function containsContiguousPath(chainNodeTypes, structuralContextPath) {
|
|
|
233
250
|
}
|
|
234
251
|
return false;
|
|
235
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;
|
|
294
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
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
3
|
import { DOC_NODE_ID } from "./constants.js";
|
|
4
4
|
interface NodeParent {
|
|
@@ -33,6 +33,7 @@ export type Op = MoveOp | AddOp;
|
|
|
33
33
|
export type MaterializedPaths = Map<string, {
|
|
34
34
|
chain: Parent[];
|
|
35
35
|
nodeType: string;
|
|
36
|
+
node: Node;
|
|
36
37
|
}>;
|
|
37
38
|
export declare function guardDocParent(parent: Parent | undefined): parent is DocParent;
|
|
38
39
|
export declare function guardStructureMarkAttrs(attrs: Attrs): attrs is StructureMarkAttrs;
|