diff --git a/.docs/categories.json b/.docs/categories.json index d5eba63906..4d8cebe92d 100644 --- a/.docs/categories.json +++ b/.docs/categories.json @@ -37,7 +37,6 @@ "ve.dm.ModelRegistry", "ve.dm.Converter", "ve.dm.DataString", - "ve.dm.DocumentSlice", "ve.dm.DocumentSynchronizer", "ve.dm.IndexValueStore", "ve.dm.NodeFactory", @@ -66,7 +65,7 @@ }, { "name": "Linear data", - "classes": ["ve.dm.*LinearData"] + "classes": ["ve.dm.*LinearData*"] }, { "name": "Models", diff --git a/VisualEditor.php b/VisualEditor.php index 25c1d28073..1829e0dcb7 100644 --- a/VisualEditor.php +++ b/VisualEditor.php @@ -294,11 +294,11 @@ $wgResourceModules += array( 've/dm/ve.dm.DataString.js', 've/dm/ve.dm.Document.js', 've/dm/ve.dm.LinearData.js', - 've/dm/ve.dm.DocumentSlice.js', 've/dm/ve.dm.DocumentSynchronizer.js', 've/dm/ve.dm.IndexValueStore.js', 've/dm/ve.dm.Converter.js', + 've/dm/lineardata/ve.dm.SlicedLinearData.js', 've/dm/lineardata/ve.dm.ElementLinearData.js', 've/dm/lineardata/ve.dm.MetaLinearData.js', diff --git a/demos/ve/index.php b/demos/ve/index.php index eef13e0d35..c3e5f29689 100644 --- a/demos/ve/index.php +++ b/demos/ve/index.php @@ -147,10 +147,10 @@ $html = file_get_contents( $page ); - + diff --git a/modules/ve/ce/ve.ce.Surface.js b/modules/ve/ce/ve.ce.Surface.js index c3d4d683f1..db9867a721 100644 --- a/modules/ve/ce/ve.ce.Surface.js +++ b/modules/ve/ce/ve.ce.Surface.js @@ -655,7 +655,7 @@ ve.ce.Surface.prototype.onCopy = function ( e ) { clipboardIndex, clipboardItem, scrollTop, view = this, - slice = this.documentView.model.getSlice( this.model.getSelection() ), + slice = this.documentView.model.getSlicedLinearData( this.model.getSelection() ), clipboardData = e.originalEvent.clipboardData, $window = $( ve.Element.getWindow( this.$$.context ) ); @@ -666,7 +666,7 @@ ve.ce.Surface.prototype.onCopy = function ( e ) { ve.dm.converter.store = this.documentView.model.getStore(); ve.dm.converter.internalList = this.documentView.model.getInternalList(); - ve.dm.converter.getDomSubtreeFromData( slice.getBalancedData(), this.$pasteTarget[0] ); + ve.dm.converter.getDomSubtreeFromData( slice.getData(), this.$pasteTarget[0] ); clipboardItem = { 'data': slice, 'hash': null }; clipboardIndex = this.clipboard.push( clipboardItem ) - 1; @@ -813,7 +813,8 @@ ve.ce.Surface.prototype.afterPaste = function () { if ( !beforePasteData.plain ) { beforePasteData.plain = this.$pasteTarget.text(); } - slice = new ve.dm.DocumentSlice( + slice = new ve.dm.ElementLinearDataSlice( + new ve.dm.IndexValueStore(), ve.splitClusters( // TODO: handle plain text line breaks better beforePasteData.plain.replace( /\n+/gm, ' ' ) @@ -825,7 +826,7 @@ ve.ce.Surface.prototype.afterPaste = function () { // Try to paste in the orignal data // Take a copy to prevent the data being annotated a second time in the catch block // and to prevent actions in the data model affecting view.clipboard - pasteData = ve.copy( slice.getData() ); + pasteData = ve.copy( slice.getOriginalData() ); // Annotate ve.dm.Document.addAnnotationsToData( pasteData, this.model.getInsertionAnnotations() ); @@ -839,7 +840,7 @@ ve.ce.Surface.prototype.afterPaste = function () { } catch ( err ) { // If that fails, balance the data before pasting // Take a copy to prevent actions in the data model affecting view.clipboard - pasteData = ve.copy( slice.getBalancedData() ); + pasteData = ve.copy( slice.getData() ); // Annotate ve.dm.Document.addAnnotationsToData( pasteData, this.model.getInsertionAnnotations() ); diff --git a/modules/ve/dm/lineardata/ve.dm.ElementLinearData.js b/modules/ve/dm/lineardata/ve.dm.ElementLinearData.js index 1944af343a..96df182e4c 100644 --- a/modules/ve/dm/lineardata/ve.dm.ElementLinearData.js +++ b/modules/ve/dm/lineardata/ve.dm.ElementLinearData.js @@ -1,5 +1,5 @@ /*! - * VisualEditor ElementLinearData class. + * VisualEditor ElementLinearData classes. * * Class containing element linear data and an index-value store. * @@ -778,3 +778,27 @@ ve.dm.ElementLinearData.prototype.remapInteralListIndexes = function ( mapping ) } } }; + +/** + * Sliced element linear data storage + * + * @class + * @extends ve.dm.ElementLinearData + * @mixins ve.dm.SlicedLinearData + * @constructor + * @param {ve.dm.IndexValueStore} store Index-value store + * @param {Array} [data] Linear data + * @param {ve.Range} [range] Original context within data + */ +ve.dm.ElementLinearDataSlice = function VeDmElementLinearDataSlice( store, data, range ) { + ve.dm.ElementLinearData.call( this, store, data ); + + // Mixins + ve.dm.SlicedLinearData.call( this, range ); +}; + +/* Inheritance */ + +ve.inheritClass( ve.dm.ElementLinearDataSlice, ve.dm.ElementLinearData ); + +ve.mixinClass( ve.dm.ElementLinearDataSlice, ve.dm.SlicedLinearData ); diff --git a/modules/ve/dm/lineardata/ve.dm.SlicedLinearData.js b/modules/ve/dm/lineardata/ve.dm.SlicedLinearData.js new file mode 100644 index 0000000000..19c9a2be05 --- /dev/null +++ b/modules/ve/dm/lineardata/ve.dm.SlicedLinearData.js @@ -0,0 +1,58 @@ +/*! + * VisualEditor DataModel SlicedLinearData class. + * + * @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt + * @license The MIT License (MIT); see LICENSE.txt + */ + +/** + * Sliced linear data storage. + * + * @abstract + * @constructor + * @param {ve.Range} [range] Original context within data + */ +ve.dm.SlicedLinearData = function VeDmSlicedLinearData( range ) { + // Clone data + this.data = ve.copy( this.data ); + + // Properties + this.range = range || new ve.Range( 0, this.data.length ); +}; + +/* Methods */ + +/** + * Get the range representing the original context within the data + * + * @returns {ve.Range} Original context within data + */ +ve.dm.SlicedLinearData.prototype.getRange = function () { + return this.range; +}; + +/** + * Get the original data excluding any balancing data that was added. + * + * @method + * @param {number} [offset] Offset to get data from + * @returns {Object|Array} Data from index, or all data (by reference) + */ +ve.dm.SlicedLinearData.prototype.getOriginalData = function ( offset ) { + return offset === undefined ? this.data.slice( this.range.start, this.range.end ) : this.data[this.range.start + offset]; +}; + +/** + * Run all elements in this document slice through getClonedElement(). This should be done if + * you intend to insert the sliced data back into the document as a copy of the original data + * (e.g. for copy and paste). + */ +ve.dm.SlicedLinearData.prototype.cloneElements = function () { + var i, len, node; + for ( i = 0, len = this.getLength(); i < len; i++ ) { + if ( this.isOpenElementData( i ) ) { + node = ve.dm.nodeFactory.create( this.getType( i ), [], this.getData( i ) ); + this.data[i] = node.getClonedElement(); + } + } +}; diff --git a/modules/ve/dm/ve.dm.Document.js b/modules/ve/dm/ve.dm.Document.js index e5b372462a..874fd79d83 100644 --- a/modules/ve/dm/ve.dm.Document.js +++ b/modules/ve/dm/ve.dm.Document.js @@ -824,24 +824,24 @@ ve.dm.Document.prototype.fixupInsertion = function ( data, offset ) { /** * Get the document data for a range. * - * Data will be fixed up so that unopened closings and unclosed openings in the document data slice - * are balanced. + * Data will be fixed up so that unopened closings and unclosed openings in the + * linear data slice are balanced. * * @param {ve.Range} range Range to get contents of - * @returns {ve.dm.DocumentSlice} Balanced slice of linear model data + * @returns {ve.dm.ElementLinearDataSlice} Balanced slice of linear model data */ -ve.dm.Document.prototype.getSlice = function ( range ) { +ve.dm.Document.prototype.getSlicedLinearData = function ( range ) { var first, last, firstNode, lastNode, node = this.getNodeFromOffset( range.start ), selection = this.selectNodes( range, 'siblings' ), addOpenings = [], addClosings = []; if ( selection.length === 0 ) { - return new ve.dm.DocumentSlice( [] ); + return new ve.dm.ElementLinearDataSlice( this.getStore(), [] ); } if ( selection.length === 1 && selection[0].range && selection[0].range.equalsSelection( range ) ) { // Nothing to fix up - return new ve.dm.DocumentSlice( this.data.slice( range.start, range.end ) ); + return new ve.dm.ElementLinearDataSlice( this.getStore(), this.data.slice( range.start, range.end ) ); } first = selection[0]; @@ -882,7 +882,8 @@ ve.dm.Document.prototype.getSlice = function ( range ) { } } - return new ve.dm.DocumentSlice( + return new ve.dm.ElementLinearDataSlice( + this.getStore(), addOpenings.reverse() .concat( this.data.slice( range.start, range.end ) ) .concat( addClosings ), diff --git a/modules/ve/dm/ve.dm.DocumentSlice.js b/modules/ve/dm/ve.dm.DocumentSlice.js deleted file mode 100644 index 9d6d1a1714..0000000000 --- a/modules/ve/dm/ve.dm.DocumentSlice.js +++ /dev/null @@ -1,56 +0,0 @@ -/*! - * VisualEditor DataModel DocumentSlice class. - * - * @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt - * @license The MIT License (MIT); see LICENSE.txt - */ - -/** - * DataModel document slice. - * - * @constructor - * @param {Array} data Balanced sliced data (will be deep copied internally) - * @param {ve.Range} [range] Original context within data - */ -ve.dm.DocumentSlice = function VeDmDocumentSlice( data, range ) { - // Properties - this.data = ve.copy( data ); - this.range = range || new ve.Range( 0, data.length ); -}; - -/* Methods */ - -/** - * Get a deep copy of the sliced data. - * - * @method - * @returns {Array} Document data - */ -ve.dm.DocumentSlice.prototype.getData = function () { - return this.data.slice( this.range.start, this.range.end ); -}; - -/** - * Get a balanced version of the sliced data. - * - * @method - * @returns {Array} Document data - */ -ve.dm.DocumentSlice.prototype.getBalancedData = function () { - return this.data.slice( 0 ); -}; - -/** - * Run all elements in this document slice through getClonedElement(). This should be done if - * you intend to insert the sliced data back into the document as a copy of the original data - * (e.g. for copy and paste). - */ -ve.dm.DocumentSlice.prototype.cloneElements = function () { - var i, len, node; - for ( i = 0, len = this.data.length; i < len; i++ ) { - if ( this.data[i].type && this.data[i].type.charAt( 0 ) !== '/' ) { - node = ve.dm.nodeFactory.create( this.data[i].type, [], this.data[i] ); - this.data[i] = node.getClonedElement(); - } - } -}; diff --git a/modules/ve/test/dm/ve.dm.Document.test.js b/modules/ve/test/dm/ve.dm.Document.test.js index eef892050b..7335e09779 100644 --- a/modules/ve/test/dm/ve.dm.Document.test.js +++ b/modules/ve/test/dm/ve.dm.Document.test.js @@ -271,19 +271,21 @@ QUnit.test( 'selectNodes', function ( assert ) { } ); QUnit.test( 'getSlice', function ( assert ) { - var i, data, doc = ve.dm.example.createExampleDocument(), + var i, expectedData, doc = ve.dm.example.createExampleDocument(), cases = [ { 'msg': 'empty range', 'range': new ve.Range( 2, 2 ), - 'expected': [] + 'expected': [], + 'expectedRange': new ve.Range( 0 ) }, { 'msg': 'range with one character', 'range': new ve.Range( 2, 3 ), 'expected': [ ['b', [ ve.dm.example.bold ]] - ] + ], + 'expectedRange': new ve.Range( 0, 1 ) }, { 'msg': 'range with two characters', @@ -291,7 +293,8 @@ QUnit.test( 'getSlice', function ( assert ) { 'expected': [ ['b', [ ve.dm.example.bold ]], ['c', [ ve.dm.example.italic ]] - ] + ], + 'expectedRange': new ve.Range( 0, 2 ) }, { 'msg': 'range with two characters and a header closing', @@ -301,7 +304,8 @@ QUnit.test( 'getSlice', function ( assert ) { ['b', [ ve.dm.example.bold ]], ['c', [ ve.dm.example.italic ]], { 'type': '/heading' } - ] + ], + 'expectedRange': new ve.Range( 1, 4 ) }, { 'msg': 'range with one character, a header closing and a table opening', @@ -312,7 +316,8 @@ QUnit.test( 'getSlice', function ( assert ) { { 'type': '/heading' }, { 'type': 'table' }, { 'type': '/table' } - ] + ], + 'expectedRange': new ve.Range( 1, 4 ) }, { 'msg': 'range from a paragraph into a list', @@ -328,7 +333,8 @@ QUnit.test( 'getSlice', function ( assert ) { { 'type': '/paragraph' }, { 'type': '/listItem' }, { 'type': '/list' } - ] + ], + 'expectedRange': new ve.Range( 1, 7 ) }, { 'msg': 'range from a paragraph inside a nested list into the next list', @@ -347,7 +353,8 @@ QUnit.test( 'getSlice', function ( assert ) { { 'type': '/list' }, { 'type': 'list', 'attributes': { 'style': 'number' } }, { 'type': '/list' } - ] + ], + 'expectedRange': new ve.Range( 5, 12 ) }, { 'msg': 'range from a paragraph inside a nested list out of both lists', @@ -364,7 +371,8 @@ QUnit.test( 'getSlice', function ( assert ) { { 'type': '/list' }, { 'type': '/listItem' }, { 'type': '/list' } - ] + ], + 'expectedRange': new ve.Range( 5, 11 ) }, { 'msg': 'range from a paragraph inside a nested list out of the outer listItem', @@ -379,16 +387,22 @@ QUnit.test( 'getSlice', function ( assert ) { { 'type': '/listItem' }, { 'type': '/list' }, { 'type': '/listItem' } - ] + ], + 'expectedRange': new ve.Range( 4, 9 ) } ]; - QUnit.expect( cases.length ); + QUnit.expect( 2 * cases.length ); for ( i = 0; i < cases.length; i++ ) { - data = ve.dm.example.preprocessAnnotations( cases[i].expected.slice(), doc.getStore() ); + expectedData = ve.dm.example.preprocessAnnotations( cases[i].expected.slice(), doc.getStore() ).getData(); assert.deepEqual( - doc.getSlice( cases[i].range ).getBalancedData(), - data.getData(), - cases[i].msg + doc.getSlicedLinearData( cases[i].range ).getData(), + expectedData, + cases[i].msg + ': balanced data' + ); + assert.deepEqual( + doc.getSlicedLinearData( cases[i].range ).getRange(), + cases[i].expectedRange, + cases[i].msg + ': range' ); } } ); diff --git a/modules/ve/test/index.php b/modules/ve/test/index.php index d188d32e77..e3f1636c94 100644 --- a/modules/ve/test/index.php +++ b/modules/ve/test/index.php @@ -92,10 +92,10 @@ - +