]> git.pond.sub.org Git - eow/blob - static/dojo-release-1.1.1/dijit/_editor/RichText.js
Comment class stub
[eow] / static / dojo-release-1.1.1 / dijit / _editor / RichText.js
1 if(!dojo._hasResource["dijit._editor.RichText"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2 dojo._hasResource["dijit._editor.RichText"] = true;
3 dojo.provide("dijit._editor.RichText");
4
5 dojo.require("dijit._Widget");
6 dojo.require("dijit._editor.selection");
7 dojo.require("dijit._editor.html");
8 dojo.require("dojo.i18n");
9 dojo.requireLocalization("dijit.form", "Textarea", null, "zh,pt,da,tr,ru,de,ROOT,sv,ja,he,fi,nb,el,ar,pt-pt,cs,fr,es,ko,nl,zh-tw,pl,it,hu");
10
11 // used to restore content when user leaves this page then comes back
12 // but do not try doing dojo.doc.write if we are using xd loading.
13 // dojo.doc.write will only work if RichText.js is included in the dojo.js
14 // file. If it is included in dojo.js and you want to allow rich text saving
15 // for back/forward actions, then set dojo.config.allowXdRichTextSave = true.
16 if(!dojo.config["useXDomain"] || dojo.config["allowXdRichTextSave"]){
17         if(dojo._postLoad){
18                 (function(){
19                         var savetextarea = dojo.doc.createElement('textarea');
20                         savetextarea.id = dijit._scopeName + "._editor.RichText.savedContent";
21                         var s = savetextarea.style;
22                         s.display='none';
23                         s.position='absolute';
24                         s.top="-100px";
25                         s.left="-100px";
26                         s.height="3px";
27                         s.width="3px";
28                         dojo.body().appendChild(savetextarea);
29                 })();
30         }else{
31                 //dojo.body() is not available before onLoad is fired
32                 try{
33                         dojo.doc.write('<textarea id="' + dijit._scopeName + '._editor.RichText.savedContent" ' +
34                                 'style="display:none;position:absolute;top:-100px;left:-100px;height:3px;width:3px;overflow:hidden;"></textarea>');
35                 }catch(e){ }
36         }
37 }
38 dojo.declare("dijit._editor.RichText", dijit._Widget, {
39         constructor: function(){
40                 // summary:
41                 //              dijit._editor.RichText is the core of the WYSIWYG editor in dojo, which
42                 //              provides the basic editing features. It also encapsulates the differences
43                 //              of different js engines for various browsers
44                 //
45                 // contentPreFilters: Array
46                 //              pre content filter function register array.
47                 //              these filters will be executed before the actual
48                 //              editing area get the html content
49                 this.contentPreFilters = [];
50
51                 // contentPostFilters: Array
52                 //              post content filter function register array.
53                 //              these will be used on the resulting html
54                 //              from contentDomPostFilters. The resuling
55                 //              content is the final html (returned by getValue())
56                 this.contentPostFilters = [];
57
58                 // contentDomPreFilters: Array
59                 //              pre content dom filter function register array.
60                 //              these filters are applied after the result from
61                 //              contentPreFilters are set to the editing area
62                 this.contentDomPreFilters = [];
63
64                 // contentDomPostFilters: Array
65                 //              post content dom filter function register array.
66                 //              these filters are executed on the editing area dom
67                 //              the result from these will be passed to contentPostFilters
68                 this.contentDomPostFilters = [];
69
70                 // editingAreaStyleSheets: Array
71                 //              array to store all the stylesheets applied to the editing area
72                 this.editingAreaStyleSheets=[];
73
74                 this._keyHandlers = {};
75                 this.contentPreFilters.push(dojo.hitch(this, "_preFixUrlAttributes"));
76                 if(dojo.isMoz){
77                         this.contentPreFilters.push(this._fixContentForMoz);
78                         this.contentPostFilters.push(this._removeMozBogus);
79                 }else if(dojo.isSafari){
80                         this.contentPostFilters.push(this._removeSafariBogus);
81                 }
82                 //this.contentDomPostFilters.push(this._postDomFixUrlAttributes);
83
84                 this.onLoadDeferred = new dojo.Deferred();
85         },
86
87         // inheritWidth: Boolean
88         //              whether to inherit the parent's width or simply use 100%
89         inheritWidth: false,
90
91         // focusOnLoad: Boolean
92         //              whether focusing into this instance of richtext when page onload
93         focusOnLoad: false,
94
95         // name: String
96         //              If a save name is specified the content is saved and restored when the user
97         //              leave this page can come back, or if the editor is not properly closed after
98         //              editing has started.
99         name: "",
100
101         // styleSheets: String
102         //              semicolon (";") separated list of css files for the editing area
103         styleSheets: "",
104
105         // _content: String
106         //              temporary content storage
107         _content: "",
108
109         // height: String
110         //              set height to fix the editor at a specific height, with scrolling.
111         //              By default, this is 300px. If you want to have the editor always
112         //              resizes to accommodate the content, use AlwaysShowToolbar plugin
113         //              and set height=""
114         height: "300px",
115
116         // minHeight: String
117         //              The minimum height that the editor should have
118         minHeight: "1em",
119         
120         // isClosed: Boolean
121         isClosed: true,
122
123         // isLoaded: Boolean
124         isLoaded: false,
125
126         // _SEPARATOR: String
127         //              used to concat contents from multiple textareas into a single string
128         _SEPARATOR: "@@**%%__RICHTEXTBOUNDRY__%%**@@",
129
130         // onLoadDeferred: dojo.Deferred
131         //              deferred which is fired when the editor finishes loading
132         onLoadDeferred: null,
133
134         postCreate: function(){
135                 // summary: init
136                 dojo.publish(dijit._scopeName + "._editor.RichText::init", [this]);
137                 this.open();
138                 this.setupDefaultShortcuts();
139         },
140
141         setupDefaultShortcuts: function(){
142                 // summary: add some default key handlers
143                 // description:
144                 //              Overwrite this to setup your own handlers. The default
145                 //              implementation does not use Editor commands, but directly
146                 //              executes the builtin commands within the underlying browser
147                 //              support.
148                 var exec = function(cmd, arg){
149                         return arguments.length == 1 ? function(){ this.execCommand(cmd); } :
150                                 function(){ this.execCommand(cmd, arg); };
151                 };
152
153                 var ctrlKeyHandlers = { b: exec("bold"),
154                         i: exec("italic"),
155                         u: exec("underline"),
156                         a: exec("selectall"),
157                         s: function(){ this.save(true); },
158
159                         "1": exec("formatblock", "h1"),
160                         "2": exec("formatblock", "h2"),
161                         "3": exec("formatblock", "h3"),
162                         "4": exec("formatblock", "h4"),
163
164                         "\\": exec("insertunorderedlist") };
165
166                 if(!dojo.isIE){
167                         ctrlKeyHandlers.Z = exec("redo"); //FIXME: undo?
168                 }
169
170                 for(var key in ctrlKeyHandlers){
171                         this.addKeyHandler(key, this.KEY_CTRL, ctrlKeyHandlers[key]);
172                 }
173         },
174
175         // events: Array
176         //               events which should be connected to the underlying editing area
177         events: ["onKeyPress", "onKeyDown", "onKeyUp", "onClick"],
178
179         // events: Array
180         //               events which should be connected to the underlying editing
181         //               area, events in this array will be addListener with
182         //               capture=true
183         captureEvents: [],
184
185         _editorCommandsLocalized: false,
186         _localizeEditorCommands: function(){
187                 if(this._editorCommandsLocalized){
188                         return;
189                 }
190                 this._editorCommandsLocalized = true;
191
192                 //in IE, names for blockformat is locale dependent, so we cache the values here
193
194                 //if the normal way fails, we try the hard way to get the list
195
196                 //do not use _cacheLocalBlockFormatNames here, as it will
197                 //trigger security warning in IE7
198
199                 //in the array below, ul can not come directly after ol,
200                 //otherwise the queryCommandValue returns Normal for it
201                 var formats = ['p', 'pre', 'address', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'div', 'ul'];
202                 var localhtml = "", format, i=0;
203                 while((format=formats[i++])){
204                         if(format.charAt(1) != 'l'){
205                                 localhtml += "<"+format+"><span>content</span></"+format+">";
206                         }else{
207                                 localhtml += "<"+format+"><li>content</li></"+format+">";
208                         }
209                 }
210                 //queryCommandValue returns empty if we hide editNode, so move it out of screen temporary
211                 var div=dojo.doc.createElement('div');
212                 div.style.position = "absolute";
213                 div.style.left = "-2000px";
214                 div.style.top = "-2000px";
215                 dojo.doc.body.appendChild(div);
216                 div.innerHTML = localhtml;
217                 var node = div.firstChild;
218                 while(node){
219                         dijit._editor.selection.selectElement(node.firstChild);
220                         dojo.withGlobal(this.window, "selectElement", dijit._editor.selection, [node.firstChild]);
221                         var nativename = node.tagName.toLowerCase();
222                         this._local2NativeFormatNames[nativename] = dojo.doc.queryCommandValue("formatblock");//this.queryCommandValue("formatblock");
223                         this._native2LocalFormatNames[this._local2NativeFormatNames[nativename]] = nativename;
224                         node = node.nextSibling;
225                 }
226                 dojo.doc.body.removeChild(div);
227         },
228
229         open: function(/*DomNode?*/element){
230                 // summary:
231                 //              Transforms the node referenced in this.domNode into a rich text editing
232                 //              node. This will result in the creation and replacement with an <iframe>
233                 //              if designMode(FF)/contentEditable(IE) is used.
234
235                 if((!this.onLoadDeferred)||(this.onLoadDeferred.fired >= 0)){
236                         this.onLoadDeferred = new dojo.Deferred();
237                 }
238
239                 if(!this.isClosed){ this.close(); }
240                 dojo.publish(dijit._scopeName + "._editor.RichText::open", [ this ]);
241
242                 this._content = "";
243                 if((arguments.length == 1)&&(element["nodeName"])){ this.domNode = element; } // else unchanged
244
245                 var html;
246                 if(     (this.domNode["nodeName"])&&
247                         (this.domNode.nodeName.toLowerCase() == "textarea")){
248                         // if we were created from a textarea, then we need to create a
249                         // new editing harness node.
250                         this.textarea = this.domNode;
251                         this.name=this.textarea.name;
252                         html = this._preFilterContent(this.textarea.value);
253                         this.domNode = dojo.doc.createElement("div");
254                         this.domNode.setAttribute('widgetId',this.id);
255                         this.textarea.removeAttribute('widgetId');
256                         this.domNode.cssText = this.textarea.cssText;
257                         this.domNode.className += " "+this.textarea.className;
258                         dojo.place(this.domNode, this.textarea, "before");
259                         var tmpFunc = dojo.hitch(this, function(){
260                                 //some browsers refuse to submit display=none textarea, so
261                                 //move the textarea out of screen instead
262                                 dojo.attr(this.textarea, 'tabIndex', '-1');
263                                 with(this.textarea.style){
264                                         display = "block";
265                                         position = "absolute";
266                                         left = top = "-1000px";
267
268                                         if(dojo.isIE){ //nasty IE bug: abnormal formatting if overflow is not hidden
269                                                 this.__overflow = overflow;
270                                                 overflow = "hidden";
271                                         }
272                                 }
273                         });
274                         if(dojo.isIE){
275                                 setTimeout(tmpFunc, 10);
276                         }else{
277                                 tmpFunc();
278                         }
279
280                         // this.domNode.innerHTML = html;
281
282 //                              if(this.textarea.form){
283 //                                      // FIXME: port: this used to be before advice!!!
284 //                                      dojo.connect(this.textarea.form, "onsubmit", this, function(){
285 //                                              // FIXME: should we be calling close() here instead?
286 //                                              this.textarea.value = this.getValue();
287 //                                      });
288 //                              }
289                 }else{
290                         html = this._preFilterContent(dijit._editor.getChildrenHtml(this.domNode));
291                         this.domNode.innerHTML = '';
292                 }
293                 if(html == ""){ html = "&nbsp;"; }
294
295                 var content = dojo.contentBox(this.domNode);
296                 // var content = dojo.contentBox(this.srcNodeRef);
297                 this._oldHeight = content.h;
298                 this._oldWidth = content.w;
299
300                 // If we're a list item we have to put in a blank line to force the
301                 // bullet to nicely align at the top of text
302                 if(     (this.domNode["nodeName"]) &&
303                         (this.domNode.nodeName == "LI") ){
304                         this.domNode.innerHTML = " <br>";
305                 }
306
307                 this.editingArea = dojo.doc.createElement("div");
308                 this.domNode.appendChild(this.editingArea);
309
310                 if(this.name != "" && (!dojo.config["useXDomain"] || dojo.config["allowXdRichTextSave"])){
311                         var saveTextarea = dojo.byId(dijit._scopeName + "._editor.RichText.savedContent");
312                         if(saveTextarea.value != ""){
313                                 var datas = saveTextarea.value.split(this._SEPARATOR), i=0, dat;
314                                 while((dat=datas[i++])){
315                                         var data = dat.split(":");
316                                         if(data[0] == this.name){
317                                                 html = data[1];
318                                                 datas.splice(i, 1);
319                                                 break;
320                                         }
321                                 }
322                         }
323
324                         // FIXME: need to do something different for Opera/Safari
325                         this.connect(window, "onbeforeunload", "_saveContent");
326                         // dojo.connect(window, "onunload", this, "_saveContent");
327                 }
328
329                 this.isClosed = false;
330                 // Safari's selections go all out of whack if we do it inline,
331                 // so for now IE is our only hero
332                 //if(typeof dojo.doc.body.contentEditable != "undefined"){
333                 if(dojo.isIE || dojo.isSafari || dojo.isOpera){ // contentEditable, easy                
334
335                         if(dojo.config["useXDomain"] && !dojo.config["dojoBlankHtmlUrl"]){
336                                 console.debug("dijit._editor.RichText: When using cross-domain Dojo builds,"
337                                 + " please save dojo/resources/blank.html to your domain and set djConfig.dojoBlankHtmlUrl"
338                                 + " to the path on your domain to blank.html");
339                         }
340
341                         var burl = dojo.config["dojoBlankHtmlUrl"] || (dojo.moduleUrl("dojo", "resources/blank.html")+"");
342                         var ifr = this.editorObject = this.iframe = dojo.doc.createElement('iframe');
343                         ifr.id = this.id+"_iframe";
344                         ifr.src = burl;
345                         ifr.style.border = "none";
346                         ifr.style.width = "100%";
347                         ifr.frameBorder = 0;
348                         // ifr.style.scrolling = this.height ? "auto" : "vertical";
349                         this.editingArea.appendChild(ifr);
350                         var h = null; // set later in non-ie6 branch
351                         var loadFunc = dojo.hitch( this, function(){
352                                 if(h){ dojo.disconnect(h); h = null; }
353                                 this.window = ifr.contentWindow;
354                                 var d = this.document = this.window.document;
355                                 d.open();
356                                 d.write(this._getIframeDocTxt(html));
357                                 d.close();
358
359                                 if(dojo.isIE >= 7){
360                                         if(this.height){
361                                                 ifr.style.height = this.height;
362                                         }
363                                         if(this.minHeight){
364                                                 ifr.style.minHeight = this.minHeight;
365                                         }
366                                 }else{
367                                         ifr.style.height = this.height ? this.height : this.minHeight;
368                                 }
369
370                                 if(dojo.isIE){
371                                         this._localizeEditorCommands();
372                                 }
373
374                                 this.onLoad();
375                                 this.savedContent = this.getValue(true);
376                         });
377                         if(dojo.isIE && dojo.isIE < 7){ // IE 6 is a steaming pile...
378                                 var t = setInterval(function(){
379                                         if(ifr.contentWindow.isLoaded){
380                                                 clearInterval(t);
381                                                 loadFunc();
382                                         }
383                                 }, 100);
384                         }else{ // blissful sanity!
385                                 h = dojo.connect(
386                                         ((dojo.isIE) ? ifr.contentWindow : ifr), "onload", loadFunc
387                                 );
388                         }
389                 }else{ // designMode in iframe
390                         this._drawIframe(html);
391                         this.savedContent = this.getValue(true);
392                 }
393
394                 // TODO: this is a guess at the default line-height, kinda works
395                 if(this.domNode.nodeName == "LI"){ this.domNode.lastChild.style.marginTop = "-1.2em"; }
396                 this.domNode.className += " RichTextEditable";
397         },
398
399         //static cache variables shared among all instance of this class
400         _local2NativeFormatNames: {},
401         _native2LocalFormatNames: {},
402         _localizedIframeTitles: null,
403
404         _getIframeDocTxt: function(/* String */ html){
405                 var _cs = dojo.getComputedStyle(this.domNode);
406                 if(dojo.isIE || (!this.height && !dojo.isMoz)){
407                         html="<div>"+html+"</div>";
408                 }
409                 var font = [ _cs.fontWeight, _cs.fontSize, _cs.fontFamily ].join(" ");
410
411                 // line height is tricky - applying a units value will mess things up.
412                 // if we can't get a non-units value, bail out.
413                 var lineHeight = _cs.lineHeight;
414                 if(lineHeight.indexOf("px") >= 0){
415                         lineHeight = parseFloat(lineHeight)/parseFloat(_cs.fontSize);
416                         // console.debug(lineHeight);
417                 }else if(lineHeight.indexOf("em")>=0){
418                         lineHeight = parseFloat(lineHeight);
419                 }else{
420                         lineHeight = "1.0";
421                 }
422                 return [
423                         this.isLeftToRight() ? "<html><head>" : "<html dir='rtl'><head>",
424                         (dojo.isMoz ? "<title>" + this._localizedIframeTitles.iframeEditTitle + "</title>" : ""),
425                         "<style>",
426                         "body,html {",
427                         "       background:transparent;",
428                         "       font:", font, ";",
429                         "       padding: 1em 0 0 0;",
430                         "       margin: -1em 0 0 0;", // remove extraneous vertical scrollbar on safari and firefox
431                         "       height: 100%;",
432                         "}",
433                         // TODO: left positioning will cause contents to disappear out of view
434                         //         if it gets too wide for the visible area
435                         "body{",
436                         "       top:0px; left:0px; right:0px;",
437                                 ((this.height||dojo.isOpera) ? "" : "position: fixed;"),
438                         // FIXME: IE 6 won't understand min-height?
439                         "       min-height:", this.minHeight, ";",
440                         "       line-height:", lineHeight,
441                         "}",
442                         "p{ margin: 1em 0 !important; }",
443                         (this.height ? // height:auto undoes the height:100%
444                                 "" : "body,html{height:auto;overflow-y:hidden;/*for IE*/} body > div {overflow-x:auto;/*for FF to show vertical scrollbar*/}"
445                         ),
446                         "li > ul:-moz-first-node, li > ol:-moz-first-node{ padding-top: 1.2em; } ",
447                         "li{ min-height:1.2em; }",
448                         "</style>",
449                         this._applyEditingAreaStyleSheets(),
450                         "</head><body>"+html+"</body></html>"
451                 ].join(""); // String
452         },
453
454         _drawIframe: function(/*String*/html){
455                 // summary:
456                 //              Draws an iFrame using the existing one if one exists.
457                 //              Used by Mozilla, Safari, and Opera
458
459                 if(!this.iframe){
460                         var ifr = this.iframe = dojo.doc.createElement("iframe");
461                         ifr.id=this.id;
462                         // this.iframe.src = "about:blank";
463                         // dojo.doc.body.appendChild(this.iframe);
464                         // console.debug(this.iframe.contentDocument.open());
465                         // dojo.body().appendChild(this.iframe);
466                         var ifrs = ifr.style;
467                         // ifrs.border = "1px solid black";
468                         ifrs.border = "none";
469                         ifrs.lineHeight = "0"; // squash line height
470                         ifrs.verticalAlign = "bottom";
471 //                      ifrs.scrolling = this.height ? "auto" : "vertical";
472                         this.editorObject = this.iframe;
473                         // get screen reader text for mozilla here, too
474                         this._localizedIframeTitles = dojo.i18n.getLocalization("dijit.form", "Textarea");
475                         // need to find any associated label element and update iframe document title
476                         var label=dojo.query('label[for="'+this.id+'"]');
477                         if(label.length){
478                                 this._localizedIframeTitles.iframeEditTitle = label[0].innerHTML + " " + this._localizedIframeTitles.iframeEditTitle;
479                         }
480                 }
481                 // opera likes this to be outside the with block
482                 //      this.iframe.src = "javascript:void(0)";//dojo.uri.dojoUri("src/widget/templates/richtextframe.html") + ((dojo.doc.domain != currentDomain) ? ("#"+dojo.doc.domain) : "");
483                 this.iframe.style.width = this.inheritWidth ? this._oldWidth : "100%";
484
485                 if(this.height){
486                         this.iframe.style.height = this.height;
487                 }else{
488                         this.iframe.height = this._oldHeight;
489                 }
490
491                 var tmpContent;
492                 if(this.textarea){
493                         tmpContent = this.srcNodeRef;
494                 }else{
495                         tmpContent = dojo.doc.createElement('div');
496                         tmpContent.style.display="none";
497                         tmpContent.innerHTML = html;
498                         //append tmpContent to under the current domNode so that the margin
499                         //calculation below is correct
500                         this.editingArea.appendChild(tmpContent);
501                 }
502
503                 this.editingArea.appendChild(this.iframe);
504
505                 //do we want to show the content before the editing area finish loading here?
506                 //if external style sheets are used for the editing area, the appearance now
507                 //and after loading of the editing area won't be the same (and padding/margin
508                 //calculation above may not be accurate)
509                 //      tmpContent.style.display = "none";
510                 //      this.editingArea.appendChild(this.iframe);
511
512                 var _iframeInitialized = false;
513                 // console.debug(this.iframe);
514                 // var contentDoc = this.iframe.contentWindow.document;
515
516
517                 // note that on Safari lower than 420+, we have to get the iframe
518                 // by ID in order to get something w/ a contentDocument property
519
520                 var contentDoc = this.iframe.contentDocument;
521                 contentDoc.open();
522                 if(dojo.isAIR){
523                         contentDoc.body.innerHTML = html;
524                 }else{
525                         contentDoc.write(this._getIframeDocTxt(html));
526                 }
527                 contentDoc.close();
528
529                 // now we wait for onload. Janky hack!
530                 var ifrFunc = dojo.hitch(this, function(){
531                         if(!_iframeInitialized){
532                                 _iframeInitialized = true;
533                         }else{ return; }
534                         if(!this.editNode){
535                                 try{
536                                         if(this.iframe.contentWindow){
537                                                 this.window = this.iframe.contentWindow;
538                                                 this.document = this.iframe.contentWindow.document
539                                         }else if(this.iframe.contentDocument){
540                                                 // for opera
541                                                 this.window = this.iframe.contentDocument.window;
542                                                 this.document = this.iframe.contentDocument;
543                                         }
544                                         if(!this.document.body){
545                                                 throw 'Error';
546                                         }
547                                 }catch(e){
548                                         setTimeout(ifrFunc,500);
549                                         _iframeInitialized = false;
550                                         return;
551                                 }
552
553                                 dojo._destroyElement(tmpContent);
554                                 this.onLoad();
555                         }else{
556                                 dojo._destroyElement(tmpContent);
557                                 this.editNode.innerHTML = html;
558                                 this.onDisplayChanged();
559                         }
560                         this._preDomFilterContent(this.editNode);
561                 });
562
563                 ifrFunc();
564         },
565
566         _applyEditingAreaStyleSheets: function(){
567                 // summary:
568                 //              apply the specified css files in styleSheets
569                 var files = [];
570                 if(this.styleSheets){
571                         files = this.styleSheets.split(';');
572                         this.styleSheets = '';
573                 }
574
575                 //empty this.editingAreaStyleSheets here, as it will be filled in addStyleSheet
576                 files = files.concat(this.editingAreaStyleSheets);
577                 this.editingAreaStyleSheets = [];
578
579                 var text='', i=0, url;
580                 while((url=files[i++])){
581                         var abstring = (new dojo._Url(dojo.global.location, url)).toString();
582                         this.editingAreaStyleSheets.push(abstring);
583                         text += '<link rel="stylesheet" type="text/css" href="'+abstring+'"/>'
584                 }
585                 return text;
586         },
587
588         addStyleSheet: function(/*dojo._Url*/uri){
589                 // summary:
590                 //              add an external stylesheet for the editing area
591                 // uri: a dojo.uri.Uri pointing to the url of the external css file
592                 var url=uri.toString();
593
594                 //if uri is relative, then convert it to absolute so that it can be resolved correctly in iframe
595                 if(url.charAt(0) == '.' || (url.charAt(0) != '/' && !uri.host)){
596                         url = (new dojo._Url(dojo.global.location, url)).toString();
597                 }
598
599                 if(dojo.indexOf(this.editingAreaStyleSheets, url) > -1){
600 //                      console.debug("dijit._editor.RichText.addStyleSheet: Style sheet "+url+" is already applied");
601                         return;
602                 }
603
604                 this.editingAreaStyleSheets.push(url);
605                 if(this.document.createStyleSheet){ //IE
606                         this.document.createStyleSheet(url);
607                 }else{ //other browser
608                         var head = this.document.getElementsByTagName("head")[0];
609                         var stylesheet = this.document.createElement("link");
610                         with(stylesheet){
611                                 rel="stylesheet";
612                                 type="text/css";
613                                 href=url;
614                         }
615                         head.appendChild(stylesheet);
616                 }
617         },
618
619         removeStyleSheet: function(/*dojo._Url*/uri){
620                 // summary:
621                 //              remove an external stylesheet for the editing area
622                 var url=uri.toString();
623                 //if uri is relative, then convert it to absolute so that it can be resolved correctly in iframe
624                 if(url.charAt(0) == '.' || (url.charAt(0) != '/' && !uri.host)){
625                         url = (new dojo._Url(dojo.global.location, url)).toString();
626                 }
627                 var index = dojo.indexOf(this.editingAreaStyleSheets, url);
628                 if(index == -1){
629 //                      console.debug("dijit._editor.RichText.removeStyleSheet: Style sheet "+url+" has not been applied");
630                         return;
631                 }
632                 delete this.editingAreaStyleSheets[index];
633                 dojo.withGlobal(this.window,'query', dojo, ['link:[href="'+url+'"]']).orphan()
634         },
635
636         disabled: true,
637         _mozSettingProps: ['styleWithCSS','insertBrOnReturn'],
638         setDisabled: function(/*Boolean*/ disabled){
639                 if(dojo.isIE || dojo.isSafari || dojo.isOpera){
640                         if(dojo.isIE){ this.editNode.unselectable = "on"; } // prevent IE from setting focus
641                         this.editNode.contentEditable = !disabled;
642                         if(dojo.isIE){
643                                 var _this = this;
644                                 setTimeout(function(){ _this.editNode.unselectable = "off"; }, 0);
645                         }
646                 }else{ //moz
647                         if(disabled){
648                                 //AP: why isn't this set in the constructor, or put in mozSettingProps as a hash?
649                                 this._mozSettings=[false,this.blockNodeForEnter==='BR'];
650                         }
651                         this.document.designMode=(disabled?'off':'on');
652                         if(!disabled && this._mozSettings){
653                                 dojo.forEach(this._mozSettingProps, function(s,i){
654                                         this.document.execCommand(s,false,this._mozSettings[i]);
655                                 },this);
656                         }
657 //                      this.document.execCommand('contentReadOnly', false, disabled);
658 //                              if(disabled){
659 //                                      this.blur(); //to remove the blinking caret
660 //                              }
661                 }
662                 this.disabled = disabled;
663         },
664
665 /* Event handlers
666  *****************/
667
668         _isResized: function(){ return false; },
669
670         onLoad: function(/* Event */ e){
671                 // summary: handler after the content of the document finishes loading
672                 this.isLoaded = true;
673                 if(!this.window.__registeredWindow){
674                         this.window.__registeredWindow=true;
675                         dijit.registerWin(this.window);
676                 }
677                 if(!dojo.isIE && (this.height || dojo.isMoz)){
678                         this.editNode=this.document.body;
679                 }else{
680                         this.editNode=this.document.body.firstChild;
681                         var _this = this;
682                         if(dojo.isIE){ // #4996 IE wants to focus the BODY tag
683                                 var tabStop = this.tabStop = dojo.doc.createElement('<div tabIndex=-1>');
684                                 this.editingArea.appendChild(tabStop);
685                                 this.iframe.onfocus = function(){ _this.editNode.setActive(); }
686                         }
687                 }
688
689                 try{
690                         this.setDisabled(false);
691                 }catch(e){
692                         // Firefox throws an exception if the editor is initially hidden
693                         // so, if this fails, try again onClick by adding "once" advice
694                         var handle = dojo.connect(this, "onClick", this, function(){
695                                 this.setDisabled(false);
696                                 dojo.disconnect(handle);
697                         });
698                 }
699
700                 this._preDomFilterContent(this.editNode);
701
702                 var events=this.events.concat(this.captureEvents),i=0,et;
703                 while((et=events[i++])){
704                         this.connect(this.document, et.toLowerCase(), et);
705                 }
706                 if(!dojo.isIE){
707                         try{ // sanity check for Mozilla
708                         //AP: what's the point of this?
709 //                                      this.document.execCommand("useCSS", false, true); // old moz call
710                                 this.document.execCommand("styleWithCSS", false, false); // new moz call
711                                 //this.document.execCommand("insertBrOnReturn", false, false); // new moz call
712                         }catch(e2){ }
713                         // FIXME: when scrollbars appear/disappear this needs to be fired
714                 }else{ // IE contentEditable
715                         // give the node Layout on IE
716                         this.connect(this.document, "onmousedown", "_onMouseDown"); // #4996 fix focus
717                         this.editNode.style.zoom = 1.0;
718                 }
719
720                 if(this.focusOnLoad){
721                         setTimeout(dojo.hitch(this, "focus"), 0); // have to wait for IE to set unselectable=off
722                 }
723
724                 this.onDisplayChanged(e);
725                 if(this.onLoadDeferred){
726                         this.onLoadDeferred.callback(true);
727                 }
728         },
729
730         onKeyDown: function(/* Event */ e){
731                 // summary: Fired on keydown
732
733                 // we need this event at the moment to get the events from control keys
734                 // such as the backspace. It might be possible to add this to Dojo, so that
735                 // keyPress events can be emulated by the keyDown and keyUp detection.
736                 if(dojo.isIE){
737                         if(e.keyCode == dojo.keys.TAB && e.shiftKey && !e.ctrlKey && !e.altKey){
738                                 // focus the BODY so the browser will tab away from it instead
739                                 this.iframe.focus();
740                         }else if(e.keyCode == dojo.keys.TAB && !e.shiftKey && !e.ctrlKey && !e.altKey){
741                                 // focus the BODY so the browser will tab away from it instead
742                                 this.tabStop.focus();
743                         }else if(e.keyCode === dojo.keys.BACKSPACE && this.document.selection.type === "Control"){
744                                 // IE has a bug where if a non-text object is selected in the editor,
745                   // hitting backspace would act as if the browser's back button was
746                   // clicked instead of deleting the object. see #1069
747                                 dojo.stopEvent(e);
748                                 this.execCommand("delete");
749                         }else if((65 <= e.keyCode&&e.keyCode <= 90) ||
750                                 (e.keyCode>=37&&e.keyCode<=40) // FIXME: get this from connect() instead!
751                         ){ //arrow keys
752                                 e.charCode = e.keyCode;
753                                 this.onKeyPress(e);
754                         }
755                 }else if(dojo.isMoz){
756                         if(e.keyCode == dojo.keys.TAB && !e.shiftKey && !e.ctrlKey && !e.altKey && this.iframe){
757                                 // update iframe document title for screen reader
758                                 this.iframe.contentDocument.title = this._localizedIframeTitles.iframeFocusTitle;
759                                 
760                                 // Place focus on the iframe. A subsequent tab or shift tab will put focus
761                                 // on the correct control.
762                                 this.iframe.focus();  // this.focus(); won't work
763                                 dojo.stopEvent(e);
764                         }else if(e.keyCode == dojo.keys.TAB && e.shiftKey){
765                                 // if there is a toolbar, set focus to it, otherwise ignore
766                                 if(this.toolbar){
767                                         this.toolbar.focus();
768                                 }
769                                 dojo.stopEvent(e);
770                         }
771                 }
772         },
773
774         onKeyUp: function(e){
775                 // summary: Fired on keyup
776                 return;
777         },
778
779         KEY_CTRL: 1,
780         KEY_SHIFT: 2,
781
782         onKeyPress: function(e){
783                 // summary: Fired on keypress
784
785                 // handle the various key events
786                 var modifiers = (e.ctrlKey && !e.altKey) ? this.KEY_CTRL : 0 | e.shiftKey ? this.KEY_SHIFT : 0;
787
788                 var key = e.keyChar || e.keyCode;
789                 if(this._keyHandlers[key]){
790                         // console.debug("char:", e.key);
791                         var handlers = this._keyHandlers[key], i = 0, h;
792                         while((h = handlers[i++])){
793                                 if(modifiers == h.modifiers){
794                                         if(!h.handler.apply(this,arguments)){
795                                                 e.preventDefault();
796                                         }
797                                         break;
798                                 }
799                         }
800                 }
801
802                 // function call after the character has been inserted
803                 setTimeout(dojo.hitch(this, function(){
804                         this.onKeyPressed(e);
805                 }), 1);
806         },
807
808         addKeyHandler: function(/*String*/key, /*Int*/modifiers, /*Function*/handler){
809                 // summary: add a handler for a keyboard shortcut
810                 if(!dojo.isArray(this._keyHandlers[key])){ this._keyHandlers[key] = []; }
811                 this._keyHandlers[key].push({
812                         modifiers: modifiers || 0,
813                         handler: handler
814                 });
815         },
816
817         onKeyPressed: function(/*Event*/e){
818                 this.onDisplayChanged(/*e*/); // can't pass in e
819         },
820
821         onClick: function(/*Event*/e){
822 //              console.info('onClick',this._tryDesignModeOn);
823                 this.onDisplayChanged(e);
824         },
825
826         _onMouseDown: function(/*Event*/e){ // IE only to prevent 2 clicks to focus
827                 if(!this._focused && !this.disabled){
828                         this.focus();
829                 }
830         },
831
832         _onBlur: function(e){
833                 this.inherited(arguments);
834                 var _c=this.getValue(true);
835                 if(_c!=this.savedContent){
836                         this.onChange(_c);
837                         this.savedContent=_c;
838                 }
839                 if(dojo.isMoz && this.iframe){
840                         this.iframe.contentDocument.title = this._localizedIframeTitles.iframeEditTitle;
841                 } 
842         },
843         _initialFocus: true,
844         _onFocus: function(/*Event*/e){
845                 // summary: Fired on focus
846                 this.inherited(arguments);
847                 if(dojo.isMoz && this._initialFocus){
848                         this._initialFocus = false;
849                         if(this.editNode.innerHTML.replace(/^\s+|\s+$/g, "") == "&nbsp;"){
850                                 this.placeCursorAtStart();
851 //                                      this.execCommand("selectall");
852 //                                      this.window.getSelection().collapseToStart();
853                         }
854                 }
855         },
856
857         // TODO: why is this needed - should we deprecate this ?
858         blur: function(){
859                 // summary: remove focus from this instance
860                 if(!dojo.isIE && this.window.document.documentElement && this.window.document.documentElement.focus){
861                         this.window.document.documentElement.focus();
862                 }else if(dojo.doc.body.focus){
863                         dojo.doc.body.focus();
864                 }
865         },
866
867         focus: function(){
868                 // summary: move focus to this instance
869                 if(!dojo.isIE){
870                         dijit.focus(this.iframe);
871                 }else if(this.editNode && this.editNode.focus){
872                         // editNode may be hidden in display:none div, lets just punt in this case
873                         //this.editNode.focus(); -> causes IE to scroll always (strict and quirks mode) to the top the Iframe 
874                         // if we fire the event manually and let the browser handle the focusing, the latest  
875                         // cursor position is focused like in FF                         
876                         this.iframe.fireEvent('onfocus', document.createEventObject()); // createEventObject only in IE 
877 //              }else{
878 // TODO: should we throw here?
879 //                      console.debug("Have no idea how to focus into the editor!");
880                 }
881         },
882
883 //              _lastUpdate: 0,
884         updateInterval: 200,
885         _updateTimer: null,
886         onDisplayChanged: function(/*Event*/e){
887                 // summary:
888                 //              This event will be fired everytime the display context
889                 //              changes and the result needs to be reflected in the UI.
890                 // description:
891                 //              If you don't want to have update too often,
892                 //              onNormalizedDisplayChanged should be used instead
893
894 //                      var _t=new Date();
895                 if(!this._updateTimer){
896 //                              this._lastUpdate=_t;
897                         if(this._updateTimer){
898                                 clearTimeout(this._updateTimer);
899                         }
900                         this._updateTimer=setTimeout(dojo.hitch(this,this.onNormalizedDisplayChanged),this.updateInterval);
901                 }
902         },
903         onNormalizedDisplayChanged: function(){
904                 // summary:
905                 //              This event is fired every updateInterval ms or more
906                 // description:
907                 //              If something needs to happen immidiately after a
908                 //              user change, please use onDisplayChanged instead
909                 this._updateTimer=null;
910         },
911         onChange: function(newContent){
912                 // summary:
913                 //              this is fired if and only if the editor loses focus and
914                 //              the content is changed
915
916 //                      console.log('onChange',newContent);
917         },
918         _normalizeCommand: function(/*String*/cmd){
919                 // summary:
920                 //              Used as the advice function by dojo.connect to map our
921                 //              normalized set of commands to those supported by the target
922                 //              browser
923
924                 var command = cmd.toLowerCase();
925                 if(command == "hilitecolor" && !dojo.isMoz){
926                         command = "backcolor";
927                 }
928
929                 return command;
930         },
931
932         queryCommandAvailable: function(/*String*/command){
933                 // summary:
934                 //              Tests whether a command is supported by the host. Clients SHOULD check
935                 //              whether a command is supported before attempting to use it, behaviour
936                 //              for unsupported commands is undefined.
937                 // command: The command to test for
938                 var ie = 1;
939                 var mozilla = 1 << 1;
940                 var safari = 1 << 2;
941                 var opera = 1 << 3;
942                 var safari420 = 1 << 4;
943
944                 var gt420 = dojo.isSafari;
945
946                 function isSupportedBy(browsers){
947                         return {
948                                 ie: Boolean(browsers & ie),
949                                 mozilla: Boolean(browsers & mozilla),
950                                 safari: Boolean(browsers & safari),
951                                 safari420: Boolean(browsers & safari420),
952                                 opera: Boolean(browsers & opera)
953                         }
954                 }
955
956                 var supportedBy = null;
957
958                 switch(command.toLowerCase()){
959                         case "bold": case "italic": case "underline":
960                         case "subscript": case "superscript":
961                         case "fontname": case "fontsize":
962                         case "forecolor": case "hilitecolor":
963                         case "justifycenter": case "justifyfull": case "justifyleft":
964                         case "justifyright": case "delete": case "selectall": case "toggledir":
965                                 supportedBy = isSupportedBy(mozilla | ie | safari | opera);
966                                 break;
967
968                         case "createlink": case "unlink": case "removeformat":
969                         case "inserthorizontalrule": case "insertimage":
970                         case "insertorderedlist": case "insertunorderedlist":
971                         case "indent": case "outdent": case "formatblock":
972                         case "inserthtml": case "undo": case "redo": case "strikethrough":
973                                 supportedBy = isSupportedBy(mozilla | ie | opera | safari420);
974                                 break;
975
976                         case "blockdirltr": case "blockdirrtl":
977                         case "dirltr": case "dirrtl":
978                         case "inlinedirltr": case "inlinedirrtl":
979                                 supportedBy = isSupportedBy(ie);
980                                 break;
981                         case "cut": case "copy": case "paste":
982                                 supportedBy = isSupportedBy( ie | mozilla | safari420);
983                                 break;
984
985                         case "inserttable":
986                                 supportedBy = isSupportedBy(mozilla | ie);
987                                 break;
988
989                         case "insertcell": case "insertcol": case "insertrow":
990                         case "deletecells": case "deletecols": case "deleterows":
991                         case "mergecells": case "splitcell":
992                                 supportedBy = isSupportedBy(ie | mozilla);
993                                 break;
994
995                         default: return false;
996                 }
997
998                 return (dojo.isIE && supportedBy.ie) ||
999                         (dojo.isMoz && supportedBy.mozilla) ||
1000                         (dojo.isSafari && supportedBy.safari) ||
1001                         (gt420 && supportedBy.safari420) ||
1002                         (dojo.isOpera && supportedBy.opera);  // Boolean return true if the command is supported, false otherwise
1003         },
1004
1005         execCommand: function(/*String*/command, argument){
1006                 // summary: Executes a command in the Rich Text area
1007                 // command: The command to execute
1008                 // argument: An optional argument to the command
1009                 var returnValue;
1010
1011                 //focus() is required for IE to work
1012                 //In addition, focus() makes sure after the execution of
1013                 //the command, the editor receives the focus as expected
1014                 this.focus();
1015
1016                 command = this._normalizeCommand(command);
1017                 if(argument != undefined){
1018                         if(command == "heading"){
1019                                 throw new Error("unimplemented");
1020                         }else if((command == "formatblock") && dojo.isIE){
1021                                 argument = '<'+argument+'>';
1022                         }
1023                 }
1024                 if(command == "inserthtml"){
1025                         argument=this._preFilterContent(argument);
1026                         if(dojo.isIE){
1027                                 var insertRange = this.document.selection.createRange();
1028                                 if(this.document.selection.type.toUpperCase()=='CONTROL'){
1029                                         var n=insertRange.item(0);
1030                                         while(insertRange.length){
1031                                                 insertRange.remove(insertRange.item(0));
1032                                         }
1033                                         n.outerHTML=argument;
1034                                 }else{
1035                                         insertRange.pasteHTML(argument);
1036                                 }
1037                                 insertRange.select();
1038                                 //insertRange.collapse(true);
1039                                 returnValue=true;
1040                         }else if(dojo.isMoz && !argument.length){
1041                                 //mozilla can not inserthtml an empty html to delete current selection
1042                                 //so we delete the selection instead in this case
1043                                 dojo.withGlobal(this.window,'remove',dijit._editor.selection);
1044                                 returnValue=true;
1045                         }else{
1046                                 returnValue=this.document.execCommand(command, false, argument);
1047                         }
1048                 }else if(
1049                         (command == "unlink")&&
1050                         (this.queryCommandEnabled("unlink"))&&
1051                         (dojo.isMoz || dojo.isSafari)
1052                 ){
1053                         // fix up unlink in Mozilla to unlink the link and not just the selection
1054
1055                         // grab selection
1056                         // Mozilla gets upset if we just store the range so we have to
1057                         // get the basic properties and recreate to save the selection
1058                         var selection = this.window.getSelection();
1059                         //      var selectionRange = selection.getRangeAt(0);
1060                         //      var selectionStartContainer = selectionRange.startContainer;
1061                         //      var selectionStartOffset = selectionRange.startOffset;
1062                         //      var selectionEndContainer = selectionRange.endContainer;
1063                         //      var selectionEndOffset = selectionRange.endOffset;
1064
1065                         // select our link and unlink
1066                         var a = dojo.withGlobal(this.window, "getAncestorElement",dijit._editor.selection, ['a']);
1067                         dojo.withGlobal(this.window, "selectElement", dijit._editor.selection, [a]);
1068
1069                         returnValue=this.document.execCommand("unlink", false, null);
1070                 }else if((command == "hilitecolor")&&(dojo.isMoz)){
1071 //                              // mozilla doesn't support hilitecolor properly when useCSS is
1072 //                              // set to false (bugzilla #279330)
1073
1074                         this.document.execCommand("styleWithCSS", false, true);
1075                         returnValue = this.document.execCommand(command, false, argument);
1076                         this.document.execCommand("styleWithCSS", false, false);
1077
1078                 }else if((dojo.isIE)&&( (command == "backcolor")||(command == "forecolor") )){
1079                         // Tested under IE 6 XP2, no problem here, comment out
1080                         // IE weirdly collapses ranges when we exec these commands, so prevent it
1081 //                              var tr = this.document.selection.createRange();
1082                         argument = arguments.length > 1 ? argument : null;
1083                         returnValue = this.document.execCommand(command, false, argument);
1084
1085                         // timeout is workaround for weird IE behavior were the text
1086                         // selection gets correctly re-created, but subsequent input
1087                         // apparently isn't bound to it
1088 //                              setTimeout(function(){tr.select();}, 1);
1089                 }else{
1090                         argument = arguments.length > 1 ? argument : null;
1091 //                              if(dojo.isMoz){
1092 //                                      this.document = this.iframe.contentWindow.document
1093 //                              }
1094
1095                         if(argument || command!="createlink"){
1096                                 returnValue = this.document.execCommand(command, false, argument);
1097                         }
1098                 }
1099
1100                 this.onDisplayChanged();
1101                 return returnValue;
1102         },
1103
1104         queryCommandEnabled: function(/*String*/command){
1105                 // summary: check whether a command is enabled or not
1106
1107                 if(this.disabled){ return false; }
1108                 command = this._normalizeCommand(command);
1109                 if(dojo.isMoz || dojo.isSafari){
1110                         if(command == "unlink"){ // mozilla returns true always
1111                                 // console.debug(dojo.withGlobal(this.window, "hasAncestorElement",dijit._editor.selection, ['a']));
1112                                 return dojo.withGlobal(this.window, "hasAncestorElement",dijit._editor.selection, ['a']);
1113                         }else if(command == "inserttable"){
1114                                 return true;
1115                         }
1116                 }
1117                 //see #4109
1118                 if(dojo.isSafari){
1119                         if(command == "copy"){
1120                                 command = "cut";
1121                         }else if(command == "paste"){
1122                                 return true;
1123                         }
1124                 }
1125
1126                 // return this.document.queryCommandEnabled(command);
1127                 var elem = dojo.isIE ? this.document.selection.createRange() : this.document;
1128                 return elem.queryCommandEnabled(command);
1129         },
1130
1131         queryCommandState: function(command){
1132                 // summary: check the state of a given command
1133
1134                 if(this.disabled){ return false; }
1135                 command = this._normalizeCommand(command);
1136                 return this.document.queryCommandState(command);
1137         },
1138
1139         queryCommandValue: function(command){
1140                 // summary: check the value of a given command
1141
1142                 if(this.disabled){ return false; }
1143                 command = this._normalizeCommand(command);
1144                 if(dojo.isIE && command == "formatblock"){
1145                         return this._local2NativeFormatNames[this.document.queryCommandValue(command)];
1146                 }
1147                 return this.document.queryCommandValue(command);
1148         },
1149
1150         // Misc.
1151
1152         placeCursorAtStart: function(){
1153                 // summary:
1154                 //              place the cursor at the start of the editing area
1155                 this.focus();
1156
1157                 //see comments in placeCursorAtEnd
1158                 var isvalid=false;
1159                 if(dojo.isMoz){
1160                         var first=this.editNode.firstChild;
1161                         while(first){
1162                                 if(first.nodeType == 3){
1163                                         if(first.nodeValue.replace(/^\s+|\s+$/g, "").length>0){
1164                                                 isvalid=true;
1165                                                 dojo.withGlobal(this.window, "selectElement", dijit._editor.selection, [first]);
1166                                                 break;
1167                                         }
1168                                 }else if(first.nodeType == 1){
1169                                         isvalid=true;
1170                                         dojo.withGlobal(this.window, "selectElementChildren",dijit._editor.selection, [first]);
1171                                         break;
1172                                 }
1173                                 first = first.nextSibling;
1174                         }
1175                 }else{
1176                         isvalid=true;
1177                         dojo.withGlobal(this.window, "selectElementChildren",dijit._editor.selection, [this.editNode]);
1178                 }
1179                 if(isvalid){
1180                         dojo.withGlobal(this.window, "collapse", dijit._editor.selection, [true]);
1181                 }
1182         },
1183
1184         placeCursorAtEnd: function(){
1185                 // summary:
1186                 //              place the cursor at the end of the editing area
1187                 this.focus();
1188
1189                 //In mozilla, if last child is not a text node, we have to use selectElementChildren on this.editNode.lastChild
1190                 //otherwise the cursor would be placed at the end of the closing tag of this.editNode.lastChild
1191                 var isvalid=false;
1192                 if(dojo.isMoz){
1193                         var last=this.editNode.lastChild;
1194                         while(last){
1195                                 if(last.nodeType == 3){
1196                                         if(last.nodeValue.replace(/^\s+|\s+$/g, "").length>0){
1197                                                 isvalid=true;
1198                                                 dojo.withGlobal(this.window, "selectElement",dijit._editor.selection, [last]);
1199                                                 break;
1200                                         }
1201                                 }else if(last.nodeType == 1){
1202                                         isvalid=true;
1203                                         if(last.lastChild){
1204                                                 dojo.withGlobal(this.window, "selectElement",dijit._editor.selection, [last.lastChild]);
1205                                         }else{
1206                                                 dojo.withGlobal(this.window, "selectElement",dijit._editor.selection, [last]);
1207                                         }
1208                                         break;
1209                                 }
1210                                 last = last.previousSibling;
1211                         }
1212                 }else{
1213                         isvalid=true;
1214                         dojo.withGlobal(this.window, "selectElementChildren",dijit._editor.selection, [this.editNode]);
1215                 }
1216                 if(isvalid){
1217                         dojo.withGlobal(this.window, "collapse", dijit._editor.selection, [false]);
1218                 }
1219         },
1220
1221         getValue: function(/*Boolean?*/nonDestructive){
1222                 // summary:
1223                 //              return the current content of the editing area (post filters are applied)
1224                 if(this.textarea){
1225                         if(this.isClosed || !this.isLoaded){
1226                                 return this.textarea.value;
1227                         }
1228                 }
1229
1230                 return this._postFilterContent(null, nonDestructive);
1231         },
1232
1233         setValue: function(/*String*/html){
1234                 // summary:
1235                 //              this function set the content. No undo history is preserved
1236
1237                 if(!this.isLoaded){
1238                         // try again after the editor is finished loading
1239                         this.onLoadDeferred.addCallback(dojo.hitch(this, function(){
1240                                 this.setValue(html);
1241                         }));
1242                         return;
1243                 }
1244
1245                 if(this.textarea && (this.isClosed || !this.isLoaded)){
1246                         this.textarea.value=html;
1247                 }else{
1248                         html = this._preFilterContent(html);
1249                         var node = this.isClosed ? this.domNode : this.editNode;
1250                         node.innerHTML = html;
1251                         this._preDomFilterContent(node);
1252                 }
1253
1254                 this.onDisplayChanged();
1255         },
1256
1257         replaceValue: function(/*String*/html){
1258                 // summary:
1259                 //              this function set the content while trying to maintain the undo stack
1260                 //              (now only works fine with Moz, this is identical to setValue in all
1261                 //              other browsers)
1262                 if(this.isClosed){
1263                         this.setValue(html);
1264                 }else if(this.window && this.window.getSelection && !dojo.isMoz){ // Safari
1265                         // look ma! it's a totally f'd browser!
1266                         this.setValue(html);
1267                 }else if(this.window && this.window.getSelection){ // Moz
1268                         html = this._preFilterContent(html);
1269                         this.execCommand("selectall");
1270                         if(dojo.isMoz && !html){ html = "&nbsp;" }
1271                         this.execCommand("inserthtml", html);
1272                         this._preDomFilterContent(this.editNode);
1273                 }else if(this.document && this.document.selection){//IE
1274                         //In IE, when the first element is not a text node, say
1275                         //an <a> tag, when replacing the content of the editing
1276                         //area, the <a> tag will be around all the content
1277                         //so for now, use setValue for IE too
1278                         this.setValue(html);
1279                 }
1280         },
1281
1282         _preFilterContent: function(/*String*/html){
1283                 // summary:
1284                 //              filter the input before setting the content of the editing area
1285                 var ec = html;
1286                 dojo.forEach(this.contentPreFilters, function(ef){ if(ef){ ec = ef(ec); } });
1287                 return ec;
1288         },
1289         _preDomFilterContent: function(/*DomNode*/dom){
1290                 // summary:
1291                 //              filter the input
1292                 dom = dom || this.editNode;
1293                 dojo.forEach(this.contentDomPreFilters, function(ef){
1294                         if(ef && dojo.isFunction(ef)){
1295                                 ef(dom);
1296                         }
1297                 }, this);
1298         },
1299
1300         _postFilterContent: function(/*DomNode|DomNode[]|String?*/dom,/*Boolean?*/nonDestructive){
1301                 // summary:
1302                 //              filter the output after getting the content of the editing area
1303                 var ec;
1304                 if(!dojo.isString(dom)){
1305                         dom = dom || this.editNode;
1306                         if(this.contentDomPostFilters.length){
1307                                 if(nonDestructive && dom['cloneNode']){
1308                                         dom = dom.cloneNode(true);
1309                                 }
1310                                 dojo.forEach(this.contentDomPostFilters, function(ef){
1311                                         dom = ef(dom);
1312                                 });
1313                         }
1314                         ec = dijit._editor.getChildrenHtml(dom);
1315                 }else{
1316                         ec = dom;
1317                 }
1318                 
1319                 if(!ec.replace(/^(?:\s|\xA0)+/g, "").replace(/(?:\s|\xA0)+$/g,"").length){ ec = ""; }
1320
1321                 //      if(dojo.isIE){
1322                 //              //removing appended <P>&nbsp;</P> for IE
1323                 //              ec = ec.replace(/(?:<p>&nbsp;</p>[\n\r]*)+$/i,"");
1324                 //      }
1325                 dojo.forEach(this.contentPostFilters, function(ef){
1326                         ec = ef(ec);
1327                 });
1328
1329                 return ec;
1330         },
1331
1332         _saveContent: function(/*Event*/e){
1333                 // summary:
1334                 //              Saves the content in an onunload event if the editor has not been closed
1335                 var saveTextarea = dojo.byId(dijit._scopeName + "._editor.RichText.savedContent");
1336                 saveTextarea.value += this._SEPARATOR + this.name + ":" + this.getValue();
1337         },
1338
1339         escapeXml: function(/*String*/str, /*Boolean*/noSingleQuotes){
1340                 dojo.deprecated('dijit.Editor::escapeXml is deprecated','use dijit._editor.escapeXml instead', 2);
1341                 return dijit._editor.escapeXml(str,noSingleQuotes);
1342         },
1343
1344         getNodeHtml: function(/* DomNode */node){
1345                 dojo.deprecated('dijit.Editor::getNodeHtml is deprecated','use dijit._editor.getNodeHtml instead', 2);
1346                 return dijit._editor.getNodeHtml(node);
1347         },
1348
1349         getNodeChildrenHtml: function(/* DomNode */dom){
1350                 dojo.deprecated('dijit.Editor::getNodeChildrenHtml is deprecated','use dijit._editor.getChildrenHtml instead', 2);
1351                 return dijit._editor.getChildrenHtml(dom);
1352         },
1353
1354         close: function(/*Boolean*/save, /*Boolean*/force){
1355                 // summary:
1356                 //              Kills the editor and optionally writes back the modified contents to the
1357                 //              element from which it originated.
1358                 // save:
1359                 //              Whether or not to save the changes. If false, the changes are discarded.
1360                 // force:
1361                 if(this.isClosed){return false; }
1362
1363                 if(!arguments.length){ save = true; }
1364                 this._content = this.getValue();
1365                 var changed = (this.savedContent != this._content);
1366
1367                 // line height is squashed for iframes
1368                 // FIXME: why was this here? if(this.iframe){ this.domNode.style.lineHeight = null; }
1369
1370                 if(this.interval){ clearInterval(this.interval); }
1371
1372                 if(this.textarea){
1373                         with(this.textarea.style){
1374                                 position = "";
1375                                 left = top = "";
1376                                 if(dojo.isIE){
1377                                         overflow = this.__overflow;
1378                                         this.__overflow = null;
1379                                 }
1380                         }
1381                         this.textarea.value = save ? this._content : this.savedContent;
1382                         dojo._destroyElement(this.domNode);
1383                         this.domNode = this.textarea;
1384                 }else{
1385 //                      if(save){
1386                                 //why we treat moz differently? comment out to fix #1061
1387 //                                      if(dojo.isMoz){
1388 //                                              var nc = dojo.doc.createElement("span");
1389 //                                              this.domNode.appendChild(nc);
1390 //                                              nc.innerHTML = this.editNode.innerHTML;
1391 //                                      }else{
1392 //                                              this.domNode.innerHTML = this._content;
1393 //                                      }
1394 //                      }
1395                         this.domNode.innerHTML = save ? this._content : this.savedContent;
1396                 }
1397
1398                 dojo.removeClass(this.domNode, "RichTextEditable");
1399                 this.isClosed = true;
1400                 this.isLoaded = false;
1401                 // FIXME: is this always the right thing to do?
1402                 delete this.editNode;
1403
1404                 if(this.window && this.window._frameElement){
1405                         this.window._frameElement = null;
1406                 }
1407
1408                 this.window = null;
1409                 this.document = null;
1410                 this.editingArea = null;
1411                 this.editorObject = null;
1412
1413                 return changed; // Boolean: whether the content has been modified
1414         },
1415
1416         destroyRendering: function(){
1417                 // summary: stub        
1418         }, 
1419
1420         destroy: function(){
1421                 this.destroyRendering();
1422                 if(!this.isClosed){ this.close(false); }
1423                 this.inherited("destroy",arguments);
1424                 //dijit._editor.RichText.superclass.destroy.call(this);
1425         },
1426
1427         _removeMozBogus: function(/* String */ html){
1428                 return html.replace(/\stype="_moz"/gi, '').replace(/\s_moz_dirty=""/gi, ''); // String
1429         },
1430         _removeSafariBogus: function(/* String */ html){
1431                 return html.replace(/\sclass="webkit-block-placeholder"/gi, ''); // String
1432         },
1433         _fixContentForMoz: function(/* String */ html){
1434                 // summary:
1435                 //              Moz can not handle strong/em tags correctly, convert them to b/i
1436                 return html.replace(/<(\/)?strong([ \>])/gi, '<$1b$2')
1437                         .replace(/<(\/)?em([ \>])/gi, '<$1i$2' ); // String
1438         },
1439
1440         _srcInImgRegex  : /(?:(<img(?=\s).*?\ssrc=)("|')(.*?)\2)|(?:(<img\s.*?src=)([^"'][^ >]+))/gi ,
1441         _hrefInARegex   : /(?:(<a(?=\s).*?\shref=)("|')(.*?)\2)|(?:(<a\s.*?href=)([^"'][^ >]+))/gi ,
1442
1443         _preFixUrlAttributes: function(/* String */ html){
1444                 return html.replace(this._hrefInARegex, '$1$4$2$3$5$2 _djrealurl=$2$3$5$2')
1445                         .replace(this._srcInImgRegex, '$1$4$2$3$5$2 _djrealurl=$2$3$5$2'); // String
1446         }
1447 });
1448
1449 }