Index: trunk/parsers/wikidom/lib/hype/models/es.DocumentModel.js |
— | — | @@ -39,37 +39,22 @@ |
40 | 40 | * Each function is called in the context of a state, and takes an operation object as a parameter. |
41 | 41 | */ |
42 | 42 | es.DocumentModel.operations = ( function() { |
43 | | - function containsElements( op ) { |
44 | | - for ( i = 0; i < op.data.length; i++ ) { |
45 | | - if ( op.data.type !== undefined ) { |
46 | | - return true; |
47 | | - } |
48 | | - } |
49 | | - return false; |
| 43 | + function invalidate( from, to ) { |
| 44 | + this.rebuild.push( { 'from': from, 'to': to } ); |
50 | 45 | } |
51 | 46 | |
52 | | - function isStructural( offset ) { |
53 | | - var edge = offset === 0 || offset === this.data.length - 1, |
54 | | - elementLeft = this.data[offset - 1].type !== undefined, |
55 | | - elementRight = this.data[offset].type !== undefined; |
56 | | - if ( edge || ( elementLeft && elementRight ) ) { |
57 | | - return true; |
58 | | - } |
59 | | - return false; |
60 | | - } |
61 | | - |
62 | 47 | function retain( op ) { |
63 | 48 | annotate.call( this, this.cursor + op.length ); |
64 | 49 | this.cursor += op.length; |
65 | 50 | } |
66 | 51 | |
67 | 52 | function insert( op ) { |
68 | | - if ( isStructural( this.cursor ) ) { |
| 53 | + if ( es.DocumentModel.isStructuralOffset( this.data, this.cursor ) ) { |
69 | 54 | // TODO: Support tree updates when inserting between elements |
70 | 55 | } else { |
71 | 56 | // Get the node we are about to insert into |
72 | 57 | var node = this.tree.getNodeFromOffset( this.cursor ); |
73 | | - if ( containsElements( op ) ) { |
| 58 | + if ( es.DocumentModel.containsElementData( op.data ) ) { |
74 | 59 | var nodeParent = node.getParent(); |
75 | 60 | if ( !nodeParent ) { |
76 | 61 | throw 'Missing parent error. Node does not have a parent node.'; |
— | — | @@ -105,10 +90,12 @@ |
106 | 91 | } |
107 | 92 | |
108 | 93 | function remove( op ) { |
109 | | - if ( isStructural( this.cursor ) && isStructural( this.cursor + op.data.length ) ) { |
| 94 | + var elementLeft = es.DocumentModel.isElementOffset( this.data, this.cursor ), |
| 95 | + elementRight = es.DocumentModel.isElementOffset( this.cursor + op.data.length ); |
| 96 | + if ( elementLeft && elementRight ) { |
110 | 97 | // TODO: Support tree updates when removing whole elements |
111 | 98 | } else { |
112 | | - if ( containsElements( op ) ) { |
| 99 | + if ( es.DocumentModel.containsElementData( op.data ) ) { |
113 | 100 | // TODO: Support tree updates when removing partial elements |
114 | 101 | } else { |
115 | 102 | // Get the node we are removing content from |
— | — | @@ -470,34 +457,108 @@ |
471 | 458 | return data; |
472 | 459 | }; |
473 | 460 | |
474 | | -/* Methods */ |
475 | | - |
476 | 461 | /** |
477 | 462 | * Checks if a data at a given offset is content. |
478 | 463 | * |
| 464 | + * Content offsets are those which are between an opening and a closing element. |
| 465 | + * |
| 466 | + * @example Content offsets: |
| 467 | + * <paragraph> a b c </paragraph> <list> <listItem> d e f </listItem> </list> |
| 468 | + * ^ ^ ^ ^ ^ ^ |
| 469 | + * |
479 | 470 | * @static |
480 | 471 | * @method |
| 472 | + * @param {Array} data Data to evaluate offset within |
481 | 473 | * @param {Integer} offset Offset in data to check |
482 | 474 | * @returns {Boolean} If data at offset is content |
483 | 475 | */ |
484 | | -es.DocumentModel.prototype.isContent = function( offset ) { |
485 | | - return typeof this.data[offset] === 'string' || $.isArray( this.data[offset] ); |
| 476 | +es.DocumentModel.isContentOffset = function( data, offset ) { |
| 477 | + // Content can't exist at the edges |
| 478 | + if ( offset > 0 && offset < data.length - 1 ) { |
| 479 | + // Shortcut: if there's already content there, we will trust it's supposed to be there |
| 480 | + if ( typeof data[offset] === 'string' || $.isArray( data[offset] ) ) { |
| 481 | + return true; |
| 482 | + } |
| 483 | + // Empty elements will have an opening and a closing next to each other |
| 484 | + var openLeft = data[offset - 1].type !== undefined && data[offset - 1].type[0] !== '/', |
| 485 | + closeRight = data[offset].type !== undefined && data[offset].type[0] !== '/'; |
| 486 | + // Check that there's an opening and a closing on the left and right, and the types match |
| 487 | + if ( openLeft && closeRight && ('/' + data[offset - 1].type === data[offset].type ) ) { |
| 488 | + return true; |
| 489 | + } |
| 490 | + } |
| 491 | + return false; |
486 | 492 | }; |
487 | 493 | |
488 | 494 | /** |
489 | 495 | * Checks if a data at a given offset is an element. |
490 | 496 | * |
| 497 | + * Element offsets are those at which an element is present. |
| 498 | + * |
| 499 | + * @example Element offsets: |
| 500 | + * <paragraph> a b c </paragraph> <list> <listItem> d e f </listItem> </list> |
| 501 | + * ^ ^ ^ ^ ^ ^ |
| 502 | + * |
491 | 503 | * @static |
492 | 504 | * @method |
| 505 | + * @param {Array} data Data to evaluate offset within |
493 | 506 | * @param {Integer} offset Offset in data to check |
494 | 507 | * @returns {Boolean} If data at offset is an element |
495 | 508 | */ |
496 | | -es.DocumentModel.prototype.isElement = function( offset ) { |
| 509 | +es.DocumentModel.isElementOffset = function( data, offset ) { |
497 | 510 | // TODO: Is there a safer way to check if it's a plain object without sacrificing speed? |
498 | | - return this.data[offset].type !== undefined; |
| 511 | + return offset >= 0 && offset < data.length && data[offset].type !== undefined; |
499 | 512 | }; |
500 | 513 | |
501 | 514 | /** |
| 515 | + * Checks if an offset within given data is structural. |
| 516 | + * |
| 517 | + * Structural offsets are those at the beginning, end or surrounded by elements. This differs |
| 518 | + * from a location at which an element is present in that elements can be safely inserted at a |
| 519 | + * structural location, but not nessecarily where an element is present. |
| 520 | + * |
| 521 | + * @example Structural offsets: |
| 522 | + * <paragraph> a b c </paragraph> <list> <listItem> d e f </listItem> </list> |
| 523 | + * ^ ^ ^ ^ ^ |
| 524 | + * |
| 525 | + * @static |
| 526 | + * @method |
| 527 | + * @param {Array} data Data to evaluate offset within |
| 528 | + * @param {Integer} offset Offset to check |
| 529 | + * @returns {Boolean} Whether offset is structural or not |
| 530 | + */ |
| 531 | +es.DocumentModel.isStructuralOffset = function( data, offset ) { |
| 532 | + // Edges are always structural |
| 533 | + if ( offset === 0 || offset === data.length - 1 ) { |
| 534 | + return true; |
| 535 | + } |
| 536 | + // Structual offsets will have elements on each side |
| 537 | + if ( data[offset - 1].type !== undefined && data[offset].type !== undefined ) { |
| 538 | + return true; |
| 539 | + } |
| 540 | + return false; |
| 541 | +}; |
| 542 | + |
| 543 | +/** |
| 544 | + * Checks if elements are present within data. |
| 545 | + * |
| 546 | + * @static |
| 547 | + * @method |
| 548 | + * @param {Array} data Data to look for elements within |
| 549 | + * @returns {Boolean} If elements exist in data |
| 550 | + */ |
| 551 | +es.DocumentModel.containsElementData = function( data ) { |
| 552 | + for ( var i = 0, length = data.length; i < length; i++ ) { |
| 553 | + if ( data[i].type !== undefined ) { |
| 554 | + return true; |
| 555 | + } |
| 556 | + } |
| 557 | + return false; |
| 558 | +}; |
| 559 | + |
| 560 | +/* Methods */ |
| 561 | + |
| 562 | +/** |
502 | 563 | * Creates a document view for this model. |
503 | 564 | * |
504 | 565 | * @method |
— | — | @@ -653,37 +714,6 @@ |
654 | 715 | */ |
655 | 716 | es.DocumentModel.prototype.prepareInsertion = function( offset, data ) { |
656 | 717 | /** |
657 | | - * Returns true if data starts with an opening element and ends with a closing element |
658 | | - */ |
659 | | - /*function isStructuralData( data ) { |
660 | | - return data.length >= 2 && |
661 | | - data[0].type !== undefined && data[0].type.charAt( 0 ) != '/' && |
662 | | - data[data.length - 1].type !== |
663 | | - undefined && data[data.length - 1].type.charAt( 0 ) == '/'; |
664 | | - }*/ |
665 | | - function isStructuralData( data ) { |
666 | | - var i; |
667 | | - for ( i = 0; i < data.length; i++ ) { |
668 | | - if ( data[i].type !== undefined ) { |
669 | | - return true; |
670 | | - } |
671 | | - } |
672 | | - return false; |
673 | | - } |
674 | | - |
675 | | - function isStructuralLocation( offset, data ) { |
676 | | - // The following are structural locations: |
677 | | - // * The beginning of the document (offset 0) |
678 | | - // * The end of the document (offset length-1) |
679 | | - // * Any location between elements, i.e. the item before is a closing and the item after is |
680 | | - // * an opening |
681 | | - return offset <= 0 || offset >= data.length - 1 || ( |
682 | | - data[offset - 1].type !== undefined && data[offset - 1].type.charAt( 0 ) == '/' && |
683 | | - data[offset].type !== undefined && data[offset].type.charAt( 0 ) != '/' |
684 | | - ); |
685 | | - } |
686 | | - |
687 | | - /** |
688 | 718 | * Balances mismatched openings/closings in data |
689 | 719 | * @return data itself if nothing was changed, or a clone of data with balancing changes made. |
690 | 720 | * data itself is never touched |
— | — | @@ -699,7 +729,7 @@ |
700 | 730 | stack.push( data[i].type ); |
701 | 731 | } else { |
702 | 732 | // Closing |
703 | | - if ( stack.length == 0 ) { |
| 733 | + if ( stack.length === 0 ) { |
704 | 734 | // The stack is empty, so this is an unopened closing |
705 | 735 | // Remove it |
706 | 736 | if ( workingData === null ) { |
— | — | @@ -736,13 +766,13 @@ |
737 | 767 | |
738 | 768 | var tx = new es.Transaction(), |
739 | 769 | insertedData = data, // may be cloned and modified |
740 | | - isStructuralLoc = isStructuralLocation( offset, this.data ); |
| 770 | + isStructuralLoc = es.DocumentModel.isStructuralOffset( this.data, offset ); |
741 | 771 | |
742 | 772 | if ( offset > 0 ) { |
743 | 773 | tx.pushRetain( offset ); |
744 | 774 | } |
745 | 775 | |
746 | | - if ( isStructuralData( insertedData ) ) { |
| 776 | + if ( es.DocumentModel.containsElementData( insertedData ) ) { |
747 | 777 | if ( isStructuralLoc ) { |
748 | 778 | insertedData = balance( insertedData ); |
749 | 779 | } else { |
— | — | @@ -1002,7 +1032,8 @@ |
1003 | 1033 | 'tree': this, |
1004 | 1034 | 'cursor': 0, |
1005 | 1035 | 'set': [], |
1006 | | - 'clear': [] |
| 1036 | + 'clear': [], |
| 1037 | + 'rebuild': [] |
1007 | 1038 | }; |
1008 | 1039 | for ( var i = 0, length = transaction.length; i < length; i++ ) { |
1009 | 1040 | var operation = transaction[i]; |
— | — | @@ -1027,7 +1058,8 @@ |
1028 | 1059 | 'tree': this, |
1029 | 1060 | 'cursor': 0, |
1030 | 1061 | 'set': [], |
1031 | | - 'clear': [] |
| 1062 | + 'clear': [], |
| 1063 | + 'rebuild': [] |
1032 | 1064 | }; |
1033 | 1065 | for ( var i = 0, length = transaction.length; i < length; i++ ) { |
1034 | 1066 | var operation = transaction[i]; |