@magic-marker/prosemirror-suggest-changes 0.4.0 → 0.4.1-wrap-unwrap.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 (89) hide show
  1. package/dist/__tests__/playwrightHelpers.d.ts +2 -2
  2. package/dist/__tests__/playwrightPage.d.ts +50 -2
  3. package/dist/commands.js +222 -43
  4. package/dist/ensureSelectionPlugin.js +3 -3
  5. package/dist/features/joinOnDelete/__tests__/joinOnDeleteInLists.playwright.test.d.ts +1 -0
  6. package/dist/features/joinOnDelete/__tests__/joinOnDeleteInListsTipTapStyle.playwright.test.d.ts +1 -0
  7. package/dist/features/joinOnDelete/__tests__/listWithJoinsAndStructureMarks.playwright.test.d.ts +1 -0
  8. package/dist/features/joinOnDelete/index.d.ts +4 -19
  9. package/dist/features/joinOnDelete/index.js +166 -53
  10. package/dist/features/joinOnDelete/normalizeJoinNodesMetadata.d.ts +6 -0
  11. package/dist/features/joinOnDelete/normalizeJoinNodesMetadata.js +24 -0
  12. package/dist/features/joinOnDelete/types.d.ts +36 -0
  13. package/dist/features/joinOnDelete/types.js +23 -0
  14. package/dist/features/transactionShaping/detectSpecialTransactionShape.d.ts +3 -0
  15. package/dist/features/transactionShaping/detectSpecialTransactionShape.js +4 -0
  16. package/dist/features/transactionShaping/index.d.ts +3 -0
  17. package/dist/features/transactionShaping/index.js +11 -0
  18. package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/detectTipTapParagraphIntoListJoin.d.ts +3 -0
  19. package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/detectTipTapParagraphIntoListJoin.js +48 -0
  20. package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/detectTipTapParagraphIntoListJoin.test.d.ts +1 -0
  21. package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/detectTipTapParagraphIntoListJoin.test.js +188 -0
  22. package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/handleTipTapParagraphIntoListJoin.d.ts +3 -0
  23. package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/handleTipTapParagraphIntoListJoin.js +69 -0
  24. package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/index.d.ts +2 -0
  25. package/dist/features/transactionShaping/tipTapParagraphIntoListJoin/index.js +2 -0
  26. package/dist/features/transactionShaping/types.d.ts +20 -0
  27. package/dist/features/transactionShaping/types.js +1 -0
  28. package/dist/features/wrapUnwrap/__tests__/blockquoteStructure.playwright.test.d.ts +1 -0
  29. package/dist/features/wrapUnwrap/__tests__/buildMaterializedPaths.test.d.ts +1 -0
  30. package/dist/features/wrapUnwrap/__tests__/listStructure.playwright.test.d.ts +1 -0
  31. package/dist/features/wrapUnwrap/__tests__/listStructureTextEdits.playwright.test.d.ts +1 -0
  32. package/dist/features/wrapUnwrap/__tests__/splitDetection.test.d.ts +1 -0
  33. package/dist/features/wrapUnwrap/addIdAttr.d.ts +2 -0
  34. package/dist/features/wrapUnwrap/addIdAttr.js +60 -0
  35. package/dist/features/wrapUnwrap/apply/applyStructureSuggestions.d.ts +10 -0
  36. package/dist/features/wrapUnwrap/apply/applyStructureSuggestions.js +41 -0
  37. package/dist/features/wrapUnwrap/apply/index.d.ts +5 -0
  38. package/dist/features/wrapUnwrap/apply/index.js +21 -0
  39. package/dist/features/wrapUnwrap/areEquivalentStructureMarks.d.ts +2 -0
  40. package/dist/features/wrapUnwrap/areEquivalentStructureMarks.js +17 -0
  41. package/dist/features/wrapUnwrap/buildMaterializedPaths.d.ts +3 -0
  42. package/dist/features/wrapUnwrap/buildMaterializedPaths.js +82 -0
  43. package/dist/features/wrapUnwrap/constants.d.ts +3 -0
  44. package/dist/features/wrapUnwrap/constants.js +3 -0
  45. package/dist/features/wrapUnwrap/generateUniqueNodeId.d.ts +1 -0
  46. package/dist/features/wrapUnwrap/generateUniqueNodeId.js +6 -0
  47. package/dist/features/wrapUnwrap/getNodeId.d.ts +2 -0
  48. package/dist/features/wrapUnwrap/getNodeId.js +5 -0
  49. package/dist/features/wrapUnwrap/revert/deleteNodeUpwards.d.ts +3 -0
  50. package/dist/features/wrapUnwrap/revert/deleteNodeUpwards.js +37 -0
  51. package/dist/features/wrapUnwrap/revert/index.d.ts +5 -0
  52. package/dist/features/wrapUnwrap/revert/index.js +19 -0
  53. package/dist/features/wrapUnwrap/revert/revertAddOp.d.ts +4 -0
  54. package/dist/features/wrapUnwrap/revert/revertAddOp.js +4 -0
  55. package/dist/features/wrapUnwrap/revert/revertMoveOp.d.ts +16 -0
  56. package/dist/features/wrapUnwrap/revert/revertMoveOp.js +122 -0
  57. package/dist/features/wrapUnwrap/revert/revertStructureSuggestions.d.ts +10 -0
  58. package/dist/features/wrapUnwrap/revert/revertStructureSuggestions.js +236 -0
  59. package/dist/features/wrapUnwrap/sameParentChain.d.ts +2 -0
  60. package/dist/features/wrapUnwrap/sameParentChain.js +4 -0
  61. package/dist/features/wrapUnwrap/structureChangesPlugin.d.ts +17 -0
  62. package/dist/features/wrapUnwrap/structureChangesPlugin.js +299 -0
  63. package/dist/features/wrapUnwrap/types.d.ts +54 -0
  64. package/dist/features/wrapUnwrap/types.js +23 -0
  65. package/dist/features/wrapUnwrap/uniqueNodeIdsPlugin.d.ts +17 -0
  66. package/dist/features/wrapUnwrap/uniqueNodeIdsPlugin.js +106 -0
  67. package/dist/generateId.js +31 -4
  68. package/dist/index.d.ts +5 -2
  69. package/dist/index.js +4 -2
  70. package/dist/listInputRules.d.ts +2 -0
  71. package/dist/listInputRules.js +14 -0
  72. package/dist/rebaseStep.d.ts +9 -0
  73. package/dist/rebaseStep.js +11 -0
  74. package/dist/replaceStep.d.ts +1 -1
  75. package/dist/replaceStep.js +1 -0
  76. package/dist/schema.d.ts +2 -1
  77. package/dist/schema.js +37 -1
  78. package/dist/testing/e2eTestSchema.d.ts +2 -0
  79. package/dist/testing/testBuilders.d.ts +1 -2
  80. package/dist/transformToSuggestionTransaction.d.ts +22 -0
  81. package/dist/transformToSuggestionTransaction.js +101 -0
  82. package/dist/utils.d.ts +1 -0
  83. package/dist/utils.js +6 -2
  84. package/dist/withSuggestChanges.d.ts +11 -14
  85. package/dist/withSuggestChanges.js +64 -94
  86. package/dist/wrappingInputRule.d.ts +4 -0
  87. package/dist/wrappingInputRule.js +28 -0
  88. package/package.json +1 -1
  89. package/src/features/joinOnDelete/README.md +0 -8
@@ -140,9 +140,9 @@ export declare function assertReverted(finalState: EditorState, initialState: Ed
140
140
  * This helper replaces the editor document with the provided JSON,
141
141
  * clears transactions, and returns initial state.
142
142
  */
143
- export declare function setupDocFromJSON(page: Page, docJSON: unknown): Promise<{
143
+ export declare function setupDocFromJSON(page: Page, docJSON: object): Promise<{
144
144
  initialState: EditorState;
145
- initialDoc: unknown;
145
+ initialDoc: object;
146
146
  }>;
147
147
  /**
148
148
  * Assert that document fully reverted to initial state.
@@ -1,15 +1,63 @@
1
+ import { type Node } from "prosemirror-model";
1
2
  import { type Locator, type Page } from "@playwright/test";
2
3
  export declare class EditorPage {
3
4
  readonly page: Page;
5
+ readonly deletionMarksVisibility: "hidden" | "visible";
4
6
  private readonly selectors;
5
- constructor(page: Page);
7
+ constructor(page: Page, deletionMarksVisibility?: "hidden" | "visible");
6
8
  get editor(): Locator;
7
- getParagraphText(index: number): Promise<string>;
9
+ getParagraphText(index: number, childIndexes?: number[]): Promise<string>;
10
+ getParagraphAt(index: number): Locator;
8
11
  getParagraphCount(): Promise<number>;
12
+ getListItems(): Locator;
13
+ getParagraphs(): Locator;
14
+ getListItemCount(): Promise<number>;
9
15
  getProseMirrorMarkCount(name: string): Promise<number>;
16
+ getProseMirrorMarksJSON(): Promise<unknown[]>;
10
17
  getProseMirrorSelection(): Promise<{
11
18
  anchor: number;
12
19
  head: number;
13
20
  }>;
14
21
  getDOMTextContentOfChildAtIndex(index: number): Promise<string>;
22
+ getDocJSON(): Promise<object>;
23
+ getCurrentDoc(): Promise<Node>;
24
+ getCurrentAndExpectedDoc(expectedDocJSON: object): Promise<{
25
+ currentDoc: Node;
26
+ expectedDoc: Node;
27
+ }>;
28
+ revertSuggestion(suggestionId: number): Promise<void>;
29
+ revertAll(): Promise<void>;
30
+ applyAll(): Promise<void>;
31
+ /**
32
+ * Perform an action and wait for ProseMirror state to update.
33
+ * Stores the current state reference before pressing, then polls until
34
+ * editor.view.state is a different object (ProseMirror creates a new
35
+ * immutable state on every transaction).
36
+ */
37
+ private doActionAndWaitForState;
38
+ /**
39
+ * Press a key and wait for ProseMirror state to update.
40
+ * Stores the current state reference before pressing, then polls until
41
+ * editor.view.state is a different object (ProseMirror creates a new
42
+ * immutable state on every transaction).
43
+ */
44
+ pressKey(key: string, opts?: {
45
+ waitForSelectionChange?: boolean;
46
+ }): Promise<void>;
47
+ /**
48
+ * Insert text and wait for ProseMirror state to update.
49
+ * Stores the current state reference before pressing, then polls until
50
+ * editor.view.state is a different object (ProseMirror creates a new
51
+ * immutable state on every transaction).
52
+ */
53
+ insertText(text: string, opts?: {
54
+ waitForSelectionChange?: boolean;
55
+ }): Promise<void>;
56
+ /**
57
+ * Press a key multiple times, waiting for editor state update after each press.
58
+ */
59
+ pressKeyMultiple(key: string, count: number, opts?: {
60
+ waitForSelectionChange?: boolean;
61
+ }): Promise<void>;
62
+ setNextNodeId(nextNodeId: number): Promise<void>;
15
63
  }
package/dist/commands.js CHANGED
@@ -5,6 +5,9 @@ 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 { isJoinMark } from "./features/joinOnDelete/types.js";
9
+ import { revertAllStructureSuggestions, revertStructureSuggestion } from "./features/wrapUnwrap/revert/index.js";
10
+ import { applyAllStructureSuggestions, applyStructureSuggestion } from "./features/wrapUnwrap/apply/index.js";
8
11
  /**
9
12
  * Given a node and a transform, add a set of steps to the
10
13
  * transform that applies all marks of type markTypeToApply
@@ -29,6 +32,7 @@ import { maybeRevertJoinMark } from "./features/joinOnDelete/index.js";
29
32
  tr.removeNodeMark(0, markTypeToApply);
30
33
  }
31
34
  }
35
+ const restoredStructureSuggestionIds = new Set();
32
36
  node.descendants((child, pos)=>{
33
37
  if (from !== undefined && pos < from) {
34
38
  return true;
@@ -60,8 +64,12 @@ import { maybeRevertJoinMark } from "./features/joinOnDelete/index.js";
60
64
  const insertionTo = insertionFrom + child.nodeSize;
61
65
  if (child.isInline) {
62
66
  tr.removeMark(insertionFrom, insertionTo, markTypeToApply);
63
- const reverted = maybeRevertJoinMark(tr, insertionFrom, insertionTo, child, markTypeToApply);
64
- if (!reverted && child.text === ZWSP) {
67
+ const joinRevertResult = maybeRevertJoinMark(tr, insertionFrom, insertionTo, child, markTypeToApply);
68
+ // reverting a join mark may produce new structure marks that were serialized in the join metadata
69
+ if (joinRevertResult) {
70
+ joinRevertResult.restoredStructureSuggestionIds.forEach((id)=>restoredStructureSuggestionIds.add(id));
71
+ }
72
+ if (!joinRevertResult && child.text === ZWSP) {
65
73
  tr.delete(insertionFrom, insertionTo);
66
74
  }
67
75
  } else {
@@ -69,6 +77,50 @@ import { maybeRevertJoinMark } from "./features/joinOnDelete/index.js";
69
77
  }
70
78
  return true;
71
79
  });
80
+ return {
81
+ restoredStructureSuggestionIds
82
+ };
83
+ }
84
+ /**
85
+ * Collect suggestion IDs of join marks in the node
86
+ *
87
+ * @param node
88
+ * @param deletion
89
+ * @returns an array of suggestion IDs
90
+ */ function findJoinSuggestionIds(node, deletion) {
91
+ const joinSuggestionIds = [];
92
+ node.descendants((child)=>{
93
+ const mark = deletion.isInSet(child.marks);
94
+ if (!mark || !isJoinMark(mark)) return true;
95
+ if (!child.isText || child.text !== ZWSP) return true;
96
+ joinSuggestionIds.push(mark.attrs.id);
97
+ return true;
98
+ });
99
+ return joinSuggestionIds.reverse();
100
+ }
101
+ /**
102
+ * Revert suggestions revealed by reverting a join mark
103
+ * Prioritize reverting revealed suggestions with the same id as the join mark
104
+ * (that means they are linked to the join mark and has to be reverted together as one)
105
+ * Revert other revealed suggestions as well so the user doesn't have to revert multiple times at the same place
106
+ *
107
+ * @param tr
108
+ * @param suggestionId
109
+ * @param restoredStructureSuggestionIds
110
+ */ function revertRestoredStructureSuggestions(tr, suggestionId, restoredStructureSuggestionIds) {
111
+ if (restoredStructureSuggestionIds.has(suggestionId)) {
112
+ const restoredStructureTransform = revertStructureSuggestion(tr.doc, suggestionId);
113
+ restoredStructureTransform.steps.forEach((step)=>{
114
+ tr.step(step);
115
+ });
116
+ }
117
+ restoredStructureSuggestionIds.forEach((restoredSuggestionId)=>{
118
+ if (restoredSuggestionId === suggestionId) return;
119
+ const restoredStructureTransform = revertStructureSuggestion(tr.doc, restoredSuggestionId);
120
+ restoredStructureTransform.steps.forEach((step)=>{
121
+ tr.step(step);
122
+ });
123
+ });
72
124
  }
73
125
  function revertModifications(node, pos, tr) {
74
126
  const { modification } = getSuggestionMarks(node.type.schema);
@@ -134,20 +186,42 @@ function applyModificationsToTransform(node, tr, dir, suggestionId, from, to) {
134
186
  }
135
187
  export function applySuggestionsToNode(node) {
136
188
  const { deletion, insertion } = getSuggestionMarks(node.type.schema);
137
- const transform = new Transform(node);
138
- applySuggestionsToTransform(node, transform, insertion, deletion);
139
- applyModificationsToTransform(node, transform, 1);
140
- return transform.doc;
189
+ // first, create a structure transform that applies all structure changes on the given node
190
+ const structureTransform = applyAllStructureSuggestions(node);
191
+ // then start a clear transform from the document where the structure changes are applied
192
+ const suggestionsTransform = new Transform(structureTransform.doc);
193
+ applySuggestionsToTransform(suggestionsTransform.doc, suggestionsTransform, insertion, deletion);
194
+ applyModificationsToTransform(suggestionsTransform.doc, suggestionsTransform, 1);
195
+ // replay suggestion transform on top of the structure transform
196
+ suggestionsTransform.steps.forEach((step)=>{
197
+ structureTransform.step(step);
198
+ });
199
+ const secondStructureTransform = applyAllStructureSuggestions(structureTransform.doc);
200
+ secondStructureTransform.steps.forEach((step)=>{
201
+ structureTransform.step(step);
202
+ });
203
+ return structureTransform.doc;
141
204
  }
142
205
  export function applySuggestionsToRange(doc, from, to) {
206
+ const { deletion, insertion } = getSuggestionMarks(doc.type.schema);
143
207
  // blockRange can only return null if a predicate is provided
144
208
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
145
209
  const nodeRange = doc.resolve(from).blockRange(doc.resolve(to));
146
- const { deletion, insertion } = getSuggestionMarks(doc.type.schema);
147
- const transform = new Transform(doc);
148
- applySuggestionsToTransform(doc, transform, insertion, deletion, undefined, nodeRange.start, nodeRange.end);
149
- applyModificationsToTransform(doc, transform, 1, undefined, nodeRange.start, nodeRange.end);
150
- return transform.doc.slice(transform.mapping.map(from), transform.mapping.map(to));
210
+ // create a structure transform that applies all structure changes on the given node range
211
+ const structureTransform = applyAllStructureSuggestions(doc, nodeRange.start, nodeRange.end);
212
+ // then start a clear transform from the document where the structure changes are applied
213
+ const suggestionsTransform = new Transform(structureTransform.doc);
214
+ applySuggestionsToTransform(suggestionsTransform.doc, suggestionsTransform, insertion, deletion, undefined, nodeRange.start, nodeRange.end);
215
+ applyModificationsToTransform(suggestionsTransform.doc, suggestionsTransform, 1, undefined, nodeRange.start, nodeRange.end);
216
+ // replay suggestion transform on top of the structure transform
217
+ suggestionsTransform.steps.forEach((step)=>{
218
+ structureTransform.step(step);
219
+ });
220
+ const secondStructureTransform = applyAllStructureSuggestions(structureTransform.doc, structureTransform.mapping.map(from), structureTransform.mapping.map(to));
221
+ secondStructureTransform.steps.forEach((step)=>{
222
+ structureTransform.step(step);
223
+ });
224
+ return structureTransform.doc.slice(structureTransform.mapping.map(from), structureTransform.mapping.map(to));
151
225
  }
152
226
  /**
153
227
  * Command that applies all tracked changes in a document.
@@ -157,13 +231,30 @@ export function applySuggestionsToRange(doc, from, to) {
157
231
  * contents left in the doc.
158
232
  */ export function applySuggestions(state, dispatch) {
159
233
  const { deletion, insertion } = getSuggestionMarks(state.schema);
160
- const tr = state.tr;
161
- applySuggestionsToTransform(state.doc, tr, insertion, deletion);
162
- applyModificationsToTransform(tr.doc, tr, 1);
163
- tr.setMeta(suggestChangesKey, {
234
+ // create a structure transform that applies all structure changes on the given document
235
+ const structureTransform = applyAllStructureSuggestions(state.doc);
236
+ // then start a clear transform from the document where the structure changes are applied
237
+ const suggestionsTransform = new Transform(structureTransform.doc);
238
+ applySuggestionsToTransform(suggestionsTransform.doc, suggestionsTransform, insertion, deletion);
239
+ applyModificationsToTransform(suggestionsTransform.doc, suggestionsTransform, 1);
240
+ // replay suggestion transform on top of the structure transform
241
+ suggestionsTransform.steps.forEach((step)=>{
242
+ structureTransform.step(step);
243
+ });
244
+ const secondStructureTransform = applyAllStructureSuggestions(structureTransform.doc);
245
+ secondStructureTransform.steps.forEach((step)=>{
246
+ structureTransform.step(step);
247
+ });
248
+ // apply the structure transform to the transaction
249
+ const transaction = state.tr;
250
+ structureTransform.steps.forEach((step)=>{
251
+ transaction.step(step);
252
+ });
253
+ if (!transaction.steps.length) return false;
254
+ transaction.setMeta(suggestChangesKey, {
164
255
  skip: true
165
256
  });
166
- dispatch?.(tr);
257
+ dispatch?.(transaction);
167
258
  return true;
168
259
  }
169
260
  /**
@@ -175,13 +266,30 @@ export function applySuggestionsToRange(doc, from, to) {
175
266
  */ export function applySuggestionsInRange(from, to) {
176
267
  return (state, dispatch)=>{
177
268
  const { deletion, insertion } = getSuggestionMarks(state.schema);
178
- const tr = state.tr;
179
- applySuggestionsToTransform(state.doc, tr, insertion, deletion, undefined, from, to);
180
- applyModificationsToTransform(tr.doc, tr, 1, undefined, from, to);
181
- tr.setMeta(suggestChangesKey, {
269
+ // create a structure transform that applies all structure changes on the given node range
270
+ const structureTransform = applyAllStructureSuggestions(state.doc, from, to);
271
+ // then start a clear transform from the document where the structure changes are applied
272
+ const suggestionsTransform = new Transform(structureTransform.doc);
273
+ applySuggestionsToTransform(suggestionsTransform.doc, suggestionsTransform, insertion, deletion, undefined, from, to);
274
+ applyModificationsToTransform(suggestionsTransform.doc, suggestionsTransform, 1, undefined, from, to);
275
+ // replay suggestion transform on top of the structure transform
276
+ suggestionsTransform.steps.forEach((step)=>{
277
+ structureTransform.step(step);
278
+ });
279
+ const secondStructureTransform = applyAllStructureSuggestions(structureTransform.doc, from === undefined ? undefined : structureTransform.mapping.map(from), to === undefined ? undefined : structureTransform.mapping.map(to));
280
+ secondStructureTransform.steps.forEach((step)=>{
281
+ structureTransform.step(step);
282
+ });
283
+ // apply the structure transform to the transaction
284
+ const transaction = state.tr;
285
+ structureTransform.steps.forEach((step)=>{
286
+ transaction.step(step);
287
+ });
288
+ if (!transaction.steps.length) return false;
289
+ transaction.setMeta(suggestChangesKey, {
182
290
  skip: true
183
291
  });
184
- dispatch?.(tr);
292
+ dispatch?.(transaction);
185
293
  return true;
186
294
  };
187
295
  }
@@ -194,14 +302,26 @@ export function applySuggestionsToRange(doc, from, to) {
194
302
  */ export function applySuggestion(suggestionId, from, to) {
195
303
  return (state, dispatch)=>{
196
304
  const { deletion, insertion } = getSuggestionMarks(state.schema);
197
- const tr = state.tr;
198
- applySuggestionsToTransform(state.doc, tr, insertion, deletion, suggestionId, from, to);
199
- applyModificationsToTransform(tr.doc, tr, 1, undefined, from, to);
200
- if (!tr.steps.length) return false;
201
- tr.setMeta(suggestChangesKey, {
305
+ // create a structure transform that applies the given structure change on the given node
306
+ const structureTransform = applyStructureSuggestion(state.doc, suggestionId);
307
+ // then start a clear transform from the document where the structure changes are applied
308
+ const suggestionsTransform = new Transform(structureTransform.doc);
309
+ applySuggestionsToTransform(suggestionsTransform.doc, suggestionsTransform, insertion, deletion, suggestionId, from, to);
310
+ applyModificationsToTransform(suggestionsTransform.doc, suggestionsTransform, 1, undefined, from, to);
311
+ // replay suggestion transform on top of the structure transform
312
+ suggestionsTransform.steps.forEach((step)=>{
313
+ structureTransform.step(step);
314
+ });
315
+ // apply the structure transform to the transaction
316
+ const transaction = state.tr;
317
+ structureTransform.steps.forEach((step)=>{
318
+ transaction.step(step);
319
+ });
320
+ if (!transaction.steps.length) return false;
321
+ transaction.setMeta(suggestChangesKey, {
202
322
  skip: true
203
323
  });
204
- dispatch?.(tr);
324
+ dispatch?.(transaction);
205
325
  return true;
206
326
  };
207
327
  }
@@ -213,13 +333,40 @@ export function applySuggestionsToRange(doc, from, to) {
213
333
  * Modifications tracked in modification marks will be reverted.
214
334
  */ export function revertSuggestions(state, dispatch) {
215
335
  const { deletion, insertion } = getSuggestionMarks(state.schema);
216
- const tr = state.tr;
217
- applySuggestionsToTransform(state.doc, tr, deletion, insertion);
218
- applyModificationsToTransform(tr.doc, tr, -1);
219
- tr.setMeta(suggestChangesKey, {
336
+ // create a structure transform that reverts all structure changes on the given document
337
+ const structureTransform = revertAllStructureSuggestions(state.doc);
338
+ // revert all join marks first as well as any structure marks they contain serialized
339
+ const joinSuggestionIds = findJoinSuggestionIds(structureTransform.doc, deletion);
340
+ joinSuggestionIds.forEach((suggestionId)=>{
341
+ const suggestionsTransform = new Transform(structureTransform.doc);
342
+ const { restoredStructureSuggestionIds } = applySuggestionsToTransform(suggestionsTransform.doc, suggestionsTransform, deletion, insertion, suggestionId);
343
+ suggestionsTransform.steps.forEach((step)=>{
344
+ structureTransform.step(step);
345
+ });
346
+ revertRestoredStructureSuggestions(structureTransform, suggestionId, restoredStructureSuggestionIds);
347
+ });
348
+ // then start a clear transform from the document where the structure changes and join marks are reverted
349
+ const suggestionsTransform = new Transform(structureTransform.doc);
350
+ applySuggestionsToTransform(suggestionsTransform.doc, suggestionsTransform, deletion, insertion);
351
+ applyModificationsToTransform(suggestionsTransform.doc, suggestionsTransform, -1);
352
+ // replay suggestion transform on top of the structure transform
353
+ suggestionsTransform.steps.forEach((step)=>{
354
+ structureTransform.step(step);
355
+ });
356
+ const secondStructureTransform = revertAllStructureSuggestions(structureTransform.doc);
357
+ secondStructureTransform.steps.forEach((step)=>{
358
+ structureTransform.step(step);
359
+ });
360
+ // apply the structure transform to the transaction
361
+ const transaction = state.tr;
362
+ structureTransform.steps.forEach((step)=>{
363
+ transaction.step(step);
364
+ });
365
+ if (!transaction.steps.length) return false;
366
+ transaction.setMeta(suggestChangesKey, {
220
367
  skip: true
221
368
  });
222
- dispatch?.(tr);
369
+ dispatch?.(transaction);
223
370
  return true;
224
371
  }
225
372
  /**
@@ -231,13 +378,30 @@ export function applySuggestionsToRange(doc, from, to) {
231
378
  */ export function revertSuggestionsInRange(from, to) {
232
379
  return (state, dispatch)=>{
233
380
  const { deletion, insertion } = getSuggestionMarks(state.schema);
234
- const tr = state.tr;
235
- applySuggestionsToTransform(state.doc, tr, deletion, insertion, undefined, from, to);
236
- applyModificationsToTransform(tr.doc, tr, -1, undefined, from, to);
237
- tr.setMeta(suggestChangesKey, {
381
+ // create a structure transform that reverts all structure changes on the given node range
382
+ const structureTransform = revertAllStructureSuggestions(state.doc, from, to);
383
+ // then start a clear transform from the document where the structure changes are reverted
384
+ const suggestionsTransform = new Transform(structureTransform.doc);
385
+ applySuggestionsToTransform(suggestionsTransform.doc, suggestionsTransform, deletion, insertion, undefined, from, to);
386
+ applyModificationsToTransform(suggestionsTransform.doc, suggestionsTransform, -1, undefined, from, to);
387
+ // replay suggestion transform on top of the structure transform
388
+ suggestionsTransform.steps.forEach((step)=>{
389
+ structureTransform.step(step);
390
+ });
391
+ const secondStructureTransform = revertAllStructureSuggestions(structureTransform.doc, from === undefined ? undefined : structureTransform.mapping.map(from), to === undefined ? undefined : structureTransform.mapping.map(to));
392
+ secondStructureTransform.steps.forEach((step)=>{
393
+ structureTransform.step(step);
394
+ });
395
+ // apply the structure transform to the transaction
396
+ const transaction = state.tr;
397
+ structureTransform.steps.forEach((step)=>{
398
+ transaction.step(step);
399
+ });
400
+ if (!transaction.steps.length) return false;
401
+ transaction.setMeta(suggestChangesKey, {
238
402
  skip: true
239
403
  });
240
- dispatch?.(tr);
404
+ dispatch?.(transaction);
241
405
  return true;
242
406
  };
243
407
  }
@@ -250,14 +414,29 @@ export function applySuggestionsToRange(doc, from, to) {
250
414
  */ export function revertSuggestion(suggestionId, from, to) {
251
415
  return (state, dispatch)=>{
252
416
  const { deletion, insertion } = getSuggestionMarks(state.schema);
253
- const tr = state.tr;
254
- applySuggestionsToTransform(state.doc, tr, deletion, insertion, suggestionId, from, to);
255
- if (!tr.steps.length) return false;
256
- tr.setMeta(suggestChangesKey, {
417
+ // create a structure transform that reverts the given structure change on the given node
418
+ const structureTransform = revertStructureSuggestion(state.doc, suggestionId);
419
+ // then start a clear transform from the document where the structure changes are reverted
420
+ const suggestionsTransform = new Transform(structureTransform.doc);
421
+ const { restoredStructureSuggestionIds } = applySuggestionsToTransform(suggestionsTransform.doc, suggestionsTransform, deletion, insertion, suggestionId, from, to);
422
+ applyModificationsToTransform(suggestionsTransform.doc, suggestionsTransform, -1, undefined, from, to);
423
+ // replay suggestion transform on top of the structure transform
424
+ suggestionsTransform.steps.forEach((step)=>{
425
+ structureTransform.step(step);
426
+ });
427
+ // in case there are structure marks revealed after join mark revert,
428
+ // revert them as well
429
+ revertRestoredStructureSuggestions(structureTransform, suggestionId, restoredStructureSuggestionIds);
430
+ // apply the structure transform to the transaction
431
+ const transaction = state.tr;
432
+ structureTransform.steps.forEach((step)=>{
433
+ transaction.step(step);
434
+ });
435
+ if (!transaction.steps.length) return false;
436
+ transaction.setMeta(suggestChangesKey, {
257
437
  skip: true
258
438
  });
259
- applyModificationsToTransform(tr.doc, tr, -1, undefined, from, to);
260
- dispatch?.(tr);
439
+ dispatch?.(transaction);
261
440
  return true;
262
441
  };
263
442
  }
@@ -63,7 +63,7 @@ export function ensureSelection() {
63
63
  $newAnchor
64
64
  });
65
65
  trace("appendTransaction", "search for new valid $head...");
66
- let $newHead = getNewValidPos(newState.selection.$head, getDirection(oldState.selection.$head, newState.selection.$head, pluginState));
66
+ let $newHead = newState.selection.empty ? $newAnchor : getNewValidPos(newState.selection.$head, getDirection(oldState.selection.$head, newState.selection.$head, pluginState));
67
67
  trace("appendTransaction", "new valid $head", $newHead?.pos, {
68
68
  $newHead
69
69
  });
@@ -147,8 +147,8 @@ function isPosValid($pos) {
147
147
  const insertionBefore = insertion.isInSet($pos.nodeBefore?.marks ?? []);
148
148
  const insertionAfter = insertion.isInSet($pos.nodeAfter?.marks ?? []);
149
149
  const ZWSP_REGEXP = new RegExp(ZWSP, "g");
150
- const isZWSPBefore = $pos.nodeBefore && $pos.nodeBefore.textContent.replace(ZWSP_REGEXP, "") === "";
151
- const isZWSPAfter = $pos.nodeAfter && $pos.nodeAfter.textContent.replace(ZWSP_REGEXP, "") === "";
150
+ const isZWSPBefore = $pos.nodeBefore && $pos.nodeBefore.isText && $pos.nodeBefore.textContent.replace(ZWSP_REGEXP, "") === "";
151
+ const isZWSPAfter = $pos.nodeAfter && $pos.nodeAfter.isText && $pos.nodeAfter.textContent.replace(ZWSP_REGEXP, "") === "";
152
152
  if (insertionBefore && insertionAfter && isZWSPBefore && isZWSPAfter) {
153
153
  trace("isPosValid", $pos.pos, "pos invalid", "reason: between two ZWSP insertions", {
154
154
  $pos
@@ -1,24 +1,10 @@
1
- import { Mark, type Node, type Attrs, type MarkType, type ResolvedPos } from "prosemirror-model";
1
+ import { Mark, type Node, type MarkType, type ResolvedPos } from "prosemirror-model";
2
2
  import { Transform } from "prosemirror-transform";
3
3
  import { type Transaction } from "prosemirror-state";
4
4
  import { type SuggestionId } from "../../generateId.js";
5
- interface JoinMarkAttrs {
6
- type: "join";
7
- data: {
8
- leftNode: {
9
- type: string;
10
- attrs: object;
11
- marks: object[];
12
- };
13
- rightNode: {
14
- type: string;
15
- attrs: object;
16
- marks: object[];
17
- };
18
- };
19
- }
20
- export declare function isJoinMarkAttrs(attrs: Attrs): attrs is JoinMarkAttrs;
21
- export declare function maybeRevertJoinMark(tr: Transform, from: number, to: number, node: Node, markType: MarkType): boolean;
5
+ export declare function maybeRevertJoinMark(tr: Transform, from: number, to: number, node: Node, markType: MarkType): false | {
6
+ restoredStructureSuggestionIds: Set<SuggestionId>;
7
+ };
22
8
  /**
23
9
  * Remove ZWSP text nodes marked as deletions (except for type=join) from the given range
24
10
  */
@@ -34,4 +20,3 @@ export declare function joinNodesAndMarkJoinPoints(trackedTransaction: Transacti
34
20
  */
35
21
  export declare function collapseZWSPNodes(trackedTransaction: Transaction, from: number, to: number): Transform;
36
22
  export declare function findJoinMark(pos: ResolvedPos): Mark | null;
37
- export {};