Index: trunk/parsers/wikidom/lib/hype/models/es.DocumentModel.js |
— | — | @@ -613,53 +613,99 @@ |
614 | 614 | * @returns {es.Transaction} |
615 | 615 | */ |
616 | 616 | es.DocumentModel.prototype.prepareRemoval = function( range ) { |
617 | | - var tx = new es.Transaction(), removed = []; |
| 617 | + var doc = this; |
| 618 | + debugger; |
| 619 | + |
| 620 | + /** |
| 621 | + * Return true if can merge the remaining contents of the elements after a selection is deleted across them. |
| 622 | + * For instance, if a selection is painted across two paragraphs, and then the text is deleted, the two paragraphs can become one paragraph. |
| 623 | + * However, if the selection crosses into a table, those cannot be merged. |
| 624 | + * @param {Number} integer offset |
| 625 | + * @param {Number} integer offset |
| 626 | + * @return {Boolean} |
| 627 | + */ |
| 628 | + function canMerge( range ) { |
| 629 | + var node1 = doc.getNodeFromOffset( range.start ); |
| 630 | + var node2 = doc.getNodeFromOffset( range.end - 1 ); |
| 631 | + // This is the simple rule we are following for now -- same type & same parent = can merge. So you can merge adjacent paragraphs, or listitems. |
| 632 | + // And you can't merge a paragraph into a table row. |
| 633 | + // There may be other rules we will want in here later, for instance, special casing merging a listitem into a paragraph. |
| 634 | + |
| 635 | + // wait, some nodes don't have types? Is this the top document node? |
| 636 | + return ( |
| 637 | + ( |
| 638 | + ( node1.type !== undefined && node2.type !== undefined ) |
| 639 | + && |
| 640 | + ( node1.type === node2.type ) |
| 641 | + ) |
| 642 | + && |
| 643 | + ( node1.getParent() === node2.getParent() ) |
| 644 | + ); |
| 645 | + } |
| 646 | + |
| 647 | + function mergeDelete( range, tx ) { |
| 648 | + // yay, content can be removed in one fell swoop |
| 649 | + var removed = doc.data.slice( range.start, range.end ); |
| 650 | + tx.pushRemove( removed ); |
| 651 | + } |
| 652 | + |
| 653 | + // remove string content only, retain structure |
| 654 | + function stripDelete( range, tx ) { |
| 655 | + var lastOperation, operationStart; |
| 656 | + |
| 657 | + var ops = []; |
| 658 | + debugger; |
| 659 | + |
| 660 | + // get a list of operations, with 0-based indexes |
| 661 | + for (var i = range.start; i < range.end; i++ ) { |
| 662 | + var neededOp = doc.data[i].type === undefined ? 'remove' : 'retain'; |
| 663 | + var op = ops[ ops.length - 1 ]; |
| 664 | + if ( op === undefined || op.type !== neededOp ) { |
| 665 | + ops.push( { type: neededOp, start: i, end: i } ); |
| 666 | + } else { |
| 667 | + op.end = i; |
| 668 | + } |
| 669 | + } |
| 670 | + |
| 671 | + debugger; |
| 672 | + // insert operations as transactions (end must be adjusted) |
| 673 | + for (var j = 0; j < ops.length; j++ ) { |
| 674 | + var op = ops[j]; |
| 675 | + if ( op.type === 'retain' ) { |
| 676 | + // we add one because retain(3,3) really means retain 1 char at pos 3 |
| 677 | + tx.pushRetain( op.end - op.start + 1 ); |
| 678 | + } else if ( op.type === 'remove' ) { |
| 679 | + // we add one because to remove(3,5) we need to slice(3,6), the ending is last subscript removed + 1. |
| 680 | + tx.pushRemove( this.data.slice( op.start, op.end + 1 ) ); |
| 681 | + } else { |
| 682 | + console.log( "this is impossible" ); |
| 683 | + } |
| 684 | + } |
| 685 | + |
| 686 | + } |
| 687 | + |
| 688 | + |
| 689 | + var tx = new es.Transaction(); |
618 | 690 | range.normalize(); |
619 | 691 | |
620 | 692 | // Retain to the start of the range |
621 | 693 | if ( range.start > 0 ) { |
622 | 694 | tx.pushRetain( range.start ); |
623 | 695 | } |
624 | | - // TODO check for overlaps with structured elements and compensate |
| 696 | + |
625 | 697 | |
626 | | - removed = this.data.slice( range.start, range.end ); |
627 | | - tx.pushRemove( removed ); |
628 | | - |
629 | | - // Retain up to the end of the document |
630 | | - if ( range.end < this.data.length ) { |
631 | | - tx.pushRetain( this.data.length - range.end ); |
| 698 | + // choose a deletion strategy; merging nodes together, or stripping content from existing structure. |
| 699 | + if ( canMerge( range ) ) { |
| 700 | + mergeDelete( range, tx ); |
| 701 | + } else { |
| 702 | + stripDelete( range, tx ); |
632 | 703 | } |
633 | | - |
634 | | - /* |
635 | | - * Loop to detect structural changes: |
636 | | - while ( i < range.end ) { |
637 | | - var data = this.data[i]; |
638 | | - if ( data.type !== undefined ) { |
639 | | - console.log( "later" ); |
640 | | - // TODO structural |
641 | | - } else { |
642 | | - removeData.push( this.data[i] ); |
643 | | - } |
644 | | - i++; |
645 | | - } |
646 | | - */ |
647 | 704 | |
648 | | - /* |
649 | | - * // Structural changes |
650 | | - * if ( The range spans structural elements ) { |
651 | | - * if ( The range partially overlaps structural elements ) { |
652 | | - * Add insertions to replace removed openings and closing to overlapped elements |
653 | | - * } else { |
654 | | - * Removing entire structural elements is OK, do nothing |
655 | | - * } |
656 | | - * } else { |
657 | | - * Removing only content is OK, do nothing |
658 | | - * } |
659 | | - / |
660 | | - i++; |
| 705 | + // Retain up to the end of the document. Why do we do this? |
| 706 | + if ( range.end < doc.data.length ) { |
| 707 | + tx.pushRetain( doc.data.length - range.end ); |
661 | 708 | } |
662 | | - */ |
663 | | - |
| 709 | + |
664 | 710 | return tx; |
665 | 711 | }; |
666 | 712 | |