Skip to content

Commit 653fba7

Browse files
authored
Properly handle deleted joins across multiple depths (#42)
1 parent e49f555 commit 653fba7

2 files changed

Lines changed: 68 additions & 53 deletions

File tree

.yarn/versions/a947fedd.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
releases:
2+
"@handlewithcare/prosemirror-suggest-changes": patch

src/replaceStep.ts

Lines changed: 66 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { type ReplaceStep, type Step } from "prosemirror-transform";
88

99
import { findSuggestionMarkEnd } from "./findSuggestionMarkEnd.js";
1010
import { rebasePos } from "./rebasePos.js";
11-
import { getSuggestionMarks, beforesInBlockRange } from "./utils.js";
11+
import { getSuggestionMarks } from "./utils.js";
1212
import { type SuggestionId } from "./generateId.js";
1313
import { type BoundarySuggestion } from "./schema.js";
1414

@@ -27,22 +27,13 @@ type WritableAttrs = Record<string, unknown>;
2727
* Any slices that are to be inserted will also be marked with
2828
* insertion marks.
2929
*
30-
* If a deletion begins at the very end of a textblock, a zero-width
31-
* space will be inserted at the end of that texblock and given
32-
* a deletion mark.
33-
*
34-
* Similarly, if a deletion ends at the very beginning fo a textblock,
35-
* a zero-width space will be inserted at the beginning of that
36-
* textblock and given a deletion mark.
37-
*
38-
* If an insertion slice is open on either end, and there is no content
39-
* adjacent to the open end(s), zero-width spaces
40-
* will be added at the open end(s) and given insertion marks.
30+
* If a deletion or insertion crosses a block boundary, a block
31+
* boundary suggestion mark will be added to all but the last
32+
* block touched by the change.
4133
*
4234
* After all of the above have been evaluated, if the resulting
4335
* insertion or deletion marks abut or join existing marks, they
44-
* will be joined and given the same ids. Any no-longer-necessary
45-
* zero-width spaces will be removed.
36+
* will be joined and given the same ids.
4637
*/
4738
export function suggestReplaceStep(
4839
trackedTransaction: Transaction,
@@ -128,46 +119,68 @@ export function suggestReplaceStep(
128119
// can leave zero-width spaces as markers if there's no other
129120
// content to anchor the deletion to.
130121
if (stepFrom !== stepTo) {
131-
const $stepFrom = trackedTransaction.doc.resolve(stepFrom);
132-
const $stepTo = trackedTransaction.doc.resolve(stepTo);
133-
const blockRange = $stepFrom.blockRange($stepTo);
134-
if (
135-
blockRange &&
136-
$stepFrom.node(blockRange.depth + 1) !==
137-
$stepTo.node(blockRange.depth + 1)
138-
) {
139-
const startsToMark = beforesInBlockRange($stepFrom, blockRange);
140-
141-
for (const stepFromBlockStart of startsToMark) {
142-
const stepFromBlockBoundarySuggestion = blockBoundarySuggestion.isInSet(
143-
trackedTransaction.doc.nodeAt(stepFromBlockStart)?.marks ?? [],
144-
)?.attrs as BoundarySuggestion | undefined;
145-
146-
// When a deletion crosses a block boundary, we add
147-
// a blockBoundarySuggestion mark to the previous
148-
// block. This allows us to render the
149-
// deleted boundary with a widget, as well as properly handle
150-
// future, adjacent deletions and insertions.
151-
if (stepFromBlockBoundarySuggestion?.type !== "insertion") {
152-
trackedTransaction.addNodeMark(
153-
stepFromBlockStart,
154-
blockBoundarySuggestion.create({
155-
id: markId,
156-
type: deletion.name,
157-
...extraAttrs,
158-
}),
159-
);
160-
} else {
161-
trackedTransaction.removeNodeMark(
162-
stepFromBlockStart,
163-
blockBoundarySuggestion,
164-
);
122+
let $stepFrom = trackedTransaction.doc.resolve(stepFrom);
123+
let $stepTo = trackedTransaction.doc.resolve(stepTo);
165124

166-
trackedTransaction.join(
167-
stepFromBlockStart +
168-
// oxlint-disable-next-line typescript/no-non-null-assertion
169-
trackedTransaction.doc.nodeAt(stepFromBlockStart)!.nodeSize,
170-
);
125+
if ($stepFrom.parent !== $stepTo.parent) {
126+
const blockRange = $stepFrom.blockRange($stepTo);
127+
128+
if (blockRange) {
129+
let alreadyDeleted = true;
130+
let d = $stepFrom.depth;
131+
let minDepth = blockRange.depth;
132+
while (d > minDepth) {
133+
const stepFromBlockStart = $stepFrom.before(d);
134+
135+
const stepFromBlockBoundarySuggestion =
136+
blockBoundarySuggestion.isInSet(
137+
trackedTransaction.doc.nodeAt(stepFromBlockStart)?.marks ?? [],
138+
)?.attrs as BoundarySuggestion | undefined;
139+
140+
// When a deletion crosses a block boundary, we add
141+
// a blockBoundarySuggestion mark to the previous
142+
// block. This allows us to render the
143+
// deleted boundary with a widget, as well as properly handle
144+
// future, adjacent deletions and insertions.
145+
if (!stepFromBlockBoundarySuggestion) {
146+
alreadyDeleted = false;
147+
trackedTransaction.addNodeMark(
148+
stepFromBlockStart,
149+
blockBoundarySuggestion.create({
150+
id: markId,
151+
type: deletion.name,
152+
...extraAttrs,
153+
}),
154+
);
155+
} else if (stepFromBlockBoundarySuggestion.type === "insertion") {
156+
alreadyDeleted = false;
157+
trackedTransaction.removeNodeMark(
158+
stepFromBlockStart,
159+
blockBoundarySuggestion,
160+
);
161+
162+
trackedTransaction.join(
163+
stepFromBlockStart +
164+
// oxlint-disable-next-line typescript/no-non-null-assertion
165+
trackedTransaction.doc.nodeAt(stepFromBlockStart)!.nodeSize,
166+
);
167+
}
168+
169+
d--;
170+
171+
// If a step attempts to delete a block boundary that
172+
// has already been deleted, the user wants to delete
173+
// the block boundary one level deeper. We expand the
174+
// deletion range by one in both directions to match
175+
// the appropriate range and loop again.
176+
if (d === blockRange.depth && alreadyDeleted) {
177+
stepFrom--;
178+
stepTo++;
179+
$stepFrom = trackedTransaction.doc.resolve(stepFrom);
180+
$stepTo = trackedTransaction.doc.resolve(stepTo);
181+
minDepth = d;
182+
d = $stepFrom.depth;
183+
}
171184
}
172185
}
173186
}

0 commit comments

Comments
 (0)