/**
* @description <p>The Rich Text Editor is a UI control that replaces a standard HTML textarea; it allows for the rich formatting of text content, including common structural treatments like lists, formatting treatments like bold and italic text, and drag-and-drop inclusion and sizing of images. The Rich Text Editor's toolbar is extensible via a plugin architecture so that advanced implementations can achieve a high degree of customization.</p>
* @namespace YAHOO.widget
* @requires yahoo, dom, element, event, toolbar
* @optional animation, container_core
* @beta
*/
(function() {
var Dom = YAHOO.util.Dom,
Event = YAHOO.util.Event,
Lang = YAHOO.lang,
Toolbar = YAHOO.widget.Toolbar;
/**
* The Rich Text Editor is a UI control that replaces a standard HTML textarea; it allows for the rich formatting of text content, including common structural treatments like lists, formatting treatments like bold and italic text, and drag-and-drop inclusion and sizing of images. The Rich Text Editor's toolbar is extensible via a plugin architecture so that advanced implementations can achieve a high degree of customization.
* @constructor
* @class SimpleEditor
* @extends YAHOO.util.Element
* @param {String/HTMLElement} el The textarea element to turn into an editor.
* @param {Object} attrs Object liternal containing configuration parameters.
*/
YAHOO.widget.SimpleEditor = function(el, attrs) {
YAHOO.log('SimpleEditor Initalizing', 'info', 'SimpleEditor');
var o = {};
if (Lang.isObject(el) && (!el.tagName) && !attrs) {
Lang.augmentObject(o, el); //Break the config reference
el = document.createElement('textarea');
this.DOMReady = true;
if (o.container) {
var c = Dom.get(o.container);
c.appendChild(el);
} else {
document.body.appendChild(el);
}
} else {
Lang.augmentObject(o, attrs); //Break the config reference
}
var oConfig = {
element: null,
attributes: o
}, id = null;
if (Lang.isString(el)) {
id = el;
} else {
if (oConfig.attributes.id) {
id = oConfig.attributes.id;
} else {
id = Dom.generateId(el);
}
}
oConfig.element = el;
var element_cont = document.createElement('DIV');
oConfig.attributes.element_cont = new YAHOO.util.Element(element_cont, {
id: id + '_container'
});
var div = document.createElement('div');
Dom.addClass(div, 'first-child');
oConfig.attributes.element_cont.appendChild(div);
if (!oConfig.attributes.toolbar_cont) {
oConfig.attributes.toolbar_cont = document.createElement('DIV');
oConfig.attributes.toolbar_cont.id = id + '_toolbar';
div.appendChild(oConfig.attributes.toolbar_cont);
}
var editorWrapper = document.createElement('DIV');
div.appendChild(editorWrapper);
oConfig.attributes.editor_wrapper = editorWrapper;
YAHOO.widget.SimpleEditor.superclass.constructor.call(this, oConfig.element, oConfig.attributes);
};
/**
* @private
* @method _cleanClassName
* @description Makes a useable classname from dynamic data, by dropping it to lowercase and replacing spaces with -'s.
* @param {String} str The classname to clean up
* @return {String}
*/
function _cleanClassName(str) {
return str.replace(/ /g, '-').toLowerCase();
}
YAHOO.extend(YAHOO.widget.SimpleEditor, YAHOO.util.Element, {
/**
* @property _docType
* @description The DOCTYPE to use in the editable container.
* @type String
*/
_docType: '<!DOCTYPE HTML PUBLIC "-/'+'/W3C/'+'/DTD HTML 4.01/'+'/EN" "http:/'+'/www.w3.org/TR/html4/strict.dtd">',
/**
* @property editorDirty
* @description This flag will be set when certain things in the Editor happen. It is to be used by the developer to check to see if content has changed.
* @type Boolean
*/
editorDirty: null,
/**
* @property _defaultCSS
* @description The default CSS used in the config for 'css'. This way you can add to the config like this: { css: YAHOO.widget.SimpleEditor.prototype._defaultCSS + 'ADD MYY CSS HERE' }
* @type String
*/
_defaultCSS: 'html { height: 95%; } body { padding: 7px; background-color: #fff; font:13px/1.22 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small; } a { color: blue; text-decoration: underline; cursor: text; } .warning-localfile { border-bottom: 1px dashed red !important; } .yui-busy { cursor: wait !important; } img.selected { border: 2px dotted #808080; } img { cursor: pointer !important; border: none; }',
/**
* @property _defaultToolbar
* @private
* @description Default toolbar config.
* @type Object
*/
_defaultToolbar: null,
/**
* @property _lastButton
* @private
* @description The last button pressed, so we don't disable it.
* @type Object
*/
_lastButton: null,
/**
* @property _baseHREF
* @private
* @description The base location of the editable page (this page) so that relative paths for image work.
* @type String
*/
_baseHREF: function() {
var href = document.location.href;
if (href.indexOf('?') !== -1) { //Remove the query string
href = href.substring(0, href.indexOf('?'));
}
href = href.substring(0, href.lastIndexOf('/')) + '/';
return href;
}(),
/**
* @property _lastImage
* @private
* @description Safari reference for the last image selected (for styling as selected).
* @type HTMLElement
*/
_lastImage: null,
/**
* @property _blankImageLoaded
* @private
* @description Don't load the blank image more than once..
* @type Boolean
*/
_blankImageLoaded: null,
/**
* @property _fixNodesTimer
* @private
* @description Holder for the fixNodes timer
* @type Date
*/
_fixNodesTimer: null,
/**
* @property _nodeChangeTimer
* @private
* @description Holds a reference to the nodeChange setTimeout call
* @type Number
*/
_nodeChangeTimer: null,
/**
* @property _lastNodeChangeEvent
* @private
* @description Flag to determine the last event that fired a node change
* @type Event
*/
_lastNodeChangeEvent: null,
/**
* @property _lastNodeChange
* @private
* @description Flag to determine when the last node change was fired
* @type Date
*/
_lastNodeChange: 0,
/**
* @property _rendered
* @private
* @description Flag to determine if editor has been rendered or not
* @type Boolean
*/
_rendered: null,
/**
* @property DOMReady
* @private
* @description Flag to determine if DOM is ready or not
* @type Boolean
*/
DOMReady: null,
/**
* @property _selection
* @private
* @description Holder for caching iframe selections
* @type Object
*/
_selection: null,
/**
* @property _mask
* @private
* @description DOM Element holder for the editor Mask when disabled
* @type Object
*/
_mask: null,
/**
* @property _showingHiddenElements
* @private
* @description Status of the hidden elements button
* @type Boolean
*/
_showingHiddenElements: null,
/**
* @property currentWindow
* @description A reference to the currently open EditorWindow
* @type Object
*/
currentWindow: null,
/**
* @property currentEvent
* @description A reference to the current editor event
* @type Event
*/
currentEvent: null,
/**
* @property operaEvent
* @private
* @description setTimeout holder for Opera and Image DoubleClick event..
* @type Object
*/
operaEvent: null,
/**
* @property currentFont
* @description A reference to the last font selected from the Toolbar
* @type HTMLElement
*/
currentFont: null,
/**
* @property currentElement
* @description A reference to the current working element in the editor
* @type Array
*/
currentElement: null,
/**
* @property dompath
* @description A reference to the dompath container for writing the current working dom path to.
* @type HTMLElement
*/
dompath: null,
/**
* @property beforeElement
* @description A reference to the H2 placed before the editor for Accessibilty.
* @type HTMLElement
*/
beforeElement: null,
/**
* @property afterElement
* @description A reference to the H2 placed after the editor for Accessibilty.
* @type HTMLElement
*/
afterElement: null,
/**
* @property invalidHTML
* @description Contains a list of HTML elements that are invalid inside the editor. They will be removed when they are found. If you set the value of a key to "{ keepContents: true }", then the element will be replaced with a yui-non span to be filtered out when cleanHTML is called. The only tag that is ignored here is the span tag as it will force the Editor into a loop and freeze the browser. However.. all of these tags will be removed in the cleanHTML routine.
* @type Object
*/
invalidHTML: {
form: true,
input: true,
button: true,
select: true,
link: true,
html: true,
body: true,
iframe: true,
script: true,
style: true,
textarea: true
},
/**
* @property toolbar
* @description Local property containing the <a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a> instance
* @type <a href="YAHOO.widget.Toolbar.html">YAHOO.widget.Toolbar</a>
*/
toolbar: null,
/**
* @private
* @property _contentTimer
* @description setTimeout holder for documentReady check
*/
_contentTimer: null,
/**
* @private
* @property _contentTimerCounter
* @description Counter to check the number of times the body is polled for before giving up
* @type Number
*/
_contentTimerCounter: 0,
/**
* @private
* @property _disabled
* @description The Toolbar items that should be disabled if there is no selection present in the editor.
* @type Array
*/
_disabled: [ 'createlink', 'fontname', 'fontsize', 'forecolor', 'backcolor' ],
/**
* @private
* @property _alwaysDisabled
* @description The Toolbar items that should ALWAYS be disabled event if there is a selection present in the editor.
* @type Object
*/
_alwaysDisabled: { },
/**
* @private
* @property _alwaysEnabled
* @description The Toolbar items that should ALWAYS be enabled event if there isn't a selection present in the editor.
* @type Object
*/
_alwaysEnabled: { },
/**
* @private
* @property _semantic
* @description The Toolbar commands that we should attempt to make tags out of instead of using styles.
* @type Object
*/
_semantic: { 'bold': true, 'italic' : true, 'underline' : true },
/**
* @private
* @property _tag2cmd
* @description A tag map of HTML tags to convert to the different types of commands so we can select the proper toolbar button.
* @type Object
*/
_tag2cmd: {
'b': 'bold',
'strong': 'bold',
'i': 'italic',
'em': 'italic',
'u': 'underline',
'sup': 'superscript',
'sub': 'subscript',
'img': 'insertimage',
'a' : 'createlink',
'ul' : 'insertunorderedlist',
'ol' : 'insertorderedlist'
},
/**
* @private _createIframe
* @description Creates the DOM and YUI Element for the iFrame editor area.
* @param {String} id The string ID to prefix the iframe with
* @return {Object} iFrame object
*/
_createIframe: function() {
var ifrmDom = document.createElement('iframe');
ifrmDom.id = this.get('id') + '_editor';
var config = {
border: '0',
frameBorder: '0',
marginWidth: '0',
marginHeight: '0',
leftMargin: '0',
topMargin: '0',
allowTransparency: 'true',
width: '100%'
};
if (this.get('autoHeight')) {
config.scrolling = 'no';
}
for (var i in config) {
if (Lang.hasOwnProperty(config, i)) {
ifrmDom.setAttribute(i, config[i]);
}
}
var isrc = 'javascript:;';
if (this.browser.ie) {
isrc = 'about:blank';
}
ifrmDom.setAttribute('src', isrc);
var ifrm = new YAHOO.util.Element(ifrmDom);
//ifrm.setStyle('zIndex', '-1');
return ifrm;
},
/**
* @private _isElement
* @description Checks to see if an Element reference is a valid one and has a certain tag type
* @param {HTMLElement} el The element to check
* @param {String} tag The tag that the element needs to be
* @return {Boolean}
*/
_isElement: function(el, tag) {
if (el && el.tagName && (el.tagName.toLowerCase() == tag)) {
return true;
}
if (el && el.getAttribute && (el.getAttribute('tag') == tag)) {
return true;
}
return false;
},
/**
* @private _hasParent
* @description Checks to see if an Element reference or one of it's parents is a valid one and has a certain tag type
* @param {HTMLElement} el The element to check
* @param {String} tag The tag that the element needs to be
* @return HTMLElement
*/
_hasParent: function(el, tag) {
if (!el || !el.parentNode) {
return false;
}
while (el.parentNode) {
if (this._isElement(el, tag)) {
return el;
}
if (el.parentNode) {
el = el.parentNode;
} else {
return false;
}
}
return false;
},
/**
* @private
* @method _getDoc
* @description Get the Document of the IFRAME
* @return {Object}
*/
_getDoc: function() {
var value = false;
if (this.get) {
if (this.get('iframe')) {
if (this.get('iframe').get) {
if (this.get('iframe').get('element')) {
try {
if (this.get('iframe').get('element').contentWindow) {
if (this.get('iframe').get('element').contentWindow.document) {
value = this.get('iframe').get('element').contentWindow.document;
return value;
}
}
} catch (e) {}
}
}
}
}
return false;
},
/**
* @private
* @method _getWindow
* @description Get the Window of the IFRAME
* @return {Object}
*/
_getWindow: function() {
return this.get('iframe').get('element').contentWindow;
},
/**
* @private
* @method _focusWindow
* @description Attempt to set the focus of the iframes window.
* @param {Boolean} onLoad Safari needs some special care to set the cursor in the iframe
*/
_focusWindow: function(onLoad) {
if (this.browser.webkit) {
if (onLoad) {
/**
* @knownissue Safari Cursor Position
* @browser Safari 2.x
* @description Can't get Safari to place the cursor at the beginning of the text..
* This workaround at least set's the toolbar into the proper state.
*/
this._getSelection().setBaseAndExtent(this._getDoc().body.firstChild, 0, this._getDoc().body.firstChild, 1);
if (this.browser.webkit3) {
this._getSelection().collapseToStart();
} else {
this._getSelection().collapse(false);
}
} else {
this._getSelection().setBaseAndExtent(this._getDoc().body, 1, this._getDoc().body, 1);
if (this.browser.webkit3) {
this._getSelection().collapseToStart();
} else {
this._getSelection().collapse(false);
}
}
this._getWindow().focus();
} else {
this._getWindow().focus();
}
},
/**
* @private
* @method _hasSelection
* @description Determines if there is a selection in the editor document.
* @return {Boolean}
*/
_hasSelection: function() {
var sel = this._getSelection();
var range = this._getRange();
var hasSel = false;
if (!sel || !range) {
return hasSel;
}
//Internet Explorer
if (this.browser.ie || this.browser.opera) {
if (range.text) {
hasSel = true;
}
if (range.html) {
hasSel = true;
}
} else {
if (this.browser.webkit) {
if (sel+'' !== '') {
hasSel = true;
}
} else {
if (sel && (sel.toString() !== '') && (sel !== undefined)) {
hasSel = true;
}
}
}
return hasSel;
},
/**
* @private
* @method _getSelection
* @description Handles the different selection objects across the A-Grade list.
* @return {Object} Selection Object
*/
_getSelection: function() {
var _sel = null;
if (this._getDoc() && this._getWindow()) {
if (this._getDoc().selection) {
_sel = this._getDoc().selection;
} else {
_sel = this._getWindow().getSelection();
}
//Handle Safari's lack of Selection Object
if (this.browser.webkit) {
if (_sel.baseNode) {
this._selection = {};
this._selection.baseNode = _sel.baseNode;
this._selection.baseOffset = _sel.baseOffset;
this._selection.extentNode = _sel.extentNode;
this._selection.extentOffset = _sel.extentOffset;
} else if (this._selection !== null) {
_sel = this._getWindow().getSelection();
_sel.setBaseAndExtent(
this._selection.baseNode,
this._selection.baseOffset,
this._selection.extentNode,
this._selection.extentOffset);
this._selection = null;
}
}
}
return _sel;
},
/**
* @private
* @method _selectNode
* @description Places the highlight around a given node
* @param {HTMLElement} node The node to select
*/
_selectNode: function(node) {
if (!node) {
return false;
}
var sel = this._getSelection(),
range = null;
if (this.browser.ie) {
try { //IE freaks out here sometimes..
range = this._getDoc().body.createTextRange();
range.moveToElementText(node);
range.select();
} catch (e) {
YAHOO.log('IE failed to select element: ' + node.tagName, 'warn', 'SimpleEditor');
}
} else if (this.browser.webkit) {
sel.setBaseAndExtent(node, 0, node, node.innerText.length);
} else if (this.browser.opera) {
sel = this._getWindow().getSelection();
range = this._getDoc().createRange();
range.selectNode(node);
sel.removeAllRanges();
sel.addRange(range);
} else {
range = this._getDoc().createRange();
range.selectNodeContents(node);
sel.removeAllRanges();
sel.addRange(range);
}
},
/**
* @private
* @method _getRange
* @description Handles the different range objects across the A-Grade list.
* @return {Object} Range Object
*/
_getRange: function() {
var sel = this._getSelection();
if (sel === null) {
return null;
}
if (this.browser.webkit && !sel.getRangeAt) {
var _range = this._getDoc().createRange();
try {
_range.setStart(sel.anchorNode, sel.anchorOffset);
_range.setEnd(sel.focusNode, sel.focusOffset);
} catch (e) {
_range = this._getWindow().getSelection()+'';
}
return _range;
}
if (this.browser.ie || this.browser.opera) {
try {
return sel.createRange();
} catch (e) {
return null;
}
}
if (sel.rangeCount > 0) {
return sel.getRangeAt(0);
}
return null;
},
/**
* @private
* @method _setDesignMode
* @description Sets the designMode of the iFrame document.
* @param {String} state This should be either on or off
*/
_setDesignMode: function(state) {
try {
var set = true;
//SourceForge Bug #1807057
if (this.browser.ie && (state.toLowerCase() == 'off')) {
set = false;
}
if (set) {
this._getDoc().designMode = state;
}
} catch(e) { }
},
/**
* @private
* @method _toggleDesignMode
* @description Toggles the designMode of the iFrame document on and off.
* @return {String} The state that it was set to.
*/
_toggleDesignMode: function() {
var _dMode = this._getDoc().designMode.toLowerCase(),
_state = 'on';
if (_dMode == 'on') {
_state = 'off';
}
this._setDesignMode(_state);
return _state;
},
/**
* @private
* @method _initEditor
* @description This method is fired from _checkLoaded when the document is ready. It turns on designMode and set's up the listeners.
*/
_initEditor: function() {
if (this.browser.ie) {
this._getDoc().body.style.margin = '0';
}
if (!this.get('disabled')) {
if (this._getDoc().designMode.toLowerCase() != 'on') {
this._setDesignMode('on');
this._contentTimerCounter = 0;
}
}
if (!this._getDoc().body) {
YAHOO.log('Body is null, check again', 'error', 'SimpleEditor');
this._contentTimerCounter = 0;
this._checkLoaded();
return false;
}
YAHOO.log('editorLoaded', 'info', 'SimpleEditor');
this.toolbar.on('buttonClick', this._handleToolbarClick, this, true);
//Setup Listeners on iFrame
Event.on(this._getDoc(), 'mouseup', this._handleMouseUp, this, true);
Event.on(this._getDoc(), 'mousedown', this._handleMouseDown, this, true);
Event.on(this._getDoc(), 'click', this._handleClick, this, true);
Event.on(this._getDoc(), 'dblclick', this._handleDoubleClick, this, true);
Event.on(this._getDoc(), 'keypress', this._handleKeyPress, this, true);
Event.on(this._getDoc(), 'keyup', this._handleKeyUp, this, true);
Event.on(this._getDoc(), 'keydown', this._handleKeyDown, this, true);
if (!this.get('disabled')) {
this.toolbar.set('disabled', false);
}
this.fireEvent('editorContentLoaded', { type: 'editorLoaded', target: this });
if (this.get('dompath')) {
YAHOO.log('Delayed DomPath write', 'info', 'SimpleEditor');
var self = this;
setTimeout(function() {
self._writeDomPath.call(self);
}, 150);
}
this.nodeChange(true);
this._setBusy(true);
},
/**
* @private
* @method _checkLoaded
* @description Called from a setTimeout loop to check if the iframes body.onload event has fired, then it will init the editor.
*/
_checkLoaded: function() {
this._contentTimerCounter++;
if (this._contentTimer) {
clearTimeout(this._contentTimer);
}
if (this._contentTimerCounter > 500) {
YAHOO.log('ERROR: Body Did Not load', 'error', 'SimpleEditor');
return false;
}
var init = false;
try {
if (this._getDoc() && this._getDoc().body) {
if (this.browser.ie) {
if (this._getDoc().body.readyState == 'complete') {
init = true;
}
} else {
if (this._getDoc().body._rteLoaded === true) {
init = true;
}
}
}
} catch (e) {
init = false;
YAHOO.log('checking body (e)' + e, 'error', 'SimpleEditor');
}
if (init === true) {
//The onload event has fired, clean up after ourselves and fire the _initEditor method
this._initEditor();
} else {
var self = this;
this._contentTimer = setTimeout(function() {
self._checkLoaded.call(self);
}, 20);
}
},
/**
* @private
* @method _setInitialContent
* @description This method will open the iframes content document and write the textareas value into it, then start the body.onload checking.
*/
_setInitialContent: function() {
YAHOO.log('Populating editor body with contents of the text area', 'info', 'SimpleEditor');
var html = Lang.substitute(this.get('html'), {
TITLE: this.STR_TITLE,
CONTENT: this._cleanIncomingHTML(this.get('element').value),
CSS: this.get('css'),
HIDDEN_CSS: ((this.get('hiddencss')) ? this.get('hiddencss') : '/* No Hidden CSS */'),
EXTRA_CSS: ((this.get('extracss')) ? this.get('extracss') : '/* No Extra CSS */')
}),
check = true;
if (document.compatMode != 'BackCompat') {
YAHOO.log('Adding Doctype to editable area', 'info', 'SimpleEditor');
html = this._docType + "\n" + html;
} else {
YAHOO.log('DocType skipped because we are in BackCompat Mode.', 'warn', 'SimpleEditor');
}
if (this.browser.ie || this.browser.webkit || this.browser.opera || (navigator.userAgent.indexOf('Firefox/1.5') != -1)) {
//Firefox 1.5 doesn't like setting designMode on an document created with a data url
try {
//Adobe AIR Code
if (this.browser.air) {
var doc = this._getDoc().implementation.createHTMLDocument();
var origDoc = this._getDoc();
origDoc.open();
origDoc.close();
doc.open();
doc.write(html);
doc.close();
var node = origDoc.importNode(doc.getElementsByTagName("html")[0], true);
origDoc.replaceChild(node, origDoc.getElementsByTagName("html")[0]);
origDoc.body._rteLoaded = true;
} else {
this._getDoc().open();
this._getDoc().write(html);
this._getDoc().close();
}
} catch (e) {
YAHOO.log('Setting doc failed.. (_setInitialContent)', 'error', 'SimpleEditor');
//Safari will only be here if we are hidden
check = false;
}
} else {
//This keeps Firefox 2 from writing the iframe to history preserving the back buttons functionality
this.get('iframe').get('element').src = 'data:text/html;charset=utf-8,' + encodeURIComponent(html);
}
if (check) {
this._checkLoaded();
}
},
/**
* @private
* @method _setMarkupType
* @param {String} action The action to take. Possible values are: css, default or semantic
* @description This method will turn on/off the useCSS execCommand.
*/
_setMarkupType: function(action) {
switch (this.get('markup')) {
case 'css':
this._setEditorStyle(true);
break;
case 'default':
this._setEditorStyle(false);
break;
case 'semantic':
case 'xhtml':
if (this._semantic[action]) {
this._setEditorStyle(false);
} else {
this._setEditorStyle(true);
}
break;
}
},
/**
* Set the editor to use CSS instead of HTML
* @param {Booleen} stat True/False
*/
_setEditorStyle: function(stat) {
try {
this._getDoc().execCommand('useCSS', false, !stat);
} catch (ex) {
}
},
/**
* @private
* @method _getSelectedElement
* @description This method will attempt to locate the element that was last interacted with, either via selection, location or event.
* @return {HTMLElement} The currently selected element.
*/
_getSelectedElement: function() {
var doc = this._getDoc(),
range = null,
sel = null,
elm = null;
if (this.browser.ie) {
this.currentEvent = this._getWindow().event; //Event utility assumes window.event, so we need to reset it to this._getWindow().event;
range = this._getRange();
if (range) {
elm = range.item ? range.item(0) : range.parentElement();
if (elm == doc.body) {
elm = null;
}
}
if ((this.currentEvent !== null) && (this.currentEvent.keyCode === 0)) {
elm = Event.getTarget(this.currentEvent);
}
} else {
sel = this._getSelection();
range = this._getRange();
if (!sel || !range) {
return null;
}
if (!this._hasSelection()) {
if (sel.anchorNode && (sel.anchorNode.nodeType == 3)) {
if (sel.anchorNode.parentNode) { //next check parentNode
elm = sel.anchorNode.parentNode;
}
if (sel.anchorNode.nextSibling != sel.focusNode.nextSibling) {
elm = sel.anchorNode.nextSibling;
}
}
if (this._isElement(elm, 'br')) {
elm = null;
}
if (!elm) {
elm = range.commonAncestorContainer;
if (!range.collapsed) {
if (range.startContainer == range.endContainer) {
if (range.startOffset - range.endOffset < 2) {
if (range.startContainer.hasChildNodes()) {
elm = range.startContainer.childNodes[range.startOffset];
}
}
}
}
}
}
}
if (this.currentEvent !== null) {
try {
switch (this.currentEvent.type) {
case 'click':
case 'mousedown':
case 'mouseup':
elm = Event.getTarget(this.currentEvent);
break;
default:
//Do nothing
break;
}
} catch (e) {
YAHOO.log('Firefox 1.5 errors here: ' + e, 'error', 'SimpleEditor');
}
} else if ((this.currentElement && this.currentElement[0]) && (!this.browser.ie)) {
elm = this.currentElement[0];
}
if (this.browser.opera || this.browser.webkit) {
if (this.currentEvent && !elm) {
elm = YAHOO.util.Event.getTarget(this.currentEvent);
}
}
if (!elm || !elm.tagName) {
elm = doc.body;
}
if (this._isElement(elm, 'html')) {
//Safari sometimes gives us the HTML node back..
elm = doc.body;
}
if (this._isElement(elm, 'body')) {
//make sure that body means this body not the parent..
elm = doc.body;
}
if (elm && !elm.parentNode) { //Not in document
elm = doc.body;
}
if (elm === undefined) {
elm = null;
}
return elm;
},
/**
* @private
* @method _getDomPath
* @description This method will attempt to build the DOM path from the currently selected element.
* @param HTMLElement el The element to start with, if not provided _getSelectedElement is used
* @return {Array} An array of node references that will create the DOM Path.
*/
_getDomPath: function(el) {
if (!el) {
el = this._getSelectedElement();
}
var domPath = [];
while (el !== null) {
if (el