@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,19 @@
|
|
|
1
|
+
import { Transform } from "prosemirror-transform";
|
|
2
|
+
import { revertStructureSuggestionsInDoc } from "./revertStructureSuggestions.js";
|
|
3
|
+
export function revertAllStructureSuggestions(doc, from, to) {
|
|
4
|
+
const tr = new Transform(doc);
|
|
5
|
+
revertStructureSuggestionsInDoc({
|
|
6
|
+
tr,
|
|
7
|
+
from,
|
|
8
|
+
to
|
|
9
|
+
});
|
|
10
|
+
return tr;
|
|
11
|
+
}
|
|
12
|
+
export function revertStructureSuggestion(doc, suggestionId) {
|
|
13
|
+
const tr = new Transform(doc);
|
|
14
|
+
revertStructureSuggestionsInDoc({
|
|
15
|
+
tr,
|
|
16
|
+
suggestionId
|
|
17
|
+
});
|
|
18
|
+
return tr;
|
|
19
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type Transform } from "prosemirror-transform";
|
|
2
|
+
import { type MoveOp } from "../types.js";
|
|
3
|
+
import { type Node } from "prosemirror-model";
|
|
4
|
+
import { type Parent } from "../types.js";
|
|
5
|
+
export declare function revertMoveOp(op: MoveOp, tr: Transform, node: Node, pos: number): void;
|
|
6
|
+
export declare function getDeepestSurvivingParent(parentChain: Parent[], doc: Node): {
|
|
7
|
+
parent: Parent;
|
|
8
|
+
node: Node;
|
|
9
|
+
pos: number | null;
|
|
10
|
+
remainingChain: Parent[];
|
|
11
|
+
};
|
|
12
|
+
export declare function wrapNodeInParentChain(parentChain: Parent[], node: Node): Node;
|
|
13
|
+
export declare function findInsertionPos(node: Node, pos: number | null, parent: Parent, child: Node): number | {
|
|
14
|
+
from: number;
|
|
15
|
+
to: number;
|
|
16
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { deleteNodeUpwards } from "./deleteNodeUpwards.js";
|
|
2
|
+
import { getNodeId } from "../getNodeId.js";
|
|
3
|
+
export function revertMoveOp(op, tr, node, pos) {
|
|
4
|
+
const parent = getDeepestSurvivingParent(op.from, tr.doc);
|
|
5
|
+
const child = wrapNodeInParentChain(parent.remainingChain, node);
|
|
6
|
+
const insertTo = findInsertionPos(parent.node, parent.pos, parent.parent, child);
|
|
7
|
+
if (typeof insertTo === "number") {
|
|
8
|
+
tr.insert(insertTo, child);
|
|
9
|
+
} else {
|
|
10
|
+
tr.replaceWith(insertTo.from, insertTo.to, child);
|
|
11
|
+
}
|
|
12
|
+
const mappedPos = tr.mapping.map(pos);
|
|
13
|
+
deleteNodeUpwards(tr, node, mappedPos);
|
|
14
|
+
}
|
|
15
|
+
// given a chain of parent node descriptors, follow the chain from top to bottom as long as nodes exist
|
|
16
|
+
// return the deepest existing parent node descriptor, along with the actual node and the pos in the current document
|
|
17
|
+
// also return the remaining part of the chain
|
|
18
|
+
export function getDeepestSurvivingParent(parentChain, doc) {
|
|
19
|
+
const chain = [
|
|
20
|
+
...parentChain
|
|
21
|
+
].reverse();
|
|
22
|
+
const root = chain.shift();
|
|
23
|
+
if (root == null) {
|
|
24
|
+
throw new Error("Parent chain is empty");
|
|
25
|
+
}
|
|
26
|
+
let result = {
|
|
27
|
+
parent: root,
|
|
28
|
+
node: doc,
|
|
29
|
+
pos: null
|
|
30
|
+
};
|
|
31
|
+
let remainingChain = [
|
|
32
|
+
...chain
|
|
33
|
+
];
|
|
34
|
+
// follow the chain up-down
|
|
35
|
+
// look for the node with the matching id in the children of the previously found node
|
|
36
|
+
for (const [index, item] of chain.entries()){
|
|
37
|
+
let found = false;
|
|
38
|
+
result.node.forEach((child, offset)=>{
|
|
39
|
+
if (found) return;
|
|
40
|
+
if (child.attrs["id"] !== item.nodeId) return;
|
|
41
|
+
found = true;
|
|
42
|
+
const pos = result.pos == null ? offset : result.pos + 1 + offset;
|
|
43
|
+
result = {
|
|
44
|
+
parent: item,
|
|
45
|
+
node: child,
|
|
46
|
+
pos
|
|
47
|
+
};
|
|
48
|
+
remainingChain = chain.slice(index + 1);
|
|
49
|
+
});
|
|
50
|
+
if (!found) break;
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
parent: result.parent,
|
|
54
|
+
node: result.node,
|
|
55
|
+
pos: result.pos,
|
|
56
|
+
remainingChain: remainingChain.reverse()
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
// given a chain of parent node descriptors and a node
|
|
60
|
+
// wrap the node in the parent chain
|
|
61
|
+
export function wrapNodeInParentChain(parentChain, node) {
|
|
62
|
+
let child = node.copy(node.content);
|
|
63
|
+
for (const parent of parentChain){
|
|
64
|
+
const schema = node.type.schema;
|
|
65
|
+
const nodeType = schema.nodes[parent.nodeType];
|
|
66
|
+
if (!nodeType) {
|
|
67
|
+
throw new Error(`node type ${parent.nodeType} not found in schema`);
|
|
68
|
+
}
|
|
69
|
+
const marks = parent.nodeMarks.map((mark)=>schema.markFromJSON(mark));
|
|
70
|
+
const parentNode = nodeType.createAndFill(parent.nodeAttrs, child, marks);
|
|
71
|
+
if (parentNode == null) throw new Error(`Unable to create node ${nodeType.name} with child ${child.toString()}`);
|
|
72
|
+
child = parentNode;
|
|
73
|
+
child.check();
|
|
74
|
+
}
|
|
75
|
+
return child;
|
|
76
|
+
}
|
|
77
|
+
// given a node, its position, and a parent descriptor of this node in some parent chain,
|
|
78
|
+
// use the info from the descriptor to find the insertion position in the node
|
|
79
|
+
// first try to find siblings, fallback to end of node
|
|
80
|
+
export function findInsertionPos(node, pos, parent, child) {
|
|
81
|
+
let leftSibling = null;
|
|
82
|
+
let rightSibling = null;
|
|
83
|
+
node.descendants((child, localChildPos)=>{
|
|
84
|
+
const childId = getNodeId(child);
|
|
85
|
+
if (childId == null) return false;
|
|
86
|
+
const globalChildPos = pos != null ? pos + 1 + localChildPos : localChildPos;
|
|
87
|
+
if (parent.childSiblingIds[0] === childId) {
|
|
88
|
+
leftSibling = {
|
|
89
|
+
node: child,
|
|
90
|
+
pos: globalChildPos
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
if (parent.childSiblingIds[1] === childId) {
|
|
94
|
+
rightSibling = {
|
|
95
|
+
node: child,
|
|
96
|
+
pos: globalChildPos
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
// iterate only direct children
|
|
100
|
+
return false;
|
|
101
|
+
});
|
|
102
|
+
if (rightSibling != null) {
|
|
103
|
+
// special case: we need to insert as the first child, but the existing first child is an empty node of the same type
|
|
104
|
+
// in this case, we need to replace the existing first child with the new node
|
|
105
|
+
const firstChild = node.children[0];
|
|
106
|
+
if (parent.childSiblingIds[0] == null && firstChild?.type === child.type && firstChild.textContent === "") {
|
|
107
|
+
const from = pos != null ? pos + 1 : 0;
|
|
108
|
+
return {
|
|
109
|
+
from,
|
|
110
|
+
to: from + firstChild.nodeSize
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
// insert before right sibling
|
|
114
|
+
return rightSibling.pos;
|
|
115
|
+
}
|
|
116
|
+
if (leftSibling != null) {
|
|
117
|
+
// insert after left sibling
|
|
118
|
+
return leftSibling.pos + leftSibling.node.nodeSize;
|
|
119
|
+
}
|
|
120
|
+
// insert at end of node
|
|
121
|
+
return pos != null ? pos + node.nodeSize - 1 : node.content.size;
|
|
122
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type Mark } from "prosemirror-model";
|
|
2
|
+
import { Transform } from "prosemirror-transform";
|
|
3
|
+
import { type SuggestionId } from "../../../generateId.js";
|
|
4
|
+
export declare function revertStructureSuggestionsInDoc({ tr, suggestionId, from, to, }: {
|
|
5
|
+
tr: Transform;
|
|
6
|
+
suggestionId?: SuggestionId;
|
|
7
|
+
from?: number | undefined;
|
|
8
|
+
to?: number | undefined;
|
|
9
|
+
}): void;
|
|
10
|
+
export declare function revertStructureMark(tr: Transform, mark: Mark, pos: number): void;
|
|
@@ -0,0 +1,236 @@
|
|
|
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
|
+
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("[revertStructureSuggestions]", ...args);
|
|
14
|
+
}
|
|
15
|
+
export function revertStructureSuggestionsInDoc({ tr, suggestionId, from, to }) {
|
|
16
|
+
if (suggestionId) {
|
|
17
|
+
// when suggestionId is given, just revert it, no other logic required
|
|
18
|
+
revertStructureSuggestionWithPrerequisites(tr, suggestionId);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (from || to) {
|
|
22
|
+
// when range is given, find suggestion ids inside that range,
|
|
23
|
+
// but make sure to revert furthermost suggestions first
|
|
24
|
+
// todo: most likely a better strategy would be to search for the next suggestion in the updated doc after each reversal (and map from and to)
|
|
25
|
+
const { structure } = getSuggestionMarks(tr.doc.type.schema);
|
|
26
|
+
const structureMarks = [];
|
|
27
|
+
tr.doc.descendants((node, pos)=>{
|
|
28
|
+
if (from !== undefined && pos < from) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
if (to !== undefined && pos > to) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
if (node.isText) return true;
|
|
35
|
+
if (!structure.isInSet(node.marks)) return true;
|
|
36
|
+
node.marks.forEach((mark)=>{
|
|
37
|
+
if (mark.type !== structure) return;
|
|
38
|
+
structureMarks.push({
|
|
39
|
+
mark,
|
|
40
|
+
node,
|
|
41
|
+
pos
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
return true;
|
|
45
|
+
});
|
|
46
|
+
structureMarks.sort((a, b)=>b.pos - a.pos);
|
|
47
|
+
const suggestionIds = new Set();
|
|
48
|
+
structureMarks.forEach(({ mark })=>{
|
|
49
|
+
suggestionIds.add(mark.attrs["id"]);
|
|
50
|
+
});
|
|
51
|
+
for (const suggestionId of suggestionIds){
|
|
52
|
+
revertStructureSuggestionWithPrerequisites(tr, suggestionId);
|
|
53
|
+
}
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
// if no suggestion id nor range is given, revert all suggestions one by one, always take furthermost first
|
|
57
|
+
// after each reversal, the next suggestion is searched in the new doc after the previous reversal
|
|
58
|
+
let nextSuggestionId = findNextStructureSuggestion(tr.doc);
|
|
59
|
+
while(nextSuggestionId != null){
|
|
60
|
+
revertStructureSuggestionWithPrerequisites(tr, nextSuggestionId);
|
|
61
|
+
nextSuggestionId = findNextStructureSuggestion(tr.doc);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function revertStructureSuggestionWithPrerequisites(tr, suggestionId) {
|
|
65
|
+
const suggestionIds = buildOrderedSuggestionIds(tr.doc, suggestionId, buildMaterializedPaths(tr.doc));
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
67
|
+
if (TRACE_ENABLED) console.group("reverting structure suggestions", suggestionIds);
|
|
68
|
+
for (const suggestionId of suggestionIds){
|
|
69
|
+
revertOneStructureSuggestion(tr, suggestionId);
|
|
70
|
+
}
|
|
71
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
72
|
+
if (TRACE_ENABLED) console.groupEnd();
|
|
73
|
+
}
|
|
74
|
+
function revertOneStructureSuggestion(tr, suggestionId) {
|
|
75
|
+
let count = 0;
|
|
76
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
77
|
+
if (TRACE_ENABLED) console.groupCollapsed("reverting structure suggestion", suggestionId);
|
|
78
|
+
let structureMark = findNextStructureMark(tr.doc, suggestionId);
|
|
79
|
+
while(structureMark !== null){
|
|
80
|
+
trace("reverting structure suggestion", suggestionId, "structure mark", structureMark.mark, "at pos", structureMark.pos, "at node", structureMark.node.toString());
|
|
81
|
+
revertStructureMark(tr, structureMark.mark, structureMark.pos);
|
|
82
|
+
structureMark = findNextStructureMark(tr.doc, suggestionId);
|
|
83
|
+
count++;
|
|
84
|
+
}
|
|
85
|
+
trace("reverted", count, "structure marks for suggestion", suggestionId);
|
|
86
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
87
|
+
if (TRACE_ENABLED) console.groupEnd();
|
|
88
|
+
}
|
|
89
|
+
export function revertStructureMark(tr, mark, pos) {
|
|
90
|
+
const transform = new Transform(tr.doc);
|
|
91
|
+
transform.removeNodeMark(pos, mark);
|
|
92
|
+
const node = transform.doc.nodeAt(pos);
|
|
93
|
+
if (!node) {
|
|
94
|
+
throw new Error(`Node not found at position ${String(pos)}`);
|
|
95
|
+
}
|
|
96
|
+
const attrs = mark.attrs;
|
|
97
|
+
if (!guardStructureMarkAttrs(attrs)) {
|
|
98
|
+
console.warn("revertStructureMark", "invalid shape of structure mark attrs", {
|
|
99
|
+
attrs
|
|
100
|
+
});
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const op = attrs.data.op;
|
|
104
|
+
trace("reverting structure mark with suggestion id", attrs.id, "at node", node.toString(), "at pos", pos, "with op", op.op, {
|
|
105
|
+
mark,
|
|
106
|
+
node,
|
|
107
|
+
$pos: transform.doc.resolve(pos),
|
|
108
|
+
op
|
|
109
|
+
});
|
|
110
|
+
switch(op.op){
|
|
111
|
+
case "add":
|
|
112
|
+
{
|
|
113
|
+
revertAddOp(op, transform, node, pos);
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
case "move":
|
|
117
|
+
{
|
|
118
|
+
revertMoveOp(op, transform, node, pos);
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
default:
|
|
122
|
+
console.warn("revertStructureMark", "unknown op", {
|
|
123
|
+
op
|
|
124
|
+
});
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
transform.steps.forEach((step)=>{
|
|
128
|
+
tr.step(step);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
// given a suggestion id
|
|
132
|
+
// return an array of suggestion ids to revert
|
|
133
|
+
// resolve suggestion dependencies, meaning,
|
|
134
|
+
// if to revert suggestion id 1 you need to revert 2, and to revert 2 you need to revert 3
|
|
135
|
+
// it will return [3,2,1]
|
|
136
|
+
// todo: this should probably use topological sort at some point
|
|
137
|
+
function buildOrderedSuggestionIds(node, suggestionId, materializedPaths) {
|
|
138
|
+
const { structure } = getSuggestionMarks(node.type.schema);
|
|
139
|
+
// collect marks with the given suggestionId
|
|
140
|
+
const markGroup = [];
|
|
141
|
+
node.descendants((descendant, pos)=>{
|
|
142
|
+
if (descendant.isText) return true;
|
|
143
|
+
if (!structure.isInSet(descendant.marks)) return true;
|
|
144
|
+
descendant.marks.forEach((mark)=>{
|
|
145
|
+
if (mark.type !== structure) return;
|
|
146
|
+
if (mark.attrs["id"] !== suggestionId) return;
|
|
147
|
+
markGroup.push({
|
|
148
|
+
mark,
|
|
149
|
+
node: descendant,
|
|
150
|
+
pos
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
return true;
|
|
154
|
+
});
|
|
155
|
+
const suggestionIds = new Set();
|
|
156
|
+
suggestionIds.add(suggestionId);
|
|
157
|
+
// find first mark that doesn't have a matching op.to
|
|
158
|
+
const mismatch = markGroup.find((mark)=>{
|
|
159
|
+
const nodeId = getNodeId(mark.node);
|
|
160
|
+
if (nodeId == null) return false;
|
|
161
|
+
const parentChain = materializedPaths.get(nodeId);
|
|
162
|
+
if (parentChain == null) return false;
|
|
163
|
+
const { attrs } = mark.mark;
|
|
164
|
+
if (!guardStructureMarkAttrs(attrs)) return false;
|
|
165
|
+
if (attrs.data.op.op !== "move") return false;
|
|
166
|
+
return !sameParentChain(attrs.data.op.to, parentChain.chain);
|
|
167
|
+
});
|
|
168
|
+
if (mismatch == null) {
|
|
169
|
+
return Array.from(suggestionIds).reverse();
|
|
170
|
+
}
|
|
171
|
+
// find first mark on the node that does have a matching op.to
|
|
172
|
+
const match = mismatch.node.marks.find((mark)=>{
|
|
173
|
+
if (mark.type !== structure) return false;
|
|
174
|
+
const { attrs } = mark;
|
|
175
|
+
if (!guardStructureMarkAttrs(attrs)) return false;
|
|
176
|
+
if (attrs.data.op.op !== "move") return false;
|
|
177
|
+
const nodeId = getNodeId(mismatch.node);
|
|
178
|
+
if (nodeId == null) return false;
|
|
179
|
+
const parentChain = materializedPaths.get(nodeId);
|
|
180
|
+
if (parentChain == null) return false;
|
|
181
|
+
return sameParentChain(attrs.data.op.to, parentChain.chain);
|
|
182
|
+
});
|
|
183
|
+
if (match) {
|
|
184
|
+
trace("suggestion", match.attrs["id"], "is a prerequisite for suggestion", suggestionId);
|
|
185
|
+
suggestionIds.add(match.attrs["id"]);
|
|
186
|
+
}
|
|
187
|
+
return Array.from(suggestionIds).reverse();
|
|
188
|
+
}
|
|
189
|
+
function findNextStructureSuggestion(doc) {
|
|
190
|
+
const { structure } = getSuggestionMarks(doc.type.schema);
|
|
191
|
+
const structureMarks = [];
|
|
192
|
+
doc.descendants((node, pos)=>{
|
|
193
|
+
if (node.isText) return true;
|
|
194
|
+
if (!structure.isInSet(node.marks)) return true;
|
|
195
|
+
node.marks.forEach((mark)=>{
|
|
196
|
+
if (mark.type !== structure) return;
|
|
197
|
+
structureMarks.push({
|
|
198
|
+
mark,
|
|
199
|
+
node,
|
|
200
|
+
pos
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
return true;
|
|
204
|
+
});
|
|
205
|
+
structureMarks.sort((a, b)=>b.pos - a.pos);
|
|
206
|
+
return structureMarks[0]?.mark.attrs["id"];
|
|
207
|
+
}
|
|
208
|
+
// given a suggestion id, find next structure mark to revert that belongs to that suggestion
|
|
209
|
+
// always take furthermost mark first
|
|
210
|
+
function findNextStructureMark(node, suggestionId) {
|
|
211
|
+
const { structure } = getSuggestionMarks(node.type.schema);
|
|
212
|
+
const structureMarks = [];
|
|
213
|
+
node.descendants((descendant, pos)=>{
|
|
214
|
+
// the assumption here is that a single node cannot have multiple structure marks with the same suggestion id
|
|
215
|
+
// check the invariant
|
|
216
|
+
const suggestionIds = new Set();
|
|
217
|
+
descendant.marks.forEach((mark)=>{
|
|
218
|
+
if (mark.type !== structure) return;
|
|
219
|
+
const markSuggestionId = mark.attrs["id"];
|
|
220
|
+
if (suggestionIds.has(markSuggestionId)) {
|
|
221
|
+
console.warn("node", node, "has multiple structure marks with the same suggestion id", markSuggestionId);
|
|
222
|
+
}
|
|
223
|
+
suggestionIds.add(markSuggestionId);
|
|
224
|
+
});
|
|
225
|
+
const mark = descendant.marks.find((mark)=>mark.type === structure && mark.attrs["id"] === suggestionId);
|
|
226
|
+
if (mark == null) return true;
|
|
227
|
+
structureMarks.push({
|
|
228
|
+
mark,
|
|
229
|
+
node: descendant,
|
|
230
|
+
pos
|
|
231
|
+
});
|
|
232
|
+
return true;
|
|
233
|
+
});
|
|
234
|
+
structureMarks.sort((a, b)=>b.pos - a.pos);
|
|
235
|
+
return structureMarks[0] ?? null;
|
|
236
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type Schema, type Node } from "prosemirror-model";
|
|
2
|
+
import { Plugin, PluginKey } from "prosemirror-state";
|
|
3
|
+
import { type SuggestionId } from "../../generateId.js";
|
|
4
|
+
import { type StructuralContextPath } from "./types.js";
|
|
5
|
+
import { Transform } from "prosemirror-transform";
|
|
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
|
+
}
|
|
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[];
|