diff --git a/modules/es/bases/es.DocumentModelLeafNode.js b/modules/es/bases/es.DocumentModelLeafNode.js index b2e58a43f4..f329737828 100644 --- a/modules/es/bases/es.DocumentModelLeafNode.js +++ b/modules/es/bases/es.DocumentModelLeafNode.js @@ -34,50 +34,10 @@ es.DocumentModelLeafNode.prototype.getPlainObject = function() { if ( this.element && this.element.attributes ) { obj.attributes = es.copyObject( this.element.attributes ); } - obj.content = es.DocumentModel.getExpandedContentData( this.getContent() ); + obj.content = es.DocumentModel.getExpandedContentData( this.getContentData() ); return obj; }; -/** - * Gets the content length. - * - * FIXME: This method makes assumptions that a node with a data property is a DocumentModel, which - * may be an issue if sub-classes of DocumentModelLeafNode other than DocumentModel have a data property - * as well. A safer way of determining this would be helpful in preventing future bugs. - * - * @method - * @param {es.Range} [range] Range of content to get - * @returns {Integer} Length of content - */ -es.DocumentModelLeafNode.prototype.getContent = function( range ) { - // Find root - var root = this.data ? this : ( this.root && this.root.data ? this.root : null ); - if ( root ) { - return root.getContentFromNode( this, range ); - } - return []; -}; - -/** - * Gets plain text version of the content within a specific range. - * - * @method - * @param {es.Range} [range] Range of text to get - * @returns {String} Text within given range - */ -es.DocumentModelLeafNode.prototype.getText = function( range ) { - var content = this.getContent( range ); - // Copy characters - var text = ''; - for ( var i = 0, length = content.length; i < length; i++ ) { - // If not using in IE6 or IE7 (which do not support array access for strings) use this.. - // text += this.data[i][0]; - // Otherwise use this... - text += typeof content[i] === 'string' ? content[i] : content[i][0]; - } - return text; -}; - /* Inheritance */ es.extendClass( es.DocumentModelLeafNode, es.DocumentLeafNode ); diff --git a/modules/es/bases/es.DocumentModelNode.js b/modules/es/bases/es.DocumentModelNode.js index 1f339568e1..c1761a0338 100644 --- a/modules/es/bases/es.DocumentModelNode.js +++ b/modules/es/bases/es.DocumentModelNode.js @@ -218,6 +218,71 @@ es.DocumentModelNode.prototype.getElementAttribute = function( key ) { return null; }; +/** + * Gets all element data, including the element opening, closing and it's contents. + * + * @method + * @returns {Array} Element data + */ +es.DocumentModelNode.prototype.getElementData = function() { + // Get reference to the document, which might be this node but otherwise should be this.root + var root = this.type === 'document' ? + this : ( this.root && this.root.type === 'document' ? this.root : null ); + if ( root ) { + return root.getElementDataFromNode( this ); + } + return []; +}; + +/** + * Gets content data within a given range. + * + * @method + * @param {es.Range} [range] Range of content to get + * @returns {Array} Content data + */ +es.DocumentModelNode.prototype.getContentData = function( range ) { + // Get reference to the document, which might be this node but otherwise should be this.root + var root = this.type === 'document' ? + this : ( this.root && this.root.type === 'document' ? this.root : null ); + if ( root ) { + return root.getContentDataFromNode( this, range ); + } + return []; +}; + +/** + * Gets plain text version of the content within a specific range. + * + * Two newlines are inserted between leaf nodes. + * + * TODO: Maybe do something more adaptive with newlines + * + * @method + * @param {es.Range} [range] Range of text to get + * @returns {String} Text within given range + */ +es.DocumentModelNode.prototype.getContentText = function( range ) { + var content = this.getContentData( range ); + // Copy characters + var text = '', + element = false; + for ( var i = 0, length = content.length; i < length; i++ ) { + if ( typeof content[i] === 'object' ) { + if ( i ) { + element = true; + } + } else { + if ( element ) { + text += '\n\n'; + element = false; + } + text += typeof content[i] === 'string' ? content[i] : content[i][0]; + } + } + return text; +}; + /* Inheritance */ es.extendClass( es.DocumentModelNode, es.DocumentNode ); diff --git a/modules/es/models/es.DocumentModel.js b/modules/es/models/es.DocumentModel.js index eebf1ee6d0..0325c8b42a 100644 --- a/modules/es/models/es.DocumentModel.js +++ b/modules/es/models/es.DocumentModel.js @@ -612,6 +612,21 @@ es.DocumentModel.prototype.getElementFromNode = function( node ) { return null; }; +/** + * Gets the element data of a node. + * + * @method + * @param {es.DocumentModelNode} node Node to get element data for + */ +es.DocumentModel.prototype.getContentDataFromNode = function( node ) { + var length = node.getElementLength(); + var offset = this.getOffsetFromNode( node ); + if ( offset !== -1 ) { + return this.data.slice( offset, offset + length ); + } + return null; +}; + /** * Gets the content data of a node. * @@ -619,7 +634,7 @@ es.DocumentModel.prototype.getElementFromNode = function( node ) { * @param {es.DocumentModelNode} node Node to get content data for * @returns {Array|null} List of content and elements inside node or null if node is not found */ -es.DocumentModel.prototype.getContentFromNode = function( node, range ) { +es.DocumentModel.prototype.getContentDataFromNode = function( node, range ) { var length = node.getContentLength(); if ( range ) { range.normalize(); @@ -1201,7 +1216,7 @@ es.DocumentModel.prototype.prepareLeafConversion = function( range, type, attrib txs.push( this.prepareInsertion( nodeOffset, [ { 'type': type, 'attributes': attributes } ] - .concat( nodes[i].getContent() ) + .concat( nodes[i].getContentData() ) .concat( [ { 'type': '/' + type } ] ) ) ); } diff --git a/modules/es/views/es.ContentView.js b/modules/es/views/es.ContentView.js index 419f70de9d..1251fef9e4 100644 --- a/modules/es/views/es.ContentView.js +++ b/modules/es/views/es.ContentView.js @@ -500,7 +500,7 @@ es.ContentView.prototype.scanBoundaries = function() { * the words. */ // Get and cache a copy of all content, the make a plain-text version of the cached content - var data = this.contentCache = this.model.getContent(), + var data = this.contentCache = this.model.getContentData(), text = ''; for ( var i = 0, length = data.length; i < length; i++ ) { text += typeof data[i] === 'string' ? data[i] : data[i][0]; @@ -712,7 +712,7 @@ es.ContentView.prototype.appendLine = function( range, wordOffset, fractional ) $line[0].innerHTML = this.getHtml( range ); // Overwrite/append line information this.lines[rs.line] = { - 'text': this.model.getText( range ), + 'text': this.model.getContentText( range ), 'range': range, 'width': $line.outerWidth(), 'height': $line.outerHeight(), diff --git a/modules/es/views/es.SurfaceView.js b/modules/es/views/es.SurfaceView.js index a1f5edc78f..178f3bdcd4 100644 --- a/modules/es/views/es.SurfaceView.js +++ b/modules/es/views/es.SurfaceView.js @@ -589,7 +589,7 @@ es.SurfaceView.prototype.handleDelete = function( backspace ) { this.model.transact( tx, true ); } else { tx = this.model.getDocument().prepareInsertion( - targetOffset, sourceNode.model.getContent() + targetOffset, sourceNode.model.getContentData() ); this.model.transact( tx, true ); diff --git a/tests/es/es.DocumentModel.test.js b/tests/es/es.DocumentModel.test.js index 21619625d0..e88d685be1 100644 --- a/tests/es/es.DocumentModel.test.js +++ b/tests/es/es.DocumentModel.test.js @@ -85,50 +85,50 @@ test( 'es.DocumentModel.getRelativeContentOffset', 7, function() { ); } ); -test( 'es.DocumentModel.getContent', 6, function() { +test( 'es.DocumentModel.getContentData', 6, function() { var documentModel = es.DocumentModel.newFromPlainObject( esTest.obj ), childNodes = documentModel.getChildren(); // Test 1 deepEqual( - childNodes[0].getContent( new es.Range( 1, 3 ) ), + childNodes[0].getContentData( new es.Range( 1, 3 ) ), [ ['b', { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }], ['c', { 'type': 'textStyle/italic', 'hash': '{"type":"textStyle/italic"}' }] ], - 'getContent can return an ending portion of the content' + 'getContentData can return an ending portion of the content' ); // Test 2 deepEqual( - childNodes[0].getContent( new es.Range( 0, 2 ) ), + childNodes[0].getContentData( new es.Range( 0, 2 ) ), ['a', ['b', { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }]], - 'getContent can return a beginning portion of the content' + 'getContentData can return a beginning portion of the content' ); // Test 3 deepEqual( - childNodes[0].getContent( new es.Range( 1, 2 ) ), + childNodes[0].getContentData( new es.Range( 1, 2 ) ), [['b', { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }]], - 'getContent can return a middle portion of the content' + 'getContentData can return a middle portion of the content' ); // Test 4 try { - childNodes[0].getContent( new es.Range( -1, 3 ) ); + childNodes[0].getContentData( new es.Range( -1, 3 ) ); } catch ( negativeIndexError ) { - ok( true, 'getContent throws exceptions when given a range with start < 0' ); + ok( true, 'getContentData throws exceptions when given a range with start < 0' ); } // Test 5 try { - childNodes[0].getContent( new es.Range( 0, 4 ) ); + childNodes[0].getContentData( new es.Range( 0, 4 ) ); } catch ( outOfRangeError ) { - ok( true, 'getContent throws exceptions when given a range with end > length' ); + ok( true, 'getContentData throws exceptions when given a range with end > length' ); } // Test 6 - deepEqual( childNodes[2].getContent(), ['h'], 'Content can be extracted from nodes' ); + deepEqual( childNodes[2].getContentData(), ['h'], 'Content can be extracted from nodes' ); } ); test( 'es.DocumentModel.getIndexOfAnnotation', 3, function() { diff --git a/tests/es/es.TransactionProcessor.test.js b/tests/es/es.TransactionProcessor.test.js index 363ddb1802..fedcb0a725 100644 --- a/tests/es/es.TransactionProcessor.test.js +++ b/tests/es/es.TransactionProcessor.test.js @@ -94,7 +94,7 @@ test( 'es.TransactionProcessor', 29, function() { // Test 6 deepEqual( - documentModel.getChildren()[0].getContent(), + documentModel.getChildren()[0].getContentData(), [ 'a', ['b', { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }], @@ -120,7 +120,7 @@ test( 'es.TransactionProcessor', 29, function() { // Test 8 deepEqual( - documentModel.getChildren()[0].getContent(), + documentModel.getChildren()[0].getContentData(), [ 'a', ['b', { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }], @@ -145,7 +145,7 @@ test( 'es.TransactionProcessor', 29, function() { // Test 10 deepEqual( - documentModel.getChildren()[0].getContent(), + documentModel.getChildren()[0].getContentData(), ['a'], 'commit keeps model tree up to date with removals' ); @@ -166,7 +166,7 @@ test( 'es.TransactionProcessor', 29, function() { // Test 12 deepEqual( - documentModel.getChildren()[0].getContent(), + documentModel.getChildren()[0].getContentData(), [ 'a', ['b', { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }], @@ -197,14 +197,14 @@ test( 'es.TransactionProcessor', 29, function() { // Test 14 deepEqual( - documentModel.getChildren()[0].getContent(), + documentModel.getChildren()[0].getContentData(), ['a'], 'commit keeps model tree up to date with paragraph split (paragraph 1)' ); // Test 15 deepEqual( - documentModel.getChildren()[1].getContent(), + documentModel.getChildren()[1].getContentData(), [ ['b', { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }], ['c', { 'type': 'textStyle/italic', 'hash': '{"type":"textStyle/italic"}' }] @@ -228,7 +228,7 @@ test( 'es.TransactionProcessor', 29, function() { // Test 17 deepEqual( - documentModel.getChildren()[0].getContent(), + documentModel.getChildren()[0].getContentData(), [ 'a', ['b', { 'type': 'textStyle/bold', 'hash': '{"type":"textStyle/bold"}' }], @@ -272,21 +272,21 @@ test( 'es.TransactionProcessor', 29, function() { // Test 21 deepEqual( - documentModel.children[1].children[0].children[0].children[1].children[0].children[0].getContent(), + documentModel.children[1].children[0].children[0].children[1].children[0].children[0].getContentData(), [ 'f' ], 'removal keeps model tree up to date with list item merge (first list item)' ); // Test 22 deepEqual( - documentModel.children[1].children[0].children[0].children[1].children[1].children[0].getContent(), + documentModel.children[1].children[0].children[0].children[1].children[1].children[0].getContentData(), [ 'g' ], 'removal keeps model tree up to date with list item merge (second list item)' ); // Test 23 deepEqual( - documentModel.children[2].getContent(), + documentModel.children[2].getContentData(), [ 'h' ], 'rollback keeps model tree up to date with list item split (final paragraph)' ); @@ -322,28 +322,28 @@ test( 'es.TransactionProcessor', 29, function() { // Test 26 deepEqual( - documentModel.children[1].children[0].children[0].children[1].children[0].children[0].getContent(), + documentModel.children[1].children[0].children[0].children[1].children[0].children[0].getContentData(), [ 'e' ], 'rollback keeps model tree up to date with list item split (first list item)' ); // Test 27 deepEqual( - documentModel.children[1].children[0].children[0].children[1].children[1].children[0].getContent(), + documentModel.children[1].children[0].children[0].children[1].children[1].children[0].getContentData(), [ 'f' ], 'rollback keeps model tree up to date with list item split (second list item)' ); // Test 28 deepEqual( - documentModel.children[1].children[0].children[0].children[1].children[2].children[0].getContent(), + documentModel.children[1].children[0].children[0].children[1].children[2].children[0].getContentData(), [ 'g' ], 'rollback keeps model tree up to date with list item split (third list item)' ); // Test 29 deepEqual( - documentModel.children[2].getContent(), + documentModel.children[2].getContentData(), [ 'h' ], 'rollback keeps model tree up to date with list item split (final paragraph)' );