@@ -289,10 +289,155 @@ const updateAsSiblingsChild = (
289
289
return newBlockMap ;
290
290
} ;
291
291
292
+ /**
293
+ * This is a utility method that abstracts the operation of moving a node up to become
294
+ * a sibling of its parent. If the operation results in a parent with no children,
295
+ * also delete the parent node.
296
+ *
297
+ * Can only operate on the first or last child (this is an invariant)
298
+ *
299
+ * This operation respects the tree data invariants - it expects and returns a
300
+ * valid tree.
301
+ */
302
+ const moveChildUp = ( blockMap : BlockMap , key : string ) : BlockMap => {
303
+ verifyTree ( blockMap ) ;
304
+ const block = blockMap . get ( key ) ;
305
+ invariant ( block != null , 'block must exist in block map' ) ;
306
+
307
+ // if there is no parent, do nothing
308
+ const parentKey = block . getParentKey ( ) ;
309
+ if ( parentKey == null ) {
310
+ return blockMap ;
311
+ }
312
+
313
+ let parent = blockMap . get ( parentKey ) ;
314
+ invariant ( parent !== null , 'parent must exist in block map' ) ;
315
+ let newBlockMap = blockMap ;
316
+ const childIndex = parent . getChildKeys ( ) . indexOf ( key ) ;
317
+ invariant (
318
+ childIndex === 0 || childIndex === parent . getChildKeys ( ) . count ( ) - 1 ,
319
+ 'block is not first or last child of its parent' ,
320
+ ) ;
321
+
322
+ // If it's the first child, move as previous sibling of parent
323
+ if ( childIndex === 0 ) {
324
+ const parentPrevSibling = parent . getPrevSiblingKey ( ) ;
325
+ newBlockMap = updateSibling ( newBlockMap , key , parentKey ) ;
326
+ // link to parent's previous sibling
327
+ if ( parentPrevSibling != null ) {
328
+ newBlockMap = updateSibling ( newBlockMap , parentPrevSibling , key ) ;
329
+ }
330
+ // remove as parent's child
331
+ parent = newBlockMap . get ( parentKey ) ;
332
+ newBlockMap = newBlockMap . set (
333
+ parentKey ,
334
+ parent . merge ( {
335
+ children : parent . getChildKeys ( ) . slice ( 1 ) ,
336
+ } ) ,
337
+ ) ;
338
+ parent = newBlockMap . get ( parentKey ) ;
339
+ // remove as previous sibling of parent's children
340
+ if ( parent . getChildKeys ( ) . count ( ) > 0 ) {
341
+ const firstChildKey = parent . getChildKeys ( ) . first ( ) ;
342
+ const firstChild = newBlockMap . get ( firstChildKey ) ;
343
+ newBlockMap = newBlockMap . set (
344
+ firstChildKey ,
345
+ firstChild . merge ( { prevSibling : null } ) ,
346
+ ) ;
347
+ }
348
+ // add the node just before its former parent in the block map
349
+ newBlockMap = newBlockMap
350
+ . takeUntil ( block => block . getKey ( ) === parentKey )
351
+ . concat (
352
+ Immutable . OrderedMap ( [
353
+ [ key , newBlockMap . get ( key ) ] ,
354
+ [ parentKey , newBlockMap . get ( parentKey ) ] ,
355
+ ] ) ,
356
+ )
357
+ . concat ( newBlockMap . skipUntil ( block => block . getKey ( ) === key ) . slice ( 1 ) ) ;
358
+
359
+ // If it's the last child, move as next sibling of parent
360
+ } else if ( childIndex === parent . getChildKeys ( ) . count ( ) - 1 ) {
361
+ const parentNextSibling = parent . getNextSiblingKey ( ) ;
362
+ newBlockMap = updateSibling ( newBlockMap , parentKey , key ) ;
363
+ // link to parent's next sibling
364
+ if ( parentNextSibling != null ) {
365
+ newBlockMap = updateSibling ( newBlockMap , key , parentNextSibling ) ;
366
+ }
367
+ // remove as parent's child
368
+ parent = newBlockMap . get ( parentKey ) ;
369
+ newBlockMap = newBlockMap . set (
370
+ parentKey ,
371
+ parent . merge ( {
372
+ children : parent . getChildKeys ( ) . slice ( 0 , - 1 ) ,
373
+ } ) ,
374
+ ) ;
375
+ parent = newBlockMap . get ( parentKey ) ;
376
+ // remove as next sibling of parent's children
377
+ if ( parent . getChildKeys ( ) . count ( ) > 0 ) {
378
+ const lastChildKey = parent . getChildKeys ( ) . last ( ) ;
379
+ const lastChild = newBlockMap . get ( lastChildKey ) ;
380
+ newBlockMap = newBlockMap . set (
381
+ lastChildKey ,
382
+ lastChild . merge ( { nextSibling : null } ) ,
383
+ ) ;
384
+ }
385
+ }
386
+
387
+ // For both cases, also link to parent's parent
388
+ const grandparentKey = parent . getParentKey ( ) ;
389
+ if ( grandparentKey != null ) {
390
+ const grandparentInsertPosition = newBlockMap
391
+ . get ( grandparentKey )
392
+ . getChildKeys ( )
393
+ . findIndex ( n => n === parentKey ) ;
394
+ newBlockMap = updateParentChild (
395
+ newBlockMap ,
396
+ grandparentKey ,
397
+ key ,
398
+ childIndex === 0
399
+ ? grandparentInsertPosition
400
+ : grandparentInsertPosition + 1 ,
401
+ ) ;
402
+ } else {
403
+ newBlockMap = newBlockMap . set (
404
+ key ,
405
+ newBlockMap . get ( key ) . merge ( { parent : null } ) ,
406
+ ) ;
407
+ }
408
+
409
+ // Delete parent if it has no children
410
+ parent = newBlockMap . get ( parentKey ) ;
411
+ if ( parent . getChildKeys ( ) . count ( ) === 0 ) {
412
+ const prevSiblingKey = parent . getPrevSiblingKey ( ) ;
413
+ const nextSiblingKey = parent . getNextSiblingKey ( ) ;
414
+ if ( prevSiblingKey != null && nextSiblingKey != null ) {
415
+ newBlockMap = updateSibling ( newBlockMap , prevSiblingKey , nextSiblingKey ) ;
416
+ }
417
+ if ( prevSiblingKey == null && nextSiblingKey != null ) {
418
+ newBlockMap = newBlockMap . set (
419
+ nextSiblingKey ,
420
+ newBlockMap . get ( nextSiblingKey ) . merge ( { prevSibling : null } ) ,
421
+ ) ;
422
+ }
423
+ if ( nextSiblingKey == null && prevSiblingKey != null ) {
424
+ newBlockMap = newBlockMap . set (
425
+ prevSiblingKey ,
426
+ newBlockMap . get ( prevSiblingKey ) . merge ( { nextSibling : null } ) ,
427
+ ) ;
428
+ }
429
+ newBlockMap = newBlockMap . delete ( parentKey ) ;
430
+ }
431
+
432
+ verifyTree ( newBlockMap ) ;
433
+ return newBlockMap ;
434
+ } ;
435
+
292
436
module . exports = {
293
437
updateParentChild,
294
438
replaceParentChild,
295
439
updateSibling,
296
440
createNewParent,
297
441
updateAsSiblingsChild,
442
+ moveChildUp,
298
443
} ;
0 commit comments