2012-07-19 00:11:26 +00:00
|
|
|
/**
|
|
|
|
* VisualEditor content editable BranchNode class.
|
2012-07-19 21:25:16 +00:00
|
|
|
*
|
2012-07-19 00:11:26 +00:00
|
|
|
* @copyright 2011-2012 VisualEditor Team and others; see AUTHORS.txt
|
|
|
|
* @license The MIT License (MIT); see LICENSE.txt
|
|
|
|
*/
|
|
|
|
|
2012-03-08 12:27:02 +00:00
|
|
|
/**
|
2012-06-20 01:20:28 +00:00
|
|
|
* ContentEditable node that can have branch or leaf children.
|
|
|
|
*
|
2012-03-08 12:27:02 +00:00
|
|
|
* @class
|
|
|
|
* @abstract
|
|
|
|
* @constructor
|
|
|
|
* @extends {ve.ce.Node}
|
2012-06-20 01:20:28 +00:00
|
|
|
* @param {String} type Symbolic name of node type
|
2012-09-17 13:30:50 +00:00
|
|
|
* @param {ve.dm.BranchNode} model Model to observe
|
2012-03-08 12:27:02 +00:00
|
|
|
* @param {jQuery} [$element] Element to use as a container
|
|
|
|
*/
|
2012-09-06 23:15:55 +00:00
|
|
|
ve.ce.BranchNode = function VeCeBranchNode( type, model, $element ) {
|
2012-09-17 23:53:03 +00:00
|
|
|
// Mixin constructor
|
2012-03-08 12:27:02 +00:00
|
|
|
ve.BranchNode.call( this );
|
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
|
|
|
|
|
|
|
// Parent constructor
|
2012-06-20 01:20:28 +00:00
|
|
|
ve.ce.Node.call( this, type, model, $element );
|
2012-03-08 12:27:02 +00:00
|
|
|
|
|
|
|
// Properties
|
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
|
|
|
this.domWrapperElementType = this.$.get( 0 ).nodeName.toLowerCase();
|
2012-10-03 22:40:00 +00:00
|
|
|
this.slugs = { };
|
2012-03-08 12:27:02 +00:00
|
|
|
|
2012-06-20 01:20:28 +00:00
|
|
|
// Events
|
|
|
|
this.model.addListenerMethod( this, 'splice', 'onSplice' );
|
|
|
|
|
|
|
|
// DOM Changes
|
|
|
|
this.$.addClass( 've-ce-branchNode' );
|
2012-03-08 12:27:02 +00:00
|
|
|
|
2012-06-20 01:20:28 +00:00
|
|
|
// Initialization
|
2012-10-03 22:40:00 +00:00
|
|
|
this.onSplice.apply( this, [0, 0].concat( model.getChildren() ) );
|
2012-03-08 12:27:02 +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
|
|
|
/* Inheritance */
|
|
|
|
|
|
|
|
ve.inheritClass( ve.ce.BranchNode, ve.ce.Node );
|
|
|
|
|
|
|
|
ve.mixinClass( ve.ce.BranchNode, ve.BranchNode );
|
|
|
|
|
2012-06-20 01:20:28 +00:00
|
|
|
/* Static Members */
|
|
|
|
|
2012-10-03 22:40:00 +00:00
|
|
|
// TODO: Consider using different (or additional) CSS classes for inline and block aliens
|
|
|
|
|
2012-06-21 20:34:13 +00:00
|
|
|
if ( $.browser.msie ) {
|
2012-10-03 22:40:00 +00:00
|
|
|
ve.ce.BranchNode.$inlineSlugTemplate = $( '<span class="ve-ce-slug"> </span>' );
|
2012-06-21 20:34:13 +00:00
|
|
|
} else {
|
2012-10-03 22:40:00 +00:00
|
|
|
ve.ce.BranchNode.$inlineSlugTemplate = $( '<span class="ve-ce-slug"></span>' );
|
2012-06-21 20:34:13 +00:00
|
|
|
}
|
2012-03-08 12:27:02 +00:00
|
|
|
|
2012-10-03 22:40:00 +00:00
|
|
|
ve.ce.BranchNode.$blockSlugTemplate =
|
|
|
|
ve.ce.BranchNode.$inlineSlugTemplate.clone().css( 'display', 'block' );
|
|
|
|
|
2012-06-20 01:20:28 +00:00
|
|
|
/* Static Methods */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the appropriate element type for the DOM wrapper of a node.
|
|
|
|
*
|
|
|
|
* This method reads the {key} attribute from a {model} and looks up a type in the node's statically
|
|
|
|
* defined {domWrapperElementTypes} member, which is a mapping of possible values of that attribute
|
|
|
|
* and DOM element types.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @param {ve.dm.BranchNode} model Model node is based on
|
|
|
|
* @param {String} key Attribute name to read type value from
|
|
|
|
* @returns {String} DOM element type for wrapper
|
|
|
|
* @throws 'Undefined attribute' if attribute is not defined in the model
|
|
|
|
* @throws 'Invalid attribute value' if attribute value is not a key in {domWrapperElementTypes}
|
|
|
|
*/
|
2012-08-07 01:50:44 +00:00
|
|
|
ve.ce.BranchNode.getDomWrapperType = function ( model, key ) {
|
2012-07-19 03:40:49 +00:00
|
|
|
var types,
|
|
|
|
value = model.getAttribute( key );
|
2012-06-20 01:20:28 +00:00
|
|
|
if ( value === undefined ) {
|
2012-08-08 17:48:53 +00:00
|
|
|
throw new Error( 'Undefined attribute: ' + key );
|
2012-03-08 12:27:02 +00:00
|
|
|
}
|
2012-07-19 03:40:49 +00:00
|
|
|
types = ve.ce.nodeFactory.lookup( model.getType() ).domWrapperElementTypes;
|
2012-06-20 01:20:28 +00:00
|
|
|
if ( types[value] === undefined ) {
|
2012-08-08 17:48:53 +00:00
|
|
|
throw new Error( 'Invalid attribute value: ' + value );
|
2012-06-20 01:20:28 +00:00
|
|
|
}
|
|
|
|
return types[value];
|
2012-03-08 12:27:02 +00:00
|
|
|
};
|
|
|
|
|
2012-06-20 01:20:28 +00:00
|
|
|
/**
|
|
|
|
* Gets a jQuery selection of a new DOM wrapper for a node.
|
|
|
|
*
|
|
|
|
* This method uses {getDomWrapperType} to determine the proper element type to use.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @param {ve.dm.BranchNode} model Model node is based on
|
|
|
|
* @param {String} key Attribute name to read type value from
|
|
|
|
* @returns {jQuery} Selection of DOM wrapper
|
|
|
|
*/
|
2012-08-07 01:50:44 +00:00
|
|
|
ve.ce.BranchNode.getDomWrapper = function ( model, key ) {
|
2012-06-20 01:20:28 +00:00
|
|
|
var type = ve.ce.BranchNode.getDomWrapperType( model, key );
|
2012-08-28 22:06:34 +00:00
|
|
|
return $( document.createElement( type ) );
|
2012-03-08 12:27:02 +00:00
|
|
|
};
|
|
|
|
|
2012-06-20 01:20:28 +00:00
|
|
|
/* Methods */
|
2012-03-08 12:27:02 +00:00
|
|
|
|
2012-06-20 01:20:28 +00:00
|
|
|
/**
|
|
|
|
* Updates the DOM wrapper of this node if needed.
|
|
|
|
*
|
|
|
|
* This method uses {getDomWrapperType} to determine the proper element type to use.
|
|
|
|
*
|
|
|
|
* WARNING: The contents, .data( 'node' ) and any classes the wrapper already has will be moved to
|
|
|
|
* the new wrapper, but other attributes and any other information added using $.data() will be
|
|
|
|
* lost upon updating the wrapper. To retain information added to the wrapper, subscribe to the
|
|
|
|
* 'rewrap' event and copy information from the {$old} wrapper the {$new} wrapper.
|
|
|
|
*
|
|
|
|
* @method
|
|
|
|
* @param {String} key Attribute name to read type value from
|
|
|
|
* @emits rewrap ($old, $new)
|
|
|
|
*/
|
2012-08-07 01:50:44 +00:00
|
|
|
ve.ce.BranchNode.prototype.updateDomWrapper = function ( key ) {
|
2012-07-19 03:40:49 +00:00
|
|
|
var $element,
|
|
|
|
type = ve.ce.BranchNode.getDomWrapperType( this.model, key );
|
|
|
|
|
2012-06-20 01:20:28 +00:00
|
|
|
if ( type !== this.domWrapperElementType ) {
|
2012-08-28 22:06:34 +00:00
|
|
|
$element = $( document.createElement( type ) );
|
2012-06-20 01:20:28 +00:00
|
|
|
// Copy classes
|
|
|
|
$element.attr( 'class', this.$.attr( 'class' ) );
|
|
|
|
// Copy .data( 'node' )
|
|
|
|
$element.data( 'node', this.$.data( 'node' ) );
|
|
|
|
// Move contents
|
|
|
|
$element.append( this.$.contents() );
|
|
|
|
// Emit an event that can be handled to copy other things over if needed
|
|
|
|
this.emit( 'rewrap', this.$, $element );
|
|
|
|
// Swap elements
|
|
|
|
this.$.replaceWith( $element );
|
|
|
|
// Use new element from now on
|
|
|
|
this.$ = $element;
|
2012-06-20 19:14:04 +00:00
|
|
|
// Remember which type we are using now
|
|
|
|
this.domWrapperElementType = type;
|
2012-06-20 01:20:28 +00:00
|
|
|
}
|
2012-03-08 12:27:02 +00:00
|
|
|
};
|
|
|
|
|
2012-06-20 01:20:28 +00:00
|
|
|
/**
|
|
|
|
* Responds to splice events on a ve.dm.BranchNode.
|
|
|
|
*
|
|
|
|
* ve.ce.Node objects are generated from the inserted ve.dm.Node objects, producing a view that's a
|
2012-06-20 23:01:02 +00:00
|
|
|
* mirror of its model.
|
2012-06-20 01:20:28 +00:00
|
|
|
*
|
|
|
|
* @method
|
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
|
|
|
* @param {Number} index Index to remove and or insert nodes at
|
|
|
|
* @param {Number} howmany Number of nodes to remove
|
2012-06-20 01:20:28 +00:00
|
|
|
* @param {ve.dm.BranchNode} [...] Variadic list of nodes to insert
|
|
|
|
*/
|
Make use of new jshint options
* Restricting "camelcase":
No changes, we were passing all of these already
* Explicitly unrestricting "forin" and "plusplus"
These are off by default in node-jshint, but some distro of jshint
and editors that use their own wrapper around jshint instead of
node-jshint (Eclipse?) may have different defaults. Therefor
setting them to false explicitly. This also serves as a reminder
for the future so we'll always know we don't pass that, in case
we would want to change that.
* Fix order ("quotemark" before "regexp")
* Restricting "unused"
We're not passing all of this, which is why I've set it to false
for now. But I did put it in .jshintrc as placeholder.
I've fixed most of them, there's some left where there is no clean
solution.
* While at it fix a few issues:
- Unused variables ($target, $window)
- Bad practices (using jQuery context for find instead of creation)
- Redundant /*global */ comments
- Parameters that are not used and don't have documentation either
- Lines longer than 100 chars @ 4 spaces/tab
* Note:
- ve.ce.Surface.prototype.onChange takes two arguments but never
uses the former. And even the second one can be null/undefined.
Aside from that, the .change() function emits
another event for the transaction already. Looks like this
should be refactored a bit, two more separated events probably
or one that is actually used better.
- Also cleaned up a lot of comments, some of which were missing,
others were incorrect
- Reworked the contentChange event so we are no longer using the
word new as an object key; expanded a complex object into multiple
arguments being passed through the event to make it easier to work
with and document
Change-Id: I8490815a508c6c379d5f9a743bb4aefd14576aa6
2012-08-07 06:02:18 +00:00
|
|
|
ve.ce.BranchNode.prototype.onSplice = function ( index ) {
|
2012-03-08 12:27:02 +00:00
|
|
|
var i,
|
|
|
|
length,
|
Make use of new jshint options
* Restricting "camelcase":
No changes, we were passing all of these already
* Explicitly unrestricting "forin" and "plusplus"
These are off by default in node-jshint, but some distro of jshint
and editors that use their own wrapper around jshint instead of
node-jshint (Eclipse?) may have different defaults. Therefor
setting them to false explicitly. This also serves as a reminder
for the future so we'll always know we don't pass that, in case
we would want to change that.
* Fix order ("quotemark" before "regexp")
* Restricting "unused"
We're not passing all of this, which is why I've set it to false
for now. But I did put it in .jshintrc as placeholder.
I've fixed most of them, there's some left where there is no clean
solution.
* While at it fix a few issues:
- Unused variables ($target, $window)
- Bad practices (using jQuery context for find instead of creation)
- Redundant /*global */ comments
- Parameters that are not used and don't have documentation either
- Lines longer than 100 chars @ 4 spaces/tab
* Note:
- ve.ce.Surface.prototype.onChange takes two arguments but never
uses the former. And even the second one can be null/undefined.
Aside from that, the .change() function emits
another event for the transaction already. Looks like this
should be refactored a bit, two more separated events probably
or one that is actually used better.
- Also cleaned up a lot of comments, some of which were missing,
others were incorrect
- Reworked the contentChange event so we are no longer using the
word new as an object key; expanded a complex object into multiple
arguments being passed through the event to make it easier to work
with and document
Change-Id: I8490815a508c6c379d5f9a743bb4aefd14576aa6
2012-08-07 06:02:18 +00:00
|
|
|
args = Array.prototype.slice.call( arguments ),
|
2012-07-19 03:40:49 +00:00
|
|
|
$anchor,
|
Make use of new jshint options
* Restricting "camelcase":
No changes, we were passing all of these already
* Explicitly unrestricting "forin" and "plusplus"
These are off by default in node-jshint, but some distro of jshint
and editors that use their own wrapper around jshint instead of
node-jshint (Eclipse?) may have different defaults. Therefor
setting them to false explicitly. This also serves as a reminder
for the future so we'll always know we don't pass that, in case
we would want to change that.
* Fix order ("quotemark" before "regexp")
* Restricting "unused"
We're not passing all of this, which is why I've set it to false
for now. But I did put it in .jshintrc as placeholder.
I've fixed most of them, there's some left where there is no clean
solution.
* While at it fix a few issues:
- Unused variables ($target, $window)
- Bad practices (using jQuery context for find instead of creation)
- Redundant /*global */ comments
- Parameters that are not used and don't have documentation either
- Lines longer than 100 chars @ 4 spaces/tab
* Note:
- ve.ce.Surface.prototype.onChange takes two arguments but never
uses the former. And even the second one can be null/undefined.
Aside from that, the .change() function emits
another event for the transaction already. Looks like this
should be refactored a bit, two more separated events probably
or one that is actually used better.
- Also cleaned up a lot of comments, some of which were missing,
others were incorrect
- Reworked the contentChange event so we are no longer using the
word new as an object key; expanded a complex object into multiple
arguments being passed through the event to make it easier to work
with and document
Change-Id: I8490815a508c6c379d5f9a743bb4aefd14576aa6
2012-08-07 06:02:18 +00:00
|
|
|
removals;
|
2012-03-08 12:27:02 +00:00
|
|
|
// Convert models to views and attach them to this node
|
|
|
|
if ( args.length >= 3 ) {
|
|
|
|
for ( i = 2, length = args.length; i < length; i++ ) {
|
2012-06-20 01:20:28 +00:00
|
|
|
args[i] = ve.ce.nodeFactory.create( args[i].getType(), args[i] );
|
2012-03-08 12:27:02 +00:00
|
|
|
}
|
|
|
|
}
|
2012-07-19 03:40:49 +00:00
|
|
|
removals = this.children.splice.apply( this.children, args );
|
2012-03-08 12:27:02 +00:00
|
|
|
for ( i = 0, length = removals.length; i < length; i++ ) {
|
|
|
|
removals[i].detach();
|
|
|
|
// Update DOM
|
|
|
|
removals[i].$.detach();
|
|
|
|
}
|
|
|
|
if ( args.length >= 3 ) {
|
|
|
|
if ( index ) {
|
|
|
|
// Get the element before the insertion point
|
2012-08-23 20:34:09 +00:00
|
|
|
$anchor = this.children[ index - 1 ].$.last();
|
2012-03-08 12:27:02 +00:00
|
|
|
}
|
|
|
|
for ( i = args.length - 1; i >= 2; i-- ) {
|
|
|
|
args[i].attach( this );
|
|
|
|
if ( index ) {
|
|
|
|
$anchor.after( args[i].$ );
|
|
|
|
} else {
|
|
|
|
this.$.prepend( args[i].$ );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-10-03 22:40:00 +00:00
|
|
|
this.setupSlugs();
|
2012-03-08 12:27:02 +00:00
|
|
|
};
|
|
|
|
|
2012-10-03 22:40:00 +00:00
|
|
|
ve.ce.BranchNode.prototype.setupSlugs = function () {
|
|
|
|
var key, $slug, i;
|
|
|
|
|
|
|
|
// Remove all slugs in this branch
|
|
|
|
for( key in this.slugs ) {
|
|
|
|
this.slugs[key].remove();
|
2012-06-21 00:40:26 +00:00
|
|
|
}
|
2012-10-03 22:40:00 +00:00
|
|
|
|
|
|
|
if ( this.canHaveGrandchildren() ) {
|
|
|
|
$slug = ve.ce.BranchNode.$blockSlugTemplate.clone();
|
|
|
|
} else {
|
|
|
|
$slug = ve.ce.BranchNode.$inlineSlugTemplate.clone();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( this.getLength() === 0 ) {
|
|
|
|
this.slugs[0] = $slug.clone().appendTo( this.$ )
|
|
|
|
} else {
|
|
|
|
// Iterate over all children of this branch and add slugs in appropriate places
|
|
|
|
for ( i = 0; i < this.children.length; i++ ) {
|
|
|
|
// First sluggable child (left side)
|
|
|
|
if ( i === 0 && this.children[i].canHaveSlugBefore() ) {
|
|
|
|
this.slugs[i] = $slug.clone().insertBefore( this.children[i].$ );
|
|
|
|
}
|
2012-07-20 00:45:41 +00:00
|
|
|
if ( this.children[i].canHaveSlugAfter() ) {
|
|
|
|
if (
|
2012-10-03 22:40:00 +00:00
|
|
|
// Last sluggable child (right side)
|
|
|
|
i === this.children.length - 1 ||
|
|
|
|
// Sluggable child followed by another sluggable child (in between)
|
|
|
|
( this.children[i + 1] && this.children[i + 1].canHaveSlugBefore() )
|
2012-07-20 00:45:41 +00:00
|
|
|
) {
|
2012-10-03 22:40:00 +00:00
|
|
|
this.slugs[i + 1] = $slug.clone().insertAfter( this.children[i].$ );
|
2012-06-20 01:20:28 +00:00
|
|
|
}
|
|
|
|
}
|
2012-03-08 12:27:02 +00:00
|
|
|
}
|
|
|
|
}
|
2012-10-03 22:40:00 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
ve.ce.BranchNode.prototype.getSlugAtOffset = function( offset ) {
|
|
|
|
var startOffset = this.model.getOffset() + ( this.isWrapped() ? 1 : 0 );
|
|
|
|
|
|
|
|
if ( offset === startOffset ) {
|
|
|
|
return this.slugs[0] || null;
|
|
|
|
}
|
|
|
|
for ( i = 0; i < this.children.length; i++ ) {
|
|
|
|
startOffset += this.children[i].model.getOuterLength();
|
|
|
|
if ( offset === startOffset ) {
|
|
|
|
return this.slugs[i + 1] || null;
|
|
|
|
}
|
|
|
|
}
|
2012-03-08 12:27:02 +00:00
|
|
|
};
|
|
|
|
|
2012-08-07 01:50:44 +00:00
|
|
|
ve.ce.BranchNode.prototype.clean = function () {
|
2012-08-17 22:36:20 +00:00
|
|
|
// Detach all child nodes from this.$
|
|
|
|
// We can't use this.$.empty() because that destroys .data() and event handlers
|
|
|
|
this.$.contents().each( function () {
|
|
|
|
$(this).detach();
|
|
|
|
} );
|
|
|
|
// Reattach the child nodes we're supposed to have
|
2012-06-20 01:20:28 +00:00
|
|
|
for ( var i = 0; i < this.children.length; i++ ) {
|
|
|
|
this.$.append( this.children[i].$ );
|
2012-03-08 12:27:02 +00:00
|
|
|
}
|
2012-10-03 22:40:00 +00:00
|
|
|
this.setupSlugs();
|
2012-03-08 12:27:02 +00:00
|
|
|
};
|