@@ -78,197 +78,106 @@ class PostEditor {
7878 * ```
7979 * let { range } = editor;
8080 * editor.run((postEditor) => {
81- * postEditor.deleteRange(range);
81+ * let nextPosition = postEditor.deleteRange(range);
82+ * postEditor.setRange(new Range(nextPosition));
8283 * });
8384 * ```
8485 * @param {Range } range Cursor Range object with head and tail Positions
8586 * @return {Position } The position where the cursor would go after deletion
8687 * @public
8788 */
8889 deleteRange ( range ) {
89- // types of selection deletion:
90- // * a selection starts at the beginning of a section
91- // -- cursor should end up at the beginning of that section
92- // -- if the section not longer has markers, add a blank one for the cursor to focus on
93- // * a selection is entirely within a section
94- // -- split the markers with the selection, remove those new markers from their section
95- // -- cursor goes at end of the marker before the selection start, or if the
96- // -- selection was at the start of the section, cursor goes at section start
97- // * a selection crosses multiple sections
98- // -- remove all the sections that are between (exclusive) selection start and end
99- // -- join the start and end sections
100- // -- mark the end section for removal
101- // -- cursor goes at end of marker before the selection start
102-
103- if ( range . isCollapsed ) {
104- return range . head ;
105- }
106-
10790 let {
108- head, head : { section : headSection , offset : headSectionOffset } ,
109- tail, tail : { section : tailSection , offset : tailSectionOffset }
91+ head, head : { section : headSection } ,
92+ tail, tail : { section : tailSection }
11093 } = range ;
111- const { post } = this . editor ;
11294
113- let nextPosition ;
95+ let { editor : { post } } = this ;
11496
11597 if ( headSection === tailSection ) {
116- nextPosition = this . cutSection ( headSection , headSectionOffset , tailSectionOffset ) ;
117- } else {
118- let removedSections = [ ] ;
119- let appendSection = headSection ;
120-
121- let recursiveRangeToDelete = null ;
98+ return this . cutSection ( headSection , head , tail ) ;
99+ }
122100
123- post . walkLeafSections ( range , section => {
124- if ( recursiveRangeToDelete ) {
125- // avoid walking the entire post if we will recurse
126- return ;
127- }
101+ let nextSection = headSection . nextLeafSection ( ) ;
128102
129- switch ( section ) {
130- case headSection :
131- if ( section . isCardSection ) {
132- // Create a section to join tailSection with if necessary
133- appendSection = this . builder . createMarkupSection ( ) ;
134-
135- if ( head . isHead ( ) ) {
136- // Remove he entire card because we are deleting from its head
137- // position into section(s) after it
138- removedSections . push ( section ) ;
139- nextPosition = appendSection . headPosition ( ) ;
140- } else {
141- // Set the position to be the card's tail
142- nextPosition = section . tailPosition ( ) ;
143- }
144- } else if ( section . isBlank ) {
145- // Grab the next leaf section before removing blank head
146- let nextHeadSection = section . nextLeafSection ( ) ;
147-
148- // Remove the blank head section now
149- this . removeSection ( section ) ;
150-
151- // Advance the range and recursively delete it
152- recursiveRangeToDelete = new Range ( nextHeadSection . headPosition ( ) , range . tail ) ;
153- } else {
154- nextPosition = this . cutSection ( section , headSectionOffset , section . length ) ;
155- }
156- break ;
157- case tailSection :
158- if ( section . isCardSection ) {
159- if ( tail . isTail ( ) ) {
160- // If the range extends to the end of the tail section, remove it
161- removedSections . push ( section ) ;
162- }
163- } else {
164-
165- // join the tail section's markers (afer `tailSectionOffset`) to
166- // the appendSection
167- section . markersFor ( tailSectionOffset , section . text . length ) . forEach ( marker => {
168- appendSection . markers . append ( marker ) ;
169- } ) ;
170-
171- // If we've modified headSection, ensure it is marked dirty so that it is re-rendered.
172- // If appendSection !== headSection, it's a new section that will be rendered regardless.
173- if ( appendSection === headSection ) {
174- this . _markDirty ( headSection ) ;
175- }
176-
177- removedSections . push ( section ) ;
178- }
179- break ;
180- default :
181- // Default is to remove sections in between head and tail sections
182- removedSections . push ( section ) ;
183- }
184- } ) ;
103+ let nextPos = this . cutSection ( headSection , head , headSection . tailPosition ( ) ) ;
104+ // cutSection can replace the section, so re-read headSection here
105+ headSection = nextPos . section ;
185106
186- if ( recursiveRangeToDelete ) {
187- // If we are recursing, return early here
188- return this . deleteRange ( recursiveRangeToDelete ) ;
189- }
107+ // Remove sections in the middle of the range
108+ while ( nextSection !== tailSection ) {
109+ let tmp = nextSection ;
110+ nextSection = nextSection . nextLeafSection ( ) ;
111+ this . removeSection ( tmp ) ;
112+ }
190113
191- removedSections . forEach ( section => this . removeSection ( section ) ) ;
114+ let tailPos = this . cutSection ( tailSection , tailSection . headPosition ( ) , tail ) ;
115+ // cutSection can replace the section, so re-read tailSection here
116+ tailSection = tailPos . section ;
192117
193- if ( headSection !== appendSection ) {
194- if ( ! appendSection . isBlank ) {
195- // if the appendSection is not blank, make sure to add it
196- this . insertSectionBefore ( post . sections , appendSection , headSection . next ) ;
197- } else if ( post . isBlank ) {
198- // if we've removed all the sections from the post, add the blank append section
199- this . insertSectionBefore ( post . sections , appendSection , headSection . next ) ;
200- }
118+ if ( tailSection . isBlank ) {
119+ this . removeSection ( tailSection ) ;
120+ } else {
121+ // If head and tail sections are markerable, join them
122+ // Note: They may not be the same section type. E.g. this may join
123+ // a tail section that was a list item onto a markup section, or vice versa.
124+ // (This is the desired behavior.)
125+ if ( headSection . isMarkerable && tailSection . isMarkerable ) {
126+ headSection . join ( tailSection ) ;
127+ this . _markDirty ( headSection ) ;
128+ this . removeSection ( tailSection ) ;
129+ } else if ( headSection . isBlank ) {
130+ this . removeSection ( headSection ) ;
131+ nextPos = tailPos ;
201132 }
202133 }
203134
204- return nextPosition ;
135+ if ( post . isBlank ) {
136+ post . sections . append ( this . builder . createMarkupSection ( 'p' ) ) ;
137+ nextPos = post . headPosition ( ) ;
138+ }
139+
140+ return nextPos ;
205141 }
206142
207143 /**
144+ * Note: This method may replace `section` with a different section.
145+ *
208146 * "Cut" out the part of the section inside `headOffset` and `tailOffset`.
209- * If the section is markerable this removes markers that are wholly inside the offsets,
210- * and splits markers that straddle the head or tail.
147+ * If section is markerable this splits markers that straddle the head or tail (if necessary),
148+ * and removes markers that are wholly inside the offsets.
149+ * If section is a card, this may replace it with a blank markup section if the
150+ * positions contain the entire card.
151+ *
211152 * @param {Section } section
212- * @param {Number } headOffset
213- * @param {Number } tailOffset
153+ * @param {Position } head
154+ * @param {Position } tail
214155 * @return {Position }
215156 * @private
216157 */
217- cutSection ( section , headOffset , tailOffset ) {
218- if ( section . isBlank || headOffset === tailOffset ) {
219- return new Position ( section , headOffset ) ;
158+ cutSection ( section , head , tail ) {
159+ assert ( 'Must pass head position and tail position to `cutSection`' ,
160+ head instanceof Position && tail instanceof Position ) ;
161+ assert ( 'Must pass positions within same section to `cutSection`' ,
162+ head . section === tail . section ) ;
163+
164+ if ( section . isBlank || head . isEqual ( tail ) ) {
165+ return head ;
220166 }
221167 if ( section . isCardSection ) {
222- let newSection = this . builder . createMarkupSection ( ) ;
223- this . replaceSection ( section , newSection ) ;
224- return newSection . headPosition ( ) ;
225- }
226-
227- let adjustedHead = 0 ,
228- marker = section . markers . head ,
229- adjustedTail = marker . length ;
230-
231- // Walk to the first node inside the headOffset, splitting
232- // a marker if needed. Leave marker as the first node inside.
233- while ( marker ) {
234- if ( adjustedTail >= headOffset ) {
235- let splitOffset = headOffset - adjustedHead ;
236- let { afterMarker } = this . splitMarker ( marker , splitOffset ) ;
237- adjustedHead = adjustedHead + splitOffset ;
238- // FIXME: That these two loops cannot agree on adjustedTail being
239- // incremented at the start or end seems prime for refactoring.
240- adjustedTail = adjustedHead ;
241- marker = afterMarker ;
242- break ;
243- }
244- adjustedHead += marker . length ;
245- marker = marker . next ;
246- if ( marker ) {
247- adjustedTail += marker . length ;
168+ if ( head . isHead ( ) && tail . isTail ( ) ) {
169+ let newSection = this . builder . createMarkupSection ( ) ;
170+ this . replaceSection ( section , newSection ) ;
171+ return newSection . headPosition ( ) ;
172+ } else {
173+ return tail ;
248174 }
249175 }
250176
251- // Walk each marker inside, removing it if needed. when the last is
252- // reached split it and remove the part inside the tailOffset
253- while ( marker ) {
254- adjustedTail += marker . length ;
255- if ( adjustedTail >= tailOffset ) {
256- let splitOffset = marker . length - ( adjustedTail - tailOffset ) ;
257- let {
258- beforeMarker
259- } = this . splitMarker ( marker , splitOffset ) ;
260- if ( beforeMarker ) {
261- this . removeMarker ( beforeMarker ) ;
262- }
263- break ;
264- }
265- adjustedHead += marker . length ;
266- let nextMarker = marker . next ;
267- this . removeMarker ( marker ) ;
268- marker = nextMarker ;
269- }
177+ let range = new Range ( head , tail ) ;
178+ this . splitMarkers ( range ) . forEach ( m => this . removeMarker ( m ) ) ;
270179
271- return new Position ( section , headOffset ) ;
180+ return head ;
272181 }
273182
274183 _coalesceMarkers ( section ) {
@@ -469,18 +378,17 @@ class PostEditor {
469378
470379 /**
471380 * Split markers at two positions, once at the head, and if necessary once
472- * at the tail. This method is designed to accept a range
473- * (e.g. `editor.range`) as an argument.
381+ * at the tail.
474382 *
475383 * Usage:
476384 * ```
477- * let markerRange = this.cursor.offsets ;
385+ * let range = editor.range ;
478386 * editor.run((postEditor) => {
479- * postEditor.splitMarkers(markerRange );
387+ * postEditor.splitMarkers(range );
480388 * });
481389 * ```
482390 * The return value will be marker object completely inside the offsets
483- * provided. Markers on the outside of the split may also have been modified.
391+ * provided. Markers outside of the split may also have been modified.
484392 *
485393 * @param {Range } markerRange
486394 * @return {Array } of markers that are inside the split
@@ -501,29 +409,6 @@ class PostEditor {
501409 edit . removed . forEach ( m => this . removeMarker ( m ) ) ;
502410 }
503411
504- splitMarker ( marker , offset ) {
505- let beforeMarker , afterMarker ;
506-
507- if ( offset === 0 ) {
508- beforeMarker = marker . prev ;
509- afterMarker = marker ;
510- } else if ( offset === marker . length ) {
511- beforeMarker = marker ;
512- afterMarker = marker . next ;
513- } else {
514- let { builder } = this . editor ,
515- { section } = marker ;
516- beforeMarker = builder . createMarker ( marker . value . substring ( 0 , offset ) , marker . markups ) ;
517- afterMarker = builder . createMarker ( marker . value . substring ( offset , marker . length ) , marker . markups ) ;
518- section . markers . splice ( marker , 1 , [ beforeMarker , afterMarker ] ) ;
519-
520- this . removeMarker ( marker ) ;
521- this . _markDirty ( section ) ;
522- }
523-
524- return { beforeMarker, afterMarker } ;
525- }
526-
527412 /**
528413 * Split the section at the position.
529414 *
@@ -541,7 +426,7 @@ class PostEditor {
541426 * headMarkerOffset is 0.
542427 *
543428 * @param {Position } position
544- * @return {Array } new sections, one for the first half and one for the second
429+ * @return {Array } new sections, one for the first half and one for the second (either one can be null)
545430 * @public
546431 */
547432 splitSection ( position ) {
0 commit comments