summaryrefslogtreecommitdiff
path: root/lib/dijit/_editor/RichText.js
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dijit/_editor/RichText.js')
-rw-r--r--lib/dijit/_editor/RichText.js3112
1 files changed, 2081 insertions, 1031 deletions
diff --git a/lib/dijit/_editor/RichText.js b/lib/dijit/_editor/RichText.js
index 696715986..978371557 100644
--- a/lib/dijit/_editor/RichText.js
+++ b/lib/dijit/_editor/RichText.js
@@ -1,1046 +1,2096 @@
/*
- Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved.
+ Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved.
Available via Academic Free License >= 2.1 OR the modified BSD license.
see: http://dojotoolkit.org/license for details
*/
-if(!dojo._hasResource["dijit._editor.RichText"]){
-dojo._hasResource["dijit._editor.RichText"]=true;
+if(!dojo._hasResource["dijit._editor.RichText"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
+dojo._hasResource["dijit._editor.RichText"] = true;
dojo.provide("dijit._editor.RichText");
dojo.require("dijit._Widget");
dojo.require("dijit._CssStateMixin");
dojo.require("dijit._editor.selection");
dojo.require("dijit._editor.range");
dojo.require("dijit._editor.html");
-if(!dojo.config["useXDomain"]||dojo.config["allowXdRichTextSave"]){
-if(dojo._postLoad){
-(function(){
-var _1=dojo.doc.createElement("textarea");
-_1.id=dijit._scopeName+"._editor.RichText.savedContent";
-dojo.style(_1,{display:"none",position:"absolute",top:"-100px",height:"3px",width:"3px"});
-dojo.body().appendChild(_1);
-})();
-}else{
-try{
-dojo.doc.write("<textarea id=\""+dijit._scopeName+"._editor.RichText.savedContent\" "+"style=\"display:none;position:absolute;top:-100px;left:-100px;height:3px;width:3px;overflow:hidden;\"></textarea>");
-}
-catch(e){
-}
-}
-}
-dojo.declare("dijit._editor.RichText",[dijit._Widget,dijit._CssStateMixin],{constructor:function(_2){
-this.contentPreFilters=[];
-this.contentPostFilters=[];
-this.contentDomPreFilters=[];
-this.contentDomPostFilters=[];
-this.editingAreaStyleSheets=[];
-this.events=[].concat(this.events);
-this._keyHandlers={};
-this.contentPreFilters.push(dojo.hitch(this,"_preFixUrlAttributes"));
-if(dojo.isMoz){
-this.contentPreFilters.push(this._normalizeFontStyle);
-this.contentPostFilters.push(this._removeMozBogus);
-}
-if(dojo.isWebKit){
-this.contentPreFilters.push(this._removeWebkitBogus);
-this.contentPostFilters.push(this._removeWebkitBogus);
-}
-if(dojo.isIE){
-this.contentPostFilters.push(this._normalizeFontStyle);
-}
-if(_2&&dojo.isString(_2.value)){
-this.value=_2.value;
-}
-this.onLoadDeferred=new dojo.Deferred();
-},baseClass:"dijitEditor",inheritWidth:false,focusOnLoad:false,name:"",styleSheets:"",_content:"",height:"300px",minHeight:"1em",isClosed:true,isLoaded:false,_SEPARATOR:"@@**%%__RICHTEXTBOUNDRY__%%**@@",onLoadDeferred:null,isTabIndent:false,disableSpellCheck:false,postCreate:function(){
-if("textarea"==this.domNode.tagName.toLowerCase()){
-console.warn("RichText should not be used with the TEXTAREA tag. See dijit._editor.RichText docs.");
-}
-this.inherited(arguments);
-dojo.publish(dijit._scopeName+"._editor.RichText::init",[this]);
-this.open();
-this.setupDefaultShortcuts();
-},setupDefaultShortcuts:function(){
-var _3=dojo.hitch(this,function(_4,_5){
-return function(){
-return !this.execCommand(_4,_5);
-};
-});
-var _6={b:_3("bold"),i:_3("italic"),u:_3("underline"),a:_3("selectall"),s:function(){
-this.save(true);
-},m:function(){
-this.isTabIndent=!this.isTabIndent;
-},"1":_3("formatblock","h1"),"2":_3("formatblock","h2"),"3":_3("formatblock","h3"),"4":_3("formatblock","h4"),"\\":_3("insertunorderedlist")};
-if(!dojo.isIE){
-_6.Z=_3("redo");
-}
-for(var _7 in _6){
-this.addKeyHandler(_7,true,false,_6[_7]);
-}
-},events:["onKeyPress","onKeyDown","onKeyUp","onClick"],captureEvents:[],_editorCommandsLocalized:false,_localizeEditorCommands:function(){
-if(this._editorCommandsLocalized){
-return;
-}
-this._editorCommandsLocalized=true;
-var _8=["div","p","pre","h1","h2","h3","h4","h5","h6","ol","ul","address"];
-var _9="",_a,i=0;
-while((_a=_8[i++])){
-if(_a.charAt(1)!="l"){
-_9+="<"+_a+"><span>content</span></"+_a+"><br/>";
-}else{
-_9+="<"+_a+"><li>content</li></"+_a+"><br/>";
-}
-}
-var _b=dojo.doc.createElement("div");
-dojo.style(_b,{position:"absolute",top:"-2000px"});
-dojo.doc.body.appendChild(_b);
-_b.innerHTML=_9;
-var _c=_b.firstChild;
-while(_c){
-dijit._editor.selection.selectElement(_c.firstChild);
-dojo.withGlobal(this.window,"selectElement",dijit._editor.selection,[_c.firstChild]);
-var _d=_c.tagName.toLowerCase();
-this._local2NativeFormatNames[_d]=document.queryCommandValue("formatblock");
-this._native2LocalFormatNames[this._local2NativeFormatNames[_d]]=_d;
-_c=_c.nextSibling.nextSibling;
-}
-dojo.body().removeChild(_b);
-},open:function(_e){
-if(!this.onLoadDeferred||this.onLoadDeferred.fired>=0){
-this.onLoadDeferred=new dojo.Deferred();
-}
-if(!this.isClosed){
-this.close();
-}
-dojo.publish(dijit._scopeName+"._editor.RichText::open",[this]);
-this._content="";
-if(arguments.length==1&&_e.nodeName){
-this.domNode=_e;
-}
-var dn=this.domNode;
-var _f;
-if(dojo.isString(this.value)){
-_f=this.value;
-delete this.value;
-dn.innerHTML="";
-}else{
-if(dn.nodeName&&dn.nodeName.toLowerCase()=="textarea"){
-var ta=(this.textarea=dn);
-this.name=ta.name;
-_f=ta.value;
-dn=this.domNode=dojo.doc.createElement("div");
-dn.setAttribute("widgetId",this.id);
-ta.removeAttribute("widgetId");
-dn.cssText=ta.cssText;
-dn.className+=" "+ta.className;
-dojo.place(dn,ta,"before");
-var _10=dojo.hitch(this,function(){
-dojo.style(ta,{display:"block",position:"absolute",top:"-1000px"});
-if(dojo.isIE){
-var s=ta.style;
-this.__overflow=s.overflow;
-s.overflow="hidden";
-}
-});
-if(dojo.isIE){
-setTimeout(_10,10);
-}else{
-_10();
-}
-if(ta.form){
-dojo.connect(ta.form,"onsubmit",this,function(){
-ta.value=this.getValue();
-});
-}
-}else{
-_f=dijit._editor.getChildrenHtml(dn);
-dn.innerHTML="";
-}
-}
-var _11=dojo.contentBox(dn);
-this._oldHeight=_11.h;
-this._oldWidth=_11.w;
-this.savedContent=_f;
-if(dn.nodeName&&dn.nodeName=="LI"){
-dn.innerHTML=" <br>";
-}
-this.header=dn.ownerDocument.createElement("div");
-dn.appendChild(this.header);
-this.editingArea=dn.ownerDocument.createElement("div");
-dn.appendChild(this.editingArea);
-this.footer=dn.ownerDocument.createElement("div");
-dn.appendChild(this.footer);
-if(this.name!==""&&(!dojo.config["useXDomain"]||dojo.config["allowXdRichTextSave"])){
-var _12=dojo.byId(dijit._scopeName+"._editor.RichText.savedContent");
-if(_12.value!==""){
-var _13=_12.value.split(this._SEPARATOR),i=0,dat;
-while((dat=_13[i++])){
-var _14=dat.split(":");
-if(_14[0]==this.name){
-_f=_14[1];
-_13.splice(i,1);
-break;
-}
-}
-}
-dojo.addOnUnload(dojo.hitch(this,"_saveContent"));
-}
-this.isClosed=false;
-var ifr=(this.editorObject=this.iframe=dojo.doc.createElement("iframe"));
-ifr.id=this.id+"_iframe";
-this._iframeSrc=this._getIframeDocTxt();
-ifr.style.border="none";
-ifr.style.width="100%";
-if(this._layoutMode){
-ifr.style.height="100%";
-}else{
-if(dojo.isIE>=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=dojo.hitch(this,function(win){
-this.window=win;
-this.document=this.window.document;
-if(dojo.isIE){
-this._localizeEditorCommands();
-}
-this.onLoad(_f);
-});
-var s="javascript:parent."+dijit._scopeName+".byId(\""+this.id+"\")._iframeSrc";
-ifr.setAttribute("src",s);
-this.editingArea.appendChild(ifr);
-if(dn.nodeName=="LI"){
-dn.lastChild.style.marginTop="-1.2em";
-}
-dojo.addClass(this.domNode,this.baseClass);
-},_local2NativeFormatNames:{},_native2LocalFormatNames:{},_getIframeDocTxt:function(){
-var _15=dojo.getComputedStyle(this.domNode);
-var _16="";
-var _17=true;
-if(dojo.isIE||(!this.height&&!dojo.isMoz)){
-_16="<div id='dijitEditorBody'></div>";
-_17=false;
-}else{
-if(dojo.isMoz){
-this._cursorToStart=true;
-_16="&nbsp;";
-}
-}
-var _18=[_15.fontWeight,_15.fontSize,_15.fontFamily].join(" ");
-var _19=_15.lineHeight;
-if(_19.indexOf("px")>=0){
-_19=parseFloat(_19)/parseFloat(_15.fontSize);
-}else{
-if(_19.indexOf("em")>=0){
-_19=parseFloat(_19);
-}else{
-_19="normal";
-}
-}
-var _1a="";
-var _1b=this;
-this.style.replace(/(^|;)\s*(line-|font-?)[^;]+/ig,function(_1c){
-_1c=_1c.replace(/^;/ig,"")+";";
-var s=_1c.split(":")[0];
-if(s){
-s=dojo.trim(s);
-s=s.toLowerCase();
-var i;
-var sC="";
-for(i=0;i<s.length;i++){
-var c=s.charAt(i);
-switch(c){
-case "-":
-i++;
-c=s.charAt(i).toUpperCase();
-default:
-sC+=c;
-}
-}
-dojo.style(_1b.domNode,sC,"");
-}
-_1a+=_1c+";";
-});
-var _1d=dojo.query("label[for=\""+this.id+"\"]");
-return [this.isLeftToRight()?"<html>\n<head>\n":"<html dir='rtl'>\n<head>\n",(dojo.isMoz&&_1d.length?"<title>"+_1d[0].innerHTML+"</title>\n":""),"<meta http-equiv='Content-Type' content='text/html'>\n","<style>\n","\tbody,html {\n","\t\tbackground:transparent;\n","\t\tpadding: 1px 0 0 0;\n","\t\tmargin: -1px 0 0 0;\n",((dojo.isWebKit)?"\t\twidth: 100%;\n":""),((dojo.isWebKit)?"\t\theight: 100%;\n":""),"\t}\n","\tbody{\n","\t\ttop:0px;\n","\t\tleft:0px;\n","\t\tright:0px;\n","\t\tfont:",_18,";\n",((this.height||dojo.isOpera)?"":"\t\tposition: fixed;\n"),"\t\tmin-height:",this.minHeight,";\n","\t\tline-height:",_19,";\n","\t}\n","\tp{ margin: 1em 0; }\n",(!_17&&!this.height?"\tbody,html {overflow-y: hidden;}\n":""),"\t#dijitEditorBody{overflow-x: auto; overflow-y:"+(this.height?"auto;":"hidden;")+"}\n","\tli > ul:-moz-first-node, li > ol:-moz-first-node{ padding-top: 1.2em; }\n","\tli{ min-height:1.2em; }\n","</style>\n",this._applyEditingAreaStyleSheets(),"\n","</head>\n<body ",(_17?"id='dijitEditorBody' ":""),"onload='frameElement._loadFunc(window,document)' style='"+_1a+"'>",_16,"</body>\n</html>"].join("");
-},_applyEditingAreaStyleSheets:function(){
-var _1e=[];
-if(this.styleSheets){
-_1e=this.styleSheets.split(";");
-this.styleSheets="";
-}
-_1e=_1e.concat(this.editingAreaStyleSheets);
-this.editingAreaStyleSheets=[];
-var _1f="",i=0,url;
-while((url=_1e[i++])){
-var _20=(new dojo._Url(dojo.global.location,url)).toString();
-this.editingAreaStyleSheets.push(_20);
-_1f+="<link rel=\"stylesheet\" type=\"text/css\" href=\""+_20+"\"/>";
-}
-return _1f;
-},addStyleSheet:function(uri){
-var url=uri.toString();
-if(url.charAt(0)=="."||(url.charAt(0)!="/"&&!uri.host)){
-url=(new dojo._Url(dojo.global.location,url)).toString();
-}
-if(dojo.indexOf(this.editingAreaStyleSheets,url)>-1){
-return;
-}
-this.editingAreaStyleSheets.push(url);
-this.onLoadDeferred.addCallback(dojo.hitch(function(){
-if(this.document.createStyleSheet){
-this.document.createStyleSheet(url);
-}else{
-var _21=this.document.getElementsByTagName("head")[0];
-var _22=this.document.createElement("link");
-_22.rel="stylesheet";
-_22.type="text/css";
-_22.href=url;
-_21.appendChild(_22);
-}
-}));
-},removeStyleSheet:function(uri){
-var url=uri.toString();
-if(url.charAt(0)=="."||(url.charAt(0)!="/"&&!uri.host)){
-url=(new dojo._Url(dojo.global.location,url)).toString();
-}
-var _23=dojo.indexOf(this.editingAreaStyleSheets,url);
-if(_23==-1){
-return;
-}
-delete this.editingAreaStyleSheets[_23];
-dojo.withGlobal(this.window,"query",dojo,["link:[href=\""+url+"\"]"]).orphan();
-},disabled:false,_mozSettingProps:{"styleWithCSS":false},_setDisabledAttr:function(_24){
-this.disabled=_24;
-if(!this.isLoaded){
-return;
-}
-_24=!!_24;
-if(dojo.isIE||dojo.isWebKit||dojo.isOpera){
-var _25=dojo.isIE&&(this.isLoaded||!this.focusOnLoad);
-if(_25){
-this.editNode.unselectable="on";
-}
-this.editNode.contentEditable=!_24;
-if(_25){
-var _26=this;
-setTimeout(function(){
-_26.editNode.unselectable="off";
-},0);
-}
-}else{
-try{
-this.document.designMode=(_24?"off":"on");
-}
-catch(e){
-return;
-}
-if(!_24&&this._mozSettingProps){
-var ps=this._mozSettingProps;
-for(var n in ps){
-if(ps.hasOwnProperty(n)){
-try{
-this.document.execCommand(n,false,ps[n]);
-}
-catch(e2){
-}
-}
-}
-}
-}
-this._disabledOK=true;
-},onLoad:function(_27){
-if(!this.window.__registeredWindow){
-this.window.__registeredWindow=true;
-this._iframeRegHandle=dijit.registerIframe(this.iframe);
-}
-if(!dojo.isIE&&(this.height||dojo.isMoz)){
-this.editNode=this.document.body;
-}else{
-this.editNode=this.document.body.firstChild;
-var _28=this;
-if(dojo.isIE){
-var _29=(this.tabStop=dojo.doc.createElement("<div tabIndex=-1>"));
-this.editingArea.appendChild(_29);
-this.iframe.onfocus=function(){
-_28.editNode.setActive();
-};
-}
-}
-this.focusNode=this.editNode;
-var _2a=this.events.concat(this.captureEvents);
-var ap=this.iframe?this.document:this.editNode;
-dojo.forEach(_2a,function(_2b){
-this.connect(ap,_2b.toLowerCase(),_2b);
-},this);
-if(dojo.isIE){
-this.connect(this.document,"onmousedown","_onIEMouseDown");
-this.editNode.style.zoom=1;
-}else{
-this.connect(this.document,"onmousedown",function(){
-delete this._cursorToStart;
-});
-}
-if(dojo.isWebKit){
-this._webkitListener=this.connect(this.document,"onmouseup","onDisplayChanged");
-}
-if(dojo.isIE){
-try{
-this.document.execCommand("RespectVisibilityInDesign",true,null);
-}
-catch(e){
-}
-}
-this.isLoaded=true;
-this.set("disabled",this.disabled);
-var _2c=dojo.hitch(this,function(){
-this.setValue(_27);
-if(this.onLoadDeferred){
-this.onLoadDeferred.callback(true);
-}
-this.onDisplayChanged();
-if(this.focusOnLoad){
-dojo.addOnLoad(dojo.hitch(this,function(){
-setTimeout(dojo.hitch(this,"focus"),this.updateInterval);
-}));
-}
-this.savedContent=this.getValue(true);
-});
-if(this.setValueDeferred){
-this.setValueDeferred.addCallback(_2c);
-}else{
-_2c();
-}
-},onKeyDown:function(e){
-if(e.keyCode===dojo.keys.TAB&&this.isTabIndent){
-dojo.stopEvent(e);
-if(this.queryCommandEnabled((e.shiftKey?"outdent":"indent"))){
-this.execCommand((e.shiftKey?"outdent":"indent"));
-}
-}
-if(dojo.isIE){
-if(e.keyCode==dojo.keys.TAB&&!this.isTabIndent){
-if(e.shiftKey&&!e.ctrlKey&&!e.altKey){
-this.iframe.focus();
-}else{
-if(!e.shiftKey&&!e.ctrlKey&&!e.altKey){
-this.tabStop.focus();
-}
-}
-}else{
-if(e.keyCode===dojo.keys.BACKSPACE&&this.document.selection.type==="Control"){
-dojo.stopEvent(e);
-this.execCommand("delete");
-}else{
-if((65<=e.keyCode&&e.keyCode<=90)||(e.keyCode>=37&&e.keyCode<=40)){
-e.charCode=e.keyCode;
-this.onKeyPress(e);
-}
-}
-}
-}
-return true;
-},onKeyUp:function(e){
-return;
-},setDisabled:function(_2d){
-dojo.deprecated("dijit.Editor::setDisabled is deprecated","use dijit.Editor::attr(\"disabled\",boolean) instead",2);
-this.set("disabled",_2d);
-},_setValueAttr:function(_2e){
-this.setValue(_2e);
-},_setDisableSpellCheckAttr:function(_2f){
-if(this.document){
-dojo.attr(this.document.body,"spellcheck",!_2f);
-}else{
-this.onLoadDeferred.addCallback(dojo.hitch(this,function(){
-dojo.attr(this.document.body,"spellcheck",!_2f);
-}));
-}
-this.disableSpellCheck=_2f;
-},onKeyPress:function(e){
-var c=(e.keyChar&&e.keyChar.toLowerCase())||e.keyCode,_30=this._keyHandlers[c],_31=arguments;
-if(_30&&!e.altKey){
-dojo.some(_30,function(h){
-if(!(h.shift^e.shiftKey)&&!(h.ctrl^(e.ctrlKey||e.metaKey))){
-if(!h.handler.apply(this,_31)){
-e.preventDefault();
-}
-return true;
-}
-},this);
-}
-if(!this._onKeyHitch){
-this._onKeyHitch=dojo.hitch(this,"onKeyPressed");
-}
-setTimeout(this._onKeyHitch,1);
-return true;
-},addKeyHandler:function(key,_32,_33,_34){
-if(!dojo.isArray(this._keyHandlers[key])){
-this._keyHandlers[key]=[];
-}
-this._keyHandlers[key].push({shift:_33||false,ctrl:_32||false,handler:_34});
-},onKeyPressed:function(){
-this.onDisplayChanged();
-},onClick:function(e){
-this.onDisplayChanged(e);
-},_onIEMouseDown:function(e){
-if(!this._focused&&!this.disabled){
-this.focus();
-}
-},_onBlur:function(e){
-this.inherited(arguments);
-var _35=this.getValue(true);
-if(_35!=this.savedContent){
-this.onChange(_35);
-this.savedContent=_35;
-}
-},_onFocus:function(e){
-if(!this.disabled){
-if(!this._disabledOK){
-this.set("disabled",false);
-}
-this.inherited(arguments);
-}
-},blur:function(){
-if(!dojo.isIE&&this.window.document.documentElement&&this.window.document.documentElement.focus){
-this.window.document.documentElement.focus();
-}else{
-if(dojo.doc.body.focus){
-dojo.doc.body.focus();
-}
-}
-},focus:function(){
-if(!this.isLoaded){
-this.focusOnLoad=true;
-return;
-}
-if(this._cursorToStart){
-delete this._cursorToStart;
-if(this.editNode.childNodes){
-this.placeCursorAtStart();
-return;
-}
-}
-if(!dojo.isIE){
-dijit.focus(this.iframe);
-}else{
-if(this.editNode&&this.editNode.focus){
-this.iframe.fireEvent("onfocus",document.createEventObject());
-}
-}
-},updateInterval:200,_updateTimer:null,onDisplayChanged:function(e){
-if(this._updateTimer){
-clearTimeout(this._updateTimer);
-}
-if(!this._updateHandler){
-this._updateHandler=dojo.hitch(this,"onNormalizedDisplayChanged");
-}
-this._updateTimer=setTimeout(this._updateHandler,this.updateInterval);
-},onNormalizedDisplayChanged:function(){
-delete this._updateTimer;
-},onChange:function(_36){
-},_normalizeCommand:function(cmd,_37){
-var _38=cmd.toLowerCase();
-if(_38=="formatblock"){
-if(dojo.isSafari&&_37===undefined){
-_38="heading";
-}
-}else{
-if(_38=="hilitecolor"&&!dojo.isMoz){
-_38="backcolor";
-}
-}
-return _38;
-},_qcaCache:{},queryCommandAvailable:function(_39){
-var ca=this._qcaCache[_39];
-if(ca!==undefined){
-return ca;
-}
-return (this._qcaCache[_39]=this._queryCommandAvailable(_39));
-},_queryCommandAvailable:function(_3a){
-var ie=1;
-var _3b=1<<1;
-var _3c=1<<2;
-var _3d=1<<3;
-var _3e=1<<4;
-function _3f(_40){
-return {ie:Boolean(_40&ie),mozilla:Boolean(_40&_3b),webkit:Boolean(_40&_3c),webkit420:Boolean(_40&_3e),opera:Boolean(_40&_3d)};
-};
-var _41=null;
-switch(_3a.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":
-_41=_3f(_3b|ie|_3c|_3d);
-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":
-_41=_3f(_3b|ie|_3d|_3e);
-break;
-case "blockdirltr":
-case "blockdirrtl":
-case "dirltr":
-case "dirrtl":
-case "inlinedirltr":
-case "inlinedirrtl":
-_41=_3f(ie);
-break;
-case "cut":
-case "copy":
-case "paste":
-_41=_3f(ie|_3b|_3e);
-break;
-case "inserttable":
-_41=_3f(_3b|ie);
-break;
-case "insertcell":
-case "insertcol":
-case "insertrow":
-case "deletecells":
-case "deletecols":
-case "deleterows":
-case "mergecells":
-case "splitcell":
-_41=_3f(ie|_3b);
-break;
-default:
-return false;
-}
-return (dojo.isIE&&_41.ie)||(dojo.isMoz&&_41.mozilla)||(dojo.isWebKit&&_41.webkit)||(dojo.isWebKit>420&&_41.webkit420)||(dojo.isOpera&&_41.opera);
-},execCommand:function(_42,_43){
-var _44;
-this.focus();
-_42=this._normalizeCommand(_42,_43);
-if(_43!==undefined){
-if(_42=="heading"){
-throw new Error("unimplemented");
-}else{
-if((_42=="formatblock")&&dojo.isIE){
-_43="<"+_43+">";
-}
-}
-}
-var _45="_"+_42+"Impl";
-if(this[_45]){
-_44=this[_45](_43);
-}else{
-_43=arguments.length>1?_43:null;
-if(_43||_42!="createlink"){
-_44=this.document.execCommand(_42,false,_43);
-}
-}
-this.onDisplayChanged();
-return _44;
-},queryCommandEnabled:function(_46){
-if(this.disabled||!this._disabledOK){
-return false;
-}
-_46=this._normalizeCommand(_46);
-if(dojo.isMoz||dojo.isWebKit){
-if(_46=="unlink"){
-return this._sCall("hasAncestorElement",["a"]);
-}else{
-if(_46=="inserttable"){
-return true;
-}
-}
-}
-if(dojo.isWebKit){
-if(_46=="copy"){
-_46="cut";
-}else{
-if(_46=="paste"){
-return true;
-}
-}
-}
-var _47=dojo.isIE?this.document.selection.createRange():this.document;
-try{
-return _47.queryCommandEnabled(_46);
-}
-catch(e){
-return false;
-}
-},queryCommandState:function(_48){
-if(this.disabled||!this._disabledOK){
-return false;
-}
-_48=this._normalizeCommand(_48);
-try{
-return this.document.queryCommandState(_48);
-}
-catch(e){
-return false;
-}
-},queryCommandValue:function(_49){
-if(this.disabled||!this._disabledOK){
-return false;
-}
-var r;
-_49=this._normalizeCommand(_49);
-if(dojo.isIE&&_49=="formatblock"){
-r=this._native2LocalFormatNames[this.document.queryCommandValue(_49)];
-}else{
-if(dojo.isMoz&&_49==="hilitecolor"){
-var _4a;
-try{
-_4a=this.document.queryCommandValue("styleWithCSS");
-}
-catch(e){
-_4a=false;
-}
-this.document.execCommand("styleWithCSS",false,true);
-r=this.document.queryCommandValue(_49);
-this.document.execCommand("styleWithCSS",false,_4a);
-}else{
-r=this.document.queryCommandValue(_49);
-}
-}
-return r;
-},_sCall:function(_4b,_4c){
-return dojo.withGlobal(this.window,_4b,dijit._editor.selection,_4c);
-},placeCursorAtStart:function(){
-this.focus();
-var _4d=false;
-if(dojo.isMoz){
-var _4e=this.editNode.firstChild;
-while(_4e){
-if(_4e.nodeType==3){
-if(_4e.nodeValue.replace(/^\s+|\s+$/g,"").length>0){
-_4d=true;
-this._sCall("selectElement",[_4e]);
-break;
-}
-}else{
-if(_4e.nodeType==1){
-_4d=true;
-var tg=_4e.tagName?_4e.tagName.toLowerCase():"";
-if(/br|input|img|base|meta|area|basefont|hr|link/.test(tg)){
-this._sCall("selectElement",[_4e]);
-}else{
-this._sCall("selectElementChildren",[_4e]);
-}
-break;
-}
-}
-_4e=_4e.nextSibling;
-}
-}else{
-_4d=true;
-this._sCall("selectElementChildren",[this.editNode]);
-}
-if(_4d){
-this._sCall("collapse",[true]);
-}
-},placeCursorAtEnd:function(){
-this.focus();
-var _4f=false;
-if(dojo.isMoz){
-var _50=this.editNode.lastChild;
-while(_50){
-if(_50.nodeType==3){
-if(_50.nodeValue.replace(/^\s+|\s+$/g,"").length>0){
-_4f=true;
-this._sCall("selectElement",[_50]);
-break;
-}
-}else{
-if(_50.nodeType==1){
-_4f=true;
-if(_50.lastChild){
-this._sCall("selectElement",[_50.lastChild]);
-}else{
-this._sCall("selectElement",[_50]);
-}
-break;
-}
-}
-_50=_50.previousSibling;
-}
-}else{
-_4f=true;
-this._sCall("selectElementChildren",[this.editNode]);
-}
-if(_4f){
-this._sCall("collapse",[false]);
-}
-},getValue:function(_51){
-if(this.textarea){
-if(this.isClosed||!this.isLoaded){
-return this.textarea.value;
-}
-}
-return this._postFilterContent(null,_51);
-},_getValueAttr:function(){
-return this.getValue(true);
-},setValue:function(_52){
-if(!this.isLoaded){
-this.onLoadDeferred.addCallback(dojo.hitch(this,function(){
-this.setValue(_52);
-}));
-return;
-}
-this._cursorToStart=true;
-if(this.textarea&&(this.isClosed||!this.isLoaded)){
-this.textarea.value=_52;
-}else{
-_52=this._preFilterContent(_52);
-var _53=this.isClosed?this.domNode:this.editNode;
-if(!_52&&dojo.isWebKit){
-_52="&nbsp;";
-}
-_53.innerHTML=_52;
-this._preDomFilterContent(_53);
-}
-this.onDisplayChanged();
-},replaceValue:function(_54){
-if(this.isClosed){
-this.setValue(_54);
-}else{
-if(this.window&&this.window.getSelection&&!dojo.isMoz){
-this.setValue(_54);
-}else{
-if(this.window&&this.window.getSelection){
-_54=this._preFilterContent(_54);
-this.execCommand("selectall");
-if(!_54){
-this._cursorToStart=true;
-_54="&nbsp;";
-}
-this.execCommand("inserthtml",_54);
-this._preDomFilterContent(this.editNode);
-}else{
-if(this.document&&this.document.selection){
-this.setValue(_54);
-}
-}
-}
-}
-},_preFilterContent:function(_55){
-var ec=_55;
-dojo.forEach(this.contentPreFilters,function(ef){
-if(ef){
-ec=ef(ec);
-}
-});
-return ec;
-},_preDomFilterContent:function(dom){
-dom=dom||this.editNode;
-dojo.forEach(this.contentDomPreFilters,function(ef){
-if(ef&&dojo.isFunction(ef)){
-ef(dom);
-}
-},this);
-},_postFilterContent:function(dom,_56){
-var ec;
-if(!dojo.isString(dom)){
-dom=dom||this.editNode;
-if(this.contentDomPostFilters.length){
-if(_56){
-dom=dojo.clone(dom);
-}
-dojo.forEach(this.contentDomPostFilters,function(ef){
-dom=ef(dom);
-});
-}
-ec=dijit._editor.getChildrenHtml(dom);
-}else{
-ec=dom;
-}
-if(!dojo.trim(ec.replace(/^\xA0\xA0*/,"").replace(/\xA0\xA0*$/,"")).length){
-ec="";
+
+
+// used to restore content when user leaves this page then comes back
+// but do not try doing dojo.doc.write if we are using xd loading.
+// dojo.doc.write will only work if RichText.js is included in the dojo.js
+// file. If it is included in dojo.js and you want to allow rich text saving
+// for back/forward actions, then set dojo.config.allowXdRichTextSave = true.
+if(!dojo.config["useXDomain"] || dojo.config["allowXdRichTextSave"]){
+ if(dojo._postLoad){
+ (function(){
+ var savetextarea = dojo.doc.createElement('textarea');
+ savetextarea.id = dijit._scopeName + "._editor.RichText.value";
+ dojo.style(savetextarea, {
+ display:'none',
+ position:'absolute',
+ top:"-100px",
+ height:"3px",
+ width:"3px"
+ });
+ dojo.body().appendChild(savetextarea);
+ })();
+ }else{
+ //dojo.body() is not available before onLoad is fired
+ try{
+ dojo.doc.write('<textarea id="' + dijit._scopeName + '._editor.RichText.value" ' +
+ 'style="display:none;position:absolute;top:-100px;left:-100px;height:3px;width:3px;overflow:hidden;"></textarea>');
+ }catch(e){ }
+ }
}
-dojo.forEach(this.contentPostFilters,function(ef){
-ec=ef(ec);
+
+dojo.declare("dijit._editor.RichText", [dijit._Widget, dijit._CssStateMixin], {
+ constructor: function(params){
+ // summary:
+ // dijit._editor.RichText is the core of dijit.Editor, which provides basic
+ // WYSIWYG editing features.
+ //
+ // description:
+ // dijit._editor.RichText is the core of dijit.Editor, which provides basic
+ // WYSIWYG editing features. It also encapsulates the differences
+ // of different js engines for various browsers. Do not use this widget
+ // with an HTML &lt;TEXTAREA&gt; tag, since the browser unescapes XML escape characters,
+ // like &lt;. This can have unexpected behavior and lead to security issues
+ // such as scripting attacks.
+ //
+ // tags:
+ // private
+
+ // contentPreFilters: Function(String)[]
+ // Pre content filter function register array.
+ // these filters will be executed before the actual
+ // editing area gets the html content.
+ this.contentPreFilters = [];
+
+ // contentPostFilters: Function(String)[]
+ // post content filter function register array.
+ // These will be used on the resulting html
+ // from contentDomPostFilters. The resulting
+ // content is the final html (returned by getValue()).
+ this.contentPostFilters = [];
+
+ // contentDomPreFilters: Function(DomNode)[]
+ // Pre content dom filter function register array.
+ // These filters are applied after the result from
+ // contentPreFilters are set to the editing area.
+ this.contentDomPreFilters = [];
+
+ // contentDomPostFilters: Function(DomNode)[]
+ // Post content dom filter function register array.
+ // These filters are executed on the editing area dom.
+ // The result from these will be passed to contentPostFilters.
+ this.contentDomPostFilters = [];
+
+ // editingAreaStyleSheets: dojo._URL[]
+ // array to store all the stylesheets applied to the editing area
+ this.editingAreaStyleSheets = [];
+
+ // Make a copy of this.events before we start writing into it, otherwise we
+ // will modify the prototype which leads to bad things on pages w/multiple editors
+ this.events = [].concat(this.events);
+
+ this._keyHandlers = {};
+
+ if(params && dojo.isString(params.value)){
+ this.value = params.value;
+ }
+
+ this.onLoadDeferred = new dojo.Deferred();
+ },
+
+ baseClass: "dijitEditor",
+
+ // inheritWidth: Boolean
+ // whether to inherit the parent's width or simply use 100%
+ inheritWidth: false,
+
+ // focusOnLoad: [deprecated] Boolean
+ // Focus into this widget when the page is loaded
+ focusOnLoad: false,
+
+ // name: String?
+ // Specifies the name of a (hidden) <textarea> node on the page that's used to save
+ // the editor content on page leave. Used to restore editor contents after navigating
+ // to a new page and then hitting the back button.
+ name: "",
+
+ // styleSheets: [const] String
+ // semicolon (";") separated list of css files for the editing area
+ styleSheets: "",
+
+ // height: String
+ // Set height to fix the editor at a specific height, with scrolling.
+ // By default, this is 300px. If you want to have the editor always
+ // resizes to accommodate the content, use AlwaysShowToolbar plugin
+ // and set height="". If this editor is used within a layout widget,
+ // set height="100%".
+ height: "300px",
+
+ // minHeight: String
+ // The minimum height that the editor should have.
+ minHeight: "1em",
+
+ // isClosed: [private] Boolean
+ isClosed: true,
+
+ // isLoaded: [private] Boolean
+ isLoaded: false,
+
+ // _SEPARATOR: [private] String
+ // Used to concat contents from multiple editors into a single string,
+ // so they can be saved into a single <textarea> node. See "name" attribute.
+ _SEPARATOR: "@@**%%__RICHTEXTBOUNDRY__%%**@@",
+
+ // _NAME_CONTENT_SEP: [private] String
+ // USed to separate name from content. Just a colon isn't safe.
+ _NAME_CONTENT_SEP: "@@**%%:%%**@@",
+
+ // onLoadDeferred: [readonly] dojo.Deferred
+ // Deferred which is fired when the editor finishes loading.
+ // Call myEditor.onLoadDeferred.then(callback) it to be informed
+ // when the rich-text area initialization is finalized.
+ onLoadDeferred: null,
+
+ // isTabIndent: Boolean
+ // Make tab key and shift-tab indent and outdent rather than navigating.
+ // Caution: sing this makes web pages inaccessible to users unable to use a mouse.
+ isTabIndent: false,
+
+ // disableSpellCheck: [const] Boolean
+ // When true, disables the browser's native spell checking, if supported.
+ // Works only in Firefox.
+ disableSpellCheck: false,
+
+ postCreate: function(){
+ if("textarea" == this.domNode.tagName.toLowerCase()){
+ console.warn("RichText should not be used with the TEXTAREA tag. See dijit._editor.RichText docs.");
+ }
+
+ // Push in the builtin filters now, making them the first executed, but not over-riding anything
+ // users passed in. See: #6062
+ this.contentPreFilters = [dojo.hitch(this, "_preFixUrlAttributes")].concat(this.contentPreFilters);
+ if(dojo.isMoz){
+ this.contentPreFilters = [this._normalizeFontStyle].concat(this.contentPreFilters);
+ this.contentPostFilters = [this._removeMozBogus].concat(this.contentPostFilters);
+ }
+ if(dojo.isWebKit){
+ // Try to clean up WebKit bogus artifacts. The inserted classes
+ // made by WebKit sometimes messes things up.
+ this.contentPreFilters = [this._removeWebkitBogus].concat(this.contentPreFilters);
+ this.contentPostFilters = [this._removeWebkitBogus].concat(this.contentPostFilters);
+ }
+ if(dojo.isIE){
+ // IE generates <strong> and <em> but we want to normalize to <b> and <i>
+ this.contentPostFilters = [this._normalizeFontStyle].concat(this.contentPostFilters);
+ }
+ this.inherited(arguments);
+
+ dojo.publish(dijit._scopeName + "._editor.RichText::init", [this]);
+ this.open();
+ this.setupDefaultShortcuts();
+ },
+
+ setupDefaultShortcuts: function(){
+ // summary:
+ // Add some default key handlers
+ // description:
+ // Overwrite this to setup your own handlers. The default
+ // implementation does not use Editor commands, but directly
+ // executes the builtin commands within the underlying browser
+ // support.
+ // tags:
+ // protected
+ var exec = dojo.hitch(this, function(cmd, arg){
+ return function(){
+ return !this.execCommand(cmd,arg);
+ };
+ });
+
+ var ctrlKeyHandlers = {
+ b: exec("bold"),
+ i: exec("italic"),
+ u: exec("underline"),
+ a: exec("selectall"),
+ s: function(){ this.save(true); },
+ m: function(){ this.isTabIndent = !this.isTabIndent; },
+
+ "1": exec("formatblock", "h1"),
+ "2": exec("formatblock", "h2"),
+ "3": exec("formatblock", "h3"),
+ "4": exec("formatblock", "h4"),
+
+ "\\": exec("insertunorderedlist")
+ };
+
+ if(!dojo.isIE){
+ ctrlKeyHandlers.Z = exec("redo"); //FIXME: undo?
+ }
+
+ for(var key in ctrlKeyHandlers){
+ this.addKeyHandler(key, true, false, ctrlKeyHandlers[key]);
+ }
+ },
+
+ // events: [private] String[]
+ // events which should be connected to the underlying editing area
+ events: ["onKeyPress", "onKeyDown", "onKeyUp"], // onClick handled specially
+
+ // captureEvents: [deprecated] String[]
+ // Events which should be connected to the underlying editing
+ // area, events in this array will be addListener with
+ // capture=true.
+ // TODO: looking at the code I don't see any distinction between events and captureEvents,
+ // so get rid of this for 2.0 if not sooner
+ captureEvents: [],
+
+ _editorCommandsLocalized: false,
+ _localizeEditorCommands: function(){
+ // summary:
+ // When IE is running in a non-English locale, the API actually changes,
+ // so that we have to say (for example) danraku instead of p (for paragraph).
+ // Handle that here.
+ // tags:
+ // private
+ if(dijit._editor._editorCommandsLocalized){
+ // Use the already generate cache of mappings.
+ this._local2NativeFormatNames = dijit._editor._local2NativeFormatNames;
+ this._native2LocalFormatNames = dijit._editor._native2LocalFormatNames;
+ return;
+ }
+ dijit._editor._editorCommandsLocalized = true;
+ dijit._editor._local2NativeFormatNames = {};
+ dijit._editor._native2LocalFormatNames = {};
+ this._local2NativeFormatNames = dijit._editor._local2NativeFormatNames;
+ this._native2LocalFormatNames = dijit._editor._native2LocalFormatNames;
+ //in IE, names for blockformat is locale dependent, so we cache the values here
+
+ //put p after div, so if IE returns Normal, we show it as paragraph
+ //We can distinguish p and div if IE returns Normal, however, in order to detect that,
+ //we have to call this.document.selection.createRange().parentElement() or such, which
+ //could slow things down. Leave it as it is for now
+ var formats = ['div', 'p', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'ul', 'address'];
+ var localhtml = "", format, i=0;
+ while((format=formats[i++])){
+ //append a <br> after each element to separate the elements more reliably
+ if(format.charAt(1) !== 'l'){
+ localhtml += "<"+format+"><span>content</span></"+format+"><br/>";
+ }else{
+ localhtml += "<"+format+"><li>content</li></"+format+"><br/>";
+ }
+ }
+ // queryCommandValue returns empty if we hide editNode, so move it out of screen temporary
+ // Also, IE9 does weird stuff unless we do it inside the editor iframe.
+ var style = { position: "absolute", top: "0px", zIndex: 10, opacity: 0.01 };
+ var div = dojo.create('div', {style: style, innerHTML: localhtml});
+ dojo.body().appendChild(div);
+
+ // IE9 has a timing issue with doing this right after setting
+ // the inner HTML, so put a delay in.
+ var inject = dojo.hitch(this, function(){
+ var node = div.firstChild;
+ while(node){
+ try{
+ dijit._editor.selection.selectElement(node.firstChild);
+ var nativename = node.tagName.toLowerCase();
+ this._local2NativeFormatNames[nativename] = document.queryCommandValue("formatblock");
+ this._native2LocalFormatNames[this._local2NativeFormatNames[nativename]] = nativename;
+ node = node.nextSibling.nextSibling;
+ //console.log("Mapped: ", nativename, " to: ", this._local2NativeFormatNames[nativename]);
+ }catch(e) { /*Sqelch the occasional IE9 error */ }
+ }
+ div.parentNode.removeChild(div);
+ div.innerHTML = "";
+ });
+ setTimeout(inject, 0);
+ },
+
+ open: function(/*DomNode?*/ element){
+ // summary:
+ // Transforms the node referenced in this.domNode into a rich text editing
+ // node.
+ // description:
+ // Sets up the editing area asynchronously. This will result in
+ // the creation and replacement with an iframe.
+ // tags:
+ // private
+
+ if(!this.onLoadDeferred || this.onLoadDeferred.fired >= 0){
+ this.onLoadDeferred = new dojo.Deferred();
+ }
+
+ if(!this.isClosed){ this.close(); }
+ dojo.publish(dijit._scopeName + "._editor.RichText::open", [ this ]);
+
+ if(arguments.length == 1 && element.nodeName){ // else unchanged
+ this.domNode = element;
+ }
+
+ var dn = this.domNode;
+
+ // "html" will hold the innerHTML of the srcNodeRef and will be used to
+ // initialize the editor.
+ var html;
+
+ if(dojo.isString(this.value)){
+ // Allow setting the editor content programmatically instead of
+ // relying on the initial content being contained within the target
+ // domNode.
+ html = this.value;
+ delete this.value;
+ dn.innerHTML = "";
+ }else if(dn.nodeName && dn.nodeName.toLowerCase() == "textarea"){
+ // if we were created from a textarea, then we need to create a
+ // new editing harness node.
+ var ta = (this.textarea = dn);
+ this.name = ta.name;
+ html = ta.value;
+ dn = this.domNode = dojo.doc.createElement("div");
+ dn.setAttribute('widgetId', this.id);
+ ta.removeAttribute('widgetId');
+ dn.cssText = ta.cssText;
+ dn.className += " " + ta.className;
+ dojo.place(dn, ta, "before");
+ var tmpFunc = dojo.hitch(this, function(){
+ //some browsers refuse to submit display=none textarea, so
+ //move the textarea off screen instead
+ dojo.style(ta, {
+ display: "block",
+ position: "absolute",
+ top: "-1000px"
+ });
+
+ if(dojo.isIE){ //nasty IE bug: abnormal formatting if overflow is not hidden
+ var s = ta.style;
+ this.__overflow = s.overflow;
+ s.overflow = "hidden";
+ }
+ });
+ if(dojo.isIE){
+ setTimeout(tmpFunc, 10);
+ }else{
+ tmpFunc();
+ }
+
+ if(ta.form){
+ var resetValue = ta.value;
+ this.reset = function(){
+ var current = this.getValue();
+ if(current != resetValue){
+ this.replaceValue(resetValue);
+ }
+ };
+ dojo.connect(ta.form, "onsubmit", this, function(){
+ // Copy value to the <textarea> so it gets submitted along with form.
+ // FIXME: should we be calling close() here instead?
+ dojo.attr(ta, 'disabled', this.disabled); // don't submit the value if disabled
+ ta.value = this.getValue();
+ });
+ }
+ }else{
+ html = dijit._editor.getChildrenHtml(dn);
+ dn.innerHTML = "";
+ }
+
+ var content = dojo.contentBox(dn);
+ this._oldHeight = content.h;
+ this._oldWidth = content.w;
+
+ this.value = html;
+
+ // If we're a list item we have to put in a blank line to force the
+ // bullet to nicely align at the top of text
+ if(dn.nodeName && dn.nodeName == "LI"){
+ dn.innerHTML = " <br>";
+ }
+
+ // Construct the editor div structure.
+ this.header = dn.ownerDocument.createElement("div");
+ dn.appendChild(this.header);
+ this.editingArea = dn.ownerDocument.createElement("div");
+ dn.appendChild(this.editingArea);
+ this.footer = dn.ownerDocument.createElement("div");
+ dn.appendChild(this.footer);
+
+ if(!this.name){
+ this.name = this.id + "_AUTOGEN";
+ }
+
+ // User has pressed back/forward button so we lost the text in the editor, but it's saved
+ // in a hidden <textarea> (which contains the data for all the editors on this page),
+ // so get editor value from there
+ if(this.name !== "" && (!dojo.config["useXDomain"] || dojo.config["allowXdRichTextSave"])){
+ var saveTextarea = dojo.byId(dijit._scopeName + "._editor.RichText.value");
+ if(saveTextarea && saveTextarea.value !== ""){
+ var datas = saveTextarea.value.split(this._SEPARATOR), i=0, dat;
+ while((dat=datas[i++])){
+ var data = dat.split(this._NAME_CONTENT_SEP);
+ if(data[0] == this.name){
+ html = data[1];
+ datas = datas.splice(i, 1);
+ saveTextarea.value = datas.join(this._SEPARATOR);
+ break;
+ }
+ }
+ }
+
+ if(!dijit._editor._globalSaveHandler){
+ dijit._editor._globalSaveHandler = {};
+ dojo.addOnUnload(function() {
+ var id;
+ for(id in dijit._editor._globalSaveHandler){
+ var f = dijit._editor._globalSaveHandler[id];
+ if(dojo.isFunction(f)){
+ f();
+ }
+ }
+ });
+ }
+ dijit._editor._globalSaveHandler[this.id] = dojo.hitch(this, "_saveContent");
+ }
+
+ this.isClosed = false;
+
+ var ifr = (this.editorObject = this.iframe = dojo.doc.createElement('iframe'));
+ ifr.id = this.id+"_iframe";
+ this._iframeSrc = this._getIframeDocTxt();
+ ifr.style.border = "none";
+ ifr.style.width = "100%";
+ if(this._layoutMode){
+ // iframe should be 100% height, thus getting it's height from surrounding
+ // <div> (which has the correct height set by Editor)
+ ifr.style.height = "100%";
+ }else{
+ if(dojo.isIE >= 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 = dojo.hitch( this, function(win){
+ this.window = win;
+ this.document = this.window.document;
+
+ if(dojo.isIE){
+ this._localizeEditorCommands();
+ }
+
+ // Do final setup and set initial contents of editor
+ this.onLoad(html);
+ });
+
+ // Set the iframe's initial (blank) content.
+ var s = 'javascript:parent.' + dijit._scopeName + '.byId("'+this.id+'")._iframeSrc';
+ ifr.setAttribute('src', s);
+ this.editingArea.appendChild(ifr);
+
+ if(dojo.isSafari <= 4){
+ var src = ifr.getAttribute("src");
+ if(!src || src.indexOf("javascript") == -1){
+ // Safari 4 and earlier sometimes act oddly
+ // So we have to set it again.
+ setTimeout(function(){ifr.setAttribute('src', s);},0);
+ }
+ }
+
+ // TODO: this is a guess at the default line-height, kinda works
+ if(dn.nodeName == "LI"){
+ dn.lastChild.style.marginTop = "-1.2em";
+ }
+
+ dojo.addClass(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, <html><head>...</head><body/></html>).
+ // Editor content (if not blank) should be added afterwards.
+ // tags:
+ // private
+ var _cs = dojo.getComputedStyle(this.domNode);
+
+ // The contents inside of <body>. The real contents are set later via a call to setValue().
+ var html = "";
+ var setBodyId = true;
+ if(dojo.isIE || dojo.isWebKit || (!this.height && !dojo.isMoz)){
+ // In auto-expand mode, need a wrapper div for AlwaysShowToolbar plugin to correctly
+ // expand/contract the editor as the content changes.
+ html = "<div id='dijitEditorBody'></div>";
+ setBodyId = false;
+ }else if(dojo.isMoz){
+ // workaround bug where can't select then delete text (until user types something
+ // into the editor)... and/or issue where typing doesn't erase selected text
+ this._cursorToStart = true;
+ html = "&nbsp;";
+ }
+
+ var font = [ _cs.fontWeight, _cs.fontSize, _cs.fontFamily ].join(" ");
+
+ // line height is tricky - applying a units value will mess things up.
+ // if we can't get a non-units value, bail out.
+ var lineHeight = _cs.lineHeight;
+ if(lineHeight.indexOf("px") >= 0){
+ lineHeight = parseFloat(lineHeight)/parseFloat(_cs.fontSize);
+ // console.debug(lineHeight);
+ }else if(lineHeight.indexOf("em")>=0){
+ lineHeight = parseFloat(lineHeight);
+ }else{
+ // If we can't get a non-units value, just default
+ // it to the CSS spec default of 'normal'. Seems to
+ // work better, esp on IE, than '1.0'
+ lineHeight = "normal";
+ }
+ var userStyle = "";
+ var self = this;
+ this.style.replace(/(^|;)\s*(line-|font-?)[^;]+/ig, function(match){
+ match = match.replace(/^;/ig,"") + ';';
+ var s = match.split(":")[0];
+ if(s){
+ s = dojo.trim(s);
+ s = s.toLowerCase();
+ var i;
+ var sC = "";
+ for(i = 0; i < s.length; i++){
+ var c = s.charAt(i);
+ switch(c){
+ case "-":
+ i++;
+ c = s.charAt(i).toUpperCase();
+ default:
+ sC += c;
+ }
+ }
+ dojo.style(self.domNode, sC, "");
+ }
+ userStyle += match + ';';
+ });
+
+
+ // need to find any associated label element and update iframe document title
+ var label=dojo.query('label[for="'+this.id+'"]');
+
+ return [
+ this.isLeftToRight() ? "<html>\n<head>\n" : "<html dir='rtl'>\n<head>\n",
+ (dojo.isMoz && label.length ? "<title>" + label[0].innerHTML + "</title>\n" : ""),
+ "<meta http-equiv='Content-Type' content='text/html'>\n",
+ "<style>\n",
+ "\tbody,html {\n",
+ "\t\tbackground:transparent;\n",
+ "\t\tpadding: 1px 0 0 0;\n",
+ "\t\tmargin: -1px 0 0 0;\n", // remove extraneous vertical scrollbar on safari and firefox
+
+ // Set the html/body sizing. Webkit always needs this, other browsers
+ // only set it when height is defined (not auto-expanding), otherwise
+ // scrollers do not appear.
+ ((dojo.isWebKit)?"\t\twidth: 100%;\n":""),
+ ((dojo.isWebKit)?"\t\theight: 100%;\n":""),
+ "\t}\n",
+
+ // TODO: left positioning will cause contents to disappear out of view
+ // if it gets too wide for the visible area
+ "\tbody{\n",
+ "\t\ttop:0px;\n",
+ "\t\tleft:0px;\n",
+ "\t\tright:0px;\n",
+ "\t\tfont:", font, ";\n",
+ ((this.height||dojo.isOpera) ? "" : "\t\tposition: fixed;\n"),
+ // FIXME: IE 6 won't understand min-height?
+ "\t\tmin-height:", this.minHeight, ";\n",
+ "\t\tline-height:", lineHeight,";\n",
+ "\t}\n",
+ "\tp{ margin: 1em 0; }\n",
+
+ // Determine how scrollers should be applied. In autoexpand mode (height = "") no scrollers on y at all.
+ // But in fixed height mode we want both x/y scrollers. Also, if it's using wrapping div and in auto-expand
+ // (Mainly IE) we need to kill the y scroller on body and html.
+ (!setBodyId && !this.height ? "\tbody,html {overflow-y: hidden;}\n" : ""),
+ "\t#dijitEditorBody{overflow-x: auto; overflow-y:" + (this.height ? "auto;" : "hidden;") + " outline: 0px;}\n",
+ "\tli > ul:-moz-first-node, li > ol:-moz-first-node{ padding-top: 1.2em; }\n",
+ // Can't set min-height in IE9, it puts layout on li, which puts move/resize handles.
+ (!dojo.isIE ? "\tli{ min-height:1.2em; }\n" : ""),
+ "</style>\n",
+ this._applyEditingAreaStyleSheets(),"\n",
+ "</head>\n<body ",
+ (setBodyId?"id='dijitEditorBody' ":""),
+ "onload='frameElement._loadFunc(window,document)' style='"+userStyle+"'>", html, "</body>\n</html>"
+ ].join(""); // String
+ },
+
+ _applyEditingAreaStyleSheets: function(){
+ // summary:
+ // apply the specified css files in styleSheets
+ // tags:
+ // private
+ var files = [];
+ if(this.styleSheets){
+ files = this.styleSheets.split(';');
+ this.styleSheets = '';
+ }
+
+ //empty this.editingAreaStyleSheets here, as it will be filled in addStyleSheet
+ files = files.concat(this.editingAreaStyleSheets);
+ this.editingAreaStyleSheets = [];
+
+ var text='', i=0, url;
+ while((url=files[i++])){
+ var abstring = (new dojo._Url(dojo.global.location, url)).toString();
+ this.editingAreaStyleSheets.push(abstring);
+ text += '<link rel="stylesheet" type="text/css" href="'+abstring+'"/>';
+ }
+ return text;
+ },
+
+ addStyleSheet: function(/*dojo._Url*/ uri){
+ // summary:
+ // add an external stylesheet for the editing area
+ // uri:
+ // A dojo.uri.Uri pointing to the url of the external css file
+ var url=uri.toString();
+
+ //if uri is relative, then convert it to absolute so that it can be resolved correctly in iframe
+ if(url.charAt(0) == '.' || (url.charAt(0) != '/' && !uri.host)){
+ url = (new dojo._Url(dojo.global.location, url)).toString();
+ }
+
+ if(dojo.indexOf(this.editingAreaStyleSheets, url) > -1){
+// console.debug("dijit._editor.RichText.addStyleSheet: Style sheet "+url+" is already applied");
+ return;
+ }
+
+ this.editingAreaStyleSheets.push(url);
+ this.onLoadDeferred.addCallback(dojo.hitch(this, function(){
+ if(this.document.createStyleSheet){ //IE
+ this.document.createStyleSheet(url);
+ }else{ //other browser
+ var head = this.document.getElementsByTagName("head")[0];
+ var stylesheet = this.document.createElement("link");
+ stylesheet.rel="stylesheet";
+ stylesheet.type="text/css";
+ stylesheet.href=url;
+ head.appendChild(stylesheet);
+ }
+ }));
+ },
+
+ removeStyleSheet: function(/*dojo._Url*/ uri){
+ // summary:
+ // remove an external stylesheet for the editing area
+ var url=uri.toString();
+ //if uri is relative, then convert it to absolute so that it can be resolved correctly in iframe
+ if(url.charAt(0) == '.' || (url.charAt(0) != '/' && !uri.host)){
+ url = (new dojo._Url(dojo.global.location, url)).toString();
+ }
+ var index = dojo.indexOf(this.editingAreaStyleSheets, url);
+ if(index == -1){
+// console.debug("dijit._editor.RichText.removeStyleSheet: Style sheet "+url+" has not been applied");
+ return;
+ }
+ delete this.editingAreaStyleSheets[index];
+ dojo.withGlobal(this.window,'query', dojo, ['link:[href="'+url+'"]']).orphan();
+ },
+
+ // disabled: Boolean
+ // The editor is disabled; the text cannot be changed.
+ disabled: false,
+
+ _mozSettingProps: {'styleWithCSS':false},
+ _setDisabledAttr: function(/*Boolean*/ value){
+ value = !!value;
+ this._set("disabled", value);
+ if(!this.isLoaded){ return; } // this method requires init to be complete
+ if(dojo.isIE || dojo.isWebKit || dojo.isOpera){
+ var preventIEfocus = dojo.isIE && (this.isLoaded || !this.focusOnLoad);
+ if(preventIEfocus){ this.editNode.unselectable = "on"; }
+ this.editNode.contentEditable = !value;
+ if(preventIEfocus){
+ var _this = this;
+ setTimeout(function(){ _this.editNode.unselectable = "off"; }, 0);
+ }
+ }else{ //moz
+ try{
+ this.document.designMode=(value?'off':'on');
+ }catch(e){ return; } // ! _disabledOK
+ if(!value && this._mozSettingProps){
+ var ps = this._mozSettingProps;
+ for(var n in ps){
+ if(ps.hasOwnProperty(n)){
+ try{
+ this.document.execCommand(n,false,ps[n]);
+ }catch(e2){}
+ }
+ }
+ }
+// this.document.execCommand('contentReadOnly', false, value);
+// if(value){
+// this.blur(); //to remove the blinking caret
+// }
+ }
+ this._disabledOK = true;
+ },
+
+/* Event handlers
+ *****************/
+
+ onLoad: function(/*String*/ html){
+ // summary:
+ // Handler after the iframe finishes loading.
+ // html: String
+ // Editor contents should be set to this value
+ // tags:
+ // protected
+
+ // TODO: rename this to _onLoad, make empty public onLoad() method, deprecate/make protected onLoadDeferred handler?
+
+ if(!this.window.__registeredWindow){
+ this.window.__registeredWindow = true;
+ this._iframeRegHandle = dijit.registerIframe(this.iframe);
+ }
+ if(!dojo.isIE && !dojo.isWebKit && (this.height || dojo.isMoz)){
+ this.editNode=this.document.body;
+ }else{
+ // there's a wrapper div around the content, see _getIframeDocTxt().
+ this.editNode=this.document.body.firstChild;
+ var _this = this;
+ if(dojo.isIE){ // #4996 IE wants to focus the BODY tag
+ this.tabStop = dojo.create('div', { tabIndex: -1 }, this.editingArea);
+ this.iframe.onfocus = function(){ _this.editNode.setActive(); };
+ }
+ }
+ this.focusNode = this.editNode; // for InlineEditBox
+
+
+ var events = this.events.concat(this.captureEvents);
+ var ap = this.iframe ? this.document : this.editNode;
+ dojo.forEach(events, function(item){
+ this.connect(ap, item.toLowerCase(), item);
+ }, this);
+
+ this.connect(ap, "onmouseup", "onClick"); // mouseup in the margin does not generate an onclick event
+
+ if(dojo.isIE){ // IE contentEditable
+ this.connect(this.document, "onmousedown", "_onIEMouseDown"); // #4996 fix focus
+
+ // give the node Layout on IE
+ // TODO: this may no longer be needed, since we've reverted IE to using an iframe,
+ // not contentEditable. Removing it would also probably remove the need for creating
+ // the extra <div> 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(dojo.isWebKit){
+ //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.
+ setTimeout(dojo.hitch(this, "placeCursorAtEnd"), 0);
+ }
+ });
+ }
+
+ if(dojo.isIE){
+ // 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 = dojo.hitch(this, function(){
+ this.setValue(html);
+ if(this.onLoadDeferred){
+ this.onLoadDeferred.callback(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
+ dojo.addOnLoad(dojo.hitch(this, function(){ setTimeout(dojo.hitch(this, "focus"), this.updateInterval); }));
+ }
+ // Save off the initial content now
+ this.value = this.getValue(true);
+ });
+ if(this.setValueDeferred){
+ this.setValueDeferred.addCallback(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 === dojo.keys.TAB && this.isTabIndent ){
+ dojo.stopEvent(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 "&nbsp;" 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(dojo.isIE){
+ if(e.keyCode == dojo.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 === dojo.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
+ dojo.stopEvent(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);
+ }
+ }
+ return true;
+ },
+
+ onKeyUp: function(e){
+ // summary:
+ // Handler for onkeyup event
+ // tags:
+ // callback
+ return;
+ },
+
+ setDisabled: function(/*Boolean*/ disabled){
+ // summary:
+ // Deprecated, use set('disabled', ...) instead.
+ // tags:
+ // deprecated
+ dojo.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){
+ dojo.attr(this.document.body, "spellcheck", !disabled);
+ }else{
+ // try again after the editor is finished loading
+ this.onLoadDeferred.addCallback(dojo.hitch(this, function(){
+ dojo.attr(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){
+ dojo.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 = dojo.hitch(this, "onKeyPressed");
+ }
+ setTimeout(this._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(!dojo.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(/*Event*/ e){
+ // 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(!dojo.isIE && this.window.document.documentElement && this.window.document.documentElement.focus){
+ this.window.document.documentElement.focus();
+ }else if(dojo.doc.body.focus){
+ dojo.doc.body.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(!dojo.isIE){
+ dijit.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 everytime 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){
+ clearTimeout(this._updateTimer);
+ }
+ if(!this._updateHandler){
+ this._updateHandler = dojo.hitch(this,"onNormalizedDisplayChanged");
+ }
+ this._updateTimer = setTimeout(this._updateHandler, 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 by dojo.connect to map our
+ // normalized set of commands to those supported by the target
+ // browser.
+ // tags:
+ // private
+
+ var command = cmd.toLowerCase();
+ if(command == "formatblock"){
+ if(dojo.isSafari && argument === undefined){ command = "heading"; }
+ }else if(command == "hilitecolor" && !dojo.isMoz){
+ 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);
+ 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 (dojo.isIE && supportedBy.ie) ||
+ (dojo.isMoz && supportedBy.mozilla) ||
+ (dojo.isWebKit && supportedBy.webkit) ||
+ (dojo.isOpera && 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") && dojo.isIE){
+ 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.
+ // tags:
+ // protected
+ if(this.disabled || !this._disabledOK){ return false; }
+ command = this._normalizeCommand(command);
+ if(dojo.isMoz || dojo.isWebKit){
+ if(command == "unlink"){ // mozilla returns true always
+ // console.debug(this._sCall("hasAncestorElement", ['a']));
+ return this._sCall("hasAncestorElement", ["a"]);
+ }else if(command == "inserttable"){
+ return true;
+ }
+ }
+ //see #4109
+ if(dojo.isWebKit){
+ if(command == "cut" || command == "copy") {
+ // WebKit deems clipboard activity as a security threat and natively would return false
+ var sel = this.window.getSelection();
+ if(sel){ sel = sel.toString(); }
+ return !!sel;
+ }else if(command == "paste"){
+ return true;
+ }
+ }
+
+ var elem = dojo.isIE ? this.document.selection.createRange() : this.document;
+ try{
+ return elem.queryCommandEnabled(command);
+ }catch(e){
+ //Squelch, occurs if editor is hidden on FF 3 (and maybe others.)
+ return false;
+ }
+
+ },
+
+ 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(dojo.isIE && command == "formatblock"){
+ r = this._native2LocalFormatNames[this.document.queryCommandValue(command)];
+ }else if(dojo.isMoz && 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 dojo.withGlobal(this.window, name, dijit._editor.selection, 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(dojo.isMoz){
+ // 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(dojo.isMoz){
+ 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;
+ if(last.lastChild){
+ this._sCall("selectElement", [ last.lastChild ]);
+ }else{
+ this._sCall("selectElement", [ 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.addCallback(dojo.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 && dojo.isMoz && html.toLowerCase() == "<p></p>"){
+ html = "<p>&nbsp;</p>";
+ }
+
+ // Use &nbsp; to avoid webkit problems where editor is disabled until the user clicks it
+ if(!html && dojo.isWebKit){
+ html = "&nbsp;";
+ }
+ 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 && !dojo.isMoz){ // 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 = "&nbsp;";
+ }
+ 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 <a> tag, when replacing the content of the editing
+ //area, the <a> 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;
+ dojo.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;
+ dojo.forEach(this.contentDomPreFilters, function(ef){
+ if(ef && dojo.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(!dojo.isString(dom)){
+ dom = dom || this.editNode;
+ if(this.contentDomPostFilters.length){
+ if(nonDestructive){
+ dom = dojo.clone(dom);
+ }
+ dojo.forEach(this.contentDomPostFilters, function(ef){
+ dom = ef(dom);
+ });
+ }
+ ec = dijit._editor.getChildrenHtml(dom);
+ }else{
+ ec = dom;
+ }
+
+ if(!dojo.trim(ec.replace(/^\xA0\xA0*/, '').replace(/\xA0\xA0*$/, '')).length){
+ ec = "";
+ }
+
+ // if(dojo.isIE){
+ // //removing appended <P>&nbsp;</P> for IE
+ // ec = ec.replace(/(?:<p>&nbsp;</p>[\n\r]*)+$/i,"");
+ // }
+ dojo.forEach(this.contentPostFilters, function(ef){
+ ec = ef(ec);
+ });
+
+ return ec;
+ },
+
+ _saveContent: function(/*Event*/ e){
+ // summary:
+ // Saves the content in an onunload event if the editor has not been closed
+ // tags:
+ // private
+
+ var saveTextarea = dojo.byId(dijit._scopeName + "._editor.RichText.value");
+ 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, "&amp;").replace(/</gm, "&lt;").replace(/>/gm, "&gt;").replace(/"/gm, "&quot;");
+ if(!noSingleQuotes){
+ str = str.replace(/'/gm, "&#39;");
+ }
+ return str; // string
+ },
+
+ getNodeHtml: function(/* DomNode */ node){
+ // summary:
+ // Deprecated. Use dijit._editor._getNodeHtml() instead.
+ // tags:
+ // deprecated
+ dojo.deprecated('dijit.Editor::getNodeHtml is deprecated','use dijit._editor.getNodeHtml instead', 2);
+ return dijit._editor.getNodeHtml(node); // String
+ },
+
+ getNodeChildrenHtml: function(/* DomNode */ dom){
+ // summary:
+ // Deprecated. Use dijit._editor.getChildrenHtml() instead.
+ // tags:
+ // deprecated
+ dojo.deprecated('dijit.Editor::getNodeChildrenHtml is deprecated','use dijit._editor.getChildrenHtml instead', 2);
+ return dijit._editor.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(dojo.isIE){
+ this.iframe.onfocus = null;
+ }
+ this.iframe._loadFunc = null;
+
+ if(this._iframeRegHandle){
+ dijit.unregisterIframe(this._iframeRegHandle);
+ delete this._iframeRegHandle;
+ }
+
+ if(this.textarea){
+ var s = this.textarea.style;
+ s.position = "";
+ s.left = s.top = "";
+ if(dojo.isIE){
+ s.overflow = this.__overflow;
+ this.__overflow = null;
+ }
+ this.textarea.value = this.value;
+ dojo.destroy(this.domNode);
+ this.domNode = this.textarea;
+ }else{
+ // Note that this destroys the iframe
+ this.domNode.innerHTML = this.value;
+ }
+ delete this.iframe;
+
+ dojo.removeClass(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); }
+ this.inherited(arguments);
+ if(dijit._editor._globalSaveHandler){
+ delete dijit._editor._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(/<meta charset=\"utf-8\" \/>/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 <a> and <img> tags
+ // tags:
+ // private
+ return html.replace(/(?:(<a(?=\s).*?\shref=)("|')(.*?)\2)|(?:(<a\s.*?href=)([^"'][^ >]+))/gi,
+ '$1$4$2$3$5$2 _djrealurl=$2$3$5$2')
+ .replace(/(?:(<img(?=\s).*?\ssrc=)("|')(.*?)\2)|(?:(<img\s.*?src=)([^"'][^ >]+))/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.
+ ******************************************************************************/
+
+ _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(dojo.isIE){
+ return this._inserthtmlImpl("<hr>");
+ }
+ 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")) && (dojo.isMoz || dojo.isWebKit)){
+ 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;
+ if(dojo.isMoz){
+ // mozilla doesn't support hilitecolor properly when useCSS is
+ // set to false (bugzilla #279330)
+ this.document.execCommand("styleWithCSS", false, true);
+ 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(dojo.isIE){
+ // 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;
+ }
+ return this.document.execCommand("backcolor", false, argument);
+ },
+
+ _forecolorImpl: function(argument){
+ // summary:
+ // This function implements the forecolor command
+ // argument:
+ // arguments to the exec command, if any.
+ // tags:
+ // protected
+ if(dojo.isIE){
+ // 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;
+ }
+ return this.document.execCommand("forecolor", false, argument);
+ },
+
+ _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(dojo.isIE){
+ 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(dojo.isMoz && !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
+ if(dojo.isIE){
+ this._adaptIESelection()
+ }
+ return this.document.execCommand("bold", false, argument);
+ },
+
+ _italicImpl: function(argument){
+ // summary:
+ // This function implements an over-ride of the italic command.
+ // argument:
+ // Not used, operates by selection.
+ // tags:
+ // protected
+ if(dojo.isIE){
+ this._adaptIESelection()
+ }
+ return this.document.execCommand("italic", false, argument);
+ },
+
+ _underlineImpl: function(argument){
+ // summary:
+ // This function implements an over-ride of the underline command.
+ // argument:
+ // Not used, operates by selection.
+ // tags:
+ // protected
+ if(dojo.isIE){
+ this._adaptIESelection()
+ }
+ return this.document.execCommand("underline", false, argument);
+ },
+
+ _strikethroughImpl: function(argument){
+ // summary:
+ // This function implements an over-ride of the strikethrough command.
+ // argument:
+ // Not used, operates by selection.
+ // tags:
+ // protected
+ if(dojo.isIE){
+ this._adaptIESelection()
+ }
+ return this.document.execCommand("strikethrough", false, argument);
+ },
+
+ 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 = dojo.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 = dijit.range.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);
+ }
+ }
});
-return ec;
-},_saveContent:function(e){
-var _57=dojo.byId(dijit._scopeName+"._editor.RichText.savedContent");
-if(_57.value){
-_57.value+=this._SEPARATOR;
-}
-_57.value+=this.name+":"+this.getValue(true);
-},escapeXml:function(str,_58){
-str=str.replace(/&/gm,"&amp;").replace(/</gm,"&lt;").replace(/>/gm,"&gt;").replace(/"/gm,"&quot;");
-if(!_58){
-str=str.replace(/'/gm,"&#39;");
-}
-return str;
-},getNodeHtml:function(_59){
-dojo.deprecated("dijit.Editor::getNodeHtml is deprecated","use dijit._editor.getNodeHtml instead",2);
-return dijit._editor.getNodeHtml(_59);
-},getNodeChildrenHtml:function(dom){
-dojo.deprecated("dijit.Editor::getNodeChildrenHtml is deprecated","use dijit._editor.getChildrenHtml instead",2);
-return dijit._editor.getChildrenHtml(dom);
-},close:function(_5a){
-if(this.isClosed){
-return false;
-}
-if(!arguments.length){
-_5a=true;
-}
-this._content=this.getValue();
-var _5b=(this.savedContent!=this._content);
-if(this.interval){
-clearInterval(this.interval);
-}
-if(this._webkitListener){
-this.disconnect(this._webkitListener);
-delete this._webkitListener;
-}
-if(dojo.isIE){
-this.iframe.onfocus=null;
-}
-this.iframe._loadFunc=null;
-if(this._iframeRegHandle){
-dijit.unregisterIframe(this._iframeRegHandle);
-delete this._iframeRegHandle;
-}
-if(this.textarea){
-var s=this.textarea.style;
-s.position="";
-s.left=s.top="";
-if(dojo.isIE){
-s.overflow=this.__overflow;
-this.__overflow=null;
-}
-this.textarea.value=_5a?this._content:this.savedContent;
-dojo.destroy(this.domNode);
-this.domNode=this.textarea;
-}else{
-this.domNode.innerHTML=_5a?this._content:this.savedContent;
-}
-delete this.iframe;
-dojo.removeClass(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;
-return _5b;
-},destroy:function(){
-if(!this.isClosed){
-this.close(false);
-}
-this.inherited(arguments);
-},_removeMozBogus:function(_5c){
-return _5c.replace(/\stype="_moz"/gi,"").replace(/\s_moz_dirty=""/gi,"").replace(/_moz_resizing="(true|false)"/gi,"");
-},_removeWebkitBogus:function(_5d){
-_5d=_5d.replace(/\sclass="webkit-block-placeholder"/gi,"");
-_5d=_5d.replace(/\sclass="apple-style-span"/gi,"");
-return _5d;
-},_normalizeFontStyle:function(_5e){
-return _5e.replace(/<(\/)?strong([ \>])/gi,"<$1b$2").replace(/<(\/)?em([ \>])/gi,"<$1i$2");
-},_preFixUrlAttributes:function(_5f){
-return _5f.replace(/(?:(<a(?=\s).*?\shref=)("|')(.*?)\2)|(?:(<a\s.*?href=)([^"'][^ >]+))/gi,"$1$4$2$3$5$2 _djrealurl=$2$3$5$2").replace(/(?:(<img(?=\s).*?\ssrc=)("|')(.*?)\2)|(?:(<img\s.*?src=)([^"'][^ >]+))/gi,"$1$4$2$3$5$2 _djrealurl=$2$3$5$2");
-},_inserthorizontalruleImpl:function(_60){
-if(dojo.isIE){
-return this._inserthtmlImpl("<hr>");
-}
-return this.document.execCommand("inserthorizontalrule",false,_60);
-},_unlinkImpl:function(_61){
-if((this.queryCommandEnabled("unlink"))&&(dojo.isMoz||dojo.isWebKit)){
-var a=this._sCall("getAncestorElement",["a"]);
-this._sCall("selectElement",[a]);
-return this.document.execCommand("unlink",false,null);
-}
-return this.document.execCommand("unlink",false,_61);
-},_hilitecolorImpl:function(_62){
-var _63;
-if(dojo.isMoz){
-this.document.execCommand("styleWithCSS",false,true);
-_63=this.document.execCommand("hilitecolor",false,_62);
-this.document.execCommand("styleWithCSS",false,false);
-}else{
-_63=this.document.execCommand("hilitecolor",false,_62);
-}
-return _63;
-},_backcolorImpl:function(_64){
-if(dojo.isIE){
-_64=_64?_64:null;
-}
-return this.document.execCommand("backcolor",false,_64);
-},_forecolorImpl:function(_65){
-if(dojo.isIE){
-_65=_65?_65:null;
-}
-return this.document.execCommand("forecolor",false,_65);
-},_inserthtmlImpl:function(_66){
-_66=this._preFilterContent(_66);
-var rv=true;
-if(dojo.isIE){
-var _67=this.document.selection.createRange();
-if(this.document.selection.type.toUpperCase()=="CONTROL"){
-var n=_67.item(0);
-while(_67.length){
-_67.remove(_67.item(0));
-}
-n.outerHTML=_66;
-}else{
-_67.pasteHTML(_66);
-}
-_67.select();
-}else{
-if(dojo.isMoz&&!_66.length){
-this._sCall("remove");
-}else{
-rv=this.document.execCommand("inserthtml",false,_66);
-}
-}
-return rv;
-},getHeaderHeight:function(){
-return this._getNodeChildrenHeight(this.header);
-},getFooterHeight:function(){
-return this._getNodeChildrenHeight(this.footer);
-},_getNodeChildrenHeight:function(_68){
-var h=0;
-if(_68&&_68.childNodes){
-var i;
-for(i=0;i<_68.childNodes.length;i++){
-var _69=dojo.position(_68.childNodes[i]);
-h+=_69.h;
-}
-}
-return h;
-}});
+
}