first commit

This commit is contained in:
/usr/bin/nano
2017-04-15 01:34:36 +03:00
commit c715e2a604
5325 changed files with 329700 additions and 0 deletions
+53
View File
@@ -0,0 +1,53 @@
/**
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or http://ckeditor.com/license
*/
/**
* @fileOverview Defines the {@link CKEDITOR.dom.comment} class, which represents
* a DOM comment node.
*/
/**
* Represents a DOM comment node.
*
* var nativeNode = document.createComment( 'Example' );
* var comment = new CKEDITOR.dom.comment( nativeNode );
*
* var comment = new CKEDITOR.dom.comment( 'Example' );
*
* @class
* @extends CKEDITOR.dom.node
* @constructor Creates a comment class instance.
* @param {Object/String} comment A native DOM comment node or a string containing
* the text to use to create a new comment node.
* @param {CKEDITOR.dom.document} [ownerDocument] The document that will contain
* the node in case of new node creation. Defaults to the current document.
*/
CKEDITOR.dom.comment = function( comment, ownerDocument ) {
if ( typeof comment == 'string' )
comment = ( ownerDocument ? ownerDocument.$ : document ).createComment( comment );
CKEDITOR.dom.domObject.call( this, comment );
};
CKEDITOR.dom.comment.prototype = new CKEDITOR.dom.node();
CKEDITOR.tools.extend( CKEDITOR.dom.comment.prototype, {
/**
* The node type. This is a constant value set to {@link CKEDITOR#NODE_COMMENT}.
*
* @readonly
* @property {Number} [=CKEDITOR.NODE_COMMENT]
*/
type: CKEDITOR.NODE_COMMENT,
/**
* Gets the outer HTML of this comment.
*
* @returns {String} The HTML `<!-- comment value -->`.
*/
getOuterHtml: function() {
return '<!--' + this.$.nodeValue + '-->';
}
});
+270
View File
@@ -0,0 +1,270 @@
/**
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or http://ckeditor.com/license
*/
/**
* @fileOverview Defines the {@link CKEDITOR.dom.document} class, which
* represents a DOM document.
*/
/**
* Represents a DOM document.
*
* var document = new CKEDITOR.dom.document( document );
*
* @class
* @extends CKEDITOR.dom.domObject
* @constructor Creates a document class instance.
* @param {Object} domDocument A native DOM document.
*/
CKEDITOR.dom.document = function( domDocument ) {
CKEDITOR.dom.domObject.call( this, domDocument );
};
// PACKAGER_RENAME( CKEDITOR.dom.document )
CKEDITOR.dom.document.prototype = new CKEDITOR.dom.domObject();
CKEDITOR.tools.extend( CKEDITOR.dom.document.prototype, {
/**
* The node type. This is a constant value set to {@link CKEDITOR#NODE_DOCUMENT}.
*
* @readonly
* @property {Number} [=CKEDITOR.NODE_DOCUMENT]
*/
type: CKEDITOR.NODE_DOCUMENT,
/**
* Appends a CSS file to the document.
*
* CKEDITOR.document.appendStyleSheet( '/mystyles.css' );
*
* @param {String} cssFileUrl The CSS file URL.
*/
appendStyleSheet: function( cssFileUrl ) {
if ( this.$.createStyleSheet )
this.$.createStyleSheet( cssFileUrl );
else {
var link = new CKEDITOR.dom.element( 'link' );
link.setAttributes({
rel: 'stylesheet',
type: 'text/css',
href: cssFileUrl
});
this.getHead().append( link );
}
},
/**
* Creates a CSS style sheet and inserts it into the document.
*
* @param cssStyleText {String} CSS style text.
* @returns {Object} The created DOM native style sheet object.
*/
appendStyleText: function( cssStyleText ) {
if ( this.$.createStyleSheet ) {
var styleSheet = this.$.createStyleSheet( "" );
styleSheet.cssText = cssStyleText;
} else {
var style = new CKEDITOR.dom.element( 'style', this );
style.append( new CKEDITOR.dom.text( cssStyleText, this ) );
this.getHead().append( style );
}
return styleSheet || style.$.sheet;
},
/**
* Creates {@link CKEDITOR.dom.element} instance in this document.
*
* @returns {CKEDITOR.dom.element}
* @todo
*/
createElement: function( name, attribsAndStyles ) {
var element = new CKEDITOR.dom.element( name, this );
if ( attribsAndStyles ) {
if ( attribsAndStyles.attributes )
element.setAttributes( attribsAndStyles.attributes );
if ( attribsAndStyles.styles )
element.setStyles( attribsAndStyles.styles );
}
return element;
},
/**
* Creates {@link CKEDITOR.dom.text} instance in this document.
*
* @param {String} text Value of the text node.
* @returns {CKEDITOR.dom.element}
*/
createText: function( text ) {
return new CKEDITOR.dom.text( text, this );
},
/**
* Moves the selection focus to this document's window.
*/
focus: function() {
this.getWindow().focus();
},
/**
* Returns the element that is currently designated as the active element in the document.
*
* **Note:** Only one element can be active at a time in a document.
* An active element does not necessarily have focus,
* but an element with focus is always the active element in a document.
*
* @returns {CKEDITOR.dom.element}
*/
getActive: function() {
return new CKEDITOR.dom.element( this.$.activeElement );
},
/**
* Gets an element based on its id.
*
* var element = CKEDITOR.document.getById( 'myElement' );
* alert( element.getId() ); // 'myElement'
*
* @param {String} elementId The element id.
* @returns {CKEDITOR.dom.element} The element instance, or null if not found.
*/
getById: function( elementId ) {
var $ = this.$.getElementById( elementId );
return $ ? new CKEDITOR.dom.element( $ ) : null;
},
/**
* Gets a node based on its address. See {@link CKEDITOR.dom.node#getAddress}.
*
* @param {Array} address
* @param {Boolean} [normalized=false]
*/
getByAddress: function( address, normalized ) {
var $ = this.$.documentElement;
for ( var i = 0; $ && i < address.length; i++ ) {
var target = address[ i ];
if ( !normalized ) {
$ = $.childNodes[ target ];
continue;
}
var currentIndex = -1;
for ( var j = 0; j < $.childNodes.length; j++ ) {
var candidate = $.childNodes[ j ];
if ( normalized === true && candidate.nodeType == 3 && candidate.previousSibling && candidate.previousSibling.nodeType == 3 ) {
continue;
}
currentIndex++;
if ( currentIndex == target ) {
$ = candidate;
break;
}
}
}
return $ ? new CKEDITOR.dom.node( $ ) : null;
},
/**
* Gets elements list based on given tag name.
*
* @param {String} tagName The element tag name.
* @returns {CKEDITOR.dom.nodeList} The nodes list.
*/
getElementsByTag: function( tagName, namespace ) {
if ( !( CKEDITOR.env.ie && !( document.documentMode > 8 ) ) && namespace )
tagName = namespace + ':' + tagName;
return new CKEDITOR.dom.nodeList( this.$.getElementsByTagName( tagName ) );
},
/**
* Gets the `<head>` element for this document.
*
* var element = CKEDITOR.document.getHead();
* alert( element.getName() ); // 'head'
*
* @returns {CKEDITOR.dom.element} The `<head>` element.
*/
getHead: function() {
var head = this.$.getElementsByTagName( 'head' )[ 0 ];
if ( !head )
head = this.getDocumentElement().append( new CKEDITOR.dom.element( 'head' ), true );
else
head = new CKEDITOR.dom.element( head );
return head;
},
/**
* Gets the `<body>` element for this document.
*
* var element = CKEDITOR.document.getBody();
* alert( element.getName() ); // 'body'
*
* @returns {CKEDITOR.dom.element} The `<body>` element.
*/
getBody: function() {
return new CKEDITOR.dom.element( this.$.body );
},
/**
* Gets the DOM document element for this document.
*
* @returns {CKEDITOR.dom.element} The DOM document element.
*/
getDocumentElement: function() {
return new CKEDITOR.dom.element( this.$.documentElement );
},
/**
* Gets the window object that holds this document.
*
* @returns {CKEDITOR.dom.window} The window object.
*/
getWindow: function() {
return new CKEDITOR.dom.window( this.$.parentWindow || this.$.defaultView );
},
/**
* Defines the document contents through document.write. Note that the
* previous document contents will be lost (cleaned).
*
* document.write(
* '<html>' +
* '<head><title>Sample Doc</title></head>' +
* '<body>Document contents created by code</body>' +
* '</html>'
* );
*
* @since 3.5
* @param {String} html The HTML defining the document contents.
*/
write: function( html ) {
// Don't leave any history log in IE. (#5657)
this.$.open( 'text/html', 'replace' );
// Support for custom document.domain in IE.
//
// The script must be appended because if placed before the
// doctype, IE will go into quirks mode and mess with
// the editable, e.g. by changing its default height.
if ( CKEDITOR.env.ie )
html = html.replace( /(?:^\s*<!DOCTYPE[^>]*?>)|^/i, '$&\n<script data-cke-temp="1">(' + CKEDITOR.tools.fixDomain + ')();</script>' );
this.$.write( html );
this.$.close();
}
});
+45
View File
@@ -0,0 +1,45 @@
/**
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or http://ckeditor.com/license
*/
/**
* DocumentFragment is a "lightweight" or "minimal" Document object. It is
* commonly used to extract a portion of a document's tree or to create a new
* fragment of a document. Various operations may take DocumentFragment objects
* as arguments and results in all the child nodes of the DocumentFragment being
* moved to the child list of this node.
*
* @class
* @constructor Creates a document fragment class instance.
* @param {Object} nodeOrDoc
* @todo example and param doc
*/
CKEDITOR.dom.documentFragment = function( nodeOrDoc ) {
nodeOrDoc = nodeOrDoc || CKEDITOR.document;
if ( nodeOrDoc.type == CKEDITOR.NODE_DOCUMENT )
this.$ = nodeOrDoc.$.createDocumentFragment();
else
this.$ = nodeOrDoc;
};
CKEDITOR.tools.extend( CKEDITOR.dom.documentFragment.prototype, CKEDITOR.dom.element.prototype, {
/**
* The node type. This is a constant value set to {@link CKEDITOR#NODE_DOCUMENT_FRAGMENT}.
*
* @readonly
* @property {Number} [=CKEDITOR.NODE_DOCUMENT_FRAGMENT]
*/
type: CKEDITOR.NODE_DOCUMENT_FRAGMENT,
/**
* Inserts document fragment's contents after specified node.
*
* @param {CKEDITOR.dom.node} node
*/
insertAfterNode: function( node ) {
node = node.$;
node.parentNode.insertBefore( this.$, node.nextSibling );
}
}, true, { 'append':1,'appendBogus':1,'getFirst':1,'getLast':1,'getParent':1,'getNext':1,'getPrevious':1,'appendTo':1,'moveChildren':1,'insertBefore':1,'insertAfterNode':1,'replace':1,'trim':1,'type':1,'ltrim':1,'rtrim':1,'getDocument':1,'getChildCount':1,'getChild':1,'getChildren':1 } );
+258
View File
@@ -0,0 +1,258 @@
/**
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or http://ckeditor.com/license
*/
/**
* @fileOverview Defines the {@link CKEDITOR.editor} class, which is the base
* for other classes representing DOM objects.
*/
/**
* Represents a DOM object. This class is not intended to be used directly. It
* serves as the base class for other classes representing specific DOM
* objects.
*
* @class
* @mixins CKEDITOR.event
* @constructor Creates a domObject class instance.
* @param {Object} nativeDomObject A native DOM object.
*/
CKEDITOR.dom.domObject = function( nativeDomObject ) {
if ( nativeDomObject ) {
/**
* The native DOM object represented by this class instance.
*
* var element = new CKEDITOR.dom.element( 'span' );
* alert( element.$.nodeType ); // '1'
*
* @readonly
* @property {Object}
*/
this.$ = nativeDomObject;
}
};
CKEDITOR.dom.domObject.prototype = (function() {
// Do not define other local variables here. We want to keep the native
// listener closures as clean as possible.
var getNativeListener = function( domObject, eventName ) {
return function( domEvent ) {
// In FF, when reloading the page with the editor focused, it may
// throw an error because the CKEDITOR global is not anymore
// available. So, we check it here first. (#2923)
if ( typeof CKEDITOR != 'undefined' )
domObject.fire( eventName, new CKEDITOR.dom.event( domEvent ) );
};
};
return {
/**
* Get the private `_` object which is bound to the native
* DOM object using {@link #getCustomData}.
*
* var elementA = new CKEDITOR.dom.element( nativeElement );
* elementA.getPrivate().value = 1;
* ...
* var elementB = new CKEDITOR.dom.element( nativeElement );
* elementB.getPrivate().value; // 1
*
* @returns {Object} The private object.
*/
getPrivate: function() {
var priv;
// Get the main private object from the custom data. Create it if not defined.
if ( !( priv = this.getCustomData( '_' ) ) )
this.setCustomData( '_', ( priv = {} ) );
return priv;
},
// Docs inherited from event.
on: function( eventName ) {
// We customize the "on" function here. The basic idea is that we'll have
// only one listener for a native event, which will then call all listeners
// set to the event.
// Get the listeners holder object.
var nativeListeners = this.getCustomData( '_cke_nativeListeners' );
if ( !nativeListeners ) {
nativeListeners = {};
this.setCustomData( '_cke_nativeListeners', nativeListeners );
}
// Check if we have a listener for that event.
if ( !nativeListeners[ eventName ] ) {
var listener = nativeListeners[ eventName ] = getNativeListener( this, eventName );
if ( this.$.addEventListener )
this.$.addEventListener( eventName, listener, !!CKEDITOR.event.useCapture );
else if ( this.$.attachEvent )
this.$.attachEvent( 'on' + eventName, listener );
}
// Call the original implementation.
return CKEDITOR.event.prototype.on.apply( this, arguments );
},
// Docs inherited from event.
removeListener: function( eventName ) {
// Call the original implementation.
CKEDITOR.event.prototype.removeListener.apply( this, arguments );
// If we don't have listeners for this event, clean the DOM up.
if ( !this.hasListeners( eventName ) ) {
var nativeListeners = this.getCustomData( '_cke_nativeListeners' );
var listener = nativeListeners && nativeListeners[ eventName ];
if ( listener ) {
if ( this.$.removeEventListener )
this.$.removeEventListener( eventName, listener, false );
else if ( this.$.detachEvent )
this.$.detachEvent( 'on' + eventName, listener );
delete nativeListeners[ eventName ];
}
}
},
/**
* Removes any listener set on this object.
*
* To avoid memory leaks we must assure that there are no
* references left after the object is no longer needed.
*/
removeAllListeners: function() {
var nativeListeners = this.getCustomData( '_cke_nativeListeners' );
for ( var eventName in nativeListeners ) {
var listener = nativeListeners[ eventName ];
if ( this.$.detachEvent )
this.$.detachEvent( 'on' + eventName, listener );
else if ( this.$.removeEventListener )
this.$.removeEventListener( eventName, listener, false );
delete nativeListeners[ eventName ];
}
}
};
})();
(function( domObjectProto ) {
var customData = {};
CKEDITOR.on( 'reset', function() {
customData = {};
});
/**
* Determines whether the specified object is equal to the current object.
*
* var doc = new CKEDITOR.dom.document( document );
* alert( doc.equals( CKEDITOR.document ) ); // true
* alert( doc == CKEDITOR.document ); // false
*
* @param {Object} object The object to compare with the current object.
* @returns {Boolean} `true` if the object is equal.
*/
domObjectProto.equals = function( object ) {
// Try/Catch to avoid IE permission error when object is from different document.
try {
return ( object && object.$ === this.$ );
} catch ( er ) {
return false;
}
};
/**
* Sets a data slot value for this object. These values are shared by all
* instances pointing to that same DOM object.
*
* **Note:** The created data slot is only guarantied to be available on this unique dom node,
* thus any wish to continue access it from other element clones (either created by
* clone node or from `innerHtml`) will fail, for such usage, please use
* {@link CKEDITOR.dom.element#setAttribute} instead.
*
* var element = new CKEDITOR.dom.element( 'span' );
* element.setCustomData( 'hasCustomData', true );
*
* @param {String} key A key used to identify the data slot.
* @param {Object} value The value to set to the data slot.
* @returns {CKEDITOR.dom.domObject} This DOM object instance.
* @chainable
*/
domObjectProto.setCustomData = function( key, value ) {
var expandoNumber = this.getUniqueId(),
dataSlot = customData[ expandoNumber ] || ( customData[ expandoNumber ] = {} );
dataSlot[ key ] = value;
return this;
};
/**
* Gets the value set to a data slot in this object.
*
* var element = new CKEDITOR.dom.element( 'span' );
* alert( element.getCustomData( 'hasCustomData' ) ); // e.g. 'true'
* alert( element.getCustomData( 'nonExistingKey' ) ); // null
*
* @param {String} key The key used to identify the data slot.
* @returns {Object} This value set to the data slot.
*/
domObjectProto.getCustomData = function( key ) {
var expandoNumber = this.$[ 'data-cke-expando' ],
dataSlot = expandoNumber && customData[ expandoNumber ];
return ( dataSlot && key in dataSlot ) ? dataSlot[ key ] : null;
};
/**
* Removes the value in data slot under given `key`.
*
* @param {String} key
* @returns {Object} Removed value or `null` if not found.
*/
domObjectProto.removeCustomData = function( key ) {
var expandoNumber = this.$[ 'data-cke-expando' ],
dataSlot = expandoNumber && customData[ expandoNumber ],
retval, hadKey;
if ( dataSlot ) {
retval = dataSlot[ key ];
hadKey = key in dataSlot;
delete dataSlot[ key ];
}
return hadKey ? retval : null;
};
/**
* Removes any data stored on this object.
* To avoid memory leaks we must assure that there are no
* references left after the object is no longer needed.
*/
domObjectProto.clearCustomData = function() {
// Clear all event listeners
this.removeAllListeners();
var expandoNumber = this.$[ 'data-cke-expando' ];
expandoNumber && delete customData[ expandoNumber ];
};
/**
* Gets an ID that can be used to identiquely identify this DOM object in
* the running session.
*
* @returns {Number} A unique ID.
*/
domObjectProto.getUniqueId = function() {
return this.$[ 'data-cke-expando' ] || ( this.$[ 'data-cke-expando' ] = CKEDITOR.tools.getNextNumber() );
};
// Implement CKEDITOR.event.
CKEDITOR.event.implementOn( domObjectProto );
})( CKEDITOR.dom.domObject.prototype );
+1869
View File
File diff suppressed because it is too large Load Diff
+222
View File
@@ -0,0 +1,222 @@
/**
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or http://ckeditor.com/license
*/
(function() {
// Elements that are considered the "Block limit" in an element path.
var pathBlockLimitElements = {};
for ( var tag in CKEDITOR.dtd.$blockLimit ) {
// Exclude from list roots.
if ( !( tag in CKEDITOR.dtd.$list ) )
pathBlockLimitElements[ tag ] = 1;
}
// Elements that are considered the "End level Block" in an element path.
var pathBlockElements = {};
for ( tag in CKEDITOR.dtd.$block ) {
// Exclude block limits, and empty block element, e.g. hr.
if ( !( tag in CKEDITOR.dtd.$blockLimit || tag in CKEDITOR.dtd.$empty ) )
pathBlockElements[ tag ] = 1;
}
// Check if an element contains any block element.
var checkHasBlock = function( element ) {
var childNodes = element.getChildren();
for ( var i = 0, count = childNodes.count(); i < count; i++ ) {
var child = childNodes.getItem( i );
if ( child.type == CKEDITOR.NODE_ELEMENT && CKEDITOR.dtd.$block[ child.getName() ] )
return true;
}
return false;
};
/**
* Retrieve the list of nodes walked from the start node up to the editable element of the editor.
*
* @class
* @constructor Creates a element path class instance.
* @param {CKEDITOR.dom.element} startNode From which the path should start.
* @param {CKEDITOR.dom.element} root To which element the path should stop, default to the body element.
*/
CKEDITOR.dom.elementPath = function( startNode, root ) {
var block = null;
var blockLimit = null;
var elements = [];
// Backward compact.
root = root || startNode.getDocument().getBody();
var e = startNode;
do {
if ( e.type == CKEDITOR.NODE_ELEMENT ) {
elements.push( e );
if ( !this.lastElement ) {
this.lastElement = e;
// If a table is fully selected at the end of the element path,
// it must not become the block limit.
if ( e.is( CKEDITOR.dtd.$object ) )
continue;
}
var elementName = e.getName();
if ( !blockLimit ) {
if ( !block && pathBlockElements[ elementName ] )
block = e;
if ( pathBlockLimitElements[ elementName ] ) {
// End level DIV is considered as the block, if no block is available. (#525)
// But it must NOT be as the root element.
if ( !block && elementName == 'div' && !checkHasBlock( e ) && !e.equals( root ) ) {
block = e;
} else
blockLimit = e;
}
}
if ( e.equals( root ) )
break;
}
}
while ( ( e = e.getParent() ) );
/**
* @property {CKEDITOR.dom.element}
* @todo
*/
this.block = block;
/**
* @property {CKEDITOR.dom.element}
* @todo
*/
this.blockLimit = blockLimit;
/**
* The root of the elements path - `startNode` argument passed to class constructor or body element.
*
* @property {CKEDITOR.dom.element}
* @todo
*/
this.root = root;
/**
* @property {CKEDITOR.dom.element[]}
* @todo
*/
this.elements = elements;
};
})();
CKEDITOR.dom.elementPath.prototype = {
/**
* Compares this element path with another one.
*
* @param {CKEDITOR.dom.elementPath} otherPath The elementPath object to be
* compared with this one.
* @returns {Boolean} `true` if the paths are equal, containing the same
* number of elements and the same elements in the same order.
*/
compare: function( otherPath ) {
var thisElements = this.elements;
var otherElements = otherPath && otherPath.elements;
if ( !otherElements || thisElements.length != otherElements.length )
return false;
for ( var i = 0; i < thisElements.length; i++ ) {
if ( !thisElements[ i ].equals( otherElements[ i ] ) )
return false;
}
return true;
},
/**
* Search the path elements that meets the specified criteria.
*
* @param {String/Array/Function/Object/CKEDITOR.dom.element} query The criteria that can be
* either a tag name, list (array and object) of tag names, element or an node evaluator function.
* @param {Boolean} excludeRoot Not taking path root element into consideration.
* @param {Boolean} fromTop Search start from the topmost element instead of bottom.
* @returns {CKEDITOR.dom.element} The first matched dom element or `null`.
*/
contains: function( query, excludeRoot, fromTop ) {
var evaluator;
if ( typeof query == 'string' )
evaluator = function( node ) {
return node.getName() == query;
};
if ( query instanceof CKEDITOR.dom.element )
evaluator = function( node ) {
return node.equals( query );
};
else if ( CKEDITOR.tools.isArray( query ) )
evaluator = function( node ) {
return CKEDITOR.tools.indexOf( query, node.getName() ) > -1;
};
else if ( typeof query == 'function' )
evaluator = query;
else if ( typeof query == 'object' )
evaluator = function( node ) {
return node.getName() in query;
};
var elements = this.elements,
length = elements.length;
excludeRoot && length--;
if ( fromTop ) {
elements = Array.prototype.slice.call( elements, 0 );
elements.reverse();
}
for ( var i = 0; i < length; i++ ) {
if ( evaluator( elements[ i ] ) )
return elements[ i ];
}
return null;
},
/**
* Check whether the elements path is the proper context for the specified
* tag name in the DTD.
*
* @param {String} tag The tag name.
* @returns {Boolean}
*/
isContextFor: function( tag ) {
var holder;
// Check for block context.
if ( tag in CKEDITOR.dtd.$block ) {
// Indeterminate elements which are not subjected to be splitted or surrounded must be checked first.
var inter = this.contains( CKEDITOR.dtd.$intermediate );
holder = inter || ( this.root.equals( this.block ) && this.block ) || this.blockLimit;
return !!holder.getDtd()[ tag ];
}
return true;
},
/**
* Retrieve the text direction for this elements path.
*
* @returns {'ltr'/'rtl'}
*/
direction: function() {
var directionNode = this.block || this.blockLimit || this.root;
return directionNode.getDirection( 1 );
}
};
+208
View File
@@ -0,0 +1,208 @@
/**
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or http://ckeditor.com/license
*/
/**
* @fileOverview Defines the {@link CKEDITOR.dom.event} class, which
* represents the a native DOM event object.
*/
/**
* Represents a native DOM event object.
*
* @class
* @constructor Creates an event class instance.
* @param {Object} domEvent A native DOM event object.
*/
CKEDITOR.dom.event = function( domEvent ) {
/**
* The native DOM event object represented by this class instance.
*
* @readonly
*/
this.$ = domEvent;
};
CKEDITOR.dom.event.prototype = {
/**
* Gets the key code associated to the event.
*
* alert( event.getKey() ); // '65' is 'a' has been pressed
*
* @returns {Number} The key code.
*/
getKey: function() {
return this.$.keyCode || this.$.which;
},
/**
* Gets a number represeting the combination of the keys pressed during the
* event. It is the sum with the current key code and the {@link CKEDITOR#CTRL},
* {@link CKEDITOR#SHIFT} and {@link CKEDITOR#ALT} constants.
*
* alert( event.getKeystroke() == 65 ); // 'a' key
* alert( event.getKeystroke() == CKEDITOR.CTRL + 65 ); // CTRL + 'a' key
* alert( event.getKeystroke() == CKEDITOR.CTRL + CKEDITOR.SHIFT + 65 ); // CTRL + SHIFT + 'a' key
*
* @returns {Number} The number representing the keys combination.
*/
getKeystroke: function() {
var keystroke = this.getKey();
if ( this.$.ctrlKey || this.$.metaKey )
keystroke += CKEDITOR.CTRL;
if ( this.$.shiftKey )
keystroke += CKEDITOR.SHIFT;
if ( this.$.altKey )
keystroke += CKEDITOR.ALT;
return keystroke;
},
/**
* Prevents the original behavior of the event to happen. It can optionally
* stop propagating the event in the event chain.
*
* var element = CKEDITOR.document.getById( 'myElement' );
* element.on( 'click', function( ev ) {
* // The DOM event object is passed by the 'data' property.
* var domEvent = ev.data;
* // Prevent the click to chave any effect in the element.
* domEvent.preventDefault();
* } );
*
* @param {Boolean} [stopPropagation=false] Stop propagating this event in the
* event chain.
*/
preventDefault: function( stopPropagation ) {
var $ = this.$;
if ( $.preventDefault )
$.preventDefault();
else
$.returnValue = false;
if ( stopPropagation )
this.stopPropagation();
},
/**
* Stops this event propagation in the event chain.
*/
stopPropagation: function() {
var $ = this.$;
if ( $.stopPropagation )
$.stopPropagation();
else
$.cancelBubble = true;
},
/**
* Returns the DOM node where the event was targeted to.
*
* var element = CKEDITOR.document.getById( 'myElement' );
* element.on( 'click', function( ev ) {
* // The DOM event object is passed by the 'data' property.
* var domEvent = ev.data;
* // Add a CSS class to the event target.
* domEvent.getTarget().addClass( 'clicked' );
* } );
*
* @returns {CKEDITOR.dom.node} The target DOM node.
*/
getTarget: function() {
var rawNode = this.$.target || this.$.srcElement;
return rawNode ? new CKEDITOR.dom.node( rawNode ) : null;
},
/**
* Returns an integer value that indicates the current processing phase of an event.
* For browsers that doesn't support event phase, {@link CKEDITOR#EVENT_PHASE_AT_TARGET} is always returned.
*
* @returns {Number} One of {@link CKEDITOR#EVENT_PHASE_CAPTURING},
* {@link CKEDITOR#EVENT_PHASE_AT_TARGET}, or {@link CKEDITOR#EVENT_PHASE_BUBBLING}.
*/
getPhase: function() {
return this.$.eventPhase || 2;
},
/**
* Retrieves the coordinates of the mouse pointer relative to the top-left
* corner of the document, in mouse related event.
*
* element.on( 'mousemouse', function( ev ) {
* var pageOffset = ev.data.getPageOffset();
* alert( pageOffset.x ); // page offset X
* alert( pageOffset.y ); // page offset Y
* } );
*
* @returns {Object} The object contains the position.
* @returns {Number} return.x
* @returns {Number} return.y
*/
getPageOffset : function() {
var doc = this.getTarget().getDocument().$;
var pageX = this.$.pageX || this.$.clientX + ( doc.documentElement.scrollLeft || doc.body.scrollLeft );
var pageY = this.$.pageY || this.$.clientY + ( doc.documentElement.scrollTop || doc.body.scrollTop );
return { x : pageX, y : pageY };
}
};
// For the followind constants, we need to go over the Unicode boundaries
// (0x10FFFF) to avoid collision.
/**
* CTRL key (0x110000).
*
* @readonly
* @property {Number} [=0x110000]
* @member CKEDITOR
*/
CKEDITOR.CTRL = 0x110000;
/**
* SHIFT key (0x220000).
*
* @readonly
* @property {Number} [=0x220000]
* @member CKEDITOR
*/
CKEDITOR.SHIFT = 0x220000;
/**
* ALT key (0x440000).
*
* @readonly
* @property {Number} [=0x440000]
* @member CKEDITOR
*/
CKEDITOR.ALT = 0x440000;
/**
* Capturing phase.
*
* @readonly
* @property {Number} [=1]
* @member CKEDITOR
*/
CKEDITOR.EVENT_PHASE_CAPTURING = 1;
/**
* Event at target.
*
* @readonly
* @property {Number} [=2]
* @member CKEDITOR
*/
CKEDITOR.EVENT_PHASE_AT_TARGET = 2;
/**
* Bubbling phase.
*
* @readonly
* @property {Number} [=3]
* @member CKEDITOR
*/
CKEDITOR.EVENT_PHASE_BUBBLING = 3;
+335
View File
@@ -0,0 +1,335 @@
/**
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or http://ckeditor.com/license
*/
/**
* @ignore
* File overview: DOM iterator, which iterates over list items, lines and paragraphs.
*/
(function() {
/**
* Represents iterator class.
*
* @class CKEDITOR.dom.iterator
* @constructor Creates an iterator class instance.
* @param {CKEDITOR.dom.range} range
* @todo
*/
function iterator( range ) {
if ( arguments.length < 1 )
return;
this.range = range;
this.forceBrBreak = 0;
// Whether include <br>s into the enlarged range.(#3730).
this.enlargeBr = 1;
this.enforceRealBlocks = 0;
this._ || ( this._ = {} );
}
var beginWhitespaceRegex = /^[\r\n\t ]+$/,
// Ignore bookmark nodes.(#3783)
bookmarkGuard = CKEDITOR.dom.walker.bookmark( false, true ),
whitespacesGuard = CKEDITOR.dom.walker.whitespaces( true ),
skipGuard = function( node ) {
return bookmarkGuard( node ) && whitespacesGuard( node );
};
// Get a reference for the next element, bookmark nodes are skipped.
function getNextSourceNode( node, startFromSibling, lastNode ) {
var next = node.getNextSourceNode( startFromSibling, null, lastNode );
while ( !bookmarkGuard( next ) )
next = next.getNextSourceNode( startFromSibling, null, lastNode );
return next;
}
iterator.prototype = {
/**
* @todo
*/
getNextParagraph: function( blockTag ) {
blockTag = blockTag || 'p';
// Block-less range should be checked first.
if ( !CKEDITOR.dtd[ this.range.root.getName() ][ blockTag ] )
return null;
// The block element to be returned.
var block;
// The range object used to identify the paragraph contents.
var range;
// Indicats that the current element in the loop is the last one.
var isLast;
// Indicate at least one of the range boundaries is inside a preformat block.
var touchPre;
// Instructs to cleanup remaining BRs.
var removePreviousBr, removeLastBr;
// This is the first iteration. Let's initialize it.
if ( !this._.started ) {
range = this.range.clone();
// Shrink the range to exclude harmful "noises" (#4087, #4450, #5435).
range.shrink( CKEDITOR.NODE_ELEMENT, true );
touchPre = range.endContainer.hasAscendant( 'pre', true ) || range.startContainer.hasAscendant( 'pre', true );
range.enlarge( this.forceBrBreak && !touchPre || !this.enlargeBr ? CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS : CKEDITOR.ENLARGE_BLOCK_CONTENTS );
if ( !range.collapsed ) {
var walker = new CKEDITOR.dom.walker( range.clone() ),
ignoreBookmarkTextEvaluator = CKEDITOR.dom.walker.bookmark( true, true );
// Avoid anchor inside bookmark inner text.
walker.evaluator = ignoreBookmarkTextEvaluator;
this._.nextNode = walker.next();
// TODO: It's better to have walker.reset() used here.
walker = new CKEDITOR.dom.walker( range.clone() );
walker.evaluator = ignoreBookmarkTextEvaluator;
var lastNode = walker.previous();
this._.lastNode = lastNode.getNextSourceNode( true );
// We may have an empty text node at the end of block due to [3770].
// If that node is the lastNode, it would cause our logic to leak to the
// next block.(#3887)
if ( this._.lastNode && this._.lastNode.type == CKEDITOR.NODE_TEXT && !CKEDITOR.tools.trim( this._.lastNode.getText() ) && this._.lastNode.getParent().isBlockBoundary() ) {
var testRange = this.range.clone();
testRange.moveToPosition( this._.lastNode, CKEDITOR.POSITION_AFTER_END );
if ( testRange.checkEndOfBlock() ) {
var path = new CKEDITOR.dom.elementPath( testRange.endContainer, testRange.root );
var lastBlock = path.block || path.blockLimit;
this._.lastNode = lastBlock.getNextSourceNode( true );
}
}
// Probably the document end is reached, we need a marker node.
if ( !this._.lastNode ) {
this._.lastNode = this._.docEndMarker = range.document.createText( '' );
this._.lastNode.insertAfter( lastNode );
}
// Let's reuse this variable.
range = null;
}
this._.started = 1;
}
var currentNode = this._.nextNode;
lastNode = this._.lastNode;
this._.nextNode = null;
while ( currentNode ) {
// closeRange indicates that a paragraph boundary has been found,
// so the range can be closed.
var closeRange = 0,
parentPre = currentNode.hasAscendant( 'pre' );
// includeNode indicates that the current node is good to be part
// of the range. By default, any non-element node is ok for it.
var includeNode = ( currentNode.type != CKEDITOR.NODE_ELEMENT ),
continueFromSibling = 0;
// If it is an element node, let's check if it can be part of the
// range.
if ( !includeNode ) {
var nodeName = currentNode.getName();
if ( currentNode.isBlockBoundary( this.forceBrBreak && !parentPre && { br:1 } ) ) {
// <br> boundaries must be part of the range. It will
// happen only if ForceBrBreak.
if ( nodeName == 'br' )
includeNode = 1;
else if ( !range && !currentNode.getChildCount() && nodeName != 'hr' ) {
// If we have found an empty block, and haven't started
// the range yet, it means we must return this block.
block = currentNode;
isLast = currentNode.equals( lastNode );
break;
}
// The range must finish right before the boundary,
// including possibly skipped empty spaces. (#1603)
if ( range ) {
range.setEndAt( currentNode, CKEDITOR.POSITION_BEFORE_START );
// The found boundary must be set as the next one at this
// point. (#1717)
if ( nodeName != 'br' )
this._.nextNode = currentNode;
}
closeRange = 1;
} else {
// If we have child nodes, let's check them.
if ( currentNode.getFirst() ) {
// If we don't have a range yet, let's start it.
if ( !range ) {
range = this.range.clone();
range.setStartAt( currentNode, CKEDITOR.POSITION_BEFORE_START );
}
currentNode = currentNode.getFirst();
continue;
}
includeNode = 1;
}
} else if ( currentNode.type == CKEDITOR.NODE_TEXT ) {
// Ignore normal whitespaces (i.e. not including &nbsp; or
// other unicode whitespaces) before/after a block node.
if ( beginWhitespaceRegex.test( currentNode.getText() ) )
includeNode = 0;
}
// The current node is good to be part of the range and we are
// starting a new range, initialize it first.
if ( includeNode && !range ) {
range = this.range.clone();
range.setStartAt( currentNode, CKEDITOR.POSITION_BEFORE_START );
}
// The last node has been found.
isLast = ( ( !closeRange || includeNode ) && currentNode.equals( lastNode ) );
// If we are in an element boundary, let's check if it is time
// to close the range, otherwise we include the parent within it.
if ( range && !closeRange ) {
while ( !currentNode.getNext( skipGuard ) && !isLast ) {
var parentNode = currentNode.getParent();
if ( parentNode.isBlockBoundary( this.forceBrBreak && !parentPre && { br:1 } ) ) {
closeRange = 1;
includeNode = 0;
isLast = isLast || ( parentNode.equals( lastNode ) );
// Make sure range includes bookmarks at the end of the block. (#7359)
range.setEndAt( parentNode, CKEDITOR.POSITION_BEFORE_END );
break;
}
currentNode = parentNode;
includeNode = 1;
isLast = ( currentNode.equals( lastNode ) );
continueFromSibling = 1;
}
}
// Now finally include the node.
if ( includeNode )
range.setEndAt( currentNode, CKEDITOR.POSITION_AFTER_END );
currentNode = getNextSourceNode( currentNode, continueFromSibling, lastNode );
isLast = !currentNode;
// We have found a block boundary. Let's close the range and move out of the
// loop.
if ( isLast || ( closeRange && range ) )
break;
}
// Now, based on the processed range, look for (or create) the block to be returned.
if ( !block ) {
// If no range has been found, this is the end.
if ( !range ) {
this._.docEndMarker && this._.docEndMarker.remove();
this._.nextNode = null;
return null;
}
var startPath = new CKEDITOR.dom.elementPath( range.startContainer, range.root );
var startBlockLimit = startPath.blockLimit,
checkLimits = { div:1,th:1,td:1 };
block = startPath.block;
if ( !block && startBlockLimit && !this.enforceRealBlocks && checkLimits[ startBlockLimit.getName() ] && range.checkStartOfBlock() && range.checkEndOfBlock() && !startBlockLimit.equals( range.root ) )
block = startBlockLimit;
else if ( !block || ( this.enforceRealBlocks && block.getName() == 'li' ) ) {
// Create the fixed block.
block = this.range.document.createElement( blockTag );
// Move the contents of the temporary range to the fixed block.
range.extractContents().appendTo( block );
block.trim();
// Insert the fixed block into the DOM.
range.insertNode( block );
removePreviousBr = removeLastBr = true;
} else if ( block.getName() != 'li' ) {
// If the range doesn't includes the entire contents of the
// block, we must split it, isolating the range in a dedicated
// block.
if ( !range.checkStartOfBlock() || !range.checkEndOfBlock() ) {
// The resulting block will be a clone of the current one.
block = block.clone( false );
// Extract the range contents, moving it to the new block.
range.extractContents().appendTo( block );
block.trim();
// Split the block. At this point, the range will be in the
// right position for our intents.
var splitInfo = range.splitBlock();
removePreviousBr = !splitInfo.wasStartOfBlock;
removeLastBr = !splitInfo.wasEndOfBlock;
// Insert the new block into the DOM.
range.insertNode( block );
}
} else if ( !isLast ) {
// LIs are returned as is, with all their children (due to the
// nested lists). But, the next node is the node right after
// the current range, which could be an <li> child (nested
// lists) or the next sibling <li>.
this._.nextNode = ( block.equals( lastNode ) ? null : getNextSourceNode( range.getBoundaryNodes().endNode, 1, lastNode ) );
}
}
if ( removePreviousBr ) {
var previousSibling = block.getPrevious();
if ( previousSibling && previousSibling.type == CKEDITOR.NODE_ELEMENT ) {
if ( previousSibling.getName() == 'br' )
previousSibling.remove();
else if ( previousSibling.getLast() && previousSibling.getLast().$.nodeName.toLowerCase() == 'br' )
previousSibling.getLast().remove();
}
}
if ( removeLastBr ) {
var lastChild = block.getLast();
if ( lastChild && lastChild.type == CKEDITOR.NODE_ELEMENT && lastChild.getName() == 'br' ) {
// Take care not to remove the block expanding <br> in non-IE browsers.
if ( CKEDITOR.env.ie || lastChild.getPrevious( bookmarkGuard ) || lastChild.getNext( bookmarkGuard ) )
lastChild.remove();
}
}
// Get a reference for the next element. This is important because the
// above block can be removed or changed, so we can rely on it for the
// next interation.
if ( !this._.nextNode ) {
this._.nextNode = ( isLast || block.equals( lastNode ) || !lastNode ) ? null : getNextSourceNode( block, 1, lastNode );
}
return block;
}
};
/**
* Creates {CKEDITOR.dom.iterator} instance for this range.
*
* @member CKEDITOR.dom.range
* @returns {CKEDITOR.dom.iterator}
*/
CKEDITOR.dom.range.prototype.createIterator = function() {
return new iterator( this );
};
})();
+741
View File
@@ -0,0 +1,741 @@
/**
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or http://ckeditor.com/license
*/
/**
* @fileOverview Defines the {@link CKEDITOR.dom.node} class which is the base
* class for classes that represent DOM nodes.
*/
/**
* Base class for classes representing DOM nodes. This constructor may return
* an instance of a class that inherits from this class, like
* {@link CKEDITOR.dom.element} or {@link CKEDITOR.dom.text}.
*
* @class
* @extends CKEDITOR.dom.domObject
* @constructor Creates a node class instance.
* @param {Object} domNode A native DOM node.
* @see CKEDITOR.dom.element
* @see CKEDITOR.dom.text
*/
CKEDITOR.dom.node = function( domNode ) {
if ( domNode ) {
var type = domNode.nodeType == CKEDITOR.NODE_DOCUMENT ? 'document' : domNode.nodeType == CKEDITOR.NODE_ELEMENT ? 'element' : domNode.nodeType == CKEDITOR.NODE_TEXT ? 'text' : domNode.nodeType == CKEDITOR.NODE_COMMENT ? 'comment' : domNode.nodeType == CKEDITOR.NODE_DOCUMENT_FRAGMENT ? 'documentFragment' : 'domObject'; // Call the base constructor otherwise.
return new CKEDITOR.dom[ type ]( domNode );
}
return this;
};
CKEDITOR.dom.node.prototype = new CKEDITOR.dom.domObject();
/**
* Element node type.
*
* @readonly
* @property {Number} [=1]
* @member CKEDITOR
*/
CKEDITOR.NODE_ELEMENT = 1;
/**
* Document node type.
*
* @readonly
* @property {Number} [=9]
* @member CKEDITOR
*/
CKEDITOR.NODE_DOCUMENT = 9;
/**
* Text node type.
*
* @readonly
* @property {Number} [=3]
* @member CKEDITOR
*/
CKEDITOR.NODE_TEXT = 3;
/**
* Comment node type.
*
* @readonly
* @property {Number} [=8]
* @member CKEDITOR
*/
CKEDITOR.NODE_COMMENT = 8;
/**
* Document fragment node type.
*
* @readonly
* @property {Number} [=11]
* @member CKEDITOR
*/
CKEDITOR.NODE_DOCUMENT_FRAGMENT = 11;
CKEDITOR.POSITION_IDENTICAL = 0;
CKEDITOR.POSITION_DISCONNECTED = 1;
CKEDITOR.POSITION_FOLLOWING = 2;
CKEDITOR.POSITION_PRECEDING = 4;
CKEDITOR.POSITION_IS_CONTAINED = 8;
CKEDITOR.POSITION_CONTAINS = 16;
CKEDITOR.tools.extend( CKEDITOR.dom.node.prototype, {
/**
* Makes this node a child of another element.
*
* var p = new CKEDITOR.dom.element( 'p' );
* var strong = new CKEDITOR.dom.element( 'strong' );
* strong.appendTo( p );
*
* // Result: '<p><strong></strong></p>'.
*
* @param {CKEDITOR.dom.element} element The target element to which this node will be appended.
* @returns {CKEDITOR.dom.element} The target element.
*/
appendTo: function( element, toStart ) {
element.append( this, toStart );
return element;
},
/**
* Clone this node.
*
* **Note**: Values set by {#setCustomData} won't be available in the clone.
*
* @param {Boolean} [includeChildren=false] If `true` then all node's
* children will be cloned recursively.
* @param {Boolean} [cloneId=false] Whether ID attributes should be cloned too.
* @returns {CKEDITOR.dom.node} Clone of this node.
*/
clone: function( includeChildren, cloneId ) {
var $clone = this.$.cloneNode( includeChildren );
var removeIds = function( node ) {
// Reset data-cke-expando only when has been cloned (IE and only for some types of objects).
if ( node['data-cke-expando'] )
node['data-cke-expando'] = false;
if ( node.nodeType != CKEDITOR.NODE_ELEMENT )
return;
if ( !cloneId )
node.removeAttribute( 'id', false );
if ( includeChildren ) {
var childs = node.childNodes;
for ( var i = 0; i < childs.length; i++ )
removeIds( childs[ i ] );
}
};
// The "id" attribute should never be cloned to avoid duplication.
removeIds( $clone );
return new CKEDITOR.dom.node( $clone );
},
/**
* Check if node is preceded by any sibling.
*
* @returns {Boolean}
*/
hasPrevious: function() {
return !!this.$.previousSibling;
},
/**
* Check if node is succeeded by any sibling.
*
* @returns {Boolean}
*/
hasNext: function() {
return !!this.$.nextSibling;
},
/**
* Inserts this element after a node.
*
* var em = new CKEDITOR.dom.element( 'em' );
* var strong = new CKEDITOR.dom.element( 'strong' );
* strong.insertAfter( em );
*
* // Result: '<em></em><strong></strong>'
*
* @param {CKEDITOR.dom.node} node The node that will precede this element.
* @returns {CKEDITOR.dom.node} The node preceding this one after insertion.
*/
insertAfter: function( node ) {
node.$.parentNode.insertBefore( this.$, node.$.nextSibling );
return node;
},
/**
* Inserts this element before a node.
*
* var em = new CKEDITOR.dom.element( 'em' );
* var strong = new CKEDITOR.dom.element( 'strong' );
* strong.insertBefore( em );
*
* // result: '<strong></strong><em></em>'
*
* @param {CKEDITOR.dom.node} node The node that will succeed this element.
* @returns {CKEDITOR.dom.node} The node being inserted.
*/
insertBefore: function( node ) {
node.$.parentNode.insertBefore( this.$, node.$ );
return node;
},
/**
* Inserts node before this node.
*
* var em = new CKEDITOR.dom.element( 'em' );
* var strong = new CKEDITOR.dom.element( 'strong' );
* strong.insertBeforeMe( em );
*
* // result: '<em></em><strong></strong>'
*
* @param {CKEDITOR.dom.node} node The node that will preceed this element.
* @returns {CKEDITOR.dom.node} The node being inserted.
*/
insertBeforeMe: function( node ) {
this.$.parentNode.insertBefore( node.$, this.$ );
return node;
},
/**
* Retrieves a uniquely identifiable tree address for this node.
* The tree address returned is an array of integers, with each integer
* indicating a child index of a DOM node, starting from
* `document.documentElement`.
*
* For example, assuming `<body>` is the second child
* of `<html>` (`<head>` being the first),
* and we would like to address the third child under the
* fourth child of `<body>`, the tree address returned would be:
* `[1, 3, 2]`.
*
* The tree address cannot be used for finding back the DOM tree node once
* the DOM tree structure has been modified.
*
* @param {Boolean} [normalized=false] See {@link #getIndex}.
* @returns {Array} The address.
*/
getAddress: function( normalized ) {
var address = [];
var $documentElement = this.getDocument().$.documentElement;
var node = this.$;
while ( node && node != $documentElement ) {
var parentNode = node.parentNode;
if ( parentNode ) {
// Get the node index. For performance, call getIndex
// directly, instead of creating a new node object.
address.unshift( this.getIndex.call({ $: node }, normalized ) );
}
node = parentNode;
}
return address;
},
/**
* Gets the document containing this element.
*
* var element = CKEDITOR.document.getById( 'example' );
* alert( element.getDocument().equals( CKEDITOR.document ) ); // true
*
* @returns {CKEDITOR.dom.document} The document.
*/
getDocument: function() {
return new CKEDITOR.dom.document( this.$.ownerDocument || this.$.parentNode.ownerDocument );
},
/**
* Get index of a node in an array of its parent.childNodes.
*
* Let's assume having childNodes array:
*
* [ emptyText, element1, text, text, element2 ]
* element1.getIndex(); // 1
* element1.getIndex( true ); // 0
* element2.getIndex(); // 4
* element2.getIndex( true ); // 2
*
* @param {Boolean} normalized When `true` empty text nodes and one followed
* by another one text node are not counted in.
* @returns {Number} Index of a node.
*/
getIndex: function( normalized ) {
// Attention: getAddress depends on this.$
// getIndex is called on a plain object: { $ : node }
var current = this.$,
index = -1,
isNormalizing;
if ( !this.$.parentNode )
return index;
do {
// Bypass blank node and adjacent text nodes.
if ( normalized && current != this.$ && current.nodeType == CKEDITOR.NODE_TEXT && ( isNormalizing || !current.nodeValue ) ) {
continue;
}
index++;
isNormalizing = current.nodeType == CKEDITOR.NODE_TEXT;
}
while ( ( current = current.previousSibling ) )
return index;
},
/**
* @todo
*/
getNextSourceNode: function( startFromSibling, nodeType, guard ) {
// If "guard" is a node, transform it in a function.
if ( guard && !guard.call ) {
var guardNode = guard;
guard = function( node ) {
return !node.equals( guardNode );
};
}
var node = ( !startFromSibling && this.getFirst && this.getFirst() ),
parent;
// Guarding when we're skipping the current element( no children or 'startFromSibling' ).
// send the 'moving out' signal even we don't actually dive into.
if ( !node ) {
if ( this.type == CKEDITOR.NODE_ELEMENT && guard && guard( this, true ) === false )
return null;
node = this.getNext();
}
while ( !node && ( parent = ( parent || this ).getParent() ) ) {
// The guard check sends the "true" paramenter to indicate that
// we are moving "out" of the element.
if ( guard && guard( parent, true ) === false )
return null;
node = parent.getNext();
}
if ( !node )
return null;
if ( guard && guard( node ) === false )
return null;
if ( nodeType && nodeType != node.type )
return node.getNextSourceNode( false, nodeType, guard );
return node;
},
/**
* @todo
*/
getPreviousSourceNode: function( startFromSibling, nodeType, guard ) {
if ( guard && !guard.call ) {
var guardNode = guard;
guard = function( node ) {
return !node.equals( guardNode );
};
}
var node = ( !startFromSibling && this.getLast && this.getLast() ),
parent;
// Guarding when we're skipping the current element( no children or 'startFromSibling' ).
// send the 'moving out' signal even we don't actually dive into.
if ( !node ) {
if ( this.type == CKEDITOR.NODE_ELEMENT && guard && guard( this, true ) === false )
return null;
node = this.getPrevious();
}
while ( !node && ( parent = ( parent || this ).getParent() ) ) {
// The guard check sends the "true" paramenter to indicate that
// we are moving "out" of the element.
if ( guard && guard( parent, true ) === false )
return null;
node = parent.getPrevious();
}
if ( !node )
return null;
if ( guard && guard( node ) === false )
return null;
if ( nodeType && node.type != nodeType )
return node.getPreviousSourceNode( false, nodeType, guard );
return node;
},
/**
* Gets the node that preceed this element in its parent's child list.
*
* var element = CKEDITOR.dom.element.createFromHtml( '<div><i>prev</i><b>Example</b></div>' );
* var first = element.getLast().getPrev();
* alert( first.getName() ); // 'i'
*
* @param {Function} [evaluator] Filtering the result node.
* @returns {CKEDITOR.dom.node} The previous node or null if not available.
*/
getPrevious: function( evaluator ) {
var previous = this.$,
retval;
do {
previous = previous.previousSibling;
// Avoid returning the doc type node.
// http://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-412266927
retval = previous && previous.nodeType != 10 && new CKEDITOR.dom.node( previous );
}
while ( retval && evaluator && !evaluator( retval ) )
return retval;
},
/**
* Gets the node that follows this element in its parent's child list.
*
* var element = CKEDITOR.dom.element.createFromHtml( '<div><b>Example</b><i>next</i></div>' );
* var last = element.getFirst().getNext();
* alert( last.getName() ); // 'i'
*
* @param {Function} [evaluator] Filtering the result node.
* @returns {CKEDITOR.dom.node} The next node or null if not available.
*/
getNext: function( evaluator ) {
var next = this.$,
retval;
do {
next = next.nextSibling;
retval = next && new CKEDITOR.dom.node( next );
}
while ( retval && evaluator && !evaluator( retval ) )
return retval;
},
/**
* Gets the parent element for this node.
*
* var node = editor.document.getBody().getFirst();
* var parent = node.getParent();
* alert( node.getName() ); // 'body'
*
* @param {Boolean} [allowFragmentParent=false] Consider also parent node that is of
* fragment type {@link CKEDITOR#NODE_DOCUMENT_FRAGMENT}.
* @returns {CKEDITOR.dom.element} The parent element.
*/
getParent: function( allowFragmentParent ) {
var parent = this.$.parentNode;
return ( parent && ( parent.nodeType == CKEDITOR.NODE_ELEMENT || allowFragmentParent && parent.nodeType == CKEDITOR.NODE_DOCUMENT_FRAGMENT ) ) ? new CKEDITOR.dom.node( parent ) : null;
},
/**
* @todo
*/
getParents: function( closerFirst ) {
var node = this;
var parents = [];
do {
parents[ closerFirst ? 'push' : 'unshift' ]( node );
}
while ( ( node = node.getParent() ) )
return parents;
},
/**
* @todo
*/
getCommonAncestor: function( node ) {
if ( node.equals( this ) )
return this;
if ( node.contains && node.contains( this ) )
return node;
var start = this.contains ? this : this.getParent();
do {
if ( start.contains( node ) ) return start;
}
while ( ( start = start.getParent() ) );
return null;
},
/**
* @todo
*/
getPosition: function( otherNode ) {
var $ = this.$;
var $other = otherNode.$;
if ( $.compareDocumentPosition )
return $.compareDocumentPosition( $other );
// IE and Safari have no support for compareDocumentPosition.
if ( $ == $other )
return CKEDITOR.POSITION_IDENTICAL;
// Only element nodes support contains and sourceIndex.
if ( this.type == CKEDITOR.NODE_ELEMENT && otherNode.type == CKEDITOR.NODE_ELEMENT ) {
if ( $.contains ) {
if ( $.contains( $other ) )
return CKEDITOR.POSITION_CONTAINS + CKEDITOR.POSITION_PRECEDING;
if ( $other.contains( $ ) )
return CKEDITOR.POSITION_IS_CONTAINED + CKEDITOR.POSITION_FOLLOWING;
}
if ( 'sourceIndex' in $ ) {
return ( $.sourceIndex < 0 || $other.sourceIndex < 0 ) ? CKEDITOR.POSITION_DISCONNECTED : ( $.sourceIndex < $other.sourceIndex ) ? CKEDITOR.POSITION_PRECEDING : CKEDITOR.POSITION_FOLLOWING;
}
}
// For nodes that don't support compareDocumentPosition, contains
// or sourceIndex, their "address" is compared.
var addressOfThis = this.getAddress(),
addressOfOther = otherNode.getAddress(),
minLevel = Math.min( addressOfThis.length, addressOfOther.length );
// Determinate preceed/follow relationship.
for ( var i = 0; i <= minLevel - 1; i++ ) {
if ( addressOfThis[ i ] != addressOfOther[ i ] ) {
if ( i < minLevel ) {
return addressOfThis[ i ] < addressOfOther[ i ] ? CKEDITOR.POSITION_PRECEDING : CKEDITOR.POSITION_FOLLOWING;
}
break;
}
}
// Determinate contains/contained relationship.
return ( addressOfThis.length < addressOfOther.length ) ? CKEDITOR.POSITION_CONTAINS + CKEDITOR.POSITION_PRECEDING : CKEDITOR.POSITION_IS_CONTAINED + CKEDITOR.POSITION_FOLLOWING;
},
/**
* Gets the closest ancestor node of this node, specified by its name.
*
* // Suppose we have the following HTML structure:
* // <div id="outer"><div id="inner"><p><b>Some text</b></p></div></div>
* // If node == <b>
* ascendant = node.getAscendant( 'div' ); // ascendant == <div id="inner">
* ascendant = node.getAscendant( 'b' ); // ascendant == null
* ascendant = node.getAscendant( 'b', true ); // ascendant == <b>
* ascendant = node.getAscendant( { div:1,p:1 } ); // Searches for the first 'div' or 'p': ascendant == <div id="inner">
*
* @since 3.6.1
* @param {String} reference The name of the ancestor node to search or
* an object with the node names to search for.
* @param {Boolean} [includeSelf] Whether to include the current
* node in the search.
* @returns {CKEDITOR.dom.node} The located ancestor node or null if not found.
*/
getAscendant: function( reference, includeSelf ) {
var $ = this.$,
name;
if ( !includeSelf )
$ = $.parentNode;
while ( $ ) {
if ( $.nodeName && ( name = $.nodeName.toLowerCase(), ( typeof reference == 'string' ? name == reference : name in reference ) ) )
return new CKEDITOR.dom.node( $ );
try {
$ = $.parentNode;
} catch( e ) {
$ = null;
}
}
return null;
},
/**
* @todo
*/
hasAscendant: function( name, includeSelf ) {
var $ = this.$;
if ( !includeSelf )
$ = $.parentNode;
while ( $ ) {
if ( $.nodeName && $.nodeName.toLowerCase() == name )
return true;
$ = $.parentNode;
}
return false;
},
/**
* @todo
*/
move: function( target, toStart ) {
target.append( this.remove(), toStart );
},
/**
* Removes this node from the document DOM.
*
* var element = CKEDITOR.document.getById( 'MyElement' );
* element.remove();
*
* @param {Boolean} [preserveChildren=false] Indicates that the children
* elements must remain in the document, removing only the outer tags.
*/
remove: function( preserveChildren ) {
var $ = this.$;
var parent = $.parentNode;
if ( parent ) {
if ( preserveChildren ) {
// Move all children before the node.
for ( var child;
( child = $.firstChild ); ) {
parent.insertBefore( $.removeChild( child ), $ );
}
}
parent.removeChild( $ );
}
return this;
},
/**
* @todo
*/
replace: function( nodeToReplace ) {
this.insertBefore( nodeToReplace );
nodeToReplace.remove();
},
/**
* @todo
*/
trim: function() {
this.ltrim();
this.rtrim();
},
/**
* @todo
*/
ltrim: function() {
var child;
while ( this.getFirst && ( child = this.getFirst() ) ) {
if ( child.type == CKEDITOR.NODE_TEXT ) {
var trimmed = CKEDITOR.tools.ltrim( child.getText() ),
originalLength = child.getLength();
if ( !trimmed ) {
child.remove();
continue;
} else if ( trimmed.length < originalLength ) {
child.split( originalLength - trimmed.length );
// IE BUG: child.remove() may raise JavaScript errors here. (#81)
this.$.removeChild( this.$.firstChild );
}
}
break;
}
},
/**
* @todo
*/
rtrim: function() {
var child;
while ( this.getLast && ( child = this.getLast() ) ) {
if ( child.type == CKEDITOR.NODE_TEXT ) {
var trimmed = CKEDITOR.tools.rtrim( child.getText() ),
originalLength = child.getLength();
if ( !trimmed ) {
child.remove();
continue;
} else if ( trimmed.length < originalLength ) {
child.split( trimmed.length );
// IE BUG: child.getNext().remove() may raise JavaScript errors here.
// (#81)
this.$.lastChild.parentNode.removeChild( this.$.lastChild );
}
}
break;
}
if ( !CKEDITOR.env.ie && !CKEDITOR.env.opera ) {
child = this.$.lastChild;
if ( child && child.type == 1 && child.nodeName.toLowerCase() == 'br' ) {
// Use "eChildNode.parentNode" instead of "node" to avoid IE bug (#324).
child.parentNode.removeChild( child );
}
}
},
/**
* Checks if this node is read-only (should not be changed).
*
* **Note:** When `attributeCheck` is not used, this method only work for elements
* that are already presented in the document, otherwise the result
* is not guaranteed, it's mainly for performance consideration.
*
* // For the following HTML:
* // <div contenteditable="false">Some <b>text</b></div>
*
* // If "ele" is the above <div>
* element.isReadOnly(); // true
*
* @since 3.5
* @returns {Boolean}
*/
isReadOnly: function() {
var element = this;
if ( this.type != CKEDITOR.NODE_ELEMENT )
element = this.getParent();
if ( element && typeof element.$.isContentEditable != 'undefined' )
return !( element.$.isContentEditable || element.data( 'cke-editable' ) );
else {
// Degrade for old browsers which don't support "isContentEditable", e.g. FF3
while ( element ) {
if ( element.data( 'cke-editable' ) )
break;
if ( element.getAttribute( 'contentEditable' ) == 'false' )
return true;
else if ( element.getAttribute( 'contentEditable' ) == 'true' )
break;
element = element.getParent();
}
// Reached the root of DOM tree, no editable found.
return !element;
}
}
});
+43
View File
@@ -0,0 +1,43 @@
/**
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or http://ckeditor.com/license
*/
/**
* Represents a list of {@link CKEDITOR.dom.node} objects.
* It's a wrapper for native nodes list.
*
* var nodeList = CKEDITOR.document.getBody().getChildren();
* alert( nodeList.count() ); // number [0;N]
*
* @class
* @constructor Creates a document class instance.
* @param {Object} nativeList
*/
CKEDITOR.dom.nodeList = function( nativeList ) {
this.$ = nativeList;
};
CKEDITOR.dom.nodeList.prototype = {
/**
* Get count of nodes in this list.
*
* @returns {Number}
*/
count: function() {
return this.$.length;
},
/**
* Get node from the list.
*
* @returns {CKEDITOR.dom.node}
*/
getItem: function( index ) {
if ( index < 0 || index >= this.$.length )
return null;
var $node = this.$[ index ];
return $node ? new CKEDITOR.dom.node( $node ) : null;
}
};
+2200
View File
File diff suppressed because it is too large Load Diff
+201
View File
@@ -0,0 +1,201 @@
/**
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or http://ckeditor.com/license
*/
(function() {
/**
* Represents a list os CKEDITOR.dom.range objects, which can be easily
* iterated sequentially.
*
* @class
* @extends Array
* @constructor Creates a rangeList class instance.
* @param {CKEDITOR.dom.range/CKEDITOR.dom.range[]} [ranges] The ranges contained on this list.
* Note that, if an array of ranges is specified, the range sequence
* should match its DOM order. This class will not help to sort them.
*/
CKEDITOR.dom.rangeList = function( ranges ) {
if ( ranges instanceof CKEDITOR.dom.rangeList )
return ranges;
if ( !ranges )
ranges = [];
else if ( ranges instanceof CKEDITOR.dom.range )
ranges = [ ranges ];
return CKEDITOR.tools.extend( ranges, mixins );
};
var mixins = {
/**
* Creates an instance of the rangeList iterator, it should be used
* only when the ranges processing could be DOM intrusive, which
* means it may pollute and break other ranges in this list.
* Otherwise, it's enough to just iterate over this array in a for loop.
*
* @returns {CKEDITOR.dom.rangeListIterator}
*/
createIterator: function() {
var rangeList = this,
bookmark = CKEDITOR.dom.walker.bookmark(),
guard = function( node ) {
return !( node.is && node.is( 'tr' ) );
},
bookmarks = [],
current;
return {
/**
* Retrieves the next range in the list.
*
* @member CKEDITOR.dom.rangeListIterator
* @param {Boolean} [mergeConsequent=false] Whether join two adjacent
* ranges into single, e.g. consequent table cells.
*/
getNextRange: function( mergeConsequent ) {
current = current == undefined ? 0 : current + 1;
var range = rangeList[ current ];
// Multiple ranges might be mangled by each other.
if ( range && rangeList.length > 1 ) {
// Bookmarking all other ranges on the first iteration,
// the range correctness after it doesn't matter since we'll
// restore them before the next iteration.
if ( !current ) {
// Make sure bookmark correctness by reverse processing.
for ( var i = rangeList.length - 1; i >= 0; i-- )
bookmarks.unshift( rangeList[ i ].createBookmark( true ) );
}
if ( mergeConsequent ) {
// Figure out how many ranges should be merged.
var mergeCount = 0;
while ( rangeList[ current + mergeCount + 1 ] ) {
var doc = range.document,
found = 0,
left = doc.getById( bookmarks[ mergeCount ].endNode ),
right = doc.getById( bookmarks[ mergeCount + 1 ].startNode ),
next;
// Check subsequent range.
while ( 1 ) {
next = left.getNextSourceNode( false );
if ( !right.equals( next ) ) {
// This could be yet another bookmark or
// walking across block boundaries.
if ( bookmark( next ) || ( next.type == CKEDITOR.NODE_ELEMENT && next.isBlockBoundary() ) ) {
left = next;
continue;
}
} else
found = 1;
break;
}
if ( !found )
break;
mergeCount++;
}
}
range.moveToBookmark( bookmarks.shift() );
// Merge ranges finally after moving to bookmarks.
while ( mergeCount-- ) {
next = rangeList[ ++current ];
next.moveToBookmark( bookmarks.shift() );
range.setEnd( next.endContainer, next.endOffset );
}
}
return range;
}
};
},
/**
* Create bookmarks for all ranges. See {@link CKEDITOR.dom.range#createBookmark}.
*
* @param {Boolean} [serializable=false] See {@link CKEDITOR.dom.range#createBookmark}.
* @returns {Array} Array of bookmarks.
*/
createBookmarks: function( serializable ) {
var retval = [],
bookmark;
for ( var i = 0; i < this.length; i++ ) {
retval.push( bookmark = this[ i ].createBookmark( serializable, true ) );
// Updating the container & offset values for ranges
// that have been touched.
for ( var j = i + 1; j < this.length; j++ ) {
this[ j ] = updateDirtyRange( bookmark, this[ j ] );
this[ j ] = updateDirtyRange( bookmark, this[ j ], true );
}
}
return retval;
},
/**
* Create "unobtrusive" bookmarks for all ranges. See {@link CKEDITOR.dom.range#createBookmark2}.
*
* @param {Boolean} [normalized=false] See {@link CKEDITOR.dom.range#createBookmark2}.
* @returns {Array} Array of bookmarks.
*/
createBookmarks2: function( normalized ) {
var bookmarks = [];
for ( var i = 0; i < this.length; i++ )
bookmarks.push( this[ i ].createBookmark2( normalized ) );
return bookmarks;
},
/**
* Move each range in the list to the position specified by a list of bookmarks.
*
* @param {Array} bookmarks The list of bookmarks, each one matching a range in the list.
*/
moveToBookmarks: function( bookmarks ) {
for ( var i = 0; i < this.length; i++ )
this[ i ].moveToBookmark( bookmarks[ i ] );
}
};
// Update the specified range which has been mangled by previous insertion of
// range bookmark nodes.(#3256)
function updateDirtyRange( bookmark, dirtyRange, checkEnd ) {
var serializable = bookmark.serializable,
container = dirtyRange[ checkEnd ? 'endContainer' : 'startContainer' ],
offset = checkEnd ? 'endOffset' : 'startOffset';
var bookmarkStart = serializable ? dirtyRange.document.getById( bookmark.startNode ) : bookmark.startNode;
var bookmarkEnd = serializable ? dirtyRange.document.getById( bookmark.endNode ) : bookmark.endNode;
if ( container.equals( bookmarkStart.getPrevious() ) ) {
dirtyRange.startOffset = dirtyRange.startOffset - container.getLength() - bookmarkEnd.getPrevious().getLength();
container = bookmarkEnd.getNext();
} else if ( container.equals( bookmarkEnd.getPrevious() ) ) {
dirtyRange.startOffset = dirtyRange.startOffset - container.getLength();
container = bookmarkEnd.getNext();
}
container.equals( bookmarkStart.getParent() ) && dirtyRange[ offset ]++;
container.equals( bookmarkEnd.getParent() ) && dirtyRange[ offset ]++;
// Update and return this range.
dirtyRange[ checkEnd ? 'endContainer' : 'startContainer' ] = container;
return dirtyRange;
}
})();
/**
* (Virtual Class) Do not call this constructor. This class is not really part
* of the API. It just describes the return type of {@link CKEDITOR.dom.rangeList#createIterator}.
*
* @class CKEDITOR.dom.rangeListIterator
*/
+139
View File
@@ -0,0 +1,139 @@
/**
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or http://ckeditor.com/license
*/
/**
* @fileOverview Defines the {@link CKEDITOR.dom.text} class, which represents
* a DOM text node.
*/
/**
* Represents a DOM text node.
*
* var nativeNode = document.createTextNode( 'Example' );
* var text = CKEDITOR.dom.text( nativeNode );
*
* var text = CKEDITOR.dom.text( 'Example' );
*
* @class
* @extends CKEDITOR.dom.node
* @constructor Creates a text class instance.
* @param {Object/String} text A native DOM text node or a string containing
* the text to use to create a new text node.
* @param {CKEDITOR.dom.document} [ownerDocument] The document that will contain
* the node in case of new node creation. Defaults to the current document.
*/
CKEDITOR.dom.text = function( text, ownerDocument ) {
if ( typeof text == 'string' )
text = ( ownerDocument ? ownerDocument.$ : document ).createTextNode( text );
// Theoretically, we should call the base constructor here
// (not CKEDITOR.dom.node though). But, IE doesn't support expando
// properties on text node, so the features provided by domObject will not
// work for text nodes (which is not a big issue for us).
//
// CKEDITOR.dom.domObject.call( this, element );
this.$ = text;
};
CKEDITOR.dom.text.prototype = new CKEDITOR.dom.node();
CKEDITOR.tools.extend( CKEDITOR.dom.text.prototype, {
/**
* The node type. This is a constant value set to {@link CKEDITOR#NODE_TEXT}.
*
* @readonly
* @property {Number} [=CKEDITOR.NODE_TEXT]
*/
type: CKEDITOR.NODE_TEXT,
/**
* Gets length of node's value.
*
* @returns {Number}
*/
getLength: function() {
return this.$.nodeValue.length;
},
/**
* Gets node's value.
*
* @returns {String}
*/
getText: function() {
return this.$.nodeValue;
},
/**
* Sets node's value.
*
* @param {String} text
*/
setText: function( text ) {
this.$.nodeValue = text;
},
/**
* Breaks this text node into two nodes at the specified offset,
* keeping both in the tree as siblings. This node then only contains
* all the content up to the offset point. A new text node, which is
* inserted as the next sibling of this node, contains all the content
* at and after the offset point. When the offset is equal to the
* length of this node, the new node has no data.
*
* @param {Number} The position at which to split, starting from zero.
* @returns {CKEDITOR.dom.text} The new text node.
*/
split: function( offset ) {
// Saved the children count and text length beforehand.
var parent = this.$.parentNode,
count = parent.childNodes.length,
length = this.getLength();
var doc = this.getDocument();
var retval = new CKEDITOR.dom.text( this.$.splitText( offset ), doc );
if ( parent.childNodes.length == count )
{
// If the offset is after the last char, IE creates the text node
// on split, but don't include it into the DOM. So, we have to do
// that manually here.
if ( offset >= length )
{
retval = doc.createText( '' );
retval.insertAfter( this );
}
else
{
// IE BUG: IE8+ does not update the childNodes array in DOM after splitText(),
// we need to make some DOM changes to make it update. (#3436)
var workaround = doc.createText( '' );
workaround.insertAfter( retval );
workaround.remove();
}
}
return retval;
},
/**
* Extracts characters from indexA up to but not including `indexB`.
*
* @param {Number} indexA An integer between `0` and one less than the
* length of the text.
* @param {Number} [indexB] An integer between `0` and the length of the
* string. If omitted, extracts characters to the end of the text.
*/
substring: function( indexA, indexB ) {
// We need the following check due to a Firefox bug
// https://bugzilla.mozilla.org/show_bug.cgi?id=458886
if ( typeof indexB != 'number' )
return this.$.nodeValue.substr( indexA );
else
return this.$.nodeValue.substring( indexA, indexB );
}
});
+477
View File
@@ -0,0 +1,477 @@
/**
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or http://ckeditor.com/license
*/
(function() {
// This function is to be called under a "walker" instance scope.
function iterate( rtl, breakOnFalse ) {
var range = this.range;
// Return null if we have reached the end.
if ( this._.end )
return null;
// This is the first call. Initialize it.
if ( !this._.start ) {
this._.start = 1;
// A collapsed range must return null at first call.
if ( range.collapsed ) {
this.end();
return null;
}
// Move outside of text node edges.
range.optimize();
}
var node,
startCt = range.startContainer,
endCt = range.endContainer,
startOffset = range.startOffset,
endOffset = range.endOffset,
guard,
userGuard = this.guard,
type = this.type,
getSourceNodeFn = ( rtl ? 'getPreviousSourceNode' : 'getNextSourceNode' );
// Create the LTR guard function, if necessary.
if ( !rtl && !this._.guardLTR ) {
// The node that stops walker from moving up.
var limitLTR = endCt.type == CKEDITOR.NODE_ELEMENT ? endCt : endCt.getParent();
// The node that stops the walker from going to next.
var blockerLTR = endCt.type == CKEDITOR.NODE_ELEMENT ? endCt.getChild( endOffset ) : endCt.getNext();
this._.guardLTR = function( node, movingOut ) {
return ( ( !movingOut || !limitLTR.equals( node ) ) && ( !blockerLTR || !node.equals( blockerLTR ) ) && ( node.type != CKEDITOR.NODE_ELEMENT || !movingOut || !node.equals( range.root ) ) );
};
}
// Create the RTL guard function, if necessary.
if ( rtl && !this._.guardRTL ) {
// The node that stops walker from moving up.
var limitRTL = startCt.type == CKEDITOR.NODE_ELEMENT ? startCt : startCt.getParent();
// The node that stops the walker from going to next.
var blockerRTL = startCt.type == CKEDITOR.NODE_ELEMENT ? startOffset ? startCt.getChild( startOffset - 1 ) : null : startCt.getPrevious();
this._.guardRTL = function( node, movingOut ) {
return ( ( !movingOut || !limitRTL.equals( node ) ) && ( !blockerRTL || !node.equals( blockerRTL ) ) && ( node.type != CKEDITOR.NODE_ELEMENT || !movingOut || !node.equals( range.root ) ) );
};
}
// Define which guard function to use.
var stopGuard = rtl ? this._.guardRTL : this._.guardLTR;
// Make the user defined guard function participate in the process,
// otherwise simply use the boundary guard.
if ( userGuard ) {
guard = function( node, movingOut ) {
if ( stopGuard( node, movingOut ) === false )
return false;
return userGuard( node, movingOut );
};
} else
guard = stopGuard;
if ( this.current )
node = this.current[ getSourceNodeFn ]( false, type, guard );
else {
// Get the first node to be returned.
if ( rtl ) {
node = endCt;
if ( node.type == CKEDITOR.NODE_ELEMENT ) {
if ( endOffset > 0 )
node = node.getChild( endOffset - 1 );
else
node = ( guard( node, true ) === false ) ? null : node.getPreviousSourceNode( true, type, guard );
}
} else {
node = startCt;
if ( node.type == CKEDITOR.NODE_ELEMENT ) {
if ( !( node = node.getChild( startOffset ) ) )
node = ( guard( startCt, true ) === false ) ? null : startCt.getNextSourceNode( true, type, guard );
}
}
if ( node && guard( node ) === false )
node = null;
}
while ( node && !this._.end ) {
this.current = node;
if ( !this.evaluator || this.evaluator( node ) !== false ) {
if ( !breakOnFalse )
return node;
} else if ( breakOnFalse && this.evaluator )
return false;
node = node[ getSourceNodeFn ]( false, type, guard );
}
this.end();
return this.current = null;
}
function iterateToLast( rtl ) {
var node,
last = null;
while ( ( node = iterate.call( this, rtl ) ) )
last = node;
return last;
}
/**
* Utility class to "walk" the DOM inside a range boundaries. If
* necessary, partially included nodes (text nodes) are broken to
* reflect the boundaries limits, so DOM and range changes may happen.
* Outside changes to the range may break the walker.
*
* The walker may return nodes that are not totaly included into the
* range boundaires. Let's take the following range representation,
* where the square brackets indicate the boundaries:
*
* [<p>Some <b>sample] text</b>
*
* While walking forward into the above range, the following nodes are
* returned: `<p>`, `"Some "`, `<b>` and `"sample"`. Going
* backwards instead we have: `"sample"` and `"Some "`. So note that the
* walker always returns nodes when "entering" them, but not when
* "leaving" them. The guard function is instead called both when
* entering and leaving nodes.
*
* @class
*/
CKEDITOR.dom.walker = CKEDITOR.tools.createClass({
/**
* Creates a walker class instance.
*
* @constructor
* @param {CKEDITOR.dom.range} range The range within which walk.
*/
$: function( range ) {
this.range = range;
/**
* A function executed for every matched node, to check whether
* it's to be considered into the walk or not. If not provided, all
* matched nodes are considered good.
*
* If the function returns `false` the node is ignored.
*
* @property {Function} evaluator
*/
// this.evaluator = null;
/**
* A function executed for every node the walk pass by to check
* whether the walk is to be finished. It's called when both
* entering and exiting nodes, as well as for the matched nodes.
*
* If this function returns `false`, the walking ends and no more
* nodes are evaluated.
* @property {Function} guard
*/
// this.guard = null;
/** @private */
this._ = {};
},
// statics :
// {
// /* Creates a CKEDITOR.dom.walker instance to walk inside DOM boundaries set by nodes.
// * @param {CKEDITOR.dom.node} startNode The node from wich the walk
// * will start.
// * @param {CKEDITOR.dom.node} [endNode] The last node to be considered
// * in the walk. No more nodes are retrieved after touching or
// * passing it. If not provided, the walker stops at the
// * &lt;body&gt; closing boundary.
// * @returns {CKEDITOR.dom.walker} A DOM walker for the nodes between the
// * provided nodes.
// */
// createOnNodes : function( startNode, endNode, startInclusive, endInclusive )
// {
// var range = new CKEDITOR.dom.range();
// if ( startNode )
// range.setStartAt( startNode, startInclusive ? CKEDITOR.POSITION_BEFORE_START : CKEDITOR.POSITION_AFTER_END ) ;
// else
// range.setStartAt( startNode.getDocument().getBody(), CKEDITOR.POSITION_AFTER_START ) ;
//
// if ( endNode )
// range.setEndAt( endNode, endInclusive ? CKEDITOR.POSITION_AFTER_END : CKEDITOR.POSITION_BEFORE_START ) ;
// else
// range.setEndAt( startNode.getDocument().getBody(), CKEDITOR.POSITION_BEFORE_END ) ;
//
// return new CKEDITOR.dom.walker( range );
// }
// },
//
proto: {
/**
* Stops walking. No more nodes are retrieved if this function gets called.
*/
end: function() {
this._.end = 1;
},
/**
* Retrieves the next node (at right).
*
* @returns {CKEDITOR.dom.node} The next node or null if no more
* nodes are available.
*/
next: function() {
return iterate.call( this );
},
/**
* Retrieves the previous node (at left).
*
* @returns {CKEDITOR.dom.node} The previous node or null if no more
* nodes are available.
*/
previous: function() {
return iterate.call( this, 1 );
},
/**
* Check all nodes at right, executing the evaluation fuction.
*
* @returns {Boolean} `false` if the evaluator function returned
* `false` for any of the matched nodes. Otherwise `true`.
*/
checkForward: function() {
return iterate.call( this, 0, 1 ) !== false;
},
/**
* Check all nodes at left, executing the evaluation fuction.
*
* @returns {Boolean} `false` if the evaluator function returned
* `false` for any of the matched nodes. Otherwise `true`.
*/
checkBackward: function() {
return iterate.call( this, 1, 1 ) !== false;
},
/**
* Executes a full walk forward (to the right), until no more nodes
* are available, returning the last valid node.
*
* @returns {CKEDITOR.dom.node} The last node at the right or null
* if no valid nodes are available.
*/
lastForward: function() {
return iterateToLast.call( this );
},
/**
* Executes a full walk backwards (to the left), until no more nodes
* are available, returning the last valid node.
*
* @returns {CKEDITOR.dom.node} The last node at the left or null
* if no valid nodes are available.
*/
lastBackward: function() {
return iterateToLast.call( this, 1 );
},
/**
* Resets walker.
*/
reset: function() {
delete this.current;
this._ = {};
}
}
});
// Anything whose display computed style is block, list-item, table,
// table-row-group, table-header-group, table-footer-group, table-row,
// table-column-group, table-column, table-cell, table-caption, or whose node
// name is hr, br (when enterMode is br only) is a block boundary.
var blockBoundaryDisplayMatch = { block:1,'list-item':1,table:1,'table-row-group':1,'table-header-group':1,'table-footer-group':1,'table-row':1,'table-column-group':1,'table-column':1,'table-cell':1,'table-caption':1 };
/**
* @member CKEDITOR.dom.element
* @todo
*/
CKEDITOR.dom.element.prototype.isBlockBoundary = function( customNodeNames ) {
var nodeNameMatches = customNodeNames ? CKEDITOR.tools.extend( {}, CKEDITOR.dtd.$block, customNodeNames || {} ) : CKEDITOR.dtd.$block;
// Don't consider floated formatting as block boundary, fall back to dtd check in that case. (#6297)
return this.getComputedStyle( 'float' ) == 'none' && blockBoundaryDisplayMatch[ this.getComputedStyle( 'display' ) ] || nodeNameMatches[ this.getName() ];
};
/**
* @static
* @todo
*/
CKEDITOR.dom.walker.blockBoundary = function( customNodeNames ) {
return function( node, type ) {
return !( node.type == CKEDITOR.NODE_ELEMENT && node.isBlockBoundary( customNodeNames ) );
};
};
/**
* @static
* @todo
*/
CKEDITOR.dom.walker.listItemBoundary = function() {
return this.blockBoundary( { br:1 } );
};
/**
* Whether the to-be-evaluated node is a bookmark node OR bookmark node
* inner contents.
*
* @static
* @param {Boolean} [contentOnly=false] Whether only test against the text content of
* bookmark node instead of the element itself (default).
* @param {Boolean} [isReject=false] Whether should return `false` for the bookmark
* node instead of `true` (default).
* @returns {Function}
*/
CKEDITOR.dom.walker.bookmark = function( contentOnly, isReject ) {
function isBookmarkNode( node ) {
return ( node && node.getName && node.getName() == 'span' && node.data( 'cke-bookmark' ) );
}
return function( node ) {
var isBookmark, parent;
// Is bookmark inner text node?
isBookmark = ( node && node.type != CKEDITOR.NODE_ELEMENT && ( parent = node.getParent() ) && isBookmarkNode( parent ) );
// Is bookmark node?
isBookmark = contentOnly ? isBookmark : isBookmark || isBookmarkNode( node );
return !!( isReject ^ isBookmark );
};
};
/**
* Whether the node is a text node containing only whitespaces characters.
*
* @static
* @param {Boolean} [isReject=false]
* @returns {Function}
*/
CKEDITOR.dom.walker.whitespaces = function( isReject ) {
return function( node ) {
var isWhitespace;
if ( node && node.type == CKEDITOR.NODE_TEXT ) {
// whitespace, as well as the text cursor filler node we used in Webkit. (#9384)
isWhitespace = !CKEDITOR.tools.trim( node.getText() ) ||
CKEDITOR.env.webkit && node.getText() == '\u200b';
}
return !! ( isReject ^ isWhitespace );
};
};
/**
* Whether the node is invisible in wysiwyg mode.
*
* @static
* @param {Boolean} [isReject=false]
* @returns {Function}
*/
CKEDITOR.dom.walker.invisible = function( isReject ) {
var whitespace = CKEDITOR.dom.walker.whitespaces();
return function( node ) {
var invisible;
if ( whitespace( node ) )
invisible = 1;
else {
// Visibility should be checked on element.
if ( node.type == CKEDITOR.NODE_TEXT )
node = node.getParent();
// Nodes that take no spaces in wysiwyg:
// 1. White-spaces but not including NBSP;
// 2. Empty inline elements, e.g. <b></b> we're checking here
// 'offsetHeight' instead of 'offsetWidth' for properly excluding
// all sorts of empty paragraph, e.g. <br />.
invisible = !node.$.offsetHeight;
}
return !!( isReject ^ invisible );
};
};
/**
* @static
* @param {Number} type
* @param {Boolean} [isReject=false]
* @returns {Function}
* @todo
*/
CKEDITOR.dom.walker.nodeType = function( type, isReject ) {
return function( node ) {
return !!( isReject ^ ( node.type == type ) );
};
};
/**
* @static
* @param {Boolean} [isReject=false]
* @returns {Function}
* @todo
*/
CKEDITOR.dom.walker.bogus = function( isReject ) {
function nonEmpty( node ) {
return !isWhitespaces( node ) && !isBookmark( node );
}
return function( node ) {
var isBogus = !CKEDITOR.env.ie ? node.is && node.is( 'br' ) : node.getText && tailNbspRegex.test( node.getText() );
if ( isBogus ) {
var parent = node.getParent(),
next = node.getNext( nonEmpty );
isBogus = parent.isBlockBoundary() && ( !next || next.type == CKEDITOR.NODE_ELEMENT && next.isBlockBoundary() );
}
return !!( isReject ^ isBogus );
};
};
var tailNbspRegex = /^[\t\r\n ]*(?:&nbsp;|\xa0)$/,
isWhitespaces = CKEDITOR.dom.walker.whitespaces(),
isBookmark = CKEDITOR.dom.walker.bookmark(),
toSkip = function( node ) {
return isBookmark( node ) || isWhitespaces( node ) || node.type == CKEDITOR.NODE_ELEMENT && node.getName() in CKEDITOR.dtd.$inline && !( node.getName() in CKEDITOR.dtd.$empty );
};
/**
* Check if there's a filler node at the end of an element, and return it.
*
* @member CKEDITOR.dom.element
* @returns {Boolean}
*/
CKEDITOR.dom.element.prototype.getBogus = function() {
// Bogus are not always at the end, e.g. <p><a>text<br /></a></p> (#7070).
var tail = this;
do {
tail = tail.getPreviousSourceNode();
}
while ( toSkip( tail ) )
if ( tail && ( !CKEDITOR.env.ie ? tail.is && tail.is( 'br' ) : tail.getText && tailNbspRegex.test( tail.getText() ) ) ) {
return tail;
}
return false;
};
})();
+95
View File
@@ -0,0 +1,95 @@
/**
* @license Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or http://ckeditor.com/license
*/
/**
* @fileOverview Defines the {@link CKEDITOR.dom.document} class, which
* represents a DOM document.
*/
/**
* Represents a DOM window.
*
* var document = new CKEDITOR.dom.window( window );
*
* @class
* @extends CKEDITOR.dom.domObject
* @constructor Creates a window class instance.
* @param {Object} domWindow A native DOM window.
*/
CKEDITOR.dom.window = function( domWindow ) {
CKEDITOR.dom.domObject.call( this, domWindow );
};
CKEDITOR.dom.window.prototype = new CKEDITOR.dom.domObject();
CKEDITOR.tools.extend( CKEDITOR.dom.window.prototype, {
/**
* Moves the selection focus to this window.
*
* var win = new CKEDITOR.dom.window( window );
* win.focus();
*/
focus: function() {
this.$.focus();
},
/**
* Gets the width and height of this window's viewable area.
*
* var win = new CKEDITOR.dom.window( window );
* var size = win.getViewPaneSize();
* alert( size.width );
* alert( size.height );
*
* @returns {Object} An object with the `width` and `height`
* properties containing the size.
*/
getViewPaneSize: function() {
var doc = this.$.document,
stdMode = doc.compatMode == 'CSS1Compat';
return {
width: ( stdMode ? doc.documentElement.clientWidth : doc.body.clientWidth ) || 0,
height: ( stdMode ? doc.documentElement.clientHeight : doc.body.clientHeight ) || 0
};
},
/**
* Gets the current position of the window's scroll.
*
* var win = new CKEDITOR.dom.window( window );
* var pos = win.getScrollPosition();
* alert( pos.x );
* alert( pos.y );
*
* @returns {Object} An object with the `x` and `y` properties
* containing the scroll position.
*/
getScrollPosition: function() {
var $ = this.$;
if ( 'pageXOffset' in $ ) {
return {
x: $.pageXOffset || 0,
y: $.pageYOffset || 0
};
} else {
var doc = $.document;
return {
x: doc.documentElement.scrollLeft || doc.body.scrollLeft || 0,
y: doc.documentElement.scrollTop || doc.body.scrollTop || 0
};
}
},
/**
* Gets the frame element containing this window context.
*
* @returns {CKEDITOR.dom.element} The frame element or `null` if not in a frame context.
*/
getFrame: function() {
var iframe = this.$.frameElement;
return iframe ? new CKEDITOR.dom.element.get( iframe ) : null;
}
});