Blame | Last modification | View Log | RSS feed
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._editor.selection");dojo.require("dojo.i18n");dojo.requireLocalization("dijit", "Textarea", null, "ROOT");// used to restore content when user leaves this page then comes back// but do not try doing document.write if we are using xd loading.// document.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 djConfig.allowXdRichTextSave = true.if(!djConfig["useXDomain"] || djConfig["allowXdRichTextSave"]){if(dojo._postLoad){(function(){var savetextarea = dojo.doc.createElement('textarea');savetextarea.id = "dijit._editor.RichText.savedContent";var s = savetextarea.style;s.display='none';s.position='absolute';s.top="-100px";s.left="-100px"s.height="3px";s.width="3px";dojo.body().appendChild(savetextarea);})();}else{//dojo.body() is not available before onLoad is firedtry {dojo.doc.write('<textarea id="dijit._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 ], {constructor: function(){// summary:// dijit._editor.RichText is the core of the WYSIWYG editor in dojo, which// provides the basic editing features. It also encapsulates the differences// of different js engines for various browsers//// contentPreFilters: Array// pre content filter function register array.// these filters will be executed before the actual// editing area get the html contentthis.contentPreFilters = [];// contentPostFilters: Array// post content filter function register array.// these will be used on the resulting html// from contentDomPostFilters. The resuling// content is the final html (returned by getValue())this.contentPostFilters = [];// contentDomPreFilters: Array// pre content dom filter function register array.// these filters are applied after the result from// contentPreFilters are set to the editing areathis.contentDomPreFilters = [];// contentDomPostFilters: Array// post content dom filter function register array.// these filters are executed on the editing area dom// the result from these will be passed to contentPostFiltersthis.contentDomPostFilters = [];// editingAreaStyleSheets: Array// array to store all the stylesheets applied to the editing areathis.editingAreaStyleSheets=[];this._keyHandlers = {};this.contentPreFilters.push(dojo.hitch(this, "_preFixUrlAttributes"));if(dojo.isMoz){this.contentPreFilters.push(this._fixContentForMoz);}//this.contentDomPostFilters.push(this._postDomFixUrlAttributes);this.onLoadDeferred = new dojo.Deferred();},// inheritWidth: Boolean// whether to inherit the parent's width or simply use 100%inheritWidth: false,// focusOnLoad: Boolean// whether focusing into this instance of richtext when page onloadfocusOnLoad: false,// name: String// If a save name is specified the content is saved and restored when the user// leave this page can come back, or if the editor is not properly closed after// editing has started.name: "",// styleSheets: String// semicolon (";") separated list of css files for the editing areastyleSheets: "",// _content: String// temporary content storage_content: "",// 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=""height: "300px",// minHeight: String// The minimum height that the editor should haveminHeight: "1em",// isClosed: BooleanisClosed: true,// isLoaded: BooleanisLoaded: false,// _SEPARATOR: String// used to concat contents from multiple textareas into a single string_SEPARATOR: "@@**%%__RICHTEXTBOUNDRY__%%**@@",// onLoadDeferred: dojo.Deferred// deferred which is fired when the editor finishes loadingonLoadDeferred: null,postCreate: function(){// summary: initdojo.publish("dijit._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.var ctrl = this.KEY_CTRL;var exec = function(cmd, arg){return arguments.length == 1 ? function(){ this.execCommand(cmd); } :function(){ this.execCommand(cmd, arg); }}this.addKeyHandler("b", ctrl, exec("bold"));this.addKeyHandler("i", ctrl, exec("italic"));this.addKeyHandler("u", ctrl, exec("underline"));this.addKeyHandler("a", ctrl, exec("selectall"));this.addKeyHandler("s", ctrl, function () { this.save(true); });this.addKeyHandler("1", ctrl, exec("formatblock", "h1"));this.addKeyHandler("2", ctrl, exec("formatblock", "h2"));this.addKeyHandler("3", ctrl, exec("formatblock", "h3"));this.addKeyHandler("4", ctrl, exec("formatblock", "h4"));this.addKeyHandler("\\", ctrl, exec("insertunorderedlist"));if(!dojo.isIE){this.addKeyHandler("Z", ctrl, exec("redo"));}},// events: Array// events which should be connected to the underlying editing areaevents: ["onKeyPress", "onKeyDown", "onKeyUp", "onClick"],// events: Array// events which should be connected to the underlying editing// area, events in this array will be addListener with// capture=truecaptureEvents: [],_editorCommandsLocalized: false,_localizeEditorCommands: function(){if(this._editorCommandsLocalized){return;}this._editorCommandsLocalized = true;//in IE, names for blockformat is locale dependent, so we cache the values here//if the normal way fails, we try the hard way to get the list//do not use _cacheLocalBlockFormatNames here, as it will//trigger security warning in IE7//in the array below, ul can not come directly after ol,//otherwise the queryCommandValue returns Normal for itvar formats = ['p', 'pre', 'address', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'div', 'ul'];var localhtml = "", format, i=0;while((format=formats[i++])){if(format.charAt(1) != 'l'){localhtml += "<"+format+"><span>content</span></"+format+">";}else{localhtml += "<"+format+"><li>content</li></"+format+">";}}//queryCommandValue returns empty if we hide editNode, so move it out of screen temporaryvar div=document.createElement('div');div.style.position = "absolute";div.style.left = "-2000px";div.style.top = "-2000px";document.body.appendChild(div);div.innerHTML = localhtml;var node = div.firstChild;while(node){dijit._editor.selection.selectElement(node.firstChild);dojo.withGlobal(this.window, "selectElement", dijit._editor.selection, [node.firstChild]);var nativename = node.tagName.toLowerCase();this._local2NativeFormatNames[nativename] = document.queryCommandValue("formatblock");//this.queryCommandValue("formatblock");this._native2LocalFormatNames[this._local2NativeFormatNames[nativename]] = nativename;node = node.nextSibling;}document.body.removeChild(div);},open: function(/*DomNode?*/element){// summary:// Transforms the node referenced in this.domNode into a rich text editing// node. This will result in the creation and replacement with an <iframe>// if designMode(FF)/contentEditable(IE) is used.if((!this.onLoadDeferred)||(this.onLoadDeferred.fired >= 0)){this.onLoadDeferred = new dojo.Deferred();}if(!this.isClosed){ this.close(); }dojo.publish("dijit._editor.RichText::open", [ this ]);this._content = "";if((arguments.length == 1)&&(element["nodeName"])){ this.domNode = element; } // else unchangedif( (this.domNode["nodeName"])&&(this.domNode.nodeName.toLowerCase() == "textarea")){// if we were created from a textarea, then we need to create a// new editing harness node.this.textarea = this.domNode;this.name=this.textarea.name;var html = this._preFilterContent(this.textarea.value);this.domNode = dojo.doc.createElement("div");this.domNode.setAttribute('widgetId',this.id);this.textarea.removeAttribute('widgetId');this.domNode.cssText = this.textarea.cssText;this.domNode.className += " "+this.textarea.className;dojo.place(this.domNode, this.textarea, "before");var tmpFunc = dojo.hitch(this, function(){//some browsers refuse to submit display=none textarea, so//move the textarea out of screen insteadwith(this.textarea.style){display = "block";position = "absolute";left = top = "-1000px";if(dojo.isIE){ //nasty IE bug: abnormal formatting if overflow is not hiddenthis.__overflow = overflow;overflow = "hidden";}}});if(dojo.isIE){setTimeout(tmpFunc, 10);}else{tmpFunc();}// this.domNode.innerHTML = html;// if(this.textarea.form){// // FIXME: port: this used to be before advice!!!// dojo.connect(this.textarea.form, "onsubmit", this, function(){// // FIXME: should we be calling close() here instead?// this.textarea.value = this.getValue();// });// }}else{var html = this._preFilterContent(this.getNodeChildrenHtml(this.domNode));this.domNode.innerHTML = '';}if(html == ""){ html = " "; }var content = dojo.contentBox(this.domNode);// var content = dojo.contentBox(this.srcNodeRef);this._oldHeight = content.h;this._oldWidth = content.w;this.savedContent = 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 textif( (this.domNode["nodeName"]) &&(this.domNode.nodeName == "LI") ){this.domNode.innerHTML = " <br>";}this.editingArea = dojo.doc.createElement("div");this.domNode.appendChild(this.editingArea);if(this.name != "" && (!djConfig["useXDomain"] || djConfig["allowXdRichTextSave"])){var saveTextarea = dojo.byId("dijit._editor.RichText.savedContent");if(saveTextarea.value != ""){var datas = saveTextarea.value.split(this._SEPARATOR), i=0, dat;while((dat=datas[i++])){var data = dat.split(":");if(data[0] == this.name){html = data[1];datas.splice(i, 1);break;}}}// FIXME: need to do something different for Opera/Safaridojo.connect(window, "onbeforeunload", this, "_saveContent");// dojo.connect(window, "onunload", this, "_saveContent");}this.isClosed = false;// Safari's selections go all out of whack if we do it inline,// so for now IE is our only hero//if (typeof document.body.contentEditable != "undefined") {if(dojo.isIE || dojo.isSafari || dojo.isOpera){ // contentEditable, easyvar ifr = this.iframe = dojo.doc.createElement('iframe');ifr.src = 'javascript:void(0)';this.editorObject = ifr;ifr.style.border = "none";ifr.style.width = "100%";ifr.frameBorder = 0;// ifr.style.scrolling = this.height ? "auto" : "vertical";this.editingArea.appendChild(ifr);this.window = ifr.contentWindow;this.document = this.window.document;this.document.open();this.document.write(this._getIframeDocTxt(html));this.document.close();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;}if(dojo.isIE){this._localizeEditorCommands();}this.onLoad();}else{ // designMode in iframethis._drawIframe(html);}// TODO: this is a guess at the default line-height, kinda worksif(this.domNode.nodeName == "LI"){ this.domNode.lastChild.style.marginTop = "-1.2em"; }this.domNode.className += " RichTextEditable";},//static cache variables shared among all instance of this class_local2NativeFormatNames: {},_native2LocalFormatNames: {},_localizedIframeTitles: null,_getIframeDocTxt: function(/* String */ html){var _cs = dojo.getComputedStyle(this.domNode);if(!this.height && !dojo.isMoz){html="<div>"+html+"</div>";}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{lineHeight = "1.0";}return [this.isLeftToRight() ? "<html><head>" : "<html dir='rtl'><head>",(dojo.isMoz ? "<title>" + this._localizedIframeTitles.iframeEditTitle + "</title>" : ""),"<style>","body,html {"," background:transparent;"," padding: 0;"," margin: 0;","}",// TODO: left positioning will cause contents to disappear out of view// if it gets too wide for the visible area"body{"," top:0px; left:0px; right:0px;",((this.height||dojo.isOpera) ? "" : "position: fixed;")," font:", font, ";",// FIXME: IE 6 won't understand min-height?" min-height:", this.minHeight, ";"," line-height:", lineHeight,"}","p{ margin: 1em 0 !important; }",(this.height ?"" : "body,html{overflow-y:hidden;/*for IE*/} body > div {overflow-x:auto;/*for FF to show vertical scrollbar*/}"),"li > ul:-moz-first-node, li > ol:-moz-first-node{ padding-top: 1.2em; } ","li{ min-height:1.2em; }","</style>",this._applyEditingAreaStyleSheets(),"</head><body>"+html+"</body></html>"].join(""); // String},_drawIframe: function(/*String*/html){// summary:// Draws an iFrame using the existing one if one exists.// Used by Mozilla, Safari, and Operaif(!this.iframe){var ifr = this.iframe = dojo.doc.createElement("iframe");// this.iframe.src = "about:blank";// document.body.appendChild(this.iframe);// console.debug(this.iframe.contentDocument.open());// dojo.body().appendChild(this.iframe);var ifrs = ifr.style;// ifrs.border = "1px solid black";ifrs.border = "none";ifrs.lineHeight = "0"; // squash line heightifrs.verticalAlign = "bottom";// ifrs.scrolling = this.height ? "auto" : "vertical";this.editorObject = this.iframe;// get screen reader text for mozilla here, toothis._localizedIframeTitles = dojo.i18n.getLocalization("dijit", "Textarea");// need to find any associated label element and update iframe document titlevar label=dojo.query('label[for="'+this.id+'"]');if(label.length){this._localizedIframeTitles.iframeEditTitle = label[0].innerHTML + " " + this._localizedIframeTitles.iframeEditTitle;}}// opera likes this to be outside the with block// this.iframe.src = "javascript:void(0)";//dojo.uri.dojoUri("src/widget/templates/richtextframe.html") + ((dojo.doc.domain != currentDomain) ? ("#"+dojo.doc.domain) : "");this.iframe.style.width = this.inheritWidth ? this._oldWidth : "100%";if(this.height){this.iframe.style.height = this.height;}else{this.iframe.height = this._oldHeight;}if(this.textarea){var tmpContent = this.srcNodeRef;}else{var tmpContent = dojo.doc.createElement('div');tmpContent.style.display="none";tmpContent.innerHTML = html;//append tmpContent to under the current domNode so that the margin//calculation below is correctthis.editingArea.appendChild(tmpContent);}this.editingArea.appendChild(this.iframe);//do we want to show the content before the editing area finish loading here?//if external style sheets are used for the editing area, the appearance now//and after loading of the editing area won't be the same (and padding/margin//calculation above may not be accurate)// tmpContent.style.display = "none";// this.editingArea.appendChild(this.iframe);var _iframeInitialized = false;// console.debug(this.iframe);// var contentDoc = this.iframe.contentWindow.document;// note that on Safari lower than 420+, we have to get the iframe// by ID in order to get something w/ a contentDocument propertyvar contentDoc = this.iframe.contentDocument;contentDoc.open();contentDoc.write(this._getIframeDocTxt(html));contentDoc.close();// now we wait for onload. Janky hack!var ifrFunc = dojo.hitch(this, function(){if(!_iframeInitialized){_iframeInitialized = true;}else{ return; }if(!this.editNode){try{if(this.iframe.contentWindow){this.window = this.iframe.contentWindow;this.document = this.iframe.contentWindow.document}else if(this.iframe.contentDocument){// for operathis.window = this.iframe.contentDocument.window;this.document = this.iframe.contentDocument;}if(!this.document.body){throw 'Error';}}catch(e){setTimeout(ifrFunc,500);_iframeInitialized = false;return;}dojo._destroyElement(tmpContent);this.document.designMode = "on";// try{// this.document.designMode = "on";// }catch(e){// this._tryDesignModeOnClick=true;// }this.onLoad();}else{dojo._destroyElement(tmpContent);this.editNode.innerHTML = html;this.onDisplayChanged();}this._preDomFilterContent(this.editNode);});ifrFunc();},_applyEditingAreaStyleSheets: function(){// summary:// apply the specified css files in styleSheetsvar files = [];if(this.styleSheets){files = this.styleSheets.split(';');this.styleSheets = '';}//empty this.editingAreaStyleSheets here, as it will be filled in addStyleSheetfiles = 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 filevar url=uri.toString();//if uri is relative, then convert it to absolute so that it can be resolved correctly in iframeif(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 to the editing area!");return;}this.editingAreaStyleSheets.push(url);if(this.document.createStyleSheet){ //IEthis.document.createStyleSheet(url);}else{ //other browservar head = this.document.getElementsByTagName("head")[0];var stylesheet = this.document.createElement("link");with(stylesheet){rel="stylesheet";type="text/css";href=url;}head.appendChild(stylesheet);}},removeStyleSheet: function(/*dojo._Url*/uri){// summary:// remove an external stylesheet for the editing areavar url=uri.toString();//if uri is relative, then convert it to absolute so that it can be resolved correctly in iframeif(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+" is not applied to the editing area so it can not be removed!");return;}delete this.editingAreaStyleSheets[index];dojo.withGlobal(this.window,'query', dojo, ['link:[href="'+url+'"]']).orphan()},disabled: false,_mozSettingProps: ['styleWithCSS','insertBrOnReturn'],setDisabled: function(/*Boolean*/ disabled){if(dojo.isIE || dojo.isSafari || dojo.isOpera){this.editNode.contentEditable=!disabled;}else{ //mozif(disabled){this._mozSettings=[false,this.blockNodeForEnter==='BR'];}this.document.designMode=(disabled?'off':'on');if(!disabled){dojo.forEach(this._mozSettingProps, function(s,i){this.document.execCommand(s,false,this._mozSettings[i]);},this);}// this.document.execCommand('contentReadOnly', false, disabled);// if(disabled){// this.blur(); //to remove the blinking caret// }//}this.disabled=disabled;},/* Event handlers*****************/_isResized: function(){ return false; },onLoad: function(/* Event */ e){// summary: handler after the content of the document finishes loadingthis.isLoaded = true;if(this.height || dojo.isMoz){this.editNode=this.document.body;}else{this.editNode=this.document.body.firstChild;}this.editNode.contentEditable = true; //should do no harm in FFthis._preDomFilterContent(this.editNode);var events=this.events.concat(this.captureEvents),i=0,et;while((et=events[i++])){this.connect(this.document, et.toLowerCase(), et);}if(!dojo.isIE){try{ // sanity check for Mozilla// this.document.execCommand("useCSS", false, true); // old moz callthis.document.execCommand("styleWithCSS", false, false); // new moz call//this.document.execCommand("insertBrOnReturn", false, false); // new moz call}catch(e2){ }// FIXME: when scrollbars appear/disappear this needs to be fired}else{ // IE contentEditable// give the node Layout on IEthis.editNode.style.zoom = 1.0;}if(this.focusOnLoad){this.focus();}this.onDisplayChanged(e);if(this.onLoadDeferred){this.onLoadDeferred.callback(true);}},onKeyDown: function(/* Event */ e){// summary: Fired on keydown// console.info("onkeydown:", e.keyCode);// 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(dojo.isIE){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 #1069dojo.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 keyse.charCode = e.keyCode;this.onKeyPress(e);}}else if (dojo.isMoz){if(e.keyCode == dojo.keys.TAB && !e.shiftKey && !e.ctrlKey && !e.altKey && this.iframe){// update iframe document title for screen readerthis.iframe.contentDocument.title = this._localizedIframeTitles.iframeFocusTitle;// Place focus on the iframe. A subsequent tab or shift tab will put focus// on the correct control.this.iframe.focus(); // this.focus(); won't workdojo.stopEvent(e);}else if (e.keyCode == dojo.keys.TAB && e.shiftKey){// if there is a toolbar, set focus to it, otherwise ignoreif (this.toolbar){this.toolbar.focus();}dojo.stopEvent(e);}}},onKeyUp: function(e){// summary: Fired on keyupreturn;},KEY_CTRL: 1,KEY_SHIFT: 2,onKeyPress: function(e){// summary: Fired on keypress// console.info("onkeypress:", e.keyCode);// handle the various key eventsvar modifiers = e.ctrlKey ? this.KEY_CTRL : 0 | e.shiftKey?this.KEY_SHIFT : 0;var key = e.keyChar||e.keyCode;if(this._keyHandlers[key]){// console.debug("char:", e.key);var handlers = this._keyHandlers[key], i = 0, h;while((h = handlers[i++])){if(modifiers == h.modifiers){if(!h.handler.apply(this,arguments)){e.preventDefault();}break;}}}// function call after the character has been insertedsetTimeout(dojo.hitch(this, function(){this.onKeyPressed(e);}), 1);},addKeyHandler: function(/*String*/key, /*Int*/modifiers, /*Function*/handler){// summary: add a handler for a keyboard shortcutif(!dojo.isArray(this._keyHandlers[key])){ this._keyHandlers[key] = []; }this._keyHandlers[key].push({modifiers: modifiers || 0,handler: handler});},onKeyPressed: function(/*Event*/e){this.onDisplayChanged(/*e*/); // can't pass in e},onClick: function(/*Event*/e){// console.debug('onClick',this._tryDesignModeOnClick);// if(this._tryDesignModeOnClick){// try{// this.document.designMode='on';// this._tryDesignModeOnClick=false;// }catch(e){}// }this.onDisplayChanged(e); },_onBlur: function(e){var _c=this.getValue(true);if(_c!=this.savedContent){this.onChange(_c);this.savedContent=_c;}if (dojo.isMoz && this.iframe){this.iframe.contentDocument.title = this._localizedIframeTitles.iframeEditTitle;}// console.info('_onBlur')},_initialFocus: true,_onFocus: function(/*Event*/e){// console.info('_onFocus')// summary: Fired on focusif( (dojo.isMoz)&&(this._initialFocus) ){this._initialFocus = false;if(this.editNode.innerHTML.replace(/^\s+|\s+$/g, "") == " "){this.placeCursorAtStart();// this.execCommand("selectall");// this.window.getSelection().collapseToStart();}}},blur: function(){// summary: remove focus from this instanceif(this.iframe){this.window.blur();}else if(this.editNode){this.editNode.blur();}},focus: function(){// summary: move focus to this instanceif(this.iframe && !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 casedijit.focus(this.editNode);}else{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// var _t=new Date();if(!this._updateTimer){// this._lastUpdate=_t;if(this._updateTimer){clearTimeout(this._updateTimer);}this._updateTimer=setTimeout(dojo.hitch(this,this.onNormalizedDisplayChanged),this.updateInterval);}},onNormalizedDisplayChanged: function(){// summary:// This event is fired every updateInterval ms or more// description:// If something needs to happen immidiately after a// user change, please use onDisplayChanged insteadthis._updateTimer=null;},onChange: function(newContent){// summary:// this is fired if and only if the editor loses focus and// the content is changed// console.log('onChange',newContent);},_normalizeCommand: function(/*String*/cmd){// summary:// Used as the advice function by dojo.connect to map our// normalized set of commands to those supported by the target// browservar command = cmd.toLowerCase();if(command == "formatblock"){if(dojo.isSafari){ command = "heading"; }}else if(command == "hilitecolor" && !dojo.isMoz){command = "backcolor";}return command;},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 forvar ie = 1;var mozilla = 1 << 1;var safari = 1 << 2;var opera = 1 << 3;var safari420 = 1 << 4;var gt420 = dojo.isSafari;function isSupportedBy(browsers){return {ie: Boolean(browsers & ie),mozilla: Boolean(browsers & mozilla),safari: Boolean(browsers & safari),safari420: Boolean(browsers & safari420),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":supportedBy = isSupportedBy(mozilla | ie | safari | 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":supportedBy = isSupportedBy(mozilla | ie | opera | safari420);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 | safari420);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.isSafari && supportedBy.safari) ||(gt420 && supportedBy.safari420) ||(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 commandvar 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 expectedthis.focus();command = this._normalizeCommand(command);if(argument != undefined){if(command == "heading"){throw new Error("unimplemented");}else if((command == "formatblock") && dojo.isIE){argument = '<'+argument+'>';}}if(command == "inserthtml"){//TODO: we shall probably call _preDomFilterContent here as wellargument=this._preFilterContent(argument);if(dojo.isIE){var insertRange = this.document.selection.createRange();insertRange.pasteHTML(argument);insertRange.select();//insertRange.collapse(true);returnValue=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 casedojo.withGlobal(this.window,'remove',dijit._editor.selection); // FIXMEreturnValue=true;}else{returnValue=this.document.execCommand(command, false, argument);}}else if((command == "unlink")&&(this.queryCommandEnabled("unlink"))&&(dojo.isMoz || dojo.isSafari)){// fix up unlink in Mozilla to unlink the link and not just the selection// grab selection// Mozilla gets upset if we just store the range so we have to// get the basic properties and recreate to save the selectionvar selection = this.window.getSelection();// var selectionRange = selection.getRangeAt(0);// var selectionStartContainer = selectionRange.startContainer;// var selectionStartOffset = selectionRange.startOffset;// var selectionEndContainer = selectionRange.endContainer;// var selectionEndOffset = selectionRange.endOffset;// select our link and unlinkvar a = dojo.withGlobal(this.window, "getAncestorElement",dijit._editor.selection, ['a']);dojo.withGlobal(this.window, "selectElement", dijit._editor.selection, [a]);returnValue=this.document.execCommand("unlink", false, null);}else if((command == "hilitecolor")&&(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(command, false, argument);this.document.execCommand("styleWithCSS", false, false);}else if((dojo.isIE)&&( (command == "backcolor")||(command == "forecolor") )){// 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 = arguments.length > 1 ? argument : null;returnValue = this.document.execCommand(command, false, argument);// timeout is workaround for weird IE behavior were the text// selection gets correctly re-created, but subsequent input// apparently isn't bound to it// setTimeout(function(){tr.select();}, 1);}else{argument = arguments.length > 1 ? argument : null;// if(dojo.isMoz){// this.document = this.iframe.contentWindow.document// }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 notcommand = this._normalizeCommand(command);if(dojo.isMoz || dojo.isSafari){if(command == "unlink"){ // mozilla returns true always// console.debug(dojo.withGlobal(this.window, "hasAncestorElement",dijit._editor.selection, ['a']));return dojo.withGlobal(this.window, "hasAncestorElement",dijit._editor.selection, ['a']);}else if (command == "inserttable"){return true;}}//see #4109if(dojo.isSafari)if(command == "copy"){command="cut";}else if(command == "paste"){return true;}// return this.document.queryCommandEnabled(command);var elem = (dojo.isIE) ? this.document.selection.createRange() : this.document;return elem.queryCommandEnabled(command);},queryCommandState: function(command){// summary: check the state of a given commandcommand = this._normalizeCommand(command);return this.document.queryCommandState(command);},queryCommandValue: function(command){// summary: check the value of a given commandcommand = this._normalizeCommand(command);if(dojo.isIE && command == "formatblock"){return this._local2NativeFormatNames[this.document.queryCommandValue(command)];}return this.document.queryCommandValue(command);},// Misc.placeCursorAtStart: function(){// summary:// place the cursor at the start of the editing areathis.focus();//see comments in placeCursorAtEndvar isvalid=false;if(dojo.isMoz){var first=this.editNode.firstChild;while(first){if(first.nodeType == 3){if(first.nodeValue.replace(/^\s+|\s+$/g, "").length>0){isvalid=true;dojo.withGlobal(this.window, "selectElement", dijit._editor.selection, [first]);break;}}else if(first.nodeType == 1){isvalid=true;dojo.withGlobal(this.window, "selectElementChildren",dijit._editor.selection, [first]);break;}first = first.nextSibling;}}else{isvalid=true;dojo.withGlobal(this.window, "selectElementChildren",dijit._editor.selection, [this.editNode]);}if(isvalid){dojo.withGlobal(this.window, "collapse", dijit._editor.selection, [true]);}},placeCursorAtEnd: function(){// summary:// place the cursor at the end of the editing areathis.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.lastChildvar 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;dojo.withGlobal(this.window, "selectElement",dijit._editor.selection, [last]);break;}}else if(last.nodeType == 1){isvalid=true;if(last.lastChild){dojo.withGlobal(this.window, "selectElement",dijit._editor.selection, [last.lastChild]);}else{dojo.withGlobal(this.window, "selectElement",dijit._editor.selection, [last]);}break;}last = last.previousSibling;}}else{isvalid=true;dojo.withGlobal(this.window, "selectElementChildren",dijit._editor.selection, [this.editNode]);}if(isvalid){dojo.withGlobal(this.window, "collapse", dijit._editor.selection, [false]);}},getValue: function(/*Boolean?*/nonDestructive){// summary:// return the current content of the editing area (post filters are applied)if(this.textarea){if(this.isClosed || !this.isLoaded){return this.textarea.value;}}return this._postFilterContent(null, nonDestructive);},setValue: function(/*String*/html){// summary:// this function set the content. No undo history is preservedif(this.textarea && (this.isClosed || !this.isLoaded)){this.textarea.value=html;}else{html = this._preFilterContent(html);if(this.isClosed){this.domNode.innerHTML = html;this._preDomFilterContent(this.domNode);}else{this.editNode.innerHTML = html;this._preDomFilterContent(this.editNode);}}},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)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){ // Mozhtml = this._preFilterContent(html);this.execCommand("selectall");if(dojo.isMoz && !html){ html = " " }this.execCommand("inserthtml", html);this._preDomFilterContent(this.editNode);}else if(this.document && this.document.selection){//IE//In IE, when the first element is not a text node, say//an <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 toothis.setValue(html);}},_preFilterContent: function(/*String*/html){// summary:// filter the input before setting the content of the editing areavar ec = html;dojo.forEach(this.contentPreFilters, function(ef){ if(ef){ ec = ef(ec); } });return ec;},_preDomFilterContent: function(/*DomNode*/dom){// summary:// filter the inputdom = dom || this.editNode;dojo.forEach(this.contentDomPreFilters, function(ef){if(ef && dojo.isFunction(ef)){ef(dom);}}, this);},_postFilterContent: function(/*DomNode|DomNode[]?*/dom,/*Boolean?*/nonDestructive){// summary:// filter the output after getting the content of the editing areadom = dom || this.editNode;if(this.contentDomPostFilters.length){if(nonDestructive && dom['cloneNode']){dom = dom.cloneNode(true);}dojo.forEach(this.contentDomPostFilters, function(ef){dom = ef(dom);});}var ec = this.getNodeChildrenHtml(dom);if(!ec.replace(/^(?:\s|\xA0)+/g, "").replace(/(?:\s|\xA0)+$/g,"").length){ ec = ""; }// if(dojo.isIE){// //removing appended <P> </P> for IE// ec = ec.replace(/(?:<p> </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 closedvar saveTextarea = dojo.byId("dijit._editor.RichText.savedContent");saveTextarea.value += this._SEPARATOR + this.name + ":" + this.getValue();},escapeXml: function(/*String*/str, /*Boolean*/noSingleQuotes){//summary:// Adds escape sequences for special characters in XML: &<>"'// Optionally skips escapes for single quotesstr = str.replace(/&/gm, "&").replace(/</gm, "<").replace(/>/gm, ">").replace(/"/gm, """);if(!noSingleQuotes){str = str.replace(/'/gm, "'");}return str; // string},getNodeHtml: function(/* DomNode */node){switch(node.nodeType){case 1: //element nodevar output = '<'+node.tagName.toLowerCase();if(dojo.isMoz){if(node.getAttribute('type')=='_moz'){node.removeAttribute('type');}if(node.getAttribute('_moz_dirty') != undefined){node.removeAttribute('_moz_dirty');}}//store the list of attributes and sort it to have the//attributes appear in the dictionary ordervar attrarray = [];if(dojo.isIE){var s = node.outerHTML;s = s.substr(0,s.indexOf('>'));s = s.replace(/(?:['"])[^"']*\1/g, '');//to make the following regexp safevar reg = /([^\s=]+)=/g;var m, key;while((m = reg.exec(s)) != undefined){key=m[1];if(key.substr(0,3) != '_dj'){if(key == 'src' || key == 'href'){if(node.getAttribute('_djrealurl')){attrarray.push([key,node.getAttribute('_djrealurl')]);continue;}}if(key == 'class'){attrarray.push([key,node.className]);}else{attrarray.push([key,node.getAttribute(key)]);}}}}else{var attr, i=0, attrs = node.attributes;while((attr=attrs[i++])){//ignore all attributes starting with _dj which are//internal temporary attributes used by the editorif(attr.name.substr(0,3) != '_dj' /*&&(attr.specified == undefined || attr.specified)*/){var v = attr.value;if(attr.name == 'src' || attr.name == 'href'){if(node.getAttribute('_djrealurl')){v = node.getAttribute('_djrealurl');}}attrarray.push([attr.name,v]);}}}attrarray.sort(function(a,b){return a[0]<b[0]?-1:(a[0]==b[0]?0:1);});i=0;while((attr=attrarray[i++])){output += ' '+attr[0]+'="'+attr[1]+'"';}if(node.childNodes.length){output += '>' + this.getNodeChildrenHtml(node)+'</'+node.tagName.toLowerCase()+'>';}else{output += ' />';}break;case 3: //text// FIXME:var output = this.escapeXml(node.nodeValue,true);break;case 8: //comment// FIXME:var output = '<!--'+this.escapeXml(node.nodeValue,true)+'-->';break;default:var output = "Element not recognized - Type: " + node.nodeType + " Name: " + node.nodeName;}return output;},getNodeChildrenHtml: function(/* DomNode */dom){// summary: Returns the html content of a DomNode and childrenvar out = "";if(!dom){ return out; }var nodes = dom["childNodes"]||dom;var i=0;var node;while((node=nodes[i++])){out += this.getNodeHtml(node);}return out; // String},close: function(/*Boolean*/save, /*Boolean*/force){// 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.// force:if(this.isClosed){return false; }if(!arguments.length){ save = true; }this._content = this.getValue();var changed = (this.savedContent != this._content);// 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.textarea){with(this.textarea.style){position = "";left = top = "";if(dojo.isIE){overflow = this.__overflow;this.__overflow = null;}}if(save){this.textarea.value = this._content;}else{this.textarea.value = this.savedContent;}dojo._destroyElement(this.domNode);this.domNode = this.textarea;}else{if(save){//why we treat moz differently? comment out to fix #1061// if(dojo.isMoz){// var nc = dojo.doc.createElement("span");// this.domNode.appendChild(nc);// nc.innerHTML = this.editNode.innerHTML;// }else{// this.domNode.innerHTML = this._content;// }this.domNode.innerHTML = this._content;}else{this.domNode.innerHTML = this.savedContent;}}dojo.removeClass(this.domNode, "RichTextEditable");this.isClosed = true;this.isLoaded = false;// FIXME: is this always the right thing to do?delete this.editNode;if(this.window && this.window._frameElement){this.window._frameElement = null;}this.window = null;this.document = null;this.editingArea = null;this.editorObject = null;return changed; // Boolean: whether the content has been modified},destroyRendering: function(){// summary: stub},destroy: function(){this.destroyRendering();if(!this.isClosed){ this.close(false); }this.inherited("destroy",arguments);//dijit._editor.RichText.superclass.destroy.call(this);},_fixContentForMoz: function(/* String */ html){// summary:// Moz can not handle strong/em tags correctly, convert them to b/ihtml = html.replace(/<(\/)?strong([ \>])/gi, '<$1b$2' );html = html.replace(/<(\/)?em([ \>])/gi, '<$1i$2' );return html; // String},_srcInImgRegex : /(?:(<img(?=\s).*?\ssrc=)("|')(.*?)\2)|(?:(<img\s.*?src=)([^"'][^ >]+))/gi ,_hrefInARegex : /(?:(<a(?=\s).*?\shref=)("|')(.*?)\2)|(?:(<a\s.*?href=)([^"'][^ >]+))/gi ,_preFixUrlAttributes: function(/* String */ html){html = html.replace(this._hrefInARegex, '$1$4$2$3$5$2 _djrealurl=$2$3$5$2') ;html = html.replace(this._srcInImgRegex, '$1$4$2$3$5$2 _djrealurl=$2$3$5$2') ;return html; // String}});}