@magic-marker/prosemirror-suggest-changes 0.2.1-wrap-unwrap.2 → 0.3.1

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.
Files changed (100) hide show
  1. package/dist/__tests__/playwrightBaseTest.d.ts +6 -0
  2. package/dist/__tests__/playwrightPage.d.ts +15 -0
  3. package/dist/commands.js +1 -10
  4. package/dist/constants.d.ts +1 -0
  5. package/dist/constants.js +1 -0
  6. package/dist/decorations.js +4 -3
  7. package/dist/ensureSelectionPlugin.d.ts +13 -0
  8. package/dist/ensureSelectionPlugin.js +306 -0
  9. package/dist/generateId.js +2 -2
  10. package/dist/index.d.ts +2 -1
  11. package/dist/index.js +2 -1
  12. package/dist/plugin.js +2 -14
  13. package/dist/prependDeletionsWithZWSP.d.ts +2 -0
  14. package/dist/prependDeletionsWithZWSP.js +45 -0
  15. package/dist/replaceAroundStep.js +1 -6
  16. package/dist/replaceStep.js +3 -7
  17. package/dist/schema.d.ts +4 -2
  18. package/dist/schema.js +32 -39
  19. package/dist/testing/testBuilders.d.ts +2 -1
  20. package/dist/utils.d.ts +0 -1
  21. package/dist/utils.js +2 -6
  22. package/dist/withSuggestChanges.js +4 -0
  23. package/package.json +1 -1
  24. package/dist/contentBetween.d.ts +0 -2
  25. package/dist/contentBetween.js +0 -33
  26. package/dist/features/wrapUnwrap/__tests__/blockquoteUnwrapAllNodes.data.d.ts +0 -38
  27. package/dist/features/wrapUnwrap/__tests__/blockquoteUnwrapOneNode.data.d.ts +0 -45
  28. package/dist/features/wrapUnwrap/__tests__/blockquoteUnwrapOneNode.test.d.ts +0 -1
  29. package/dist/features/wrapUnwrap/__tests__/blockquoteUnwrapSingleNode.data.d.ts +0 -38
  30. package/dist/features/wrapUnwrap/__tests__/blockquoteUnwrapSingleNode.playwright.test.d.ts +0 -1
  31. package/dist/features/wrapUnwrap/__tests__/blockquoteUnwrapSingleNode.test.d.ts +0 -1
  32. package/dist/features/wrapUnwrap/__tests__/blockquoteWrapAllNodes.data.d.ts +0 -38
  33. package/dist/features/wrapUnwrap/__tests__/blockquoteWrapAllNodes.playwright.test.d.ts +0 -1
  34. package/dist/features/wrapUnwrap/__tests__/blockquoteWrapAllNodes.test.d.ts +0 -1
  35. package/dist/features/wrapUnwrap/__tests__/blockquoteWrapSingleNode.data.d.ts +0 -38
  36. package/dist/features/wrapUnwrap/__tests__/blockquoteWrapSingleNode.playwright.test.d.ts +0 -1
  37. package/dist/features/wrapUnwrap/__tests__/blockquoteWrapSingleNode.test.d.ts +0 -1
  38. package/dist/features/wrapUnwrap/__tests__/listItemLiftLast.data.d.ts +0 -54
  39. package/dist/features/wrapUnwrap/__tests__/listItemLiftLast.playwright.test.d.ts +0 -1
  40. package/dist/features/wrapUnwrap/__tests__/listItemLiftLast.test.d.ts +0 -1
  41. package/dist/features/wrapUnwrap/__tests__/listItemLiftMiddle.data.d.ts +0 -48
  42. package/dist/features/wrapUnwrap/__tests__/listItemLiftMiddle.playwright.test.d.ts +0 -1
  43. package/dist/features/wrapUnwrap/__tests__/listItemLiftMiddle.test.d.ts +0 -1
  44. package/dist/features/wrapUnwrap/__tests__/listItemLiftMultipleToTheTop.data.d.ts +0 -74
  45. package/dist/features/wrapUnwrap/__tests__/listItemLiftMultipleToTheTop.playwright.test.d.ts +0 -1
  46. package/dist/features/wrapUnwrap/__tests__/listItemLiftMultipleToTheTop.test.d.ts +0 -1
  47. package/dist/features/wrapUnwrap/__tests__/listItemLiftNestedOnce.data.d.ts +0 -71
  48. package/dist/features/wrapUnwrap/__tests__/listItemLiftNestedOnce.playwright.test.d.ts +0 -1
  49. package/dist/features/wrapUnwrap/__tests__/listItemLiftNestedOnce.test.d.ts +0 -1
  50. package/dist/features/wrapUnwrap/__tests__/listItemLiftTop.data.d.ts +0 -54
  51. package/dist/features/wrapUnwrap/__tests__/listItemLiftTop.playwright.test.d.ts +0 -1
  52. package/dist/features/wrapUnwrap/__tests__/listItemLiftTop.test.d.ts +0 -1
  53. package/dist/features/wrapUnwrap/__tests__/listItemSinkMultiple.data.d.ts +0 -221
  54. package/dist/features/wrapUnwrap/__tests__/listItemSinkMultiple.page.d.ts +0 -16
  55. package/dist/features/wrapUnwrap/__tests__/listItemSinkMultiple.playwright.test.d.ts +0 -1
  56. package/dist/features/wrapUnwrap/__tests__/listItemSinkMultiple.test.d.ts +0 -1
  57. package/dist/features/wrapUnwrap/__tests__/listItemSinkOneOnce.data.d.ts +0 -51
  58. package/dist/features/wrapUnwrap/__tests__/listItemSinkOneOnce.playwright.test.d.ts +0 -1
  59. package/dist/features/wrapUnwrap/__tests__/listItemSinkOneOnce.test.d.ts +0 -1
  60. package/dist/features/wrapUnwrap/__tests__/listItemsLiftMixedLevels.data.d.ts +0 -74
  61. package/dist/features/wrapUnwrap/__tests__/listItemsLiftMixedLevels.playwright.test.d.ts +0 -1
  62. package/dist/features/wrapUnwrap/__tests__/listItemsLiftMixedLevels.test.d.ts +0 -1
  63. package/dist/features/wrapUnwrap/__tests__/nestedListItemLiftToOuter.data.d.ts +0 -71
  64. package/dist/features/wrapUnwrap/__tests__/nestedListItemLiftToOuter.playwright.test.d.ts +0 -1
  65. package/dist/features/wrapUnwrap/__tests__/nestedListItemLiftToOuter.test.d.ts +0 -1
  66. package/dist/features/wrapUnwrap/__tests__/nestedListItemsLiftMultipleLevels.data.d.ts +0 -114
  67. package/dist/features/wrapUnwrap/__tests__/nestedListItemsLiftMultipleLevels.playwright.test.d.ts +0 -1
  68. package/dist/features/wrapUnwrap/__tests__/nestedListItemsLiftMultipleLevels.test.d.ts +0 -1
  69. package/dist/features/wrapUnwrap/__tests__/testUtils.d.ts +0 -5
  70. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftMultipleNodesToTheTop.data.d.ts +0 -44
  71. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftMultipleNodesToTheTop.playwright.test.d.ts +0 -1
  72. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftMultipleNodesToTheTop.test.d.ts +0 -1
  73. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftMultipleNodesUp.data.d.ts +0 -38
  74. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftMultipleNodesUp.playwright.test.d.ts +0 -1
  75. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftMultipleNodesUp.test.d.ts +0 -1
  76. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftOneNodeFromMiddle.data.d.ts +0 -46
  77. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftOneNodeFromMiddle.playwright.test.d.ts +0 -1
  78. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftOneNodeFromMiddle.test.d.ts +0 -1
  79. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftOneNodeToTheTop.data.d.ts +0 -57
  80. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftOneNodeToTheTop.playwright.test.d.ts +0 -1
  81. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftOneNodeToTheTop.test.d.ts +0 -1
  82. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftOneNodeUp.data.d.ts +0 -45
  83. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftOneNodeUp.playwright.test.d.ts +0 -1
  84. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteLiftOneNodeUp.test.d.ts +0 -1
  85. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteWrap.data.d.ts +0 -44
  86. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteWrap.playwright.test.d.ts +0 -1
  87. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteWrap.test.d.ts +0 -1
  88. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteWrapMultiple.data.d.ts +0 -44
  89. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteWrapMultiple.playwright.test.d.ts +0 -1
  90. package/dist/features/wrapUnwrap/__tests__/tripleBlockquoteWrapMultiple.test.d.ts +0 -1
  91. package/dist/features/wrapUnwrap/handleStructureStep.d.ts +0 -4
  92. package/dist/features/wrapUnwrap/handleStructureStep.js +0 -630
  93. package/dist/features/wrapUnwrap/revertStructureSuggestion.d.ts +0 -44
  94. package/dist/features/wrapUnwrap/revertStructureSuggestion.js +0 -368
  95. package/dist/rebaseStep.d.ts +0 -9
  96. package/dist/rebaseStep.js +0 -11
  97. package/src/features/wrapUnwrap/README.md +0 -198
  98. /package/dist/features/{wrapUnwrap/__tests__/blockquoteUnwrapAllNodes.playwright.test.d.ts → hiddenDeletions/__tests__/hiddenDeletionsBehavior.playwright.test.d.ts} +0 -0
  99. /package/dist/features/{wrapUnwrap/__tests__/blockquoteUnwrapAllNodes.test.d.ts → joinBlocks/__tests__/joinBlockBackspace.playwright.test.d.ts} +0 -0
  100. /package/dist/features/{wrapUnwrap/__tests__/blockquoteUnwrapOneNode.playwright.test.d.ts → joinBlocks/__tests__/joinBlockDelete.playwright.test.d.ts} +0 -0
@@ -1,368 +0,0 @@
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,
20
- // and then mapping the position, figuring out if the node is present in the new doc or was deleted
21
- const findStructureMark = (doc)=>{
22
- let structureMark = null;
23
- doc.nodesBetween(0, doc.content.size, (node)=>{
24
- structureMark = structureMark ?? structure.isInSet(node.marks) ?? null;
25
- return structureMark === null;
26
- });
27
- return structureMark;
28
- };
29
- let curDoc = doc;
30
- let structureMark = findStructureMark(doc);
31
- while(structureMark !== null){
32
- const suggestionId = structureMark.attrs["id"];
33
- performStructureRevert(suggestionId, tr);
34
- curDoc = tr.doc;
35
- structureMark = findStructureMark(curDoc);
36
- }
37
- }
38
- export function revertStructureSuggestion(suggestionId) {
39
- return (state, dispatch)=>{
40
- const tr = state.tr;
41
- performStructureRevert(suggestionId, tr);
42
- if (!tr.steps.length) return false;
43
- tr.setMeta(suggestChangesKey, {
44
- skip: true
45
- });
46
- dispatch?.(tr);
47
- return true;
48
- };
49
- }
50
- export function applyStructureSuggestion(suggestionId) {
51
- return (state, dispatch)=>{
52
- const tr = state.tr;
53
- performStructureRevert(suggestionId, tr, "apply");
54
- if (!tr.steps.length) return false;
55
- tr.setMeta(suggestChangesKey, {
56
- skip: true
57
- });
58
- dispatch?.(tr);
59
- return true;
60
- };
61
- }
62
- export function revertStructureSuggestions(suggestionIds) {
63
- return (state, dispatch)=>{
64
- const tr = state.tr;
65
- suggestionIds.forEach((suggestionId)=>{
66
- performStructureRevert(suggestionId, tr);
67
- });
68
- if (!tr.steps.length) return false;
69
- tr.setMeta(suggestChangesKey, {
70
- skip: true
71
- });
72
- dispatch?.(tr);
73
- return true;
74
- };
75
- }
76
- function performStructureRevert(suggestionId, tr, direction = "revert") {
77
- console.groupCollapsed(// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
78
- `performStructureRevert, suggestionId = ${suggestionId}`);
79
- const { structure } = getSuggestionMarks(tr.doc.type.schema);
80
- // find main suggestion from and to
81
- const { markFrom, markTo, markGapFrom, markGapTo } = findStructureMarkGroupBySuggestionId(suggestionId, tr);
82
- const from = getPosFromMark(markFrom.mark, markFrom.pos, markFrom.node);
83
- const to = getPosFromMark(markTo.mark, markTo.pos, markTo.node);
84
- if (from == null || to == null) {
85
- throw new Error(`Could not find all positions for suggestion`);
86
- }
87
- // find all other structure suggestions within from and to interval of the main suggestion
88
- const structureMarkGroups = new Set();
89
- structureMarkGroups.add(suggestionId);
90
- if (markGapFrom != null) {
91
- const gapFrom = getPosFromMark(markGapFrom.mark, markGapFrom.pos, markGapFrom.node);
92
- const gapTo = getPosFromMark(markGapTo.mark, markGapTo.pos, markGapTo.node);
93
- if (gapFrom == null || gapTo == null) {
94
- throw new Error(`Could not find all positions for suggestion`);
95
- }
96
- tr.doc.nodesBetween(from, to, (node, pos)=>{
97
- node.marks.forEach((mark)=>{
98
- if (mark.type !== structure) return;
99
- const markData = mark.attrs["data"];
100
- if (!markData) return;
101
- const id = mark.attrs["id"];
102
- if (id === suggestionId) return;
103
- const startsInGap = gapFrom <= pos && pos <= gapTo;
104
- const endsInGap = gapFrom <= pos + node.nodeSize && pos + node.nodeSize <= gapTo;
105
- if (startsInGap && endsInGap) {
106
- console.log("ignored mark", {
107
- node,
108
- pos,
109
- end: pos + node.nodeSize,
110
- mark
111
- }, " - starts and ends in gap", "from-gapFrom-gapTo-to", {
112
- from,
113
- gapFrom,
114
- gapTo,
115
- to
116
- });
117
- return;
118
- }
119
- const startsInRange = from <= pos && pos <= gapFrom || gapTo <= pos && pos <= to;
120
- const endsInRange = from <= pos + node.nodeSize && pos + node.nodeSize <= gapFrom || gapTo <= pos + node.nodeSize && pos + node.nodeSize <= to;
121
- if (!startsInRange && !endsInRange) {
122
- console.log("ignored mark", {
123
- node,
124
- pos,
125
- end: pos + node.nodeSize,
126
- mark
127
- }, " - does not start or end in range", "from-gapFrom-gapTo-to", {
128
- from,
129
- gapFrom,
130
- gapTo,
131
- to
132
- });
133
- return;
134
- }
135
- console.log("found nested structure mark", {
136
- id,
137
- node,
138
- pos,
139
- nodeStart: pos,
140
- nodeEnd: pos + node.nodeSize
141
- }, "from-gapFrom-gapTo-to", {
142
- from,
143
- gapFrom,
144
- gapTo,
145
- to
146
- });
147
- structureMarkGroups.add(id);
148
- });
149
- });
150
- } else {
151
- tr.doc.nodesBetween(from, to, (node, pos)=>{
152
- node.marks.forEach((mark)=>{
153
- if (mark.type !== structure) return;
154
- const markData = mark.attrs["data"];
155
- if (!markData) return;
156
- const id = mark.attrs["id"];
157
- if (id === suggestionId) return;
158
- const startsInRange = from <= pos && pos <= to;
159
- const endsInRange = from <= pos + node.nodeSize && pos + node.nodeSize <= to;
160
- if (!startsInRange && !endsInRange) {
161
- console.log("ignored mark", {
162
- node,
163
- pos,
164
- end: pos + node.nodeSize,
165
- mark
166
- }, " - does not start or end in range", "from-to", {
167
- from,
168
- to
169
- });
170
- return;
171
- }
172
- console.log("found nested structure mark", {
173
- id,
174
- node,
175
- pos,
176
- nodeStart: pos,
177
- nodeEnd: pos + node.nodeSize
178
- }, "from-to", {
179
- from,
180
- to
181
- });
182
- structureMarkGroups.add(id);
183
- });
184
- return true;
185
- });
186
- }
187
- // revert structure mark groups in decreasing order of their ids
188
- const markIds = Array.from(structureMarkGroups.values()).sort((a, b)=>Number(b) - Number(a));
189
- markIds.forEach((id)=>{
190
- const group = findStructureMarkGroupBySuggestionId(id, tr);
191
- if (direction === "apply") {
192
- applyStructureMarkGroup(group, tr);
193
- } else {
194
- revertStructureMarkGroup(group, tr);
195
- }
196
- });
197
- console.groupEnd();
198
- }
199
- function getPosFromMark(mark, pos, node) {
200
- const markData = mark.attrs["data"];
201
- if (!markData) return null;
202
- if (markData.position === "start") {
203
- return pos;
204
- }
205
- if (markData.position === "end") {
206
- return pos + node.nodeSize;
207
- }
208
- if (markData.position === "innerStart") {
209
- return pos + 1;
210
- }
211
- if (markData.position === "innerEnd") {
212
- return pos + node.nodeSize - 1;
213
- }
214
- return null;
215
- }
216
- export function applyStructureMarkGroup(group, tr) {
217
- console.groupCollapsed("apply structure group, id = ", group.markFrom.mark.attrs["id"]);
218
- console.log({
219
- group
220
- });
221
- if (group.type === "replace") {
222
- const { markFrom, markTo } = group;
223
- tr.removeNodeMark(markFrom.pos, markFrom.mark);
224
- tr.removeNodeMark(markTo.pos, markTo.mark);
225
- console.groupEnd();
226
- return;
227
- }
228
- const { markFrom, markTo, markGapFrom, markGapTo } = group;
229
- tr.removeNodeMark(markFrom.pos, markFrom.mark);
230
- tr.removeNodeMark(markTo.pos, markTo.mark);
231
- tr.removeNodeMark(markGapFrom.pos, markGapFrom.mark);
232
- tr.removeNodeMark(markGapTo.pos, markGapTo.mark);
233
- console.groupEnd();
234
- }
235
- function revertStructureMarkGroup(group, tr) {
236
- console.groupCollapsed("revert structure group, id = ", group.markFrom.mark.attrs["id"]);
237
- console.log({
238
- group
239
- });
240
- if (group.type === "replace") {
241
- const { markFrom, markTo } = group;
242
- // extract positions from, to, gapFrom and gapTo from marks
243
- // the positions are either the mark's start, or the mark's end
244
- const from = getPosFromMark(markFrom.mark, markFrom.pos, markFrom.node);
245
- const to = getPosFromMark(markTo.mark, markTo.pos, markTo.node);
246
- if (from === null || to === null) {
247
- throw new Error(`Could not find all positions for suggestion`);
248
- }
249
- // extract the rest of the data required to reconstruct the step: slice, and structure
250
- // any of those 2 marks can be used for that, this data is identical in all of them
251
- const mark = markFrom.mark;
252
- const markData = mark.attrs["data"];
253
- if (!markData) {
254
- throw new Error(`Missing mark data for suggestion`);
255
- }
256
- const slice = markData.slice ? Slice.fromJSON(tr.doc.type.schema, markData.slice) : Slice.empty;
257
- const isStepStructural = markData.structure ?? false;
258
- // reconstruct the step
259
- // this is the inverse step of the step that created this change
260
- const step = new ReplaceStep(from, to, slice, isStepStructural);
261
- console.log({
262
- step
263
- });
264
- tr.removeNodeMark(markFrom.pos, markFrom.mark);
265
- tr.removeNodeMark(markTo.pos, markTo.mark);
266
- tr.step(step);
267
- console.groupEnd();
268
- return;
269
- }
270
- const { markFrom, markTo, markGapFrom, markGapTo } = group;
271
- // extract positions from, to, gapFrom and gapTo from marks
272
- // the positions are either the mark's start, or the mark's end
273
- const from = getPosFromMark(markFrom.mark, markFrom.pos, markFrom.node);
274
- const to = getPosFromMark(markTo.mark, markTo.pos, markTo.node);
275
- const gapFrom = getPosFromMark(markGapFrom.mark, markGapFrom.pos, markGapFrom.node);
276
- const gapTo = getPosFromMark(markGapTo.mark, markGapTo.pos, markGapTo.node);
277
- if (from === null || to === null || gapFrom === null || gapTo === null) {
278
- throw new Error(`Could not find all positions for suggestion`);
279
- }
280
- // extract the rest of the data required to reconstruct the step: insert, slice, and structure
281
- // any of those 4 marks can be used for that, this data is identical in all of them
282
- const mark = markGapFrom.mark;
283
- const markData = mark.attrs["data"];
284
- if (markData?.insert == null) {
285
- throw new Error(`Missing insert for suggestion`);
286
- }
287
- const slice = markData.slice ? Slice.fromJSON(tr.doc.type.schema, markData.slice) : Slice.empty;
288
- const insert = markData.insert;
289
- const isStepStructural = markData.structure ?? false;
290
- // reconstruct the step
291
- // this is the inverse step of the step that created this change
292
- const step = new ReplaceAroundStep(from, to, gapFrom, gapTo, slice, insert, isStepStructural);
293
- console.log({
294
- step
295
- });
296
- tr.removeNodeMark(markFrom.pos, markFrom.mark);
297
- tr.removeNodeMark(markTo.pos, markTo.mark);
298
- tr.removeNodeMark(markGapFrom.pos, markGapFrom.mark);
299
- tr.removeNodeMark(markGapTo.pos, markGapTo.mark);
300
- tr.step(step);
301
- console.groupEnd();
302
- }
303
- function findStructureMarkGroupBySuggestionId(suggestionId, tr) {
304
- const { structure } = getSuggestionMarks(tr.doc.type.schema);
305
- let markFrom = null;
306
- let markTo = null;
307
- let markGapFrom = null;
308
- let markGapTo = null;
309
- // using suggestionId, find 4 marks: from, to, gapFrom and gapTo
310
- tr.doc.nodesBetween(0, tr.doc.content.size, (node, pos)=>{
311
- node.marks.forEach((mark)=>{
312
- if (mark.type !== structure) return;
313
- if (mark.attrs["id"] !== suggestionId) return;
314
- const markData = mark.attrs["data"];
315
- if (!markData) return;
316
- if (markData.value === "from") {
317
- markFrom = {
318
- pos,
319
- node,
320
- mark
321
- };
322
- }
323
- if (markData.value === "to") {
324
- markTo = {
325
- pos,
326
- node,
327
- mark
328
- };
329
- }
330
- if (markData.value === "gapFrom") {
331
- markGapFrom = {
332
- pos,
333
- node,
334
- mark
335
- };
336
- }
337
- if (markData.value === "gapTo") {
338
- markGapTo = {
339
- pos,
340
- node,
341
- mark
342
- };
343
- }
344
- });
345
- return markFrom == null || markTo == null || markGapFrom == null || markGapTo == null;
346
- });
347
- if (markFrom == null || markTo == null) {
348
- throw new Error(`Could not find all marks for suggestion id ${suggestionId}`);
349
- }
350
- const type = markFrom.mark.attrs["data"]?.type;
351
- if (type === "replace") {
352
- return {
353
- type: "replace",
354
- markFrom,
355
- markTo
356
- };
357
- }
358
- if (markGapFrom == null || markGapTo == null) {
359
- throw new Error(`Could not find all gap marks for replace around suggestion id ${suggestionId}`);
360
- }
361
- return {
362
- type: "replaceAround",
363
- markFrom,
364
- markTo,
365
- markGapFrom,
366
- markGapTo
367
- };
368
- }
@@ -1,9 +0,0 @@
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;
@@ -1,11 +0,0 @@
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
- }
@@ -1,198 +0,0 @@
1
- # Structure Suggestion Tracking & Reversal
2
-
3
- ## Purpose
4
-
5
- Track structural changes (wrap/unwrap in blockquotes, list item lift/sink) in
6
- ProseMirror as "suggestions" that can be reverted later, without losing the
7
- ability to reconstruct the original document state.
8
-
9
- ---
10
-
11
- ## Why Marks on Nodes?
12
-
13
- **The Problem**: Document positions are ephemeral. A position like `from: 10`
14
- becomes invalid after any edit before it.
15
-
16
- **The Solution**: Anchor positions to **actual nodes** in the document using
17
- marks. To recover a position:
18
-
19
- 1. Find the marked node (which moves with document edits)
20
- 2. Compute position from node's current `pos` + the `position` attribute:
21
- - `position: "start"` → use `pos`
22
- - `position: "end"` → use `pos + node.nodeSize`
23
-
24
- This way, positions are **self-healing** — they remain valid as long as the
25
- marked nodes exist.
26
-
27
- **Why "start" vs "end"?** Because the 4 positions of a `ReplaceAroundStep` don't
28
- always align with node boundaries in the same way. The mark must be placed on a
29
- node that **exists in the final document**, and the position must be derivable
30
- from that node's boundaries.
31
-
32
- ---
33
-
34
- ## Mark Structure
35
-
36
- ### For `ReplaceAroundStep` (4 marks)
37
-
38
- Each mark contains:
39
-
40
- | Field | Purpose |
41
- | ----------- | ------------------------------------------------------- |
42
- | `id` | Suggestion ID (groups marks together) |
43
- | `value` | `"from"` \| `"to"` \| `"gapFrom"` \| `"gapTo"` |
44
- | `position` | `"start"` \| `"end"` (how to derive position from node) |
45
- | `type` | `"replaceAround"` |
46
- | `slice` | JSON of the slice for the **inverse** step |
47
- | `insert` | Where gap content goes in the slice |
48
- | `structure` | Boolean flag for structural step |
49
-
50
- ### For `ReplaceStep` (2 marks)
51
-
52
- Only `from` and `to` marks are needed (no gap positions).
53
-
54
- | Field | Purpose |
55
- | ----------- | ------------------------------------------ |
56
- | `id` | Suggestion ID |
57
- | `value` | `"from"` \| `"to"` |
58
- | `position` | `"start"` \| `"end"` |
59
- | `type` | `"replace"` |
60
- | `slice` | JSON of the slice for the **inverse** step |
61
- | `structure` | Boolean flag |
62
-
63
- ### Mark Nesting
64
-
65
- Marks are nested around content in the document:
66
-
67
- ```javascript
68
- doc(
69
- structure[from, position:start](
70
- structure[to, position:end](
71
- blockquote(
72
- structure[gapFrom, position:start](
73
- structure[gapTo, position:end](
74
- paragraph("Hello World")
75
- ))))))
76
- ```
77
-
78
- When traversing with `nodesBetween`, we find each marked node and compute
79
- positions from it.
80
-
81
- ---
82
-
83
- ## How Reversal Works
84
-
85
- The `revertStructureSuggestion(suggestionId)` command:
86
-
87
- ```
88
- 1. FIND the main suggestion's marks
89
- └── findStructureMarkGroupBySuggestionId(suggestionId)
90
- └── Scan document for marks with matching ID
91
- └── Collect: markFrom, markTo, [markGapFrom, markGapTo]
92
-
93
- 2. FIND nested suggestions within the range
94
- └── nodesBetween(from, to) to find all structure marks
95
- └── Collect all unique suggestion IDs
96
-
97
- 3. SORT by decreasing ID (revert newest first)
98
- └── [3, 2, 1] — important for nested changes!
99
-
100
- 4. For EACH suggestion, REVERT:
101
- └── Compute positions: getPosFromMark(mark, pos, node)
102
- └── Deserialize slice from JSON
103
- └── Reconstruct inverse step:
104
- - ReplaceAroundStep(from, to, gapFrom, gapTo, slice, insert, structure)
105
- - or ReplaceStep(from, to, slice, structure)
106
- └── Remove the marks
107
- └── Apply the step
108
- ```
109
-
110
- **Why reverse order?** If step 1 created structure A, and step 2 modified within
111
- A, you must undo step 2 before step 1, otherwise positions break.
112
-
113
- ---
114
-
115
- ## How Tests Work
116
-
117
- Each test file has 3 data structures:
118
-
119
- ```typescript
120
- // 1. Initial document state (before any changes)
121
- const initialState = doc(paragraph("Hello World"));
122
-
123
- // 2. Final state (after applying steps, NO marks)
124
- const finalState = doc(blockquote(paragraph("Hello World")));
125
-
126
- // 3. Final state WITH structure marks embedded
127
- // (manually constructed to simulate what forward tracking would produce)
128
- const finalStateWithMarks = doc(
129
- structure({ id: 1, data: { value: "from", position: "start", ... } },
130
- structure({ id: 1, data: { value: "to", position: "end", ... } },
131
- blockquote(
132
- structure({ id: 1, data: { value: "gapFrom", ... } },
133
- structure({ id: 1, data: { value: "gapTo", ... } },
134
- paragraph("Hello World")
135
- )))))
136
- );
137
-
138
- // The steps that transform initial → final
139
- const steps = [{ stepType: "replaceAround", from: 0, to: 13, ... }];
140
-
141
- // The inverse steps that transform final → initial
142
- const inverseSteps = [{ stepType: "replaceAround", from: 0, to: 15, ... }];
143
- ```
144
-
145
- Three test cases per scenario:
146
-
147
- ```typescript
148
- // Test 1: Forward transformation works
149
- it("applies steps correctly", () => {
150
- assertDocumentChanged(initialState, finalState, applySteps(steps));
151
- });
152
-
153
- // Test 2: Inverse steps work (sanity check)
154
- it("applies inverse steps correctly", () => {
155
- assertDocumentChanged(
156
- finalState,
157
- initialState,
158
- applySteps(inverseSteps.reverse()),
159
- );
160
- });
161
-
162
- // Test 3: THE MAIN TEST - revert from marks works
163
- it("reverts via structure suggestion", () => {
164
- assertDocumentChanged(
165
- finalStateWithMarks,
166
- initialState,
167
- revertStructureSuggestion(1),
168
- );
169
- });
170
- ```
171
-
172
- ---
173
-
174
- ## What's Missing: Forward Tracking
175
-
176
- The code that would:
177
-
178
- 1. Intercept `ReplaceAroundStep` / `ReplaceStep` in suggest-changes mode
179
- 2. Apply the step to get the new document
180
- 3. Compute the inverse step
181
- 4. Place structure marks on appropriate nodes with:
182
- - Position encoding (which node, start vs end)
183
- - Inverse step data (slice, insert, structure flag)
184
-
185
- Currently `suggestReplaceAroundStep` in `replaceAroundStep.ts` converts
186
- structural operations to `ReplaceStep` and uses insertion/deletion marks
187
- instead, losing the structural information needed for proper reversal.
188
-
189
- ---
190
-
191
- ## Additional Notes
192
-
193
- ### Offset Fields
194
-
195
- The marks contain offset fields (`gapFromOffset`, `fromOffset`, `toOffset`,
196
- `gapToOffset`) and a `debug` object with computed inverse positions. These are
197
- present in test data but **not currently used** by the revert code — positions
198
- are computed purely from node position + `position` field.