1 /*
  2 Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.
  3 For licensing, see LICENSE.html or http://ckeditor.com/license
  4 */
  5
  6 /**
  7  * @fileOverview The "wysiwygarea" plugin. It registers the "wysiwyg" editing
  8  *		mode, which handles the main editing area space.
  9  */
 10
 11 (function()
 12 {
 13 	// Matches all self-closing tags that are not defined as empty elements in
 14 	// the DTD (like <span/>).
 15 	var invalidSelfCloseTagsRegex = /(<(?!br|hr|base|meta|link|param|img|area|input|col)([a-zA-Z0-9:]+)[^>]*)\/>/gi;
 16
 17 	// #### protectEvents - START
 18
 19 	// Matches all tags that have event attributes (onXYZ).
 20 	var tagsWithEventRegex = /<[^\>]+ on\w+\s*=[\s\S]+?\>/g;
 21
 22 	// Matches all event attributes.
 23 	var eventAttributesRegex = /\s(on\w+)(?=\s*=\s*?('|")[\s\S]*?\2)/g;
 24
 25 	// Matches the protected attribute prefix.
 26 	var protectedEventsRegex = /_cke_pa_/g;
 27
 28 	var protectEvents = function( html )
 29 	{
 30 		return html.replace( tagsWithEventRegex, protectEvents_ReplaceTags );
 31 	};
 32
 33 	var protectEvents_ReplaceTags = function( tagMatch )
 34 	{
 35 		// Appends the "_cke_pa_" prefix to the event name.
 36 		return tagMatch.replace( eventAttributesRegex, ' _cke_pa_$1' );
 37 	};
 38
 39 	var protectEventsRestore = function( html )
 40 	{
 41 		return html.replace( protectedEventsRegex, '' ) ;
 42 	};
 43
 44 	// #### protectEvents - END
 45
 46 	// #### protectAttributes - START
 47 	var protectUrlTagRegex = /<(?:a|area|img)(?=\s).*?\s(?:href|src)=((?:(?:\s*)("|').*?\2)|(?:[^"'][^ >]+))/gi,
 48 		protectUrlAttributeRegex = /\s(href|src)(\s*=\s*?('|")[\s\S]*?\3)/gi,
 49 		protectedUrlTagRegex = /<(?:a|area|img)(?=\s)(?:"[^"]*"|'[^']*'|[^<])*>/gi,
 50 		protectedAttributeRegex = /_cke_saved_/gi,
 51 		protectUrls = function( html )
 52 		{
 53 			return html.replace( protectUrlTagRegex, protectUrls_ReplaceTags );
 54 		},
 55 		protectUrls_ReplaceTags = function( tagMatch )
 56 		{
 57 			return tagMatch.replace( protectUrlAttributeRegex, '$& _cke_saved_$1$2');
 58 		},
 59 		protectUrlsRestore = function( html )
 60 		{
 61 			return html.replace( protectedUrlTagRegex, protectUrlsRestore_ReplaceTags );
 62 		},
 63 		protectUrlsRestore_ReplaceTags = function( tagMatch )
 64 		{
 65 			return tagMatch.replace( protectUrlAttributeRegex, '' ).replace( protectedAttributeRegex, '' );
 66 		};
 67 	// #### protectAttributes - END
 68
 69 	var onInsertHtml = function( evt )
 70 	{
 71 		if ( this.mode == 'wysiwyg' )
 72 		{
 73 			var $doc = this.document.$;
 74
 75 			if ( CKEDITOR.env.ie )
 76 				$doc.selection.createRange().pasteHTML( evt.data );
 77 			else
 78 				$doc.execCommand( 'inserthtml', false, evt.data );
 79 		}
 80 	};
 81
 82 	var onInsertElement = function( evt )
 83 	{
 84 		if ( this.mode == 'wysiwyg' )
 85 		{
 86 			var element = evt.data,
 87 				isBlock = CKEDITOR.dtd.$block[ element.getName() ];
 88
 89 			var selection = this.getSelection(),
 90 				ranges = selection.getRanges();
 91
 92 			var range, clone, lastElement, bookmark;
 93
 94 			for ( var i = ranges.length - 1 ; i >= 0 ; i-- )
 95 			{
 96 				range = ranges[ i ];
 97
 98 				// Remove the original contents.
 99 				range.deleteContents();
100
101 				clone = element.clone( true );
102
103 				// If the new node is a block element, split the current block.
104 				if ( this.config.enterMode != 'br' && isBlock )
105 					range.splitBlock();
106
107 				// Insert the new node.
108 				range.insertNode( clone );
109
110 				// Save the last element reference so we can make the
111 				// selection later.
112 				if ( !lastElement )
113 					lastElement = clone;
114 			}
115
116 			range.moveToPosition( lastElement, CKEDITOR.POSITION_AFTER_END );
117 			selection.selectRanges( [ range ] );
118 		}
119 	};
120
121 	CKEDITOR.plugins.add( 'wysiwygarea',
122 	{
123 		requires : [ 'editingblock' ],
124
125 		init : function( editor, pluginPath )
126 		{
127 			editor.on( 'editingBlockReady', function()
128 				{
129 					var mainElement,
130 						iframe,
131 						isLoadingData,
132 						isPendingFocus,
133 						fireMode;
134
135 					// The following information is needed for IE only.
136 					var isCustomDomain = CKEDITOR.env.ie && document.domain != window.location.hostname;
137
138 					// Creates the iframe that holds the editable document.
139 					var createIFrame = function()
140 					{
141 						if ( iframe )
142 							iframe.remove();
143
144 						iframe = new CKEDITOR.dom.element( 'iframe' )
145 							.setAttributes({
146 								frameBorder : 0,
147 								allowTransparency : true })
148 							.setStyles({
149 								width : '100%',
150 								height : '100%' });
151
152 						if ( CKEDITOR.env.ie )
153 						{
154 							if ( isCustomDomain )
155 							{
156 								// The document domain must be set within the src
157 								// attribute.
158 								iframe.setAttribute( 'src',
159 									'javascript:void( (function(){' +
160 										'document.open();' +
161 										'document.domain="' + document.domain + '";' +
162 										'document.write( window.parent._cke_htmlToLoad_' + editor.name + ' );' +
163 										'document.close();' +
164 										'window.parent._cke_htmlToLoad_' + editor.name + ' = null;' +
165 									'})() )' );
166 							}
167 							else
168 								// To avoid HTTPS warnings.
169 								iframe.setAttribute( 'src', 'javascript:void(0)' );
170 						}
171
172 						// Append the new IFRAME to the main element. For IE, it
173 						// must be done after setting the "src", to avoid the
174 						// "secure/unsecure" message under HTTPS.
175 						mainElement.append( iframe );
176 					};
177
178 					// The script that is appended to the data being loaded. It
179 					// enables editing, and makes some
180 					var activationScript =
181 						'<script id="cke_actscrpt" type="text/javascript">' +
182 							'window.onload = function()' +
183 							'{' +
184 								// Remove this script from the DOM.
185 								'var s = document.getElementById( "cke_actscrpt" );' +
186 								's.parentNode.removeChild( s );' +
187
188 								// Call the temporary function for the editing
189 								// boostrap.
190 								'window.parent.CKEDITOR._.contentDomReady' + editor.name + '( window );' +
191 							'}' +
192 						'</script>';
193
194 					// Editing area bootstrap code.
195 					var contentDomReady = function( domWindow )
196 					{
197 						delete CKEDITOR._[ 'contentDomReady' + editor.name ];
198
199 						var domDocument = domWindow.document,
200 							body = domDocument.body;
201
202 						body.spellcheck = !editor.config.disableNativeSpellChecker;
203
204 						if ( CKEDITOR.env.ie )
205 						{
206 							// Don't display the focus border.
207 							body.hideFocus = true;
208
209 							// Disable and re-enable the body to avoid IE from
210 							// taking the editing focus at startup. (#141 / #523)
211 							body.disabled = true;
212 							body.contentEditable = true;
213 							body.removeAttribute( 'disabled' );
214 						}
215 						else
216 							domDocument.designMode = 'on';
217
218 						// IE, Opera and Safari may not support it and throw
219 						// errors.
220 						try { domDocument.execCommand( 'enableObjectResizing', false, !editor.config.disableObjectResizing ) ; } catch(e) {}
221 						try { domDocument.execCommand( 'enableInlineTableEditing', false, !editor.config.disableNativeTableHandles ) ; } catch(e) {}
222
223 						domWindow	= editor.window		= new CKEDITOR.dom.window( domWindow );
224 						domDocument	= editor.document	= new CKEDITOR.dom.document( domDocument );
225
226 						var focusTarget = ( CKEDITOR.env.ie || CKEDITOR.env.safari ) ?
227 								domWindow : domDocument;
228
229 						focusTarget.on( 'blur', function()
230 							{
231 								editor.focusManager.blur();
232 							});
233
234 						focusTarget.on( 'focus', function()
235 							{
236 								editor.focusManager.focus();
237 							});
238
239 						var keystrokeHandler = editor.keystrokeHandler;
240 						if ( keystrokeHandler )
241 							keystrokeHandler.attach( domDocument );
242
243 						editor.fire( 'contentDom' );
244
245 						if ( fireMode )
246 						{
247 							editor.mode = 'wysiwyg';
248 							editor.fire( 'mode' );
249 							fireMode = false;
250 						}
251
252 						isLoadingData = false;
253
254 						if ( isPendingFocus )
255 							editor.focus();
256 					};
257
258 					editor.addMode( 'wysiwyg',
259 						{
260 							load : function( holderElement, data, isSnapshot )
261 							{
262 								mainElement = holderElement;
263
264 								// Create the iframe at load for all browsers
265 								// except FF and IE with custom domain.
266 								if ( !isCustomDomain || !CKEDITOR.env.gecko )
267 									createIFrame();
268
269 								// The editor data "may be dirty" after this
270 								// point.
271 								editor.mayBeDirty = true;
272
273 								fireMode = true;
274
275 								if ( isSnapshot )
276 									this.loadSnapshotData( data );
277 								else
278 									this.loadData( data );
279 							},
280
281 							loadData : function( data )
282 							{
283 								isLoadingData = true;
284
285 								// Get the HTML version of the data.
286 								if ( editor.dataProcessor )
287 									data = editor.dataProcessor.toHtml( data );
288
289 								// Fix for invalid self-closing tags (see #152).
290 								// TODO: Check if this fix is really needed as
291 								// soon as we have the XHTML generator.
292 								if ( CKEDITOR.env.ie )
293 									data = data.replace( invalidSelfCloseTagsRegex, '$1></$2>' );
294
295 								// Prevent event attributes (like "onclick") to
296 								// execute while editing.
297 								if ( CKEDITOR.env.ie || CKEDITOR.env.webkit )
298 									data = protectEvents( data );
299
300 								// Protect src or href attributes.
301 								data = protectUrls( data );
302
303 								// Replace tags with fake elements.
304 								if ( editor.fakeobjects )
305 									data = editor.fakeobjects.protectHtml( data );
306
307 								data =
308 									editor.config.docType +
309 									'<html dir="' + editor.config.contentsLangDirection + '">' +
310 									'<head>' +
311 										'<link href="' + editor.config.contentsCss + '" type="text/css" rel="stylesheet" _fcktemp="true"/>' +
312 									'</head>' +
313 									'<body>' +
314 										data +
315 									'</body>' +
316 									'</html>' +
317 									activationScript;
318
319 								// For custom domain in IE, set the global variable
320 								// that will temporarily hold the editor data. This
321 								// reference will be used in the ifram src.
322 								if ( isCustomDomain )
323 									window[ '_cke_htmlToLoad_' + editor.name ] = data;
324
325 								CKEDITOR._[ 'contentDomReady' + editor.name ] = contentDomReady;
326
327 								// We need to recreate the iframe in FF for every
328 								// data load, otherwise the following spellcheck
329 								// and execCommand features will be active only for
330 								// the first time.
331 								// The same is valid for IE with custom domain,
332 								// because the iframe src must be reset every time.
333 								if ( isCustomDomain || CKEDITOR.env.gecko )
334 									createIFrame();
335
336 								// For custom domain in IE, the data loading is
337 								// done through the src attribute of the iframe.
338 								if ( !isCustomDomain )
339 								{
340 									var doc = iframe.$.contentWindow.document;
341 									doc.open();
342 									doc.write( data );
343 									doc.close();
344 								}
345 							},
346
347 							getData : function()
348 							{
349 								var data = iframe.$.contentWindow.document.body;
350
351 								if ( editor.dataProcessor )
352 									data = editor.dataProcessor.toDataFormat( new CKEDITOR.dom.element( data ) );
353 								else
354 									data = data.innerHTML;
355
356 								// Restore protected attributes.
357 								data = protectEventsRestore( data );
358
359 								// Restore protected URLs.
360 								data = protectUrlsRestore( data );
361
362 								// Restore fake elements.
363 								if ( editor.fakeobjects )
364 									data = editor.fakeobjects.restoreHtml( data );
365
366 								return data;
367 							},
368
369 							getSnapshotData : function()
370 							{
371 								return iframe.$.contentWindow.document.body.innerHTML;
372 							},
373
374 							loadSnapshotData : function( data )
375 							{
376 								iframe.$.contentWindow.document.body.innerHTML = data;
377 							},
378
379 							unload : function( holderElement )
380 							{
381 								editor.window = editor.document = iframe = mainElement = isPendingFocus = null;
382
383 								editor.fire( 'contentDomUnload' );
384 							},
385
386 							focus : function()
387 							{
388 								if ( isLoadingData )
389 									isPendingFocus = true;
390 								else if ( editor.window )
391 									editor.window.focus();
392 							}
393 						});
394
395 					editor.on( 'insertHtml', onInsertHtml, null, null, 20 );
396 					editor.on( 'insertElement', onInsertElement, null, null, 20 );
397 				});
398 		}
399 	});
400 })();
401
402 /**
403  * Disables the ability of resize objects (image and tables) in the editing
404  * area
405  * @type Boolean
406  * @default false
407  * @example
408  * config.disableObjectResizing = true;
409  */
410 CKEDITOR.config.disableObjectResizing = false;
411
412 /**
413  * Disables the "table tools" offered natively by the browser (currently
414  * Firefox only) to make quick table editing operations, like adding or
415  * deleting rows and columns.
416  * @type Boolean
417  * @default true
418  * @example
419  * config.disableNativeTableHandles = false;
420  */
421 CKEDITOR.config.disableNativeTableHandles = true;
422
423 /**
424  * Disables the built-in spell checker while typing natively available in the
425  * browser (currently Firefox and Safari only).<br /><br />
426  *
427  * Even if word suggestions will not appear in the FCKeditor context menu, this
428  * feature is useful to help quickly identifying misspelled words.<br /><br />
429  *
430  * This setting is currently compatible with Firefox only due to limitations in
431  * other browsers.
432  * @type Boolean
433  * @default true
434  * @example
435  * config.disableNativeSpellChecker = false;
436  */
437 CKEDITOR.config.disableNativeSpellChecker = true;
438