2012-08-02 00:59:38 +00:00
|
|
|
/**
|
|
|
|
* VisualEditor data model Fragment class.
|
|
|
|
*
|
|
|
|
* @copyright 2011-2012 VisualEditor Team and others; see AUTHORS.txt
|
|
|
|
* @license The MIT License (MIT); see LICENSE.txt
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* DataModel document fragment.
|
|
|
|
*
|
|
|
|
* @class
|
|
|
|
* @constructor
|
|
|
|
* @param {ve.dm.Surface} surface Target surface
|
|
|
|
* @param {ve.Range} [range] Range within target document, current selection used by default
|
2012-08-10 23:59:50 +00:00
|
|
|
* @param {Boolean} [autoSelect] Update the surface's selection when making changes
|
2012-08-02 00:59:38 +00:00
|
|
|
*/
|
Object management: Object create/inherit/clone utilities
* For the most common case:
- replace ve.extendClass with ve.inheritClass (chose slightly
different names to detect usage of the old/new one, and I
like 'inherit' better).
- move it up to below the constructor, see doc block for why.
* Cases where more than 2 arguments were passed to
ve.extendClass are handled differently depending on the case.
In case of a longer inheritance tree, the other arguments
could be omitted (like in "ve.ce.FooBar, ve.FooBar,
ve.Bar". ve.ce.FooBar only needs to inherit from ve.FooBar,
because ve.ce.FooBar inherits from ve.Bar).
In the case of where it previously had two mixins with
ve.extendClass(), either one becomes inheritClass and one
a mixin, both to mixinClass().
No visible changes should come from this commit as the
instances still all have the same visible properties in the
end. No more or less than before.
* Misc.:
- Be consistent in calling parent constructors in the
same order as the inheritance.
- Add missing @extends and @param documentation.
- Replace invalid {Integer} type hint with {Number}.
- Consistent doc comments order:
@class, @abstract, @constructor, @extends, @params.
- Fix indentation errors
A fairly common mistake was a superfluous space before the
identifier on the assignment line directly below the
documentation comment.
$ ack "^ [^*]" --js modules/ve
- Typo "Inhertiance" -> "Inheritance".
- Replacing the other confusing comment "Inheritance" (inside
the constructor) with "Parent constructor".
- Add missing @abstract for ve.ui.Tool.
- Corrected ve.FormatDropdownTool to ve.ui.FormatDropdownTool.js
- Add function names to all @constructor functions. Now that we
have inheritance it is important and useful to have these
functions not be anonymous.
Example of debug shot: http://cl.ly/image/1j3c160w3D45
Makes the difference between
< documentNode;
> ve_dm_DocumentNode
...
: ve_dm_BranchNode
...
: ve_dm_Node
...
: ve_dm_Node
...
: Object
...
without names (current situation):
< documentNode;
> Object
...
: Object
...
: Object
...
: Object
...
: Object
...
though before this commit, it really looks like this
(flattened since ve.extendClass really did a mixin):
< documentNode;
> Object
...
...
...
Pattern in Sublime (case-sensitive) to find nameless
constructor functions:
"^ve\..*\.([A-Z])([^\.]+) = function \("
Change-Id: Iab763954fb8cf375900d7a9a92dec1c755d5407e
2012-09-05 06:07:47 +00:00
|
|
|
ve.dm.SurfaceFragment = function ve_dm_SurfaceFragment( surface, range, autoSelect ) {
|
2012-08-17 17:48:40 +00:00
|
|
|
// Short-circuit for missing-surface null fragment
|
2012-08-02 00:59:38 +00:00
|
|
|
if ( !surface ) {
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Properties
|
2012-08-17 17:48:40 +00:00
|
|
|
this.range = range && range instanceof ve.Range ? range : surface.getSelection();
|
|
|
|
// Short-circuit for invalid range null fragment
|
|
|
|
if ( !this.range ) {
|
|
|
|
return this;
|
|
|
|
}
|
2012-08-02 00:59:38 +00:00
|
|
|
this.surface = surface;
|
|
|
|
this.document = surface.getDocument();
|
2012-08-10 23:59:50 +00:00
|
|
|
this.autoSelect = !!autoSelect;
|
2012-08-02 00:59:38 +00:00
|
|
|
|
|
|
|
// Events
|
2012-08-17 17:48:40 +00:00
|
|
|
surface.on( 'transact', ve.bind( this.onTransact, this ) );
|
2012-08-02 00:59:38 +00:00
|
|
|
|
|
|
|
// Initialization
|
|
|
|
var length = this.document.getLength();
|
|
|
|
this.range = new ve.Range(
|
|
|
|
// Clamp range to valid document offsets
|
|
|
|
Math.min( Math.max( this.range.from, 0 ), length ),
|
|
|
|
Math.min( Math.max( this.range.to, 0 ), length )
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Methods */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Responds to transactions being processed on the document.
|
|
|
|
*
|
|
|
|
* This keeps the range of this fragment valid, even while other transactions are being processed.
|
|
|
|
*
|
|
|
|
* @method
|
2012-08-21 00:51:52 +00:00
|
|
|
* @param {ve.dm.Transaction[]} txs Transactions that have just been processed
|
2012-08-02 00:59:38 +00:00
|
|
|
*/
|
Remainder JSHint fixes on modules/ve/*
[jshint]
ce/ve.ce.Surface.js: line 670, col 9, Too many var statements.
ce/ve.ce.Surface.js: line 695, col 6, Missing semicolon.
ce/ve.ce.Surface.js: line 726, col 22, Expected '===' and instead saw '=='.
ce/ve.ce.Surface.js: line 726, col 41, Expected '===' and instead saw '=='.
ce/ve.ce.Surface.js: line 733, col 13, Too many var statements.
ce/ve.ce.Surface.js: line 734, col 24, Expected '===' and instead saw '=='.
ce/ve.ce.Surface.js: line 1013, col 13, Too many var statements.
ce/ve.ce.Surface.js: line 1019, col 17, Too many var statements.
ce/ve.ce.Surface.js: line 1023, col 18, Too many ar statements.
ce/ve.ce.Surface.js: line 1027, col 13, Too many var statements.
dm/annotations/ve.dm.LinkAnnotation.js: line 70, col 52, Insecure '.'.
dm/ve.dm.Converter.js: line 383, col 29, Empty block.
dm/ve.dm.Converter.js: line 423, col 33, Empty block.
Commands:
* jshint .
* ack '(if|else|function|switch|for|while)\('
* Sublime Text 2:
Find(*): (if|else|function|switch|for|while)\(
Replace: $1 (
* ack ' ' -Q # double spaces, except in certain comments
Change-Id: I8e34bf2924bc8688fdf8acef08bbc4f6707e93be
2012-09-02 21:45:01 +00:00
|
|
|
ve.dm.SurfaceFragment.prototype.onTransact = function ( txs ) {
|
|
|
|
for ( var i = 0; i < txs.length; i++ ) {
|
2012-08-21 00:49:16 +00:00
|
|
|
this.range = txs[i].translateRange( this.range );
|
|
|
|
}
|
2012-08-02 00:59:38 +00:00
|
|
|
};
|
|
|
|
|
2012-08-17 17:48:40 +00:00
|
|
|
/**
|
|
|
|
* Gets the surface this fragment is a part of.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @returns {ve.dm.Surface} Surface of fragment
|
|
|
|
*/
|
Remainder JSHint fixes on modules/ve/*
[jshint]
ce/ve.ce.Surface.js: line 670, col 9, Too many var statements.
ce/ve.ce.Surface.js: line 695, col 6, Missing semicolon.
ce/ve.ce.Surface.js: line 726, col 22, Expected '===' and instead saw '=='.
ce/ve.ce.Surface.js: line 726, col 41, Expected '===' and instead saw '=='.
ce/ve.ce.Surface.js: line 733, col 13, Too many var statements.
ce/ve.ce.Surface.js: line 734, col 24, Expected '===' and instead saw '=='.
ce/ve.ce.Surface.js: line 1013, col 13, Too many var statements.
ce/ve.ce.Surface.js: line 1019, col 17, Too many var statements.
ce/ve.ce.Surface.js: line 1023, col 18, Too many ar statements.
ce/ve.ce.Surface.js: line 1027, col 13, Too many var statements.
dm/annotations/ve.dm.LinkAnnotation.js: line 70, col 52, Insecure '.'.
dm/ve.dm.Converter.js: line 383, col 29, Empty block.
dm/ve.dm.Converter.js: line 423, col 33, Empty block.
Commands:
* jshint .
* ack '(if|else|function|switch|for|while)\('
* Sublime Text 2:
Find(*): (if|else|function|switch|for|while)\(
Replace: $1 (
* ack ' ' -Q # double spaces, except in certain comments
Change-Id: I8e34bf2924bc8688fdf8acef08bbc4f6707e93be
2012-09-02 21:45:01 +00:00
|
|
|
ve.dm.SurfaceFragment.prototype.getSurface = function () {
|
2012-08-17 17:48:40 +00:00
|
|
|
return this.surface;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the document of the surface this fragment is a part of.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @returns {ve.dm.Document} Document of surface of fragment
|
|
|
|
*/
|
Remainder JSHint fixes on modules/ve/*
[jshint]
ce/ve.ce.Surface.js: line 670, col 9, Too many var statements.
ce/ve.ce.Surface.js: line 695, col 6, Missing semicolon.
ce/ve.ce.Surface.js: line 726, col 22, Expected '===' and instead saw '=='.
ce/ve.ce.Surface.js: line 726, col 41, Expected '===' and instead saw '=='.
ce/ve.ce.Surface.js: line 733, col 13, Too many var statements.
ce/ve.ce.Surface.js: line 734, col 24, Expected '===' and instead saw '=='.
ce/ve.ce.Surface.js: line 1013, col 13, Too many var statements.
ce/ve.ce.Surface.js: line 1019, col 17, Too many var statements.
ce/ve.ce.Surface.js: line 1023, col 18, Too many ar statements.
ce/ve.ce.Surface.js: line 1027, col 13, Too many var statements.
dm/annotations/ve.dm.LinkAnnotation.js: line 70, col 52, Insecure '.'.
dm/ve.dm.Converter.js: line 383, col 29, Empty block.
dm/ve.dm.Converter.js: line 423, col 33, Empty block.
Commands:
* jshint .
* ack '(if|else|function|switch|for|while)\('
* Sublime Text 2:
Find(*): (if|else|function|switch|for|while)\(
Replace: $1 (
* ack ' ' -Q # double spaces, except in certain comments
Change-Id: I8e34bf2924bc8688fdf8acef08bbc4f6707e93be
2012-09-02 21:45:01 +00:00
|
|
|
ve.dm.SurfaceFragment.prototype.getDocument = function () {
|
2012-08-17 17:48:40 +00:00
|
|
|
return this.document;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the range of this fragment within the surface.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @returns {ve.Range} Surface range
|
|
|
|
*/
|
Remainder JSHint fixes on modules/ve/*
[jshint]
ce/ve.ce.Surface.js: line 670, col 9, Too many var statements.
ce/ve.ce.Surface.js: line 695, col 6, Missing semicolon.
ce/ve.ce.Surface.js: line 726, col 22, Expected '===' and instead saw '=='.
ce/ve.ce.Surface.js: line 726, col 41, Expected '===' and instead saw '=='.
ce/ve.ce.Surface.js: line 733, col 13, Too many var statements.
ce/ve.ce.Surface.js: line 734, col 24, Expected '===' and instead saw '=='.
ce/ve.ce.Surface.js: line 1013, col 13, Too many var statements.
ce/ve.ce.Surface.js: line 1019, col 17, Too many var statements.
ce/ve.ce.Surface.js: line 1023, col 18, Too many ar statements.
ce/ve.ce.Surface.js: line 1027, col 13, Too many var statements.
dm/annotations/ve.dm.LinkAnnotation.js: line 70, col 52, Insecure '.'.
dm/ve.dm.Converter.js: line 383, col 29, Empty block.
dm/ve.dm.Converter.js: line 423, col 33, Empty block.
Commands:
* jshint .
* ack '(if|else|function|switch|for|while)\('
* Sublime Text 2:
Find(*): (if|else|function|switch|for|while)\(
Replace: $1 (
* ack ' ' -Q # double spaces, except in certain comments
Change-Id: I8e34bf2924bc8688fdf8acef08bbc4f6707e93be
2012-09-02 21:45:01 +00:00
|
|
|
ve.dm.SurfaceFragment.prototype.getRange = function () {
|
2012-08-17 17:48:40 +00:00
|
|
|
return this.range.clone();
|
|
|
|
};
|
|
|
|
|
2012-08-02 00:59:38 +00:00
|
|
|
/**
|
|
|
|
* Checks if this is a null fragment.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @returns {Boolean} Fragment is a null fragment
|
|
|
|
*/
|
Remainder JSHint fixes on modules/ve/*
[jshint]
ce/ve.ce.Surface.js: line 670, col 9, Too many var statements.
ce/ve.ce.Surface.js: line 695, col 6, Missing semicolon.
ce/ve.ce.Surface.js: line 726, col 22, Expected '===' and instead saw '=='.
ce/ve.ce.Surface.js: line 726, col 41, Expected '===' and instead saw '=='.
ce/ve.ce.Surface.js: line 733, col 13, Too many var statements.
ce/ve.ce.Surface.js: line 734, col 24, Expected '===' and instead saw '=='.
ce/ve.ce.Surface.js: line 1013, col 13, Too many var statements.
ce/ve.ce.Surface.js: line 1019, col 17, Too many var statements.
ce/ve.ce.Surface.js: line 1023, col 18, Too many ar statements.
ce/ve.ce.Surface.js: line 1027, col 13, Too many var statements.
dm/annotations/ve.dm.LinkAnnotation.js: line 70, col 52, Insecure '.'.
dm/ve.dm.Converter.js: line 383, col 29, Empty block.
dm/ve.dm.Converter.js: line 423, col 33, Empty block.
Commands:
* jshint .
* ack '(if|else|function|switch|for|while)\('
* Sublime Text 2:
Find(*): (if|else|function|switch|for|while)\(
Replace: $1 (
* ack ' ' -Q # double spaces, except in certain comments
Change-Id: I8e34bf2924bc8688fdf8acef08bbc4f6707e93be
2012-09-02 21:45:01 +00:00
|
|
|
ve.dm.SurfaceFragment.prototype.isNull = function () {
|
2012-08-02 00:59:38 +00:00
|
|
|
return this.surface === undefined;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets a new fragment with an adjusted position
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @param {Number} [start] Adjustment for start position
|
|
|
|
* @param {Number} [end] Adjustment for end position
|
|
|
|
* @returns {ve.dm.SurfaceFragment} Adjusted fragment
|
|
|
|
*/
|
|
|
|
ve.dm.SurfaceFragment.prototype.adjustRange = function ( start, end ) {
|
|
|
|
// Handle null fragment
|
|
|
|
if ( !this.surface ) {
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
return new ve.dm.SurfaceFragment(
|
2012-08-17 17:48:40 +00:00
|
|
|
this.surface,
|
Remainder JSHint fixes on modules/ve/*
[jshint]
ce/ve.ce.Surface.js: line 670, col 9, Too many var statements.
ce/ve.ce.Surface.js: line 695, col 6, Missing semicolon.
ce/ve.ce.Surface.js: line 726, col 22, Expected '===' and instead saw '=='.
ce/ve.ce.Surface.js: line 726, col 41, Expected '===' and instead saw '=='.
ce/ve.ce.Surface.js: line 733, col 13, Too many var statements.
ce/ve.ce.Surface.js: line 734, col 24, Expected '===' and instead saw '=='.
ce/ve.ce.Surface.js: line 1013, col 13, Too many var statements.
ce/ve.ce.Surface.js: line 1019, col 17, Too many var statements.
ce/ve.ce.Surface.js: line 1023, col 18, Too many ar statements.
ce/ve.ce.Surface.js: line 1027, col 13, Too many var statements.
dm/annotations/ve.dm.LinkAnnotation.js: line 70, col 52, Insecure '.'.
dm/ve.dm.Converter.js: line 383, col 29, Empty block.
dm/ve.dm.Converter.js: line 423, col 33, Empty block.
Commands:
* jshint .
* ack '(if|else|function|switch|for|while)\('
* Sublime Text 2:
Find(*): (if|else|function|switch|for|while)\(
Replace: $1 (
* ack ' ' -Q # double spaces, except in certain comments
Change-Id: I8e34bf2924bc8688fdf8acef08bbc4f6707e93be
2012-09-02 21:45:01 +00:00
|
|
|
new ve.Range( this.range.start + ( start || 0 ), this.range.end + ( end || 0 ) ),
|
2012-08-10 23:59:50 +00:00
|
|
|
this.autoSelect
|
2012-08-02 00:59:38 +00:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets a new fragment with a zero-length selection at the start offset.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @returns {ve.dm.SurfaceFragment} Collapsed fragment
|
|
|
|
*/
|
|
|
|
ve.dm.SurfaceFragment.prototype.collapseRange = function () {
|
|
|
|
// Handle null fragment
|
|
|
|
if ( !this.surface ) {
|
|
|
|
return this;
|
|
|
|
}
|
2012-08-10 23:59:50 +00:00
|
|
|
return new ve.dm.SurfaceFragment(
|
2012-08-17 17:48:40 +00:00
|
|
|
this.surface, new ve.Range( this.range.start ), this.autoSelect
|
2012-08-10 23:59:50 +00:00
|
|
|
);
|
2012-08-02 00:59:38 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets a new fragment with a range that no longer includes leading and trailing whitespace.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @returns {ve.dm.SurfaceFragment} Trimmed fragment
|
|
|
|
*/
|
|
|
|
ve.dm.SurfaceFragment.prototype.trimRange = function () {
|
|
|
|
// Handle null fragment
|
|
|
|
if ( !this.surface ) {
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
return new ve.dm.SurfaceFragment(
|
2012-08-17 17:48:40 +00:00
|
|
|
this.surface, this.document.trimOuterSpaceFromRange( this.range ), this.autoSelect
|
2012-08-02 00:59:38 +00:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets a new fragment that covers an expanded range of the document.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @param {String} [scope=parent] Method of expansion:
|
|
|
|
* 'root': Expands to cover the entire document
|
|
|
|
* 'siblings': Expands to cover all sibling nodes
|
|
|
|
* 'closest': Expands to cover the closest common ancestor node of a specified type
|
|
|
|
* 'parent': Expands to cover the closest common parent node
|
|
|
|
* @param {String} [type] Node type to use with certain scope methods that require it
|
|
|
|
* @returns {ve.dm.SurfaceFragment} Expanded fragment
|
|
|
|
*/
|
|
|
|
ve.dm.SurfaceFragment.prototype.expandRange = function ( scope, type ) {
|
|
|
|
// Handle null fragment
|
|
|
|
if ( !this.surface ) {
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
var range, node, nodes, parent;
|
|
|
|
switch ( scope || 'parent' ) {
|
|
|
|
case 'root':
|
|
|
|
range = new ve.Range( 0, this.document.getData().length );
|
|
|
|
break;
|
|
|
|
case 'siblings':
|
|
|
|
// Grow range to cover all siblings
|
|
|
|
nodes = this.document.selectNodes( this.range, 'siblings' );
|
|
|
|
if ( nodes.length === 1 ) {
|
|
|
|
range = nodes[0].node.getOuterRange();
|
|
|
|
} else {
|
|
|
|
range = new ve.Range(
|
|
|
|
nodes[0].node.getOuterRange().start,
|
|
|
|
nodes[nodes.length - 1].node.getOuterRange().end
|
|
|
|
);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'closest':
|
|
|
|
// Grow range to cover closest common ancestor node of given type
|
|
|
|
node = this.document.selectNodes( this.range, 'siblings' )[0].node;
|
|
|
|
parent = node.getParent();
|
2012-08-17 17:48:40 +00:00
|
|
|
while ( parent && parent.getType() !== type ) {
|
2012-08-02 00:59:38 +00:00
|
|
|
node = parent;
|
|
|
|
parent = parent.getParent();
|
2012-08-17 17:48:40 +00:00
|
|
|
}
|
|
|
|
if ( !parent ) {
|
|
|
|
return new ve.dm.SurfaceFragment( null );
|
2012-08-02 00:59:38 +00:00
|
|
|
}
|
|
|
|
range = parent.getOuterRange();
|
|
|
|
break;
|
|
|
|
case 'parent':
|
|
|
|
// Grow range to cover the closest common parent node
|
|
|
|
node = this.document.selectNodes( this.range, 'siblings' )[0].node;
|
|
|
|
parent = node.getParent();
|
|
|
|
if ( !parent ) {
|
|
|
|
return new ve.dm.SurfaceFragment( null );
|
|
|
|
}
|
|
|
|
range = parent.getOuterRange();
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new Error( 'Invalid scope argument: ' + scope );
|
|
|
|
}
|
2012-08-17 17:48:40 +00:00
|
|
|
return new ve.dm.SurfaceFragment( this.surface, range, this.autoSelect );
|
2012-08-10 23:59:50 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks if the surface's selection will be updated automatically when changes are made.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @returns {Boolean} Will automatically update surface selection
|
|
|
|
*/
|
|
|
|
ve.dm.SurfaceFragment.prototype.willAutoSelect = function () {
|
|
|
|
return this.autoSelect;
|
2012-08-02 00:59:38 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get data for the fragment.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @param {Boolean} [deep] Get a deep copy of the data
|
|
|
|
* @returns {Array} Fragment data
|
|
|
|
*/
|
|
|
|
ve.dm.SurfaceFragment.prototype.getData = function ( deep ) {
|
|
|
|
// Handle null fragment
|
|
|
|
if ( !this.surface ) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
return this.document.getData( this.range, deep );
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get plain text for the fragment.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @returns {Array} Fragment text
|
|
|
|
*/
|
|
|
|
ve.dm.SurfaceFragment.prototype.getText = function () {
|
|
|
|
// Handle null fragment
|
|
|
|
if ( !this.surface ) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
var i, length,
|
|
|
|
text = '',
|
|
|
|
data = this.document.getData( this.range );
|
|
|
|
for ( i = 0, length = data.length; i < length; i++ ) {
|
|
|
|
if ( data[i].type === undefined ) {
|
|
|
|
// Annotated characters have a string at index 0, plain characters are 1-char strings
|
|
|
|
text += typeof data[i] === 'string' ? data[i] : data[i][0];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return text;
|
|
|
|
};
|
|
|
|
|
2012-08-10 23:59:50 +00:00
|
|
|
/**
|
|
|
|
* Get all leaf nodes covered by the fragment.
|
|
|
|
*
|
|
|
|
* @see {ve.Document.selectNodes} for more information about the return value.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @returns {Array} List of nodes and related information
|
|
|
|
*/
|
|
|
|
ve.dm.SurfaceFragment.prototype.getLeafNodes = function () {
|
|
|
|
// Handle null fragment
|
|
|
|
if ( !this.surface ) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
return this.document.selectNodes( this.range, 'leaves' );
|
|
|
|
};
|
|
|
|
|
2012-08-02 00:59:38 +00:00
|
|
|
/**
|
|
|
|
* Get nodes covered by the fragment.
|
|
|
|
*
|
2012-08-10 23:59:50 +00:00
|
|
|
* Does not descend into nodes that are entirely covered by the range. The result is
|
|
|
|
* similar to that of {ve.dm.SurfaceFragment.prototype.getLeafNodes} except that if a node is
|
|
|
|
* entirely covered, its children aren't returned separately.
|
|
|
|
*
|
|
|
|
* @see {ve.Document.selectNodes} for more information about the return value.
|
2012-08-02 00:59:38 +00:00
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @returns {Array} List of nodes and related information
|
|
|
|
*/
|
2012-08-10 23:59:50 +00:00
|
|
|
ve.dm.SurfaceFragment.prototype.getCoveredNodes = function () {
|
2012-08-02 00:59:38 +00:00
|
|
|
// Handle null fragment
|
|
|
|
if ( !this.surface ) {
|
|
|
|
return [];
|
|
|
|
}
|
2012-08-10 23:59:50 +00:00
|
|
|
return this.document.selectNodes( this.range, 'coveredNodes' );
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get nodes covered by the fragment.
|
|
|
|
*
|
|
|
|
* Includes adjacent siblings covered by the range, descending if the range is in a single node.
|
|
|
|
*
|
|
|
|
* @see {ve.Document.selectNodes} for more information about the return value.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @returns {Array} List of nodes and related information
|
|
|
|
*/
|
|
|
|
ve.dm.SurfaceFragment.prototype.getSiblingNodes = function () {
|
|
|
|
// Handle null fragment
|
|
|
|
if ( !this.surface ) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
return this.document.selectNodes( this.range, 'siblings' );
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Chooses whether to automatically update the surface selection when making changes.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @param {Boolean} [value=true] Automatically update surface selection
|
|
|
|
* @returns {ve.dm.SurfaceFragment} This fragment
|
|
|
|
*/
|
|
|
|
ve.dm.SurfaceFragment.prototype.autoSelect = function ( value ) {
|
|
|
|
this.autoSelect = value === undefined || !!value;
|
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Applies the fragment's range to the surface as a selection.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @returns {ve.dm.SurfaceFragment} This fragment
|
|
|
|
*/
|
|
|
|
ve.dm.SurfaceFragment.prototype.select = function () {
|
|
|
|
// Handle null fragment
|
|
|
|
if ( !this.surface ) {
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
this.surface.setSelection( this.range );
|
|
|
|
return this;
|
2012-08-02 00:59:38 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Applies an annotation to content in the fragment.
|
|
|
|
*
|
|
|
|
* To avoid problems identified in bug 33108, use the {ve.dm.SurfaceFragment.trimRange} method.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @param {String} method Mode of annotation, either 'set' or 'clear'
|
|
|
|
* @param {String} type Annotation type, for example: 'textStyle/bold'
|
|
|
|
* @param {Object} [data] Additional annotation data
|
|
|
|
* @returns {ve.dm.SurfaceFragment} This fragment
|
|
|
|
*/
|
|
|
|
ve.dm.SurfaceFragment.prototype.annotateContent = function ( method, type, data ) {
|
|
|
|
// Handle null fragment
|
|
|
|
if ( !this.surface ) {
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
var tx,
|
|
|
|
annotation = { 'type': type };
|
|
|
|
if ( data ) {
|
|
|
|
annotation.data = data;
|
|
|
|
}
|
|
|
|
if ( this.range.getLength() ) {
|
|
|
|
tx = ve.dm.Transaction.newFromAnnotation( this.document, this.range, method, annotation );
|
2012-08-24 22:24:23 +00:00
|
|
|
this.surface.change( tx, this.autoSelect && tx.translateRange( this.range ) );
|
2012-08-02 00:59:38 +00:00
|
|
|
}
|
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove content in the fragment and insert content before it.
|
|
|
|
*
|
2012-08-10 23:59:50 +00:00
|
|
|
* This will move the fragment's range to the end of the insertion and make it zero-length.
|
2012-08-02 00:59:38 +00:00
|
|
|
*
|
|
|
|
* @method
|
2012-08-10 23:59:50 +00:00
|
|
|
* @param {String|Array} content Content to insert, can be either a string or array of data
|
2012-08-02 00:59:38 +00:00
|
|
|
* @param {Boolean} annotate Content should be automatically annotated to match surrounding content
|
|
|
|
* @returns {ve.dm.SurfaceFragment} This fragment
|
|
|
|
*/
|
|
|
|
ve.dm.SurfaceFragment.prototype.insertContent = function ( content, annotate ) {
|
|
|
|
// Handle null fragment
|
|
|
|
if ( !this.surface ) {
|
|
|
|
return this;
|
|
|
|
}
|
2012-08-10 23:59:50 +00:00
|
|
|
var tx, annotations;
|
|
|
|
if ( this.range.getLength() ) {
|
|
|
|
this.removeContent();
|
|
|
|
}
|
|
|
|
// Auto-convert content to array of plain text characters
|
|
|
|
if ( typeof content === 'string' ) {
|
|
|
|
content = content.split( '' );
|
|
|
|
}
|
|
|
|
if ( content.length ) {
|
|
|
|
if ( annotate ) {
|
|
|
|
annotations = this.document.getAnnotationsFromOffset( this.range.start - 1 );
|
2012-08-24 02:06:36 +00:00
|
|
|
if ( annotations.getLength() > 0 ) {
|
2012-08-10 23:59:50 +00:00
|
|
|
ve.dm.Document.addAnnotationsToData( content, annotations );
|
|
|
|
}
|
|
|
|
}
|
2012-08-24 22:24:23 +00:00
|
|
|
tx = ve.dm.Transaction.newFromInsertion( this.document, this.range.start, content );
|
|
|
|
this.surface.change( tx, this.autoSelect && tx.translateRange( this.range ) );
|
2012-08-10 23:59:50 +00:00
|
|
|
}
|
2012-08-02 00:59:38 +00:00
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes content in the fragment.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @returns {ve.dm.SurfaceFragment} This fragment
|
|
|
|
*/
|
|
|
|
ve.dm.SurfaceFragment.prototype.removeContent = function () {
|
|
|
|
// Handle null fragment
|
|
|
|
if ( !this.surface ) {
|
|
|
|
return this;
|
|
|
|
}
|
2012-08-10 23:59:50 +00:00
|
|
|
var tx;
|
|
|
|
if ( this.range.getLength() ) {
|
2012-08-17 17:48:40 +00:00
|
|
|
tx = ve.dm.Transaction.newFromRemoval( this.document, this.range );
|
2012-08-24 22:24:23 +00:00
|
|
|
// this.range will be translated via the onTransact event handler
|
|
|
|
this.surface.change( tx, this.autoSelect && tx.translateRange( this.range ) );
|
|
|
|
// Check if the range didn't get collapsed automatically - this will occur when removing
|
|
|
|
// content across un-mergable nodes because the delete only strips out content leaving
|
|
|
|
// structure at the beginning and end of the range in place
|
|
|
|
if ( this.range.getLength() ) {
|
|
|
|
// Collapse the range manually
|
|
|
|
this.range = new ve.Range( this.range.start );
|
|
|
|
if ( this.autoSelect ) {
|
|
|
|
// Update the surface selection
|
|
|
|
this.surface.setSelection( this.range );
|
|
|
|
}
|
|
|
|
}
|
2012-08-10 23:59:50 +00:00
|
|
|
}
|
2012-08-02 00:59:38 +00:00
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2012-08-10 23:59:50 +00:00
|
|
|
* Converts each content branch in the fragment from one type to another.
|
2012-08-02 00:59:38 +00:00
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @param {String} type Element type to convert to
|
2012-08-10 23:59:50 +00:00
|
|
|
* @param {Object} [attr] Initial attributes for new element
|
|
|
|
* @returns {ve.dm.SurfaceFragment} This fragment
|
|
|
|
*/
|
|
|
|
ve.dm.SurfaceFragment.prototype.convertNodes = function ( type, attr ) {
|
|
|
|
// Handle null fragment
|
|
|
|
if ( !this.surface ) {
|
|
|
|
return this;
|
|
|
|
}
|
2012-08-17 17:48:40 +00:00
|
|
|
var tx = ve.dm.Transaction.newFromContentBranchConversion( this.document, this.range, type, attr );
|
2012-08-24 22:24:23 +00:00
|
|
|
this.surface.change( tx, this.autoSelect && tx.translateRange( this.range ) );
|
2012-08-10 23:59:50 +00:00
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Wraps each node in the fragment with one or more elements.
|
|
|
|
*
|
|
|
|
* A wrapper object is a linear model element; a plain object containing a type property and an
|
|
|
|
* optional attributes property.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @param {Object|Object[]} wrapper Wrapper object, or array of wrapper objects (see above)
|
|
|
|
* @param {String} wrapper.type Node type of wrapper
|
|
|
|
* @param {Object} [wrapper.attributes] Attributes of wrapper
|
2012-08-02 00:59:38 +00:00
|
|
|
* @returns {ve.dm.SurfaceFragment} This fragment
|
|
|
|
*/
|
2012-08-10 23:59:50 +00:00
|
|
|
ve.dm.SurfaceFragment.prototype.wrapNodes = function ( wrapper ) {
|
2012-08-02 00:59:38 +00:00
|
|
|
// Handle null fragment
|
|
|
|
if ( !this.surface ) {
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
// TODO: Implement
|
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2012-08-10 23:59:50 +00:00
|
|
|
* Unwraps each node in the fragment out of one or more elements.
|
2012-08-02 00:59:38 +00:00
|
|
|
*
|
2012-08-10 23:59:50 +00:00
|
|
|
* @method
|
|
|
|
* @param {String|String[]} type Node types to unwrap, or array of node types to unwrap
|
|
|
|
* @returns {ve.dm.SurfaceFragment} This fragment
|
|
|
|
*/
|
|
|
|
ve.dm.SurfaceFragment.prototype.unwrapNodes = function ( type ) {
|
|
|
|
// Handle null fragment
|
|
|
|
if ( !this.surface ) {
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
// TODO: Implement
|
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Changes the wrapping of each node in the fragment from one type to another.
|
|
|
|
*
|
|
|
|
* A wrapper object is a linear model element; a plain object containing a type property and an
|
|
|
|
* optional attributes property.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @param {String|String[]} type Node types to unwrap, or array of node types to unwrap
|
|
|
|
* @param {Object|Object[]} wrapper Wrapper object, or array of wrapper objects (see above)
|
|
|
|
* @param {String} wrapper.type Node type of wrapper
|
|
|
|
* @param {Object} [wrapper.attributes] Attributes of wrapper
|
|
|
|
* @returns {ve.dm.SurfaceFragment} This fragment
|
|
|
|
*/
|
|
|
|
ve.dm.SurfaceFragment.prototype.rewrapNodes = function ( type, wrapper ) {
|
|
|
|
// Handle null fragment
|
|
|
|
if ( !this.surface ) {
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
// TODO: Implement
|
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Wraps nodes in the fragment with one or more elements.
|
|
|
|
*
|
|
|
|
* A wrapper object is a linear model element; a plain object containing a type property and an
|
|
|
|
* optional attributes property.
|
2012-08-02 00:59:38 +00:00
|
|
|
*
|
|
|
|
* @method
|
2012-08-10 23:59:50 +00:00
|
|
|
* @param {Object|Object[]} wrapper Wrapper object, or array of wrapper objects (see above)
|
|
|
|
* @param {String} wrapper.type Node type of wrapper
|
|
|
|
* @param {Object} [wrapper.attributes] Attributes of wrapper
|
2012-08-02 00:59:38 +00:00
|
|
|
* @returns {ve.dm.SurfaceFragment} This fragment
|
|
|
|
*/
|
2012-08-10 23:59:50 +00:00
|
|
|
ve.dm.SurfaceFragment.prototype.wrapAllNodes = function ( wrapper ) {
|
2012-08-02 00:59:38 +00:00
|
|
|
// Handle null fragment
|
|
|
|
if ( !this.surface ) {
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
// TODO: Implement
|
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2012-08-10 23:59:50 +00:00
|
|
|
* Unwraps nodes in the fragment out of one or more elements.
|
2012-08-02 00:59:38 +00:00
|
|
|
*
|
|
|
|
* TODO: Figure out what the arguments for this function should be
|
|
|
|
*
|
|
|
|
* @method
|
2012-08-10 23:59:50 +00:00
|
|
|
* @param {String|String[]} type Node types to unwrap, or array of node types to unwrap
|
|
|
|
* @returns {ve.dm.SurfaceFragment} This fragment
|
|
|
|
*/
|
|
|
|
ve.dm.SurfaceFragment.prototype.unwrapAllNodes = function ( type ) {
|
|
|
|
// Handle null fragment
|
|
|
|
if ( !this.surface ) {
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
// TODO: Implement
|
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Changes the wrapping of nodes in the fragment from one type to another.
|
|
|
|
*
|
|
|
|
* A wrapper object is a linear model element; a plain object containing a type property and an
|
|
|
|
* optional attributes property.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @param {String|String[]} type Node types to unwrap, or array of node types to unwrap
|
|
|
|
* @param {Object|Object[]} wrapper Wrapper object, or array of wrapper objects (see above)
|
|
|
|
* @param {String} wrapper.type Node type of wrapper
|
|
|
|
* @param {Object} [wrapper.attributes] Attributes of wrapper
|
2012-08-02 00:59:38 +00:00
|
|
|
* @returns {ve.dm.SurfaceFragment} This fragment
|
|
|
|
*/
|
2012-08-10 23:59:50 +00:00
|
|
|
ve.dm.SurfaceFragment.prototype.rewrapAllNodes = function ( type, wrapper ) {
|
2012-08-02 00:59:38 +00:00
|
|
|
// Handle null fragment
|
|
|
|
if ( !this.surface ) {
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
// TODO: Implement
|
|
|
|
return this;
|
|
|
|
};
|