Subversion Repositories Applications.papyrus

Rev

Details | Last modification | View Log | RSS feed

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