@magic-marker/prosemirror-suggest-changes 0.3.3-wrap-unwrap.6 → 0.3.3-wrap-unwrap.8
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/commands.js +8 -7
- 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/revert/deleteNodeUpwards.d.ts +3 -0
- package/dist/features/wrapUnwrap/revert/deleteNodeUpwards.js +31 -0
- package/dist/features/wrapUnwrap/revert/index.d.ts +5 -0
- package/dist/features/wrapUnwrap/revert/index.js +21 -0
- package/dist/features/wrapUnwrap/revert/revertAddOp.d.ts +4 -0
- package/dist/features/wrapUnwrap/revert/revertAddOp.js +9 -0
- package/dist/features/wrapUnwrap/revert/revertMoveOp.d.ts +14 -0
- package/dist/features/wrapUnwrap/revert/revertMoveOp.js +149 -0
- package/dist/features/wrapUnwrap/revert/revertStructureSuggestions.d.ts +12 -0
- package/dist/features/wrapUnwrap/revert/revertStructureSuggestions.js +180 -0
- package/dist/index.d.ts +1 -1
- package/package.json +1 -1
- package/dist/features/wrapUnwrap/revertStructureSuggestions.d.ts +0 -9
- package/dist/features/wrapUnwrap/revertStructureSuggestions.js +0 -434
package/dist/commands.js
CHANGED
|
@@ -5,7 +5,8 @@ import { suggestChangesKey } from "./plugin.js";
|
|
|
5
5
|
import { getSuggestionMarks } from "./utils.js";
|
|
6
6
|
import { ZWSP } from "./constants.js";
|
|
7
7
|
import { maybeRevertJoinMark } from "./features/joinOnDelete/index.js";
|
|
8
|
-
import {
|
|
8
|
+
import { revertAllStructureSuggestions, revertStructureSuggestion } from "./features/wrapUnwrap/revert/index.js";
|
|
9
|
+
import { applyAllStructureSuggestions, applyStructureSuggestion } from "./features/wrapUnwrap/apply/index.js";
|
|
9
10
|
/**
|
|
10
11
|
* Given a node and a transform, add a set of steps to the
|
|
11
12
|
* transform that applies all marks of type markTypeToApply
|
|
@@ -136,7 +137,7 @@ function applyModificationsToTransform(node, tr, dir, suggestionId, from, to) {
|
|
|
136
137
|
export function applySuggestionsToNode(node) {
|
|
137
138
|
const { deletion, insertion } = getSuggestionMarks(node.type.schema);
|
|
138
139
|
// first, create a structure transform that applies all structure changes on the given node
|
|
139
|
-
const structureTransform =
|
|
140
|
+
const structureTransform = applyAllStructureSuggestions(node);
|
|
140
141
|
// then start a clear transform from the document where the structure changes are applied
|
|
141
142
|
const suggestionsTransform = new Transform(structureTransform.doc);
|
|
142
143
|
applySuggestionsToTransform(suggestionsTransform.doc, suggestionsTransform, insertion, deletion);
|
|
@@ -153,7 +154,7 @@ export function applySuggestionsToRange(doc, from, to) {
|
|
|
153
154
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
154
155
|
const nodeRange = doc.resolve(from).blockRange(doc.resolve(to));
|
|
155
156
|
// create a structure transform that applies all structure changes on the given node range
|
|
156
|
-
const structureTransform =
|
|
157
|
+
const structureTransform = applyAllStructureSuggestions(doc, nodeRange.start, nodeRange.end);
|
|
157
158
|
// then start a clear transform from the document where the structure changes are applied
|
|
158
159
|
const suggestionsTransform = new Transform(structureTransform.doc);
|
|
159
160
|
applySuggestionsToTransform(suggestionsTransform.doc, suggestionsTransform, insertion, deletion, undefined, nodeRange.start, nodeRange.end);
|
|
@@ -204,7 +205,7 @@ export function applySuggestionsToRange(doc, from, to) {
|
|
|
204
205
|
return (state, dispatch)=>{
|
|
205
206
|
const { deletion, insertion } = getSuggestionMarks(state.schema);
|
|
206
207
|
// create a structure transform that applies all structure changes on the given node range
|
|
207
|
-
const structureTransform =
|
|
208
|
+
const structureTransform = applyAllStructureSuggestions(state.doc, from, to);
|
|
208
209
|
// then start a clear transform from the document where the structure changes are applied
|
|
209
210
|
const suggestionsTransform = new Transform(structureTransform.doc);
|
|
210
211
|
applySuggestionsToTransform(suggestionsTransform.doc, suggestionsTransform, insertion, deletion, undefined, from, to);
|
|
@@ -236,7 +237,7 @@ export function applySuggestionsToRange(doc, from, to) {
|
|
|
236
237
|
return (state, dispatch)=>{
|
|
237
238
|
const { deletion, insertion } = getSuggestionMarks(state.schema);
|
|
238
239
|
// create a structure transform that applies the given structure change on the given node
|
|
239
|
-
const structureTransform =
|
|
240
|
+
const structureTransform = applyStructureSuggestion(state.doc, suggestionId);
|
|
240
241
|
// then start a clear transform from the document where the structure changes are applied
|
|
241
242
|
const suggestionsTransform = new Transform(structureTransform.doc);
|
|
242
243
|
applySuggestionsToTransform(suggestionsTransform.doc, suggestionsTransform, insertion, deletion, suggestionId, from, to);
|
|
@@ -298,7 +299,7 @@ export function applySuggestionsToRange(doc, from, to) {
|
|
|
298
299
|
return (state, dispatch)=>{
|
|
299
300
|
const { deletion, insertion } = getSuggestionMarks(state.schema);
|
|
300
301
|
// create a structure transform that reverts all structure changes on the given node range
|
|
301
|
-
const structureTransform =
|
|
302
|
+
const structureTransform = revertAllStructureSuggestions(state.doc, from, to);
|
|
302
303
|
// then start a clear transform from the document where the structure changes are reverted
|
|
303
304
|
const suggestionsTransform = new Transform(structureTransform.doc);
|
|
304
305
|
applySuggestionsToTransform(suggestionsTransform.doc, suggestionsTransform, deletion, insertion, undefined, from, to);
|
|
@@ -330,7 +331,7 @@ export function applySuggestionsToRange(doc, from, to) {
|
|
|
330
331
|
return (state, dispatch)=>{
|
|
331
332
|
const { deletion, insertion } = getSuggestionMarks(state.schema);
|
|
332
333
|
// create a structure transform that reverts the given structure change on the given node
|
|
333
|
-
const structureTransform =
|
|
334
|
+
const structureTransform = revertStructureSuggestion(state.doc, suggestionId);
|
|
334
335
|
// then start a clear transform from the document where the structure changes are reverted
|
|
335
336
|
const suggestionsTransform = new Transform(structureTransform.doc);
|
|
336
337
|
applySuggestionsToTransform(suggestionsTransform.doc, suggestionsTransform, deletion, insertion, suggestionId, from, to);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type Node } from "prosemirror-model";
|
|
2
|
+
import { type Transform } from "prosemirror-transform";
|
|
3
|
+
import { type SuggestionId } from "../../../generateId.js";
|
|
4
|
+
export declare function applyStructureSuggestionsInNode({ tr, node, suggestionId, from, to, }: {
|
|
5
|
+
tr: Transform;
|
|
6
|
+
node: Node;
|
|
7
|
+
suggestionId?: SuggestionId;
|
|
8
|
+
from?: number | undefined;
|
|
9
|
+
to?: number | undefined;
|
|
10
|
+
}): void;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { getSuggestionMarks } from "../../../utils.js";
|
|
2
|
+
export function applyStructureSuggestionsInNode({ tr, node, suggestionId, from, to }) {
|
|
3
|
+
const { structure } = getSuggestionMarks(node.type.schema);
|
|
4
|
+
const suggestionIds = new Set();
|
|
5
|
+
node.descendants((node, pos)=>{
|
|
6
|
+
if (from !== undefined && pos < from) {
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
if (to !== undefined && pos > to) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
if (node.isText) return true;
|
|
13
|
+
if (!structure.isInSet(node.marks)) return true;
|
|
14
|
+
node.marks.forEach((mark)=>{
|
|
15
|
+
if (mark.type !== structure) return;
|
|
16
|
+
const markSuggestionId = mark.attrs["id"];
|
|
17
|
+
if (suggestionId != null && markSuggestionId !== suggestionId) return;
|
|
18
|
+
suggestionIds.add(markSuggestionId);
|
|
19
|
+
});
|
|
20
|
+
return true;
|
|
21
|
+
});
|
|
22
|
+
for (const suggestionId of suggestionIds){
|
|
23
|
+
applyOneStructureSuggestion(tr, suggestionId);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function applyOneStructureSuggestion(tr, suggestionId) {
|
|
27
|
+
const { structure } = getSuggestionMarks(tr.doc.type.schema);
|
|
28
|
+
tr.doc.descendants((node, pos)=>{
|
|
29
|
+
if (node.isText) return true;
|
|
30
|
+
if (!structure.isInSet(node.marks)) return true;
|
|
31
|
+
node.marks.forEach((mark)=>{
|
|
32
|
+
if (mark.type !== structure) return;
|
|
33
|
+
if (mark.attrs["id"] !== suggestionId) return;
|
|
34
|
+
applyStructureMark(tr, mark, pos);
|
|
35
|
+
});
|
|
36
|
+
return true;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
function applyStructureMark(tr, mark, pos) {
|
|
40
|
+
tr.removeNodeMark(pos, mark);
|
|
41
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Transform } from "prosemirror-transform";
|
|
2
|
+
import { type Node } from "prosemirror-model";
|
|
3
|
+
import { type SuggestionId } from "../../../generateId.js";
|
|
4
|
+
export declare function applyAllStructureSuggestions(node: Node, from?: number, to?: number): Transform;
|
|
5
|
+
export declare function applyStructureSuggestion(node: Node, suggestionId: SuggestionId): Transform;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Transform } from "prosemirror-transform";
|
|
2
|
+
import { applyStructureSuggestionsInNode } from "./applyStructureSuggestions.js";
|
|
3
|
+
export function applyAllStructureSuggestions(node, from, to) {
|
|
4
|
+
const tr = new Transform(node);
|
|
5
|
+
applyStructureSuggestionsInNode({
|
|
6
|
+
tr,
|
|
7
|
+
node: tr.doc,
|
|
8
|
+
from,
|
|
9
|
+
to
|
|
10
|
+
});
|
|
11
|
+
return tr;
|
|
12
|
+
}
|
|
13
|
+
export function applyStructureSuggestion(node, suggestionId) {
|
|
14
|
+
const tr = new Transform(node);
|
|
15
|
+
applyStructureSuggestionsInNode({
|
|
16
|
+
tr,
|
|
17
|
+
node: tr.doc,
|
|
18
|
+
suggestionId
|
|
19
|
+
});
|
|
20
|
+
return tr;
|
|
21
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// delete a given node, and traverse upwards deleting parent nodes if they are now empty
|
|
2
|
+
export function deleteNodeUpwards(transform, node, pos) {
|
|
3
|
+
let $mappedPos = transform.doc.resolve(pos);
|
|
4
|
+
let deleteFrom = $mappedPos.pos;
|
|
5
|
+
let deleteTo = $mappedPos.pos + node.nodeSize;
|
|
6
|
+
console.log("deleteNodeUpwards", "initial delete range covers node", node.toString(), {
|
|
7
|
+
deleteFrom,
|
|
8
|
+
deleteTo,
|
|
9
|
+
$mappedPos
|
|
10
|
+
});
|
|
11
|
+
while($mappedPos.depth > 0){
|
|
12
|
+
const $nextMappedPos = transform.doc.resolve($mappedPos.before());
|
|
13
|
+
console.log("deleteNodeUpwards", "considering", $nextMappedPos.nodeAfter?.toString(), "for deletion", "childCount is", $nextMappedPos.nodeAfter?.childCount);
|
|
14
|
+
if ($nextMappedPos.nodeAfter?.childCount !== 1) break;
|
|
15
|
+
console.log("deleteNodeUpwards", "expanding to deleting node", $nextMappedPos.nodeAfter.toString());
|
|
16
|
+
$mappedPos = $nextMappedPos;
|
|
17
|
+
deleteFrom = $nextMappedPos.pos;
|
|
18
|
+
deleteTo = $nextMappedPos.pos + $nextMappedPos.nodeAfter.nodeSize;
|
|
19
|
+
console.log("deleteNodeUpwards", "expanded delete range to cover node", $nextMappedPos.nodeAfter.toString(), {
|
|
20
|
+
deleteFrom,
|
|
21
|
+
deleteTo,
|
|
22
|
+
$mappedPos: $nextMappedPos
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
console.log("deleteNodeUpwards", "final delete range covers node", $mappedPos.nodeAfter?.toString(), {
|
|
26
|
+
deleteFrom,
|
|
27
|
+
deleteTo,
|
|
28
|
+
$mappedPos
|
|
29
|
+
});
|
|
30
|
+
transform.delete(deleteFrom, deleteTo);
|
|
31
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Transform } from "prosemirror-transform";
|
|
2
|
+
import { type Node } from "prosemirror-model";
|
|
3
|
+
import { type SuggestionId } from "../../../generateId.js";
|
|
4
|
+
export declare function revertAllStructureSuggestions(node: Node, from?: number, to?: number): Transform;
|
|
5
|
+
export declare function revertStructureSuggestion(node: Node, suggestionId: SuggestionId): Transform;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Transform } from "prosemirror-transform";
|
|
2
|
+
import { revertStructureSuggestionsInNode } from "./revertStructureSuggestions.js";
|
|
3
|
+
export function revertAllStructureSuggestions(node, from, to) {
|
|
4
|
+
const tr = new Transform(node);
|
|
5
|
+
revertStructureSuggestionsInNode({
|
|
6
|
+
tr,
|
|
7
|
+
node: tr.doc,
|
|
8
|
+
from,
|
|
9
|
+
to
|
|
10
|
+
});
|
|
11
|
+
return tr;
|
|
12
|
+
}
|
|
13
|
+
export function revertStructureSuggestion(node, suggestionId) {
|
|
14
|
+
const tr = new Transform(node);
|
|
15
|
+
revertStructureSuggestionsInNode({
|
|
16
|
+
tr,
|
|
17
|
+
node: tr.doc,
|
|
18
|
+
suggestionId
|
|
19
|
+
});
|
|
20
|
+
return tr;
|
|
21
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Transform } from "prosemirror-transform";
|
|
2
|
+
import { type MoveOp } from "../types.js";
|
|
3
|
+
import { type Node } from "prosemirror-model";
|
|
4
|
+
import { type NodeWithChildren, type Parent } from "../types.js";
|
|
5
|
+
export declare function revertMoveOp(op: MoveOp, tr: Transform, node: Node, pos: number): void;
|
|
6
|
+
export declare function getNodesWithChildren(doc: Node): Map<string, NodeWithChildren>;
|
|
7
|
+
export declare function getDeepestSurvivingParent(parentChain: Parent[], doc: Node): {
|
|
8
|
+
parent: Parent;
|
|
9
|
+
node: Node;
|
|
10
|
+
pos: number | null;
|
|
11
|
+
remainingChain: Parent[];
|
|
12
|
+
};
|
|
13
|
+
export declare function wrapNodeInParentChain(parentChain: Parent[], node: Node): Node;
|
|
14
|
+
export declare function findInsertionPos(node: Node, pos: number | null, parent: Parent): number;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { deleteNodeUpwards } from "./deleteNodeUpwards.js";
|
|
2
|
+
import { getNodeId } from "../getNodeId.js";
|
|
3
|
+
import { guardDocParent, guardDocWithChildren } from "../types.js";
|
|
4
|
+
export function revertMoveOp(op, tr, node, pos) {
|
|
5
|
+
console.log("revertMoveOp", "node", node.toString(), "was moved from", op.from, {
|
|
6
|
+
op,
|
|
7
|
+
node
|
|
8
|
+
});
|
|
9
|
+
const parent = getDeepestSurvivingParent(op.from, tr.doc);
|
|
10
|
+
console.log("revertMoveOp", "deepest surviving parent is", parent.node.toString(), {
|
|
11
|
+
parent
|
|
12
|
+
});
|
|
13
|
+
const child = wrapNodeInParentChain(parent.remainingChain, node);
|
|
14
|
+
console.log("revertMoveOp", "wrapped node in parent chain is", child.toString(), {
|
|
15
|
+
child
|
|
16
|
+
});
|
|
17
|
+
const insertionPos = findInsertionPos(parent.node, parent.pos, parent.parent);
|
|
18
|
+
console.log("revertMoveOp", "insertion pos", {
|
|
19
|
+
insertionPos
|
|
20
|
+
});
|
|
21
|
+
tr.insert(insertionPos, child);
|
|
22
|
+
const mappedPos = tr.mapping.map(pos);
|
|
23
|
+
deleteNodeUpwards(tr, node, mappedPos);
|
|
24
|
+
}
|
|
25
|
+
export function getNodesWithChildren(doc) {
|
|
26
|
+
const nodesWithChildren = new Map();
|
|
27
|
+
const docWithChildren = {
|
|
28
|
+
node: doc,
|
|
29
|
+
pos: null,
|
|
30
|
+
children: new Set()
|
|
31
|
+
};
|
|
32
|
+
doc.children.forEach((child)=>{
|
|
33
|
+
const nodeId = getNodeId(child);
|
|
34
|
+
if (nodeId == null) return;
|
|
35
|
+
docWithChildren.children.add(nodeId);
|
|
36
|
+
});
|
|
37
|
+
nodesWithChildren.set("__doc__", docWithChildren);
|
|
38
|
+
doc.descendants((node, pos)=>{
|
|
39
|
+
if (node.isText) return true;
|
|
40
|
+
const nodeId = getNodeId(node);
|
|
41
|
+
if (nodeId == null) return true;
|
|
42
|
+
if (nodesWithChildren.has(nodeId)) return true;
|
|
43
|
+
const children = node.children.reduce((acc, child)=>{
|
|
44
|
+
const childId = getNodeId(child);
|
|
45
|
+
if (childId == null) return acc;
|
|
46
|
+
acc.add(childId);
|
|
47
|
+
return acc;
|
|
48
|
+
}, new Set());
|
|
49
|
+
nodesWithChildren.set(nodeId, {
|
|
50
|
+
node,
|
|
51
|
+
pos,
|
|
52
|
+
children
|
|
53
|
+
});
|
|
54
|
+
return true;
|
|
55
|
+
});
|
|
56
|
+
return nodesWithChildren;
|
|
57
|
+
}
|
|
58
|
+
// given a chain of parent node descriptors, follow the chain from top to bottom as long as nodes exist
|
|
59
|
+
// return the deepest existing parent node descriptor, along with the actual node and the pos in the current document
|
|
60
|
+
// also return the remaining part of the chain
|
|
61
|
+
export function getDeepestSurvivingParent(parentChain, doc) {
|
|
62
|
+
const chain = [
|
|
63
|
+
...parentChain
|
|
64
|
+
].reverse();
|
|
65
|
+
const currentNodes = getNodesWithChildren(doc);
|
|
66
|
+
let currentNode = currentNodes.get("__doc__"); // get doc node with children from current doc
|
|
67
|
+
if (!guardDocWithChildren(currentNode)) {
|
|
68
|
+
throw new Error("doc not found in nodesWithChildren");
|
|
69
|
+
}
|
|
70
|
+
let parent = chain.shift(); // get doc node descriptor from parent chain
|
|
71
|
+
if (!guardDocParent(parent)) {
|
|
72
|
+
throw new Error("doc parent not found in op chain");
|
|
73
|
+
}
|
|
74
|
+
let remainingChain = [];
|
|
75
|
+
for (const [index, nextParent] of chain.entries()){
|
|
76
|
+
const nextNodeWithChildren = currentNodes.get(nextParent.nodeId);
|
|
77
|
+
// nextParent does not exist in the current document at all
|
|
78
|
+
if (nextNodeWithChildren == null) {
|
|
79
|
+
remainingChain = chain.slice(index);
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
// nextParent exists in the document, but in a different parent chain
|
|
83
|
+
if (!currentNode.children.has(nextParent.nodeId)) {
|
|
84
|
+
remainingChain = chain.slice(index);
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
currentNode = nextNodeWithChildren;
|
|
88
|
+
parent = nextParent;
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
parent,
|
|
92
|
+
node: currentNode.node,
|
|
93
|
+
pos: currentNode.pos,
|
|
94
|
+
remainingChain: [
|
|
95
|
+
...remainingChain
|
|
96
|
+
].reverse()
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
// given a chain of parent node descriptors and a node
|
|
100
|
+
// wrap the node in the parent chain
|
|
101
|
+
export function wrapNodeInParentChain(parentChain, node) {
|
|
102
|
+
let child = node.copy(node.content);
|
|
103
|
+
for (const parent of parentChain){
|
|
104
|
+
const schema = node.type.schema;
|
|
105
|
+
const nodeType = schema.nodes[parent.nodeType];
|
|
106
|
+
if (!nodeType) {
|
|
107
|
+
throw new Error(`node type ${parent.nodeType} not found in schema`);
|
|
108
|
+
}
|
|
109
|
+
const marks = parent.nodeMarks.map((mark)=>schema.markFromJSON(mark));
|
|
110
|
+
child = nodeType.create(parent.nodeAttrs, child, marks);
|
|
111
|
+
}
|
|
112
|
+
return child;
|
|
113
|
+
}
|
|
114
|
+
// given a node, its position, and a parent descriptor of this node in some parent chain,
|
|
115
|
+
// use the info from the descriptor to find the insertion position in the node
|
|
116
|
+
// first try to find siblings, fallback to end of node
|
|
117
|
+
export function findInsertionPos(node, pos, parent) {
|
|
118
|
+
let leftSibling = null;
|
|
119
|
+
let rightSibling = null;
|
|
120
|
+
node.descendants((child, localChildPos)=>{
|
|
121
|
+
const childId = getNodeId(child);
|
|
122
|
+
if (childId == null) return false;
|
|
123
|
+
const globalChildPos = pos != null ? pos + 1 + localChildPos : localChildPos;
|
|
124
|
+
if (parent.childSiblingIds[0] === childId) {
|
|
125
|
+
leftSibling = {
|
|
126
|
+
node: child,
|
|
127
|
+
pos: globalChildPos
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (parent.childSiblingIds[1] === childId) {
|
|
131
|
+
rightSibling = {
|
|
132
|
+
node: child,
|
|
133
|
+
pos: globalChildPos
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
// iterate only direct children
|
|
137
|
+
return false;
|
|
138
|
+
});
|
|
139
|
+
if (rightSibling != null) {
|
|
140
|
+
// insert before right sibling
|
|
141
|
+
return rightSibling.pos;
|
|
142
|
+
}
|
|
143
|
+
if (leftSibling != null) {
|
|
144
|
+
// insert after left sibling
|
|
145
|
+
return leftSibling.pos + leftSibling.node.nodeSize;
|
|
146
|
+
}
|
|
147
|
+
// insert at end of node
|
|
148
|
+
return pos != null ? pos + node.nodeSize - 1 : node.nodeSize - 1;
|
|
149
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type Node } from "prosemirror-model";
|
|
2
|
+
import { type Mark } from "prosemirror-model";
|
|
3
|
+
import { Transform } from "prosemirror-transform";
|
|
4
|
+
import { type SuggestionId } from "../../../generateId.js";
|
|
5
|
+
export declare function revertStructureSuggestionsInNode({ tr, node, suggestionId, from, to, }: {
|
|
6
|
+
tr: Transform;
|
|
7
|
+
node: Node;
|
|
8
|
+
suggestionId?: SuggestionId;
|
|
9
|
+
from?: number | undefined;
|
|
10
|
+
to?: number | undefined;
|
|
11
|
+
}): void;
|
|
12
|
+
export declare function revertStructureMark(tr: Transform, mark: Mark, pos: number): void;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { getSuggestionMarks } from "../../../utils.js";
|
|
2
|
+
import { getNodeId } from "../getNodeId.js";
|
|
3
|
+
import { Transform } from "prosemirror-transform";
|
|
4
|
+
import { guardStructureMarkAttrs } from "../types.js";
|
|
5
|
+
import { sameParentChain } from "../sameParentChain.js";
|
|
6
|
+
import { buildMaterializedPaths } from "../buildMaterializedPaths.js";
|
|
7
|
+
import { revertAddOp } from "./revertAddOp.js";
|
|
8
|
+
import { revertMoveOp } from "./revertMoveOp.js";
|
|
9
|
+
export function revertStructureSuggestionsInNode({ tr, node, suggestionId, from, to }) {
|
|
10
|
+
const { structure } = getSuggestionMarks(node.type.schema);
|
|
11
|
+
// collect all structure mark ids
|
|
12
|
+
const suggestionIds = new Set();
|
|
13
|
+
node.descendants((node, pos)=>{
|
|
14
|
+
if (from !== undefined && pos < from) {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
if (to !== undefined && pos > to) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
if (node.isText) return true;
|
|
21
|
+
if (!structure.isInSet(node.marks)) return true;
|
|
22
|
+
node.marks.forEach((mark)=>{
|
|
23
|
+
if (mark.type !== structure) return;
|
|
24
|
+
const markSuggestionId = mark.attrs["id"];
|
|
25
|
+
if (suggestionId != null && markSuggestionId !== suggestionId) return;
|
|
26
|
+
suggestionIds.add(markSuggestionId);
|
|
27
|
+
});
|
|
28
|
+
return true;
|
|
29
|
+
});
|
|
30
|
+
for (const suggestionId of suggestionIds){
|
|
31
|
+
revertStructureSuggestionWithPrerequisites(tr, suggestionId);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function revertStructureSuggestionWithPrerequisites(tr, suggestionId) {
|
|
35
|
+
console.group("revertStructureMarkGroupInOrder", "reverting structure mark group", suggestionId);
|
|
36
|
+
const suggestionIds = buildOrderedSuggestionIds(tr.doc, suggestionId, buildMaterializedPaths(tr.doc));
|
|
37
|
+
console.log("revertStructureMarkGroupInOrder", "suggestion groups to revert", suggestionIds);
|
|
38
|
+
for (const suggestionId of suggestionIds){
|
|
39
|
+
revertOneStructureSuggestion(tr, suggestionId);
|
|
40
|
+
}
|
|
41
|
+
console.groupEnd();
|
|
42
|
+
}
|
|
43
|
+
function revertOneStructureSuggestion(tr, suggestionId) {
|
|
44
|
+
console.group("revertStructureSuggestion", "reverting structure suggestion", suggestionId);
|
|
45
|
+
let structureMark = findNextStructureMark(tr.doc, suggestionId);
|
|
46
|
+
while(structureMark != null){
|
|
47
|
+
console.groupCollapsed("revertStructureSuggestion", "reverting structure mark", structureMark.mark.attrs["id"], "at pos", structureMark.$pos.pos, "at node", structureMark.node.toString(), {
|
|
48
|
+
structureMark
|
|
49
|
+
});
|
|
50
|
+
revertStructureMark(tr, structureMark.mark, structureMark.$pos.pos);
|
|
51
|
+
console.groupEnd();
|
|
52
|
+
structureMark = findNextStructureMark(tr.doc, suggestionId);
|
|
53
|
+
}
|
|
54
|
+
console.groupEnd();
|
|
55
|
+
}
|
|
56
|
+
export function revertStructureMark(tr, mark, pos) {
|
|
57
|
+
const transform = new Transform(tr.doc);
|
|
58
|
+
transform.removeNodeMark(pos, mark);
|
|
59
|
+
const node = transform.doc.nodeAt(pos);
|
|
60
|
+
if (!node) {
|
|
61
|
+
throw new Error(`Node not found at position ${String(pos)}`);
|
|
62
|
+
}
|
|
63
|
+
console.log("revertStructureMark", mark.attrs["id"], "at node", node.toString(), {
|
|
64
|
+
node,
|
|
65
|
+
mark,
|
|
66
|
+
pos
|
|
67
|
+
});
|
|
68
|
+
const attrs = mark.attrs;
|
|
69
|
+
if (!guardStructureMarkAttrs(attrs)) {
|
|
70
|
+
console.warn("revertStructureMark", "invalid shape of structure mark attrs", {
|
|
71
|
+
attrs
|
|
72
|
+
});
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const op = attrs.data.op;
|
|
76
|
+
switch(op.op){
|
|
77
|
+
case "add":
|
|
78
|
+
{
|
|
79
|
+
revertAddOp(op, transform, node, pos);
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
case "move":
|
|
83
|
+
{
|
|
84
|
+
revertMoveOp(op, transform, node, pos);
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
default:
|
|
88
|
+
console.warn("revertStructureMark", "unknown op", {
|
|
89
|
+
op
|
|
90
|
+
});
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
transform.steps.forEach((step)=>{
|
|
94
|
+
tr.step(step);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
// given a suggestion id
|
|
98
|
+
// return an array of suggestion ids to revert
|
|
99
|
+
// resolve suggestion dependencies, meaning,
|
|
100
|
+
// if to revert suggestion id 1 you need to revert 2, and to revert 2 you need to revert 3
|
|
101
|
+
// it will return [3,2,1]
|
|
102
|
+
// todo: this should probably use topological sort at some point
|
|
103
|
+
function buildOrderedSuggestionIds(node, suggestionId, materializedPaths) {
|
|
104
|
+
const { structure } = getSuggestionMarks(node.type.schema);
|
|
105
|
+
// collect marks with the given suggestionId
|
|
106
|
+
const markGroup = [];
|
|
107
|
+
node.descendants((descendant, pos)=>{
|
|
108
|
+
if (descendant.isText) return true;
|
|
109
|
+
if (!structure.isInSet(descendant.marks)) return true;
|
|
110
|
+
descendant.marks.forEach((mark)=>{
|
|
111
|
+
if (mark.type !== structure) return;
|
|
112
|
+
if (mark.attrs["id"] !== suggestionId) return;
|
|
113
|
+
markGroup.push({
|
|
114
|
+
mark,
|
|
115
|
+
node: descendant,
|
|
116
|
+
pos
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
return true;
|
|
120
|
+
});
|
|
121
|
+
const suggestionIds = new Set();
|
|
122
|
+
suggestionIds.add(suggestionId);
|
|
123
|
+
// find first mark that doesn't have a matching op.to
|
|
124
|
+
const mismatch = markGroup.find((mark)=>{
|
|
125
|
+
const nodeId = getNodeId(mark.node);
|
|
126
|
+
if (nodeId == null) return false;
|
|
127
|
+
const parentChain = materializedPaths.get(nodeId);
|
|
128
|
+
if (parentChain == null) return false;
|
|
129
|
+
const { attrs } = mark.mark;
|
|
130
|
+
if (!guardStructureMarkAttrs(attrs)) return false;
|
|
131
|
+
if (attrs.data.op.op !== "move") return false;
|
|
132
|
+
return !sameParentChain(attrs.data.op.to, parentChain.chain);
|
|
133
|
+
});
|
|
134
|
+
if (mismatch == null) {
|
|
135
|
+
return Array.from(suggestionIds).reverse();
|
|
136
|
+
}
|
|
137
|
+
console.log("findOrderedSuggestionIdsToRevert", "suggestion", suggestionId, "contains mark with a mismatched 'to':", mismatch, "searching a different suggestion with the matching 'to'...");
|
|
138
|
+
// find first mark on the node that does have a matching op.to
|
|
139
|
+
const match = mismatch.node.marks.find((mark)=>{
|
|
140
|
+
if (mark.type !== structure) return false;
|
|
141
|
+
const { attrs } = mark;
|
|
142
|
+
if (!guardStructureMarkAttrs(attrs)) return false;
|
|
143
|
+
if (attrs.data.op.op !== "move") return false;
|
|
144
|
+
const nodeId = getNodeId(mismatch.node);
|
|
145
|
+
if (nodeId == null) return false;
|
|
146
|
+
const parentChain = materializedPaths.get(nodeId);
|
|
147
|
+
if (parentChain == null) return false;
|
|
148
|
+
return sameParentChain(attrs.data.op.to, parentChain.chain);
|
|
149
|
+
});
|
|
150
|
+
if (match) {
|
|
151
|
+
console.log("findOrderedSuggestionIdsToRevert", "found suggestin with matching 'to'", match);
|
|
152
|
+
suggestionIds.add(match.attrs["id"]);
|
|
153
|
+
}
|
|
154
|
+
return Array.from(suggestionIds).reverse();
|
|
155
|
+
}
|
|
156
|
+
function findNextStructureMark(node, suggestionId) {
|
|
157
|
+
console.log("findNextStructureMark", suggestionId);
|
|
158
|
+
const { structure } = getSuggestionMarks(node.type.schema);
|
|
159
|
+
const structureMarks = [];
|
|
160
|
+
node.descendants((descendant, pos)=>{
|
|
161
|
+
const mark = descendant.marks.find((mark)=>mark.type === structure && mark.attrs["id"] === suggestionId);
|
|
162
|
+
if (mark == null) return true;
|
|
163
|
+
structureMarks.push({
|
|
164
|
+
mark,
|
|
165
|
+
node: descendant,
|
|
166
|
+
$pos: node.resolve(pos)
|
|
167
|
+
});
|
|
168
|
+
return true;
|
|
169
|
+
});
|
|
170
|
+
structureMarks.sort((a, b)=>b.$pos.depth - a.$pos.depth);
|
|
171
|
+
if (structureMarks[0]) {
|
|
172
|
+
console.log("findStructureMark", "found structure mark with id", suggestionId, {
|
|
173
|
+
structureMark: structureMarks[0],
|
|
174
|
+
suggestionId
|
|
175
|
+
});
|
|
176
|
+
} else {
|
|
177
|
+
console.log("findStructureMark", "no structure mark found with id", suggestionId);
|
|
178
|
+
}
|
|
179
|
+
return structureMarks[0];
|
|
180
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,4 +4,4 @@ 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 } from "./features/wrapUnwrap/types.js";
|
|
7
|
+
export type { Op as StructureOp, StructureMarkAttrs, } from "./features/wrapUnwrap/types.js";
|
package/package.json
CHANGED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { type Node } from "prosemirror-model";
|
|
2
|
-
import { Transform } from "prosemirror-transform";
|
|
3
|
-
import { type SuggestionId } from "../../generateId.js";
|
|
4
|
-
export declare function applyAllStructureSuggestions(node: Node): Transform;
|
|
5
|
-
export declare function revertAllStructureSuggestions(node: Node): Transform;
|
|
6
|
-
export declare function applyOneStructureSuggestion(node: Node, suggestionId: SuggestionId): Transform;
|
|
7
|
-
export declare function revertOneStructureSuggestion(node: Node, suggestionId: SuggestionId): Transform;
|
|
8
|
-
export declare function applyAllStructureSuggestionsOnNode(node: Node, from?: number, to?: number): Transform;
|
|
9
|
-
export declare function revertAllStructureSuggestionsOnNode(node: Node, from?: number, to?: number): Transform;
|
|
@@ -1,434 +0,0 @@
|
|
|
1
|
-
import { getSuggestionMarks } from "../../utils.js";
|
|
2
|
-
import { getNodeId } from "./getNodeId.js";
|
|
3
|
-
import { Transform } from "prosemirror-transform";
|
|
4
|
-
import { guardDocParent, guardDocWithChildren, guardStructureMarkAttrs } from "./types.js";
|
|
5
|
-
import { sameParentChain } from "./sameParentChain.js";
|
|
6
|
-
import { buildMaterializedPaths } from "./buildMaterializedPaths.js";
|
|
7
|
-
/* public */ export function applyAllStructureSuggestions(node) {
|
|
8
|
-
const tr = new Transform(node);
|
|
9
|
-
applyAllStructureMarks(tr);
|
|
10
|
-
return tr;
|
|
11
|
-
}
|
|
12
|
-
export function revertAllStructureSuggestions(node) {
|
|
13
|
-
const tr = new Transform(node);
|
|
14
|
-
revertAllStructureMarks(tr);
|
|
15
|
-
return tr;
|
|
16
|
-
}
|
|
17
|
-
export function applyOneStructureSuggestion(node, suggestionId) {
|
|
18
|
-
const tr = new Transform(node);
|
|
19
|
-
applyStructureMarkGroup(tr, suggestionId);
|
|
20
|
-
return tr;
|
|
21
|
-
}
|
|
22
|
-
export function revertOneStructureSuggestion(node, suggestionId) {
|
|
23
|
-
const tr = new Transform(node);
|
|
24
|
-
revertStructureMarkGroupInOrder(tr, suggestionId);
|
|
25
|
-
return tr;
|
|
26
|
-
}
|
|
27
|
-
export function applyAllStructureSuggestionsOnNode(node, from, to) {
|
|
28
|
-
const tr = new Transform(node);
|
|
29
|
-
applyAllStructureMarksOnNode(tr, node, from, to);
|
|
30
|
-
return tr;
|
|
31
|
-
}
|
|
32
|
-
export function revertAllStructureSuggestionsOnNode(node, from, to) {
|
|
33
|
-
const tr = new Transform(node);
|
|
34
|
-
revertAllStructureMarksOnNode(tr, node, from, to);
|
|
35
|
-
return tr;
|
|
36
|
-
}
|
|
37
|
-
/* private */ function applyAllStructureMarks(tr) {
|
|
38
|
-
applyAllStructureMarksOnNode(tr, tr.doc);
|
|
39
|
-
}
|
|
40
|
-
function revertAllStructureMarks(tr) {
|
|
41
|
-
revertAllStructureMarksOnNode(tr, tr.doc);
|
|
42
|
-
}
|
|
43
|
-
function applyAllStructureMarksOnNode(tr, node, from, to) {
|
|
44
|
-
const { structure } = getSuggestionMarks(node.type.schema);
|
|
45
|
-
const suggestionIds = new Set();
|
|
46
|
-
node.descendants((node, pos)=>{
|
|
47
|
-
if (from !== undefined && pos < from) {
|
|
48
|
-
return true;
|
|
49
|
-
}
|
|
50
|
-
if (to !== undefined && pos > to) {
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
if (node.isText) return true;
|
|
54
|
-
if (!structure.isInSet(node.marks)) return true;
|
|
55
|
-
node.marks.forEach((mark)=>{
|
|
56
|
-
if (mark.type !== structure) return;
|
|
57
|
-
const suggestionId = mark.attrs["id"];
|
|
58
|
-
suggestionIds.add(suggestionId);
|
|
59
|
-
});
|
|
60
|
-
return true;
|
|
61
|
-
});
|
|
62
|
-
for (const suggestionId of suggestionIds){
|
|
63
|
-
applyStructureMarkGroup(tr, suggestionId);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
function revertAllStructureMarksOnNode(tr, node, from, to) {
|
|
67
|
-
const { structure } = getSuggestionMarks(node.type.schema);
|
|
68
|
-
// collect all structure mark ids
|
|
69
|
-
const suggestionIds = new Set();
|
|
70
|
-
node.descendants((node, pos)=>{
|
|
71
|
-
if (from !== undefined && pos < from) {
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
|
-
if (to !== undefined && pos > to) {
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
if (node.isText) return true;
|
|
78
|
-
if (!structure.isInSet(node.marks)) return true;
|
|
79
|
-
node.marks.forEach((mark)=>{
|
|
80
|
-
if (mark.type !== structure) return;
|
|
81
|
-
const suggestionId = mark.attrs["id"];
|
|
82
|
-
suggestionIds.add(suggestionId);
|
|
83
|
-
});
|
|
84
|
-
return true;
|
|
85
|
-
});
|
|
86
|
-
for (const suggestionId of suggestionIds){
|
|
87
|
-
revertStructureMarkGroupInOrder(tr, suggestionId);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
function applyStructureMarkGroup(tr, suggestionId) {
|
|
91
|
-
const { structure } = getSuggestionMarks(tr.doc.type.schema);
|
|
92
|
-
tr.doc.descendants((node, pos)=>{
|
|
93
|
-
if (node.isText) return true;
|
|
94
|
-
if (!structure.isInSet(node.marks)) return true;
|
|
95
|
-
node.marks.forEach((mark)=>{
|
|
96
|
-
if (mark.type !== structure) return;
|
|
97
|
-
if (mark.attrs["id"] !== suggestionId) return;
|
|
98
|
-
applyStructureMark(tr, mark, pos);
|
|
99
|
-
});
|
|
100
|
-
return true;
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
function revertStructureMarkGroupInOrder(tr, suggestionId) {
|
|
104
|
-
console.group("revertStructureMarkGroupInOrder", "reverting structure mark group", suggestionId);
|
|
105
|
-
const suggestionIds = findOrderedSuggestionIdsToRevert(tr.doc, suggestionId, buildMaterializedPaths(tr.doc));
|
|
106
|
-
console.log("revertStructureMarkGroupInOrder", "suggestion groups to revert", suggestionIds);
|
|
107
|
-
for (const suggestionId of suggestionIds){
|
|
108
|
-
revertStructureMarkGroup(tr, suggestionId);
|
|
109
|
-
}
|
|
110
|
-
console.groupEnd();
|
|
111
|
-
}
|
|
112
|
-
function revertStructureMarkGroup(tr, suggestionId) {
|
|
113
|
-
console.group("revertStructureMarkGroup", "reverting structure suggestion", suggestionId);
|
|
114
|
-
let structureMark = findNextStructureMark(tr.doc, suggestionId);
|
|
115
|
-
while(structureMark !== null){
|
|
116
|
-
console.groupCollapsed("revertStructureMarkGroup", "reverting structure mark", structureMark.mark.attrs["id"], "at pos", structureMark.pos, "at node", structureMark.node.toString(), {
|
|
117
|
-
structureMark
|
|
118
|
-
});
|
|
119
|
-
revertStructureMark(tr, structureMark.mark, structureMark.pos);
|
|
120
|
-
console.groupEnd();
|
|
121
|
-
structureMark = findNextStructureMark(tr.doc, suggestionId);
|
|
122
|
-
}
|
|
123
|
-
console.groupEnd();
|
|
124
|
-
}
|
|
125
|
-
function applyStructureMark(tr, mark, pos) {
|
|
126
|
-
tr.removeNodeMark(pos, mark);
|
|
127
|
-
}
|
|
128
|
-
function revertStructureMark(tr, mark, pos) {
|
|
129
|
-
const transform = new Transform(tr.doc);
|
|
130
|
-
transform.removeNodeMark(pos, mark);
|
|
131
|
-
const node = transform.doc.nodeAt(pos);
|
|
132
|
-
if (!node) {
|
|
133
|
-
throw new Error(`Node not found at position ${String(pos)}`);
|
|
134
|
-
}
|
|
135
|
-
console.log("revertStructureMark", mark.attrs["id"], "at node", node.toString(), {
|
|
136
|
-
node,
|
|
137
|
-
mark,
|
|
138
|
-
pos
|
|
139
|
-
});
|
|
140
|
-
const attrs = mark.attrs;
|
|
141
|
-
if (!guardStructureMarkAttrs(attrs)) {
|
|
142
|
-
console.warn("revertStructureMark", "invalid shape of structure mark attrs", {
|
|
143
|
-
attrs
|
|
144
|
-
});
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
const op = attrs.data.op;
|
|
148
|
-
switch(op.op){
|
|
149
|
-
case "add":
|
|
150
|
-
{
|
|
151
|
-
revertAdd(op, transform, node, pos);
|
|
152
|
-
break;
|
|
153
|
-
}
|
|
154
|
-
case "move":
|
|
155
|
-
{
|
|
156
|
-
revertMove(op, transform, node, pos);
|
|
157
|
-
break;
|
|
158
|
-
}
|
|
159
|
-
default:
|
|
160
|
-
console.warn("revertStructureMark", "unknown op", {
|
|
161
|
-
op
|
|
162
|
-
});
|
|
163
|
-
break;
|
|
164
|
-
}
|
|
165
|
-
transform.steps.forEach((step)=>{
|
|
166
|
-
tr.step(step);
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
function revertAdd(op, tr, node, pos) {
|
|
170
|
-
console.log("revertAdd", "node", node.toString(), "was added at", pos, {
|
|
171
|
-
op,
|
|
172
|
-
node,
|
|
173
|
-
pos
|
|
174
|
-
});
|
|
175
|
-
deleteNodeWithParents(tr, node, pos);
|
|
176
|
-
}
|
|
177
|
-
function revertMove(op, tr, node, pos) {
|
|
178
|
-
console.log("revertMove", "node", node.toString(), "was moved from", op.from, {
|
|
179
|
-
op,
|
|
180
|
-
node
|
|
181
|
-
});
|
|
182
|
-
const parent = getDeepestSurvivingParent(op.from, tr.doc);
|
|
183
|
-
console.log("revertMove", "deepest surviving parent is", parent.node.toString(), {
|
|
184
|
-
parent
|
|
185
|
-
});
|
|
186
|
-
const child = wrapNodeInParentChain(parent.remainingChain, node);
|
|
187
|
-
console.log("revertMove", "wrapped node in parent chain is", child.toString(), {
|
|
188
|
-
child
|
|
189
|
-
});
|
|
190
|
-
const insertionPos = findInsertionPos(parent.node, parent.pos, parent.parent);
|
|
191
|
-
console.log("revertMove", "insertion pos", {
|
|
192
|
-
insertionPos
|
|
193
|
-
});
|
|
194
|
-
tr.insert(insertionPos, child);
|
|
195
|
-
const mappedPos = tr.mapping.map(pos);
|
|
196
|
-
deleteNodeWithParents(tr, node, mappedPos);
|
|
197
|
-
}
|
|
198
|
-
// given a suggestion id
|
|
199
|
-
// return an array of suggestion ids to revert
|
|
200
|
-
// resolve suggestion dependencies, meaning,
|
|
201
|
-
// if to revert suggestion id 1 you need to revert 2, and to revert 2 you need to revert 3
|
|
202
|
-
// it will return [3,2,1]
|
|
203
|
-
// todo: this should probably use topological sort at some point
|
|
204
|
-
function findOrderedSuggestionIdsToRevert(node, suggestionId, materializedPaths) {
|
|
205
|
-
const { structure } = getSuggestionMarks(node.type.schema);
|
|
206
|
-
// collect marks with the given suggestionId
|
|
207
|
-
const markGroup = [];
|
|
208
|
-
node.descendants((descendant, pos)=>{
|
|
209
|
-
if (descendant.isText) return true;
|
|
210
|
-
if (!structure.isInSet(descendant.marks)) return true;
|
|
211
|
-
descendant.marks.forEach((mark)=>{
|
|
212
|
-
if (mark.type !== structure) return;
|
|
213
|
-
if (mark.attrs["id"] !== suggestionId) return;
|
|
214
|
-
markGroup.push({
|
|
215
|
-
mark,
|
|
216
|
-
node: descendant,
|
|
217
|
-
pos
|
|
218
|
-
});
|
|
219
|
-
});
|
|
220
|
-
return true;
|
|
221
|
-
});
|
|
222
|
-
const suggestionIds = new Set();
|
|
223
|
-
suggestionIds.add(suggestionId);
|
|
224
|
-
// find first mark that doesn't have a matching op.to
|
|
225
|
-
const mismatch = markGroup.find((mark)=>{
|
|
226
|
-
const nodeId = getNodeId(mark.node);
|
|
227
|
-
if (nodeId == null) return false;
|
|
228
|
-
const parentChain = materializedPaths.get(nodeId);
|
|
229
|
-
if (parentChain == null) return false;
|
|
230
|
-
const { attrs } = mark.mark;
|
|
231
|
-
if (!guardStructureMarkAttrs(attrs)) return false;
|
|
232
|
-
if (attrs.data.op.op !== "move") return false;
|
|
233
|
-
return !sameParentChain(attrs.data.op.to, parentChain.chain);
|
|
234
|
-
});
|
|
235
|
-
if (mismatch == null) {
|
|
236
|
-
return Array.from(suggestionIds).reverse();
|
|
237
|
-
}
|
|
238
|
-
console.log("findOrderedSuggestionIdsToRevert", "suggestion", suggestionId, "contains mark with a mismatched 'to':", mismatch, "searching a different suggestion with the matching 'to'...");
|
|
239
|
-
// find first mark on the node that does have a matching op.to
|
|
240
|
-
const match = mismatch.node.marks.find((mark)=>{
|
|
241
|
-
if (mark.type !== structure) return false;
|
|
242
|
-
const { attrs } = mark;
|
|
243
|
-
if (!guardStructureMarkAttrs(attrs)) return false;
|
|
244
|
-
if (attrs.data.op.op !== "move") return false;
|
|
245
|
-
const nodeId = getNodeId(mismatch.node);
|
|
246
|
-
if (nodeId == null) return false;
|
|
247
|
-
const parentChain = materializedPaths.get(nodeId);
|
|
248
|
-
if (parentChain == null) return false;
|
|
249
|
-
return sameParentChain(attrs.data.op.to, parentChain.chain);
|
|
250
|
-
});
|
|
251
|
-
if (match) {
|
|
252
|
-
console.log("findOrderedSuggestionIdsToRevert", "found suggestin with matching 'to'", match);
|
|
253
|
-
suggestionIds.add(match.attrs["id"]);
|
|
254
|
-
}
|
|
255
|
-
return Array.from(suggestionIds).reverse();
|
|
256
|
-
}
|
|
257
|
-
function findNextStructureMark(doc, suggestionId) {
|
|
258
|
-
console.log("findNextStructureMark", suggestionId);
|
|
259
|
-
const { structure } = getSuggestionMarks(doc.type.schema);
|
|
260
|
-
let structureMark = null;
|
|
261
|
-
doc.nodesBetween(0, doc.content.size, (node, pos)=>{
|
|
262
|
-
const mark = node.marks.find((mark)=>mark.type === structure && mark.attrs["id"] === suggestionId);
|
|
263
|
-
if (mark) {
|
|
264
|
-
structureMark = {
|
|
265
|
-
mark,
|
|
266
|
-
node,
|
|
267
|
-
pos
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
return structureMark === null;
|
|
271
|
-
});
|
|
272
|
-
if (structureMark) {
|
|
273
|
-
console.log("findStructureMark", "found structure mark with id", suggestionId, {
|
|
274
|
-
structureMark,
|
|
275
|
-
suggestionId
|
|
276
|
-
});
|
|
277
|
-
} else {
|
|
278
|
-
console.log("findStructureMark", "no structure mark found with id", suggestionId);
|
|
279
|
-
}
|
|
280
|
-
return structureMark;
|
|
281
|
-
}
|
|
282
|
-
function getNodesWithChildren(doc) {
|
|
283
|
-
const nodesWithChildren = new Map();
|
|
284
|
-
const docWithChildren = {
|
|
285
|
-
node: doc,
|
|
286
|
-
pos: null,
|
|
287
|
-
children: new Set()
|
|
288
|
-
};
|
|
289
|
-
doc.children.forEach((child)=>{
|
|
290
|
-
const nodeId = getNodeId(child);
|
|
291
|
-
if (nodeId == null) return;
|
|
292
|
-
docWithChildren.children.add(nodeId);
|
|
293
|
-
});
|
|
294
|
-
nodesWithChildren.set("__doc__", docWithChildren);
|
|
295
|
-
doc.descendants((node, pos)=>{
|
|
296
|
-
if (node.isText) return true;
|
|
297
|
-
const nodeId = getNodeId(node);
|
|
298
|
-
if (nodeId == null) return true;
|
|
299
|
-
if (nodesWithChildren.has(nodeId)) return true;
|
|
300
|
-
const children = node.children.reduce((acc, child)=>{
|
|
301
|
-
const childId = getNodeId(child);
|
|
302
|
-
if (childId == null) return acc;
|
|
303
|
-
acc.add(childId);
|
|
304
|
-
return acc;
|
|
305
|
-
}, new Set());
|
|
306
|
-
nodesWithChildren.set(nodeId, {
|
|
307
|
-
node,
|
|
308
|
-
pos,
|
|
309
|
-
children
|
|
310
|
-
});
|
|
311
|
-
return true;
|
|
312
|
-
});
|
|
313
|
-
return nodesWithChildren;
|
|
314
|
-
}
|
|
315
|
-
// given a chain of parent node descriptors, follow the chain from top to bottom as long as nodes exist
|
|
316
|
-
// return the deepest existing parent node descriptor, along with the actual node and the pos in the current document
|
|
317
|
-
// also return the remaining part of the chain
|
|
318
|
-
function getDeepestSurvivingParent(parentChain, doc) {
|
|
319
|
-
const chain = [
|
|
320
|
-
...parentChain
|
|
321
|
-
].reverse();
|
|
322
|
-
const currentNodes = getNodesWithChildren(doc);
|
|
323
|
-
let currentNode = currentNodes.get("__doc__"); // get doc node with children from current doc
|
|
324
|
-
if (!guardDocWithChildren(currentNode)) {
|
|
325
|
-
throw new Error("doc not found in nodesWithChildren");
|
|
326
|
-
}
|
|
327
|
-
let parent = chain.shift(); // get doc node descriptor from parent chain
|
|
328
|
-
if (!guardDocParent(parent)) {
|
|
329
|
-
throw new Error("doc parent not found in op chain");
|
|
330
|
-
}
|
|
331
|
-
let remainingChain = [];
|
|
332
|
-
for (const [index, nextParent] of chain.entries()){
|
|
333
|
-
const nextNodeWithChildren = currentNodes.get(nextParent.nodeId);
|
|
334
|
-
// nextParent does not exist in the current document at all
|
|
335
|
-
if (nextNodeWithChildren == null) {
|
|
336
|
-
remainingChain = chain.slice(index);
|
|
337
|
-
break;
|
|
338
|
-
}
|
|
339
|
-
// nextParent exists in the document, but in a different parent chain
|
|
340
|
-
if (!currentNode.children.has(nextParent.nodeId)) {
|
|
341
|
-
remainingChain = chain.slice(index);
|
|
342
|
-
break;
|
|
343
|
-
}
|
|
344
|
-
currentNode = nextNodeWithChildren;
|
|
345
|
-
parent = nextParent;
|
|
346
|
-
}
|
|
347
|
-
return {
|
|
348
|
-
parent,
|
|
349
|
-
node: currentNode.node,
|
|
350
|
-
pos: currentNode.pos,
|
|
351
|
-
remainingChain: [
|
|
352
|
-
...remainingChain
|
|
353
|
-
].reverse()
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
|
-
// given a chain of parent node descriptors and a node
|
|
357
|
-
// wrap the node in the parent chain
|
|
358
|
-
function wrapNodeInParentChain(parentChain, node) {
|
|
359
|
-
let child = node.copy(node.content);
|
|
360
|
-
for (const parent of parentChain){
|
|
361
|
-
const schema = node.type.schema;
|
|
362
|
-
const nodeType = schema.nodes[parent.nodeType];
|
|
363
|
-
if (!nodeType) {
|
|
364
|
-
throw new Error(`node type ${parent.nodeType} not found in schema`);
|
|
365
|
-
}
|
|
366
|
-
const marks = parent.nodeMarks.map((mark)=>schema.markFromJSON(mark));
|
|
367
|
-
child = nodeType.create(parent.nodeAttrs, child, marks);
|
|
368
|
-
}
|
|
369
|
-
return child;
|
|
370
|
-
}
|
|
371
|
-
// given a node, its position, and a parent descriptor of this node in some parent chain,
|
|
372
|
-
// use the info from the descriptor to find the insertion position in the node
|
|
373
|
-
// first try to find siblings, fallback to end of node
|
|
374
|
-
function findInsertionPos(node, pos, parent) {
|
|
375
|
-
let leftSibling = null;
|
|
376
|
-
let rightSibling = null;
|
|
377
|
-
node.descendants((child, localChildPos)=>{
|
|
378
|
-
const childId = getNodeId(child);
|
|
379
|
-
if (childId == null) return false;
|
|
380
|
-
const globalChildPos = pos != null ? pos + 1 + localChildPos : localChildPos;
|
|
381
|
-
if (parent.childSiblingIds[0] === childId) {
|
|
382
|
-
leftSibling = {
|
|
383
|
-
node: child,
|
|
384
|
-
pos: globalChildPos
|
|
385
|
-
};
|
|
386
|
-
}
|
|
387
|
-
if (parent.childSiblingIds[1] === childId) {
|
|
388
|
-
rightSibling = {
|
|
389
|
-
node: child,
|
|
390
|
-
pos: globalChildPos
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
// iterate only direct children
|
|
394
|
-
return false;
|
|
395
|
-
});
|
|
396
|
-
if (rightSibling != null) {
|
|
397
|
-
// insert before right sibling
|
|
398
|
-
return rightSibling.pos;
|
|
399
|
-
}
|
|
400
|
-
if (leftSibling != null) {
|
|
401
|
-
// insert after left sibling
|
|
402
|
-
return leftSibling.pos + leftSibling.node.nodeSize;
|
|
403
|
-
}
|
|
404
|
-
// insert at end of node
|
|
405
|
-
return pos != null ? pos + node.nodeSize - 1 : node.nodeSize - 1;
|
|
406
|
-
}
|
|
407
|
-
// delete a given node, and traverse upwards deleting parent nodes if they are now empty
|
|
408
|
-
function deleteNodeWithParents(transform, node, pos) {
|
|
409
|
-
let $mappedPos = transform.doc.resolve(pos);
|
|
410
|
-
let deleteFrom = $mappedPos.pos;
|
|
411
|
-
let deleteTo = $mappedPos.pos + node.nodeSize;
|
|
412
|
-
console.log("deleteNodeWithParents", "initial delete range covers node", node.toString(), {
|
|
413
|
-
deleteFrom,
|
|
414
|
-
deleteTo,
|
|
415
|
-
$mappedPos
|
|
416
|
-
});
|
|
417
|
-
while($mappedPos.depth > 0){
|
|
418
|
-
$mappedPos = transform.doc.resolve($mappedPos.before());
|
|
419
|
-
if ($mappedPos.nodeAfter?.childCount !== 1) break;
|
|
420
|
-
deleteFrom = $mappedPos.pos;
|
|
421
|
-
deleteTo = $mappedPos.pos + $mappedPos.nodeAfter.nodeSize;
|
|
422
|
-
console.log("deleteNodeWithParents", "expanded delete range to cover node", $mappedPos.nodeAfter.toString(), {
|
|
423
|
-
deleteFrom,
|
|
424
|
-
deleteTo,
|
|
425
|
-
$mappedPos
|
|
426
|
-
});
|
|
427
|
-
}
|
|
428
|
-
console.log("deleteNodeWithParents", "final delete range covers node", $mappedPos.nodeAfter?.toString(), {
|
|
429
|
-
deleteFrom,
|
|
430
|
-
deleteTo,
|
|
431
|
-
$mappedPos
|
|
432
|
-
});
|
|
433
|
-
transform.delete(deleteFrom, deleteTo);
|
|
434
|
-
}
|