0byt3m1n1
Path:
/
data
/
applications
/
aps.bak
/
phprojekt
/
6.0.6-0
/
standard
/
htdocs
/
htdocs
/
dojo
/
dijit
/
_editor
/
[
Home
]
File: RichText.js
/* Copyright (c) 2004-2010, 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"]){ //_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"); // 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.savedContent"; 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.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(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 <TEXTAREA> tag, since the browser unescapes XML escape characters, // like <. 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 = {}; this.contentPreFilters.push(dojo.hitch(this, "_preFixUrlAttributes")); if(dojo.isMoz){ this.contentPreFilters.push(this._normalizeFontStyle); this.contentPostFilters.push(this._removeMozBogus); } if(dojo.isWebKit){ // Try to clean up WebKit bogus artifacts. The inserted classes // made by WebKit sometimes messes things up. this.contentPreFilters.push(this._removeWebkitBogus); this.contentPostFilters.push(this._removeWebkitBogus); } if(dojo.isIE){ // IE generates <strong> and <em> but we want to normalize to <b> and <i> this.contentPostFilters.push(this._normalizeFontStyle); } //this.contentDomPostFilters.push(this._postDomFixUrlAttributes); 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: "", // _content: [private] 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="". 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__%%**@@", // onLoadDeferred: [protected] dojo.Deferred // Deferred which is fired when the editor finishes loading 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."); } 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"], // 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(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 //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 var div = dojo.doc.createElement('div'); dojo.style(div, { position: "absolute", top: "-2000px" }); dojo.doc.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.nextSibling; } dojo.body().removeChild(div); }, 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. // // A dojo.Deferred object is created at this.onLoadDeferred, and // users may attach to it to be informed when the rich-text area // initialization is finalized. // 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 ]); this._content = ""; 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){ dojo.connect(ta.form, "onsubmit", this, function(){ // FIXME: should we be calling close() here instead? 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.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 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); // 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.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); // TODO: this has no effect break; } } } // TODO: this is troublesome if this editor has been destroyed, should have global handler. // TODO: need to clear <textarea> in global handler 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){ // 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); // 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 || (!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 = " "; } 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;") + "}\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 ", (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(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){ this.disabled = value; if(!this.isLoaded){ return; } // this method requires init to be complete value = !!value; 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 && (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 var tabStop = (this.tabStop = dojo.doc.createElement('<div tabIndex=-1>')); this.editingArea.appendChild(tabStop); 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); 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"); } 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.savedContent = 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 " " 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.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 _c=this.getValue(true); if(_c!=this.savedContent){ this.onChange(_c); this.savedContent=_c; } }, _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: why is this needed - should we deprecate this ? 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); }, 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; var webkit420 = 1 << 4; function isSupportedBy(browsers){ return { ie: Boolean(browsers & ie), mozilla: Boolean(browsers & mozilla), webkit: Boolean(browsers & webkit), webkit420: Boolean(browsers & webkit420), 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 | webkit420); 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 | webkit420); 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.isWebKit > 420 && supportedBy.webkit420) || (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 == "copy"){ command = "cut"; }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 attr('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; // Use to avoid webkit problems where editor is disabled until the user clicks it if(!html && dojo.isWebKit){ html = " "; } node.innerHTML = html; this._preDomFilterContent(node); } this.onDisplayChanged(); }, 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 = " "; } 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); } }, _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> </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 closed // tags: // private var saveTextarea = dojo.byId(dijit._scopeName + "._editor.RichText.savedContent"); if(saveTextarea.value){ saveTextarea.value += this._SEPARATOR; } saveTextarea.value += this.name + ":" + this.getValue(true); }, escapeXml: function(/*String*/ str, /*Boolean*/ noSingleQuotes){ // summary: // Adds escape sequences for special characters in XML. // Optionally skips escapes for single quotes // tags: // private str = str.replace(/&/gm, "&").replace(/</gm, "<").replace(/>/gm, ">").replace(/"/gm, """); if(!noSingleQuotes){ str = str.replace(/'/gm, "'"); } 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 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._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 = save ? this._content : this.savedContent; dojo.destroy(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; // } // } // Note that this destroys the iframe this.domNode.innerHTML = save ? 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 changed; // Boolean: whether the content has been modified }, destroy: function(){ if(!this.isClosed){ this.close(false); } this.inherited(arguments); }, _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, ''); 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; }, 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 } }); }