diff options
Diffstat (limited to '_source/plugins')
102 files changed, 5031 insertions, 1550 deletions
diff --git a/_source/plugins/about/dialogs/about.js b/_source/plugins/about/dialogs/about.js index ac79144..ef94596 100644 --- a/_source/plugins/about/dialogs/about.js +++ b/_source/plugins/about/dialogs/about.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/about/plugin.js b/_source/plugins/about/plugin.js index 1b702d9..b8512c4 100644 --- a/_source/plugins/about/plugin.js +++ b/_source/plugins/about/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/basicstyles/plugin.js b/_source/plugins/basicstyles/plugin.js index 4eaabb9..91a2523 100644 --- a/_source/plugins/basicstyles/plugin.js +++ b/_source/plugins/basicstyles/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/blockquote/plugin.js b/_source/plugins/blockquote/plugin.js index f9dccd9..d5f88aa 100644 --- a/_source/plugins/blockquote/plugin.js +++ b/_source/plugins/blockquote/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/button/plugin.js b/_source/plugins/button/plugin.js index fb8ec88..f39c61d 100644 --- a/_source/plugins/button/plugin.js +++ b/_source/plugins/button/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/clipboard/dialogs/paste.js b/_source/plugins/clipboard/dialogs/paste.js index ed75087..5ba6e48 100644 --- a/_source/plugins/clipboard/dialogs/paste.js +++ b/_source/plugins/clipboard/dialogs/paste.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -118,7 +118,7 @@ CKEDITOR.dialog.add( 'paste', function( editor ) html = iframe.$.contentWindow.document.body.innerHTML; setTimeout( function(){ - editor.insertHtml( html ); + editor.fire( 'paste', { 'html' : html } ); }, 0 ); }, diff --git a/_source/plugins/clipboard/plugin.js b/_source/plugins/clipboard/plugin.js index c1f67b1..0efa518 100644 --- a/_source/plugins/clipboard/plugin.js +++ b/_source/plugins/clipboard/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -78,59 +78,66 @@ For licensing, see LICENSE.html or http://ckeditor.com/license // Paste command. var pasteCmd = - CKEDITOR.env.ie ? - { - exec : function( editor, data ) + { + canUndo : false, + + exec : + CKEDITOR.env.ie ? + function( editor ) { // Prevent IE from pasting at the begining of the document. editor.focus(); - if ( !editor.fire( 'beforePaste' ) - && !execIECommand( editor, 'paste' ) ) + if ( !editor.document.getBody().fire( 'beforepaste' ) + && !execIECommand( editor, 'paste' ) ) { - editor.openDialog( 'paste' ); + editor.fire( 'pasteDialog' ); + return false; } } - } - : - { - exec : function( editor ) + : + function( editor ) { try { - if ( !editor.fire( 'beforePaste' ) - && !editor.document.$.execCommand( 'Paste', false, null ) ) + if ( !editor.document.getBody().fire( 'beforepaste' ) + && !editor.document.$.execCommand( 'Paste', false, null ) ) { throw 0; } } catch ( e ) { - // Open the paste dialog. - editor.openDialog( 'paste' ); + setTimeout( function() + { + editor.fire( 'pasteDialog' ); + }, 0 ); + return false; } } - }; + }; // Listens for some clipboard related keystrokes, so they get customized. var onKey = function( event ) { + if ( this.mode != 'wysiwyg' ) + return; + switch ( event.data.keyCode ) { // Paste case CKEDITOR.CTRL + 86 : // CTRL+V case CKEDITOR.SHIFT + 45 : // SHIFT+INS - var editor = this; - editor.fire( 'saveSnapshot' ); // Save before paste + var body = this.document.getBody(); - if ( editor.fire( 'beforePaste' ) ) + // Simulate 'beforepaste' event for all none-IEs. + if ( !CKEDITOR.env.ie && body.fire( 'beforepaste' ) ) event.cancel(); - - setTimeout( function() - { - editor.fire( 'saveSnapshot' ); // Save after paste - }, 0 ); + // Simulate 'paste' event for Opera/Firefox2. + else if ( CKEDITOR.env.opera + || CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 ) + body.fire( 'paste' ); return; // Cut @@ -138,8 +145,8 @@ For licensing, see LICENSE.html or http://ckeditor.com/license case CKEDITOR.SHIFT + 46 : // SHIFT+DEL // Save Undo snapshot. - editor = this; - editor.fire( 'saveSnapshot' ); // Save before paste + var editor = this; + this.fire( 'saveSnapshot' ); // Save before paste setTimeout( function() { editor.fire( 'saveSnapshot' ); // Save after paste @@ -147,11 +154,117 @@ For licensing, see LICENSE.html or http://ckeditor.com/license } }; + // Allow to peek clipboard content by redirecting the + // pasting content into a temporary bin and grab the content of it. + function getClipboardData( evt, mode, callback ) + { + var doc = this.document; + + // Avoid recursions on 'paste' event for IE. + if ( CKEDITOR.env.ie && doc.getById( 'cke_pastebin' ) ) + return; + + var sel = this.getSelection(), + range = new CKEDITOR.dom.range( doc ); + + // Create container to paste into + var pastebin = new CKEDITOR.dom.element( mode == 'text' ? 'textarea' : 'div', doc ); + pastebin.setAttribute( 'id', 'cke_pastebin' ); + // Safari requires a filler node inside the div to have the content pasted into it. (#4882) + CKEDITOR.env.webkit && pastebin.append( doc.createText( '\xa0' ) ); + doc.getBody().append( pastebin ); + + // It's definitely a better user experience if we make the paste-bin pretty unnoticed + // by pulling it off the screen, while this hack will make the paste-bin a control type element + // and that become a selection plain later. + if ( !CKEDITOR.env.ie && mode != 'html' ) + { + pastebin.setStyles( + { + position : 'absolute', + left : '-1000px', + // Position the bin exactly at the position of the selected element + // to avoid any subsequent document scroll. + top : sel.getStartElement().getDocumentPosition().y + 'px', + width : '1px', + height : '1px', + overflow : 'hidden' + }); + } + + var bms = sel.createBookmarks(); + + // Turn off design mode temporarily before give focus to the paste bin. + if ( mode == 'text' ) + { + if ( CKEDITOR.env.ie ) + { + var ieRange = doc.getBody().$.createTextRange(); + ieRange.moveToElementText( pastebin.$ ); + ieRange.execCommand( 'Paste' ); + evt.data.preventDefault(); + } + else + { + doc.$.designMode = 'off'; + pastebin.$.focus(); + } + } + else + { + range.setStartAt( pastebin, CKEDITOR.POSITION_AFTER_START ); + range.setEndAt( pastebin, CKEDITOR.POSITION_BEFORE_END ); + range.select( true ); + } + + // Wait a while and grab the pasted contents + window.setTimeout( function() + { + mode == 'text' && !CKEDITOR.env.ie && ( doc.$.designMode = 'on' ); + pastebin.remove(); + + // Grab the HTML contents. + // We need to look for a apple style wrapper on webkit it also adds + // a div wrapper if you copy/paste the body of the editor. + // Remove hidden div and restore selection. + var bogusSpan; + pastebin = ( CKEDITOR.env.webkit + && ( bogusSpan = pastebin.getFirst() ) + && ( bogusSpan.is && bogusSpan.hasClass( 'Apple-style-span' ) ) ? + bogusSpan : pastebin ); + + sel.selectBookmarks( bms ); + callback( pastebin[ 'get' + ( mode == 'text' ? 'Value' : 'Html' ) ]() ); + }, 0 ); + } + // Register the plugin. CKEDITOR.plugins.add( 'clipboard', { + requires : [ 'htmldataprocessor' ], init : function( editor ) { + // Inserts processed data into the editor at the end of the + // events chain. + editor.on( 'paste', function( evt ) + { + var data = evt.data; + if ( data[ 'html' ] ) + editor.insertHtml( data[ 'html' ] ); + else if ( data[ 'text' ] ) + editor.insertText( data[ 'text' ] ); + + }, null, null, 1000 ); + + editor.on( 'pasteDialog', function( evt ) + { + setTimeout( function() + { + // Open default paste dialog. + editor.openDialog( 'paste' ); + }, 0 ); + }); + function addButtonCommand( buttonName, commandName, command, ctxMenuOrder ) { var lang = editor.lang[ commandName ]; @@ -184,12 +297,49 @@ For licensing, see LICENSE.html or http://ckeditor.com/license editor.on( 'key', onKey, editor ); + var mode = editor.config.forcePasteAsPlainText ? 'text' : 'html'; + + // We'll be catching all pasted content in one line, regardless of whether the + // it's introduced by a document command execution (e.g. toolbar buttons) or + // user paste behaviors. (e.g. Ctrl-V) + editor.on( 'contentDom', function() + { + var body = editor.document.getBody(); + body.on( ( mode == 'text' && CKEDITOR.env.ie ) ? 'paste' : 'beforepaste', + function( evt ) + { + if( depressBeforePasteEvent ) + return; + + getClipboardData.call( editor, evt, mode, function ( data ) + { + // The very last guard to make sure the + // paste has successfully happened. + if ( !data ) + return; + + var dataTransfer = {}; + dataTransfer[ mode ] = data; + editor.fire( 'paste', dataTransfer ); + } ); + }); + + }); + // If the "contextmenu" plugin is loaded, register the listeners. if ( editor.contextMenu ) { + var depressBeforePasteEvent; function stateFromNamedCommand( command ) { - return editor.document.$.queryCommandEnabled( command ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED; + // IE Bug: queryCommandEnabled('paste') fires also 'beforepaste', + // guard to distinguish from the ordinary sources( either + // keyboard paste or execCommand ) (#4874). + CKEDITOR.env.ie && command == 'Paste'&& ( depressBeforePasteEvent = 1 ); + + var retval = editor.document.$.queryCommandEnabled( command ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED; + depressBeforePasteEvent = 0; + return retval; } editor.contextMenu.addListener( function() diff --git a/_source/plugins/colorbutton/plugin.js b/_source/plugins/colorbutton/plugin.js index 69dd762..58882c1 100644 --- a/_source/plugins/colorbutton/plugin.js +++ b/_source/plugins/colorbutton/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -31,7 +31,7 @@ CKEDITOR.plugins.add( 'colorbutton', panel : { - css : [ CKEDITOR.getUrl( editor.skinPath + 'editor.css' ) ] + css : editor.skin.editor.css }, onBlock : function( panel, blockName ) @@ -61,8 +61,21 @@ CKEDITOR.plugins.add( 'colorbutton', { if ( color == '?' ) { - // TODO : Implement the colors dialog. - // editor.openDialog( '' ); + var applyColorStyle = arguments.callee; + function onColorDialogClose( evt ) + { + this.removeListener( 'ok', onColorDialogClose ); + this.removeListener( 'cancel', onColorDialogClose ); + + evt.name == 'ok' && applyColorStyle( this.getContentElement( 'picker', 'selectedColor' ).getValue(), type ); + } + + editor.openDialog( 'colordialog', function() + { + this.on( 'ok', onColorDialogClose ); + this.on( 'cancel', onColorDialogClose ); + } ); + return; } @@ -148,7 +161,7 @@ CKEDITOR.plugins.add( 'colorbutton', * @example * config.colorButton_enableMore = false; */ -CKEDITOR.config.colorButton_enableMore = false; +CKEDITOR.config.colorButton_enableMore = true; /** * Defines the colors to be displayed in the color selectors. It's a string diff --git a/_source/plugins/colordialog/dialogs/colordialog.js b/_source/plugins/colordialog/dialogs/colordialog.js index 5fb8a37..63dfa2b 100644 --- a/_source/plugins/colordialog/dialogs/colordialog.js +++ b/_source/plugins/colordialog/dialogs/colordialog.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/contextmenu/plugin.js b/_source/plugins/contextmenu/plugin.js index 5e1bf61..1cbf7d7 100644 --- a/_source/plugins/contextmenu/plugin.js +++ b/_source/plugins/contextmenu/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -83,17 +83,15 @@ CKEDITOR.plugins.contextMenu = CKEDITOR.tools.createClass( var selection = this.editor.getSelection(), element = selection && selection.getStartElement(); - // Lock the selection in IE, so it can be restored when closing the - // menu. - if ( CKEDITOR.env.ie ) - selection.lock(); - menu.onHide = CKEDITOR.tools.bind( function() { menu.onHide = null; if ( CKEDITOR.env.ie ) - editor.getSelection().unlock(); + { + var selection = editor.getSelection(); + selection && selection.unlock(); + } this.onHide && this.onHide(); }, @@ -119,18 +117,84 @@ CKEDITOR.plugins.contextMenu = CKEDITOR.tools.createClass( } } - menu.show( offsetParent, corner || ( editor.lang.dir == 'rtl' ? 2 : 1 ), offsetX, offsetY ); + // Don't show context menu with zero items. + menu.items.length && menu.show( offsetParent, corner || ( editor.lang.dir == 'rtl' ? 2 : 1 ), offsetX, offsetY ); } }, proto : { - addTarget : function( element ) + addTarget : function( element, nativeContextMenuOnCtrl ) { + // Opera doesn't support 'contextmenu' event, we have duo approaches employed here: + // 1. Inherit the 'button override' hack we introduced in v2 (#4530), while this require the Opera browser + // option 'Allow script to detect context menu/right click events' to be always turned on. + // 2. Considering the fact that ctrl/meta key is not been occupied + // for multiple range selecting (like Gecko), we use this key + // combination as a fallback for triggering context-menu. (#4530) + if ( CKEDITOR.env.opera ) + { + var contextMenuOverrideButton; + element.on( 'mousedown', function( evt ) + { + evt = evt.data; + if( evt.$.button != 2 ) + { + if ( evt.getKeystroke() == CKEDITOR.CTRL + 1 ) + element.fire( 'contextmenu', evt ); + return; + } + + if ( nativeContextMenuOnCtrl + && ( evt.$.ctrlKey || evt.$.metaKey ) ) + return; + + var target = evt.getTarget(); + + if( !contextMenuOverrideButton ) + { + var ownerDoc = target.getDocument(); + contextMenuOverrideButton = ownerDoc.createElement( 'input' ) ; + contextMenuOverrideButton.$.type = 'button' ; + ownerDoc.getBody().append( contextMenuOverrideButton ) ; + } + + contextMenuOverrideButton.setAttribute( 'style', 'position:absolute;top:' + ( evt.$.clientY - 2 ) + + 'px;left:' + ( evt.$.clientX - 2 ) + + 'px;width:5px;height:5px;opacity:0.01' ); + + } ); + + element.on( 'mouseup', function ( evt ) + { + if ( contextMenuOverrideButton ) + { + contextMenuOverrideButton.remove(); + contextMenuOverrideButton = undefined; + // Simulate 'contextmenu' event. + element.fire( 'contextmenu', evt.data ); + } + } ); + } + element.on( 'contextmenu', function( event ) { var domEvent = event.data; + if ( nativeContextMenuOnCtrl && + // Safari on Windows always show 'ctrlKey' as true in 'contextmenu' event, + // which make this property unreliable. (#4826) + ( CKEDITOR.env.webkit ? holdCtrlKey : domEvent.$.ctrlKey || domEvent.$.metaKey ) ) + return; + + // Selection will be unavailable after context menu shows up + // in IE, lock it now. + if ( CKEDITOR.env.ie ) + { + var selection = this.editor.getSelection(); + selection && selection.lock(); + } + // Cancel the browser context menu. domEvent.preventDefault(); @@ -145,6 +209,23 @@ CKEDITOR.plugins.contextMenu = CKEDITOR.tools.createClass( 0, this ); }, this ); + + if( CKEDITOR.env.webkit ) + { + var holdCtrlKey, + onKeyDown = function( event ) + { + holdCtrlKey = event.data.$.ctrlKey || event.data.$.metaKey; + }, + resetOnKeyUp = function() + { + holdCtrlKey = 0; + }; + + element.on( 'keydown', onKeyDown ); + element.on( 'keyup', resetOnKeyUp ); + element.on( 'contextmenu', resetOnKeyUp ); + } }, addListener : function( listenerFn ) @@ -160,19 +241,13 @@ CKEDITOR.plugins.contextMenu = CKEDITOR.tools.createClass( } }); -// Fix the "contextmenu" event for DOM elements. -// We may do this if we identify browsers that don't support the context meny -// event on element directly. Leaving here for reference. -//if ( <specific browsers> ) -//{ -// CKEDITOR.dom.element.prototype.on = CKEDITOR.tools.override( CKEDITOR.dom.element.prototype.on, function( originalOn ) -// { -// return function( eventName ) -// { -// if ( eventName != 'contextmenu' ) -// return originalOn.apply( this, arguments ); -// -// // TODO : Implement the fix. -// }; -// }); -//} +/** + * Whether to show the browser native context menu when the CTRL or the + * META (Mac) key is pressed while opening the context menu. + * @name CKEDITOR.config.browserContextMenuOnCtrl + * @since 3.0.2 + * @type Boolean + * @default true + * @example + * config.browserContextMenuOnCtrl = false; + */ diff --git a/_source/plugins/dialog/dialogDefinition.js b/_source/plugins/dialog/dialogDefinition.js index f171b6c..9dd252f 100644 --- a/_source/plugins/dialog/dialogDefinition.js +++ b/_source/plugins/dialog/dialogDefinition.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/dialog/plugin.js b/_source/plugins/dialog/plugin.js index b87779a..6106eda 100644 --- a/_source/plugins/dialog/plugin.js +++ b/_source/plugins/dialog/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -7,11 +7,6 @@ For licensing, see LICENSE.html or http://ckeditor.com/license * @fileOverview The floating dialog plugin. */ -CKEDITOR.plugins.add( 'dialog', - { - requires : [ 'dialogui' ] - }); - /** * No resize for this dialog. * @constant @@ -73,9 +68,6 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; return null; } - // Stores dialog related data from skin definitions. e.g. margin sizes. - var skinData = {}; - /** * This is the base class for runtime dialog objects. An instance of this * class represents a single named dialog for a single editor instance. @@ -89,11 +81,6 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; { // Load the dialog definition. var definition = CKEDITOR.dialog._.dialogDefinitions[ dialogName ]; - if ( !definition ) - { - console.log( 'Error: The dialog "' + dialogName + '" is not defined.' ); - return; - } // Completes the definition with the default values. definition = CKEDITOR.tools.extend( definition( editor ), defaultDialogDefinition ); @@ -264,11 +251,12 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; if ( focusList.length < 1 ) return; - var currentIndex = ( me._.currentFocusIndex + offset + focusList.length ) % focusList.length; + var startIndex = ( me._.currentFocusIndex + offset + focusList.length ) % focusList.length, + currentIndex = startIndex; while ( !focusList[ currentIndex ].isFocusable() ) { currentIndex = ( currentIndex + offset + focusList.length ) % focusList.length; - if ( currentIndex == me._.currentFocusIndex ) + if ( currentIndex == startIndex ) break; } focusList[ currentIndex ].focus(); @@ -278,14 +266,17 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; focusList[ currentIndex ].select(); } + var processed; + function focusKeydownHandler( evt ) { // If I'm not the top dialog, ignore. if ( me != CKEDITOR.dialog._.currentTop ) return; - var keystroke = evt.data.getKeystroke(), - processed = false; + var keystroke = evt.data.getKeystroke(); + + processed = 0; if ( keystroke == 9 || keystroke == CKEDITOR.SHIFT + 9 ) { var shiftPressed = ( keystroke == CKEDITOR.SHIFT + 9 ); @@ -304,14 +295,14 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; changeFocus( !shiftPressed ); } - processed = true; + processed = 1; } else if ( keystroke == CKEDITOR.ALT + 121 && !me._.tabBarMode ) { // Alt-F10 puts focus into the current tab item in the tab bar. me._.tabBarMode = true; me._.tabs[ me._.currentTabId ][ 0 ].focus(); - processed = true; + processed = 1; } else if ( ( keystroke == 37 || keystroke == 39 ) && me._.tabBarMode ) { @@ -319,7 +310,7 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; nextId = ( keystroke == 37 ? getPreviousVisibleTab.call( me ) : getNextVisibleTab.call( me ) ); me.selectPage( nextId ); me._.tabs[ nextId ][ 0 ].focus(); - processed = true; + processed = 1; } if ( processed ) @@ -329,10 +320,19 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; } } + function focusKeyPressHandler( evt ) + { + processed && evt.data.preventDefault(); + } + // Add the dialog keyboard handlers. this.on( 'show', function() { CKEDITOR.document.on( 'keydown', focusKeydownHandler, this, null, 0 ); + // Some browsers instead, don't cancel key events in the keydown, but in the + // keypress. So we must do a longer trip in those cases. (#4531) + if ( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.mac ) ) + CKEDITOR.document.on( 'keypress', focusKeyPressHandler, this ); if ( CKEDITOR.env.ie6Compat ) { @@ -343,6 +343,8 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; this.on( 'hide', function() { CKEDITOR.document.removeListener( 'keydown', focusKeydownHandler ); + if ( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.mac ) ) + CKEDITOR.document.removeListener( 'keypress', focusKeyPressHandler ); } ); this.on( 'iframeAdded', function( evt ) { @@ -447,12 +449,13 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; }; // Focusable interface. Use it via dialog.addFocusable. - function Focusable( dialog, element, index ) { + function Focusable( dialog, element, index ) + { this.element = element; this.focusIndex = index; this.isFocusable = function() { - return true; + return !element.getAttribute( 'disabled' ) && element.isVisible(); }; this.focus = function() { @@ -580,8 +583,12 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; */ show : function() { - if ( this._.editor.mode == 'wysiwyg' && CKEDITOR.env.ie ) - this._.editor.getSelection().lock(); + var editor = this._.editor; + if ( editor.mode == 'wysiwyg' && CKEDITOR.env.ie ) + { + var selection = editor.getSelection(); + selection && selection.lock(); + } // Insert the dialog's element to the root document. var element = this._.element; @@ -625,12 +632,12 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; this._.parentDialog = null; addCover( this._.editor ); - CKEDITOR.document.on( 'keydown', accessKeyDownHandler ); - CKEDITOR.document.on( 'keyup', accessKeyUpHandler ); + element.on( 'keydown', accessKeyDownHandler ); + element.on( CKEDITOR.env.opera ? 'keypress' : 'keyup', accessKeyUpHandler ); // Prevent some keys from bubbling up. (#4269) for ( var event in { keyup :1, keydown :1, keypress :1 } ) - CKEDITOR.document.on( event, preventKeyBubbling ); + element.on( event, preventKeyBubbling ); } else { @@ -664,6 +671,7 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; // Execute onLoad for the first show. this.fireOnce( 'load', {} ); this.fire( 'show', {} ); + this._.editor.fire( 'dialogShow', this ); // Save the initial values of the dialog. this.foreach( function( contentObj ) { contentObj.setInitValue && contentObj.setInitValue(); } ); @@ -727,6 +735,7 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; hide : function() { this.fire( 'hide', {} ); + this._.editor.fire( 'dialogHide', this ); // Remove the dialog's element from the root document. var element = this._.element; @@ -755,19 +764,21 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; CKEDITOR.dialog._.currentZIndex = null; // Remove access key handlers. - CKEDITOR.document.removeListener( 'keydown', accessKeyDownHandler ); - CKEDITOR.document.removeListener( 'keyup', accessKeyUpHandler ); - CKEDITOR.document.removeListener( 'keypress', accessKeyUpHandler ); + element.removeListener( 'keydown', accessKeyDownHandler ); + element.removeListener( CKEDITOR.env.opera ? 'keypress' : 'keyup', accessKeyUpHandler ); // Remove bubbling-prevention handler. (#4269) for ( var event in { keyup :1, keydown :1, keypress :1 } ) - CKEDITOR.document.removeListener( event, preventKeyBubbling ); + element.removeListener( event, preventKeyBubbling ); var editor = this._.editor; editor.focus(); if ( editor.mode == 'wysiwyg' && CKEDITOR.env.ie ) - editor.getSelection().unlock( true ); + { + var selection = editor.getSelection(); + selection && selection.unlock( true ); + } } else CKEDITOR.dialog._.currentZIndex -= 10; @@ -794,7 +805,7 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; children : contents.elements, expand : !!contents.expand, padding : contents.padding, - style : contents.style || 'width: 100%; height: 100%;' + style : contents.style || 'width: 100%;' }, pageHtml ); // Create the HTML for the tab and the content block. @@ -1437,7 +1448,7 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; element = dialog.getElement().getFirst(), editor = dialog.getParentEditor(), magnetDistance = editor.config.dialog_magnetDistance, - margins = skinData[ editor.skinName ].margins || [ 0, 0, 0, 0 ]; + margins = editor.skin.margins || [ 0, 0, 0, 0 ]; if ( typeof magnetDistance == 'undefined' ) magnetDistance = 20; @@ -1515,7 +1526,7 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; minWidth = definition.minWidth || 0, minHeight = definition.minHeight || 0, resizable = definition.resizable, - margins = skinData[ dialog.getParentEditor().skinName ].margins || [ 0, 0, 0, 0 ]; + margins = dialog.getParentEditor().skin.margins || [ 0, 0, 0, 0 ]; function topSizer( coords, dy ) { @@ -1661,11 +1672,13 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; if ( !coverElement ) { + var backgroundColorStyle = editor.config.dialog_backgroundCoverColor || 'white'; + var html = [ '<div style="position: ', ( CKEDITOR.env.ie6Compat ? 'absolute' : 'fixed' ), '; z-index: ', editor.config.baseFloatZIndex, '; top: 0px; left: 0px; ', - 'background-color: ', editor.config.dialog_backgroundCoverColor || 'white', + ( !CKEDITOR.env.ie6Compat ? 'background-color: ' + backgroundColorStyle : '' ), '" id="cke_dialog_background_cover">' ]; @@ -1673,7 +1686,8 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; if ( CKEDITOR.env.ie6Compat ) { // Support for custom document.domain in IE. - var isCustomDomain = CKEDITOR.env.isCustomDomain(); + var isCustomDomain = CKEDITOR.env.isCustomDomain(), + iframeHtml = '<html><body style=\\\'background-color:' + backgroundColorStyle + ';\\\'></body></html>'; html.push( '<iframe' + @@ -1682,15 +1696,12 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; ' id="cke_dialog_background_iframe"' + ' src="javascript:' ); - html.push( - isCustomDomain ? - 'void((function(){' + + html.push( 'void((function(){' + 'document.open();' + - 'document.domain=\'' + document.domain + '\';' + + ( isCustomDomain ? 'document.domain=\'' + document.domain + '\';' : '' ) + + 'document.write( \'' + iframeHtml + '\' );' + 'document.close();' + - '})())' - : - '\'\'' ); + '})())' ); html.push( '"' + @@ -1814,8 +1825,11 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; return; keyProcessor = keyProcessor[keyProcessor.length - 1]; - keyProcessor.keyup && keyProcessor.keyup.call( keyProcessor.uiElement, keyProcessor.dialog, keyProcessor.key ); - evt.data.preventDefault(); + if ( keyProcessor.keyup ) + { + keyProcessor.keyup.call( keyProcessor.uiElement, keyProcessor.dialog, keyProcessor.key ); + evt.data.preventDefault(); + } }; var registerAccessKey = function( uiElement, dialog, key, downFunc, upFunc ) @@ -2448,7 +2462,7 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; */ isVisible : function() { - return !!this.getInputElement().$.offsetHeight; + return this.getInputElement().isVisible(); }, /** @@ -2672,17 +2686,6 @@ CKEDITOR.DIALOG_RESIZE_BOTH = 3; } }; })(); - - // Grab the margin data from skin definition and store it away. - CKEDITOR.skins.add = ( function() - { - var original = CKEDITOR.skins.add; - return function( skinName, skinDefinition ) - { - skinData[ skinName ] = { margins : skinDefinition.margins }; - return original.apply( this, arguments ); - }; - } )(); })(); // Extend the CKEDITOR.editor class with dialog specific functions. @@ -2692,12 +2695,13 @@ CKEDITOR.tools.extend( CKEDITOR.editor.prototype, /** * Loads and opens a registered dialog. * @param {String} dialogName The registered name of the dialog. + * @param {Function} callback The function to be invoked after dialog instance created. * @see CKEDITOR.dialog.add * @example * CKEDITOR.instances.editor1.openDialog( 'smiley' ); * @returns {CKEDITOR.dialog} The dialog object corresponding to the dialog displayed. null if the dialog name is not registered. */ - openDialog : function( dialogName ) + openDialog : function( dialogName, callback ) { var dialogDefinitions = CKEDITOR.dialog._.dialogDefinitions[ dialogName ]; @@ -2710,6 +2714,7 @@ CKEDITOR.tools.extend( CKEDITOR.editor.prototype, var dialog = storedDialogs[ dialogName ] || ( storedDialogs[ dialogName ] = new CKEDITOR.dialog( this, dialogName ) ); + callback && callback.call( dialog, dialog ); dialog.show(); return dialog; @@ -2728,7 +2733,7 @@ CKEDITOR.tools.extend( CKEDITOR.editor.prototype, // In case of plugin error, mark it as loading failed. if ( typeof CKEDITOR.dialog._.dialogDefinitions[ dialogName ] != 'function' ) CKEDITOR.dialog._.dialogDefinitions[ dialogName ] = 'failed'; - me.openDialog( dialogName ); + me.openDialog( dialogName, callback ); body.setStyle( 'cursor', cursor ); } ); @@ -2736,6 +2741,11 @@ CKEDITOR.tools.extend( CKEDITOR.editor.prototype, } }); +CKEDITOR.plugins.add( 'dialog', + { + requires : [ 'dialogui' ] + }); + // Dialog related configurations. /** diff --git a/_source/plugins/dialogui/plugin.js b/_source/plugins/dialogui/plugin.js index 4ac431f..c916872 100644 --- a/_source/plugins/dialogui/plugin.js +++ b/_source/plugins/dialogui/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -222,7 +222,10 @@ CKEDITOR.plugins.add( 'dialogui' ); { if ( evt.data.getKeystroke() == 13 && keyPressedOnMe ) { - dialog.getButton( 'ok' ) && dialog.getButton( 'ok' ).click(); + dialog.getButton( 'ok' ) && setTimeout( function () + { + dialog.getButton( 'ok' ).click(); + }, 0 ); keyPressedOnMe = false; } }, null, null, 1000 ); @@ -838,7 +841,7 @@ CKEDITOR.plugins.add( 'dialogui' ); isVisible : function() { - return !!this.getElement().$.firstChild.offsetHeight; + return this.getElement().getFirst().isVisible(); }, isEnabled : function() diff --git a/_source/plugins/div/dialogs/div.js b/_source/plugins/div/dialogs/div.js new file mode 100644 index 0000000..304dc6a --- /dev/null +++ b/_source/plugins/div/dialogs/div.js @@ -0,0 +1,442 @@ +/* + * Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.html or http://ckeditor.com/license + */ + +(function() +{ + + /** + * Add to collection with DUP examination. + * @param {Object} collection + * @param {Object} element + * @param {Object} database + */ + function addSafely( collection, element, database ) + { + // 1. IE doesn't support customData on text nodes; + // 2. Text nodes never get chance to appear twice; + if ( !element.is || !element.getCustomData( 'block_processed' ) ) + { + element.is && CKEDITOR.dom.element.setMarker( database, element, 'block_processed', true ); + collection.push( element ); + } + } + + function getNonEmptyChildren( element ) + { + var retval = []; + var children = element.getChildren(); + for( var i = 0 ; i < children.count() ; i++ ) + { + var child = children.getItem( i ); + if( ! ( child.type === CKEDITOR.NODE_TEXT + && ( /^[ \t\n\r]+$/ ).test( child.getText() ) ) ) + retval.push( child ); + } + return retval; + } + + + /** + * Dialog reused by both 'creatediv' and 'editdiv' commands. + * @param {Object} editor + * @param {String} command The command name which indicate what the current command is. + */ + function divDialog( editor, command ) + { + // Definition of elements at which div operation should stopped. + var divLimitDefinition = ( function(){ + + // Customzie from specialize blockLimit elements + var definition = CKEDITOR.tools.extend( {}, CKEDITOR.dtd.$blockLimit ); + + // Exclude 'div' itself. + delete definition.div; + + // Exclude 'td' and 'th' when 'wrapping table' + if( editor.config.div_wrapTable ) + { + delete definition.td; + delete definition.th; + } + return definition; + })(); + + // DTD of 'div' element + var dtd = CKEDITOR.dtd.div; + + /** + * Get the first div limit element on the element's path. + * @param {Object} element + */ + function getDivLimitElement( element ) + { + var pathElements = new CKEDITOR.dom.elementPath( element ).elements; + var divLimit; + for ( var i = 0; i < pathElements.length ; i++ ) + { + if ( pathElements[ i ].getName() in divLimitDefinition ) + { + divLimit = pathElements[ i ]; + break; + } + } + return divLimit; + } + + /** + * Init all fields' setup/commit function. + * @memberof divDialog + */ + function setupFields() + { + this.foreach( function( field ) + { + // Exclude layout container elements + if( /^(?!vbox|hbox)/.test( field.type ) ) + { + if ( !field.setup ) + { + // Read the dialog fields values from the specified + // element attributes. + field.setup = function( element ) + { + field.setValue( element.getAttribute( field.id ) || '' ); + }; + } + if ( !field.commit ) + { + // Set element attributes assigned by the dialog + // fields. + field.commit = function( element ) + { + var fieldValue = this.getValue(); + // ignore default element attribute values + if ( 'dir' == field.id && element.getComputedStyle( 'direction' ) == fieldValue ) + return; + + if ( fieldValue ) + element.setAttribute( field.id, fieldValue ); + else + element.removeAttribute( field.id ); + }; + } + } + } ); + } + + /** + * Wrapping 'div' element around appropriate blocks among the selected ranges. + * @param {Object} editor + */ + function createDiv( editor ) + { + // new adding containers OR detected pre-existed containers. + var containers = []; + // node markers store. + var database = {}; + // All block level elements which contained by the ranges. + var containedBlocks = [], block; + + // Get all ranges from the selection. + var selection = editor.document.getSelection(); + var ranges = selection.getRanges(); + var bookmarks = selection.createBookmarks(); + var i, iterator; + + // Calcualte a default block tag if we need to create blocks. + var blockTag = editor.config.enterMode == CKEDITOR.ENTER_DIV ? 'div' : 'p'; + + // collect all included elements from dom-iterator + for( i = 0 ; i < ranges.length ; i++ ) + { + iterator = ranges[ i ].createIterator(); + while( ( block = iterator.getNextParagraph() ) ) + { + // include contents of blockLimit elements. + if( block.getName() in divLimitDefinition ) + { + var j, childNodes = block.getChildren(); + for ( j = 0 ; j < childNodes.count() ; j++ ) + addSafely( containedBlocks, childNodes.getItem( j ) , database ); + } + else + { + // Bypass dtd disallowed elements. + while( !dtd[ block.getName() ] && block.getName() != 'body' ) + block = block.getParent(); + addSafely( containedBlocks, block, database ); + } + } + } + + CKEDITOR.dom.element.clearAllMarkers( database ); + + var blockGroups = groupByDivLimit( containedBlocks ); + var ancestor, blockEl, divElement; + + for( i = 0 ; i < blockGroups.length ; i++ ) + { + var currentNode = blockGroups[ i ][ 0 ]; + + // Calculate the common parent node of all contained elements. + ancestor = currentNode.getParent(); + for ( j = 1 ; j < blockGroups[ i ].length; j++ ) + ancestor = ancestor.getCommonAncestor( blockGroups[ i ][ j ] ); + + divElement = new CKEDITOR.dom.element( 'div', editor.document ); + + // Normalize the blocks in each group to a common parent. + for( j = 0; j < blockGroups[ i ].length ; j++ ) + { + currentNode = blockGroups[ i ][ j ]; + + while( !currentNode.getParent().equals( ancestor ) ) + currentNode = currentNode.getParent(); + + // This could introduce some duplicated elements in array. + blockGroups[ i ][ j ] = currentNode; + } + + // Wrapped blocks counting + var fixedBlock = null; + for ( j = 0 ; j < blockGroups[ i ].length ; j++ ) + { + currentNode = blockGroups[ i ][ j ]; + + // Avoid DUP elements introduced by grouping. + if ( !( currentNode.getCustomData && currentNode.getCustomData( 'block_processed' ) ) ) + { + currentNode.is && CKEDITOR.dom.element.setMarker( database, currentNode, 'block_processed', true ); + + // Establish new container, wrapping all elements in this group. + if ( !j ) + divElement.insertBefore( currentNode ); + + divElement.append( currentNode ); + } + } + + CKEDITOR.dom.element.clearAllMarkers( database ); + containers.push( divElement ); + } + + selection.selectBookmarks( bookmarks ); + return containers; + } + + function getDiv( editor ) + { + var path = new CKEDITOR.dom.elementPath( editor.getSelection().getStartElement() ), + blockLimit = path.blockLimit, + div = blockLimit && blockLimit.getAscendant( 'div', true ); + return div; + } + /** + * Divide a set of nodes to different groups by their path's blocklimit element. + * Note: the specified nodes should be in source order naturally, which mean they are supposed to producea by following class: + * * CKEDITOR.dom.range.Iterator + * * CKEDITOR.dom.domWalker + * @return {Array []} the grouped nodes + */ + function groupByDivLimit( nodes ) + { + var groups = [], + lastDivLimit = null, + path, block; + for ( var i = 0 ; i < nodes.length ; i++ ) + { + block = nodes[i]; + var limit = getDivLimitElement( block ); + if ( !limit.equals( lastDivLimit ) ) + { + lastDivLimit = limit ; + groups.push( [] ) ; + } + groups[ groups.length - 1 ].push( block ) ; + } + return groups; + } + + /** + * Hold a collection of created block container elements. + */ + var containers = []; + /** + * @type divDialog + */ + return { + title : editor.lang.div.title, + minWidth : 400, + minHeight : 165, + contents : + [ + { + id :'info', + label :editor.lang.common.generalTab, + title :editor.lang.common.generalTab, + elements : + [ + { + type :'hbox', + widths : [ '50%', '50%' ], + children : + [ + { + id :'elementStyle', + type :'select', + style :'width: 100%;', + label :editor.lang.div.styleSelectLabel, + 'default' : '', + items : [], + setup : function( element ) + { + this.setValue( element.$.style.cssText || '' ); + }, + commit: function( element ) + { + if ( this.getValue() ) + element.$.style.cssText = this.getValue(); + else + element.removeAttribute( 'style' ); + } + }, + { + id :'class', + type :'text', + label :editor.lang.common.cssClass, + 'default' : '' + } + ] + } + ] + }, + { + id :'advanced', + label :editor.lang.common.advancedTab, + title :editor.lang.common.advancedTab, + elements : + [ + { + type :'vbox', + padding :1, + children : + [ + { + type :'hbox', + widths : [ '50%', '50%' ], + children : + [ + { + type :'text', + id :'id', + label :editor.lang.common.id, + 'default' : '' + }, + { + type :'text', + id :'lang', + label :editor.lang.link.langCode, + 'default' : '' + } + ] + }, + { + type :'hbox', + children : + [ + { + type :'text', + id :'style', + style :'width: 100%;', + label :editor.lang.common.cssStyle, + 'default' : '' + } + ] + }, + { + type :'hbox', + children : + [ + { + type :'text', + id :'title', + style :'width: 100%;', + label :editor.lang.common.advisoryTitle, + 'default' : '' + } + ] + }, + { + type :'select', + id :'dir', + style :'width: 100%;', + label :editor.lang.common.langDir, + 'default' : '', + items : + [ + [ + editor.lang.common.langDirLtr, + 'ltr' + ], + [ + editor.lang.common.langDirRtl, + 'rtl' + ] + ] + } + ] + } + ] + } + ], + onLoad : function() + { + setupFields.call(this); + }, + onShow : function() + { + // Whether always create new container regardless of existed + // ones. + if ( command == 'editdiv' ) + { + // Try to discover the containers that already existed in + // ranges + var div = getDiv( editor ); + // update dialog field values + div && this.setupContent( this._element = div ); + } + }, + onOk : function() + { + if( command == 'editdiv' ) + containers = [ this._element ]; + else + containers = createDiv( editor, true ); + + // Update elements attributes + for( var i = 0 ; i < containers.length ; i++ ) + this.commitContent( containers[ i ] ); + this.hide(); + } + }; + } + + CKEDITOR.dialog.add( 'creatediv', function( editor ) + { + return divDialog( editor, 'creatediv' ); + } ); + CKEDITOR.dialog.add( 'editdiv', function( editor ) + { + return divDialog( editor, 'editdiv' ); + } ); +} )(); + +/* + * @name CKEDITOR.config.div_wrapTable + * Whether to wrap the whole table instead of indivisual cells when created 'div' in table cell. + * @type Boolean + * @default false + * @example config.div_wrapTable = true; + */ diff --git a/_source/plugins/div/plugin.js b/_source/plugins/div/plugin.js new file mode 100644 index 0000000..256cb82 --- /dev/null +++ b/_source/plugins/div/plugin.js @@ -0,0 +1,121 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview The "div" plugin. It wraps the selected block level elements with a 'div' element with specified styles and attributes. + * + */ + +(function() +{ + CKEDITOR.plugins.add( 'div', + { + requires : [ 'editingblock', 'domiterator' ], + + init : function( editor ) + { + var lang = editor.lang.div; + + editor.addCommand( 'creatediv', new CKEDITOR.dialogCommand( 'creatediv' ) ); + editor.addCommand( 'editdiv', new CKEDITOR.dialogCommand( 'editdiv' ) ); + editor.addCommand( 'removediv', + { + exec : function( editor ) + { + var selection = editor.getSelection(), + ranges = selection && selection.getRanges(), + range, + bookmarks = selection.createBookmarks(), + walker, + toRemove = []; + + function findDiv( node ) + { + var path = new CKEDITOR.dom.elementPath( node ), + blockLimit = path.blockLimit, + div = blockLimit.is( 'div' ) && blockLimit; + + if ( div && !div.getAttribute( '_cke_div_added' ) ) + { + toRemove.push( div ); + div.setAttribute( '_cke_div_added' ); + } + } + + for ( var i = 0 ; i < ranges.length ; i++ ) + { + range = ranges[ i ]; + if( range.collapsed ) + findDiv( selection.getStartElement() ); + else + { + walker = new CKEDITOR.dom.walker( range ); + walker.evaluator = findDiv; + walker.lastForward(); + } + } + + for ( i = 0 ; i < toRemove.length ; i++ ) + toRemove[ i ].remove( true ); + + selection.selectBookmarks( bookmarks ); + } + } ); + + editor.ui.addButton( 'CreateDiv', + { + label : lang.toolbar, + command :'creatediv' + } ); + + if ( editor.addMenuItems ) + { + editor.addMenuItems( + { + editdiv : + { + label : lang.edit, + command : 'editdiv', + group : 'div', + order : 1 + }, + + removediv: + { + label : lang.remove, + command : 'removediv', + group : 'div', + order : 5 + } + } ); + + if ( editor.contextMenu ) + { + editor.contextMenu.addListener( function( element, selection ) + { + if ( !element ) + return null; + + var elementPath = new CKEDITOR.dom.elementPath( element ), + blockLimit = elementPath.blockLimit; + + if ( blockLimit && blockLimit.getAscendant( 'div', true ) ) + { + return { + editdiv : CKEDITOR.TRISTATE_OFF, + removediv : CKEDITOR.TRISTATE_OFF + }; + } + + return null; + } ); + } + } + + CKEDITOR.dialog.add( 'creatediv', this.path + 'dialogs/div.js' ); + CKEDITOR.dialog.add( 'editdiv', this.path + 'dialogs/div.js' ); + } + } ); +})(); diff --git a/_source/plugins/domiterator/plugin.js b/_source/plugins/domiterator/plugin.js index aa59c90..6063a17 100644 --- a/_source/plugins/domiterator/plugin.js +++ b/_source/plugins/domiterator/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -12,7 +12,7 @@ CKEDITOR.plugins.add( 'domiterator' ); (function() { - var iterator = function( range ) + function iterator( range ) { if ( arguments.length < 1 ) return; @@ -25,9 +25,10 @@ CKEDITOR.plugins.add( 'domiterator' ); this.enforceRealBlocks = false; this._ || ( this._ = {} ); - }, - beginWhitespaceRegex = /^[\r\n\t ]+$/; + } + var beginWhitespaceRegex = /^[\r\n\t ]+$/, + isBookmark = CKEDITOR.dom.walker.bookmark(); iterator.prototype = { getNextParagraph : function( blockTag ) @@ -212,14 +213,12 @@ CKEDITOR.plugins.add( 'domiterator' ); if ( ( closeRange || isLast ) && range ) { var boundaryNodes = range.getBoundaryNodes(), - startPath = new CKEDITOR.dom.elementPath( range.startContainer ), - endPath = new CKEDITOR.dom.elementPath( range.endContainer ); + startPath = new CKEDITOR.dom.elementPath( range.startContainer ); - // Drop the range if it only contains bookmark nodes.(#4087) - if ( boundaryNodes.startNode.equals( boundaryNodes.endNode ) - && boundaryNodes.startNode.getParent().equals( startPath.blockLimit ) - && boundaryNodes.startNode.type == CKEDITOR.NODE_ELEMENT - && boundaryNodes.startNode.getAttribute( '_fck_bookmark' ) ) + // Drop the range if it only contains bookmark nodes, and is + // not because of the original collapsed range. (#4087,#4450) + if ( boundaryNodes.startNode.getParent().equals( startPath.blockLimit ) + && isBookmark( boundaryNodes.startNode ) && isBookmark( boundaryNodes.endNode ) ) { range = null; this._.nextNode = null; diff --git a/_source/plugins/editingblock/plugin.js b/_source/plugins/editingblock/plugin.js index 7a52582..48c9557 100644 --- a/_source/plugins/editingblock/plugin.js +++ b/_source/plugins/editingblock/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/elementspath/plugin.js b/_source/plugins/elementspath/plugin.js index a23f4ce..4ef11f6 100644 --- a/_source/plugins/elementspath/plugin.js +++ b/_source/plugins/elementspath/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/enterkey/plugin.js b/_source/plugins/enterkey/plugin.js index b8d6449..963462d 100644 --- a/_source/plugins/enterkey/plugin.js +++ b/_source/plugins/enterkey/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -17,296 +17,301 @@ For licensing, see LICENSE.html or http://ckeditor.com/license } }); - var forceMode, - headerTagRegex = /^h[1-6]$/; - - function shiftEnter( editor ) + CKEDITOR.plugins.enterkey = { - // On SHIFT+ENTER we want to enforce the mode to be respected, instead - // of cloning the current block. (#77) - forceMode = 1; + enterBlock : function( editor, mode, range, forceMode ) + { + // Get the range for the current selection. + range = range || getRange( editor ); - return enter( editor, editor.config.shiftEnterMode ); - } + var doc = range.document; - function enter( editor, mode ) - { - // Only effective within document. - if ( editor.mode != 'wysiwyg' ) - return false; + // Determine the block element to be used. + var blockTag = ( mode == CKEDITOR.ENTER_DIV ? 'div' : 'p' ); - if ( !mode ) - mode = editor.config.enterMode; + // Split the range. + var splitInfo = range.splitBlock( blockTag ); - // Use setTimout so the keys get cancelled immediatelly. - setTimeout( function() - { - editor.fire( 'saveSnapshot' ); // Save undo step. - if ( mode == CKEDITOR.ENTER_BR || editor.getSelection().getStartElement().hasAscendant( 'pre', true ) ) - enterBr( editor, mode ); - else - enterBlock( editor, mode ); + if ( !splitInfo ) + return; - forceMode = 0; - }, 0 ); + // Get the current blocks. + var previousBlock = splitInfo.previousBlock, + nextBlock = splitInfo.nextBlock; - return true; - } + var isStartOfBlock = splitInfo.wasStartOfBlock, + isEndOfBlock = splitInfo.wasEndOfBlock; - function enterBlock( editor, mode, range ) - { - // Get the range for the current selection. - range = range || getRange( editor ); + var node; - var doc = range.document; + // If this is a block under a list item, split it as well. (#1647) + if ( nextBlock ) + { + node = nextBlock.getParent(); + if ( node.is( 'li' ) ) + { + nextBlock.breakParent( node ); + nextBlock.move( nextBlock.getNext(), true ); + } + } + else if ( previousBlock && ( node = previousBlock.getParent() ) && node.is( 'li' ) ) + { + previousBlock.breakParent( node ); + range.moveToElementEditStart( previousBlock.getNext() ); + previousBlock.move( previousBlock.getPrevious() ); + } - // Determine the block element to be used. - var blockTag = ( mode == CKEDITOR.ENTER_DIV ? 'div' : 'p' ); + // If we have both the previous and next blocks, it means that the + // boundaries were on separated blocks, or none of them where on the + // block limits (start/end). + if ( !isStartOfBlock && !isEndOfBlock ) + { + // If the next block is an <li> with another list tree as the first + // child, we'll need to append a filler (<br>/NBSP) or the list item + // wouldn't be editable. (#1420) + if ( nextBlock.is( 'li' ) + && ( node = nextBlock.getFirst( CKEDITOR.dom.walker.invisible( true ) ) ) + && node.is && node.is( 'ul', 'ol' ) ) + ( CKEDITOR.env.ie ? doc.createText( '\xa0' ) : doc.createElement( 'br' ) ).insertBefore( node ); - // Split the range. - var splitInfo = range.splitBlock( blockTag ); + // Move the selection to the end block. + if ( nextBlock ) + range.moveToElementEditStart( nextBlock ); + } + else + { - if ( !splitInfo ) - return; + if ( isStartOfBlock && isEndOfBlock && previousBlock.is( 'li' ) ) + { + editor.execCommand( 'outdent' ); + return; + } - // Get the current blocks. - var previousBlock = splitInfo.previousBlock, - nextBlock = splitInfo.nextBlock; + var newBlock; - var isStartOfBlock = splitInfo.wasStartOfBlock, - isEndOfBlock = splitInfo.wasEndOfBlock; + if ( previousBlock ) + { + // Do not enter this block if it's a header tag, or we are in + // a Shift+Enter (#77). Create a new block element instead + // (later in the code). + if ( !forceMode && !headerTagRegex.test( previousBlock.getName() ) ) + { + // Otherwise, duplicate the previous block. + newBlock = previousBlock.clone(); + } + } + else if ( nextBlock ) + newBlock = nextBlock.clone(); - var node; + if ( !newBlock ) + newBlock = doc.createElement( blockTag ); - // If this is a block under a list item, split it as well. (#1647) - if ( nextBlock ) - { - node = nextBlock.getParent(); - if ( node.is( 'li' ) ) - { - nextBlock.breakParent( node ); - nextBlock.move( nextBlock.getNext(), true ); - } - } - else if ( previousBlock && ( node = previousBlock.getParent() ) && node.is( 'li' ) ) - { - previousBlock.breakParent( node ); - range.moveToElementEditStart( previousBlock.getNext() ); - previousBlock.move( previousBlock.getPrevious() ); - } + // Recreate the inline elements tree, which was available + // before hitting enter, so the same styles will be available in + // the new block. + var elementPath = splitInfo.elementPath; + if ( elementPath ) + { + for ( var i = 0, len = elementPath.elements.length ; i < len ; i++ ) + { + var element = elementPath.elements[ i ]; - // If we have both the previous and next blocks, it means that the - // boundaries were on separated blocks, or none of them where on the - // block limits (start/end). - if ( !isStartOfBlock && !isEndOfBlock ) - { - // If the next block is an <li> with another list tree as the first - // child, we'll need to append a placeholder or the list item - // wouldn't be editable. (#1420) - if ( nextBlock.is( 'li' ) && ( node = nextBlock.getFirst() ) - && node.is && node.is( 'ul', 'ol') ) - nextBlock.insertBefore( doc.createText( '\xa0' ), node ); + if ( element.equals( elementPath.block ) || element.equals( elementPath.blockLimit ) ) + break; - // Move the selection to the end block. - if ( nextBlock ) - range.moveToElementEditStart( nextBlock ); - } - else - { + if ( CKEDITOR.dtd.$removeEmpty[ element.getName() ] ) + { + element = element.clone(); + newBlock.moveChildren( element ); + newBlock.append( element ); + } + } + } - if ( isStartOfBlock && isEndOfBlock && previousBlock.is( 'li' ) ) - { - editor.execCommand( 'outdent' ); - return; - } + if ( !CKEDITOR.env.ie ) + newBlock.appendBogus(); - var newBlock; + range.insertNode( newBlock ); - if ( previousBlock ) - { - // Do not enter this block if it's a header tag, or we are in - // a Shift+Enter (#77). Create a new block element instead - // (later in the code). - if ( !forceMode && !headerTagRegex.test( previousBlock.getName() ) ) + // This is tricky, but to make the new block visible correctly + // we must select it. + // The previousBlock check has been included because it may be + // empty if we have fixed a block-less space (like ENTER into an + // empty table cell). + if ( CKEDITOR.env.ie && isStartOfBlock && ( !isEndOfBlock || !previousBlock.getChildCount() ) ) { - // Otherwise, duplicate the previous block. - newBlock = previousBlock.clone(); + // Move the selection to the new block. + range.moveToElementEditStart( isEndOfBlock ? previousBlock : newBlock ); + range.select(); } - } - else if ( nextBlock ) - newBlock = nextBlock.clone(); - if ( !newBlock ) - newBlock = doc.createElement( blockTag ); + // Move the selection to the new block. + range.moveToElementEditStart( isStartOfBlock && !isEndOfBlock ? nextBlock : newBlock ); + } - // Recreate the inline elements tree, which was available - // before hitting enter, so the same styles will be available in - // the new block. - var elementPath = splitInfo.elementPath; - if ( elementPath ) + if ( !CKEDITOR.env.ie ) { - for ( var i = 0, len = elementPath.elements.length ; i < len ; i++ ) + if ( nextBlock ) { - var element = elementPath.elements[ i ]; + // If we have split the block, adds a temporary span at the + // range position and scroll relatively to it. + var tmpNode = doc.createElement( 'span' ); - if ( element.equals( elementPath.block ) || element.equals( elementPath.blockLimit ) ) - break; + // We need some content for Safari. + tmpNode.setHtml( ' ' ); - if ( CKEDITOR.dtd.$removeEmpty[ element.getName() ] ) - { - element = element.clone(); - newBlock.moveChildren( element ); - newBlock.append( element ); - } + range.insertNode( tmpNode ); + tmpNode.scrollIntoView(); + range.deleteContents(); + } + else + { + // We may use the above scroll logic for the new block case + // too, but it gives some weird result with Opera. + newBlock.scrollIntoView(); } } - if ( !CKEDITOR.env.ie ) - newBlock.appendBogus(); + range.select(); + }, + + enterBr : function( editor, mode, range, forceMode ) + { + // Get the range for the current selection. + range = range || getRange( editor ); + + var doc = range.document; + + // Determine the block element to be used. + var blockTag = ( mode == CKEDITOR.ENTER_DIV ? 'div' : 'p' ); + + var isEndOfBlock = range.checkEndOfBlock(); + + var elementPath = new CKEDITOR.dom.elementPath( editor.getSelection().getStartElement() ); - range.insertNode( newBlock ); + var startBlock = elementPath.block, + startBlockTag = startBlock && elementPath.block.getName(); - // This is tricky, but to make the new block visible correctly - // we must select it. - // The previousBlock check has been included because it may be - // empty if we have fixed a block-less space (like ENTER into an - // empty table cell). - if ( CKEDITOR.env.ie && isStartOfBlock && ( !isEndOfBlock || !previousBlock.getChildCount() ) ) + var isPre = false; + + if ( !forceMode && startBlockTag == 'li' ) { - // Move the selection to the new block. - range.moveToElementEditStart( isEndOfBlock ? previousBlock : newBlock ); - range.select(); + enterBlock( editor, mode, range, forceMode ); + return; } - // Move the selection to the new block. - range.moveToElementEditStart( isStartOfBlock && !isEndOfBlock ? nextBlock : newBlock ); - } - - if ( !CKEDITOR.env.ie ) - { - if ( nextBlock ) + // If we are at the end of a header block. + if ( !forceMode && isEndOfBlock && headerTagRegex.test( startBlockTag ) ) { - // If we have split the block, adds a temporary span at the - // range position and scroll relatively to it. - var tmpNode = doc.createElement( 'span' ); + // Insert a <br> after the current paragraph. + doc.createElement( 'br' ).insertAfter( startBlock ); - // We need some content for Safari. - tmpNode.setHtml( ' ' ); + // A text node is required by Gecko only to make the cursor blink. + if ( CKEDITOR.env.gecko ) + doc.createText( '' ).insertAfter( startBlock ); - range.insertNode( tmpNode ); - tmpNode.scrollIntoView(); - range.deleteContents(); + // IE has different behaviors regarding position. + range.setStartAt( startBlock.getNext(), CKEDITOR.env.ie ? CKEDITOR.POSITION_BEFORE_START : CKEDITOR.POSITION_AFTER_START ); } else { - // We may use the above scroll logic for the new block case - // too, but it gives some weird result with Opera. - newBlock.scrollIntoView(); - } - } + var lineBreak; - range.select(); - } + isPre = ( startBlockTag == 'pre' ); - function enterBr( editor, mode ) - { - // Get the range for the current selection. - var range = getRange( editor ), - doc = range.document; + if ( isPre ) + lineBreak = doc.createText( CKEDITOR.env.ie ? '\r' : '\n' ); + else + lineBreak = doc.createElement( 'br' ); - // Determine the block element to be used. - var blockTag = ( mode == CKEDITOR.ENTER_DIV ? 'div' : 'p' ); + range.deleteContents(); + range.insertNode( lineBreak ); - var isEndOfBlock = range.checkEndOfBlock(); + // A text node is required by Gecko only to make the cursor blink. + // We need some text inside of it, so the bogus <br> is properly + // created. + if ( !CKEDITOR.env.ie ) + doc.createText( '\ufeff' ).insertAfter( lineBreak ); - var elementPath = new CKEDITOR.dom.elementPath( editor.getSelection().getStartElement() ); + // If we are at the end of a block, we must be sure the bogus node is available in that block. + if ( isEndOfBlock && !CKEDITOR.env.ie ) + lineBreak.getParent().appendBogus(); - var startBlock = elementPath.block, - startBlockTag = startBlock && elementPath.block.getName(); + // Now we can remove the text node contents, so the caret doesn't + // stop on it. + if ( !CKEDITOR.env.ie ) + lineBreak.getNext().$.nodeValue = ''; + // IE has different behavior regarding position. + if ( CKEDITOR.env.ie ) + range.setStartAt( lineBreak, CKEDITOR.POSITION_AFTER_END ); + else + range.setStartAt( lineBreak.getNext(), CKEDITOR.POSITION_AFTER_START ); - var isPre = false; + // Scroll into view, for non IE. + if ( !CKEDITOR.env.ie ) + { + var dummy = null; - if ( !forceMode && startBlockTag == 'li' ) - { - enterBlock( editor, mode, range ); - return; - } + // BR is not positioned in Opera and Webkit. + if ( !CKEDITOR.env.gecko ) + { + dummy = doc.createElement( 'span' ); + // We need have some contents for Webkit to position it + // under parent node. ( #3681) + dummy.setHtml(' '); + } + else + dummy = doc.createElement( 'br' ); - // If we are at the end of a header block. - if ( !forceMode && isEndOfBlock && headerTagRegex.test( startBlockTag ) ) - { - // Insert a <br> after the current paragraph. - doc.createElement( 'br' ).insertAfter( startBlock ); + dummy.insertBefore( lineBreak.getNext() ); + dummy.scrollIntoView(); + dummy.remove(); + } + } - // A text node is required by Gecko only to make the cursor blink. - if ( CKEDITOR.env.gecko ) - doc.createText( '' ).insertAfter( startBlock ); + // This collapse guarantees the cursor will be blinking. + range.collapse( true ); - // IE has different behaviors regarding position. - range.setStartAt( startBlock.getNext(), CKEDITOR.env.ie ? CKEDITOR.POSITION_BEFORE_START : CKEDITOR.POSITION_AFTER_START ); + range.select( isPre ); } - else - { - var lineBreak; + }; - isPre = ( startBlockTag == 'pre' ); - - if ( isPre ) - lineBreak = doc.createText( CKEDITOR.env.ie ? '\r' : '\n' ); - else - lineBreak = doc.createElement( 'br' ); - - range.deleteContents(); - range.insertNode( lineBreak ); + var plugin = CKEDITOR.plugins.enterkey, + enterBr = plugin.enterBr, + enterBlock = plugin.enterBlock, + headerTagRegex = /^h[1-6]$/; - // A text node is required by Gecko only to make the cursor blink. - // We need some text inside of it, so the bogus <br> is properly - // created. - if ( !CKEDITOR.env.ie ) - doc.createText( '\ufeff' ).insertAfter( lineBreak ); + function shiftEnter( editor ) + { + // On SHIFT+ENTER we want to enforce the mode to be respected, instead + // of cloning the current block. (#77) + return enter( editor, editor.config.shiftEnterMode, true ); + } - // If we are at the end of a block, we must be sure the bogus node is available in that block. - if ( isEndOfBlock && !CKEDITOR.env.ie ) - lineBreak.getParent().appendBogus(); + function enter( editor, mode, forceMode ) + { + // Only effective within document. + if ( editor.mode != 'wysiwyg' ) + return false; - // Now we can remove the text node contents, so the caret doesn't - // stop on it. - if ( !CKEDITOR.env.ie ) - lineBreak.getNext().$.nodeValue = ''; - // IE has different behavior regarding position. - if ( CKEDITOR.env.ie ) - range.setStartAt( lineBreak, CKEDITOR.POSITION_AFTER_END ); - else - range.setStartAt( lineBreak.getNext(), CKEDITOR.POSITION_AFTER_START ); + if ( !mode ) + mode = editor.config.enterMode; - // Scroll into view, for non IE. - if ( !CKEDITOR.env.ie ) + // Use setTimout so the keys get cancelled immediatelly. + setTimeout( function() { - var dummy = null; - - // BR is not positioned in Opera and Webkit. - if ( !CKEDITOR.env.gecko ) - { - dummy = doc.createElement( 'span' ); - // We need have some contents for Webkit to position it - // under parent node. ( #3681) - dummy.setHtml(' '); - } + editor.fire( 'saveSnapshot' ); // Save undo step. + if ( mode == CKEDITOR.ENTER_BR || editor.getSelection().getStartElement().hasAscendant( 'pre', true ) ) + enterBr( editor, mode, null, forceMode ); else - dummy = doc.createElement( 'br' ); - - dummy.insertBefore( lineBreak.getNext() ); - dummy.scrollIntoView(); - dummy.remove(); - } - } + enterBlock( editor, mode, null, forceMode ); - // This collapse guarantees the cursor will be blinking. - range.collapse( true ); + }, 0 ); - range.select( isPre ); + return true; } + function getRange( editor ) { // Get the selection ranges. diff --git a/_source/plugins/entities/plugin.js b/_source/plugins/entities/plugin.js index 43c5279..838b37b 100644 --- a/_source/plugins/entities/plugin.js +++ b/_source/plugins/entities/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/fakeobjects/plugin.js b/_source/plugins/fakeobjects/plugin.js index 8d761ab..c53085a 100644 --- a/_source/plugins/fakeobjects/plugin.js +++ b/_source/plugins/fakeobjects/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -11,25 +11,25 @@ For licensing, see LICENSE.html or http://ckeditor.com/license { $ : function( element ) { - var realHtml = element.attributes._cke_realelement, + var attributes = element.attributes, + realHtml = attributes && attributes._cke_realelement, realFragment = realHtml && new CKEDITOR.htmlParser.fragment.fromHtml( decodeURIComponent( realHtml ) ), realElement = realFragment && realFragment.children[ 0 ]; - if ( realElement ) + // If we have width/height in the element, we must move it into + // the real element. + if ( realElement && element.attributes._cke_resizable ) { - // If we have width/height in the element, we must move it into - // the real element. - var style = element.attributes.style; if ( style ) { // Get the width from the style. - var match = /(?:^|\s)width\s*:\s*(\d+)/.exec( style ), + var match = /(?:^|\s)width\s*:\s*(\d+)/i.exec( style ), width = match && match[1]; // Get the height from the style. - match = /(?:^|\s)height\s*:\s*(\d+)/.exec( style ); + match = /(?:^|\s)height\s*:\s*(\d+)/i.exec( style ); var height = match && match[1]; if ( width ) @@ -63,15 +63,19 @@ For licensing, see LICENSE.html or http://ckeditor.com/license CKEDITOR.editor.prototype.createFakeElement = function( realElement, className, realElementType, isResizable ) { var lang = this.lang.fakeobjects; + var attributes = { 'class' : className, src : CKEDITOR.getUrl( 'images/spacer.gif' ), _cke_realelement : encodeURIComponent( realElement.getOuterHtml() ), + _cke_real_node_type : realElement.type, alt : lang[ realElementType ] || lang.unknown }; + if ( realElementType ) attributes._cke_real_element_type = realElementType; + if ( isResizable ) attributes._cke_resizable = isResizable; @@ -80,18 +84,19 @@ CKEDITOR.editor.prototype.createFakeElement = function( realElement, className, CKEDITOR.editor.prototype.createFakeParserElement = function( realElement, className, realElementType, isResizable ) { - var writer = new CKEDITOR.htmlParser.basicWriter(); + var lang = this.lang.fakeobjects, + html; + var writer = new CKEDITOR.htmlParser.basicWriter(); realElement.writeHtml( writer ); - - var html = writer.getHtml(); - var lang = this.lang.fakeobjects; + html = writer.getHtml(); var attributes = { 'class' : className, src : CKEDITOR.getUrl( 'images/spacer.gif' ), _cke_realelement : encodeURIComponent( html ), + _cke_real_node_type : realElement.type, alt : lang[ realElementType ] || lang.unknown }; @@ -106,6 +111,10 @@ CKEDITOR.editor.prototype.createFakeParserElement = function( realElement, class CKEDITOR.editor.prototype.restoreRealElement = function( fakeElement ) { - var html = decodeURIComponent( fakeElement.getAttribute( '_cke_realelement' ) ); - return CKEDITOR.dom.element.createFromHtml( html, this.document ); + if ( fakeElement.getAttribute( '_cke_real_node_type' ) != CKEDITOR.NODE_ELEMENT ) + return null; + + return CKEDITOR.dom.element.createFromHtml( + decodeURIComponent( fakeElement.getAttribute( '_cke_realelement' ) ), + this.document ); }; diff --git a/_source/plugins/filebrowser/plugin.js b/_source/plugins/filebrowser/plugin.js index d90e8cd..78e8b66 100644 --- a/_source/plugins/filebrowser/plugin.js +++ b/_source/plugins/filebrowser/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/find/dialogs/find.js b/_source/plugins/find/dialogs/find.js index 4776499..e24705c 100644 --- a/_source/plugins/find/dialogs/find.js +++ b/_source/plugins/find/dialogs/find.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/find/plugin.js b/_source/plugins/find/plugin.js index 78b40aa..1f5e200 100644 --- a/_source/plugins/find/plugin.js +++ b/_source/plugins/find/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/flash/dialogs/flash.js b/_source/plugins/flash/dialogs/flash.js index a1ff2b5..94f86ce 100644 --- a/_source/plugins/flash/dialogs/flash.js +++ b/_source/plugins/flash/dialogs/flash.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -173,7 +173,8 @@ For licensing, see LICENSE.html or http://ckeditor.com/license var makeObjectTag = !editor.config.flashEmbedTagOnly, makeEmbedTag = editor.config.flashAddEmbedTag || editor.config.flashEmbedTagOnly; - var previewAreaHtml = '<div>' + CKEDITOR.tools.htmlEncode( editor.lang.image.preview ) +'<br>' + + var previewPreloader, + previewAreaHtml = '<div>' + CKEDITOR.tools.htmlEncode( editor.lang.image.preview ) +'<br>' + '<div id="FlashPreviewLoader" style="display:none"><div class="loading"> </div></div>' + '<div id="FlashPreviewBox"></div></div>'; @@ -185,6 +186,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license { // Clear previously saved elements. this.fakeImage = this.objectNode = this.embedNode = null; + previewPreloader = new CKEDITOR.dom.element( 'embeded', editor.document ); // Try to detect any embed or object tag that has Flash parameters. var fakeImage = this.getSelectedElement(); @@ -271,7 +273,10 @@ For licensing, see LICENSE.html or http://ckeditor.com/license var newFakeImage = editor.createFakeElement( objectNode || embedNode, 'cke_flash', 'flash', true ); newFakeImage.setStyles( extraStyles ); if ( this.fakeImage ) + { newFakeImage.replace( this.fakeImage ); + editor.getSelection().selectElement( newFakeImage ); + } else editor.insertElement( newFakeImage ); }, @@ -315,9 +320,10 @@ For licensing, see LICENSE.html or http://ckeditor.com/license { var dialog = this.getDialog(), updatePreview = function( src ){ - + // Query the preloader to figure out the url impacted by based href. + previewPreloader.setAttribute( 'src', src ); dialog.preview.setHtml( '<embed height="100%" width="100%" src="' - + CKEDITOR.tools.htmlEncode( src ) + + CKEDITOR.tools.htmlEncode( previewPreloader.getAttribute( 'src' ) ) + '" type="application/x-shockwave-flash"></embed>' ); }; // Preview element @@ -556,15 +562,15 @@ For licensing, see LICENSE.html or http://ckeditor.com/license items : [ [ editor.lang.common.notSet , ''], - [ editor.lang.image.alignLeft , 'left'], - [ editor.lang.image.alignAbsBottom , 'absBottom'], - [ editor.lang.image.alignAbsMiddle , 'absMiddle'], - [ editor.lang.image.alignBaseline , 'baseline'], - [ editor.lang.image.alignBottom , 'bottom'], - [ editor.lang.image.alignMiddle , 'middle'], - [ editor.lang.image.alignRight , 'right'], - [ editor.lang.image.alignTextTop , 'textTop'], - [ editor.lang.image.alignTop , 'top'] + [ editor.lang.flash.alignLeft , 'left'], + [ editor.lang.flash.alignAbsBottom , 'absBottom'], + [ editor.lang.flash.alignAbsMiddle , 'absMiddle'], + [ editor.lang.flash.alignBaseline , 'baseline'], + [ editor.lang.flash.alignBottom , 'bottom'], + [ editor.lang.flash.alignMiddle , 'middle'], + [ editor.lang.flash.alignRight , 'right'], + [ editor.lang.flash.alignTextTop , 'textTop'], + [ editor.lang.flash.alignTop , 'top'] ], setup : loadValue, commit : commitValue diff --git a/_source/plugins/flash/plugin.js b/_source/plugins/flash/plugin.js index 07694c8..8d08041 100644 --- a/_source/plugins/flash/plugin.js +++ b/_source/plugins/flash/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -109,7 +109,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license // Look for the inner <embed> for ( var i = 0 ; i < element.children.length ; i++ ) { - if ( element.children[ i ].name == 'embed' ) + if ( element.children[ i ].name == 'cke:embed' ) { if ( !isFlashEmbed( element.children[ i ] ) ) return null; diff --git a/_source/plugins/floatpanel/plugin.js b/_source/plugins/floatpanel/plugin.js index b7031c6..55e4532 100644 --- a/_source/plugins/floatpanel/plugin.js +++ b/_source/plugins/floatpanel/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -55,10 +55,6 @@ CKEDITOR.plugins.add( 'floatpanel', this.element = element; - // Register panels to editor for easy destroying ( #4241 ). - editor.panels ? editor.panels.push( element ) : editor.panels = [ element ]; - - this._ = { // The panel that will be floating. @@ -135,7 +131,6 @@ CKEDITOR.plugins.add( 'floatpanel', { top : top + 'px', left : '-3000px', - visibility : 'hidden', opacity : '0', // FF3 is ignoring "visibility" display : '' }); @@ -153,7 +148,7 @@ CKEDITOR.plugins.add( 'floatpanel', focused.on( 'blur', function( ev ) { - if ( CKEDITOR.env.ie && !this.allowBlur() ) + if ( !this.allowBlur() ) return; // As we are using capture to register the listener, @@ -195,16 +190,9 @@ CKEDITOR.plugins.add( 'floatpanel', if ( rtl ) left -= element.$.offsetWidth; - element.setStyles( - { - left : left + 'px', - visibility : '', - opacity : '1' // FF3 is ignoring "visibility" - }); - - if ( block.autoSize ) + var panelLoad = CKEDITOR.tools.bind( function () { - function setHeight() + if ( block.autoSize ) { var target = element.getFirst(); var height = block.element.$.scrollHeight; @@ -220,16 +208,40 @@ CKEDITOR.plugins.add( 'floatpanel', // Fix IE < 8 visibility. panel._.currentBlock.element.setStyle( 'display', 'none' ).removeStyle( 'display' ); } - - if ( panel.isLoaded ) - setHeight(); else - panel.onLoad = setHeight; - } - else - element.getFirst().removeStyle( 'height' ); + element.getFirst().removeStyle( 'height' ); + + var panelElement = panel.element, + panelWindow = panelElement.getWindow(), + windowScroll = panelWindow.getScrollPosition(), + viewportSize = panelWindow.getViewPaneSize(), + panelSize = + { + 'height' : panelElement.$.offsetHeight, + 'width' : panelElement.$.offsetWidth + }; + + // If the menu is horizontal off, shift it toward + // the opposite language direction. + if ( rtl ? left < 0 : left + panelSize.width > viewportSize.width + windowScroll.x ) + left += ( panelSize.width * ( rtl ? 1 : -1 ) ); + + // Vertical off screen is simpler. + if( top + panelSize.height > viewportSize.height + windowScroll.y ) + top -= panelSize.height; + + element.setStyles( + { + top : top + 'px', + left : left + 'px', + opacity : '1' + } ); + + } , this ); + + panel.isLoaded ? panelLoad() : panel.onLoad = panelLoad; - // Set the IFrame focus, so the blur event gets fired. + // Set the panel frame focus, so the blur event gets fired. CKEDITOR.tools.setTimeout( function() { if ( definition.voiceLabel ) @@ -243,14 +255,11 @@ CKEDITOR.plugins.add( 'floatpanel', iframe.setAttribute( 'title', ' ' ); } } - if ( CKEDITOR.env.ie && CKEDITOR.env.quirks ) - iframe.focus(); - else - iframe.$.contentWindow.focus(); + iframe.$.contentWindow.focus(); // We need this get fired manually because of unfired focus() function. - if ( CKEDITOR.env.ie && !CKEDITOR.env.quirks ) - this.allowBlur( true ); + this.allowBlur( true ); + }, 0, this); }, 0, this); this.visible = 1; @@ -329,4 +338,23 @@ CKEDITOR.plugins.add( 'floatpanel', } } }); + + CKEDITOR.on( 'instanceDestroyed', function() + { + var isLastInstance = CKEDITOR.tools.isEmpty( CKEDITOR.instances ); + + for( var i in panels ) + { + var panel = panels[ i ]; + // Safe to destroy it since there're no more instances.(#4241) + if( isLastInstance ) + panel.destroy(); + // Panel might be used by other instances, just hide them.(#4552) + else + panel.element.hide(); + } + // Remove the registration. + isLastInstance && ( panels = {} ); + + } ); })(); diff --git a/_source/plugins/font/plugin.js b/_source/plugins/font/plugin.js index 727717e..77c7bc3 100644 --- a/_source/plugins/font/plugin.js +++ b/_source/plugins/font/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -36,7 +36,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license panel : { - css : [ CKEDITOR.getUrl( editor.skinPath + 'editor.css' ) ].concat( config.contentsCss ), + css : editor.skin.editor.css.concat( config.contentsCss ), voiceLabel : lang.panelVoiceLabel }, diff --git a/_source/plugins/format/plugin.js b/_source/plugins/format/plugin.js index d73462c..34a510b 100644 --- a/_source/plugins/format/plugin.js +++ b/_source/plugins/format/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -33,7 +33,7 @@ CKEDITOR.plugins.add( 'format', panel : { - css : [ CKEDITOR.getUrl( editor.skinPath + 'editor.css' ) ].concat( config.contentsCss ), + css : editor.skin.editor.css.concat( config.contentsCss ), voiceLabel : lang.panelVoiceLabel }, @@ -57,7 +57,11 @@ CKEDITOR.plugins.add( 'format', styles[ value ].apply( editor.document ); - editor.fire( 'saveSnapshot' ); + // Save the undo snapshot after all changes are affected. (#4899) + setTimeout( function() + { + editor.fire( 'saveSnapshot' ); + }, 0 ); }, onRender : function() diff --git a/_source/plugins/forms/dialogs/button.js b/_source/plugins/forms/dialogs/button.js index 800c4d4..6cf178e 100644 --- a/_source/plugins/forms/dialogs/button.js +++ b/_source/plugins/forms/dialogs/button.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ CKEDITOR.dialog.add( 'button', function( editor ) diff --git a/_source/plugins/forms/dialogs/checkbox.js b/_source/plugins/forms/dialogs/checkbox.js index 6f6f37a..3742642 100644 --- a/_source/plugins/forms/dialogs/checkbox.js +++ b/_source/plugins/forms/dialogs/checkbox.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ CKEDITOR.dialog.add( 'checkbox', function( editor ) @@ -79,14 +79,17 @@ CKEDITOR.dialog.add( 'checkbox', function( editor ) accessKey : 'V', setup : function( element ) { - this.setValue( element.getAttribute( 'value' ) || '' ); + var value = element.getAttribute( 'value' ); + // IE Return 'on' as default attr value. + this.setValue( CKEDITOR.env.ie && value == 'on' ? '' : value ); }, commit : function( data ) { - var element = data.element; + var element = data.element, + value = this.getValue(); - if ( this.getValue() ) - element.setAttribute( 'value', this.getValue() ); + if ( value && !( CKEDITOR.env.ie && value == 'on' ) ) + element.setAttribute( 'value', value ); else element.removeAttribute( 'value' ); } @@ -115,7 +118,8 @@ CKEDITOR.dialog.add( 'checkbox', function( editor ) { var replace = CKEDITOR.dom.element.createFromHtml( '<input type="checkbox"' + ( isChecked ? ' checked="checked"' : '' ) - + '></input>', editor.document ); + + '/>', editor.document ); + element.copyAttributes( replace, { type : 1, checked : 1 } ); replace.replace( element ); editor.getSelection().selectElement( replace ); @@ -124,8 +128,9 @@ CKEDITOR.dialog.add( 'checkbox', function( editor ) } else { - if ( this.getValue() ) - element.setAttribute( 'checked', this.getValue() ); + var value = this.getValue(); + if ( value ) + element.setAttribute( 'checked', 'checked' ); else element.removeAttribute( 'checked' ); } diff --git a/_source/plugins/forms/dialogs/form.js b/_source/plugins/forms/dialogs/form.js index 10827e3..1228fae 100644 --- a/_source/plugins/forms/dialogs/form.js +++ b/_source/plugins/forms/dialogs/form.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ CKEDITOR.dialog.add( 'form', function( editor ) diff --git a/_source/plugins/forms/dialogs/hiddenfield.js b/_source/plugins/forms/dialogs/hiddenfield.js index a98c089..45e130b 100644 --- a/_source/plugins/forms/dialogs/hiddenfield.js +++ b/_source/plugins/forms/dialogs/hiddenfield.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ CKEDITOR.dialog.add( 'hiddenfield', function( editor ) diff --git a/_source/plugins/forms/dialogs/radio.js b/_source/plugins/forms/dialogs/radio.js index d9f5fd4..8cbd996 100644 --- a/_source/plugins/forms/dialogs/radio.js +++ b/_source/plugins/forms/dialogs/radio.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ CKEDITOR.dialog.add( 'radio', function( editor ) diff --git a/_source/plugins/forms/dialogs/select.js b/_source/plugins/forms/dialogs/select.js index be91cba..7d2f92f 100644 --- a/_source/plugins/forms/dialogs/select.js +++ b/_source/plugins/forms/dialogs/select.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ CKEDITOR.dialog.add( 'select', function( editor ) @@ -160,7 +160,18 @@ CKEDITOR.dialog.add( 'select', function( editor ) this.commitContent( element ); if ( isInsertMode ) - editor.insertElement( element ); + { + editor.insertElement(element); + if( CKEDITOR.env.ie ) + { + var sel = editor.getSelection(), + bms = sel.createBookmarks(); + setTimeout(function () + { + sel.selectBookmarks( bms ); + }, 0 ); + } + } }, contents : [ { diff --git a/_source/plugins/forms/dialogs/textarea.js b/_source/plugins/forms/dialogs/textarea.js index f56b42a..8a4fd5f 100644 --- a/_source/plugins/forms/dialogs/textarea.js +++ b/_source/plugins/forms/dialogs/textarea.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ CKEDITOR.dialog.add( 'textarea', function( editor ) diff --git a/_source/plugins/forms/dialogs/textfield.js b/_source/plugins/forms/dialogs/textfield.js index 66e357d..bf0a131 100644 --- a/_source/plugins/forms/dialogs/textfield.js +++ b/_source/plugins/forms/dialogs/textfield.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ CKEDITOR.dialog.add( 'textfield', function( editor ) @@ -179,7 +179,7 @@ CKEDITOR.dialog.add( 'textfield', function( editor ) element.copyAttributes( replace, { type : 1 } ); replace.replace( element ); editor.getSelection().selectElement( replace ); - data.element = element; + data.element = replace; } } else diff --git a/_source/plugins/forms/plugin.js b/_source/plugins/forms/plugin.js index e95dc13..859bba1 100644 --- a/_source/plugins/forms/plugin.js +++ b/_source/plugins/forms/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -120,7 +120,7 @@ CKEDITOR.plugins.add( 'forms', { editor.contextMenu.addListener( function( element ) { - if ( element && element.hasAscendant( 'form' ) ) + if ( element && element.hasAscendant( 'form', true ) ) return { form : CKEDITOR.TRISTATE_OFF }; }); @@ -162,6 +162,30 @@ CKEDITOR.plugins.add( 'forms', }); } }, + + afterInit : function( editor ) + { + // Cleanup certain IE form elements default values. + if( CKEDITOR.env.ie ) + { + var dataProcessor = editor.dataProcessor, + htmlFilter = dataProcessor && dataProcessor.htmlFilter; + + htmlFilter && htmlFilter.addRules( + { + elements : + { + input : function( input ) + { + var attrs = input.attributes, + type = attrs.type; + if( type == 'checkbox' || type == 'radio' ) + attrs.value == 'on' && delete attrs.value; + } + } + } ); + } + }, requires : [ 'image' ] } ); diff --git a/_source/plugins/horizontalrule/plugin.js b/_source/plugins/horizontalrule/plugin.js index cfbfe0c..3fb90b3 100644 --- a/_source/plugins/horizontalrule/plugin.js +++ b/_source/plugins/horizontalrule/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -11,6 +11,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license { var horizontalruleCmd = { + canUndo : false, // The undo snapshot will be handled by 'insertElement'. exec : function( editor ) { editor.insertElement( editor.document.createElement( 'hr' ) ); diff --git a/_source/plugins/htmldataprocessor/plugin.js b/_source/plugins/htmldataprocessor/plugin.js index 98d46a6..cae6522 100644 --- a/_source/plugins/htmldataprocessor/plugin.js +++ b/_source/plugins/htmldataprocessor/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -11,7 +11,6 @@ For licensing, see LICENSE.html or http://ckeditor.com/license var protectedSourceMarker = '{cke_protected}'; - // Return the last non-space child node of the block (#4344). function lastNoneSpaceChild( block ) { @@ -110,18 +109,24 @@ For licensing, see LICENSE.html or http://ckeditor.com/license [ ( /^_cke_(saved|pa)_/ ), '' ], // All "_cke" attributes are to be ignored. - [ ( /^_cke.*/ ), '' ] + [ ( /^_cke.*/ ), '' ], + + [ 'hidefocus', '' ] ], elements : { $ : function( element ) { - // Remove duplicated attributes - #3789. var attribs = element.attributes; if ( attribs ) { + // Elements marked as temporary are to be ignored. + if ( attribs.cke_temp ) + return false; + + // Remove duplicated attributes - #3789. var attributeNames = [ 'name', 'href', 'src' ], savedAttributeName; for ( var i = 0 ; i < attributeNames.length ; i++ ) @@ -130,6 +135,8 @@ For licensing, see LICENSE.html or http://ckeditor.com/license savedAttributeName in attribs && ( delete attribs[ attributeNames[ i ] ] ); } } + + return element; }, embed : function( element ) @@ -163,6 +170,21 @@ For licensing, see LICENSE.html or http://ckeditor.com/license { return false; } + }, + + body : function( element ) + { + delete element.attributes.spellcheck; + delete element.attributes.contenteditable; + }, + + style : function( element ) + { + var child = element.children[ 0 ]; + child && child.value && ( child.value = CKEDITOR.tools.trim( child.value )); + + if ( !element.attributes.type ) + element.attributes.type = 'text/css'; } }, @@ -177,8 +199,17 @@ For licensing, see LICENSE.html or http://ckeditor.com/license comment : function( contents ) { + // If this is a comment for protected source. if ( contents.substr( 0, protectedSourceMarker.length ) == protectedSourceMarker ) - return new CKEDITOR.htmlParser.cdata( decodeURIComponent( contents.substr( protectedSourceMarker.length ) ) ); + { + // Remove the extra marker for real comments from it. + if ( contents.substr( protectedSourceMarker.length, 3 ) == '{C}' ) + contents = contents.substr( protectedSourceMarker.length + 3 ); + else + contents = contents.substr( protectedSourceMarker.length ); + + return new CKEDITOR.htmlParser.cdata( decodeURIComponent( contents ) ); + } return contents; } @@ -199,71 +230,102 @@ For licensing, see LICENSE.html or http://ckeditor.com/license }; } - var protectAttributeRegex = /<(?:a|area|img|input).*?\s((?:href|src|name)\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|(?:[^ "'>]+)))/gi; + var protectAttributeRegex = /<(?:a|area|img|input)[\s\S]*?\s((?:href|src|name)\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|(?:[^ "'>]+)))/gi; + + var protectElementsRegex = /(?:<style(?=[ >])[^>]*>[\s\S]*<\/style>)|(?:<(:?link|meta|base)[^>]*>)/gi, + encodedElementsRegex = /<cke:encoded>([^<]*)<\/cke:encoded>/gi; + + var protectElementNamesRegex = /(<\/?)((?:object|embed|param|html|body|head|title)[^>]*>)/gi, + unprotectElementNamesRegex = /(<\/?)cke:((?:html|body|head|title)[^>]*>)/gi; + + var protectSelfClosingRegex = /<cke:(param|embed)([^>]*?)\/?>(?!\s*<\/cke:\1)/gi; function protectAttributes( html ) { return html.replace( protectAttributeRegex, '$& _cke_saved_$1' ); } - var protectStyleTagsRegex = /<(style)(?=[ >])[^>]*>[^<]*<\/\1>/gi; - var encodedTagsRegex = /<cke:encoded>([^<]*)<\/cke:encoded>/gi; - var protectElementNamesRegex = /(<\/?)((?:object|embed|param).*?>)/gi; - var protectSelfClosingRegex = /<cke:param(.*?)\/>/gi; - - function protectStyleTagsMatch( match ) + function protectElements( html ) { - return '<cke:encoded>' + encodeURIComponent( match ) + '</cke:encoded>'; + return html.replace( protectElementsRegex, function( match ) + { + return '<cke:encoded>' + encodeURIComponent( match ) + '</cke:encoded>'; + }); } - function protectStyleTags( html ) + function unprotectElements( html ) { - return html.replace( protectStyleTagsRegex, protectStyleTagsMatch ); + return html.replace( encodedElementsRegex, function( match, encoded ) + { + return decodeURIComponent( encoded ); + }); } + function protectElementsNames( html ) { return html.replace( protectElementNamesRegex, '$1cke:$2'); } + + function unprotectElementNames( html ) + { + return html.replace( unprotectElementNamesRegex, '$1$2' ); + } + function protectSelfClosingElements( html ) { - return html.replace( protectSelfClosingRegex, '<cke:param$1></cke:param>' ); + return html.replace( protectSelfClosingRegex, '<cke:$1$2></cke:$1>' ); } - function unprotectEncodedTagsMatch( match, encoded ) + function protectRealComments( html ) { - return decodeURIComponent( encoded ); + return html.replace( /<!--(?!{cke_protected})[\s\S]+?-->/g, function( match ) + { + return '<!--' + protectedSourceMarker + + '{C}' + + encodeURIComponent( match ).replace( /--/g, '%2D%2D' ) + + '-->'; + }); } - function unprotectEncodedTags( html ) + function unprotectRealComments( html ) { - return html.replace( encodedTagsRegex, unprotectEncodedTagsMatch ); + return html.replace( /<!--\{cke_protected\}\{C\}([\s\S]+?)-->/g, function( match, data ) + { + return decodeURIComponent( data ); + }); } function protectSource( data, protectRegexes ) { var protectedHtml = [], - tempRegex = /<\!--\{cke_temp\}(\d*?)-->/g; + tempRegex = /<\!--\{cke_temp(comment)?\}(\d*?)-->/g; + var regexes = [ - // First of any other protection, we must protect all comments - // to avoid loosing them (of course, IE related). - (/<!--[\s\S]*?-->/g), - // Script tags will also be forced to be protected, otherwise // IE will execute them. - /<script[\s\S]*?<\/script>/gi, + ( /<script[\s\S]*?<\/script>/gi ), // <noscript> tags (get lost in IE and messed up in FF). /<noscript[\s\S]*?<\/noscript>/gi ] .concat( protectRegexes ); + // First of any other protection, we must protect all comments + // to avoid loosing them (of course, IE related). + // Note that we use a different tag for comments, as we need to + // transform them when applying filters. + data = data.replace( (/<!--[\s\S]*?-->/g), function( match ) + { + return '<!--{cke_tempcomment}' + ( protectedHtml.push( match ) - 1 ) + '-->'; + }); + for ( var i = 0 ; i < regexes.length ; i++ ) { data = data.replace( regexes[i], function( match ) { match = match.replace( tempRegex, // There could be protected source inside another one. (#3869). - function( $, id ) + function( $, isComment, id ) { return protectedHtml[ id ]; } @@ -271,9 +333,10 @@ For licensing, see LICENSE.html or http://ckeditor.com/license return '<!--{cke_temp}' + ( protectedHtml.push( match ) - 1 ) + '-->'; }); } - data = data.replace( tempRegex, function( $, id ) + data = data.replace( tempRegex, function( $, isComment, id ) { return '<!--' + protectedSourceMarker + + ( isComment ? '{C}' : '' ) + encodeURIComponent( protectedHtml[ id ] ).replace( /--/g, '%2D%2D' ) + '-->'; } @@ -321,27 +384,33 @@ For licensing, see LICENSE.html or http://ckeditor.com/license // the code. data = protectAttributes( data ); - // IE remvoes style tags from innerHTML. (#3710). - if ( CKEDITOR.env.ie ) - data = protectStyleTags( data ); + // Protect elements than can't be set inside a DIV. E.g. IE removes + // style tags from innerHTML. (#3710) + data = protectElements( data ); // Certain elements has problem to go through DOM operation, protect - // them by prefixing 'cke' namespace.(#3591) + // them by prefixing 'cke' namespace. (#3591) data = protectElementsNames( data ); // All none-IE browsers ignore self-closed custom elements, - // protecting them into open-close.(#3591) + // protecting them into open-close. (#3591) data = protectSelfClosingElements( data ); // Call the browser to help us fixing a possibly invalid HTML // structure. - var div = document.createElement( 'div' ); + var div = new CKEDITOR.dom.element( 'div' ); // Add fake character to workaround IE comments bug. (#3801) - div.innerHTML = 'a' + data; - data = div.innerHTML.substr( 1 ); + div.setHtml( 'a' + data ); + data = div.getHtml().substr( 1 ); - if ( CKEDITOR.env.ie ) - data = unprotectEncodedTags( data ); + // Unprotect "some" of the protected elements at this point. + data = unprotectElementNames( data ); + + data = unprotectElements( data ); + + // Restore the comments that have been protected, in this way they + // can be properly filtered. + data = unprotectRealComments( data ); // Now use our parser to make further fixes to the structure, as // well as apply the filter. @@ -349,8 +418,12 @@ For licensing, see LICENSE.html or http://ckeditor.com/license writer = new CKEDITOR.htmlParser.basicWriter(); fragment.writeHtml( writer, this.dataFilter ); + data = writer.getHtml( true ); - return writer.getHtml( true ); + // Protect the real comments again. + data = protectRealComments( data ); + + return data; }, toDataFormat : function( html, fixForBody ) diff --git a/_source/plugins/htmlwriter/plugin.js b/_source/plugins/htmlwriter/plugin.js index 0848be9..87f7906 100644 --- a/_source/plugins/htmlwriter/plugin.js +++ b/_source/plugins/htmlwriter/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -67,7 +67,7 @@ CKEDITOR.htmlWriter = CKEDITOR.tools.createClass( var dtd = CKEDITOR.dtd; - for ( var e in CKEDITOR.tools.extend( {}, dtd.$block, dtd.$listItem, dtd.$tableContent ) ) + for ( var e in CKEDITOR.tools.extend( {}, dtd.$nonBodyContent, dtd.$block, dtd.$listItem, dtd.$tableContent ) ) { this.setRules( e, { @@ -78,15 +78,29 @@ CKEDITOR.htmlWriter = CKEDITOR.tools.createClass( breakAfterClose : true }); } + this.setRules( 'br', { breakAfterOpen : true }); + + this.setRules( 'title', + { + indent : false, + breakAfterOpen : false + }); + + this.setRules( 'style', + { + indent : false, + breakBeforeClose : true + }); + // Disable indentation on <pre>. this.setRules( 'pre', - { - indent: false - } ); + { + indent: false + }); }, proto : @@ -262,7 +276,8 @@ CKEDITOR.htmlWriter = CKEDITOR.tools.createClass( * <li><b>breakAfterClose</b>: break line after the closer tag for this element.</li> * </ul> * - * All rules default to "false". + * All rules default to "false". Each call to the function overrides + * already present rules, leaving the undefined untouched. * * By default, all elements available in the {@link CKEDITOR.dtd.$block), * {@link CKEDITOR.dtd.$listItem} and {@link CKEDITOR.dtd.$tableContent} @@ -283,7 +298,12 @@ CKEDITOR.htmlWriter = CKEDITOR.tools.createClass( */ setRules : function( tagName, rules ) { - this._.rules[ tagName ] = rules; + var currentRules = this._.rules[ tagName ]; + + if ( currentRules ) + CKEDITOR.tools.extend( currentRules, rules, true ); + else + this._.rules[ tagName ] = rules; } } }); diff --git a/_source/plugins/iframedialog/plugin.js b/_source/plugins/iframedialog/plugin.js index c67cf6a..34a69d8 100644 --- a/_source/plugins/iframedialog/plugin.js +++ b/_source/plugins/iframedialog/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/image/dialogs/image.js b/_source/plugins/image/dialogs/image.js index 003af07..fd165af 100644 --- a/_source/plugins/image/dialogs/image.js +++ b/_source/plugins/image/dialogs/image.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -11,7 +11,8 @@ For licensing, see LICENSE.html or http://ckeditor.com/license PREVIEW = 4, CLEANUP = 8, regexGetSize = /^\s*(\d+)((px)|\%)?\s*$/i, - regexGetSizeOrEmpty = /(^\s*(\d+)((px)|\%)?\s*$)|^$/i; + regexGetSizeOrEmpty = /(^\s*(\d+)((px)|\%)?\s*$)|^$/i, + pxLengthRegex = /^\d+px$/; var onSizeChange = function() { @@ -61,6 +62,55 @@ For licensing, see LICENSE.html or http://ckeditor.com/license return 0; }; + // Custom commit dialog logic, where we're intended to give inline style + // field (txtdlgGenStyle) higher priority to avoid overwriting styles contribute + // by other fields. + function commitContent() + { + var args = arguments; + var inlineStyleField = this.getContentElement( 'advanced', 'txtdlgGenStyle' ); + inlineStyleField && inlineStyleField.commit.apply( inlineStyleField, args ); + + this.foreach( function( widget ) + { + if ( widget.commit && widget.id != 'txtdlgGenStyle' ) + widget.commit.apply( widget, args ); + }); + } + + // Avoid recursions. + var incommit; + + // Synchronous field values to other impacted fields is required, e.g. border + // size change should alter inline-style text as well. + function commitInternally( targetFields ) + { + if( incommit ) + return; + + incommit = 1; + + var dialog = this.getDialog(), + element = dialog.imageElement; + if( element ) + { + // Commit this field and broadcast to target fields. + this.commit( IMAGE, element ); + + targetFields = [].concat( targetFields ); + var length = targetFields.length, + field; + for ( var i = 0; i < length; i++ ) + { + field = dialog.getContentElement.apply( dialog, targetFields[ i ].split( ':' ) ); + // May cause recursion. + field && field.setup( IMAGE, element ); + } + } + + incommit = 0; + } + var switchLockRatio = function( dialog, value ) { var oImageOriginal = dialog.originalElement, @@ -138,13 +188,15 @@ For licensing, see LICENSE.html or http://ckeditor.com/license if ( size ) value = checkDimension( size, value ); - value = checkDimension( element.$.style[ dimension ], value ); + value = checkDimension( element.getStyle( dimension ), value ); this.setValue( value ); }; var imageDialog = function( editor, dialogType ) { + var previewPreloader; + var onImgLoadEvent = function() { // Image is ready. @@ -162,7 +214,8 @@ For licensing, see LICENSE.html or http://ckeditor.com/license resetSize( this ); if ( this.firstLoad ) - switchLockRatio( this, 'check' ); + CKEDITOR.tools.setTimeout( function(){ switchLockRatio( this, 'check' ); }, 0, this ); + this.firstLoad = false; this.dontResetSize = false; }; @@ -203,16 +256,17 @@ For licensing, see LICENSE.html or http://ckeditor.com/license this.firstLoad = true; this.addLink = false; - //Hide loader. - CKEDITOR.document.getById( 'ImagePreviewLoader' ).setStyle( 'display', 'none' ); - // Preview - this.preview = CKEDITOR.document.getById( 'previewImage' ); - var editor = this.getParentEditor(), sel = this.getParentEditor().getSelection(), element = sel.getSelectedElement(), link = element && element.getAscendant( 'a' ); + //Hide loader. + CKEDITOR.document.getById( 'ImagePreviewLoader' ).setStyle( 'display', 'none' ); + // Create the preview before setup the dialog contents. + previewPreloader = new CKEDITOR.dom.element( 'img', editor.document ); + this.preview = CKEDITOR.document.getById( 'previewImage' ); + // Copy of the image this.originalElement = editor.document.createElement( 'img' ); this.originalElement.setAttribute( 'alt', '' ); @@ -242,15 +296,19 @@ For licensing, see LICENSE.html or http://ckeditor.com/license this.setupContent( LINK, link ); } - if ( element && element.getName() == 'img' && !element.getAttribute( '_cke_protected_html' ) ) - this.imageEditMode = 'img'; - else if ( element && element.getName() == 'input' && element.getAttribute( 'type' ) && element.getAttribute( 'type' ) == 'image' ) - this.imageEditMode = 'input'; + if ( element && element.getName() == 'img' && !element.getAttribute( '_cke_realelement' ) + || element && element.getName() == 'input' && element.getAttribute( 'type' ) == 'image' ) + { + this.imageEditMode = element.getName(); + this.imageElement = element; + } - if ( this.imageEditMode || this.imageElement ) + if ( this.imageEditMode ) { - if ( !this.imageElement ) - this.imageElement = element; + // Use the original element as a buffer from since we don't want + // temporary changes to be committed, e.g. if the dialog is canceled. + this.cleanImageElement = this.imageElement; + this.imageElement = this.cleanImageElement.clone( true, true ); // Fill out all fields. this.setupContent( IMAGE, this.imageElement ); @@ -258,6 +316,8 @@ For licensing, see LICENSE.html or http://ckeditor.com/license // Refresh LockRatio button switchLockRatio ( this, true ); } + else + this.imageElement = editor.document.createElement( 'img' ); // Dont show preview if no URL given. if ( !CKEDITOR.tools.trim( this.getValueOf( 'info', 'txtUrl' ) ) ) @@ -296,6 +356,12 @@ For licensing, see LICENSE.html or http://ckeditor.com/license ); editor.insertElement( this.imageElement ); } + else + { + // Restore the original element before all commits. + this.imageElement = this.cleanImageElement; + delete this.cleanImageElement; + } } else // Create a new image. { @@ -318,6 +384,10 @@ For licensing, see LICENSE.html or http://ckeditor.com/license this.commitContent( IMAGE, this.imageElement ); this.commitContent( LINK, this.linkElement ); + // Remove empty style attribute. + if( !this.imageElement.getAttribute( 'style' ) ) + this.imageElement.removeAttribute( 'style' ); + // Insert a new Image. if ( !this.imageEditMode ) { @@ -358,6 +428,8 @@ For licensing, see LICENSE.html or http://ckeditor.com/license var doc = this._.element.getDocument(); this.addFocusable( doc.getById( 'btnResetSize' ), 5 ); this.addFocusable( doc.getById( 'btnLockSizes' ), 5 ); + + this.commitContent = commitContent; }, onHide : function() { @@ -372,6 +444,8 @@ For licensing, see LICENSE.html or http://ckeditor.com/license this.originalElement.remove(); this.originalElement = false; // Dialog is closed. } + + delete this.imageElement; }, contents : [ { @@ -422,8 +496,10 @@ For licensing, see LICENSE.html or http://ckeditor.com/license original.on( 'error', onImgLoadErrorEvent, dialog ); original.on( 'abort', onImgLoadErrorEvent, dialog ); original.setAttribute( 'src', newUrl ); - dialog.preview.setAttribute( 'src', newUrl ); + // Query the preloader to figure out the url impacted by based href. + previewPreloader.setAttribute( 'src', newUrl ); + dialog.preview.setAttribute( 'src', previewPreloader.$.src ); updatePreview( dialog ); } // Dont show preview if no URL given. @@ -442,15 +518,10 @@ For licensing, see LICENSE.html or http://ckeditor.com/license this.getDialog().dontResetSize = true; - // In IE7 the dialog is being rendered improperly when loading - // an image with a long URL. So we need to delay it a bit. (#4122) - setTimeout( function() - { - field.setValue( url ); // And call this.onChange() - // Manually set the initial value.(#4191) - field.setInitValue(); - field.focus(); - }, 0 ); + field.setValue( url ); // And call this.onChange() + // Manually set the initial value.(#4191) + field.setInitValue(); + field.focus(); } }, commit : function( type, element ) @@ -539,6 +610,10 @@ For licensing, see LICENSE.html or http://ckeditor.com/license labelLayout : 'horizontal', label : editor.lang.image.width, onKeyUp : onSizeChange, + onChange : function() + { + commitInternally.call( this, 'advanced:txtdlgGenStyle' ); + }, validate: function() { var aMatch = this.getValue().match( regexGetSizeOrEmpty ); @@ -547,19 +622,20 @@ For licensing, see LICENSE.html or http://ckeditor.com/license return !!aMatch; }, setup : setupDimension, - commit : function( type, element ) + commit : function( type, element, internalCommit ) { + var value = this.getValue(); if ( type == IMAGE ) { - var value = this.getValue(); if ( value ) - element.setAttribute( 'width', value ); - else if ( !value && this.isChanged() ) - element.removeAttribute( 'width' ); + element.setStyle( 'width', CKEDITOR.tools.cssLength( value ) ); + else if ( !value && this.isChanged( ) ) + element.removeStyle( 'width' ); + + !internalCommit && element.removeAttribute( 'width' ); } else if ( type == PREVIEW ) { - value = this.getValue(); var aMatch = value.match( regexGetSize ); if ( !aMatch ) { @@ -572,7 +648,6 @@ For licensing, see LICENSE.html or http://ckeditor.com/license } else if ( type == CLEANUP ) { - element.setStyle( 'width', '0px' ); // If removeAttribute doesn't work. element.removeAttribute( 'width' ); element.removeStyle( 'width' ); } @@ -585,6 +660,10 @@ For licensing, see LICENSE.html or http://ckeditor.com/license labelLayout : 'horizontal', label : editor.lang.image.height, onKeyUp : onSizeChange, + onChange : function() + { + commitInternally.call( this, 'advanced:txtdlgGenStyle' ); + }, validate: function() { var aMatch = this.getValue().match( regexGetSizeOrEmpty ); @@ -593,32 +672,33 @@ For licensing, see LICENSE.html or http://ckeditor.com/license return !!aMatch; }, setup : setupDimension, - commit : function( type, element ) + commit : function( type, element, internalCommit ) { + var value = this.getValue(); if ( type == IMAGE ) { - var value = this.getValue(); if ( value ) - element.setAttribute( 'height', value ); - else if ( !value && this.isChanged() ) + element.setStyle( 'height', CKEDITOR.tools.cssLength( value ) ); + else if ( !value && this.isChanged( ) ) + element.removeStyle( 'height' ); + + if( !internalCommit && type == IMAGE ) element.removeAttribute( 'height' ); } else if ( type == PREVIEW ) { - value = this.getValue(); var aMatch = value.match( regexGetSize ); if ( !aMatch ) { var oImageOriginal = this.getDialog().originalElement; if ( oImageOriginal.getCustomData( 'isReady' ) == 'true' ) - element.setStyle( 'height', oImageOriginal.$.height + 'px'); + element.setStyle( 'height', oImageOriginal.$.height + 'px' ); } else - element.setStyle( 'height', value + 'px'); + element.setStyle( 'height', value + 'px' ); } else if ( type == CLEANUP ) { - element.setStyle( 'height', '0px' ); // If removeAttribute doesn't work. element.removeAttribute( 'height' ); element.removeStyle( 'height' ); } @@ -703,6 +783,10 @@ For licensing, see LICENSE.html or http://ckeditor.com/license { updatePreview( this.getDialog() ); }, + onChange : function() + { + commitInternally.call( this, 'advanced:txtdlgGenStyle' ); + }, validate: function() { var func = CKEDITOR.dialog.validate.integer( editor.lang.common.validateNumberFailed ); @@ -711,26 +795,43 @@ For licensing, see LICENSE.html or http://ckeditor.com/license setup : function( type, element ) { if ( type == IMAGE ) - this.setValue( element.getAttribute( 'border' ) ); - }, - commit : function( type, element ) - { - if ( type == IMAGE ) { - if ( this.getValue() || this.isChanged() ) - element.setAttribute( 'border', this.getValue() ); + var value, + borderStyle = element.getStyle( 'border-width' ); + + borderStyle = borderStyle && borderStyle.match( /^(\d+px)(?: \1 \1 \1)?$/ ); + value = borderStyle && parseInt( borderStyle[ 1 ], 10 ); + !value && ( value = element.getAttribute( 'border' ) ); + + this.setValue( value ); } - else if ( type == PREVIEW ) + }, + commit : function( type, element, internalCommit ) + { + var value = parseInt( this.getValue(), 10 ); + if ( type == IMAGE || type == PREVIEW ) { - var value = parseInt( this.getValue(), 10 ); - value = isNaN( value ) ? 0 : value; - element.setAttribute( 'border', value ); - element.setStyle( 'border', value + 'px solid black' ); + if ( value ) + { + element.setStyle( 'border-width', CKEDITOR.tools.cssLength( value ) ); + element.setStyle( 'border-style', 'solid' ); + } + else if ( !value && this.isChanged() ) + { + element.removeStyle( 'border-width' ); + element.removeStyle( 'border-style' ); + element.removeStyle( 'border-color' ); + } + + if( !internalCommit && type == IMAGE ) + element.removeAttribute( 'border' ); } else if ( type == CLEANUP ) { element.removeAttribute( 'border' ); - element.removeStyle( 'border' ); + element.removeStyle( 'border-width' ); + element.removeStyle( 'border-style' ); + element.removeStyle( 'border-color' ); } } }, @@ -745,6 +846,10 @@ For licensing, see LICENSE.html or http://ckeditor.com/license { updatePreview( this.getDialog() ); }, + onChange : function() + { + commitInternally.call( this, 'advanced:txtdlgGenStyle' ); + }, validate: function() { var func = CKEDITOR.dialog.validate.integer( editor.lang.common.validateNumberFailed ); @@ -754,25 +859,41 @@ For licensing, see LICENSE.html or http://ckeditor.com/license { if ( type == IMAGE ) { - var value = element.getAttribute( 'hspace' ); - if ( value != -1 ) // In IE empty = -1. - this.setValue( value ); + var value, + marginLeftPx, + marginRightPx, + marginLeftStyle = element.getStyle( 'margin-left' ), + marginRightStyle = element.getStyle( 'margin-right' ); + + marginLeftStyle = marginLeftStyle && marginLeftStyle.match( pxLengthRegex ); + marginRightStyle = marginRightStyle && marginRightStyle.match( pxLengthRegex ); + marginLeftPx = parseInt( marginLeftStyle, 10 ); + marginRightPx = parseInt( marginRightStyle, 10 ); + + value = ( marginLeftPx == marginRightPx ) && marginLeftPx; + !value && ( value = element.getAttribute( 'hspace' ) ); + + this.setValue( value ); } }, - commit : function( type, element ) + commit : function( type, element, internalCommit ) { - if ( type == IMAGE ) + var value = parseInt( this.getValue(), 10 ); + if ( type == IMAGE || type == PREVIEW ) { - if ( this.getValue() || this.isChanged() ) - element.setAttribute( 'hspace', this.getValue() ); - } - else if ( type == PREVIEW ) - { - var value = parseInt( this.getValue(), 10 ); - value = isNaN( value ) ? 0 : value; - element.setAttribute( 'hspace', value ); - element.setStyle( 'margin-left', value + 'px' ); - element.setStyle( 'margin-right', value + 'px' ); + if ( value ) + { + element.setStyle( 'margin-left', CKEDITOR.tools.cssLength( value ) ); + element.setStyle( 'margin-right', CKEDITOR.tools.cssLength( value ) ); + } + else if ( !value && this.isChanged( ) ) + { + element.removeStyle( 'margin-left' ); + element.removeStyle( 'margin-right' ); + } + + if( !internalCommit && type == IMAGE ) + element.removeAttribute( 'hspace' ); } else if ( type == CLEANUP ) { @@ -793,6 +914,10 @@ For licensing, see LICENSE.html or http://ckeditor.com/license { updatePreview( this.getDialog() ); }, + onChange : function() + { + commitInternally.call( this, 'advanced:txtdlgGenStyle' ); + }, validate: function() { var func = CKEDITOR.dialog.validate.integer( editor.lang.common.validateNumberFailed ); @@ -801,22 +926,41 @@ For licensing, see LICENSE.html or http://ckeditor.com/license setup : function( type, element ) { if ( type == IMAGE ) - this.setValue( element.getAttribute( 'vspace' ) ); - }, - commit : function( type, element ) - { - if ( type == IMAGE ) { - if ( this.getValue() || this.isChanged() ) - element.setAttribute( 'vspace', this.getValue() ); + var value, + marginTopPx, + marginBottomPx, + marginTopStyle = element.getStyle( 'margin-top' ), + marginBottomStyle = element.getStyle( 'margin-bottom' ); + + marginTopStyle = marginTopStyle && marginTopStyle.match( pxLengthRegex ); + marginBottomStyle = marginBottomStyle && marginBottomStyle.match( pxLengthRegex ); + marginTopPx = parseInt( marginTopStyle, 10 ); + marginBottomPx = parseInt( marginBottomStyle, 10 ); + + value = ( marginTopPx == marginBottomPx ) && marginTopPx; + !value && ( value = element.getAttribute( 'vspace' ) ); + this.setValue( value ); } - else if ( type == PREVIEW ) + }, + commit : function( type, element, internalCommit ) + { + var value = parseInt( this.getValue(), 10 ); + if ( type == IMAGE || type == PREVIEW ) { - var value = parseInt( this.getValue(), 10 ); - value = isNaN( value ) ? 0 : value; - element.setAttribute( 'vspace', this.getValue() ); - element.setStyle( 'margin-top', value + 'px' ); - element.setStyle( 'margin-bottom', value + 'px' ); + if ( value ) + { + element.setStyle( 'margin-top', CKEDITOR.tools.cssLength( value ) ); + element.setStyle( 'margin-bottom', CKEDITOR.tools.cssLength( value ) ); + } + else if ( !value && this.isChanged( ) ) + { + element.removeStyle( 'margin-top' ); + element.removeStyle( 'margin-bottom' ); + } + + if( !internalCommit && type == IMAGE ) + element.removeAttribute( 'vspace' ); } else if ( type == CLEANUP ) { @@ -838,53 +982,65 @@ For licensing, see LICENSE.html or http://ckeditor.com/license [ [ editor.lang.common.notSet , ''], [ editor.lang.image.alignLeft , 'left'], - [ editor.lang.image.alignAbsBottom , 'absBottom'], - [ editor.lang.image.alignAbsMiddle , 'absMiddle'], - [ editor.lang.image.alignBaseline , 'baseline'], - [ editor.lang.image.alignBottom , 'bottom'], - [ editor.lang.image.alignMiddle , 'middle'], - [ editor.lang.image.alignRight , 'right'], - [ editor.lang.image.alignTextTop , 'textTop'], - [ editor.lang.image.alignTop , 'top'] + [ editor.lang.image.alignRight , 'right'] + // Backward compatible with v2 on setup when specified as attribute value, + // while these values are no more available as select options. + // [ editor.lang.image.alignAbsBottom , 'absBottom'], + // [ editor.lang.image.alignAbsMiddle , 'absMiddle'], + // [ editor.lang.image.alignBaseline , 'baseline'], + // [ editor.lang.image.alignTextTop , 'text-top'], + // [ editor.lang.image.alignBottom , 'bottom'], + // [ editor.lang.image.alignMiddle , 'middle'], + // [ editor.lang.image.alignTop , 'top'] ], onChange : function() { updatePreview( this.getDialog() ); + commitInternally.call( this, 'advanced:txtdlgGenStyle' ); }, setup : function( type, element ) { if ( type == IMAGE ) - this.setValue( element.getAttribute( 'align' ) ); + { + var value = element.getStyle( 'float' ); + switch( value ) + { + // Ignore those unrelated values. + case 'inherit': + case 'none': + value = ''; + } + + !value && ( value = ( element.getAttribute( 'align' ) || '' ).toLowerCase() ); + this.setValue( value ); + } }, - commit : function( type, element ) + commit : function( type, element, internalCommit ) { var value = this.getValue(); - if ( type == IMAGE ) - { - if ( value || this.isChanged() ) - element.setAttribute( 'align', value ); - } - else if ( type == PREVIEW ) + if ( type == IMAGE || type == PREVIEW ) { - element.setAttribute( 'align', this.getValue() ); - - if ( value == 'absMiddle' || value == 'middle' ) - element.setStyle( 'vertical-align', 'middle' ); - else if ( value == 'top' || value == 'textTop' ) - element.setStyle( 'vertical-align', 'top' ); - else - element.removeStyle( 'vertical-align' ); - - if ( value == 'right' || value == 'left' ) - element.setStyle( 'styleFloat', value ); - else - element.removeStyle( 'styleFloat' ); + if ( value ) + element.setStyle( 'float', value ); + else if ( !value && this.isChanged( ) ) + element.removeStyle( 'float' ); + if( !internalCommit && type == IMAGE ) + { + value = ( element.getAttribute( 'align' ) || '' ).toLowerCase(); + switch( value ) + { + // we should remove it only if it matches "left" or "right", + // otherwise leave it intact. + case 'left': + case 'right': + element.removeAttribute( 'align' ); + } + } } else if ( type == CLEANUP ) - { - element.removeAttribute( 'align' ); - } + element.removeStyle( 'float' ); + } } ] @@ -903,7 +1059,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license '<div id="ImagePreviewLoader" style="display:none"><div class="loading"> </div></div>'+ '<div id="ImagePreviewBox">'+ '<a href="javascript:void(0)" target="_blank" onclick="return false;" id="previewLink">'+ - '<img id="previewImage" src="" alt="" /></a>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. '+ + '<img id="previewImage" alt="" /></a>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. '+ 'Maecenas feugiat consequat diam. Maecenas metus. Vivamus diam purus, cursus a, commodo non, facilisis vitae, '+ 'nulla. Aenean dictum lacinia tortor. Nunc iaculis, nibh non iaculis aliquam, orci felis euismod neque, sed ornare massa mauris sed velit. Nulla pretium mi et risus. Fusce mi pede, tempor id, cursus ac, ullamcorper nec, enim. Sed tortor. Curabitur molestie. Duis velit augue, condimentum at, ultrices a, luctus ut, orci. Donec pellentesque egestas eros. Integer cursus, augue in cursus faucibus, eros pede bibendum sem, in tempus tellus justo quis ligula. Etiam eget tortor. Vestibulum rutrum, est ut placerat elementum, lectus nisl aliquam velit, tempor aliquam eros nunc nonummy metus. In eros metus, gravida a, gravida sed, lobortis id, turpis. Ut ultrices, ipsum at venenatis fringilla, sem nulla lacinia tellus, eget aliquet turpis mauris non enim. Nam turpis. Suspendisse lacinia. Curabitur ac tortor ut ipsum egestas elementum. Nunc imperdiet gravida mauris.' + '</div>'+'</div>' @@ -1188,40 +1344,20 @@ For licensing, see LICENSE.html or http://ckeditor.com/license }; } }, + onChange : function () + { + commitInternally.call( this, + [ 'info:cmbFloat', 'info:cmbAlign', + 'info:txtVSpace', 'info:txtHSpace', + 'info:txtBorder', + 'info:txtWidth', 'info:txtHeight' ] ); + updatePreview( this ); + }, commit : function( type, element ) { if ( type == IMAGE && ( this.getValue() || this.isChanged() ) ) { element.setAttribute( 'style', this.getValue() ); - - // Set STYLE dimensions. - var height = element.getAttribute( 'height' ), - width = element.getAttribute( 'width' ); - - if ( this.attributesInStyle && this.attributesInStyle.height ) - { - if ( height ) - { - if ( height.match( regexGetSize )[2] == '%' ) // % is allowed - element.setStyle( 'height', height + '%' ); - else - element.setStyle( 'height', height + 'px' ); - } - else - element.removeStyle( 'height' ); - } - if ( this.attributesInStyle && this.attributesInStyle.width ) - { - if ( width ) - { - if ( width.match( regexGetSize )[2] == '%' ) // % is allowed - element.setStyle( 'width', width + '%' ); - else - element.setStyle( 'width', width + 'px' ); - } - else - element.removeStyle( 'width' ); - } } } } diff --git a/_source/plugins/image/plugin.js b/_source/plugins/image/plugin.js index 6b4cbeb..0222b56 100644 --- a/_source/plugins/image/plugin.js +++ b/_source/plugins/image/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/indent/plugin.js b/_source/plugins/indent/plugin.js index be8c75a..a8fe201 100644 --- a/_source/plugins/indent/plugin.js +++ b/_source/plugins/indent/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/justify/plugin.js b/_source/plugins/justify/plugin.js index e65044d..778b6df 100644 --- a/_source/plugins/justify/plugin.js +++ b/_source/plugins/justify/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/keystrokes/plugin.js b/_source/plugins/keystrokes/plugin.js index 092acb2..572313a 100644 --- a/_source/plugins/keystrokes/plugin.js +++ b/_source/plugins/keystrokes/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/link/dialogs/anchor.js b/_source/plugins/link/dialogs/anchor.js index ad670ce..5355307 100644 --- a/_source/plugins/link/dialogs/anchor.js +++ b/_source/plugins/link/dialogs/anchor.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/link/dialogs/link.js b/_source/plugins/link/dialogs/link.js index 8df1de7..18f08ce 100644 --- a/_source/plugins/link/dialogs/link.js +++ b/_source/plugins/link/dialogs/link.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -37,7 +37,8 @@ CKEDITOR.dialog.add( 'link', function( editor ) var dialog = this.getDialog(), partIds = [ 'urlOptions', 'anchorOptions', 'emailOptions' ], typeValue = this.getValue(), - uploadInitiallyHidden = dialog.definition.getContents( 'upload' ).hidden; + uploadTab = dialog.definition.getContents( 'upload' ), + uploadInitiallyHidden = uploadTab && uploadTab.hidden; if ( typeValue == 'url' ) { @@ -72,8 +73,10 @@ CKEDITOR.dialog.add( 'link', function( editor ) emailSubjectRegex = /subject=([^;?:@&=$,\/]*)/, emailBodyRegex = /body=([^;?:@&=$,\/]*)/, anchorRegex = /^#(.*)$/, - urlRegex = /^((?:http|https|ftp|news):\/\/)?(.*)$/, - selectableTargets = /^(_(?:self|top|parent|blank))$/; + urlRegex = /^(?!javascript)((?:http|https|ftp|news):\/\/)?(.*)$/, + selectableTargets = /^(_(?:self|top|parent|blank))$/, + encodedEmailLinkRegex = /^javascript:void\(location\.href='mailto:'\+String\.fromCharCode\(([^)]+)\)(?:\+'(.*)')?\)$/, + functionCallProtectedEmailLinkRegex = /^javascript:([^(]+)\(([^)]+)\)$/; var popupRegex = /\s*window.open\(\s*this\.href\s*,\s*(?:'([^']*)'|null)\s*,\s*'([^']*)'\s*\)\s*;\s*return\s*false;*\s*/; @@ -82,42 +85,80 @@ CKEDITOR.dialog.add( 'link', function( editor ) var parseLink = function( editor, element ) { var href = element ? ( element.getAttribute( '_cke_saved_href' ) || element.getAttribute( 'href' ) ) : '', - emailMatch = '', - anchorMatch = '', - urlMatch = false, + emailMatch, + anchorMatch, + urlMatch, retval = {}; - if ( href ) - { - emailMatch = href.match( emailRegex ); - anchorMatch = href.match( anchorRegex ); - urlMatch = href.match( urlRegex ); - } - - // Load the link type and URL. - if ( emailMatch ) - { - var subjectMatch = href.match( emailSubjectRegex ), - bodyMatch = href.match( emailBodyRegex ); - retval.type = 'email'; - retval.email = {}; - retval.email.address = emailMatch[1]; - subjectMatch && ( retval.email.subject = decodeURIComponent( subjectMatch[1] ) ); - bodyMatch && ( retval.email.body = decodeURIComponent( bodyMatch[1] ) ); - } - else if ( anchorMatch ) + if ( ( anchorMatch = href.match( anchorRegex ) ) ) { retval.type = 'anchor'; retval.anchor = {}; retval.anchor.name = retval.anchor.id = anchorMatch[1]; } - else if ( href && urlMatch ) // urlRegex matches empty strings, so need to check for href as well. + // urlRegex matches empty strings, so need to check for href as well. + else if ( href && ( urlMatch = href.match( urlRegex ) ) ) { retval.type = 'url'; retval.url = {}; retval.url.protocol = urlMatch[1]; retval.url.url = urlMatch[2]; } + // Protected email link as encoded string. + else if ( !emailProtection || emailProtection == 'encode' ) + { + if( emailProtection == 'encode' ) + { + href = href.replace( encodedEmailLinkRegex, + function ( match, protectedAddress, rest ) + { + return 'mailto:' + + String.fromCharCode.apply( String, protectedAddress.split( ',' ) ) + + ( rest && unescapeSingleQuote( rest ) ); + } ); + } + + emailMatch = href.match( emailRegex ); + + if( emailMatch ) + { + var subjectMatch = href.match( emailSubjectRegex ), + bodyMatch = href.match( emailBodyRegex ); + + retval.type = 'email'; + var email = ( retval.email = {} ); + email.address = emailMatch[ 1 ]; + subjectMatch && ( email.subject = decodeURIComponent( subjectMatch[ 1 ] ) ); + bodyMatch && ( email.body = decodeURIComponent( bodyMatch[ 1 ] ) ); + } + } + // Protected email link as function call. + else if( emailProtection ) + { + href.replace( functionCallProtectedEmailLinkRegex, function( match, funcName, funcArgs ) + { + if( funcName == compiledProtectionFunction.name ) + { + retval.type = 'email'; + var email = retval.email = {}; + + var paramRegex = /[^,\s]+/g, + paramQuoteRegex = /(^')|('$)/g, + paramsMatch = funcArgs.match( paramRegex ), + paramsMatchLength = paramsMatch.length, + paramName, + paramVal; + + for ( var i = 0; i < paramsMatchLength; i++ ) + { + paramVal = decodeURIComponent( unescapeSingleQuote( paramsMatch[ i ].replace( paramQuoteRegex, '' ) ) ); + paramName = compiledProtectionFunction.params[ i ].toLowerCase(); + email[ paramName ] = paramVal; + } + email.address = [ email.name, email.domain ].join( '@' ); + } + } ); + } else retval.type = 'url'; @@ -244,6 +285,72 @@ CKEDITOR.dialog.add( 'link', function( editor ) return commitParams.call( this, 'adv', data ); }; + function unescapeSingleQuote( str ) + { + return str.replace( /\\'/g, '\'' ); + } + + function escapeSingleQuote( str ) + { + return str.replace( /'/g, '\\$&' ); + } + + var emailProtection = editor.config.emailProtection || ''; + + // Compile the protection function pattern. + if( emailProtection && emailProtection != 'encode' ) + { + var compiledProtectionFunction = {}; + + emailProtection.replace( /^([^(]+)\(([^)]+)\)$/, function( match, funcName, params ) + { + compiledProtectionFunction.name = funcName; + compiledProtectionFunction.params = []; + params.replace( /[^,\s]+/g, function( param ) + { + compiledProtectionFunction.params.push( param ); + } ); + } ); + } + + function protectEmailLinkAsFunction( email ) + { + var retval, + name = compiledProtectionFunction.name, + params = compiledProtectionFunction.params, + paramName, + paramValue; + + retval = [ name, '(' ]; + for ( var i = 0; i < params.length; i++ ) + { + paramName = params[ i ].toLowerCase(); + paramValue = email[ paramName ]; + + i > 0 && retval.push( ',' ); + retval.push( '\'', + paramValue ? + escapeSingleQuote( encodeURIComponent( email[ paramName ] ) ) + : '', + '\''); + } + retval.push( ')' ); + return retval.join( '' ); + } + + function protectEmailAddressAsEncodedString( address ) + { + var charCode, + length = address.length, + encodedChars = []; + for ( var i = 0; i < length; i++ ) + { + charCode = address.charCodeAt( i ); + encodedChars.push( charCode ); + } + return 'String.fromCharCode(' + encodedChars.join( ',' ) + ')'; + } + return { title : editor.lang.link.title, minWidth : 350, @@ -304,7 +411,7 @@ CKEDITOR.dialog.add( 'link', function( editor ) setup : function( data ) { if ( data.url ) - this.setValue( data.url.protocol ); + this.setValue( data.url.protocol || '' ); }, commit : function( data ) { @@ -1040,7 +1147,8 @@ CKEDITOR.dialog.add( 'link', function( editor ) var attributes = { href : 'javascript:void(0)/*' + CKEDITOR.tools.getNextNumber() + '*/' }, removeAttributes = [], data = { href : attributes.href }, - me = this, editor = this.getParentEditor(); + me = this, + editor = this.getParentEditor(); this.commitContent( data ); @@ -1058,21 +1166,52 @@ CKEDITOR.dialog.add( 'link', function( editor ) attributes._cke_saved_href = '#' + ( name || id || '' ); break; case 'email': - var address = ( data.email && data.email.address ), - subject = ( data.email && encodeURIComponent( data.email.subject || '' ) ), - body = ( data.email && encodeURIComponent( data.email.body || '' ) ), - linkList = [ 'mailto:', address ]; - if ( subject || body ) + + var linkHref, + email = data.email, + address = email.address; + + switch( emailProtection ) { - var argList = []; - linkList.push( '?' ); - subject && argList.push( 'subject=' + subject ); - body && argList.push( 'body=' + body ); - linkList.push( argList.join( '&' ) ); + case '' : + case 'encode' : + { + var subject = encodeURIComponent( email.subject || '' ), + body = encodeURIComponent( email.body || '' ); + + // Build the e-mail parameters first. + var argList = []; + subject && argList.push( 'subject=' + subject ); + body && argList.push( 'body=' + body ); + argList = argList.length ? '?' + argList.join( '&' ) : ''; + + if ( emailProtection == 'encode' ) + { + linkHref = [ 'javascript:void(location.href=\'mailto:\'+', + protectEmailAddressAsEncodedString( address ) ]; + // parameters are optional. + argList && linkHref.push( '+\'', escapeSingleQuote( argList ), '\'' ); + + linkHref.push( ')' ); + } + else + linkHref = [ 'mailto:', address, argList ]; + + break; + } + default : + { + // Separating name and domain. + var nameAndDomain = address.split( '@', 2 ); + email.name = nameAndDomain[ 0 ]; + email.domain = nameAndDomain[ 1 ]; + + linkHref = [ 'javascript:', protectEmailLinkAsFunction( email ) ]; + } } - attributes._cke_saved_href = linkList.join( '' ); + + attributes._cke_saved_href = linkHref.join( '' ); break; - default: } // Popups and target. @@ -1099,12 +1238,15 @@ CKEDITOR.dialog.add( 'link', function( editor ) addFeature( 'top' ); onclickList.push( featureList.join( ',' ), '\'); return false;' ); - attributes[ CKEDITOR.env.ie || CKEDITOR.env.webkit ? '_cke_pa_onclick' : 'onclick' ] = onclickList.join( '' ); + attributes[ '_cke_pa_onclick' ] = onclickList.join( '' ); } else { if ( data.target.type != 'notSet' && data.target.name ) attributes.target = data.target.name; + else + removeAttributes.push( 'target' ); + removeAttributes.push( '_cke_pa_onclick', 'onclick' ); } } @@ -1214,4 +1356,24 @@ CKEDITOR.dialog.add( 'link', function( editor ) } }; -} ); +}); + +/** + * The e-mail address anti-spam protection option. + * @name CKEDITOR.config.emailProtection + * @type {String} + * Two forms of protection could be choosed from : + * 1. The whole address parts ( name, domain with any other query string ) are assembled into a + * function call pattern which invoke you own provided function, with the specified arguments. + * 2. Only the e-mail address is obfuscated into unicode code point sequences, replacement are + * done by a String.fromCharCode() call. + * Note: Both approaches require JavaScript to be enabled. + * @default '' + * @example + * config.emailProtection = ''; + * // href="mailto:tester@ckeditor.com?subject=subject&body=body" + * config.emailProtection = 'encode'; + * // href="<a href=\"javascript:void(location.href=\'mailto:\'+String.fromCharCode(116,101,115,116,101,114,64,99,107,101,100,105,116,111,114,46,99,111,109)+\'?subject=subject&body=body\')\">e-mail</a>" + * config.emailProtection = 'mt(NAME,DOMAIN,SUBJECT,BODY)'; + * // href="javascript:mt('tester','ckeditor.com','subject','body')" + */ diff --git a/_source/plugins/link/plugin.js b/_source/plugins/link/plugin.js index e111fc0..3da5bc7 100644 --- a/_source/plugins/link/plugin.js +++ b/_source/plugins/link/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/list/plugin.js b/_source/plugins/list/plugin.js index 425258a..8c19edd 100644 --- a/_source/plugins/list/plugin.js +++ b/_source/plugins/list/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -356,7 +356,6 @@ For licensing, see LICENSE.html or http://ckeditor.com/license compensateBrs( true ); compensateBrs(); - var rootParent = groupObj.root.getParent(); docFragment.replace( groupObj.root ); } @@ -445,16 +444,18 @@ For licensing, see LICENSE.html or http://ckeditor.com/license while ( ( block = iterator.getNextParagraph() ) ) { var path = new CKEDITOR.dom.elementPath( block ), + pathElements = path.elements, + pathElementsCount = pathElements.length, listNode = null, processedFlag = false, blockLimit = path.blockLimit, element; // First, try to group by a list ancestor. - for ( var i = 0 ; i < path.elements.length && - ( element = path.elements[ i ] ) && !element.equals( blockLimit ); i++ ) + for ( var i = pathElementsCount - 1; i >= 0 && ( element = pathElements[ i ] ); i-- ) { - if ( listNodeNames[ element.getName() ] ) + if ( listNodeNames[ element.getName() ] + && blockLimit.contains( element ) ) // Don't leak outside block limit (#3940). { // If we've encountered a list inside a block limit // The last group object of the block limit element should @@ -538,6 +539,68 @@ For licensing, see LICENSE.html or http://ckeditor.com/license } }; + var dtd = CKEDITOR.dtd; + var tailNbspRegex = /[\t\r\n ]*(?: |\xa0)$/; + + function indexOfFirstChildElement( element, tagNameList ) + { + var child, + children = element.children, + length = children.length; + + for ( var i = 0 ; i < length ; i++ ) + { + child = children[ i ]; + if ( child.name && ( child.name in tagNameList ) ) + return i; + } + + return length; + } + + function getExtendNestedListFilter( isHtmlFilter ) + { + // An element filter function that corrects nested list start in an empty + // list item for better displaying/outputting. (#3165) + return function( listItem ) + { + var children = listItem.children, + firstNestedListIndex = indexOfFirstChildElement( listItem, dtd.$list ), + firstNestedList = children[ firstNestedListIndex ], + nodeBefore = firstNestedList && firstNestedList.previous, + tailNbspmatch; + + if( nodeBefore + && ( nodeBefore.name && nodeBefore.name == 'br' + || nodeBefore.value && ( tailNbspmatch = nodeBefore.value.match( tailNbspRegex ) ) ) ) + { + var fillerNode = nodeBefore; + + // Always use 'nbsp' as filler node if we found a nested list appear + // in front of a list item. + if ( !( tailNbspmatch && tailNbspmatch.index ) && fillerNode == children[ 0 ] ) + children[ 0 ] = ( isHtmlFilter || CKEDITOR.env.ie ) ? + new CKEDITOR.htmlParser.text( '\xa0' ) : + new CKEDITOR.htmlParser.element( 'br', {} ); + + // Otherwise the filler is not needed anymore. + else if ( fillerNode.name == 'br' ) + children.splice( firstNestedListIndex - 1, 1 ); + else + fillerNode.value = fillerNode.value.replace( tailNbspRegex, '' ); + } + + }; + } + + var defaultListDataFilterRules = { elements : {} }; + for( var i in dtd.$listItem ) + defaultListDataFilterRules.elements[ i ] = getExtendNestedListFilter(); + + var defaultListHtmlFilterRules = { elements : {} }; + for( i in dtd.$listItem ) + defaultListHtmlFilterRules.elements[ i ] = getExtendNestedListFilter( true ); + CKEDITOR.plugins.add( 'list', { init : function( editor ) @@ -565,6 +628,16 @@ For licensing, see LICENSE.html or http://ckeditor.com/license editor.on( 'selectionChange', CKEDITOR.tools.bind( onSelectionChange, bulletedListCommand ) ); }, + afterInit : function ( editor ) + { + var dataProcessor = editor.dataProcessor; + if( dataProcessor ) + { + dataProcessor.dataFilter.addRules( defaultListDataFilterRules ); + dataProcessor.htmlFilter.addRules( defaultListHtmlFilterRules ); + } + }, + requires : [ 'domiterator' ] } ); })(); diff --git a/_source/plugins/listblock/plugin.js b/_source/plugins/listblock/plugin.js index f5037bd..dc87481 100644 --- a/_source/plugins/listblock/plugin.js +++ b/_source/plugins/listblock/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/maximize/plugin.js b/_source/plugins/maximize/plugin.js index a81e97a..bd25d5c 100644 --- a/_source/plugins/maximize/plugin.js +++ b/_source/plugins/maximize/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -114,7 +114,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license editorFocus : false, exec : function() { - var container = editor.container.getChild( [ 0, 0 ] ); + var container = editor.container.getChild( 0 ); var contents = editor.getThemeSpace( 'contents' ); // Save current selection and scroll position in editing area. @@ -165,8 +165,10 @@ For licensing, see LICENSE.html or http://ckeditor.com/license } ); } - // Scroll to the top left. - mainWindow.$.scrollTo( 0, 0 ); + // Scroll to the top left (IE needs some time for it - #4923). + CKEDITOR.env.ie ? + setTimeout( function() { mainWindow.$.scrollTo( 0, 0 ); }, 0 ) : + mainWindow.$.scrollTo( 0, 0 ); // Resize and move to top left. var viewPaneSize = mainWindow.getViewPaneSize(); @@ -212,7 +214,9 @@ For licensing, see LICENSE.html or http://ckeditor.com/license } // Restore the window scroll position. - mainWindow.$.scrollTo( outerScroll.x, outerScroll.y ); + CKEDITOR.env.ie ? + setTimeout( function() { mainWindow.$.scrollTo( outerScroll.x, outerScroll.y ); }, 0 ) : + mainWindow.$.scrollTo( outerScroll.x, outerScroll.y ); // Remove cke_maximized class. container.removeClass( 'cke_maximized' ); diff --git a/_source/plugins/menu/plugin.js b/_source/plugins/menu/plugin.js index 16e8443..ccc1b96 100644 --- a/_source/plugins/menu/plugin.js +++ b/_source/plugins/menu/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -67,11 +67,11 @@ CKEDITOR.tools.extend( CKEDITOR.editor.prototype, { var menu = this._.subMenu, item = this.items[ index ], - subItems = item.getItems && item.getItems(); + subItemDefs = item.getItems && item.getItems(); // If this item has no subitems, we just hide the submenu, if // available, and return back. - if ( !subItems ) + if ( !subItemDefs ) { this._.panel.hideChild(); return; @@ -89,9 +89,14 @@ CKEDITOR.tools.extend( CKEDITOR.editor.prototype, } // Add all submenu items to the menu. - for ( var itemName in subItems ) + for ( var subItemName in subItemDefs ) { - menu.add( this.editor.getMenuItem( itemName ) ); + var subItem = this.editor.getMenuItem( subItemName ); + if ( subItem ) + { + subItem.state = subItemDefs[ subItemName ]; + menu.add( subItem ); + } } // Get the element representing the current item. @@ -132,7 +137,7 @@ CKEDITOR.tools.extend( CKEDITOR.editor.prototype, { panel = this._.panel = new CKEDITOR.ui.floatPanel( this.editor, CKEDITOR.document.getBody(), { - css : [ CKEDITOR.getUrl( editor.skinPath + 'editor.css' ) ], + css : editor.skin.editor.css, level : this._.level - 1, className : editor.skinClass + ' cke_contextmenu' }, @@ -376,4 +381,4 @@ CKEDITOR.config.menu_groups = 'form,' + 'tablecell,tablecellproperties,tablerow,tablecolumn,table,'+ 'anchor,link,image,flash,' + - 'checkbox,radio,textfield,hiddenfield,imagebutton,button,select,textarea'; + 'checkbox,radio,textfield,hiddenfield,imagebutton,button,select,textarea,div'; diff --git a/_source/plugins/menubutton/plugin.js b/_source/plugins/menubutton/plugin.js index c533536..6b48775 100644 --- a/_source/plugins/menubutton/plugin.js +++ b/_source/plugins/menubutton/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/newpage/plugin.js b/_source/plugins/newpage/plugin.js index 49fc40f..e10ecb5 100644 --- a/_source/plugins/newpage/plugin.js +++ b/_source/plugins/newpage/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -21,11 +21,16 @@ CKEDITOR.plugins.add( 'newpage', var command = this; editor.setData( editor.config.newpage_html, function() { - editor.fire( 'afterCommandExec', + // Save the undo snapshot after all document changes are affected. (#4889) + setTimeout( function () { - name: command.name, - command: command - } ); + editor.fire( 'afterCommandExec', + { + name: command.name, + command: command + } ); + + }, 200 ); } ); editor.focus(); }, diff --git a/_source/plugins/pagebreak/plugin.js b/_source/plugins/pagebreak/plugin.js index 1543438..4ba39a4 100644 --- a/_source/plugins/pagebreak/plugin.js +++ b/_source/plugins/pagebreak/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -55,7 +55,8 @@ CKEDITOR.plugins.add( 'pagebreak', { div : function( element ) { - var style = element.attributes.style, + var attributes = element.attributes, + style = attributes && attributes.style, child = style && element.children.length == 1 && element.children[ 0 ], childStyle = child && ( child.name == 'span' ) && child.attributes.style; diff --git a/_source/plugins/panel/plugin.js b/_source/plugins/panel/plugin.js index b2d6cbe..ac81edb 100644 --- a/_source/plugins/panel/plugin.js +++ b/_source/plugins/panel/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -160,7 +160,7 @@ CKEDITOR.ui.panel.prototype = // It looks strange, but for FF2, the styles must go // after <body>, so it (body) becames immediatelly // available. (#3031) - '<link type="text/css" rel=stylesheet href="' + this.css.join( '"><link type="text/css" rel="stylesheet" href="' ) + '">' + + CKEDITOR.tools.buildStyleHtml( this.css ) + '<\/html>' ); doc.$.close(); @@ -230,6 +230,11 @@ CKEDITOR.ui.panel.prototype = block.show(); return block; + }, + + destroy : function() + { + this.element && this.element.remove(); } }; diff --git a/_source/plugins/panelbutton/plugin.js b/_source/plugins/panelbutton/plugin.js index 5f3561a..9a3a121 100644 --- a/_source/plugins/panelbutton/plugin.js +++ b/_source/plugins/panelbutton/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/pastefromword/filter/default.js b/_source/plugins/pastefromword/filter/default.js new file mode 100644 index 0000000..16d1e87 --- /dev/null +++ b/_source/plugins/pastefromword/filter/default.js @@ -0,0 +1,1176 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +(function() +{ + var fragmentPrototype = CKEDITOR.htmlParser.fragment.prototype, + elementPrototype = CKEDITOR.htmlParser.element.prototype; + + fragmentPrototype.onlyChild = elementPrototype.onlyChild = function() + { + var children = this.children, + count = children.length, + firstChild = ( count == 1 ) && children[ 0 ]; + return firstChild || null; + }; + + elementPrototype.removeAnyChildWithName = function( tagName ) + { + var children = this.children, + childs = [], + child; + + for ( var i = 0; i < children.length; i++ ) + { + child = children[ i ]; + if( !child.name ) + continue; + + if ( child.name == tagName ) + { + childs.push( child ); + children.splice( i--, 1 ); + } + childs = childs.concat( child.removeAnyChildWithName( tagName ) ); + } + return childs; + }; + + elementPrototype.getAncestor = function( tagNameRegex ) + { + var parent = this.parent; + while ( parent && !( parent.name && parent.name.match( tagNameRegex ) ) ) + parent = parent.parent; + return parent; + }; + + fragmentPrototype.firstChild = elementPrototype.firstChild = function( evaluator ) + { + var child; + + for ( var i = 0 ; i < this.children.length ; i++ ) + { + child = this.children[ i ]; + if ( evaluator( child ) ) + return child; + else if ( child.name ) + { + child = child.firstChild( evaluator ); + if ( child ) + return child; + else + continue; + } + } + + return null; + }; + + // Adding a (set) of styles to the element's attributes. + elementPrototype.addStyle = function( name, value, isPrepend ) + { + var styleText, addingStyleText = ''; + // name/value pair. + if ( typeof value == 'string' ) + addingStyleText += name + ':' + value + ';'; + else + { + // style literal. + if ( typeof name == 'object' ) + { + for( var style in name ) + { + if( name.hasOwnProperty( style ) ) + addingStyleText += style + ':' + name[ style ] + ';'; + } + } + // raw style text form. + else + addingStyleText += name; + + isPrepend = value; + } + + if( !this.attributes ) + this.attributes = {}; + + styleText = this.attributes.style || ''; + + styleText = ( isPrepend ? + [ addingStyleText, styleText ] + : [ styleText, addingStyleText ] ).join( ';' ); + + this.attributes.style = styleText.replace( /^;|;(?=;)/, '' ); + }; + + /** + * Return the DTD-valid parent tag names of the specified one. + * @param tagName + */ + CKEDITOR.dtd.parentOf = function( tagName ) + { + var result = {}; + for ( var tag in this ) + { + if ( tag.indexOf( '$' ) == -1 && this[ tag ][ tagName ] ) + result[ tag ] = 1; + } + return result; + }; + + var cssLengthRelativeUnit = /^(\d[.\d]*)+(em|ex|px|gd|rem|vw|vh|vm|ch|mm|cm|in|pt|pc|deg|rad|ms|s|hz|khz){1}?/i; + var emptyMarginRegex = /^(?:\b0[^\s]*\s*){1,4}$/; + + var listBaseIndent = 0, + previousListItemMargin; + + CKEDITOR.plugins.pastefromword = + { + utils : + { + // Create a <cke:listbullet> which indicate an list item type. + createListBulletMarker : function ( bulletStyle, bulletText ) + { + var marker = new CKEDITOR.htmlParser.element( 'cke:listbullet' ), + listType; + + // TODO: Support more list style type from MS-Word. + if ( !bulletStyle ) + { + bulletStyle = 'decimal'; + listType = 'ol'; + } + else if ( bulletStyle[ 2 ] ) + { + if ( !isNaN( bulletStyle[ 1 ] ) ) + bulletStyle = 'decimal'; + // No way to distinguish between Roman numerals and Alphas, + // detect them as a whole. + else if ( /^[a-z]+$/.test( bulletStyle[ 1 ] ) ) + bulletStyle = 'lower-alpha'; + else if ( /^[A-Z]+$/.test( bulletStyle[ 1 ] ) ) + bulletStyle = 'upper-alpha'; + // Simply use decimal for the rest forms of unrepresentable + // numerals, e.g. Chinese... + else + bulletStyle = 'decimal'; + + listType = 'ol'; + } + else + { + if ( /[l\u00B7\u2002]/.test( bulletStyle[ 1 ] ) ) //l·• + bulletStyle = 'disc'; + else if ( /[\u006F\u00D8]/.test( bulletStyle[ 1 ] ) ) //oØ + bulletStyle = 'circle'; + else if ( /[\u006E\u25C6]/.test( bulletStyle[ 1 ] ) ) //n◆ + bulletStyle = 'square'; + else + bulletStyle = 'disc'; + + listType = 'ul'; + } + + // Represent list type as CSS style. + marker.attributes = + { + 'cke:listtype' : listType, + 'style' : 'list-style-type:' + bulletStyle + ';' + }; + marker.add( new CKEDITOR.htmlParser.text( bulletText ) ); + return marker; + }, + + isListBulletIndicator : function( element ) + { + var styleText = element.attributes && element.attributes.style; + if( /mso-list\s*:\s*Ignore/i.test( styleText ) ) + return true; + }, + + isContainingOnlySpaces : function( element ) + { + var text; + return ( ( text = element.onlyChild() ) + && ( /^(:?\s| )+$/ ).test( text.value ) ); + }, + + resolveList : function( element ) + { + // <cke:listbullet> indicate a list item. + var children = element.children, + attrs = element.attributes, + listMarker; + + if ( ( listMarker = element.removeAnyChildWithName( 'cke:listbullet' ) ) + && listMarker.length + && ( listMarker = listMarker[ 0 ] ) ) + { + element.name = 'cke:li'; + + if ( attrs.style ) + { + attrs.style = CKEDITOR.plugins.pastefromword.filters.stylesFilter( + [ + // Text-indent is not representing list item level any more. + [ 'text-indent' ], + [ 'line-height' ], + // Resolve indent level from 'margin-left' value. + [ ( /^margin(:?-left)?$/ ), null, function( margin ) + { + // Be able to deal with component/short-hand form style. + var values = margin.split( ' ' ); + margin = values[ 3 ] || values[ 1 ] || values [ 0 ]; + margin = parseInt( margin, 10 ); + + // Figure out the indent unit by looking at the first increament. + if ( !listBaseIndent && previousListItemMargin && margin > previousListItemMargin ) + listBaseIndent = margin - previousListItemMargin; + + attrs[ 'cke:margin' ] = previousListItemMargin = margin; + } ] + ] )( attrs.style, element ) || '' ; + } + + // Inherit list-type-style from bullet. + var listBulletAttrs = listMarker.attributes, + listBulletStyle = listBulletAttrs.style; + + element.addStyle( listBulletStyle ); + CKEDITOR.tools.extend( attrs, listBulletAttrs ); + return true; + } + + return false; + }, + + // Convert various length units to 'px' in ignorance of DPI. + convertToPx : ( function () + { + var calculator = CKEDITOR.dom.element.createFromHtml( + '<div style="position:absolute;left:-9999px;' + + 'top:-9999px;margin:0px;padding:0px;border:0px;"' + + '></div>', CKEDITOR.document ); + CKEDITOR.document.getBody().append( calculator ); + + return function( cssLength ) + { + if( cssLengthRelativeUnit.test( cssLength ) ) + { + calculator.setStyle( 'width', cssLength ); + return calculator.$.clientWidth + 'px'; + } + + return cssLength; + }; + } )(), + + // Providing a shorthand style then retrieve one or more style component values. + getStyleComponents : ( function() + { + var calculator = CKEDITOR.dom.element.createFromHtml( + '<div style="position:absolute;left:-9999px;top:-9999px;"></div>', + CKEDITOR.document ); + CKEDITOR.document.getBody().append( calculator ); + + return function( name, styleValue, fetchList ) + { + calculator.setStyle( name, styleValue ); + var styles = {}, + count = fetchList.length; + for ( var i = 0; i < count; i++ ) + styles[ fetchList[ i ] ] = calculator.getStyle( fetchList[ i ] ); + + return styles; + }; + } )(), + + listDtdParents : CKEDITOR.dtd.parentOf( 'ol' ) + }, + + filters : + { + // Transform a normal list into flat list items only presentation. + // E.g. <ul><li>level1<ol><li>level2</li></ol></li> => + // <cke:li cke:listtype="ul" cke:indent="1">level1</cke:li> + // <cke:li cke:listtype="ol" cke:indent="2">level2</cke:li> + flattenList : function( element ) + { + var attrs = element.attributes, + parent = element.parent; + + var listStyleType, + indentLevel = 1; + + // Resolve how many level nested. + while ( parent ) + { + parent.attributes && parent.attributes[ 'cke:list'] && indentLevel++; + parent = parent.parent; + } + + // All list items are of the same type. + switch ( attrs.type ) + { + case 'a' : + listStyleType = 'lower-alpha'; + break; + // TODO: Support more list style type from MS-Word. + } + + var children = element.children, + child; + + for ( var i = 0; i < children.length; i++ ) + { + child = children[ i ]; + var attributes = child.attributes; + + if( child.name in CKEDITOR.dtd.$listItem ) + { + var listItemChildren = child.children, + count = listItemChildren.length, + last = listItemChildren[ count - 1 ]; + + // Move out nested list. + if( last.name in CKEDITOR.dtd.$list ) + { + children.splice( i + 1, 0, last ); + last.parent = element; + + // Remove the parent list item if it's just a holder. + if ( !--listItemChildren.length ) + children.splice( i, 1 ); + } + + child.name = 'cke:li'; + attributes[ 'cke:indent' ] = indentLevel; + previousListItemMargin = 0; + attributes[ 'cke:listtype' ] = element.name; + listStyleType && child.addStyle( 'list-style-type', listStyleType, true ); + } + } + + delete element.name; + + // We're loosing tag name here, signalize this element as a list. + attrs[ 'cke:list' ] = 1; + }, + + /** + * Try to collect all list items among the children and establish one + * or more HTML list structures for them. + * @param element + */ + assembleList : function( element ) + { + var children = element.children, child, + listItem, // The current processing cke:li element. + listItemAttrs, + listType, // Determine the root type of the list. + listItemIndent, // Indent level of current list item. + lastListItem, // The previous one just been added to the list. + list, parentList, // Current staging list and it's parent list if any. + indent; + + for ( var i = 0; i < children.length; i++ ) + { + child = children[ i ]; + + if ( 'cke:li' == child.name ) + { + child.name = 'li'; + listItem = child; + listItemAttrs = listItem.attributes; + listType = listItem.attributes[ 'cke:listtype' ]; + + // List item indent level might come from a real list indentation or + // been resolved from a pseudo list item's margin value, even get + // no indentation at all. + listItemIndent = parseInt( listItemAttrs[ 'cke:indent' ], 10 ) + || listBaseIndent && ( Math.ceil( listItemAttrs[ 'cke:margin' ] / listBaseIndent ) ) + || 1; + + // Ignore the 'list-style-type' attribute if it's matched with + // the list root element's default style type. + listItemAttrs.style && ( listItemAttrs.style = + CKEDITOR.plugins.pastefromword.filters.stylesFilter( + [ + [ 'list-style-type', listType == 'ol' ? 'decimal' : 'disc' ] + ] )( listItemAttrs.style ) + || '' ); + + if ( !list ) + { + list = new CKEDITOR.htmlParser.element( listType ); + list.add( listItem ); + children[ i ] = list; + } + else + { + if ( listItemIndent > indent ) + { + list = new CKEDITOR.htmlParser.element( listType ); + list.add( listItem ); + lastListItem.add( list ); + } + else if ( listItemIndent < indent ) + { + // There might be a negative gap between two list levels. (#4944) + var diff = indent - listItemIndent, + parent = list.parent; + while( diff-- && parent ) + list = parent.parent; + + list.add( listItem ); + } + else + list.add( listItem ); + + children.splice( i--, 1 ); + } + + lastListItem = listItem; + indent = listItemIndent; + } + else + list = null; + } + + listBaseIndent = 0; + }, + + /** + * A simple filter which always rejecting. + */ + falsyFilter : function( value ) + { + return false; + }, + + /** + * A filter dedicated on the 'style' attribute filtering, e.g. dropping/replacing style properties. + * @param styles {Array} in form of [ styleNameRegexp, styleValueRegexp, + * newStyleValue/newStyleGenerator, newStyleName ] where only the first + * parameter is mandatory. + * @param whitelist {Boolean} Whether the {@param styles} will be considered as a white-list. + */ + stylesFilter : function( styles, whitelist ) + { + return function( styleText, element ) + { + var rules = []; + // html-encoded quote might be introduced by 'font-family' + // from MS-Word which confused the following regexp. e.g. + //'font-family: "Lucida, Console"' + styleText + .replace( /"/g, '"' ) + .replace( /\s*([^ :;]+)\s*:\s*([^;]+)\s*(?=;|$)/g, + function( match, name, value ) + { + name = name.toLowerCase(); + name == 'font-family' && ( value = value.replace( /["']/g, '' ) ); + + var namePattern, + valuePattern, + newValue, + newName; + for( var i = 0 ; i < styles.length; i++ ) + { + if( styles[ i ] ) + { + namePattern = styles[ i ][ 0 ]; + valuePattern = styles[ i ][ 1 ]; + newValue = styles[ i ][ 2 ]; + newName = styles[ i ][ 3 ]; + + if ( name.match( namePattern ) + && ( !valuePattern || value.match( valuePattern ) ) ) + { + name = newName || name; + whitelist && ( newValue = newValue || value ); + + if( typeof newValue == 'function' ) + newValue = newValue( value, element, name ); + + // Return an couple indicate both name and value + // changed. + if( newValue && newValue.push ) + name = newValue[ 0 ], newValue = newValue[ 1 ]; + + if( typeof newValue == 'string' ) + rules.push( [ name, newValue ] ); + return; + } + } + } + + !whitelist && rules.push( [ name, value ] ); + + }); + + for ( var i = 0 ; i < rules.length ; i++ ) + rules[ i ] = rules[ i ].join( ':' ); + return rules.length ? + ( rules.join( ';' ) + ';' ) : false; + }; + }, + + /** + * Migrate the element by decorate styles on it. + * @param styleDefiniton + * @param variables + */ + elementMigrateFilter : function ( styleDefiniton, variables ) + { + return function( element ) + { + var styleDef = + variables ? + new CKEDITOR.style( styleDefiniton, variables )._.definition + : styleDefiniton; + element.name = styleDef.element; + CKEDITOR.tools.extend( element.attributes, CKEDITOR.tools.clone( styleDef.attributes ) ); + element.addStyle( CKEDITOR.style.getStyleText( styleDef ) ); + }; + }, + + /** + * Migrate styles by creating a new nested stylish element. + * @param styleDefinition + */ + styleMigrateFilter : function( styleDefinition, variableName ) + { + + var elementMigrateFilter = this.elementMigrateFilter; + return function( value, element ) + { + // Build an stylish element first. + var styleElement = new CKEDITOR.htmlParser.element( null ), + variables = {}; + + variables[ variableName ] = value; + elementMigrateFilter( styleDefinition, variables )( styleElement ); + // Place the new element inside the existing span. + styleElement.children = element.children; + element.children = [ styleElement ]; + }; + }, + + /** + * A filter which remove cke-namespaced-attribute on + * all none-cke-namespaced elements. + * @param value + * @param element + */ + bogusAttrFilter : function( value, element ) + { + if( element.name.indexOf( 'cke:' ) == -1 ) + return false; + }, + + /** + * A filter which will be used to apply inline css style according the stylesheet + * definition rules, is generated lazily when filtering. + */ + applyStyleFilter : null + + }, + + getRules : function( editor ) + { + var dtd = CKEDITOR.dtd, + blockLike = CKEDITOR.tools.extend( {}, dtd.$block, dtd.$listItem, dtd.$tableContent ), + config = editor.config, + filters = this.filters, + falsyFilter = filters.falsyFilter, + stylesFilter = filters.stylesFilter, + elementMigrateFilter = filters.elementMigrateFilter, + styleMigrateFilter = CKEDITOR.tools.bind( this.filters.styleMigrateFilter, this.filters ), + bogusAttrFilter = filters.bogusAttrFilter, + createListBulletMarker = this.utils.createListBulletMarker, + flattenList = filters.flattenList, + assembleList = filters.assembleList, + isListBulletIndicator = this.utils.isListBulletIndicator, + containsNothingButSpaces = this.utils.isContainingOnlySpaces, + resolveListItem = this.utils.resolveList, + convertToPx = this.utils.convertToPx, + getStyleComponents = this.utils.getStyleComponents, + listDtdParents = this.utils.listDtdParents, + removeFontStyles = config.pasteFromWordRemoveFontStyles !== false, + removeStyles = config.pasteFromWordRemoveStyles !== false; + + return { + + elementNames : + [ + // Remove script, meta and link elements. + [ ( /meta|link|script/ ), '' ] + ], + + root : function( element ) + { + element.filterChildren(); + assembleList( element ); + }, + + elements : + { + '^' : function( element ) + { + // Transform CSS style declaration to inline style. + var applyStyleFilter; + if ( CKEDITOR.env.gecko && ( applyStyleFilter = filters.applyStyleFilter ) ) + applyStyleFilter( element ); + }, + + $ : function( element ) + { + var tagName = element.name || '', + attrs = element.attributes; + + // Convert length unit of width/height on blocks to + // a more editor-friendly way (px). + if ( tagName in blockLike + && attrs.style ) + { + attrs.style = stylesFilter( + [ [ ( /^(:?width|height)$/ ), null, convertToPx ] ] )( attrs.style ) || ''; + } + + // Processing headings. + if ( tagName.match( /h\d/ ) ) + { + element.filterChildren(); + // Is the heading actually a list item? + if( resolveListItem( element ) ) + return; + + // Adapt heading styles to editor's convention. + elementMigrateFilter( config[ 'format_' + tagName ] )( element ); + } + // Remove inline elements which contain only empty spaces. + else if ( tagName in dtd.$inline ) + { + element.filterChildren(); + if ( containsNothingButSpaces( element ) ) + delete element.name; + } + // Remove element with ms-office namespace, + // with it's content preserved, e.g. 'o:p'. + else if ( tagName.indexOf( ':' ) != -1 + && tagName.indexOf( 'cke' ) == -1 ) + { + element.filterChildren(); + + // Restore image real link from vml. + if ( tagName == 'v:imagedata' ) + { + var href = element.attributes[ 'o:href' ]; + if ( href ) + element.attributes.src = href; + element.name = 'img'; + return; + } + delete element.name; + } + + // Assembling list items into a whole list. + if ( tagName in listDtdParents ) + { + element.filterChildren(); + assembleList( element ); + } + }, + + // We'll drop any style sheet, but Firefox conclude + // certain styles in a single style element, which are + // required to be changed into inline ones. + 'style' : function( element ) + { + if ( CKEDITOR.env.gecko ) + { + // Grab only the style definition section. + var styleDefSection = element.onlyChild().value.match( /\/\* Style Definitions \*\/([\s\S]*?)\/\*/ ), + styleDefText = styleDefSection && styleDefSection[ 1 ], + rules = {}; // Storing the parsed result. + + if ( styleDefText ) + { + styleDefText + // Remove line-breaks. + .replace(/[\n\r]/g,'') + // Extract selectors and style properties. + .replace( /(.+?)\{(.+?)\}/g, + function( rule, selectors, styleBlock ) + { + selectors = selectors.split( ',' ); + var length = selectors.length, selector; + for ( var i = 0; i < length; i++ ) + { + // Assume MS-Word mostly generate only simple + // selector( [Type selector][Class selector]). + CKEDITOR.tools.trim( selectors[ i ] ) + .replace( /^(\w+)(\.[\w-]+)?$/g, + function( match, tagName, className ) + { + tagName = tagName || '*'; + className = className.substring( 1, className.length ); + + // Reject MS-Word Normal styles. + if( className.match( /MsoNormal/ ) ) + return; + + if( !rules[ tagName ] ) + rules[ tagName ] = {}; + if( className ) + rules[ tagName ][ className ] = styleBlock; + else + rules[ tagName ] = styleBlock; + } ); + } + }); + + filters.applyStyleFilter = function( element ) + { + var name = rules[ '*' ] ? '*' : element.name, + className = element.attributes && element.attributes[ 'class' ], + style; + if( name in rules ) + { + style = rules[ name ]; + if( typeof style == 'object' ) + style = style[ className ]; + // Maintain style rules priorities. + style && element.addStyle( style, true ); + } + }; + } + } + return false; + }, + + 'p' : function( element ) + { + element.filterChildren(); + + var attrs = element.attributes, + parent = element.parent, + children = element.children; + + // Is the paragraph actually a list item? + if ( resolveListItem( element ) ) + return; + + // Adapt paragraph formatting to editor's convention + // according to enter-mode. + if ( config.enterMode == CKEDITOR.ENTER_BR ) + { + // We suffer from attribute/style lost in this situation. + delete element.name; + element.add( new CKEDITOR.htmlParser.element( 'br' ) ); + } + else + elementMigrateFilter( config[ 'format_' + ( config.enterMode == CKEDITOR.ENTER_P ? 'p' : 'div' ) ] )( element ); + }, + + 'div' : function( element ) + { + // Aligned table with no text surrounded is represented by a wrapper div, from which + // table cells inherit as text-align styles, which is wrong. + // Instead we use a clear-float div after the table to properly achieve the same layout. + var singleChild = element.onlyChild(); + if( singleChild && singleChild.name == 'table' ) + { + var attrs = element.attributes; + singleChild.attributes = CKEDITOR.tools.extend( singleChild.attributes, attrs ); + attrs.style && singleChild.addStyle( attrs.style ); + + var clearFloatDiv = new CKEDITOR.htmlParser.element( 'div' ); + clearFloatDiv.addStyle( 'clear' ,'both' ); + element.add( clearFloatDiv ); + delete element.name; + } + }, + + 'td' : function ( element ) + { + // 'td' in 'thead' is actually <th>. + if ( element.getAncestor( 'thead') ) + element.name = 'th'; + }, + + // MS-Word sometimes present list as a mixing of normal list + // and pseudo-list, normalize the previous ones into pseudo form. + 'ol' : flattenList, + 'ul' : flattenList, + 'dl' : flattenList, + + 'font' : function( element ) + { + // IE/Safari: drop the font tag if it comes from list bullet text. + if ( !CKEDITOR.env.gecko && isListBulletIndicator( element.parent ) ) + { + delete element.name; + return; + } + + element.filterChildren(); + + var attrs = element.attributes, + styleText = attrs.style, + parent = element.parent; + + if ( 'font' == parent.name ) // Merge nested <font> tags. + { + CKEDITOR.tools.extend( parent.attributes, + element.attributes ); + styleText && parent.addStyle( styleText ); + delete element.name; + return; + } + // Convert the merged into a span with all attributes preserved. + else + { + styleText = styleText || ''; + // IE's having those deprecated attributes, normalize them. + if ( attrs.color ) + { + attrs.color != '#000000' && ( styleText += 'color:' + attrs.color + ';' ); + delete attrs.color; + } + if ( attrs.face ) + { + styleText += 'font-family:' + attrs.face + ';'; + delete attrs.face; + } + // TODO: Mapping size in ranges of xx-small, + // x-small, small, medium, large, x-large, xx-large. + if ( attrs.size ) + { + styleText += 'font-size:' + + ( attrs.size > 3 ? 'large' + : ( attrs.size < 3 ? 'small' : 'medium' ) ) + ';'; + delete attrs.size; + } + + element.name = 'span'; + element.addStyle( styleText ); + } + }, + + 'span' : function( element ) + { + // IE/Safari: remove the span if it comes from list bullet text. + if ( !CKEDITOR.env.gecko && isListBulletIndicator( element.parent ) ) + return false; + + element.filterChildren(); + if ( containsNothingButSpaces( element ) ) + { + delete element.name; + return null; + } + + // For IE/Safari: List item bullet type is supposed to be indicated by + // the text of a span with style 'mso-list : Ignore' or an image. + if ( !CKEDITOR.env.gecko && isListBulletIndicator( element ) ) + { + var listSymbolNode = element.firstChild( function( node ) + { + return node.value || node.name == 'img'; + }); + + var listSymbol = listSymbolNode && ( listSymbolNode.value || 'l.' ), + listType = listSymbol.match( /^([^\s]+?)([.)]?)$/ ); + return createListBulletMarker( listType, listSymbol ); + } + + // Update the src attribute of image element with href. + var children = element.children, + attrs = element.attributes, + styleText = attrs && attrs.style, + firstChild = children && children[ 0 ]; + + // Assume MS-Word mostly carry font related styles on <span>, + // adapting them to editor's convention. + if ( styleText ) + { + attrs.style = stylesFilter( + [ + // Drop 'inline-height' style which make lines overlapping. + [ 'line-height' ], + [ ( /^font-family$/ ), null, !removeFontStyles ? styleMigrateFilter( config[ 'font_style' ], 'family' ) : null ] , + [ ( /^font-size$/ ), null, !removeFontStyles ? styleMigrateFilter( config[ 'fontSize_style' ], 'size' ) : null ] , + [ ( /^color$/ ), null, !removeFontStyles ? styleMigrateFilter( config[ 'colorButton_foreStyle' ], 'color' ) : null ] , + [ ( /^background-color$/ ), null, !removeFontStyles ? styleMigrateFilter( config[ 'colorButton_backStyle' ], 'color' ) : null ] + ] )( styleText, element ) || ''; + } + + return null; + }, + + // Migrate basic style formats to editor configured ones. + 'b' : elementMigrateFilter( config[ 'coreStyles_bold' ] ), + 'i' : elementMigrateFilter( config[ 'coreStyles_italic' ] ), + 'u' : elementMigrateFilter( config[ 'coreStyles_underline' ] ), + 's' : elementMigrateFilter( config[ 'coreStyles_strike' ] ), + 'sup' : elementMigrateFilter( config[ 'coreStyles_superscript' ] ), + 'sub' : elementMigrateFilter( config[ 'coreStyles_subscript' ] ), + // Editor doesn't support anchor with content currently (#3582), + // drop such anchors with content preserved. + 'a' : function( element ) + { + var attrs = element.attributes; + if( attrs && !attrs.href && attrs.name ) + delete element.name; + }, + 'cke:listbullet' : function( element ) + { + if ( element.getAncestor( /h\d/ ) && !config.pasteFromWordNumberedHeadingToList ) + delete element.name; + } + }, + + attributeNames : + [ + // Remove onmouseover and onmouseout events (from MS Word comments effect) + [ ( /^onmouse(:?out|over)/ ), '' ], + // Onload on image element. + [ ( /^onload$/ ), '' ], + // Remove office and vml attribute from elements. + [ ( /(?:v|o):\w+/ ), '' ], + // Remove lang/language attributes. + [ ( /^lang/ ), '' ] + ], + + attributes : + { + 'style' : stylesFilter( + removeStyles ? + // Provide a white-list of styles that we preserve, those should + // be the ones that could later be altered with editor tools. + [ + // Preserve margin-left/right which used as default indent style in the editor. + [ ( /^margin$|^margin-(?!bottom|top)/ ), null, function( value, element, name ) + { + if ( element.name in { p : 1, div : 1 } ) + { + var indentStyleName = config.contentsLangDirection == 'ltr' ? + 'margin-left' : 'margin-right'; + + // Extract component value from 'margin' shorthand. + if ( name == 'margin' ) + { + value = getStyleComponents( name, value, + [ indentStyleName ] )[ indentStyleName ]; + } + else if ( name != indentStyleName ) + return null; + + if ( value && !emptyMarginRegex.test( value ) ) + return [ indentStyleName, value ]; + } + + return null; + } ], + + // Preserve clear float style. + [ ( /^clear$/ ) ], + + [ ( /^border.*|margin.*|vertical-align|float$/ ), null, + function( value, element ) + { + if( element.name == 'img' ) + return value; + } ], + + [ (/^width|height$/ ), null, + function( value, element ) + { + if( element.name in { table : 1, td : 1, th : 1, img : 1 } ) + return value; + } ] + ] : + // Otherwise provide a black-list of styles that we remove. + [ + [ ( /^mso-/ ) ], + // Fixing color values. + [ ( /-color$/ ), null, function( value ) + { + if( value == 'transparent' ) + return false; + if( CKEDITOR.env.gecko ) + return value.replace( /-moz-use-text-color/g, 'transparent' ); + } ], + // Remove empty margin values, e.g. 0.00001pt 0em 0pt + [ ( /^margin$/ ), emptyMarginRegex ], + [ 'text-indent', '0cm' ], + [ 'page-break-before' ], + [ 'tab-stops' ], + [ 'display', 'none' ], + removeFontStyles ? [ ( /font-?/ ) ] : null + ], removeStyles ), + + // Prefer width styles over 'width' attributes. + 'width' : function( value, element ) + { + if( element.name in dtd.$tableContent ) + return false; + }, + // Prefer border styles over table 'border' attributes. + 'border' : function( value, element ) + { + if( element.name in dtd.$tableContent ) + return false; + }, + + // Only Firefox carry style sheet from MS-Word, which + // will be applied by us manually. For other browsers + // the css className is useless. + 'class' : falsyFilter, + + // MS-Word always generate 'background-color' along with 'bgcolor', + // simply drop the deprecated attributes. + 'bgcolor' : falsyFilter, + + // Deprecate 'valign' attribute in favor of 'vertical-align'. + 'valign' : removeStyles ? falsyFilter : function( value, element ) + { + element.addStyle( 'vertical-align', value ); + return false; + } + }, + + // Fore none-IE, some useful data might be buried under these IE-conditional + // comments where RegExp were the right approach to dig them out where usual approach + // is transform it into a fake element node which hold the desired data. + comment : + !CKEDITOR.env.ie ? + function( value, node ) + { + var imageInfo = value.match( /<img.*?>/ ), + listInfo = value.match( /^\[if !supportLists\]([\s\S]*?)\[endif\]$/ ); + + // Seek for list bullet indicator. + if ( listInfo ) + { + // Bullet symbol could be either text or an image. + var listSymbol = listInfo[ 1 ] || ( imageInfo && 'l.' ), + listType = listSymbol && listSymbol.match( />([^\s]+?)([.)]?)</ ); + return createListBulletMarker( listType, listSymbol ); + } + + // Reveal the <img> element in conditional comments for Firefox. + if( CKEDITOR.env.gecko && imageInfo ) + { + var img = CKEDITOR.htmlParser.fragment.fromHtml( imageInfo[ 0 ] ).children[ 0 ], + previousComment = node.previous, + // Try to dig the real image link from vml markup from previous comment text. + imgSrcInfo = previousComment && previousComment.value.match( /<v:imagedata[^>]*o:href=['"](.*?)['"]/ ), + imgSrc = imgSrcInfo && imgSrcInfo[ 1 ]; + + // Is there a real 'src' url to be used? + imgSrc && ( img.attributes.src = imgSrc ); + return img; + } + + return false; + } + : falsyFilter + }; + } + }; + + // The paste processor here is just a reduced copy of html data processor. + var pasteProcessor = function() + { + this.dataFilter = new CKEDITOR.htmlParser.filter(); + }; + + pasteProcessor.prototype = + { + toHtml : function( data ) + { + var fragment = CKEDITOR.htmlParser.fragment.fromHtml( data, false ), + writer = new CKEDITOR.htmlParser.basicWriter(); + + fragment.writeHtml( writer, this.dataFilter ); + return writer.getHtml( true ); + } + }; + + CKEDITOR.cleanWord = function( data, editor ) + { + // Firefox will be confused by those downlevel-revealed IE conditional + // comments, fixing them first( convert it to upperlevel-revealed one ). + // e.g. <![if !vml]>...<![endif]> + if( CKEDITOR.env.gecko ) + data = data.replace( /(<!--\[if[^<]*?\])-->([\S\s]*?)<!--(\[endif\]-->)/gi, '$1$2$3' ); + + var dataProcessor = new pasteProcessor(), + dataFilter = dataProcessor.dataFilter; + + // These rules will have higher priorities than default ones. + dataFilter.addRules( CKEDITOR.plugins.pastefromword.getRules( editor ) ); + + // Allow extending data filter rules. + editor.fire( 'beforeCleanWord', { filter : dataFilter } ); + + try + { + data = dataProcessor.toHtml( data, false ); + } + catch ( e ) + { + alert( editor.lang.pastefromword.error ); + } + + /* Below post processing those things that are unable to delivered by filter rules. */ + + // Remove 'cke' namespaced attribute used in filter rules as marker. + data = data.replace( /cke:.*?".*?"/g, '' ); + + // Remove empty style attribute. + data = data.replace( /style=""/g, '' ); + + // Remove the dummy spans ( having no inline style ). + data = data.replace( /<span>/g, '' ); + + return data; + }; +})(); + +/** + * Whether the ignore all font-related format styles, including: + * - font size; + * - font family; + * - font fore/background color; + * @name CKEDITOR.config.pasteFromWordRemoveFontStyles + * @type Boolean + * @default true + * @example + * config.pasteFromWordRemoveFontStyles = false; + */ + +/** + * Whether transform MS-Word Outline Numbered Heading into html list. + * @name CKEDITOR.config.pasteFromWordNumberedHeadingToList + * @type Boolean + * @default false + * @example + * config.pasteFromWordNumberedHeadingToList = true; + */ + +/** + * Whether remove element styles that can't be managed with editor, note that this + * this doesn't handle the font-specific styles, which depends on + * how {@link CKEDITOR.config.pasteFromWordRemoveFontStyles} is configured. + * @name CKEDITOR.config.pasteFromWordRemoveStyles + * @type Boolean + * @default true + * @example + * config.pasteFromWordRemoveStyles = false; + */ diff --git a/_source/plugins/pastefromword/plugin.js b/_source/plugins/pastefromword/plugin.js index 6f446d9..913b785 100644 --- a/_source/plugins/pastefromword/plugin.js +++ b/_source/plugins/pastefromword/plugin.js @@ -1,54 +1,116 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ - -CKEDITOR.plugins.add( 'pastefromword', +(function() { - init : function( editor ) + CKEDITOR.plugins.add( 'pastefromword', { - // Register the command. - editor.addCommand( 'pastefromword', new CKEDITOR.dialogCommand( 'pastefromword' ) ); + init : function( editor ) + { + + // Flag indicate this command is actually been asked instead of a generic + // pasting. + var forceFromWord = 0; + var resetFromWord = function() + { + setTimeout( function() { forceFromWord = 0; }, 0 ); + }; - // Register the toolbar button. - editor.ui.addButton( 'PasteFromWord', + // Features bring by this command beside the normal process: + // 1. No more bothering of user about the clean-up. + // 2. Perform the clean-up even if content is not from MS-Word. + // (e.g. from a MS-Word similar application.) + editor.addCommand( 'pastefromword', { - label : editor.lang.pastefromword.toolbar, - command : 'pastefromword' - } ); + canUndo : false, + exec : function() + { + forceFromWord = 1; + if( editor.execCommand( 'paste' ) === false ) + { + editor.on( 'dialogHide', function ( evt ) + { + evt.removeListener(); + resetFromWord(); + }); + } + } + }); - // Register the dialog. - CKEDITOR.dialog.add( 'pastefromword', this.path + 'dialogs/pastefromword.js' ); - } -} ); + // Register the toolbar button. + editor.ui.addButton( 'PasteFromWord', + { + label : editor.lang.pastefromword.toolbar, + command : 'pastefromword' + }); -/** - * Whether the "Ignore font face definitions" checkbox is enabled by default in - * the Paste from Word dialog. - * @type Boolean - * @default true - * @example - * config.pasteFromWordIgnoreFontFace = false; - */ -CKEDITOR.config.pasteFromWordIgnoreFontFace = true; + editor.on( 'paste', function( evt ) + { + var data = evt.data, + mswordHtml; + + // MS-WORD format sniffing. + if ( ( mswordHtml = data[ 'html' ] ) + && ( forceFromWord || ( /(class=\"?Mso|style=\"[^\"]*\bmso\-|w:WordDocument)/ ).test( mswordHtml ) ) ) + { + var isLazyLoad = this.loadFilterRules( function() + { + // Event continuation with the original data. + if ( isLazyLoad ) + editor.fire( 'paste', data ); + else if( !editor.config.pasteFromWordPromptCleanup + || ( forceFromWord || confirm( editor.lang.pastefromword.confirmCleanup ) ) ) + { + data[ 'html' ] = CKEDITOR.cleanWord( mswordHtml, editor ); + } + }); + + // The cleanup rules are to be loaded, we should just cancel + // this event. + isLazyLoad && evt.cancel(); + } + }, this ); + }, + + loadFilterRules : function( callback ) + { + + var isLoaded = CKEDITOR.cleanWord; + + if ( isLoaded ) + callback(); + else + { + var filterFilePath = CKEDITOR.getUrl( + CKEDITOR.config.pasteFromWordCleanupFile + || ( this.path + 'filter/default.js' ) ); + + // Load with busy indicator. + CKEDITOR.scriptLoader.load( filterFilePath, callback, null, false, true ); + } + + return !isLoaded; + } + }); +})(); /** - * Whether the "Remove styles definitions" checkbox is enabled by default in - * the Paste from Word dialog. + * Whether prompt the user about the clean-up of content from MS-Word. + * @name CKEDITOR.config.pasteFromWordPromptCleanup * @type Boolean - * @default false + * @default undefined * @example - * config.pasteFromWordRemoveStyle = true; + * config.pasteFromWordPromptCleanup = true; */ -CKEDITOR.config.pasteFromWordRemoveStyle = false; /** - * Whether to keep structure markup (<h1>, <h2>, etc.) or replace - * it with elements that create more similar pasting results when pasting - * content from Microsoft Word into the Paste from Word dialog. - * @type Boolean - * @default false + * The file that provides the MS-Word Filtering rules. + * Note: It's a global configuration which are shared by all editor instances. + * @name CKEDITOR.config.pasteFromWordCleanupFile + * @type String + * @default 'default' * @example - * config.pasteFromWordKeepsStructure = true; + * // Load from 'pastefromword' plugin 'filter' sub folder (custom.js file). + * CKEDITOR.config.pasteFromWordCleanupFile = 'custom'; */ -CKEDITOR.config.pasteFromWordKeepsStructure = false; diff --git a/_source/plugins/pastetext/dialogs/pastetext.js b/_source/plugins/pastetext/dialogs/pastetext.js index daf45be..4fadc88 100644 --- a/_source/plugins/pastetext/dialogs/pastetext.js +++ b/_source/plugins/pastetext/dialogs/pastetext.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -22,10 +22,13 @@ For licensing, see LICENSE.html or http://ckeditor.com/license onOk : function() { // Get the textarea value. - var text = this.getContentElement( 'general', 'content' ).getInputElement().getValue(); + var text = this.getContentElement( 'general', 'content' ).getInputElement().getValue(), + editor = this.getParentEditor(); - // Inserts the text. - this.getParentEditor().insertText( text ); + setTimeout( function() + { + editor.fire( 'paste', { 'text' : text } ); + }, 0 ); }, contents : diff --git a/_source/plugins/pastetext/plugin.js b/_source/plugins/pastetext/plugin.js index 191c19f..527c62b 100644 --- a/_source/plugins/pastetext/plugin.js +++ b/_source/plugins/pastetext/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -14,18 +14,60 @@ For licensing, see LICENSE.html or http://ckeditor.com/license { exec : function( editor ) { - // We use getClipboardData just to test if the clipboard access has - // been granted by the user. - if ( CKEDITOR.getClipboardData() === false || !window.clipboardData ) + var clipboardText = CKEDITOR.tools.tryThese( + function() + { + var clipboardText = window.clipboardData.getData( 'Text' ); + if ( !clipboardText ) + throw 0; + return clipboardText; + }, + function() + { + window.netscape.security.PrivilegeManager.enablePrivilege( "UniversalXPConnect" ); + + var clip = window.Components.classes[ "@mozilla.org/widget/clipboard;1" ] + .getService( window.Components.interfaces.nsIClipboard ); + var trans = window.Components.classes[ "@mozilla.org/widget/transferable;1" ] + .createInstance( window.Components.interfaces.nsITransferable ); + trans.addDataFlavor( "text/unicode" ); + clip.getData( trans, clip.kGlobalClipboard ); + + var str = {}, strLength = {}, clipboardText; + trans.getTransferData( "text/unicode", str, strLength ); + str = str.value.QueryInterface( window.Components.interfaces.nsISupportsString ); + clipboardText = str.data.substring( 0, strLength.value / 2 ); + return clipboardText; + } + // Any other approach that's working... + ); + + if ( !clipboardText ) // Clipboard access privilege is not granted. { editor.openDialog( 'pastetext' ); - return; + return false; } + else + editor.fire( 'paste', { 'text' : clipboardText } ); - editor.insertText( window.clipboardData.getData( 'Text' ) ); + return true; } }; + function doInsertText( doc, text ) + { + // Native text insertion. + if( CKEDITOR.env.ie ) + { + var selection = doc.selection; + if ( selection.type == 'Control' ) + selection.clear(); + selection.createRange().pasteHTML( text ); + } + else + doc.execCommand( 'inserthtml', false, text ); + } + // Register the plugin. CKEDITOR.plugins.add( 'pastetext', { @@ -44,99 +86,77 @@ For licensing, see LICENSE.html or http://ckeditor.com/license if ( editor.config.forcePasteAsPlainText ) { - editor.on( 'beforePaste', function( event ) + // Intercept the default pasting process. + editor.on( 'beforeCommandExec', function ( evt ) + { + if ( evt.data.name == 'paste' ) { - if ( editor.mode == "wysiwyg" ) - { - setTimeout( function() { command.exec(); }, 0 ); - event.cancel(); - } - }, - null, null, 20 ); + editor.execCommand( 'pastetext' ); + evt.cancel(); + } + }, null, null, 0 ); } }, + requires : [ 'clipboard' ] }); - var clipboardDiv; - - CKEDITOR.getClipboardData = function() + function doEnter( editor, mode, times, forceMode ) { - if ( !CKEDITOR.env.ie ) - return false; - - var doc = CKEDITOR.document, - body = doc.getBody(); - - if ( !clipboardDiv ) + while ( times-- ) { - clipboardDiv = doc.createElement( 'div', - { - attributes : - { - id: 'cke_hiddenDiv' - }, - styles : - { - position : 'absolute', - visibility : 'hidden', - overflow : 'hidden', - width : '1px', - height : '1px' - } - }); - - clipboardDiv.setHtml( '' ); - - clipboardDiv.appendTo( body ); + CKEDITOR.plugins.enterkey[ mode == CKEDITOR.ENTER_BR ? 'enterBr' : 'enterBlock' ] + ( editor, mode, null, forceMode ); } + } - // The "enabled" flag is used to check whether the paste operation has - // been completed (the onpaste event has been fired). - var enabled = false; - var setEnabled = function() - { - enabled = true; - }; + CKEDITOR.editor.prototype.insertText = function( text ) + { + this.focus(); + this.fire( 'saveSnapshot' ); + + var mode = this.getSelection().getStartElement().hasAscendant( 'pre', true ) ? CKEDITOR.ENTER_BR : this.config.enterMode, + isEnterBrMode = mode == CKEDITOR.ENTER_BR, + doc = this.document.$, + self = this, + line; - body.on( 'paste', setEnabled ); + text = CKEDITOR.tools.htmlEncode( text.replace( /\r\n|\r/g, '\n' ) ); - // Create a text range and move it inside the div. - var textRange = body.$.createTextRange(); - textRange.moveToElementText( clipboardDiv.$ ); + var startIndex = 0; + text.replace( /\n+/g, function( match, lastIndex ) + { + line = text.substring( startIndex, lastIndex ); + startIndex = lastIndex + match.length; + line.length && doInsertText( doc, line ); - // The execCommand in will fire the "onpaste", only if the - // security settings are enabled. - textRange.execCommand( 'Paste' ); + var lineBreakNums = match.length, + // Duo consequence line-break as a enter block. + enterBlockTimes = isEnterBrMode ? 0 : Math.floor( lineBreakNums / 2 ), + // Per link-break as a enter br. + enterBrTimes = isEnterBrMode ? lineBreakNums : lineBreakNums % 2; - // Get the DIV html and reset it. - var html = clipboardDiv.getHtml(); - clipboardDiv.setHtml( '' ); + // Line-breaks are converted to editor enter key strokes. + doEnter( self, mode, enterBlockTimes ); + doEnter( self, CKEDITOR.ENTER_BR, enterBrTimes, isEnterBrMode ? false : true ); + }); - body.removeListener( 'paste', setEnabled ); + // Insert the last text line of text. + line = text.substring( startIndex, text.length ); + line.length && doInsertText( doc, line ); - // Return the HTML or false if not enabled. - return enabled && html; + this.fire( 'saveSnapshot' ); }; })(); -CKEDITOR.editor.prototype.insertText = function( text ) -{ - text = CKEDITOR.tools.htmlEncode( text ); - - // TODO: Replace the following with fill line break processing (see V2). - text = text.replace( /(?:\r\n)|\n|\r/g, '<br>' ); - - this.insertHtml( text ); -}; /** * Whether to force all pasting operations to insert on plain text into the * editor, loosing any formatting information possibly available in the source * text. + * @name CKEDITOR.config.forcePasteAsPlainText * @type Boolean * @default false * @example * config.forcePasteAsPlainText = true; */ -CKEDITOR.config.forcePasteAsPlainText = false; diff --git a/_source/plugins/popup/plugin.js b/_source/plugins/popup/plugin.js index 429a0f1..48c923b 100644 --- a/_source/plugins/popup/plugin.js +++ b/_source/plugins/popup/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/preview/plugin.js b/_source/plugins/preview/plugin.js index 5e42ad8..53f57c7 100644 --- a/_source/plugins/preview/plugin.js +++ b/_source/plugins/preview/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -16,19 +16,29 @@ For licensing, see LICENSE.html or http://ckeditor.com/license exec : function( editor ) { var sHTML, + config = editor.config, + baseTag = config.baseHref ? '<base href="' + config.baseHref + '"/>' : '', isCustomDomain = CKEDITOR.env.isCustomDomain(); - if ( editor.config.fullPage ) - sHTML = editor.getData(); + + if ( config.fullPage ) + { + sHTML = editor.getData() + .replace( /<head>/, '$&' + baseTag ) + .replace( /[^>]*(?=<\/title>)/, editor.lang.preview ); + } else { var bodyHtml = '<body ', - body = CKEDITOR.document.getBody(), - baseTag = ( editor.config.baseHref.length > 0 ) ? '<base href="' + editor.config.baseHref + '" _cktemp="true"></base>' : ''; + body = editor.document && editor.document.getBody(); + + if ( body ) + { + if ( body.getAttribute( 'id' ) ) + bodyHtml += 'id="' + body.getAttribute( 'id' ) + '" '; + if ( body.getAttribute( 'class' ) ) + bodyHtml += 'class="' + body.getAttribute( 'class' ) + '" '; + } - if ( body.getAttribute( 'id' ) ) - bodyHtml += 'id="' + body.getAttribute( 'id' ) + '" '; - if ( body.getAttribute( 'class' ) ) - bodyHtml += 'class="' + body.getAttribute( 'class' ) + '" '; bodyHtml += '>'; sHTML = @@ -37,9 +47,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license '<head>' + baseTag + '<title>' + editor.lang.preview + '</title>' + - '<link type="text/css" rel="stylesheet" href="' + - [].concat( editor.config.contentsCss ).join( '"><link type="text/css" rel="stylesheet" href="' ) + - '">' + + CKEDITOR.tools.buildStyleHtml( editor.config.contentsCss ) + '</head>' + bodyHtml + editor.getData() + '</body></html>'; @@ -75,6 +83,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license if ( !isCustomDomain ) { + oWindow.document.open(); oWindow.document.write( sHTML ); oWindow.document.close(); } diff --git a/_source/plugins/print/plugin.js b/_source/plugins/print/plugin.js index bd92ce5..2ea7cf7 100644 --- a/_source/plugins/print/plugin.js +++ b/_source/plugins/print/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/removeformat/plugin.js b/_source/plugins/removeformat/plugin.js index ef8d358..91282d1 100644 --- a/_source/plugins/removeformat/plugin.js +++ b/_source/plugins/removeformat/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -92,7 +92,7 @@ CKEDITOR.plugins.removeformat = var nextNode = currentNode.getNextSourceNode( false, CKEDITOR.NODE_ELEMENT ); // This node must not be a fake element. - if ( currentNode.getName() != 'img' || !currentNode.getAttribute( '_cke_protected_html' ) ) + if ( !( currentNode.getName() == 'img' && currentNode.getAttribute( '_cke_realelement' ) ) ) { // Remove elements nodes that match with this style rules. if ( tagsRegex.test( currentNode.getName() ) ) diff --git a/_source/plugins/resize/plugin.js b/_source/plugins/resize/plugin.js index d7bdd19..fc20e49 100644 --- a/_source/plugins/resize/plugin.js +++ b/_source/plugins/resize/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/richcombo/plugin.js b/_source/plugins/richcombo/plugin.js index 0716a7f..d0e7617 100644 --- a/_source/plugins/richcombo/plugin.js +++ b/_source/plugins/richcombo/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -283,7 +283,7 @@ CKEDITOR.ui.richCombo = CKEDITOR.tools.createClass( var textElement = this.document.getById( 'cke_' + this.id + '_text' ); - if ( !value ) + if ( !( value || text ) ) { text = this.label; textElement.addClass( 'cke_inline_label' ); diff --git a/_source/plugins/save/plugin.js b/_source/plugins/save/plugin.js index a44ae00..5152174 100644 --- a/_source/plugins/save/plugin.js +++ b/_source/plugins/save/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/scayt/dialogs/options.js b/_source/plugins/scayt/dialogs/options.js index 7e35dee..99b9ba7 100644 --- a/_source/plugins/scayt/dialogs/options.js +++ b/_source/plugins/scayt/dialogs/options.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -11,13 +11,40 @@ CKEDITOR.dialog.add( 'scaytcheck', function( editor ) tags = [], i, contents = [], - userDicActive = false; - var dic_buttons = [ - // [0] contains buttons for creating - "dic_create,dic_restore", - // [1] contains buton for manipulation - "dic_rename,dic_delete" - ]; + userDicActive = false, + dic_buttons = [ + // [0] contains buttons for creating + "dic_create,dic_restore", + // [1] contains buton for manipulation + "dic_rename,dic_delete" + ], + optionsIds= [ 'mixedCase','mixedWithDigits','allCaps','ignoreDomainNames' ]; + + // common operations + + function getBOMAllOptions () { + return document.forms.optionsbar["options"]; + } + function getBOMAllLangs () { + return document.forms.languagesbar["scayt_lang"]; + } + + function setCheckedValue(radioObj, newValue) { + if(!radioObj) + return; + var radioLength = radioObj.length; + if(radioLength == undefined) { + radioObj.checked = (radioObj.value == newValue.toString()); + return; + } + for(var i = 0; i < radioLength; i++) { + radioObj[i].checked = false; + if(radioObj[i].value == newValue.toString()) { + radioObj[i].checked = true; + } + } + } + var tags_contents = [ { id : 'options', @@ -26,25 +53,25 @@ CKEDITOR.dialog.add( 'scaytcheck', function( editor ) { type : 'html', id : 'options', - html : '<div class="inner_options">' + + html : '<form name="optionsbar"><div class="inner_options">' + ' <div class="messagebox"></div>' + ' <div style="display:none;">' + - ' <input type="checkbox" value="0" id="allCaps" />' + + ' <input type="checkbox" name="options" id="allCaps" />' + ' <label for="allCaps" id="label_allCaps"></label>' + ' </div>' + ' <div style="display:none;">' + - ' <input type="checkbox" value="0" id="ignoreDomainNames" />' + + ' <input name="options" type="checkbox" id="ignoreDomainNames" />' + ' <label for="ignoreDomainNames" id="label_ignoreDomainNames"></label>' + ' </div>' + ' <div style="display:none;">' + - ' <input type="checkbox" value="0" id="mixedCase" />' + + ' <input name="options" type="checkbox" id="mixedCase" />' + ' <label for="mixedCase" id="label_mixedCase"></label>' + ' </div>' + ' <div style="display:none;">' + - ' <input type="checkbox" value="0" id="mixedWithDigits" />' + + ' <input name="options" type="checkbox" id="mixedWithDigits" />' + ' <label for="mixedWithDigits" id="label_mixedWithDigits"></label>' + ' </div>' + - '</div>' + '</div></form>' } ] }, @@ -55,11 +82,11 @@ CKEDITOR.dialog.add( 'scaytcheck', function( editor ) { type : 'html', id : 'langs', - html : '<div class="inner_langs">' + + html : '<form name="languagesbar"><div class="inner_langs">' + ' <div class="messagebox"></div> ' + - ' <div style="float:left;width:47%;margin-left:5px;" id="scayt_lcol" ></div>' + - ' <div style="float:left;width:47%;margin-left:15px;" id="scayt_rcol"></div>' + - '</div>' + ' <div style="float:left;width:45%;margin-left:5px;" id="scayt_lcol" ></div>' + + ' <div style="float:left;width:45%;margin-left:15px;" id="scayt_rcol"></div>' + + '</div></form>' } ] }, @@ -70,8 +97,8 @@ CKEDITOR.dialog.add( 'scaytcheck', function( editor ) { type : 'html', style: '', - id : 'dic', - html : '<div class="inner_dictionary" style="text-align:left; white-space:normal;">' + + id : 'dictionaries', + html : '<form name="dictionarybar"><div class="inner_dictionary" style="text-align:left; white-space:normal; width:320px; overflow: hidden;">' + ' <div style="margin:5px auto; width:80%;white-space:normal; overflow:hidden;" id="dic_message"> </div>' + ' <div style="margin:5px auto; width:80%;white-space:normal;"> ' + ' <span class="cke_dialog_ui_labeled_label" >Dictionary name</span><br>'+ @@ -90,7 +117,7 @@ CKEDITOR.dialog.add( 'scaytcheck', function( editor ) ' </a>' + ' </div>' + ' <div style="margin:5px auto; width:95%;white-space:normal;" id="dic_info"></div>' + - '</div>' + '</div></form>' } ] }, @@ -101,16 +128,17 @@ CKEDITOR.dialog.add( 'scaytcheck', function( editor ) { type : 'html', id : 'about', - style : 'margin: 10px 40px;', + style : 'margin: 5px 5px;', html : '<div id="scayt_about"></div>' } ] } ]; + var dialogDefiniton = { title : editor.lang.scayt.title, - minWidth : 340, - minHeight : 200, + minWidth : 360, + minHeight : 220, onShow : function() { var dialog = this; @@ -128,15 +156,15 @@ CKEDITOR.dialog.add( 'scaytcheck', function( editor ) var stop = 0; if ( firstLoad ) { - dialog.data.scayt.getCaption( 'en', function( caps ) - { - if ( stop++ > 0 ) // Once only - return; - captions = caps; - init_with_captions.apply( dialog ); - reload.apply( dialog ); - firstLoad = false; - }); + dialog.data.scayt.getCaption( editor.langCode || 'en', function( caps ) + { + if ( stop++ > 0 ) // Once only + return; + captions = caps; + init_with_captions.apply( dialog ); + reload.apply( dialog ); + firstLoad = false; + }); } else reload.apply( dialog ); @@ -145,65 +173,43 @@ CKEDITOR.dialog.add( 'scaytcheck', function( editor ) }, onOk : function() { - var scayt_control = this.data.scayt_control, - o = scayt_control.option(), - c = 0; - - // Set up options if any was set. - for ( var i in this.options ) - { - if (o[i] != this.options[ i ] && c === 0 ) - { - scayt_control.option( this.options ); - c++; - } - } - + var scayt_control = this.data.scayt_control; + scayt_control.option( this.options ); // Setup languge if it was changed. var csLang = this.chosed_lang; - if ( csLang && this.data.sLang != csLang ) - { - scayt_control.setLang( csLang ); - c++; - } - if ( c > 0 ) - scayt_control.refresh(); + scayt_control.setLang( csLang ); + scayt_control.refresh(); + }, + onCancel: function() + { + var o = getBOMAllOptions(); + for (i in o) + o[i].checked = false; + + setCheckedValue(getBOMAllLangs(),""); }, contents : contents - }; + }; var scayt_control = CKEDITOR.plugins.scayt.getScayt( editor ); - if ( scayt_control ) - { - tags = scayt_control.uiTags; - } - for ( i in tags ) { + tags = CKEDITOR.plugins.scayt.uiTabs; + + for ( i in tags ) + { if ( tags[ i ] == 1 ) contents[ contents.length ] = tags_contents[ i ]; } if ( tags[2] == 1 ) userDicActive = true; - function onDicButtonClick() - { - var dic_name = doc.getById('dic_name').getValue(); - if ( !dic_name ) - { - dic_error_message(" Dictionary name should not be empty. "); - return false; - } - //apply handler - window.dic[ this.getId() ].apply( null, [ this, dic_name, dic_buttons ] ); - return true; - } var init_with_captions = function() { var dialog = this, lang_list = dialog.data.scayt.getLangList(), buttons = [ 'dic_create','dic_delete','dic_rename','dic_restore' ], - labels = [ 'mixedCase','mixedWithDigits','allCaps','ignoreDomainNames' ], + labels = optionsIds, i; // Add buttons titles @@ -219,18 +225,21 @@ CKEDITOR.dialog.add( 'scaytcheck', function( editor ) // Fill options and dictionary labels. - for ( i in labels ) + if (tags[0] == 1) { - var label = 'label_' + labels[ i ], - labelElement = doc.getById( label ); - - if ( 'undefined' != typeof labelElement - && 'undefined' != typeof captions[ label ] - && 'undefined' != typeof dialog.options[labels[ i ]] ) + for ( i in labels ) { - labelElement.setHtml( captions[ label ] ); - var labelParent = labelElement.getParent(); - labelParent.$.style.display = "block"; + var label = 'label_' + labels[ i ], + labelElement = doc.getById( label ); + + if ( 'undefined' != typeof labelElement + && 'undefined' != typeof captions[ label ] + && 'undefined' != typeof dialog.options[labels[ i ]] ) + { + labelElement.setHtml( captions[ label ] ); + var labelParent = labelElement.getParent(); + labelParent.$.style.display = "block"; + } } } @@ -273,104 +282,97 @@ CKEDITOR.dialog.add( 'scaytcheck', function( editor ) }; var langList = []; - for ( i in lang_list.rtl ) - langList[ langList.length ] = createOption( i, lang_list.ltr ); + if (tags[1] ==1 ) + { + for ( i in lang_list.rtl ) + langList[ langList.length ] = createOption( i, lang_list.ltr ); - for ( i in lang_list.ltr ) - langList[ langList.length ] = createOption( i, lang_list.ltr ); + for ( i in lang_list.ltr ) + langList[ langList.length ] = createOption( i, lang_list.ltr ); - langList.sort( function( lang1, lang2 ) - { - return ( lang2.lang > lang1.lang ) ? -1 : 1 ; - }); + langList.sort( function( lang1, lang2 ) + { + return ( lang2.lang > lang1.lang ) ? -1 : 1 ; + }); - var fieldL = doc.getById( 'scayt_lcol' ), - fieldR = doc.getById( 'scayt_rcol' ); - for ( i=0; i < langList.length; i++ ) - { - var field = ( i < langList.length / 2 ) ? fieldL : fieldR; - field.append( langList[ i ].radio ); + var fieldL = doc.getById( 'scayt_lcol' ), + fieldR = doc.getById( 'scayt_rcol' ); + for ( i=0; i < langList.length; i++ ) + { + var field = ( i < langList.length / 2 ) ? fieldL : fieldR; + field.append( langList[ i ].radio ); + } } // user dictionary handlers var dic = {}; dic.dic_create = function( el, dic_name , dic_buttons ) - { - // comma separated button's ids include repeats if exists - var all_buttons = dic_buttons[0] + ',' + dic_buttons[1]; + { + // comma separated button's ids include repeats if exists + var all_buttons = dic_buttons[0] + ',' + dic_buttons[1]; - var err_massage = captions["err_dic_create"]; - var suc_massage = captions["succ_dic_create"]; - //console.info("--plugin "); + var err_massage = captions["err_dic_create"]; + var suc_massage = captions["succ_dic_create"]; - window.scayt.createUserDictionary(dic_name, - function(arg) - { - //console.info( "dic_create callback called with args" , arg ); - hide_dic_buttons ( all_buttons ); - display_dic_buttons ( dic_buttons[1] ); - suc_massage = suc_massage.replace("%s" , arg.dname ); - dic_success_message (suc_massage); - }, - function(arg) - { - //console.info( "dic_create errorback called with args" , arg ) - err_massage = err_massage.replace("%s" ,arg.dname ); - dic_error_message ( err_massage + "( "+ (arg.message || "") +")"); - }); + window.scayt.createUserDictionary(dic_name, + function(arg) + { + hide_dic_buttons ( all_buttons ); + display_dic_buttons ( dic_buttons[1] ); + suc_massage = suc_massage.replace("%s" , arg.dname ); + dic_success_message (suc_massage); + }, + function(arg) + { + err_massage = err_massage.replace("%s" ,arg.dname ); + dic_error_message ( err_massage + "( "+ (arg.message || "") +")"); + }); - }; + }; dic.dic_rename = function( el, dic_name ) - { - // - // try to rename dictionary - // @TODO: rename dict - //console.info ( captions["err_dic_rename"] ) - var err_massage = captions["err_dic_rename"] || ""; - var suc_massage = captions["succ_dic_rename"] || ""; - window.scayt.renameUserDictionary(dic_name, - function(arg) - { - //console.info( "dic_rename callback called with args" , arg ); - suc_massage = suc_massage.replace("%s" , arg.dname ); - set_dic_name( dic_name ); - dic_success_message ( suc_massage ); - }, - function(arg) - { - //console.info( "dic_rename errorback called with args" , arg ) - err_massage = err_massage.replace("%s" , arg.dname ); - set_dic_name( dic_name ); - dic_error_message( err_massage + "( " + ( arg.message || "" ) + " )" ); - }); - }; + { + // + // try to rename dictionary + var err_massage = captions["err_dic_rename"] || ""; + var suc_massage = captions["succ_dic_rename"] || ""; + window.scayt.renameUserDictionary(dic_name, + function(arg) + { + suc_massage = suc_massage.replace("%s" , arg.dname ); + set_dic_name( dic_name ); + dic_success_message ( suc_massage ); + }, + function(arg) + { + err_massage = err_massage.replace("%s" , arg.dname ); + set_dic_name( dic_name ); + dic_error_message( err_massage + "( " + ( arg.message || "" ) + " )" ); + }); + }; dic.dic_delete = function ( el, dic_name , dic_buttons ) - { - var all_buttons = dic_buttons[0] + ',' + dic_buttons[1]; - var err_massage = captions["err_dic_delete"]; - var suc_massage = captions["succ_dic_delete"]; + { + var all_buttons = dic_buttons[0] + ',' + dic_buttons[1]; + var err_massage = captions["err_dic_delete"]; + var suc_massage = captions["succ_dic_delete"]; - // try to delete dictionary - // @TODO: delete dict - window.scayt.deleteUserDictionary( - function(arg) - { - //console.info( "dic_delete callback " , dic_name ,arg ); - suc_massage = suc_massage.replace("%s" , arg.dname ); - hide_dic_buttons ( all_buttons ); - display_dic_buttons ( dic_buttons[0] ); - set_dic_name( "" ); // empty input field - dic_success_message( suc_massage ); - }, - function(arg) - { - //console.info( " dic_delete errorback called with args" , arg ) - err_massage = err_massage.replace("%s" , arg.dname ); - dic_error_message(err_massage); - }); - }; + // try to delete dictionary + window.scayt.deleteUserDictionary( + function(arg) + { + suc_massage = suc_massage.replace("%s" , arg.dname ); + hide_dic_buttons ( all_buttons ); + display_dic_buttons ( dic_buttons[0] ); + set_dic_name( "" ); // empty input field + dic_success_message( suc_massage ); + }, + function(arg) + { + err_massage = err_massage.replace("%s" , arg.dname ); + dic_error_message(err_massage); + }); + }; dic.dic_restore = dialog.dic_restore || function ( el, dic_name , dic_buttons ) { @@ -381,28 +383,45 @@ CKEDITOR.dialog.add( 'scaytcheck', function( editor ) window.scayt.restoreUserDictionary(dic_name, function(arg) - { - //console.info( "dic_restore callback called with args" , arg ); - suc_massage = suc_massage.replace("%s" , arg.dname ); - hide_dic_buttons ( all_buttons ); - display_dic_buttons(dic_buttons[1]); - dic_success_message( suc_massage ); - }, + { + suc_massage = suc_massage.replace("%s" , arg.dname ); + hide_dic_buttons ( all_buttons ); + display_dic_buttons(dic_buttons[1]); + dic_success_message( suc_massage ); + }, function(arg) - { - //console.info( " dic_restore errorback called with args" , arg ) - err_massage = err_massage.replace("%s" , arg.dname ); - dic_error_message( err_massage ); - }); + { + err_massage = err_massage.replace("%s" , arg.dname ); + dic_error_message( err_massage ); + }); }; + function onDicButtonClick( ev ) + { + var dic_name = doc.getById('dic_name').getValue(); + if ( !dic_name ) + { + dic_error_message(" Dictionary name should not be empty. "); + return false; + } + try{ + var el = id = ev.data.getTarget().getParent(); + var id = el.getId(); + dic[ id ].apply( null, [ el, dic_name, dic_buttons ] ); + }catch(err){ + dic_error_message(" Dictionary error. "); + } + + return true; + } + // ** bind event listeners var arr_buttons = ( dic_buttons[0] + ',' + dic_buttons[1] ).split( ',' ), l; for ( i = 0, l = arr_buttons.length ; i < l ; i += 1 ) { - var dic_button = doc.getById(arr_buttons[i]); + var dic_button = doc.getById(arr_buttons[i]); if ( dic_button ) dic_button.on( 'click', onDicButtonClick, this ); } @@ -411,84 +430,104 @@ CKEDITOR.dialog.add( 'scaytcheck', function( editor ) var reload = function() { var dialog = this; + // for enabled options tab + if (tags[0] == 1){ + var opto = getBOMAllOptions(); - // Animate options. - for ( var i in dialog.options ) - { - var checkbox = doc.getById( i ); - if ( checkbox ) + // Animate options. + for ( var k=0,l = opto.length; k<l;k++ ) { - checkbox.removeAttribute( 'checked' ); - if ( dialog.options[ i ] == 1 ) - checkbox.setAttribute( 'checked', 'checked' ); - // Bind events. Do it only once. - if ( firstLoad ) + var i = opto[k].id; + var checkbox = doc.getById( i ); + + if ( checkbox ) { - checkbox.on( 'click', function() + opto[k].checked = false; + //alert (opto[k].removeAttribute) + if ( dialog.options[ i ] == 1 ) + { + opto[k].checked = true; + } + + + // Bind events. Do it only once. + if ( firstLoad ) + { + checkbox.on( 'click', function() { dialog.options[ this.getId() ] = this.$.checked ? 1 : 0 ; - } ); + }); + } } } } + //for enabled languages tab + if ( tags[1] == 1 ) + { + var domLang = doc.getById("cke_option"+dialog.sLang); + setCheckedValue(domLang.$,dialog.sLang); + } + // * user dictionary - if ( userDicActive ){ + if ( userDicActive ) + { window.scayt.getNameUserDictionary( - function( o ) - { - var dic_name = o.dname; - if ( dic_name ) + function( o ) { - doc.getById( 'dic_name' ).setValue(dic_name); - display_dic_buttons( dic_buttons[1] ); - } - else - display_dic_buttons( dic_buttons[0] ); + var dic_name = o.dname; + hide_dic_buttons( dic_buttons[0] + ',' + dic_buttons[1] ); + if ( dic_name ) + { + doc.getById( 'dic_name' ).setValue(dic_name); + display_dic_buttons( dic_buttons[1] ); + } + else + display_dic_buttons( dic_buttons[0] ); - }, - function () - { - doc.getById( 'dic_name' ).setValue(""); - }); + }, + function () + { + doc.getById( 'dic_name' ).setValue(""); + }); dic_success_message(""); } }; function dic_error_message ( m ) - { - doc.getById('dic_message').setHtml('<span style="color:red;">' + m + '</span>' ); - } - function dic_success_message ( m ) - { - doc.getById('dic_message').setHtml('<span style="color:blue;">' + m + '</span>') ; - } + { + doc.getById('dic_message').setHtml('<span style="color:red;">' + m + '</span>' ); + } + function dic_success_message ( m ) + { + doc.getById('dic_message').setHtml('<span style="color:blue;">' + m + '</span>') ; + } function display_dic_buttons ( sIds ) - { - - sIds = String( sIds ); - var aIds = sIds.split(','); - for ( var i=0, l = aIds.length; i < l ; i+=1) - { - doc.getById( aIds[i] ).$.style.display = "inline"; - } + { + sIds = String( sIds ); + var aIds = sIds.split(','); + for ( var i=0, l = aIds.length; i < l ; i+=1) + { + doc.getById( aIds[i] ).$.style.display = "inline"; } + + } function hide_dic_buttons ( sIds ) + { + sIds = String( sIds ); + var aIds = sIds.split(','); + for ( var i = 0, l = aIds.length; i < l ; i += 1 ) { - sIds = String( sIds ); - var aIds = sIds.split(','); - for ( var i = 0, l = aIds.length; i < l ; i += 1 ) - { - doc.getById( aIds[i] ).$.style.display = "none"; - } + doc.getById( aIds[i] ).$.style.display = "none"; } + } function set_dic_name ( dic_name ) - { - doc.getById('dic_name').$.value= dic_name; - } + { + doc.getById('dic_name').$.value= dic_name; + } return dialogDefiniton; }); diff --git a/_source/plugins/scayt/plugin.js b/_source/plugins/scayt/plugin.js index ba929f1..c240aa4 100644 --- a/_source/plugins/scayt/plugin.js +++ b/_source/plugins/scayt/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -11,7 +11,23 @@ For licensing, see LICENSE.html or http://ckeditor.com/license (function() { var commandName = 'scaytcheck', - openPage = ''; + openPage = '', + scayt_paused = null; + + // Checks if a value exists in an array + function in_array(needle, haystack) + { + var found = false, key; + for (key in haystack) + { + if ((haystack[key] === needle) || ( haystack[key] == needle)) + { + found = true; + break; + } + } + return found; + } var onEngineLoad = function() { @@ -24,11 +40,10 @@ For licensing, see LICENSE.html or http://ckeditor.com/license oParams.srcNodeRef = editor.document.getWindow().$.frameElement; // Get the iframe. // syntax : AppName.AppVersion@AppRevision oParams.assocApp = "CKEDITOR." + CKEDITOR.version + "@" + CKEDITOR.revision; - - oParams.customerid = editor.config.scayt_customerid || "1:11111111111111111111111111111111111111"; - oParams.customDictionaryName = editor.config.scayt_customDictionaryName; + oParams.customerid = editor.config.scayt_customerid || "1:WvF0D4-UtPqN1-43nkD4-NKvUm2-daQqk3-LmNiI-z7Ysb4-mwry24-T8YrS3-Q2tpq2"; + oParams.customDictionaryIds = editor.config.scayt_customDictionaryIds; oParams.userDictionaryName = editor.config.scayt_userDictionaryName; - oParams.defLang = editor.scayt_defLang; + oParams.sLang = editor.config.scayt_sLang || "en_US"; if ( CKEDITOR._scaytParams ) { @@ -51,9 +66,19 @@ For licensing, see LICENSE.html or http://ckeditor.com/license plugin.instances[ editor.name ] = scayt_control; + //window.scayt.uiTags + var menuGroup = 'scaytButton'; + var uiTabs = window.scayt.uiTags; + var fTabs = []; + + for (var i = 0,l=4; i<l; i++) + fTabs.push( uiTabs[i] && plugin.uiTabs[i] ); + + plugin.uiTabs = fTabs; try { - scayt_control.setDisabled( scayt_control.paused === false ); // I really don't know why it causes JS error in IE + scayt_control.setDisabled( scayt_paused === false ); } catch (e) {} + editor.fire( 'showScaytState' ); }; @@ -83,13 +108,18 @@ For licensing, see LICENSE.html or http://ckeditor.com/license var scayt_instanse = plugin.getScayt( editor ); if ( scayt_instanse ) { - scayt_instanse.paused = !scayt_instanse.disabled; + scayt_paused = scayt_instanse.paused = !scayt_instanse.disabled; scayt_instanse.destroy(); delete plugin.instances[ editor.name ]; } } }); + + editor.on( 'destroy', function() + { + plugin.getScayt( editor ).destroy(); + }); // Listen to data manipulation to reflect scayt markup. editor.on( 'afterSetData', function() { @@ -116,6 +146,25 @@ For licensing, see LICENSE.html or http://ckeditor.com/license } }, this, null, 50 ); + editor.on( 'insertHtml', function() + { + + var scayt_instance = plugin.getScayt( editor ); + if ( plugin.isScaytEnabled( editor ) ) + { + // Unlock the selection before reload, SCAYT will take + // care selection update. + if ( CKEDITOR.env.ie ) + editor.getSelection().unlock( true ); + + // Swallow any SCAYT engine errors. + try{ + scayt_instance.refresh(); + }catch( er ) + {} + } + }, this, null, 50 ); + editor.on( 'scaytDialog', function( ev ) // Communication with dialog. { ev.data.djConfig = window.djConfig; @@ -189,7 +238,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license var protocol = document.location.protocol; // Default to 'http' for unknown. protocol = protocol.search( /https?:/) != -1? protocol : 'http:'; - var baseUrl = "svc.spellchecker.net/spellcheck/lf/scayt/scayt1.js"; + var baseUrl = "svc.spellchecker.net/spellcheck3/lf/scayt/scayt21.js"; var scaytUrl = editor.config.scayt_srcUrl || ( protocol + "//" + baseUrl ); var scaytConfigBaseUrl = plugin.parseUrl( scaytUrl ).path + "/"; @@ -300,51 +349,81 @@ For licensing, see LICENSE.html or http://ckeditor.com/license // Add Options dialog. CKEDITOR.dialog.add( commandName, CKEDITOR.getUrl( this.path + 'dialogs/options.js' ) ); + // read ui tags + var confuiTabs = editor.config.scayt_uiTabs || "1,1,1"; + var uiTabs =[]; + // string tp array convert + confuiTabs = confuiTabs.split(","); + // check array length ! allwaays must be 3 filled with 1 or 0 + for (var i=0,l=3; i<l; i++){ + var flag = parseInt(confuiTabs[i] || "1" ,10); + uiTabs.push( flag ); + } var menuGroup = 'scaytButton'; editor.addMenuGroup( menuGroup ); - editor.addMenuItems( + // combine menu items to render + var uiMuneItems = {}; + + // allways added + uiMuneItems.scaytToggle = { - scaytToggle : - { - label : editor.lang.scayt.enable, - command : commandName, - group : menuGroup - }, + label : editor.lang.scayt.enable, + command : commandName, + group : menuGroup + }; - scaytOptions : + if (uiTabs[0] == 1) + uiMuneItems.scaytOptions = + { + label : editor.lang.scayt.options, + group : menuGroup, + onClick : function() { - label : editor.lang.scayt.options, - group : menuGroup, - onClick : function() - { - openPage = 'options'; - editor.openDialog( commandName ); - } - }, + openPage = 'options'; + editor.openDialog( commandName ); + } + }; - scaytLangs : + if (uiTabs[1] == 1) + uiMuneItems.scaytLangs = + { + label : editor.lang.scayt.langs, + group : menuGroup, + onClick : function() { - label : editor.lang.scayt.langs, - group : menuGroup, - onClick : function() - { - openPage = 'langs'; - editor.openDialog( commandName ); - } - }, - - scaytAbout : + openPage = 'langs'; + editor.openDialog( commandName ); + } + }; + if (uiTabs[2] == 1) + uiMuneItems.scaytDict = + { + label : editor.lang.scayt.dictionariesTab, + group : menuGroup, + onClick : function() { - label : editor.lang.scayt.about, - group : menuGroup, - onClick : function() - { - openPage = 'about'; - editor.openDialog( commandName ); - } + openPage = 'dictionaries'; + editor.openDialog( commandName ); } - }); + }; + // allways added + uiMuneItems.scaytAbout = + { + label : editor.lang.scayt.about, + group : menuGroup, + onClick : function() + { + openPage = 'about'; + editor.openDialog( commandName ); + } + } + ; + + uiTabs[3] = 1; // about us tab is allways on + plugin.uiTabs = uiTabs; + + editor.addMenuItems( uiMuneItems ); editor.ui.add( 'Scayt', CKEDITOR.UI_MENUBUTTON, { @@ -353,23 +432,24 @@ For licensing, see LICENSE.html or http://ckeditor.com/license className : 'cke_button_scayt', onRender: function() { - command.on( 'state', function() + command.on( 'state', function() { this.setState( command.state ); }, this); - }, - onMenu : function() - { - var isEnabled = plugin.isScaytEnabled( editor ); + }, + onMenu : function() + { + var isEnabled = plugin.isScaytEnabled( editor ); - editor.getMenuItem( 'scaytToggle' ).label = editor.lang.scayt[ isEnabled ? 'disable' : 'enable' ]; + editor.getMenuItem( 'scaytToggle' ).label = editor.lang.scayt[ isEnabled ? 'disable' : 'enable' ]; return { - scaytToggle : CKEDITOR.TRISTATE_OFF, - scaytOptions : isEnabled ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED, - scaytLangs : isEnabled ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED, - scaytAbout : isEnabled ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED + scaytToggle : CKEDITOR.TRISTATE_OFF, + scaytOptions : isEnabled && plugin.uiTabs[0] ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED, + scaytLangs : isEnabled && plugin.uiTabs[1] ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED, + scaytDict : isEnabled && plugin.uiTabs[2] ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED, + scaytAbout : isEnabled && plugin.uiTabs[3] ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED }; } }); @@ -407,7 +487,15 @@ For licensing, see LICENSE.html or http://ckeditor.com/license moreSuggestions = {}; // Reset items. mainSuggestions = {}; - var moreSuggestionsUnable = false; + var moreSuggestionsUnable = editor.config.scayt_moreSuggestions || "on"; + var moreSuggestionsUnableAdded = false; + + var maxSuggestions = editor.config.scayt_maxSuggestions; + ( typeof maxSuggestions != 'number' ) && ( maxSuggestions = 5 ); + !maxSuggestions && ( maxSuggestions = items_suggestion.length ); + + var contextCommands = editor.config.scayt_contextCommands || "all"; + contextCommands = contextCommands.split("|"); for ( var i = 0, l = items_suggestion.length; i < l; i += 1 ) { @@ -422,68 +510,70 @@ For licensing, see LICENSE.html or http://ckeditor.com/license }; })( element.$, items_suggestion[i] ); - if ( i < editor.config.scayt_maxSuggestions ) + if ( i < maxSuggestions ) { addButtonCommand( editor, 'button_' + commandName, items_suggestion[i], commandName, exec, 'scayt_suggest', i + 1 ); _r[ commandName ] = CKEDITOR.TRISTATE_OFF; mainSuggestions[ commandName ] = CKEDITOR.TRISTATE_OFF; } - else + else if ( moreSuggestionsUnable == "on" ) { addButtonCommand( editor, 'button_' + commandName, items_suggestion[i], commandName, exec, 'scayt_moresuggest', i + 1 ); moreSuggestions[ commandName ] = CKEDITOR.TRISTATE_OFF; - moreSuggestionsUnable = true; + moreSuggestionsUnableAdded = true; } } - if ( moreSuggestionsUnable ) + + if ( moreSuggestionsUnableAdded ){ // Rgister the More suggestions group; editor.addMenuItem( 'scayt_moresuggest', + { + label : editor.lang.scayt.moreSuggestions, + group : 'scayt_moresuggest', + order : 10, + getItems : function() { - label : editor.lang.scayt.moreSuggestions, - group : 'scayt_moresuggest', - order : 10, - getItems : function() - { - return moreSuggestions; - } - }); + return moreSuggestions; + } + }); + mainSuggestions[ 'scayt_moresuggest' ] = CKEDITOR.TRISTATE_OFF; + } - var ignore_command = - { - exec: function() - { - scayt_control.ignore( element.$ ); - } - }; - var ignore_all_command = - { - exec: function() - { - scayt_control.ignoreAll( element.$ ); - } - }; - var addword_command = + if ( in_array( "all",contextCommands ) || in_array("ignore",contextCommands) ) { - exec: function() - { - window.scayt.addWordToUserDictionary( element.$ ); - } - }; + var ignore_command = { + exec: function(){ + scayt_control.ignore(element.$); + } + }; + addButtonCommand(editor, 'ignore', editor.lang.scayt.ignore, 'scayt_ignore', ignore_command, 'scayt_control', 1); + mainSuggestions['scayt_ignore'] = CKEDITOR.TRISTATE_OFF; + } - addButtonCommand( editor, 'ignore', editor.lang.scayt.ignore, - 'scayt_ignore', ignore_command, 'scayt_control', 1); - addButtonCommand( editor, 'ignore_all', editor.lang.scayt.ignoreAll, - 'scayt_ignore_all', ignore_all_command, 'scayt_control', 2); - addButtonCommand( editor, 'add_word', editor.lang.scayt.addWord, - 'scayt_add_word', addword_command, 'scayt_control', 3); + if ( in_array( "all",contextCommands ) || in_array("ignoreall",contextCommands) ) + { + var ignore_all_command = { + exec: function(){ + scayt_control.ignoreAll(element.$); + } + }; + addButtonCommand(editor, 'ignore_all', editor.lang.scayt.ignoreAll, 'scayt_ignore_all', ignore_all_command, 'scayt_control', 2); + mainSuggestions['scayt_ignore_all'] = CKEDITOR.TRISTATE_OFF; + } - mainSuggestions[ 'scayt_moresuggest' ] = CKEDITOR.TRISTATE_OFF; - mainSuggestions[ 'scayt_ignore' ] = CKEDITOR.TRISTATE_OFF; - mainSuggestions[ 'scayt_ignore_all' ] = CKEDITOR.TRISTATE_OFF; - mainSuggestions[ 'scayt_add_word' ] = CKEDITOR.TRISTATE_OFF; + if ( in_array( "all",contextCommands ) || in_array("add",contextCommands) ) + { + var addword_command = { + exec: function(){ + window.scayt.addWordToUserDictionary(element.$); + } + }; + addButtonCommand(editor, 'add_word', editor.lang.scayt.addWord, 'scayt_add_word', addword_command, 'scayt_control', 3); + mainSuggestions['scayt_add_word'] = CKEDITOR.TRISTATE_OFF; + } if ( scayt_control.fireOnContextMenu ) scayt_control.fireOnContextMenu( editor ); @@ -508,5 +598,6 @@ For licensing, see LICENSE.html or http://ckeditor.com/license }); })(); -CKEDITOR.config.scayt_maxSuggestions = 5; -CKEDITOR.config.scayt_autoStartup = false; +// TODO: Documentation +// CKEDITOR.config.scayt_maxSuggestions +// CKEDITOR.config.scayt_autoStartup diff --git a/_source/plugins/selection/plugin.js b/_source/plugins/selection/plugin.js index 7d11e7e..febb027 100644 --- a/_source/plugins/selection/plugin.js +++ b/_source/plugins/selection/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -92,7 +92,8 @@ For licensing, see LICENSE.html or http://ckeditor.com/license { editor.on( 'contentDom', function() { - var doc = editor.document; + var doc = editor.document, + body = doc.getBody(); if ( CKEDITOR.env.ie ) { @@ -107,7 +108,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license // "onfocusin" is fired before "onfocus". It makes it // possible to restore the selection before click // events get executed. - doc.on( 'focusin', function() + body.on( 'focusin', function() { // If we have saved a range, restore it at this // point. @@ -133,25 +134,16 @@ For licensing, see LICENSE.html or http://ckeditor.com/license saveSelection(); }); - // Check document selection before 'blur' fired, this - // will prevent us from breaking text selection somewhere - // else on the host page.(#3909) - editor.document.on( 'beforedeactivate', function() + body.on( 'beforedeactivate', function() { // Disable selections from being saved. saveEnabled = false; - - // IE may leave the selection still inside the - // document. Let's force it to be removed. - // TODO: The following has effect for - // collapsed selections. - editor.document.$.execCommand( 'Unselect' ); }); // IE fires the "selectionchange" event when clicking // inside a selection. We don't want to capture that. - doc.on( 'mousedown', disableSave ); - doc.on( 'mouseup', + body.on( 'mousedown', disableSave ); + body.on( 'mouseup', function() { saveEnabled = true; @@ -162,8 +154,8 @@ For licensing, see LICENSE.html or http://ckeditor.com/license 0 ); }); - doc.on( 'keydown', disableSave ); - doc.on( 'keyup', + body.on( 'keydown', disableSave ); + body.on( 'keyup', function() { saveEnabled = true; @@ -333,7 +325,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license var styleObjectElements = { - img:1,hr:1,li:1,table:1,tr:1,td:1,embed:1,object:1,ol:1,ul:1, + img:1,hr:1,li:1,table:1,tr:1,td:1,th:1,embed:1,object:1,ol:1,ul:1, a:1, input:1, form:1, select:1, textarea:1, button:1, fieldset:1, th:1, thead:1, tfoot:1 }; @@ -640,8 +632,10 @@ For licensing, see LICENSE.html or http://ckeditor.com/license { var startContainer = range.startContainer, startOffset = range.startOffset; + // Limit the fix only to non-block elements.(#3950) if ( startOffset == ( startContainer.getChildCount ? - startContainer.getChildCount() : startContainer.getLength() ) ) + startContainer.getChildCount() : startContainer.getLength() ) + && !startContainer.isBlockBoundary() ) range.setStartAfter( startContainer ); else break; } @@ -939,9 +933,30 @@ For licensing, see LICENSE.html or http://ckeditor.com/license } this.selectRanges( ranges ); return this; + }, + + getCommonAncestor : function() + { + var ranges = this.getRanges(), + startNode = ranges[ 0 ].startContainer, + endNode = ranges[ ranges.length - 1 ].endContainer; + return startNode.getCommonAncestor( endNode ); + }, + + // Moving scroll bar to the current selection's start position. + scrollIntoView : function() + { + // If we have split the block, adds a temporary span at the + // range position and scroll relatively to it. + var start = this.getStartElement(); + start.scrollIntoView(); } }; })(); +( function() +{ +var notWhitespaces = CKEDITOR.dom.walker.whitespaces( true ); +var fillerTextRegex = /\ufeff|\u00a0/; CKEDITOR.dom.range.prototype.select = CKEDITOR.env.ie ? @@ -986,7 +1001,9 @@ CKEDITOR.dom.range.prototype.select = // will expand and that the cursor will be blinking on the right place. // Actually, we are using this flag just to avoid using this hack in all // situations, but just on those needed. - isStartMarkerAlone = forceExpand || !startNode.hasPrevious() || ( startNode.getPrevious().is && startNode.getPrevious().is( 'br' ) ); + var next = startNode.getNext( notWhitespaces ); + isStartMarkerAlone = ( !( next && next.getText && next.getText().match( fillerTextRegex ) ) // already a filler there? + && ( forceExpand || !startNode.hasPrevious() || ( startNode.getPrevious().is && startNode.getPrevious().is( 'br' ) ) ) ); // Append a temporary <span></span> before the selection. // This is needed to avoid IE destroying selections inside empty @@ -1070,3 +1087,4 @@ CKEDITOR.dom.range.prototype.select = selection.removeAllRanges(); selection.addRange( nativeRange ); }; +} )(); diff --git a/_source/plugins/showblocks/plugin.js b/_source/plugins/showblocks/plugin.js index e66e787..29b654c 100644 --- a/_source/plugins/showblocks/plugin.js +++ b/_source/plugins/showblocks/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/showborders/plugin.js b/_source/plugins/showborders/plugin.js new file mode 100644 index 0000000..a42ad10 --- /dev/null +++ b/_source/plugins/showborders/plugin.js @@ -0,0 +1,170 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview The "show border" plugin. The command display visible outline + * border line around all table elements if table doesn't have a none-zero 'border' attribute specified. + */ + +(function() +{ + var showBorderClassName = 'cke_show_border', + cssStyleText, + cssTemplate = + // TODO: For IE6, we don't have child selector support, + // where nested table cells could be incorrect. + ( CKEDITOR.env.ie6Compat ? + [ + '.%1 table.%2,', + '.%1 table.%2 td, .%1 table.%2 th,', + '{', + 'border : #d3d3d3 1px dotted', + '}' + ] : + [ + '.%1 table.%2,', + '.%1 table.%2 > tr > td, .%1 table.%2 > tr > th,', + '.%1 table.%2 > tbody > tr > td, .%1 table.%2 > tbody > tr > th,', + '.%1 table.%2 > thead > tr > td, .%1 table.%2 > thead > tr > th,', + '.%1 table.%2 > tfoot > tr > td, .%1 table.%2 > tfoot > tr > th', + '{', + 'border : #d3d3d3 1px dotted', + '}' + ] ).join( '' ); + + cssStyleText = cssTemplate.replace( /%2/g, showBorderClassName ).replace( /%1/g, 'cke_show_borders ' ); + + var commandDefinition = + { + preserveState : true, + editorFocus : false, + + exec : function ( editor ) + { + this.toggleState(); + this.refresh( editor ); + }, + + refresh : function( editor ) + { + var funcName = ( this.state == CKEDITOR.TRISTATE_ON ) ? 'addClass' : 'removeClass'; + editor.document.getBody()[ funcName ]( 'cke_show_borders' ); + } + }; + + CKEDITOR.plugins.add( 'showborders', + { + requires : [ 'wysiwygarea' ], + modes : { 'wysiwyg' : 1 }, + + init : function( editor ) + { + + var command = editor.addCommand( 'showborders', commandDefinition ); + command.canUndo = false; + + if ( editor.config.startupShowBorders !== false ) + command.setState( CKEDITOR.TRISTATE_ON ); + + editor.addCss( cssStyleText ); + + // Refresh the command on setData. + editor.on( 'mode', function() + { + if ( command.state != CKEDITOR.TRISTATE_DISABLED ) + command.refresh( editor ); + }, null, null, 100 ); + + // Refresh the command on wysiwyg frame reloads. + editor.on( 'contentDom', function() + { + if ( command.state != CKEDITOR.TRISTATE_DISABLED ) + command.refresh( editor ); + }); + }, + + afterInit : function( editor ) + { + var dataProcessor = editor.dataProcessor, + dataFilter = dataProcessor && dataProcessor.dataFilter, + htmlFilter = dataProcessor && dataProcessor.htmlFilter; + + if ( dataFilter ) + { + dataFilter.addRules( + { + elements : + { + 'table' : function( element ) + { + var attributes = element.attributes, + cssClass = attributes[ 'class' ], + border = parseInt( attributes.border, 10 ); + + if ( !border || border <= 0 ) + attributes[ 'class' ] = ( cssClass || '' ) + ' ' + showBorderClassName; + } + } + } ); + } + + if( htmlFilter ) + { + htmlFilter.addRules( + { + elements : + { + 'table' : function( table ) + { + var attributes = table.attributes, + cssClass = attributes[ 'class' ]; + + cssClass && ( attributes[ 'class' ] = + cssClass.replace( showBorderClassName, '' ) + .replace( /\s{2}/, ' ' ) + .replace( /^\s+|\s+$/, '' ) ); + } + } + } ); + } + + // Table dialog must be aware of it. + CKEDITOR.on( 'dialogDefinition', function( ev ) + { + if( ev.editor != editor ) + return; + + var dialogName = ev.data.name; + + if ( dialogName == 'table' || dialogName == 'tableProperties' ) + { + var dialogDefinition = ev.data.definition, + infoTab = dialogDefinition.getContents( 'info' ), + borderField = infoTab.get( 'txtBorder' ), + originalCommit = borderField.commit; + + borderField.commit = CKEDITOR.tools.override( originalCommit, function( org ) + { + return function( data, selectedTable ) + { + org.apply( this, arguments ); + var value = parseInt( this.getValue(), 10 ); + selectedTable[ ( !value || value <= 0 ) ? 'addClass' : 'removeClass' ]( showBorderClassName ); + }; + } ); + } + }); + } + + }); +} )(); + +/** + * Whether to automatically enable the "show borders" command when the editor loads. + * @type Boolean + * @default true + * @example + * config.startupShowBorders = false; + */ diff --git a/_source/plugins/smiley/dialogs/smiley.js b/_source/plugins/smiley/dialogs/smiley.js index 0b86551..245b006 100644 --- a/_source/plugins/smiley/dialogs/smiley.js +++ b/_source/plugins/smiley/dialogs/smiley.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/smiley/plugin.js b/_source/plugins/smiley/plugin.js index bf65820..a037896 100644 --- a/_source/plugins/smiley/plugin.js +++ b/_source/plugins/smiley/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/sourcearea/plugin.js b/_source/plugins/sourcearea/plugin.js index b369c04..184c2fd 100644 --- a/_source/plugins/sourcearea/plugin.js +++ b/_source/plugins/sourcearea/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/specialchar/dialogs/specialchar.js b/_source/plugins/specialchar/dialogs/specialchar.js index 8021dcb..9a90984 100644 --- a/_source/plugins/specialchar/dialogs/specialchar.js +++ b/_source/plugins/specialchar/dialogs/specialchar.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/specialchar/plugin.js b/_source/plugins/specialchar/plugin.js index 8b96772..fa5e309 100644 --- a/_source/plugins/specialchar/plugin.js +++ b/_source/plugins/specialchar/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/styles/plugin.js b/_source/plugins/styles/plugin.js index ed83120..fcab9a0 100644 --- a/_source/plugins/styles/plugin.js +++ b/_source/plugins/styles/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -198,8 +198,8 @@ CKEDITOR.STYLE_OBJECT = 3; if ( attName == '_length' ) continue; - var elementAttr = element.getAttribute( attName ); - if ( attribs[attName] == + var elementAttr = element.getAttribute( attName ) || ''; + if ( attribs[ attName ] == ( attName == 'style' ? normalizeCssText( elementAttr, false ) : elementAttr ) ) { diff --git a/_source/plugins/stylescombo/plugin.js b/_source/plugins/stylescombo/plugin.js index d457fa2..bda148b 100644 --- a/_source/plugins/stylescombo/plugin.js +++ b/_source/plugins/stylescombo/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -26,7 +26,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license panel : { - css : [ CKEDITOR.getUrl( editor.skinPath + 'editor.css' ) ].concat( config.contentsCss ), + css : editor.skin.editor.css.concat( config.contentsCss ), voiceLabel : lang.panelVoiceLabel }, diff --git a/_source/plugins/stylescombo/styles/default.js b/_source/plugins/stylescombo/styles/default.js index 1ccce45..1d98e26 100644 --- a/_source/plugins/stylescombo/styles/default.js +++ b/_source/plugins/stylescombo/styles/default.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/tab/plugin.js b/_source/plugins/tab/plugin.js index 4924ad3..c014d00 100644 --- a/_source/plugins/tab/plugin.js +++ b/_source/plugins/tab/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/table/dialogs/table.js b/_source/plugins/table/dialogs/table.js index 02031f4..45cab90 100644 --- a/_source/plugins/table/dialogs/table.js +++ b/_source/plugins/table/dialogs/table.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -68,6 +68,12 @@ For licensing, see LICENSE.html or http://ckeditor.com/license }, onOk : function() { + if ( this._.selectedElement ) + { + var selection = editor.getSelection(), + bms = editor.getSelection().createBookmarks(); + } + var table = this._.selectedElement || makeElement( 'table' ), me = this, data = {}; @@ -174,22 +180,28 @@ For licensing, see LICENSE.html or http://ckeditor.com/license // Set the width and height. var styles = []; if ( info.txtHeight ) - styles.push( 'height:' + info.txtHeight + 'px' ); + table.setStyle( 'height', CKEDITOR.tools.cssLength( info.txtHeight ) ); + else + table.removeStyle( 'height' ); + if ( info.txtWidth ) { var type = info.cmbWidthType || 'pixels'; - styles.push( 'width:' + info.txtWidth + ( type == 'pixels' ? 'px' : '%' ) ); + table.setStyle( 'width', info.txtWidth + ( type == 'pixels' ? 'px' : '%' ) ); } - styles = styles.join( ';' ); - if ( styles ) - table.$.style.cssText = styles; else + table.removeStyle( 'width' ); + + if( !table.getAttribute( 'style' ) ) table.removeAttribute( 'style' ); } // Insert the table element if we're creating one. if ( !this._.selectedElement ) editor.insertElement( table ); + // Properly restore the selection inside table. (#4822) + else + selection.selectBookmarks( bms ); return true; }, diff --git a/_source/plugins/table/plugin.js b/_source/plugins/table/plugin.js index e8b6de1..61cf362 100644 --- a/_source/plugins/table/plugin.js +++ b/_source/plugins/table/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/tabletools/dialogs/tableCell.js b/_source/plugins/tabletools/dialogs/tableCell.js index f89de23..caf406f 100644 --- a/_source/plugins/tabletools/dialogs/tableCell.js +++ b/_source/plugins/tabletools/dialogs/tableCell.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/tabletools/plugin.js b/_source/plugins/tabletools/plugin.js index a2104a5..e93a007 100644 --- a/_source/plugins/tabletools/plugin.js +++ b/_source/plugins/tabletools/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -86,158 +86,6 @@ For licensing, see LICENSE.html or http://ckeditor.com/license return retval; } - function createTableMap( $refCell ) - { - var refCell = new CKEDITOR.dom.element( $refCell ); - var $table = ( refCell.getName() == 'table' ? $refCell : refCell.getAscendant( 'table' ) ).$; - var $rows = $table.rows; - - // Row and column counters. - var r = -1; - var map = []; - for ( var i = 0 ; i < $rows.length ; i++ ) - { - r++; - if ( !map[ r ] ) - map[ r ] = []; - - var c = -1; - - for ( var j = 0 ; j < $rows[ i ].cells.length ; j++ ) - { - var $cell = $rows[ i ].cells[ j ]; - - c++; - while ( map[ r ][ c ] ) - c++; - - var colSpan = isNaN( $cell.colSpan ) ? 1 : $cell.colSpan; - var rowSpan = isNaN( $cell.rowSpan ) ? 1 : $cell.rowSpan; - - for ( var rs = 0 ; rs < rowSpan ; rs++ ) - { - if ( !map[ r + rs ] ) - map[ r + rs ] = []; - - for ( var cs = 0 ; cs < colSpan ; cs++ ) - map [ r + rs ][ c + cs ] = $rows[ i ].cells[ j ]; - } - - c += colSpan - 1; - } - } - - return map; - } - - function installTableMap( tableMap, $table ) - { - /* - * IE BUG: rowSpan is always 1 in IE if the cell isn't attached to a row. So - * store is separately in another attribute. (#1917) - */ - var rowSpanAttr = CKEDITOR.env.ie ? '_cke_rowspan' : 'rowSpan'; - - /* - * Disconnect all the cells in tableMap from their parents, set all colSpan - * and rowSpan attributes to 1. - */ - for ( var i = 0 ; i < tableMap.length ; i++ ) - { - for ( var j = 0 ; j < tableMap[ i ].length ; j++ ) - { - var $cell = tableMap[ i ][ j ]; - if ( $cell.parentNode ) - $cell.parentNode.removeChild( $cell ); - $cell.colSpan = $cell[ rowSpanAttr ] = 1; - } - } - - // Scan by rows and set colSpan. - var maxCol = 0; - for ( i = 0 ; i < tableMap.length ; i++ ) - { - for ( j = 0 ; j < tableMap[ i ].length ; j++ ) - { - $cell = tableMap[ i ][ j ]; - if ( !$cell ) - continue; - if ( j > maxCol ) - maxCol = j; - if ( $cell[ '_cke_colScanned' ] ) - continue; - if ( tableMap[ i ][ j - 1 ] == $cell ) - $cell.colSpan++; - if ( tableMap[ i ][ j + 1 ] != $cell ) - $cell[ '_cke_colScanned' ] = 1; - } - } - - // Scan by columns and set rowSpan. - for ( i = 0 ; i <= maxCol ; i++ ) - { - for ( j = 0 ; j < tableMap.length ; j++ ) - { - if ( !tableMap[ j ] ) - continue; - $cell = tableMap[ j ][ i ]; - if ( !$cell || $cell[ '_cke_rowScanned' ] ) - continue; - if ( tableMap[ j - 1 ] && tableMap[ j - 1 ][ i ] == $cell ) - $cell[ rowSpanAttr ]++; - if ( !tableMap[ j + 1 ] || tableMap[ j + 1 ][ i ] != $cell ) - $cell[ '_cke_rowScanned' ] = 1; - } - } - - // Clear all temporary flags. - for ( i = 0 ; i < tableMap.length ; i++ ) - { - for ( j = 0 ; j < tableMap[ i ].length ; j++ ) - { - $cell = tableMap[ i ][ j ]; - removeRawAttribute( $cell, '_cke_colScanned' ); - removeRawAttribute( $cell, '_cke_rowScanned' ); - } - } - - // Insert physical rows and columns to table. - for ( i = 0 ; i < tableMap.length ; i++ ) - { - var $row = $table.ownerDocument.createElement( 'tr' ); - for ( j = 0 ; j < tableMap[ i ].length ; ) - { - $cell = tableMap[ i ][ j ]; - if ( tableMap[ i - 1 ] && tableMap[ i - 1 ][ j ] == $cell ) - { - j += $cell.colSpan; - continue; - } - $row.appendChild( $cell ); - if ( rowSpanAttr != 'rowSpan' ) - { - $cell.rowSpan = $cell[ rowSpanAttr ]; - $cell.removeAttribute( rowSpanAttr ); - } - j += $cell.colSpan; - if ( $cell.colSpan == 1 ) - $cell.removeAttribute( 'colSpan' ); - if ( $cell.rowSpan == 1 ) - $cell.removeAttribute( 'rowSpan' ); - } - - if ( CKEDITOR.env.ie ) - $table.rows[ i ].replaceNode( $row ); - else - { - var dest = new CKEDITOR.dom.element( $table.rows[ i ] ); - var src = new CKEDITOR.dom.element( $row ); - dest.setHtml( '' ); - src.moveChildren( dest ); - } - } - } - function clearRow( $tr ) { // Get the array of row's cells. @@ -416,6 +264,368 @@ For licensing, see LICENSE.html or http://ckeditor.com/license } } + // Remove filler at end and empty spaces around the cell content. + function trimCell( cell ) + { + var bogus = cell.getBogus(); + bogus && bogus.remove(); + cell.trim(); + } + + function placeCursorInCell( cell, placeAtEnd ) + { + var range = new CKEDITOR.dom.range( cell.getDocument() ); + if ( !range[ 'moveToElementEdit' + ( placeAtEnd ? 'End' : 'Start' ) ]( cell ) ) + { + range.selectNodeContents( cell ); + range.collapse( placeAtEnd ? false : true ); + } + range.select( true ); + } + + function buildTableMap( table ) + { + + var aRows = table.$.rows ; + + // Row and Column counters. + var r = -1 ; + + var aMap = []; + + for ( var i = 0 ; i < aRows.length ; i++ ) + { + r++ ; + !aMap[r] && ( aMap[r] = [] ); + + var c = -1 ; + + for ( var j = 0 ; j < aRows[i].cells.length ; j++ ) + { + var oCell = aRows[i].cells[j] ; + + c++ ; + while ( aMap[r][c] ) + c++ ; + + var iColSpan = isNaN( oCell.colSpan ) ? 1 : oCell.colSpan ; + var iRowSpan = isNaN( oCell.rowSpan ) ? 1 : oCell.rowSpan ; + + for ( var rs = 0 ; rs < iRowSpan ; rs++ ) + { + if ( !aMap[r + rs] ) + aMap[r + rs] = new Array() ; + + for ( var cs = 0 ; cs < iColSpan ; cs++ ) + { + aMap[r + rs][c + cs] = aRows[i].cells[j] ; + } + } + + c += iColSpan - 1 ; + } + } + return aMap ; + } + + function cellInRow( tableMap, rowIndex, cell ) + { + var oRow = tableMap[ rowIndex ]; + if( typeof cell == 'undefined' ) + return oRow; + + for ( var c = 0 ; oRow && c < oRow.length ; c++ ) + { + if ( cell.is && oRow[c] == cell.$ ) + return c; + else if( c == cell ) + return new CKEDITOR.dom.element( oRow[ c ] ); + } + return cell.is ? -1 : null; + } + + function cellInCol( tableMap, colIndex, cell ) + { + var oCol = []; + for ( var r = 0; r < tableMap.length; r++ ) + { + var row = tableMap[ r ]; + if( typeof cell == 'undefined' ) + oCol.push( row[ colIndex ] ); + else if( cell.is && row[ colIndex ] == cell.$ ) + return r; + else if( r == cell ) + return new CKEDITOR.dom.element( row[ colIndex ] ); + } + + return ( typeof cell == 'undefined' )? oCol : cell.is ? -1 : null; + } + + function mergeCells( selection, mergeDirection, isDetect ) + { + var cells = getSelectedCells( selection ); + + // Invalid merge request if: + // 1. In batch mode despite that less than two selected. + // 2. In solo mode while not exactly only one selected. + // 3. Cells distributed in different table groups (e.g. from both thead and tbody). + var commonAncestor; + if ( ( mergeDirection ? cells.length != 1 : cells.length < 2 ) + || ( commonAncestor = selection.getCommonAncestor() ) + && commonAncestor.type == CKEDITOR.NODE_ELEMENT + && commonAncestor.is( 'table' ) ) + { + return false; + } + + var cell, + firstCell = cells[ 0 ], + table = firstCell.getAscendant( 'table' ), + map = buildTableMap( table ), + mapHeight = map.length, + mapWidth = map[ 0 ].length, + startRow = firstCell.getParent().$.rowIndex, + startColumn = cellInRow( map, startRow, firstCell ); + + if( mergeDirection ) + { + var targetCell; + try + { + targetCell = + map[ mergeDirection == 'up' ? + ( startRow - 1 ): + mergeDirection == 'down' ? ( startRow + 1 ) : startRow ] [ + mergeDirection == 'left' ? + ( startColumn - 1 ): + mergeDirection == 'right' ? ( startColumn + 1 ) : startColumn ]; + + } + catch( er ) + { + return false; + } + + // 1. No cell could be merged. + // 2. Same cell actually. + if( !targetCell || firstCell.$ == targetCell ) + return false; + + // Sort in map order regardless of the DOM sequence. + cells[ ( mergeDirection == 'up' || mergeDirection == 'left' ) ? + 'unshift' : 'push' ]( new CKEDITOR.dom.element( targetCell ) ); + } + + // Start from here are merging way ignorance (merge up/right, batch merge). + var doc = firstCell.getDocument(), + lastRowIndex = startRow, + totalRowSpan = 0, + totalColSpan = 0, + // Use a documentFragment as buffer when appending cell contents. + frag = !isDetect && new CKEDITOR.dom.documentFragment( doc ), + dimension = 0; + + for ( var i = 0; i < cells.length; i++ ) + { + cell = cells[ i ]; + + var tr = cell.getParent(), + cellFirstChild = cell.getFirst(), + colSpan = cell.$.colSpan, + rowSpan = cell.$.rowSpan, + rowIndex = tr.$.rowIndex, + colIndex = cellInRow( map, rowIndex, cell ); + + // Accumulated the actual places taken by all selected cells. + dimension += colSpan * rowSpan; + // Accumulated the maximum virtual spans from column and row. + totalColSpan = Math.max( totalColSpan, colIndex - startColumn + colSpan ) ; + totalRowSpan = Math.max( totalRowSpan, rowIndex - startRow + rowSpan ); + + if ( !isDetect ) + { + // Trim all cell fillers and check to remove empty cells. + if( trimCell( cell ), cell.getChildren().count() ) + { + // Merge vertically cells as two separated paragraphs. + if( rowIndex != lastRowIndex + && cellFirstChild + && !( cellFirstChild.isBlockBoundary + && cellFirstChild.isBlockBoundary( { br : 1 } ) ) ) + { + var last = frag.getLast( CKEDITOR.dom.walker.whitespaces( true ) ); + if( last && !( last.is && last.is( 'br' ) ) ) + frag.append( new CKEDITOR.dom.element( 'br' ) ); + } + + cell.moveChildren( frag ); + } + i ? cell.remove() : cell.setHtml( '' ); + } + lastRowIndex = rowIndex; + } + + if ( !isDetect ) + { + frag.moveChildren( firstCell ); + + if( !CKEDITOR.env.ie ) + firstCell.appendBogus(); + + if( totalColSpan >= mapWidth ) + firstCell.removeAttribute( 'rowSpan' ); + else + firstCell.$.rowSpan = totalRowSpan; + + if( totalRowSpan >= mapHeight ) + firstCell.removeAttribute( 'colSpan' ); + else + firstCell.$.colSpan = totalColSpan; + + // Swip empty <tr> left at the end of table due to the merging. + var trs = new CKEDITOR.dom.nodeList( table.$.rows ), + count = trs.count(); + + for ( i = count - 1; i >= 0; i-- ) + { + var tailTr = trs.getItem( i ); + if( !tailTr.$.cells.length ) + { + tailTr.remove(); + count++; + continue; + } + } + + return firstCell; + } + // Be able to merge cells only if actual dimension of selected + // cells equals to the caculated rectangle. + else + return ( totalRowSpan * totalColSpan ) == dimension; + } + + function verticalSplitCell ( selection, isDetect ) + { + var cells = getSelectedCells( selection ); + if( cells.length > 1 ) + return false; + else if( isDetect ) + return true; + + var cell = cells[ 0 ], + tr = cell.getParent(), + table = tr.getAscendant( 'table' ), + map = buildTableMap( table ), + rowIndex = tr.$.rowIndex, + colIndex = cellInRow( map, rowIndex, cell ), + rowSpan = cell.$.rowSpan, + newCell, + newRowSpan, + newCellRowSpan, + newRowIndex; + + if( rowSpan > 1 ) + { + newRowSpan = Math.ceil( rowSpan / 2 ); + newCellRowSpan = Math.floor( rowSpan / 2 ); + newRowIndex = rowIndex + newRowSpan; + var newCellTr = new CKEDITOR.dom.element( table.$.rows[ newRowIndex ] ), + newCellRow = cellInRow( map, newRowIndex ), + candidateCell; + + newCell = cell.clone(); + + // Figure out where to insert the new cell by checking the vitual row. + for ( var c = 0; c < newCellRow.length; c++ ) + { + candidateCell = newCellRow[ c ]; + // Catch first cell actually following the column. + if( candidateCell.parentNode == newCellTr.$ + && c > colIndex ) + { + newCell.insertBefore( new CKEDITOR.dom.element( candidateCell ) ); + break; + } + else + candidateCell = null; + } + + // The destination row is empty, append at will. + if( !candidateCell ) + newCellTr.append( newCell, true ); + } + else + { + newCellRowSpan = newRowSpan = 1; + + newCellTr = tr.clone(); + newCellTr.insertAfter( tr ); + newCellTr.append( newCell = cell.clone() ); + + var cellsInSameRow = cellInRow( map, rowIndex ); + for ( var i = 0; i < cellsInSameRow.length; i++ ) + cellsInSameRow[ i ].rowSpan++; + } + + if( !CKEDITOR.env.ie ) + newCell.appendBogus(); + + cell.$.rowSpan = newRowSpan; + newCell.$.rowSpan = newCellRowSpan; + if( newRowSpan == 1 ) + cell.removeAttribute( 'rowSpan' ); + if( newCellRowSpan == 1 ) + newCell.removeAttribute( 'rowSpan' ); + + return newCell; + } + + function horizontalSplitCell( selection, isDetect ) + { + var cells = getSelectedCells( selection ); + if( cells.length > 1 ) + return false; + else if( isDetect ) + return true; + + var cell = cells[ 0 ], + tr = cell.getParent(), + table = tr.getAscendant( 'table' ), + map = buildTableMap( table ), + rowIndex = tr.$.rowIndex, + colIndex = cellInRow( map, rowIndex, cell ), + colSpan = cell.$.colSpan, + newCell, + newColSpan, + newCellColSpan; + + if( colSpan > 1 ) + { + newColSpan = Math.ceil( colSpan / 2 ); + newCellColSpan = Math.floor( colSpan / 2 ); + } + else + { + newCellColSpan = newColSpan = 1; + var cellsInSameCol = cellInCol( map, colIndex ); + for ( var i = 0; i < cellsInSameCol.length; i++ ) + cellsInSameCol[ i ].colSpan++; + } + newCell = cell.clone(); + newCell.insertAfter( cell ); + if( !CKEDITOR.env.ie ) + newCell.appendBogus(); + + cell.$.colSpan = newColSpan; + newCell.$.colSpan = newCellColSpan; + if( newColSpan == 1 ) + cell.removeAttribute( 'colSpan' ); + if( newCellColSpan == 1 ) + newCell.removeAttribute( 'colSpan' ); + + return newCell; + } // Context menu on table caption incorrect (#3834) var contextMenuTags = { thead : 1, tbody : 1, tfoot : 1, td : 1, tr : 1, th : 1 }; @@ -516,6 +726,46 @@ For licensing, see LICENSE.html or http://ckeditor.com/license } } ); + editor.addCommand( 'cellMerge', + { + exec : function( editor ) + { + placeCursorInCell( mergeCells( editor.getSelection() ), true ); + } + } ); + + editor.addCommand( 'cellMergeRight', + { + exec : function( editor ) + { + placeCursorInCell( mergeCells( editor.getSelection(), 'right' ), true ); + } + } ); + + editor.addCommand( 'cellMergeDown', + { + exec : function( editor ) + { + placeCursorInCell( mergeCells( editor.getSelection(), 'down' ), true ); + } + } ); + + editor.addCommand( 'cellVerticalSplit', + { + exec : function( editor ) + { + placeCursorInCell( verticalSplitCell( editor.getSelection() ) ); + } + } ); + + editor.addCommand( 'cellHorizontalSplit', + { + exec : function( editor ) + { + placeCursorInCell( horizontalSplitCell( editor.getSelection() ) ); + } + } ); + editor.addCommand( 'cellInsertBefore', { exec : function( editor ) @@ -546,11 +796,17 @@ For licensing, see LICENSE.html or http://ckeditor.com/license order : 1, getItems : function() { - var cells = getSelectedCells( editor.getSelection() ); + var selection = editor.getSelection(), + cells = getSelectedCells( selection ); return { tablecell_insertBefore : CKEDITOR.TRISTATE_OFF, tablecell_insertAfter : CKEDITOR.TRISTATE_OFF, tablecell_delete : CKEDITOR.TRISTATE_OFF, + tablecell_merge : mergeCells( selection, null, true ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED, + tablecell_merge_right : mergeCells( selection, 'right', true ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED, + tablecell_merge_down : mergeCells( selection, 'down', true ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED, + tablecell_split_vertical : verticalSplitCell( selection, true ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED, + tablecell_split_horizontal : horizontalSplitCell( selection, true ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED, tablecell_properties : cells.length > 0 ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED }; } @@ -580,12 +836,52 @@ For licensing, see LICENSE.html or http://ckeditor.com/license order : 15 }, + tablecell_merge : + { + label : lang.cell.merge, + group : 'tablecell', + command : 'cellMerge', + order : 16 + }, + + tablecell_merge_right : + { + label : lang.cell.mergeRight, + group : 'tablecell', + command : 'cellMergeRight', + order : 17 + }, + + tablecell_merge_down : + { + label : lang.cell.mergeDown, + group : 'tablecell', + command : 'cellMergeDown', + order : 18 + }, + + tablecell_split_horizontal : + { + label : lang.cell.splitHorizontal, + group : 'tablecell', + command : 'cellHorizontalSplit', + order : 19 + }, + + tablecell_split_vertical : + { + label : lang.cell.splitVertical, + group : 'tablecell', + command : 'cellVerticalSplit', + order : 20 + }, + tablecell_properties : { label : lang.cell.title, group : 'tablecellproperties', command : 'cellProperties', - order : 20 + order : 21 }, tablerow : diff --git a/_source/plugins/templates/dialogs/templates.js b/_source/plugins/templates/dialogs/templates.js index 52d9692..732511f 100644 --- a/_source/plugins/templates/dialogs/templates.js +++ b/_source/plugins/templates/dialogs/templates.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -81,14 +81,24 @@ For licensing, see LICENSE.html or http://ckeditor.com/license if( isInsert ) { + // Everything should happen after the document is loaded (#4073). + editor.on( 'contentDom', function( evt ) + { + evt.removeListener(); + dialog.hide(); + + // Place the cursor at the first editable place. + var range = new CKEDITOR.dom.range( editor.document ); + range.moveToElementEditStart( editor.document.getBody() ); + range.select( true ); + } ); editor.setData( html ); } else { editor.insertHtml( html ); + dialog.hide(); } - - dialog.hide(); } CKEDITOR.dialog.add( 'templates', function( editor ) diff --git a/_source/plugins/templates/plugin.js b/_source/plugins/templates/plugin.js index c2d232c..0c1ae27 100644 --- a/_source/plugins/templates/plugin.js +++ b/_source/plugins/templates/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/templates/templates/default.js b/_source/plugins/templates/templates/default.js index c433dc3..dc8ef91 100644 --- a/_source/plugins/templates/templates/default.js +++ b/_source/plugins/templates/templates/default.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/toolbar/plugin.js b/_source/plugins/toolbar/plugin.js index 136ca9d..dce5cb7 100644 --- a/_source/plugins/toolbar/plugin.js +++ b/_source/plugins/toolbar/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -113,7 +113,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license editor.toolbox = new toolbox(); var output = [ '<div class="cke_toolbox"' ], - expanded = editor.config.toolbarStartupExpanded, + expanded = editor.config.toolbarStartupExpanded !== false, groupStarted; output.push( expanded ? '>' : ' style="display:none">' ); @@ -252,11 +252,13 @@ For licensing, see LICENSE.html or http://ckeditor.com/license { toolbox.hide(); collapser.addClass( 'cke_toolbox_collapser_min' ); + collapser.setAttribute( 'title', editor.lang.toolbarExpand ); } else { toolbox.show(); collapser.removeClass( 'cke_toolbox_collapser_min' ); + collapser.setAttribute( 'title', editor.lang.toolbarCollapse ); } var dy = toolboxContainer.$.offsetHeight - previousHeight; @@ -266,7 +268,8 @@ For licensing, see LICENSE.html or http://ckeditor.com/license modes : { wysiwyg : 1, source : 1 } } ); - output.push( '<a id="' + collapserId + '" class="cke_toolbox_collapser' ); + output.push( '<a title="' + ( expanded ? editor.lang.toolbarCollapse : editor.lang.toolbarExpand ) + + '" id="' + collapserId + '" class="cke_toolbox_collapser' ); if ( !expanded ) output.push( ' cke_toolbox_collapser_min' ); @@ -364,7 +367,7 @@ CKEDITOR.config.toolbar_Full = ['Form', 'Checkbox', 'Radio', 'TextField', 'Textarea', 'Select', 'Button', 'ImageButton', 'HiddenField'], '/', ['Bold','Italic','Underline','Strike','-','Subscript','Superscript'], - ['NumberedList','BulletedList','-','Outdent','Indent','Blockquote'], + ['NumberedList','BulletedList','-','Outdent','Indent','Blockquote','CreateDiv'], ['JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock'], ['Link','Unlink','Anchor'], ['Image','Flash','Table','HorizontalRule','Smiley','SpecialChar','PageBreak'], @@ -404,9 +407,9 @@ CKEDITOR.config.toolbarCanCollapse = true; /** * Whether the toolbar must start expanded when the editor is loaded. + * @name CKEDITOR.config.toolbarStartupExpanded * @type Boolean * @default true * @example * config.toolbarStartupExpanded = false; */ -CKEDITOR.config.toolbarStartupExpanded = true; diff --git a/_source/plugins/uicolor/dialogs/uicolor.js b/_source/plugins/uicolor/dialogs/uicolor.js index 34f4448..a2e23eb 100644 --- a/_source/plugins/uicolor/dialogs/uicolor.js +++ b/_source/plugins/uicolor/dialogs/uicolor.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/uicolor/lang/en.js b/_source/plugins/uicolor/lang/en.js index 4d8e25d..ef6bc22 100644 --- a/_source/plugins/uicolor/lang/en.js +++ b/_source/plugins/uicolor/lang/en.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/uicolor/plugin.js b/_source/plugins/uicolor/plugin.js index d178db4..3c354ef 100644 --- a/_source/plugins/uicolor/plugin.js +++ b/_source/plugins/uicolor/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/undo/plugin.js b/_source/plugins/undo/plugin.js index f2e8362..f532d45 100644 --- a/_source/plugins/undo/plugin.js +++ b/_source/plugins/undo/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -130,11 +130,24 @@ For licensing, see LICENSE.html or http://ckeditor.com/license this.contents = this.contents.replace( /\s+_cke_expando=".*?"/g, '' ); } + // Attributes that browser may changing them when setting via innerHTML. + var protectedAttrs = /\b(?:href|src|name)="[^"]*?"/gi; + Image.prototype = { equals : function( otherImage, contentOnly ) { - if ( this.contents != otherImage.contents ) + var thisContents = this.contents, + otherContents = otherImage.contents; + + // For IE6/7 : Comparing only the protected attribute values but not the original ones.(#4522) + if( CKEDITOR.env.ie && ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) ) + { + thisContents = thisContents.replace( protectedAttrs, '' ); + otherContents = otherContents.replace( protectedAttrs, '' ); + } + + if( thisContents != otherContents ) return false; if ( contentOnly ) @@ -179,6 +192,11 @@ For licensing, see LICENSE.html or http://ckeditor.com/license this.reset(); } + + var editingKeyCodes = { /*Backspace*/ 8:1, /*Delete*/ 46:1 }, + modifierKeyCodes = { /*Shift*/ 16:1, /*Ctrl*/ 17:1, /*Alt*/ 18:1 }, + navigationKeyCodes = { 37:1, 38:1, 39:1, 40:1 }; // Arrows: L, T, R, B + UndoManager.prototype = { /** @@ -187,32 +205,26 @@ For licensing, see LICENSE.html or http://ckeditor.com/license */ type : function( event ) { - var keystroke = event && event.data.getKeystroke(), - - // Backspace, Delete - modifierCodes = { 8:1, 46:1 }, - // Keystrokes which will modify the contents. - isModifier = keystroke in modifierCodes, - wasModifier = this.lastKeystroke in modifierCodes, - lastWasSameModifier = isModifier && keystroke == this.lastKeystroke, - - // Arrows: L, T, R, B - resetTypingCodes = { 37:1, 38:1, 39:1, 40:1 }, + var keystroke = event && event.data.getKey(), + isModifierKey = keystroke in modifierKeyCodes, + isEditingKey = keystroke in editingKeyCodes, + wasEditingKey = this.lastKeystroke in editingKeyCodes, + sameAsLastEditingKey = isEditingKey && keystroke == this.lastKeystroke, // Keystrokes which navigation through contents. - isReset = keystroke in resetTypingCodes, - wasReset = this.lastKeystroke in resetTypingCodes, + isReset = keystroke in navigationKeyCodes, + wasReset = this.lastKeystroke in navigationKeyCodes, // Keystrokes which just introduce new contents. - isContent = ( !isModifier && !isReset ), + isContent = ( !isEditingKey && !isReset ), // Create undo snap for every different modifier key. - modifierSnapshot = ( isModifier && !lastWasSameModifier ), + modifierSnapshot = ( isEditingKey && !sameAsLastEditingKey ), // Create undo snap on the following cases: - // 1. Just start to type. + // 1. Just start to type . // 2. Typing some content after a modifier. // 3. Typing some content after make a visible selection. - startedTyping = !this.typing - || ( isContent && ( wasModifier || wasReset ) ); + startedTyping = !( isModifierKey || this.typing ) + || ( isContent && ( wasEditingKey || wasReset ) ); if ( startedTyping || modifierSnapshot ) { @@ -230,6 +242,9 @@ For licensing, see LICENSE.html or http://ckeditor.com/license if ( beforeTypeImage.contents != currentSnapshot ) { + // It's safe to now indicate typing state. + this.typing = true; + // This's a special save, with specified snapshot // and without auto 'fireChange'. if ( !this.save( false, beforeTypeImage, false ) ) @@ -250,15 +265,16 @@ For licensing, see LICENSE.html or http://ckeditor.com/license } this.lastKeystroke = keystroke; + // Create undo snap after typed too much (over 25 times). - if ( isModifier ) + if ( isEditingKey ) { this.typesCount = 0; this.modifiersCount++; if ( this.modifiersCount > 25 ) { - this.save(); + this.save( false, null, false ); this.modifiersCount = 1; } } @@ -269,12 +285,11 @@ For licensing, see LICENSE.html or http://ckeditor.com/license if ( this.typesCount > 25 ) { - this.save(); + this.save( false, null, false ); this.typesCount = 1; } } - this.typing = true; }, reset : function() // Reset the undo stack. diff --git a/_source/plugins/wsc/dialogs/ciframe.html b/_source/plugins/wsc/dialogs/ciframe.html index 33ba3f2..ca742c6 100644 --- a/_source/plugins/wsc/dialogs/ciframe.html +++ b/_source/plugins/wsc/dialogs/ciframe.html @@ -1,6 +1,6 @@ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <!-- -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license --> <html> diff --git a/_source/plugins/wsc/dialogs/tmpFrameset.html b/_source/plugins/wsc/dialogs/tmpFrameset.html index 207d116..c7f7ab5 100644 --- a/_source/plugins/wsc/dialogs/tmpFrameset.html +++ b/_source/plugins/wsc/dialogs/tmpFrameset.html @@ -1,6 +1,6 @@ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd"> <!-- -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license --> <html> diff --git a/_source/plugins/wsc/dialogs/wsc.css b/_source/plugins/wsc/dialogs/wsc.css index ea324a3..b347540 100644 --- a/_source/plugins/wsc/dialogs/wsc.css +++ b/_source/plugins/wsc/dialogs/wsc.css @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/wsc/dialogs/wsc.js b/_source/plugins/wsc/dialogs/wsc.js index 9f7496c..534dc69 100644 --- a/_source/plugins/wsc/dialogs/wsc.js +++ b/_source/plugins/wsc/dialogs/wsc.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -84,7 +84,9 @@ CKEDITOR.dialog.add( 'checkspell', function( editor ) window.doSpell({ ctrl : textareaId, - lang : LangComparer.getSPLangCode( editor.langCode ), + + lang : editor.config.wsc_lang || LangComparer.getSPLangCode(editor.langCode ), + intLang: editor.config.wsc_uiLang || LangComparer.getSPLangCode(editor.langCode ), winType : iframeId, // If not defined app will run on winpopup. // Callback binding section. @@ -105,7 +107,12 @@ CKEDITOR.dialog.add( 'checkspell', function( editor ) iframePath : pluginPath + 'ciframe.html', // Styles defining. - schemaURI : pluginPath + 'wsc.css' + schemaURI : pluginPath + 'wsc.css', + + userDictionaryName: editor.config.wsc_userDictionaryName, + customDictionaryName: editor.config.wsc_customDictionaryIds && editor.config.wsc_customDictionaryIds.split(","), + domainName: editor.config.wsc_domainName + }); // Hide user message console (if application was loaded more then after timeout). @@ -114,7 +121,7 @@ CKEDITOR.dialog.add( 'checkspell', function( editor ) } return { - title : editor.lang.spellCheck.title, + title : editor.config.wsc_dialogTitle || editor.lang.spellCheck.title, minWidth : 485, minHeight : 380, buttons : [ CKEDITOR.dialog.cancelButton ], @@ -153,7 +160,7 @@ CKEDITOR.dialog.add( 'checkspell', function( editor ) contents : [ { id : 'general', - label : editor.lang.spellCheck.title, + label : editor.config.wsc_dialogTitle || editor.lang.spellCheck.title, padding : 0, elements : [ { diff --git a/_source/plugins/wsc/plugin.js b/_source/plugins/wsc/plugin.js index efbb92c..ed26864 100644 --- a/_source/plugins/wsc/plugin.js +++ b/_source/plugins/wsc/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ diff --git a/_source/plugins/wysiwygarea/plugin.js b/_source/plugins/wysiwygarea/plugin.js index 973882d..b2364b1 100644 --- a/_source/plugins/wysiwygarea/plugin.js +++ b/_source/plugins/wysiwygarea/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -10,18 +10,18 @@ For licensing, see LICENSE.html or http://ckeditor.com/license (function() { - /** - * List of elements in which has no way to move editing focus outside. - */ + // List of elements in which has no way to move editing focus outside. var nonExitableElementNames = { table:1,pre:1 }; + // Matching an empty paragraph at the end of document. - var emptyParagraphRegexp = /\s*<(p|div|address|h\d|center)[^>]*>\s*(?:<br[^>]*>| | )\s*(:?<\/\1>)?\s*$/gi; + var emptyParagraphRegexp = /\s*<(p|div|address|h\d|center)[^>]*>\s*(?:<br[^>]*>| |\u00A0| )?\s*(:?<\/\1>)?\s*(?=$|<\/body>)/gi; function onInsertHtml( evt ) { if ( this.mode == 'wysiwyg' ) { this.focus(); + this.fire( 'saveSnapshot' ); var selection = this.getSelection(), data = evt.data; @@ -46,6 +46,11 @@ For licensing, see LICENSE.html or http://ckeditor.com/license } else this.document.$.execCommand( 'inserthtml', false, data ); + + CKEDITOR.tools.setTimeout( function() + { + this.fire( 'saveSnapshot' ); + }, 0, this ); } } @@ -88,9 +93,12 @@ For licensing, see LICENSE.html or http://ckeditor.com/license && ( dtd = CKEDITOR.dtd[ current.getName() ] ) && !( dtd && dtd [ elementName ] ) ) { + // Split up inline elements. + if ( current.getName() in CKEDITOR.dtd.span ) + range.splitElement( current ); // If we're in an empty block which indicate a new paragraph, // simply replace it with the inserting block.(#3664) - if ( range.checkStartOfBlock() + else if ( range.checkStartOfBlock() && range.checkEndOfBlock() ) { range.setStartBefore( current ); @@ -139,6 +147,20 @@ For licensing, see LICENSE.html or http://ckeditor.com/license setTimeout( function(){ editor.resetDirty(); } ); } + var isNotWhitespace = CKEDITOR.dom.walker.whitespaces( true ), + isNotBookmark = CKEDITOR.dom.walker.bookmark( false, true ); + + function isNotEmpty( node ) + { + return isNotWhitespace( node ) && isNotBookmark( node ); + } + + function isNbsp( node ) + { + return node.type == CKEDITOR.NODE_TEXT + && CKEDITOR.tools.trim( node.getText() ).match( /^(?: |\xa0)$/ ); + } + /** * Auto-fixing block-less content by wrapping paragraph (#3190), prevent * non-exitable-block by padding extra br.(#3189) @@ -161,61 +183,52 @@ For licensing, see LICENSE.html or http://ckeditor.com/license && !path.block ) { restoreDirty( editor ); - var bms = selection.createBookmarks(), - fixedBlock = range.fixBlock( true, + var fixedBlock = range.fixBlock( true, editor.config.enterMode == CKEDITOR.ENTER_DIV ? 'div' : 'p' ); - // For IE, we'll be removing any bogus br ( introduce by fixing body ) - // right now to prevent it introducing visual line break. + // For IE, we should remove any filler node which was introduced before. if ( CKEDITOR.env.ie ) { - var brNodeList = fixedBlock.getElementsByTag( 'br' ), brNode; - for ( var i = 0 ; i < brNodeList.count() ; i++ ) - { - if( ( brNode = brNodeList.getItem( i ) ) && brNode.hasAttribute( '_cke_bogus' ) ) - brNode.remove(); - } + var first = fixedBlock.getFirst( isNotEmpty ); + first && isNbsp( first ) && first.remove(); } - selection.selectBookmarks( bms ); + // If the fixed block is blank and already followed by a exitable + // block, we should revert the fix. (#3684) + if( fixedBlock.getOuterHtml().match( emptyParagraphRegexp ) ) + { + var previousElement = fixedBlock.getPrevious( isNotWhitespace ), + nextElement = fixedBlock.getNext( isNotWhitespace ); - // If the fixed block is blank and is already followed by a exitable - // block, we should drop it and move to the exist block(#3684). - var children = fixedBlock.getChildren(), - count = children.count(), - firstChild, - whitespaceGuard = CKEDITOR.dom.walker.whitespaces( true ), - previousElement = fixedBlock.getPrevious( whitespaceGuard ), - nextElement = fixedBlock.getNext( whitespaceGuard ), - enterBlock; - if ( previousElement && previousElement.getName - && !( previousElement.getName() in nonExitableElementNames ) ) - enterBlock = previousElement; - else if ( nextElement && nextElement.getName - && !( nextElement.getName() in nonExitableElementNames ) ) - enterBlock = nextElement; - // Not all blocks are editable, e.g. <hr />, further checking it.(#3994) - if( ( !count - || ( firstChild = children.getItem( 0 ) ) && firstChild.is && firstChild.is( 'br' ) ) - && enterBlock - && range.moveToElementEditStart( enterBlock ) ) - { - fixedBlock.remove(); - range.select(); + if ( previousElement && previousElement.getName + && !( previousElement.getName() in nonExitableElementNames ) + && range.moveToElementEditStart( previousElement ) + || nextElement && nextElement.getName + && !( nextElement.getName() in nonExitableElementNames ) + && range.moveToElementEditStart( nextElement ) ) + { + fixedBlock.remove(); + } } + + range.select(); + // Notify non-IE that selection has changed. + if( !CKEDITOR.env.ie ) + editor.selectionChange(); } - // Inserting the padding-br before body if it's preceded by an - // unexitable block. + // All browsers are incapable to moving cursor out of certain non-exitable + // blocks (e.g. table, list, pre) at the end of document, make this happen by + // place a bogus node there, which would be later removed by dataprocessor. var lastNode = body.getLast( CKEDITOR.dom.walker.whitespaces( true ) ); if ( lastNode && lastNode.getName && ( lastNode.getName() in nonExitableElementNames ) ) { restoreDirty( editor ); - var paddingBlock = editor.document.createElement( - ( CKEDITOR.env.ie && enterMode != CKEDITOR.ENTER_BR ) ? - '<br _cke_bogus="true" />' : 'br' ); - body.append( paddingBlock ); + if( !CKEDITOR.env.ie ) + body.appendBogus(); + else + body.append( editor.document.createText( '\xa0' ) ); } } @@ -242,7 +255,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license var isCustomDomain = CKEDITOR.env.isCustomDomain(); // Creates the iframe that holds the editable document. - var createIFrame = function() + var createIFrame = function( data ) { if ( iframe ) iframe.remove(); @@ -250,46 +263,41 @@ For licensing, see LICENSE.html or http://ckeditor.com/license fieldset.remove(); frameLoaded = 0; - // The document domain must be set within the src - // attribute; - // Defer the script execution until iframe - // has been added to main window, this is needed for some - // browsers which will begin to load the frame content - // prior to it's presentation in DOM.(#3894) - var src = 'void( ' - + ( CKEDITOR.env.gecko ? 'setTimeout' : '' ) + '( function(){' + - 'document.open();' + - ( CKEDITOR.env.ie && isCustomDomain ? 'document.domain="' + document.domain + '";' : '' ) + - 'document.write( window.parent[ "_cke_htmlToLoad_' + editor.name + '" ] );' + - 'document.close();' + - 'window.parent[ "_cke_htmlToLoad_' + editor.name + '" ] = null;' + - '}' - + ( CKEDITOR.env.gecko ? ', 0 )' : ')()' ) - + ' )'; - - // Loading via src attribute does not work in Opera. - if ( CKEDITOR.env.opera ) - src = 'void(0);'; iframe = CKEDITOR.dom.element.createFromHtml( '<iframe' + - ' style="width:100%;height:100%"' + - ' frameBorder="0"' + - ' tabIndex="-1"' + - ' allowTransparency="true"' + - ' src="javascript:' + encodeURIComponent( src ) + '"' + - '></iframe>' ); + ' style="width:100%;height:100%"' + + ' frameBorder="0"' + + // Support for custom document.domain in IE. + ( isCustomDomain ? + ' src="javascript:void((function(){' + + 'document.open();' + + 'document.domain=\'' + document.domain + '\';' + + 'document.close();' + + '})())"' : '' ) + + ' tabIndex="-1"' + + ' allowTransparency="true"' + + '></iframe>' ); + + // Register onLoad event for iframe element, which + // will fill it with content and set custom domain. + iframe.on( 'load', function( e ) + { + e.removeListener(); + var doc = iframe.getFrameDocument().$; + + // Custom domain handling is needed after each document.open(). + doc.open(); + if ( isCustomDomain ) + doc.domain = document.domain; + doc.write( data ); + doc.close(); + + } ); var accTitle = editor.lang.editorTitle.replace( '%1', editor.name ); if ( CKEDITOR.env.gecko ) { - // Double checking the iframe will be loaded properly(#4058). - iframe.on( 'load', function( ev ) - { - ev.removeListener(); - contentDomReady( iframe.$.contentWindow ); - } ); - // Accessibility attributes for Firefox. mainElement.setAttributes( { @@ -329,16 +337,11 @@ For licensing, see LICENSE.html or http://ckeditor.com/license mainElement.append( iframe ); }; - // The script that is appended to the data being loaded. It - // enables editing, and makes some + // The script that launches the bootstrap logic on 'domReady', so the document + // is fully editable even before the editing iframe is fully loaded (#4455). var activationScript = '<script id="cke_actscrpt" type="text/javascript">' + - 'window.onload = function()' + - '{' + - // Call the temporary function for the editing - // boostrap. - 'window.parent.CKEDITOR._["contentDomReady' + editor.name + '"]( window );' + - '}' + + 'window.parent.CKEDITOR._["contentDomReady' + editor.name + '"]( window );' + '</script>'; // Editing area bootstrap code. @@ -411,6 +414,22 @@ For licensing, see LICENSE.html or http://ckeditor.com/license } ); } + // IE standard compliant in editing frame doesn't focus the editor when + // clicking outside actual content, manually apply the focus. (#1659) + if( CKEDITOR.env.ie + && domDocument.$.compatMode == 'CSS1Compat' ) + { + var htmlElement = domDocument.getDocumentElement(); + htmlElement.on( 'mousedown', function( evt ) + { + // Setting focus directly on editor doesn't work, we + // have to use here a temporary element to 'redirect' + // the focus. + if ( evt.data.getTarget().equals( htmlElement ) ) + ieFocusGrabber.focus(); + } ); + } + var focusTarget = ( CKEDITOR.env.ie || CKEDITOR.env.webkit ) ? domWindow : domDocument; @@ -454,13 +473,13 @@ For licensing, see LICENSE.html or http://ckeditor.com/license if ( keystrokeHandler ) keystrokeHandler.attach( domDocument ); - // Cancel default action for backspace in IE on control types. (#4047) if ( CKEDITOR.env.ie ) { - editor.on( 'key', function( event ) + // Cancel default action for backspace in IE on control types. (#4047) + domDocument.on( 'keydown', function( evt ) { // Backspace. - var control = event.data.keyCode == 8 + var control = evt.data.getKeystroke() == 8 && editor.getSelection().getSelectedElement(); if ( control ) { @@ -469,14 +488,31 @@ For licensing, see LICENSE.html or http://ckeditor.com/license // Remove manually. control.remove(); editor.fire( 'saveSnapshot' ); - event.cancel(); + evt.cancel(); } } ); + + // PageUp/PageDown scrolling is broken in document + // with standard doctype, manually fix it. (#4736) + if( domDocument.$.compatMode == 'CSS1Compat' ) + { + var pageUpDownKeys = { 33 : 1, 34 : 1 }; + domDocument.on( 'keydown', function( evt ) + { + if( evt.data.getKeystroke() in pageUpDownKeys ) + { + setTimeout( function () + { + editor.getSelection().scrollIntoView(); + }, 0 ); + } + } ); + } } // Adds the document body as a context menu target. if ( editor.contextMenu ) - editor.contextMenu.addTarget( domDocument ); + editor.contextMenu.addTarget( domDocument, editor.config.browserContextMenuOnCtrl !== false ); setTimeout( function() { @@ -550,54 +586,107 @@ For licensing, see LICENSE.html or http://ckeditor.com/license { isLoadingData = true; + var config = editor.config, + fullPage = config.fullPage, + docType = config.docType; + + // Build the additional stuff to be included into <head>. + var headExtra = + '<style type="text/css" cke_temp="1">' + + editor._.styles.join( '\n' ) + + '</style>'; + + !fullPage && ( headExtra = + CKEDITOR.tools.buildStyleHtml( editor.config.contentsCss ) + + headExtra ); + + var baseTag = config.baseHref ? '<base href="' + config.baseHref + '" cke_temp="1" />' : ''; + + if ( fullPage ) + { + // Search and sweep out the doctype declaration. + data = data.replace( /<!DOCTYPE[^>]*>/i, function( match ) + { + editor.docType = docType = match; + return ''; + }); + } + // Get the HTML version of the data. if ( editor.dataProcessor ) - { data = editor.dataProcessor.toHtml( data, fixForBody ); - } - data = - editor.config.docType + - '<html dir="' + editor.config.contentsLangDirection + '">' + - '<head>' + - '<link type="text/css" rel="stylesheet" href="' + - [].concat( editor.config.contentsCss ).join( '"><link type="text/css" rel="stylesheet" href="' ) + - '">' + - '<style type="text/css" _fcktemp="true">' + - editor._.styles.join( '\n' ) + - '</style>'+ - '</head>' + - '<body>' + - data + - '</body>' + - '</html>' + - activationScript; + if ( fullPage ) + { + // Check if the <body> tag is available. + if ( !(/<body[\s|>]/).test( data ) ) + data = '<body>' + data; - window[ '_cke_htmlToLoad_' + editor.name ] = data; - CKEDITOR._[ 'contentDomReady' + editor.name ] = contentDomReady; - createIFrame(); + // Check if the <html> tag is available. + if ( !(/<html[\s|>]/).test( data ) ) + data = '<html>' + data + '</html>'; + + // Check if the <head> tag is available. + if ( !(/<head[\s|>]/).test( data ) ) + data = data.replace( /<html[^>]*>/, '$&<head><title></title></head>' ) ; - // Opera must use the old method for loading contents. - if ( CKEDITOR.env.opera ) + // The base must be the first tag in the HEAD, e.g. to get relative + // links on styles. + baseTag && ( data = data.replace( /<head>/, '$&' + baseTag ) ); + + // Inject the extra stuff into <head>. + // Attention: do not change it before testing it well. (V2) + // This is tricky... if the head ends with <meta ... content type>, + // Firefox will break. But, it works if we place our extra stuff as + // the last elements in the HEAD. + data = data.replace( /<\/head\s*>/, headExtra + '$&' ); + + // Add the DOCTYPE back to it. + data = docType + data; + } + else { - var doc = iframe.$.contentWindow.document; - doc.open(); - doc.write( data ); - doc.close(); + data = + config.docType + + '<html dir="' + config.contentsLangDirection + '">' + + '<head>' + + baseTag + + headExtra + + '</head>' + + '<body' + ( config.bodyId ? ' id="' + config.bodyId + '"' : '' ) + + ( config.bodyClass ? ' class="' + config.bodyClass + '"' : '' ) + + '>' + + data + + '</html>'; } + + data += activationScript; + + CKEDITOR._[ 'contentDomReady' + editor.name ] = contentDomReady; + createIFrame( data ); }, getData : function() { - var data = iframe.getFrameDocument().getBody().getHtml(); + var config = editor.config, + fullPage = config.fullPage, + docType = fullPage && editor.docType, + doc = iframe.getFrameDocument(); + + var data = fullPage + ? doc.getDocumentElement().getOuterHtml() + : doc.getBody().getHtml(); if ( editor.dataProcessor ) data = editor.dataProcessor.toDataFormat( data, fixForBody ); // Strip the last blank paragraph within document. - if ( editor.config.ignoreEmptyParagraph ) + if ( config.ignoreEmptyParagraph ) data = data.replace( emptyParagraphRegexp, '' ); + if ( docType ) + data = docType + '\n' + data; + return data; }, @@ -625,6 +714,25 @@ For licensing, see LICENSE.html or http://ckeditor.com/license else if ( editor.window ) { editor.window.focus(); + + // Force the selection to happen, in this way + // we guarantee the focus will be there. (#4848) + if ( CKEDITOR.env.ie ) + { + try + { + var sel = editor.getSelection(); + sel = sel && sel.getNative(); + var range = sel && sel.type && sel.createRange(); + if ( range ) + { + sel.empty(); + range.select(); + } + } + catch (e) {} + } + editor.selectionChange(); } } @@ -635,8 +743,60 @@ For licensing, see LICENSE.html or http://ckeditor.com/license // Auto fixing on some document structure weakness to enhance usabilities. (#3190 and #3189) editor.on( 'selectionChange', onSelectionChangeFixBody, null, null, 1 ); }); + + // Create an invisible element to grab focus. + if( CKEDITOR.env.ie ) + { + var ieFocusGrabber; + editor.on( 'uiReady', function() + { + ieFocusGrabber = editor.container.append( CKEDITOR.dom.element.createFromHtml( + '<input tabindex="-1" style="position:absolute; left:-10000">' ) ); + + ieFocusGrabber.on( 'focus', function() + { + editor.focus(); + } ); + } ); + } } }); + + // Fixing Firefox 'Back-Forward Cache' break design mode. (#4514) + if( CKEDITOR.env.gecko ) + { + var topWin = window.top; + + ( function () + { + var topBody = topWin.document.body; + + if( !topBody ) + topWin.addEventListener( 'load', arguments.callee, false ); + else + { + topBody.setAttribute( 'onpageshow', topBody.getAttribute( 'onpageshow' ) + + ';event.persisted && CKEDITOR.tools.callFunction(' + + CKEDITOR.tools.addFunction( function() + { + var allInstances = CKEDITOR.instances, + editor, + doc; + for( var i in allInstances ) + { + editor = allInstances[ i ]; + doc = editor.document; + if( doc ) + { + doc.$.designMode = 'off'; + doc.$.designMode = 'on'; + } + } + } ) + ')' ); + } + } )(); + + } })(); /** |
