2 Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
3 For licensing, see LICENSE.html or http://ckeditor.com/license
7 * @fileOverview The "wysiwygarea" plugin. It registers the "wysiwyg" editing
8 * mode, which handles the main editing area space.
13 // Matching an empty paragraph at the end of document.
14 var emptyParagraphRegexp
= /(^|<body\b[^>]*>)\s*<(p|div|address|h\d|center|pre)[^>]*>\s*(?:<br[^>]*>| |\u00A0| )?\s*(:?<\/\2>)?\s*(?=$|<\/body>)/gi;
16 var notWhitespaceEval
= CKEDITOR
.dom
.walker
.whitespaces( true );
18 // Elements that could blink the cursor anchoring beside it, like hr, page-break. (#6554)
19 function nonEditable( element
)
21 return element
.isBlockBoundary() && CKEDITOR
.dtd
.$empty
[ element
.getName() ];
25 function onInsert( insertFunc
)
27 return function( evt
)
29 if ( this.mode
== 'wysiwyg' )
33 this.fire( 'saveSnapshot' );
35 insertFunc
.call( this, evt
.data
);
37 // Save snaps after the whole execution completed.
38 // This's a workaround for make DOM modification's happened after
39 // 'insertElement' to be included either, e.g. Form-based dialogs' 'commitContents'
41 CKEDITOR
.tools
.setTimeout( function()
43 this.fire( 'saveSnapshot' );
49 function doInsertHtml( data
)
51 if ( this.dataProcessor
)
52 data
= this.dataProcessor
.toHtml( data
);
57 // HTML insertion only considers the first range.
58 var selection
= this.getSelection(),
59 range
= selection
.getRanges()[ 0 ];
61 if ( range
.checkReadOnly() )
64 // Opera: force block splitting when pasted content contains block. (#7801)
65 if ( CKEDITOR
.env
.opera
)
67 var path
= new CKEDITOR
.dom
.elementPath( range
.startContainer
);
70 var nodes
= CKEDITOR
.htmlParser
.fragment
.fromHtml( data
, false ).children
;
71 for ( var i
= 0, count
= nodes
.length
; i
< count
; i
++ )
73 if ( nodes
[ i
]._
.isBlockLike
)
75 range
.splitBlock( this.enterMode
== CKEDITOR
.ENTER_DIV
? 'div' : 'p' );
76 range
.insertNode( range
.document
.createText( '' ) );
84 if ( CKEDITOR
.env
.ie
)
86 var selIsLocked
= selection
.isLocked
;
91 var $sel
= selection
.getNative();
93 // Delete control selections to avoid IE bugs on pasteHTML.
94 if ( $sel
.type
== 'Control' )
96 else if ( selection
.getType() == CKEDITOR
.SELECTION_TEXT
)
98 // Due to IE bugs on handling contenteditable=false blocks
99 // (#6005), we need to make some checks and eventually
100 // delete the selection first.
102 range
= selection
.getRanges()[ 0 ];
103 var endContainer
= range
&& range
.endContainer
;
106 endContainer
.type
== CKEDITOR
.NODE_ELEMENT
&&
107 endContainer
.getAttribute( 'contenteditable' ) == 'false' &&
108 range
.checkBoundaryOfElement( endContainer
, CKEDITOR
.END
) )
110 range
.setEndAfter( range
.endContainer
);
111 range
.deleteContents();
117 $sel
.createRange().pasteHTML( data
);
122 this.getSelection().lock();
125 this.document
.$.execCommand( 'inserthtml', false, data
);
127 // Webkit does not scroll to the cursor position after pasting (#5558)
128 if ( CKEDITOR
.env
.webkit
)
130 selection
= this.getSelection();
131 selection
.scrollIntoView();
135 function doInsertText( text
)
137 var selection
= this.getSelection(),
138 mode
= selection
.getStartElement().hasAscendant( 'pre', true ) ?
139 CKEDITOR
.ENTER_BR
: this.config
.enterMode
,
140 isEnterBrMode
= mode
== CKEDITOR
.ENTER_BR
;
142 var html
= CKEDITOR
.tools
.htmlEncode( text
.replace( /\r\n|\r/g, '\n' ) );
144 // Convert leading and trailing whitespaces into
145 html
= html
.replace( /^[ \t]+|[ \t]+$/g, function( match
, offset
, s
)
147 if ( match
.length
== 1 ) // one space, preserve it
149 else if ( !offset
) // beginning of block
150 return CKEDITOR
.tools
.repeat( ' ', match
.length
- 1 ) + ' ';
152 return ' ' + CKEDITOR
.tools
.repeat( ' ', match
.length
- 1 );
155 // Convert subsequent whitespaces into
156 html
= html
.replace( /[ \t]{2,}/g, function ( match
)
158 return CKEDITOR
.tools
.repeat( ' ', match
.length
- 1 ) + ' ';
161 var paragraphTag
= mode
== CKEDITOR
.ENTER_P
? 'p' : 'div';
163 // Two line-breaks create one paragraph.
164 if ( !isEnterBrMode
)
166 html
= html
.replace( /(\n{2})([\s\S]*?)(?:$|\1)/g,
167 function( match
, group1
, text
)
169 return '<'+paragraphTag
+ '>' + text
+ '</' + paragraphTag
+ '>';
173 // One <br> per line-break.
174 html
= html
.replace( /\n/g, '<br>' );
176 // Compensate padding <br> for non-IE.
177 if ( !( isEnterBrMode
|| CKEDITOR
.env
.ie
) )
179 html
= html
.replace( new RegExp( '<br>(?=</' + paragraphTag
+ '>)' ), function( match
)
181 return CKEDITOR
.tools
.repeat( match
, 2 );
185 // Inline styles have to be inherited in Firefox.
186 if ( CKEDITOR
.env
.gecko
|| CKEDITOR
.env
.webkit
)
188 var path
= new CKEDITOR
.dom
.elementPath( selection
.getStartElement() ),
191 for ( var i
= 0; i
< path
.elements
.length
; i
++ )
193 var tag
= path
.elements
[ i
].getName();
194 if ( tag
in CKEDITOR
.dtd
.$inline
)
195 context
.unshift( path
.elements
[ i
].getOuterHtml().match( /^<.*?>/) );
196 else if ( tag
in CKEDITOR
.dtd
.$block
)
200 // Reproduce the context by preceding the pasted HTML with opening inline tags.
201 html
= context
.join( '' ) + html
;
204 doInsertHtml
.call( this, html
);
207 function doInsertElement( element
)
209 var selection
= this.getSelection(),
210 ranges
= selection
.getRanges(),
211 elementName
= element
.getName(),
212 isBlock
= CKEDITOR
.dtd
.$block
[ elementName
];
214 var selIsLocked
= selection
.isLocked
;
219 var range
, clone
, lastElement
, bookmark
;
221 for ( var i
= ranges
.length
- 1 ; i
>= 0 ; i
-- )
225 if ( !range
.checkReadOnly() )
227 // Remove the original contents, merge splitted nodes.
228 range
.deleteContents( 1 );
230 clone
= !i
&& element
|| element
.clone( 1 );
232 // If we're inserting a block at dtd-violated position, split
233 // the parent blocks until we reach blockLimit.
237 while ( ( current
= range
.getCommonAncestor( 0, 1 ) )
238 && ( dtd
= CKEDITOR
.dtd
[ current
.getName() ] )
239 && !( dtd
&& dtd
[ elementName
] ) )
241 // Split up inline elements.
242 if ( current
.getName() in CKEDITOR
.dtd
.span
)
243 range
.splitElement( current
);
244 // If we're in an empty block which indicate a new paragraph,
245 // simply replace it with the inserting block.(#3664)
246 else if ( range
.checkStartOfBlock()
247 && range
.checkEndOfBlock() )
249 range
.setStartBefore( current
);
250 range
.collapse( true );
258 // Insert the new node.
259 range
.insertNode( clone
);
261 // Save the last element reference so we can make the
270 range
.moveToPosition( lastElement
, CKEDITOR
.POSITION_AFTER_END
);
272 // If we're inserting a block element immediatelly followed by
273 // another block element, the selection must move there. (#3100,#5436)
276 var next
= lastElement
.getNext( notWhitespaceEval
),
277 nextName
= next
&& next
.type
== CKEDITOR
.NODE_ELEMENT
&& next
.getName();
279 // Check if it's a block element that accepts text.
280 if ( nextName
&& CKEDITOR
.dtd
.$block
[ nextName
] && CKEDITOR
.dtd
[ nextName
]['#'] )
281 range
.moveToElementEditStart( next
);
285 selection
.selectRanges( [ range
] );
288 this.getSelection().lock();
291 // DOM modification here should not bother dirty flag.(#4385)
292 function restoreDirty( editor
)
294 if ( !editor
.checkDirty() )
295 setTimeout( function(){ editor
.resetDirty(); }, 0 );
298 var isNotWhitespace
= CKEDITOR
.dom
.walker
.whitespaces( true ),
299 isNotBookmark
= CKEDITOR
.dom
.walker
.bookmark( false, true );
301 function isNotEmpty( node
)
303 return isNotWhitespace( node
) && isNotBookmark( node
);
306 function isNbsp( node
)
308 return node
.type
== CKEDITOR
.NODE_TEXT
309 && CKEDITOR
.tools
.trim( node
.getText() ).match( /^(?: |\xa0)$/ );
312 function restoreSelection( selection
)
314 if ( selection
.isLocked
)
317 setTimeout( function() { selection
.lock(); }, 0 );
321 function isBlankParagraph( block
)
323 return block
.getOuterHtml().match( emptyParagraphRegexp
);
326 isNotWhitespace
= CKEDITOR
.dom
.walker
.whitespaces( true );
328 // Gecko need a key event to 'wake up' the editing
329 // ability when document is empty.(#3864, #5781)
330 function activateEditing( editor
)
332 var win
= editor
.window
,
333 doc
= editor
.document
,
334 body
= editor
.document
.getBody(),
335 bodyFirstChild
= body
.getFirst(),
336 bodyChildsNum
= body
.getChildren().count();
339 || bodyChildsNum
== 1
340 && bodyFirstChild
.type
== CKEDITOR
.NODE_ELEMENT
341 && bodyFirstChild
.hasAttribute( '_moz_editor_bogus_node' ) )
343 restoreDirty( editor
);
345 // Memorize scroll position to restore it later (#4472).
346 var hostDocument
= editor
.element
.getDocument();
347 var hostDocumentElement
= hostDocument
.getDocumentElement();
348 var scrollTop
= hostDocumentElement
.$.scrollTop
;
349 var scrollLeft
= hostDocumentElement
.$.scrollLeft
;
351 // Simulating keyboard character input by dispatching a keydown of white-space text.
352 var keyEventSimulate
= doc
.$.createEvent( "KeyEvents" );
353 keyEventSimulate
.initKeyEvent( 'keypress', true, true, win
.$, false,
354 false, false, false, 0, 32 );
355 doc
.$.dispatchEvent( keyEventSimulate
);
357 if ( scrollTop
!= hostDocumentElement
.$.scrollTop
|| scrollLeft
!= hostDocumentElement
.$.scrollLeft
)
358 hostDocument
.getWindow().$.scrollTo( scrollLeft
, scrollTop
);
360 // Restore the original document status by placing the cursor before a bogus br created (#5021).
361 bodyChildsNum
&& body
.getFirst().remove();
362 doc
.getBody().appendBogus();
363 var nativeRange
= new CKEDITOR
.dom
.range( doc
);
364 nativeRange
.setStartAt( body
, CKEDITOR
.POSITION_AFTER_START
);
365 nativeRange
.select();
370 * Auto-fixing block-less content by wrapping paragraph (#3190), prevent
371 * non-exitable-block by padding extra br.(#3189)
373 function onSelectionChangeFixBody( evt
)
375 var editor
= evt
.editor
,
376 path
= evt
.data
.path
,
377 blockLimit
= path
.blockLimit
,
378 selection
= evt
.data
.selection
,
379 range
= selection
.getRanges()[0],
380 body
= editor
.document
.getBody(),
381 enterMode
= editor
.config
.enterMode
;
383 if ( CKEDITOR
.env
.gecko
)
385 activateEditing( editor
);
387 // Ensure bogus br could help to move cursor (out of styles) to the end of block. (#7041)
388 var pathBlock
= path
.block
|| path
.blockLimit
,
389 lastNode
= pathBlock
&& pathBlock
.getLast( isNotEmpty
);
391 // Check some specialities of the current path block:
392 // 1. It is really displayed as block; (#7221)
393 // 2. It doesn't end with one inner block; (#7467)
394 // 3. It doesn't have bogus br yet.
396 && pathBlock
.isBlockBoundary()
397 && !( lastNode
&& lastNode
.type
== CKEDITOR
.NODE_ELEMENT
&& lastNode
.isBlockBoundary() )
398 && !pathBlock
.is( 'pre' )
399 && !pathBlock
.getBogus() )
401 editor
.fire( 'updateSnapshot' );
402 restoreDirty( editor
);
403 pathBlock
.appendBogus();
407 // When we're in block enter mode, a new paragraph will be established
408 // to encapsulate inline contents right under body. (#3657)
409 if ( editor
.config
.autoParagraph
!== false
410 && enterMode
!= CKEDITOR
.ENTER_BR
412 && blockLimit
.getName() == 'body'
415 editor
.fire( 'updateSnapshot' );
416 restoreDirty( editor
);
417 CKEDITOR
.env
.ie
&& restoreSelection( selection
);
419 var fixedBlock
= range
.fixBlock( true,
420 editor
.config
.enterMode
== CKEDITOR
.ENTER_DIV
? 'div' : 'p' );
422 // For IE, we should remove any filler node which was introduced before.
423 if ( CKEDITOR
.env
.ie
)
425 var first
= fixedBlock
.getFirst( isNotEmpty
);
426 first
&& isNbsp( first
) && first
.remove();
429 // If the fixed block is actually blank and is already followed by an exitable blank
430 // block, we should revert the fix and move into the existed one. (#3684)
431 if ( isBlankParagraph( fixedBlock
) )
433 var element
= fixedBlock
.getNext( isNotWhitespace
);
435 element
.type
== CKEDITOR
.NODE_ELEMENT
&&
436 !nonEditable( element
) )
438 range
.moveToElementEditStart( element
);
443 element
= fixedBlock
.getPrevious( isNotWhitespace
);
445 element
.type
== CKEDITOR
.NODE_ELEMENT
&&
446 !nonEditable( element
) )
448 range
.moveToElementEditEnd( element
);
455 // Cancel this selection change in favor of the next (correct). (#6811)
459 // Browsers are incapable of moving cursor out of certain block elements (e.g. table, div, pre)
460 // at the end of document, makes it unable to continue adding content, we have to make this
461 // easier by opening an new empty paragraph.
462 var testRange
= new CKEDITOR
.dom
.range( editor
.document
);
463 testRange
.moveToElementEditEnd( editor
.document
.getBody() );
464 var testPath
= new CKEDITOR
.dom
.elementPath( testRange
.startContainer
);
465 if ( !testPath
.blockLimit
.is( 'body') )
467 editor
.fire( 'updateSnapshot' );
468 restoreDirty( editor
);
469 CKEDITOR
.env
.ie
&& restoreSelection( selection
);
472 if ( enterMode
!= CKEDITOR
.ENTER_BR
)
473 paddingBlock
= body
.append( editor
.document
.createElement( enterMode
== CKEDITOR
.ENTER_P
? 'p' : 'div' ) );
477 if ( !CKEDITOR
.env
.ie
)
478 paddingBlock
.appendBogus();
482 CKEDITOR
.plugins
.add( 'wysiwygarea',
484 requires
: [ 'editingblock' ],
486 init : function( editor
)
488 var fixForBody
= ( editor
.config
.enterMode
!= CKEDITOR
.ENTER_BR
&& editor
.config
.autoParagraph
!== false )
489 ? editor
.config
.enterMode
== CKEDITOR
.ENTER_DIV
? 'div' : 'p' : false;
491 var frameLabel
= editor
.lang
.editorTitle
.replace( '%1', editor
.name
);
493 var contentDomReadyHandler
;
494 editor
.on( 'editingBlockReady', function()
504 // Support for custom document.domain in IE.
505 var isCustomDomain
= CKEDITOR
.env
.isCustomDomain();
507 // Creates the iframe that holds the editable document.
508 var createIFrame = function( data
)
516 // The document domain must be set any time we
517 // call document.open().
518 ( isCustomDomain
? ( 'document.domain="' + document
.domain
+ '";' ) : '' ) +
522 // With IE, the custom domain has to be taken care at first,
523 // for other browers, the 'src' attribute should be left empty to
524 // trigger iframe's 'load' event.
527 'javascript:void(0)' :
529 'javascript:void(function(){' + encodeURIComponent( src
) + '}())'
533 iframe
= CKEDITOR
.dom
.element
.createFromHtml( '<iframe' +
534 ' style="width:100%;height:100%"' +
536 ' title="' + frameLabel
+ '"' +
537 ' src="' + src
+ '"' +
538 ' tabIndex="' + ( CKEDITOR
.env
.webkit
? -1 : editor
.tabIndex
) + '"' +
539 ' allowTransparency="true"' +
542 // Running inside of Firefox chrome the load event doesn't bubble like in a normal page (#5689)
543 if ( document
.location
.protocol
== 'chrome:' )
544 CKEDITOR
.event
.useCapture
= true;
546 // With FF, it's better to load the data on iframe.load. (#3894,#4058)
547 iframe
.on( 'load', function( ev
)
552 var doc
= iframe
.getFrameDocument();
555 CKEDITOR
.env
.air
&& contentDomReady( doc
.getWindow().$ );
558 // Reset adjustment back to default (#5689)
559 if ( document
.location
.protocol
== 'chrome:' )
560 CKEDITOR
.event
.useCapture
= false;
562 mainElement
.append( iframe
);
565 // The script that launches the bootstrap logic on 'domReady', so the document
566 // is fully editable even before the editing iframe is fully loaded (#4455).
567 contentDomReadyHandler
= CKEDITOR
.tools
.addFunction( contentDomReady
);
568 var activationScript
=
569 '<script id="cke_actscrpt" type="text/javascript" data-cke-temp="1">' +
570 ( isCustomDomain
? ( 'document.domain="' + document
.domain
+ '";' ) : '' ) +
571 'window.parent.CKEDITOR.tools.callFunction( ' + contentDomReadyHandler
+ ', window );' +
574 // Editing area bootstrap code.
575 function contentDomReady( domWindow
)
581 editor
.fire( 'ariaWidget', iframe
);
583 var domDocument
= domWindow
.document
,
584 body
= domDocument
.body
;
586 // Remove this script from the DOM.
587 var script
= domDocument
.getElementById( "cke_actscrpt" );
588 script
&& script
.parentNode
.removeChild( script
);
590 body
.spellcheck
= !editor
.config
.disableNativeSpellChecker
;
592 var editable
= !editor
.readOnly
;
594 if ( CKEDITOR
.env
.ie
)
596 // Don't display the focus border.
597 body
.hideFocus
= true;
599 // Disable and re-enable the body to avoid IE from
600 // taking the editing focus at startup. (#141 / #523)
601 body
.disabled
= true;
602 body
.contentEditable
= editable
;
603 body
.removeAttribute( 'disabled' );
607 // Avoid opening design mode in a frame window thread,
608 // which will cause host page scrolling.(#4397)
609 setTimeout( function()
611 // Prefer 'contentEditable' instead of 'designMode'. (#3593)
612 if ( CKEDITOR
.env
.gecko
&& CKEDITOR
.env
.version
>= 10900
613 || CKEDITOR
.env
.opera
)
614 domDocument
.$.body
.contentEditable
= editable
;
615 else if ( CKEDITOR
.env
.webkit
)
616 domDocument
.$.body
.parentNode
.contentEditable
= editable
;
618 domDocument
.$.designMode
= editable
? 'off' : 'on';
622 editable
&& CKEDITOR
.env
.gecko
&& CKEDITOR
.tools
.setTimeout( activateEditing
, 0, null, editor
);
624 domWindow
= editor
.window
= new CKEDITOR
.dom
.window( domWindow
);
625 domDocument
= editor
.document
= new CKEDITOR
.dom
.document( domDocument
);
627 editable
&& domDocument
.on( 'dblclick', function( evt
)
629 var element
= evt
.data
.getTarget(),
630 data
= { element
: element
, dialog
: '' };
631 editor
.fire( 'doubleclick', data
);
632 data
.dialog
&& editor
.openDialog( data
.dialog
);
635 // Prevent automatic submission in IE #6336
636 CKEDITOR
.env
.ie
&& domDocument
.on( 'click', function( evt
)
638 var element
= evt
.data
.getTarget();
639 if ( element
.is( 'input' ) )
641 var type
= element
.getAttribute( 'type' );
642 if ( type
== 'submit' || type
== 'reset' )
643 evt
.data
.preventDefault();
647 // Gecko/Webkit need some help when selecting control type elements. (#3448)
648 if ( !( CKEDITOR
.env
.ie
|| CKEDITOR
.env
.opera
) )
650 domDocument
.on( 'mousedown', function( ev
)
652 var control
= ev
.data
.getTarget();
653 if ( control
.is( 'img', 'hr', 'input', 'textarea', 'select' ) )
654 editor
.getSelection().selectElement( control
);
658 if ( CKEDITOR
.env
.gecko
)
660 domDocument
.on( 'mouseup', function( ev
)
662 if ( ev
.data
.$.button
== 2 )
664 var target
= ev
.data
.getTarget();
666 // Prevent right click from selecting an empty block even
667 // when selection is anchored inside it. (#5845)
668 if ( !target
.getOuterHtml().replace( emptyParagraphRegexp
, '' ) )
670 var range
= new CKEDITOR
.dom
.range( domDocument
);
671 range
.moveToElementEditStart( target
);
672 range
.select( true );
678 // Prevent the browser opening links in read-only blocks. (#6032)
679 domDocument
.on( 'click', function( ev
)
682 if ( ev
.getTarget().is( 'a' ) && ev
.$.button
!= 2 )
686 // Webkit: avoid from editing form control elements content.
687 if ( CKEDITOR
.env
.webkit
)
689 // Mark that cursor will right blinking (#7113).
690 domDocument
.on( 'mousedown', function() { wasFocused
= 1; } );
691 // Prevent from tick checkbox/radiobox/select
692 domDocument
.on( 'click', function( ev
)
694 if ( ev
.data
.getTarget().is( 'input', 'select' ) )
695 ev
.data
.preventDefault();
698 // Prevent from editig textfield/textarea value.
699 domDocument
.on( 'mouseup', function( ev
)
701 if ( ev
.data
.getTarget().is( 'input', 'textarea' ) )
702 ev
.data
.preventDefault();
706 // IE standard compliant in editing frame doesn't focus the editor when
707 // clicking outside actual content, manually apply the focus. (#1659)
709 CKEDITOR
.env
.ie
&& domDocument
.$.compatMode
== 'CSS1Compat'
710 || CKEDITOR
.env
.gecko
711 || CKEDITOR
.env
.opera
)
713 var htmlElement
= domDocument
.getDocumentElement();
714 htmlElement
.on( 'mousedown', function( evt
)
716 // Setting focus directly on editor doesn't work, we
717 // have to use here a temporary element to 'redirect'
719 if ( evt
.data
.getTarget().equals( htmlElement
) )
721 if ( CKEDITOR
.env
.gecko
&& CKEDITOR
.env
.version
>= 10900 )
723 focusGrabber
.focus();
728 var focusTarget
= CKEDITOR
.env
.ie
? iframe
: domWindow
;
729 focusTarget
.on( 'blur', function()
731 editor
.focusManager
.blur();
736 focusTarget
.on( 'focus', function()
738 var doc
= editor
.document
;
740 if ( editable
&& CKEDITOR
.env
.gecko
&& CKEDITOR
.env
.version
>= 10900 )
742 else if ( CKEDITOR
.env
.opera
)
743 doc
.getBody().focus();
744 // Webkit needs focus for the first time on the HTML element. (#6153)
745 else if ( CKEDITOR
.env
.webkit
)
749 editor
.document
.getDocumentElement().focus();
754 editor
.focusManager
.focus();
757 var keystrokeHandler
= editor
.keystrokeHandler
;
758 // Prevent backspace from navigating off the page.
759 keystrokeHandler
.blockedKeystrokes
[ 8 ] = !editable
;
760 keystrokeHandler
.attach( domDocument
);
762 if ( CKEDITOR
.env
.ie
)
764 domDocument
.getDocumentElement().addClass( domDocument
.$.compatMode
);
765 // Override keystrokes which should have deletion behavior
766 // on control types in IE . (#4047)
767 editable
&& domDocument
.on( 'keydown', function( evt
)
769 var keyCode
= evt
.data
.getKeystroke();
771 // Backspace OR Delete.
772 if ( keyCode
in { 8 : 1, 46 : 1 } )
774 var sel
= editor
.getSelection(),
775 control
= sel
.getSelectedElement();
779 // Make undo snapshot.
780 editor
.fire( 'saveSnapshot' );
782 // Delete any element that 'hasLayout' (e.g. hr,table) in IE8 will
783 // break up the selection, safely manage it here. (#4795)
784 var bookmark
= sel
.getRanges()[ 0 ].createBookmark();
785 // Remove the control manually.
787 sel
.selectBookmarks( [ bookmark
] );
789 editor
.fire( 'saveSnapshot' );
791 evt
.data
.preventDefault();
796 // PageUp/PageDown scrolling is broken in document
797 // with standard doctype, manually fix it. (#4736)
798 if ( domDocument
.$.compatMode
== 'CSS1Compat' )
800 var pageUpDownKeys
= { 33 : 1, 34 : 1 };
801 domDocument
.on( 'keydown', function( evt
)
803 if ( evt
.data
.getKeystroke() in pageUpDownKeys
)
805 setTimeout( function ()
807 editor
.getSelection().scrollIntoView();
813 // Prevent IE from leaving new paragraph after deleting all contents in body. (#6966)
814 editor
.config
.enterMode
!= CKEDITOR
.ENTER_P
815 && domDocument
.on( 'selectionchange', function()
817 var body
= domDocument
.getBody(),
818 range
= editor
.getSelection().getRanges()[ 0 ];
820 if ( body
.getHtml().match( /^<p> <\/p>$/i )
821 && range
.startContainer
.equals( body
) )
823 // Avoid the ambiguity from a real user cursor position.
824 setTimeout( function ()
826 range
= editor
.getSelection().getRanges()[ 0 ];
827 if ( !range
.startContainer
.equals ( 'body' ) )
829 body
.getFirst().remove( 1 );
830 range
.moveToElementEditEnd( body
);
838 // Adds the document body as a context menu target.
839 if ( editor
.contextMenu
)
840 editor
.contextMenu
.addTarget( domDocument
, editor
.config
.browserContextMenuOnCtrl
!== false );
842 setTimeout( function()
844 editor
.fire( 'contentDom' );
848 editor
.mode
= 'wysiwyg';
849 editor
.fire( 'mode' );
853 isLoadingData
= false;
855 if ( isPendingFocus
)
858 isPendingFocus
= false;
860 setTimeout( function()
862 editor
.fire( 'dataReady' );
865 // IE, Opera and Safari may not support it and throw errors.
866 try { editor
.document
.$.execCommand( 'enableInlineTableEditing', false, !editor
.config
.disableNativeTableHandles
); } catch(e
) {}
867 if ( editor
.config
.disableObjectResizing
)
871 editor
.document
.$.execCommand( 'enableObjectResizing', false, false );
875 // For browsers in which the above method failed, we can cancel the resizing on the fly (#4208)
876 editor
.document
.getBody().on( CKEDITOR
.env
.ie
? 'resizestart' : 'resize', function( evt
)
878 evt
.data
.preventDefault();
884 * IE BUG: IE might have rendered the iframe with invisible contents.
885 * (#3623). Push some inconsequential CSS style changes to force IE to
888 * Also, for some unknown reasons, short timeouts (e.g. 100ms) do not
889 * fix the problem. :(
891 if ( CKEDITOR
.env
.ie
)
893 setTimeout( function()
895 if ( editor
.document
)
897 var $body
= editor
.document
.$.body
;
898 $body
.runtimeStyle
.marginBottom
= '0px';
899 $body
.runtimeStyle
.marginBottom
= '';
907 editor
.addMode( 'wysiwyg',
909 load : function( holderElement
, data
, isSnapshot
)
911 mainElement
= holderElement
;
913 if ( CKEDITOR
.env
.ie
&& CKEDITOR
.env
.quirks
)
914 holderElement
.setStyle( 'position', 'relative' );
916 // The editor data "may be dirty" after this
918 editor
.mayBeDirty
= true;
923 this.loadSnapshotData( data
);
925 this.loadData( data
);
928 loadData : function( data
)
930 isLoadingData
= true;
931 editor
._
.dataStore
= { id
: 1 };
933 var config
= editor
.config
,
934 fullPage
= config
.fullPage
,
935 docType
= config
.docType
;
937 // Build the additional stuff to be included into <head>.
939 '<style type="text/css" data-cke-temp="1">' +
940 editor
._
.styles
.join( '\n' ) +
943 !fullPage
&& ( headExtra
=
944 CKEDITOR
.tools
.buildStyleHtml( editor
.config
.contentsCss
) +
947 var baseTag
= config
.baseHref
? '<base href="' + config
.baseHref
+ '" data-cke-temp="1" />' : '';
951 // Search and sweep out the doctype declaration.
952 data
= data
.replace( /<!DOCTYPE[^>]*>/i, function( match
)
954 editor
.docType
= docType
= match
;
956 }).replace( /<\?xml\s[^\?]*\?>/i, function( match
)
958 editor
.xmlDeclaration
= match
;
963 // Get the HTML version of the data.
964 if ( editor
.dataProcessor
)
965 data
= editor
.dataProcessor
.toHtml( data
, fixForBody
);
969 // Check if the <body> tag is available.
970 if ( !(/<body[\s|>]/).test( data
) )
971 data
= '<body>' + data
;
973 // Check if the <html> tag is available.
974 if ( !(/<html[\s|>]/).test( data
) )
975 data
= '<html>' + data
+ '</html>';
977 // Check if the <head> tag is available.
978 if ( !(/<head[\s|>]/).test( data
) )
979 data
= data
.replace( /<html[^>]*>/, '$&<head><title></title></head>' ) ;
980 else if ( !(/<title[\s|>]/).test( data
) )
981 data
= data
.replace( /<head[^>]*>/, '$&<title></title>' ) ;
983 // The base must be the first tag in the HEAD, e.g. to get relative
985 baseTag
&& ( data
= data
.replace( /<head>/, '$&' + baseTag
) );
987 // Inject the extra stuff into <head>.
988 // Attention: do not change it before testing it well. (V2)
989 // This is tricky... if the head ends with <meta ... content type>,
990 // Firefox will break. But, it works if we place our extra stuff as
991 // the last elements in the HEAD.
992 data
= data
.replace( /<\/head\s*>/, headExtra
+ '$&' );
994 // Add the DOCTYPE back to it.
995 data
= docType
+ data
;
1001 '<html dir="' + config
.contentsLangDirection
+ '"' +
1002 ' lang="' + ( config
.contentsLanguage
|| editor
.langCode
) + '">' +
1004 '<title>' + frameLabel
+ '</title>' +
1008 '<body' + ( config
.bodyId
? ' id="' + config
.bodyId
+ '"' : '' ) +
1009 ( config
.bodyClass
? ' class="' + config
.bodyClass
+ '"' : '' ) +
1015 // Distinguish bogus to normal BR at the end of document for Mozilla. (#5293).
1016 if ( CKEDITOR
.env
.gecko
)
1017 data
= data
.replace( /<br \/>(?=\s*<\/(:?html|body)>)/, '$&<br type="_moz" />' );
1019 data
+= activationScript
;
1022 // The iframe is recreated on each call of setData, so we need to clear DOM objects
1024 createIFrame( data
);
1027 getData : function()
1029 var config
= editor
.config
,
1030 fullPage
= config
.fullPage
,
1031 docType
= fullPage
&& editor
.docType
,
1032 xmlDeclaration
= fullPage
&& editor
.xmlDeclaration
,
1033 doc
= iframe
.getFrameDocument();
1036 ? doc
.getDocumentElement().getOuterHtml()
1037 : doc
.getBody().getHtml();
1039 // BR at the end of document is bogus node for Mozilla. (#5293).
1040 if ( CKEDITOR
.env
.gecko
)
1041 data
= data
.replace( /<br>(?=\s*(:?$|<\/body>))/, '' );
1043 if ( editor
.dataProcessor
)
1044 data
= editor
.dataProcessor
.toDataFormat( data
, fixForBody
);
1046 // Reset empty if the document contains only one empty paragraph.
1047 if ( config
.ignoreEmptyParagraph
)
1048 data
= data
.replace( emptyParagraphRegexp
, function( match
, lookback
) { return lookback
; } );
1050 if ( xmlDeclaration
)
1051 data
= xmlDeclaration
+ '\n' + data
;
1053 data
= docType
+ '\n' + data
;
1058 getSnapshotData : function()
1060 return iframe
.getFrameDocument().getBody().getHtml();
1063 loadSnapshotData : function( data
)
1065 iframe
.getFrameDocument().getBody().setHtml( data
);
1068 onDispose : function()
1070 if ( !editor
.document
)
1073 editor
.document
.getDocumentElement().clearCustomData();
1074 editor
.document
.getBody().clearCustomData();
1076 editor
.window
.clearCustomData();
1077 editor
.document
.clearCustomData();
1079 iframe
.clearCustomData();
1082 * IE BUG: When destroying editor DOM with the selection remains inside
1083 * editing area would break IE7/8's selection system, we have to put the editing
1084 * iframe offline first. (#3812 and #5441)
1089 unload : function( holderElement
)
1093 editor
.window
= editor
.document
= iframe
= mainElement
= isPendingFocus
= null;
1095 editor
.fire( 'contentDomUnload' );
1100 var win
= editor
.window
;
1102 if ( isLoadingData
)
1103 isPendingFocus
= true;
1104 // Temporary solution caused by #6025, supposed be unified by #6154.
1105 else if ( CKEDITOR
.env
.opera
&& editor
.document
)
1107 // Required for Opera when switching focus
1108 // from another iframe, e.g. panels. (#6444)
1109 var iframe
= editor
.window
.$.frameElement
;
1110 iframe
.blur(), iframe
.focus();
1111 editor
.document
.getBody().focus();
1113 editor
.selectionChange();
1115 else if ( !CKEDITOR
.env
.opera
&& win
)
1117 // AIR needs a while to focus when moving from a link.
1118 CKEDITOR
.env
.air
? setTimeout( function () { win
.focus(); }, 0 ) : win
.focus();
1119 editor
.selectionChange();
1124 editor
.on( 'insertHtml', onInsert( doInsertHtml
) , null, null, 20 );
1125 editor
.on( 'insertElement', onInsert( doInsertElement
), null, null, 20 );
1126 editor
.on( 'insertText', onInsert( doInsertText
), null, null, 20 );
1127 // Auto fixing on some document structure weakness to enhance usabilities. (#3190 and #3189)
1128 editor
.on( 'selectionChange', onSelectionChangeFixBody
, null, null, 1 );
1132 // Setting voice label as window title, backup the original one
1133 // and restore it before running into use.
1134 editor
.on( 'contentDom', function()
1136 var title
= editor
.document
.getElementsByTag( 'title' ).getItem( 0 );
1137 title
.data( 'cke-title', editor
.document
.$.title
);
1138 editor
.document
.$.title
= frameLabel
;
1141 editor
.on( 'readOnly', function()
1143 if ( editor
.mode
== 'wysiwyg' )
1145 // Symply reload the wysiwyg area. It'll take care of read-only.
1146 var wysiwyg
= editor
.getMode();
1147 wysiwyg
.loadData( wysiwyg
.getData() );
1151 // IE>=8 stricts mode doesn't have 'contentEditable' in effect
1152 // on element unless it has layout. (#5562)
1153 if ( CKEDITOR
.document
.$.documentMode
>= 8 )
1155 editor
.addCss( 'html.CSS1Compat [contenteditable=false]{ min-height:0 !important;}' );
1158 for ( var tag
in CKEDITOR
.dtd
.$removeEmpty
)
1159 selectors
.push( 'html.CSS1Compat ' + tag
+ '[contenteditable=false]' );
1160 editor
.addCss( selectors
.join( ',' ) + '{ display:inline-block;}' );
1162 // Set the HTML style to 100% to have the text cursor in affect (#6341)
1163 else if ( CKEDITOR
.env
.gecko
)
1165 editor
.addCss( 'html { height: 100% !important; }' );
1166 editor
.addCss( 'img:-moz-broken { -moz-force-broken-image-icon : 1; width : 24px; height : 24px; }' );
1169 /* #3658: [IE6] Editor document has horizontal scrollbar on long lines
1170 To prevent this misbehavior, we show the scrollbar always */
1171 /* #6341: The text cursor must be set on the editor area. */
1172 /* #6632: Avoid having "text" shape of cursor in IE7 scrollbars.*/
1173 editor
.addCss( 'html { _overflow-y: scroll; cursor: text; *cursor:auto;}' );
1174 // Use correct cursor for these elements
1175 editor
.addCss( 'img, input, textarea { cursor: default;}' );
1177 // Switch on design mode for a short while and close it after then.
1178 function blinkCursor( retry
)
1180 if ( editor
.readOnly
)
1183 CKEDITOR
.tools
.tryThese(
1186 editor
.document
.$.designMode
= 'on';
1187 setTimeout( function()
1189 editor
.document
.$.designMode
= 'off';
1190 if ( CKEDITOR
.currentInstance
== editor
)
1191 editor
.document
.getBody().focus();
1196 // The above call is known to fail when parent DOM
1197 // tree layout changes may break design mode. (#5782)
1198 // Refresh the 'contentEditable' is a cue to this.
1199 editor
.document
.$.designMode
= 'off';
1200 var body
= editor
.document
.getBody();
1201 body
.setAttribute( 'contentEditable', false );
1202 body
.setAttribute( 'contentEditable', true );
1203 // Try it again once..
1204 !retry
&& blinkCursor( 1 );
1208 // Create an invisible element to grab focus.
1209 if ( CKEDITOR
.env
.gecko
|| CKEDITOR
.env
.ie
|| CKEDITOR
.env
.opera
)
1212 editor
.on( 'uiReady', function()
1214 focusGrabber
= editor
.container
.append( CKEDITOR
.dom
.element
.createFromHtml(
1215 // Use 'span' instead of anything else to fly under the screen-reader radar. (#5049)
1216 '<span tabindex="-1" style="position:absolute;" role="presentation"></span>' ) );
1218 focusGrabber
.on( 'focus', function()
1223 editor
.focusGrabber
= focusGrabber
;
1225 editor
.on( 'destroy', function()
1227 CKEDITOR
.tools
.removeFunction( contentDomReadyHandler
);
1228 focusGrabber
.clearCustomData();
1229 delete editor
.focusGrabber
;
1233 // Disable form elements editing mode provided by some browers. (#5746)
1234 editor
.on( 'insertElement', function ( evt
)
1236 var element
= evt
.data
;
1237 if ( element
.type
== CKEDITOR
.NODE_ELEMENT
1238 && ( element
.is( 'input' ) || element
.is( 'textarea' ) ) )
1240 // We should flag that the element was locked by our code so
1241 // it'll be editable by the editor functions (#6046).
1242 if ( !element
.isReadOnly() )
1243 element
.data( 'cke-editable', element
.hasAttribute( 'contenteditable' ) ? 'true' : '1' );
1244 element
.setAttribute( 'contentEditable', false );
1251 // Fixing Firefox 'Back-Forward Cache' break design mode. (#4514)
1252 if ( CKEDITOR
.env
.gecko
)
1256 var body
= document
.body
;
1259 window
.addEventListener( 'load', arguments
.callee
, false );
1262 var currentHandler
= body
.getAttribute( 'onpageshow' );
1263 body
.setAttribute( 'onpageshow', ( currentHandler
? currentHandler
+ ';' : '') +
1264 'event.persisted && (function(){' +
1265 'var allInstances = CKEDITOR.instances, editor, doc;' +
1266 'for ( var i in allInstances )' +
1268 ' editor = allInstances[ i ];' +
1269 ' doc = editor.document;' +
1272 ' doc.$.designMode = "off";' +
1273 ' doc.$.designMode = "on";' +
1284 * Disables the ability of resize objects (image and tables) in the editing
1289 * config.disableObjectResizing = true;
1291 CKEDITOR
.config
.disableObjectResizing
= false;
1294 * Disables the "table tools" offered natively by the browser (currently
1295 * Firefox only) to make quick table editing operations, like adding or
1296 * deleting rows and columns.
1300 * config.disableNativeTableHandles = false;
1302 CKEDITOR
.config
.disableNativeTableHandles
= true;
1305 * Disables the built-in words spell checker if browser provides one.<br /><br />
1307 * <strong>Note:</strong> Although word suggestions provided by browsers (natively) will not appear in CKEditor's default context menu,
1308 * users can always reach the native context menu by holding the <em>Ctrl</em> key when right-clicking if {@link CKEDITOR.config.browserContextMenuOnCtrl}
1309 * is enabled or you're simply not using the context menu plugin.
1314 * config.disableNativeSpellChecker = false;
1316 CKEDITOR
.config
.disableNativeSpellChecker
= true;
1319 * Whether the editor must output an empty value ("") if it's contents is made
1320 * by an empty paragraph only.
1324 * config.ignoreEmptyParagraph = false;
1326 CKEDITOR
.config
.ignoreEmptyParagraph
= true;
1329 * Fired when data is loaded and ready for retrieval in an editor instance.
1330 * @name CKEDITOR.editor#dataReady
1335 * Whether automatically create wrapping blocks around inline contents inside document body,
1336 * this helps to ensure the integrality of the block enter mode.
1337 * <strong>Note:</strong> Changing the default value might introduce unpredictable usability issues.
1338 * @name CKEDITOR.config.autoParagraph
1343 * config.autoParagraph = false;
1347 * Fired when some elements are added to the document
1348 * @name CKEDITOR.editor#ariaWidget
1350 * @param {Object} element The element being added