@magic-marker/prosemirror-suggest-changes 0.3.1 → 0.3.3-wrap-unwrap.0
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.d.ts +3 -1
- package/dist/commands.js +14 -2
- package/dist/contentBetween.d.ts +2 -0
- package/dist/contentBetween.js +33 -0
- package/dist/features/wrapUnwrap/__tests__/blockquoteUnwrapAllNodes.data.d.ts +38 -0
- package/dist/features/wrapUnwrap/__tests__/blockquoteUnwrapAllNodes.playwright.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/blockquoteUnwrapAllNodes.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/blockquoteUnwrapOneNode.data.d.ts +45 -0
- package/dist/features/wrapUnwrap/__tests__/blockquoteUnwrapOneNode.playwright.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/blockquoteUnwrapOneNode.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/blockquoteUnwrapSingleNode.data.d.ts +38 -0
- package/dist/features/wrapUnwrap/__tests__/blockquoteUnwrapSingleNode.playwright.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/blockquoteUnwrapSingleNode.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/blockquoteWrapAllNodes.data.d.ts +38 -0
- package/dist/features/wrapUnwrap/__tests__/blockquoteWrapAllNodes.playwright.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/blockquoteWrapAllNodes.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/blockquoteWrapSingleNode.data.d.ts +38 -0
- package/dist/features/wrapUnwrap/__tests__/blockquoteWrapSingleNode.playwright.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/blockquoteWrapSingleNode.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/listItemLiftLast.data.d.ts +54 -0
- package/dist/features/wrapUnwrap/__tests__/listItemLiftLast.playwright.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/listItemLiftLast.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/listItemLiftMiddle.data.d.ts +48 -0
- package/dist/features/wrapUnwrap/__tests__/listItemLiftMiddle.playwright.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/listItemLiftMiddle.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/listItemLiftMultipleToTheTop.data.d.ts +74 -0
- package/dist/features/wrapUnwrap/__tests__/listItemLiftMultipleToTheTop.playwright.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/listItemLiftMultipleToTheTop.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/listItemLiftNestedOnce.data.d.ts +71 -0
- package/dist/features/wrapUnwrap/__tests__/listItemLiftNestedOnce.playwright.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/listItemLiftNestedOnce.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/listItemLiftTop.data.d.ts +54 -0
- package/dist/features/wrapUnwrap/__tests__/listItemLiftTop.playwright.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/listItemLiftTop.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/listItemSinkMultiple.data.d.ts +181 -0
- package/dist/features/wrapUnwrap/__tests__/listItemSinkMultiple.page.d.ts +30 -0
- package/dist/features/wrapUnwrap/__tests__/listItemSinkMultiple.playwright.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/listItemSinkMultiple.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/listItemSinkOneOnce.data.d.ts +51 -0
- package/dist/features/wrapUnwrap/__tests__/listItemSinkOneOnce.playwright.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/listItemSinkOneOnce.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/listItemsLiftMixedLevels.data.d.ts +74 -0
- package/dist/features/wrapUnwrap/__tests__/listItemsLiftMixedLevels.playwright.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/listItemsLiftMixedLevels.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/nestedListItemLiftToOuter.data.d.ts +71 -0
- package/dist/features/wrapUnwrap/__tests__/nestedListItemLiftToOuter.playwright.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/nestedListItemLiftToOuter.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/nestedListItemsLiftMultipleLevels.data.d.ts +114 -0
- package/dist/features/wrapUnwrap/__tests__/nestedListItemsLiftMultipleLevels.playwright.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/nestedListItemsLiftMultipleLevels.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/testUtils.d.ts +5 -0
- package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftMultipleNodesToTheTop.data.d.ts +44 -0
- package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftMultipleNodesToTheTop.playwright.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftMultipleNodesToTheTop.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftMultipleNodesUp.data.d.ts +38 -0
- package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftMultipleNodesUp.playwright.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftMultipleNodesUp.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftOneNodeFromMiddle.data.d.ts +46 -0
- package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftOneNodeFromMiddle.playwright.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftOneNodeFromMiddle.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftOneNodeToTheTop.data.d.ts +57 -0
- package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftOneNodeToTheTop.playwright.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftOneNodeToTheTop.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftOneNodeUp.data.d.ts +45 -0
- package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftOneNodeUp.playwright.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftOneNodeUp.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteWrap.data.d.ts +44 -0
- package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteWrap.playwright.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteWrap.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteWrapMultiple.data.d.ts +44 -0
- package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteWrapMultiple.playwright.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteWrapMultiple.test.d.ts +1 -0
- package/dist/features/wrapUnwrap/addStructureMark.d.ts +41 -0
- package/dist/features/wrapUnwrap/addStructureMark.js +15 -0
- package/dist/features/wrapUnwrap/findMatchingNodeSides.d.ts +15 -0
- package/dist/features/wrapUnwrap/findMatchingNodeSides.js +133 -0
- package/dist/features/wrapUnwrap/handleStructureStep.d.ts +4 -0
- package/dist/features/wrapUnwrap/handleStructureStep.js +174 -0
- package/dist/features/wrapUnwrap/revertStructureSuggestion.d.ts +44 -0
- package/dist/features/wrapUnwrap/revertStructureSuggestion.js +374 -0
- package/dist/generateId.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/rebaseStep.d.ts +9 -0
- package/dist/rebaseStep.js +11 -0
- package/dist/replaceAroundStep.js +6 -1
- package/dist/replaceStep.js +5 -0
- package/dist/schema.d.ts +2 -1
- package/dist/schema.js +37 -1
- package/dist/testing/testBuilders.d.ts +1 -2
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +6 -2
- package/package.json +1 -1
- package/src/features/wrapUnwrap/README.md +198 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type Transaction, type Command } from "prosemirror-state";
|
|
2
|
+
import { type SuggestionId } from "../../generateId.js";
|
|
3
|
+
import { type Node, type Mark } from "prosemirror-model";
|
|
4
|
+
import { type Transform } from "prosemirror-transform";
|
|
5
|
+
export declare function isStructureSuggestion(suggestionId: SuggestionId, tr: Transaction): boolean;
|
|
6
|
+
export declare function revertAllStructureSuggestions(doc: Node, tr: Transaction): void;
|
|
7
|
+
export declare function revertStructureSuggestion(suggestionId: SuggestionId): Command;
|
|
8
|
+
export declare function applyStructureSuggestion(suggestionId: SuggestionId): Command;
|
|
9
|
+
export declare function revertStructureSuggestions(suggestionIds: SuggestionId[]): Command;
|
|
10
|
+
export declare function applyStructureMarkGroup(group: {
|
|
11
|
+
type: "replaceAround";
|
|
12
|
+
markFrom: {
|
|
13
|
+
pos: number;
|
|
14
|
+
node: Node;
|
|
15
|
+
mark: Mark;
|
|
16
|
+
};
|
|
17
|
+
markTo: {
|
|
18
|
+
pos: number;
|
|
19
|
+
node: Node;
|
|
20
|
+
mark: Mark;
|
|
21
|
+
};
|
|
22
|
+
markGapFrom: {
|
|
23
|
+
pos: number;
|
|
24
|
+
node: Node;
|
|
25
|
+
mark: Mark;
|
|
26
|
+
};
|
|
27
|
+
markGapTo: {
|
|
28
|
+
pos: number;
|
|
29
|
+
node: Node;
|
|
30
|
+
mark: Mark;
|
|
31
|
+
};
|
|
32
|
+
} | {
|
|
33
|
+
type: "replace";
|
|
34
|
+
markFrom: {
|
|
35
|
+
pos: number;
|
|
36
|
+
node: Node;
|
|
37
|
+
mark: Mark;
|
|
38
|
+
};
|
|
39
|
+
markTo: {
|
|
40
|
+
pos: number;
|
|
41
|
+
node: Node;
|
|
42
|
+
mark: Mark;
|
|
43
|
+
};
|
|
44
|
+
}, tr: Transform): void;
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import { suggestChangesKey } from "../../plugin.js";
|
|
2
|
+
import { getSuggestionMarks } from "../../utils.js";
|
|
3
|
+
import { Slice } from "prosemirror-model";
|
|
4
|
+
import { ReplaceAroundStep, ReplaceStep } from "prosemirror-transform";
|
|
5
|
+
export function isStructureSuggestion(suggestionId, tr) {
|
|
6
|
+
try {
|
|
7
|
+
findStructureMarkGroupBySuggestionId(suggestionId, tr);
|
|
8
|
+
return true;
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
10
|
+
} catch (error) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function revertAllStructureSuggestions(doc, tr) {
|
|
15
|
+
const { structure } = getSuggestionMarks(doc.type.schema);
|
|
16
|
+
// find structure mark in the doc
|
|
17
|
+
// if found, revert it, produce a new doc
|
|
18
|
+
// find next structure mark in the new doc
|
|
19
|
+
// easier than going nodesBetween on the original doc, and then mapping the position, figuring out if the node is present in the new doc or was deleted
|
|
20
|
+
const findStructureMark = (doc)=>{
|
|
21
|
+
let structureMark = null;
|
|
22
|
+
doc.nodesBetween(0, doc.content.size, (node)=>{
|
|
23
|
+
structureMark = structureMark ?? structure.isInSet(node.marks) ?? null;
|
|
24
|
+
return structureMark === null;
|
|
25
|
+
});
|
|
26
|
+
return structureMark;
|
|
27
|
+
};
|
|
28
|
+
let curDoc = doc;
|
|
29
|
+
let structureMark = findStructureMark(doc);
|
|
30
|
+
while(structureMark !== null){
|
|
31
|
+
const suggestionId = structureMark.attrs["id"];
|
|
32
|
+
performStructureRevert(suggestionId, tr);
|
|
33
|
+
curDoc = tr.doc;
|
|
34
|
+
structureMark = findStructureMark(curDoc);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function revertStructureSuggestion(suggestionId) {
|
|
38
|
+
return (state, dispatch)=>{
|
|
39
|
+
const tr = state.tr;
|
|
40
|
+
performStructureRevert(suggestionId, tr);
|
|
41
|
+
if (!tr.steps.length) return false;
|
|
42
|
+
tr.setMeta(suggestChangesKey, {
|
|
43
|
+
skip: true
|
|
44
|
+
});
|
|
45
|
+
dispatch?.(tr);
|
|
46
|
+
return true;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function applyStructureSuggestion(suggestionId) {
|
|
50
|
+
return (state, dispatch)=>{
|
|
51
|
+
const tr = state.tr;
|
|
52
|
+
performStructureRevert(suggestionId, tr, "apply");
|
|
53
|
+
if (!tr.steps.length) return false;
|
|
54
|
+
tr.setMeta(suggestChangesKey, {
|
|
55
|
+
skip: true
|
|
56
|
+
});
|
|
57
|
+
dispatch?.(tr);
|
|
58
|
+
return true;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export function revertStructureSuggestions(suggestionIds) {
|
|
62
|
+
return (state, dispatch)=>{
|
|
63
|
+
const tr = state.tr;
|
|
64
|
+
suggestionIds.forEach((suggestionId)=>{
|
|
65
|
+
performStructureRevert(suggestionId, tr);
|
|
66
|
+
});
|
|
67
|
+
if (!tr.steps.length) return false;
|
|
68
|
+
tr.setMeta(suggestChangesKey, {
|
|
69
|
+
skip: true
|
|
70
|
+
});
|
|
71
|
+
dispatch?.(tr);
|
|
72
|
+
return true;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function performStructureRevert(suggestionId, tr, direction = "revert") {
|
|
76
|
+
console.groupCollapsed(// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
77
|
+
`performStructureRevert, suggestionId = ${suggestionId}`);
|
|
78
|
+
const { structure } = getSuggestionMarks(tr.doc.type.schema);
|
|
79
|
+
// find main suggestion from and to
|
|
80
|
+
const { markFrom, markTo, markGapFrom, markGapTo } = findStructureMarkGroupBySuggestionId(suggestionId, tr);
|
|
81
|
+
console.log("found main suggestion", {
|
|
82
|
+
markFrom,
|
|
83
|
+
markTo,
|
|
84
|
+
markGapFrom,
|
|
85
|
+
markGapTo,
|
|
86
|
+
suggestionId
|
|
87
|
+
});
|
|
88
|
+
let from = getPosFromMark(markFrom.mark, markFrom.pos, markFrom.node);
|
|
89
|
+
let to = getPosFromMark(markTo.mark, markTo.pos, markTo.node);
|
|
90
|
+
console.log("main suggestion positions", {
|
|
91
|
+
from,
|
|
92
|
+
to
|
|
93
|
+
});
|
|
94
|
+
if (from == null || to == null) {
|
|
95
|
+
throw new Error(`Could not find all positions for suggestion`);
|
|
96
|
+
}
|
|
97
|
+
[from, to] = from > to ? [
|
|
98
|
+
to,
|
|
99
|
+
from
|
|
100
|
+
] : [
|
|
101
|
+
from,
|
|
102
|
+
to
|
|
103
|
+
];
|
|
104
|
+
// find all other structure suggestions within from and to interval of the main suggestion
|
|
105
|
+
const structureMarkGroups = new Set();
|
|
106
|
+
structureMarkGroups.add(suggestionId);
|
|
107
|
+
let gapFrom = null;
|
|
108
|
+
let gapTo = null;
|
|
109
|
+
if (markGapFrom != null) {
|
|
110
|
+
gapFrom = getPosFromMark(markGapFrom.mark, markGapFrom.pos, markGapFrom.node);
|
|
111
|
+
gapTo = getPosFromMark(markGapTo.mark, markGapTo.pos, markGapTo.node);
|
|
112
|
+
if (gapFrom != null && gapTo != null) {
|
|
113
|
+
[gapFrom, gapTo] = gapFrom > gapTo ? [
|
|
114
|
+
gapTo,
|
|
115
|
+
gapFrom
|
|
116
|
+
] : [
|
|
117
|
+
gapFrom,
|
|
118
|
+
gapTo
|
|
119
|
+
];
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
tr.doc.nodesBetween(from, to, (node, pos)=>{
|
|
123
|
+
node.marks.forEach((mark)=>{
|
|
124
|
+
if (mark.type !== structure) return;
|
|
125
|
+
const markData = mark.attrs["data"];
|
|
126
|
+
if (!markData) return;
|
|
127
|
+
const id = mark.attrs["id"];
|
|
128
|
+
if (id <= suggestionId) return;
|
|
129
|
+
let startsInGap = false;
|
|
130
|
+
let endsInGap = false;
|
|
131
|
+
if (gapFrom != null && gapTo != null) {
|
|
132
|
+
startsInGap = gapFrom <= pos && pos <= gapTo;
|
|
133
|
+
endsInGap = gapFrom <= pos + node.nodeSize && pos + node.nodeSize <= gapTo;
|
|
134
|
+
}
|
|
135
|
+
// check if mark starts and ends in gap - skip mark if true
|
|
136
|
+
// for ReplaceStep marks startsInGap and endsInGap is always false
|
|
137
|
+
if (startsInGap && endsInGap) {
|
|
138
|
+
console.log("ignored mark - starts and ends in gap", {
|
|
139
|
+
startsInGap,
|
|
140
|
+
endsInGap,
|
|
141
|
+
markId: id,
|
|
142
|
+
markData,
|
|
143
|
+
node,
|
|
144
|
+
pos
|
|
145
|
+
});
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
let startsInRange = false;
|
|
149
|
+
let endsInRange = false;
|
|
150
|
+
if (gapFrom != null && gapTo != null) {
|
|
151
|
+
// for ReplaceAround marks "startsInRange" means starts in [from, gapFrom] or [gapTo, to] ranges
|
|
152
|
+
// same for endsInRange
|
|
153
|
+
startsInRange = from <= pos && pos <= gapFrom || gapTo <= pos && pos <= to;
|
|
154
|
+
endsInRange = from <= pos + node.nodeSize && pos + node.nodeSize <= gapFrom || gapTo <= pos + node.nodeSize && pos + node.nodeSize <= to;
|
|
155
|
+
} else {
|
|
156
|
+
// for ReplaceStep marks "startsInRange" means starts in [from, to] range
|
|
157
|
+
// same for endsInRange
|
|
158
|
+
startsInRange = from <= pos && pos <= to;
|
|
159
|
+
endsInRange = from <= pos + node.nodeSize && pos + node.nodeSize <= to;
|
|
160
|
+
}
|
|
161
|
+
if (!startsInRange && !endsInRange) {
|
|
162
|
+
console.log("ignored mark - does not start or end in range", {
|
|
163
|
+
startsInRange,
|
|
164
|
+
endsInRange,
|
|
165
|
+
markId: id,
|
|
166
|
+
markData,
|
|
167
|
+
node,
|
|
168
|
+
pos
|
|
169
|
+
});
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
console.log("found nested structure mark", {
|
|
173
|
+
mark,
|
|
174
|
+
markId: id,
|
|
175
|
+
markData,
|
|
176
|
+
startsInGap,
|
|
177
|
+
endsInGap,
|
|
178
|
+
startsInRange,
|
|
179
|
+
endsInRange,
|
|
180
|
+
node,
|
|
181
|
+
pos,
|
|
182
|
+
mainMark: {
|
|
183
|
+
markFrom,
|
|
184
|
+
markTo,
|
|
185
|
+
markGapFrom,
|
|
186
|
+
markGapTo
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
structureMarkGroups.add(id);
|
|
190
|
+
});
|
|
191
|
+
return true;
|
|
192
|
+
});
|
|
193
|
+
// revert structure mark groups in decreasing order of their ids
|
|
194
|
+
const markIds = Array.from(structureMarkGroups.values()).sort((a, b)=>Number(b) - Number(a));
|
|
195
|
+
markIds.forEach((id)=>{
|
|
196
|
+
const group = findStructureMarkGroupBySuggestionId(id, tr);
|
|
197
|
+
if (direction === "apply") {
|
|
198
|
+
applyStructureMarkGroup(group, tr);
|
|
199
|
+
} else {
|
|
200
|
+
revertStructureMarkGroup(group, tr);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
console.groupEnd();
|
|
204
|
+
}
|
|
205
|
+
function getPosFromMark(mark, pos, node) {
|
|
206
|
+
const markData = mark.attrs["data"];
|
|
207
|
+
if (!markData) return null;
|
|
208
|
+
if (markData.position === "start") {
|
|
209
|
+
return pos;
|
|
210
|
+
}
|
|
211
|
+
if (markData.position === "end") {
|
|
212
|
+
return pos + node.nodeSize;
|
|
213
|
+
}
|
|
214
|
+
if (markData.position === "innerStart") {
|
|
215
|
+
return pos + 1;
|
|
216
|
+
}
|
|
217
|
+
if (markData.position === "innerEnd") {
|
|
218
|
+
return pos + node.nodeSize - 1;
|
|
219
|
+
}
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
export function applyStructureMarkGroup(group, tr) {
|
|
223
|
+
console.groupCollapsed("apply structure group, id = ", group.markFrom.mark.attrs["id"]);
|
|
224
|
+
console.log({
|
|
225
|
+
group
|
|
226
|
+
});
|
|
227
|
+
if (group.type === "replace") {
|
|
228
|
+
const { markFrom, markTo } = group;
|
|
229
|
+
tr.removeNodeMark(markFrom.pos, markFrom.mark);
|
|
230
|
+
tr.removeNodeMark(markTo.pos, markTo.mark);
|
|
231
|
+
console.groupEnd();
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const { markFrom, markTo, markGapFrom, markGapTo } = group;
|
|
235
|
+
tr.removeNodeMark(markFrom.pos, markFrom.mark);
|
|
236
|
+
tr.removeNodeMark(markTo.pos, markTo.mark);
|
|
237
|
+
tr.removeNodeMark(markGapFrom.pos, markGapFrom.mark);
|
|
238
|
+
tr.removeNodeMark(markGapTo.pos, markGapTo.mark);
|
|
239
|
+
console.groupEnd();
|
|
240
|
+
}
|
|
241
|
+
function revertStructureMarkGroup(group, tr) {
|
|
242
|
+
console.groupCollapsed("revert structure group, id = ", group.markFrom.mark.attrs["id"]);
|
|
243
|
+
console.log({
|
|
244
|
+
group
|
|
245
|
+
});
|
|
246
|
+
if (group.type === "replace") {
|
|
247
|
+
const { markFrom, markTo } = group;
|
|
248
|
+
// extract positions from, to, gapFrom and gapTo from marks
|
|
249
|
+
// the positions are either the mark's start, or the mark's end
|
|
250
|
+
const from = getPosFromMark(markFrom.mark, markFrom.pos, markFrom.node);
|
|
251
|
+
const to = getPosFromMark(markTo.mark, markTo.pos, markTo.node);
|
|
252
|
+
if (from === null || to === null) {
|
|
253
|
+
throw new Error(`Could not find all positions for suggestion`);
|
|
254
|
+
}
|
|
255
|
+
// extract the rest of the data required to reconstruct the step: slice, and structure
|
|
256
|
+
// any of those 2 marks can be used for that, this data is identical in all of them
|
|
257
|
+
const mark = markFrom.mark;
|
|
258
|
+
const markData = mark.attrs["data"];
|
|
259
|
+
if (!markData) {
|
|
260
|
+
throw new Error(`Missing mark data for suggestion`);
|
|
261
|
+
}
|
|
262
|
+
const slice = markData.slice ? Slice.fromJSON(tr.doc.type.schema, markData.slice) : Slice.empty;
|
|
263
|
+
const isStepStructural = markData.structure ?? false;
|
|
264
|
+
// reconstruct the step
|
|
265
|
+
// this is the inverse step of the step that created this change
|
|
266
|
+
const step = new ReplaceStep(from, to, slice, isStepStructural);
|
|
267
|
+
console.log({
|
|
268
|
+
step
|
|
269
|
+
});
|
|
270
|
+
tr.removeNodeMark(markFrom.pos, markFrom.mark);
|
|
271
|
+
tr.removeNodeMark(markTo.pos, markTo.mark);
|
|
272
|
+
tr.step(step);
|
|
273
|
+
console.groupEnd();
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const { markFrom, markTo, markGapFrom, markGapTo } = group;
|
|
277
|
+
// extract positions from, to, gapFrom and gapTo from marks
|
|
278
|
+
// the positions are either the mark's start, or the mark's end
|
|
279
|
+
const from = getPosFromMark(markFrom.mark, markFrom.pos, markFrom.node);
|
|
280
|
+
const to = getPosFromMark(markTo.mark, markTo.pos, markTo.node);
|
|
281
|
+
const gapFrom = getPosFromMark(markGapFrom.mark, markGapFrom.pos, markGapFrom.node);
|
|
282
|
+
const gapTo = getPosFromMark(markGapTo.mark, markGapTo.pos, markGapTo.node);
|
|
283
|
+
if (from === null || to === null || gapFrom === null || gapTo === null) {
|
|
284
|
+
throw new Error(`Could not find all positions for suggestion`);
|
|
285
|
+
}
|
|
286
|
+
// extract the rest of the data required to reconstruct the step: insert, slice, and structure
|
|
287
|
+
// any of those 4 marks can be used for that, this data is identical in all of them
|
|
288
|
+
const mark = markGapFrom.mark;
|
|
289
|
+
const markData = mark.attrs["data"];
|
|
290
|
+
if (markData?.insert == null) {
|
|
291
|
+
throw new Error(`Missing insert for suggestion`);
|
|
292
|
+
}
|
|
293
|
+
const slice = markData.slice ? Slice.fromJSON(tr.doc.type.schema, markData.slice) : Slice.empty;
|
|
294
|
+
const insert = markData.insert;
|
|
295
|
+
const isStepStructural = markData.structure ?? false;
|
|
296
|
+
// reconstruct the step
|
|
297
|
+
// this is the inverse step of the step that created this change
|
|
298
|
+
const step = new ReplaceAroundStep(from, to, gapFrom, gapTo, slice, insert, isStepStructural);
|
|
299
|
+
console.log({
|
|
300
|
+
step
|
|
301
|
+
});
|
|
302
|
+
tr.removeNodeMark(markFrom.pos, markFrom.mark);
|
|
303
|
+
tr.removeNodeMark(markTo.pos, markTo.mark);
|
|
304
|
+
tr.removeNodeMark(markGapFrom.pos, markGapFrom.mark);
|
|
305
|
+
tr.removeNodeMark(markGapTo.pos, markGapTo.mark);
|
|
306
|
+
tr.step(step);
|
|
307
|
+
console.groupEnd();
|
|
308
|
+
}
|
|
309
|
+
function findStructureMarkGroupBySuggestionId(suggestionId, tr) {
|
|
310
|
+
const { structure } = getSuggestionMarks(tr.doc.type.schema);
|
|
311
|
+
let markFrom = null;
|
|
312
|
+
let markTo = null;
|
|
313
|
+
let markGapFrom = null;
|
|
314
|
+
let markGapTo = null;
|
|
315
|
+
// using suggestionId, find 4 marks: from, to, gapFrom and gapTo
|
|
316
|
+
tr.doc.nodesBetween(0, tr.doc.content.size, (node, pos)=>{
|
|
317
|
+
node.marks.forEach((mark)=>{
|
|
318
|
+
if (mark.type !== structure) return;
|
|
319
|
+
if (mark.attrs["id"] !== suggestionId) return;
|
|
320
|
+
const markData = mark.attrs["data"];
|
|
321
|
+
if (!markData) return;
|
|
322
|
+
if (markData.value === "from") {
|
|
323
|
+
markFrom = {
|
|
324
|
+
pos,
|
|
325
|
+
node,
|
|
326
|
+
mark
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
if (markData.value === "to") {
|
|
330
|
+
markTo = {
|
|
331
|
+
pos,
|
|
332
|
+
node,
|
|
333
|
+
mark
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
if (markData.value === "gapFrom") {
|
|
337
|
+
markGapFrom = {
|
|
338
|
+
pos,
|
|
339
|
+
node,
|
|
340
|
+
mark
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
if (markData.value === "gapTo") {
|
|
344
|
+
markGapTo = {
|
|
345
|
+
pos,
|
|
346
|
+
node,
|
|
347
|
+
mark
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
return markFrom == null || markTo == null || markGapFrom == null || markGapTo == null;
|
|
352
|
+
});
|
|
353
|
+
if (markFrom == null || markTo == null) {
|
|
354
|
+
throw new Error(`Could not find all marks for suggestion id ${suggestionId}`);
|
|
355
|
+
}
|
|
356
|
+
const type = markFrom.mark.attrs["data"]?.type;
|
|
357
|
+
if (type === "replace") {
|
|
358
|
+
return {
|
|
359
|
+
type: "replace",
|
|
360
|
+
markFrom,
|
|
361
|
+
markTo
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
if (markGapFrom == null || markGapTo == null) {
|
|
365
|
+
throw new Error(`Could not find all gap marks for replace around suggestion id ${suggestionId}`);
|
|
366
|
+
}
|
|
367
|
+
return {
|
|
368
|
+
type: "replaceAround",
|
|
369
|
+
markFrom,
|
|
370
|
+
markTo,
|
|
371
|
+
markGapFrom,
|
|
372
|
+
markGapTo
|
|
373
|
+
};
|
|
374
|
+
}
|
package/dist/generateId.js
CHANGED
|
@@ -8,12 +8,12 @@ export function parseSuggestionId(id) {
|
|
|
8
8
|
return parsed;
|
|
9
9
|
}
|
|
10
10
|
export function generateNextNumberId(schema, doc) {
|
|
11
|
-
const { deletion, insertion, modification } = getSuggestionMarks(schema);
|
|
11
|
+
const { deletion, insertion, modification, structure } = getSuggestionMarks(schema);
|
|
12
12
|
// Find the highest change id in the document so far,
|
|
13
13
|
// and use that as the starting point for new changes
|
|
14
14
|
let suggestionId = 0;
|
|
15
15
|
doc?.descendants((node)=>{
|
|
16
|
-
const mark = node.marks.find((mark)=>mark.type === insertion || mark.type === deletion || mark.type === modification);
|
|
16
|
+
const mark = node.marks.find((mark)=>mark.type === insertion || mark.type === deletion || mark.type === modification || mark.type === structure);
|
|
17
17
|
if (mark) {
|
|
18
18
|
suggestionId = Math.max(suggestionId, mark.attrs["id"]);
|
|
19
19
|
return false;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { addSuggestionMarks, insertion, deletion, modification, hiddenDeletion, } from "./schema.js";
|
|
1
|
+
export { addSuggestionMarks, insertion, deletion, modification, hiddenDeletion, structure, } from "./schema.js";
|
|
2
2
|
export { selectSuggestion, revertSuggestion, revertSuggestions, applySuggestion, applySuggestions, enableSuggestChanges, disableSuggestChanges, toggleSuggestChanges, } from "./commands.js";
|
|
3
3
|
export { suggestChanges, suggestChangesKey, isSuggestChangesEnabled, } from "./plugin.js";
|
|
4
4
|
export { withSuggestChanges, transformToSuggestionTransaction, } from "./withSuggestChanges.js";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { addSuggestionMarks, insertion, deletion, modification, hiddenDeletion } from "./schema.js";
|
|
1
|
+
export { addSuggestionMarks, insertion, deletion, modification, hiddenDeletion, structure } from "./schema.js";
|
|
2
2
|
export { selectSuggestion, revertSuggestion, revertSuggestions, applySuggestion, applySuggestions, enableSuggestChanges, disableSuggestChanges, toggleSuggestChanges } from "./commands.js";
|
|
3
3
|
export { suggestChanges, suggestChangesKey, isSuggestChangesEnabled } from "./plugin.js";
|
|
4
4
|
export { withSuggestChanges, transformToSuggestionTransaction } from "./withSuggestChanges.js";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type Step } from "prosemirror-transform";
|
|
2
|
+
/**
|
|
3
|
+
* Rebase a step onto a new lineage of steps
|
|
4
|
+
*
|
|
5
|
+
* @param step The step to rebase
|
|
6
|
+
* @param back The old steps to undo, in the order they were originally applied
|
|
7
|
+
* @param forth The new steps to map through
|
|
8
|
+
*/
|
|
9
|
+
export declare function rebaseStep(step: Step, back: Step[], forth: Step[]): Step | null;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rebase a step onto a new lineage of steps
|
|
3
|
+
*
|
|
4
|
+
* @param step The step to rebase
|
|
5
|
+
* @param back The old steps to undo, in the order they were originally applied
|
|
6
|
+
* @param forth The new steps to map through
|
|
7
|
+
*/ export function rebaseStep(step, back, forth) {
|
|
8
|
+
const reset = back.slice().reverse().reduce((acc, step)=>acc?.map(step.getMap().invert()) ?? null, step);
|
|
9
|
+
const rebased = forth.reduce((acc, step)=>acc?.map(step.getMap()) ?? null, reset);
|
|
10
|
+
return rebased;
|
|
11
|
+
}
|
|
@@ -6,6 +6,7 @@ import { suggestRemoveNodeMarkStep } from "./removeNodeMarkStep.js";
|
|
|
6
6
|
import { suggestReplaceStep } from "./replaceStep.js";
|
|
7
7
|
import { getSuggestionMarks } from "./utils.js";
|
|
8
8
|
import { applySuggestionsToRange } from "./commands.js";
|
|
9
|
+
import { handleStructureStep } from "./features/wrapUnwrap/handleStructureStep.js";
|
|
9
10
|
/**
|
|
10
11
|
* This detects and handles changes from `setNodeMarkup` so that these are tracked as a modification
|
|
11
12
|
* instead of a deletion + insertion
|
|
@@ -80,7 +81,11 @@ import { applySuggestionsToRange } from "./commands.js";
|
|
|
80
81
|
* equivalent replace step will be generated, and then processed via
|
|
81
82
|
* trackReplaceStep().
|
|
82
83
|
*/ export function suggestReplaceAroundStep(trackedTransaction, state, doc, step, prevSteps, suggestionId) {
|
|
83
|
-
|
|
84
|
+
let handled = handleStructureStep(trackedTransaction, step, prevSteps, suggestionId);
|
|
85
|
+
if (handled) {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
handled = suggestSetNodeMarkup(trackedTransaction, state, doc, step, prevSteps, suggestionId);
|
|
84
89
|
if (handled) {
|
|
85
90
|
return true;
|
|
86
91
|
}
|
package/dist/replaceStep.js
CHANGED
|
@@ -4,6 +4,7 @@ import { rebasePos } from "./rebasePos.js";
|
|
|
4
4
|
import { getSuggestionMarks } from "./utils.js";
|
|
5
5
|
import { joinBlocks } from "./features/joinBlocks/index.js";
|
|
6
6
|
import { collapseZWSPNodes, findJoinMark, joinNodesAndMarkJoinPoints, removeZWSPDeletions } from "./features/joinOnDelete/index.js";
|
|
7
|
+
import { handleStructureStep } from "./features/wrapUnwrap/handleStructureStep.js";
|
|
7
8
|
/**
|
|
8
9
|
* Transform a replace step into its equivalent tracked steps.
|
|
9
10
|
*
|
|
@@ -34,6 +35,10 @@ import { collapseZWSPNodes, findJoinMark, joinNodesAndMarkJoinPoints, removeZWSP
|
|
|
34
35
|
* will be joined and given the same ids. Any no-longer-necessary
|
|
35
36
|
* zero-width spaces will be removed.
|
|
36
37
|
*/ export function suggestReplaceStep(trackedTransaction, state, doc, step, prevSteps, suggestionId) {
|
|
38
|
+
const handled = handleStructureStep(trackedTransaction, step, prevSteps, suggestionId);
|
|
39
|
+
if (handled) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
37
42
|
const { deletion, insertion } = getSuggestionMarks(state.schema);
|
|
38
43
|
// Check for insertion and deletion marks directly
|
|
39
44
|
// adjacent to this step's boundaries. If they exist,
|
package/dist/schema.d.ts
CHANGED
|
@@ -3,10 +3,11 @@ export declare const deletion: MarkSpec;
|
|
|
3
3
|
export declare const hiddenDeletion: MarkSpec;
|
|
4
4
|
export declare const insertion: MarkSpec;
|
|
5
5
|
export declare const modification: MarkSpec;
|
|
6
|
+
export declare const structure: MarkSpec;
|
|
6
7
|
/**
|
|
7
8
|
* Add the deletion, insertion, and modification marks to
|
|
8
9
|
* the provided MarkSpec map.
|
|
9
10
|
*/
|
|
10
11
|
export declare function addSuggestionMarks<Marks extends string>(marks: Record<Marks, MarkSpec>, opts?: {
|
|
11
12
|
experimental_deletions?: "hidden" | "visible";
|
|
12
|
-
}): Record<Marks | "deletion" | "insertion" | "modification", MarkSpec>;
|
|
13
|
+
}): Record<Marks | "deletion" | "insertion" | "modification" | "structure", MarkSpec>;
|
package/dist/schema.js
CHANGED
|
@@ -166,6 +166,41 @@ export const modification = {
|
|
|
166
166
|
}
|
|
167
167
|
]
|
|
168
168
|
};
|
|
169
|
+
export const structure = {
|
|
170
|
+
inclusive: false,
|
|
171
|
+
excludes: "deletion insertion modification",
|
|
172
|
+
attrs: {
|
|
173
|
+
id: {
|
|
174
|
+
validate: suggestionIdValidate
|
|
175
|
+
},
|
|
176
|
+
data: {
|
|
177
|
+
default: null
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
toDOM (mark) {
|
|
181
|
+
return [
|
|
182
|
+
"div",
|
|
183
|
+
{
|
|
184
|
+
"data-type": "structure",
|
|
185
|
+
"data-id": JSON.stringify(mark.attrs["id"]),
|
|
186
|
+
"data-data": JSON.stringify(mark.attrs["data"])
|
|
187
|
+
},
|
|
188
|
+
0
|
|
189
|
+
];
|
|
190
|
+
},
|
|
191
|
+
parseDOM: [
|
|
192
|
+
{
|
|
193
|
+
tag: "div[data-type='structure']",
|
|
194
|
+
getAttrs (node) {
|
|
195
|
+
if (!node.dataset["id"]) return false;
|
|
196
|
+
return {
|
|
197
|
+
id: JSON.parse(node.dataset["id"]),
|
|
198
|
+
data: JSON.parse(node.dataset["data"] ?? "null")
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
]
|
|
203
|
+
};
|
|
169
204
|
/**
|
|
170
205
|
* Add the deletion, insertion, and modification marks to
|
|
171
206
|
* the provided MarkSpec map.
|
|
@@ -174,6 +209,7 @@ export const modification = {
|
|
|
174
209
|
...marks,
|
|
175
210
|
deletion: opts?.experimental_deletions === "hidden" ? hiddenDeletion : deletion,
|
|
176
211
|
insertion,
|
|
177
|
-
modification
|
|
212
|
+
modification,
|
|
213
|
+
structure
|
|
178
214
|
};
|
|
179
215
|
}
|
|
@@ -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
|
-
declare const schema: Schema<"blockquote" | "text" | "doc" | "paragraph" | "
|
|
3
|
+
export declare const schema: Schema<"blockquote" | "text" | "doc" | "paragraph" | "bulletList" | "listItem" | "orderedList" | "image" | "horizontal_rule" | "heading" | "code_block" | "hard_break", "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
|
};
|
|
@@ -8,4 +8,3 @@ export type TaggedNode = Node & {
|
|
|
8
8
|
flat: Node;
|
|
9
9
|
tag: Record<string, number>;
|
|
10
10
|
};
|
|
11
|
-
export {};
|
package/dist/utils.d.ts
CHANGED
package/dist/utils.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Get the suggestion mark types from a schema, with proper error handling.
|
|
3
3
|
* Throws an error if any of the required marks are not found.
|
|
4
4
|
*/ export function getSuggestionMarks(schema) {
|
|
5
|
-
const { insertion, deletion, modification } = schema.marks;
|
|
5
|
+
const { insertion, deletion, modification, structure } = schema.marks;
|
|
6
6
|
if (!insertion) {
|
|
7
7
|
throw new Error("Failed to find insertion mark in schema. Did you forget to add it?");
|
|
8
8
|
}
|
|
@@ -12,9 +12,13 @@
|
|
|
12
12
|
if (!modification) {
|
|
13
13
|
throw new Error("Failed to find modification mark in schema. Did you forget to add it?");
|
|
14
14
|
}
|
|
15
|
+
if (!structure) {
|
|
16
|
+
throw new Error("Failed to find structure mark in schema. Did you forget to add it?");
|
|
17
|
+
}
|
|
15
18
|
return {
|
|
16
19
|
insertion,
|
|
17
20
|
deletion,
|
|
18
|
-
modification
|
|
21
|
+
modification,
|
|
22
|
+
structure
|
|
19
23
|
};
|
|
20
24
|
}
|