      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 	/**
     14 	 * List of elements in which has no way to move editing focus outside.
     15 	 */
     16 	var nonExitableElementNames = { table:1,pre:1 };
     17 	// Matching an empty paragraph at the end of document.
     18 	var emptyParagraphRegexp = /\s*<(p|div|address|h\d|center)[^>]*>\s*(?:<br[^>]*>| | )\s*(:?<\/\1>)?\s*$/gi;
     19
     20 	function onInsertHtml( evt )
     21 	{
     22 		if ( this.mode == 'wysiwyg' )
     23 		{
     24 			this.focus();
     25
     26 			var selection = this.getSelection(),
     27 				data = evt.data;
     28
     29 			if ( this.dataProcessor )
     30 				data = this.dataProcessor.toHtml( data );
     31
     32 			if ( CKEDITOR.env.ie )
     33 			{
     34 				var selIsLocked = selection.isLocked;
     35
     36 				if ( selIsLocked )
     37 					selection.unlock();
     38
     39 				var $sel = selection.getNative();
     40 				if ( $sel.type == 'Control' )
     41 					$sel.clear();
     42 				$sel.createRange().pasteHTML( data );
     43
     44 				if ( selIsLocked )
     45 					this.getSelection().lock();
     46 			}
     47 			else
     48 				this.document.$.execCommand( 'inserthtml', false, data );
     49 		}
     50 	}
     51
     52 	function onInsertElement( evt )
     53 	{
     54 		if ( this.mode == 'wysiwyg' )
     55 		{
     56 			this.focus();
     57 			this.fire( 'saveSnapshot' );
     58
     59 			var element = evt.data,
     60 				elementName = element.getName(),
     61 				isBlock = CKEDITOR.dtd.$block[ elementName ];
     62
     63 			var selection = this.getSelection(),
     64 				ranges = selection.getRanges();
     65
     66 			var selIsLocked = selection.isLocked;
     67
     68 			if ( selIsLocked )
     69 				selection.unlock();
     70
     71 			var range, clone, lastElement, bookmark;
     72
     73 			for ( var i = ranges.length - 1 ; i >= 0 ; i-- )
     74 			{
     75 				range = ranges[ i ];
     76
     77 				// Remove the original contents.
     78 				range.deleteContents();
     79
     80 				clone = !i && element || element.clone( true );
     81
     82 				// If we're inserting a block at dtd-violated position, split
     83 				// the parent blocks until we reach blockLimit.
     84 				var parent, dtd;
     85 				if ( this.config.enterMode != CKEDITOR.ENTER_BR && isBlock )
     86 				{
     87 					while( ( parent = range.getCommonAncestor( false, true ) )
     88 							&& ( dtd = CKEDITOR.dtd[ parent.getName() ] )
     89 							&& !( dtd && dtd [ elementName ] ) )
     90 					{
     91 						range.splitBlock();
     92 					}
     93 				}
     94
     95 				// Insert the new node.
     96 				range.insertNode( clone );
     97
     98 				// Save the last element reference so we can make the
     99 				// selection later.
    100 				if ( !lastElement )
    101 					lastElement = clone;
    102 			}
    103
    104 			range.moveToPosition( lastElement, CKEDITOR.POSITION_AFTER_END );
    105
    106 			var next = lastElement.getNextSourceNode( true );
    107 			if ( next && next.type == CKEDITOR.NODE_ELEMENT )
    108 				range.moveToElementEditStart( next );
    109
    110 			selection.selectRanges( [ range ] );
    111
    112 			if ( selIsLocked )
    113 				this.getSelection().lock();
    114
    115 			// Save snaps after the whole execution completed.
    116 			// This's a workaround for make DOM modification's happened after
    117 			// 'insertElement' to be included either, e.g. Form-based dialogs' 'commitContents'
    118 			// call.
    119 			CKEDITOR.tools.setTimeout( function(){
    120 				this.fire( 'saveSnapshot' );
    121 			}, 0, this );
    122 		}
    123 	}
    124
    125 	/**
    126 	 *  Auto-fixing block-less content by wrapping paragraph (#3190), prevent
    127 	 *  non-exitable-block by padding extra br.(#3189)
    128 	 */
    129 	function onSelectionChangeFixBody( evt )
    130 	{
    131 		var editor = evt.editor,
    132 			path = evt.data.path,
    133 			blockLimit = path.blockLimit,
    134 			selection = evt.data.selection,
    135 			range = selection.getRanges()[0],
    136 			body = editor.document.getBody(),
    137 			enterMode = editor.config.enterMode;
    138
    139 		// When enterMode set to block, we'll establing new paragraph only if we're
    140 		// selecting inline contents right under body. (#3657)
    141 		if ( enterMode != CKEDITOR.ENTER_BR
    142 		     && range.collapsed
    143 			 && blockLimit.getName() == 'body'
    144 			 && !path.block )
    145 		{
    146 			var bms = selection.createBookmarks(),
    147 				fixedBlock = range.fixBlock( true,
    148 					editor.config.enterMode == CKEDITOR.ENTER_DIV ? 'div' : 'p'  );
    149
    150 			// For IE, we'll be removing any bogus br ( introduce by fixing body )
    151 			// right now to prevent it introducing visual line break.
    152 			if ( CKEDITOR.env.ie )
    153 			{
    154 				var brNodeList = fixedBlock.getElementsByTag( 'br' ), brNode;
    155 				for ( var i = 0 ; i < brNodeList.count() ; i++ )
    156 				{
    157 					if( ( brNode = brNodeList.getItem( i ) ) && brNode.hasAttribute( '_cke_bogus' ) )
    158 						brNode.remove();
    159 				}
    160 			}
    161
    162 			selection.selectBookmarks( bms );
    163
    164 			// If the fixed block is blank and is already followed by a exitable
    165 			// block, we should drop it and move to the exist block(#3684).
    166 			var children = fixedBlock.getChildren(),
    167 				count = children.count(),
    168 				firstChild,
    169 				previousElement = fixedBlock.getPrevious( true ),
    170 				nextElement = fixedBlock.getNext( true ),
    171 				enterBlock;
    172 			if ( previousElement && previousElement.getName
    173 				 && !( previousElement.getName() in nonExitableElementNames ) )
    174 				enterBlock = previousElement;
    175 			else if ( nextElement && nextElement.getName
    176 					  && !( nextElement.getName() in nonExitableElementNames ) )
    177 				enterBlock = nextElement;
    178
    179 			if( ( !count
    180 				  || ( firstChild = children.getItem( 0 ) ) && firstChild.is && firstChild.is( 'br' ) )
    181 				&& enterBlock )
    182 			{
    183 				fixedBlock.remove();
    184 				range.moveToElementEditStart( enterBlock );
    185 				range.select();
    186 			}
    187 		}
    188
    189 		// Inserting the padding-br before body if it's preceded by an
    190 		// unexitable block.
    191 		var lastNode = body.getLast( true );
    192 		if ( lastNode.getName && ( lastNode.getName() in nonExitableElementNames ) )
    193 		{
    194 			var paddingBlock = editor.document.createElement(
    195 					( CKEDITOR.env.ie && enterMode != CKEDITOR.ENTER_BR ) ?
    196 						'<br _cke_bogus="true" />' : 'br' );
    197 			body.append( paddingBlock );
    198 		}
    199 	}
    200
    201 	CKEDITOR.plugins.add( 'wysiwygarea',
    202 	{
    203 		requires : [ 'editingblock' ],
    204
    205 		init : function( editor )
    206 		{
    207 			var fixForBody = ( editor.config.enterMode != CKEDITOR.ENTER_BR )
    208 				? editor.config.enterMode == CKEDITOR.ENTER_DIV ? 'div' : 'p' : false;
    209
    210 			editor.on( 'editingBlockReady', function()
    211 				{
    212 					var mainElement,
    213 						iframe,
    214 						isLoadingData,
    215 						isPendingFocus,
    216 						fireMode;
    217
    218 					// Support for custom document.domain in IE.
    219 					var isCustomDomain = CKEDITOR.env.isCustomDomain();
    220
    221 					// Creates the iframe that holds the editable document.
    222 					var createIFrame = function()
    223 					{
    224 						if ( iframe )
    225 							iframe.remove();
    226
    227 						iframe = new CKEDITOR.dom.element( 'iframe' )
    228 							.setAttributes({
    229 								frameBorder : 0,
    230 								tabIndex : -1,
    231 								allowTransparency : true })
    232 							.setStyles({
    233 								width : '100%',
    234 								height : '100%' });
    235
    236 						if ( CKEDITOR.env.ie )
    237 						{
    238 							if ( isCustomDomain )
    239 							{
    240 								// The document domain must be set within the src
    241 								// attribute.
    242 								iframe.setAttribute( 'src',
    243 									'javascript:void( (function(){' +
    244 										'document.open();' +
    245 										'document.domain="' + document.domain + '";' +
    246 										'document.write( window.parent._cke_htmlToLoad_' + editor.name + ' );' +
    247 										'document.close();' +
    248 										'window.parent._cke_htmlToLoad_' + editor.name + ' = null;' +
    249 									'})() )' );
    250 							}
    251 							else
    252 								// To avoid HTTPS warnings.
    253 								iframe.setAttribute( 'src', 'javascript:void(0)' );
    254 						}
    255
    256 						var accTitle = editor.lang.editorTitle.replace( '%1', editor.name );
    257
    258 						if ( CKEDITOR.env.gecko )
    259 						{
    260 							// Accessibility attributes for Firefox.
    261 							mainElement.setAttributes(
    262 								{
    263 									role : 'region',
    264 									title : accTitle
    265 								} );
    266 							iframe.setAttributes(
    267 								{
    268 									role : 'region',
    269 									title : ' '
    270 								} );
    271 						}
    272 						else if ( CKEDITOR.env.webkit )
    273 						{
    274 							iframe.setAttribute( 'title', accTitle );	// Safari 4
    275 							iframe.setAttribute( 'name', accTitle );	// Safari 3
    276 						}
    277 						else if ( CKEDITOR.env.ie )
    278 						{
    279 							// Accessibility label for IE.
    280 							var fieldset = CKEDITOR.dom.element.createFromHtml(
    281 								'<fieldset style="height:100%' +
    282 									( CKEDITOR.env.quirks ? ';position:relative' : '' ) +
    283 								'">' +
    284 									'<legend style="position:absolute;left:-10000px">' +
    285 										CKEDITOR.tools.htmlEncode( accTitle ) +
    286 									'</legend>' +
    287 								'</fieldset>'
    288 								, CKEDITOR.document );
    289 							iframe.appendTo( fieldset );
    290 							fieldset.appendTo( mainElement );
    291 						}
    292
    293 						if ( !CKEDITOR.env.ie )
    294 							mainElement.append( iframe );
    295 					};
    296
    297 					// The script that is appended to the data being loaded. It
    298 					// enables editing, and makes some
    299 					var activationScript =
    300 						'<script id="cke_actscrpt" type="text/javascript">' +
    301 							'window.onload = function()' +
    302 							'{' +
    303 								// Remove this script from the DOM.
    304 								'var s = document.getElementById( "cke_actscrpt" );' +
    305 								's.parentNode.removeChild( s );' +
    306
    307 								// Call the temporary function for the editing
    308 								// boostrap.
    309 								'window.parent.CKEDITOR._["contentDomReady' + editor.name + '"]( window );' +
    310 							'}' +
    311 						'</script>';
    312
    313 					// Editing area bootstrap code.
    314 					var contentDomReady = function( domWindow )
    315 					{
    316 						delete CKEDITOR._[ 'contentDomReady' + editor.name ];
    317
    318 						var domDocument = domWindow.document,
    319 							body = domDocument.body;
    320
    321 						body.spellcheck = !editor.config.disableNativeSpellChecker;
    322
    323 						if ( CKEDITOR.env.ie )
    324 						{
    325 							// Don't display the focus border.
    326 							body.hideFocus = true;
    327
    328 							// Disable and re-enable the body to avoid IE from
    329 							// taking the editing focus at startup. (#141 / #523)
    330 							body.disabled = true;
    331 							body.contentEditable = true;
    332 							body.removeAttribute( 'disabled' );
    333 						}
    334 						else
    335 							domDocument.designMode = 'on';
    336
    337 						// IE, Opera and Safari may not support it and throw
    338 						// errors.
    339 						try { domDocument.execCommand( 'enableObjectResizing', false, !editor.config.disableObjectResizing ) ; } catch(e) {}
    340 						try { domDocument.execCommand( 'enableInlineTableEditing', false, !editor.config.disableNativeTableHandles ) ; } catch(e) {}
    341
    342 						domWindow	= editor.window		= new CKEDITOR.dom.window( domWindow );
    343 						domDocument	= editor.document	= new CKEDITOR.dom.document( domDocument );
    344
    345 						// Gecko/Webkit need some help when selecting control type elements. (#3448)
    346 						if ( !( CKEDITOR.env.ie || CKEDITOR.env.opera) )
    347 						{
    348 							domDocument.on( 'mousedown', function( ev )
    349 							{
    350 								var control = ev.data.getTarget();
    351 								if ( control.is( 'img', 'hr', 'input', 'textarea', 'select' ) )
    352 									editor.getSelection().selectElement( control );
    353 							} );
    354 						}
    355
    356 						// Webkit: avoid from editing form control elements content.
    357 						if ( CKEDITOR.env.webkit )
    358 						{
    359 							// Prevent from tick checkbox/radiobox/select
    360 							domDocument.on( 'click', function( ev )
    361 							{
    362 								if ( ev.data.getTarget().is( 'input', 'select' ) )
    363 									ev.data.preventDefault();
    364 							} );
    365
    366 							// Prevent from editig textfield/textarea value.
    367 							domDocument.on( 'mouseup', function( ev )
    368 							{
    369 								if ( ev.data.getTarget().is( 'input', 'textarea' ) )
    370 									ev.data.preventDefault();
    371 							} );
    372 						}
    373
    374 						var focusTarget = ( CKEDITOR.env.ie || CKEDITOR.env.safari ) ?
    375 								domWindow : domDocument;
    376
    377 						focusTarget.on( 'blur', function()
    378 							{
    379 								editor.focusManager.blur();
    380 							});
    381
    382 						focusTarget.on( 'focus', function()
    383 							{
    384 								editor.focusManager.focus();
    385 							});
    386
    387 						var keystrokeHandler = editor.keystrokeHandler;
    388 						if ( keystrokeHandler )
    389 							keystrokeHandler.attach( domDocument );
    390
    391 						// Adds the document body as a context menu target.
    392 						if ( editor.contextMenu )
    393 							editor.contextMenu.addTarget( domDocument );
    394
    395 						setTimeout( function()
    396 							{
    397 								editor.fire( 'contentDom' );
    398
    399 								if ( fireMode )
    400 								{
    401 									editor.mode = 'wysiwyg';
    402 									editor.fire( 'mode' );
    403 									fireMode = false;
    404 								}
    405
    406 								isLoadingData = false;
    407
    408 								if ( isPendingFocus )
    409 								{
    410 									editor.focus();
    411 									isPendingFocus = false;
    412 								}
    413
    414 								/*
    415 								 * IE BUG: IE might have rendered the iframe with invisible contents.
    416 								 * (#3623). Push some inconsequential CSS style changes to force IE to
    417 								 * refresh it.
    418 								 *
    419 								 * Also, for some unknown reasons, short timeouts (e.g. 100ms) do not
    420 								 * fix the problem. :(
    421 								 */
    422 								if ( CKEDITOR.env.ie )
    423 								{
    424 									setTimeout( function()
    425 										{
    426 											if ( editor.document )
    427 											{
    428 												var $body = editor.document.$.body;
    429 												$body.runtimeStyle.marginBottom = '0px';
    430 												$body.runtimeStyle.marginBottom = '';
    431 											}
    432 										}, 1000 );
    433 								}
    434 							},
    435 							0 );
    436 					};
    437
    438 					editor.addMode( 'wysiwyg',
    439 						{
    440 							load : function( holderElement, data, isSnapshot )
    441 							{
    442 								mainElement = holderElement;
    443
    444 								if ( CKEDITOR.env.ie && ( CKEDITOR.env.quirks || CKEDITOR.env.version < 8 ) )
    445 									holderElement.setStyle( 'position', 'relative' );
    446
    447 								// Create the iframe at load for all browsers
    448 								// except FF and IE with custom domain.
    449 								if ( !isCustomDomain || !CKEDITOR.env.gecko )
    450 									createIFrame();
    451
    452 								// The editor data "may be dirty" after this
    453 								// point.
    454 								editor.mayBeDirty = true;
    455
    456 								fireMode = true;
    457
    458 								if ( isSnapshot )
    459 									this.loadSnapshotData( data );
    460 								else
    461 									this.loadData( data );
    462 							},
    463
    464 							loadData : function( data )
    465 							{
    466 								isLoadingData = true;
    467
    468 								// Get the HTML version of the data.
    469 								if ( editor.dataProcessor )
    470 								{
    471 									data = editor.dataProcessor.toHtml( data, fixForBody );
    472 								}
    473
    474 								data =
    475 									editor.config.docType +
    476 									'<html dir="' + editor.config.contentsLangDirection + '">' +
    477 									'<head>' +
    478 										'<link href="' + editor.config.contentsCss + '" type="text/css" rel="stylesheet" _fcktemp="true"/>' +
    479 										'<style type="text/css" _fcktemp="true">' +
    480 											editor._.styles.join( '\n' ) +
    481 										'</style>'+
    482 									'</head>' +
    483 									'<body>' +
    484 										data +
    485 									'</body>' +
    486 									'</html>' +
    487 									activationScript;
    488
    489 								// For custom domain in IE, set the global variable
    490 								// that will temporarily hold the editor data. This
    491 								// reference will be used in the ifram src.
    492 								if ( isCustomDomain )
    493 									window[ '_cke_htmlToLoad_' + editor.name ] = data;
    494
    495 								CKEDITOR._[ 'contentDomReady' + editor.name ] = contentDomReady;
    496
    497 								// We need to recreate the iframe in FF for every
    498 								// data load, otherwise the following spellcheck
    499 								// and execCommand features will be active only for
    500 								// the first time.
    501 								// The same is valid for IE with custom domain,
    502 								// because the iframe src must be reset every time.
    503 								if ( isCustomDomain || CKEDITOR.env.gecko )
    504 									createIFrame();
    505
    506 								// For custom domain in IE, the data loading is
    507 								// done through the src attribute of the iframe.
    508 								if ( !isCustomDomain )
    509 								{
    510 									var doc = iframe.$.contentWindow.document;
    511 									doc.open();
    512 									doc.write( data );
    513 									doc.close();
    514 								}
    515 							},
    516
    517 							getData : function()
    518 							{
    519 								var data = iframe.getFrameDocument().getBody().getHtml();
    520
    521 								if ( editor.dataProcessor )
    522 									data = editor.dataProcessor.toDataFormat( data, fixForBody );
    523
    524 								// Strip the last blank paragraph within document.
    525 								if ( editor.config.ignoreEmptyParagraph )
    526 									data = data.replace( emptyParagraphRegexp, '' );
    527
    528 								return data;
    529 							},
    530
    531 							getSnapshotData : function()
    532 							{
    533 								return iframe.getFrameDocument().getBody().getHtml();
    534 							},
    535
    536 							loadSnapshotData : function( data )
    537 							{
    538 								iframe.getFrameDocument().getBody().setHtml( data );
    539 							},
    540
    541 							unload : function( holderElement )
    542 							{
    543 								editor.window = editor.document = iframe = mainElement = isPendingFocus = null;
    544
    545 								editor.fire( 'contentDomUnload' );
    546 							},
    547
    548 							focus : function()
    549 							{
    550 								if ( isLoadingData )
    551 									isPendingFocus = true;
    552 								else if ( editor.window )
    553 								{
    554 									editor.window.focus();
    555 									editor.selectionChange();
    556 								}
    557 							}
    558 						});
    559
    560 					editor.on( 'insertHtml', onInsertHtml, null, null, 20 );
    561 					editor.on( 'insertElement', onInsertElement, null, null, 20 );
    562 					// Auto fixing on some document structure weakness to enhance usabilities. (#3190 and #3189)
    563 					editor.on( 'selectionChange', onSelectionChangeFixBody, null, null, 1 );
    564 				});
    565 		}
    566 	});
    567 })();
    568
    569 /**
    570  * Disables the ability of resize objects (image and tables) in the editing
    571  * area
    572  * @type Boolean
    573  * @default false
    574  * @example
    575  * config.disableObjectResizing = true;
    576  */
    577 CKEDITOR.config.disableObjectResizing = false;
    578
    579 /**
    580  * Disables the "table tools" offered natively by the browser (currently
    581  * Firefox only) to make quick table editing operations, like adding or
    582  * deleting rows and columns.
    583  * @type Boolean
    584  * @default true
    585  * @example
    586  * config.disableNativeTableHandles = false;
    587  */
    588 CKEDITOR.config.disableNativeTableHandles = true;
    589
    590 /**
    591  * Disables the built-in spell checker while typing natively available in the
    592  * browser (currently Firefox and Safari only).<br /><br />
    593  *
    594  * Even if word suggestions will not appear in the CKEditor context menu, this
    595  * feature is useful to help quickly identifying misspelled words.<br /><br />
    596  *
    597  * This setting is currently compatible with Firefox only due to limitations in
    598  * other browsers.
    599  * @type Boolean
    600  * @default true
    601  * @example
    602  * config.disableNativeSpellChecker = false;
    603  */
    604 CKEDITOR.config.disableNativeSpellChecker = true;
    605 /**
    606  * The editor will post an empty value ("") if you have just an empty paragraph on it, like this:
    607  * @example
    608  * <p></p>
    609  * <p><br /></p>
    610  * <p><b></b></p>
    611  */
    612 CKEDITOR.config.ignoreEmptyParagraph = true;
    613