@@ -8,7 +8,7 @@ import { type ReplaceStep, type Step } from "prosemirror-transform";
88
99import { findSuggestionMarkEnd } from "./findSuggestionMarkEnd.js" ;
1010import { rebasePos } from "./rebasePos.js" ;
11- import { getSuggestionMarks , beforesInBlockRange } from "./utils.js" ;
11+ import { getSuggestionMarks } from "./utils.js" ;
1212import { type SuggestionId } from "./generateId.js" ;
1313import { 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 */
4738export 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