(which has the correct height set by Editor)
ifr.style.height = "100%";
}else{
if(has("ie") >= 7){
if(this.height){
ifr.style.height = this.height;
}
if(this.minHeight){
ifr.style.minHeight = this.minHeight;
}
}else{
ifr.style.height = this.height ? this.height : this.minHeight;
}
}
ifr.frameBorder = 0;
ifr._loadFunc = lang.hitch( this, function(w){
this.window = w;
this.document = this.window.document;
if(has("ie")){
this._localizeEditorCommands();
}
// Do final setup and set initial contents of editor
this.onLoad(html);
});
// Set the iframe's initial (blank) content.
var src = this._getIframeDocTxt(),
s = "javascript: '" + src.replace(/\\/g, "\\\\").replace(/'/g, "\\'") + "'";
ifr.setAttribute('src', s);
this.editingArea.appendChild(ifr);
if(has("safari") <= 4){
src = ifr.getAttribute("src");
if(!src || src.indexOf("javascript") === -1){
// Safari 4 and earlier sometimes act oddly
// So we have to set it again.
this.defer(function(){ ifr.setAttribute('src', s); });
}
}
// TODO: this is a guess at the default line-height, kinda works
if(dn.nodeName === "LI"){
dn.lastChild.style.marginTop = "-1.2em";
}
domClass.add(this.domNode, this.baseClass);
},
//static cache variables shared among all instance of this class
_local2NativeFormatNames: {},
_native2LocalFormatNames: {},
_getIframeDocTxt: function(){
// summary:
// Generates the boilerplate text of the document inside the iframe (ie, `...
`).
// Editor content (if not blank) should be added afterwards.
// tags:
// private
var _cs = domStyle.getComputedStyle(this.domNode);
// The contents inside of . The real contents are set later via a call to setValue().
var html = "";
var setBodyId = true;
if(has("ie") || has("webkit") || (!this.height && !has("mozilla"))){
// In auto-expand mode, need a wrapper div for AlwaysShowToolbar plugin to correctly
// expand/contract the editor as the content changes.
html = " in _getIframeDocTxt()
this.editNode.style.zoom = 1.0;
}else{
this.connect(this.document, "onmousedown", function(){
// Clear the moveToStart focus, as mouse
// down will set cursor point. Required to properly
// work with selection/position driven plugins and clicks in
// the window. refs: #10678
delete this._cursorToStart;
});
}
if(has("webkit")){
//WebKit sometimes doesn't fire right on selections, so the toolbar
//doesn't update right. Therefore, help it out a bit with an additional
//listener. A mouse up will typically indicate a display change, so fire this
//and get the toolbar to adapt. Reference: #9532
this._webkitListener = this.connect(this.document, "onmouseup", "onDisplayChanged");
this.connect(this.document, "onmousedown", function(e){
var t = e.target;
if(t && (t === this.document.body || t === this.document)){
// Since WebKit uses the inner DIV, we need to check and set position.
// See: #12024 as to why the change was made.
this.defer("placeCursorAtEnd");
}
});
}
if(has("ie")){
// Try to make sure 'hidden' elements aren't visible in edit mode (like browsers other than IE
// do). See #9103
try{
this.document.execCommand('RespectVisibilityInDesign', true, null);
}catch(e){/* squelch */}
}
this.isLoaded = true;
this.set('disabled', this.disabled); // initialize content to editable (or not)
// Note that setValue() call will only work after isLoaded is set to true (above)
// Set up a function to allow delaying the setValue until a callback is fired
// This ensures extensions like dijit.Editor have a way to hold the value set
// until plugins load (and do things like register filters).
var setContent = lang.hitch(this, function(){
this.setValue(html);
if(this.onLoadDeferred){
this.onLoadDeferred.resolve(true);
}
this.onDisplayChanged();
if(this.focusOnLoad){
// after the document loads, then set focus after updateInterval expires so that
// onNormalizedDisplayChanged has run to avoid input caret issues
ready(lang.hitch(this, "defer", "focus", this.updateInterval));
}
// Save off the initial content now
this.value = this.getValue(true);
});
if(this.setValueDeferred){
this.setValueDeferred.then(setContent);
}else{
setContent();
}
},
onKeyDown: function(/* Event */ e){
// summary:
// Handler for onkeydown event
// tags:
// protected
// we need this event at the moment to get the events from control keys
// such as the backspace. It might be possible to add this to Dojo, so that
// keyPress events can be emulated by the keyDown and keyUp detection.
if(e.keyCode === keys.TAB && this.isTabIndent ){
event.stop(e); //prevent tab from moving focus out of editor
// FIXME: this is a poor-man's indent/outdent. It would be
// better if it added 4 " " chars in an undoable way.
// Unfortunately pasteHTML does not prove to be undoable
if(this.queryCommandEnabled((e.shiftKey ? "outdent" : "indent"))){
this.execCommand((e.shiftKey ? "outdent" : "indent"));
}
}
if(has("ie")){
if(e.keyCode == keys.TAB && !this.isTabIndent){
if(e.shiftKey && !e.ctrlKey && !e.altKey){
// focus the BODY so the browser will tab away from it instead
this.iframe.focus();
}else if(!e.shiftKey && !e.ctrlKey && !e.altKey){
// focus the BODY so the browser will tab away from it instead
this.tabStop.focus();
}
}else if(e.keyCode === keys.BACKSPACE && this.document.selection.type === "Control"){
// IE has a bug where if a non-text object is selected in the editor,
// hitting backspace would act as if the browser's back button was
// clicked instead of deleting the object. see #1069
event.stop(e);
this.execCommand("delete");
}else if((65 <= e.keyCode && e.keyCode <= 90) ||
(e.keyCode>=37 && e.keyCode<=40) // FIXME: get this from connect() instead!
){ //arrow keys
e.charCode = e.keyCode;
this.onKeyPress(e);
}
}
if(has("ff")){
if(e.keyCode === keys.PAGE_UP || e.keyCode === keys.PAGE_DOWN ){
if(this.editNode.clientHeight >= this.editNode.scrollHeight){
// Stop the event to prevent firefox from trapping the cursor when there is no scroll bar.
e.preventDefault();
}
}
}
return true;
},
onKeyUp: function(/*===== e =====*/){
// summary:
// Handler for onkeyup event
// tags:
// callback
},
setDisabled: function(/*Boolean*/ disabled){
// summary:
// Deprecated, use set('disabled', ...) instead.
// tags:
// deprecated
kernel.deprecated('dijit.Editor::setDisabled is deprecated','use dijit.Editor::attr("disabled",boolean) instead', 2.0);
this.set('disabled',disabled);
},
_setValueAttr: function(/*String*/ value){
// summary:
// Registers that attr("value", foo) should call setValue(foo)
this.setValue(value);
},
_setDisableSpellCheckAttr: function(/*Boolean*/ disabled){
if(this.document){
domAttr.set(this.document.body, "spellcheck", !disabled);
}else{
// try again after the editor is finished loading
this.onLoadDeferred.then(lang.hitch(this, function(){
domAttr.set(this.document.body, "spellcheck", !disabled);
}));
}
this._set("disableSpellCheck", disabled);
},
onKeyPress: function(e){
// summary:
// Handle the various key events
// tags:
// protected
var c = (e.keyChar && e.keyChar.toLowerCase()) || e.keyCode,
handlers = this._keyHandlers[c],
args = arguments;
if(handlers && !e.altKey){
array.some(handlers, function(h){
// treat meta- same as ctrl-, for benefit of mac users
if(!(h.shift ^ e.shiftKey) && !(h.ctrl ^ (e.ctrlKey||e.metaKey))){
if(!h.handler.apply(this, args)){
e.preventDefault();
}
return true;
}
}, this);
}
// function call after the character has been inserted
if(!this._onKeyHitch){
this._onKeyHitch = lang.hitch(this, "onKeyPressed");
}
this.defer("_onKeyHitch", 1);
return true;
},
addKeyHandler: function(/*String*/ key, /*Boolean*/ ctrl, /*Boolean*/ shift, /*Function*/ handler){
// summary:
// Add a handler for a keyboard shortcut
// description:
// The key argument should be in lowercase if it is a letter character
// tags:
// protected
if(!lang.isArray(this._keyHandlers[key])){
this._keyHandlers[key] = [];
}
//TODO: would be nice to make this a hash instead of an array for quick lookups
this._keyHandlers[key].push({
shift: shift || false,
ctrl: ctrl || false,
handler: handler
});
},
onKeyPressed: function(){
// summary:
// Handler for after the user has pressed a key, and the display has been updated.
// (Runs on a timer so that it runs after the display is updated)
// tags:
// private
this.onDisplayChanged(/*e*/); // can't pass in e
},
onClick: function(/*Event*/ e){
// summary:
// Handler for when the user clicks.
// tags:
// private
// console.info('onClick',this._tryDesignModeOn);
this.onDisplayChanged(e);
},
_onIEMouseDown: function(){
// summary:
// IE only to prevent 2 clicks to focus
// tags:
// protected
if(!this.focused && !this.disabled){
this.focus();
}
},
_onBlur: function(e){
// summary:
// Called from focus manager when focus has moved away from this editor
// tags:
// protected
// console.info('_onBlur')
this.inherited(arguments);
var newValue = this.getValue(true);
if(newValue !== this.value){
this.onChange(newValue);
}
this._set("value", newValue);
},
_onFocus: function(/*Event*/ e){
// summary:
// Called from focus manager when focus has moved into this editor
// tags:
// protected
// console.info('_onFocus')
if(!this.disabled){
if(!this._disabledOK){
this.set('disabled', false);
}
this.inherited(arguments);
}
},
// TODO: remove in 2.0
blur: function(){
// summary:
// Remove focus from this instance.
// tags:
// deprecated
if(!has("ie") && this.window.document.documentElement && this.window.document.documentElement.focus){
this.window.document.documentElement.focus();
}else if(this.ownerDocumentBody.focus){
this.ownerDocumentBody.focus();
}
},
focus: function(){
// summary:
// Move focus to this editor
if(!this.isLoaded){
this.focusOnLoad = true;
return;
}
if(this._cursorToStart){
delete this._cursorToStart;
if(this.editNode.childNodes){
this.placeCursorAtStart(); // this calls focus() so return
return;
}
}
if(!has("ie")){
focus.focus(this.iframe);
}else if(this.editNode && this.editNode.focus){
// editNode may be hidden in display:none div, lets just punt in this case
//this.editNode.focus(); -> causes IE to scroll always (strict and quirks mode) to the top the Iframe
// if we fire the event manually and let the browser handle the focusing, the latest
// cursor position is focused like in FF
this.iframe.fireEvent('onfocus', document.createEventObject()); // createEventObject only in IE
// }else{
// TODO: should we throw here?
// console.debug("Have no idea how to focus into the editor!");
}
},
// _lastUpdate: 0,
updateInterval: 200,
_updateTimer: null,
onDisplayChanged: function(/*Event*/ /*===== e =====*/){
// summary:
// This event will be fired every time the display context
// changes and the result needs to be reflected in the UI.
// description:
// If you don't want to have update too often,
// onNormalizedDisplayChanged should be used instead
// tags:
// private
// var _t=new Date();
if(this._updateTimer){
this._updateTimer.remove();
}
this._updateTimer = this.defer("onNormalizedDisplayChanged", this.updateInterval);
// Technically this should trigger a call to watch("value", ...) registered handlers,
// but getValue() is too slow to call on every keystroke so we don't.
},
onNormalizedDisplayChanged: function(){
// summary:
// This event is fired every updateInterval ms or more
// description:
// If something needs to happen immediately after a
// user change, please use onDisplayChanged instead.
// tags:
// private
delete this._updateTimer;
},
onChange: function(/*===== newContent =====*/){
// summary:
// This is fired if and only if the editor loses focus and
// the content is changed.
},
_normalizeCommand: function(/*String*/ cmd, /*Anything?*/argument){
// summary:
// Used as the advice function to map our
// normalized set of commands to those supported by the target
// browser.
// tags:
// private
var command = cmd.toLowerCase();
if(command === "formatblock"){
if(has("safari") && argument === undefined){ command = "heading"; }
}else if(command === "hilitecolor" && !has("mozilla")){
command = "backcolor";
}
return command;
},
_qcaCache: {},
queryCommandAvailable: function(/*String*/ command){
// summary:
// Tests whether a command is supported by the host. Clients
// SHOULD check whether a command is supported before attempting
// to use it, behaviour for unsupported commands is undefined.
// command:
// The command to test for
// tags:
// private
// memoizing version. See _queryCommandAvailable for computing version
var ca = this._qcaCache[command];
if(ca !== undefined){ return ca; }
return (this._qcaCache[command] = this._queryCommandAvailable(command));
},
_queryCommandAvailable: function(/*String*/ command){
// summary:
// See queryCommandAvailable().
// tags:
// private
var ie = 1;
var mozilla = 1 << 1;
var webkit = 1 << 2;
var opera = 1 << 3;
function isSupportedBy(browsers){
return {
ie: Boolean(browsers & ie),
mozilla: Boolean(browsers & mozilla),
webkit: Boolean(browsers & webkit),
opera: Boolean(browsers & opera)
};
}
var supportedBy = null;
switch(command.toLowerCase()){
case "bold": case "italic": case "underline":
case "subscript": case "superscript":
case "fontname": case "fontsize":
case "forecolor": case "hilitecolor":
case "justifycenter": case "justifyfull": case "justifyleft":
case "justifyright": case "delete": case "selectall": case "toggledir":
supportedBy = isSupportedBy(mozilla | ie | webkit | opera);
break;
case "createlink": case "unlink": case "removeformat":
case "inserthorizontalrule": case "insertimage":
case "insertorderedlist": case "insertunorderedlist":
case "indent": case "outdent": case "formatblock":
case "inserthtml": case "undo": case "redo": case "strikethrough": case "tabindent":
supportedBy = isSupportedBy(mozilla | ie | opera | webkit);
break;
case "blockdirltr": case "blockdirrtl":
case "dirltr": case "dirrtl":
case "inlinedirltr": case "inlinedirrtl":
supportedBy = isSupportedBy(ie);
break;
case "cut": case "copy": case "paste":
supportedBy = isSupportedBy( ie | mozilla | webkit | opera);
break;
case "inserttable":
supportedBy = isSupportedBy(mozilla | ie);
break;
case "insertcell": case "insertcol": case "insertrow":
case "deletecells": case "deletecols": case "deleterows":
case "mergecells": case "splitcell":
supportedBy = isSupportedBy(ie | mozilla);
break;
default: return false;
}
return (has("ie") && supportedBy.ie) ||
(has("mozilla") && supportedBy.mozilla) ||
(has("webkit") && supportedBy.webkit) ||
(has("opera") && supportedBy.opera); // Boolean return true if the command is supported, false otherwise
},
execCommand: function(/*String*/ command, argument){
// summary:
// Executes a command in the Rich Text area
// command:
// The command to execute
// argument:
// An optional argument to the command
// tags:
// protected
var returnValue;
//focus() is required for IE to work
//In addition, focus() makes sure after the execution of
//the command, the editor receives the focus as expected
this.focus();
command = this._normalizeCommand(command, argument);
if(argument !== undefined){
if(command === "heading"){
throw new Error("unimplemented");
}else if((command === "formatblock") && has("ie")){
argument = '<'+argument+'>';
}
}
//Check to see if we have any over-rides for commands, they will be functions on this
//widget of the form _commandImpl. If we don't, fall through to the basic native
//exec command of the browser.
var implFunc = "_" + command + "Impl";
if(this[implFunc]){
returnValue = this[implFunc](argument);
}else{
argument = arguments.length > 1 ? argument : null;
if(argument || command !== "createlink"){
returnValue = this.document.execCommand(command, false, argument);
}
}
this.onDisplayChanged();
return returnValue;
},
queryCommandEnabled: function(/*String*/ command){
// summary:
// Check whether a command is enabled or not.
// command:
// The command to execute
// tags:
// protected
if(this.disabled || !this._disabledOK){ return false; }
command = this._normalizeCommand(command);
//Check to see if we have any over-rides for commands, they will be functions on this
//widget of the form _commandEnabledImpl. If we don't, fall through to the basic native
//command of the browser.
var implFunc = "_" + command + "EnabledImpl";
if(this[implFunc]){
return this[implFunc](command);
}else{
return this._browserQueryCommandEnabled(command);
}
},
queryCommandState: function(command){
// summary:
// Check the state of a given command and returns true or false.
// tags:
// protected
if(this.disabled || !this._disabledOK){ return false; }
command = this._normalizeCommand(command);
try{
return this.document.queryCommandState(command);
}catch(e){
//Squelch, occurs if editor is hidden on FF 3 (and maybe others.)
return false;
}
},
queryCommandValue: function(command){
// summary:
// Check the value of a given command. This matters most for
// custom selections and complex values like font value setting.
// tags:
// protected
if(this.disabled || !this._disabledOK){ return false; }
var r;
command = this._normalizeCommand(command);
if(has("ie") && command === "formatblock"){
r = this._native2LocalFormatNames[this.document.queryCommandValue(command)];
}else if(has("mozilla") && command === "hilitecolor"){
var oldValue;
try{
oldValue = this.document.queryCommandValue("styleWithCSS");
}catch(e){
oldValue = false;
}
this.document.execCommand("styleWithCSS", false, true);
r = this.document.queryCommandValue(command);
this.document.execCommand("styleWithCSS", false, oldValue);
}else{
r = this.document.queryCommandValue(command);
}
return r;
},
// Misc.
_sCall: function(name, args){
// summary:
// Run the named method of dijit/_editor/selection over the
// current editor instance's window, with the passed args.
// tags:
// private
return win.withGlobal(this.window, name, selectionapi, args);
},
// FIXME: this is a TON of code duplication. Why?
placeCursorAtStart: function(){
// summary:
// Place the cursor at the start of the editing area.
// tags:
// private
this.focus();
//see comments in placeCursorAtEnd
var isvalid=false;
if(has("mozilla")){
// TODO: Is this branch even necessary?
var first=this.editNode.firstChild;
while(first){
if(first.nodeType === 3){
if(first.nodeValue.replace(/^\s+|\s+$/g, "").length>0){
isvalid=true;
this._sCall("selectElement", [ first ]);
break;
}
}else if(first.nodeType === 1){
isvalid=true;
var tg = first.tagName ? first.tagName.toLowerCase() : "";
// Collapse before childless tags.
if(/br|input|img|base|meta|area|basefont|hr|link/.test(tg)){
this._sCall("selectElement", [ first ]);
}else{
// Collapse inside tags with children.
this._sCall("selectElementChildren", [ first ]);
}
break;
}
first = first.nextSibling;
}
}else{
isvalid=true;
this._sCall("selectElementChildren", [ this.editNode ]);
}
if(isvalid){
this._sCall("collapse", [ true ]);
}
},
placeCursorAtEnd: function(){
// summary:
// Place the cursor at the end of the editing area.
// tags:
// private
this.focus();
//In mozilla, if last child is not a text node, we have to use
// selectElementChildren on this.editNode.lastChild otherwise the
// cursor would be placed at the end of the closing tag of
//this.editNode.lastChild
var isvalid=false;
if(has("mozilla")){
var last=this.editNode.lastChild;
while(last){
if(last.nodeType === 3){
if(last.nodeValue.replace(/^\s+|\s+$/g, "").length>0){
isvalid=true;
this._sCall("selectElement", [ last ]);
break;
}
}else if(last.nodeType === 1){
isvalid=true;
this._sCall("selectElement", [ last.lastChild || last]);
break;
}
last = last.previousSibling;
}
}else{
isvalid=true;
this._sCall("selectElementChildren", [ this.editNode ]);
}
if(isvalid){
this._sCall("collapse", [ false ]);
}
},
getValue: function(/*Boolean?*/ nonDestructive){
// summary:
// Return the current content of the editing area (post filters
// are applied). Users should call get('value') instead.
// nonDestructive:
// defaults to false. Should the post-filtering be run over a copy
// of the live DOM? Most users should pass "true" here unless they
// *really* know that none of the installed filters are going to
// mess up the editing session.
// tags:
// private
if(this.textarea){
if(this.isClosed || !this.isLoaded){
return this.textarea.value;
}
}
return this._postFilterContent(null, nonDestructive);
},
_getValueAttr: function(){
// summary:
// Hook to make attr("value") work
return this.getValue(true);
},
setValue: function(/*String*/ html){
// summary:
// This function sets the content. No undo history is preserved.
// Users should use set('value', ...) instead.
// tags:
// deprecated
// TODO: remove this and getValue() for 2.0, and move code to _setValueAttr()
if(!this.isLoaded){
// try again after the editor is finished loading
this.onLoadDeferred.then(lang.hitch(this, function(){
this.setValue(html);
}));
return;
}
this._cursorToStart = true;
if(this.textarea && (this.isClosed || !this.isLoaded)){
this.textarea.value=html;
}else{
html = this._preFilterContent(html);
var node = this.isClosed ? this.domNode : this.editNode;
if(html && has("mozilla") && html.toLowerCase() === "
"){
html = "
"; //
}
// Use to avoid webkit problems where editor is disabled until the user clicks it
if(!html && has("webkit")){
html = " "; //
}
node.innerHTML = html;
this._preDomFilterContent(node);
}
this.onDisplayChanged();
this._set("value", this.getValue(true));
},
replaceValue: function(/*String*/ html){
// summary:
// This function set the content while trying to maintain the undo stack
// (now only works fine with Moz, this is identical to setValue in all
// other browsers)
// tags:
// protected
if(this.isClosed){
this.setValue(html);
}else if(this.window && this.window.getSelection && !has("mozilla")){ // Safari
// look ma! it's a totally f'd browser!
this.setValue(html);
}else if(this.window && this.window.getSelection){ // Moz
html = this._preFilterContent(html);
this.execCommand("selectall");
if(!html){
this._cursorToStart = true;
html = " "; //
}
this.execCommand("inserthtml", html);
this._preDomFilterContent(this.editNode);
}else if(this.document && this.document.selection){//IE
//In IE, when the first element is not a text node, say
//an
tag, when replacing the content of the editing
//area, the tag will be around all the content
//so for now, use setValue for IE too
this.setValue(html);
}
this._set("value", this.getValue(true));
},
_preFilterContent: function(/*String*/ html){
// summary:
// Filter the input before setting the content of the editing
// area. DOM pre-filtering may happen after this
// string-based filtering takes place but as of 1.2, this is not
// guaranteed for operations such as the inserthtml command.
// tags:
// private
var ec = html;
array.forEach(this.contentPreFilters, function(ef){ if(ef){ ec = ef(ec); } });
return ec;
},
_preDomFilterContent: function(/*DomNode*/ dom){
// summary:
// filter the input's live DOM. All filter operations should be
// considered to be "live" and operating on the DOM that the user
// will be interacting with in their editing session.
// tags:
// private
dom = dom || this.editNode;
array.forEach(this.contentDomPreFilters, function(ef){
if(ef && lang.isFunction(ef)){
ef(dom);
}
}, this);
},
_postFilterContent: function(
/*DomNode|DomNode[]|String?*/ dom,
/*Boolean?*/ nonDestructive){
// summary:
// filter the output after getting the content of the editing area
//
// description:
// post-filtering allows plug-ins and users to specify any number
// of transforms over the editor's content, enabling many common
// use-cases such as transforming absolute to relative URLs (and
// vice-versa), ensuring conformance with a particular DTD, etc.
// The filters are registered in the contentDomPostFilters and
// contentPostFilters arrays. Each item in the
// contentDomPostFilters array is a function which takes a DOM
// Node or array of nodes as its only argument and returns the
// same. It is then passed down the chain for further filtering.
// The contentPostFilters array behaves the same way, except each
// member operates on strings. Together, the DOM and string-based
// filtering allow the full range of post-processing that should
// be necessaray to enable even the most agressive of post-editing
// conversions to take place.
//
// If nonDestructive is set to "true", the nodes are cloned before
// filtering proceeds to avoid potentially destructive transforms
// to the content which may still needed to be edited further.
// Once DOM filtering has taken place, the serialized version of
// the DOM which is passed is run through each of the
// contentPostFilters functions.
//
// dom:
// a node, set of nodes, which to filter using each of the current
// members of the contentDomPostFilters and contentPostFilters arrays.
//
// nonDestructive:
// defaults to "false". If true, ensures that filtering happens on
// a clone of the passed-in content and not the actual node
// itself.
//
// tags:
// private
var ec;
if(!lang.isString(dom)){
dom = dom || this.editNode;
if(this.contentDomPostFilters.length){
if(nonDestructive){
dom = lang.clone(dom);
}
array.forEach(this.contentDomPostFilters, function(ef){
dom = ef(dom);
});
}
ec = htmlapi.getChildrenHtml(dom);
}else{
ec = dom;
}
if(!lang.trim(ec.replace(/^\xA0\xA0*/, '').replace(/\xA0\xA0*$/, '')).length){
ec = "";
}
// if(has("ie")){
// //removing appended
for IE
// ec = ec.replace(/(?:
[\n\r]*)+$/i,"");
// }
array.forEach(this.contentPostFilters, function(ef){
ec = ef(ec);
});
return ec;
},
_saveContent: function(){
// summary:
// Saves the content in an onunload event if the editor has not been closed
// tags:
// private
var saveTextarea = dom.byId(dijit._scopeName + "._editor.RichText.value");
if(saveTextarea){
if(saveTextarea.value){
saveTextarea.value += this._SEPARATOR;
}
saveTextarea.value += this.name + this._NAME_CONTENT_SEP + this.getValue(true);
}
},
escapeXml: function(/*String*/ str, /*Boolean*/ noSingleQuotes){
// summary:
// Adds escape sequences for special characters in XML.
// Optionally skips escapes for single quotes
// tags:
// private
str = str.replace(/&/gm, "&").replace(//gm, ">").replace(/"/gm, """);
if(!noSingleQuotes){
str = str.replace(/'/gm, "'");
}
return str; // string
},
getNodeHtml: function(/* DomNode */ node){
// summary:
// Deprecated. Use dijit/_editor/html::_getNodeHtml() instead.
// tags:
// deprecated
kernel.deprecated('dijit.Editor::getNodeHtml is deprecated','use dijit/_editor/html::getNodeHtml instead', 2);
return htmlapi.getNodeHtml(node); // String
},
getNodeChildrenHtml: function(/* DomNode */ dom){
// summary:
// Deprecated. Use dijit/_editor/html::getChildrenHtml() instead.
// tags:
// deprecated
kernel.deprecated('dijit.Editor::getNodeChildrenHtml is deprecated','use dijit/_editor/html::getChildrenHtml instead', 2);
return htmlapi.getChildrenHtml(dom);
},
close: function(/*Boolean?*/ save){
// summary:
// Kills the editor and optionally writes back the modified contents to the
// element from which it originated.
// save:
// Whether or not to save the changes. If false, the changes are discarded.
// tags:
// private
if(this.isClosed){ return; }
if(!arguments.length){ save = true; }
if(save){
this._set("value", this.getValue(true));
}
// line height is squashed for iframes
// FIXME: why was this here? if(this.iframe){ this.domNode.style.lineHeight = null; }
if(this.interval){ clearInterval(this.interval); }
if(this._webkitListener){
//Cleaup of WebKit fix: #9532
this.disconnect(this._webkitListener);
delete this._webkitListener;
}
// Guard against memory leaks on IE (see #9268)
if(has("ie")){
this.iframe.onfocus = null;
}
this.iframe._loadFunc = null;
if(this._iframeRegHandle){
this._iframeRegHandle.remove();
delete this._iframeRegHandle;
}
if(this.textarea){
var s = this.textarea.style;
s.position = "";
s.left = s.top = "";
if(has("ie")){
s.overflow = this.__overflow;
this.__overflow = null;
}
this.textarea.value = this.value;
domConstruct.destroy(this.domNode);
this.domNode = this.textarea;
}else{
// Note that this destroys the iframe
this.domNode.innerHTML = this.value;
}
delete this.iframe;
domClass.remove(this.domNode, this.baseClass);
this.isClosed = true;
this.isLoaded = false;
delete this.editNode;
delete this.focusNode;
if(this.window && this.window._frameElement){
this.window._frameElement = null;
}
this.window = null;
this.document = null;
this.editingArea = null;
this.editorObject = null;
},
destroy: function(){
if(!this.isClosed){ this.close(false); }
if(this._updateTimer){
this._updateTimer.remove();
}
this.inherited(arguments);
if(RichText._globalSaveHandler){
delete RichText._globalSaveHandler[this.id];
}
},
_removeMozBogus: function(/* String */ html){
// summary:
// Post filter to remove unwanted HTML attributes generated by mozilla
// tags:
// private
return html.replace(/\stype="_moz"/gi, '').replace(/\s_moz_dirty=""/gi, '').replace(/_moz_resizing="(true|false)"/gi,''); // String
},
_removeWebkitBogus: function(/* String */ html){
// summary:
// Post filter to remove unwanted HTML attributes generated by webkit
// tags:
// private
html = html.replace(/\sclass="webkit-block-placeholder"/gi, '');
html = html.replace(/\sclass="apple-style-span"/gi, '');
// For some reason copy/paste sometime adds extra meta tags for charset on
// webkit (chrome) on mac.They need to be removed. See: #12007"
html = html.replace(//gi, '');
return html; // String
},
_normalizeFontStyle: function(/* String */ html){
// summary:
// Convert 'strong' and 'em' to 'b' and 'i'.
// description:
// Moz can not handle strong/em tags correctly, so to help
// mozilla and also to normalize output, convert them to 'b' and 'i'.
//
// Note the IE generates 'strong' and 'em' rather than 'b' and 'i'
// tags:
// private
return html.replace(/<(\/)?strong([ \>])/gi, '<$1b$2')
.replace(/<(\/)?em([ \>])/gi, '<$1i$2' ); // String
},
_preFixUrlAttributes: function(/* String */ html){
// summary:
// Pre-filter to do fixing to href attributes on `` and `` tags
// tags:
// private
return html.replace(/(?:(]+))/gi,
'$1$4$2$3$5$2 _djrealurl=$2$3$5$2')
.replace(/(?:(]+))/gi,
'$1$4$2$3$5$2 _djrealurl=$2$3$5$2'); // String
},
/*****************************************************************************
The following functions implement HTML manipulation commands for various
browser/contentEditable implementations. The goal of them is to enforce
standard behaviors of them.
******************************************************************************/
/*** queryCommandEnabled implementations ***/
_browserQueryCommandEnabled: function(command){
// summary:
// Implementation to call to the native queryCommandEnabled of the browser.
// command:
// The command to check.
// tags:
// protected
if(!command) { return false; }
var elem = has("ie") ? this.document.selection.createRange() : this.document;
try{
return elem.queryCommandEnabled(command);
}catch(e){
return false;
}
},
_createlinkEnabledImpl: function(/*===== argument =====*/){
// summary:
// This function implements the test for if the create link
// command should be enabled or not.
// argument:
// arguments to the exec command, if any.
// tags:
// protected
var enabled = true;
if(has("opera")){
var sel = this.window.getSelection();
if(sel.isCollapsed){
enabled = true;
}else{
enabled = this.document.queryCommandEnabled("createlink");
}
}else{
enabled = this._browserQueryCommandEnabled("createlink");
}
return enabled;
},
_unlinkEnabledImpl: function(/*===== argument =====*/){
// summary:
// This function implements the test for if the unlink
// command should be enabled or not.
// argument:
// arguments to the exec command, if any.
// tags:
// protected
var enabled = true;
if(has("mozilla") || has("webkit")){
enabled = this._sCall("hasAncestorElement", ["a"]);
}else{
enabled = this._browserQueryCommandEnabled("unlink");
}
return enabled;
},
_inserttableEnabledImpl: function(/*===== argument =====*/){
// summary:
// This function implements the test for if the inserttable
// command should be enabled or not.
// argument:
// arguments to the exec command, if any.
// tags:
// protected
var enabled = true;
if(has("mozilla") || has("webkit")){
enabled = true;
}else{
enabled = this._browserQueryCommandEnabled("inserttable");
}
return enabled;
},
_cutEnabledImpl: function(/*===== argument =====*/){
// summary:
// This function implements the test for if the cut
// command should be enabled or not.
// argument:
// arguments to the exec command, if any.
// tags:
// protected
var enabled = true;
if(has("webkit")){
// WebKit deems clipboard activity as a security threat and natively would return false
var sel = this.window.getSelection();
if(sel){ sel = sel.toString(); }
enabled = !!sel;
}else{
enabled = this._browserQueryCommandEnabled("cut");
}
return enabled;
},
_copyEnabledImpl: function(/*===== argument =====*/){
// summary:
// This function implements the test for if the copy
// command should be enabled or not.
// argument:
// arguments to the exec command, if any.
// tags:
// protected
var enabled = true;
if(has("webkit")){
// WebKit deems clipboard activity as a security threat and natively would return false
var sel = this.window.getSelection();
if(sel){ sel = sel.toString(); }
enabled = !!sel;
}else{
enabled = this._browserQueryCommandEnabled("copy");
}
return enabled;
},
_pasteEnabledImpl: function(/*===== argument =====*/){
// summary:c
// This function implements the test for if the paste
// command should be enabled or not.
// argument:
// arguments to the exec command, if any.
// tags:
// protected
var enabled = true;
if(has("webkit")){
return true;
}else{
enabled = this._browserQueryCommandEnabled("paste");
}
return enabled;
},
/*** execCommand implementations ***/
_inserthorizontalruleImpl: function(argument){
// summary:
// This function implements the insertion of HTML 'HR' tags.
// into a point on the page. IE doesn't to it right, so
// we have to use an alternate form
// argument:
// arguments to the exec command, if any.
// tags:
// protected
if(has("ie")){
return this._inserthtmlImpl("
");
}
return this.document.execCommand("inserthorizontalrule", false, argument);
},
_unlinkImpl: function(argument){
// summary:
// This function implements the unlink of an 'a' tag.
// argument:
// arguments to the exec command, if any.
// tags:
// protected
if((this.queryCommandEnabled("unlink")) && (has("mozilla") || has("webkit"))){
var a = this._sCall("getAncestorElement", [ "a" ]);
this._sCall("selectElement", [ a ]);
return this.document.execCommand("unlink", false, null);
}
return this.document.execCommand("unlink", false, argument);
},
_hilitecolorImpl: function(argument){
// summary:
// This function implements the hilitecolor command
// argument:
// arguments to the exec command, if any.
// tags:
// protected
var returnValue;
var isApplied = this._handleTextColorOrProperties("hilitecolor", argument);
if(!isApplied){
if(has("mozilla")){
// mozilla doesn't support hilitecolor properly when useCSS is
// set to false (bugzilla #279330)
this.document.execCommand("styleWithCSS", false, true);
console.log("Executing color command.");
returnValue = this.document.execCommand("hilitecolor", false, argument);
this.document.execCommand("styleWithCSS", false, false);
}else{
returnValue = this.document.execCommand("hilitecolor", false, argument);
}
}
return returnValue;
},
_backcolorImpl: function(argument){
// summary:
// This function implements the backcolor command
// argument:
// arguments to the exec command, if any.
// tags:
// protected
if(has("ie")){
// Tested under IE 6 XP2, no problem here, comment out
// IE weirdly collapses ranges when we exec these commands, so prevent it
// var tr = this.document.selection.createRange();
argument = argument ? argument : null;
}
var isApplied = this._handleTextColorOrProperties("backcolor", argument);
if(!isApplied){
isApplied = this.document.execCommand("backcolor", false, argument);
}
return isApplied;
},
_forecolorImpl: function(argument){
// summary:
// This function implements the forecolor command
// argument:
// arguments to the exec command, if any.
// tags:
// protected
if(has("ie")){
// Tested under IE 6 XP2, no problem here, comment out
// IE weirdly collapses ranges when we exec these commands, so prevent it
// var tr = this.document.selection.createRange();
argument = argument? argument : null;
}
var isApplied = false;
isApplied = this._handleTextColorOrProperties("forecolor", argument);
if(!isApplied){
isApplied = this.document.execCommand("forecolor", false, argument);
}
return isApplied;
},
_inserthtmlImpl: function(argument){
// summary:
// This function implements the insertion of HTML content into
// a point on the page.
// argument:
// The content to insert, if any.
// tags:
// protected
argument = this._preFilterContent(argument);
var rv = true;
if(has("ie")){
var insertRange = this.document.selection.createRange();
if(this.document.selection.type.toUpperCase() === 'CONTROL'){
var n=insertRange.item(0);
while(insertRange.length){
insertRange.remove(insertRange.item(0));
}
n.outerHTML=argument;
}else{
insertRange.pasteHTML(argument);
}
insertRange.select();
//insertRange.collapse(true);
}else if(has("mozilla") && !argument.length){
//mozilla can not inserthtml an empty html to delete current selection
//so we delete the selection instead in this case
this._sCall("remove"); // FIXME
}else{
rv = this.document.execCommand("inserthtml", false, argument);
}
return rv;
},
_boldImpl: function(argument){
// summary:
// This function implements an over-ride of the bold command.
// argument:
// Not used, operates by selection.
// tags:
// protected
var applied = false;
if(has("ie")){
this._adaptIESelection();
applied = this._adaptIEFormatAreaAndExec("bold");
}
if(!applied){
applied = this.document.execCommand("bold", false, argument);
}
return applied;
},
_italicImpl: function(argument){
// summary:
// This function implements an over-ride of the italic command.
// argument:
// Not used, operates by selection.
// tags:
// protected
var applied = false;
if(has("ie")){
this._adaptIESelection();
applied = this._adaptIEFormatAreaAndExec("italic");
}
if(!applied){
applied = this.document.execCommand("italic", false, argument);
}
return applied;
},
_underlineImpl: function(argument){
// summary:
// This function implements an over-ride of the underline command.
// argument:
// Not used, operates by selection.
// tags:
// protected
var applied = false;
if(has("ie")){
this._adaptIESelection();
applied = this._adaptIEFormatAreaAndExec("underline");
}
if(!applied){
applied = this.document.execCommand("underline", false, argument);
}
return applied;
},
_strikethroughImpl: function(argument){
// summary:
// This function implements an over-ride of the strikethrough command.
// argument:
// Not used, operates by selection.
// tags:
// protected
var applied = false;
if(has("ie")){
this._adaptIESelection();
applied = this._adaptIEFormatAreaAndExec("strikethrough");
}
if(!applied){
applied = this.document.execCommand("strikethrough", false, argument);
}
return applied;
},
_superscriptImpl: function(argument){
// summary:
// This function implements an over-ride of the superscript command.
// argument:
// Not used, operates by selection.
// tags:
// protected
var applied = false;
if(has("ie")){
this._adaptIESelection();
applied = this._adaptIEFormatAreaAndExec("superscript");
}
if(!applied){
applied = this.document.execCommand("superscript", false, argument);
}
return applied;
},
_subscriptImpl: function(argument){
// summary:
// This function implements an over-ride of the superscript command.
// argument:
// Not used, operates by selection.
// tags:
// protected
var applied = false;
if(has("ie")){
this._adaptIESelection();
applied = this._adaptIEFormatAreaAndExec("subscript");
}
if(!applied){
applied = this.document.execCommand("subscript", false, argument);
}
return applied;
},
_fontnameImpl: function(argument){
// summary:
// This function implements the fontname command
// argument:
// arguments to the exec command, if any.
// tags:
// protected
var isApplied;
if(has("ie")){
isApplied = this._handleTextColorOrProperties("fontname", argument);
}
if(!isApplied){
isApplied = this.document.execCommand("fontname", false, argument);
}
return isApplied;
},
_fontsizeImpl: function(argument){
// summary:
// This function implements the fontsize command
// argument:
// arguments to the exec command, if any.
// tags:
// protected
var isApplied;
if(has("ie")){
isApplied = this._handleTextColorOrProperties("fontsize", argument);
}
if(!isApplied){
isApplied = this.document.execCommand("fontsize", false, argument);
}
return isApplied;
},
_insertorderedlistImpl: function(argument){
// summary:
// This function implements the insertorderedlist command
// argument:
// arguments to the exec command, if any.
// tags:
// protected
var applied = false;
if(has("ie")){
applied = this._adaptIEList("insertorderedlist", argument);
}
if(!applied){
applied = this.document.execCommand("insertorderedlist", false, argument);
}
return applied;
},
_insertunorderedlistImpl: function(argument){
// summary:
// This function implements the insertunorderedlist command
// argument:
// arguments to the exec command, if any.
// tags:
// protected
var applied = false;
if(has("ie")){
applied = this._adaptIEList("insertunorderedlist", argument);
}
if(!applied){
applied = this.document.execCommand("insertunorderedlist", false, argument);
}
return applied;
},
getHeaderHeight: function(){
// summary:
// A function for obtaining the height of the header node
return this._getNodeChildrenHeight(this.header); // Number
},
getFooterHeight: function(){
// summary:
// A function for obtaining the height of the footer node
return this._getNodeChildrenHeight(this.footer); // Number
},
_getNodeChildrenHeight: function(node){
// summary:
// An internal function for computing the cumulative height of all child nodes of 'node'
// node:
// The node to process the children of;
var h = 0;
if(node && node.childNodes){
// IE didn't compute it right when position was obtained on the node directly is some cases,
// so we have to walk over all the children manually.
var i;
for(i = 0; i < node.childNodes.length; i++){
var size = domGeometry.position(node.childNodes[i]);
h += size.h;
}
}
return h; // Number
},
_isNodeEmpty: function(node, startOffset){
// summary:
// Function to test if a node is devoid of real content.
// node:
// The node to check.
// tags:
// private.
if(node.nodeType === 1/*element*/){
if(node.childNodes.length > 0){
return this._isNodeEmpty(node.childNodes[0], startOffset);
}
return true;
}else if(node.nodeType === 3/*text*/){
return (node.nodeValue.substring(startOffset) === "");
}
return false;
},
_removeStartingRangeFromRange: function(node, range){
// summary:
// Function to adjust selection range by removing the current
// start node.
// node:
// The node to remove from the starting range.
// range:
// The range to adapt.
// tags:
// private
if(node.nextSibling){
range.setStart(node.nextSibling,0);
}else{
var parent = node.parentNode;
while(parent && parent.nextSibling == null){
//move up the tree until we find a parent that has another node, that node will be the next node
parent = parent.parentNode;
}
if(parent){
range.setStart(parent.nextSibling,0);
}
}
return range;
},
_adaptIESelection: function(){
// summary:
// Function to adapt the IE range by removing leading 'newlines'
// Needed to fix issue with bold/italics/underline not working if
// range included leading 'newlines'.
// In IE, if a user starts a selection at the very end of a line,
// then the native browser commands will fail to execute correctly.
// To work around the issue, we can remove all empty nodes from
// the start of the range selection.
var selection = rangeapi.getSelection(this.window);
if(selection && selection.rangeCount && !selection.isCollapsed){
var range = selection.getRangeAt(0);
var firstNode = range.startContainer;
var startOffset = range.startOffset;
while(firstNode.nodeType === 3/*text*/ && startOffset >= firstNode.length && firstNode.nextSibling){
//traverse the text nodes until we get to the one that is actually highlighted
startOffset = startOffset - firstNode.length;
firstNode = firstNode.nextSibling;
}
//Remove the starting ranges until the range does not start with an empty node.
var lastNode=null;
while(this._isNodeEmpty(firstNode, startOffset) && firstNode !== lastNode){
lastNode =firstNode; //this will break the loop in case we can't find the next sibling
range = this._removeStartingRangeFromRange(firstNode, range); //move the start container to the next node in the range
firstNode = range.startContainer;
startOffset = 0; //start at the beginning of the new starting range
}
selection.removeAllRanges();// this will work as long as users cannot select multiple ranges. I have not been able to do that in the editor.
selection.addRange(range);
}
},
_adaptIEFormatAreaAndExec: function(command){
// summary:
// Function to handle IE's quirkiness regarding how it handles
// format commands on a word. This involves a lit of node splitting
// and format cloning.
// command:
// The format command, needed to check if the desired
// command is true or not.
var selection = rangeapi.getSelection(this.window);
var doc = this.document;
var rs, ret, range, txt, startNode, endNode, breaker, sNode;
if(command && selection && selection.isCollapsed){
var isApplied = this.queryCommandValue(command);
if(isApplied){
// We have to split backwards until we hit the format
var nNames = this._tagNamesForCommand(command);
range = selection.getRangeAt(0);
var fs = range.startContainer;
if(fs.nodeType === 3){
var offset = range.endOffset;
if(fs.length < offset){
//We are not looking from the right node, try to locate the correct one
ret = this._adjustNodeAndOffset(rs, offset);
fs = ret.node;
offset = ret.offset;
}
}
var topNode;
while(fs && fs !== this.editNode){
// We have to walk back and see if this is still a format or not.
// Hm, how do I do this?
var tName = fs.tagName? fs.tagName.toLowerCase() : "";
if(array.indexOf(nNames, tName) > -1){
topNode = fs;
break;
}
fs = fs.parentNode;
}
// Okay, we have a stopping place, time to split things apart.
if(topNode){
// Okay, we know how far we have to split backwards, so we have to split now.
rs = range.startContainer;
var newblock = doc.createElement(topNode.tagName);
domConstruct.place(newblock, topNode, "after");
if(rs && rs.nodeType === 3){
// Text node, we have to split it.
var nodeToMove, tNode;
var endOffset = range.endOffset;
if(rs.length < endOffset){
//We are not splitting the right node, try to locate the correct one
ret = this._adjustNodeAndOffset(rs, endOffset);
rs = ret.node;
endOffset = ret.offset;
}
txt = rs.nodeValue;
startNode = doc.createTextNode(txt.substring(0, endOffset));
var endText = txt.substring(endOffset, txt.length);
if(endText){
endNode = doc.createTextNode(endText);
}
// Place the split, then remove original nodes.
domConstruct.place(startNode, rs, "before");
if(endNode){
breaker = doc.createElement("span");
breaker.className = "ieFormatBreakerSpan";
domConstruct.place(breaker, rs, "after");
domConstruct.place(endNode, breaker, "after");
endNode = breaker;
}
domConstruct.destroy(rs);
// Okay, we split the text. Now we need to see if we're
// parented to the block element we're splitting and if
// not, we have to split all the way up. Ugh.
var parentC = startNode.parentNode;
var tagList = [];
var tagData;
while(parentC !== topNode){
var tg = parentC.tagName;
tagData = {tagName: tg};
tagList.push(tagData);
var newTg = doc.createElement(tg);
// Clone over any 'style' data.
if(parentC.style){
if(newTg.style){
if(parentC.style.cssText){
newTg.style.cssText = parentC.style.cssText;
tagData.cssText = parentC.style.cssText;
}
}
}
// If font also need to clone over any font data.
if(parentC.tagName === "FONT"){
if(parentC.color){
newTg.color = parentC.color;
tagData.color = parentC.color;
}
if(parentC.face){
newTg.face = parentC.face;
tagData.face = parentC.face;
}
if(parentC.size){ // this check was necessary on IE
newTg.size = parentC.size;
tagData.size = parentC.size;
}
}
if(parentC.className){
newTg.className = parentC.className;
tagData.className = parentC.className;
}
// Now move end node and every sibling
// after it over into the new tag.
if(endNode){
nodeToMove = endNode;
while(nodeToMove){
tNode = nodeToMove.nextSibling;
newTg.appendChild(nodeToMove);
nodeToMove = tNode;
}
}
if(newTg.tagName == parentC.tagName){
breaker = doc.createElement("span");
breaker.className = "ieFormatBreakerSpan";
domConstruct.place(breaker, parentC, "after");
domConstruct.place(newTg, breaker, "after");
}else{
domConstruct.place(newTg, parentC, "after");
}
startNode = parentC;
endNode = newTg;
parentC = parentC.parentNode;
}
// Lastly, move the split out all the split tags
// to the new block as they should now be split properly.
if(endNode){
nodeToMove = endNode;
if(nodeToMove.nodeType === 1 || (nodeToMove.nodeType === 3 && nodeToMove.nodeValue)){
// Non-blank text and non-text nodes need to clear out that blank space
// before moving the contents.
newblock.innerHTML = "";
}
while(nodeToMove){
tNode = nodeToMove.nextSibling;
newblock.appendChild(nodeToMove);
nodeToMove = tNode;
}
}
// We had intermediate tags, we have to now recreate them inbetween the split
// and restore what styles, classnames, etc, we can.
var newrange;
if(tagList.length){
tagData = tagList.pop();
var newContTag = doc.createElement(tagData.tagName);
if(tagData.cssText && newContTag.style){
newContTag.style.cssText = tagData.cssText;
}
if(tagData.className){
newContTag.className = tagData.className;
}
if(tagData.tagName === "FONT"){
if(tagData.color){
newContTag.color = tagData.color;
}
if(tagData.face){
newContTag.face = tagData.face;
}
if(tagData.size){
newContTag.size = tagData.size;
}
}
domConstruct.place(newContTag, newblock, "before");
while(tagList.length){
tagData = tagList.pop();
var newTgNode = doc.createElement(tagData.tagName);
if(tagData.cssText && newTgNode.style){
newTgNode.style.cssText = tagData.cssText;
}
if(tagData.className){
newTgNode.className = tagData.className;
}
if(tagData.tagName === "FONT"){
if(tagData.color){
newTgNode.color = tagData.color;
}
if(tagData.face){
newTgNode.face = tagData.face;
}
if(tagData.size){
newTgNode.size = tagData.size;
}
}
newContTag.appendChild(newTgNode);
newContTag = newTgNode;
}
// Okay, everything is theoretically split apart and removed from the content
// so insert the dummy text to select, select it, then
// clear to position cursor.
sNode = doc.createTextNode(".");
breaker.appendChild(sNode);
newContTag.appendChild(sNode);
newrange = rangeapi.create(this.window);
newrange.setStart(sNode, 0);
newrange.setEnd(sNode, sNode.length);
selection.removeAllRanges();
selection.addRange(newrange);
this._sCall("collapse", [false]);
sNode.parentNode.innerHTML = "";
}else{
// No extra tags, so we have to insert a breaker point and rely
// on filters to remove it later.
breaker = doc.createElement("span");
breaker.className="ieFormatBreakerSpan";
sNode = doc.createTextNode(".");
breaker.appendChild(sNode);
domConstruct.place(breaker, newblock, "before");
newrange = rangeapi.create(this.window);
newrange.setStart(sNode, 0);
newrange.setEnd(sNode, sNode.length);
selection.removeAllRanges();
selection.addRange(newrange);
this._sCall("collapse", [false]);
sNode.parentNode.innerHTML = "";
}
if(!newblock.firstChild){
// Empty, we don't need it. Split was at end or similar
// So, remove it.
domConstruct.destroy(newblock);
}
return true;
}
}
return false;
}else{
range = selection.getRangeAt(0);
rs = range.startContainer;
if(rs && rs.nodeType === 3){
// Text node, we have to split it.
var offset = range.startOffset;
if(rs.length < offset){
//We are not splitting the right node, try to locate the correct one
ret = this._adjustNodeAndOffset(rs, offset);
rs = ret.node;
offset = ret.offset;
}
txt = rs.nodeValue;
startNode = doc.createTextNode(txt.substring(0, offset));
var endText = txt.substring(offset);
if(endText !== ""){
endNode = doc.createTextNode(txt.substring(offset));
}
// Create a space, we'll select and bold it, so
// the whole word doesn't get bolded
breaker = doc.createElement("span");
sNode = doc.createTextNode(".");
breaker.appendChild(sNode);
if(startNode.length){
domConstruct.place(startNode, rs, "after");
}else{
startNode = rs;
}
domConstruct.place(breaker, startNode, "after");
if(endNode){
domConstruct.place(endNode, breaker, "after");
}
domConstruct.destroy(rs);
var newrange = rangeapi.create(this.window);
newrange.setStart(sNode, 0);
newrange.setEnd(sNode, sNode.length);
selection.removeAllRanges();
selection.addRange(newrange);
doc.execCommand(command);
domConstruct.place(breaker.firstChild, breaker, "before");
domConstruct.destroy(breaker);
newrange.setStart(sNode, 0);
newrange.setEnd(sNode, sNode.length);
selection.removeAllRanges();
selection.addRange(newrange);
this._sCall("collapse", [false]);
sNode.parentNode.innerHTML = "";
return true;
}
}
}else{
return false;
}
},
_adaptIEList: function(command /*===== , argument =====*/){
// summary:
// This function handles normalizing the IE list behavior as
// much as possible.
// command:
// The list command to execute.
// argument:
// Any additional argument.
// tags:
// private
var selection = rangeapi.getSelection(this.window);
if(selection.isCollapsed){
// In the case of no selection, lets commonize the behavior and
// make sure that it indents if needed.
if(selection.rangeCount && !this.queryCommandValue(command)){
var range = selection.getRangeAt(0);
var sc = range.startContainer;
if(sc && sc.nodeType == 3){
// text node. Lets see if there is a node before it that isn't
// some sort of breaker.
if(!range.startOffset){
// We're at the beginning of a text area. It may have been br split
// Who knows? In any event, we must create the list manually
// or IE may shove too much into the list element. It seems to
// grab content before the text node too if it's br split.
// Why can't IE work like everyone else?
// Create a space, we'll select and bold it, so
// the whole word doesn't get bolded
var lType = "ul";
if(command === "insertorderedlist"){
lType = "ol";
}
var list = this.document.createElement(lType);
var li = domConstruct.create("li", null, list);
domConstruct.place(list, sc, "before");
// Move in the text node as part of the li.
li.appendChild(sc);
// We need a br after it or the enter key handler
// sometimes throws errors.
domConstruct.create("br", null, list, "after");
// Okay, now lets move our cursor to the beginning.
var newrange = rangeapi.create(this.window);
newrange.setStart(sc, 0);
newrange.setEnd(sc, sc.length);
selection.removeAllRanges();
selection.addRange(newrange);
this._sCall("collapse", [true]);
return true;
}
}
}
}
return false;
},
_handleTextColorOrProperties: function(command, argument){
// summary:
// This function handles appplying text color as best it is
// able to do so when the selection is collapsed, making the
// behavior cross-browser consistent. It also handles the name
// and size for IE.
// command:
// The command.
// argument:
// Any additional arguments.
// tags:
// private
var selection = rangeapi.getSelection(this.window);
var doc = this.document;
var rs, ret, range, txt, startNode, endNode, breaker, sNode;
argument = argument || null;
if(command && selection && selection.isCollapsed){
if(selection.rangeCount){
range = selection.getRangeAt(0);
rs = range.startContainer;
if(rs && rs.nodeType === 3){
// Text node, we have to split it.
var offset = range.startOffset;
if(rs.length < offset){
//We are not splitting the right node, try to locate the correct one
ret = this._adjustNodeAndOffset(rs, offset);
rs = ret.node;
offset = ret.offset;
}
txt = rs.nodeValue;
startNode = doc.createTextNode(txt.substring(0, offset));
var endText = txt.substring(offset);
if(endText !== ""){
endNode = doc.createTextNode(txt.substring(offset));
}
// Create a space, we'll select and bold it, so
// the whole word doesn't get bolded
breaker = doc.createElement("span");
sNode = doc.createTextNode(".");
breaker.appendChild(sNode);
// Create a junk node to avoid it trying to style the breaker.
// This will get destroyed later.
var extraSpan = doc.createElement("span");
breaker.appendChild(extraSpan);
if(startNode.length){
domConstruct.place(startNode, rs, "after");
}else{
startNode = rs;
}
domConstruct.place(breaker, startNode, "after");
if(endNode){
domConstruct.place(endNode, breaker, "after");
}
domConstruct.destroy(rs);
var newrange = rangeapi.create(this.window);
newrange.setStart(sNode, 0);
newrange.setEnd(sNode, sNode.length);
selection.removeAllRanges();
selection.addRange(newrange);
if(has("webkit")){
// WebKit is frustrating with positioning the cursor.
// It stinks to have a selected space, but there really
// isn't much choice here.
var style = "color";
if(command === "hilitecolor" || command === "backcolor"){
style = "backgroundColor";
}
domStyle.set(breaker, style, argument);
this._sCall("remove", []);
domConstruct.destroy(extraSpan);
breaker.innerHTML = " "; //
this._sCall("selectElement", [breaker]);
this.focus();
}else{
this.execCommand(command, argument);
domConstruct.place(breaker.firstChild, breaker, "before");
domConstruct.destroy(breaker);
newrange.setStart(sNode, 0);
newrange.setEnd(sNode, sNode.length);
selection.removeAllRanges();
selection.addRange(newrange);
this._sCall("collapse", [false]);
sNode.parentNode.removeChild(sNode);
}
return true;
}
}
}
return false;
},
_adjustNodeAndOffset: function(/*DomNode*/node, /*Int*/offset){
// summary:
// In the case there are multiple text nodes in a row the offset may not be within the node.
// If the offset is larger than the node length, it will attempt to find
// the next text sibling until it locates the text node in which the offset refers to
// node:
// The node to check.
// offset:
// The position to find within the text node
// tags:
// private.
while(node.length < offset && node.nextSibling && node.nextSibling.nodeType === 3){
//Adjust the offset and node in the case of multiple text nodes in a row
offset = offset - node.length;
node = node.nextSibling;
}
return {"node": node, "offset": offset};
},
_tagNamesForCommand: function(command){
// summary:
// Function to return the tab names that are associated
// with a particular style.
// command: String
// The command to return tags for.
// tags:
// private
if(command === "bold"){
return ["b", "strong"];
}else if(command === "italic"){
return ["i","em"];
}else if(command === "strikethrough"){
return ["s", "strike"];
}else if(command === "superscript"){
return ["sup"];
}else if(command === "subscript"){
return ["sub"];
}else if(command === "underline"){
return ["u"];
}
return [];
},
_stripBreakerNodes: function(/*DOMNode*/ node){
// summary:
// Function for stripping out the breaker spans inserted by the formatting command.
// Registered as a filter for IE, handles the breaker spans needed to fix up
// How bold/italic/etc, work when selection is collapsed (single cursor).
if(!this.isLoaded){ return; } // this method requires init to be complete
query(".ieFormatBreakerSpan", node).forEach(function(b){
while(b.firstChild){
domConstruct.place(b.firstChild, b, "before");
}
domConstruct.destroy(b);
});
return node;
}
});
return RichText;
});