diff options
| author | Lester Caine <lsces@lsces.co.uk> | 2010-07-29 20:31:06 +0100 |
|---|---|---|
| committer | Lester Caine <lsces@lsces.co.uk> | 2010-07-29 20:31:06 +0100 |
| commit | 1a293c5c57779b3750b561b5fe295aff33b3ebf9 (patch) | |
| tree | 254fc36fc473e7d60b88324d550ce95f94e18cca /_source/plugins | |
| parent | 33c69a19cc35f82336bce6020b15e33152ff8763 (diff) | |
| download | ckeditor-1a293c5c57779b3750b561b5fe295aff33b3ebf9.tar.gz ckeditor-1a293c5c57779b3750b561b5fe295aff33b3ebf9.tar.bz2 ckeditor-1a293c5c57779b3750b561b5fe295aff33b3ebf9.zip | |
ync with version 3.3.1 of CKEditor
Diffstat (limited to '_source/plugins')
71 files changed, 29109 insertions, 26831 deletions
diff --git a/_source/plugins/a11yhelp/lang/he.js b/_source/plugins/a11yhelp/lang/he.js new file mode 100644 index 0000000..1f77cf5 --- /dev/null +++ b/_source/plugins/a11yhelp/lang/he.js @@ -0,0 +1,216 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.plugins.setLang( 'a11yhelp', 'he', +{ + accessibilityHelp : + { + title : 'הוראות נגישות', + contents : 'הוראות נגישות. לסגירה לחץ אסקייפ (ESC).', + legend : + [ + { + name : 'כללי', + items : + [ + { + name : 'סרגל הכלים', + legend: + 'לחץ על ${toolbarFocus} כדי לנווט לסרגל הכלים. ' + + 'עבור לכפתור הבא עם מקש הטאב (TAB) או חץ שמאלי. ' + + 'עבור לכפתור הקודם עם מקש השיפט (SHIFT) + טאב (TAB) או חץ ימני. ' + + 'לחץ רווח או אנטר (ENTER) כדי להפעיל את הכפתור הנבחר.' + }, + + { + name : 'דיאלוגים (חלונות תשאול)', + legend : + 'בתוך דיאלוג, לחץ טאב (TAB) כדי לנווט לשדה הבא, לחץ שיפט (SHIFT) + טאב (TAB) כדי לנווט לשדה הקודם, לחץ אנטר (ENTER) כדי לשלוח את הדיאלוג, לחץ אסקייפ (ESC) כדי לבטל. ' + + 'בתוך דיאלוגים בעלי מספר טאבים (לשוניות), לחץ אלט (ALT) + F10 כדי לנווט לשורת הטאבים. ' + + 'נווט לטאב הבא עם טאב (TAB) או חץ שמאלי. ' + + 'עבור לטאב הקודם עם שיפט (SHIFT) + טאב (TAB) או חץ שמאלי. ' + + 'לחץ רווח או אנטר (ENTER) כדי להיכנס לטאב.' + }, + + { + name : 'תפריט ההקשר (Context Menu)', + legend : + 'לחץ ${contextMenu} או APPLICATION KEYכדי לפתוח את תפריט ההקשר. ' + + 'עבור לאפשרות הבאה עם טאב (TAB) או חץ למטה. ' + + 'עבור לאפשרות הקודמת עם שיפט (SHIFT) + טאב (TAB) או חץ למעלה. ' + + 'לחץ רווח או אנטר (ENTER) כדי לבחור את האפשרות. ' + + 'פתח את תת התפריט (Sub-menu) של האפשרות הנוכחית עם רווח או אנטר (ENTER) או חץ שמאלי. ' + + 'חזור לתפריט האב עם אסקייפ (ESC) או חץ שמאלי. ' + + 'סגור את תפריט ההקשר עם אסקייפ (ESC).' + }, + + { + name : 'תפריטים צפים (List boxes)', + legend : + 'בתוך תפריט צף, עבור לפריט הבא עם טאב (TAB) או חץ למטה. ' + + 'עבור לתפריט הקודם עם שיפט (SHIFT) + טאב (TAB) or חץ עליון. ' + + 'Press SPACE or ENTER to select the list option. ' + + 'Press ESC to close the list-box.' + }, + + { + name : 'עץ אלמנטים (Elements Path)', + legend : + 'לחץ ${elementsPathFocus} כדי לנווט לעץ האלמנטים. ' + + 'עבור לפריט הבא עם טאב (TAB) או חץ ימני. ' + + 'עבור לפריט הקודם עם שיפט (SHIFT) + טאב (TAB) או חץ שמאלי. ' + + 'לחץ רווח או אנטר (ENTER) כדי לבחור את האלמנט בעורך.' + } + ] + }, + { + name : 'פקודות', + items : + [ + { + name : ' ביטול צעד אחרון', + legend : 'לחץ ${undo}' + }, + { + name : ' חזרה על צעד אחרון', + legend : 'לחץ ${redo}' + }, + { + name : ' הדגשה', + legend : 'לחץ ${bold}' + }, + { + name : ' הטייה', + legend : 'לחץ ${italic}' + }, + { + name : ' הוספת קו תחתון', + legend : 'לחץ ${underline}' + }, + { + name : ' הוספת לינק', + legend : 'לחץ ${link}' + }, + { + name : ' כיווץ סרגל הכלים', + legend : 'לחץ ${toolbarCollapse}' + }, + { + name : ' הוראות נגישות', + legend : 'לחץ ${a11yHelp}' + } + ] + } + ] + } +}); +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.plugins.setLang( 'a11yhelp', 'he', +{ + accessibilityHelp : + { + title : 'הוראות נגישות', + contents : 'הוראות נגישות. לסגירה לחץ אסקייפ (ESC).', + legend : + [ + { + name : 'כללי', + items : + [ + { + name : 'סרגל הכלים', + legend: + 'לחץ על ${toolbarFocus} כדי לנווט לסרגל הכלים. ' + + 'עבור לכפתור הבא עם מקש הטאב (TAB) או חץ שמאלי. ' + + 'עבור לכפתור הקודם עם מקש השיפט (SHIFT) + טאב (TAB) או חץ ימני. ' + + 'לחץ רווח או אנטר (ENTER) כדי להפעיל את הכפתור הנבחר.' + }, + + { + name : 'דיאלוגים (חלונות תשאול)', + legend : + 'בתוך דיאלוג, לחץ טאב (TAB) כדי לנווט לשדה הבא, לחץ שיפט (SHIFT) + טאב (TAB) כדי לנווט לשדה הקודם, לחץ אנטר (ENTER) כדי לשלוח את הדיאלוג, לחץ אסקייפ (ESC) כדי לבטל. ' + + 'בתוך דיאלוגים בעלי מספר טאבים (לשוניות), לחץ אלט (ALT) + F10 כדי לנווט לשורת הטאבים. ' + + 'נווט לטאב הבא עם טאב (TAB) או חץ שמאלי. ' + + 'עבור לטאב הקודם עם שיפט (SHIFT) + טאב (TAB) או חץ שמאלי. ' + + 'לחץ רווח או אנטר (ENTER) כדי להיכנס לטאב.' + }, + + { + name : 'תפריט ההקשר (Context Menu)', + legend : + 'לחץ ${contextMenu} או APPLICATION KEYכדי לפתוח את תפריט ההקשר. ' + + 'עבור לאפשרות הבאה עם טאב (TAB) או חץ למטה. ' + + 'עבור לאפשרות הקודמת עם שיפט (SHIFT) + טאב (TAB) או חץ למעלה. ' + + 'לחץ רווח או אנטר (ENTER) כדי לבחור את האפשרות. ' + + 'פתח את תת התפריט (Sub-menu) של האפשרות הנוכחית עם רווח או אנטר (ENTER) או חץ שמאלי. ' + + 'חזור לתפריט האב עם אסקייפ (ESC) או חץ שמאלי. ' + + 'סגור את תפריט ההקשר עם אסקייפ (ESC).' + }, + + { + name : 'תפריטים צפים (List boxes)', + legend : + 'בתוך תפריט צף, עבור לפריט הבא עם טאב (TAB) או חץ למטה. ' + + 'עבור לתפריט הקודם עם שיפט (SHIFT) + טאב (TAB) or חץ עליון. ' + + 'Press SPACE or ENTER to select the list option. ' + + 'Press ESC to close the list-box.' + }, + + { + name : 'עץ אלמנטים (Elements Path)', + legend : + 'לחץ ${elementsPathFocus} כדי לנווט לעץ האלמנטים. ' + + 'עבור לפריט הבא עם טאב (TAB) או חץ ימני. ' + + 'עבור לפריט הקודם עם שיפט (SHIFT) + טאב (TAB) או חץ שמאלי. ' + + 'לחץ רווח או אנטר (ENTER) כדי לבחור את האלמנט בעורך.' + } + ] + }, + { + name : 'פקודות', + items : + [ + { + name : ' ביטול צעד אחרון', + legend : 'לחץ ${undo}' + }, + { + name : ' חזרה על צעד אחרון', + legend : 'לחץ ${redo}' + }, + { + name : ' הדגשה', + legend : 'לחץ ${bold}' + }, + { + name : ' הטייה', + legend : 'לחץ ${italic}' + }, + { + name : ' הוספת קו תחתון', + legend : 'לחץ ${underline}' + }, + { + name : ' הוספת לינק', + legend : 'לחץ ${link}' + }, + { + name : ' כיווץ סרגל הכלים', + legend : 'לחץ ${toolbarCollapse}' + }, + { + name : ' הוראות נגישות', + legend : 'לחץ ${a11yHelp}' + } + ] + } + ] + } +}); diff --git a/_source/plugins/a11yhelp/plugin.js b/_source/plugins/a11yhelp/plugin.js index 455d21e..9409b37 100644 --- a/_source/plugins/a11yhelp/plugin.js +++ b/_source/plugins/a11yhelp/plugin.js @@ -1,46 +1,46 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-/**
- * @fileOverview Plugin definition for the a11yhelp, which provides a dialog
- * with accessibility related help.
- */
-
-(function()
-{
- var pluginName = 'a11yhelp',
- commandName = 'a11yHelp';
-
- CKEDITOR.plugins.add( pluginName,
- {
- // List of available localizations.
- availableLangs : { en:1 },
-
- init : function( editor )
- {
- var plugin = this;
- editor.addCommand( commandName,
- {
- exec : function()
- {
- var langCode = editor.langCode;
- langCode = plugin.availableLangs[ langCode ] ? langCode : 'en';
-
- CKEDITOR.scriptLoader.load(
- CKEDITOR.getUrl( plugin.path + 'lang/' + langCode + '.js' ),
- function()
- {
- CKEDITOR.tools.extend( editor.lang, plugin.lang[ langCode ] );
- editor.openDialog( commandName );
- });
- },
- modes : { wysiwyg:1, source:1 },
- canUndo : false
- });
-
- CKEDITOR.dialog.add( commandName, this.path + 'dialogs/a11yhelp.js' );
- }
- });
-})();
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Plugin definition for the a11yhelp, which provides a dialog + * with accessibility related help. + */ + +(function() +{ + var pluginName = 'a11yhelp', + commandName = 'a11yHelp'; + + CKEDITOR.plugins.add( pluginName, + { + // List of available localizations. + availableLangs : { en:1, he:1 }, + + init : function( editor ) + { + var plugin = this; + editor.addCommand( commandName, + { + exec : function() + { + var langCode = editor.langCode; + langCode = plugin.availableLangs[ langCode ] ? langCode : 'en'; + + CKEDITOR.scriptLoader.load( + CKEDITOR.getUrl( plugin.path + 'lang/' + langCode + '.js' ), + function() + { + CKEDITOR.tools.extend( editor.lang, plugin.lang[ langCode ] ); + editor.openDialog( commandName ); + }); + }, + modes : { wysiwyg:1, source:1 }, + canUndo : false + }); + + CKEDITOR.dialog.add( commandName, this.path + 'dialogs/a11yhelp.js' ); + } + }); +})(); diff --git a/_source/plugins/basicstyles/plugin.js b/_source/plugins/basicstyles/plugin.js index cf64afc..b1bc5bc 100644 --- a/_source/plugins/basicstyles/plugin.js +++ b/_source/plugins/basicstyles/plugin.js @@ -1,93 +1,101 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-CKEDITOR.plugins.add( 'basicstyles',
-{
- requires : [ 'styles', 'button' ],
-
- init : function( editor )
- {
- // All buttons use the same code to register. So, to avoid
- // duplications, let's use this tool function.
- var addButtonCommand = function( buttonName, buttonLabel, commandName, styleDefiniton )
- {
- var style = new CKEDITOR.style( styleDefiniton );
-
- editor.attachStyleStateChange( style, function( state )
- {
- editor.getCommand( commandName ).setState( state );
- });
-
- editor.addCommand( commandName, new CKEDITOR.styleCommand( style ) );
-
- editor.ui.addButton( buttonName,
- {
- label : buttonLabel,
- command : commandName
- });
- };
-
- var config = editor.config;
- var lang = editor.lang;
-
- addButtonCommand( 'Bold' , lang.bold , 'bold' , config.coreStyles_bold );
- addButtonCommand( 'Italic' , lang.italic , 'italic' , config.coreStyles_italic );
- addButtonCommand( 'Underline' , lang.underline , 'underline' , config.coreStyles_underline );
- addButtonCommand( 'Strike' , lang.strike , 'strike' , config.coreStyles_strike );
- addButtonCommand( 'Subscript' , lang.subscript , 'subscript' , config.coreStyles_subscript );
- addButtonCommand( 'Superscript' , lang.superscript , 'superscript' , config.coreStyles_superscript );
- }
-});
-
-// Basic Inline Styles.
-/**
- * The style definition to be used to apply the bold style in the text.
- * @type Object
- * @example
- * config.coreStyles_bold = { element : 'b', overrides : 'strong' };
- * @example
- * config.coreStyles_bold = { element : 'span', attributes : {'class': 'Bold'} };
- */
-CKEDITOR.config.coreStyles_bold = { element : 'strong', overrides : 'b' };
-/**
- * The style definition to be used to apply the italic style in the text.
- * @type Object
- * @default { element : 'em', overrides : 'i' }
- * @example
- * CKEDITOR.config.coreStyles_italic = { element : 'span', attributes : {'class': 'Italic'} };
- */
-CKEDITOR.config.coreStyles_italic = { element : 'em', overrides : 'i' };
-/**
- * The style definition to be used to apply the underline style in the text.
- * @type Object
- * @default { element : 'u' }
- * @example
- * CKEDITOR.config.coreStyles_underline = { element : 'span', attributes : {'class': 'Underline'}};
- */
-CKEDITOR.config.coreStyles_underline = { element : 'u' };
-/**
- * The style definition to be used to apply the strike style in the text.
- * @type Object
- * @default { element : 'strike' }
- * @example
- * CKEDITOR.config.coreStyles_strike = { element : 'span', attributes : {'class': 'StrikeThrough'}, overrides : 'strike' };
- */
-CKEDITOR.config.coreStyles_strike = { element : 'strike' };
-/**
- * The style definition to be used to apply the subscript style in the text.
- * @type Object
- * @default { element : 'sub' }
- * @example
- * CKEDITOR.config.coreStyles_subscript = { element : 'span', attributes : {'class': 'Subscript'}, overrides : 'sub' };
- */
-CKEDITOR.config.coreStyles_subscript = { element : 'sub' };
-/**
- * The style definition to be used to apply the superscript style in the text.
- * @type Object
- * @default { element : 'sup' }
- * @example
- * CKEDITOR.config.coreStyles_superscript = { element : 'span', attributes : {'class': 'Superscript'}, overrides : 'sup' };
- */
-CKEDITOR.config.coreStyles_superscript = { element : 'sup' };
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.plugins.add( 'basicstyles', +{ + requires : [ 'styles', 'button' ], + + init : function( editor ) + { + // All buttons use the same code to register. So, to avoid + // duplications, let's use this tool function. + var addButtonCommand = function( buttonName, buttonLabel, commandName, styleDefiniton ) + { + var style = new CKEDITOR.style( styleDefiniton ); + + editor.attachStyleStateChange( style, function( state ) + { + editor.getCommand( commandName ).setState( state ); + }); + + editor.addCommand( commandName, new CKEDITOR.styleCommand( style ) ); + + editor.ui.addButton( buttonName, + { + label : buttonLabel, + command : commandName + }); + }; + + var config = editor.config; + var lang = editor.lang; + + addButtonCommand( 'Bold' , lang.bold , 'bold' , config.coreStyles_bold ); + addButtonCommand( 'Italic' , lang.italic , 'italic' , config.coreStyles_italic ); + addButtonCommand( 'Underline' , lang.underline , 'underline' , config.coreStyles_underline ); + addButtonCommand( 'Strike' , lang.strike , 'strike' , config.coreStyles_strike ); + addButtonCommand( 'Subscript' , lang.subscript , 'subscript' , config.coreStyles_subscript ); + addButtonCommand( 'Superscript' , lang.superscript , 'superscript' , config.coreStyles_superscript ); + } +}); + +// Basic Inline Styles. + +/** + * The style definition to be used to apply the bold style in the text. + * @type Object + * @example + * config.coreStyles_bold = { element : 'b', overrides : 'strong' }; + * @example + * config.coreStyles_bold = { element : 'span', attributes : {'class': 'Bold'} }; + */ +CKEDITOR.config.coreStyles_bold = { element : 'strong', overrides : 'b' }; + +/** + * The style definition to be used to apply the italic style in the text. + * @type Object + * @default { element : 'em', overrides : 'i' } + * @example + * config.coreStyles_bold = { element : 'i', overrides : 'em' }; + * @example + * CKEDITOR.config.coreStyles_italic = { element : 'span', attributes : {'class': 'Italic'} }; + */ +CKEDITOR.config.coreStyles_italic = { element : 'em', overrides : 'i' }; + +/** + * The style definition to be used to apply the underline style in the text. + * @type Object + * @default { element : 'u' } + * @example + * CKEDITOR.config.coreStyles_underline = { element : 'span', attributes : {'class': 'Underline'}}; + */ +CKEDITOR.config.coreStyles_underline = { element : 'u' }; + +/** + * The style definition to be used to apply the strike style in the text. + * @type Object + * @default { element : 'strike' } + * @example + * CKEDITOR.config.coreStyles_strike = { element : 'span', attributes : {'class': 'StrikeThrough'}, overrides : 'strike' }; + */ +CKEDITOR.config.coreStyles_strike = { element : 'strike' }; + +/** + * The style definition to be used to apply the subscript style in the text. + * @type Object + * @default { element : 'sub' } + * @example + * CKEDITOR.config.coreStyles_subscript = { element : 'span', attributes : {'class': 'Subscript'}, overrides : 'sub' }; + */ +CKEDITOR.config.coreStyles_subscript = { element : 'sub' }; + +/** + * The style definition to be used to apply the superscript style in the text. + * @type Object + * @default { element : 'sup' } + * @example + * CKEDITOR.config.coreStyles_superscript = { element : 'span', attributes : {'class': 'Superscript'}, overrides : 'sup' }; + */ +CKEDITOR.config.coreStyles_superscript = { element : 'sup' }; diff --git a/_source/plugins/button/plugin.js b/_source/plugins/button/plugin.js index b211a28..26909bc 100644 --- a/_source/plugins/button/plugin.js +++ b/_source/plugins/button/plugin.js @@ -1,270 +1,277 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-CKEDITOR.plugins.add( 'button',
-{
- beforeInit : function( editor )
- {
- editor.ui.addHandler( CKEDITOR.UI_BUTTON, CKEDITOR.ui.button.handler );
- }
-});
-
-/**
- * Button UI element.
- * @constant
- * @example
- */
-CKEDITOR.UI_BUTTON = 1;
-
-/**
- * Represents a button UI element. This class should not be called directly. To
- * create new buttons use {@link CKEDITOR.ui.prototype.addButton} instead.
- * @constructor
- * @param {Object} definition The button definition.
- * @example
- */
-CKEDITOR.ui.button = function( definition )
-{
- // Copy all definition properties to this object.
- CKEDITOR.tools.extend( this, definition,
- // Set defaults.
- {
- title : definition.label,
- className : definition.className || ( definition.command && 'cke_button_' + definition.command ) || '',
- click : definition.click || function( editor )
- {
- editor.execCommand( definition.command );
- }
- });
-
- this._ = {};
-};
-
-/**
- * Transforms a button definition in a {@link CKEDITOR.ui.button} instance.
- * @type Object
- * @example
- */
-CKEDITOR.ui.button.handler =
-{
- create : function( definition )
- {
- return new CKEDITOR.ui.button( definition );
- }
-};
-
-CKEDITOR.ui.button.prototype =
-{
- canGroup : true,
-
- /**
- * Renders the button.
- * @param {CKEDITOR.editor} editor The editor instance which this button is
- * to be used by.
- * @param {Array} output The output array to which append the HTML relative
- * to this button.
- * @example
- */
- render : function( editor, output )
- {
- var env = CKEDITOR.env;
-
- var id = this._.id = 'cke_' + CKEDITOR.tools.getNextNumber();
- this._.editor = editor;
-
- var instance =
- {
- id : id,
- button : this,
- editor : editor,
- focus : function()
- {
- var element = CKEDITOR.document.getById( id );
- element.focus();
- },
- execute : function()
- {
- this.button.click( editor );
- }
- };
-
- var clickFn = CKEDITOR.tools.addFunction( instance.execute, instance );
-
- var index = CKEDITOR.ui.button._.instances.push( instance ) - 1;
-
- var classes = '';
-
- // Get the command name.
- var command = this.command;
-
- if ( this.modes )
- {
- editor.on( 'mode', function()
- {
- this.setState( this.modes[ editor.mode ] ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED );
- }, this);
- }
- else if ( command )
- {
- // Get the command instance.
- command = editor.getCommand( command );
-
- if ( command )
- {
- command.on( 'state', function()
- {
- this.setState( command.state );
- }, this);
-
- classes += 'cke_' + (
- command.state == CKEDITOR.TRISTATE_ON ? 'on' :
- command.state == CKEDITOR.TRISTATE_DISABLED ? 'disabled' :
- 'off' );
- }
- }
-
- if ( !command )
- classes += 'cke_off';
-
- if ( this.className )
- classes += ' ' + this.className;
-
- output.push(
- '<span class="cke_button">',
- '<a id="', id, '"' +
- ' class="', classes, '"',
- env.gecko && env.version >= 10900 && !env.hc ? '' : '" href="javascript:void(\''+ ( this.title || '' ).replace( "'"+ '' )+ '\')"',
- ' title="', this.title, '"' +
- ' tabindex="-1"' +
- ' hidefocus="true"' +
- ' role="button"' +
- ' aria-labelledby="' + id + '_label"' +
- ( this.hasArrow ? ' aria-haspopup="true"' : '' ) );
-
- // Some browsers don't cancel key events in the keydown but in the
- // keypress.
- // TODO: Check if really needed for Gecko+Mac.
- if ( env.opera || ( env.gecko && env.mac ) )
- {
- output.push(
- ' onkeypress="return false;"' );
- }
-
- // With Firefox, we need to force the button to redraw, otherwise it
- // will remain in the focus state.
- if ( env.gecko )
- {
- output.push(
- ' onblur="this.style.cssText = this.style.cssText;"' );
- }
-
- output.push(
- ' onkeydown="return CKEDITOR.ui.button._.keydown(', index, ', event);"' +
- ' onfocus="return CKEDITOR.ui.button._.focus(', index, ', event);"' +
- ' onclick="CKEDITOR.tools.callFunction(', clickFn, ', this); return false;">' +
- '<span class="cke_icon"' );
-
- if ( this.icon )
- {
- var offset = ( this.iconOffset || 0 ) * -16;
- output.push( ' style="background-image:url(', CKEDITOR.getUrl( this.icon ), ');background-position:0 ' + offset + 'px;"' );
- }
-
- output.push(
- '></span>' +
- '<span id="', id, '_label" class="cke_label">', this.label, '</span>' );
-
- if ( this.hasArrow )
- {
- output.push(
- '<span class="cke_buttonarrow"></span>' );
- }
-
- output.push(
- '</a>',
- '</span>' );
-
- if ( this.onRender )
- this.onRender();
-
- return instance;
- },
-
- setState : function( state )
- {
- if ( this._.state == state )
- return false;
-
- this._.state = state;
-
- var element = CKEDITOR.document.getById( this._.id );
-
- if ( element )
- {
- element.setState( state );
- state == CKEDITOR.TRISTATE_DISABLED ?
- element.setAttribute( 'aria-disabled', true ) :
- element.removeAttribute( 'aria-disabled' );
-
- state == CKEDITOR.TRISTATE_ON ?
- element.setAttribute( 'aria-pressed', true ) :
- element.removeAttribute( 'aria-pressed' );
-
- return true;
- }
- else
- return false;
- }
-};
-
-/**
- * Handles a button click.
- * @private
- */
-CKEDITOR.ui.button._ =
-{
- instances : [],
-
- keydown : function( index, ev )
- {
- var instance = CKEDITOR.ui.button._.instances[ index ];
-
- if ( instance.onkey )
- {
- ev = new CKEDITOR.dom.event( ev );
- return ( instance.onkey( instance, ev.getKeystroke() ) !== false );
- }
- },
-
- focus : function( index, ev )
- {
- var instance = CKEDITOR.ui.button._.instances[ index ],
- retVal;
-
- if ( instance.onfocus )
- retVal = ( instance.onfocus( instance, new CKEDITOR.dom.event( ev ) ) !== false );
-
- // FF2: prevent focus event been bubbled up to editor container, which caused unexpected editor focus.
- if ( CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 )
- ev.preventBubble();
- return retVal;
- }
-};
-
-/**
- * Adds a button definition to the UI elements list.
- * @param {String} The button name.
- * @param {Object} The button definition.
- * @example
- * editorInstance.ui.addButton( 'MyBold',
- * {
- * label : 'My Bold',
- * command : 'bold'
- * });
- */
-CKEDITOR.ui.prototype.addButton = function( name, definition )
-{
- this.add( name, CKEDITOR.UI_BUTTON, definition );
-};
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.plugins.add( 'button', +{ + beforeInit : function( editor ) + { + editor.ui.addHandler( CKEDITOR.UI_BUTTON, CKEDITOR.ui.button.handler ); + } +}); + +/** + * Button UI element. + * @constant + * @example + */ +CKEDITOR.UI_BUTTON = 1; + +/** + * Represents a button UI element. This class should not be called directly. To + * create new buttons use {@link CKEDITOR.ui.prototype.addButton} instead. + * @constructor + * @param {Object} definition The button definition. + * @example + */ +CKEDITOR.ui.button = function( definition ) +{ + // Copy all definition properties to this object. + CKEDITOR.tools.extend( this, definition, + // Set defaults. + { + title : definition.label, + className : definition.className || ( definition.command && 'cke_button_' + definition.command ) || '', + click : definition.click || function( editor ) + { + editor.execCommand( definition.command ); + } + }); + + this._ = {}; +}; + +/** + * Transforms a button definition in a {@link CKEDITOR.ui.button} instance. + * @type Object + * @example + */ +CKEDITOR.ui.button.handler = +{ + create : function( definition ) + { + return new CKEDITOR.ui.button( definition ); + } +}; + +CKEDITOR.ui.button.prototype = +{ + canGroup : true, + + /** + * Renders the button. + * @param {CKEDITOR.editor} editor The editor instance which this button is + * to be used by. + * @param {Array} output The output array to which append the HTML relative + * to this button. + * @example + */ + render : function( editor, output ) + { + var env = CKEDITOR.env, + id = this._.id = 'cke_' + CKEDITOR.tools.getNextNumber(), + classes = '', + command = this.command, // Get the command name. + clickFn, + index; + + this._.editor = editor; + + var instance = + { + id : id, + button : this, + editor : editor, + focus : function() + { + var element = CKEDITOR.document.getById( id ); + element.focus(); + }, + execute : function() + { + this.button.click( editor ); + } + }; + + instance.clickFn = clickFn = CKEDITOR.tools.addFunction( instance.execute, instance ); + + instance.index = index = CKEDITOR.ui.button._.instances.push( instance ) - 1; + + if ( this.modes ) + { + editor.on( 'mode', function() + { + this.setState( this.modes[ editor.mode ] ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED ); + }, this); + } + else if ( command ) + { + // Get the command instance. + command = editor.getCommand( command ); + + if ( command ) + { + command.on( 'state', function() + { + this.setState( command.state ); + }, this); + + classes += 'cke_' + ( + command.state == CKEDITOR.TRISTATE_ON ? 'on' : + command.state == CKEDITOR.TRISTATE_DISABLED ? 'disabled' : + 'off' ); + } + } + + if ( !command ) + classes += 'cke_off'; + + if ( this.className ) + classes += ' ' + this.className; + + output.push( + '<span class="cke_button">', + '<a id="', id, '"' + + ' class="', classes, '"', + env.gecko && env.version >= 10900 && !env.hc ? '' : '" href="javascript:void(\''+ ( this.title || '' ).replace( "'"+ '' )+ '\')"', + ' title="', this.title, '"' + + ' tabindex="-1"' + + ' hidefocus="true"' + + ' role="button"' + + ' aria-labelledby="' + id + '_label"' + + ( this.hasArrow ? ' aria-haspopup="true"' : '' ) ); + + // Some browsers don't cancel key events in the keydown but in the + // keypress. + // TODO: Check if really needed for Gecko+Mac. + if ( env.opera || ( env.gecko && env.mac ) ) + { + output.push( + ' onkeypress="return false;"' ); + } + + // With Firefox, we need to force the button to redraw, otherwise it + // will remain in the focus state. + if ( env.gecko ) + { + output.push( + ' onblur="this.style.cssText = this.style.cssText;"' ); + } + + output.push( + ' onkeydown="return CKEDITOR.ui.button._.keydown(', index, ', event);"' + + ' onfocus="return CKEDITOR.ui.button._.focus(', index, ', event);"' + + ' onclick="CKEDITOR.tools.callFunction(', clickFn, ', this); return false;">' + + '<span class="cke_icon"' ); + + if ( this.icon ) + { + var offset = ( this.iconOffset || 0 ) * -16; + output.push( ' style="background-image:url(', CKEDITOR.getUrl( this.icon ), ');background-position:0 ' + offset + 'px;"' ); + } + + output.push( + '> </span>' + + '<span id="', id, '_label" class="cke_label">', this.label, '</span>' ); + + if ( this.hasArrow ) + { + output.push( + '<span class="cke_buttonarrow">' + // BLACK DOWN-POINTING TRIANGLE + + ( CKEDITOR.env.hc ? '▼' : ' ' ) + + '</span>' ); + } + + output.push( + '</a>', + '</span>' ); + + if ( this.onRender ) + this.onRender(); + + return instance; + }, + + setState : function( state ) + { + if ( this._.state == state ) + return false; + + this._.state = state; + + var element = CKEDITOR.document.getById( this._.id ); + + if ( element ) + { + element.setState( state ); + state == CKEDITOR.TRISTATE_DISABLED ? + element.setAttribute( 'aria-disabled', true ) : + element.removeAttribute( 'aria-disabled' ); + + state == CKEDITOR.TRISTATE_ON ? + element.setAttribute( 'aria-pressed', true ) : + element.removeAttribute( 'aria-pressed' ); + + return true; + } + else + return false; + } +}; + +/** + * Handles a button click. + * @private + */ +CKEDITOR.ui.button._ = +{ + instances : [], + + keydown : function( index, ev ) + { + var instance = CKEDITOR.ui.button._.instances[ index ]; + + if ( instance.onkey ) + { + ev = new CKEDITOR.dom.event( ev ); + return ( instance.onkey( instance, ev.getKeystroke() ) !== false ); + } + }, + + focus : function( index, ev ) + { + var instance = CKEDITOR.ui.button._.instances[ index ], + retVal; + + if ( instance.onfocus ) + retVal = ( instance.onfocus( instance, new CKEDITOR.dom.event( ev ) ) !== false ); + + // FF2: prevent focus event been bubbled up to editor container, which caused unexpected editor focus. + if ( CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 ) + ev.preventBubble(); + return retVal; + } +}; + +/** + * Adds a button definition to the UI elements list. + * @param {String} The button name. + * @param {Object} The button definition. + * @example + * editorInstance.ui.addButton( 'MyBold', + * { + * label : 'My Bold', + * command : 'bold' + * }); + */ +CKEDITOR.ui.prototype.addButton = function( name, definition ) +{ + this.add( name, CKEDITOR.UI_BUTTON, definition ); +}; + +CKEDITOR.on( 'reset', function() + { + CKEDITOR.ui.button._.instances = []; + }); diff --git a/_source/plugins/clipboard/dialogs/paste.js b/_source/plugins/clipboard/dialogs/paste.js index 70527ad..42401d1 100644 --- a/_source/plugins/clipboard/dialogs/paste.js +++ b/_source/plugins/clipboard/dialogs/paste.js @@ -1,186 +1,211 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-CKEDITOR.dialog.add( 'paste', function( editor )
-{
- var lang = editor.lang.clipboard;
- var isCustomDomain = CKEDITOR.env.isCustomDomain();
-
- function onPasteFrameLoad( win )
- {
- var doc = new CKEDITOR.dom.document( win.document ),
- $ = doc.$;
-
- doc.getById( "cke_actscrpt" ).remove();
-
- CKEDITOR.env.ie ?
- $.body.contentEditable = "true" :
- $.designMode = "on";
-
- CKEDITOR.env.ie && doc.getWindow().on( 'blur', function()
- {
- $.body.contentEditable = "false";
- } );
-
- doc.on( "keydown", function( e )
- {
- var domEvent = e.data,
- key = domEvent.getKeystroke(),
- processed;
-
- switch( key )
- {
- case 27 :
- this.hide();
- processed = 1;
- break;
-
- case 9 :
- case CKEDITOR.SHIFT + 9 :
- this.changeFocus( true );
- processed = 1;
- }
-
- processed && domEvent.preventDefault();
- }, this );
-
- editor.fire( 'ariaWidget', new CKEDITOR.dom.element( win.frameElement ) );
- }
-
- return {
- title : lang.title,
-
- minWidth : CKEDITOR.env.ie && CKEDITOR.env.quirks ? 370 : 350,
- minHeight : CKEDITOR.env.quirks ? 250 : 245,
- onShow : function()
- {
- // FIREFOX BUG: Force the browser to render the dialog to make the to-be-
- // inserted iframe editable. (#3366)
- this.parts.dialog.$.offsetHeight;
-
- var htmlToLoad = '<!doctype html><html><head><style>body { margin: 3px; height: 95%; } </style></head><body>' +
- '<script id="cke_actscrpt" type="text/javascript">' +
- 'window.parent.CKEDITOR.tools.callFunction( ' + CKEDITOR.tools.addFunction( onPasteFrameLoad, this ) + ', this );' +
- '</script></body></html>';
-
- var iframe = CKEDITOR.dom.element.createFromHtml(
- '<iframe' +
- ' frameborder="0" ' +
- ' allowTransparency="true"' +
- // Support for custom document.domain in IE.
- ( isCustomDomain ?
- ' src="javascript:void((function(){' +
- 'document.open();' +
- 'document.domain=\'' + document.domain + '\';' +
- 'document.close();' +
- '})())"' : '' ) +
- ' role="region"' +
- ' aria-label="' + lang.pasteArea + '"' +
- ' aria-describedby="' + this.getContentElement( 'general', 'pasteMsg' ).domId + '"' +
- ' aria-multiple="true"' +
- '></iframe>' );
-
- 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( htmlToLoad );
- doc.close();
- }, this );
-
- iframe.setStyles(
- {
- width : '346px',
- height : '130px',
- 'background-color' : 'white',
- border : '1px solid black'
- } );
- iframe.setCustomData( 'dialog', this );
-
- var field = this.getContentElement( 'general', 'editing_area' ),
- container = field.getElement();
- container.setHtml( '' );
- container.append( iframe );
-
- field.getInputElement = function(){ return iframe; };
-
- // Force container to scale in IE.
- if ( CKEDITOR.env.ie )
- {
- container.setStyle( 'display', 'block' );
- container.setStyle( 'height', ( iframe.$.offsetHeight + 2 ) + 'px' );
- }
- },
-
- onHide : function()
- {
- if ( CKEDITOR.env.ie )
- this.getParentEditor().document.getBody().$.contentEditable = 'true';
- },
-
- onLoad : function()
- {
- if ( ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) && editor.lang.dir == 'rtl' )
- this.parts.contents.setStyle( 'overflow', 'hidden' );
- },
-
- onOk : function()
- {
- var container = this.getContentElement( 'general', 'editing_area' ).getElement(),
- iframe = container.getElementsByTag( 'iframe' ).getItem( 0 ),
- editor = this.getParentEditor(),
- html = iframe.$.contentWindow.document.body.innerHTML;
-
- setTimeout( function(){
- editor.fire( 'paste', { 'html' : html } );
- }, 0 );
-
- },
-
- contents : [
- {
- id : 'general',
- label : editor.lang.common.generalTab,
- elements : [
- {
- type : 'html',
- id : 'securityMsg',
- html : '<div style="white-space:normal;width:340px;">' + lang.securityMsg + '</div>'
- },
- {
- type : 'html',
- id : 'pasteMsg',
- html : '<div style="white-space:normal;width:340px;">'+lang.pasteMsg +'</div>'
- },
- {
- type : 'html',
- id : 'editing_area',
- style : 'width: 100%; height: 100%;',
- html : '',
- focus : function()
- {
- var win = this.getInputElement().$.contentWindow,
- body = win && win.document.body;
-
- // #3291 : JAWS needs the 500ms delay to detect that the editor iframe
- // iframe is no longer editable. So that it will put the focus into the
- // Paste from Word dialog's editable area instead.
- setTimeout( function()
- {
- // Reactivate design mode for IE to make the cursor blinking.
- CKEDITOR.env.ie && body && ( body.contentEditable = "true" );
- win.focus();
- }, 500 );
- }
- }
- ]
- }
- ]
- };
-});
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.dialog.add( 'paste', function( editor ) +{ + var lang = editor.lang.clipboard; + var isCustomDomain = CKEDITOR.env.isCustomDomain(); + + function onPasteFrameLoad( win ) + { + var doc = new CKEDITOR.dom.document( win.document ), + docElement = doc.$; + + doc.getById( "cke_actscrpt" ).remove(); + + CKEDITOR.env.ie ? + docElement.body.contentEditable = "true" : + docElement.designMode = "on"; + + // IE before version 8 will leave cursor blinking inside the document after + // editor blurred unless we clean up the selection. (#4716) + if ( CKEDITOR.env.ie && CKEDITOR.env.version < 8 ) + { + doc.getWindow().on( 'blur', function() + { + docElement.selection.empty(); + } ); + } + + doc.on( "keydown", function( e ) + { + var domEvent = e.data, + key = domEvent.getKeystroke(), + processed; + + switch( key ) + { + case 27 : + this.hide(); + processed = 1; + break; + + case 9 : + case CKEDITOR.SHIFT + 9 : + this.changeFocus( true ); + processed = 1; + } + + processed && domEvent.preventDefault(); + }, this ); + + editor.fire( 'ariaWidget', new CKEDITOR.dom.element( win.frameElement ) ); + } + + return { + title : lang.title, + + minWidth : CKEDITOR.env.ie && CKEDITOR.env.quirks ? 370 : 350, + minHeight : CKEDITOR.env.quirks ? 250 : 245, + onShow : function() + { + // FIREFOX BUG: Force the browser to render the dialog to make the to-be- + // inserted iframe editable. (#3366) + this.parts.dialog.$.offsetHeight; + + var htmlToLoad = + '<html dir="' + editor.config.contentsLangDirection + '"' + + ' lang="' + ( editor.config.contentsLanguage || editor.langCode ) + '">' + + '<head><style>body { margin: 3px; height: 95%; } </style></head><body>' + + '<script id="cke_actscrpt" type="text/javascript">' + + 'window.parent.CKEDITOR.tools.callFunction( ' + CKEDITOR.tools.addFunction( onPasteFrameLoad, this ) + ', this );' + + '</script></body>' + + '</html>'; + + var iframe = CKEDITOR.dom.element.createFromHtml( + '<iframe' + + ' frameborder="0" ' + + ' allowTransparency="true"' + + // Support for custom document.domain in IE. + ( isCustomDomain ? + ' src="javascript:void((function(){' + + 'document.open();' + + 'document.domain=\'' + document.domain + '\';' + + 'document.close();' + + '})())"' : '' ) + + ' role="region"' + + ' aria-label="' + lang.pasteArea + '"' + + ' aria-describedby="' + this.getContentElement( 'general', 'pasteMsg' ).domId + '"' + + ' aria-multiple="true"' + + '></iframe>' ); + + 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( htmlToLoad ); + doc.close(); + }, this ); + + iframe.setStyles( + { + width : '346px', + height : '130px', + 'background-color' : 'white', + border : '1px solid black' + } ); + iframe.setCustomData( 'dialog', this ); + + var field = this.getContentElement( 'general', 'editing_area' ), + container = field.getElement(); + container.setHtml( '' ); + container.append( iframe ); + + // IE need a redirect on focus to make + // the cursor blinking inside iframe. (#5461) + if ( CKEDITOR.env.ie ) + { + var focusGrabber = CKEDITOR.dom.element.createFromHtml( '<span tabindex="-1" style="position:absolute;" role="presentation"></span>' ); + focusGrabber.on( 'focus', function() + { + iframe.$.contentWindow.focus(); + }); + container.append( focusGrabber ); + + // Override focus handler on field. + field.focus = function() + { + focusGrabber.focus(); + this.fire( 'focus' ); + }; + } + + field.getInputElement = function(){ return iframe; }; + + // Force container to scale in IE. + if ( CKEDITOR.env.ie ) + { + container.setStyle( 'display', 'block' ); + container.setStyle( 'height', ( iframe.$.offsetHeight + 2 ) + 'px' ); + } + }, + + onHide : function() + { + if ( CKEDITOR.env.ie ) + this.getParentEditor().document.getBody().$.contentEditable = 'true'; + }, + + onLoad : function() + { + if ( ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) && editor.lang.dir == 'rtl' ) + this.parts.contents.setStyle( 'overflow', 'hidden' ); + }, + + onOk : function() + { + var container = this.getContentElement( 'general', 'editing_area' ).getElement(), + iframe = container.getElementsByTag( 'iframe' ).getItem( 0 ), + editor = this.getParentEditor(), + html = iframe.$.contentWindow.document.body.innerHTML; + + setTimeout( function(){ + editor.fire( 'paste', { 'html' : html } ); + }, 0 ); + + }, + + contents : [ + { + id : 'general', + label : editor.lang.common.generalTab, + elements : [ + { + type : 'html', + id : 'securityMsg', + html : '<div style="white-space:normal;width:340px;">' + lang.securityMsg + '</div>' + }, + { + type : 'html', + id : 'pasteMsg', + html : '<div style="white-space:normal;width:340px;">'+lang.pasteMsg +'</div>' + }, + { + type : 'html', + id : 'editing_area', + style : 'width: 100%; height: 100%;', + html : '', + focus : function() + { + var win = this.getInputElement().$.contentWindow; + + // #3291 : JAWS needs the 500ms delay to detect that the editor iframe + // iframe is no longer editable. So that it will put the focus into the + // Paste from Word dialog's editable area instead. + setTimeout( function() + { + win.focus(); + }, 500 ); + } + } + ] + } + ] + }; +}); diff --git a/_source/plugins/clipboard/plugin.js b/_source/plugins/clipboard/plugin.js index 8dbacc6..b679a3f 100644 --- a/_source/plugins/clipboard/plugin.js +++ b/_source/plugins/clipboard/plugin.js @@ -1,382 +1,413 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-/**
- * @file Clipboard support
- */
-
-(function()
-{
- // Tries to execute any of the paste, cut or copy commands in IE. Returns a
- // boolean indicating that the operation succeeded.
- var execIECommand = function( editor, command )
- {
- var doc = editor.document,
- body = doc.getBody();
-
- var enabled = false;
- var onExec = function()
- {
- enabled = true;
- };
-
- // The following seems to be the only reliable way to detect that
- // clipboard commands are enabled in IE. It will fire the
- // onpaste/oncut/oncopy events only if the security settings allowed
- // the command to execute.
- body.on( command, onExec );
-
- doc.$.execCommand( command );
-
- body.removeListener( command, onExec );
-
- return enabled;
- };
-
- // Attempts to execute the Cut and Copy operations.
- var tryToCutCopy =
- CKEDITOR.env.ie ?
- function( editor, type )
- {
- return execIECommand( editor, type );
- }
- : // !IE.
- function( editor, type )
- {
- try
- {
- // Other browsers throw an error if the command is disabled.
- return editor.document.$.execCommand( type );
- }
- catch( e )
- {
- return false;
- }
- };
-
- // A class that represents one of the cut or copy commands.
- var cutCopyCmd = function( type )
- {
- this.type = type;
- this.canUndo = ( this.type == 'cut' ); // We can't undo copy to clipboard.
- };
-
- cutCopyCmd.prototype =
- {
- exec : function( editor, data )
- {
- var success = tryToCutCopy( editor, this.type );
-
- if ( !success )
- alert( editor.lang.clipboard[ this.type + 'Error' ] ); // Show cutError or copyError.
-
- return success;
- }
- };
-
- // Paste command.
- var pasteCmd =
- {
- canUndo : false,
-
- exec :
- CKEDITOR.env.ie ?
- function( editor )
- {
- // Prevent IE from pasting at the begining of the document.
- editor.focus();
-
- if ( !editor.document.getBody().fire( 'beforepaste' )
- && !execIECommand( editor, 'paste' ) )
- {
- editor.fire( 'pasteDialog' );
- return false;
- }
- }
- :
- function( editor )
- {
- try
- {
- if ( !editor.document.getBody().fire( 'beforepaste' )
- && !editor.document.$.execCommand( 'Paste', false, null ) )
- {
- throw 0;
- }
- }
- catch ( e )
- {
- 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 body = this.document.getBody();
-
- // Simulate 'beforepaste' event for all none-IEs.
- if ( !CKEDITOR.env.ie && body.fire( 'beforepaste' ) )
- event.cancel();
- // Simulate 'paste' event for Opera/Firefox2.
- else if ( CKEDITOR.env.opera
- || CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 )
- body.fire( 'paste' );
- return;
-
- // Cut
- case CKEDITOR.CTRL + 88 : // CTRL+X
- case CKEDITOR.SHIFT + 46 : // SHIFT+DEL
-
- // Save Undo snapshot.
- var editor = this;
- this.fire( 'saveSnapshot' ); // Save before paste
- setTimeout( function()
- {
- editor.fire( 'saveSnapshot' ); // Save after paste
- }, 0 );
- }
- };
-
- // 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;
-
- // If the browser supports it, get the data directly
- if (mode == 'text' && evt.data && evt.data.$.clipboardData)
- {
- // evt.data.$.clipboardData.types contains all the flavours in Mac's Safari, but not on windows.
- var plain = evt.data.$.clipboardData.getData( 'text/plain' );
- if (plain)
- {
- evt.data.preventDefault();
- callback( plain );
- 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 : [ 'dialog', '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 ];
-
- editor.addCommand( commandName, command );
- editor.ui.addButton( buttonName,
- {
- label : lang,
- command : commandName
- });
-
- // If the "menu" plugin is loaded, register the menu item.
- if ( editor.addMenuItems )
- {
- editor.addMenuItem( commandName,
- {
- label : lang,
- command : commandName,
- group : 'clipboard',
- order : ctxMenuOrder
- });
- }
- }
-
- addButtonCommand( 'Cut', 'cut', new cutCopyCmd( 'cut' ), 1 );
- addButtonCommand( 'Copy', 'copy', new cutCopyCmd( 'copy' ), 4 );
- addButtonCommand( 'Paste', 'paste', pasteCmd, 8 );
-
- CKEDITOR.dialog.add( 'paste', CKEDITOR.getUrl( this.path + 'dialogs/paste.js' ) );
-
- 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) || CKEDITOR.env.webkit ) ? '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 )
- {
- // 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()
- {
- return {
- cut : stateFromNamedCommand( 'Cut' ),
-
- // Browser bug: 'Cut' has the correct states for both Copy and Cut.
- copy : stateFromNamedCommand( 'Cut' ),
- paste : CKEDITOR.env.webkit ? CKEDITOR.TRISTATE_OFF : stateFromNamedCommand( 'Paste' )
- };
- });
- }
- }
- });
-})();
-
-/**
- * Fired when a clipboard operation is about to be taken into the editor.
- * Listeners can manipulate the data to be pasted before having it effectively
- * inserted into the document.
- * @name CKEDITOR.editor#paste
- * @since 3.1
- * @event
- * @param {String} [data.html] The HTML data to be pasted. If not available, e.data.text will be defined.
- * @param {String} [data.text] The plain text data to be pasted, available when plain text operations are to used. If not available, e.data.html will be defined.
- */
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @file Clipboard support + */ + +(function() +{ + // Tries to execute any of the paste, cut or copy commands in IE. Returns a + // boolean indicating that the operation succeeded. + var execIECommand = function( editor, command ) + { + var doc = editor.document, + body = doc.getBody(); + + var enabled = false; + var onExec = function() + { + enabled = true; + }; + + // The following seems to be the only reliable way to detect that + // clipboard commands are enabled in IE. It will fire the + // onpaste/oncut/oncopy events only if the security settings allowed + // the command to execute. + body.on( command, onExec ); + + // IE6/7: document.execCommand has problem to paste into positioned element. + ( CKEDITOR.env.version > 7 ? doc.$ : doc.$.selection.createRange() ) [ 'execCommand' ]( command ); + + body.removeListener( command, onExec ); + + return enabled; + }; + + // Attempts to execute the Cut and Copy operations. + var tryToCutCopy = + CKEDITOR.env.ie ? + function( editor, type ) + { + return execIECommand( editor, type ); + } + : // !IE. + function( editor, type ) + { + try + { + // Other browsers throw an error if the command is disabled. + return editor.document.$.execCommand( type ); + } + catch( e ) + { + return false; + } + }; + + // A class that represents one of the cut or copy commands. + var cutCopyCmd = function( type ) + { + this.type = type; + this.canUndo = ( this.type == 'cut' ); // We can't undo copy to clipboard. + }; + + cutCopyCmd.prototype = + { + exec : function( editor, data ) + { + this.type == 'cut' && fixCut( editor ); + + var success = tryToCutCopy( editor, this.type ); + + if ( !success ) + alert( editor.lang.clipboard[ this.type + 'Error' ] ); // Show cutError or copyError. + + return success; + } + }; + + // Paste command. + var pasteCmd = + { + canUndo : false, + + exec : + CKEDITOR.env.ie ? + function( editor ) + { + // Prevent IE from pasting at the begining of the document. + editor.focus(); + + if ( !editor.document.getBody().fire( 'beforepaste' ) + && !execIECommand( editor, 'paste' ) ) + { + editor.fire( 'pasteDialog' ); + return false; + } + } + : + function( editor ) + { + try + { + if ( !editor.document.getBody().fire( 'beforepaste' ) + && !editor.document.$.execCommand( 'Paste', false, null ) ) + { + throw 0; + } + } + catch ( e ) + { + 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 body = this.document.getBody(); + + // Simulate 'beforepaste' event for all none-IEs. + if ( !CKEDITOR.env.ie && body.fire( 'beforepaste' ) ) + event.cancel(); + // Simulate 'paste' event for Opera/Firefox2. + else if ( CKEDITOR.env.opera + || CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 ) + body.fire( 'paste' ); + return; + + // Cut + case CKEDITOR.CTRL + 88 : // CTRL+X + case CKEDITOR.SHIFT + 46 : // SHIFT+DEL + + // Save Undo snapshot. + var editor = this; + this.fire( 'saveSnapshot' ); // Save before paste + setTimeout( function() + { + editor.fire( 'saveSnapshot' ); // Save after paste + }, 0 ); + } + }; + + // 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; + + // If the browser supports it, get the data directly + if (mode == 'text' && evt.data && evt.data.$.clipboardData) + { + // evt.data.$.clipboardData.types contains all the flavours in Mac's Safari, but not on windows. + var plain = evt.data.$.clipboardData.getData( 'text/plain' ); + if (plain) + { + evt.data.preventDefault(); + callback( plain ); + 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' : CKEDITOR.env.webkit ? 'body' : '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 ); + + pastebin.setStyles( + { + position : 'absolute', + // 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' + }); + + // It's definitely a better user experience if we make the paste-bin pretty unnoticed + // by pulling it off the screen. + pastebin.setStyle( this.config.contentsLangDirection == 'ltr' ? 'left' : 'right', '-1000px' ); + + 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 ); + } + + // Cutting off control type element in IE standards breaks the selection entirely. (#4881) + function fixCut( editor ) + { + if ( !CKEDITOR.env.ie || editor.document.$.compatMode == 'BackCompat' ) + return; + + var sel = editor.getSelection(); + var control; + if( ( sel.getType() == CKEDITOR.SELECTION_ELEMENT ) && ( control = sel.getSelectedElement() ) ) + { + var range = sel.getRanges()[ 0 ]; + var dummy = editor.document.createText( '' ); + dummy.insertBefore( control ); + range.setStartBefore( dummy ); + range.setEndAfter( control ); + sel.selectRanges( [ range ] ); + + // Clear up the fix if the paste wasn't succeeded. + setTimeout( function() + { + // Element still online? + if ( control.getParent() ) + { + dummy.remove(); + sel.selectElement( control ); + } + }, 0 ); + } + } + + // Register the plugin. + CKEDITOR.plugins.add( 'clipboard', + { + requires : [ 'dialog', '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 ]; + + editor.addCommand( commandName, command ); + editor.ui.addButton( buttonName, + { + label : lang, + command : commandName + }); + + // If the "menu" plugin is loaded, register the menu item. + if ( editor.addMenuItems ) + { + editor.addMenuItem( commandName, + { + label : lang, + command : commandName, + group : 'clipboard', + order : ctxMenuOrder + }); + } + } + + addButtonCommand( 'Cut', 'cut', new cutCopyCmd( 'cut' ), 1 ); + addButtonCommand( 'Copy', 'copy', new cutCopyCmd( 'copy' ), 4 ); + addButtonCommand( 'Paste', 'paste', pasteCmd, 8 ); + + CKEDITOR.dialog.add( 'paste', CKEDITOR.getUrl( this.path + 'dialogs/paste.js' ) ); + + 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) || CKEDITOR.env.webkit ) ? 'paste' : 'beforepaste', + function( evt ) + { + if ( depressBeforeEvent ) + 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 ); + } ); + }); + + body.on( 'beforecut', function() { !depressBeforeEvent && fixCut( editor ); } ); + }); + + // If the "contextmenu" plugin is loaded, register the listeners. + if ( editor.contextMenu ) + { + var depressBeforeEvent; + function stateFromNamedCommand( command ) + { + // IE Bug: queryCommandEnabled('paste') fires also 'beforepaste(copy/cut)', + // guard to distinguish from the ordinary sources( either + // keyboard paste or execCommand ) (#4874). + CKEDITOR.env.ie && ( depressBeforeEvent = 1 ); + + var retval = editor.document.$.queryCommandEnabled( command ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED; + depressBeforeEvent = 0; + return retval; + } + + editor.contextMenu.addListener( function() + { + return { + cut : stateFromNamedCommand( 'Cut' ), + + // Browser bug: 'Cut' has the correct states for both Copy and Cut. + copy : stateFromNamedCommand( 'Cut' ), + paste : CKEDITOR.env.webkit ? CKEDITOR.TRISTATE_OFF : stateFromNamedCommand( 'Paste' ) + }; + }); + } + } + }); +})(); + +/** + * Fired when a clipboard operation is about to be taken into the editor. + * Listeners can manipulate the data to be pasted before having it effectively + * inserted into the document. + * @name CKEDITOR.editor#paste + * @since 3.1 + * @event + * @param {String} [data.html] The HTML data to be pasted. If not available, e.data.text will be defined. + * @param {String} [data.text] The plain text data to be pasted, available when plain text operations are to used. If not available, e.data.html will be defined. + */ diff --git a/_source/plugins/colorbutton/plugin.js b/_source/plugins/colorbutton/plugin.js index c87d068..dd127f9 100644 --- a/_source/plugins/colorbutton/plugin.js +++ b/_source/plugins/colorbutton/plugin.js @@ -1,227 +1,248 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-CKEDITOR.plugins.add( 'colorbutton',
-{
- requires : [ 'panelbutton', 'floatpanel', 'styles' ],
-
- init : function( editor )
- {
- var config = editor.config,
- lang = editor.lang.colorButton;
-
- var clickFn;
-
- if ( !CKEDITOR.env.hc )
- {
- addButton( 'TextColor', 'fore', lang.textColorTitle );
- addButton( 'BGColor', 'back', lang.bgColorTitle );
- }
-
- function addButton( name, type, title )
- {
- editor.ui.add( name, CKEDITOR.UI_PANELBUTTON,
- {
- label : title,
- title : title,
- className : 'cke_button_' + name.toLowerCase(),
- modes : { wysiwyg : 1 },
-
- panel :
- {
- css : editor.skin.editor.css,
- attributes : { role : 'listbox', 'aria-label' : lang.panelTitle }
- },
-
- onBlock : function( panel, block )
- {
- block.autoSize = true;
- block.element.addClass( 'cke_colorblock' );
- block.element.setHtml( renderColors( panel, type ) );
-
- var keys = block.keys;
- keys[ 39 ] = 'next'; // ARROW-RIGHT
- keys[ 40 ] = 'next'; // ARROW-DOWN
- keys[ 9 ] = 'next'; // TAB
- keys[ 37 ] = 'prev'; // ARROW-LEFT
- keys[ 38 ] = 'prev'; // ARROW-UP
- keys[ CKEDITOR.SHIFT + 9 ] = 'prev'; // SHIFT + TAB
- keys[ 32 ] = 'click'; // SPACE
- }
- });
- }
-
-
- function renderColors( panel, type )
- {
- var output = [],
- colors = config.colorButton_colors.split( ',' ),
- total = colors.length + ( config.colorButton_enableMore ? 2 : 1 );
-
- var clickFn = CKEDITOR.tools.addFunction( function( color, type )
- {
- if ( color == '?' )
- {
- 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;
- }
-
- editor.focus();
-
- panel.hide();
-
- var style = new CKEDITOR.style( config['colorButton_' + type + 'Style'], color && { color : color } );
-
- editor.fire( 'saveSnapshot' );
- if ( color )
- style.apply( editor.document );
- else
- style.remove( editor.document );
- editor.fire( 'saveSnapshot' );
- });
-
- // Render the "Automatic" button.
- output.push(
- '<a class="cke_colorauto" _cke_focus=1 hidefocus=true' +
- ' title="', lang.auto, '"' +
- ' onclick="CKEDITOR.tools.callFunction(', clickFn, ',null,\'', type, '\');return false;"' +
- ' href="javascript:void(\'', lang.auto, '\')"' +
- ' role="option" aria-posinset="1" aria-setsize="', total, '">' +
- '<table role="presentation" cellspacing=0 cellpadding=0 width="100%">' +
- '<tr>' +
- '<td>' +
- '<span class="cke_colorbox" style="background-color:#000"></span>' +
- '</td>' +
- '<td colspan=7 align=center>',
- lang.auto,
- '</td>' +
- '</tr>' +
- '</table>' +
- '</a>' +
- '<table role="presentation" cellspacing=0 cellpadding=0 width="100%">' );
-
- // Render the color boxes.
- for ( var i = 0 ; i < colors.length ; i++ )
- {
- if ( ( i % 8 ) === 0 )
- output.push( '</tr><tr>' );
-
- var colorCode = colors[ i ];
- var colorLabel = editor.lang.colors[ colorCode ] || colorCode;
- output.push(
- '<td>' +
- '<a class="cke_colorbox" _cke_focus=1 hidefocus=true' +
- ' title="', colorLabel, '"' +
- ' onclick="CKEDITOR.tools.callFunction(', clickFn, ',\'#', colorCode, '\',\'', type, '\'); return false;"' +
- ' href="javascript:void(\'', colorLabel, '\')"' +
- ' role="option" aria-posinset="', ( i + 2 ), '" aria-setsize="', total, '">' +
- '<span class="cke_colorbox" style="background-color:#', colorCode, '"></span>' +
- '</a>' +
- '</td>' );
- }
-
- // Render the "More Colors" button.
- if ( config.colorButton_enableMore )
- {
- output.push(
- '</tr>' +
- '<tr>' +
- '<td colspan=8 align=center>' +
- '<a class="cke_colormore" _cke_focus=1 hidefocus=true' +
- ' title="', lang.more, '"' +
- ' onclick="CKEDITOR.tools.callFunction(', clickFn, ',\'?\',\'', type, '\');return false;"' +
- ' href="javascript:void(\'', lang.more, '\')"',
- ' role="option" aria-posinset="', total, '" aria-setsize="', total, '">',
- lang.more,
- '</a>' +
- '</td>' ); // It is later in the code.
- }
-
- output.push( '</tr></table>' );
-
- return output.join( '' );
- }
- }
-});
-
-/**
- * Whether to enable the "More Colors..." button in the color selectors.
- * @default false
- * @type Boolean
- * @example
- * config.colorButton_enableMore = false;
- */
-CKEDITOR.config.colorButton_enableMore = true;
-
-/**
- * Defines the colors to be displayed in the color selectors. It's a string
- * containing the hexadecimal notation for HTML colors, without the "#" prefix.
- * @type String
- * @default '000,800000,8B4513,2F4F4F,008080,000080,4B0082,696969,B22222,A52A2A,DAA520,006400,40E0D0,0000CD,800080,808080,F00,FF8C00,FFD700,008000,0FF,00F,EE82EE,A9A9A9,FFA07A,FFA500,FFFF00,00FF00,AFEEEE,ADD8E6,DDA0DD,D3D3D3,FFF0F5,FAEBD7,FFFFE0,F0FFF0,F0FFFF,F0F8FF,E6E6FA,FFF'
- * @example
- * // Brazil colors only.
- * config.colorButton_colors = '00923E,F8C100,28166F';
- */
-CKEDITOR.config.colorButton_colors =
- '000,800000,8B4513,2F4F4F,008080,000080,4B0082,696969,' +
- 'B22222,A52A2A,DAA520,006400,40E0D0,0000CD,800080,808080,' +
- 'F00,FF8C00,FFD700,008000,0FF,00F,EE82EE,A9A9A9,' +
- 'FFA07A,FFA500,FFFF00,00FF00,AFEEEE,ADD8E6,DDA0DD,D3D3D3,' +
- 'FFF0F5,FAEBD7,FFFFE0,F0FFF0,F0FFFF,F0F8FF,E6E6FA,FFF';
-
-/**
- * Holds the style definition to be used to apply the text foreground color.
- * @type Object
- * @example
- * // This is basically the default setting value.
- * config.colorButton_foreStyle =
- * {
- * element : 'span',
- * styles : { 'color' : '#(color)' }
- * };
- */
-CKEDITOR.config.colorButton_foreStyle =
- {
- element : 'span',
- styles : { 'color' : '#(color)' },
- overrides : [ { element : 'font', attributes : { 'color' : null } } ],
-
- // Fore color style must be applied inside links instead of around it.
- childRule : function( element )
- {
- return element.getName() != 'a';
- }
- };
-
-/**
- * Holds the style definition to be used to apply the text background color.
- * @type Object
- * @example
- * // This is basically the default setting value.
- * config.colorButton_backStyle =
- * {
- * element : 'span',
- * styles : { 'background-color' : '#(color)' }
- * };
- */
-CKEDITOR.config.colorButton_backStyle =
- {
- element : 'span',
- styles : { 'background-color' : '#(color)' }
- };
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.plugins.add( 'colorbutton', +{ + requires : [ 'panelbutton', 'floatpanel', 'styles' ], + + init : function( editor ) + { + var config = editor.config, + lang = editor.lang.colorButton; + + var clickFn; + + if ( !CKEDITOR.env.hc ) + { + addButton( 'TextColor', 'fore', lang.textColorTitle ); + addButton( 'BGColor', 'back', lang.bgColorTitle ); + } + + function addButton( name, type, title ) + { + editor.ui.add( name, CKEDITOR.UI_PANELBUTTON, + { + label : title, + title : title, + className : 'cke_button_' + name.toLowerCase(), + modes : { wysiwyg : 1 }, + + panel : + { + css : editor.skin.editor.css, + attributes : { role : 'listbox', 'aria-label' : lang.panelTitle } + }, + + onBlock : function( panel, block ) + { + block.autoSize = true; + block.element.addClass( 'cke_colorblock' ); + block.element.setHtml( renderColors( panel, type ) ); + + var keys = block.keys; + keys[ 39 ] = 'next'; // ARROW-RIGHT + keys[ 40 ] = 'next'; // ARROW-DOWN + keys[ 9 ] = 'next'; // TAB + keys[ 37 ] = 'prev'; // ARROW-LEFT + keys[ 38 ] = 'prev'; // ARROW-UP + keys[ CKEDITOR.SHIFT + 9 ] = 'prev'; // SHIFT + TAB + keys[ 32 ] = 'click'; // SPACE + } + }); + } + + + function renderColors( panel, type ) + { + var output = [], + colors = config.colorButton_colors.split( ',' ), + total = colors.length + ( config.colorButton_enableMore ? 2 : 1 ); + + var clickFn = CKEDITOR.tools.addFunction( function( color, type ) + { + if ( color == '?' ) + { + 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; + } + + editor.focus(); + + panel.hide(); + + + editor.fire( 'saveSnapshot' ); + + // Clean up any conflicting style within the range. + new CKEDITOR.style( config['colorButton_' + type + 'Style'], { color : 'inherit' } ).remove( editor.document ); + + if ( color ) + { + var colorStyle = config['colorButton_' + type + 'Style']; + + colorStyle.childRule = type == 'back' ? + // It's better to apply background color as the innermost style. (#3599) + function(){ return false; } : + // Fore color style must be applied inside links instead of around it. + function( element ){ return element.getName() != 'a'; }; + + new CKEDITOR.style( colorStyle, { color : color } ).apply( editor.document ); + } + + editor.fire( 'saveSnapshot' ); + }); + + // Render the "Automatic" button. + output.push( + '<a class="cke_colorauto" _cke_focus=1 hidefocus=true' + + ' title="', lang.auto, '"' + + ' onclick="CKEDITOR.tools.callFunction(', clickFn, ',null,\'', type, '\');return false;"' + + ' href="javascript:void(\'', lang.auto, '\')"' + + ' role="option" aria-posinset="1" aria-setsize="', total, '">' + + '<table role="presentation" cellspacing=0 cellpadding=0 width="100%">' + + '<tr>' + + '<td>' + + '<span class="cke_colorbox" style="background-color:#000"></span>' + + '</td>' + + '<td colspan=7 align=center>', + lang.auto, + '</td>' + + '</tr>' + + '</table>' + + '</a>' + + '<table role="presentation" cellspacing=0 cellpadding=0 width="100%">' ); + + // Render the color boxes. + for ( var i = 0 ; i < colors.length ; i++ ) + { + if ( ( i % 8 ) === 0 ) + output.push( '</tr><tr>' ); + + var parts = colors[ i ].split( '/' ), + colorName = parts[ 0 ], + colorCode = parts[ 1 ] || colorName; + + // The data can be only a color code (without #) or colorName + color code + // If only a color code is provided, then the colorName is the color with the hash + // Convert the color from RGB to RRGGBB for better compatibility with IE and <font>. See #5676 + if (!parts[1]) + colorName = '#' + colorName.replace( /^(.)(.)(.)$/, '$1$1$2$2$3$3' ); + + var colorLabel = editor.lang.colors[ colorCode ] || colorCode; + output.push( + '<td>' + + '<a class="cke_colorbox" _cke_focus=1 hidefocus=true' + + ' title="', colorLabel, '"' + + ' onclick="CKEDITOR.tools.callFunction(', clickFn, ',\'', colorName, '\',\'', type, '\'); return false;"' + + ' href="javascript:void(\'', colorLabel, '\')"' + + ' role="option" aria-posinset="', ( i + 2 ), '" aria-setsize="', total, '">' + + '<span class="cke_colorbox" style="background-color:#', colorCode, '"></span>' + + '</a>' + + '</td>' ); + } + + // Render the "More Colors" button. + if ( config.colorButton_enableMore ) + { + output.push( + '</tr>' + + '<tr>' + + '<td colspan=8 align=center>' + + '<a class="cke_colormore" _cke_focus=1 hidefocus=true' + + ' title="', lang.more, '"' + + ' onclick="CKEDITOR.tools.callFunction(', clickFn, ',\'?\',\'', type, '\');return false;"' + + ' href="javascript:void(\'', lang.more, '\')"', + ' role="option" aria-posinset="', total, '" aria-setsize="', total, '">', + lang.more, + '</a>' + + '</td>' ); // It is later in the code. + } + + output.push( '</tr></table>' ); + + return output.join( '' ); + } + } +}); + +/** + * Whether to enable the "More Colors..." button in the color selectors. + * @default false + * @type Boolean + * @example + * config.colorButton_enableMore = false; + */ +CKEDITOR.config.colorButton_enableMore = true; + +/** + * Defines the colors to be displayed in the color selectors. It's a string + * containing the hexadecimal notation for HTML colors, without the "#" prefix. + * + * Since 3.3: A name may be optionally defined by prefixing the entries with the + * name and the slash character. For example, "FontColor1/FF9900" will be + * displayed as the color #FF9900 in the selector, but will be outputted as "FontColor1". + * @type String + * @default '000,800000,8B4513,2F4F4F,008080,000080,4B0082,696969,B22222,A52A2A,DAA520,006400,40E0D0,0000CD,800080,808080,F00,FF8C00,FFD700,008000,0FF,00F,EE82EE,A9A9A9,FFA07A,FFA500,FFFF00,00FF00,AFEEEE,ADD8E6,DDA0DD,D3D3D3,FFF0F5,FAEBD7,FFFFE0,F0FFF0,F0FFFF,F0F8FF,E6E6FA,FFF' + * @example + * // Brazil colors only. + * config.colorButton_colors = '00923E,F8C100,28166F'; + * @example + * config.colorButton_colors = 'FontColor1/FF9900,FontColor2/0066CC,FontColor3/F00' + */ +CKEDITOR.config.colorButton_colors = + '000,800000,8B4513,2F4F4F,008080,000080,4B0082,696969,' + + 'B22222,A52A2A,DAA520,006400,40E0D0,0000CD,800080,808080,' + + 'F00,FF8C00,FFD700,008000,0FF,00F,EE82EE,A9A9A9,' + + 'FFA07A,FFA500,FFFF00,00FF00,AFEEEE,ADD8E6,DDA0DD,D3D3D3,' + + 'FFF0F5,FAEBD7,FFFFE0,F0FFF0,F0FFFF,F0F8FF,E6E6FA,FFF'; + +/** + * Holds the style definition to be used to apply the text foreground color. + * @type Object + * @example + * // This is basically the default setting value. + * config.colorButton_foreStyle = + * { + * element : 'span', + * styles : { 'color' : '#(color)' } + * }; + */ +CKEDITOR.config.colorButton_foreStyle = + { + element : 'span', + styles : { 'color' : '#(color)' }, + overrides : [ { element : 'font', attributes : { 'color' : null } } ] + }; + +/** + * Holds the style definition to be used to apply the text background color. + * @type Object + * @example + * // This is basically the default setting value. + * config.colorButton_backStyle = + * { + * element : 'span', + * styles : { 'background-color' : '#(color)' } + * }; + */ +CKEDITOR.config.colorButton_backStyle = + { + element : 'span', + styles : { 'background-color' : '#(color)' } + }; diff --git a/_source/plugins/colordialog/dialogs/colordialog.js b/_source/plugins/colordialog/dialogs/colordialog.js index d99d033..9eb2261 100644 --- a/_source/plugins/colordialog/dialogs/colordialog.js +++ b/_source/plugins/colordialog/dialogs/colordialog.js @@ -1,191 +1,339 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-CKEDITOR.dialog.add( 'colordialog', function( editor )
- {
- // Define some shorthands.
- var $el = CKEDITOR.dom.element,
- $doc = CKEDITOR.document,
- $tools = CKEDITOR.tools,
- lang = editor.lang.colordialog;
-
- // Reference the dialog.
- var dialog;
-
- function spacer()
- {
- return {
- type : 'html',
- html : ' '
- };
- }
-
- var table = new $el( 'table' );
- createColorTable();
-
- var cellMouseover = function( event )
- {
- var color = new $el( event.data.getTarget() ).getAttribute( 'title' );
- $doc.getById( 'hicolor' ).setStyle( 'background-color', color );
- $doc.getById( 'hicolortext' ).setHtml( color );
- };
-
- var cellClick = function( event )
- {
- var color = new $el( event.data.getTarget() ).getAttribute( 'title' );
- dialog.getContentElement( 'picker', 'selectedColor' ).setValue( color );
- };
-
- function createColorTable()
- {
- // Create the base colors array.
- var aColors = ['00','33','66','99','cc','ff'];
-
- // This function combines two ranges of three values from the color array into a row.
- function appendColorRow( rangeA, rangeB )
- {
- for ( var i = rangeA ; i < rangeA + 3 ; i++ )
- {
- var row = table.$.insertRow(-1);
-
- for ( var j = rangeB ; j < rangeB + 3 ; j++ )
- {
- for ( var n = 0 ; n < 6 ; n++ )
- {
- appendColorCell( row, '#' + aColors[j] + aColors[n] + aColors[i] );
- }
- }
- }
- }
-
- // This function create a single color cell in the color table.
- function appendColorCell( targetRow, color )
- {
- var cell = new $el( targetRow.insertCell( -1 ) );
- cell.setAttribute( 'class', 'ColorCell' );
- cell.setStyle( 'background-color', color );
-
- cell.setStyle( 'width', '15px' );
- cell.setStyle( 'height', '15px' );
-
- // Pass unparsed color value in some markup-degradable form.
- cell.setAttribute( 'title', color );
- }
-
- appendColorRow( 0, 0 );
- appendColorRow( 3, 0 );
- appendColorRow( 0, 3 );
- appendColorRow( 3, 3 );
-
- // Create the last row.
- var oRow = table.$.insertRow(-1) ;
-
- // Create the gray scale colors cells.
- for ( var n = 0 ; n < 6 ; n++ )
- {
- appendColorCell( oRow, '#' + aColors[n] + aColors[n] + aColors[n] ) ;
- }
-
- // Fill the row with black cells.
- for ( var i = 0 ; i < 12 ; i++ )
- {
- appendColorCell( oRow, '#000000' ) ;
- }
- }
-
- function clear()
- {
- $doc.getById( 'selhicolor' ).removeStyle( 'background-color' );
- dialog.getContentElement( 'picker', 'selectedColor' ).setValue( '' );
- }
-
- var clearActual = $tools.addFunction( function()
- {
- $doc.getById( 'hicolor' ).removeStyle( 'background-color' );
- $doc.getById( 'hicolortext' ).setHtml( ' ' );
- } );
-
- return {
- title : lang.title,
- minWidth : 360,
- minHeight : 220,
- onLoad : function()
- {
- // Update reference.
- dialog = this;
- },
- contents : [
- {
- id : 'picker',
- label : lang.title,
- accessKey : 'I',
- elements :
- [
- {
- type : 'hbox',
- padding : 0,
- widths : [ '70%', '10%', '30%' ],
- children :
- [
- {
- type : 'html',
- html : '<table onmouseout="CKEDITOR.tools.callFunction( ' + clearActual + ' );">' + table.getHtml() + '</table>',
- onLoad : function()
- {
- var table = CKEDITOR.document.getById( this.domId );
- table.on( 'mouseover', cellMouseover );
- table.on( 'click', cellClick );
- }
- },
- spacer(),
- {
- type : 'vbox',
- padding : 0,
- widths : [ '70%', '5%', '25%' ],
- children :
- [
- {
- type : 'html',
- html : '<span>' + lang.highlight +'</span>\
- <div id="hicolor" style="border: 1px solid; height: 74px; width: 74px;"></div>\
- <div id="hicolortext"> </div>\
- <span>' + lang.selected +'</span>\
- <div id="selhicolor" style="border: 1px solid; height: 20px; width: 74px;"></div>'
- },
- {
- type : 'text',
- id : 'selectedColor',
- style : 'width: 74px',
- onChange : function()
- {
- // Try to update color preview with new value. If fails, then set it no none.
- try
- {
- $doc.getById( 'selhicolor' ).setStyle( 'background-color', this.getValue() );
- }
- catch ( e )
- {
- clear();
- }
- }
- },
- spacer(),
- {
- type : 'button',
- id : 'clear',
- style : 'margin-top: 5px',
- label : lang.clear,
- onClick : clear
- }
- ]
- }
- ]
- }
- ]
- }
- ]
- };
- }
- );
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.dialog.add( 'colordialog', function( editor ) + { + // Define some shorthands. + var $el = CKEDITOR.dom.element, + $doc = CKEDITOR.document, + $tools = CKEDITOR.tools, + lang = editor.lang.colordialog; + + // Reference the dialog. + var dialog; + + function spacer() + { + return { + type : 'html', + html : ' ' + }; + } + + function clearSelected() + { + $doc.getById( selHiColorId ).removeStyle( 'background-color' ); + dialog.getContentElement( 'picker', 'selectedColor' ).setValue( '' ); + } + + function updateSelected( evt ) + { + if ( ! (evt instanceof CKEDITOR.dom.event ) ) + evt = new CKEDITOR.dom.event( evt ); + + var target = evt.getTarget(), + color; + + if ( target.getName() == 'a' && ( color = target.getChild( 0 ).getHtml() ) ) + dialog.getContentElement( 'picker', 'selectedColor' ).setValue( color ); + } + + function updateHighlight( event ) + { + if ( ! (event instanceof CKEDITOR.dom.event ) ) + event = event.data; + + var target = event.getTarget(), + color; + + if ( target.getName() == 'a' && ( color = target.getChild( 0 ).getHtml() ) ) + { + $doc.getById( hicolorId ).setStyle( 'background-color', color ); + $doc.getById( hicolorTextId ).setHtml( color ); + } + } + + function clearHighlight() + { + $doc.getById( hicolorId ).removeStyle( 'background-color' ); + $doc.getById( hicolorTextId ).setHtml( ' ' ); + } + + var onMouseout = $tools.addFunction( clearHighlight ); + + var onClick = updateSelected, + onClickHandler = CKEDITOR.tools.addFunction( onClick ); + + var onFocus = updateHighlight, + onBlur = clearHighlight; + + var onKeydownHandler = CKEDITOR.tools.addFunction( function( ev ) + { + ev = new CKEDITOR.dom.event( ev ); + var element = ev.getTarget(); + var relative, nodeToMove; + var keystroke = ev.getKeystroke(); + var rtl = editor.lang.dir == 'rtl'; + + switch ( keystroke ) + { + // UP-ARROW + case 38 : + // relative is TR + if ( ( relative = element.getParent().getParent().getPrevious() ) ) + { + nodeToMove = relative.getChild( [element.getParent().getIndex(), 0] ); + nodeToMove.focus(); + onBlur( ev, element ); + onFocus( ev, nodeToMove ); + } + ev.preventDefault(); + break; + // DOWN-ARROW + case 40 : + // relative is TR + if ( ( relative = element.getParent().getParent().getNext() ) ) + { + nodeToMove = relative.getChild( [ element.getParent().getIndex(), 0 ] ); + if ( nodeToMove && nodeToMove.type == 1 ) + { + nodeToMove.focus(); + onBlur( ev, element ); + onFocus( ev, nodeToMove ); + } + } + ev.preventDefault(); + break; + // SPACE + // ENTER is already handled as onClick + case 32 : + onClick( ev ); + ev.preventDefault(); + break; + + // RIGHT-ARROW + case rtl ? 37 : 39 : + // relative is TD + if ( ( relative = element.getParent().getNext() ) ) + { + nodeToMove = relative.getChild( 0 ); + if ( nodeToMove.type == 1 ) + { + nodeToMove.focus(); + onBlur( ev, element ); + onFocus( ev, nodeToMove ); + ev.preventDefault( true ); + } + else + onBlur( null, element ); + } + // relative is TR + else if ( ( relative = element.getParent().getParent().getNext() ) ) + { + nodeToMove = relative.getChild( [ 0, 0 ] ); + if ( nodeToMove && nodeToMove.type == 1 ) + { + nodeToMove.focus(); + onBlur( ev, element ); + onFocus( ev, nodeToMove ); + ev.preventDefault( true ); + } + else + onBlur( null, element ); + } + break; + + // LEFT-ARROW + case rtl ? 39 : 37 : + // relative is TD + if ( ( relative = element.getParent().getPrevious() ) ) + { + nodeToMove = relative.getChild( 0 ); + nodeToMove.focus(); + onBlur( ev, element ); + onFocus( ev, nodeToMove ); + ev.preventDefault( true ); + } + // relative is TR + else if ( ( relative = element.getParent().getParent().getPrevious() ) ) + { + nodeToMove = relative.getLast().getChild( 0 ); + nodeToMove.focus(); + onBlur( ev, element ); + onFocus( ev, nodeToMove ); + ev.preventDefault( true ); + } + else + onBlur( null, element ); + break; + default : + // Do not stop not handled events. + return; + } + }); + + function createColorTable() + { + // Create the base colors array. + var aColors = ['00','33','66','99','cc','ff']; + + // This function combines two ranges of three values from the color array into a row. + function appendColorRow( rangeA, rangeB ) + { + for ( var i = rangeA ; i < rangeA + 3 ; i++ ) + { + var row = table.$.insertRow(-1); + + for ( var j = rangeB ; j < rangeB + 3 ; j++ ) + { + for ( var n = 0 ; n < 6 ; n++ ) + { + appendColorCell( row, '#' + aColors[j] + aColors[n] + aColors[i] ); + } + } + } + } + + // This function create a single color cell in the color table. + function appendColorCell( targetRow, color ) + { + var cell = new $el( targetRow.insertCell( -1 ) ); + cell.setAttribute( 'class', 'ColorCell' ); + cell.setStyle( 'background-color', color ); + + cell.setStyle( 'width', '15px' ); + cell.setStyle( 'height', '15px' ); + + var index = cell.$.cellIndex + 1 + 18 * targetRow.rowIndex; + cell.append( CKEDITOR.dom.element.createFromHtml( + '<a href="javascript: void(0);" role="option"' + + ' aria-posinset="' + index + '"' + + ' aria-setsize="' + 13 * 18 + '"' + + ' style="cursor: pointer;display:block;width:100%;height:100% " title="'+ CKEDITOR.tools.htmlEncode( color )+ '"' + + ' onkeydown="CKEDITOR.tools.callFunction( ' + onKeydownHandler + ', event, this )"' + + ' onclick="CKEDITOR.tools.callFunction(' + onClickHandler + ', event, this ); return false;"' + + ' tabindex="-1"><span class="cke_voice_label">' + color + '</span> </a>', CKEDITOR.document ) ); + } + + appendColorRow( 0, 0 ); + appendColorRow( 3, 0 ); + appendColorRow( 0, 3 ); + appendColorRow( 3, 3 ); + + // Create the last row. + var oRow = table.$.insertRow(-1) ; + + // Create the gray scale colors cells. + for ( var n = 0 ; n < 6 ; n++ ) + { + appendColorCell( oRow, '#' + aColors[n] + aColors[n] + aColors[n] ) ; + } + + // Fill the row with black cells. + for ( var i = 0 ; i < 12 ; i++ ) + { + appendColorCell( oRow, '#000000' ) ; + } + } + + var table = new $el( 'table' ); + createColorTable(); + + var numbering = function( id ) + { + return id + CKEDITOR.tools.getNextNumber(); + }, + hicolorId = numbering( 'hicolor' ), + hicolorTextId = numbering( 'hicolortext' ), + selHiColorId = numbering( 'selhicolor' ); + + return { + title : lang.title, + minWidth : 360, + minHeight : 220, + onLoad : function() + { + // Update reference. + dialog = this; + }, + contents : [ + { + id : 'picker', + label : lang.title, + accessKey : 'I', + elements : + [ + { + type : 'hbox', + padding : 0, + widths : [ '70%', '10%', '30%' ], + children : + [ + { + type : 'html', + html : '<table role="listbox" aria-labelledby="color_table_label" onmouseout="CKEDITOR.tools.callFunction( ' + onMouseout + ' );">' + table.getHtml() + '</table>' + + '<span id="color_table_label" class="cke_voice_label">' + lang.options +'</span>', + onLoad : function() + { + var table = CKEDITOR.document.getById( this.domId ); + table.on( 'mouseover', updateHighlight ); + }, + focus: function() + { + var firstColor = this.getElement().getElementsByTag( 'a' ).getItem( 0 ); + firstColor.focus(); + } + }, + spacer(), + { + type : 'vbox', + padding : 0, + widths : [ '70%', '5%', '25%' ], + children : + [ + { + type : 'html', + html : '<span>' + lang.highlight +'</span>\ + <div id="' + hicolorId + '" style="border: 1px solid; height: 74px; width: 74px;"></div>\ + <div id="' + hicolorTextId + '"> </div><span>' + lang.selected + '</span>\ + <div id="' + selHiColorId + '" style="border: 1px solid; height: 20px; width: 74px;"></div>' + }, + { + type : 'text', + label : lang.selected, + labelStyle: 'display:none', + id : 'selectedColor', + style : 'width: 74px', + onChange : function() + { + // Try to update color preview with new value. If fails, then set it no none. + try + { + $doc.getById( selHiColorId ).setStyle( 'background-color', this.getValue() ); + } + catch ( e ) + { + clearSelected(); + } + } + }, + spacer(), + { + type : 'button', + id : 'clear', + style : 'margin-top: 5px', + label : lang.clear, + onClick : clearSelected + } + ] + } + ] + } + ] + } + ] + }; + } + ); diff --git a/_source/plugins/contextmenu/plugin.js b/_source/plugins/contextmenu/plugin.js index 60aad92..4516167 100644 --- a/_source/plugins/contextmenu/plugin.js +++ b/_source/plugins/contextmenu/plugin.js @@ -1,274 +1,276 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-CKEDITOR.plugins.add( 'contextmenu',
-{
- requires : [ 'menu' ],
-
- beforeInit : function( editor )
- {
- editor.contextMenu = new CKEDITOR.plugins.contextMenu( editor );
-
- editor.addCommand( 'contextMenu',
- {
- exec : function()
- {
- editor.contextMenu.show( editor.document.getBody() );
- }
- });
- }
-});
-
-CKEDITOR.plugins.contextMenu = CKEDITOR.tools.createClass(
-{
- $ : function( editor )
- {
- this.id = 'cke_' + CKEDITOR.tools.getNextNumber();
- this.editor = editor;
- this._.listeners = [];
- this._.functionId = CKEDITOR.tools.addFunction( function( commandName )
- {
- this._.panel.hide();
- editor.focus();
- editor.execCommand( commandName );
- },
- this);
-
- this._.definiton =
- {
- panel:
- {
- className : editor.skinClass + ' cke_contextmenu',
- attributes :
- {
- 'aria-label' : editor.lang.common.options
- }
- }
- };
- },
-
- _ :
- {
- onMenu : function( offsetParent, corner, offsetX, offsetY )
- {
- var menu = this._.menu,
- editor = this.editor;
-
- if ( menu )
- {
- menu.hide();
- menu.removeAll();
- }
- else
- {
- menu = this._.menu = new CKEDITOR.menu( editor, this._.definiton );
- menu.onClick = CKEDITOR.tools.bind( function( item )
- {
- menu.hide();
-
- if ( item.onClick )
- item.onClick();
- else if ( item.command )
- editor.execCommand( item.command );
-
- }, this );
-
- menu.onEscape = function( keystroke )
- {
- var parent = this.parent;
- // 1. If it's sub-menu, restore the last focused item
- // of upper level menu.
- // 2. In case of a top-menu, close it.
- if ( parent )
- {
- parent._.panel.hideChild();
- // Restore parent block item focus.
- var parentBlock = parent._.panel._.panel._.currentBlock,
- parentFocusIndex = parentBlock._.focusIndex;
- parentBlock._.markItem( parentFocusIndex );
- }
- else if ( keystroke == 27 )
- {
- this.hide();
- editor.focus();
- }
- return false;
- };
- }
-
- var listeners = this._.listeners,
- includedItems = [];
-
- var selection = this.editor.getSelection(),
- element = selection && selection.getStartElement();
-
- menu.onHide = CKEDITOR.tools.bind( function()
- {
- menu.onHide = null;
-
- if ( CKEDITOR.env.ie )
- {
- var selection = editor.getSelection();
- selection && selection.unlock();
- }
-
- this.onHide && this.onHide();
- },
- this );
-
- // Call all listeners, filling the list of items to be displayed.
- for ( var i = 0 ; i < listeners.length ; i++ )
- {
- var listenerItems = listeners[ i ]( element, selection );
-
- if ( listenerItems )
- {
- for ( var itemName in listenerItems )
- {
- var item = this.editor.getMenuItem( itemName );
-
- if ( item )
- {
- item.state = listenerItems[ itemName ];
- menu.add( item );
- }
- }
- }
- }
-
- // 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, 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();
-
- var offsetParent = domEvent.getTarget().getDocument().getDocumentElement(),
- offsetX = domEvent.$.clientX,
- offsetY = domEvent.$.clientY;
-
- CKEDITOR.tools.setTimeout( function()
- {
- this.show( offsetParent, null, offsetX, offsetY );
- },
- 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 )
- {
- this._.listeners.push( listenerFn );
- },
-
- show : function( offsetParent, corner, offsetX, offsetY )
- {
- this.editor.focus();
- this._.onMenu( offsetParent || CKEDITOR.document.getDocumentElement(), corner, offsetX || 0, offsetY || 0 );
- }
- }
-});
-
-/**
- * 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;
- */
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.plugins.add( 'contextmenu', +{ + requires : [ 'menu' ], + + beforeInit : function( editor ) + { + editor.contextMenu = new CKEDITOR.plugins.contextMenu( editor ); + + editor.addCommand( 'contextMenu', + { + exec : function() + { + editor.contextMenu.show( editor.document.getBody() ); + } + }); + } +}); + +CKEDITOR.plugins.contextMenu = CKEDITOR.tools.createClass( +{ + $ : function( editor ) + { + this.id = 'cke_' + CKEDITOR.tools.getNextNumber(); + this.editor = editor; + this._.listeners = []; + this._.functionId = CKEDITOR.tools.addFunction( function( commandName ) + { + this._.panel.hide(); + editor.focus(); + editor.execCommand( commandName ); + }, + this); + + this.definition = + { + panel: + { + className : editor.skinClass + ' cke_contextmenu', + attributes : + { + 'aria-label' : editor.lang.contextmenu.options + } + } + }; + }, + + _ : + { + onMenu : function( offsetParent, corner, offsetX, offsetY ) + { + var menu = this._.menu, + editor = this.editor; + + if ( menu ) + { + menu.hide(); + menu.removeAll(); + } + else + { + menu = this._.menu = new CKEDITOR.menu( editor, this.definition ); + menu.onClick = CKEDITOR.tools.bind( function( item ) + { + menu.hide(); + + if ( item.onClick ) + item.onClick(); + else if ( item.command ) + editor.execCommand( item.command ); + + }, this ); + + menu.onEscape = function( keystroke ) + { + var parent = this.parent; + // 1. If it's sub-menu, restore the last focused item + // of upper level menu. + // 2. In case of a top-menu, close it. + if ( parent ) + { + parent._.panel.hideChild(); + // Restore parent block item focus. + var parentBlock = parent._.panel._.panel._.currentBlock, + parentFocusIndex = parentBlock._.focusIndex; + parentBlock._.markItem( parentFocusIndex ); + } + else if ( keystroke == 27 ) + { + this.hide(); + editor.focus(); + } + return false; + }; + } + + var listeners = this._.listeners, + includedItems = []; + + var selection = this.editor.getSelection(), + element = selection && selection.getStartElement(); + + menu.onHide = CKEDITOR.tools.bind( function() + { + menu.onHide = null; + + if ( CKEDITOR.env.ie ) + { + var selection = editor.getSelection(); + selection && selection.unlock(); + } + + this.onHide && this.onHide(); + }, + this ); + + // Call all listeners, filling the list of items to be displayed. + for ( var i = 0 ; i < listeners.length ; i++ ) + { + var listenerItems = listeners[ i ]( element, selection ); + + if ( listenerItems ) + { + for ( var itemName in listenerItems ) + { + var item = this.editor.getMenuItem( itemName ); + + if ( item ) + { + item.state = listenerItems[ itemName ]; + menu.add( item ); + } + } + } + } + + // 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, 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 + && ( CKEDITOR.env.mac ? evt.$.metaKey : evt.$.ctrlKey ) ) + 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 : ( CKEDITOR.env.mac ? domEvent.$.metaKey : domEvent.$.ctrlKey ) ) ) + return; + + + // Cancel the browser context menu. + domEvent.preventDefault(); + + var offsetParent = domEvent.getTarget().getDocument().getDocumentElement(), + offsetX = domEvent.$.clientX, + offsetY = domEvent.$.clientY; + + CKEDITOR.tools.setTimeout( function() + { + this.show( offsetParent, null, offsetX, offsetY ); + }, + 0, this ); + }, + this ); + + if ( CKEDITOR.env.webkit ) + { + var holdCtrlKey, + onKeyDown = function( event ) + { + holdCtrlKey = CKEDITOR.env.mac ? event.data.$.metaKey : event.data.$.ctrlKey ; + }, + resetOnKeyUp = function() + { + holdCtrlKey = 0; + }; + + element.on( 'keydown', onKeyDown ); + element.on( 'keyup', resetOnKeyUp ); + element.on( 'contextmenu', resetOnKeyUp ); + } + }, + + addListener : function( listenerFn ) + { + this._.listeners.push( listenerFn ); + }, + + show : function( offsetParent, corner, offsetX, offsetY ) + { + this.editor.focus(); + + // 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(); + } + + this._.onMenu( offsetParent || CKEDITOR.document.getDocumentElement(), corner, offsetX || 0, offsetY || 0 ); + } + } +}); + +/** + * 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/plugin.js b/_source/plugins/dialog/plugin.js index 6b5c2e4..8f48a2e 100644 --- a/_source/plugins/dialog/plugin.js +++ b/_source/plugins/dialog/plugin.js @@ -1,2884 +1,2950 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-/**
- * @fileOverview The floating dialog plugin.
- */
-
-/**
- * No resize for this dialog.
- * @constant
- */
-CKEDITOR.DIALOG_RESIZE_NONE = 0;
-
-/**
- * Only allow horizontal resizing for this dialog, disable vertical resizing.
- * @constant
- */
-CKEDITOR.DIALOG_RESIZE_WIDTH = 1;
-
-/**
- * Only allow vertical resizing for this dialog, disable horizontal resizing.
- * @constant
- */
-CKEDITOR.DIALOG_RESIZE_HEIGHT = 2;
-
-/*
- * Allow the dialog to be resized in both directions.
- * @constant
- */
-CKEDITOR.DIALOG_RESIZE_BOTH = 3;
-
-(function()
-{
- function isTabVisible( tabId )
- {
- return !!this._.tabs[ tabId ][ 0 ].$.offsetHeight;
- }
-
- function getPreviousVisibleTab()
- {
- var tabId = this._.currentTabId,
- length = this._.tabIdList.length,
- tabIndex = CKEDITOR.tools.indexOf( this._.tabIdList, tabId ) + length;
-
- for ( var i = tabIndex - 1 ; i > tabIndex - length ; i-- )
- {
- if ( isTabVisible.call( this, this._.tabIdList[ i % length ] ) )
- return this._.tabIdList[ i % length ];
- }
-
- return null;
- }
-
- function getNextVisibleTab()
- {
- var tabId = this._.currentTabId,
- length = this._.tabIdList.length,
- tabIndex = CKEDITOR.tools.indexOf( this._.tabIdList, tabId );
-
- for ( var i = tabIndex + 1 ; i < tabIndex + length ; i++ )
- {
- if ( isTabVisible.call( this, this._.tabIdList[ i % length ] ) )
- return this._.tabIdList[ i % length ];
- }
-
- return null;
- }
-
- /**
- * This is the base class for runtime dialog objects. An instance of this
- * class represents a single named dialog for a single editor instance.
- * @param {Object} editor The editor which created the dialog.
- * @param {String} dialogName The dialog's registered name.
- * @constructor
- * @example
- * var dialogObj = new CKEDITOR.dialog( editor, 'smiley' );
- */
- CKEDITOR.dialog = function( editor, dialogName )
- {
- // Load the dialog definition.
- var definition = CKEDITOR.dialog._.dialogDefinitions[ dialogName ];
-
- // Completes the definition with the default values.
- definition = CKEDITOR.tools.extend( definition( editor ), defaultDialogDefinition );
-
- // Clone a functionally independent copy for this dialog.
- definition = CKEDITOR.tools.clone( definition );
-
- // Create a complex definition object, extending it with the API
- // functions.
- definition = new definitionObject( this, definition );
-
-
- var doc = CKEDITOR.document;
-
- var themeBuilt = editor.theme.buildDialog( editor );
-
- // Initialize some basic parameters.
- this._ =
- {
- editor : editor,
- element : themeBuilt.element,
- name : dialogName,
- contentSize : { width : 0, height : 0 },
- size : { width : 0, height : 0 },
- updateSize : false,
- contents : {},
- buttons : {},
- accessKeyMap : {},
-
- // Initialize the tab and page map.
- tabs : {},
- tabIdList : [],
- currentTabId : null,
- currentTabIndex : null,
- pageCount : 0,
- lastTab : null,
- tabBarMode : false,
-
- // Initialize the tab order array for input widgets.
- focusList : [],
- currentFocusIndex : 0,
- hasFocus : false
- };
-
- this.parts = themeBuilt.parts;
-
- CKEDITOR.tools.setTimeout( function()
- {
- editor.fire( 'ariaWidget', this.parts.contents );
- },
- 0, this );
-
- // Set the startup styles for the dialog, avoiding it enlarging the
- // page size on the dialog creation.
- this.parts.dialog.setStyles(
- {
- position : CKEDITOR.env.ie6Compat ? 'absolute' : 'fixed',
- top : 0,
- left: 0,
- visibility : 'hidden'
- });
-
- // Call the CKEDITOR.event constructor to initialize this instance.
- CKEDITOR.event.call( this );
-
- // Fire the "dialogDefinition" event, making it possible to customize
- // the dialog definition.
- this.definition = definition = CKEDITOR.fire( 'dialogDefinition',
- {
- name : dialogName,
- definition : definition
- }
- , editor ).definition;
- // Initialize load, show, hide, ok and cancel events.
- if ( definition.onLoad )
- this.on( 'load', definition.onLoad );
-
- if ( definition.onShow )
- this.on( 'show', definition.onShow );
-
- if ( definition.onHide )
- this.on( 'hide', definition.onHide );
-
- if ( definition.onOk )
- {
- this.on( 'ok', function( evt )
- {
- if ( definition.onOk.call( this, evt ) === false )
- evt.data.hide = false;
- });
- }
-
- if ( definition.onCancel )
- {
- this.on( 'cancel', function( evt )
- {
- if ( definition.onCancel.call( this, evt ) === false )
- evt.data.hide = false;
- });
- }
-
- var me = this;
-
- // Iterates over all items inside all content in the dialog, calling a
- // function for each of them.
- var iterContents = function( func )
- {
- var contents = me._.contents,
- stop = false;
-
- for ( var i in contents )
- {
- for ( var j in contents[i] )
- {
- stop = func.call( this, contents[i][j] );
- if ( stop )
- return;
- }
- }
- };
-
- this.on( 'ok', function( evt )
- {
- iterContents( function( item )
- {
- if ( item.validate )
- {
- var isValid = item.validate( this );
-
- if ( typeof isValid == 'string' )
- {
- alert( isValid );
- isValid = false;
- }
-
- if ( isValid === false )
- {
- if ( item.select )
- item.select();
- else
- item.focus();
-
- evt.data.hide = false;
- evt.stop();
- return true;
- }
- }
- });
- }, this, null, 0 );
-
- this.on( 'cancel', function( evt )
- {
- iterContents( function( item )
- {
- if ( item.isChanged() )
- {
- if ( !confirm( editor.lang.common.confirmCancel ) )
- evt.data.hide = false;
- return true;
- }
- });
- }, this, null, 0 );
-
- this.parts.close.on( 'click', function( evt )
- {
- if ( this.fire( 'cancel', { hide : true } ).hide !== false )
- this.hide();
- }, this );
-
- // Sort focus list according to tab order definitions.
- function setupFocus()
- {
- var focusList = me._.focusList;
- focusList.sort( function( a, b )
- {
- // Mimics browser tab order logics;
- if ( a.tabIndex != b.tabIndex )
- return b.tabIndex - a.tabIndex;
- // Sort is not stable in some browsers,
- // fall-back the comparator to 'focusIndex';
- else
- return a.focusIndex - b.focusIndex;
- });
-
- var size = focusList.length;
- for ( var i = 0; i < size; i++ )
- focusList[ i ].focusIndex = i;
- }
-
- function changeFocus( forward )
- {
- var focusList = me._.focusList,
- offset = forward ? 1 : -1;
- if ( focusList.length < 1 )
- return;
-
- var current = me._.currentFocusIndex;
-
- // Trigger the 'blur' event of any input element before anything,
- // since certain UI updates may depend on it.
- try
- {
- focusList[ current ].getInputElement().$.blur();
- }
- catch( e ){}
-
- var startIndex = ( current + offset + focusList.length ) % focusList.length,
- currentIndex = startIndex;
- while ( !focusList[ currentIndex ].isFocusable() )
- {
- currentIndex = ( currentIndex + offset + focusList.length ) % focusList.length;
- if ( currentIndex == startIndex )
- break;
- }
- focusList[ currentIndex ].focus();
-
- // Select whole field content.
- if ( focusList[ currentIndex ].type == 'text' )
- focusList[ currentIndex ].select();
- }
-
- this.changeFocus = changeFocus;
-
- 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 = 0;
- if ( keystroke == 9 || keystroke == CKEDITOR.SHIFT + 9 )
- {
- var shiftPressed = ( keystroke == CKEDITOR.SHIFT + 9 );
-
- // Handling Tab and Shift-Tab.
- if ( me._.tabBarMode )
- {
- // Change tabs.
- var nextId = shiftPressed ? getPreviousVisibleTab.call( me ) : getNextVisibleTab.call( me );
- me.selectPage( nextId );
- me._.tabs[ nextId ][ 0 ].focus();
- }
- else
- {
- // Change the focus of inputs.
- changeFocus( !shiftPressed );
- }
-
- 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 = 1;
- }
- else if ( ( keystroke == 37 || keystroke == 39 ) && me._.tabBarMode )
- {
- // Arrow keys - used for changing tabs.
- nextId = ( keystroke == 37 ? getPreviousVisibleTab.call( me ) : getNextVisibleTab.call( me ) );
- me.selectPage( nextId );
- me._.tabs[ nextId ][ 0 ].focus();
- processed = 1;
- }
- else if ( ( keystroke == 13 || keystroke == 32 ) && me._.tabBarMode )
- {
- this.selectPage( this._.currentTabId );
- this._.tabBarMode = false;
- this._.currentFocusIndex = -1;
- changeFocus( true );
- processed = 1;
- }
-
- if ( processed )
- {
- evt.stop();
- evt.data.preventDefault();
- }
- }
-
- function focusKeyPressHandler( evt )
- {
- processed && evt.data.preventDefault();
- }
-
- var dialogElement = this._.element;
- // Add the dialog keyboard handlers.
- this.on( 'show', function()
- {
- dialogElement.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 ) )
- dialogElement.on( 'keypress', focusKeyPressHandler, this );
-
- if ( CKEDITOR.env.ie6Compat )
- {
- var coverDoc = coverElement.getChild( 0 ).getFrameDocument();
- coverDoc.on( 'keydown', focusKeydownHandler, this, null, 0 );
- }
- } );
- this.on( 'hide', function()
- {
- dialogElement.removeListener( 'keydown', focusKeydownHandler );
- if ( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.mac ) )
- dialogElement.removeListener( 'keypress', focusKeyPressHandler );
- } );
- this.on( 'iframeAdded', function( evt )
- {
- var doc = new CKEDITOR.dom.document( evt.data.iframe.$.contentWindow.document );
- doc.on( 'keydown', focusKeydownHandler, this, null, 0 );
- } );
-
- // Auto-focus logic in dialog.
- this.on( 'show', function()
- {
- // Setup tabIndex on showing the dialog instead of on loading
- // to allow dynamic tab order happen in dialog definition.
- setupFocus();
-
- if ( editor.config.dialog_startupFocusTab
- && me._.tabIdList.length > 1 )
- {
- me._.tabBarMode = true;
- me._.tabs[ me._.currentTabId ][ 0 ].focus();
- }
- else if ( !this._.hasFocus )
- {
- this._.currentFocusIndex = -1;
-
- // Decide where to put the initial focus.
- if ( definition.onFocus )
- {
- var initialFocus = definition.onFocus.call( this );
- // Focus the field that the user specified.
- initialFocus && initialFocus.focus();
- }
- // Focus the first field in layout order.
- else
- changeFocus( true );
-
- /*
- * IE BUG: If the initial focus went into a non-text element (e.g. button),
- * then IE would still leave the caret inside the editing area.
- */
- if ( this._.editor.mode == 'wysiwyg' && CKEDITOR.env.ie )
- {
- var $selection = editor.document.$.selection,
- $range = $selection.createRange();
-
- if ( $range )
- {
- if ( $range.parentElement && $range.parentElement().ownerDocument == editor.document.$
- || $range.item && $range.item( 0 ).ownerDocument == editor.document.$ )
- {
- var $myRange = document.body.createTextRange();
- $myRange.moveToElementText( this.getElement().getFirst().$ );
- $myRange.collapse( true );
- $myRange.select();
- }
- }
- }
- }
- }, this, null, 0xffffffff );
-
- // IE6 BUG: Text fields and text areas are only half-rendered the first time the dialog appears in IE6 (#2661).
- // This is still needed after [2708] and [2709] because text fields in hidden TR tags are still broken.
- if ( CKEDITOR.env.ie6Compat )
- {
- this.on( 'load', function( evt )
- {
- var outer = this.getElement(),
- inner = outer.getFirst();
- inner.remove();
- inner.appendTo( outer );
- }, this );
- }
-
- initDragAndDrop( this );
- initResizeHandles( this );
-
- // Insert the title.
- ( new CKEDITOR.dom.text( definition.title, CKEDITOR.document ) ).appendTo( this.parts.title );
-
- // Insert the tabs and contents.
- for ( var i = 0 ; i < definition.contents.length ; i++ )
- this.addPage( definition.contents[i] );
-
- this.parts['tabs'].on( 'click', function( evt )
- {
- var target = evt.data.getTarget();
- // If we aren't inside a tab, bail out.
- if ( target.hasClass( 'cke_dialog_tab' ) )
- {
- var id = target.$.id;
- this.selectPage( id.substr( 0, id.lastIndexOf( '_' ) ) );
- if ( this._.tabBarMode )
- {
- this._.tabBarMode = false;
- this._.currentFocusIndex = -1;
- changeFocus( true );
- }
- evt.data.preventDefault();
- }
- }, this );
-
- // Insert buttons.
- var buttonsHtml = [],
- buttons = CKEDITOR.dialog._.uiElementBuilders.hbox.build( this,
- {
- type : 'hbox',
- className : 'cke_dialog_footer_buttons',
- widths : [],
- children : definition.buttons
- }, buttonsHtml ).getChild();
- this.parts.footer.setHtml( buttonsHtml.join( '' ) );
-
- for ( i = 0 ; i < buttons.length ; i++ )
- this._.buttons[ buttons[i].id ] = buttons[i];
-
- CKEDITOR.skins.load( editor, 'dialog' );
- };
-
- // Focusable interface. Use it via dialog.addFocusable.
- function Focusable( dialog, element, index )
- {
- this.element = element;
- this.focusIndex = index;
- // TODO: support tabIndex for focusables.
- this.tabIndex = 0;
- this.isFocusable = function()
- {
- return !element.getAttribute( 'disabled' ) && element.isVisible();
- };
- this.focus = function()
- {
- dialog._.currentFocusIndex = this.focusIndex;
- this.element.focus();
- };
- // Bind events
- element.on( 'keydown', function( e )
- {
- if ( e.data.getKeystroke() in { 32:1, 13:1 } )
- this.fire( 'click' );
- } );
- element.on( 'focus', function()
- {
- this.fire( 'mouseover' );
- } );
- element.on( 'blur', function()
- {
- this.fire( 'mouseout' );
- } );
- }
-
- CKEDITOR.dialog.prototype =
- {
- /**
- * Resizes the dialog.
- * @param {Number} width The width of the dialog in pixels.
- * @param {Number} height The height of the dialog in pixels.
- * @function
- * @example
- * dialogObj.resize( 800, 640 );
- */
- resize : (function()
- {
- return function( width, height )
- {
- if ( this._.contentSize && this._.contentSize.width == width && this._.contentSize.height == height )
- return;
-
- CKEDITOR.dialog.fire( 'resize',
- {
- dialog : this,
- skin : this._.editor.skinName,
- width : width,
- height : height
- }, this._.editor );
-
- this._.contentSize = { width : width, height : height };
- this._.updateSize = true;
- };
- })(),
-
- /**
- * Gets the current size of the dialog in pixels.
- * @returns {Object} An object with "width" and "height" properties.
- * @example
- * var width = dialogObj.getSize().width;
- */
- getSize : function()
- {
- if ( !this._.updateSize )
- return this._.size;
- var element = this._.element.getFirst();
- var size = this._.size = { width : element.$.offsetWidth || 0, height : element.$.offsetHeight || 0};
-
- // If either the offsetWidth or offsetHeight is 0, the element isn't visible.
- this._.updateSize = !size.width || !size.height;
-
- return size;
- },
-
- /**
- * Moves the dialog to an (x, y) coordinate relative to the window.
- * @function
- * @param {Number} x The target x-coordinate.
- * @param {Number} y The target y-coordinate.
- * @example
- * dialogObj.move( 10, 40 );
- */
- move : (function()
- {
- var isFixed;
- return function( x, y )
- {
- // The dialog may be fixed positioned or absolute positioned. Ask the
- // browser what is the current situation first.
- var element = this._.element.getFirst();
- if ( isFixed === undefined )
- isFixed = element.getComputedStyle( 'position' ) == 'fixed';
-
- if ( isFixed && this._.position && this._.position.x == x && this._.position.y == y )
- return;
-
- // Save the current position.
- this._.position = { x : x, y : y };
-
- // If not fixed positioned, add scroll position to the coordinates.
- if ( !isFixed )
- {
- var scrollPosition = CKEDITOR.document.getWindow().getScrollPosition();
- x += scrollPosition.x;
- y += scrollPosition.y;
- }
-
- element.setStyles(
- {
- 'left' : ( x > 0 ? x : 0 ) + 'px',
- 'top' : ( y > 0 ? y : 0 ) + 'px'
- });
- };
- })(),
-
- /**
- * Gets the dialog's position in the window.
- * @returns {Object} An object with "x" and "y" properties.
- * @example
- * var dialogX = dialogObj.getPosition().x;
- */
- getPosition : function(){ return CKEDITOR.tools.extend( {}, this._.position ); },
-
- /**
- * Shows the dialog box.
- * @example
- * dialogObj.show();
- */
- show : function()
- {
- 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;
- var definition = this.definition;
- if ( !( element.getParent() && element.getParent().equals( CKEDITOR.document.getBody() ) ) )
- element.appendTo( CKEDITOR.document.getBody() );
- else
- return;
-
- // FIREFOX BUG: Fix vanishing caret for Firefox 2 or Gecko 1.8.
- if ( CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 )
- {
- var dialogElement = this.parts.dialog;
- dialogElement.setStyle( 'position', 'absolute' );
- setTimeout( function()
- {
- dialogElement.setStyle( 'position', 'fixed' );
- }, 0 );
- }
-
-
- // First, set the dialog to an appropriate size.
- this.resize( definition.minWidth, definition.minHeight );
-
- // Select the first tab by default.
- this.selectPage( this.definition.contents[0].id );
-
- // Reset all inputs back to their default value.
- this.reset();
-
- // Set z-index.
- if ( CKEDITOR.dialog._.currentZIndex === null )
- CKEDITOR.dialog._.currentZIndex = this._.editor.config.baseFloatZIndex;
- this._.element.getFirst().setStyle( 'z-index', CKEDITOR.dialog._.currentZIndex += 10 );
-
- // Maintain the dialog ordering and dialog cover.
- // Also register key handlers if first dialog.
- if ( CKEDITOR.dialog._.currentTop === null )
- {
- CKEDITOR.dialog._.currentTop = this;
- this._.parentDialog = null;
- addCover( this._.editor );
-
- 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 } )
- element.on( event, preventKeyBubbling );
- }
- else
- {
- this._.parentDialog = CKEDITOR.dialog._.currentTop;
- var parentElement = this._.parentDialog.getElement().getFirst();
- parentElement.$.style.zIndex -= Math.floor( this._.editor.config.baseFloatZIndex / 2 );
- CKEDITOR.dialog._.currentTop = this;
- }
-
- // Register the Esc hotkeys.
- registerAccessKey( this, this, '\x1b', null, function()
- {
- this.getButton( 'cancel' ) && this.getButton( 'cancel' ).click();
- } );
-
- // Reset the hasFocus state.
- this._.hasFocus = false;
-
- // Rearrange the dialog to the middle of the window.
- CKEDITOR.tools.setTimeout( function()
- {
- var viewSize = CKEDITOR.document.getWindow().getViewPaneSize();
- var dialogSize = this.getSize();
-
- // We're using definition size for initial position because of
- // offten corrupted data in offsetWidth at this point. (#4084)
- this.move( ( viewSize.width - definition.minWidth ) / 2, ( viewSize.height - dialogSize.height ) / 2 );
-
- this.parts.dialog.setStyle( 'visibility', '' );
-
- // 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(); } );
-
- },
- 100, this );
- },
-
- /**
- * Executes a function for each UI element.
- * @param {Function} fn Function to execute for each UI element.
- * @returns {CKEDITOR.dialog} The current dialog object.
- */
- foreach : function( fn )
- {
- for ( var i in this._.contents )
- {
- for ( var j in this._.contents[i] )
- fn( this._.contents[i][j]);
- }
- return this;
- },
-
- /**
- * Resets all input values in the dialog.
- * @example
- * dialogObj.reset();
- * @returns {CKEDITOR.dialog} The current dialog object.
- */
- reset : (function()
- {
- var fn = function( widget ){ if ( widget.reset ) widget.reset(); };
- return function(){ this.foreach( fn ); return this; };
- })(),
-
- setupContent : function()
- {
- var args = arguments;
- this.foreach( function( widget )
- {
- if ( widget.setup )
- widget.setup.apply( widget, args );
- });
- },
-
- commitContent : function()
- {
- var args = arguments;
- this.foreach( function( widget )
- {
- if ( widget.commit )
- widget.commit.apply( widget, args );
- });
- },
-
- /**
- * Hides the dialog box.
- * @example
- * dialogObj.hide();
- */
- hide : function()
- {
- this.fire( 'hide', {} );
- this._.editor.fire( 'dialogHide', this );
-
- // Remove the dialog's element from the root document.
- var element = this._.element;
- if ( !element.getParent() )
- return;
-
- element.remove();
- this.parts.dialog.setStyle( 'visibility', 'hidden' );
-
- // Unregister all access keys associated with this dialog.
- unregisterAccessKey( this );
-
- // Maintain dialog ordering and remove cover if needed.
- if ( !this._.parentDialog )
- removeCover();
- else
- {
- var parentElement = this._.parentDialog.getElement().getFirst();
- parentElement.setStyle( 'z-index', parseInt( parentElement.$.style.zIndex, 10 ) + Math.floor( this._.editor.config.baseFloatZIndex / 2 ) );
- }
- CKEDITOR.dialog._.currentTop = this._.parentDialog;
-
- // Deduct or clear the z-index.
- if ( !this._.parentDialog )
- {
- CKEDITOR.dialog._.currentZIndex = null;
-
- // Remove access key handlers.
- 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 } )
- element.removeListener( event, preventKeyBubbling );
-
- var editor = this._.editor;
- editor.focus();
-
- if ( editor.mode == 'wysiwyg' && CKEDITOR.env.ie )
- {
- var selection = editor.getSelection();
- selection && selection.unlock( true );
- }
- }
- else
- CKEDITOR.dialog._.currentZIndex -= 10;
-
-
- // Reset the initial values of the dialog.
- this.foreach( function( contentObj ) { contentObj.resetInitValue && contentObj.resetInitValue(); } );
- },
-
- /**
- * Adds a tabbed page into the dialog.
- * @param {Object} contents Content definition.
- * @example
- */
- addPage : function( contents )
- {
- var pageHtml = [],
- titleHtml = contents.label ? ' title="' + CKEDITOR.tools.htmlEncode( contents.label ) + '"' : '',
- elements = contents.elements,
- vbox = CKEDITOR.dialog._.uiElementBuilders.vbox.build( this,
- {
- type : 'vbox',
- className : 'cke_dialog_page_contents',
- children : contents.elements,
- expand : !!contents.expand,
- padding : contents.padding,
- style : contents.style || 'width: 100%;' + ( CKEDITOR.env.ie6Compat ? '' : 'height: 100%;' )
- }, pageHtml );
-
- // Create the HTML for the tab and the content block.
- var page = CKEDITOR.dom.element.createFromHtml( pageHtml.join( '' ) );
- page.setAttribute( 'role', 'tabpanel' );
-
- var env = CKEDITOR.env;
- var tabId = contents.id + '_' + CKEDITOR.tools.getNextNumber(),
- tab = CKEDITOR.dom.element.createFromHtml( [
- '<a class="cke_dialog_tab"',
- ( this._.pageCount > 0 ? ' cke_last' : 'cke_first' ),
- titleHtml,
- ( !!contents.hidden ? ' style="display:none"' : '' ),
- ' id="', tabId, '"',
- env.gecko && env.version >= 10900 && !env.hc ? '' : ' href="javascript:void(0)"',
- ' tabIndex="-1"',
- ' hidefocus="true"',
- ' role="tab">',
- contents.label,
- '</a>'
- ].join( '' ) );
-
- page.setAttribute( 'aria-labelledby', tabId );
-
- // If only a single page exist, a different style is used in the central pane.
- if ( this._.pageCount === 0 )
- this.parts.dialog.addClass( 'cke_single_page' );
- else
- this.parts.dialog.removeClass( 'cke_single_page' );
-
- // Take records for the tabs and elements created.
- this._.tabs[ contents.id ] = [ tab, page ];
- this._.tabIdList.push( contents.id );
- this._.pageCount++;
- this._.lastTab = tab;
-
- var contentMap = this._.contents[ contents.id ] = {},
- cursor,
- children = vbox.getChild();
-
- while ( ( cursor = children.shift() ) )
- {
- contentMap[ cursor.id ] = cursor;
- if ( typeof( cursor.getChild ) == 'function' )
- children.push.apply( children, cursor.getChild() );
- }
-
- // Attach the DOM nodes.
-
- page.setAttribute( 'name', contents.id );
- page.appendTo( this.parts.contents );
-
- tab.unselectable();
- this.parts.tabs.append( tab );
-
- // Add access key handlers if access key is defined.
- if ( contents.accessKey )
- {
- registerAccessKey( this, this, 'CTRL+' + contents.accessKey,
- tabAccessKeyDown, tabAccessKeyUp );
- this._.accessKeyMap[ 'CTRL+' + contents.accessKey ] = contents.id;
- }
- },
-
- /**
- * Activates a tab page in the dialog by its id.
- * @param {String} id The id of the dialog tab to be activated.
- * @example
- * dialogObj.selectPage( 'tab_1' );
- */
- selectPage : function( id )
- {
- // Hide the non-selected tabs and pages.
- for ( var i in this._.tabs )
- {
- var tab = this._.tabs[i][0],
- page = this._.tabs[i][1];
- if ( i != id )
- {
- tab.removeClass( 'cke_dialog_tab_selected' );
- page.hide();
- }
- page.setAttribute( 'aria-hidden', i != id );
- }
-
- var selected = this._.tabs[id];
- selected[0].addClass( 'cke_dialog_tab_selected' );
- selected[1].show();
- this._.currentTabId = id;
- this._.currentTabIndex = CKEDITOR.tools.indexOf( this._.tabIdList, id );
- },
-
- /**
- * Hides a page's tab away from the dialog.
- * @param {String} id The page's Id.
- * @example
- * dialog.hidePage( 'tab_3' );
- */
- hidePage : function( id )
- {
- var tab = this._.tabs[id] && this._.tabs[id][0];
- if ( !tab )
- return;
- tab.hide();
- },
-
- /**
- * Unhides a page's tab.
- * @param {String} id The page's Id.
- * @example
- * dialog.showPage( 'tab_2' );
- */
- showPage : function( id )
- {
- var tab = this._.tabs[id] && this._.tabs[id][0];
- if ( !tab )
- return;
- tab.show();
- },
-
- /**
- * Gets the root DOM element of the dialog.
- * @returns {CKEDITOR.dom.element} The <span> element containing this dialog.
- * @example
- * var dialogElement = dialogObj.getElement().getFirst();
- * dialogElement.setStyle( 'padding', '5px' );
- */
- getElement : function()
- {
- return this._.element;
- },
-
- /**
- * Gets the name of the dialog.
- * @returns {String} The name of this dialog.
- * @example
- * var dialogName = dialogObj.getName();
- */
- getName : function()
- {
- return this._.name;
- },
-
- /**
- * Gets a dialog UI element object from a dialog page.
- * @param {String} pageId id of dialog page.
- * @param {String} elementId id of UI element.
- * @example
- * @returns {CKEDITOR.ui.dialog.uiElement} The dialog UI element.
- */
- getContentElement : function( pageId, elementId )
- {
- var page = this._.contents[ pageId ];
- return page && page[ elementId ];
- },
-
- /**
- * Gets the value of a dialog UI element.
- * @param {String} pageId id of dialog page.
- * @param {String} elementId id of UI element.
- * @example
- * @returns {Object} The value of the UI element.
- */
- getValueOf : function( pageId, elementId )
- {
- return this.getContentElement( pageId, elementId ).getValue();
- },
-
- /**
- * Sets the value of a dialog UI element.
- * @param {String} pageId id of the dialog page.
- * @param {String} elementId id of the UI element.
- * @param {Object} value The new value of the UI element.
- * @example
- */
- setValueOf : function( pageId, elementId, value )
- {
- return this.getContentElement( pageId, elementId ).setValue( value );
- },
-
- /**
- * Gets the UI element of a button in the dialog's button row.
- * @param {String} id The id of the button.
- * @example
- * @returns {CKEDITOR.ui.dialog.button} The button object.
- */
- getButton : function( id )
- {
- return this._.buttons[ id ];
- },
-
- /**
- * Simulates a click to a dialog button in the dialog's button row.
- * @param {String} id The id of the button.
- * @example
- * @returns The return value of the dialog's "click" event.
- */
- click : function( id )
- {
- return this._.buttons[ id ].click();
- },
-
- /**
- * Disables a dialog button.
- * @param {String} id The id of the button.
- * @example
- */
- disableButton : function( id )
- {
- return this._.buttons[ id ].disable();
- },
-
- /**
- * Enables a dialog button.
- * @param {String} id The id of the button.
- * @example
- */
- enableButton : function( id )
- {
- return this._.buttons[ id ].enable();
- },
-
- /**
- * Gets the number of pages in the dialog.
- * @returns {Number} Page count.
- */
- getPageCount : function()
- {
- return this._.pageCount;
- },
-
- /**
- * Gets the editor instance which opened this dialog.
- * @returns {CKEDITOR.editor} Parent editor instances.
- */
- getParentEditor : function()
- {
- return this._.editor;
- },
-
- /**
- * Gets the element that was selected when opening the dialog, if any.
- * @returns {CKEDITOR.dom.element} The element that was selected, or null.
- */
- getSelectedElement : function()
- {
- return this.getParentEditor().getSelection().getSelectedElement();
- },
-
- /**
- * Adds element to dialog's focusable list.
- *
- * @param {CKEDITOR.dom.element} element
- * @param {Number} [index]
- */
- addFocusable: function( element, index ) {
- if ( typeof index == 'undefined' )
- {
- index = this._.focusList.length;
- this._.focusList.push( new Focusable( this, element, index ) );
- }
- else
- {
- this._.focusList.splice( index, 0, new Focusable( this, element, index ) );
- for ( var i = index + 1 ; i < this._.focusList.length ; i++ )
- this._.focusList[ i ].focusIndex++;
- }
- }
- };
-
- CKEDITOR.tools.extend( CKEDITOR.dialog,
- /**
- * @lends CKEDITOR.dialog
- */
- {
- /**
- * Registers a dialog.
- * @param {String} name The dialog's name.
- * @param {Function|String} dialogDefinition
- * A function returning the dialog's definition, or the URL to the .js file holding the function.
- * The function should accept an argument "editor" which is the current editor instance, and
- * return an object conforming to {@link CKEDITOR.dialog.dialogDefinition}.
- * @example
- * @see CKEDITOR.dialog.dialogDefinition
- */
- add : function( name, dialogDefinition )
- {
- // Avoid path registration from multiple instances override definition.
- if ( !this._.dialogDefinitions[name]
- || typeof dialogDefinition == 'function' )
- this._.dialogDefinitions[name] = dialogDefinition;
- },
-
- exists : function( name )
- {
- return !!this._.dialogDefinitions[ name ];
- },
-
- getCurrent : function()
- {
- return CKEDITOR.dialog._.currentTop;
- },
-
- /**
- * The default OK button for dialogs. Fires the "ok" event and closes the dialog if the event succeeds.
- * @static
- * @field
- * @example
- * @type Function
- */
- okButton : (function()
- {
- var retval = function( editor, override )
- {
- override = override || {};
- return CKEDITOR.tools.extend( {
- id : 'ok',
- type : 'button',
- label : editor.lang.common.ok,
- 'class' : 'cke_dialog_ui_button_ok',
- onClick : function( evt )
- {
- var dialog = evt.data.dialog;
- if ( dialog.fire( 'ok', { hide : true } ).hide !== false )
- dialog.hide();
- }
- }, override, true );
- };
- retval.type = 'button';
- retval.override = function( override )
- {
- return CKEDITOR.tools.extend( function( editor ){ return retval( editor, override ); },
- { type : 'button' }, true );
- };
- return retval;
- })(),
-
- /**
- * The default cancel button for dialogs. Fires the "cancel" event and closes the dialog if no UI element value changed.
- * @static
- * @field
- * @example
- * @type Function
- */
- cancelButton : (function()
- {
- var retval = function( editor, override )
- {
- override = override || {};
- return CKEDITOR.tools.extend( {
- id : 'cancel',
- type : 'button',
- label : editor.lang.common.cancel,
- 'class' : 'cke_dialog_ui_button_cancel',
- onClick : function( evt )
- {
- var dialog = evt.data.dialog;
- if ( dialog.fire( 'cancel', { hide : true } ).hide !== false )
- dialog.hide();
- }
- }, override, true );
- };
- retval.type = 'button';
- retval.override = function( override )
- {
- return CKEDITOR.tools.extend( function( editor ){ return retval( editor, override ); },
- { type : 'button' }, true );
- };
- return retval;
- })(),
-
- /**
- * Registers a dialog UI element.
- * @param {String} typeName The name of the UI element.
- * @param {Function} builder The function to build the UI element.
- * @example
- */
- addUIElement : function( typeName, builder )
- {
- this._.uiElementBuilders[ typeName ] = builder;
- }
- });
-
- CKEDITOR.dialog._ =
- {
- uiElementBuilders : {},
-
- dialogDefinitions : {},
-
- currentTop : null,
-
- currentZIndex : null
- };
-
- // "Inherit" (copy actually) from CKEDITOR.event.
- CKEDITOR.event.implementOn( CKEDITOR.dialog );
- CKEDITOR.event.implementOn( CKEDITOR.dialog.prototype, true );
-
- var defaultDialogDefinition =
- {
- resizable : CKEDITOR.DIALOG_RESIZE_NONE,
- minWidth : 600,
- minHeight : 400,
- buttons : [ CKEDITOR.dialog.okButton, CKEDITOR.dialog.cancelButton ]
- };
-
- // Tool function used to return an item from an array based on its id
- // property.
- var getById = function( array, id, recurse )
- {
- for ( var i = 0, item ; ( item = array[ i ] ) ; i++ )
- {
- if ( item.id == id )
- return item;
- if ( recurse && item[ recurse ] )
- {
- var retval = getById( item[ recurse ], id, recurse ) ;
- if ( retval )
- return retval;
- }
- }
- return null;
- };
-
- // Tool function used to add an item into an array.
- var addById = function( array, newItem, nextSiblingId, recurse, nullIfNotFound )
- {
- if ( nextSiblingId )
- {
- for ( var i = 0, item ; ( item = array[ i ] ) ; i++ )
- {
- if ( item.id == nextSiblingId )
- {
- array.splice( i, 0, newItem );
- return newItem;
- }
-
- if ( recurse && item[ recurse ] )
- {
- var retval = addById( item[ recurse ], newItem, nextSiblingId, recurse, true );
- if ( retval )
- return retval;
- }
- }
-
- if ( nullIfNotFound )
- return null;
- }
-
- array.push( newItem );
- return newItem;
- };
-
- // Tool function used to remove an item from an array based on its id.
- var removeById = function( array, id, recurse )
- {
- for ( var i = 0, item ; ( item = array[ i ] ) ; i++ )
- {
- if ( item.id == id )
- return array.splice( i, 1 );
- if ( recurse && item[ recurse ] )
- {
- var retval = removeById( item[ recurse ], id, recurse );
- if ( retval )
- return retval;
- }
- }
- return null;
- };
-
- /**
- * This class is not really part of the API. It is the "definition" property value
- * passed to "dialogDefinition" event handlers.
- * @constructor
- * @name CKEDITOR.dialog.dialogDefinitionObject
- * @extends CKEDITOR.dialog.dialogDefinition
- * @example
- * CKEDITOR.on( 'dialogDefinition', function( evt )
- * {
- * var definition = evt.data.definition;
- * var content = definition.getContents( 'page1' );
- * ...
- * } );
- */
- var definitionObject = function( dialog, dialogDefinition )
- {
- // TODO : Check if needed.
- this.dialog = dialog;
-
- // Transform the contents entries in contentObjects.
- var contents = dialogDefinition.contents;
- for ( var i = 0, content ; ( content = contents[i] ) ; i++ )
- contents[ i ] = new contentObject( dialog, content );
-
- CKEDITOR.tools.extend( this, dialogDefinition );
- };
-
- definitionObject.prototype =
- /** @lends CKEDITOR.dialog.dialogDefinitionObject.prototype */
- {
- /**
- * Gets a content definition.
- * @param {String} id The id of the content definition.
- * @returns {CKEDITOR.dialog.contentDefinition} The content definition
- * matching id.
- */
- getContents : function( id )
- {
- return getById( this.contents, id );
- },
-
- /**
- * Gets a button definition.
- * @param {String} id The id of the button definition.
- * @returns {CKEDITOR.dialog.buttonDefinition} The button definition
- * matching id.
- */
- getButton : function( id )
- {
- return getById( this.buttons, id );
- },
-
- /**
- * Adds a content definition object under this dialog definition.
- * @param {CKEDITOR.dialog.contentDefinition} contentDefinition The
- * content definition.
- * @param {String} [nextSiblingId] The id of an existing content
- * definition which the new content definition will be inserted
- * before. Omit if the new content definition is to be inserted as
- * the last item.
- * @returns {CKEDITOR.dialog.contentDefinition} The inserted content
- * definition.
- */
- addContents : function( contentDefinition, nextSiblingId )
- {
- return addById( this.contents, contentDefinition, nextSiblingId );
- },
-
- /**
- * Adds a button definition object under this dialog definition.
- * @param {CKEDITOR.dialog.buttonDefinition} buttonDefinition The
- * button definition.
- * @param {String} [nextSiblingId] The id of an existing button
- * definition which the new button definition will be inserted
- * before. Omit if the new button definition is to be inserted as
- * the last item.
- * @returns {CKEDITOR.dialog.buttonDefinition} The inserted button
- * definition.
- */
- addButton : function( buttonDefinition, nextSiblingId )
- {
- return addById( this.buttons, buttonDefinition, nextSiblingId );
- },
-
- /**
- * Removes a content definition from this dialog definition.
- * @param {String} id The id of the content definition to be removed.
- * @returns {CKEDITOR.dialog.contentDefinition} The removed content
- * definition.
- */
- removeContents : function( id )
- {
- removeById( this.contents, id );
- },
-
- /**
- * Removes a button definition from the dialog definition.
- * @param {String} id The id of the button definition to be removed.
- * @returns {CKEDITOR.dialog.buttonDefinition} The removed button
- * definition.
- */
- removeButton : function( id )
- {
- removeById( this.buttons, id );
- }
- };
-
- /**
- * This class is not really part of the API. It is the template of the
- * objects representing content pages inside the
- * CKEDITOR.dialog.dialogDefinitionObject.
- * @constructor
- * @name CKEDITOR.dialog.contentDefinitionObject
- * @example
- * CKEDITOR.on( 'dialogDefinition', function( evt )
- * {
- * var definition = evt.data.definition;
- * var content = definition.getContents( 'page1' );
- * content.remove( 'textInput1' );
- * ...
- * } );
- */
- function contentObject( dialog, contentDefinition )
- {
- this._ =
- {
- dialog : dialog
- };
-
- CKEDITOR.tools.extend( this, contentDefinition );
- }
-
- contentObject.prototype =
- /** @lends CKEDITOR.dialog.contentDefinitionObject.prototype */
- {
- /**
- * Gets a UI element definition under the content definition.
- * @param {String} id The id of the UI element definition.
- * @returns {CKEDITOR.dialog.uiElementDefinition}
- */
- get : function( id )
- {
- return getById( this.elements, id, 'children' );
- },
-
- /**
- * Adds a UI element definition to the content definition.
- * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition The
- * UI elemnet definition to be added.
- * @param {String} nextSiblingId The id of an existing UI element
- * definition which the new UI element definition will be inserted
- * before. Omit if the new button definition is to be inserted as
- * the last item.
- * @returns {CKEDITOR.dialog.uiElementDefinition} The element
- * definition inserted.
- */
- add : function( elementDefinition, nextSiblingId )
- {
- return addById( this.elements, elementDefinition, nextSiblingId, 'children' );
- },
-
- /**
- * Removes a UI element definition from the content definition.
- * @param {String} id The id of the UI element definition to be
- * removed.
- * @returns {CKEDITOR.dialog.uiElementDefinition} The element
- * definition removed.
- * @example
- */
- remove : function( id )
- {
- removeById( this.elements, id, 'children' );
- }
- };
-
- function initDragAndDrop( dialog )
- {
- var lastCoords = null,
- abstractDialogCoords = null,
- element = dialog.getElement().getFirst(),
- editor = dialog.getParentEditor(),
- magnetDistance = editor.config.dialog_magnetDistance,
- margins = editor.skin.margins || [ 0, 0, 0, 0 ];
-
- if ( typeof magnetDistance == 'undefined' )
- magnetDistance = 20;
-
- function mouseMoveHandler( evt )
- {
- var dialogSize = dialog.getSize(),
- viewPaneSize = CKEDITOR.document.getWindow().getViewPaneSize(),
- x = evt.data.$.screenX,
- y = evt.data.$.screenY,
- dx = x - lastCoords.x,
- dy = y - lastCoords.y,
- realX, realY;
-
- lastCoords = { x : x, y : y };
- abstractDialogCoords.x += dx;
- abstractDialogCoords.y += dy;
-
- if ( abstractDialogCoords.x + margins[3] < magnetDistance )
- realX = - margins[3];
- else if ( abstractDialogCoords.x - margins[1] > viewPaneSize.width - dialogSize.width - magnetDistance )
- realX = viewPaneSize.width - dialogSize.width + margins[1];
- else
- realX = abstractDialogCoords.x;
-
- if ( abstractDialogCoords.y + margins[0] < magnetDistance )
- realY = - margins[0];
- else if ( abstractDialogCoords.y - margins[2] > viewPaneSize.height - dialogSize.height - magnetDistance )
- realY = viewPaneSize.height - dialogSize.height + margins[2];
- else
- realY = abstractDialogCoords.y;
-
- dialog.move( realX, realY );
-
- evt.data.preventDefault();
- }
-
- function mouseUpHandler( evt )
- {
- CKEDITOR.document.removeListener( 'mousemove', mouseMoveHandler );
- CKEDITOR.document.removeListener( 'mouseup', mouseUpHandler );
-
- if ( CKEDITOR.env.ie6Compat )
- {
- var coverDoc = coverElement.getChild( 0 ).getFrameDocument();
- coverDoc.removeListener( 'mousemove', mouseMoveHandler );
- coverDoc.removeListener( 'mouseup', mouseUpHandler );
- }
- }
-
- dialog.parts.title.on( 'mousedown', function( evt )
- {
- dialog._.updateSize = true;
-
- lastCoords = { x : evt.data.$.screenX, y : evt.data.$.screenY };
-
- CKEDITOR.document.on( 'mousemove', mouseMoveHandler );
- CKEDITOR.document.on( 'mouseup', mouseUpHandler );
- abstractDialogCoords = dialog.getPosition();
-
- if ( CKEDITOR.env.ie6Compat )
- {
- var coverDoc = coverElement.getChild( 0 ).getFrameDocument();
- coverDoc.on( 'mousemove', mouseMoveHandler );
- coverDoc.on( 'mouseup', mouseUpHandler );
- }
-
- evt.data.preventDefault();
- }, dialog );
- }
-
- function initResizeHandles( dialog )
- {
- var definition = dialog.definition,
- minWidth = definition.minWidth || 0,
- minHeight = definition.minHeight || 0,
- resizable = definition.resizable,
- margins = dialog.getParentEditor().skin.margins || [ 0, 0, 0, 0 ];
-
- function topSizer( coords, dy )
- {
- coords.y += dy;
- }
-
- function rightSizer( coords, dx )
- {
- coords.x2 += dx;
- }
-
- function bottomSizer( coords, dy )
- {
- coords.y2 += dy;
- }
-
- function leftSizer( coords, dx )
- {
- coords.x += dx;
- }
-
- var lastCoords = null,
- abstractDialogCoords = null,
- magnetDistance = dialog._.editor.config.magnetDistance,
- parts = [ 'tl', 't', 'tr', 'l', 'r', 'bl', 'b', 'br' ];
-
- function mouseDownHandler( evt )
- {
- var partName = evt.listenerData.part, size = dialog.getSize();
- abstractDialogCoords = dialog.getPosition();
- CKEDITOR.tools.extend( abstractDialogCoords,
- {
- x2 : abstractDialogCoords.x + size.width,
- y2 : abstractDialogCoords.y + size.height
- } );
- lastCoords = { x : evt.data.$.screenX, y : evt.data.$.screenY };
-
- CKEDITOR.document.on( 'mousemove', mouseMoveHandler, dialog, { part : partName } );
- CKEDITOR.document.on( 'mouseup', mouseUpHandler, dialog, { part : partName } );
-
- if ( CKEDITOR.env.ie6Compat )
- {
- var coverDoc = coverElement.getChild( 0 ).getFrameDocument();
- coverDoc.on( 'mousemove', mouseMoveHandler, dialog, { part : partName } );
- coverDoc.on( 'mouseup', mouseUpHandler, dialog, { part : partName } );
- }
-
- evt.data.preventDefault();
- }
-
- function mouseMoveHandler( evt )
- {
- var x = evt.data.$.screenX,
- y = evt.data.$.screenY,
- dx = x - lastCoords.x,
- dy = y - lastCoords.y,
- viewPaneSize = CKEDITOR.document.getWindow().getViewPaneSize(),
- partName = evt.listenerData.part;
-
- if ( partName.search( 't' ) != -1 )
- topSizer( abstractDialogCoords, dy );
- if ( partName.search( 'l' ) != -1 )
- leftSizer( abstractDialogCoords, dx );
- if ( partName.search( 'b' ) != -1 )
- bottomSizer( abstractDialogCoords, dy );
- if ( partName.search( 'r' ) != -1 )
- rightSizer( abstractDialogCoords, dx );
-
- lastCoords = { x : x, y : y };
-
- var realX, realY, realX2, realY2;
-
- if ( abstractDialogCoords.x + margins[3] < magnetDistance )
- realX = - margins[3];
- else if ( partName.search( 'l' ) != -1 && abstractDialogCoords.x2 - abstractDialogCoords.x < minWidth + magnetDistance )
- realX = abstractDialogCoords.x2 - minWidth;
- else
- realX = abstractDialogCoords.x;
-
- if ( abstractDialogCoords.y + margins[0] < magnetDistance )
- realY = - margins[0];
- else if ( partName.search( 't' ) != -1 && abstractDialogCoords.y2 - abstractDialogCoords.y < minHeight + magnetDistance )
- realY = abstractDialogCoords.y2 - minHeight;
- else
- realY = abstractDialogCoords.y;
-
- if ( abstractDialogCoords.x2 - margins[1] > viewPaneSize.width - magnetDistance )
- realX2 = viewPaneSize.width + margins[1] ;
- else if ( partName.search( 'r' ) != -1 && abstractDialogCoords.x2 - abstractDialogCoords.x < minWidth + magnetDistance )
- realX2 = abstractDialogCoords.x + minWidth;
- else
- realX2 = abstractDialogCoords.x2;
-
- if ( abstractDialogCoords.y2 - margins[2] > viewPaneSize.height - magnetDistance )
- realY2= viewPaneSize.height + margins[2] ;
- else if ( partName.search( 'b' ) != -1 && abstractDialogCoords.y2 - abstractDialogCoords.y < minHeight + magnetDistance )
- realY2 = abstractDialogCoords.y + minHeight;
- else
- realY2 = abstractDialogCoords.y2 ;
-
- dialog.move( realX, realY );
- dialog.resize( realX2 - realX, realY2 - realY );
-
- evt.data.preventDefault();
- }
-
- function mouseUpHandler( evt )
- {
- CKEDITOR.document.removeListener( 'mouseup', mouseUpHandler );
- CKEDITOR.document.removeListener( 'mousemove', mouseMoveHandler );
-
- if ( CKEDITOR.env.ie6Compat )
- {
- var coverDoc = coverElement.getChild( 0 ).getFrameDocument();
- coverDoc.removeListener( 'mouseup', mouseUpHandler );
- coverDoc.removeListener( 'mousemove', mouseMoveHandler );
- }
- }
-
-// TODO : Simplify the resize logic, having just a single resize grip <div>.
-// var widthTest = /[lr]/,
-// heightTest = /[tb]/;
-// for ( var i = 0 ; i < parts.length ; i++ )
-// {
-// var element = dialog.parts[ parts[i] + '_resize' ];
-// if ( resizable == CKEDITOR.DIALOG_RESIZE_NONE ||
-// resizable == CKEDITOR.DIALOG_RESIZE_HEIGHT && widthTest.test( parts[i] ) ||
-// resizable == CKEDITOR.DIALOG_RESIZE_WIDTH && heightTest.test( parts[i] ) )
-// {
-// element.hide();
-// continue;
-// }
-// element.on( 'mousedown', mouseDownHandler, dialog, { part : parts[i] } );
-// }
- }
-
- var resizeCover;
- var coverElement;
-
- var addCover = function( editor )
- {
- var win = CKEDITOR.document.getWindow();
-
- 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; ',
- ( !CKEDITOR.env.ie6Compat ? 'background-color: ' + backgroundColorStyle : '' ),
- '" id="cke_dialog_background_cover">'
- ];
-
-
- if ( CKEDITOR.env.ie6Compat )
- {
- // Support for custom document.domain in IE.
- var isCustomDomain = CKEDITOR.env.isCustomDomain(),
- iframeHtml = '<html><body style=\\\'background-color:' + backgroundColorStyle + ';\\\'></body></html>';
-
- html.push(
- '<iframe' +
- ' hidefocus="true"' +
- ' frameborder="0"' +
- ' id="cke_dialog_background_iframe"' +
- ' src="javascript:' );
-
- html.push( 'void((function(){' +
- 'document.open();' +
- ( isCustomDomain ? 'document.domain=\'' + document.domain + '\';' : '' ) +
- 'document.write( \'' + iframeHtml + '\' );' +
- 'document.close();' +
- '})())' );
-
- html.push(
- '"' +
- ' style="' +
- 'position:absolute;' +
- 'left:0;' +
- 'top:0;' +
- 'width:100%;' +
- 'height: 100%;' +
- 'progid:DXImageTransform.Microsoft.Alpha(opacity=0)">' +
- '</iframe>' );
- }
-
- html.push( '</div>' );
-
- coverElement = CKEDITOR.dom.element.createFromHtml( html.join( '' ) );
- }
-
- var element = coverElement;
-
- var resizeFunc = function()
- {
- var size = win.getViewPaneSize();
- element.setStyles(
- {
- width : size.width + 'px',
- height : size.height + 'px'
- } );
- };
-
- var scrollFunc = function()
- {
- var pos = win.getScrollPosition(),
- cursor = CKEDITOR.dialog._.currentTop;
- element.setStyles(
- {
- left : pos.x + 'px',
- top : pos.y + 'px'
- });
-
- do
- {
- var dialogPos = cursor.getPosition();
- cursor.move( dialogPos.x, dialogPos.y );
- } while ( ( cursor = cursor._.parentDialog ) );
- };
-
- resizeCover = resizeFunc;
- win.on( 'resize', resizeFunc );
- resizeFunc();
- if ( CKEDITOR.env.ie6Compat )
- {
- // IE BUG: win.$.onscroll assignment doesn't work.. it must be window.onscroll.
- // So we need to invent a really funny way to make it work.
- var myScrollHandler = function()
- {
- scrollFunc();
- arguments.callee.prevScrollHandler.apply( this, arguments );
- };
- win.$.setTimeout( function()
- {
- myScrollHandler.prevScrollHandler = window.onscroll || function(){};
- window.onscroll = myScrollHandler;
- }, 0 );
- scrollFunc();
- }
-
- var opacity = editor.config.dialog_backgroundCoverOpacity;
- element.setOpacity( typeof opacity != 'undefined' ? opacity : 0.5 );
-
- element.appendTo( CKEDITOR.document.getBody() );
- };
-
- var removeCover = function()
- {
- if ( !coverElement )
- return;
-
- var win = CKEDITOR.document.getWindow();
- coverElement.remove();
- win.removeListener( 'resize', resizeCover );
-
- if ( CKEDITOR.env.ie6Compat )
- {
- win.$.setTimeout( function()
- {
- var prevScrollHandler = window.onscroll && window.onscroll.prevScrollHandler;
- window.onscroll = prevScrollHandler || null;
- }, 0 );
- }
- resizeCover = null;
- };
-
- var accessKeyProcessors = {};
-
- var accessKeyDownHandler = function( evt )
- {
- var ctrl = evt.data.$.ctrlKey || evt.data.$.metaKey,
- alt = evt.data.$.altKey,
- shift = evt.data.$.shiftKey,
- key = String.fromCharCode( evt.data.$.keyCode ),
- keyProcessor = accessKeyProcessors[( ctrl ? 'CTRL+' : '' ) + ( alt ? 'ALT+' : '') + ( shift ? 'SHIFT+' : '' ) + key];
-
- if ( !keyProcessor || !keyProcessor.length )
- return;
-
- keyProcessor = keyProcessor[keyProcessor.length - 1];
- keyProcessor.keydown && keyProcessor.keydown.call( keyProcessor.uiElement, keyProcessor.dialog, keyProcessor.key );
- evt.data.preventDefault();
- };
-
- var accessKeyUpHandler = function( evt )
- {
- var ctrl = evt.data.$.ctrlKey || evt.data.$.metaKey,
- alt = evt.data.$.altKey,
- shift = evt.data.$.shiftKey,
- key = String.fromCharCode( evt.data.$.keyCode ),
- keyProcessor = accessKeyProcessors[( ctrl ? 'CTRL+' : '' ) + ( alt ? 'ALT+' : '') + ( shift ? 'SHIFT+' : '' ) + key];
-
- if ( !keyProcessor || !keyProcessor.length )
- return;
-
- keyProcessor = keyProcessor[keyProcessor.length - 1];
- if ( keyProcessor.keyup )
- {
- keyProcessor.keyup.call( keyProcessor.uiElement, keyProcessor.dialog, keyProcessor.key );
- evt.data.preventDefault();
- }
- };
-
- var registerAccessKey = function( uiElement, dialog, key, downFunc, upFunc )
- {
- var procList = accessKeyProcessors[key] || ( accessKeyProcessors[key] = [] );
- procList.push( {
- uiElement : uiElement,
- dialog : dialog,
- key : key,
- keyup : upFunc || uiElement.accessKeyUp,
- keydown : downFunc || uiElement.accessKeyDown
- } );
- };
-
- var unregisterAccessKey = function( obj )
- {
- for ( var i in accessKeyProcessors )
- {
- var list = accessKeyProcessors[i];
- for ( var j = list.length - 1 ; j >= 0 ; j-- )
- {
- if ( list[j].dialog == obj || list[j].uiElement == obj )
- list.splice( j, 1 );
- }
- if ( list.length === 0 )
- delete accessKeyProcessors[i];
- }
- };
-
- var tabAccessKeyUp = function( dialog, key )
- {
- if ( dialog._.accessKeyMap[key] )
- dialog.selectPage( dialog._.accessKeyMap[key] );
- };
-
- var tabAccessKeyDown = function( dialog, key )
- {
- };
-
- // ESC, ENTER
- var preventKeyBubblingKeys = { 27 :1, 13 :1 };
- var preventKeyBubbling = function( e )
- {
- if ( e.data.getKeystroke() in preventKeyBubblingKeys )
- e.data.stopPropagation();
- };
-
- (function()
- {
- CKEDITOR.ui.dialog =
- {
- /**
- * The base class of all dialog UI elements.
- * @constructor
- * @param {CKEDITOR.dialog} dialog Parent dialog object.
- * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition Element
- * definition. Accepted fields:
- * <ul>
- * <li><strong>id</strong> (Required) The id of the UI element. See {@link
- * CKEDITOR.dialog#getContentElement}</li>
- * <li><strong>type</strong> (Required) The type of the UI element. The
- * value to this field specifies which UI element class will be used to
- * generate the final widget.</li>
- * <li><strong>title</strong> (Optional) The popup tooltip for the UI
- * element.</li>
- * <li><strong>hidden</strong> (Optional) A flag that tells if the element
- * should be initially visible.</li>
- * <li><strong>className</strong> (Optional) Additional CSS class names
- * to add to the UI element. Separated by space.</li>
- * <li><strong>style</strong> (Optional) Additional CSS inline styles
- * to add to the UI element. A semicolon (;) is required after the last
- * style declaration.</li>
- * <li><strong>accessKey</strong> (Optional) The alphanumeric access key
- * for this element. Access keys are automatically prefixed by CTRL.</li>
- * <li><strong>on*</strong> (Optional) Any UI element definition field that
- * starts with <em>on</em> followed immediately by a capital letter and
- * probably more letters is an event handler. Event handlers may be further
- * divided into registered event handlers and DOM event handlers. Please
- * refer to {@link CKEDITOR.ui.dialog.uiElement#registerEvents} and
- * {@link CKEDITOR.ui.dialog.uiElement#eventProcessors} for more
- * information.</li>
- * </ul>
- * @param {Array} htmlList
- * List of HTML code to be added to the dialog's content area.
- * @param {Function|String} nodeNameArg
- * A function returning a string, or a simple string for the node name for
- * the root DOM node. Default is 'div'.
- * @param {Function|Object} stylesArg
- * A function returning an object, or a simple object for CSS styles applied
- * to the DOM node. Default is empty object.
- * @param {Function|Object} attributesArg
- * A fucntion returning an object, or a simple object for attributes applied
- * to the DOM node. Default is empty object.
- * @param {Function|String} contentsArg
- * A function returning a string, or a simple string for the HTML code inside
- * the root DOM node. Default is empty string.
- * @example
- */
- uiElement : function( dialog, elementDefinition, htmlList, nodeNameArg, stylesArg, attributesArg, contentsArg )
- {
- if ( arguments.length < 4 )
- return;
-
- var nodeName = ( nodeNameArg.call ? nodeNameArg( elementDefinition ) : nodeNameArg ) || 'div',
- html = [ '<', nodeName, ' ' ],
- styles = ( stylesArg && stylesArg.call ? stylesArg( elementDefinition ) : stylesArg ) || {},
- attributes = ( attributesArg && attributesArg.call ? attributesArg( elementDefinition ) : attributesArg ) || {},
- innerHTML = ( contentsArg && contentsArg.call ? contentsArg.call( this, dialog, elementDefinition ) : contentsArg ) || '',
- domId = this.domId = attributes.id || CKEDITOR.tools.getNextNumber() + '_uiElement',
- id = this.id = elementDefinition.id,
- i;
-
- // Set the id, a unique id is required for getElement() to work.
- attributes.id = domId;
-
- // Set the type and definition CSS class names.
- var classes = {};
- if ( elementDefinition.type )
- classes[ 'cke_dialog_ui_' + elementDefinition.type ] = 1;
- if ( elementDefinition.className )
- classes[ elementDefinition.className ] = 1;
- var attributeClasses = ( attributes['class'] && attributes['class'].split ) ? attributes['class'].split( ' ' ) : [];
- for ( i = 0 ; i < attributeClasses.length ; i++ )
- {
- if ( attributeClasses[i] )
- classes[ attributeClasses[i] ] = 1;
- }
- var finalClasses = [];
- for ( i in classes )
- finalClasses.push( i );
- attributes['class'] = finalClasses.join( ' ' );
-
- // Set the popup tooltop.
- if ( elementDefinition.title )
- attributes.title = elementDefinition.title;
-
- // Write the inline CSS styles.
- var styleStr = ( elementDefinition.style || '' ).split( ';' );
- for ( i in styles )
- styleStr.push( i + ':' + styles[i] );
- if ( elementDefinition.hidden )
- styleStr.push( 'display:none' );
- for ( i = styleStr.length - 1 ; i >= 0 ; i-- )
- {
- if ( styleStr[i] === '' )
- styleStr.splice( i, 1 );
- }
- if ( styleStr.length > 0 )
- attributes.style = ( attributes.style ? ( attributes.style + '; ' ) : '' ) + styleStr.join( '; ' );
-
- // Write the attributes.
- for ( i in attributes )
- html.push( i + '="' + CKEDITOR.tools.htmlEncode( attributes[i] ) + '" ');
-
- // Write the content HTML.
- html.push( '>', innerHTML, '</', nodeName, '>' );
-
- // Add contents to the parent HTML array.
- htmlList.push( html.join( '' ) );
-
- ( this._ || ( this._ = {} ) ).dialog = dialog;
-
- // Override isChanged if it is defined in element definition.
- if ( typeof( elementDefinition.isChanged ) == 'boolean' )
- this.isChanged = function(){ return elementDefinition.isChanged; };
- if ( typeof( elementDefinition.isChanged ) == 'function' )
- this.isChanged = elementDefinition.isChanged;
-
- // Add events.
- CKEDITOR.event.implementOn( this );
-
- this.registerEvents( elementDefinition );
- if ( this.accessKeyUp && this.accessKeyDown && elementDefinition.accessKey )
- registerAccessKey( this, dialog, 'CTRL+' + elementDefinition.accessKey );
-
- var me = this;
- dialog.on( 'load', function()
- {
- if ( me.getInputElement() )
- {
- me.getInputElement().on( 'focus', function()
- {
- dialog._.tabBarMode = false;
- dialog._.hasFocus = true;
- me.fire( 'focus' );
- }, me );
- }
- } );
-
- // Register the object as a tab focus if it can be included.
- if ( this.keyboardFocusable )
- {
- this.tabIndex = elementDefinition.tabIndex || 0;
-
- this.focusIndex = dialog._.focusList.push( this ) - 1;
- this.on( 'focus', function()
- {
- dialog._.currentFocusIndex = me.focusIndex;
- } );
- }
-
- // Completes this object with everything we have in the
- // definition.
- CKEDITOR.tools.extend( this, elementDefinition );
- },
-
- /**
- * Horizontal layout box for dialog UI elements, auto-expends to available width of container.
- * @constructor
- * @extends CKEDITOR.ui.dialog.uiElement
- * @param {CKEDITOR.dialog} dialog
- * Parent dialog object.
- * @param {Array} childObjList
- * Array of {@link CKEDITOR.ui.dialog.uiElement} objects inside this
- * container.
- * @param {Array} childHtmlList
- * Array of HTML code that correspond to the HTML output of all the
- * objects in childObjList.
- * @param {Array} htmlList
- * Array of HTML code that this element will output to.
- * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition
- * The element definition. Accepted fields:
- * <ul>
- * <li><strong>widths</strong> (Optional) The widths of child cells.</li>
- * <li><strong>height</strong> (Optional) The height of the layout.</li>
- * <li><strong>padding</strong> (Optional) The padding width inside child
- * cells.</li>
- * <li><strong>align</strong> (Optional) The alignment of the whole layout
- * </li>
- * </ul>
- * @example
- */
- hbox : function( dialog, childObjList, childHtmlList, htmlList, elementDefinition )
- {
- if ( arguments.length < 4 )
- return;
-
- this._ || ( this._ = {} );
-
- var children = this._.children = childObjList,
- widths = elementDefinition && elementDefinition.widths || null,
- height = elementDefinition && elementDefinition.height || null,
- styles = {},
- i;
- /** @ignore */
- var innerHTML = function()
- {
- var html = [ '<tbody><tr class="cke_dialog_ui_hbox">' ];
- for ( i = 0 ; i < childHtmlList.length ; i++ )
- {
- var className = 'cke_dialog_ui_hbox_child',
- styles = [];
- if ( i === 0 )
- className = 'cke_dialog_ui_hbox_first';
- if ( i == childHtmlList.length - 1 )
- className = 'cke_dialog_ui_hbox_last';
- html.push( '<td class="', className, '" role="presentation" ' );
- if ( widths )
- {
- if ( widths[i] )
- styles.push( 'width:' + CKEDITOR.tools.cssLength( widths[i] ) );
- }
- else
- styles.push( 'width:' + Math.floor( 100 / childHtmlList.length ) + '%' );
- if ( height )
- styles.push( 'height:' + CKEDITOR.tools.cssLength( height ) );
- if ( elementDefinition && elementDefinition.padding != undefined )
- styles.push( 'padding:' + CKEDITOR.tools.cssLength( elementDefinition.padding ) );
- if ( styles.length > 0 )
- html.push( 'style="' + styles.join('; ') + '" ' );
- html.push( '>', childHtmlList[i], '</td>' );
- }
- html.push( '</tr></tbody>' );
- return html.join( '' );
- };
-
- var attribs = { role : 'presentation' };
- elementDefinition && elementDefinition.align && ( attribs.align = elementDefinition.align );
-
- CKEDITOR.ui.dialog.uiElement.call(
- this,
- dialog,
- elementDefinition || { type : 'hbox' },
- htmlList,
- 'table',
- styles,
- attribs,
- innerHTML );
- },
-
- /**
- * Vertical layout box for dialog UI elements.
- * @constructor
- * @extends CKEDITOR.ui.dialog.hbox
- * @param {CKEDITOR.dialog} dialog
- * Parent dialog object.
- * @param {Array} childObjList
- * Array of {@link CKEDITOR.ui.dialog.uiElement} objects inside this
- * container.
- * @param {Array} childHtmlList
- * Array of HTML code that correspond to the HTML output of all the
- * objects in childObjList.
- * @param {Array} htmlList
- * Array of HTML code that this element will output to.
- * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition
- * The element definition. Accepted fields:
- * <ul>
- * <li><strong>width</strong> (Optional) The width of the layout.</li>
- * <li><strong>heights</strong> (Optional) The heights of individual cells.
- * </li>
- * <li><strong>align</strong> (Optional) The alignment of the layout.</li>
- * <li><strong>padding</strong> (Optional) The padding width inside child
- * cells.</li>
- * <li><strong>expand</strong> (Optional) Whether the layout should expand
- * vertically to fill its container.</li>
- * </ul>
- * @example
- */
- vbox : function( dialog, childObjList, childHtmlList, htmlList, elementDefinition )
- {
- if (arguments.length < 3 )
- return;
-
- this._ || ( this._ = {} );
-
- var children = this._.children = childObjList,
- width = elementDefinition && elementDefinition.width || null,
- heights = elementDefinition && elementDefinition.heights || null;
- /** @ignore */
- var innerHTML = function()
- {
- var html = [ '<table role="presentation" cellspacing="0" border="0" ' ];
- html.push( 'style="' );
- if ( elementDefinition && elementDefinition.expand )
- html.push( 'height:100%;' );
- html.push( 'width:' + CKEDITOR.tools.cssLength( width || '100%' ), ';' );
- html.push( '"' );
- html.push( 'align="', CKEDITOR.tools.htmlEncode(
- ( elementDefinition && elementDefinition.align ) || ( dialog.getParentEditor().lang.dir == 'ltr' ? 'left' : 'right' ) ), '" ' );
-
- html.push( '><tbody>' );
- for ( var i = 0 ; i < childHtmlList.length ; i++ )
- {
- var styles = [];
- html.push( '<tr><td role="presentation" ' );
- if ( width )
- styles.push( 'width:' + CKEDITOR.tools.cssLength( width || '100%' ) );
- if ( heights )
- styles.push( 'height:' + CKEDITOR.tools.cssLength( heights[i] ) );
- else if ( elementDefinition && elementDefinition.expand )
- styles.push( 'height:' + Math.floor( 100 / childHtmlList.length ) + '%' );
- if ( elementDefinition && elementDefinition.padding != undefined )
- styles.push( 'padding:' + CKEDITOR.tools.cssLength( elementDefinition.padding ) );
- if ( styles.length > 0 )
- html.push( 'style="', styles.join( '; ' ), '" ' );
- html.push( ' class="cke_dialog_ui_vbox_child">', childHtmlList[i], '</td></tr>' );
- }
- html.push( '</tbody></table>' );
- return html.join( '' );
- };
- CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition || { type : 'vbox' }, htmlList, 'div', null, { role : 'presentation' }, innerHTML );
- }
- };
- })();
-
- CKEDITOR.ui.dialog.uiElement.prototype =
- {
- /**
- * Gets the root DOM element of this dialog UI object.
- * @returns {CKEDITOR.dom.element} Root DOM element of UI object.
- * @example
- * uiElement.getElement().hide();
- */
- getElement : function()
- {
- return CKEDITOR.document.getById( this.domId );
- },
-
- /**
- * Gets the DOM element that the user inputs values.
- * This function is used by setValue(), getValue() and focus(). It should
- * be overrided in child classes where the input element isn't the root
- * element.
- * @returns {CKEDITOR.dom.element} The element where the user input values.
- * @example
- * var rawValue = textInput.getInputElement().$.value;
- */
- getInputElement : function()
- {
- return this.getElement();
- },
-
- /**
- * Gets the parent dialog object containing this UI element.
- * @returns {CKEDITOR.dialog} Parent dialog object.
- * @example
- * var dialog = uiElement.getDialog();
- */
- getDialog : function()
- {
- return this._.dialog;
- },
-
- /**
- * Sets the value of this dialog UI object.
- * @param {Object} value The new value.
- * @returns {CKEDITOR.dialog.uiElement} The current UI element.
- * @example
- * uiElement.setValue( 'Dingo' );
- */
- setValue : function( value )
- {
- this.getInputElement().setValue( value );
- this.fire( 'change', { value : value } );
- return this;
- },
-
- /**
- * Gets the current value of this dialog UI object.
- * @returns {Object} The current value.
- * @example
- * var myValue = uiElement.getValue();
- */
- getValue : function()
- {
- return this.getInputElement().getValue();
- },
-
- /**
- * Tells whether the UI object's value has changed.
- * @returns {Boolean} true if changed, false if not changed.
- * @example
- * if ( uiElement.isChanged() )
- * confirm( 'Value changed! Continue?' );
- */
- isChanged : function()
- {
- // Override in input classes.
- return false;
- },
-
- /**
- * Selects the parent tab of this element. Usually called by focus() or overridden focus() methods.
- * @returns {CKEDITOR.dialog.uiElement} The current UI element.
- * @example
- * focus : function()
- * {
- * this.selectParentTab();
- * // do something else.
- * }
- */
- selectParentTab : function()
- {
- var element = this.getInputElement(),
- cursor = element,
- tabId;
- while ( ( cursor = cursor.getParent() ) && cursor.$.className.search( 'cke_dialog_page_contents' ) == -1 )
- { /*jsl:pass*/ }
-
- // Some widgets don't have parent tabs (e.g. OK and Cancel buttons).
- if ( !cursor )
- return this;
-
- tabId = cursor.getAttribute( 'name' );
- // Avoid duplicate select.
- if ( this._.dialog._.currentTabId != tabId )
- this._.dialog.selectPage( tabId );
- return this;
- },
-
- /**
- * Puts the focus to the UI object. Switches tabs if the UI object isn't in the active tab page.
- * @returns {CKEDITOR.dialog.uiElement} The current UI element.
- * @example
- * uiElement.focus();
- */
- focus : function()
- {
- this.selectParentTab().getInputElement().focus();
- return this;
- },
-
- /**
- * Registers the on* event handlers defined in the element definition.
- * The default behavior of this function is:
- * <ol>
- * <li>
- * If the on* event is defined in the class's eventProcesors list,
- * then the registration is delegated to the corresponding function
- * in the eventProcessors list.
- * </li>
- * <li>
- * If the on* event is not defined in the eventProcessors list, then
- * register the event handler under the corresponding DOM event of
- * the UI element's input DOM element (as defined by the return value
- * of {@link CKEDITOR.ui.dialog.uiElement#getInputElement}).
- * </li>
- * </ol>
- * This function is only called at UI element instantiation, but can
- * be overridded in child classes if they require more flexibility.
- * @param {CKEDITOR.dialog.uiElementDefinition} definition The UI element
- * definition.
- * @returns {CKEDITOR.dialog.uiElement} The current UI element.
- * @example
- */
- registerEvents : function( definition )
- {
- var regex = /^on([A-Z]\w+)/,
- match;
-
- var registerDomEvent = function( uiElement, dialog, eventName, func )
- {
- dialog.on( 'load', function()
- {
- uiElement.getInputElement().on( eventName, func, uiElement );
- });
- };
-
- for ( var i in definition )
- {
- if ( !( match = i.match( regex ) ) )
- continue;
- if ( this.eventProcessors[i] )
- this.eventProcessors[i].call( this, this._.dialog, definition[i] );
- else
- registerDomEvent( this, this._.dialog, match[1].toLowerCase(), definition[i] );
- }
-
- return this;
- },
-
- /**
- * The event processor list used by
- * {@link CKEDITOR.ui.dialog.uiElement#getInputElement} at UI element
- * instantiation. The default list defines three on* events:
- * <ol>
- * <li>onLoad - Called when the element's parent dialog opens for the
- * first time</li>
- * <li>onShow - Called whenever the element's parent dialog opens.</li>
- * <li>onHide - Called whenever the element's parent dialog closes.</li>
- * </ol>
- * @field
- * @type Object
- * @example
- * // This connects the 'click' event in CKEDITOR.ui.dialog.button to onClick
- * // handlers in the UI element's definitions.
- * CKEDITOR.ui.dialog.button.eventProcessors = CKEDITOR.tools.extend( {},
- * CKEDITOR.ui.dialog.uiElement.prototype.eventProcessors,
- * { onClick : function( dialog, func ) { this.on( 'click', func ); } },
- * true );
- */
- eventProcessors :
- {
- onLoad : function( dialog, func )
- {
- dialog.on( 'load', func, this );
- },
-
- onShow : function( dialog, func )
- {
- dialog.on( 'show', func, this );
- },
-
- onHide : function( dialog, func )
- {
- dialog.on( 'hide', func, this );
- }
- },
-
- /**
- * The default handler for a UI element's access key down event, which
- * tries to put focus to the UI element.<br />
- * Can be overridded in child classes for more sophisticaed behavior.
- * @param {CKEDITOR.dialog} dialog The parent dialog object.
- * @param {String} key The key combination pressed. Since access keys
- * are defined to always include the CTRL key, its value should always
- * include a 'CTRL+' prefix.
- * @example
- */
- accessKeyDown : function( dialog, key )
- {
- this.focus();
- },
-
- /**
- * The default handler for a UI element's access key up event, which
- * does nothing.<br />
- * Can be overridded in child classes for more sophisticated behavior.
- * @param {CKEDITOR.dialog} dialog The parent dialog object.
- * @param {String} key The key combination pressed. Since access keys
- * are defined to always include the CTRL key, its value should always
- * include a 'CTRL+' prefix.
- * @example
- */
- accessKeyUp : function( dialog, key )
- {
- },
-
- /**
- * Disables a UI element.
- * @example
- */
- disable : function()
- {
- var element = this.getInputElement();
- element.setAttribute( 'disabled', 'true' );
- element.addClass( 'cke_disabled' );
- },
-
- /**
- * Enables a UI element.
- * @example
- */
- enable : function()
- {
- var element = this.getInputElement();
- element.removeAttribute( 'disabled' );
- element.removeClass( 'cke_disabled' );
- },
-
- /**
- * Determines whether an UI element is enabled or not.
- * @returns {Boolean} Whether the UI element is enabled.
- * @example
- */
- isEnabled : function()
- {
- return !this.getInputElement().getAttribute( 'disabled' );
- },
-
- /**
- * Determines whether an UI element is visible or not.
- * @returns {Boolean} Whether the UI element is visible.
- * @example
- */
- isVisible : function()
- {
- return this.getInputElement().isVisible();
- },
-
- /**
- * Determines whether an UI element is focus-able or not.
- * Focus-able is defined as being both visible and enabled.
- * @returns {Boolean} Whether the UI element can be focused.
- * @example
- */
- isFocusable : function()
- {
- if ( !this.isEnabled() || !this.isVisible() )
- return false;
- return true;
- }
- };
-
- CKEDITOR.ui.dialog.hbox.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.uiElement,
- /**
- * @lends CKEDITOR.ui.dialog.hbox.prototype
- */
- {
- /**
- * Gets a child UI element inside this container.
- * @param {Array|Number} indices An array or a single number to indicate the child's
- * position in the container's descendant tree. Omit to get all the children in an array.
- * @returns {Array|CKEDITOR.ui.dialog.uiElement} Array of all UI elements in the container
- * if no argument given, or the specified UI element if indices is given.
- * @example
- * var checkbox = hbox.getChild( [0,1] );
- * checkbox.setValue( true );
- */
- getChild : function( indices )
- {
- // If no arguments, return a clone of the children array.
- if ( arguments.length < 1 )
- return this._.children.concat();
-
- // If indices isn't array, make it one.
- if ( !indices.splice )
- indices = [ indices ];
-
- // Retrieve the child element according to tree position.
- if ( indices.length < 2 )
- return this._.children[ indices[0] ];
- else
- return ( this._.children[ indices[0] ] && this._.children[ indices[0] ].getChild ) ?
- this._.children[ indices[0] ].getChild( indices.slice( 1, indices.length ) ) :
- null;
- }
- }, true );
-
- CKEDITOR.ui.dialog.vbox.prototype = new CKEDITOR.ui.dialog.hbox();
-
-
-
- (function()
- {
- var commonBuilder = {
- build : function( dialog, elementDefinition, output )
- {
- var children = elementDefinition.children,
- child,
- childHtmlList = [],
- childObjList = [];
- for ( var i = 0 ; ( i < children.length && ( child = children[i] ) ) ; i++ )
- {
- var childHtml = [];
- childHtmlList.push( childHtml );
- childObjList.push( CKEDITOR.dialog._.uiElementBuilders[ child.type ].build( dialog, child, childHtml ) );
- }
- return new CKEDITOR.ui.dialog[elementDefinition.type]( dialog, childObjList, childHtmlList, output, elementDefinition );
- }
- };
-
- CKEDITOR.dialog.addUIElement( 'hbox', commonBuilder );
- CKEDITOR.dialog.addUIElement( 'vbox', commonBuilder );
- })();
-
- /**
- * Generic dialog command. It opens a specific dialog when executed.
- * @constructor
- * @augments CKEDITOR.commandDefinition
- * @param {string} dialogName The name of the dialog to open when executing
- * this command.
- * @example
- * // Register the "link" command, which opens the "link" dialog.
- * editor.addCommand( 'link', <b>new CKEDITOR.dialogCommand( 'link' )</b> );
- */
- CKEDITOR.dialogCommand = function( dialogName )
- {
- this.dialogName = dialogName;
- };
-
- CKEDITOR.dialogCommand.prototype =
- {
- /** @ignore */
- exec : function( editor )
- {
- editor.openDialog( this.dialogName );
- },
-
- // Dialog commands just open a dialog ui, thus require no undo logic,
- // undo support should dedicate to specific dialog implementation.
- canUndo: false,
-
- editorFocus : CKEDITOR.env.ie
- };
-
- (function()
- {
- var notEmptyRegex = /^([a]|[^a])+$/,
- integerRegex = /^\d*$/,
- numberRegex = /^\d*(?:\.\d+)?$/;
-
- CKEDITOR.VALIDATE_OR = 1;
- CKEDITOR.VALIDATE_AND = 2;
-
- CKEDITOR.dialog.validate =
- {
- functions : function()
- {
- return function()
- {
- /**
- * It's important for validate functions to be able to accept the value
- * as argument in addition to this.getValue(), so that it is possible to
- * combine validate functions together to make more sophisticated
- * validators.
- */
- var value = this && this.getValue ? this.getValue() : arguments[0];
-
- var msg = undefined,
- relation = CKEDITOR.VALIDATE_AND,
- functions = [], i;
-
- for ( i = 0 ; i < arguments.length ; i++ )
- {
- if ( typeof( arguments[i] ) == 'function' )
- functions.push( arguments[i] );
- else
- break;
- }
-
- if ( i < arguments.length && typeof( arguments[i] ) == 'string' )
- {
- msg = arguments[i];
- i++;
- }
-
- if ( i < arguments.length && typeof( arguments[i]) == 'number' )
- relation = arguments[i];
-
- var passed = ( relation == CKEDITOR.VALIDATE_AND ? true : false );
- for ( i = 0 ; i < functions.length ; i++ )
- {
- if ( relation == CKEDITOR.VALIDATE_AND )
- passed = passed && functions[i]( value );
- else
- passed = passed || functions[i]( value );
- }
-
- if ( !passed )
- {
- if ( msg !== undefined )
- alert( msg );
- if ( this && ( this.select || this.focus ) )
- ( this.select || this.focus )();
- return false;
- }
-
- return true;
- };
- },
-
- regex : function( regex, msg )
- {
- /*
- * Can be greatly shortened by deriving from functions validator if code size
- * turns out to be more important than performance.
- */
- return function()
- {
- var value = this && this.getValue ? this.getValue() : arguments[0];
- if ( !regex.test( value ) )
- {
- if ( msg !== undefined )
- alert( msg );
- if ( this && ( this.select || this.focus ) )
- {
- if ( this.select )
- this.select();
- else
- this.focus();
- }
- return false;
- }
- return true;
- };
- },
-
- notEmpty : function( msg )
- {
- return this.regex( notEmptyRegex, msg );
- },
-
- integer : function( msg )
- {
- return this.regex( integerRegex, msg );
- },
-
- 'number' : function( msg )
- {
- return this.regex( numberRegex, msg );
- },
-
- equals : function( value, msg )
- {
- return this.functions( function( val ){ return val == value; }, msg );
- },
-
- notEqual : function( value, msg )
- {
- return this.functions( function( val ){ return val != value; }, msg );
- }
- };
- })();
-})();
-
-// Extend the CKEDITOR.editor class with dialog specific functions.
-CKEDITOR.tools.extend( CKEDITOR.editor.prototype,
- /** @lends 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, callback )
- {
- var dialogDefinitions = CKEDITOR.dialog._.dialogDefinitions[ dialogName ];
-
- // If the dialogDefinition is already loaded, open it immediately.
- if ( typeof dialogDefinitions == 'function' )
- {
- var storedDialogs = this._.storedDialogs ||
- ( this._.storedDialogs = {} );
-
- var dialog = storedDialogs[ dialogName ] ||
- ( storedDialogs[ dialogName ] = new CKEDITOR.dialog( this, dialogName ) );
-
- callback && callback.call( dialog, dialog );
- dialog.show();
-
- return dialog;
- }
- else if ( dialogDefinitions == 'failed' )
- throw new Error( '[CKEDITOR.dialog.openDialog] Dialog "' + dialogName + '" failed when loading definition.' );
-
- // Not loaded? Load the .js file first.
- var body = CKEDITOR.document.getBody(),
- cursor = body.$.style.cursor,
- me = this;
-
- body.setStyle( 'cursor', 'wait' );
- CKEDITOR.scriptLoader.load( CKEDITOR.getUrl( dialogDefinitions ), function()
- {
- // 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, callback );
- body.setStyle( 'cursor', cursor );
- } );
-
- return null;
- }
- });
-
-CKEDITOR.plugins.add( 'dialog',
- {
- requires : [ 'dialogui' ]
- });
-
-// Dialog related configurations.
-
-/**
- * The color of the dialog background cover. It should be a valid CSS color
- * string.
- * @name CKEDITOR.config.dialog_backgroundCoverColor
- * @type String
- * @default 'white'
- * @example
- * config.dialog_backgroundCoverColor = 'rgb(255, 254, 253)';
- */
-
-/**
- * The opacity of the dialog background cover. It should be a number within the
- * range [0.0, 1.0].
- * @name CKEDITOR.config.dialog_backgroundCoverOpacity
- * @type Number
- * @default 0.5
- * @example
- * config.dialog_backgroundCoverOpacity = 0.7;
- */
-
-/**
- * If the dialog has more than one tab, put focus into the first tab as soon as dialog is opened.
- * @name CKEDITOR.config.dialog_startupFocusTab
- * @type Boolean
- * @default false
- * @example
- * config.dialog_startupFocusTab = true;
- */
-
-/**
- * The distance of magnetic borders used in moving and resizing dialogs,
- * measured in pixels.
- * @name CKEDITOR.config.dialog_magnetDistance
- * @type Number
- * @default 20
- * @example
- * config.dialog_magnetDistance = 30;
- */
-
-/**
- * Fired when a dialog definition is about to be used to create a dialog into
- * an editor instance. This event makes it possible to customize the definition
- * before creating it.
- * <p>Note that this event is called only the first time a specific dialog is
- * opened. Successive openings will use the cached dialog, and this event will
- * not get fired.</p>
- * @name CKEDITOR#dialogDefinition
- * @event
- * @param {CKEDITOR.dialog.dialogDefinition} data The dialog defination that
- * is being loaded.
- * @param {CKEDITOR.editor} editor The editor instance that will use the
- * dialog.
- */
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview The floating dialog plugin. + */ + +/** + * No resize for this dialog. + * @constant + */ +CKEDITOR.DIALOG_RESIZE_NONE = 0; + +/** + * Only allow horizontal resizing for this dialog, disable vertical resizing. + * @constant + */ +CKEDITOR.DIALOG_RESIZE_WIDTH = 1; + +/** + * Only allow vertical resizing for this dialog, disable horizontal resizing. + * @constant + */ +CKEDITOR.DIALOG_RESIZE_HEIGHT = 2; + +/* + * Allow the dialog to be resized in both directions. + * @constant + */ +CKEDITOR.DIALOG_RESIZE_BOTH = 3; + +(function() +{ + function isTabVisible( tabId ) + { + return !!this._.tabs[ tabId ][ 0 ].$.offsetHeight; + } + + function getPreviousVisibleTab() + { + var tabId = this._.currentTabId, + length = this._.tabIdList.length, + tabIndex = CKEDITOR.tools.indexOf( this._.tabIdList, tabId ) + length; + + for ( var i = tabIndex - 1 ; i > tabIndex - length ; i-- ) + { + if ( isTabVisible.call( this, this._.tabIdList[ i % length ] ) ) + return this._.tabIdList[ i % length ]; + } + + return null; + } + + function getNextVisibleTab() + { + var tabId = this._.currentTabId, + length = this._.tabIdList.length, + tabIndex = CKEDITOR.tools.indexOf( this._.tabIdList, tabId ); + + for ( var i = tabIndex + 1 ; i < tabIndex + length ; i++ ) + { + if ( isTabVisible.call( this, this._.tabIdList[ i % length ] ) ) + return this._.tabIdList[ i % length ]; + } + + return null; + } + + /** + * This is the base class for runtime dialog objects. An instance of this + * class represents a single named dialog for a single editor instance. + * @param {Object} editor The editor which created the dialog. + * @param {String} dialogName The dialog's registered name. + * @constructor + * @example + * var dialogObj = new CKEDITOR.dialog( editor, 'smiley' ); + */ + CKEDITOR.dialog = function( editor, dialogName ) + { + // Load the dialog definition. + var definition = CKEDITOR.dialog._.dialogDefinitions[ dialogName ]; + + // Completes the definition with the default values. + definition = CKEDITOR.tools.extend( definition( editor ), defaultDialogDefinition ); + + // Clone a functionally independent copy for this dialog. + definition = CKEDITOR.tools.clone( definition ); + + // Create a complex definition object, extending it with the API + // functions. + definition = new definitionObject( this, definition ); + + + var doc = CKEDITOR.document; + + var themeBuilt = editor.theme.buildDialog( editor ); + + // Initialize some basic parameters. + this._ = + { + editor : editor, + element : themeBuilt.element, + name : dialogName, + contentSize : { width : 0, height : 0 }, + size : { width : 0, height : 0 }, + updateSize : false, + contents : {}, + buttons : {}, + accessKeyMap : {}, + + // Initialize the tab and page map. + tabs : {}, + tabIdList : [], + currentTabId : null, + currentTabIndex : null, + pageCount : 0, + lastTab : null, + tabBarMode : false, + + // Initialize the tab order array for input widgets. + focusList : [], + currentFocusIndex : 0, + hasFocus : false + }; + + this.parts = themeBuilt.parts; + + CKEDITOR.tools.setTimeout( function() + { + editor.fire( 'ariaWidget', this.parts.contents ); + }, + 0, this ); + + // Set the startup styles for the dialog, avoiding it enlarging the + // page size on the dialog creation. + this.parts.dialog.setStyles( + { + position : CKEDITOR.env.ie6Compat ? 'absolute' : 'fixed', + top : 0, + left: 0, + visibility : 'hidden' + }); + + // Call the CKEDITOR.event constructor to initialize this instance. + CKEDITOR.event.call( this ); + + // Fire the "dialogDefinition" event, making it possible to customize + // the dialog definition. + this.definition = definition = CKEDITOR.fire( 'dialogDefinition', + { + name : dialogName, + definition : definition + } + , editor ).definition; + // Initialize load, show, hide, ok and cancel events. + if ( definition.onLoad ) + this.on( 'load', definition.onLoad ); + + if ( definition.onShow ) + this.on( 'show', definition.onShow ); + + if ( definition.onHide ) + this.on( 'hide', definition.onHide ); + + if ( definition.onOk ) + { + this.on( 'ok', function( evt ) + { + if ( definition.onOk.call( this, evt ) === false ) + evt.data.hide = false; + }); + } + + if ( definition.onCancel ) + { + this.on( 'cancel', function( evt ) + { + if ( definition.onCancel.call( this, evt ) === false ) + evt.data.hide = false; + }); + } + + var me = this; + + // Iterates over all items inside all content in the dialog, calling a + // function for each of them. + var iterContents = function( func ) + { + var contents = me._.contents, + stop = false; + + for ( var i in contents ) + { + for ( var j in contents[i] ) + { + stop = func.call( this, contents[i][j] ); + if ( stop ) + return; + } + } + }; + + this.on( 'ok', function( evt ) + { + iterContents( function( item ) + { + if ( item.validate ) + { + var isValid = item.validate( this ); + + if ( typeof isValid == 'string' ) + { + alert( isValid ); + isValid = false; + } + + if ( isValid === false ) + { + if ( item.select ) + item.select(); + else + item.focus(); + + evt.data.hide = false; + evt.stop(); + return true; + } + } + }); + }, this, null, 0 ); + + this.on( 'cancel', function( evt ) + { + iterContents( function( item ) + { + if ( item.isChanged() ) + { + if ( !confirm( editor.lang.common.confirmCancel ) ) + evt.data.hide = false; + return true; + } + }); + }, this, null, 0 ); + + this.parts.close.on( 'click', function( evt ) + { + if ( this.fire( 'cancel', { hide : true } ).hide !== false ) + this.hide(); + evt.data.preventDefault(); + }, this ); + + // Sort focus list according to tab order definitions. + function setupFocus() + { + var focusList = me._.focusList; + focusList.sort( function( a, b ) + { + // Mimics browser tab order logics; + if ( a.tabIndex != b.tabIndex ) + return b.tabIndex - a.tabIndex; + // Sort is not stable in some browsers, + // fall-back the comparator to 'focusIndex'; + else + return a.focusIndex - b.focusIndex; + }); + + var size = focusList.length; + for ( var i = 0; i < size; i++ ) + focusList[ i ].focusIndex = i; + } + + function changeFocus( forward ) + { + var focusList = me._.focusList, + offset = forward ? 1 : -1; + if ( focusList.length < 1 ) + return; + + var current = me._.currentFocusIndex; + + // Trigger the 'blur' event of any input element before anything, + // since certain UI updates may depend on it. + try + { + focusList[ current ].getInputElement().$.blur(); + } + catch( e ){} + + var startIndex = ( current + offset + focusList.length ) % focusList.length, + currentIndex = startIndex; + while ( !focusList[ currentIndex ].isFocusable() ) + { + currentIndex = ( currentIndex + offset + focusList.length ) % focusList.length; + if ( currentIndex == startIndex ) + break; + } + focusList[ currentIndex ].focus(); + + // Select whole field content. + if ( focusList[ currentIndex ].type == 'text' ) + focusList[ currentIndex ].select(); + } + + this.changeFocus = changeFocus; + + var processed; + + function focusKeydownHandler( evt ) + { + // If I'm not the top dialog, ignore. + if ( me != CKEDITOR.dialog._.currentTop ) + return; + + var keystroke = evt.data.getKeystroke(), + rtl = editor.lang.dir == 'rtl'; + + processed = 0; + if ( keystroke == 9 || keystroke == CKEDITOR.SHIFT + 9 ) + { + var shiftPressed = ( keystroke == CKEDITOR.SHIFT + 9 ); + + // Handling Tab and Shift-Tab. + if ( me._.tabBarMode ) + { + // Change tabs. + var nextId = shiftPressed ? getPreviousVisibleTab.call( me ) : getNextVisibleTab.call( me ); + me.selectPage( nextId ); + me._.tabs[ nextId ][ 0 ].focus(); + } + else + { + // Change the focus of inputs. + changeFocus( !shiftPressed ); + } + + processed = 1; + } + else if ( keystroke == CKEDITOR.ALT + 121 && !me._.tabBarMode && me.getPageCount() > 1 ) + { + // Alt-F10 puts focus into the current tab item in the tab bar. + me._.tabBarMode = true; + me._.tabs[ me._.currentTabId ][ 0 ].focus(); + processed = 1; + } + else if ( ( keystroke == 37 || keystroke == 39 ) && me._.tabBarMode ) + { + // Arrow keys - used for changing tabs. + nextId = ( keystroke == ( rtl ? 39 : 37 ) ? getPreviousVisibleTab.call( me ) : getNextVisibleTab.call( me ) ); + me.selectPage( nextId ); + me._.tabs[ nextId ][ 0 ].focus(); + processed = 1; + } + else if ( ( keystroke == 13 || keystroke == 32 ) && me._.tabBarMode ) + { + this.selectPage( this._.currentTabId ); + this._.tabBarMode = false; + this._.currentFocusIndex = -1; + changeFocus( true ); + processed = 1; + } + + if ( processed ) + { + evt.stop(); + evt.data.preventDefault(); + } + } + + function focusKeyPressHandler( evt ) + { + processed && evt.data.preventDefault(); + } + + var dialogElement = this._.element; + // Add the dialog keyboard handlers. + this.on( 'show', function() + { + dialogElement.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 ) ) + dialogElement.on( 'keypress', focusKeyPressHandler, this ); + + } ); + this.on( 'hide', function() + { + dialogElement.removeListener( 'keydown', focusKeydownHandler ); + if ( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.mac ) ) + dialogElement.removeListener( 'keypress', focusKeyPressHandler ); + } ); + this.on( 'iframeAdded', function( evt ) + { + var doc = new CKEDITOR.dom.document( evt.data.iframe.$.contentWindow.document ); + doc.on( 'keydown', focusKeydownHandler, this, null, 0 ); + } ); + + // Auto-focus logic in dialog. + this.on( 'show', function() + { + // Setup tabIndex on showing the dialog instead of on loading + // to allow dynamic tab order happen in dialog definition. + setupFocus(); + + if ( editor.config.dialog_startupFocusTab + && me._.tabIdList.length > 1 ) + { + me._.tabBarMode = true; + me._.tabs[ me._.currentTabId ][ 0 ].focus(); + } + else if ( !this._.hasFocus ) + { + this._.currentFocusIndex = -1; + + // Decide where to put the initial focus. + if ( definition.onFocus ) + { + var initialFocus = definition.onFocus.call( this ); + // Focus the field that the user specified. + initialFocus && initialFocus.focus(); + } + // Focus the first field in layout order. + else + changeFocus( true ); + + /* + * IE BUG: If the initial focus went into a non-text element (e.g. button), + * then IE would still leave the caret inside the editing area. + */ + if ( this._.editor.mode == 'wysiwyg' && CKEDITOR.env.ie ) + { + var $selection = editor.document.$.selection, + $range = $selection.createRange(); + + if ( $range ) + { + if ( $range.parentElement && $range.parentElement().ownerDocument == editor.document.$ + || $range.item && $range.item( 0 ).ownerDocument == editor.document.$ ) + { + var $myRange = document.body.createTextRange(); + $myRange.moveToElementText( this.getElement().getFirst().$ ); + $myRange.collapse( true ); + $myRange.select(); + } + } + } + } + }, this, null, 0xffffffff ); + + // IE6 BUG: Text fields and text areas are only half-rendered the first time the dialog appears in IE6 (#2661). + // This is still needed after [2708] and [2709] because text fields in hidden TR tags are still broken. + if ( CKEDITOR.env.ie6Compat ) + { + this.on( 'load', function( evt ) + { + var outer = this.getElement(), + inner = outer.getFirst(); + inner.remove(); + inner.appendTo( outer ); + }, this ); + } + + initDragAndDrop( this ); + initResizeHandles( this ); + + // Insert the title. + ( new CKEDITOR.dom.text( definition.title, CKEDITOR.document ) ).appendTo( this.parts.title ); + + // Insert the tabs and contents. + for ( var i = 0 ; i < definition.contents.length ; i++ ) + this.addPage( definition.contents[i] ); + + this.parts['tabs'].on( 'click', function( evt ) + { + var target = evt.data.getTarget(); + // If we aren't inside a tab, bail out. + if ( target.hasClass( 'cke_dialog_tab' ) ) + { + var id = target.$.id; + this.selectPage( id.substr( 0, id.lastIndexOf( '_' ) ) ); + if ( this._.tabBarMode ) + { + this._.tabBarMode = false; + this._.currentFocusIndex = -1; + changeFocus( true ); + } + evt.data.preventDefault(); + } + }, this ); + + // Insert buttons. + var buttonsHtml = [], + buttons = CKEDITOR.dialog._.uiElementBuilders.hbox.build( this, + { + type : 'hbox', + className : 'cke_dialog_footer_buttons', + widths : [], + children : definition.buttons + }, buttonsHtml ).getChild(); + this.parts.footer.setHtml( buttonsHtml.join( '' ) ); + + for ( i = 0 ; i < buttons.length ; i++ ) + this._.buttons[ buttons[i].id ] = buttons[i]; + }; + + // Focusable interface. Use it via dialog.addFocusable. + function Focusable( dialog, element, index ) + { + this.element = element; + this.focusIndex = index; + // TODO: support tabIndex for focusables. + this.tabIndex = 0; + this.isFocusable = function() + { + return !element.getAttribute( 'disabled' ) && element.isVisible(); + }; + this.focus = function() + { + dialog._.currentFocusIndex = this.focusIndex; + this.element.focus(); + }; + // Bind events + element.on( 'keydown', function( e ) + { + if ( e.data.getKeystroke() in { 32:1, 13:1 } ) + this.fire( 'click' ); + } ); + element.on( 'focus', function() + { + this.fire( 'mouseover' ); + } ); + element.on( 'blur', function() + { + this.fire( 'mouseout' ); + } ); + } + + CKEDITOR.dialog.prototype = + { + destroy : function() + { + this.hide(); + this._.element.remove(); + }, + + /** + * Resizes the dialog. + * @param {Number} width The width of the dialog in pixels. + * @param {Number} height The height of the dialog in pixels. + * @function + * @example + * dialogObj.resize( 800, 640 ); + */ + resize : (function() + { + return function( width, height ) + { + if ( this._.contentSize && this._.contentSize.width == width && this._.contentSize.height == height ) + return; + + CKEDITOR.dialog.fire( 'resize', + { + dialog : this, + skin : this._.editor.skinName, + width : width, + height : height + }, this._.editor ); + + this._.contentSize = { width : width, height : height }; + this._.updateSize = true; + }; + })(), + + /** + * Gets the current size of the dialog in pixels. + * @returns {Object} An object with "width" and "height" properties. + * @example + * var width = dialogObj.getSize().width; + */ + getSize : function() + { + if ( !this._.updateSize ) + return this._.size; + var element = this._.element.getFirst(); + var size = this._.size = { width : element.$.offsetWidth || 0, height : element.$.offsetHeight || 0}; + + // If either the offsetWidth or offsetHeight is 0, the element isn't visible. + this._.updateSize = !size.width || !size.height; + + return size; + }, + + /** + * Moves the dialog to an (x, y) coordinate relative to the window. + * @function + * @param {Number} x The target x-coordinate. + * @param {Number} y The target y-coordinate. + * @example + * dialogObj.move( 10, 40 ); + */ + move : (function() + { + var isFixed; + return function( x, y ) + { + // The dialog may be fixed positioned or absolute positioned. Ask the + // browser what is the current situation first. + var element = this._.element.getFirst(); + if ( isFixed === undefined ) + isFixed = element.getComputedStyle( 'position' ) == 'fixed'; + + if ( isFixed && this._.position && this._.position.x == x && this._.position.y == y ) + return; + + // Save the current position. + this._.position = { x : x, y : y }; + + // If not fixed positioned, add scroll position to the coordinates. + if ( !isFixed ) + { + var scrollPosition = CKEDITOR.document.getWindow().getScrollPosition(); + x += scrollPosition.x; + y += scrollPosition.y; + } + + element.setStyles( + { + 'left' : ( x > 0 ? x : 0 ) + 'px', + 'top' : ( y > 0 ? y : 0 ) + 'px' + }); + }; + })(), + + /** + * Gets the dialog's position in the window. + * @returns {Object} An object with "x" and "y" properties. + * @example + * var dialogX = dialogObj.getPosition().x; + */ + getPosition : function(){ return CKEDITOR.tools.extend( {}, this._.position ); }, + + /** + * Shows the dialog box. + * @example + * dialogObj.show(); + */ + show : function() + { + 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; + var definition = this.definition; + if ( !( element.getParent() && element.getParent().equals( CKEDITOR.document.getBody() ) ) ) + element.appendTo( CKEDITOR.document.getBody() ); + else + element.setStyle( 'display', 'block' ); + + // FIREFOX BUG: Fix vanishing caret for Firefox 2 or Gecko 1.8. + if ( CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 ) + { + var dialogElement = this.parts.dialog; + dialogElement.setStyle( 'position', 'absolute' ); + setTimeout( function() + { + dialogElement.setStyle( 'position', 'fixed' ); + }, 0 ); + } + + + // First, set the dialog to an appropriate size. + this.resize( definition.minWidth, definition.minHeight ); + + // Select the first tab by default. + this.selectPage( this.definition.contents[0].id ); + + // Reset all inputs back to their default value. + this.reset(); + + // Set z-index. + if ( CKEDITOR.dialog._.currentZIndex === null ) + CKEDITOR.dialog._.currentZIndex = this._.editor.config.baseFloatZIndex; + this._.element.getFirst().setStyle( 'z-index', CKEDITOR.dialog._.currentZIndex += 10 ); + + // Maintain the dialog ordering and dialog cover. + // Also register key handlers if first dialog. + if ( CKEDITOR.dialog._.currentTop === null ) + { + CKEDITOR.dialog._.currentTop = this; + this._.parentDialog = null; + showCover( this._.editor ); + + 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 } ) + element.on( event, preventKeyBubbling ); + } + else + { + this._.parentDialog = CKEDITOR.dialog._.currentTop; + var parentElement = this._.parentDialog.getElement().getFirst(); + parentElement.$.style.zIndex -= Math.floor( this._.editor.config.baseFloatZIndex / 2 ); + CKEDITOR.dialog._.currentTop = this; + } + + // Register the Esc hotkeys. + registerAccessKey( this, this, '\x1b', null, function() + { + this.getButton( 'cancel' ) && this.getButton( 'cancel' ).click(); + } ); + + // Reset the hasFocus state. + this._.hasFocus = false; + + // Rearrange the dialog to the middle of the window. + CKEDITOR.tools.setTimeout( function() + { + var viewSize = CKEDITOR.document.getWindow().getViewPaneSize(); + var dialogSize = this.getSize(); + + // We're using definition size for initial position because of + // offten corrupted data in offsetWidth at this point. (#4084) + this.move( ( viewSize.width - definition.minWidth ) / 2, ( viewSize.height - dialogSize.height ) / 2 ); + + this.parts.dialog.setStyle( 'visibility', '' ); + + // 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(); } ); + + }, + 100, this ); + }, + + /** + * Executes a function for each UI element. + * @param {Function} fn Function to execute for each UI element. + * @returns {CKEDITOR.dialog} The current dialog object. + */ + foreach : function( fn ) + { + for ( var i in this._.contents ) + { + for ( var j in this._.contents[i] ) + fn( this._.contents[i][j]); + } + return this; + }, + + /** + * Resets all input values in the dialog. + * @example + * dialogObj.reset(); + * @returns {CKEDITOR.dialog} The current dialog object. + */ + reset : (function() + { + var fn = function( widget ){ if ( widget.reset ) widget.reset(); }; + return function(){ this.foreach( fn ); return this; }; + })(), + + setupContent : function() + { + var args = arguments; + this.foreach( function( widget ) + { + if ( widget.setup ) + widget.setup.apply( widget, args ); + }); + }, + + commitContent : function() + { + var args = arguments; + this.foreach( function( widget ) + { + if ( widget.commit ) + widget.commit.apply( widget, args ); + }); + }, + + /** + * Hides the dialog box. + * @example + * dialogObj.hide(); + */ + hide : function() + { + if ( !this.parts.dialog.isVisible() ) + return; + + this.fire( 'hide', {} ); + this._.editor.fire( 'dialogHide', this ); + var element = this._.element; + element.setStyle( 'display', 'none' ); + this.parts.dialog.setStyle( 'visibility', 'hidden' ); + // Unregister all access keys associated with this dialog. + unregisterAccessKey( this ); + + // Close any child(top) dialogs first. + while( CKEDITOR.dialog._.currentTop != this ) + CKEDITOR.dialog._.currentTop.hide(); + + // Maintain dialog ordering and remove cover if needed. + if ( !this._.parentDialog ) + hideCover(); + else + { + var parentElement = this._.parentDialog.getElement().getFirst(); + parentElement.setStyle( 'z-index', parseInt( parentElement.$.style.zIndex, 10 ) + Math.floor( this._.editor.config.baseFloatZIndex / 2 ) ); + } + CKEDITOR.dialog._.currentTop = this._.parentDialog; + + // Deduct or clear the z-index. + if ( !this._.parentDialog ) + { + CKEDITOR.dialog._.currentZIndex = null; + + // Remove access key handlers. + 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 } ) + element.removeListener( event, preventKeyBubbling ); + + var editor = this._.editor; + editor.focus(); + + if ( editor.mode == 'wysiwyg' && CKEDITOR.env.ie ) + { + var selection = editor.getSelection(); + selection && selection.unlock( true ); + } + } + else + CKEDITOR.dialog._.currentZIndex -= 10; + + delete this._.parentDialog; + // Reset the initial values of the dialog. + this.foreach( function( contentObj ) { contentObj.resetInitValue && contentObj.resetInitValue(); } ); + }, + + /** + * Adds a tabbed page into the dialog. + * @param {Object} contents Content definition. + * @example + */ + addPage : function( contents ) + { + var pageHtml = [], + titleHtml = contents.label ? ' title="' + CKEDITOR.tools.htmlEncode( contents.label ) + '"' : '', + elements = contents.elements, + vbox = CKEDITOR.dialog._.uiElementBuilders.vbox.build( this, + { + type : 'vbox', + className : 'cke_dialog_page_contents', + children : contents.elements, + expand : !!contents.expand, + padding : contents.padding, + style : contents.style || 'width: 100%; height: 100%;' + }, pageHtml ); + + // Create the HTML for the tab and the content block. + var page = CKEDITOR.dom.element.createFromHtml( pageHtml.join( '' ) ); + page.setAttribute( 'role', 'tabpanel' ); + + var env = CKEDITOR.env; + var tabId = contents.id + '_' + CKEDITOR.tools.getNextNumber(), + tab = CKEDITOR.dom.element.createFromHtml( [ + '<a class="cke_dialog_tab"', + ( this._.pageCount > 0 ? ' cke_last' : 'cke_first' ), + titleHtml, + ( !!contents.hidden ? ' style="display:none"' : '' ), + ' id="', tabId, '"', + env.gecko && env.version >= 10900 && !env.hc ? '' : ' href="javascript:void(0)"', + ' tabIndex="-1"', + ' hidefocus="true"', + ' role="tab">', + contents.label, + '</a>' + ].join( '' ) ); + + page.setAttribute( 'aria-labelledby', tabId ); + + // Take records for the tabs and elements created. + this._.tabs[ contents.id ] = [ tab, page ]; + this._.tabIdList.push( contents.id ); + !contents.hidden && this._.pageCount++; + this._.lastTab = tab; + this.updateStyle(); + + var contentMap = this._.contents[ contents.id ] = {}, + cursor, + children = vbox.getChild(); + + while ( ( cursor = children.shift() ) ) + { + contentMap[ cursor.id ] = cursor; + if ( typeof( cursor.getChild ) == 'function' ) + children.push.apply( children, cursor.getChild() ); + } + + // Attach the DOM nodes. + + page.setAttribute( 'name', contents.id ); + page.appendTo( this.parts.contents ); + + tab.unselectable(); + this.parts.tabs.append( tab ); + + // Add access key handlers if access key is defined. + if ( contents.accessKey ) + { + registerAccessKey( this, this, 'CTRL+' + contents.accessKey, + tabAccessKeyDown, tabAccessKeyUp ); + this._.accessKeyMap[ 'CTRL+' + contents.accessKey ] = contents.id; + } + }, + + /** + * Activates a tab page in the dialog by its id. + * @param {String} id The id of the dialog tab to be activated. + * @example + * dialogObj.selectPage( 'tab_1' ); + */ + selectPage : function( id ) + { + // Hide the non-selected tabs and pages. + for ( var i in this._.tabs ) + { + var tab = this._.tabs[i][0], + page = this._.tabs[i][1]; + if ( i != id ) + { + tab.removeClass( 'cke_dialog_tab_selected' ); + page.hide(); + } + page.setAttribute( 'aria-hidden', i != id ); + } + + var selected = this._.tabs[id]; + selected[0].addClass( 'cke_dialog_tab_selected' ); + selected[1].show(); + this._.currentTabId = id; + this._.currentTabIndex = CKEDITOR.tools.indexOf( this._.tabIdList, id ); + }, + + // Dialog state-specific style updates. + updateStyle : function() + { + // If only a single page shown, a different style is used in the central pane. + this.parts.dialog[ ( this._.pageCount === 1 ? 'add' : 'remove' ) + 'Class' ]( 'cke_single_page' ); + }, + + /** + * Hides a page's tab away from the dialog. + * @param {String} id The page's Id. + * @example + * dialog.hidePage( 'tab_3' ); + */ + hidePage : function( id ) + { + var tab = this._.tabs[id] && this._.tabs[id][0]; + if ( !tab || this._.pageCount == 1 ) + return; + // Switch to other tab first when we're hiding the active tab. + else if ( id == this._.currentTabId ) + this.selectPage( getPreviousVisibleTab.call( this ) ); + + tab.hide(); + this._.pageCount--; + this.updateStyle(); + }, + + /** + * Unhides a page's tab. + * @param {String} id The page's Id. + * @example + * dialog.showPage( 'tab_2' ); + */ + showPage : function( id ) + { + var tab = this._.tabs[id] && this._.tabs[id][0]; + if ( !tab ) + return; + tab.show(); + this._.pageCount++; + this.updateStyle(); + }, + + /** + * Gets the root DOM element of the dialog. + * @returns {CKEDITOR.dom.element} The <span> element containing this dialog. + * @example + * var dialogElement = dialogObj.getElement().getFirst(); + * dialogElement.setStyle( 'padding', '5px' ); + */ + getElement : function() + { + return this._.element; + }, + + /** + * Gets the name of the dialog. + * @returns {String} The name of this dialog. + * @example + * var dialogName = dialogObj.getName(); + */ + getName : function() + { + return this._.name; + }, + + /** + * Gets a dialog UI element object from a dialog page. + * @param {String} pageId id of dialog page. + * @param {String} elementId id of UI element. + * @example + * @returns {CKEDITOR.ui.dialog.uiElement} The dialog UI element. + */ + getContentElement : function( pageId, elementId ) + { + var page = this._.contents[ pageId ]; + return page && page[ elementId ]; + }, + + /** + * Gets the value of a dialog UI element. + * @param {String} pageId id of dialog page. + * @param {String} elementId id of UI element. + * @example + * @returns {Object} The value of the UI element. + */ + getValueOf : function( pageId, elementId ) + { + return this.getContentElement( pageId, elementId ).getValue(); + }, + + /** + * Sets the value of a dialog UI element. + * @param {String} pageId id of the dialog page. + * @param {String} elementId id of the UI element. + * @param {Object} value The new value of the UI element. + * @example + */ + setValueOf : function( pageId, elementId, value ) + { + return this.getContentElement( pageId, elementId ).setValue( value ); + }, + + /** + * Gets the UI element of a button in the dialog's button row. + * @param {String} id The id of the button. + * @example + * @returns {CKEDITOR.ui.dialog.button} The button object. + */ + getButton : function( id ) + { + return this._.buttons[ id ]; + }, + + /** + * Simulates a click to a dialog button in the dialog's button row. + * @param {String} id The id of the button. + * @example + * @returns The return value of the dialog's "click" event. + */ + click : function( id ) + { + return this._.buttons[ id ].click(); + }, + + /** + * Disables a dialog button. + * @param {String} id The id of the button. + * @example + */ + disableButton : function( id ) + { + return this._.buttons[ id ].disable(); + }, + + /** + * Enables a dialog button. + * @param {String} id The id of the button. + * @example + */ + enableButton : function( id ) + { + return this._.buttons[ id ].enable(); + }, + + /** + * Gets the number of pages in the dialog. + * @returns {Number} Page count. + */ + getPageCount : function() + { + return this._.pageCount; + }, + + /** + * Gets the editor instance which opened this dialog. + * @returns {CKEDITOR.editor} Parent editor instances. + */ + getParentEditor : function() + { + return this._.editor; + }, + + /** + * Gets the element that was selected when opening the dialog, if any. + * @returns {CKEDITOR.dom.element} The element that was selected, or null. + */ + getSelectedElement : function() + { + return this.getParentEditor().getSelection().getSelectedElement(); + }, + + /** + * Adds element to dialog's focusable list. + * + * @param {CKEDITOR.dom.element} element + * @param {Number} [index] + */ + addFocusable: function( element, index ) { + if ( typeof index == 'undefined' ) + { + index = this._.focusList.length; + this._.focusList.push( new Focusable( this, element, index ) ); + } + else + { + this._.focusList.splice( index, 0, new Focusable( this, element, index ) ); + for ( var i = index + 1 ; i < this._.focusList.length ; i++ ) + this._.focusList[ i ].focusIndex++; + } + } + }; + + CKEDITOR.tools.extend( CKEDITOR.dialog, + /** + * @lends CKEDITOR.dialog + */ + { + /** + * Registers a dialog. + * @param {String} name The dialog's name. + * @param {Function|String} dialogDefinition + * A function returning the dialog's definition, or the URL to the .js file holding the function. + * The function should accept an argument "editor" which is the current editor instance, and + * return an object conforming to {@link CKEDITOR.dialog.dialogDefinition}. + * @example + * @see CKEDITOR.dialog.dialogDefinition + */ + add : function( name, dialogDefinition ) + { + // Avoid path registration from multiple instances override definition. + if ( !this._.dialogDefinitions[name] + || typeof dialogDefinition == 'function' ) + this._.dialogDefinitions[name] = dialogDefinition; + }, + + exists : function( name ) + { + return !!this._.dialogDefinitions[ name ]; + }, + + getCurrent : function() + { + return CKEDITOR.dialog._.currentTop; + }, + + /** + * The default OK button for dialogs. Fires the "ok" event and closes the dialog if the event succeeds. + * @static + * @field + * @example + * @type Function + */ + okButton : (function() + { + var retval = function( editor, override ) + { + override = override || {}; + return CKEDITOR.tools.extend( { + id : 'ok', + type : 'button', + label : editor.lang.common.ok, + 'class' : 'cke_dialog_ui_button_ok', + onClick : function( evt ) + { + var dialog = evt.data.dialog; + if ( dialog.fire( 'ok', { hide : true } ).hide !== false ) + dialog.hide(); + } + }, override, true ); + }; + retval.type = 'button'; + retval.override = function( override ) + { + return CKEDITOR.tools.extend( function( editor ){ return retval( editor, override ); }, + { type : 'button' }, true ); + }; + return retval; + })(), + + /** + * The default cancel button for dialogs. Fires the "cancel" event and closes the dialog if no UI element value changed. + * @static + * @field + * @example + * @type Function + */ + cancelButton : (function() + { + var retval = function( editor, override ) + { + override = override || {}; + return CKEDITOR.tools.extend( { + id : 'cancel', + type : 'button', + label : editor.lang.common.cancel, + 'class' : 'cke_dialog_ui_button_cancel', + onClick : function( evt ) + { + var dialog = evt.data.dialog; + if ( dialog.fire( 'cancel', { hide : true } ).hide !== false ) + dialog.hide(); + } + }, override, true ); + }; + retval.type = 'button'; + retval.override = function( override ) + { + return CKEDITOR.tools.extend( function( editor ){ return retval( editor, override ); }, + { type : 'button' }, true ); + }; + return retval; + })(), + + /** + * Registers a dialog UI element. + * @param {String} typeName The name of the UI element. + * @param {Function} builder The function to build the UI element. + * @example + */ + addUIElement : function( typeName, builder ) + { + this._.uiElementBuilders[ typeName ] = builder; + } + }); + + CKEDITOR.dialog._ = + { + uiElementBuilders : {}, + + dialogDefinitions : {}, + + currentTop : null, + + currentZIndex : null + }; + + // "Inherit" (copy actually) from CKEDITOR.event. + CKEDITOR.event.implementOn( CKEDITOR.dialog ); + CKEDITOR.event.implementOn( CKEDITOR.dialog.prototype, true ); + + var defaultDialogDefinition = + { + resizable : CKEDITOR.DIALOG_RESIZE_BOTH, + minWidth : 600, + minHeight : 400, + buttons : [ CKEDITOR.dialog.okButton, CKEDITOR.dialog.cancelButton ] + }; + + // The buttons in MacOS Apps are in reverse order #4750 + CKEDITOR.env.mac && defaultDialogDefinition.buttons.reverse(); + + // Tool function used to return an item from an array based on its id + // property. + var getById = function( array, id, recurse ) + { + for ( var i = 0, item ; ( item = array[ i ] ) ; i++ ) + { + if ( item.id == id ) + return item; + if ( recurse && item[ recurse ] ) + { + var retval = getById( item[ recurse ], id, recurse ) ; + if ( retval ) + return retval; + } + } + return null; + }; + + // Tool function used to add an item into an array. + var addById = function( array, newItem, nextSiblingId, recurse, nullIfNotFound ) + { + if ( nextSiblingId ) + { + for ( var i = 0, item ; ( item = array[ i ] ) ; i++ ) + { + if ( item.id == nextSiblingId ) + { + array.splice( i, 0, newItem ); + return newItem; + } + + if ( recurse && item[ recurse ] ) + { + var retval = addById( item[ recurse ], newItem, nextSiblingId, recurse, true ); + if ( retval ) + return retval; + } + } + + if ( nullIfNotFound ) + return null; + } + + array.push( newItem ); + return newItem; + }; + + // Tool function used to remove an item from an array based on its id. + var removeById = function( array, id, recurse ) + { + for ( var i = 0, item ; ( item = array[ i ] ) ; i++ ) + { + if ( item.id == id ) + return array.splice( i, 1 ); + if ( recurse && item[ recurse ] ) + { + var retval = removeById( item[ recurse ], id, recurse ); + if ( retval ) + return retval; + } + } + return null; + }; + + /** + * This class is not really part of the API. It is the "definition" property value + * passed to "dialogDefinition" event handlers. + * @constructor + * @name CKEDITOR.dialog.dialogDefinitionObject + * @extends CKEDITOR.dialog.dialogDefinition + * @example + * CKEDITOR.on( 'dialogDefinition', function( evt ) + * { + * var definition = evt.data.definition; + * var content = definition.getContents( 'page1' ); + * ... + * } ); + */ + var definitionObject = function( dialog, dialogDefinition ) + { + // TODO : Check if needed. + this.dialog = dialog; + + // Transform the contents entries in contentObjects. + var contents = dialogDefinition.contents; + for ( var i = 0, content ; ( content = contents[i] ) ; i++ ) + contents[ i ] = new contentObject( dialog, content ); + + CKEDITOR.tools.extend( this, dialogDefinition ); + }; + + definitionObject.prototype = + /** @lends CKEDITOR.dialog.dialogDefinitionObject.prototype */ + { + /** + * Gets a content definition. + * @param {String} id The id of the content definition. + * @returns {CKEDITOR.dialog.contentDefinition} The content definition + * matching id. + */ + getContents : function( id ) + { + return getById( this.contents, id ); + }, + + /** + * Gets a button definition. + * @param {String} id The id of the button definition. + * @returns {CKEDITOR.dialog.buttonDefinition} The button definition + * matching id. + */ + getButton : function( id ) + { + return getById( this.buttons, id ); + }, + + /** + * Adds a content definition object under this dialog definition. + * @param {CKEDITOR.dialog.contentDefinition} contentDefinition The + * content definition. + * @param {String} [nextSiblingId] The id of an existing content + * definition which the new content definition will be inserted + * before. Omit if the new content definition is to be inserted as + * the last item. + * @returns {CKEDITOR.dialog.contentDefinition} The inserted content + * definition. + */ + addContents : function( contentDefinition, nextSiblingId ) + { + return addById( this.contents, contentDefinition, nextSiblingId ); + }, + + /** + * Adds a button definition object under this dialog definition. + * @param {CKEDITOR.dialog.buttonDefinition} buttonDefinition The + * button definition. + * @param {String} [nextSiblingId] The id of an existing button + * definition which the new button definition will be inserted + * before. Omit if the new button definition is to be inserted as + * the last item. + * @returns {CKEDITOR.dialog.buttonDefinition} The inserted button + * definition. + */ + addButton : function( buttonDefinition, nextSiblingId ) + { + return addById( this.buttons, buttonDefinition, nextSiblingId ); + }, + + /** + * Removes a content definition from this dialog definition. + * @param {String} id The id of the content definition to be removed. + * @returns {CKEDITOR.dialog.contentDefinition} The removed content + * definition. + */ + removeContents : function( id ) + { + removeById( this.contents, id ); + }, + + /** + * Removes a button definition from the dialog definition. + * @param {String} id The id of the button definition to be removed. + * @returns {CKEDITOR.dialog.buttonDefinition} The removed button + * definition. + */ + removeButton : function( id ) + { + removeById( this.buttons, id ); + } + }; + + /** + * This class is not really part of the API. It is the template of the + * objects representing content pages inside the + * CKEDITOR.dialog.dialogDefinitionObject. + * @constructor + * @name CKEDITOR.dialog.contentDefinitionObject + * @example + * CKEDITOR.on( 'dialogDefinition', function( evt ) + * { + * var definition = evt.data.definition; + * var content = definition.getContents( 'page1' ); + * content.remove( 'textInput1' ); + * ... + * } ); + */ + function contentObject( dialog, contentDefinition ) + { + this._ = + { + dialog : dialog + }; + + CKEDITOR.tools.extend( this, contentDefinition ); + } + + contentObject.prototype = + /** @lends CKEDITOR.dialog.contentDefinitionObject.prototype */ + { + /** + * Gets a UI element definition under the content definition. + * @param {String} id The id of the UI element definition. + * @returns {CKEDITOR.dialog.uiElementDefinition} + */ + get : function( id ) + { + return getById( this.elements, id, 'children' ); + }, + + /** + * Adds a UI element definition to the content definition. + * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition The + * UI elemnet definition to be added. + * @param {String} nextSiblingId The id of an existing UI element + * definition which the new UI element definition will be inserted + * before. Omit if the new button definition is to be inserted as + * the last item. + * @returns {CKEDITOR.dialog.uiElementDefinition} The element + * definition inserted. + */ + add : function( elementDefinition, nextSiblingId ) + { + return addById( this.elements, elementDefinition, nextSiblingId, 'children' ); + }, + + /** + * Removes a UI element definition from the content definition. + * @param {String} id The id of the UI element definition to be + * removed. + * @returns {CKEDITOR.dialog.uiElementDefinition} The element + * definition removed. + * @example + */ + remove : function( id ) + { + removeById( this.elements, id, 'children' ); + } + }; + + function initDragAndDrop( dialog ) + { + var lastCoords = null, + abstractDialogCoords = null, + element = dialog.getElement().getFirst(), + editor = dialog.getParentEditor(), + magnetDistance = editor.config.dialog_magnetDistance, + margins = editor.skin.margins || [ 0, 0, 0, 0 ]; + + if ( typeof magnetDistance == 'undefined' ) + magnetDistance = 20; + + function mouseMoveHandler( evt ) + { + var dialogSize = dialog.getSize(), + viewPaneSize = CKEDITOR.document.getWindow().getViewPaneSize(), + x = evt.data.$.screenX, + y = evt.data.$.screenY, + dx = x - lastCoords.x, + dy = y - lastCoords.y, + realX, realY; + + lastCoords = { x : x, y : y }; + abstractDialogCoords.x += dx; + abstractDialogCoords.y += dy; + + if ( abstractDialogCoords.x + margins[3] < magnetDistance ) + realX = - margins[3]; + else if ( abstractDialogCoords.x - margins[1] > viewPaneSize.width - dialogSize.width - magnetDistance ) + realX = viewPaneSize.width - dialogSize.width + margins[1]; + else + realX = abstractDialogCoords.x; + + if ( abstractDialogCoords.y + margins[0] < magnetDistance ) + realY = - margins[0]; + else if ( abstractDialogCoords.y - margins[2] > viewPaneSize.height - dialogSize.height - magnetDistance ) + realY = viewPaneSize.height - dialogSize.height + margins[2]; + else + realY = abstractDialogCoords.y; + + dialog.move( realX, realY ); + + evt.data.preventDefault(); + } + + function mouseUpHandler( evt ) + { + CKEDITOR.document.removeListener( 'mousemove', mouseMoveHandler ); + CKEDITOR.document.removeListener( 'mouseup', mouseUpHandler ); + + if ( CKEDITOR.env.ie6Compat ) + { + var coverDoc = currentCover.getChild( 0 ).getFrameDocument(); + coverDoc.removeListener( 'mousemove', mouseMoveHandler ); + coverDoc.removeListener( 'mouseup', mouseUpHandler ); + } + } + + dialog.parts.title.on( 'mousedown', function( evt ) + { + dialog._.updateSize = true; + + lastCoords = { x : evt.data.$.screenX, y : evt.data.$.screenY }; + + CKEDITOR.document.on( 'mousemove', mouseMoveHandler ); + CKEDITOR.document.on( 'mouseup', mouseUpHandler ); + abstractDialogCoords = dialog.getPosition(); + + if ( CKEDITOR.env.ie6Compat ) + { + var coverDoc = currentCover.getChild( 0 ).getFrameDocument(); + coverDoc.on( 'mousemove', mouseMoveHandler ); + coverDoc.on( 'mouseup', mouseUpHandler ); + } + + evt.data.preventDefault(); + }, dialog ); + } + + function initResizeHandles( dialog ) + { + var definition = dialog.definition, + minWidth = definition.minWidth || 0, + minHeight = definition.minHeight || 0, + resizable = definition.resizable, + margins = dialog.getParentEditor().skin.margins || [ 0, 0, 0, 0 ]; + + function topSizer( coords, dy ) + { + coords.y += dy; + } + + function rightSizer( coords, dx ) + { + coords.x2 += dx; + } + + function bottomSizer( coords, dy ) + { + coords.y2 += dy; + } + + function leftSizer( coords, dx ) + { + coords.x += dx; + } + + var lastCoords = null, + abstractDialogCoords = null, + magnetDistance = dialog._.editor.config.magnetDistance, + parts = [ 'tl', 't', 'tr', 'l', 'r', 'bl', 'b', 'br' ]; + + function mouseDownHandler( evt ) + { + var partName = evt.listenerData.part, size = dialog.getSize(); + abstractDialogCoords = dialog.getPosition(); + CKEDITOR.tools.extend( abstractDialogCoords, + { + x2 : abstractDialogCoords.x + size.width, + y2 : abstractDialogCoords.y + size.height + } ); + lastCoords = { x : evt.data.$.screenX, y : evt.data.$.screenY }; + + CKEDITOR.document.on( 'mousemove', mouseMoveHandler, dialog, { part : partName } ); + CKEDITOR.document.on( 'mouseup', mouseUpHandler, dialog, { part : partName } ); + + if ( CKEDITOR.env.ie6Compat ) + { + var coverDoc = currentCover.getChild( 0 ).getFrameDocument(); + coverDoc.on( 'mousemove', mouseMoveHandler, dialog, { part : partName } ); + coverDoc.on( 'mouseup', mouseUpHandler, dialog, { part : partName } ); + } + + evt.data.preventDefault(); + } + + function mouseMoveHandler( evt ) + { + var x = evt.data.$.screenX, + y = evt.data.$.screenY, + dx = x - lastCoords.x, + dy = y - lastCoords.y, + viewPaneSize = CKEDITOR.document.getWindow().getViewPaneSize(), + partName = evt.listenerData.part; + + if ( partName.search( 't' ) != -1 ) + topSizer( abstractDialogCoords, dy ); + if ( partName.search( 'l' ) != -1 ) + leftSizer( abstractDialogCoords, dx ); + if ( partName.search( 'b' ) != -1 ) + bottomSizer( abstractDialogCoords, dy ); + if ( partName.search( 'r' ) != -1 ) + rightSizer( abstractDialogCoords, dx ); + + lastCoords = { x : x, y : y }; + + var realX, realY, realX2, realY2; + + if ( abstractDialogCoords.x + margins[3] < magnetDistance ) + realX = - margins[3]; + else if ( partName.search( 'l' ) != -1 && abstractDialogCoords.x2 - abstractDialogCoords.x < minWidth + magnetDistance ) + realX = abstractDialogCoords.x2 - minWidth; + else + realX = abstractDialogCoords.x; + + if ( abstractDialogCoords.y + margins[0] < magnetDistance ) + realY = - margins[0]; + else if ( partName.search( 't' ) != -1 && abstractDialogCoords.y2 - abstractDialogCoords.y < minHeight + magnetDistance ) + realY = abstractDialogCoords.y2 - minHeight; + else + realY = abstractDialogCoords.y; + + if ( abstractDialogCoords.x2 - margins[1] > viewPaneSize.width - magnetDistance ) + realX2 = viewPaneSize.width + margins[1] ; + else if ( partName.search( 'r' ) != -1 && abstractDialogCoords.x2 - abstractDialogCoords.x < minWidth + magnetDistance ) + realX2 = abstractDialogCoords.x + minWidth; + else + realX2 = abstractDialogCoords.x2; + + if ( abstractDialogCoords.y2 - margins[2] > viewPaneSize.height - magnetDistance ) + realY2= viewPaneSize.height + margins[2] ; + else if ( partName.search( 'b' ) != -1 && abstractDialogCoords.y2 - abstractDialogCoords.y < minHeight + magnetDistance ) + realY2 = abstractDialogCoords.y + minHeight; + else + realY2 = abstractDialogCoords.y2 ; + + dialog.move( realX, realY ); + dialog.resize( realX2 - realX, realY2 - realY ); + + evt.data.preventDefault(); + } + + function mouseUpHandler( evt ) + { + CKEDITOR.document.removeListener( 'mouseup', mouseUpHandler ); + CKEDITOR.document.removeListener( 'mousemove', mouseMoveHandler ); + + if ( CKEDITOR.env.ie6Compat ) + { + var coverDoc = currentCover.getChild( 0 ).getFrameDocument(); + coverDoc.removeListener( 'mouseup', mouseUpHandler ); + coverDoc.removeListener( 'mousemove', mouseMoveHandler ); + } + } + +// TODO : Simplify the resize logic, having just a single resize grip <div>. +// var widthTest = /[lr]/, +// heightTest = /[tb]/; +// for ( var i = 0 ; i < parts.length ; i++ ) +// { +// var element = dialog.parts[ parts[i] + '_resize' ]; +// if ( resizable == CKEDITOR.DIALOG_RESIZE_NONE || +// resizable == CKEDITOR.DIALOG_RESIZE_HEIGHT && widthTest.test( parts[i] ) || +// resizable == CKEDITOR.DIALOG_RESIZE_WIDTH && heightTest.test( parts[i] ) ) +// { +// element.hide(); +// continue; +// } +// element.on( 'mousedown', mouseDownHandler, dialog, { part : parts[i] } ); +// } + } + + var resizeCover; + // Caching resuable covers and allowing only one cover + // on screen. + var covers = {}, + currentCover; + + function showCover( editor ) + { + var win = CKEDITOR.document.getWindow(); + var backgroundColorStyle = editor.config.dialog_backgroundCoverColor || 'white', + backgroundCoverOpacity = editor.config.dialog_backgroundCoverOpacity, + baseFloatZIndex = editor.config.baseFloatZIndex, + coverKey = CKEDITOR.tools.genKey( + backgroundColorStyle, + backgroundCoverOpacity, + baseFloatZIndex ), + coverElement = covers[ coverKey ]; + + if ( !coverElement ) + { + var html = [ + '<div style="position: ', ( CKEDITOR.env.ie6Compat ? 'absolute' : 'fixed' ), + '; z-index: ', baseFloatZIndex, + '; top: 0px; left: 0px; ', + ( !CKEDITOR.env.ie6Compat ? 'background-color: ' + backgroundColorStyle : '' ), + '" class="cke_dialog_background_cover">' + ]; + + if ( CKEDITOR.env.ie6Compat ) + { + // Support for custom document.domain in IE. + var isCustomDomain = CKEDITOR.env.isCustomDomain(), + iframeHtml = '<html><body style=\\\'background-color:' + backgroundColorStyle + ';\\\'></body></html>'; + + html.push( + '<iframe' + + ' hidefocus="true"' + + ' frameborder="0"' + + ' id="cke_dialog_background_iframe"' + + ' src="javascript:' ); + + html.push( 'void((function(){' + + 'document.open();' + + ( isCustomDomain ? 'document.domain=\'' + document.domain + '\';' : '' ) + + 'document.write( \'' + iframeHtml + '\' );' + + 'document.close();' + + '})())' ); + + html.push( + '"' + + ' style="' + + 'position:absolute;' + + 'left:0;' + + 'top:0;' + + 'width:100%;' + + 'height: 100%;' + + 'progid:DXImageTransform.Microsoft.Alpha(opacity=0)">' + + '</iframe>' ); + } + + html.push( '</div>' ); + + coverElement = CKEDITOR.dom.element.createFromHtml( html.join( '' ) ); + coverElement.setOpacity( backgroundCoverOpacity != undefined ? backgroundCoverOpacity : 0.5 ); + + coverElement.appendTo( CKEDITOR.document.getBody() ); + covers[ coverKey ] = coverElement; + } + else + coverElement. show(); + + currentCover = coverElement; + var resizeFunc = function() + { + var size = win.getViewPaneSize(); + coverElement.setStyles( + { + width : size.width + 'px', + height : size.height + 'px' + } ); + }; + + var scrollFunc = function() + { + var pos = win.getScrollPosition(), + cursor = CKEDITOR.dialog._.currentTop; + coverElement.setStyles( + { + left : pos.x + 'px', + top : pos.y + 'px' + }); + + do + { + var dialogPos = cursor.getPosition(); + cursor.move( dialogPos.x, dialogPos.y ); + } while ( ( cursor = cursor._.parentDialog ) ); + }; + + resizeCover = resizeFunc; + win.on( 'resize', resizeFunc ); + resizeFunc(); + if ( CKEDITOR.env.ie6Compat ) + { + // IE BUG: win.$.onscroll assignment doesn't work.. it must be window.onscroll. + // So we need to invent a really funny way to make it work. + var myScrollHandler = function() + { + scrollFunc(); + arguments.callee.prevScrollHandler.apply( this, arguments ); + }; + win.$.setTimeout( function() + { + myScrollHandler.prevScrollHandler = window.onscroll || function(){}; + window.onscroll = myScrollHandler; + }, 0 ); + scrollFunc(); + } + } + + function hideCover() + { + if ( !currentCover ) + return; + + var win = CKEDITOR.document.getWindow(); + currentCover.hide(); + win.removeListener( 'resize', resizeCover ); + + if ( CKEDITOR.env.ie6Compat ) + { + win.$.setTimeout( function() + { + var prevScrollHandler = window.onscroll && window.onscroll.prevScrollHandler; + window.onscroll = prevScrollHandler || null; + }, 0 ); + } + resizeCover = null; + } + + function removeCovers() + { + for ( var coverId in covers ) + covers[ coverId ].remove(); + covers = {}; + } + + var accessKeyProcessors = {}; + + var accessKeyDownHandler = function( evt ) + { + var ctrl = evt.data.$.ctrlKey || evt.data.$.metaKey, + alt = evt.data.$.altKey, + shift = evt.data.$.shiftKey, + key = String.fromCharCode( evt.data.$.keyCode ), + keyProcessor = accessKeyProcessors[( ctrl ? 'CTRL+' : '' ) + ( alt ? 'ALT+' : '') + ( shift ? 'SHIFT+' : '' ) + key]; + + if ( !keyProcessor || !keyProcessor.length ) + return; + + keyProcessor = keyProcessor[keyProcessor.length - 1]; + keyProcessor.keydown && keyProcessor.keydown.call( keyProcessor.uiElement, keyProcessor.dialog, keyProcessor.key ); + evt.data.preventDefault(); + }; + + var accessKeyUpHandler = function( evt ) + { + var ctrl = evt.data.$.ctrlKey || evt.data.$.metaKey, + alt = evt.data.$.altKey, + shift = evt.data.$.shiftKey, + key = String.fromCharCode( evt.data.$.keyCode ), + keyProcessor = accessKeyProcessors[( ctrl ? 'CTRL+' : '' ) + ( alt ? 'ALT+' : '') + ( shift ? 'SHIFT+' : '' ) + key]; + + if ( !keyProcessor || !keyProcessor.length ) + return; + + keyProcessor = keyProcessor[keyProcessor.length - 1]; + if ( keyProcessor.keyup ) + { + keyProcessor.keyup.call( keyProcessor.uiElement, keyProcessor.dialog, keyProcessor.key ); + evt.data.preventDefault(); + } + }; + + var registerAccessKey = function( uiElement, dialog, key, downFunc, upFunc ) + { + var procList = accessKeyProcessors[key] || ( accessKeyProcessors[key] = [] ); + procList.push( { + uiElement : uiElement, + dialog : dialog, + key : key, + keyup : upFunc || uiElement.accessKeyUp, + keydown : downFunc || uiElement.accessKeyDown + } ); + }; + + var unregisterAccessKey = function( obj ) + { + for ( var i in accessKeyProcessors ) + { + var list = accessKeyProcessors[i]; + for ( var j = list.length - 1 ; j >= 0 ; j-- ) + { + if ( list[j].dialog == obj || list[j].uiElement == obj ) + list.splice( j, 1 ); + } + if ( list.length === 0 ) + delete accessKeyProcessors[i]; + } + }; + + var tabAccessKeyUp = function( dialog, key ) + { + if ( dialog._.accessKeyMap[key] ) + dialog.selectPage( dialog._.accessKeyMap[key] ); + }; + + var tabAccessKeyDown = function( dialog, key ) + { + }; + + // ESC, ENTER + var preventKeyBubblingKeys = { 27 :1, 13 :1 }; + var preventKeyBubbling = function( e ) + { + if ( e.data.getKeystroke() in preventKeyBubblingKeys ) + e.data.stopPropagation(); + }; + + (function() + { + CKEDITOR.ui.dialog = + { + /** + * The base class of all dialog UI elements. + * @constructor + * @param {CKEDITOR.dialog} dialog Parent dialog object. + * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition Element + * definition. Accepted fields: + * <ul> + * <li><strong>id</strong> (Required) The id of the UI element. See {@link + * CKEDITOR.dialog#getContentElement}</li> + * <li><strong>type</strong> (Required) The type of the UI element. The + * value to this field specifies which UI element class will be used to + * generate the final widget.</li> + * <li><strong>title</strong> (Optional) The popup tooltip for the UI + * element.</li> + * <li><strong>hidden</strong> (Optional) A flag that tells if the element + * should be initially visible.</li> + * <li><strong>className</strong> (Optional) Additional CSS class names + * to add to the UI element. Separated by space.</li> + * <li><strong>style</strong> (Optional) Additional CSS inline styles + * to add to the UI element. A semicolon (;) is required after the last + * style declaration.</li> + * <li><strong>accessKey</strong> (Optional) The alphanumeric access key + * for this element. Access keys are automatically prefixed by CTRL.</li> + * <li><strong>on*</strong> (Optional) Any UI element definition field that + * starts with <em>on</em> followed immediately by a capital letter and + * probably more letters is an event handler. Event handlers may be further + * divided into registered event handlers and DOM event handlers. Please + * refer to {@link CKEDITOR.ui.dialog.uiElement#registerEvents} and + * {@link CKEDITOR.ui.dialog.uiElement#eventProcessors} for more + * information.</li> + * </ul> + * @param {Array} htmlList + * List of HTML code to be added to the dialog's content area. + * @param {Function|String} nodeNameArg + * A function returning a string, or a simple string for the node name for + * the root DOM node. Default is 'div'. + * @param {Function|Object} stylesArg + * A function returning an object, or a simple object for CSS styles applied + * to the DOM node. Default is empty object. + * @param {Function|Object} attributesArg + * A fucntion returning an object, or a simple object for attributes applied + * to the DOM node. Default is empty object. + * @param {Function|String} contentsArg + * A function returning a string, or a simple string for the HTML code inside + * the root DOM node. Default is empty string. + * @example + */ + uiElement : function( dialog, elementDefinition, htmlList, nodeNameArg, stylesArg, attributesArg, contentsArg ) + { + if ( arguments.length < 4 ) + return; + + var nodeName = ( nodeNameArg.call ? nodeNameArg( elementDefinition ) : nodeNameArg ) || 'div', + html = [ '<', nodeName, ' ' ], + styles = ( stylesArg && stylesArg.call ? stylesArg( elementDefinition ) : stylesArg ) || {}, + attributes = ( attributesArg && attributesArg.call ? attributesArg( elementDefinition ) : attributesArg ) || {}, + innerHTML = ( contentsArg && contentsArg.call ? contentsArg.call( this, dialog, elementDefinition ) : contentsArg ) || '', + domId = this.domId = attributes.id || CKEDITOR.tools.getNextNumber() + '_uiElement', + id = this.id = elementDefinition.id, + i; + + // Set the id, a unique id is required for getElement() to work. + attributes.id = domId; + + // Set the type and definition CSS class names. + var classes = {}; + if ( elementDefinition.type ) + classes[ 'cke_dialog_ui_' + elementDefinition.type ] = 1; + if ( elementDefinition.className ) + classes[ elementDefinition.className ] = 1; + var attributeClasses = ( attributes['class'] && attributes['class'].split ) ? attributes['class'].split( ' ' ) : []; + for ( i = 0 ; i < attributeClasses.length ; i++ ) + { + if ( attributeClasses[i] ) + classes[ attributeClasses[i] ] = 1; + } + var finalClasses = []; + for ( i in classes ) + finalClasses.push( i ); + attributes['class'] = finalClasses.join( ' ' ); + + // Set the popup tooltop. + if ( elementDefinition.title ) + attributes.title = elementDefinition.title; + + // Write the inline CSS styles. + var styleStr = ( elementDefinition.style || '' ).split( ';' ); + for ( i in styles ) + styleStr.push( i + ':' + styles[i] ); + if ( elementDefinition.hidden ) + styleStr.push( 'display:none' ); + for ( i = styleStr.length - 1 ; i >= 0 ; i-- ) + { + if ( styleStr[i] === '' ) + styleStr.splice( i, 1 ); + } + if ( styleStr.length > 0 ) + attributes.style = ( attributes.style ? ( attributes.style + '; ' ) : '' ) + styleStr.join( '; ' ); + + // Write the attributes. + for ( i in attributes ) + html.push( i + '="' + CKEDITOR.tools.htmlEncode( attributes[i] ) + '" '); + + // Write the content HTML. + html.push( '>', innerHTML, '</', nodeName, '>' ); + + // Add contents to the parent HTML array. + htmlList.push( html.join( '' ) ); + + ( this._ || ( this._ = {} ) ).dialog = dialog; + + // Override isChanged if it is defined in element definition. + if ( typeof( elementDefinition.isChanged ) == 'boolean' ) + this.isChanged = function(){ return elementDefinition.isChanged; }; + if ( typeof( elementDefinition.isChanged ) == 'function' ) + this.isChanged = elementDefinition.isChanged; + + // Add events. + CKEDITOR.event.implementOn( this ); + + this.registerEvents( elementDefinition ); + if ( this.accessKeyUp && this.accessKeyDown && elementDefinition.accessKey ) + registerAccessKey( this, dialog, 'CTRL+' + elementDefinition.accessKey ); + + var me = this; + dialog.on( 'load', function() + { + if ( me.getInputElement() ) + { + me.getInputElement().on( 'focus', function() + { + dialog._.tabBarMode = false; + dialog._.hasFocus = true; + me.fire( 'focus' ); + }, me ); + } + } ); + + // Register the object as a tab focus if it can be included. + if ( this.keyboardFocusable ) + { + this.tabIndex = elementDefinition.tabIndex || 0; + + this.focusIndex = dialog._.focusList.push( this ) - 1; + this.on( 'focus', function() + { + dialog._.currentFocusIndex = me.focusIndex; + } ); + } + + // Completes this object with everything we have in the + // definition. + CKEDITOR.tools.extend( this, elementDefinition ); + }, + + /** + * Horizontal layout box for dialog UI elements, auto-expends to available width of container. + * @constructor + * @extends CKEDITOR.ui.dialog.uiElement + * @param {CKEDITOR.dialog} dialog + * Parent dialog object. + * @param {Array} childObjList + * Array of {@link CKEDITOR.ui.dialog.uiElement} objects inside this + * container. + * @param {Array} childHtmlList + * Array of HTML code that correspond to the HTML output of all the + * objects in childObjList. + * @param {Array} htmlList + * Array of HTML code that this element will output to. + * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition + * The element definition. Accepted fields: + * <ul> + * <li><strong>widths</strong> (Optional) The widths of child cells.</li> + * <li><strong>height</strong> (Optional) The height of the layout.</li> + * <li><strong>padding</strong> (Optional) The padding width inside child + * cells.</li> + * <li><strong>align</strong> (Optional) The alignment of the whole layout + * </li> + * </ul> + * @example + */ + hbox : function( dialog, childObjList, childHtmlList, htmlList, elementDefinition ) + { + if ( arguments.length < 4 ) + return; + + this._ || ( this._ = {} ); + + var children = this._.children = childObjList, + widths = elementDefinition && elementDefinition.widths || null, + height = elementDefinition && elementDefinition.height || null, + styles = {}, + i; + /** @ignore */ + var innerHTML = function() + { + var html = [ '<tbody><tr class="cke_dialog_ui_hbox">' ]; + for ( i = 0 ; i < childHtmlList.length ; i++ ) + { + var className = 'cke_dialog_ui_hbox_child', + styles = []; + if ( i === 0 ) + className = 'cke_dialog_ui_hbox_first'; + if ( i == childHtmlList.length - 1 ) + className = 'cke_dialog_ui_hbox_last'; + html.push( '<td class="', className, '" role="presentation" ' ); + if ( widths ) + { + if ( widths[i] ) + styles.push( 'width:' + CKEDITOR.tools.cssLength( widths[i] ) ); + } + else + styles.push( 'width:' + Math.floor( 100 / childHtmlList.length ) + '%' ); + if ( height ) + styles.push( 'height:' + CKEDITOR.tools.cssLength( height ) ); + if ( elementDefinition && elementDefinition.padding != undefined ) + styles.push( 'padding:' + CKEDITOR.tools.cssLength( elementDefinition.padding ) ); + if ( styles.length > 0 ) + html.push( 'style="' + styles.join('; ') + '" ' ); + html.push( '>', childHtmlList[i], '</td>' ); + } + html.push( '</tr></tbody>' ); + return html.join( '' ); + }; + + var attribs = { role : 'presentation' }; + elementDefinition && elementDefinition.align && ( attribs.align = elementDefinition.align ); + + CKEDITOR.ui.dialog.uiElement.call( + this, + dialog, + elementDefinition || { type : 'hbox' }, + htmlList, + 'table', + styles, + attribs, + innerHTML ); + }, + + /** + * Vertical layout box for dialog UI elements. + * @constructor + * @extends CKEDITOR.ui.dialog.hbox + * @param {CKEDITOR.dialog} dialog + * Parent dialog object. + * @param {Array} childObjList + * Array of {@link CKEDITOR.ui.dialog.uiElement} objects inside this + * container. + * @param {Array} childHtmlList + * Array of HTML code that correspond to the HTML output of all the + * objects in childObjList. + * @param {Array} htmlList + * Array of HTML code that this element will output to. + * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition + * The element definition. Accepted fields: + * <ul> + * <li><strong>width</strong> (Optional) The width of the layout.</li> + * <li><strong>heights</strong> (Optional) The heights of individual cells. + * </li> + * <li><strong>align</strong> (Optional) The alignment of the layout.</li> + * <li><strong>padding</strong> (Optional) The padding width inside child + * cells.</li> + * <li><strong>expand</strong> (Optional) Whether the layout should expand + * vertically to fill its container.</li> + * </ul> + * @example + */ + vbox : function( dialog, childObjList, childHtmlList, htmlList, elementDefinition ) + { + if (arguments.length < 3 ) + return; + + this._ || ( this._ = {} ); + + var children = this._.children = childObjList, + width = elementDefinition && elementDefinition.width || null, + heights = elementDefinition && elementDefinition.heights || null; + /** @ignore */ + var innerHTML = function() + { + var html = [ '<table role="presentation" cellspacing="0" border="0" ' ]; + html.push( 'style="' ); + if ( elementDefinition && elementDefinition.expand ) + html.push( 'height:100%;' ); + html.push( 'width:' + CKEDITOR.tools.cssLength( width || '100%' ), ';' ); + html.push( '"' ); + html.push( 'align="', CKEDITOR.tools.htmlEncode( + ( elementDefinition && elementDefinition.align ) || ( dialog.getParentEditor().lang.dir == 'ltr' ? 'left' : 'right' ) ), '" ' ); + + html.push( '><tbody>' ); + for ( var i = 0 ; i < childHtmlList.length ; i++ ) + { + var styles = []; + html.push( '<tr><td role="presentation" ' ); + if ( width ) + styles.push( 'width:' + CKEDITOR.tools.cssLength( width || '100%' ) ); + if ( heights ) + styles.push( 'height:' + CKEDITOR.tools.cssLength( heights[i] ) ); + else if ( elementDefinition && elementDefinition.expand ) + styles.push( 'height:' + Math.floor( 100 / childHtmlList.length ) + '%' ); + if ( elementDefinition && elementDefinition.padding != undefined ) + styles.push( 'padding:' + CKEDITOR.tools.cssLength( elementDefinition.padding ) ); + if ( styles.length > 0 ) + html.push( 'style="', styles.join( '; ' ), '" ' ); + html.push( ' class="cke_dialog_ui_vbox_child">', childHtmlList[i], '</td></tr>' ); + } + html.push( '</tbody></table>' ); + return html.join( '' ); + }; + CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition || { type : 'vbox' }, htmlList, 'div', null, { role : 'presentation' }, innerHTML ); + } + }; + })(); + + CKEDITOR.ui.dialog.uiElement.prototype = + { + /** + * Gets the root DOM element of this dialog UI object. + * @returns {CKEDITOR.dom.element} Root DOM element of UI object. + * @example + * uiElement.getElement().hide(); + */ + getElement : function() + { + return CKEDITOR.document.getById( this.domId ); + }, + + /** + * Gets the DOM element that the user inputs values. + * This function is used by setValue(), getValue() and focus(). It should + * be overrided in child classes where the input element isn't the root + * element. + * @returns {CKEDITOR.dom.element} The element where the user input values. + * @example + * var rawValue = textInput.getInputElement().$.value; + */ + getInputElement : function() + { + return this.getElement(); + }, + + /** + * Gets the parent dialog object containing this UI element. + * @returns {CKEDITOR.dialog} Parent dialog object. + * @example + * var dialog = uiElement.getDialog(); + */ + getDialog : function() + { + return this._.dialog; + }, + + /** + * Sets the value of this dialog UI object. + * @param {Object} value The new value. + * @returns {CKEDITOR.dialog.uiElement} The current UI element. + * @example + * uiElement.setValue( 'Dingo' ); + */ + setValue : function( value ) + { + this.getInputElement().setValue( value ); + this.fire( 'change', { value : value } ); + return this; + }, + + /** + * Gets the current value of this dialog UI object. + * @returns {Object} The current value. + * @example + * var myValue = uiElement.getValue(); + */ + getValue : function() + { + return this.getInputElement().getValue(); + }, + + /** + * Tells whether the UI object's value has changed. + * @returns {Boolean} true if changed, false if not changed. + * @example + * if ( uiElement.isChanged() ) + * confirm( 'Value changed! Continue?' ); + */ + isChanged : function() + { + // Override in input classes. + return false; + }, + + /** + * Selects the parent tab of this element. Usually called by focus() or overridden focus() methods. + * @returns {CKEDITOR.dialog.uiElement} The current UI element. + * @example + * focus : function() + * { + * this.selectParentTab(); + * // do something else. + * } + */ + selectParentTab : function() + { + var element = this.getInputElement(), + cursor = element, + tabId; + while ( ( cursor = cursor.getParent() ) && cursor.$.className.search( 'cke_dialog_page_contents' ) == -1 ) + { /*jsl:pass*/ } + + // Some widgets don't have parent tabs (e.g. OK and Cancel buttons). + if ( !cursor ) + return this; + + tabId = cursor.getAttribute( 'name' ); + // Avoid duplicate select. + if ( this._.dialog._.currentTabId != tabId ) + this._.dialog.selectPage( tabId ); + return this; + }, + + /** + * Puts the focus to the UI object. Switches tabs if the UI object isn't in the active tab page. + * @returns {CKEDITOR.dialog.uiElement} The current UI element. + * @example + * uiElement.focus(); + */ + focus : function() + { + this.selectParentTab().getInputElement().focus(); + return this; + }, + + /** + * Registers the on* event handlers defined in the element definition. + * The default behavior of this function is: + * <ol> + * <li> + * If the on* event is defined in the class's eventProcesors list, + * then the registration is delegated to the corresponding function + * in the eventProcessors list. + * </li> + * <li> + * If the on* event is not defined in the eventProcessors list, then + * register the event handler under the corresponding DOM event of + * the UI element's input DOM element (as defined by the return value + * of {@link CKEDITOR.ui.dialog.uiElement#getInputElement}). + * </li> + * </ol> + * This function is only called at UI element instantiation, but can + * be overridded in child classes if they require more flexibility. + * @param {CKEDITOR.dialog.uiElementDefinition} definition The UI element + * definition. + * @returns {CKEDITOR.dialog.uiElement} The current UI element. + * @example + */ + registerEvents : function( definition ) + { + var regex = /^on([A-Z]\w+)/, + match; + + var registerDomEvent = function( uiElement, dialog, eventName, func ) + { + dialog.on( 'load', function() + { + uiElement.getInputElement().on( eventName, func, uiElement ); + }); + }; + + for ( var i in definition ) + { + if ( !( match = i.match( regex ) ) ) + continue; + if ( this.eventProcessors[i] ) + this.eventProcessors[i].call( this, this._.dialog, definition[i] ); + else + registerDomEvent( this, this._.dialog, match[1].toLowerCase(), definition[i] ); + } + + return this; + }, + + /** + * The event processor list used by + * {@link CKEDITOR.ui.dialog.uiElement#getInputElement} at UI element + * instantiation. The default list defines three on* events: + * <ol> + * <li>onLoad - Called when the element's parent dialog opens for the + * first time</li> + * <li>onShow - Called whenever the element's parent dialog opens.</li> + * <li>onHide - Called whenever the element's parent dialog closes.</li> + * </ol> + * @field + * @type Object + * @example + * // This connects the 'click' event in CKEDITOR.ui.dialog.button to onClick + * // handlers in the UI element's definitions. + * CKEDITOR.ui.dialog.button.eventProcessors = CKEDITOR.tools.extend( {}, + * CKEDITOR.ui.dialog.uiElement.prototype.eventProcessors, + * { onClick : function( dialog, func ) { this.on( 'click', func ); } }, + * true ); + */ + eventProcessors : + { + onLoad : function( dialog, func ) + { + dialog.on( 'load', func, this ); + }, + + onShow : function( dialog, func ) + { + dialog.on( 'show', func, this ); + }, + + onHide : function( dialog, func ) + { + dialog.on( 'hide', func, this ); + } + }, + + /** + * The default handler for a UI element's access key down event, which + * tries to put focus to the UI element.<br /> + * Can be overridded in child classes for more sophisticaed behavior. + * @param {CKEDITOR.dialog} dialog The parent dialog object. + * @param {String} key The key combination pressed. Since access keys + * are defined to always include the CTRL key, its value should always + * include a 'CTRL+' prefix. + * @example + */ + accessKeyDown : function( dialog, key ) + { + this.focus(); + }, + + /** + * The default handler for a UI element's access key up event, which + * does nothing.<br /> + * Can be overridded in child classes for more sophisticated behavior. + * @param {CKEDITOR.dialog} dialog The parent dialog object. + * @param {String} key The key combination pressed. Since access keys + * are defined to always include the CTRL key, its value should always + * include a 'CTRL+' prefix. + * @example + */ + accessKeyUp : function( dialog, key ) + { + }, + + /** + * Disables a UI element. + * @example + */ + disable : function() + { + var element = this.getInputElement(); + element.setAttribute( 'disabled', 'true' ); + element.addClass( 'cke_disabled' ); + }, + + /** + * Enables a UI element. + * @example + */ + enable : function() + { + var element = this.getInputElement(); + element.removeAttribute( 'disabled' ); + element.removeClass( 'cke_disabled' ); + }, + + /** + * Determines whether an UI element is enabled or not. + * @returns {Boolean} Whether the UI element is enabled. + * @example + */ + isEnabled : function() + { + return !this.getInputElement().getAttribute( 'disabled' ); + }, + + /** + * Determines whether an UI element is visible or not. + * @returns {Boolean} Whether the UI element is visible. + * @example + */ + isVisible : function() + { + return this.getInputElement().isVisible(); + }, + + /** + * Determines whether an UI element is focus-able or not. + * Focus-able is defined as being both visible and enabled. + * @returns {Boolean} Whether the UI element can be focused. + * @example + */ + isFocusable : function() + { + if ( !this.isEnabled() || !this.isVisible() ) + return false; + return true; + } + }; + + CKEDITOR.ui.dialog.hbox.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.uiElement, + /** + * @lends CKEDITOR.ui.dialog.hbox.prototype + */ + { + /** + * Gets a child UI element inside this container. + * @param {Array|Number} indices An array or a single number to indicate the child's + * position in the container's descendant tree. Omit to get all the children in an array. + * @returns {Array|CKEDITOR.ui.dialog.uiElement} Array of all UI elements in the container + * if no argument given, or the specified UI element if indices is given. + * @example + * var checkbox = hbox.getChild( [0,1] ); + * checkbox.setValue( true ); + */ + getChild : function( indices ) + { + // If no arguments, return a clone of the children array. + if ( arguments.length < 1 ) + return this._.children.concat(); + + // If indices isn't array, make it one. + if ( !indices.splice ) + indices = [ indices ]; + + // Retrieve the child element according to tree position. + if ( indices.length < 2 ) + return this._.children[ indices[0] ]; + else + return ( this._.children[ indices[0] ] && this._.children[ indices[0] ].getChild ) ? + this._.children[ indices[0] ].getChild( indices.slice( 1, indices.length ) ) : + null; + } + }, true ); + + CKEDITOR.ui.dialog.vbox.prototype = new CKEDITOR.ui.dialog.hbox(); + + + + (function() + { + var commonBuilder = { + build : function( dialog, elementDefinition, output ) + { + var children = elementDefinition.children, + child, + childHtmlList = [], + childObjList = []; + for ( var i = 0 ; ( i < children.length && ( child = children[i] ) ) ; i++ ) + { + var childHtml = []; + childHtmlList.push( childHtml ); + childObjList.push( CKEDITOR.dialog._.uiElementBuilders[ child.type ].build( dialog, child, childHtml ) ); + } + return new CKEDITOR.ui.dialog[elementDefinition.type]( dialog, childObjList, childHtmlList, output, elementDefinition ); + } + }; + + CKEDITOR.dialog.addUIElement( 'hbox', commonBuilder ); + CKEDITOR.dialog.addUIElement( 'vbox', commonBuilder ); + })(); + + /** + * Generic dialog command. It opens a specific dialog when executed. + * @constructor + * @augments CKEDITOR.commandDefinition + * @param {string} dialogName The name of the dialog to open when executing + * this command. + * @example + * // Register the "link" command, which opens the "link" dialog. + * editor.addCommand( 'link', <b>new CKEDITOR.dialogCommand( 'link' )</b> ); + */ + CKEDITOR.dialogCommand = function( dialogName ) + { + this.dialogName = dialogName; + }; + + CKEDITOR.dialogCommand.prototype = + { + /** @ignore */ + exec : function( editor ) + { + editor.openDialog( this.dialogName ); + }, + + // Dialog commands just open a dialog ui, thus require no undo logic, + // undo support should dedicate to specific dialog implementation. + canUndo: false, + + editorFocus : CKEDITOR.env.ie || CKEDITOR.env.webkit + }; + + (function() + { + var notEmptyRegex = /^([a]|[^a])+$/, + integerRegex = /^\d*$/, + numberRegex = /^\d*(?:\.\d+)?$/; + + CKEDITOR.VALIDATE_OR = 1; + CKEDITOR.VALIDATE_AND = 2; + + CKEDITOR.dialog.validate = + { + functions : function() + { + return function() + { + /** + * It's important for validate functions to be able to accept the value + * as argument in addition to this.getValue(), so that it is possible to + * combine validate functions together to make more sophisticated + * validators. + */ + var value = this && this.getValue ? this.getValue() : arguments[0]; + + var msg = undefined, + relation = CKEDITOR.VALIDATE_AND, + functions = [], i; + + for ( i = 0 ; i < arguments.length ; i++ ) + { + if ( typeof( arguments[i] ) == 'function' ) + functions.push( arguments[i] ); + else + break; + } + + if ( i < arguments.length && typeof( arguments[i] ) == 'string' ) + { + msg = arguments[i]; + i++; + } + + if ( i < arguments.length && typeof( arguments[i]) == 'number' ) + relation = arguments[i]; + + var passed = ( relation == CKEDITOR.VALIDATE_AND ? true : false ); + for ( i = 0 ; i < functions.length ; i++ ) + { + if ( relation == CKEDITOR.VALIDATE_AND ) + passed = passed && functions[i]( value ); + else + passed = passed || functions[i]( value ); + } + + if ( !passed ) + { + if ( msg !== undefined ) + alert( msg ); + if ( this && ( this.select || this.focus ) ) + ( this.select || this.focus )(); + return false; + } + + return true; + }; + }, + + regex : function( regex, msg ) + { + /* + * Can be greatly shortened by deriving from functions validator if code size + * turns out to be more important than performance. + */ + return function() + { + var value = this && this.getValue ? this.getValue() : arguments[0]; + if ( !regex.test( value ) ) + { + if ( msg !== undefined ) + alert( msg ); + if ( this && ( this.select || this.focus ) ) + { + if ( this.select ) + this.select(); + else + this.focus(); + } + return false; + } + return true; + }; + }, + + notEmpty : function( msg ) + { + return this.regex( notEmptyRegex, msg ); + }, + + integer : function( msg ) + { + return this.regex( integerRegex, msg ); + }, + + 'number' : function( msg ) + { + return this.regex( numberRegex, msg ); + }, + + equals : function( value, msg ) + { + return this.functions( function( val ){ return val == value; }, msg ); + }, + + notEqual : function( value, msg ) + { + return this.functions( function( val ){ return val != value; }, msg ); + } + }; + + CKEDITOR.on( 'instanceDestroyed', function( evt ) + { + // Remove dialog cover on last instance destroy. + if ( CKEDITOR.tools.isEmpty( CKEDITOR.instances ) ) + { + var currentTopDialog; + while ( ( currentTopDialog = CKEDITOR.dialog._.currentTop ) ) + currentTopDialog.hide(); + removeCovers(); + } + + var dialogs = evt.editor._.storedDialogs; + for ( var name in dialogs ) + dialogs[ name ].destroy(); + + }); + + })(); +})(); + +// Extend the CKEDITOR.editor class with dialog specific functions. +CKEDITOR.tools.extend( CKEDITOR.editor.prototype, + /** @lends 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, callback ) + { + var dialogDefinitions = CKEDITOR.dialog._.dialogDefinitions[ dialogName ], + dialogSkin = this.skin.dialog; + + // If the dialogDefinition is already loaded, open it immediately. + if ( typeof dialogDefinitions == 'function' && dialogSkin._isLoaded ) + { + var storedDialogs = this._.storedDialogs || + ( this._.storedDialogs = {} ); + + var dialog = storedDialogs[ dialogName ] || + ( storedDialogs[ dialogName ] = new CKEDITOR.dialog( this, dialogName ) ); + + callback && callback.call( dialog, dialog ); + dialog.show(); + + return dialog; + } + else if ( dialogDefinitions == 'failed' ) + throw new Error( '[CKEDITOR.dialog.openDialog] Dialog "' + dialogName + '" failed when loading definition.' ); + + // Not loaded? Load the .js file first. + var body = CKEDITOR.document.getBody(), + cursor = body.$.style.cursor, + me = this; + + body.setStyle( 'cursor', 'wait' ); + + function onDialogFileLoaded( success ) + { + var dialogDefinition = CKEDITOR.dialog._.dialogDefinitions[ dialogName ], + skin = me.skin.dialog; + + // Check if both skin part and definition is loaded. + if ( !skin._isLoaded || loadDefinition && typeof success == 'undefined' ) + return; + + // In case of plugin error, mark it as loading failed. + if ( typeof dialogDefinition != 'function' ) + CKEDITOR.dialog._.dialogDefinitions[ dialogName ] = 'failed'; + + me.openDialog( dialogName, callback ); + body.setStyle( 'cursor', cursor ); + } + + if ( typeof dialogDefinitions == 'string' ) + { + var loadDefinition = 1; + CKEDITOR.scriptLoader.load( CKEDITOR.getUrl( dialogDefinitions ), onDialogFileLoaded ); + } + + CKEDITOR.skins.load( this, 'dialog', onDialogFileLoaded ); + + return null; + } + }); + +CKEDITOR.plugins.add( 'dialog', + { + requires : [ 'dialogui' ] + }); + +// Dialog related configurations. + +/** + * The color of the dialog background cover. It should be a valid CSS color + * string. + * @name CKEDITOR.config.dialog_backgroundCoverColor + * @type String + * @default 'white' + * @example + * config.dialog_backgroundCoverColor = 'rgb(255, 254, 253)'; + */ + +/** + * The opacity of the dialog background cover. It should be a number within the + * range [0.0, 1.0]. + * @name CKEDITOR.config.dialog_backgroundCoverOpacity + * @type Number + * @default 0.5 + * @example + * config.dialog_backgroundCoverOpacity = 0.7; + */ + +/** + * If the dialog has more than one tab, put focus into the first tab as soon as dialog is opened. + * @name CKEDITOR.config.dialog_startupFocusTab + * @type Boolean + * @default false + * @example + * config.dialog_startupFocusTab = true; + */ + +/** + * The distance of magnetic borders used in moving and resizing dialogs, + * measured in pixels. + * @name CKEDITOR.config.dialog_magnetDistance + * @type Number + * @default 20 + * @example + * config.dialog_magnetDistance = 30; + */ + +/** + * Fired when a dialog definition is about to be used to create a dialog into + * an editor instance. This event makes it possible to customize the definition + * before creating it. + * <p>Note that this event is called only the first time a specific dialog is + * opened. Successive openings will use the cached dialog, and this event will + * not get fired.</p> + * @name CKEDITOR#dialogDefinition + * @event + * @param {CKEDITOR.dialog.dialogDefinition} data The dialog defination that + * is being loaded. + * @param {CKEDITOR.editor} editor The editor instance that will use the + * dialog. + */ diff --git a/_source/plugins/dialogui/plugin.js b/_source/plugins/dialogui/plugin.js index 1567c94..fd4df91 100644 --- a/_source/plugins/dialogui/plugin.js +++ b/_source/plugins/dialogui/plugin.js @@ -1,1408 +1,1415 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-/** @fileoverview The "dialogui" plugin. */
-
-CKEDITOR.plugins.add( 'dialogui' );
-
-(function()
-{
- var initPrivateObject = function( elementDefinition )
- {
- this._ || ( this._ = {} );
- this._['default'] = this._.initValue = elementDefinition['default'] || '';
- this._.required = elementDefinition[ 'required' ] || false;
- var args = [ this._ ];
- for ( var i = 1 ; i < arguments.length ; i++ )
- args.push( arguments[i] );
- args.push( true );
- CKEDITOR.tools.extend.apply( CKEDITOR.tools, args );
- return this._;
- },
- textBuilder =
- {
- build : function( dialog, elementDefinition, output )
- {
- return new CKEDITOR.ui.dialog.textInput( dialog, elementDefinition, output );
- }
- },
- commonBuilder =
- {
- build : function( dialog, elementDefinition, output )
- {
- return new CKEDITOR.ui.dialog[elementDefinition.type]( dialog, elementDefinition, output );
- }
- },
- containerBuilder =
- {
- build : function( dialog, elementDefinition, output )
- {
- var children = elementDefinition.children,
- child,
- childHtmlList = [],
- childObjList = [];
- for ( var i = 0 ; ( i < children.length && ( child = children[i] ) ) ; i++ )
- {
- var childHtml = [];
- childHtmlList.push( childHtml );
- childObjList.push( CKEDITOR.dialog._.uiElementBuilders[ child.type ].build( dialog, child, childHtml ) );
- }
- return new CKEDITOR.ui.dialog[ elementDefinition.type ]( dialog, childObjList, childHtmlList, output, elementDefinition );
- }
- },
- commonPrototype =
- {
- isChanged : function()
- {
- return this.getValue() != this.getInitValue();
- },
-
- reset : function()
- {
- this.setValue( this.getInitValue() );
- },
-
- setInitValue : function()
- {
- this._.initValue = this.getValue();
- },
-
- resetInitValue : function()
- {
- this._.initValue = this._['default'];
- },
-
- getInitValue : function()
- {
- return this._.initValue;
- }
- },
- commonEventProcessors = CKEDITOR.tools.extend( {}, CKEDITOR.ui.dialog.uiElement.prototype.eventProcessors,
- {
- onChange : function( dialog, func )
- {
- if ( !this._.domOnChangeRegistered )
- {
- dialog.on( 'load', function()
- {
- this.getInputElement().on( 'change', function(){ this.fire( 'change', { value : this.getValue() } ); }, this );
- }, this );
- this._.domOnChangeRegistered = true;
- }
-
- this.on( 'change', func );
- }
- }, true ),
- eventRegex = /^on([A-Z]\w+)/,
- cleanInnerDefinition = function( def )
- {
- // An inner UI element should not have the parent's type, title or events.
- for ( var i in def )
- {
- if ( eventRegex.test( i ) || i == 'title' || i == 'type' )
- delete def[i];
- }
- return def;
- };
-
- CKEDITOR.tools.extend( CKEDITOR.ui.dialog,
- /** @lends CKEDITOR.ui.dialog */
- {
- /**
- * Base class for all dialog elements with a textual label on the left.
- * @constructor
- * @example
- * @extends CKEDITOR.ui.dialog.uiElement
- * @param {CKEDITOR.dialog} dialog
- * Parent dialog object.
- * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition
- * The element definition. Accepted fields:
- * <ul>
- * <li><strong>label</strong> (Required) The label string.</li>
- * <li><strong>labelLayout</strong> (Optional) Put 'horizontal' here if the
- * label element is to be layed out horizontally. Otherwise a vertical
- * layout will be used.</li>
- * <li><strong>widths</strong> (Optional) This applies only for horizontal
- * layouts - an 2-element array of lengths to specify the widths of the
- * label and the content element.</li>
- * </ul>
- * @param {Array} htmlList
- * List of HTML code to output to.
- * @param {Function} contentHtml
- * A function returning the HTML code string to be added inside the content
- * cell.
- */
- labeledElement : function( dialog, elementDefinition, htmlList, contentHtml )
- {
- if ( arguments.length < 4 )
- return;
-
- var _ = initPrivateObject.call( this, elementDefinition );
- _.labelId = CKEDITOR.tools.getNextNumber() + '_label';
- var children = this._.children = [];
- /** @ignore */
- var innerHTML = function()
- {
- var html = [];
- if ( elementDefinition.labelLayout != 'horizontal' )
- html.push( '<label class="cke_dialog_ui_labeled_label" ',
- ' id="'+ _.labelId + '"',
- ' for="' + _.inputId + '"',
- ' style="' + elementDefinition.labelStyle + '">',
- elementDefinition.label,
- '</label>',
- '<div class="cke_dialog_ui_labeled_content" role="presentation">',
- contentHtml.call( this, dialog, elementDefinition ),
- '</div>' );
- else
- {
- var hboxDefinition = {
- type : 'hbox',
- widths : elementDefinition.widths,
- padding : 0,
- children :
- [
- {
- type : 'html',
- html : '<label class="cke_dialog_ui_labeled_label"' +
- ' id="' + _.labelId + '"' +
- ' for="' + _.inputId + '"' +
- ' style="' + elementDefinition.labelStyle + '">' +
- CKEDITOR.tools.htmlEncode( elementDefinition.label ) +
- '</span>'
- },
- {
- type : 'html',
- html : '<span class="cke_dialog_ui_labeled_content">' +
- contentHtml.call( this, dialog, elementDefinition ) +
- '</span>'
- }
- ]
- };
- CKEDITOR.dialog._.uiElementBuilders.hbox.build( dialog, hboxDefinition, html );
- }
- return html.join( '' );
- };
- CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition, htmlList, 'div', null, { role : 'presentation' }, innerHTML );
- },
-
- /**
- * A text input with a label. This UI element class represents both the
- * single-line text inputs and password inputs in dialog boxes.
- * @constructor
- * @example
- * @extends CKEDITOR.ui.dialog.labeledElement
- * @param {CKEDITOR.dialog} dialog
- * Parent dialog object.
- * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition
- * The element definition. Accepted fields:
- * <ul>
- * <li><strong>default</strong> (Optional) The default value.</li>
- * <li><strong>validate</strong> (Optional) The validation function. </li>
- * <li><strong>maxLength</strong> (Optional) The maximum length of text box
- * contents.</li>
- * <li><strong>size</strong> (Optional) The size of the text box. This is
- * usually overridden by the size defined by the skin, however.</li>
- * </ul>
- * @param {Array} htmlList
- * List of HTML code to output to.
- */
- textInput : function( dialog, elementDefinition, htmlList )
- {
- if ( arguments.length < 3 )
- return;
-
- initPrivateObject.call( this, elementDefinition );
- var domId = this._.inputId = CKEDITOR.tools.getNextNumber() + '_textInput',
- attributes = { 'class' : 'cke_dialog_ui_input_' + elementDefinition.type, id : domId, type : 'text' },
- i;
-
- // Set the validator, if any.
- if ( elementDefinition.validate )
- this.validate = elementDefinition.validate;
-
- // Set the max length and size.
- if ( elementDefinition.maxLength )
- attributes.maxlength = elementDefinition.maxLength;
- if ( elementDefinition.size )
- attributes.size = elementDefinition.size;
-
- // If user presses Enter in a text box, it implies clicking OK for the dialog.
- var me = this, keyPressedOnMe = false;
- dialog.on( 'load', function()
- {
- me.getInputElement().on( 'keydown', function( evt )
- {
- if ( evt.data.getKeystroke() == 13 )
- keyPressedOnMe = true;
- } );
-
- // Lower the priority this 'keyup' since 'ok' will close the dialog.(#3749)
- me.getInputElement().on( 'keyup', function( evt )
- {
- if ( evt.data.getKeystroke() == 13 && keyPressedOnMe )
- {
- dialog.getButton( 'ok' ) && setTimeout( function ()
- {
- dialog.getButton( 'ok' ).click();
- }, 0 );
- keyPressedOnMe = false;
- }
- }, null, null, 1000 );
- } );
-
- /** @ignore */
- var innerHTML = function()
- {
- // IE BUG: Text input fields in IE at 100% would exceed a <td> or inline
- // container's width, so need to wrap it inside a <div>.
- var html = [ '<div class="cke_dialog_ui_input_', elementDefinition.type, '" role="presentation"' ];
-
- if ( elementDefinition.width )
- html.push( 'style="width:'+ elementDefinition.width +'" ' );
-
- html.push( '><input ' );
-
- attributes[ 'aria-labelledby' ] = this._.labelId;
- this._.required && ( attributes[ 'aria-required' ] = this._.required );
- for ( var i in attributes )
- html.push( i + '="' + attributes[i] + '" ' );
- html.push( ' /></div>' );
- return html.join( '' );
- };
- CKEDITOR.ui.dialog.labeledElement.call( this, dialog, elementDefinition, htmlList, innerHTML );
- },
-
- /**
- * A text area with a label on the top or left.
- * @constructor
- * @extends CKEDITOR.ui.dialog.labeledElement
- * @example
- * @param {CKEDITOR.dialog} dialog
- * Parent dialog object.
- * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition
- * The element definition. Accepted fields:
- * <ul>
- * <li><strong>rows</strong> (Optional) The number of rows displayed.
- * Defaults to 5 if not defined.</li>
- * <li><strong>cols</strong> (Optional) The number of cols displayed.
- * Defaults to 20 if not defined. Usually overridden by skins.</li>
- * <li><strong>default</strong> (Optional) The default value.</li>
- * <li><strong>validate</strong> (Optional) The validation function. </li>
- * </ul>
- * @param {Array} htmlList
- * List of HTML code to output to.
- */
- textarea : function( dialog, elementDefinition, htmlList )
- {
- if ( arguments.length < 3 )
- return;
-
- initPrivateObject.call( this, elementDefinition );
- var me = this,
- domId = this._.inputId = CKEDITOR.tools.getNextNumber() + '_textarea',
- attributes = {};
-
- if ( elementDefinition.validate )
- this.validate = elementDefinition.validate;
-
- // Generates the essential attributes for the textarea tag.
- attributes.rows = elementDefinition.rows || 5;
- attributes.cols = elementDefinition.cols || 20;
-
- /** @ignore */
- var innerHTML = function()
- {
- attributes[ 'aria-labelledby' ] = this._.labelId;
- this._.required && ( attributes[ 'aria-required' ] = this._.required );
- var html = [ '<div class="cke_dialog_ui_input_textarea" role="presentation"><textarea class="cke_dialog_ui_input_textarea" id="', domId, '" ' ];
- for ( var i in attributes )
- html.push( i + '="' + CKEDITOR.tools.htmlEncode( attributes[i] ) + '" ' );
- html.push( '>', CKEDITOR.tools.htmlEncode( me._['default'] ), '</textarea></div>' );
- return html.join( '' );
- };
- CKEDITOR.ui.dialog.labeledElement.call( this, dialog, elementDefinition, htmlList, innerHTML );
- },
-
- /**
- * A single checkbox with a label on the right.
- * @constructor
- * @extends CKEDITOR.ui.dialog.uiElement
- * @example
- * @param {CKEDITOR.dialog} dialog
- * Parent dialog object.
- * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition
- * The element definition. Accepted fields:
- * <ul>
- * <li><strong>checked</strong> (Optional) Whether the checkbox is checked
- * on instantiation. Defaults to false.</li>
- * <li><strong>validate</strong> (Optional) The validation function.</li>
- * <li><strong>label</strong> (Optional) The checkbox label.</li>
- * </ul>
- * @param {Array} htmlList
- * List of HTML code to output to.
- */
- checkbox : function( dialog, elementDefinition, htmlList )
- {
- if ( arguments.length < 3 )
- return;
-
- var _ = initPrivateObject.call( this, elementDefinition, { 'default' : !!elementDefinition[ 'default' ] } );
-
- if ( elementDefinition.validate )
- this.validate = elementDefinition.validate;
-
- /** @ignore */
- var innerHTML = function()
- {
- var myDefinition = CKEDITOR.tools.extend( {}, elementDefinition,
- {
- id : elementDefinition.id ? elementDefinition.id + '_checkbox' : CKEDITOR.tools.getNextNumber() + '_checkbox'
- }, true ),
- html = [];
-
- var labelId = CKEDITOR.tools.getNextNumber() + '_label';
- var attributes = { 'class' : 'cke_dialog_ui_checkbox_input', type : 'checkbox', 'aria-labelledby' : labelId };
- cleanInnerDefinition( myDefinition );
- if ( elementDefinition[ 'default' ] )
- attributes.checked = 'checked';
- _.checkbox = new CKEDITOR.ui.dialog.uiElement( dialog, myDefinition, html, 'input', null, attributes );
- html.push( ' <label id="', labelId, '" for="', attributes.id, '">',
- CKEDITOR.tools.htmlEncode( elementDefinition.label ),
- '</label>' );
- return html.join( '' );
- };
-
- CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition, htmlList, 'span', null, null, innerHTML );
- },
-
- /**
- * A group of radio buttons.
- * @constructor
- * @example
- * @extends CKEDITOR.ui.dialog.labeledElement
- * @param {CKEDITOR.dialog} dialog
- * Parent dialog object.
- * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition
- * The element definition. Accepted fields:
- * <ul>
- * <li><strong>default</strong> (Required) The default value.</li>
- * <li><strong>validate</strong> (Optional) The validation function.</li>
- * <li><strong>items</strong> (Required) An array of options. Each option
- * is a 1- or 2-item array of format [ 'Description', 'Value' ]. If 'Value'
- * is missing, then the value would be assumed to be the same as the
- * description.</li>
- * </ul>
- * @param {Array} htmlList
- * List of HTML code to output to.
- */
- radio : function( dialog, elementDefinition, htmlList )
- {
- if ( arguments.length < 3)
- return;
-
- initPrivateObject.call( this, elementDefinition );
- if ( !this._['default'] )
- this._['default'] = this._.initValue = elementDefinition.items[0][1];
- if ( elementDefinition.validate )
- this.validate = elementDefinition.valdiate;
- var children = [], me = this;
-
- /** @ignore */
- var innerHTML = function()
- {
- var inputHtmlList = [], html = [],
- commonAttributes = { 'class' : 'cke_dialog_ui_radio_item', 'aria-labelledby' : this._.labelId },
- commonName = elementDefinition.id ? elementDefinition.id + '_radio' : CKEDITOR.tools.getNextNumber() + '_radio';
- for ( var i = 0 ; i < elementDefinition.items.length ; i++ )
- {
- var item = elementDefinition.items[i],
- title = item[2] !== undefined ? item[2] : item[0],
- value = item[1] !== undefined ? item[1] : item[0],
- inputId = CKEDITOR.tools.getNextNumber() + '_radio_input',
- labelId = inputId + '_label',
- inputDefinition = CKEDITOR.tools.extend( {}, elementDefinition,
- {
- id : inputId,
- title : null,
- type : null
- }, true ),
- labelDefinition = CKEDITOR.tools.extend( {}, inputDefinition,
- {
- title : title
- }, true ),
- inputAttributes =
- {
- type : 'radio',
- 'class' : 'cke_dialog_ui_radio_input',
- name : commonName,
- value : value,
- 'aria-labelledby' : labelId
- },
- inputHtml = [];
- if ( me._['default'] == value )
- inputAttributes.checked = 'checked';
- cleanInnerDefinition( inputDefinition );
- cleanInnerDefinition( labelDefinition );
- children.push( new CKEDITOR.ui.dialog.uiElement( dialog, inputDefinition, inputHtml, 'input', null, inputAttributes ) );
- inputHtml.push( ' ' );
- new CKEDITOR.ui.dialog.uiElement( dialog, labelDefinition, inputHtml, 'label', null, { id : labelId, 'for' : inputAttributes.id },
- item[0] );
- inputHtmlList.push( inputHtml.join( '' ) );
- }
- new CKEDITOR.ui.dialog.hbox( dialog, [], inputHtmlList, html );
- return html.join( '' );
- };
-
- CKEDITOR.ui.dialog.labeledElement.call( this, dialog, elementDefinition, htmlList, innerHTML );
- this._.children = children;
- },
-
- /**
- * A button with a label inside.
- * @constructor
- * @example
- * @extends CKEDITOR.ui.dialog.uiElement
- * @param {CKEDITOR.dialog} dialog
- * Parent dialog object.
- * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition
- * The element definition. Accepted fields:
- * <ul>
- * <li><strong>label</strong> (Required) The button label.</li>
- * <li><strong>disabled</strong> (Optional) Set to true if you want the
- * button to appear in disabled state.</li>
- * </ul>
- * @param {Array} htmlList
- * List of HTML code to output to.
- */
- button : function( dialog, elementDefinition, htmlList )
- {
- if ( !arguments.length )
- return;
-
- if ( typeof elementDefinition == 'function' )
- elementDefinition = elementDefinition( dialog.getParentEditor() );
-
- initPrivateObject.call( this, elementDefinition, { disabled : elementDefinition.disabled || false } );
-
- // Add OnClick event to this input.
- CKEDITOR.event.implementOn( this );
-
- var me = this;
-
- // Register an event handler for processing button clicks.
- dialog.on( 'load', function( eventInfo )
- {
- var element = this.getElement();
-
- (function()
- {
- element.on( 'click', function( evt )
- {
- me.fire( 'click', { dialog : me.getDialog() } );
- evt.data.preventDefault();
- } );
-
- element.on( 'keydown', function( evt )
- {
- if ( evt.data.getKeystroke() in { 32:1, 13:1 } )
- {
- me.click();
- evt.data.preventDefault();
- }
- } );
- })();
-
- element.unselectable();
- }, this );
-
- var outerDefinition = CKEDITOR.tools.extend( {}, elementDefinition );
- delete outerDefinition.style;
-
- var labelId = CKEDITOR.tools.getNextNumber() + '_label';
- CKEDITOR.ui.dialog.uiElement.call(
- this,
- dialog,
- outerDefinition,
- htmlList,
- 'a',
- null,
- {
- style : elementDefinition.style,
- href : 'javascript:void(0)',
- title : elementDefinition.label,
- hidefocus : 'true',
- 'class' : elementDefinition['class'],
- role : 'button',
- 'aria-labelledby' : labelId
- },
- '<span id="' + labelId + '" class="cke_dialog_ui_button">' +
- CKEDITOR.tools.htmlEncode( elementDefinition.label ) +
- '</span>' );
- },
-
- /**
- * A select box.
- * @extends CKEDITOR.ui.dialog.uiElement
- * @example
- * @constructor
- * @param {CKEDITOR.dialog} dialog
- * Parent dialog object.
- * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition
- * The element definition. Accepted fields:
- * <ul>
- * <li><strong>default</strong> (Required) The default value.</li>
- * <li><strong>validate</strong> (Optional) The validation function.</li>
- * <li><strong>items</strong> (Required) An array of options. Each option
- * is a 1- or 2-item array of format [ 'Description', 'Value' ]. If 'Value'
- * is missing, then the value would be assumed to be the same as the
- * description.</li>
- * <li><strong>multiple</strong> (Optional) Set this to true if you'd like
- * to have a multiple-choice select box.</li>
- * <li><strong>size</strong> (Optional) The number of items to display in
- * the select box.</li>
- * </ul>
- * @param {Array} htmlList
- * List of HTML code to output to.
- */
- select : function( dialog, elementDefinition, htmlList )
- {
- if ( arguments.length < 3 )
- return;
-
- var _ = initPrivateObject.call( this, elementDefinition );
-
- if ( elementDefinition.validate )
- this.validate = elementDefinition.validate;
-
- _.inputId = CKEDITOR.tools.getNextNumber() + '_select';
- /** @ignore */
- var innerHTML = function()
- {
- var myDefinition = CKEDITOR.tools.extend( {}, elementDefinition,
- {
- id : elementDefinition.id ? elementDefinition.id + '_select' : CKEDITOR.tools.getNextNumber() + '_select'
- }, true ),
- html = [],
- innerHTML = [],
- attributes = { 'id' : _.inputId, 'class' : 'cke_dialog_ui_input_select', 'aria-labelledby' : this._.labelId };
-
- // Add multiple and size attributes from element definition.
- if ( elementDefinition.size != undefined )
- attributes.size = elementDefinition.size;
- if ( elementDefinition.multiple != undefined )
- attributes.multiple = elementDefinition.multiple;
-
- cleanInnerDefinition( myDefinition );
- for ( var i = 0, item ; i < elementDefinition.items.length && ( item = elementDefinition.items[i] ) ; i++ )
- {
- innerHTML.push( '<option value="',
- CKEDITOR.tools.htmlEncode( item[1] !== undefined ? item[1] : item[0] ), '" /> ',
- CKEDITOR.tools.htmlEncode( item[0] ) );
- }
-
- _.select = new CKEDITOR.ui.dialog.uiElement( dialog, myDefinition, html, 'select', null, attributes, innerHTML.join( '' ) );
- return html.join( '' );
- };
-
- CKEDITOR.ui.dialog.labeledElement.call( this, dialog, elementDefinition, htmlList, innerHTML );
- },
-
- /**
- * A file upload input.
- * @extends CKEDITOR.ui.dialog.labeledElement
- * @example
- * @constructor
- * @param {CKEDITOR.dialog} dialog
- * Parent dialog object.
- * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition
- * The element definition. Accepted fields:
- * <ul>
- * <li><strong>validate</strong> (Optional) The validation function.</li>
- * </ul>
- * @param {Array} htmlList
- * List of HTML code to output to.
- */
- file : function( dialog, elementDefinition, htmlList )
- {
- if ( arguments.length < 3 )
- return;
-
- if ( elementDefinition['default'] === undefined )
- elementDefinition['default'] = '';
-
- var _ = CKEDITOR.tools.extend( initPrivateObject.call( this, elementDefinition ), { definition : elementDefinition, buttons : [] } );
-
- if ( elementDefinition.validate )
- this.validate = elementDefinition.validate;
-
- /** @ignore */
- var innerHTML = function()
- {
- _.frameId = CKEDITOR.tools.getNextNumber() + '_fileInput';
-
- // Support for custom document.domain in IE.
- var isCustomDomain = CKEDITOR.env.isCustomDomain();
-
- var html = [
- '<iframe' +
- ' frameborder="0"' +
- ' allowtransparency="0"' +
- ' class="cke_dialog_ui_input_file"' +
- ' id="', _.frameId, '"' +
- ' title="', elementDefinition.label, '"' +
- ' src="javascript:void(' ];
-
- html.push(
- isCustomDomain ?
- '(function(){' +
- 'document.open();' +
- 'document.domain=\'' + document.domain + '\';' +
- 'document.close();' +
- '})()'
- :
- '0' );
-
- html.push(
- ')">' +
- '</iframe>' );
-
- return html.join( '' );
- };
-
- // IE BUG: Parent container does not resize to contain the iframe automatically.
- dialog.on( 'load', function()
- {
- var iframe = CKEDITOR.document.getById( _.frameId ),
- contentDiv = iframe.getParent();
- contentDiv.addClass( 'cke_dialog_ui_input_file' );
- } );
-
- CKEDITOR.ui.dialog.labeledElement.call( this, dialog, elementDefinition, htmlList, innerHTML );
- },
-
- /**
- * A button for submitting the file in a file upload input.
- * @extends CKEDITOR.ui.dialog.button
- * @example
- * @constructor
- * @param {CKEDITOR.dialog} dialog
- * Parent dialog object.
- * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition
- * The element definition. Accepted fields:
- * <ul>
- * <li><strong>for</strong> (Required) The file input's page and element Id
- * to associate to, in a 2-item array format: [ 'page_id', 'element_id' ].
- * </li>
- * <li><strong>validate</strong> (Optional) The validation function.</li>
- * </ul>
- * @param {Array} htmlList
- * List of HTML code to output to.
- */
- fileButton : function( dialog, elementDefinition, htmlList )
- {
- if ( arguments.length < 3 )
- return;
-
- var _ = initPrivateObject.call( this, elementDefinition ),
- me = this;
-
- if ( elementDefinition.validate )
- this.validate = elementDefinition.validate;
-
- var myDefinition = CKEDITOR.tools.extend( {}, elementDefinition );
- var onClick = myDefinition.onClick;
- myDefinition.className = ( myDefinition.className ? myDefinition.className + ' ' : '' ) + 'cke_dialog_ui_button';
- myDefinition.onClick = function( evt )
- {
- var target = elementDefinition[ 'for' ]; // [ pageId, elementId ]
- if ( !onClick || onClick.call( this, evt ) !== false )
- {
- dialog.getContentElement( target[0], target[1] ).submit();
- this.disable();
- }
- };
-
- dialog.on( 'load', function()
- {
- dialog.getContentElement( elementDefinition[ 'for' ][0], elementDefinition[ 'for' ][1] )._.buttons.push( me );
- } );
-
- CKEDITOR.ui.dialog.button.call( this, dialog, myDefinition, htmlList );
- },
-
- html : (function()
- {
- var myHtmlRe = /^\s*<[\w:]+\s+([^>]*)?>/,
- theirHtmlRe = /^(\s*<[\w:]+(?:\s+[^>]*)?)((?:.|\r|\n)+)$/,
- emptyTagRe = /\/$/;
- /**
- * A dialog element made from raw HTML code.
- * @extends CKEDITOR.ui.dialog.uiElement
- * @name CKEDITOR.ui.dialog.html
- * @param {CKEDITOR.dialog} dialog Parent dialog object.
- * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition Element definition.
- * Accepted fields:
- * <ul>
- * <li><strong>html</strong> (Required) HTML code of this element.</li>
- * </ul>
- * @param {Array} htmlList List of HTML code to be added to the dialog's content area.
- * @example
- * @constructor
- */
- return function( dialog, elementDefinition, htmlList )
- {
- if ( arguments.length < 3 )
- return;
-
- var myHtmlList = [],
- myHtml,
- theirHtml = elementDefinition.html,
- myMatch, theirMatch;
-
- // If the HTML input doesn't contain any tags at the beginning, add a <span> tag around it.
- if ( theirHtml.charAt( 0 ) != '<' )
- theirHtml = '<span>' + theirHtml + '</span>';
-
- // Look for focus function in definition.
- var focus = elementDefinition.focus;
- if ( focus )
- {
- var oldFocus = this.focus;
- this.focus = function()
- {
- oldFocus.call( this );
- typeof focus == 'function' && focus.call( this );
- this.fire( 'focus' );
- };
- if ( elementDefinition.isFocusable )
- {
- var oldIsFocusable = this.isFocusable;
- this.isFocusable = oldIsFocusable;
- }
- this.keyboardFocusable = true;
- }
-
- CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition, myHtmlList, 'span', null, null, '' );
-
- // Append the attributes created by the uiElement call to the real HTML.
- myHtml = myHtmlList.join( '' );
- myMatch = myHtml.match( myHtmlRe );
- theirMatch = theirHtml.match( theirHtmlRe ) || [ '', '', '' ];
-
- if ( emptyTagRe.test( theirMatch[1] ) )
- {
- theirMatch[1] = theirMatch[1].slice( 0, -1 );
- theirMatch[2] = '/' + theirMatch[2];
- }
-
- htmlList.push( [ theirMatch[1], ' ', myMatch[1] || '', theirMatch[2] ].join( '' ) );
- };
- })(),
-
- /**
- * Form fieldset for grouping dialog UI elements.
- * @constructor
- * @extends CKEDITOR.ui.dialog.uiElement
- * @param {CKEDITOR.dialog} dialog Parent dialog object.
- * @param {Array} childObjList
- * Array of {@link CKEDITOR.ui.dialog.uiElement} objects inside this
- * container.
- * @param {Array} childHtmlList
- * Array of HTML code that correspond to the HTML output of all the
- * objects in childObjList.
- * @param {Array} htmlList
- * Array of HTML code that this element will output to.
- * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition
- * The element definition. Accepted fields:
- * <ul>
- * <li><strong>label</strong> (Optional) The legend of the this fieldset.</li>
- * <li><strong>children</strong> (Required) An array of dialog field definitions which will be grouped inside this fieldset. </li>
- * </ul>
- */
- fieldset : function( dialog, childObjList, childHtmlList, htmlList, elementDefinition )
- {
- var legendLabel = elementDefinition.label;
- /** @ignore */
- var innerHTML = function()
- {
- var html = [];
- legendLabel && html.push( '<legend>' + legendLabel + '</legend>' );
- for ( var i = 0; i < childHtmlList.length; i++ )
- html.push( childHtmlList[ i ] );
- return html.join( '' );
- };
-
- this._ = { children : childObjList };
- CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition, htmlList, 'fieldset', null, null, innerHTML );
- }
-
- }, true );
-
- CKEDITOR.ui.dialog.html.prototype = new CKEDITOR.ui.dialog.uiElement;
-
- CKEDITOR.ui.dialog.labeledElement.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.uiElement,
- /** @lends CKEDITOR.ui.dialog.labeledElement.prototype */
- {
- /**
- * Sets the label text of the element.
- * @param {String} label The new label text.
- * @returns {CKEDITOR.ui.dialog.labeledElement} The current labeled element.
- * @example
- */
- setLabel : function( label )
- {
- var node = CKEDITOR.document.getById( this._.labelId );
- if ( node.getChildCount() < 1 )
- ( new CKEDITOR.dom.text( label, CKEDITOR.document ) ).appendTo( node );
- else
- node.getChild( 0 ).$.nodeValue = label;
- return this;
- },
-
- /**
- * Retrieves the current label text of the elment.
- * @returns {String} The current label text.
- * @example
- */
- getLabel : function()
- {
- var node = CKEDITOR.document.getById( this._.labelId );
- if ( !node || node.getChildCount() < 1 )
- return '';
- else
- return node.getChild( 0 ).getText();
- },
-
- /**
- * Defines the onChange event for UI element definitions.
- * @field
- * @type Object
- * @example
- */
- eventProcessors : commonEventProcessors
- }, true );
-
- CKEDITOR.ui.dialog.button.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.uiElement,
- /** @lends CKEDITOR.ui.dialog.button.prototype */
- {
- /**
- * Simulates a click to the button.
- * @example
- * @returns {Object} Return value of the 'click' event.
- */
- click : function()
- {
- if ( !this._.disabled )
- return this.fire( 'click', { dialog : this._.dialog } );
- this.getElement().$.blur();
- return false;
- },
-
- /**
- * Enables the button.
- * @example
- */
- enable : function()
- {
- this._.disabled = false;
- var element = this.getElement();
- element && element.removeClass( 'disabled' );
- },
-
- /**
- * Disables the button.
- * @example
- */
- disable : function()
- {
- this._.disabled = true;
- this.getElement().addClass( 'disabled' );
- },
-
- isVisible : function()
- {
- return this.getElement().getFirst().isVisible();
- },
-
- isEnabled : function()
- {
- return !this._.disabled;
- },
-
- /**
- * Defines the onChange event and onClick for button element definitions.
- * @field
- * @type Object
- * @example
- */
- eventProcessors : CKEDITOR.tools.extend( {}, CKEDITOR.ui.dialog.uiElement.prototype.eventProcessors,
- {
- /** @ignore */
- onClick : function( dialog, func )
- {
- this.on( 'click', func );
- }
- }, true ),
-
- /**
- * Handler for the element's access key up event. Simulates a click to
- * the button.
- * @example
- */
- accessKeyUp : function()
- {
- this.click();
- },
-
- /**
- * Handler for the element's access key down event. Simulates a mouse
- * down to the button.
- * @example
- */
- accessKeyDown : function()
- {
- this.focus();
- },
-
- keyboardFocusable : true
- }, true );
-
- CKEDITOR.ui.dialog.textInput.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.labeledElement,
- /** @lends CKEDITOR.ui.dialog.textInput.prototype */
- {
- /**
- * Gets the text input DOM element under this UI object.
- * @example
- * @returns {CKEDITOR.dom.element} The DOM element of the text input.
- */
- getInputElement : function()
- {
- return CKEDITOR.document.getById( this._.inputId );
- },
-
- /**
- * Puts focus into the text input.
- * @example
- */
- focus : function()
- {
- var me = this.selectParentTab();
-
- // GECKO BUG: setTimeout() is needed to workaround invisible selections.
- setTimeout( function()
- {
- var element = me.getInputElement();
- element && element.$.focus();
- }, 0 );
- },
-
- /**
- * Selects all the text in the text input.
- * @example
- */
- select : function()
- {
- var me = this.selectParentTab();
-
- // GECKO BUG: setTimeout() is needed to workaround invisible selections.
- setTimeout( function()
- {
- var e = me.getInputElement();
- if ( e )
- {
- e.$.focus();
- e.$.select();
- }
- }, 0 );
- },
-
- /**
- * Handler for the text input's access key up event. Makes a select()
- * call to the text input.
- * @example
- */
- accessKeyUp : function()
- {
- this.select();
- },
-
- /**
- * Sets the value of this text input object.
- * @param {Object} value The new value.
- * @returns {CKEDITOR.ui.dialog.textInput} The current UI element.
- * @example
- * uiElement.setValue( 'Blamo' );
- */
- setValue : function( value )
- {
- !value && ( value = '' );
- return CKEDITOR.ui.dialog.uiElement.prototype.setValue.call( this, value );
- },
-
- keyboardFocusable : true
- }, commonPrototype, true );
-
- CKEDITOR.ui.dialog.textarea.prototype = new CKEDITOR.ui.dialog.textInput();
-
- CKEDITOR.ui.dialog.select.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.labeledElement,
- /** @lends CKEDITOR.ui.dialog.select.prototype */
- {
- /**
- * Gets the DOM element of the select box.
- * @returns {CKEDITOR.dom.element} The <select> element of this UI
- * element.
- * @example
- */
- getInputElement : function()
- {
- return this._.select.getElement();
- },
-
- /**
- * Adds an option to the select box.
- * @param {String} label Option label.
- * @param {String} value (Optional) Option value, if not defined it'll be
- * assumed to be the same as the label.
- * @param {Number} index (Optional) Position of the option to be inserted
- * to. If not defined the new option will be inserted to the end of list.
- * @example
- * @returns {CKEDITOR.ui.dialog.select} The current select UI element.
- */
- add : function( label, value, index )
- {
- var option = new CKEDITOR.dom.element( 'option', this.getDialog().getParentEditor().document ),
- selectElement = this.getInputElement().$;
- option.$.text = label;
- option.$.value = ( value === undefined || value === null ) ? label : value;
- if ( index === undefined || index === null )
- {
- if ( CKEDITOR.env.ie )
- selectElement.add( option.$ );
- else
- selectElement.add( option.$, null );
- }
- else
- selectElement.add( option.$, index );
- return this;
- },
-
- /**
- * Removes an option from the selection list.
- * @param {Number} index Index of the option to be removed.
- * @example
- * @returns {CKEDITOR.ui.dialog.select} The current select UI element.
- */
- remove : function( index )
- {
- var selectElement = this.getInputElement().$;
- selectElement.remove( index );
- return this;
- },
-
- /**
- * Clears all options out of the selection list.
- * @returns {CKEDITOR.ui.dialog.select} The current select UI element.
- */
- clear : function()
- {
- var selectElement = this.getInputElement().$;
- while ( selectElement.length > 0 )
- selectElement.remove( 0 );
- return this;
- },
-
- keyboardFocusable : true
- }, commonPrototype, true );
-
- CKEDITOR.ui.dialog.checkbox.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.uiElement,
- /** @lends CKEDITOR.ui.dialog.checkbox.prototype */
- {
- /**
- * Gets the checkbox DOM element.
- * @example
- * @returns {CKEDITOR.dom.element} The DOM element of the checkbox.
- */
- getInputElement : function()
- {
- return this._.checkbox.getElement();
- },
-
- /**
- * Sets the state of the checkbox.
- * @example
- * @param {Boolean} true to tick the checkbox, false to untick it.
- */
- setValue : function( checked )
- {
- this.getInputElement().$.checked = checked;
- this.fire( 'change', { value : checked } );
- },
-
- /**
- * Gets the state of the checkbox.
- * @example
- * @returns {Boolean} true means the checkbox is ticked, false means it's not ticked.
- */
- getValue : function()
- {
- return this.getInputElement().$.checked;
- },
-
- /**
- * Handler for the access key up event. Toggles the checkbox.
- * @example
- */
- accessKeyUp : function()
- {
- this.setValue( !this.getValue() );
- },
-
- /**
- * Defines the onChange event for UI element definitions.
- * @field
- * @type Object
- * @example
- */
- eventProcessors :
- {
- onChange : function( dialog, func )
- {
- if ( !CKEDITOR.env.ie )
- return commonEventProcessors.onChange.apply( this, arguments );
- else
- {
- dialog.on( 'load', function()
- {
- var element = this._.checkbox.getElement();
- element.on( 'propertychange', function( evt )
- {
- evt = evt.data.$;
- if ( evt.propertyName == 'checked' )
- this.fire( 'change', { value : element.$.checked } );
- }, this );
- }, this );
- this.on( 'change', func );
- }
- return null;
- }
- },
-
- keyboardFocusable : true
- }, commonPrototype, true );
-
- CKEDITOR.ui.dialog.radio.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.uiElement,
- /** @lends CKEDITOR.ui.dialog.radio.prototype */
- {
- /**
- * Checks one of the radio buttons in this button group.
- * @example
- * @param {String} value The value of the button to be chcked.
- */
- setValue : function( value )
- {
- var children = this._.children,
- item;
- for ( var i = 0 ; ( i < children.length ) && ( item = children[i] ) ; i++ )
- item.getElement().$.checked = ( item.getValue() == value );
- this.fire( 'change', { value : value } );
- },
-
- /**
- * Gets the value of the currently checked radio button.
- * @example
- * @returns {String} The currently checked button's value.
- */
- getValue : function()
- {
- var children = this._.children;
- for ( var i = 0 ; i < children.length ; i++ )
- {
- if ( children[i].getElement().$.checked )
- return children[i].getValue();
- }
- return null;
- },
-
- /**
- * Handler for the access key up event. Focuses the currently
- * selected radio button, or the first radio button if none is
- * selected.
- * @example
- */
- accessKeyUp : function()
- {
- var children = this._.children, i;
- for ( i = 0 ; i < children.length ; i++ )
- {
- if ( children[i].getElement().$.checked )
- {
- children[i].getElement().focus();
- return;
- }
- }
- children[0].getElement().focus();
- },
-
- /**
- * Defines the onChange event for UI element definitions.
- * @field
- * @type Object
- * @example
- */
- eventProcessors :
- {
- onChange : function( dialog, func )
- {
- if ( !CKEDITOR.env.ie )
- return commonEventProcessors.onChange.apply( this, arguments );
- else
- {
- dialog.on( 'load', function()
- {
- var children = this._.children, me = this;
- for ( var i = 0 ; i < children.length ; i++ )
- {
- var element = children[i].getElement();
- element.on( 'propertychange', function( evt )
- {
- evt = evt.data.$;
- if ( evt.propertyName == 'checked' && this.$.checked )
- me.fire( 'change', { value : this.getAttribute( 'value' ) } );
- } );
- }
- }, this );
- this.on( 'change', func );
- }
- return null;
- }
- },
-
- keyboardFocusable : true
- }, commonPrototype, true );
-
- CKEDITOR.ui.dialog.file.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.labeledElement,
- commonPrototype,
- /** @lends CKEDITOR.ui.dialog.file.prototype */
- {
- /**
- * Gets the <input> element of this file input.
- * @returns {CKEDITOR.dom.element} The file input element.
- * @example
- */
- getInputElement : function()
- {
- var frameDocument = CKEDITOR.document.getById( this._.frameId ).getFrameDocument();
- return frameDocument.$.forms.length > 0 ?
- new CKEDITOR.dom.element( frameDocument.$.forms[0].elements[0] ) :
- this.getElement();
- },
-
- /**
- * Uploads the file in the file input.
- * @returns {CKEDITOR.ui.dialog.file} This object.
- * @example
- */
- submit : function()
- {
- this.getInputElement().getParent().$.submit();
- return this;
- },
-
- /**
- * Get the action assigned to the form.
- * @returns {String} The value of the action.
- * @example
- */
- getAction : function( action )
- {
- return this.getInputElement().getParent().$.action;
- },
-
- /**
- * Redraws the file input and resets the file path in the file input.
- * The redraw logic is necessary because non-IE browsers tend to clear
- * the <iframe> containing the file input after closing the dialog.
- * @example
- */
- reset : function()
- {
- var frameElement = CKEDITOR.document.getById( this._.frameId ),
- frameDocument = frameElement.getFrameDocument(),
- elementDefinition = this._.definition,
- buttons = this._.buttons;
-
- function generateFormField()
- {
- frameDocument.$.open();
-
- // Support for custom document.domain in IE.
- if ( CKEDITOR.env.isCustomDomain() )
- frameDocument.$.domain = document.domain;
-
- var size = '';
- if ( elementDefinition.size )
- size = elementDefinition.size - ( CKEDITOR.env.ie ? 7 : 0 ); // "Browse" button is bigger in IE.
-
- frameDocument.$.write( [ '<html><head><title></title></head><body style="margin: 0; overflow: hidden; background: transparent;">',
- '<form enctype="multipart/form-data" method="POST" action="',
- CKEDITOR.tools.htmlEncode( elementDefinition.action ),
- '">',
- '<input type="file" name="',
- CKEDITOR.tools.htmlEncode( elementDefinition.id || 'cke_upload' ),
- '" size="',
- CKEDITOR.tools.htmlEncode( size > 0 ? size : "" ),
- '" />',
- '</form>',
- '</body></html>' ].join( '' ) );
-
- frameDocument.$.close();
-
- for ( var i = 0 ; i < buttons.length ; i++ )
- buttons[i].enable();
- }
-
- // #3465: Wait for the browser to finish rendering the dialog first.
- if ( CKEDITOR.env.gecko )
- setTimeout( generateFormField, 500 );
- else
- generateFormField();
- },
-
- getValue : function()
- {
- // The file path returned from the input tag is incomplete anyway, so it's
- // safe to ignore it and prevent the confirmation dialog from appearing.
- // (Part of #3465)
- return '';
- },
-
- /**
- * Defines the onChange event for UI element definitions.
- * @field
- * @type Object
- * @example
- */
- eventProcessors : commonEventProcessors,
-
- keyboardFocusable : true
- }, true );
-
- CKEDITOR.ui.dialog.fileButton.prototype = new CKEDITOR.ui.dialog.button;
-
- CKEDITOR.ui.dialog.fieldset.prototype = CKEDITOR.tools.clone( CKEDITOR.ui.dialog.hbox.prototype );
-
- CKEDITOR.dialog.addUIElement( 'text', textBuilder );
- CKEDITOR.dialog.addUIElement( 'password', textBuilder );
- CKEDITOR.dialog.addUIElement( 'textarea', commonBuilder );
- CKEDITOR.dialog.addUIElement( 'checkbox', commonBuilder );
- CKEDITOR.dialog.addUIElement( 'radio', commonBuilder );
- CKEDITOR.dialog.addUIElement( 'button', commonBuilder );
- CKEDITOR.dialog.addUIElement( 'select', commonBuilder );
- CKEDITOR.dialog.addUIElement( 'file', commonBuilder );
- CKEDITOR.dialog.addUIElement( 'fileButton', commonBuilder );
- CKEDITOR.dialog.addUIElement( 'html', commonBuilder );
- CKEDITOR.dialog.addUIElement( 'fieldset', containerBuilder );
-})();
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** @fileoverview The "dialogui" plugin. */ + +CKEDITOR.plugins.add( 'dialogui' ); + +(function() +{ + var initPrivateObject = function( elementDefinition ) + { + this._ || ( this._ = {} ); + this._['default'] = this._.initValue = elementDefinition['default'] || ''; + this._.required = elementDefinition[ 'required' ] || false; + var args = [ this._ ]; + for ( var i = 1 ; i < arguments.length ; i++ ) + args.push( arguments[i] ); + args.push( true ); + CKEDITOR.tools.extend.apply( CKEDITOR.tools, args ); + return this._; + }, + textBuilder = + { + build : function( dialog, elementDefinition, output ) + { + return new CKEDITOR.ui.dialog.textInput( dialog, elementDefinition, output ); + } + }, + commonBuilder = + { + build : function( dialog, elementDefinition, output ) + { + return new CKEDITOR.ui.dialog[elementDefinition.type]( dialog, elementDefinition, output ); + } + }, + containerBuilder = + { + build : function( dialog, elementDefinition, output ) + { + var children = elementDefinition.children, + child, + childHtmlList = [], + childObjList = []; + for ( var i = 0 ; ( i < children.length && ( child = children[i] ) ) ; i++ ) + { + var childHtml = []; + childHtmlList.push( childHtml ); + childObjList.push( CKEDITOR.dialog._.uiElementBuilders[ child.type ].build( dialog, child, childHtml ) ); + } + return new CKEDITOR.ui.dialog[ elementDefinition.type ]( dialog, childObjList, childHtmlList, output, elementDefinition ); + } + }, + commonPrototype = + { + isChanged : function() + { + return this.getValue() != this.getInitValue(); + }, + + reset : function() + { + this.setValue( this.getInitValue() ); + }, + + setInitValue : function() + { + this._.initValue = this.getValue(); + }, + + resetInitValue : function() + { + this._.initValue = this._['default']; + }, + + getInitValue : function() + { + return this._.initValue; + } + }, + commonEventProcessors = CKEDITOR.tools.extend( {}, CKEDITOR.ui.dialog.uiElement.prototype.eventProcessors, + { + onChange : function( dialog, func ) + { + if ( !this._.domOnChangeRegistered ) + { + dialog.on( 'load', function() + { + this.getInputElement().on( 'change', function() + { + // Make sure 'onchange' doesn't get fired after dialog closed. (#5719) + if ( !dialog.parts.dialog.isVisible() ) + return; + + this.fire( 'change', { value : this.getValue() } ); + }, this ); + }, this ); + this._.domOnChangeRegistered = true; + } + + this.on( 'change', func ); + } + }, true ), + eventRegex = /^on([A-Z]\w+)/, + cleanInnerDefinition = function( def ) + { + // An inner UI element should not have the parent's type, title or events. + for ( var i in def ) + { + if ( eventRegex.test( i ) || i == 'title' || i == 'type' ) + delete def[i]; + } + return def; + }; + + CKEDITOR.tools.extend( CKEDITOR.ui.dialog, + /** @lends CKEDITOR.ui.dialog */ + { + /** + * Base class for all dialog elements with a textual label on the left. + * @constructor + * @example + * @extends CKEDITOR.ui.dialog.uiElement + * @param {CKEDITOR.dialog} dialog + * Parent dialog object. + * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition + * The element definition. Accepted fields: + * <ul> + * <li><strong>label</strong> (Required) The label string.</li> + * <li><strong>labelLayout</strong> (Optional) Put 'horizontal' here if the + * label element is to be layed out horizontally. Otherwise a vertical + * layout will be used.</li> + * <li><strong>widths</strong> (Optional) This applies only for horizontal + * layouts - an 2-element array of lengths to specify the widths of the + * label and the content element.</li> + * </ul> + * @param {Array} htmlList + * List of HTML code to output to. + * @param {Function} contentHtml + * A function returning the HTML code string to be added inside the content + * cell. + */ + labeledElement : function( dialog, elementDefinition, htmlList, contentHtml ) + { + if ( arguments.length < 4 ) + return; + + var _ = initPrivateObject.call( this, elementDefinition ); + _.labelId = CKEDITOR.tools.getNextNumber() + '_label'; + var children = this._.children = []; + /** @ignore */ + var innerHTML = function() + { + var html = []; + if ( elementDefinition.labelLayout != 'horizontal' ) + html.push( '<label class="cke_dialog_ui_labeled_label" ', + ' id="'+ _.labelId + '"', + ' for="' + _.inputId + '"', + ' style="' + elementDefinition.labelStyle + '">', + elementDefinition.label, + '</label>', + '<div class="cke_dialog_ui_labeled_content" role="presentation">', + contentHtml.call( this, dialog, elementDefinition ), + '</div>' ); + else + { + var hboxDefinition = { + type : 'hbox', + widths : elementDefinition.widths, + padding : 0, + children : + [ + { + type : 'html', + html : '<label class="cke_dialog_ui_labeled_label"' + + ' id="' + _.labelId + '"' + + ' for="' + _.inputId + '"' + + ' style="' + elementDefinition.labelStyle + '">' + + CKEDITOR.tools.htmlEncode( elementDefinition.label ) + + '</span>' + }, + { + type : 'html', + html : '<span class="cke_dialog_ui_labeled_content">' + + contentHtml.call( this, dialog, elementDefinition ) + + '</span>' + } + ] + }; + CKEDITOR.dialog._.uiElementBuilders.hbox.build( dialog, hboxDefinition, html ); + } + return html.join( '' ); + }; + CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition, htmlList, 'div', null, { role : 'presentation' }, innerHTML ); + }, + + /** + * A text input with a label. This UI element class represents both the + * single-line text inputs and password inputs in dialog boxes. + * @constructor + * @example + * @extends CKEDITOR.ui.dialog.labeledElement + * @param {CKEDITOR.dialog} dialog + * Parent dialog object. + * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition + * The element definition. Accepted fields: + * <ul> + * <li><strong>default</strong> (Optional) The default value.</li> + * <li><strong>validate</strong> (Optional) The validation function. </li> + * <li><strong>maxLength</strong> (Optional) The maximum length of text box + * contents.</li> + * <li><strong>size</strong> (Optional) The size of the text box. This is + * usually overridden by the size defined by the skin, however.</li> + * </ul> + * @param {Array} htmlList + * List of HTML code to output to. + */ + textInput : function( dialog, elementDefinition, htmlList ) + { + if ( arguments.length < 3 ) + return; + + initPrivateObject.call( this, elementDefinition ); + var domId = this._.inputId = CKEDITOR.tools.getNextNumber() + '_textInput', + attributes = { 'class' : 'cke_dialog_ui_input_' + elementDefinition.type, id : domId, type : 'text' }, + i; + + // Set the validator, if any. + if ( elementDefinition.validate ) + this.validate = elementDefinition.validate; + + // Set the max length and size. + if ( elementDefinition.maxLength ) + attributes.maxlength = elementDefinition.maxLength; + if ( elementDefinition.size ) + attributes.size = elementDefinition.size; + + // If user presses Enter in a text box, it implies clicking OK for the dialog. + var me = this, keyPressedOnMe = false; + dialog.on( 'load', function() + { + me.getInputElement().on( 'keydown', function( evt ) + { + if ( evt.data.getKeystroke() == 13 ) + keyPressedOnMe = true; + } ); + + // Lower the priority this 'keyup' since 'ok' will close the dialog.(#3749) + me.getInputElement().on( 'keyup', function( evt ) + { + if ( evt.data.getKeystroke() == 13 && keyPressedOnMe ) + { + dialog.getButton( 'ok' ) && setTimeout( function () + { + dialog.getButton( 'ok' ).click(); + }, 0 ); + keyPressedOnMe = false; + } + }, null, null, 1000 ); + } ); + + /** @ignore */ + var innerHTML = function() + { + // IE BUG: Text input fields in IE at 100% would exceed a <td> or inline + // container's width, so need to wrap it inside a <div>. + var html = [ '<div class="cke_dialog_ui_input_', elementDefinition.type, '" role="presentation"' ]; + + if ( elementDefinition.width ) + html.push( 'style="width:'+ elementDefinition.width +'" ' ); + + html.push( '><input ' ); + + attributes[ 'aria-labelledby' ] = this._.labelId; + this._.required && ( attributes[ 'aria-required' ] = this._.required ); + for ( var i in attributes ) + html.push( i + '="' + attributes[i] + '" ' ); + html.push( ' /></div>' ); + return html.join( '' ); + }; + CKEDITOR.ui.dialog.labeledElement.call( this, dialog, elementDefinition, htmlList, innerHTML ); + }, + + /** + * A text area with a label on the top or left. + * @constructor + * @extends CKEDITOR.ui.dialog.labeledElement + * @example + * @param {CKEDITOR.dialog} dialog + * Parent dialog object. + * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition + * The element definition. Accepted fields: + * <ul> + * <li><strong>rows</strong> (Optional) The number of rows displayed. + * Defaults to 5 if not defined.</li> + * <li><strong>cols</strong> (Optional) The number of cols displayed. + * Defaults to 20 if not defined. Usually overridden by skins.</li> + * <li><strong>default</strong> (Optional) The default value.</li> + * <li><strong>validate</strong> (Optional) The validation function. </li> + * </ul> + * @param {Array} htmlList + * List of HTML code to output to. + */ + textarea : function( dialog, elementDefinition, htmlList ) + { + if ( arguments.length < 3 ) + return; + + initPrivateObject.call( this, elementDefinition ); + var me = this, + domId = this._.inputId = CKEDITOR.tools.getNextNumber() + '_textarea', + attributes = {}; + + if ( elementDefinition.validate ) + this.validate = elementDefinition.validate; + + // Generates the essential attributes for the textarea tag. + attributes.rows = elementDefinition.rows || 5; + attributes.cols = elementDefinition.cols || 20; + + /** @ignore */ + var innerHTML = function() + { + attributes[ 'aria-labelledby' ] = this._.labelId; + this._.required && ( attributes[ 'aria-required' ] = this._.required ); + var html = [ '<div class="cke_dialog_ui_input_textarea" role="presentation"><textarea class="cke_dialog_ui_input_textarea" id="', domId, '" ' ]; + for ( var i in attributes ) + html.push( i + '="' + CKEDITOR.tools.htmlEncode( attributes[i] ) + '" ' ); + html.push( '>', CKEDITOR.tools.htmlEncode( me._['default'] ), '</textarea></div>' ); + return html.join( '' ); + }; + CKEDITOR.ui.dialog.labeledElement.call( this, dialog, elementDefinition, htmlList, innerHTML ); + }, + + /** + * A single checkbox with a label on the right. + * @constructor + * @extends CKEDITOR.ui.dialog.uiElement + * @example + * @param {CKEDITOR.dialog} dialog + * Parent dialog object. + * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition + * The element definition. Accepted fields: + * <ul> + * <li><strong>checked</strong> (Optional) Whether the checkbox is checked + * on instantiation. Defaults to false.</li> + * <li><strong>validate</strong> (Optional) The validation function.</li> + * <li><strong>label</strong> (Optional) The checkbox label.</li> + * </ul> + * @param {Array} htmlList + * List of HTML code to output to. + */ + checkbox : function( dialog, elementDefinition, htmlList ) + { + if ( arguments.length < 3 ) + return; + + var _ = initPrivateObject.call( this, elementDefinition, { 'default' : !!elementDefinition[ 'default' ] } ); + + if ( elementDefinition.validate ) + this.validate = elementDefinition.validate; + + /** @ignore */ + var innerHTML = function() + { + var myDefinition = CKEDITOR.tools.extend( {}, elementDefinition, + { + id : elementDefinition.id ? elementDefinition.id + '_checkbox' : CKEDITOR.tools.getNextNumber() + '_checkbox' + }, true ), + html = []; + + var labelId = CKEDITOR.tools.getNextNumber() + '_label'; + var attributes = { 'class' : 'cke_dialog_ui_checkbox_input', type : 'checkbox', 'aria-labelledby' : labelId }; + cleanInnerDefinition( myDefinition ); + if ( elementDefinition[ 'default' ] ) + attributes.checked = 'checked'; + _.checkbox = new CKEDITOR.ui.dialog.uiElement( dialog, myDefinition, html, 'input', null, attributes ); + html.push( ' <label id="', labelId, '" for="', attributes.id, '">', + CKEDITOR.tools.htmlEncode( elementDefinition.label ), + '</label>' ); + return html.join( '' ); + }; + + CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition, htmlList, 'span', null, null, innerHTML ); + }, + + /** + * A group of radio buttons. + * @constructor + * @example + * @extends CKEDITOR.ui.dialog.labeledElement + * @param {CKEDITOR.dialog} dialog + * Parent dialog object. + * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition + * The element definition. Accepted fields: + * <ul> + * <li><strong>default</strong> (Required) The default value.</li> + * <li><strong>validate</strong> (Optional) The validation function.</li> + * <li><strong>items</strong> (Required) An array of options. Each option + * is a 1- or 2-item array of format [ 'Description', 'Value' ]. If 'Value' + * is missing, then the value would be assumed to be the same as the + * description.</li> + * </ul> + * @param {Array} htmlList + * List of HTML code to output to. + */ + radio : function( dialog, elementDefinition, htmlList ) + { + if ( arguments.length < 3) + return; + + initPrivateObject.call( this, elementDefinition ); + if ( !this._['default'] ) + this._['default'] = this._.initValue = elementDefinition.items[0][1]; + if ( elementDefinition.validate ) + this.validate = elementDefinition.valdiate; + var children = [], me = this; + + /** @ignore */ + var innerHTML = function() + { + var inputHtmlList = [], html = [], + commonAttributes = { 'class' : 'cke_dialog_ui_radio_item', 'aria-labelledby' : this._.labelId }, + commonName = elementDefinition.id ? elementDefinition.id + '_radio' : CKEDITOR.tools.getNextNumber() + '_radio'; + for ( var i = 0 ; i < elementDefinition.items.length ; i++ ) + { + var item = elementDefinition.items[i], + title = item[2] !== undefined ? item[2] : item[0], + value = item[1] !== undefined ? item[1] : item[0], + inputId = CKEDITOR.tools.getNextNumber() + '_radio_input', + labelId = inputId + '_label', + inputDefinition = CKEDITOR.tools.extend( {}, elementDefinition, + { + id : inputId, + title : null, + type : null + }, true ), + labelDefinition = CKEDITOR.tools.extend( {}, inputDefinition, + { + title : title + }, true ), + inputAttributes = + { + type : 'radio', + 'class' : 'cke_dialog_ui_radio_input', + name : commonName, + value : value, + 'aria-labelledby' : labelId + }, + inputHtml = []; + if ( me._['default'] == value ) + inputAttributes.checked = 'checked'; + cleanInnerDefinition( inputDefinition ); + cleanInnerDefinition( labelDefinition ); + children.push( new CKEDITOR.ui.dialog.uiElement( dialog, inputDefinition, inputHtml, 'input', null, inputAttributes ) ); + inputHtml.push( ' ' ); + new CKEDITOR.ui.dialog.uiElement( dialog, labelDefinition, inputHtml, 'label', null, { id : labelId, 'for' : inputAttributes.id }, + item[0] ); + inputHtmlList.push( inputHtml.join( '' ) ); + } + new CKEDITOR.ui.dialog.hbox( dialog, [], inputHtmlList, html ); + return html.join( '' ); + }; + + CKEDITOR.ui.dialog.labeledElement.call( this, dialog, elementDefinition, htmlList, innerHTML ); + this._.children = children; + }, + + /** + * A button with a label inside. + * @constructor + * @example + * @extends CKEDITOR.ui.dialog.uiElement + * @param {CKEDITOR.dialog} dialog + * Parent dialog object. + * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition + * The element definition. Accepted fields: + * <ul> + * <li><strong>label</strong> (Required) The button label.</li> + * <li><strong>disabled</strong> (Optional) Set to true if you want the + * button to appear in disabled state.</li> + * </ul> + * @param {Array} htmlList + * List of HTML code to output to. + */ + button : function( dialog, elementDefinition, htmlList ) + { + if ( !arguments.length ) + return; + + if ( typeof elementDefinition == 'function' ) + elementDefinition = elementDefinition( dialog.getParentEditor() ); + + initPrivateObject.call( this, elementDefinition, { disabled : elementDefinition.disabled || false } ); + + // Add OnClick event to this input. + CKEDITOR.event.implementOn( this ); + + var me = this; + + // Register an event handler for processing button clicks. + dialog.on( 'load', function( eventInfo ) + { + var element = this.getElement(); + + (function() + { + element.on( 'click', function( evt ) + { + me.fire( 'click', { dialog : me.getDialog() } ); + evt.data.preventDefault(); + } ); + + element.on( 'keydown', function( evt ) + { + if ( evt.data.getKeystroke() in { 32:1 } ) + { + me.click(); + evt.data.preventDefault(); + } + } ); + })(); + + element.unselectable(); + }, this ); + + var outerDefinition = CKEDITOR.tools.extend( {}, elementDefinition ); + delete outerDefinition.style; + + var labelId = CKEDITOR.tools.getNextNumber() + '_label'; + CKEDITOR.ui.dialog.uiElement.call( + this, + dialog, + outerDefinition, + htmlList, + 'a', + null, + { + style : elementDefinition.style, + href : 'javascript:void(0)', + title : elementDefinition.label, + hidefocus : 'true', + 'class' : elementDefinition['class'], + role : 'button', + 'aria-labelledby' : labelId + }, + '<span id="' + labelId + '" class="cke_dialog_ui_button">' + + CKEDITOR.tools.htmlEncode( elementDefinition.label ) + + '</span>' ); + }, + + /** + * A select box. + * @extends CKEDITOR.ui.dialog.uiElement + * @example + * @constructor + * @param {CKEDITOR.dialog} dialog + * Parent dialog object. + * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition + * The element definition. Accepted fields: + * <ul> + * <li><strong>default</strong> (Required) The default value.</li> + * <li><strong>validate</strong> (Optional) The validation function.</li> + * <li><strong>items</strong> (Required) An array of options. Each option + * is a 1- or 2-item array of format [ 'Description', 'Value' ]. If 'Value' + * is missing, then the value would be assumed to be the same as the + * description.</li> + * <li><strong>multiple</strong> (Optional) Set this to true if you'd like + * to have a multiple-choice select box.</li> + * <li><strong>size</strong> (Optional) The number of items to display in + * the select box.</li> + * </ul> + * @param {Array} htmlList + * List of HTML code to output to. + */ + select : function( dialog, elementDefinition, htmlList ) + { + if ( arguments.length < 3 ) + return; + + var _ = initPrivateObject.call( this, elementDefinition ); + + if ( elementDefinition.validate ) + this.validate = elementDefinition.validate; + + _.inputId = CKEDITOR.tools.getNextNumber() + '_select'; + /** @ignore */ + var innerHTML = function() + { + var myDefinition = CKEDITOR.tools.extend( {}, elementDefinition, + { + id : elementDefinition.id ? elementDefinition.id + '_select' : CKEDITOR.tools.getNextNumber() + '_select' + }, true ), + html = [], + innerHTML = [], + attributes = { 'id' : _.inputId, 'class' : 'cke_dialog_ui_input_select', 'aria-labelledby' : this._.labelId }; + + // Add multiple and size attributes from element definition. + if ( elementDefinition.size != undefined ) + attributes.size = elementDefinition.size; + if ( elementDefinition.multiple != undefined ) + attributes.multiple = elementDefinition.multiple; + + cleanInnerDefinition( myDefinition ); + for ( var i = 0, item ; i < elementDefinition.items.length && ( item = elementDefinition.items[i] ) ; i++ ) + { + innerHTML.push( '<option value="', + CKEDITOR.tools.htmlEncode( item[1] !== undefined ? item[1] : item[0] ), '" /> ', + CKEDITOR.tools.htmlEncode( item[0] ) ); + } + + _.select = new CKEDITOR.ui.dialog.uiElement( dialog, myDefinition, html, 'select', null, attributes, innerHTML.join( '' ) ); + return html.join( '' ); + }; + + CKEDITOR.ui.dialog.labeledElement.call( this, dialog, elementDefinition, htmlList, innerHTML ); + }, + + /** + * A file upload input. + * @extends CKEDITOR.ui.dialog.labeledElement + * @example + * @constructor + * @param {CKEDITOR.dialog} dialog + * Parent dialog object. + * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition + * The element definition. Accepted fields: + * <ul> + * <li><strong>validate</strong> (Optional) The validation function.</li> + * </ul> + * @param {Array} htmlList + * List of HTML code to output to. + */ + file : function( dialog, elementDefinition, htmlList ) + { + if ( arguments.length < 3 ) + return; + + if ( elementDefinition['default'] === undefined ) + elementDefinition['default'] = ''; + + var _ = CKEDITOR.tools.extend( initPrivateObject.call( this, elementDefinition ), { definition : elementDefinition, buttons : [] } ); + + if ( elementDefinition.validate ) + this.validate = elementDefinition.validate; + + /** @ignore */ + var innerHTML = function() + { + _.frameId = CKEDITOR.tools.getNextNumber() + '_fileInput'; + + // Support for custom document.domain in IE. + var isCustomDomain = CKEDITOR.env.isCustomDomain(); + + var html = [ + '<iframe' + + ' frameborder="0"' + + ' allowtransparency="0"' + + ' class="cke_dialog_ui_input_file"' + + ' id="', _.frameId, '"' + + ' title="', elementDefinition.label, '"' + + ' src="javascript:void(' ]; + + html.push( + isCustomDomain ? + '(function(){' + + 'document.open();' + + 'document.domain=\'' + document.domain + '\';' + + 'document.close();' + + '})()' + : + '0' ); + + html.push( + ')">' + + '</iframe>' ); + + return html.join( '' ); + }; + + // IE BUG: Parent container does not resize to contain the iframe automatically. + dialog.on( 'load', function() + { + var iframe = CKEDITOR.document.getById( _.frameId ), + contentDiv = iframe.getParent(); + contentDiv.addClass( 'cke_dialog_ui_input_file' ); + } ); + + CKEDITOR.ui.dialog.labeledElement.call( this, dialog, elementDefinition, htmlList, innerHTML ); + }, + + /** + * A button for submitting the file in a file upload input. + * @extends CKEDITOR.ui.dialog.button + * @example + * @constructor + * @param {CKEDITOR.dialog} dialog + * Parent dialog object. + * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition + * The element definition. Accepted fields: + * <ul> + * <li><strong>for</strong> (Required) The file input's page and element Id + * to associate to, in a 2-item array format: [ 'page_id', 'element_id' ]. + * </li> + * <li><strong>validate</strong> (Optional) The validation function.</li> + * </ul> + * @param {Array} htmlList + * List of HTML code to output to. + */ + fileButton : function( dialog, elementDefinition, htmlList ) + { + if ( arguments.length < 3 ) + return; + + var _ = initPrivateObject.call( this, elementDefinition ), + me = this; + + if ( elementDefinition.validate ) + this.validate = elementDefinition.validate; + + var myDefinition = CKEDITOR.tools.extend( {}, elementDefinition ); + var onClick = myDefinition.onClick; + myDefinition.className = ( myDefinition.className ? myDefinition.className + ' ' : '' ) + 'cke_dialog_ui_button'; + myDefinition.onClick = function( evt ) + { + var target = elementDefinition[ 'for' ]; // [ pageId, elementId ] + if ( !onClick || onClick.call( this, evt ) !== false ) + { + dialog.getContentElement( target[0], target[1] ).submit(); + this.disable(); + } + }; + + dialog.on( 'load', function() + { + dialog.getContentElement( elementDefinition[ 'for' ][0], elementDefinition[ 'for' ][1] )._.buttons.push( me ); + } ); + + CKEDITOR.ui.dialog.button.call( this, dialog, myDefinition, htmlList ); + }, + + html : (function() + { + var myHtmlRe = /^\s*<[\w:]+\s+([^>]*)?>/, + theirHtmlRe = /^(\s*<[\w:]+(?:\s+[^>]*)?)((?:.|\r|\n)+)$/, + emptyTagRe = /\/$/; + /** + * A dialog element made from raw HTML code. + * @extends CKEDITOR.ui.dialog.uiElement + * @name CKEDITOR.ui.dialog.html + * @param {CKEDITOR.dialog} dialog Parent dialog object. + * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition Element definition. + * Accepted fields: + * <ul> + * <li><strong>html</strong> (Required) HTML code of this element.</li> + * </ul> + * @param {Array} htmlList List of HTML code to be added to the dialog's content area. + * @example + * @constructor + */ + return function( dialog, elementDefinition, htmlList ) + { + if ( arguments.length < 3 ) + return; + + var myHtmlList = [], + myHtml, + theirHtml = elementDefinition.html, + myMatch, theirMatch; + + // If the HTML input doesn't contain any tags at the beginning, add a <span> tag around it. + if ( theirHtml.charAt( 0 ) != '<' ) + theirHtml = '<span>' + theirHtml + '</span>'; + + // Look for focus function in definition. + var focus = elementDefinition.focus; + if ( focus ) + { + var oldFocus = this.focus; + this.focus = function() + { + oldFocus.call( this ); + typeof focus == 'function' && focus.call( this ); + this.fire( 'focus' ); + }; + if ( elementDefinition.isFocusable ) + { + var oldIsFocusable = this.isFocusable; + this.isFocusable = oldIsFocusable; + } + this.keyboardFocusable = true; + } + + CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition, myHtmlList, 'span', null, null, '' ); + + // Append the attributes created by the uiElement call to the real HTML. + myHtml = myHtmlList.join( '' ); + myMatch = myHtml.match( myHtmlRe ); + theirMatch = theirHtml.match( theirHtmlRe ) || [ '', '', '' ]; + + if ( emptyTagRe.test( theirMatch[1] ) ) + { + theirMatch[1] = theirMatch[1].slice( 0, -1 ); + theirMatch[2] = '/' + theirMatch[2]; + } + + htmlList.push( [ theirMatch[1], ' ', myMatch[1] || '', theirMatch[2] ].join( '' ) ); + }; + })(), + + /** + * Form fieldset for grouping dialog UI elements. + * @constructor + * @extends CKEDITOR.ui.dialog.uiElement + * @param {CKEDITOR.dialog} dialog Parent dialog object. + * @param {Array} childObjList + * Array of {@link CKEDITOR.ui.dialog.uiElement} objects inside this + * container. + * @param {Array} childHtmlList + * Array of HTML code that correspond to the HTML output of all the + * objects in childObjList. + * @param {Array} htmlList + * Array of HTML code that this element will output to. + * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition + * The element definition. Accepted fields: + * <ul> + * <li><strong>label</strong> (Optional) The legend of the this fieldset.</li> + * <li><strong>children</strong> (Required) An array of dialog field definitions which will be grouped inside this fieldset. </li> + * </ul> + */ + fieldset : function( dialog, childObjList, childHtmlList, htmlList, elementDefinition ) + { + var legendLabel = elementDefinition.label; + /** @ignore */ + var innerHTML = function() + { + var html = []; + legendLabel && html.push( '<legend>' + legendLabel + '</legend>' ); + for ( var i = 0; i < childHtmlList.length; i++ ) + html.push( childHtmlList[ i ] ); + return html.join( '' ); + }; + + this._ = { children : childObjList }; + CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition, htmlList, 'fieldset', null, null, innerHTML ); + } + + }, true ); + + CKEDITOR.ui.dialog.html.prototype = new CKEDITOR.ui.dialog.uiElement; + + CKEDITOR.ui.dialog.labeledElement.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.uiElement, + /** @lends CKEDITOR.ui.dialog.labeledElement.prototype */ + { + /** + * Sets the label text of the element. + * @param {String} label The new label text. + * @returns {CKEDITOR.ui.dialog.labeledElement} The current labeled element. + * @example + */ + setLabel : function( label ) + { + var node = CKEDITOR.document.getById( this._.labelId ); + if ( node.getChildCount() < 1 ) + ( new CKEDITOR.dom.text( label, CKEDITOR.document ) ).appendTo( node ); + else + node.getChild( 0 ).$.nodeValue = label; + return this; + }, + + /** + * Retrieves the current label text of the elment. + * @returns {String} The current label text. + * @example + */ + getLabel : function() + { + var node = CKEDITOR.document.getById( this._.labelId ); + if ( !node || node.getChildCount() < 1 ) + return ''; + else + return node.getChild( 0 ).getText(); + }, + + /** + * Defines the onChange event for UI element definitions. + * @field + * @type Object + * @example + */ + eventProcessors : commonEventProcessors + }, true ); + + CKEDITOR.ui.dialog.button.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.uiElement, + /** @lends CKEDITOR.ui.dialog.button.prototype */ + { + /** + * Simulates a click to the button. + * @example + * @returns {Object} Return value of the 'click' event. + */ + click : function() + { + if ( !this._.disabled ) + return this.fire( 'click', { dialog : this._.dialog } ); + this.getElement().$.blur(); + return false; + }, + + /** + * Enables the button. + * @example + */ + enable : function() + { + this._.disabled = false; + var element = this.getElement(); + element && element.removeClass( 'disabled' ); + }, + + /** + * Disables the button. + * @example + */ + disable : function() + { + this._.disabled = true; + this.getElement().addClass( 'disabled' ); + }, + + isVisible : function() + { + return this.getElement().getFirst().isVisible(); + }, + + isEnabled : function() + { + return !this._.disabled; + }, + + /** + * Defines the onChange event and onClick for button element definitions. + * @field + * @type Object + * @example + */ + eventProcessors : CKEDITOR.tools.extend( {}, CKEDITOR.ui.dialog.uiElement.prototype.eventProcessors, + { + /** @ignore */ + onClick : function( dialog, func ) + { + this.on( 'click', func ); + } + }, true ), + + /** + * Handler for the element's access key up event. Simulates a click to + * the button. + * @example + */ + accessKeyUp : function() + { + this.click(); + }, + + /** + * Handler for the element's access key down event. Simulates a mouse + * down to the button. + * @example + */ + accessKeyDown : function() + { + this.focus(); + }, + + keyboardFocusable : true + }, true ); + + CKEDITOR.ui.dialog.textInput.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.labeledElement, + /** @lends CKEDITOR.ui.dialog.textInput.prototype */ + { + /** + * Gets the text input DOM element under this UI object. + * @example + * @returns {CKEDITOR.dom.element} The DOM element of the text input. + */ + getInputElement : function() + { + return CKEDITOR.document.getById( this._.inputId ); + }, + + /** + * Puts focus into the text input. + * @example + */ + focus : function() + { + var me = this.selectParentTab(); + + // GECKO BUG: setTimeout() is needed to workaround invisible selections. + setTimeout( function() + { + var element = me.getInputElement(); + element && element.$.focus(); + }, 0 ); + }, + + /** + * Selects all the text in the text input. + * @example + */ + select : function() + { + var me = this.selectParentTab(); + + // GECKO BUG: setTimeout() is needed to workaround invisible selections. + setTimeout( function() + { + var e = me.getInputElement(); + if ( e ) + { + e.$.focus(); + e.$.select(); + } + }, 0 ); + }, + + /** + * Handler for the text input's access key up event. Makes a select() + * call to the text input. + * @example + */ + accessKeyUp : function() + { + this.select(); + }, + + /** + * Sets the value of this text input object. + * @param {Object} value The new value. + * @returns {CKEDITOR.ui.dialog.textInput} The current UI element. + * @example + * uiElement.setValue( 'Blamo' ); + */ + setValue : function( value ) + { + !value && ( value = '' ); + return CKEDITOR.ui.dialog.uiElement.prototype.setValue.call( this, value ); + }, + + keyboardFocusable : true + }, commonPrototype, true ); + + CKEDITOR.ui.dialog.textarea.prototype = new CKEDITOR.ui.dialog.textInput(); + + CKEDITOR.ui.dialog.select.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.labeledElement, + /** @lends CKEDITOR.ui.dialog.select.prototype */ + { + /** + * Gets the DOM element of the select box. + * @returns {CKEDITOR.dom.element} The <select> element of this UI + * element. + * @example + */ + getInputElement : function() + { + return this._.select.getElement(); + }, + + /** + * Adds an option to the select box. + * @param {String} label Option label. + * @param {String} value (Optional) Option value, if not defined it'll be + * assumed to be the same as the label. + * @param {Number} index (Optional) Position of the option to be inserted + * to. If not defined the new option will be inserted to the end of list. + * @example + * @returns {CKEDITOR.ui.dialog.select} The current select UI element. + */ + add : function( label, value, index ) + { + var option = new CKEDITOR.dom.element( 'option', this.getDialog().getParentEditor().document ), + selectElement = this.getInputElement().$; + option.$.text = label; + option.$.value = ( value === undefined || value === null ) ? label : value; + if ( index === undefined || index === null ) + { + if ( CKEDITOR.env.ie ) + selectElement.add( option.$ ); + else + selectElement.add( option.$, null ); + } + else + selectElement.add( option.$, index ); + return this; + }, + + /** + * Removes an option from the selection list. + * @param {Number} index Index of the option to be removed. + * @example + * @returns {CKEDITOR.ui.dialog.select} The current select UI element. + */ + remove : function( index ) + { + var selectElement = this.getInputElement().$; + selectElement.remove( index ); + return this; + }, + + /** + * Clears all options out of the selection list. + * @returns {CKEDITOR.ui.dialog.select} The current select UI element. + */ + clear : function() + { + var selectElement = this.getInputElement().$; + while ( selectElement.length > 0 ) + selectElement.remove( 0 ); + return this; + }, + + keyboardFocusable : true + }, commonPrototype, true ); + + CKEDITOR.ui.dialog.checkbox.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.uiElement, + /** @lends CKEDITOR.ui.dialog.checkbox.prototype */ + { + /** + * Gets the checkbox DOM element. + * @example + * @returns {CKEDITOR.dom.element} The DOM element of the checkbox. + */ + getInputElement : function() + { + return this._.checkbox.getElement(); + }, + + /** + * Sets the state of the checkbox. + * @example + * @param {Boolean} true to tick the checkbox, false to untick it. + */ + setValue : function( checked ) + { + this.getInputElement().$.checked = checked; + this.fire( 'change', { value : checked } ); + }, + + /** + * Gets the state of the checkbox. + * @example + * @returns {Boolean} true means the checkbox is ticked, false means it's not ticked. + */ + getValue : function() + { + return this.getInputElement().$.checked; + }, + + /** + * Handler for the access key up event. Toggles the checkbox. + * @example + */ + accessKeyUp : function() + { + this.setValue( !this.getValue() ); + }, + + /** + * Defines the onChange event for UI element definitions. + * @field + * @type Object + * @example + */ + eventProcessors : + { + onChange : function( dialog, func ) + { + if ( !CKEDITOR.env.ie ) + return commonEventProcessors.onChange.apply( this, arguments ); + else + { + dialog.on( 'load', function() + { + var element = this._.checkbox.getElement(); + element.on( 'propertychange', function( evt ) + { + evt = evt.data.$; + if ( evt.propertyName == 'checked' ) + this.fire( 'change', { value : element.$.checked } ); + }, this ); + }, this ); + this.on( 'change', func ); + } + return null; + } + }, + + keyboardFocusable : true + }, commonPrototype, true ); + + CKEDITOR.ui.dialog.radio.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.uiElement, + /** @lends CKEDITOR.ui.dialog.radio.prototype */ + { + /** + * Checks one of the radio buttons in this button group. + * @example + * @param {String} value The value of the button to be chcked. + */ + setValue : function( value ) + { + var children = this._.children, + item; + for ( var i = 0 ; ( i < children.length ) && ( item = children[i] ) ; i++ ) + item.getElement().$.checked = ( item.getValue() == value ); + this.fire( 'change', { value : value } ); + }, + + /** + * Gets the value of the currently checked radio button. + * @example + * @returns {String} The currently checked button's value. + */ + getValue : function() + { + var children = this._.children; + for ( var i = 0 ; i < children.length ; i++ ) + { + if ( children[i].getElement().$.checked ) + return children[i].getValue(); + } + return null; + }, + + /** + * Handler for the access key up event. Focuses the currently + * selected radio button, or the first radio button if none is + * selected. + * @example + */ + accessKeyUp : function() + { + var children = this._.children, i; + for ( i = 0 ; i < children.length ; i++ ) + { + if ( children[i].getElement().$.checked ) + { + children[i].getElement().focus(); + return; + } + } + children[0].getElement().focus(); + }, + + /** + * Defines the onChange event for UI element definitions. + * @field + * @type Object + * @example + */ + eventProcessors : + { + onChange : function( dialog, func ) + { + if ( !CKEDITOR.env.ie ) + return commonEventProcessors.onChange.apply( this, arguments ); + else + { + dialog.on( 'load', function() + { + var children = this._.children, me = this; + for ( var i = 0 ; i < children.length ; i++ ) + { + var element = children[i].getElement(); + element.on( 'propertychange', function( evt ) + { + evt = evt.data.$; + if ( evt.propertyName == 'checked' && this.$.checked ) + me.fire( 'change', { value : this.getAttribute( 'value' ) } ); + } ); + } + }, this ); + this.on( 'change', func ); + } + return null; + } + }, + + keyboardFocusable : true + }, commonPrototype, true ); + + CKEDITOR.ui.dialog.file.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.labeledElement, + commonPrototype, + /** @lends CKEDITOR.ui.dialog.file.prototype */ + { + /** + * Gets the <input> element of this file input. + * @returns {CKEDITOR.dom.element} The file input element. + * @example + */ + getInputElement : function() + { + var frameDocument = CKEDITOR.document.getById( this._.frameId ).getFrameDocument(); + return frameDocument.$.forms.length > 0 ? + new CKEDITOR.dom.element( frameDocument.$.forms[0].elements[0] ) : + this.getElement(); + }, + + /** + * Uploads the file in the file input. + * @returns {CKEDITOR.ui.dialog.file} This object. + * @example + */ + submit : function() + { + this.getInputElement().getParent().$.submit(); + return this; + }, + + /** + * Get the action assigned to the form. + * @returns {String} The value of the action. + * @example + */ + getAction : function( action ) + { + return this.getInputElement().getParent().$.action; + }, + + /** + * Redraws the file input and resets the file path in the file input. + * The redraw logic is necessary because non-IE browsers tend to clear + * the <iframe> containing the file input after closing the dialog. + * @example + */ + reset : function() + { + var frameElement = CKEDITOR.document.getById( this._.frameId ), + frameDocument = frameElement.getFrameDocument(), + elementDefinition = this._.definition, + buttons = this._.buttons; + + function generateFormField() + { + frameDocument.$.open(); + + // Support for custom document.domain in IE. + if ( CKEDITOR.env.isCustomDomain() ) + frameDocument.$.domain = document.domain; + + var size = ''; + if ( elementDefinition.size ) + size = elementDefinition.size - ( CKEDITOR.env.ie ? 7 : 0 ); // "Browse" button is bigger in IE. + + frameDocument.$.write( [ '<html><head><title></title></head><body style="margin: 0; overflow: hidden; background: transparent;">', + '<form enctype="multipart/form-data" method="POST" action="', + CKEDITOR.tools.htmlEncode( elementDefinition.action ), + '">', + '<input type="file" name="', + CKEDITOR.tools.htmlEncode( elementDefinition.id || 'cke_upload' ), + '" size="', + CKEDITOR.tools.htmlEncode( size > 0 ? size : "" ), + '" />', + '</form>', + '</body></html>' ].join( '' ) ); + + frameDocument.$.close(); + + for ( var i = 0 ; i < buttons.length ; i++ ) + buttons[i].enable(); + } + + // #3465: Wait for the browser to finish rendering the dialog first. + if ( CKEDITOR.env.gecko ) + setTimeout( generateFormField, 500 ); + else + generateFormField(); + }, + + getValue : function() + { + // The file path returned from the input tag is incomplete anyway, so it's + // safe to ignore it and prevent the confirmation dialog from appearing. + // (Part of #3465) + return ''; + }, + + /** + * Defines the onChange event for UI element definitions. + * @field + * @type Object + * @example + */ + eventProcessors : commonEventProcessors, + + keyboardFocusable : true + }, true ); + + CKEDITOR.ui.dialog.fileButton.prototype = new CKEDITOR.ui.dialog.button; + + CKEDITOR.ui.dialog.fieldset.prototype = CKEDITOR.tools.clone( CKEDITOR.ui.dialog.hbox.prototype ); + + CKEDITOR.dialog.addUIElement( 'text', textBuilder ); + CKEDITOR.dialog.addUIElement( 'password', textBuilder ); + CKEDITOR.dialog.addUIElement( 'textarea', commonBuilder ); + CKEDITOR.dialog.addUIElement( 'checkbox', commonBuilder ); + CKEDITOR.dialog.addUIElement( 'radio', commonBuilder ); + CKEDITOR.dialog.addUIElement( 'button', commonBuilder ); + CKEDITOR.dialog.addUIElement( 'select', commonBuilder ); + CKEDITOR.dialog.addUIElement( 'file', commonBuilder ); + CKEDITOR.dialog.addUIElement( 'fileButton', commonBuilder ); + CKEDITOR.dialog.addUIElement( 'html', commonBuilder ); + CKEDITOR.dialog.addUIElement( 'fieldset', containerBuilder ); +})(); diff --git a/_source/plugins/div/dialogs/div.js b/_source/plugins/div/dialogs/div.js index e3b39c3..55b1f70 100644 --- a/_source/plugins/div/dialogs/div.js +++ b/_source/plugins/div/dialogs/div.js @@ -1,535 +1,529 @@ -/*
- * 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;
- }
-
- // Synchronous field values to other impacted fields is required, e.g. div styles
- // change should also alter inline-style text.
- function commitInternally( targetFields )
- {
- var dialog = this.getDialog(),
- element = dialog._element && dialog._element.clone()
- || new CKEDITOR.dom.element( 'div', editor.document );
-
- // Commit this field and broadcast to target fields.
- this.commit( element, true );
-
- targetFields = [].concat( targetFields );
- var length = targetFields.length, field;
- for ( var i = 0; i < length; i++ )
- {
- field = dialog.getContentElement.apply( dialog, targetFields[ i ].split( ':' ) );
- field && field.setup && field.setup( element, true );
- }
- }
-
-
- // Registered 'CKEDITOR.style' instances.
- var styles = {} ;
- /**
- * 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' : '',
- // Options are loaded dynamically.
- items :
- [
- [ editor.lang.common.notSet , '' ]
- ],
- onChange : function()
- {
- commitInternally.call( this, [ 'info:class', 'advanced:dir', 'advanced:style' ] );
- },
- setup : function( element )
- {
- for ( var name in styles )
- styles[ name ].checkElementRemovable( element, true ) && this.setValue( name );
- },
- commit: function( element )
- {
- var styleName;
- if ( ( styleName = this.getValue() ) )
- styles[ styleName ].applyToObject( element );
- }
- },
- {
- 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' : '',
- commit : function( element )
- {
- // Merge with 'elementStyle', which is of higher priority.
- var value = this.getValue(),
- merged = [ value, element.getAttribute( 'style' ) ].join( ';' );
- value && element.setAttribute( 'style', merged );
- }
- }
- ]
- },
- {
- 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.notSet , '' ],
- [
- editor.lang.common.langDirLtr,
- 'ltr'
- ],
- [
- editor.lang.common.langDirRtl,
- 'rtl'
- ]
- ]
- }
- ]
- }
- ]
- }
- ],
- onLoad : function()
- {
- setupFields.call(this);
-
- // Preparing for the 'elementStyle' field.
- var dialog = this,
- stylesField = this.getContentElement( 'info', 'elementStyle' ),
- // Reuse the 'stylescombo' plugin's styles definition.
- customStylesConfig = editor.config.stylesCombo_stylesSet,
- stylesSetName = customStylesConfig && customStylesConfig.split( ':' )[ 0 ];
-
- if ( stylesSetName )
- {
- CKEDITOR.stylesSet.load( stylesSetName,
- function( stylesSet )
- {
- var stylesDefinitions = stylesSet[ stylesSetName ],
- styleName;
-
- if ( stylesDefinitions )
- {
- // Digg only those styles that apply to 'div'.
- for ( var i = 0 ; i < stylesDefinitions.length ; i++ )
- {
- var styleDefinition = stylesDefinitions[ i ];
- if ( styleDefinition.element && styleDefinition.element == 'div' )
- {
- styleName = styleDefinition.name;
- styles[ styleName ] = new CKEDITOR.style( styleDefinition );
-
- // Populate the styles field options with style name.
- stylesField.items.push( [ styleName, styleName ] );
- stylesField.add( styleName, styleName );
- }
- }
- }
-
-
- // We should disable the content element
- // it if no options are available at all.
- stylesField[ stylesField.items.length > 1 ? 'enable' : 'disable' ]();
-
- // Now setup the field value manually.
- setTimeout( function() { stylesField.setup( dialog._element ); }, 0 );
- } );
- }
- },
- 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
- var size = containers.length;
- for ( var i = 0; i < size; i++ )
- {
- this.commitContent( containers[ i ] );
-
- // Remove empty 'style' attribute.
- !containers[ i ].getAttribute( 'style' ) && containers[ i ].removeAttribute( 'style' );
- }
-
- this.hide();
- },
- onHide : function()
- {
- delete this._element;
- }
- };
- }
-
- 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;
- */
+/* + * 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; + } + + // Synchronous field values to other impacted fields is required, e.g. div styles + // change should also alter inline-style text. + function commitInternally( targetFields ) + { + var dialog = this.getDialog(), + element = dialog._element && dialog._element.clone() + || new CKEDITOR.dom.element( 'div', editor.document ); + + // Commit this field and broadcast to target fields. + this.commit( element, true ); + + targetFields = [].concat( targetFields ); + var length = targetFields.length, field; + for ( var i = 0; i < length; i++ ) + { + field = dialog.getContentElement.apply( dialog, targetFields[ i ].split( ':' ) ); + field && field.setup && field.setup( element, true ); + } + } + + + // Registered 'CKEDITOR.style' instances. + var styles = {} ; + /** + * 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' : '', + // Options are loaded dynamically. + items : + [ + [ editor.lang.common.notSet , '' ] + ], + onChange : function() + { + commitInternally.call( this, [ 'info:class', 'advanced:dir', 'advanced:style' ] ); + }, + setup : function( element ) + { + for ( var name in styles ) + styles[ name ].checkElementRemovable( element, true ) && this.setValue( name ); + }, + commit: function( element ) + { + var styleName; + if ( ( styleName = this.getValue() ) ) + styles[ styleName ].applyToObject( element ); + } + }, + { + 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' : '', + commit : function( element ) + { + // Merge with 'elementStyle', which is of higher priority. + var value = this.getValue(), + merged = [ value, element.getAttribute( 'style' ) ].join( ';' ); + value && element.setAttribute( 'style', merged ); + } + } + ] + }, + { + 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.notSet , '' ], + [ + editor.lang.common.langDirLtr, + 'ltr' + ], + [ + editor.lang.common.langDirRtl, + 'rtl' + ] + ] + } + ] + } + ] + } + ], + onLoad : function() + { + setupFields.call(this); + + // Preparing for the 'elementStyle' field. + var dialog = this, + stylesField = this.getContentElement( 'info', 'elementStyle' ); + + // Reuse the 'stylescombo' plugin's styles definition. + editor.getStylesSet( function( stylesDefinitions ) + { + var styleName; + + if ( stylesDefinitions ) + { + // Digg only those styles that apply to 'div'. + for ( var i = 0 ; i < stylesDefinitions.length ; i++ ) + { + var styleDefinition = stylesDefinitions[ i ]; + if ( styleDefinition.element && styleDefinition.element == 'div' ) + { + styleName = styleDefinition.name; + styles[ styleName ] = new CKEDITOR.style( styleDefinition ); + + // Populate the styles field options with style name. + stylesField.items.push( [ styleName, styleName ] ); + stylesField.add( styleName, styleName ); + } + } + } + + // We should disable the content element + // it if no options are available at all. + stylesField[ stylesField.items.length > 1 ? 'enable' : 'disable' ](); + + // Now setup the field value manually. + setTimeout( function() { stylesField.setup( dialog._element ); }, 0 ); + } ); + }, + 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() + { + editor.fire( 'saveSnapshot' ); + if ( command == 'editdiv' ) + containers = [ this._element ]; + else + containers = createDiv( editor, true ); + + // Update elements attributes + var size = containers.length; + for ( var i = 0; i < size; i++ ) + { + this.commitContent( containers[ i ] ); + + // Remove empty 'style' attribute. + !containers[ i ].getAttribute( 'style' ) && containers[ i ].removeAttribute( 'style' ); + } + editor.fire( 'saveSnapshot' ); + + this.hide(); + }, + onHide : function() + { + delete this._element; + } + }; + } + + 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/editingblock/plugin.js b/_source/plugins/editingblock/plugin.js index 8d54142..30e929d 100644 --- a/_source/plugins/editingblock/plugin.js +++ b/_source/plugins/editingblock/plugin.js @@ -1,224 +1,230 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-/**
- * @fileOverview The default editing block plugin, which holds the editing area
- * and source view.
- */
-
-(function()
-{
- var getMode = function( editor, mode )
- {
- return editor._.modes && editor._.modes[ mode || editor.mode ];
- };
-
- // This is a semaphore used to avoid recursive calls between
- // the following data handling functions.
- var isHandlingData;
-
- CKEDITOR.plugins.add( 'editingblock',
- {
- init : function( editor )
- {
- if ( !editor.config.editingBlock )
- return;
-
- editor.on( 'themeSpace', function( event )
- {
- if ( event.data.space == 'contents' )
- event.data.html += '<br>';
- });
-
- editor.on( 'themeLoaded', function()
- {
- editor.fireOnce( 'editingBlockReady' );
- });
-
- editor.on( 'uiReady', function()
- {
- editor.setMode( editor.config.startupMode );
- });
-
- editor.on( 'afterSetData', function()
- {
- if ( !isHandlingData )
- {
- function setData()
- {
- isHandlingData = true;
- getMode( editor ).loadData( editor.getData() );
- isHandlingData = false;
- }
-
- if ( editor.mode )
- setData();
- else
- {
- editor.on( 'mode', function()
- {
- setData();
- editor.removeListener( 'mode', arguments.callee );
- });
- }
- }
- });
-
- editor.on( 'beforeGetData', function()
- {
- if ( !isHandlingData && editor.mode )
- {
- isHandlingData = true;
- editor.setData( getMode( editor ).getData() );
- isHandlingData = false;
- }
- });
-
- editor.on( 'getSnapshot', function( event )
- {
- if ( editor.mode )
- event.data = getMode( editor ).getSnapshotData();
- });
-
- editor.on( 'loadSnapshot', function( event )
- {
- if ( editor.mode )
- getMode( editor ).loadSnapshotData( event.data );
- });
-
- // For the first "mode" call, we'll also fire the "instanceReady"
- // event.
- editor.on( 'mode', function( event )
- {
- // Do that once only.
- event.removeListener();
-
- if ( editor.config.startupFocus )
- editor.focus();
-
- // Fire instanceReady for both the editor and CKEDITOR, but
- // defer this until the whole execution has completed
- // to guarantee the editor is fully responsible.
- setTimeout( function(){
- editor.fireOnce( 'instanceReady' );
- CKEDITOR.fire( 'instanceReady', null, editor );
- } );
- });
- }
- });
-
- /**
- * The current editing mode. An editing mode is basically a viewport for
- * editing or content viewing. By default the possible values for this
- * property are "wysiwyg" and "source".
- * @type String
- * @example
- * alert( CKEDITOR.instances.editor1.mode ); // "wysiwyg" (e.g.)
- */
- CKEDITOR.editor.prototype.mode = '';
-
- /**
- * Registers an editing mode. This function is to be used mainly by plugins.
- * @param {String} mode The mode name.
- * @param {Object} modeEditor The mode editor definition.
- * @example
- */
- CKEDITOR.editor.prototype.addMode = function( mode, modeEditor )
- {
- modeEditor.name = mode;
- ( this._.modes || ( this._.modes = {} ) )[ mode ] = modeEditor;
- };
-
- /**
- * Sets the current editing mode in this editor instance.
- * @param {String} mode A registered mode name.
- * @example
- * // Switch to "source" view.
- * CKEDITOR.instances.editor1.setMode( 'source' );
- */
- CKEDITOR.editor.prototype.setMode = function( mode )
- {
- var data,
- holderElement = this.getThemeSpace( 'contents' ),
- isDirty = this.checkDirty();
-
- // Unload the previous mode.
- if ( this.mode )
- {
- if ( mode == this.mode )
- return;
-
- this.fire( 'beforeModeUnload' );
-
- var currentMode = getMode( this );
- data = currentMode.getData();
- currentMode.unload( holderElement );
- this.mode = '';
- }
-
- holderElement.setHtml( '' );
-
- // Load required mode.
- var modeEditor = getMode( this, mode );
- if ( !modeEditor )
- throw '[CKEDITOR.editor.setMode] Unknown mode "' + mode + '".';
-
- if ( !isDirty )
- {
- this.on( 'mode', function()
- {
- this.resetDirty();
- this.removeListener( 'mode', arguments.callee );
- });
- }
-
- modeEditor.load( holderElement, ( typeof data ) != 'string' ? this.getData() : data);
- };
-
- /**
- * Moves the selection focus to the editing are space in the editor.
- */
- CKEDITOR.editor.prototype.focus = function()
- {
- var mode = getMode( this );
- if ( mode )
- mode.focus();
- };
-})();
-
-/**
- * The mode to load at the editor startup. It depends on the plugins
- * loaded. By default, the "wysiwyg" and "source" modes are available.
- * @type String
- * @default 'wysiwyg'
- * @example
- * config.startupMode = 'source';
- */
-CKEDITOR.config.startupMode = 'wysiwyg';
-
-/**
- * Sets whether the editor should have the focus when the page loads.
- * @type Boolean
- * @default false
- * @example
- * config.startupFocus = true;
- */
-CKEDITOR.config.startupFocus = false;
-
-/**
- * Whether to render or not the editing block area in the editor interface.
- * @type Boolean
- * @default true
- * @example
- * config.editingBlock = false;
- */
-CKEDITOR.config.editingBlock = true;
-
-/**
- * Fired when a CKEDITOR instance is created, fully initialized and ready for interaction.
- * @name CKEDITOR#instanceReady
- * @event
- * @param {CKEDITOR.editor} editor The editor instance that has been created.
- */
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview The default editing block plugin, which holds the editing area + * and source view. + */ + +(function() +{ + var getMode = function( editor, mode ) + { + return editor._.modes && editor._.modes[ mode || editor.mode ]; + }; + + // This is a semaphore used to avoid recursive calls between + // the following data handling functions. + var isHandlingData; + + CKEDITOR.plugins.add( 'editingblock', + { + init : function( editor ) + { + if ( !editor.config.editingBlock ) + return; + + editor.on( 'themeSpace', function( event ) + { + if ( event.data.space == 'contents' ) + event.data.html += '<br>'; + }); + + editor.on( 'themeLoaded', function() + { + editor.fireOnce( 'editingBlockReady' ); + }); + + editor.on( 'uiReady', function() + { + editor.setMode( editor.config.startupMode ); + }); + + editor.on( 'afterSetData', function() + { + if ( !isHandlingData ) + { + function setData() + { + isHandlingData = true; + getMode( editor ).loadData( editor.getData() ); + isHandlingData = false; + } + + if ( editor.mode ) + setData(); + else + { + editor.on( 'mode', function() + { + setData(); + editor.removeListener( 'mode', arguments.callee ); + }); + } + } + }); + + editor.on( 'beforeGetData', function() + { + if ( !isHandlingData && editor.mode ) + { + isHandlingData = true; + editor.setData( getMode( editor ).getData() ); + isHandlingData = false; + } + }); + + editor.on( 'getSnapshot', function( event ) + { + if ( editor.mode ) + event.data = getMode( editor ).getSnapshotData(); + }); + + editor.on( 'loadSnapshot', function( event ) + { + if ( editor.mode ) + getMode( editor ).loadSnapshotData( event.data ); + }); + + // For the first "mode" call, we'll also fire the "instanceReady" + // event. + editor.on( 'mode', function( event ) + { + // Do that once only. + event.removeListener(); + + // Redirect the focus into editor for webkit. (#5713) + CKEDITOR.env.webkit && editor.container.on( 'focus', function() + { + editor.focus(); + }); + + if ( editor.config.startupFocus ) + editor.focus(); + + // Fire instanceReady for both the editor and CKEDITOR, but + // defer this until the whole execution has completed + // to guarantee the editor is fully responsible. + setTimeout( function(){ + editor.fireOnce( 'instanceReady' ); + CKEDITOR.fire( 'instanceReady', null, editor ); + } ); + }); + } + }); + + /** + * The current editing mode. An editing mode is basically a viewport for + * editing or content viewing. By default the possible values for this + * property are "wysiwyg" and "source". + * @type String + * @example + * alert( CKEDITOR.instances.editor1.mode ); // "wysiwyg" (e.g.) + */ + CKEDITOR.editor.prototype.mode = ''; + + /** + * Registers an editing mode. This function is to be used mainly by plugins. + * @param {String} mode The mode name. + * @param {Object} modeEditor The mode editor definition. + * @example + */ + CKEDITOR.editor.prototype.addMode = function( mode, modeEditor ) + { + modeEditor.name = mode; + ( this._.modes || ( this._.modes = {} ) )[ mode ] = modeEditor; + }; + + /** + * Sets the current editing mode in this editor instance. + * @param {String} mode A registered mode name. + * @example + * // Switch to "source" view. + * CKEDITOR.instances.editor1.setMode( 'source' ); + */ + CKEDITOR.editor.prototype.setMode = function( mode ) + { + var data, + holderElement = this.getThemeSpace( 'contents' ), + isDirty = this.checkDirty(); + + // Unload the previous mode. + if ( this.mode ) + { + if ( mode == this.mode ) + return; + + this.fire( 'beforeModeUnload' ); + + var currentMode = getMode( this ); + data = currentMode.getData(); + currentMode.unload( holderElement ); + this.mode = ''; + } + + holderElement.setHtml( '' ); + + // Load required mode. + var modeEditor = getMode( this, mode ); + if ( !modeEditor ) + throw '[CKEDITOR.editor.setMode] Unknown mode "' + mode + '".'; + + if ( !isDirty ) + { + this.on( 'mode', function() + { + this.resetDirty(); + this.removeListener( 'mode', arguments.callee ); + }); + } + + modeEditor.load( holderElement, ( typeof data ) != 'string' ? this.getData() : data); + }; + + /** + * Moves the selection focus to the editing are space in the editor. + */ + CKEDITOR.editor.prototype.focus = function() + { + var mode = getMode( this ); + if ( mode ) + mode.focus(); + }; +})(); + +/** + * The mode to load at the editor startup. It depends on the plugins + * loaded. By default, the "wysiwyg" and "source" modes are available. + * @type String + * @default 'wysiwyg' + * @example + * config.startupMode = 'source'; + */ +CKEDITOR.config.startupMode = 'wysiwyg'; + +/** + * Sets whether the editor should have the focus when the page loads. + * @type Boolean + * @default false + * @example + * config.startupFocus = true; + */ +CKEDITOR.config.startupFocus = false; + +/** + * Whether to render or not the editing block area in the editor interface. + * @type Boolean + * @default true + * @example + * config.editingBlock = false; + */ +CKEDITOR.config.editingBlock = true; + +/** + * Fired when a CKEDITOR instance is created, fully initialized and ready for interaction. + * @name CKEDITOR#instanceReady + * @event + * @param {CKEDITOR.editor} editor The editor instance that has been created. + */ diff --git a/_source/plugins/elementspath/plugin.js b/_source/plugins/elementspath/plugin.js index eaab6f8..095c35c 100644 --- a/_source/plugins/elementspath/plugin.js +++ b/_source/plugins/elementspath/plugin.js @@ -1,189 +1,204 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-/**
- * @fileOverview The "elementspath" plugin. It shows all elements in the DOM
- * parent tree relative to the current selection in the editing area.
- */
-
-(function()
-{
- var commands =
- {
- toolbarFocus :
- {
- exec : function( editor )
- {
- var idBase = editor._.elementsPath.idBase;
- var element = CKEDITOR.document.getById( idBase + '0' );
-
- if ( element )
- element.focus();
- }
- }
- };
-
- var emptyHtml = '<span class="cke_empty"> </span>';
-
- CKEDITOR.plugins.add( 'elementspath',
- {
- requires : [ 'selection' ],
-
- init : function( editor )
- {
- var spaceId = 'cke_path_' + editor.name;
- var spaceElement;
- var getSpaceElement = function()
- {
- if ( !spaceElement )
- spaceElement = CKEDITOR.document.getById( spaceId );
- return spaceElement;
- };
-
- var idBase = 'cke_elementspath_' + CKEDITOR.tools.getNextNumber() + '_';
-
- editor._.elementsPath = { idBase : idBase };
-
- editor.on( 'themeSpace', function( event )
- {
- if ( event.data.space == 'bottom' )
- {
- event.data.html +=
- '<span id="' + spaceId + '_label" class="cke_voice_label">' + editor.lang.elementsPath.eleLabel + '</span>' +
- '<div id="' + spaceId + '" class="cke_path" role="group" aria-labelledby="' + spaceId + '_label">' + emptyHtml + '</div>';
- }
- });
-
- editor.on( 'selectionChange', function( ev )
- {
- var env = CKEDITOR.env;
-
- var selection = ev.data.selection;
-
- var element = selection.getStartElement(),
- html = [],
- elementsList = this._.elementsPath.list = [];
-
- while ( element )
- {
- var index = elementsList.push( element ) - 1;
- var name;
- if ( element.getAttribute( '_cke_real_element_type' ) )
- name = element.getAttribute( '_cke_real_element_type' );
- else
- name = element.getName();
-
- // Use this variable to add conditional stuff to the
- // HTML (because we are doing it in reverse order... unshift).
- var extra = '';
-
- // Some browsers don't cancel key events in the keydown but in the
- // keypress.
- // TODO: Check if really needed for Gecko+Mac.
- if ( env.opera || ( env.gecko && env.mac ) )
- extra += ' onkeypress="return false;"';
-
- // With Firefox, we need to force the button to redraw, otherwise it
- // will remain in the focus state.
- if ( env.gecko )
- extra += ' onblur="this.style.cssText = this.style.cssText;"';
-
- var label = editor.lang.elementsPath.eleTitle.replace( /%1/, name );
- html.unshift(
- '<a' +
- ' id="', idBase, index, '"' +
- ' href="javascript:void(\'', name, '\')"' +
- ' tabindex="-1"' +
- ' title="', label, '"' +
- ( ( CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 ) ?
- ' onfocus="event.preventBubble();"' : '' ) +
- ' hidefocus="true" ' +
- ' onkeydown="return CKEDITOR._.elementsPath.keydown(\'', this.name, '\',', index, ', event);"' +
- extra ,
- ' onclick="return CKEDITOR._.elementsPath.click(\'', this.name, '\',', index, ');"',
- ' role="button" aria-labelledby="' + idBase + index + '_label">',
- name,
- '<span id="', idBase, index, '_label" class="cke_label">' + label + '</span>',
- '</a>' );
-
- if ( name == 'body' )
- break;
-
- element = element.getParent();
- }
-
- getSpaceElement().setHtml( html.join('') + emptyHtml );
- });
-
- editor.on( 'contentDomUnload', function()
- {
- getSpaceElement().setHtml( emptyHtml );
- });
-
- editor.addCommand( 'elementsPathFocus', commands.toolbarFocus );
- }
- });
-})();
-
-/**
- * Handles the click on an element in the element path.
- * @private
- */
-CKEDITOR._.elementsPath =
-{
- click : function( instanceName, elementIndex )
- {
- var editor = CKEDITOR.instances[ instanceName ];
- editor.focus();
-
- var element = editor._.elementsPath.list[ elementIndex ];
- editor.getSelection().selectElement( element );
-
- return false;
- },
-
- keydown : function( instanceName, elementIndex, ev )
- {
- var instance = CKEDITOR.ui.button._.instances[ elementIndex ];
- var editor = CKEDITOR.instances[ instanceName ];
- var idBase = editor._.elementsPath.idBase;
-
- var element;
-
- ev = new CKEDITOR.dom.event( ev );
-
- switch ( ev.getKeystroke() )
- {
- case 37 : // LEFT-ARROW
- case 9 : // TAB
- element = CKEDITOR.document.getById( idBase + ( elementIndex + 1 ) );
- if ( !element )
- element = CKEDITOR.document.getById( idBase + '0' );
- element.focus();
- return false;
-
- case 39 : // RIGHT-ARROW
- case CKEDITOR.SHIFT + 9 : // SHIFT + TAB
- element = CKEDITOR.document.getById( idBase + ( elementIndex - 1 ) );
- if ( !element )
- element = CKEDITOR.document.getById( idBase + ( editor._.elementsPath.list.length - 1 ) );
- element.focus();
- return false;
-
- case 27 : // ESC
- editor.focus();
- return false;
-
- case 13 : // ENTER // Opera
- case 32 : // SPACE
- this.click( instanceName, elementIndex );
- return false;
-
- //default :
- // alert( ev.getKeystroke() );
- }
- return true;
- }
-};
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview The "elementspath" plugin. It shows all elements in the DOM + * parent tree relative to the current selection in the editing area. + */ + +(function() +{ + var commands = + { + toolbarFocus : + { + exec : function( editor ) + { + var idBase = editor._.elementsPath.idBase; + var element = CKEDITOR.document.getById( idBase + '0' ); + + if ( element ) + element.focus(); + } + } + }; + + var emptyHtml = '<span class="cke_empty"> </span>'; + + CKEDITOR.plugins.add( 'elementspath', + { + requires : [ 'selection' ], + + init : function( editor ) + { + var spaceId = 'cke_path_' + editor.name; + var spaceElement; + var getSpaceElement = function() + { + if ( !spaceElement ) + spaceElement = CKEDITOR.document.getById( spaceId ); + return spaceElement; + }; + + var idBase = 'cke_elementspath_' + CKEDITOR.tools.getNextNumber() + '_'; + + editor._.elementsPath = { idBase : idBase, filters : [] }; + + editor.on( 'themeSpace', function( event ) + { + if ( event.data.space == 'bottom' ) + { + event.data.html += + '<span id="' + spaceId + '_label" class="cke_voice_label">' + editor.lang.elementsPath.eleLabel + '</span>' + + '<div id="' + spaceId + '" class="cke_path" role="group" aria-labelledby="' + spaceId + '_label">' + emptyHtml + '</div>'; + } + }); + + editor.on( 'selectionChange', function( ev ) + { + var env = CKEDITOR.env, + selection = ev.data.selection, + element = selection.getStartElement(), + html = [], + editor = ev.editor, + elementsList = editor._.elementsPath.list = [], + filters = editor._.elementsPath.filters; + + while ( element ) + { + var ignore = 0; + for ( var i = 0; i < filters.length; i++ ) + { + if ( filters[ i ]( element ) === false ) + { + ignore = 1; + break; + } + } + + if ( !ignore ) + { + var index = elementsList.push( element ) - 1; + var name; + if ( element.getAttribute( '_cke_real_element_type' ) ) + name = element.getAttribute( '_cke_real_element_type' ); + else + name = element.getName(); + + // Use this variable to add conditional stuff to the + // HTML (because we are doing it in reverse order... unshift). + var extra = ''; + + // Some browsers don't cancel key events in the keydown but in the + // keypress. + // TODO: Check if really needed for Gecko+Mac. + if ( env.opera || ( env.gecko && env.mac ) ) + extra += ' onkeypress="return false;"'; + + // With Firefox, we need to force the button to redraw, otherwise it + // will remain in the focus state. + if ( env.gecko ) + extra += ' onblur="this.style.cssText = this.style.cssText;"'; + + var label = editor.lang.elementsPath.eleTitle.replace( /%1/, name ); + html.unshift( + '<a' + + ' id="', idBase, index, '"' + + ' href="javascript:void(\'', name, '\')"' + + ' tabindex="-1"' + + ' title="', label, '"' + + ( ( CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 ) ? + ' onfocus="event.preventBubble();"' : '' ) + + ' hidefocus="true" ' + + ' onkeydown="return CKEDITOR._.elementsPath.keydown(\'', editor.name, '\',', index, ', event);"' + + extra , + ' onclick="return CKEDITOR._.elementsPath.click(\'', editor.name, '\',', index, ');"', + ' role="button" aria-labelledby="' + idBase + index + '_label">', + name, + '<span id="', idBase, index, '_label" class="cke_label">' + label + '</span>', + '</a>' ); + + } + + if ( name == 'body' ) + break; + + element = element.getParent(); + } + + getSpaceElement().setHtml( html.join('') + emptyHtml ); + }); + + editor.on( 'contentDomUnload', function() + { + getSpaceElement().setHtml( emptyHtml ); + }); + + editor.addCommand( 'elementsPathFocus', commands.toolbarFocus ); + } + }); +})(); + +/** + * Handles the click on an element in the element path. + * @private + */ +CKEDITOR._.elementsPath = +{ + click : function( instanceName, elementIndex ) + { + var editor = CKEDITOR.instances[ instanceName ]; + editor.focus(); + + var element = editor._.elementsPath.list[ elementIndex ]; + editor.getSelection().selectElement( element ); + + return false; + }, + + keydown : function( instanceName, elementIndex, ev ) + { + var instance = CKEDITOR.ui.button._.instances[ elementIndex ]; + var editor = CKEDITOR.instances[ instanceName ]; + var idBase = editor._.elementsPath.idBase; + + var element; + + ev = new CKEDITOR.dom.event( ev ); + + var rtl = editor.lang.dir == 'rtl'; + switch ( ev.getKeystroke() ) + { + case rtl ? 39 : 37 : // LEFT-ARROW + case 9 : // TAB + element = CKEDITOR.document.getById( idBase + ( elementIndex + 1 ) ); + if ( !element ) + element = CKEDITOR.document.getById( idBase + '0' ); + element.focus(); + return false; + + case rtl ? 37 : 39 : // RIGHT-ARROW + case CKEDITOR.SHIFT + 9 : // SHIFT + TAB + element = CKEDITOR.document.getById( idBase + ( elementIndex - 1 ) ); + if ( !element ) + element = CKEDITOR.document.getById( idBase + ( editor._.elementsPath.list.length - 1 ) ); + element.focus(); + return false; + + case 27 : // ESC + editor.focus(); + return false; + + case 13 : // ENTER // Opera + case 32 : // SPACE + this.click( instanceName, elementIndex ); + return false; + + //default : + // alert( ev.getKeystroke() ); + } + return true; + } +}; diff --git a/_source/plugins/enterkey/plugin.js b/_source/plugins/enterkey/plugin.js index 0127ede..bb57c61 100644 --- a/_source/plugins/enterkey/plugin.js +++ b/_source/plugins/enterkey/plugin.js @@ -1,329 +1,353 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-(function()
-{
- CKEDITOR.plugins.add( 'enterkey',
- {
- requires : [ 'keystrokes', 'indent' ],
-
- init : function( editor )
- {
- var specialKeys = editor.specialKeys;
- specialKeys[ 13 ] = enter;
- specialKeys[ CKEDITOR.SHIFT + 13 ] = shiftEnter;
- }
- });
-
- CKEDITOR.plugins.enterkey =
- {
- enterBlock : 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' );
-
- // Split the range.
- var splitInfo = range.splitBlock( blockTag );
-
- if ( !splitInfo )
- return;
-
- // Get the current blocks.
- var previousBlock = splitInfo.previousBlock,
- nextBlock = splitInfo.nextBlock;
-
- var isStartOfBlock = splitInfo.wasStartOfBlock,
- isEndOfBlock = splitInfo.wasEndOfBlock;
-
- var node;
-
- // 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() );
- }
-
- // 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 );
-
- // Move the selection to the end block.
- if ( nextBlock )
- range.moveToElementEditStart( nextBlock );
- }
- else
- {
-
- if ( isStartOfBlock && isEndOfBlock && previousBlock.is( 'li' ) )
- {
- editor.execCommand( 'outdent' );
- return;
- }
-
- var 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() ) )
- {
- // Otherwise, duplicate the previous block.
- newBlock = previousBlock.clone();
- }
- }
- else if ( nextBlock )
- newBlock = nextBlock.clone();
-
- if ( !newBlock )
- newBlock = doc.createElement( blockTag );
-
- // 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 ( element.equals( elementPath.block ) || element.equals( elementPath.blockLimit ) )
- break;
-
- if ( CKEDITOR.dtd.$removeEmpty[ element.getName() ] )
- {
- element = element.clone();
- newBlock.moveChildren( element );
- newBlock.append( element );
- }
- }
- }
-
- if ( !CKEDITOR.env.ie )
- newBlock.appendBogus();
-
- range.insertNode( newBlock );
-
- // 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() ) )
- {
- // Move the selection to the new block.
- range.moveToElementEditStart( isEndOfBlock ? previousBlock : newBlock );
- range.select();
- }
-
- // Move the selection to the new block.
- range.moveToElementEditStart( isStartOfBlock && !isEndOfBlock ? nextBlock : newBlock );
- }
-
- if ( !CKEDITOR.env.ie )
- {
- if ( nextBlock )
- {
- // If we have split the block, adds a temporary span at the
- // range position and scroll relatively to it.
- var tmpNode = doc.createElement( 'span' );
-
- // We need some content for Safari.
- tmpNode.setHtml( ' ' );
-
- 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();
- }
- }
-
- 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() );
-
- var startBlock = elementPath.block,
- startBlockTag = startBlock && elementPath.block.getName();
-
- var isPre = false;
-
- if ( !forceMode && startBlockTag == 'li' )
- {
- enterBlock( editor, mode, range, forceMode );
- return;
- }
-
- // 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 );
-
- // A text node is required by Gecko only to make the cursor blink.
- if ( CKEDITOR.env.gecko )
- doc.createText( '' ).insertAfter( startBlock );
-
- // IE has different behaviors regarding position.
- range.setStartAt( startBlock.getNext(), CKEDITOR.env.ie ? CKEDITOR.POSITION_BEFORE_START : CKEDITOR.POSITION_AFTER_START );
- }
- 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 );
-
- // 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 );
-
- // 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();
-
- // 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 );
-
- // Scroll into view, for non IE.
- if ( !CKEDITOR.env.ie )
- {
- 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(' ');
- }
- else
- dummy = doc.createElement( 'br' );
-
- dummy.insertBefore( lineBreak.getNext() );
- dummy.scrollIntoView();
- dummy.remove();
- }
- }
-
- // This collapse guarantees the cursor will be blinking.
- range.collapse( true );
-
- range.select( isPre );
- }
- };
-
- var plugin = CKEDITOR.plugins.enterkey,
- enterBr = plugin.enterBr,
- enterBlock = plugin.enterBlock,
- headerTagRegex = /^h[1-6]$/;
-
- 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 );
- }
-
- function enter( editor, mode, forceMode )
- {
- // Only effective within document.
- if ( editor.mode != 'wysiwyg' )
- return false;
-
- if ( !mode )
- mode = editor.config.enterMode;
-
- // 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, null, forceMode );
- else
- enterBlock( editor, mode, null, forceMode );
-
- }, 0 );
-
- return true;
- }
-
-
- function getRange( editor )
- {
- // Get the selection ranges.
- var ranges = editor.getSelection().getRanges();
-
- // Delete the contents of all ranges except the first one.
- for ( var i = ranges.length - 1 ; i > 0 ; i-- )
- {
- ranges[ i ].deleteContents();
- }
-
- // Return the first range.
- return ranges[ 0 ];
- }
-})();
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +(function() +{ + CKEDITOR.plugins.add( 'enterkey', + { + requires : [ 'keystrokes', 'indent' ], + + init : function( editor ) + { + var specialKeys = editor.specialKeys; + specialKeys[ 13 ] = enter; + specialKeys[ CKEDITOR.SHIFT + 13 ] = shiftEnter; + } + }); + + CKEDITOR.plugins.enterkey = + { + enterBlock : function( editor, mode, range, forceMode ) + { + // Get the range for the current selection. + range = range || getRange( editor ); + + var doc = range.document; + + // Exit the list when we're inside an empty list item block. (#5376) + if ( range.checkStartOfBlock() && range.checkEndOfBlock() ) + { + var path = new CKEDITOR.dom.elementPath( range.startContainer ), + block = path.block; + + if ( block && ( block.is( 'li' ) || block.getParent().is( 'li' ) ) ) + { + editor.execCommand( 'outdent' ); + return; + } + } + + // Determine the block element to be used. + var blockTag = ( mode == CKEDITOR.ENTER_DIV ? 'div' : 'p' ); + + // Split the range. + var splitInfo = range.splitBlock( blockTag ); + + if ( !splitInfo ) + return; + + // Get the current blocks. + var previousBlock = splitInfo.previousBlock, + nextBlock = splitInfo.nextBlock; + + var isStartOfBlock = splitInfo.wasStartOfBlock, + isEndOfBlock = splitInfo.wasEndOfBlock; + + var node; + + // 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() ); + } + + // 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 ); + + // Move the selection to the end block. + if ( nextBlock ) + range.moveToElementEditStart( nextBlock ); + } + else + { + var 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 ( previousBlock.is( 'li' ) || !headerTagRegex.test( previousBlock.getName() ) ) + { + // Otherwise, duplicate the previous block. + newBlock = previousBlock.clone(); + } + } + else if ( nextBlock ) + newBlock = nextBlock.clone(); + + if ( !newBlock ) + newBlock = doc.createElement( blockTag ); + // Force the enter block unless we're talking of a list item. + else if ( forceMode && !newBlock.is( 'li' ) ) + newBlock.renameNode( blockTag ); + + // 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 ( element.equals( elementPath.block ) || element.equals( elementPath.blockLimit ) ) + break; + + if ( CKEDITOR.dtd.$removeEmpty[ element.getName() ] ) + { + element = element.clone(); + newBlock.moveChildren( element ); + newBlock.append( element ); + } + } + } + + if ( !CKEDITOR.env.ie ) + newBlock.appendBogus(); + + range.insertNode( newBlock ); + + // 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() ) ) + { + // Move the selection to the new block. + range.moveToElementEditStart( isEndOfBlock ? previousBlock : newBlock ); + range.select(); + } + + // Move the selection to the new block. + range.moveToElementEditStart( isStartOfBlock && !isEndOfBlock ? nextBlock : newBlock ); + } + + if ( !CKEDITOR.env.ie ) + { + if ( nextBlock ) + { + // If we have split the block, adds a temporary span at the + // range position and scroll relatively to it. + var tmpNode = doc.createElement( 'span' ); + + // We need some content for Safari. + tmpNode.setHtml( ' ' ); + + 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(); + } + } + + 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() ); + + var startBlock = elementPath.block, + startBlockTag = startBlock && elementPath.block.getName(); + + var isPre = false; + + if ( !forceMode && startBlockTag == 'li' ) + { + enterBlock( editor, mode, range, forceMode ); + return; + } + + // 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 ); + + // A text node is required by Gecko only to make the cursor blink. + if ( CKEDITOR.env.gecko ) + doc.createText( '' ).insertAfter( startBlock ); + + // IE has different behaviors regarding position. + range.setStartAt( startBlock.getNext(), CKEDITOR.env.ie ? CKEDITOR.POSITION_BEFORE_START : CKEDITOR.POSITION_AFTER_START ); + } + else + { + var lineBreak; + + isPre = ( startBlockTag == 'pre' ); + + // Gecko prefers <br> as line-break inside <pre> (#4711). + if ( isPre && !CKEDITOR.env.gecko ) + lineBreak = doc.createText( CKEDITOR.env.ie ? '\r' : '\n' ); + else + lineBreak = doc.createElement( 'br' ); + + range.deleteContents(); + range.insertNode( lineBreak ); + + // 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 ); + + // 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(); + + // 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 ); + + // Scroll into view, for non IE. + if ( !CKEDITOR.env.ie ) + { + 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(' '); + } + else + dummy = doc.createElement( 'br' ); + + dummy.insertBefore( lineBreak.getNext() ); + dummy.scrollIntoView(); + dummy.remove(); + } + } + + // This collapse guarantees the cursor will be blinking. + range.collapse( true ); + + range.select( isPre ); + } + }; + + var plugin = CKEDITOR.plugins.enterkey, + enterBr = plugin.enterBr, + enterBlock = plugin.enterBlock, + headerTagRegex = /^h[1-6]$/; + + function shiftEnter( editor ) + { + // Only effective within document. + if ( editor.mode != 'wysiwyg' ) + return false; + + // On SHIFT+ENTER: + // 1. We want to enforce the mode to be respected, instead + // of cloning the current block. (#77) + // 2. Always perform a block break when inside <pre> (#5402). + if ( editor.getSelection().getStartElement().hasAscendant( 'pre', true ) ) + { + setTimeout( function() { enterBlock( editor, editor.config.enterMode, null, true ); }, 0 ); + return true; + } + else + return enter( editor, editor.config.shiftEnterMode, true ); + } + + function enter( editor, mode, forceMode ) + { + forceMode = editor.config.forceEnterMode || forceMode; + + // Only effective within document. + if ( editor.mode != 'wysiwyg' ) + return false; + + if ( !mode ) + mode = editor.config.enterMode; + + // 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, null, forceMode ); + else + enterBlock( editor, mode, null, forceMode ); + + }, 0 ); + + return true; + } + + + function getRange( editor ) + { + // Get the selection ranges. + var ranges = editor.getSelection().getRanges(); + + // Delete the contents of all ranges except the first one. + for ( var i = ranges.length - 1 ; i > 0 ; i-- ) + { + ranges[ i ].deleteContents(); + } + + // Return the first range. + return ranges[ 0 ]; + } +})(); diff --git a/_source/plugins/fakeobjects/plugin.js b/_source/plugins/fakeobjects/plugin.js index 14f0357..bb21062 100644 --- a/_source/plugins/fakeobjects/plugin.js +++ b/_source/plugins/fakeobjects/plugin.js @@ -1,120 +1,122 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-(function()
-{
- var htmlFilterRules =
- {
- elements :
- {
- $ : function( element )
- {
- var attributes = element.attributes,
- realHtml = attributes && attributes._cke_realelement,
- realFragment = realHtml && new CKEDITOR.htmlParser.fragment.fromHtml( decodeURIComponent( realHtml ) ),
- realElement = realFragment && realFragment.children[ 0 ];
-
- // If we have width/height in the element, we must move it into
- // the real element.
- if ( realElement && element.attributes._cke_resizable )
- {
- var style = element.attributes.style;
-
- if ( style )
- {
- // Get the width from the 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+)/i.exec( style );
- var height = match && match[1];
-
- if ( width )
- realElement.attributes.width = width;
-
- if ( height )
- realElement.attributes.height = height;
- }
- }
-
- return realElement;
- }
- }
- };
-
- CKEDITOR.plugins.add( 'fakeobjects',
- {
- requires : [ 'htmlwriter' ],
-
- afterInit : function( editor )
- {
- var dataProcessor = editor.dataProcessor,
- htmlFilter = dataProcessor && dataProcessor.htmlFilter;
-
- if ( htmlFilter )
- htmlFilter.addRules( htmlFilterRules );
- }
- });
-})();
-
-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;
-
- return this.document.createElement( 'img', { attributes : attributes } );
-};
-
-CKEDITOR.editor.prototype.createFakeParserElement = function( realElement, className, realElementType, isResizable )
-{
- var lang = this.lang.fakeobjects,
- html;
-
- var writer = new CKEDITOR.htmlParser.basicWriter();
- realElement.writeHtml( writer );
- 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
- };
-
- if ( realElementType )
- attributes._cke_real_element_type = realElementType;
-
- if ( isResizable )
- attributes._cke_resizable = isResizable;
-
- return new CKEDITOR.htmlParser.element( 'img', attributes );
-};
-
-CKEDITOR.editor.prototype.restoreRealElement = function( fakeElement )
-{
- if ( fakeElement.getAttribute( '_cke_real_node_type' ) != CKEDITOR.NODE_ELEMENT )
- return null;
-
- return CKEDITOR.dom.element.createFromHtml(
- decodeURIComponent( fakeElement.getAttribute( '_cke_realelement' ) ),
- this.document );
-};
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +(function() +{ + var htmlFilterRules = + { + elements : + { + $ : function( element ) + { + var attributes = element.attributes, + realHtml = attributes && attributes._cke_realelement, + realFragment = realHtml && new CKEDITOR.htmlParser.fragment.fromHtml( decodeURIComponent( realHtml ) ), + realElement = realFragment && realFragment.children[ 0 ]; + + // If we have width/height in the element, we must move it into + // the real element. + if ( realElement && element.attributes._cke_resizable ) + { + var style = element.attributes.style; + + if ( style ) + { + // Get the width from the 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+)/i.exec( style ); + var height = match && match[1]; + + if ( width ) + realElement.attributes.width = width; + + if ( height ) + realElement.attributes.height = height; + } + } + + return realElement; + } + } + }; + + CKEDITOR.plugins.add( 'fakeobjects', + { + requires : [ 'htmlwriter' ], + + afterInit : function( editor ) + { + var dataProcessor = editor.dataProcessor, + htmlFilter = dataProcessor && dataProcessor.htmlFilter; + + if ( htmlFilter ) + htmlFilter.addRules( htmlFilterRules ); + } + }); +})(); + +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, + align : realElement.getAttribute( 'align' ) || '' + }; + + if ( realElementType ) + attributes._cke_real_element_type = realElementType; + + if ( isResizable ) + attributes._cke_resizable = isResizable; + + return this.document.createElement( 'img', { attributes : attributes } ); +}; + +CKEDITOR.editor.prototype.createFakeParserElement = function( realElement, className, realElementType, isResizable ) +{ + var lang = this.lang.fakeobjects, + html; + + var writer = new CKEDITOR.htmlParser.basicWriter(); + realElement.writeHtml( writer ); + 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, + align : realElement.attributes.align || '' + }; + + if ( realElementType ) + attributes._cke_real_element_type = realElementType; + + if ( isResizable ) + attributes._cke_resizable = isResizable; + + return new CKEDITOR.htmlParser.element( 'img', attributes ); +}; + +CKEDITOR.editor.prototype.restoreRealElement = function( fakeElement ) +{ + 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 ae628cf..1651a21 100644 --- a/_source/plugins/filebrowser/plugin.js +++ b/_source/plugins/filebrowser/plugin.js @@ -1,479 +1,479 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-/**
- * @fileOverview The "filebrowser" plugin, it adds support for file uploads and
- * browsing.
- *
- * When file is selected inside of the file browser or uploaded, its url is
- * inserted automatically to a field, which is described in the 'filebrowser'
- * attribute. To specify field that should be updated, pass the tab id and
- * element id, separated with a colon.
- *
- * Example 1: (Browse)
- *
- * <pre>
- * {
- * type : 'button',
- * id : 'browse',
- * filebrowser : 'tabId:elementId',
- * label : editor.lang.common.browseServer
- * }
- * </pre>
- *
- * If you set the 'filebrowser' attribute on any element other than
- * 'fileButton', the 'Browse' action will be triggered.
- *
- * Example 2: (Quick Upload)
- *
- * <pre>
- * {
- * type : 'fileButton',
- * id : 'uploadButton',
- * filebrowser : 'tabId:elementId',
- * label : editor.lang.common.uploadSubmit,
- * 'for' : [ 'upload', 'upload' ]
- * }
- * </pre>
- *
- * If you set the 'filebrowser' attribute on a fileButton element, the
- * 'QuickUpload' action will be executed.
- *
- * Filebrowser plugin also supports more advanced configuration (through
- * javascript object).
- *
- * The following settings are supported:
- *
- * <pre>
- * [action] - Browse or QuickUpload
- * [target] - field to update, tabId:elementId
- * [params] - additional arguments to be passed to the server connector (optional)
- * [onSelect] - function to execute when file is selected/uploaded (optional)
- * [url] - the URL to be called (optional)
- * </pre>
- *
- * Example 3: (Quick Upload)
- *
- * <pre>
- * {
- * type : 'fileButton',
- * label : editor.lang.common.uploadSubmit,
- * id : 'buttonId',
- * filebrowser :
- * {
- * action : 'QuickUpload', //required
- * target : 'tab1:elementId', //required
- * params : //optional
- * {
- * type : 'Files',
- * currentFolder : '/folder/'
- * },
- * onSelect : function( fileUrl, errorMessage ) //optional
- * {
- * // Do not call the built-in selectFuntion
- * // return false;
- * }
- * },
- * 'for' : [ 'tab1', 'myFile' ]
- * }
- * </pre>
- *
- * Suppose we have a file element with id 'myFile', text field with id
- * 'elementId' and a fileButton. If filebowser.url is not specified explicitly,
- * form action will be set to 'filebrowser[DialogName]UploadUrl' or, if not
- * specified, to 'filebrowserUploadUrl'. Additional parameters from 'params'
- * object will be added to the query string. It is possible to create your own
- * uploadHandler and cancel the built-in updateTargetElement command.
- *
- * Example 4: (Browse)
- *
- * <pre>
- * {
- * type : 'button',
- * id : 'buttonId',
- * label : editor.lang.common.browseServer,
- * filebrowser :
- * {
- * action : 'Browse',
- * url : '/ckfinder/ckfinder.html&type=Images',
- * target : 'tab1:elementId'
- * }
- * }
- * </pre>
- *
- * In this example, after pressing a button, file browser will be opened in a
- * popup. If we don't specify filebrowser.url attribute,
- * 'filebrowser[DialogName]BrowseUrl' or 'filebrowserBrowseUrl' will be used.
- * After selecting a file in a file browser, an element with id 'elementId' will
- * be updated. Just like in the third example, a custom 'onSelect' function may be
- * defined.
- */
-( function()
-{
- /**
- * Adds (additional) arguments to given url.
- *
- * @param {String}
- * url The url.
- * @param {Object}
- * params Additional parameters.
- */
- function addQueryString( url, params )
- {
- var queryString = [];
-
- if ( !params )
- return url;
- else
- {
- for ( var i in params )
- queryString.push( i + "=" + encodeURIComponent( params[ i ] ) );
- }
-
- return url + ( ( url.indexOf( "?" ) != -1 ) ? "&" : "?" ) + queryString.join( "&" );
- }
-
- /**
- * Make a string's first character uppercase.
- *
- * @param {String}
- * str String.
- */
- function ucFirst( str )
- {
- str += '';
- var f = str.charAt( 0 ).toUpperCase();
- return f + str.substr( 1 );
- }
-
- /**
- * The onlick function assigned to the 'Browse Server' button. Opens the
- * file browser and updates target field when file is selected.
- *
- * @param {CKEDITOR.event}
- * evt The event object.
- */
- function browseServer( evt )
- {
- var dialog = this.getDialog();
- var editor = dialog.getParentEditor();
-
- editor._.filebrowserSe = this;
-
- var width = editor.config[ 'filebrowser' + ucFirst( dialog.getName() ) + 'WindowWidth' ]
- || editor.config.filebrowserWindowWidth || '80%';
- var height = editor.config[ 'filebrowser' + ucFirst( dialog.getName() ) + 'WindowHeight' ]
- || editor.config.filebrowserWindowHeight || '70%';
-
- var params = this.filebrowser.params || {};
- params.CKEditor = editor.name;
- params.CKEditorFuncNum = editor._.filebrowserFn;
- if ( !params.langCode )
- params.langCode = editor.langCode;
-
- var url = addQueryString( this.filebrowser.url, params );
- editor.popup( url, width, height );
- }
-
- /**
- * The onlick function assigned to the 'Upload' button. Makes the final
- * decision whether form is really submitted and updates target field when
- * file is uploaded.
- *
- * @param {CKEDITOR.event}
- * evt The event object.
- */
- function uploadFile( evt )
- {
- var dialog = this.getDialog();
- var editor = dialog.getParentEditor();
-
- editor._.filebrowserSe = this;
-
- // If user didn't select the file, stop the upload.
- if ( !dialog.getContentElement( this[ 'for' ][ 0 ], this[ 'for' ][ 1 ] ).getInputElement().$.value )
- return false;
-
- if ( !dialog.getContentElement( this[ 'for' ][ 0 ], this[ 'for' ][ 1 ] ).getAction() )
- return false;
-
- return true;
- }
-
- /**
- * Setups the file element.
- *
- * @param {CKEDITOR.ui.dialog.file}
- * fileInput The file element used during file upload.
- * @param {Object}
- * filebrowser Object containing filebrowser settings assigned to
- * the fileButton associated with this file element.
- */
- function setupFileElement( editor, fileInput, filebrowser )
- {
- var params = filebrowser.params || {};
- params.CKEditor = editor.name;
- params.CKEditorFuncNum = editor._.filebrowserFn;
- if ( !params.langCode )
- params.langCode = editor.langCode;
-
- fileInput.action = addQueryString( filebrowser.url, params );
- fileInput.filebrowser = filebrowser;
- }
-
- /**
- * Traverse through the content definition and attach filebrowser to
- * elements with 'filebrowser' attribute.
- *
- * @param String
- * dialogName Dialog name.
- * @param {CKEDITOR.dialog.dialogDefinitionObject}
- * definition Dialog definition.
- * @param {Array}
- * elements Array of {@link CKEDITOR.dialog.contentDefinition}
- * objects.
- */
- function attachFileBrowser( editor, dialogName, definition, elements )
- {
- var element, fileInput;
-
- for ( var i in elements )
- {
- element = elements[ i ];
-
- if ( element.type == 'hbox' || element.type == 'vbox' )
- attachFileBrowser( editor, dialogName, definition, element.children );
-
- if ( !element.filebrowser )
- continue;
-
- if ( typeof element.filebrowser == 'string' )
- {
- var fb =
- {
- action : ( element.type == 'fileButton' ) ? 'QuickUpload' : 'Browse',
- target : element.filebrowser
- };
- element.filebrowser = fb;
- }
-
- if ( element.filebrowser.action == 'Browse' )
- {
- var url = element.filebrowser.url || editor.config[ 'filebrowser' + ucFirst( dialogName ) + 'BrowseUrl' ]
- || editor.config.filebrowserBrowseUrl;
-
- if ( url )
- {
- element.onClick = browseServer;
- element.filebrowser.url = url;
- element.hidden = false;
- }
- }
- else if ( element.filebrowser.action == 'QuickUpload' && element[ 'for' ] )
- {
- url = element.filebrowser.url || editor.config[ 'filebrowser' + ucFirst( dialogName ) + 'UploadUrl' ]
- || editor.config.filebrowserUploadUrl;
-
- if ( url )
- {
- var onClick = element.onClick;
- element.onClick = function( evt )
- {
- // "element" here means the definition object, so we need to find the correct
- // button to scope the event call
- var sender = evt.sender;
- if ( onClick && onClick.call( sender, evt ) === false )
- return false;
-
- return uploadFile.call( sender, evt );
- };
-
- element.filebrowser.url = url;
- element.hidden = false;
- setupFileElement( editor, definition.getContents( element[ 'for' ][ 0 ] ).get( element[ 'for' ][ 1 ] ), element.filebrowser );
- }
- }
- }
- }
-
- /**
- * Updates the target element with the url of uploaded/selected file.
- *
- * @param {String}
- * url The url of a file.
- */
- function updateTargetElement( url, sourceElement )
- {
- var dialog = sourceElement.getDialog();
- var targetElement = sourceElement.filebrowser.target || null;
- url = url.replace( /#/g, '%23' );
-
- // If there is a reference to targetElement, update it.
- if ( targetElement )
- {
- var target = targetElement.split( ':' );
- var element = dialog.getContentElement( target[ 0 ], target[ 1 ] );
- if ( element )
- {
- element.setValue( url );
- dialog.selectPage( target[ 0 ] );
- }
- }
- }
-
- /**
- * Returns true if filebrowser is configured in one of the elements.
- *
- * @param {CKEDITOR.dialog.dialogDefinitionObject}
- * definition Dialog definition.
- * @param String
- * tabId The tab id where element(s) can be found.
- * @param String
- * elementId The element id (or ids, separated with a semicolon) to check.
- */
- function isConfigured( definition, tabId, elementId )
- {
- if ( elementId.indexOf( ";" ) !== -1 )
- {
- var ids = elementId.split( ";" );
- for ( var i = 0 ; i < ids.length ; i++ )
- {
- if ( isConfigured( definition, tabId, ids[i]) )
- return true;
- }
- return false;
- }
-
- var elementFileBrowser = definition.getContents( tabId ).get( elementId ).filebrowser;
- return ( elementFileBrowser && elementFileBrowser.url );
- }
-
- function setUrl( fileUrl, data )
- {
- var dialog = this._.filebrowserSe.getDialog(),
- targetInput = this._.filebrowserSe[ 'for' ],
- onSelect = this._.filebrowserSe.filebrowser.onSelect;
-
- if ( targetInput )
- dialog.getContentElement( targetInput[ 0 ], targetInput[ 1 ] ).reset();
-
- if ( typeof data == 'function' && data.call( this._.filebrowserSe ) === false )
- return;
-
- if ( onSelect && onSelect.call( this._.filebrowserSe, fileUrl, data ) === false )
- return;
-
- // The "data" argument may be used to pass the error message to the editor.
- if ( typeof data == 'string' && data )
- alert( data );
-
- if ( fileUrl )
- updateTargetElement( fileUrl, this._.filebrowserSe );
- }
-
- CKEDITOR.plugins.add( 'filebrowser',
- {
- init : function( editor, pluginPath )
- {
- editor._.filebrowserFn = CKEDITOR.tools.addFunction( setUrl, editor );
-
- CKEDITOR.on( 'dialogDefinition', function( evt )
- {
- var definition = evt.data.definition,
- element;
- // Associate filebrowser to elements with 'filebrowser' attribute.
- for ( var i in definition.contents )
- {
- element = definition.contents[ i ] ;
- attachFileBrowser( evt.editor, evt.data.name, definition, element.elements );
- if ( element.hidden && element.filebrowser )
- {
- element.hidden = !isConfigured( definition, element[ 'id' ], element.filebrowser );
- }
- }
- } );
- }
- } );
-
-} )();
-
-/**
- * The location of an external file browser, that should be launched when "Browse Server" button is pressed.
- * If configured, the "Browse Server" button will appear in Link, Image and Flash dialogs.
- * @see The <a href="http://docs.cksource.com/CKEditor_3.x/Developers_Guide/File_Browser_(Uploader)">File Browser/Uploader</a> documentation.
- * @name CKEDITOR.config.filebrowserBrowseUrl
- * @since 3.0
- * @type String
- * @default '' (empty string = disabled)
- * @example
- * config.filebrowserBrowseUrl = '/browser/browse.php';
- */
-
-/**
- * The location of a script that handles file uploads.
- * If set, the "Upload" tab will appear in "Link", "Image" and "Flash" dialogs.
- * @name CKEDITOR.config.filebrowserUploadUrl
- * @see The <a href="http://docs.cksource.com/CKEditor_3.x/Developers_Guide/File_Browser_(Uploader)">File Browser/Uploader</a> documentation.
- * @since 3.0
- * @type String
- * @default '' (empty string = disabled)
- * @example
- * config.filebrowserUploadUrl = '/uploader/upload.php';
- */
-
-/**
- * The location of an external file browser, that should be launched when "Browse Server" button is pressed in the Image dialog.
- * If not set, CKEditor will use {@link CKEDITOR.config.filebrowserBrowseUrl}.
- * @name CKEDITOR.config.filebrowserImageBrowseUrl
- * @since 3.0
- * @type String
- * @default '' (empty string = disabled)
- * @example
- * config.filebrowserImageBrowseUrl = '/browser/browse.php?type=Images';
- */
-
-/**
- * The location of an external file browser, that should be launched when "Browse Server" button is pressed in the Flash dialog.
- * If not set, CKEditor will use {@link CKEDITOR.config.filebrowserBrowseUrl}.
- * @name CKEDITOR.config.filebrowserFlashBrowseUrl
- * @since 3.0
- * @type String
- * @default '' (empty string = disabled)
- * @example
- * config.filebrowserFlashBrowseUrl = '/browser/browse.php?type=Flash';
- */
-
-/**
- * The location of a script that handles file uploads in the Image dialog.
- * If not set, CKEditor will use {@link CKEDITOR.config.filebrowserUploadUrl}.
- * @name CKEDITOR.config.filebrowserImageUploadUrl
- * @since 3.0
- * @type String
- * @default '' (empty string = disabled)
- * @example
- * config.filebrowserImageUploadUrl = '/uploader/upload.php?type=Images';
- */
-
-/**
- * The location of a script that handles file uploads in the Flash dialog.
- * If not set, CKEditor will use {@link CKEDITOR.config.filebrowserUploadUrl}.
- * @name CKEDITOR.config.filebrowserFlashUploadUrl
- * @since 3.0
- * @type String
- * @default '' (empty string = disabled)
- * @example
- * config.filebrowserFlashUploadUrl = '/uploader/upload.php?type=Flash';
- */
-
-/**
- * The location of an external file browser, that should be launched when "Browse Server" button is pressed in the Link tab of Image dialog.
- * If not set, CKEditor will use {@link CKEDITOR.config.filebrowserBrowseUrl}.
- * @name CKEDITOR.config.filebrowserImageBrowseLinkUrl
- * @since 3.2
- * @type String
- * @default '' (empty string = disabled)
- * @example
- * config.filebrowserImageBrowseLinkUrl = '/browser/browse.php';
- */
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview The "filebrowser" plugin, it adds support for file uploads and + * browsing. + * + * When file is selected inside of the file browser or uploaded, its url is + * inserted automatically to a field, which is described in the 'filebrowser' + * attribute. To specify field that should be updated, pass the tab id and + * element id, separated with a colon. + * + * Example 1: (Browse) + * + * <pre> + * { + * type : 'button', + * id : 'browse', + * filebrowser : 'tabId:elementId', + * label : editor.lang.common.browseServer + * } + * </pre> + * + * If you set the 'filebrowser' attribute on any element other than + * 'fileButton', the 'Browse' action will be triggered. + * + * Example 2: (Quick Upload) + * + * <pre> + * { + * type : 'fileButton', + * id : 'uploadButton', + * filebrowser : 'tabId:elementId', + * label : editor.lang.common.uploadSubmit, + * 'for' : [ 'upload', 'upload' ] + * } + * </pre> + * + * If you set the 'filebrowser' attribute on a fileButton element, the + * 'QuickUpload' action will be executed. + * + * Filebrowser plugin also supports more advanced configuration (through + * javascript object). + * + * The following settings are supported: + * + * <pre> + * [action] - Browse or QuickUpload + * [target] - field to update, tabId:elementId + * [params] - additional arguments to be passed to the server connector (optional) + * [onSelect] - function to execute when file is selected/uploaded (optional) + * [url] - the URL to be called (optional) + * </pre> + * + * Example 3: (Quick Upload) + * + * <pre> + * { + * type : 'fileButton', + * label : editor.lang.common.uploadSubmit, + * id : 'buttonId', + * filebrowser : + * { + * action : 'QuickUpload', //required + * target : 'tab1:elementId', //required + * params : //optional + * { + * type : 'Files', + * currentFolder : '/folder/' + * }, + * onSelect : function( fileUrl, errorMessage ) //optional + * { + * // Do not call the built-in selectFuntion + * // return false; + * } + * }, + * 'for' : [ 'tab1', 'myFile' ] + * } + * </pre> + * + * Suppose we have a file element with id 'myFile', text field with id + * 'elementId' and a fileButton. If filebowser.url is not specified explicitly, + * form action will be set to 'filebrowser[DialogName]UploadUrl' or, if not + * specified, to 'filebrowserUploadUrl'. Additional parameters from 'params' + * object will be added to the query string. It is possible to create your own + * uploadHandler and cancel the built-in updateTargetElement command. + * + * Example 4: (Browse) + * + * <pre> + * { + * type : 'button', + * id : 'buttonId', + * label : editor.lang.common.browseServer, + * filebrowser : + * { + * action : 'Browse', + * url : '/ckfinder/ckfinder.html&type=Images', + * target : 'tab1:elementId' + * } + * } + * </pre> + * + * In this example, after pressing a button, file browser will be opened in a + * popup. If we don't specify filebrowser.url attribute, + * 'filebrowser[DialogName]BrowseUrl' or 'filebrowserBrowseUrl' will be used. + * After selecting a file in a file browser, an element with id 'elementId' will + * be updated. Just like in the third example, a custom 'onSelect' function may be + * defined. + */ +( function() +{ + /** + * Adds (additional) arguments to given url. + * + * @param {String} + * url The url. + * @param {Object} + * params Additional parameters. + */ + function addQueryString( url, params ) + { + var queryString = []; + + if ( !params ) + return url; + else + { + for ( var i in params ) + queryString.push( i + "=" + encodeURIComponent( params[ i ] ) ); + } + + return url + ( ( url.indexOf( "?" ) != -1 ) ? "&" : "?" ) + queryString.join( "&" ); + } + + /** + * Make a string's first character uppercase. + * + * @param {String} + * str String. + */ + function ucFirst( str ) + { + str += ''; + var f = str.charAt( 0 ).toUpperCase(); + return f + str.substr( 1 ); + } + + /** + * The onlick function assigned to the 'Browse Server' button. Opens the + * file browser and updates target field when file is selected. + * + * @param {CKEDITOR.event} + * evt The event object. + */ + function browseServer( evt ) + { + var dialog = this.getDialog(); + var editor = dialog.getParentEditor(); + + editor._.filebrowserSe = this; + + var width = editor.config[ 'filebrowser' + ucFirst( dialog.getName() ) + 'WindowWidth' ] + || editor.config.filebrowserWindowWidth || '80%'; + var height = editor.config[ 'filebrowser' + ucFirst( dialog.getName() ) + 'WindowHeight' ] + || editor.config.filebrowserWindowHeight || '70%'; + + var params = this.filebrowser.params || {}; + params.CKEditor = editor.name; + params.CKEditorFuncNum = editor._.filebrowserFn; + if ( !params.langCode ) + params.langCode = editor.langCode; + + var url = addQueryString( this.filebrowser.url, params ); + editor.popup( url, width, height ); + } + + /** + * The onlick function assigned to the 'Upload' button. Makes the final + * decision whether form is really submitted and updates target field when + * file is uploaded. + * + * @param {CKEDITOR.event} + * evt The event object. + */ + function uploadFile( evt ) + { + var dialog = this.getDialog(); + var editor = dialog.getParentEditor(); + + editor._.filebrowserSe = this; + + // If user didn't select the file, stop the upload. + if ( !dialog.getContentElement( this[ 'for' ][ 0 ], this[ 'for' ][ 1 ] ).getInputElement().$.value ) + return false; + + if ( !dialog.getContentElement( this[ 'for' ][ 0 ], this[ 'for' ][ 1 ] ).getAction() ) + return false; + + return true; + } + + /** + * Setups the file element. + * + * @param {CKEDITOR.ui.dialog.file} + * fileInput The file element used during file upload. + * @param {Object} + * filebrowser Object containing filebrowser settings assigned to + * the fileButton associated with this file element. + */ + function setupFileElement( editor, fileInput, filebrowser ) + { + var params = filebrowser.params || {}; + params.CKEditor = editor.name; + params.CKEditorFuncNum = editor._.filebrowserFn; + if ( !params.langCode ) + params.langCode = editor.langCode; + + fileInput.action = addQueryString( filebrowser.url, params ); + fileInput.filebrowser = filebrowser; + } + + /** + * Traverse through the content definition and attach filebrowser to + * elements with 'filebrowser' attribute. + * + * @param String + * dialogName Dialog name. + * @param {CKEDITOR.dialog.dialogDefinitionObject} + * definition Dialog definition. + * @param {Array} + * elements Array of {@link CKEDITOR.dialog.contentDefinition} + * objects. + */ + function attachFileBrowser( editor, dialogName, definition, elements ) + { + var element, fileInput; + + for ( var i in elements ) + { + element = elements[ i ]; + + if ( element.type == 'hbox' || element.type == 'vbox' ) + attachFileBrowser( editor, dialogName, definition, element.children ); + + if ( !element.filebrowser ) + continue; + + if ( typeof element.filebrowser == 'string' ) + { + var fb = + { + action : ( element.type == 'fileButton' ) ? 'QuickUpload' : 'Browse', + target : element.filebrowser + }; + element.filebrowser = fb; + } + + if ( element.filebrowser.action == 'Browse' ) + { + var url = element.filebrowser.url || editor.config[ 'filebrowser' + ucFirst( dialogName ) + 'BrowseUrl' ] + || editor.config.filebrowserBrowseUrl; + + if ( url ) + { + element.onClick = browseServer; + element.filebrowser.url = url; + element.hidden = false; + } + } + else if ( element.filebrowser.action == 'QuickUpload' && element[ 'for' ] ) + { + url = element.filebrowser.url || editor.config[ 'filebrowser' + ucFirst( dialogName ) + 'UploadUrl' ] + || editor.config.filebrowserUploadUrl; + + if ( url ) + { + var onClick = element.onClick; + element.onClick = function( evt ) + { + // "element" here means the definition object, so we need to find the correct + // button to scope the event call + var sender = evt.sender; + if ( onClick && onClick.call( sender, evt ) === false ) + return false; + + return uploadFile.call( sender, evt ); + }; + + element.filebrowser.url = url; + element.hidden = false; + setupFileElement( editor, definition.getContents( element[ 'for' ][ 0 ] ).get( element[ 'for' ][ 1 ] ), element.filebrowser ); + } + } + } + } + + /** + * Updates the target element with the url of uploaded/selected file. + * + * @param {String} + * url The url of a file. + */ + function updateTargetElement( url, sourceElement ) + { + var dialog = sourceElement.getDialog(); + var targetElement = sourceElement.filebrowser.target || null; + url = url.replace( /#/g, '%23' ); + + // If there is a reference to targetElement, update it. + if ( targetElement ) + { + var target = targetElement.split( ':' ); + var element = dialog.getContentElement( target[ 0 ], target[ 1 ] ); + if ( element ) + { + element.setValue( url ); + dialog.selectPage( target[ 0 ] ); + } + } + } + + /** + * Returns true if filebrowser is configured in one of the elements. + * + * @param {CKEDITOR.dialog.dialogDefinitionObject} + * definition Dialog definition. + * @param String + * tabId The tab id where element(s) can be found. + * @param String + * elementId The element id (or ids, separated with a semicolon) to check. + */ + function isConfigured( definition, tabId, elementId ) + { + if ( elementId.indexOf( ";" ) !== -1 ) + { + var ids = elementId.split( ";" ); + for ( var i = 0 ; i < ids.length ; i++ ) + { + if ( isConfigured( definition, tabId, ids[i]) ) + return true; + } + return false; + } + + var elementFileBrowser = definition.getContents( tabId ).get( elementId ).filebrowser; + return ( elementFileBrowser && elementFileBrowser.url ); + } + + function setUrl( fileUrl, data ) + { + var dialog = this._.filebrowserSe.getDialog(), + targetInput = this._.filebrowserSe[ 'for' ], + onSelect = this._.filebrowserSe.filebrowser.onSelect; + + if ( targetInput ) + dialog.getContentElement( targetInput[ 0 ], targetInput[ 1 ] ).reset(); + + if ( typeof data == 'function' && data.call( this._.filebrowserSe ) === false ) + return; + + if ( onSelect && onSelect.call( this._.filebrowserSe, fileUrl, data ) === false ) + return; + + // The "data" argument may be used to pass the error message to the editor. + if ( typeof data == 'string' && data ) + alert( data ); + + if ( fileUrl ) + updateTargetElement( fileUrl, this._.filebrowserSe ); + } + + CKEDITOR.plugins.add( 'filebrowser', + { + init : function( editor, pluginPath ) + { + editor._.filebrowserFn = CKEDITOR.tools.addFunction( setUrl, editor ); + } + } ); + + CKEDITOR.on( 'dialogDefinition', function( evt ) + { + var definition = evt.data.definition, + element; + // Associate filebrowser to elements with 'filebrowser' attribute. + for ( var i in definition.contents ) + { + element = definition.contents[ i ] ; + attachFileBrowser( evt.editor, evt.data.name, definition, element.elements ); + if ( element.hidden && element.filebrowser ) + { + element.hidden = !isConfigured( definition, element[ 'id' ], element.filebrowser ); + } + } + } ); + +} )(); + +/** + * The location of an external file browser, that should be launched when "Browse Server" button is pressed. + * If configured, the "Browse Server" button will appear in Link, Image and Flash dialogs. + * @see The <a href="http://docs.cksource.com/CKEditor_3.x/Developers_Guide/File_Browser_(Uploader)">File Browser/Uploader</a> documentation. + * @name CKEDITOR.config.filebrowserBrowseUrl + * @since 3.0 + * @type String + * @default '' (empty string = disabled) + * @example + * config.filebrowserBrowseUrl = '/browser/browse.php'; + */ + +/** + * The location of a script that handles file uploads. + * If set, the "Upload" tab will appear in "Link", "Image" and "Flash" dialogs. + * @name CKEDITOR.config.filebrowserUploadUrl + * @see The <a href="http://docs.cksource.com/CKEditor_3.x/Developers_Guide/File_Browser_(Uploader)">File Browser/Uploader</a> documentation. + * @since 3.0 + * @type String + * @default '' (empty string = disabled) + * @example + * config.filebrowserUploadUrl = '/uploader/upload.php'; + */ + +/** + * The location of an external file browser, that should be launched when "Browse Server" button is pressed in the Image dialog. + * If not set, CKEditor will use {@link CKEDITOR.config.filebrowserBrowseUrl}. + * @name CKEDITOR.config.filebrowserImageBrowseUrl + * @since 3.0 + * @type String + * @default '' (empty string = disabled) + * @example + * config.filebrowserImageBrowseUrl = '/browser/browse.php?type=Images'; + */ + +/** + * The location of an external file browser, that should be launched when "Browse Server" button is pressed in the Flash dialog. + * If not set, CKEditor will use {@link CKEDITOR.config.filebrowserBrowseUrl}. + * @name CKEDITOR.config.filebrowserFlashBrowseUrl + * @since 3.0 + * @type String + * @default '' (empty string = disabled) + * @example + * config.filebrowserFlashBrowseUrl = '/browser/browse.php?type=Flash'; + */ + +/** + * The location of a script that handles file uploads in the Image dialog. + * If not set, CKEditor will use {@link CKEDITOR.config.filebrowserUploadUrl}. + * @name CKEDITOR.config.filebrowserImageUploadUrl + * @since 3.0 + * @type String + * @default '' (empty string = disabled) + * @example + * config.filebrowserImageUploadUrl = '/uploader/upload.php?type=Images'; + */ + +/** + * The location of a script that handles file uploads in the Flash dialog. + * If not set, CKEditor will use {@link CKEDITOR.config.filebrowserUploadUrl}. + * @name CKEDITOR.config.filebrowserFlashUploadUrl + * @since 3.0 + * @type String + * @default '' (empty string = disabled) + * @example + * config.filebrowserFlashUploadUrl = '/uploader/upload.php?type=Flash'; + */ + +/** + * The location of an external file browser, that should be launched when "Browse Server" button is pressed in the Link tab of Image dialog. + * If not set, CKEditor will use {@link CKEDITOR.config.filebrowserBrowseUrl}. + * @name CKEDITOR.config.filebrowserImageBrowseLinkUrl + * @since 3.2 + * @type String + * @default '' (empty string = disabled) + * @example + * config.filebrowserImageBrowseLinkUrl = '/browser/browse.php'; + */ diff --git a/_source/plugins/find/dialogs/find.js b/_source/plugins/find/dialogs/find.js index 70a082c..fd999ee 100644 --- a/_source/plugins/find/dialogs/find.js +++ b/_source/plugins/find/dialogs/find.js @@ -1,853 +1,867 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-(function()
-{
- function guardDomWalkerNonEmptyTextNode( node )
- {
- return ( node.type == CKEDITOR.NODE_TEXT && node.getLength() > 0 );
- }
-
- /**
- * Elements which break characters been considered as sequence.
- */
- function checkCharactersBoundary ( node )
- {
- var dtd = CKEDITOR.dtd;
- return node.isBlockBoundary(
- CKEDITOR.tools.extend( {}, dtd.$empty, dtd.$nonEditable ) );
- }
-
- /**
- * Get the cursor object which represent both current character and it's dom
- * position thing.
- */
- var cursorStep = function()
- {
- return {
- textNode : this.textNode,
- offset : this.offset,
- character : this.textNode ?
- this.textNode.getText().charAt( this.offset ) : null,
- hitMatchBoundary : this._.matchBoundary
- };
- };
-
- var pages = [ 'find', 'replace' ],
- fieldsMapping = [
- [ 'txtFindFind', 'txtFindReplace' ],
- [ 'txtFindCaseChk', 'txtReplaceCaseChk' ],
- [ 'txtFindWordChk', 'txtReplaceWordChk' ],
- [ 'txtFindCyclic', 'txtReplaceCyclic' ] ];
-
- /**
- * Synchronize corresponding filed values between 'replace' and 'find' pages.
- * @param {String} currentPageId The page id which receive values.
- */
- function syncFieldsBetweenTabs( currentPageId )
- {
- var sourceIndex, targetIndex,
- sourceField, targetField;
-
- sourceIndex = currentPageId === 'find' ? 1 : 0;
- targetIndex = 1 - sourceIndex;
- var i, l = fieldsMapping.length;
- for ( i = 0 ; i < l ; i++ )
- {
- sourceField = this.getContentElement( pages[ sourceIndex ],
- fieldsMapping[ i ][ sourceIndex ] );
- targetField = this.getContentElement( pages[ targetIndex ],
- fieldsMapping[ i ][ targetIndex ] );
-
- targetField.setValue( sourceField.getValue() );
- }
- }
-
- var findDialog = function( editor, startupPage )
- {
- // Style object for highlights.
- var highlightStyle = new CKEDITOR.style( editor.config.find_highlight );
-
- /**
- * Iterator which walk through the specified range char by char. By
- * default the walking will not stop at the character boundaries, until
- * the end of the range is encountered.
- * @param { CKEDITOR.dom.range } range
- * @param {Boolean} matchWord Whether the walking will stop at character boundary.
- */
- var characterWalker = function( range , matchWord )
- {
- var walker =
- new CKEDITOR.dom.walker( range );
- walker[ matchWord ? 'guard' : 'evaluator' ] =
- guardDomWalkerNonEmptyTextNode;
- walker.breakOnFalse = true;
-
- this._ = {
- matchWord : matchWord,
- walker : walker,
- matchBoundary : false
- };
- };
-
- characterWalker.prototype = {
- next : function()
- {
- return this.move();
- },
-
- back : function()
- {
- return this.move( true );
- },
-
- move : function( rtl )
- {
- var currentTextNode = this.textNode;
- // Already at the end of document, no more character available.
- if ( currentTextNode === null )
- return cursorStep.call( this );
-
- this._.matchBoundary = false;
-
- // There are more characters in the text node, step forward.
- if ( currentTextNode
- && rtl
- && this.offset > 0 )
- {
- this.offset--;
- return cursorStep.call( this );
- }
- else if ( currentTextNode
- && this.offset < currentTextNode.getLength() - 1 )
- {
- this.offset++;
- return cursorStep.call( this );
- }
- else
- {
- currentTextNode = null;
- // At the end of the text node, walking foward for the next.
- while ( !currentTextNode )
- {
- currentTextNode =
- this._.walker[ rtl ? 'previous' : 'next' ].call( this._.walker );
-
- // Stop searching if we're need full word match OR
- // already reach document end.
- if ( this._.matchWord && !currentTextNode
- ||this._.walker._.end )
- break;
-
- // Marking as match character boundaries.
- if ( !currentTextNode
- && checkCharactersBoundary( this._.walker.current ) )
- this._.matchBoundary = true;
-
- }
- // Found a fresh text node.
- this.textNode = currentTextNode;
- if ( currentTextNode )
- this.offset = rtl ? currentTextNode.getLength() - 1 : 0;
- else
- this.offset = 0;
- }
-
- return cursorStep.call( this );
- }
-
- };
-
- /**
- * A range of cursors which represent a trunk of characters which try to
- * match, it has the same length as the pattern string.
- */
- var characterRange = function( characterWalker, rangeLength )
- {
- this._ = {
- walker : characterWalker,
- cursors : [],
- rangeLength : rangeLength,
- highlightRange : null,
- isMatched : false
- };
- };
-
- characterRange.prototype = {
- /**
- * Translate this range to {@link CKEDITOR.dom.range}
- */
- toDomRange : function()
- {
- var cursors = this._.cursors;
- if ( cursors.length < 1 )
- return null;
-
- var first = cursors[0],
- last = cursors[ cursors.length - 1 ],
- range = new CKEDITOR.dom.range( editor.document );
-
- range.setStart( first.textNode, first.offset );
- range.setEnd( last.textNode, last.offset + 1 );
- return range;
- },
- /**
- * Reflect the latest changes from dom range.
- */
- updateFromDomRange : function( domRange )
- {
- var cursor,
- walker = new characterWalker( domRange );
- this._.cursors = [];
- do
- {
- cursor = walker.next();
- if ( cursor.character )
- this._.cursors.push( cursor );
- }
- while ( cursor.character );
- this._.rangeLength = this._.cursors.length;
- },
-
- setMatched : function()
- {
- this._.isMatched = true;
- },
-
- clearMatched : function()
- {
- this._.isMatched = false;
- },
-
- isMatched : function()
- {
- return this._.isMatched;
- },
-
- /**
- * Hightlight the current matched chunk of text.
- */
- highlight : function()
- {
- // Do not apply if nothing is found.
- if ( this._.cursors.length < 1 )
- return;
-
- // Remove the previous highlight if there's one.
- if ( this._.highlightRange )
- this.removeHighlight();
-
- // Apply the highlight.
- var range = this.toDomRange();
- highlightStyle.applyToRange( range );
- this._.highlightRange = range;
-
- // Scroll the editor to the highlighted area.
- var element = range.startContainer;
- if ( element.type != CKEDITOR.NODE_ELEMENT )
- element = element.getParent();
- element.scrollIntoView();
-
- // Update the character cursors.
- this.updateFromDomRange( range );
- },
-
- /**
- * Remove highlighted find result.
- */
- removeHighlight : function()
- {
- if ( !this._.highlightRange )
- return;
-
- highlightStyle.removeFromRange( this._.highlightRange );
- this.updateFromDomRange( this._.highlightRange );
- this._.highlightRange = null;
- },
-
- moveBack : function()
- {
- var retval = this._.walker.back(),
- cursors = this._.cursors;
-
- if ( retval.hitMatchBoundary )
- this._.cursors = cursors = [];
-
- cursors.unshift( retval );
- if ( cursors.length > this._.rangeLength )
- cursors.pop();
-
- return retval;
- },
-
- moveNext : function()
- {
- var retval = this._.walker.next(),
- cursors = this._.cursors;
-
- // Clear the cursors queue if we've crossed a match boundary.
- if ( retval.hitMatchBoundary )
- this._.cursors = cursors = [];
-
- cursors.push( retval );
- if ( cursors.length > this._.rangeLength )
- cursors.shift();
-
- return retval;
- },
-
- getEndCharacter : function()
- {
- var cursors = this._.cursors;
- if ( cursors.length < 1 )
- return null;
-
- return cursors[ cursors.length - 1 ].character;
- },
-
- getNextCharacterRange : function( maxLength )
- {
- var lastCursor,
- nextRangeWalker,
- cursors = this._.cursors;
-
- if ( ( lastCursor = cursors[ cursors.length - 1 ] ) )
- nextRangeWalker = new characterWalker( getRangeAfterCursor( lastCursor ) );
- // In case it's an empty range (no cursors), figure out next range from walker (#4951).
- else
- nextRangeWalker = this._.walker;
-
- return new characterRange( nextRangeWalker, maxLength );
- },
-
- getCursors : function()
- {
- return this._.cursors;
- }
- };
-
-
- // The remaining document range after the character cursor.
- function getRangeAfterCursor( cursor , inclusive )
- {
- var range = new CKEDITOR.dom.range();
- range.setStart( cursor.textNode,
- ( inclusive ? cursor.offset : cursor.offset + 1 ) );
- range.setEndAt( editor.document.getBody(),
- CKEDITOR.POSITION_BEFORE_END );
- return range;
- }
-
- // The document range before the character cursor.
- function getRangeBeforeCursor( cursor )
- {
- var range = new CKEDITOR.dom.range();
- range.setStartAt( editor.document.getBody(),
- CKEDITOR.POSITION_AFTER_START );
- range.setEnd( cursor.textNode, cursor.offset );
- return range;
- }
-
- var KMP_NOMATCH = 0,
- KMP_ADVANCED = 1,
- KMP_MATCHED = 2;
- /**
- * Examination the occurrence of a word which implement KMP algorithm.
- */
- var kmpMatcher = function( pattern, ignoreCase )
- {
- var overlap = [ -1 ];
- if ( ignoreCase )
- pattern = pattern.toLowerCase();
- for ( var i = 0 ; i < pattern.length ; i++ )
- {
- overlap.push( overlap[i] + 1 );
- while ( overlap[ i + 1 ] > 0
- && pattern.charAt( i ) != pattern
- .charAt( overlap[ i + 1 ] - 1 ) )
- overlap[ i + 1 ] = overlap[ overlap[ i + 1 ] - 1 ] + 1;
- }
-
- this._ = {
- overlap : overlap,
- state : 0,
- ignoreCase : !!ignoreCase,
- pattern : pattern
- };
- };
-
- kmpMatcher.prototype =
- {
- feedCharacter : function( c )
- {
- if ( this._.ignoreCase )
- c = c.toLowerCase();
-
- while ( true )
- {
- if ( c == this._.pattern.charAt( this._.state ) )
- {
- this._.state++;
- if ( this._.state == this._.pattern.length )
- {
- this._.state = 0;
- return KMP_MATCHED;
- }
- return KMP_ADVANCED;
- }
- else if ( !this._.state )
- return KMP_NOMATCH;
- else
- this._.state = this._.overlap[ this._.state ];
- }
-
- return null;
- },
-
- reset : function()
- {
- this._.state = 0;
- }
- };
-
- var wordSeparatorRegex =
- /[.,"'?!;: \u0085\u00a0\u1680\u280e\u2028\u2029\u202f\u205f\u3000]/;
-
- var isWordSeparator = function( c )
- {
- if ( !c )
- return true;
- var code = c.charCodeAt( 0 );
- return ( code >= 9 && code <= 0xd )
- || ( code >= 0x2000 && code <= 0x200a )
- || wordSeparatorRegex.test( c );
- };
-
- var finder = {
- searchRange : null,
- matchRange : null,
- find : function( pattern, matchCase, matchWord, matchCyclic, highlightMatched, cyclicRerun )
- {
- if ( !this.matchRange )
- this.matchRange =
- new characterRange(
- new characterWalker( this.searchRange ),
- pattern.length );
- else
- {
- this.matchRange.removeHighlight();
- this.matchRange = this.matchRange.getNextCharacterRange( pattern.length );
- }
-
- var matcher = new kmpMatcher( pattern, !matchCase ),
- matchState = KMP_NOMATCH,
- character = '%';
-
- while ( character !== null )
- {
- this.matchRange.moveNext();
- while ( ( character = this.matchRange.getEndCharacter() ) )
- {
- matchState = matcher.feedCharacter( character );
- if ( matchState == KMP_MATCHED )
- break;
- if ( this.matchRange.moveNext().hitMatchBoundary )
- matcher.reset();
- }
-
- if ( matchState == KMP_MATCHED )
- {
- if ( matchWord )
- {
- var cursors = this.matchRange.getCursors(),
- tail = cursors[ cursors.length - 1 ],
- head = cursors[ 0 ];
-
- var headWalker = new characterWalker( getRangeBeforeCursor( head ), true ),
- tailWalker = new characterWalker( getRangeAfterCursor( tail ), true );
-
- if ( ! ( isWordSeparator( headWalker.back().character )
- && isWordSeparator( tailWalker.next().character ) ) )
- continue;
- }
- this.matchRange.setMatched();
- if ( highlightMatched !== false )
- this.matchRange.highlight();
- return true;
- }
- }
-
- this.matchRange.clearMatched();
- this.matchRange.removeHighlight();
- // Clear current session and restart with the default search
- // range.
- // Re-run the finding once for cyclic.(#3517)
- if ( matchCyclic && !cyclicRerun )
- {
- this.searchRange = getSearchRange( true );
- this.matchRange = null;
- return arguments.callee.apply( this,
- Array.prototype.slice.call( arguments ).concat( [ true ] ) );
- }
-
- return false;
- },
-
- /**
- * Record how much replacement occurred toward one replacing.
- */
- replaceCounter : 0,
-
- replace : function( dialog, pattern, newString, matchCase, matchWord,
- matchCyclic , isReplaceAll )
- {
- // Successiveness of current replace/find.
- var result = false;
-
- // 1. Perform the replace when there's already a match here.
- // 2. Otherwise perform the find but don't replace it immediately.
- if ( this.matchRange && this.matchRange.isMatched()
- && !this.matchRange._.isReplaced )
- {
- // Turn off highlight for a while when saving snapshots.
- this.matchRange.removeHighlight();
- var domRange = this.matchRange.toDomRange();
- var text = editor.document.createText( newString );
- if ( !isReplaceAll )
- {
- // Save undo snaps before and after the replacement.
- var selection = editor.getSelection();
- selection.selectRanges( [ domRange ] );
- editor.fire( 'saveSnapshot' );
- }
- domRange.deleteContents();
- domRange.insertNode( text );
- if ( !isReplaceAll )
- {
- selection.selectRanges( [ domRange ] );
- editor.fire( 'saveSnapshot' );
- }
- this.matchRange.updateFromDomRange( domRange );
- if ( !isReplaceAll )
- this.matchRange.highlight();
- this.matchRange._.isReplaced = true;
- this.replaceCounter++;
- result = true;
- }
- else
- result = this.find( pattern, matchCase, matchWord, matchCyclic, !isReplaceAll );
-
- return result;
- }
- };
-
- /**
- * The range in which find/replace happened, receive from user
- * selection prior.
- */
- function getSearchRange( isDefault )
- {
- var searchRange,
- sel = editor.getSelection(),
- body = editor.document.getBody();
- if ( sel && !isDefault )
- {
- searchRange = sel.getRanges()[ 0 ].clone();
- searchRange.collapse( true );
- }
- else
- {
- searchRange = new CKEDITOR.dom.range();
- searchRange.setStartAt( body, CKEDITOR.POSITION_AFTER_START );
- }
- searchRange.setEndAt( body, CKEDITOR.POSITION_BEFORE_END );
- return searchRange;
- }
-
- return {
- title : editor.lang.findAndReplace.title,
- resizable : CKEDITOR.DIALOG_RESIZE_NONE,
- minWidth : 350,
- minHeight : 165,
- buttons : [ CKEDITOR.dialog.cancelButton ], //Cancel button only.
- contents : [
- {
- id : 'find',
- label : editor.lang.findAndReplace.find,
- title : editor.lang.findAndReplace.find,
- accessKey : '',
- elements : [
- {
- type : 'hbox',
- widths : [ '230px', '90px' ],
- children :
- [
- {
- type : 'text',
- id : 'txtFindFind',
- label : editor.lang.findAndReplace.findWhat,
- isChanged : false,
- labelLayout : 'horizontal',
- accessKey : 'F'
- },
- {
- type : 'button',
- align : 'left',
- style : 'width:100%',
- label : editor.lang.findAndReplace.find,
- onClick : function()
- {
- var dialog = this.getDialog();
- if ( !finder.find( dialog.getValueOf( 'find', 'txtFindFind' ),
- dialog.getValueOf( 'find', 'txtFindCaseChk' ),
- dialog.getValueOf( 'find', 'txtFindWordChk' ),
- dialog.getValueOf( 'find', 'txtFindCyclic' ) ) )
- alert( editor.lang.findAndReplace
- .notFoundMsg );
- }
- }
- ]
- },
- {
- type : 'vbox',
- padding : 0,
- children :
- [
- {
- type : 'checkbox',
- id : 'txtFindCaseChk',
- isChanged : false,
- style : 'margin-top:28px',
- label : editor.lang.findAndReplace.matchCase
- },
- {
- type : 'checkbox',
- id : 'txtFindWordChk',
- isChanged : false,
- label : editor.lang.findAndReplace.matchWord
- },
- {
- type : 'checkbox',
- id : 'txtFindCyclic',
- isChanged : false,
- 'default' : true,
- label : editor.lang.findAndReplace.matchCyclic
- }
- ]
- }
- ]
- },
- {
- id : 'replace',
- label : editor.lang.findAndReplace.replace,
- accessKey : 'M',
- elements : [
- {
- type : 'hbox',
- widths : [ '230px', '90px' ],
- children :
- [
- {
- type : 'text',
- id : 'txtFindReplace',
- label : editor.lang.findAndReplace.findWhat,
- isChanged : false,
- labelLayout : 'horizontal',
- accessKey : 'F'
- },
- {
- type : 'button',
- align : 'left',
- style : 'width:100%',
- label : editor.lang.findAndReplace.replace,
- onClick : function()
- {
- var dialog = this.getDialog();
- if ( !finder.replace( dialog,
- dialog.getValueOf( 'replace', 'txtFindReplace' ),
- dialog.getValueOf( 'replace', 'txtReplace' ),
- dialog.getValueOf( 'replace', 'txtReplaceCaseChk' ),
- dialog.getValueOf( 'replace', 'txtReplaceWordChk' ),
- dialog.getValueOf( 'replace', 'txtReplaceCyclic' ) ) )
- alert( editor.lang.findAndReplace
- .notFoundMsg );
- }
- }
- ]
- },
- {
- type : 'hbox',
- widths : [ '230px', '90px' ],
- children :
- [
- {
- type : 'text',
- id : 'txtReplace',
- label : editor.lang.findAndReplace.replaceWith,
- isChanged : false,
- labelLayout : 'horizontal',
- accessKey : 'R'
- },
- {
- type : 'button',
- align : 'left',
- style : 'width:100%',
- label : editor.lang.findAndReplace.replaceAll,
- isChanged : false,
- onClick : function()
- {
- var dialog = this.getDialog();
- var replaceNums;
-
- finder.replaceCounter = 0;
-
- // Scope to full document.
- finder.searchRange = getSearchRange( true );
- if ( finder.matchRange )
- {
- finder.matchRange.removeHighlight();
- finder.matchRange = null;
- }
- editor.fire( 'saveSnapshot' );
- while ( finder.replace( dialog,
- dialog.getValueOf( 'replace', 'txtFindReplace' ),
- dialog.getValueOf( 'replace', 'txtReplace' ),
- dialog.getValueOf( 'replace', 'txtReplaceCaseChk' ),
- dialog.getValueOf( 'replace', 'txtReplaceWordChk' ),
- false, true ) )
- { /*jsl:pass*/ }
-
- if ( finder.replaceCounter )
- {
- alert( editor.lang.findAndReplace.replaceSuccessMsg.replace( /%1/, finder.replaceCounter ) );
- editor.fire( 'saveSnapshot' );
- }
- else
- alert( editor.lang.findAndReplace.notFoundMsg );
- }
- }
- ]
- },
- {
- type : 'vbox',
- padding : 0,
- children :
- [
- {
- type : 'checkbox',
- id : 'txtReplaceCaseChk',
- isChanged : false,
- label : editor.lang.findAndReplace
- .matchCase
- },
- {
- type : 'checkbox',
- id : 'txtReplaceWordChk',
- isChanged : false,
- label : editor.lang.findAndReplace
- .matchWord
- },
- {
- type : 'checkbox',
- id : 'txtReplaceCyclic',
- isChanged : false,
- 'default' : true,
- label : editor.lang.findAndReplace
- .matchCyclic
- }
- ]
- }
- ]
- }
- ],
- onLoad : function()
- {
- var dialog = this;
-
- //keep track of the current pattern field in use.
- var patternField, wholeWordChkField;
-
- //Ignore initial page select on dialog show
- var isUserSelect = false;
- this.on('hide', function()
- {
- isUserSelect = false;
- } );
- this.on('show', function()
- {
- isUserSelect = true;
- } );
-
- this.selectPage = CKEDITOR.tools.override( this.selectPage, function( originalFunc )
- {
- return function( pageId )
- {
- originalFunc.call( dialog, pageId );
-
- var currPage = dialog._.tabs[ pageId ];
- var patternFieldInput, patternFieldId, wholeWordChkFieldId;
- patternFieldId = pageId === 'find' ? 'txtFindFind' : 'txtFindReplace';
- wholeWordChkFieldId = pageId === 'find' ? 'txtFindWordChk' : 'txtReplaceWordChk';
-
- patternField = dialog.getContentElement( pageId,
- patternFieldId );
- wholeWordChkField = dialog.getContentElement( pageId,
- wholeWordChkFieldId );
-
- // prepare for check pattern text filed 'keyup' event
- if ( !currPage.initialized )
- {
- patternFieldInput = CKEDITOR.document
- .getById( patternField._.inputId );
- currPage.initialized = true;
- }
-
- if ( isUserSelect )
- // synchronize fields on tab switch.
- syncFieldsBetweenTabs.call( this, pageId );
- };
- } );
-
- },
- onShow : function()
- {
- // Establish initial searching start position.
- finder.searchRange = getSearchRange();
-
- this.selectPage( startupPage );
- },
- onHide : function()
- {
- if ( finder.matchRange && finder.matchRange.isMatched() )
- {
- finder.matchRange.removeHighlight();
- editor.focus();
- editor.getSelection().selectRanges(
- [ finder.matchRange.toDomRange() ] );
- }
-
- // Clear current session before dialog close
- delete finder.matchRange;
- },
- onFocus : function()
- {
- if ( startupPage == 'replace' )
- return this.getContentElement( 'replace', 'txtFindReplace' );
- else
- return this.getContentElement( 'find', 'txtFindFind' );
- }
- };
- };
-
- CKEDITOR.dialog.add( 'find', function( editor )
- {
- return findDialog( editor, 'find' );
- });
-
- CKEDITOR.dialog.add( 'replace', function( editor )
- {
- return findDialog( editor, 'replace' );
- });
-})();
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +(function() +{ + function nonEmptyText( node ) + { + return ( node.type == CKEDITOR.NODE_TEXT && node.getLength() > 0 ); + } + + /** + * Elements which break characters been considered as sequence. + */ + function nonCharactersBoundary ( node ) + { + return !( node.type == CKEDITOR.NODE_ELEMENT && node.isBlockBoundary( + CKEDITOR.tools.extend( {}, CKEDITOR.dtd.$empty, CKEDITOR.dtd.$nonEditable ) ) ); + } + + /** + * Get the cursor object which represent both current character and it's dom + * position thing. + */ + var cursorStep = function() + { + return { + textNode : this.textNode, + offset : this.offset, + character : this.textNode ? + this.textNode.getText().charAt( this.offset ) : null, + hitMatchBoundary : this._.matchBoundary + }; + }; + + var pages = [ 'find', 'replace' ], + fieldsMapping = [ + [ 'txtFindFind', 'txtFindReplace' ], + [ 'txtFindCaseChk', 'txtReplaceCaseChk' ], + [ 'txtFindWordChk', 'txtReplaceWordChk' ], + [ 'txtFindCyclic', 'txtReplaceCyclic' ] ]; + + /** + * Synchronize corresponding filed values between 'replace' and 'find' pages. + * @param {String} currentPageId The page id which receive values. + */ + function syncFieldsBetweenTabs( currentPageId ) + { + var sourceIndex, targetIndex, + sourceField, targetField; + + sourceIndex = currentPageId === 'find' ? 1 : 0; + targetIndex = 1 - sourceIndex; + var i, l = fieldsMapping.length; + for ( i = 0 ; i < l ; i++ ) + { + sourceField = this.getContentElement( pages[ sourceIndex ], + fieldsMapping[ i ][ sourceIndex ] ); + targetField = this.getContentElement( pages[ targetIndex ], + fieldsMapping[ i ][ targetIndex ] ); + + targetField.setValue( sourceField.getValue() ); + } + } + + var findDialog = function( editor, startupPage ) + { + // Style object for highlights: (#5018) + // 1. Defined as full match style to avoid compromising ordinary text color styles. + // 2. Must be apply onto inner-most text to avoid conflicting with ordinary text color styles visually. + var highlightStyle = new CKEDITOR.style( CKEDITOR.tools.extend( { fullMatch : true, childRule : function(){ return false; } }, + editor.config.find_highlight ) ); + + /** + * Iterator which walk through the specified range char by char. By + * default the walking will not stop at the character boundaries, until + * the end of the range is encountered. + * @param { CKEDITOR.dom.range } range + * @param {Boolean} matchWord Whether the walking will stop at character boundary. + */ + var characterWalker = function( range , matchWord ) + { + var walker = + new CKEDITOR.dom.walker( range ); + walker.guard = matchWord ? nonCharactersBoundary : null; + walker[ 'evaluator' ] = nonEmptyText; + walker.breakOnFalse = true; + + this._ = { + matchWord : matchWord, + walker : walker, + matchBoundary : false + }; + }; + + characterWalker.prototype = { + next : function() + { + return this.move(); + }, + + back : function() + { + return this.move( true ); + }, + + move : function( rtl ) + { + var currentTextNode = this.textNode; + // Already at the end of document, no more character available. + if ( currentTextNode === null ) + return cursorStep.call( this ); + + this._.matchBoundary = false; + + // There are more characters in the text node, step forward. + if ( currentTextNode + && rtl + && this.offset > 0 ) + { + this.offset--; + return cursorStep.call( this ); + } + else if ( currentTextNode + && this.offset < currentTextNode.getLength() - 1 ) + { + this.offset++; + return cursorStep.call( this ); + } + else + { + currentTextNode = null; + // At the end of the text node, walking foward for the next. + while ( !currentTextNode ) + { + currentTextNode = + this._.walker[ rtl ? 'previous' : 'next' ].call( this._.walker ); + + // Stop searching if we're need full word match OR + // already reach document end. + if ( this._.matchWord && !currentTextNode + ||this._.walker._.end ) + break; + + // Marking as match character boundaries. + if ( !currentTextNode + && !nonCharactersBoundary( this._.walker.current ) ) + this._.matchBoundary = true; + + } + // Found a fresh text node. + this.textNode = currentTextNode; + if ( currentTextNode ) + this.offset = rtl ? currentTextNode.getLength() - 1 : 0; + else + this.offset = 0; + } + + return cursorStep.call( this ); + } + + }; + + /** + * A range of cursors which represent a trunk of characters which try to + * match, it has the same length as the pattern string. + */ + var characterRange = function( characterWalker, rangeLength ) + { + this._ = { + walker : characterWalker, + cursors : [], + rangeLength : rangeLength, + highlightRange : null, + isMatched : false + }; + }; + + characterRange.prototype = { + /** + * Translate this range to {@link CKEDITOR.dom.range} + */ + toDomRange : function() + { + var range = new CKEDITOR.dom.range( editor.document ); + var cursors = this._.cursors; + if ( cursors.length < 1 ) + { + var textNode = this._.walker.textNode; + if ( textNode ) + range.setStartAfter( textNode ); + else + return null; + } + else + { + var first = cursors[0], + last = cursors[ cursors.length - 1 ]; + + range.setStart( first.textNode, first.offset ); + range.setEnd( last.textNode, last.offset + 1 ); + } + + return range; + }, + /** + * Reflect the latest changes from dom range. + */ + updateFromDomRange : function( domRange ) + { + var cursor, + walker = new characterWalker( domRange ); + this._.cursors = []; + do + { + cursor = walker.next(); + if ( cursor.character ) + this._.cursors.push( cursor ); + } + while ( cursor.character ); + this._.rangeLength = this._.cursors.length; + }, + + setMatched : function() + { + this._.isMatched = true; + }, + + clearMatched : function() + { + this._.isMatched = false; + }, + + isMatched : function() + { + return this._.isMatched; + }, + + /** + * Hightlight the current matched chunk of text. + */ + highlight : function() + { + // Do not apply if nothing is found. + if ( this._.cursors.length < 1 ) + return; + + // Remove the previous highlight if there's one. + if ( this._.highlightRange ) + this.removeHighlight(); + + // Apply the highlight. + var range = this.toDomRange(); + highlightStyle.applyToRange( range ); + this._.highlightRange = range; + + // Scroll the editor to the highlighted area. + var element = range.startContainer; + if ( element.type != CKEDITOR.NODE_ELEMENT ) + element = element.getParent(); + element.scrollIntoView(); + + // Update the character cursors. + this.updateFromDomRange( range ); + }, + + /** + * Remove highlighted find result. + */ + removeHighlight : function() + { + if ( !this._.highlightRange ) + return; + + highlightStyle.removeFromRange( this._.highlightRange ); + this.updateFromDomRange( this._.highlightRange ); + this._.highlightRange = null; + }, + + moveBack : function() + { + var retval = this._.walker.back(), + cursors = this._.cursors; + + if ( retval.hitMatchBoundary ) + this._.cursors = cursors = []; + + cursors.unshift( retval ); + if ( cursors.length > this._.rangeLength ) + cursors.pop(); + + return retval; + }, + + moveNext : function() + { + var retval = this._.walker.next(), + cursors = this._.cursors; + + // Clear the cursors queue if we've crossed a match boundary. + if ( retval.hitMatchBoundary ) + this._.cursors = cursors = []; + + cursors.push( retval ); + if ( cursors.length > this._.rangeLength ) + cursors.shift(); + + return retval; + }, + + getEndCharacter : function() + { + var cursors = this._.cursors; + if ( cursors.length < 1 ) + return null; + + return cursors[ cursors.length - 1 ].character; + }, + + getNextCharacterRange : function( maxLength ) + { + var lastCursor, + nextRangeWalker, + cursors = this._.cursors; + + if ( ( lastCursor = cursors[ cursors.length - 1 ] ) && lastCursor.textNode ) + nextRangeWalker = new characterWalker( getRangeAfterCursor( lastCursor ) ); + // In case it's an empty range (no cursors), figure out next range from walker (#4951). + else + nextRangeWalker = this._.walker; + + return new characterRange( nextRangeWalker, maxLength ); + }, + + getCursors : function() + { + return this._.cursors; + } + }; + + + // The remaining document range after the character cursor. + function getRangeAfterCursor( cursor , inclusive ) + { + var range = new CKEDITOR.dom.range(); + range.setStart( cursor.textNode, + ( inclusive ? cursor.offset : cursor.offset + 1 ) ); + range.setEndAt( editor.document.getBody(), + CKEDITOR.POSITION_BEFORE_END ); + return range; + } + + // The document range before the character cursor. + function getRangeBeforeCursor( cursor ) + { + var range = new CKEDITOR.dom.range(); + range.setStartAt( editor.document.getBody(), + CKEDITOR.POSITION_AFTER_START ); + range.setEnd( cursor.textNode, cursor.offset ); + return range; + } + + var KMP_NOMATCH = 0, + KMP_ADVANCED = 1, + KMP_MATCHED = 2; + /** + * Examination the occurrence of a word which implement KMP algorithm. + */ + var kmpMatcher = function( pattern, ignoreCase ) + { + var overlap = [ -1 ]; + if ( ignoreCase ) + pattern = pattern.toLowerCase(); + for ( var i = 0 ; i < pattern.length ; i++ ) + { + overlap.push( overlap[i] + 1 ); + while ( overlap[ i + 1 ] > 0 + && pattern.charAt( i ) != pattern + .charAt( overlap[ i + 1 ] - 1 ) ) + overlap[ i + 1 ] = overlap[ overlap[ i + 1 ] - 1 ] + 1; + } + + this._ = { + overlap : overlap, + state : 0, + ignoreCase : !!ignoreCase, + pattern : pattern + }; + }; + + kmpMatcher.prototype = + { + feedCharacter : function( c ) + { + if ( this._.ignoreCase ) + c = c.toLowerCase(); + + while ( true ) + { + if ( c == this._.pattern.charAt( this._.state ) ) + { + this._.state++; + if ( this._.state == this._.pattern.length ) + { + this._.state = 0; + return KMP_MATCHED; + } + return KMP_ADVANCED; + } + else if ( !this._.state ) + return KMP_NOMATCH; + else + this._.state = this._.overlap[ this._.state ]; + } + + return null; + }, + + reset : function() + { + this._.state = 0; + } + }; + + var wordSeparatorRegex = + /[.,"'?!;: \u0085\u00a0\u1680\u280e\u2028\u2029\u202f\u205f\u3000]/; + + var isWordSeparator = function( c ) + { + if ( !c ) + return true; + var code = c.charCodeAt( 0 ); + return ( code >= 9 && code <= 0xd ) + || ( code >= 0x2000 && code <= 0x200a ) + || wordSeparatorRegex.test( c ); + }; + + var finder = { + searchRange : null, + matchRange : null, + find : function( pattern, matchCase, matchWord, matchCyclic, highlightMatched, cyclicRerun ) + { + if ( !this.matchRange ) + this.matchRange = + new characterRange( + new characterWalker( this.searchRange ), + pattern.length ); + else + { + this.matchRange.removeHighlight(); + this.matchRange = this.matchRange.getNextCharacterRange( pattern.length ); + } + + var matcher = new kmpMatcher( pattern, !matchCase ), + matchState = KMP_NOMATCH, + character = '%'; + + while ( character !== null ) + { + this.matchRange.moveNext(); + while ( ( character = this.matchRange.getEndCharacter() ) ) + { + matchState = matcher.feedCharacter( character ); + if ( matchState == KMP_MATCHED ) + break; + if ( this.matchRange.moveNext().hitMatchBoundary ) + matcher.reset(); + } + + if ( matchState == KMP_MATCHED ) + { + if ( matchWord ) + { + var cursors = this.matchRange.getCursors(), + tail = cursors[ cursors.length - 1 ], + head = cursors[ 0 ]; + + var headWalker = new characterWalker( getRangeBeforeCursor( head ), true ), + tailWalker = new characterWalker( getRangeAfterCursor( tail ), true ); + + if ( ! ( isWordSeparator( headWalker.back().character ) + && isWordSeparator( tailWalker.next().character ) ) ) + continue; + } + this.matchRange.setMatched(); + if ( highlightMatched !== false ) + this.matchRange.highlight(); + return true; + } + } + + this.matchRange.clearMatched(); + this.matchRange.removeHighlight(); + // Clear current session and restart with the default search + // range. + // Re-run the finding once for cyclic.(#3517) + if ( matchCyclic && !cyclicRerun ) + { + this.searchRange = getSearchRange( true ); + this.matchRange = null; + return arguments.callee.apply( this, + Array.prototype.slice.call( arguments ).concat( [ true ] ) ); + } + + return false; + }, + + /** + * Record how much replacement occurred toward one replacing. + */ + replaceCounter : 0, + + replace : function( dialog, pattern, newString, matchCase, matchWord, + matchCyclic , isReplaceAll ) + { + // Successiveness of current replace/find. + var result = false; + + // 1. Perform the replace when there's already a match here. + // 2. Otherwise perform the find but don't replace it immediately. + if ( this.matchRange && this.matchRange.isMatched() + && !this.matchRange._.isReplaced ) + { + // Turn off highlight for a while when saving snapshots. + this.matchRange.removeHighlight(); + var domRange = this.matchRange.toDomRange(); + var text = editor.document.createText( newString ); + if ( !isReplaceAll ) + { + // Save undo snaps before and after the replacement. + var selection = editor.getSelection(); + selection.selectRanges( [ domRange ] ); + editor.fire( 'saveSnapshot' ); + } + domRange.deleteContents(); + domRange.insertNode( text ); + if ( !isReplaceAll ) + { + selection.selectRanges( [ domRange ] ); + editor.fire( 'saveSnapshot' ); + } + this.matchRange.updateFromDomRange( domRange ); + if ( !isReplaceAll ) + this.matchRange.highlight(); + this.matchRange._.isReplaced = true; + this.replaceCounter++; + result = true; + } + else + result = this.find( pattern, matchCase, matchWord, matchCyclic, !isReplaceAll ); + + return result; + } + }; + + /** + * The range in which find/replace happened, receive from user + * selection prior. + */ + function getSearchRange( isDefault ) + { + var searchRange, + sel = editor.getSelection(), + body = editor.document.getBody(); + if ( sel && !isDefault ) + { + searchRange = sel.getRanges()[ 0 ].clone(); + searchRange.collapse( true ); + } + else + { + searchRange = new CKEDITOR.dom.range(); + searchRange.setStartAt( body, CKEDITOR.POSITION_AFTER_START ); + } + searchRange.setEndAt( body, CKEDITOR.POSITION_BEFORE_END ); + return searchRange; + } + + return { + title : editor.lang.findAndReplace.title, + resizable : CKEDITOR.DIALOG_RESIZE_NONE, + minWidth : 350, + minHeight : 165, + buttons : [ CKEDITOR.dialog.cancelButton ], //Cancel button only. + contents : [ + { + id : 'find', + label : editor.lang.findAndReplace.find, + title : editor.lang.findAndReplace.find, + accessKey : '', + elements : [ + { + type : 'hbox', + widths : [ '230px', '90px' ], + children : + [ + { + type : 'text', + id : 'txtFindFind', + label : editor.lang.findAndReplace.findWhat, + isChanged : false, + labelLayout : 'horizontal', + accessKey : 'F' + }, + { + type : 'button', + align : 'left', + style : 'width:100%', + label : editor.lang.findAndReplace.find, + onClick : function() + { + var dialog = this.getDialog(); + if ( !finder.find( dialog.getValueOf( 'find', 'txtFindFind' ), + dialog.getValueOf( 'find', 'txtFindCaseChk' ), + dialog.getValueOf( 'find', 'txtFindWordChk' ), + dialog.getValueOf( 'find', 'txtFindCyclic' ) ) ) + alert( editor.lang.findAndReplace + .notFoundMsg ); + } + } + ] + }, + { + type : 'vbox', + padding : 0, + children : + [ + { + type : 'checkbox', + id : 'txtFindCaseChk', + isChanged : false, + style : 'margin-top:28px', + label : editor.lang.findAndReplace.matchCase + }, + { + type : 'checkbox', + id : 'txtFindWordChk', + isChanged : false, + label : editor.lang.findAndReplace.matchWord + }, + { + type : 'checkbox', + id : 'txtFindCyclic', + isChanged : false, + 'default' : true, + label : editor.lang.findAndReplace.matchCyclic + } + ] + } + ] + }, + { + id : 'replace', + label : editor.lang.findAndReplace.replace, + accessKey : 'M', + elements : [ + { + type : 'hbox', + widths : [ '230px', '90px' ], + children : + [ + { + type : 'text', + id : 'txtFindReplace', + label : editor.lang.findAndReplace.findWhat, + isChanged : false, + labelLayout : 'horizontal', + accessKey : 'F' + }, + { + type : 'button', + align : 'left', + style : 'width:100%', + label : editor.lang.findAndReplace.replace, + onClick : function() + { + var dialog = this.getDialog(); + if ( !finder.replace( dialog, + dialog.getValueOf( 'replace', 'txtFindReplace' ), + dialog.getValueOf( 'replace', 'txtReplace' ), + dialog.getValueOf( 'replace', 'txtReplaceCaseChk' ), + dialog.getValueOf( 'replace', 'txtReplaceWordChk' ), + dialog.getValueOf( 'replace', 'txtReplaceCyclic' ) ) ) + alert( editor.lang.findAndReplace + .notFoundMsg ); + } + } + ] + }, + { + type : 'hbox', + widths : [ '230px', '90px' ], + children : + [ + { + type : 'text', + id : 'txtReplace', + label : editor.lang.findAndReplace.replaceWith, + isChanged : false, + labelLayout : 'horizontal', + accessKey : 'R' + }, + { + type : 'button', + align : 'left', + style : 'width:100%', + label : editor.lang.findAndReplace.replaceAll, + isChanged : false, + onClick : function() + { + var dialog = this.getDialog(); + var replaceNums; + + finder.replaceCounter = 0; + + // Scope to full document. + finder.searchRange = getSearchRange( true ); + if ( finder.matchRange ) + { + finder.matchRange.removeHighlight(); + finder.matchRange = null; + } + editor.fire( 'saveSnapshot' ); + while ( finder.replace( dialog, + dialog.getValueOf( 'replace', 'txtFindReplace' ), + dialog.getValueOf( 'replace', 'txtReplace' ), + dialog.getValueOf( 'replace', 'txtReplaceCaseChk' ), + dialog.getValueOf( 'replace', 'txtReplaceWordChk' ), + false, true ) ) + { /*jsl:pass*/ } + + if ( finder.replaceCounter ) + { + alert( editor.lang.findAndReplace.replaceSuccessMsg.replace( /%1/, finder.replaceCounter ) ); + editor.fire( 'saveSnapshot' ); + } + else + alert( editor.lang.findAndReplace.notFoundMsg ); + } + } + ] + }, + { + type : 'vbox', + padding : 0, + children : + [ + { + type : 'checkbox', + id : 'txtReplaceCaseChk', + isChanged : false, + label : editor.lang.findAndReplace + .matchCase + }, + { + type : 'checkbox', + id : 'txtReplaceWordChk', + isChanged : false, + label : editor.lang.findAndReplace + .matchWord + }, + { + type : 'checkbox', + id : 'txtReplaceCyclic', + isChanged : false, + 'default' : true, + label : editor.lang.findAndReplace + .matchCyclic + } + ] + } + ] + } + ], + onLoad : function() + { + var dialog = this; + + //keep track of the current pattern field in use. + var patternField, wholeWordChkField; + + //Ignore initial page select on dialog show + var isUserSelect = false; + this.on('hide', function() + { + isUserSelect = false; + } ); + this.on('show', function() + { + isUserSelect = true; + } ); + + this.selectPage = CKEDITOR.tools.override( this.selectPage, function( originalFunc ) + { + return function( pageId ) + { + originalFunc.call( dialog, pageId ); + + var currPage = dialog._.tabs[ pageId ]; + var patternFieldInput, patternFieldId, wholeWordChkFieldId; + patternFieldId = pageId === 'find' ? 'txtFindFind' : 'txtFindReplace'; + wholeWordChkFieldId = pageId === 'find' ? 'txtFindWordChk' : 'txtReplaceWordChk'; + + patternField = dialog.getContentElement( pageId, + patternFieldId ); + wholeWordChkField = dialog.getContentElement( pageId, + wholeWordChkFieldId ); + + // prepare for check pattern text filed 'keyup' event + if ( !currPage.initialized ) + { + patternFieldInput = CKEDITOR.document + .getById( patternField._.inputId ); + currPage.initialized = true; + } + + if ( isUserSelect ) + // synchronize fields on tab switch. + syncFieldsBetweenTabs.call( this, pageId ); + }; + } ); + + }, + onShow : function() + { + // Establish initial searching start position. + finder.searchRange = getSearchRange(); + + this.selectPage( startupPage ); + }, + onHide : function() + { + var range; + if ( finder.matchRange && finder.matchRange.isMatched() ) + { + finder.matchRange.removeHighlight(); + editor.focus(); + + range = finder.matchRange.toDomRange(); + if ( range ) + editor.getSelection().selectRanges( [ range ] ); + } + + // Clear current session before dialog close + delete finder.matchRange; + }, + onFocus : function() + { + if ( startupPage == 'replace' ) + return this.getContentElement( 'replace', 'txtFindReplace' ); + else + return this.getContentElement( 'find', 'txtFindFind' ); + } + }; + }; + + CKEDITOR.dialog.add( 'find', function( editor ) + { + return findDialog( editor, 'find' ); + }); + + CKEDITOR.dialog.add( 'replace', function( editor ) + { + return findDialog( editor, 'replace' ); + }); +})(); diff --git a/_source/plugins/flash/dialogs/flash.js b/_source/plugins/flash/dialogs/flash.js index 5634229..2ad07c9 100644 --- a/_source/plugins/flash/dialogs/flash.js +++ b/_source/plugins/flash/dialogs/flash.js @@ -1,690 +1,698 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-(function()
-{
- /*
- * It is possible to set things in three different places.
- * 1. As attributes in the object tag.
- * 2. As param tags under the object tag.
- * 3. As attributes in the embed tag.
- * It is possible for a single attribute to be present in more than one place.
- * So let's define a mapping between a sementic attribute and its syntactic
- * equivalents.
- * Then we'll set and retrieve attribute values according to the mapping,
- * instead of having to check and set each syntactic attribute every time.
- *
- * Reference: http://kb.adobe.com/selfservice/viewContent.do?externalId=tn_12701
- */
- var ATTRTYPE_OBJECT = 1,
- ATTRTYPE_PARAM = 2,
- ATTRTYPE_EMBED = 4;
-
- var attributesMap =
- {
- id : [ { type : ATTRTYPE_OBJECT, name : 'id' } ],
- classid : [ { type : ATTRTYPE_OBJECT, name : 'classid' } ],
- codebase : [ { type : ATTRTYPE_OBJECT, name : 'codebase'} ],
- pluginspage : [ { type : ATTRTYPE_EMBED, name : 'pluginspage' } ],
- src : [ { type : ATTRTYPE_PARAM, name : 'movie' }, { type : ATTRTYPE_EMBED, name : 'src' } ],
- name : [ { type : ATTRTYPE_EMBED, name : 'name' } ],
- align : [ { type : ATTRTYPE_OBJECT, name : 'align' } ],
- title : [ { type : ATTRTYPE_OBJECT, name : 'title' }, { type : ATTRTYPE_EMBED, name : 'title' } ],
- 'class' : [ { type : ATTRTYPE_OBJECT, name : 'class' }, { type : ATTRTYPE_EMBED, name : 'class'} ],
- width : [ { type : ATTRTYPE_OBJECT, name : 'width' }, { type : ATTRTYPE_EMBED, name : 'width' } ],
- height : [ { type : ATTRTYPE_OBJECT, name : 'height' }, { type : ATTRTYPE_EMBED, name : 'height' } ],
- hSpace : [ { type : ATTRTYPE_OBJECT, name : 'hSpace' }, { type : ATTRTYPE_EMBED, name : 'hSpace' } ],
- vSpace : [ { type : ATTRTYPE_OBJECT, name : 'vSpace' }, { type : ATTRTYPE_EMBED, name : 'vSpace' } ],
- style : [ { type : ATTRTYPE_OBJECT, name : 'style' }, { type : ATTRTYPE_EMBED, name : 'style' } ],
- type : [ { type : ATTRTYPE_EMBED, name : 'type' } ]
- };
-
- var names = [ 'play', 'loop', 'menu', 'quality', 'scale', 'salign', 'wmode', 'bgcolor', 'base', 'flashvars', 'allowScriptAccess',
- 'allowFullScreen' ];
- for ( var i = 0 ; i < names.length ; i++ )
- attributesMap[ names[i] ] = [ { type : ATTRTYPE_EMBED, name : names[i] }, { type : ATTRTYPE_PARAM, name : names[i] } ];
- names = [ 'allowFullScreen', 'play', 'loop', 'menu' ];
- for ( i = 0 ; i < names.length ; i++ )
- attributesMap[ names[i] ][0]['default'] = attributesMap[ names[i] ][1]['default'] = true;
-
- function loadValue( objectNode, embedNode, paramMap )
- {
- var attributes = attributesMap[ this.id ];
- if ( !attributes )
- return;
-
- var isCheckbox = ( this instanceof CKEDITOR.ui.dialog.checkbox );
- for ( var i = 0 ; i < attributes.length ; i++ )
- {
- var attrDef = attributes[ i ];
- switch ( attrDef.type )
- {
- case ATTRTYPE_OBJECT:
- if ( !objectNode )
- continue;
- if ( objectNode.getAttribute( attrDef.name ) !== null )
- {
- var value = objectNode.getAttribute( attrDef.name );
- if ( isCheckbox )
- this.setValue( value.toLowerCase() == 'true' );
- else
- this.setValue( value );
- return;
- }
- else if ( isCheckbox )
- this.setValue( !!attrDef[ 'default' ] );
- break;
- case ATTRTYPE_PARAM:
- if ( !objectNode )
- continue;
- if ( attrDef.name in paramMap )
- {
- value = paramMap[ attrDef.name ];
- if ( isCheckbox )
- this.setValue( value.toLowerCase() == 'true' );
- else
- this.setValue( value );
- return;
- }
- else if ( isCheckbox )
- this.setValue( !!attrDef[ 'default' ] );
- break;
- case ATTRTYPE_EMBED:
- if ( !embedNode )
- continue;
- if ( embedNode.getAttribute( attrDef.name ) )
- {
- value = embedNode.getAttribute( attrDef.name );
- if ( isCheckbox )
- this.setValue( value.toLowerCase() == 'true' );
- else
- this.setValue( value );
- return;
- }
- else if ( isCheckbox )
- this.setValue( !!attrDef[ 'default' ] );
- }
- }
- }
-
- function commitValue( objectNode, embedNode, paramMap )
- {
- var attributes = attributesMap[ this.id ];
- if ( !attributes )
- return;
-
- var isRemove = ( this.getValue() === '' ),
- isCheckbox = ( this instanceof CKEDITOR.ui.dialog.checkbox );
-
- for ( var i = 0 ; i < attributes.length ; i++ )
- {
- var attrDef = attributes[i];
- switch ( attrDef.type )
- {
- case ATTRTYPE_OBJECT:
- if ( !objectNode )
- continue;
- var value = this.getValue();
- if ( isRemove || isCheckbox && value === attrDef[ 'default' ] )
- objectNode.removeAttribute( attrDef.name );
- else
- objectNode.setAttribute( attrDef.name, value );
- break;
- case ATTRTYPE_PARAM:
- if ( !objectNode )
- continue;
- value = this.getValue();
- if ( isRemove || isCheckbox && value === attrDef[ 'default' ] )
- {
- if ( attrDef.name in paramMap )
- paramMap[ attrDef.name ].remove();
- }
- else
- {
- if ( attrDef.name in paramMap )
- paramMap[ attrDef.name ].setAttribute( 'value', value );
- else
- {
- var param = CKEDITOR.dom.element.createFromHtml( '<cke:param></cke:param>', objectNode.getDocument() );
- param.setAttributes( { name : attrDef.name, value : value } );
- if ( objectNode.getChildCount() < 1 )
- param.appendTo( objectNode );
- else
- param.insertBefore( objectNode.getFirst() );
- }
- }
- break;
- case ATTRTYPE_EMBED:
- if ( !embedNode )
- continue;
- value = this.getValue();
- if ( isRemove || isCheckbox && value === attrDef[ 'default' ])
- embedNode.removeAttribute( attrDef.name );
- else
- embedNode.setAttribute( attrDef.name, value );
- }
- }
- }
-
- CKEDITOR.dialog.add( 'flash', function( editor )
- {
- var makeObjectTag = !editor.config.flashEmbedTagOnly,
- makeEmbedTag = editor.config.flashAddEmbedTag || editor.config.flashEmbedTagOnly;
-
- var previewPreloader,
- previewAreaHtml = '<div>' + CKEDITOR.tools.htmlEncode( editor.lang.common.preview ) +'<br>' +
- '<div id="FlashPreviewLoader" style="display:none"><div class="loading"> </div></div>' +
- '<div id="FlashPreviewBox"></div></div>';
-
- return {
- title : editor.lang.flash.title,
- minWidth : 420,
- minHeight : 310,
- onShow : function()
- {
- // 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();
- if ( fakeImage && fakeImage.getAttribute( '_cke_real_element_type' ) && fakeImage.getAttribute( '_cke_real_element_type' ) == 'flash' )
- {
- this.fakeImage = fakeImage;
-
- var realElement = editor.restoreRealElement( fakeImage ),
- objectNode = null, embedNode = null, paramMap = {};
- if ( realElement.getName() == 'cke:object' )
- {
- objectNode = realElement;
- var embedList = objectNode.getElementsByTag( 'embed', 'cke' );
- if ( embedList.count() > 0 )
- embedNode = embedList.getItem( 0 );
- var paramList = objectNode.getElementsByTag( 'param', 'cke' );
- for ( var i = 0, length = paramList.count() ; i < length ; i++ )
- {
- var item = paramList.getItem( i ),
- name = item.getAttribute( 'name' ),
- value = item.getAttribute( 'value' );
- paramMap[ name ] = value;
- }
- }
- else if ( realElement.getName() == 'cke:embed' )
- embedNode = realElement;
-
- this.objectNode = objectNode;
- this.embedNode = embedNode;
-
- this.setupContent( objectNode, embedNode, paramMap, fakeImage );
- }
- },
- onOk : function()
- {
- // If there's no selected object or embed, create one. Otherwise, reuse the
- // selected object and embed nodes.
- var objectNode = null,
- embedNode = null,
- paramMap = null;
- if ( !this.fakeImage )
- {
- if ( makeObjectTag )
- {
- objectNode = CKEDITOR.dom.element.createFromHtml( '<cke:object></cke:object>', editor.document );
- var attributes = {
- classid : 'clsid:d27cdb6e-ae6d-11cf-96b8-444553540000',
- codebase : 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0'
- };
- objectNode.setAttributes( attributes );
- }
- if ( makeEmbedTag )
- {
- embedNode = CKEDITOR.dom.element.createFromHtml( '<cke:embed></cke:embed>', editor.document );
- embedNode.setAttributes(
- {
- type : 'application/x-shockwave-flash',
- pluginspage : 'http://www.macromedia.com/go/getflashplayer'
- } );
- if ( objectNode )
- embedNode.appendTo( objectNode );
- }
- }
- else
- {
- objectNode = this.objectNode;
- embedNode = this.embedNode;
- }
-
- // Produce the paramMap if there's an object tag.
- if ( objectNode )
- {
- paramMap = {};
- var paramList = objectNode.getElementsByTag( 'param', 'cke' );
- for ( var i = 0, length = paramList.count() ; i < length ; i++ )
- paramMap[ paramList.getItem( i ).getAttribute( 'name' ) ] = paramList.getItem( i );
- }
-
- // Apply or remove flash parameters.
- var extraStyles = {};
- this.commitContent( objectNode, embedNode, paramMap, extraStyles );
-
- // Refresh the fake image.
- 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 );
- },
-
- onHide : function()
- {
- if ( this.preview )
- this.preview.setHtml('');
- },
-
- contents : [
- {
- id : 'info',
- label : editor.lang.common.generalTab,
- accessKey : 'I',
- elements :
- [
- {
- type : 'vbox',
- padding : 0,
- children :
- [
- {
- type : 'hbox',
- widths : [ '280px', '110px' ],
- align : 'right',
- children :
- [
- {
- id : 'src',
- type : 'text',
- label : editor.lang.common.url,
- required : true,
- validate : CKEDITOR.dialog.validate.notEmpty( editor.lang.flash.validateSrc ),
- setup : loadValue,
- commit : commitValue,
- onLoad : function()
- {
- 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( previewPreloader.getAttribute( 'src' ) )
- + '" type="application/x-shockwave-flash"></embed>' );
- };
- // Preview element
- dialog.preview = dialog.getContentElement( 'info', 'preview' ).getElement().getChild( 3 );
-
- // Sync on inital value loaded.
- this.on( 'change', function( evt ){
-
- if ( evt.data && evt.data.value )
- updatePreview( evt.data.value );
- } );
- // Sync when input value changed.
- this.getInputElement().on( 'change', function( evt ){
-
- updatePreview( this.getValue() );
- }, this );
- }
- },
- {
- type : 'button',
- id : 'browse',
- filebrowser : 'info:src',
- hidden : true,
- // v-align with the 'src' field.
- // TODO: We need something better than a fixed size here.
- style : 'display:inline-block;margin-top:10px;',
- label : editor.lang.common.browseServer
- }
- ]
- }
- ]
- },
- {
- type : 'hbox',
- widths : [ '25%', '25%', '25%', '25%', '25%' ],
- children :
- [
- {
- type : 'text',
- id : 'width',
- style : 'width:95px',
- label : editor.lang.flash.width,
- validate : CKEDITOR.dialog.validate.integer( editor.lang.flash.validateWidth ),
- setup : function( objectNode, embedNode, paramMap, fakeImage )
- {
- loadValue.apply( this, arguments );
- if ( fakeImage )
- {
- var fakeImageWidth = parseInt( fakeImage.$.style.width, 10 );
- if ( !isNaN( fakeImageWidth ) )
- this.setValue( fakeImageWidth );
- }
- },
- commit : function( objectNode, embedNode, paramMap, extraStyles )
- {
- commitValue.apply( this, arguments );
- if ( this.getValue() )
- extraStyles.width = this.getValue() + 'px';
- }
- },
- {
- type : 'text',
- id : 'height',
- style : 'width:95px',
- label : editor.lang.flash.height,
- validate : CKEDITOR.dialog.validate.integer( editor.lang.flash.validateHeight ),
- setup : function( objectNode, embedNode, paramMap, fakeImage )
- {
- loadValue.apply( this, arguments );
- if ( fakeImage )
- {
- var fakeImageHeight = parseInt( fakeImage.$.style.height, 10 );
- if ( !isNaN( fakeImageHeight ) )
- this.setValue( fakeImageHeight );
- }
- },
- commit : function( objectNode, embedNode, paramMap, extraStyles )
- {
- commitValue.apply( this, arguments );
- if ( this.getValue() )
- extraStyles.height = this.getValue() + 'px';
- }
- },
- {
- type : 'text',
- id : 'hSpace',
- style : 'width:95px',
- label : editor.lang.flash.hSpace,
- validate : CKEDITOR.dialog.validate.integer( editor.lang.flash.validateHSpace ),
- setup : loadValue,
- commit : commitValue
- },
- {
- type : 'text',
- id : 'vSpace',
- style : 'width:95px',
- label : editor.lang.flash.vSpace,
- validate : CKEDITOR.dialog.validate.integer( editor.lang.flash.validateVSpace ),
- setup : loadValue,
- commit : commitValue
- }
- ]
- },
-
- {
- type : 'vbox',
- children :
- [
- {
- type : 'html',
- id : 'preview',
- style : 'width:95%;',
- html : previewAreaHtml
- }
- ]
- }
- ]
- },
- {
- id : 'Upload',
- hidden : true,
- filebrowser : 'uploadButton',
- label : editor.lang.common.upload,
- elements :
- [
- {
- type : 'file',
- id : 'upload',
- label : editor.lang.common.upload,
- size : 38
- },
- {
- type : 'fileButton',
- id : 'uploadButton',
- label : editor.lang.common.uploadSubmit,
- filebrowser : 'info:src',
- 'for' : [ 'Upload', 'upload' ]
- }
- ]
- },
- {
- id : 'properties',
- label : editor.lang.flash.propertiesTab,
- elements :
- [
- {
- type : 'hbox',
- widths : [ '50%', '50%' ],
- children :
- [
- {
- id : 'scale',
- type : 'select',
- label : editor.lang.flash.scale,
- 'default' : '',
- style : 'width : 100%;',
- items :
- [
- [ editor.lang.common.notSet , ''],
- [ editor.lang.flash.scaleAll, 'showall' ],
- [ editor.lang.flash.scaleNoBorder, 'noborder' ],
- [ editor.lang.flash.scaleFit, 'exactfit' ]
- ],
- setup : loadValue,
- commit : commitValue
- },
- {
- id : 'allowScriptAccess',
- type : 'select',
- label : editor.lang.flash.access,
- 'default' : '',
- style : 'width : 100%;',
- items :
- [
- [ editor.lang.common.notSet , ''],
- [ editor.lang.flash.accessAlways, 'always' ],
- [ editor.lang.flash.accessSameDomain, 'samedomain' ],
- [ editor.lang.flash.accessNever, 'never' ]
- ],
- setup : loadValue,
- commit : commitValue
- }
- ]
- },
- {
- type : 'hbox',
- widths : [ '50%', '50%' ],
- children :
- [
- {
- id : 'wmode',
- type : 'select',
- label : editor.lang.flash.windowMode,
- 'default' : '',
- style : 'width : 100%;',
- items :
- [
- [ editor.lang.common.notSet , '' ],
- [ editor.lang.flash.windowModeWindow, 'window' ],
- [ editor.lang.flash.windowModeOpaque, 'opaque' ],
- [ editor.lang.flash.windowModeTransparent, 'transparent' ]
- ],
- setup : loadValue,
- commit : commitValue
- },
- {
- id : 'quality',
- type : 'select',
- label : editor.lang.flash.quality,
- 'default' : 'high',
- style : 'width : 100%;',
- items :
- [
- [ editor.lang.common.notSet , '' ],
- [ editor.lang.flash.qualityBest, 'best' ],
- [ editor.lang.flash.qualityHigh, 'high' ],
- [ editor.lang.flash.qualityAutoHigh, 'autohigh' ],
- [ editor.lang.flash.qualityMedium, 'medium' ],
- [ editor.lang.flash.qualityAutoLow, 'autolow' ],
- [ editor.lang.flash.qualityLow, 'low' ]
- ],
- setup : loadValue,
- commit : commitValue
- }
- ]
- },
- {
- type : 'hbox',
- widths : [ '50%', '50%' ],
- children :
- [
- {
- id : 'align',
- type : 'select',
- label : editor.lang.flash.align,
- 'default' : '',
- style : 'width : 100%;',
- items :
- [
- [ editor.lang.common.notSet , ''],
- [ 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
- },
- {
- type : 'html',
- html : '<div></div>'
- }
- ]
- },
- {
- type : 'fieldset',
- label : CKEDITOR.tools.htmlEncode( editor.lang.flash.flashvars ),
- children :
- [
- {
- type : 'vbox',
- padding : 0,
- children :
- [
- {
- type : 'checkbox',
- id : 'menu',
- label : editor.lang.flash.chkMenu,
- 'default' : true,
- setup : loadValue,
- commit : commitValue
- },
- {
- type : 'checkbox',
- id : 'play',
- label : editor.lang.flash.chkPlay,
- 'default' : true,
- setup : loadValue,
- commit : commitValue
- },
- {
- type : 'checkbox',
- id : 'loop',
- label : editor.lang.flash.chkLoop,
- 'default' : true,
- setup : loadValue,
- commit : commitValue
- },
- {
- type : 'checkbox',
- id : 'allowFullScreen',
- label : editor.lang.flash.chkFull,
- 'default' : true,
- setup : loadValue,
- commit : commitValue
- }
- ]
- }
- ]
- }
- ]
- },
- {
- id : 'advanced',
- label : editor.lang.common.advancedTab,
- elements :
- [
- {
- type : 'hbox',
- widths : [ '45%', '55%' ],
- children :
- [
- {
- type : 'text',
- id : 'id',
- label : editor.lang.common.id,
- setup : loadValue,
- commit : commitValue
- },
- {
- type : 'text',
- id : 'title',
- label : editor.lang.common.advisoryTitle,
- setup : loadValue,
- commit : commitValue
- }
- ]
- },
- {
- type : 'hbox',
- widths : [ '45%', '55%' ],
- children :
- [
- {
- type : 'text',
- id : 'bgcolor',
- label : editor.lang.flash.bgcolor,
- setup : loadValue,
- commit : commitValue
- },
- {
- type : 'text',
- id : 'class',
- label : editor.lang.common.cssClass,
- setup : loadValue,
- commit : commitValue
- }
- ]
- },
- {
- type : 'text',
- id : 'style',
- label : editor.lang.common.cssStyle,
- setup : loadValue,
- commit : commitValue
- }
- ]
- }
- ]
- };
- } );
-})();
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +(function() +{ + /* + * It is possible to set things in three different places. + * 1. As attributes in the object tag. + * 2. As param tags under the object tag. + * 3. As attributes in the embed tag. + * It is possible for a single attribute to be present in more than one place. + * So let's define a mapping between a sementic attribute and its syntactic + * equivalents. + * Then we'll set and retrieve attribute values according to the mapping, + * instead of having to check and set each syntactic attribute every time. + * + * Reference: http://kb.adobe.com/selfservice/viewContent.do?externalId=tn_12701 + */ + var ATTRTYPE_OBJECT = 1, + ATTRTYPE_PARAM = 2, + ATTRTYPE_EMBED = 4; + + var attributesMap = + { + id : [ { type : ATTRTYPE_OBJECT, name : 'id' } ], + classid : [ { type : ATTRTYPE_OBJECT, name : 'classid' } ], + codebase : [ { type : ATTRTYPE_OBJECT, name : 'codebase'} ], + pluginspage : [ { type : ATTRTYPE_EMBED, name : 'pluginspage' } ], + src : [ { type : ATTRTYPE_PARAM, name : 'movie' }, { type : ATTRTYPE_EMBED, name : 'src' } ], + name : [ { type : ATTRTYPE_EMBED, name : 'name' } ], + align : [ { type : ATTRTYPE_OBJECT, name : 'align' } ], + title : [ { type : ATTRTYPE_OBJECT, name : 'title' }, { type : ATTRTYPE_EMBED, name : 'title' } ], + 'class' : [ { type : ATTRTYPE_OBJECT, name : 'class' }, { type : ATTRTYPE_EMBED, name : 'class'} ], + width : [ { type : ATTRTYPE_OBJECT, name : 'width' }, { type : ATTRTYPE_EMBED, name : 'width' } ], + height : [ { type : ATTRTYPE_OBJECT, name : 'height' }, { type : ATTRTYPE_EMBED, name : 'height' } ], + hSpace : [ { type : ATTRTYPE_OBJECT, name : 'hSpace' }, { type : ATTRTYPE_EMBED, name : 'hSpace' } ], + vSpace : [ { type : ATTRTYPE_OBJECT, name : 'vSpace' }, { type : ATTRTYPE_EMBED, name : 'vSpace' } ], + style : [ { type : ATTRTYPE_OBJECT, name : 'style' }, { type : ATTRTYPE_EMBED, name : 'style' } ], + type : [ { type : ATTRTYPE_EMBED, name : 'type' } ] + }; + + var names = [ 'play', 'loop', 'menu', 'quality', 'scale', 'salign', 'wmode', 'bgcolor', 'base', 'flashvars', 'allowScriptAccess', + 'allowFullScreen' ]; + for ( var i = 0 ; i < names.length ; i++ ) + attributesMap[ names[i] ] = [ { type : ATTRTYPE_EMBED, name : names[i] }, { type : ATTRTYPE_PARAM, name : names[i] } ]; + names = [ 'allowFullScreen', 'play', 'loop', 'menu' ]; + for ( i = 0 ; i < names.length ; i++ ) + attributesMap[ names[i] ][0]['default'] = attributesMap[ names[i] ][1]['default'] = true; + + function loadValue( objectNode, embedNode, paramMap ) + { + var attributes = attributesMap[ this.id ]; + if ( !attributes ) + return; + + var isCheckbox = ( this instanceof CKEDITOR.ui.dialog.checkbox ); + for ( var i = 0 ; i < attributes.length ; i++ ) + { + var attrDef = attributes[ i ]; + switch ( attrDef.type ) + { + case ATTRTYPE_OBJECT: + if ( !objectNode ) + continue; + if ( objectNode.getAttribute( attrDef.name ) !== null ) + { + var value = objectNode.getAttribute( attrDef.name ); + if ( isCheckbox ) + this.setValue( value.toLowerCase() == 'true' ); + else + this.setValue( value ); + return; + } + else if ( isCheckbox ) + this.setValue( !!attrDef[ 'default' ] ); + break; + case ATTRTYPE_PARAM: + if ( !objectNode ) + continue; + if ( attrDef.name in paramMap ) + { + value = paramMap[ attrDef.name ]; + if ( isCheckbox ) + this.setValue( value.toLowerCase() == 'true' ); + else + this.setValue( value ); + return; + } + else if ( isCheckbox ) + this.setValue( !!attrDef[ 'default' ] ); + break; + case ATTRTYPE_EMBED: + if ( !embedNode ) + continue; + if ( embedNode.getAttribute( attrDef.name ) ) + { + value = embedNode.getAttribute( attrDef.name ); + if ( isCheckbox ) + this.setValue( value.toLowerCase() == 'true' ); + else + this.setValue( value ); + return; + } + else if ( isCheckbox ) + this.setValue( !!attrDef[ 'default' ] ); + } + } + } + + function commitValue( objectNode, embedNode, paramMap ) + { + var attributes = attributesMap[ this.id ]; + if ( !attributes ) + return; + + var isRemove = ( this.getValue() === '' ), + isCheckbox = ( this instanceof CKEDITOR.ui.dialog.checkbox ); + + for ( var i = 0 ; i < attributes.length ; i++ ) + { + var attrDef = attributes[i]; + switch ( attrDef.type ) + { + case ATTRTYPE_OBJECT: + if ( !objectNode ) + continue; + var value = this.getValue(); + if ( isRemove || isCheckbox && value === attrDef[ 'default' ] ) + objectNode.removeAttribute( attrDef.name ); + else + objectNode.setAttribute( attrDef.name, value ); + break; + case ATTRTYPE_PARAM: + if ( !objectNode ) + continue; + value = this.getValue(); + if ( isRemove || isCheckbox && value === attrDef[ 'default' ] ) + { + if ( attrDef.name in paramMap ) + paramMap[ attrDef.name ].remove(); + } + else + { + if ( attrDef.name in paramMap ) + paramMap[ attrDef.name ].setAttribute( 'value', value ); + else + { + var param = CKEDITOR.dom.element.createFromHtml( '<cke:param></cke:param>', objectNode.getDocument() ); + param.setAttributes( { name : attrDef.name, value : value } ); + if ( objectNode.getChildCount() < 1 ) + param.appendTo( objectNode ); + else + param.insertBefore( objectNode.getFirst() ); + } + } + break; + case ATTRTYPE_EMBED: + if ( !embedNode ) + continue; + value = this.getValue(); + if ( isRemove || isCheckbox && value === attrDef[ 'default' ]) + embedNode.removeAttribute( attrDef.name ); + else + embedNode.setAttribute( attrDef.name, value ); + } + } + } + + CKEDITOR.dialog.add( 'flash', function( editor ) + { + var makeObjectTag = !editor.config.flashEmbedTagOnly, + makeEmbedTag = editor.config.flashAddEmbedTag || editor.config.flashEmbedTagOnly; + + var previewPreloader, + previewAreaHtml = '<div>' + CKEDITOR.tools.htmlEncode( editor.lang.common.preview ) +'<br>' + + '<div id="FlashPreviewLoader' + CKEDITOR.tools.getNextNumber() + '" style="display:none"><div class="loading"> </div></div>' + + '<div id="FlashPreviewBox' + CKEDITOR.tools.getNextNumber() + '" class="FlashPreviewBox"></div></div>'; + + return { + title : editor.lang.flash.title, + minWidth : 420, + minHeight : 310, + onShow : function() + { + // 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(); + if ( fakeImage && fakeImage.getAttribute( '_cke_real_element_type' ) && fakeImage.getAttribute( '_cke_real_element_type' ) == 'flash' ) + { + this.fakeImage = fakeImage; + + var realElement = editor.restoreRealElement( fakeImage ), + objectNode = null, embedNode = null, paramMap = {}; + if ( realElement.getName() == 'cke:object' ) + { + objectNode = realElement; + var embedList = objectNode.getElementsByTag( 'embed', 'cke' ); + if ( embedList.count() > 0 ) + embedNode = embedList.getItem( 0 ); + var paramList = objectNode.getElementsByTag( 'param', 'cke' ); + for ( var i = 0, length = paramList.count() ; i < length ; i++ ) + { + var item = paramList.getItem( i ), + name = item.getAttribute( 'name' ), + value = item.getAttribute( 'value' ); + paramMap[ name ] = value; + } + } + else if ( realElement.getName() == 'cke:embed' ) + embedNode = realElement; + + this.objectNode = objectNode; + this.embedNode = embedNode; + + this.setupContent( objectNode, embedNode, paramMap, fakeImage ); + } + }, + onOk : function() + { + // If there's no selected object or embed, create one. Otherwise, reuse the + // selected object and embed nodes. + var objectNode = null, + embedNode = null, + paramMap = null; + if ( !this.fakeImage ) + { + if ( makeObjectTag ) + { + objectNode = CKEDITOR.dom.element.createFromHtml( '<cke:object></cke:object>', editor.document ); + var attributes = { + classid : 'clsid:d27cdb6e-ae6d-11cf-96b8-444553540000', + codebase : 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0' + }; + objectNode.setAttributes( attributes ); + } + if ( makeEmbedTag ) + { + embedNode = CKEDITOR.dom.element.createFromHtml( '<cke:embed></cke:embed>', editor.document ); + embedNode.setAttributes( + { + type : 'application/x-shockwave-flash', + pluginspage : 'http://www.macromedia.com/go/getflashplayer' + } ); + if ( objectNode ) + embedNode.appendTo( objectNode ); + } + } + else + { + objectNode = this.objectNode; + embedNode = this.embedNode; + } + + // Produce the paramMap if there's an object tag. + if ( objectNode ) + { + paramMap = {}; + var paramList = objectNode.getElementsByTag( 'param', 'cke' ); + for ( var i = 0, length = paramList.count() ; i < length ; i++ ) + paramMap[ paramList.getItem( i ).getAttribute( 'name' ) ] = paramList.getItem( i ); + } + + // A subset of the specified attributes/styles + // should also be applied on the fake element to + // have better visual effect. (#5240) + var extraStyles = {}, extraAttributes = {}; + this.commitContent( objectNode, embedNode, paramMap, extraStyles, extraAttributes ); + + // Refresh the fake image. + var newFakeImage = editor.createFakeElement( objectNode || embedNode, 'cke_flash', 'flash', true ); + newFakeImage.setAttributes( extraAttributes ); + newFakeImage.setStyles( extraStyles ); + if ( this.fakeImage ) + { + newFakeImage.replace( this.fakeImage ); + editor.getSelection().selectElement( newFakeImage ); + } + else + editor.insertElement( newFakeImage ); + }, + + onHide : function() + { + if ( this.preview ) + this.preview.setHtml(''); + }, + + contents : [ + { + id : 'info', + label : editor.lang.common.generalTab, + accessKey : 'I', + elements : + [ + { + type : 'vbox', + padding : 0, + children : + [ + { + type : 'hbox', + widths : [ '280px', '110px' ], + align : 'right', + children : + [ + { + id : 'src', + type : 'text', + label : editor.lang.common.url, + required : true, + validate : CKEDITOR.dialog.validate.notEmpty( editor.lang.flash.validateSrc ), + setup : loadValue, + commit : commitValue, + onLoad : function() + { + 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( previewPreloader.getAttribute( 'src' ) ) + + '" type="application/x-shockwave-flash"></embed>' ); + }; + // Preview element + dialog.preview = dialog.getContentElement( 'info', 'preview' ).getElement().getChild( 3 ); + + // Sync on inital value loaded. + this.on( 'change', function( evt ){ + + if ( evt.data && evt.data.value ) + updatePreview( evt.data.value ); + } ); + // Sync when input value changed. + this.getInputElement().on( 'change', function( evt ){ + + updatePreview( this.getValue() ); + }, this ); + } + }, + { + type : 'button', + id : 'browse', + filebrowser : 'info:src', + hidden : true, + // v-align with the 'src' field. + // TODO: We need something better than a fixed size here. + style : 'display:inline-block;margin-top:10px;', + label : editor.lang.common.browseServer + } + ] + } + ] + }, + { + type : 'hbox', + widths : [ '25%', '25%', '25%', '25%', '25%' ], + children : + [ + { + type : 'text', + id : 'width', + style : 'width:95px', + label : editor.lang.flash.width, + validate : CKEDITOR.dialog.validate.integer( editor.lang.flash.validateWidth ), + setup : function( objectNode, embedNode, paramMap, fakeImage ) + { + loadValue.apply( this, arguments ); + if ( fakeImage ) + { + var fakeImageWidth = parseInt( fakeImage.$.style.width, 10 ); + if ( !isNaN( fakeImageWidth ) ) + this.setValue( fakeImageWidth ); + } + }, + commit : function( objectNode, embedNode, paramMap, extraStyles ) + { + commitValue.apply( this, arguments ); + if ( this.getValue() ) + extraStyles.width = this.getValue() + 'px'; + } + }, + { + type : 'text', + id : 'height', + style : 'width:95px', + label : editor.lang.flash.height, + validate : CKEDITOR.dialog.validate.integer( editor.lang.flash.validateHeight ), + setup : function( objectNode, embedNode, paramMap, fakeImage ) + { + loadValue.apply( this, arguments ); + if ( fakeImage ) + { + var fakeImageHeight = parseInt( fakeImage.$.style.height, 10 ); + if ( !isNaN( fakeImageHeight ) ) + this.setValue( fakeImageHeight ); + } + }, + commit : function( objectNode, embedNode, paramMap, extraStyles ) + { + commitValue.apply( this, arguments ); + if ( this.getValue() ) + extraStyles.height = this.getValue() + 'px'; + } + }, + { + type : 'text', + id : 'hSpace', + style : 'width:95px', + label : editor.lang.flash.hSpace, + validate : CKEDITOR.dialog.validate.integer( editor.lang.flash.validateHSpace ), + setup : loadValue, + commit : commitValue + }, + { + type : 'text', + id : 'vSpace', + style : 'width:95px', + label : editor.lang.flash.vSpace, + validate : CKEDITOR.dialog.validate.integer( editor.lang.flash.validateVSpace ), + setup : loadValue, + commit : commitValue + } + ] + }, + + { + type : 'vbox', + children : + [ + { + type : 'html', + id : 'preview', + style : 'width:95%;', + html : previewAreaHtml + } + ] + } + ] + }, + { + id : 'Upload', + hidden : true, + filebrowser : 'uploadButton', + label : editor.lang.common.upload, + elements : + [ + { + type : 'file', + id : 'upload', + label : editor.lang.common.upload, + size : 38 + }, + { + type : 'fileButton', + id : 'uploadButton', + label : editor.lang.common.uploadSubmit, + filebrowser : 'info:src', + 'for' : [ 'Upload', 'upload' ] + } + ] + }, + { + id : 'properties', + label : editor.lang.flash.propertiesTab, + elements : + [ + { + type : 'hbox', + widths : [ '50%', '50%' ], + children : + [ + { + id : 'scale', + type : 'select', + label : editor.lang.flash.scale, + 'default' : '', + style : 'width : 100%;', + items : + [ + [ editor.lang.common.notSet , ''], + [ editor.lang.flash.scaleAll, 'showall' ], + [ editor.lang.flash.scaleNoBorder, 'noborder' ], + [ editor.lang.flash.scaleFit, 'exactfit' ] + ], + setup : loadValue, + commit : commitValue + }, + { + id : 'allowScriptAccess', + type : 'select', + label : editor.lang.flash.access, + 'default' : '', + style : 'width : 100%;', + items : + [ + [ editor.lang.common.notSet , ''], + [ editor.lang.flash.accessAlways, 'always' ], + [ editor.lang.flash.accessSameDomain, 'samedomain' ], + [ editor.lang.flash.accessNever, 'never' ] + ], + setup : loadValue, + commit : commitValue + } + ] + }, + { + type : 'hbox', + widths : [ '50%', '50%' ], + children : + [ + { + id : 'wmode', + type : 'select', + label : editor.lang.flash.windowMode, + 'default' : '', + style : 'width : 100%;', + items : + [ + [ editor.lang.common.notSet , '' ], + [ editor.lang.flash.windowModeWindow, 'window' ], + [ editor.lang.flash.windowModeOpaque, 'opaque' ], + [ editor.lang.flash.windowModeTransparent, 'transparent' ] + ], + setup : loadValue, + commit : commitValue + }, + { + id : 'quality', + type : 'select', + label : editor.lang.flash.quality, + 'default' : 'high', + style : 'width : 100%;', + items : + [ + [ editor.lang.common.notSet , '' ], + [ editor.lang.flash.qualityBest, 'best' ], + [ editor.lang.flash.qualityHigh, 'high' ], + [ editor.lang.flash.qualityAutoHigh, 'autohigh' ], + [ editor.lang.flash.qualityMedium, 'medium' ], + [ editor.lang.flash.qualityAutoLow, 'autolow' ], + [ editor.lang.flash.qualityLow, 'low' ] + ], + setup : loadValue, + commit : commitValue + } + ] + }, + { + type : 'hbox', + widths : [ '50%', '50%' ], + children : + [ + { + id : 'align', + type : 'select', + label : editor.lang.flash.align, + 'default' : '', + style : 'width : 100%;', + items : + [ + [ editor.lang.common.notSet , ''], + [ 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 : function( objectNode, embedNode, paramMap, extraStyles, extraAttributes ) + { + var value = this.getValue(); + commitValue.apply( this, arguments ); + value && ( extraAttributes.align = value ); + } + }, + { + type : 'html', + html : '<div></div>' + } + ] + }, + { + type : 'fieldset', + label : CKEDITOR.tools.htmlEncode( editor.lang.flash.flashvars ), + children : + [ + { + type : 'vbox', + padding : 0, + children : + [ + { + type : 'checkbox', + id : 'menu', + label : editor.lang.flash.chkMenu, + 'default' : true, + setup : loadValue, + commit : commitValue + }, + { + type : 'checkbox', + id : 'play', + label : editor.lang.flash.chkPlay, + 'default' : true, + setup : loadValue, + commit : commitValue + }, + { + type : 'checkbox', + id : 'loop', + label : editor.lang.flash.chkLoop, + 'default' : true, + setup : loadValue, + commit : commitValue + }, + { + type : 'checkbox', + id : 'allowFullScreen', + label : editor.lang.flash.chkFull, + 'default' : true, + setup : loadValue, + commit : commitValue + } + ] + } + ] + } + ] + }, + { + id : 'advanced', + label : editor.lang.common.advancedTab, + elements : + [ + { + type : 'hbox', + widths : [ '45%', '55%' ], + children : + [ + { + type : 'text', + id : 'id', + label : editor.lang.common.id, + setup : loadValue, + commit : commitValue + }, + { + type : 'text', + id : 'title', + label : editor.lang.common.advisoryTitle, + setup : loadValue, + commit : commitValue + } + ] + }, + { + type : 'hbox', + widths : [ '45%', '55%' ], + children : + [ + { + type : 'text', + id : 'bgcolor', + label : editor.lang.flash.bgcolor, + setup : loadValue, + commit : commitValue + }, + { + type : 'text', + id : 'class', + label : editor.lang.common.cssClass, + setup : loadValue, + commit : commitValue + } + ] + }, + { + type : 'text', + id : 'style', + label : editor.lang.common.cssStyle, + setup : loadValue, + commit : commitValue + } + ] + } + ] + }; + } ); +})(); diff --git a/_source/plugins/flash/plugin.js b/_source/plugins/flash/plugin.js index 41814e3..f6d9c99 100644 --- a/_source/plugins/flash/plugin.js +++ b/_source/plugins/flash/plugin.js @@ -1,165 +1,173 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-(function()
-{
- var flashFilenameRegex = /\.swf(?:$|\?)/i,
- numberRegex = /^\d+(?:\.\d+)?$/;
-
- function cssifyLength( length )
- {
- if ( numberRegex.test( length ) )
- return length + 'px';
- return length;
- }
-
- function isFlashEmbed( element )
- {
- var attributes = element.attributes;
-
- return ( attributes.type == 'application/x-shockwave-flash' || flashFilenameRegex.test( attributes.src || '' ) );
- }
-
- function createFakeElement( editor, realElement )
- {
- var fakeElement = editor.createFakeParserElement( realElement, 'cke_flash', 'flash', true ),
- fakeStyle = fakeElement.attributes.style || '';
-
- var width = realElement.attributes.width,
- height = realElement.attributes.height;
-
- if ( typeof width != 'undefined' )
- fakeStyle = fakeElement.attributes.style = fakeStyle + 'width:' + cssifyLength( width ) + ';';
-
- if ( typeof height != 'undefined' )
- fakeStyle = fakeElement.attributes.style = fakeStyle + 'height:' + cssifyLength( height ) + ';';
-
- return fakeElement;
- }
-
- CKEDITOR.plugins.add( 'flash',
- {
- init : function( editor )
- {
- editor.addCommand( 'flash', new CKEDITOR.dialogCommand( 'flash' ) );
- editor.ui.addButton( 'Flash',
- {
- label : editor.lang.common.flash,
- command : 'flash'
- });
- CKEDITOR.dialog.add( 'flash', this.path + 'dialogs/flash.js' );
-
- editor.addCss(
- 'img.cke_flash' +
- '{' +
- 'background-image: url(' + CKEDITOR.getUrl( this.path + 'images/placeholder.png' ) + ');' +
- 'background-position: center center;' +
- 'background-repeat: no-repeat;' +
- 'border: 1px solid #a9a9a9;' +
- 'width: 80px;' +
- 'height: 80px;' +
- '}'
- );
-
- // If the "menu" plugin is loaded, register the menu items.
- if ( editor.addMenuItems )
- {
- editor.addMenuItems(
- {
- flash :
- {
- label : editor.lang.flash.properties,
- command : 'flash',
- group : 'flash'
- }
- });
- }
-
- // If the "contextmenu" plugin is loaded, register the listeners.
- if ( editor.contextMenu )
- {
- editor.contextMenu.addListener( function( element, selection )
- {
- if ( element && element.is( 'img' ) && element.getAttribute( '_cke_real_element_type' ) == 'flash' )
- return { flash : CKEDITOR.TRISTATE_OFF };
- });
- }
- },
-
- afterInit : function( editor )
- {
- var dataProcessor = editor.dataProcessor,
- dataFilter = dataProcessor && dataProcessor.dataFilter;
-
- if ( dataFilter )
- {
- dataFilter.addRules(
- {
- elements :
- {
- 'cke:object' : function( element )
- {
- var attributes = element.attributes,
- classId = attributes.classid && String( attributes.classid ).toLowerCase();
-
- if ( !classId )
- {
- // Look for the inner <embed>
- for ( var i = 0 ; i < element.children.length ; i++ )
- {
- if ( element.children[ i ].name == 'cke:embed' )
- {
- if ( !isFlashEmbed( element.children[ i ] ) )
- return null;
-
- return createFakeElement( editor, element );
- }
- }
- return null;
- }
-
- return createFakeElement( editor, element );
- },
-
- 'cke:embed' : function( element )
- {
- if ( !isFlashEmbed( element ) )
- return null;
-
- return createFakeElement( editor, element );
- }
- }
- },
- 5);
- }
- },
-
- requires : [ 'fakeobjects' ]
- });
-})();
-
-CKEDITOR.tools.extend( CKEDITOR.config,
-{
- /**
- * Save as EMBED tag only. This tag is unrecommended.
- * @type Boolean
- * @default false
- */
- flashEmbedTagOnly : false,
-
- /**
- * Add EMBED tag as alternative: <object><embed></embed></object>
- * @type Boolean
- * @default false
- */
- flashAddEmbedTag : true,
-
- /**
- * Use embedTagOnly and addEmbedTag values on edit.
- * @type Boolean
- * @default false
- */
- flashConvertOnEdit : false
-} );
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +(function() +{ + var flashFilenameRegex = /\.swf(?:$|\?)/i, + numberRegex = /^\d+(?:\.\d+)?$/; + + function cssifyLength( length ) + { + if ( numberRegex.test( length ) ) + return length + 'px'; + return length; + } + + function isFlashEmbed( element ) + { + var attributes = element.attributes; + + return ( attributes.type == 'application/x-shockwave-flash' || flashFilenameRegex.test( attributes.src || '' ) ); + } + + function createFakeElement( editor, realElement ) + { + var fakeElement = editor.createFakeParserElement( realElement, 'cke_flash', 'flash', true ), + fakeStyle = fakeElement.attributes.style || ''; + + var width = realElement.attributes.width, + height = realElement.attributes.height; + + if ( typeof width != 'undefined' ) + fakeStyle = fakeElement.attributes.style = fakeStyle + 'width:' + cssifyLength( width ) + ';'; + + if ( typeof height != 'undefined' ) + fakeStyle = fakeElement.attributes.style = fakeStyle + 'height:' + cssifyLength( height ) + ';'; + + return fakeElement; + } + + CKEDITOR.plugins.add( 'flash', + { + init : function( editor ) + { + editor.addCommand( 'flash', new CKEDITOR.dialogCommand( 'flash' ) ); + editor.ui.addButton( 'Flash', + { + label : editor.lang.common.flash, + command : 'flash' + }); + CKEDITOR.dialog.add( 'flash', this.path + 'dialogs/flash.js' ); + + editor.addCss( + 'img.cke_flash' + + '{' + + 'background-image: url(' + CKEDITOR.getUrl( this.path + 'images/placeholder.png' ) + ');' + + 'background-position: center center;' + + 'background-repeat: no-repeat;' + + 'border: 1px solid #a9a9a9;' + + 'width: 80px;' + + 'height: 80px;' + + '}' + ); + + // If the "menu" plugin is loaded, register the menu items. + if ( editor.addMenuItems ) + { + editor.addMenuItems( + { + flash : + { + label : editor.lang.flash.properties, + command : 'flash', + group : 'flash' + } + }); + } + + editor.on( 'doubleclick', function( evt ) + { + var element = evt.data.element; + + if ( element.is( 'img' ) && element.getAttribute( '_cke_real_element_type' ) == 'flash' ) + evt.data.dialog = 'flash'; + }); + + // If the "contextmenu" plugin is loaded, register the listeners. + if ( editor.contextMenu ) + { + editor.contextMenu.addListener( function( element, selection ) + { + if ( element && element.is( 'img' ) && element.getAttribute( '_cke_real_element_type' ) == 'flash' ) + return { flash : CKEDITOR.TRISTATE_OFF }; + }); + } + }, + + afterInit : function( editor ) + { + var dataProcessor = editor.dataProcessor, + dataFilter = dataProcessor && dataProcessor.dataFilter; + + if ( dataFilter ) + { + dataFilter.addRules( + { + elements : + { + 'cke:object' : function( element ) + { + var attributes = element.attributes, + classId = attributes.classid && String( attributes.classid ).toLowerCase(); + + if ( !classId ) + { + // Look for the inner <embed> + for ( var i = 0 ; i < element.children.length ; i++ ) + { + if ( element.children[ i ].name == 'cke:embed' ) + { + if ( !isFlashEmbed( element.children[ i ] ) ) + return null; + + return createFakeElement( editor, element ); + } + } + return null; + } + + return createFakeElement( editor, element ); + }, + + 'cke:embed' : function( element ) + { + if ( !isFlashEmbed( element ) ) + return null; + + return createFakeElement( editor, element ); + } + } + }, + 5); + } + }, + + requires : [ 'fakeobjects' ] + }); +})(); + +CKEDITOR.tools.extend( CKEDITOR.config, +{ + /** + * Save as EMBED tag only. This tag is unrecommended. + * @type Boolean + * @default false + */ + flashEmbedTagOnly : false, + + /** + * Add EMBED tag as alternative: <object><embed></embed></object> + * @type Boolean + * @default false + */ + flashAddEmbedTag : true, + + /** + * Use embedTagOnly and addEmbedTag values on edit. + * @type Boolean + * @default false + */ + flashConvertOnEdit : false +} ); diff --git a/_source/plugins/font/plugin.js b/_source/plugins/font/plugin.js index 70f3d9d..f38002d 100644 --- a/_source/plugins/font/plugin.js +++ b/_source/plugins/font/plugin.js @@ -1,233 +1,234 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-(function()
-{
- function addCombo( editor, comboName, styleType, lang, entries, defaultLabel, styleDefinition )
- {
- var config = editor.config;
-
- // Gets the list of fonts from the settings.
- var names = entries.split( ';' ),
- values = [];
-
- // Create style objects for all fonts.
- var styles = {};
- for ( var i = 0 ; i < names.length ; i++ )
- {
- var parts = names[ i ];
-
- if ( parts )
- {
- parts = parts.split( '/' );
-
- var vars = {},
- name = names[ i ] = parts[ 0 ];
-
- vars[ styleType ] = values[ i ] = parts[ 1 ] || name;
-
- styles[ name ] = new CKEDITOR.style( styleDefinition, vars );
- }
- else
- names.splice( i--, 1 );
- }
-
- editor.ui.addRichCombo( comboName,
- {
- label : lang.label,
- title : lang.panelTitle,
- className : 'cke_' + ( styleType == 'size' ? 'fontSize' : 'font' ),
- panel :
- {
- css : editor.skin.editor.css.concat( config.contentsCss ),
- multiSelect : false,
- attributes : { 'aria-label' : lang.panelTitle }
- },
-
- init : function()
- {
- this.startGroup( lang.panelTitle );
-
- for ( var i = 0 ; i < names.length ; i++ )
- {
- var name = names[ i ];
-
- // Add the tag entry to the panel list.
- this.add( name, '<span style="font-' + styleType + ':' + values[ i ] + '">' + name + '</span>', name );
- }
- },
-
- onClick : function( value )
- {
- editor.focus();
- editor.fire( 'saveSnapshot' );
-
- var style = styles[ value ];
-
- if ( this.getValue() == value )
- style.remove( editor.document );
- else
- style.apply( editor.document );
-
- editor.fire( 'saveSnapshot' );
- },
-
- onRender : function()
- {
- editor.on( 'selectionChange', function( ev )
- {
- var currentValue = this.getValue();
-
- var elementPath = ev.data.path,
- elements = elementPath.elements;
-
- // For each element into the elements path.
- for ( var i = 0, element ; i < elements.length ; i++ )
- {
- element = elements[i];
-
- // Check if the element is removable by any of
- // the styles.
- for ( var value in styles )
- {
- if ( styles[ value ].checkElementRemovable( element, true ) )
- {
- if ( value != currentValue )
- this.setValue( value );
- return;
- }
- }
- }
-
- // If no styles match, just empty it.
- this.setValue( '', defaultLabel );
- },
- this);
- }
- });
- }
-
- CKEDITOR.plugins.add( 'font',
- {
- requires : [ 'richcombo', 'styles' ],
-
- init : function( editor )
- {
- var config = editor.config;
-
- addCombo( editor, 'Font', 'family', editor.lang.font, config.font_names, config.font_defaultLabel, config.font_style );
- addCombo( editor, 'FontSize', 'size', editor.lang.fontSize, config.fontSize_sizes, config.fontSize_defaultLabel, config.fontSize_style );
- }
- });
-})();
-
-/**
- * The list of fonts names to be displayed in the Font combo in the toolbar.
- * Entries are separated by semi-colons (;), while it's possible to have more
- * than one font for each entry, in the HTML way (separated by comma).
- *
- * A display name may be optionally defined by prefixing the entries with the
- * name and the slash character. For example, "Arial/Arial, Helvetica, sans-serif"
- * will be displayed as "Arial" in the list, but will be outputted as
- * "Arial, Helvetica, sans-serif".
- * @type String
- * @example
- * config.font_names =
- * 'Arial/Arial, Helvetica, sans-serif;' +
- * 'Times New Roman/Times New Roman, Times, serif;' +
- * 'Verdana';
- * @example
- * config.font_names = 'Arial;Times New Roman;Verdana';
- */
-CKEDITOR.config.font_names =
- 'Arial/Arial, Helvetica, sans-serif;' +
- 'Comic Sans MS/Comic Sans MS, cursive;' +
- 'Courier New/Courier New, Courier, monospace;' +
- 'Georgia/Georgia, serif;' +
- 'Lucida Sans Unicode/Lucida Sans Unicode, Lucida Grande, sans-serif;' +
- 'Tahoma/Tahoma, Geneva, sans-serif;' +
- 'Times New Roman/Times New Roman, Times, serif;' +
- 'Trebuchet MS/Trebuchet MS, Helvetica, sans-serif;' +
- 'Verdana/Verdana, Geneva, sans-serif';
-
-/**
- * The text to be displayed in the Font combo is none of the available values
- * matches the current cursor position or text selection.
- * @type String
- * @example
- * // If the default site font is Arial, we may making it more explicit to the end user.
- * config.font_defaultLabel = 'Arial';
- */
-CKEDITOR.config.font_defaultLabel = '';
-
-/**
- * The style definition to be used to apply the font in the text.
- * @type Object
- * @example
- * // This is actually the default value for it.
- * config.font_style =
- * {
- * element : 'span',
- * styles : { 'font-family' : '#(family)' },
- * overrides : [ { element : 'font', attributes : { 'face' : null } } ]
- * };
- */
-CKEDITOR.config.font_style =
- {
- element : 'span',
- styles : { 'font-family' : '#(family)' },
- overrides : [ { element : 'font', attributes : { 'face' : null } } ]
- };
-
-/**
- * The list of fonts size to be displayed in the Font Size combo in the
- * toolbar. Entries are separated by semi-colons (;).
- *
- * Any kind of "CSS like" size can be used, like "12px", "2.3em", "130%",
- * "larger" or "x-small".
- *
- * A display name may be optionally defined by prefixing the entries with the
- * name and the slash character. For example, "Bigger Font/14px" will be
- * displayed as "Bigger Font" in the list, but will be outputted as "14px".
- * @type String
- * @default '8/8px;9/9px;10/10px;11/11px;12/12px;14/14px;16/16px;18/18px;20/20px;22/22px;24/24px;26/26px;28/28px;36/36px;48/48px;72/72px'
- * @example
- * config.fontSize_sizes = '16/16px;24/24px;48/48px;';
- * @example
- * config.fontSize_sizes = '12px;2.3em;130%;larger;x-small';
- * @example
- * config.fontSize_sizes = '12 Pixels/12px;Big/2.3em;30 Percent More/130%;Bigger/larger;Very Small/x-small';
- */
-CKEDITOR.config.fontSize_sizes =
- '8/8px;9/9px;10/10px;11/11px;12/12px;14/14px;16/16px;18/18px;20/20px;22/22px;24/24px;26/26px;28/28px;36/36px;48/48px;72/72px';
-
-/**
- * The text to be displayed in the Font Size combo is none of the available
- * values matches the current cursor position or text selection.
- * @type String
- * @example
- * // If the default site font size is 12px, we may making it more explicit to the end user.
- * config.fontSize_defaultLabel = '12px';
- */
-CKEDITOR.config.fontSize_defaultLabel = '';
-
-/**
- * The style definition to be used to apply the font size in the text.
- * @type Object
- * @example
- * // This is actually the default value for it.
- * config.fontSize_style =
- * {
- * element : 'span',
- * styles : { 'font-size' : '#(size)' },
- * overrides : [ { element : 'font', attributes : { 'size' : null } } ]
- * };
- */
-CKEDITOR.config.fontSize_style =
- {
- element : 'span',
- styles : { 'font-size' : '#(size)' },
- overrides : [ { element : 'font', attributes : { 'size' : null } } ]
- };
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +(function() +{ + function addCombo( editor, comboName, styleType, lang, entries, defaultLabel, styleDefinition ) + { + var config = editor.config; + + // Gets the list of fonts from the settings. + var names = entries.split( ';' ), + values = []; + + // Create style objects for all fonts. + var styles = {}; + for ( var i = 0 ; i < names.length ; i++ ) + { + var parts = names[ i ]; + + if ( parts ) + { + parts = parts.split( '/' ); + + var vars = {}, + name = names[ i ] = parts[ 0 ]; + + vars[ styleType ] = values[ i ] = parts[ 1 ] || name; + + styles[ name ] = new CKEDITOR.style( styleDefinition, vars ); + styles[ name ]._.definition.name = name; + } + else + names.splice( i--, 1 ); + } + + editor.ui.addRichCombo( comboName, + { + label : lang.label, + title : lang.panelTitle, + className : 'cke_' + ( styleType == 'size' ? 'fontSize' : 'font' ), + panel : + { + css : editor.skin.editor.css.concat( config.contentsCss ), + multiSelect : false, + attributes : { 'aria-label' : lang.panelTitle } + }, + + init : function() + { + this.startGroup( lang.panelTitle ); + + for ( var i = 0 ; i < names.length ; i++ ) + { + var name = names[ i ]; + + // Add the tag entry to the panel list. + this.add( name, styles[ name ].buildPreview(), name ); + } + }, + + onClick : function( value ) + { + editor.focus(); + editor.fire( 'saveSnapshot' ); + + var style = styles[ value ]; + + if ( this.getValue() == value ) + style.remove( editor.document ); + else + style.apply( editor.document ); + + editor.fire( 'saveSnapshot' ); + }, + + onRender : function() + { + editor.on( 'selectionChange', function( ev ) + { + var currentValue = this.getValue(); + + var elementPath = ev.data.path, + elements = elementPath.elements; + + // For each element into the elements path. + for ( var i = 0, element ; i < elements.length ; i++ ) + { + element = elements[i]; + + // Check if the element is removable by any of + // the styles. + for ( var value in styles ) + { + if ( styles[ value ].checkElementRemovable( element, true ) ) + { + if ( value != currentValue ) + this.setValue( value ); + return; + } + } + } + + // If no styles match, just empty it. + this.setValue( '', defaultLabel ); + }, + this); + } + }); + } + + CKEDITOR.plugins.add( 'font', + { + requires : [ 'richcombo', 'styles' ], + + init : function( editor ) + { + var config = editor.config; + + addCombo( editor, 'Font', 'family', editor.lang.font, config.font_names, config.font_defaultLabel, config.font_style ); + addCombo( editor, 'FontSize', 'size', editor.lang.fontSize, config.fontSize_sizes, config.fontSize_defaultLabel, config.fontSize_style ); + } + }); +})(); + +/** + * The list of fonts names to be displayed in the Font combo in the toolbar. + * Entries are separated by semi-colons (;), while it's possible to have more + * than one font for each entry, in the HTML way (separated by comma). + * + * A display name may be optionally defined by prefixing the entries with the + * name and the slash character. For example, "Arial/Arial, Helvetica, sans-serif" + * will be displayed as "Arial" in the list, but will be outputted as + * "Arial, Helvetica, sans-serif". + * @type String + * @example + * config.font_names = + * 'Arial/Arial, Helvetica, sans-serif;' + + * 'Times New Roman/Times New Roman, Times, serif;' + + * 'Verdana'; + * @example + * config.font_names = 'Arial;Times New Roman;Verdana'; + */ +CKEDITOR.config.font_names = + 'Arial/Arial, Helvetica, sans-serif;' + + 'Comic Sans MS/Comic Sans MS, cursive;' + + 'Courier New/Courier New, Courier, monospace;' + + 'Georgia/Georgia, serif;' + + 'Lucida Sans Unicode/Lucida Sans Unicode, Lucida Grande, sans-serif;' + + 'Tahoma/Tahoma, Geneva, sans-serif;' + + 'Times New Roman/Times New Roman, Times, serif;' + + 'Trebuchet MS/Trebuchet MS, Helvetica, sans-serif;' + + 'Verdana/Verdana, Geneva, sans-serif'; + +/** + * The text to be displayed in the Font combo is none of the available values + * matches the current cursor position or text selection. + * @type String + * @example + * // If the default site font is Arial, we may making it more explicit to the end user. + * config.font_defaultLabel = 'Arial'; + */ +CKEDITOR.config.font_defaultLabel = ''; + +/** + * The style definition to be used to apply the font in the text. + * @type Object + * @example + * // This is actually the default value for it. + * config.font_style = + * { + * element : 'span', + * styles : { 'font-family' : '#(family)' }, + * overrides : [ { element : 'font', attributes : { 'face' : null } } ] + * }; + */ +CKEDITOR.config.font_style = + { + element : 'span', + styles : { 'font-family' : '#(family)' }, + overrides : [ { element : 'font', attributes : { 'face' : null } } ] + }; + +/** + * The list of fonts size to be displayed in the Font Size combo in the + * toolbar. Entries are separated by semi-colons (;). + * + * Any kind of "CSS like" size can be used, like "12px", "2.3em", "130%", + * "larger" or "x-small". + * + * A display name may be optionally defined by prefixing the entries with the + * name and the slash character. For example, "Bigger Font/14px" will be + * displayed as "Bigger Font" in the list, but will be outputted as "14px". + * @type String + * @default '8/8px;9/9px;10/10px;11/11px;12/12px;14/14px;16/16px;18/18px;20/20px;22/22px;24/24px;26/26px;28/28px;36/36px;48/48px;72/72px' + * @example + * config.fontSize_sizes = '16/16px;24/24px;48/48px;'; + * @example + * config.fontSize_sizes = '12px;2.3em;130%;larger;x-small'; + * @example + * config.fontSize_sizes = '12 Pixels/12px;Big/2.3em;30 Percent More/130%;Bigger/larger;Very Small/x-small'; + */ +CKEDITOR.config.fontSize_sizes = + '8/8px;9/9px;10/10px;11/11px;12/12px;14/14px;16/16px;18/18px;20/20px;22/22px;24/24px;26/26px;28/28px;36/36px;48/48px;72/72px'; + +/** + * The text to be displayed in the Font Size combo is none of the available + * values matches the current cursor position or text selection. + * @type String + * @example + * // If the default site font size is 12px, we may making it more explicit to the end user. + * config.fontSize_defaultLabel = '12px'; + */ +CKEDITOR.config.fontSize_defaultLabel = ''; + +/** + * The style definition to be used to apply the font size in the text. + * @type Object + * @example + * // This is actually the default value for it. + * config.fontSize_style = + * { + * element : 'span', + * styles : { 'font-size' : '#(size)' }, + * overrides : [ { element : 'font', attributes : { 'size' : null } } ] + * }; + */ +CKEDITOR.config.fontSize_style = + { + element : 'span', + styles : { 'font-size' : '#(size)' }, + overrides : [ { element : 'font', attributes : { 'size' : null } } ] + }; diff --git a/_source/plugins/format/plugin.js b/_source/plugins/format/plugin.js index 402672d..f2d9df2 100644 --- a/_source/plugins/format/plugin.js +++ b/_source/plugins/format/plugin.js @@ -1,193 +1,194 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-CKEDITOR.plugins.add( 'format',
-{
- requires : [ 'richcombo', 'styles' ],
-
- init : function( editor )
- {
- var config = editor.config,
- lang = editor.lang.format;
-
- // Gets the list of tags from the settings.
- var tags = config.format_tags.split( ';' );
-
- // Create style objects for all defined styles.
- var styles = {};
- for ( var i = 0 ; i < tags.length ; i++ )
- {
- var tag = tags[ i ];
- styles[ tag ] = new CKEDITOR.style( config[ 'format_' + tag ] );
- }
-
- editor.ui.addRichCombo( 'Format',
- {
- label : lang.label,
- title : lang.panelTitle,
- className : 'cke_format',
- panel :
- {
- css : editor.skin.editor.css.concat( config.contentsCss ),
- multiSelect : false,
- attributes : { 'aria-label' : lang.panelTitle }
- },
-
- init : function()
- {
- this.startGroup( lang.panelTitle );
-
- for ( var tag in styles )
- {
- var label = lang[ 'tag_' + tag ];
-
- // Add the tag entry to the panel list.
- this.add( tag, '<' + tag + '>' + label + '</' + tag + '>', label );
- }
- },
-
- onClick : function( value )
- {
- editor.focus();
- editor.fire( 'saveSnapshot' );
-
- styles[ value ].apply( editor.document );
-
- // Save the undo snapshot after all changes are affected. (#4899)
- setTimeout( function()
- {
- editor.fire( 'saveSnapshot' );
- }, 0 );
- },
-
- onRender : function()
- {
- editor.on( 'selectionChange', function( ev )
- {
- var currentTag = this.getValue();
-
- var elementPath = ev.data.path;
-
- for ( var tag in styles )
- {
- if ( styles[ tag ].checkActive( elementPath ) )
- {
- if ( tag != currentTag )
- this.setValue( tag, editor.lang.format[ 'tag_' + tag ] );
- return;
- }
- }
-
- // If no styles match, just empty it.
- this.setValue( '' );
- },
- this);
- }
- });
- }
-});
-
-/**
- * A list of semi colon separated style names (by default tags) representing
- * the style definition for each entry to be displayed in the Format combo in
- * the toolbar. Each entry must have its relative definition configuration in a
- * setting named "format_(tagName)". For example, the "p" entry has its
- * definition taken from config.format_p.
- * @type String
- * @default 'p;h1;h2;h3;h4;h5;h6;pre;address;div'
- * @example
- * config.format_tags = 'p;h2;h3;pre'
- */
-CKEDITOR.config.format_tags = 'p;h1;h2;h3;h4;h5;h6;pre;address;div';
-
-/**
- * The style definition to be used to apply the "Normal" format.
- * @type Object
- * @default { element : 'p' }
- * @example
- * config.format_p = { element : 'p', attributes : { class : 'normalPara' } };
- */
-CKEDITOR.config.format_p = { element : 'p' };
-
-/**
- * The style definition to be used to apply the "Normal (DIV)" format.
- * @type Object
- * @default { element : 'div' }
- * @example
- * config.format_div = { element : 'div', attributes : { class : 'normalDiv' } };
- */
-CKEDITOR.config.format_div = { element : 'div' };
-
-/**
- * The style definition to be used to apply the "Formatted" format.
- * @type Object
- * @default { element : 'pre' }
- * @example
- * config.format_pre = { element : 'pre', attributes : { class : 'code' } };
- */
-CKEDITOR.config.format_pre = { element : 'pre' };
-
-/**
- * The style definition to be used to apply the "Address" format.
- * @type Object
- * @default { element : 'address' }
- * @example
- * config.format_address = { element : 'address', attributes : { class : 'styledAddress' } };
- */
-CKEDITOR.config.format_address = { element : 'address' };
-
-/**
- * The style definition to be used to apply the "Heading 1" format.
- * @type Object
- * @default { element : 'h1' }
- * @example
- * config.format_h1 = { element : 'h1', attributes : { class : 'contentTitle1' } };
- */
-CKEDITOR.config.format_h1 = { element : 'h1' };
-
-/**
- * The style definition to be used to apply the "Heading 1" format.
- * @type Object
- * @default { element : 'h2' }
- * @example
- * config.format_h2 = { element : 'h2', attributes : { class : 'contentTitle2' } };
- */
-CKEDITOR.config.format_h2 = { element : 'h2' };
-
-/**
- * The style definition to be used to apply the "Heading 1" format.
- * @type Object
- * @default { element : 'h3' }
- * @example
- * config.format_h3 = { element : 'h3', attributes : { class : 'contentTitle3' } };
- */
-CKEDITOR.config.format_h3 = { element : 'h3' };
-
-/**
- * The style definition to be used to apply the "Heading 1" format.
- * @type Object
- * @default { element : 'h4' }
- * @example
- * config.format_h4 = { element : 'h4', attributes : { class : 'contentTitle4' } };
- */
-CKEDITOR.config.format_h4 = { element : 'h4' };
-
-/**
- * The style definition to be used to apply the "Heading 1" format.
- * @type Object
- * @default { element : 'h5' }
- * @example
- * config.format_h5 = { element : 'h5', attributes : { class : 'contentTitle5' } };
- */
-CKEDITOR.config.format_h5 = { element : 'h5' };
-
-/**
- * The style definition to be used to apply the "Heading 1" format.
- * @type Object
- * @default { element : 'h6' }
- * @example
- * config.format_h6 = { element : 'h6', attributes : { class : 'contentTitle6' } };
- */
-CKEDITOR.config.format_h6 = { element : 'h6' };
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.plugins.add( 'format', +{ + requires : [ 'richcombo', 'styles' ], + + init : function( editor ) + { + var config = editor.config, + lang = editor.lang.format; + + // Gets the list of tags from the settings. + var tags = config.format_tags.split( ';' ); + + // Create style objects for all defined styles. + var styles = {}; + for ( var i = 0 ; i < tags.length ; i++ ) + { + var tag = tags[ i ]; + styles[ tag ] = new CKEDITOR.style( config[ 'format_' + tag ] ); + styles[ tag ]._.enterMode = editor.config.enterMode; + } + + editor.ui.addRichCombo( 'Format', + { + label : lang.label, + title : lang.panelTitle, + className : 'cke_format', + panel : + { + css : editor.skin.editor.css.concat( config.contentsCss ), + multiSelect : false, + attributes : { 'aria-label' : lang.panelTitle } + }, + + init : function() + { + this.startGroup( lang.panelTitle ); + + for ( var tag in styles ) + { + var label = lang[ 'tag_' + tag ]; + + // Add the tag entry to the panel list. + this.add( tag, '<' + tag + '>' + label + '</' + tag + '>', label ); + } + }, + + onClick : function( value ) + { + editor.focus(); + editor.fire( 'saveSnapshot' ); + + styles[ value ].apply( editor.document ); + + // Save the undo snapshot after all changes are affected. (#4899) + setTimeout( function() + { + editor.fire( 'saveSnapshot' ); + }, 0 ); + }, + + onRender : function() + { + editor.on( 'selectionChange', function( ev ) + { + var currentTag = this.getValue(); + + var elementPath = ev.data.path; + + for ( var tag in styles ) + { + if ( styles[ tag ].checkActive( elementPath ) ) + { + if ( tag != currentTag ) + this.setValue( tag, editor.lang.format[ 'tag_' + tag ] ); + return; + } + } + + // If no styles match, just empty it. + this.setValue( '' ); + }, + this); + } + }); + } +}); + +/** + * A list of semi colon separated style names (by default tags) representing + * the style definition for each entry to be displayed in the Format combo in + * the toolbar. Each entry must have its relative definition configuration in a + * setting named "format_(tagName)". For example, the "p" entry has its + * definition taken from config.format_p. + * @type String + * @default 'p;h1;h2;h3;h4;h5;h6;pre;address;div' + * @example + * config.format_tags = 'p;h2;h3;pre' + */ +CKEDITOR.config.format_tags = 'p;h1;h2;h3;h4;h5;h6;pre;address;div'; + +/** + * The style definition to be used to apply the "Normal" format. + * @type Object + * @default { element : 'p' } + * @example + * config.format_p = { element : 'p', attributes : { class : 'normalPara' } }; + */ +CKEDITOR.config.format_p = { element : 'p' }; + +/** + * The style definition to be used to apply the "Normal (DIV)" format. + * @type Object + * @default { element : 'div' } + * @example + * config.format_div = { element : 'div', attributes : { class : 'normalDiv' } }; + */ +CKEDITOR.config.format_div = { element : 'div' }; + +/** + * The style definition to be used to apply the "Formatted" format. + * @type Object + * @default { element : 'pre' } + * @example + * config.format_pre = { element : 'pre', attributes : { class : 'code' } }; + */ +CKEDITOR.config.format_pre = { element : 'pre' }; + +/** + * The style definition to be used to apply the "Address" format. + * @type Object + * @default { element : 'address' } + * @example + * config.format_address = { element : 'address', attributes : { class : 'styledAddress' } }; + */ +CKEDITOR.config.format_address = { element : 'address' }; + +/** + * The style definition to be used to apply the "Heading 1" format. + * @type Object + * @default { element : 'h1' } + * @example + * config.format_h1 = { element : 'h1', attributes : { class : 'contentTitle1' } }; + */ +CKEDITOR.config.format_h1 = { element : 'h1' }; + +/** + * The style definition to be used to apply the "Heading 1" format. + * @type Object + * @default { element : 'h2' } + * @example + * config.format_h2 = { element : 'h2', attributes : { class : 'contentTitle2' } }; + */ +CKEDITOR.config.format_h2 = { element : 'h2' }; + +/** + * The style definition to be used to apply the "Heading 1" format. + * @type Object + * @default { element : 'h3' } + * @example + * config.format_h3 = { element : 'h3', attributes : { class : 'contentTitle3' } }; + */ +CKEDITOR.config.format_h3 = { element : 'h3' }; + +/** + * The style definition to be used to apply the "Heading 1" format. + * @type Object + * @default { element : 'h4' } + * @example + * config.format_h4 = { element : 'h4', attributes : { class : 'contentTitle4' } }; + */ +CKEDITOR.config.format_h4 = { element : 'h4' }; + +/** + * The style definition to be used to apply the "Heading 1" format. + * @type Object + * @default { element : 'h5' } + * @example + * config.format_h5 = { element : 'h5', attributes : { class : 'contentTitle5' } }; + */ +CKEDITOR.config.format_h5 = { element : 'h5' }; + +/** + * The style definition to be used to apply the "Heading 1" format. + * @type Object + * @default { element : 'h6' } + * @example + * config.format_h6 = { element : 'h6', attributes : { class : 'contentTitle6' } }; + */ +CKEDITOR.config.format_h6 = { element : 'h6' }; diff --git a/_source/plugins/forms/dialogs/checkbox.js b/_source/plugins/forms/dialogs/checkbox.js index 1016d38..980ae08 100644 --- a/_source/plugins/forms/dialogs/checkbox.js +++ b/_source/plugins/forms/dialogs/checkbox.js @@ -1,143 +1,155 @@ -/*
-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 )
-{
- return {
- title : editor.lang.checkboxAndRadio.checkboxTitle,
- minWidth : 350,
- minHeight : 140,
- onShow : function()
- {
- delete this.checkbox;
-
- var element = this.getParentEditor().getSelection().getSelectedElement();
-
- if ( element && element.getAttribute( 'type' ) == "checkbox" )
- {
- this.checkbox = element;
- this.setupContent( element );
- }
- },
- onOk : function()
- {
- var editor,
- element = this.checkbox,
- isInsertMode = !element;
-
- if ( isInsertMode )
- {
- editor = this.getParentEditor();
- element = editor.document.createElement( 'input' );
- element.setAttribute( 'type', 'checkbox' );
- }
-
- if ( isInsertMode )
- editor.insertElement( element );
- this.commitContent( { element : element } );
- },
- contents : [
- {
- id : 'info',
- label : editor.lang.checkboxAndRadio.checkboxTitle,
- title : editor.lang.checkboxAndRadio.checkboxTitle,
- startupFocus : 'txtName',
- elements : [
- {
- id : 'txtName',
- type : 'text',
- label : editor.lang.common.name,
- 'default' : '',
- accessKey : 'N',
- setup : function( element )
- {
- this.setValue(
- element.getAttribute( '_cke_saved_name' ) ||
- element.getAttribute( 'name' ) ||
- '' );
- },
- commit : function( data )
- {
- var element = data.element;
-
- // IE failed to update 'name' property on input elements, protect it now.
- if ( this.getValue() )
- element.setAttribute( '_cke_saved_name', this.getValue() );
- else
- {
- element.removeAttribute( '_cke_saved_name' );
- element.removeAttribute( 'name' );
- }
- }
- },
- {
- id : 'txtValue',
- type : 'text',
- label : editor.lang.checkboxAndRadio.value,
- 'default' : '',
- accessKey : 'V',
- setup : function( element )
- {
- 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,
- value = this.getValue();
-
- if ( value && !( CKEDITOR.env.ie && value == 'on' ) )
- element.setAttribute( 'value', value );
- else
- element.removeAttribute( 'value' );
- }
- },
- {
- id : 'cmbSelected',
- type : 'checkbox',
- label : editor.lang.checkboxAndRadio.selected,
- 'default' : '',
- accessKey : 'S',
- value : "checked",
- setup : function( element )
- {
- this.setValue( element.getAttribute( 'checked' ) );
- },
- commit : function( data )
- {
- var element = data.element;
-
- if ( CKEDITOR.env.ie )
- {
- var isElementChecked = !!element.getAttribute( 'checked' );
- var isChecked = !!this.getValue();
-
- if ( isElementChecked != isChecked )
- {
- var replace = CKEDITOR.dom.element.createFromHtml( '<input type="checkbox"'
- + ( isChecked ? ' checked="checked"' : '' )
- + '/>', editor.document );
-
- element.copyAttributes( replace, { type : 1, checked : 1 } );
- replace.replace( element );
- editor.getSelection().selectElement( replace );
- data.element = replace;
- }
- }
- else
- {
- var value = this.getValue();
- if ( value )
- element.setAttribute( 'checked', 'checked' );
- else
- element.removeAttribute( 'checked' );
- }
- }
- }
- ]
- }
- ]
- };
-});
+/* +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 ) +{ + return { + title : editor.lang.checkboxAndRadio.checkboxTitle, + minWidth : 350, + minHeight : 140, + onShow : function() + { + delete this.checkbox; + + var element = this.getParentEditor().getSelection().getSelectedElement(); + + if ( element && element.getAttribute( 'type' ) == "checkbox" ) + { + this.checkbox = element; + this.setupContent( element ); + } + }, + onOk : function() + { + var editor, + element = this.checkbox, + isInsertMode = !element; + + if ( isInsertMode ) + { + editor = this.getParentEditor(); + element = editor.document.createElement( 'input' ); + element.setAttribute( 'type', 'checkbox' ); + } + + if ( isInsertMode ) + editor.insertElement( element ); + this.commitContent( { element : element } ); + }, + contents : [ + { + id : 'info', + label : editor.lang.checkboxAndRadio.checkboxTitle, + title : editor.lang.checkboxAndRadio.checkboxTitle, + startupFocus : 'txtName', + elements : [ + { + id : 'txtName', + type : 'text', + label : editor.lang.common.name, + 'default' : '', + accessKey : 'N', + setup : function( element ) + { + this.setValue( + element.getAttribute( '_cke_saved_name' ) || + element.getAttribute( 'name' ) || + '' ); + }, + commit : function( data ) + { + var element = data.element; + + // IE failed to update 'name' property on input elements, protect it now. + if ( this.getValue() ) + element.setAttribute( '_cke_saved_name', this.getValue() ); + else + { + element.removeAttribute( '_cke_saved_name' ); + element.removeAttribute( 'name' ); + } + } + }, + { + id : 'txtValue', + type : 'text', + label : editor.lang.checkboxAndRadio.value, + 'default' : '', + accessKey : 'V', + setup : function( element ) + { + 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, + value = this.getValue(); + + if ( value && !( CKEDITOR.env.ie && value == 'on' ) ) + element.setAttribute( 'value', value ); + else + { + if ( CKEDITOR.env.ie ) + { + // Remove attribute 'value' of checkbox #4721. + var checkbox = new CKEDITOR.dom.element( 'input', element.getDocument() ); + element.copyAttributes( checkbox, { value: 1 } ); + checkbox.replace( element ); + editor.getSelection().selectElement( checkbox ); + data.element = checkbox; + } + else + element.removeAttribute( 'value' ); + } + } + }, + { + id : 'cmbSelected', + type : 'checkbox', + label : editor.lang.checkboxAndRadio.selected, + 'default' : '', + accessKey : 'S', + value : "checked", + setup : function( element ) + { + this.setValue( element.getAttribute( 'checked' ) ); + }, + commit : function( data ) + { + var element = data.element; + + if ( CKEDITOR.env.ie ) + { + var isElementChecked = !!element.getAttribute( 'checked' ); + var isChecked = !!this.getValue(); + + if ( isElementChecked != isChecked ) + { + var replace = CKEDITOR.dom.element.createFromHtml( '<input type="checkbox"' + + ( isChecked ? ' checked="checked"' : '' ) + + '/>', editor.document ); + + element.copyAttributes( replace, { type : 1, checked : 1 } ); + replace.replace( element ); + editor.getSelection().selectElement( replace ); + data.element = replace; + } + } + else + { + var value = this.getValue(); + if ( value ) + element.setAttribute( 'checked', 'checked' ); + else + element.removeAttribute( 'checked' ); + } + } + } + ] + } + ] + }; +}); diff --git a/_source/plugins/forms/dialogs/hiddenfield.js b/_source/plugins/forms/dialogs/hiddenfield.js index 21fa26f..3e31706 100644 --- a/_source/plugins/forms/dialogs/hiddenfield.js +++ b/_source/plugins/forms/dialogs/hiddenfield.js @@ -1,91 +1,98 @@ -/*
-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 )
-{
- return {
- title : editor.lang.hidden.title,
- minWidth : 350,
- minHeight : 110,
- onShow : function()
- {
- delete this.hiddenField;
-
- var element = this.getParentEditor().getSelection().getSelectedElement();
- if ( element && element.getName() == "input" && element.getAttribute( 'type' ) == "checkbox" )
- {
- this.hiddenField = element;
- this.setupContent( element );
- }
- },
- onOk : function()
- {
- var editor,
- element = this.hiddenField,
- isInsertMode = !element;
-
- if ( isInsertMode )
- {
- editor = this.getParentEditor();
- element = editor.document.createElement( 'input' );
- element.setAttribute( 'type', 'hidden' );
- }
-
- if ( isInsertMode )
- editor.insertElement( element );
- this.commitContent( element );
- },
- contents : [
- {
- id : 'info',
- label : editor.lang.hidden.title,
- title : editor.lang.hidden.title,
- elements : [
- {
- id : '_cke_saved_name',
- type : 'text',
- label : editor.lang.hidden.name,
- 'default' : '',
- accessKey : 'N',
- setup : function( element )
- {
- this.setValue(
- element.getAttribute( '_cke_saved_name' ) ||
- element.getAttribute( 'name' ) ||
- '' );
- },
- commit : function( element )
- {
- if ( this.getValue() )
- element.setAttribute( '_cke_saved_name', this.getValue() );
- else
- {
- element.removeAttribute( '_cke_saved_name' );
- element.removeAttribute( 'name' );
- }
- }
- },
- {
- id : 'value',
- type : 'text',
- label : editor.lang.hidden.value,
- 'default' : '',
- accessKey : 'V',
- setup : function( element )
- {
- this.setValue( element.getAttribute( 'value' ) || '' );
- },
- commit : function( element )
- {
- if ( this.getValue() )
- element.setAttribute( 'value', this.getValue() );
- else
- element.removeAttribute( 'value' );
- }
- }
- ]
- }
- ]
- };
-});
+/* +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 ) +{ + return { + title : editor.lang.hidden.title, + hiddenField : null, + minWidth : 350, + minHeight : 110, + onShow : function() + { + delete this.hiddenField; + + var editor = this.getParentEditor(), + selection = editor.getSelection(), + element = selection.getSelectedElement(); + + if ( element && element.getAttribute( '_cke_real_element_type' ) && element.getAttribute( '_cke_real_element_type' ) == 'hiddenfield' ) + { + this.hiddenField = element; + element = editor.restoreRealElement( this.hiddenField ); + this.setupContent( element ); + selection.selectElement( this.hiddenField ); + } + }, + onOk : function() + { + var name = this.getValueOf( 'info', '_cke_saved_name' ), + value = this.getValueOf( 'info', 'value' ), + editor = this.getParentEditor(), + element = CKEDITOR.env.ie ? editor.document.createElement( '<input name="' + CKEDITOR.tools.htmlEncode( name ) + '">' ) : editor.document.createElement( 'input' ); + + element.setAttribute( 'type', 'hidden' ); + this.commitContent( element ); + var fakeElement = editor.createFakeElement( element, 'cke_hidden', 'hiddenfield' ); + if ( !this.hiddenField ) + editor.insertElement( fakeElement ); + else + { + fakeElement.replace( this.hiddenField ); + editor.getSelection().selectElement( fakeElement ); + } + return true; + }, + contents : [ + { + id : 'info', + label : editor.lang.hidden.title, + title : editor.lang.hidden.title, + elements : [ + { + id : '_cke_saved_name', + type : 'text', + label : editor.lang.hidden.name, + 'default' : '', + accessKey : 'N', + setup : function( element ) + { + this.setValue( + element.getAttribute( '_cke_saved_name' ) || + element.getAttribute( 'name' ) || + '' ); + }, + commit : function( element ) + { + if ( this.getValue() ) + element.setAttribute( 'name', this.getValue() ); + else + { + element.removeAttribute( 'name' ); + } + } + }, + { + id : 'value', + type : 'text', + label : editor.lang.hidden.value, + 'default' : '', + accessKey : 'V', + setup : function( element ) + { + this.setValue( element.getAttribute( 'value' ) || '' ); + }, + commit : function( element ) + { + if ( this.getValue() ) + element.setAttribute( 'value', this.getValue() ); + else + element.removeAttribute( 'value' ); + } + } + ] + } + ] + }; +}); diff --git a/_source/plugins/forms/images/hiddenfield.gif b/_source/plugins/forms/images/hiddenfield.gif Binary files differnew file mode 100644 index 0000000..953f643 --- /dev/null +++ b/_source/plugins/forms/images/hiddenfield.gif diff --git a/_source/plugins/forms/plugin.js b/_source/plugins/forms/plugin.js index 5978e0b..f82dbdf 100644 --- a/_source/plugins/forms/plugin.js +++ b/_source/plugins/forms/plugin.js @@ -1,217 +1,281 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-/**
- * @file Forms Plugin
- */
-
-CKEDITOR.plugins.add( 'forms',
-{
- init : function( editor )
- {
- var lang = editor.lang;
-
- editor.addCss(
- 'form' +
- '{' +
- 'border: 1px dotted #FF0000;' +
- 'padding: 2px;' +
- '}' );
-
- // All buttons use the same code to register. So, to avoid
- // duplications, let's use this tool function.
- var addButtonCommand = function( buttonName, commandName, dialogFile )
- {
- editor.addCommand( commandName, new CKEDITOR.dialogCommand( commandName ) );
-
- editor.ui.addButton( buttonName,
- {
- label : lang.common[ buttonName.charAt(0).toLowerCase() + buttonName.slice(1) ],
- command : commandName
- });
- CKEDITOR.dialog.add( commandName, dialogFile );
- };
-
- var dialogPath = this.path + 'dialogs/';
- addButtonCommand( 'Form', 'form', dialogPath + 'form.js' );
- addButtonCommand( 'Checkbox', 'checkbox', dialogPath + 'checkbox.js' );
- addButtonCommand( 'Radio', 'radio', dialogPath + 'radio.js' );
- addButtonCommand( 'TextField', 'textfield', dialogPath + 'textfield.js' );
- addButtonCommand( 'Textarea', 'textarea', dialogPath + 'textarea.js' );
- addButtonCommand( 'Select', 'select', dialogPath + 'select.js' );
- addButtonCommand( 'Button', 'button', dialogPath + 'button.js' );
- addButtonCommand( 'ImageButton', 'imagebutton', CKEDITOR.plugins.getPath('image') + 'dialogs/image.js' );
- addButtonCommand( 'HiddenField', 'hiddenfield', dialogPath + 'hiddenfield.js' );
-
- // If the "menu" plugin is loaded, register the menu items.
- if ( editor.addMenuItems )
- {
- editor.addMenuItems(
- {
- form :
- {
- label : lang.form.menu,
- command : 'form',
- group : 'form'
- },
-
- checkbox :
- {
- label : lang.checkboxAndRadio.checkboxTitle,
- command : 'checkbox',
- group : 'checkbox'
- },
-
- radio :
- {
- label : lang.checkboxAndRadio.radioTitle,
- command : 'radio',
- group : 'radio'
- },
-
- textfield :
- {
- label : lang.textfield.title,
- command : 'textfield',
- group : 'textfield'
- },
-
- hiddenfield :
- {
- label : lang.hidden.title,
- command : 'hiddenfield',
- group : 'hiddenfield'
- },
-
- imagebutton :
- {
- label : lang.image.titleButton,
- command : 'imagebutton',
- group : 'imagebutton'
- },
-
- button :
- {
- label : lang.button.title,
- command : 'button',
- group : 'button'
- },
-
- select :
- {
- label : lang.select.title,
- command : 'select',
- group : 'select'
- },
-
- textarea :
- {
- label : lang.textarea.title,
- command : 'textarea',
- group : 'textarea'
- }
- });
- }
-
- // If the "contextmenu" plugin is loaded, register the listeners.
- if ( editor.contextMenu )
- {
- editor.contextMenu.addListener( function( element )
- {
- if ( element && element.hasAscendant( 'form', true ) )
- return { form : CKEDITOR.TRISTATE_OFF };
- });
-
- editor.contextMenu.addListener( function( element )
- {
- if ( element )
- {
- var name = element.getName();
-
- if ( name == 'select' )
- return { select : CKEDITOR.TRISTATE_OFF };
-
- if ( name == 'textarea' )
- return { textarea : CKEDITOR.TRISTATE_OFF };
-
- if ( name == 'input' )
- {
- var type = element.getAttribute( 'type' );
-
- if ( type == 'text' || type == 'password' )
- return { textfield : CKEDITOR.TRISTATE_OFF };
-
- if ( type == 'button' || type == 'submit' || type == 'reset' )
- return { button : CKEDITOR.TRISTATE_OFF };
-
- if ( type == 'checkbox' )
- return { checkbox : CKEDITOR.TRISTATE_OFF };
-
- if ( type == 'radio' )
- return { radio : CKEDITOR.TRISTATE_OFF };
-
- if ( type == 'image' )
- return { imagebutton : CKEDITOR.TRISTATE_OFF };
- }
-
- if ( name == 'img' && element.getAttribute( '_cke_real_element_type' ) == 'hiddenfield' )
- return { hiddenfield : CKEDITOR.TRISTATE_OFF };
- }
- });
- }
- },
-
- 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' ]
-} );
-
-if ( CKEDITOR.env.ie )
-{
- CKEDITOR.dom.element.prototype.hasAttribute = function( name )
- {
- var $attr = this.$.attributes.getNamedItem( name );
-
- if ( this.getName() == 'input' )
- {
- switch ( name )
- {
- case 'class' :
- return this.$.className.length > 0;
- case 'checked' :
- return !!this.$.checked;
- case 'value' :
- var type = this.getAttribute( 'type' );
- if ( type == 'checkbox' || type == 'radio' )
- return this.$.value != 'on';
- break;
- default:
- }
- }
-
- return !!( $attr && $attr.specified );
- };
-}
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @file Forms Plugin + */ + +CKEDITOR.plugins.add( 'forms', +{ + init : function( editor ) + { + var lang = editor.lang; + + editor.addCss( + 'form' + + '{' + + 'border: 1px dotted #FF0000;' + + 'padding: 2px;' + + '}\n' ); + + editor.addCss( + 'img.cke_hidden' + + '{' + + 'background-image: url(' + CKEDITOR.getUrl( this.path + 'images/hiddenfield.gif' ) + ');' + + 'background-position: center center;' + + 'background-repeat: no-repeat;' + + 'border: 1px solid #a9a9a9;' + + 'width: 16px;' + + 'height: 16px;' + + '}' ); + + // All buttons use the same code to register. So, to avoid + // duplications, let's use this tool function. + var addButtonCommand = function( buttonName, commandName, dialogFile ) + { + editor.addCommand( commandName, new CKEDITOR.dialogCommand( commandName ) ); + + editor.ui.addButton( buttonName, + { + label : lang.common[ buttonName.charAt(0).toLowerCase() + buttonName.slice(1) ], + command : commandName + }); + CKEDITOR.dialog.add( commandName, dialogFile ); + }; + + var dialogPath = this.path + 'dialogs/'; + addButtonCommand( 'Form', 'form', dialogPath + 'form.js' ); + addButtonCommand( 'Checkbox', 'checkbox', dialogPath + 'checkbox.js' ); + addButtonCommand( 'Radio', 'radio', dialogPath + 'radio.js' ); + addButtonCommand( 'TextField', 'textfield', dialogPath + 'textfield.js' ); + addButtonCommand( 'Textarea', 'textarea', dialogPath + 'textarea.js' ); + addButtonCommand( 'Select', 'select', dialogPath + 'select.js' ); + addButtonCommand( 'Button', 'button', dialogPath + 'button.js' ); + addButtonCommand( 'ImageButton', 'imagebutton', CKEDITOR.plugins.getPath('image') + 'dialogs/image.js' ); + addButtonCommand( 'HiddenField', 'hiddenfield', dialogPath + 'hiddenfield.js' ); + + // If the "menu" plugin is loaded, register the menu items. + if ( editor.addMenuItems ) + { + editor.addMenuItems( + { + form : + { + label : lang.form.menu, + command : 'form', + group : 'form' + }, + + checkbox : + { + label : lang.checkboxAndRadio.checkboxTitle, + command : 'checkbox', + group : 'checkbox' + }, + + radio : + { + label : lang.checkboxAndRadio.radioTitle, + command : 'radio', + group : 'radio' + }, + + textfield : + { + label : lang.textfield.title, + command : 'textfield', + group : 'textfield' + }, + + hiddenfield : + { + label : lang.hidden.title, + command : 'hiddenfield', + group : 'hiddenfield' + }, + + imagebutton : + { + label : lang.image.titleButton, + command : 'imagebutton', + group : 'imagebutton' + }, + + button : + { + label : lang.button.title, + command : 'button', + group : 'button' + }, + + select : + { + label : lang.select.title, + command : 'select', + group : 'select' + }, + + textarea : + { + label : lang.textarea.title, + command : 'textarea', + group : 'textarea' + } + }); + } + + // If the "contextmenu" plugin is loaded, register the listeners. + if ( editor.contextMenu ) + { + editor.contextMenu.addListener( function( element ) + { + if ( element && element.hasAscendant( 'form', true ) ) + return { form : CKEDITOR.TRISTATE_OFF }; + }); + + editor.contextMenu.addListener( function( element ) + { + if ( element ) + { + var name = element.getName(); + + if ( name == 'select' ) + return { select : CKEDITOR.TRISTATE_OFF }; + + if ( name == 'textarea' ) + return { textarea : CKEDITOR.TRISTATE_OFF }; + + if ( name == 'input' ) + { + var type = element.getAttribute( 'type' ); + + if ( type == 'text' || type == 'password' ) + return { textfield : CKEDITOR.TRISTATE_OFF }; + + if ( type == 'button' || type == 'submit' || type == 'reset' ) + return { button : CKEDITOR.TRISTATE_OFF }; + + if ( type == 'checkbox' ) + return { checkbox : CKEDITOR.TRISTATE_OFF }; + + if ( type == 'radio' ) + return { radio : CKEDITOR.TRISTATE_OFF }; + + if ( type == 'image' ) + return { imagebutton : CKEDITOR.TRISTATE_OFF }; + } + + if ( name == 'img' && element.getAttribute( '_cke_real_element_type' ) == 'hiddenfield' ) + return { hiddenfield : CKEDITOR.TRISTATE_OFF }; + } + }); + } + + editor.on( 'doubleclick', function( evt ) + { + var element = evt.data.element; + + if ( element.is( 'form' ) ) + evt.data.dialog = 'form'; + else if ( element.is( 'select' ) ) + evt.data.dialog = 'select'; + else if ( element.is( 'textarea' ) ) + evt.data.dialog = 'textarea'; + else if ( element.is( 'img' ) && element.getAttribute( '_cke_real_element_type' ) == 'hiddenfield' ) + evt.data.dialog = 'hiddenfield'; + else if ( element.is( 'input' ) ) + { + var type = element.getAttribute( 'type' ); + + switch ( type ) + { + case 'text' : case 'password': + evt.data.dialog = 'textfield'; + break; + case 'button' : case 'submit' : case 'reset' : + evt.data.dialog = 'button'; + break; + case 'checkbox' : + evt.data.dialog = 'checkbox'; + break; + case 'radio' : + evt.data.dialog = 'radio'; + break; + case 'image' : + evt.data.dialog = 'imagebutton'; + break; + } + } + }); + }, + + afterInit : function( editor ) + { + var dataProcessor = editor.dataProcessor, + htmlFilter = dataProcessor && dataProcessor.htmlFilter, + dataFilter = dataProcessor && dataProcessor.dataFilter; + + // Cleanup certain IE form elements default values. + if ( CKEDITOR.env.ie ) + { + 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; + } + } + } ); + } + + if ( dataFilter ) + { + dataFilter.addRules( + { + elements : + { + input : function( element ) + { + if ( element.attributes.type == 'hidden' ) + return editor.createFakeParserElement( element, 'cke_hidden', 'hiddenfield' ); + } + } + } ); + } + }, + requires : [ 'image', 'fakeobjects' ] +} ); + +if ( CKEDITOR.env.ie ) +{ + CKEDITOR.dom.element.prototype.hasAttribute = function( name ) + { + var $attr = this.$.attributes.getNamedItem( name ); + + if ( this.getName() == 'input' ) + { + switch ( name ) + { + case 'class' : + return this.$.className.length > 0; + case 'checked' : + return !!this.$.checked; + case 'value' : + var type = this.getAttribute( 'type' ); + if ( type == 'checkbox' || type == 'radio' ) + return this.$.value != 'on'; + break; + default: + } + } + + return !!( $attr && $attr.specified ); + }; +} diff --git a/_source/plugins/htmldataprocessor/plugin.js b/_source/plugins/htmldataprocessor/plugin.js index b449355..503cae5 100644 --- a/_source/plugins/htmldataprocessor/plugin.js +++ b/_source/plugins/htmldataprocessor/plugin.js @@ -1,458 +1,485 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-(function()
-{
- // Regex to scan for at the end of blocks, which are actually placeholders.
- // Safari transforms the to \xa0. (#4172)
- var tailNbspRegex = /^[\t\r\n ]*(?: |\xa0)$/;
-
- var protectedSourceMarker = '{cke_protected}';
-
- // Return the last non-space child node of the block (#4344).
- function lastNoneSpaceChild( block )
- {
- var lastIndex = block.children.length,
- last = block.children[ lastIndex - 1 ];
- while ( last && last.type == CKEDITOR.NODE_TEXT && !CKEDITOR.tools.trim( last.value ) )
- last = block.children[ --lastIndex ];
- return last;
- }
-
- function trimFillers( block, fromSource )
- {
- // If the current node is a block, and if we're converting from source or
- // we're not in IE then search for and remove any tailing BR node.
- //
- // Also, any at the end of blocks are fillers, remove them as well.
- // (#2886)
- var children = block.children, lastChild = lastNoneSpaceChild( block );
- if ( lastChild )
- {
- if ( ( fromSource || !CKEDITOR.env.ie ) && lastChild.type == CKEDITOR.NODE_ELEMENT && lastChild.name == 'br' )
- children.pop();
- if ( lastChild.type == CKEDITOR.NODE_TEXT && tailNbspRegex.test( lastChild.value ) )
- children.pop();
- }
- }
-
- function blockNeedsExtension( block )
- {
- var lastChild = lastNoneSpaceChild( block );
- return !lastChild || lastChild.type == CKEDITOR.NODE_ELEMENT && lastChild.name == 'br';
- }
-
- function extendBlockForDisplay( block )
- {
- trimFillers( block, true );
-
- if ( blockNeedsExtension( block ) )
- {
- if ( CKEDITOR.env.ie )
- block.add( new CKEDITOR.htmlParser.text( '\xa0' ) );
- else
- block.add( new CKEDITOR.htmlParser.element( 'br', {} ) );
- }
- }
-
- function extendBlockForOutput( block )
- {
- trimFillers( block );
-
- if ( blockNeedsExtension( block ) )
- block.add( new CKEDITOR.htmlParser.text( '\xa0' ) );
- }
-
- var dtd = CKEDITOR.dtd;
-
- // Find out the list of block-like tags that can contain <br>.
- var blockLikeTags = CKEDITOR.tools.extend( {}, dtd.$block, dtd.$listItem, dtd.$tableContent );
- for ( var i in blockLikeTags )
- {
- if ( ! ( 'br' in dtd[i] ) )
- delete blockLikeTags[i];
- }
- // We just avoid filler in <pre> right now.
- // TODO: Support filler for <pre>, line break is also occupy line height.
- delete blockLikeTags.pre;
- var defaultDataFilterRules =
- {
- attributeNames :
- [
- // Event attributes (onXYZ) must not be directly set. They can become
- // active in the editing area (IE|WebKit).
- [ ( /^on/ ), '_cke_pa_on' ]
- ]
- };
-
- var defaultDataBlockFilterRules = { elements : {} };
-
- for ( i in blockLikeTags )
- defaultDataBlockFilterRules.elements[ i ] = extendBlockForDisplay;
-
- var defaultHtmlFilterRules =
- {
- elementNames :
- [
- // Remove the "cke:" namespace prefix.
- [ ( /^cke:/ ), '' ],
-
- // Ignore <?xml:namespace> tags.
- [ ( /^\?xml:namespace$/ ), '' ]
- ],
-
- attributeNames :
- [
- // Attributes saved for changes and protected attributes.
- [ ( /^_cke_(saved|pa)_/ ), '' ],
-
- // All "_cke" attributes are to be ignored.
- [ ( /^_cke.*/ ), '' ],
-
- [ 'hidefocus', '' ]
- ],
-
- elements :
- {
- $ : function( element )
- {
- 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++ )
- {
- savedAttributeName = '_cke_saved_' + attributeNames[ i ];
- savedAttributeName in attribs && ( delete attribs[ attributeNames[ i ] ] );
- }
- }
-
- return element;
- },
-
- embed : function( element )
- {
- var parent = element.parent;
-
- // If the <embed> is child of a <object>, copy the width
- // and height attributes from it.
- if ( parent && parent.name == 'object' )
- {
- var parentWidth = parent.attributes.width,
- parentHeight = parent.attributes.height;
- parentWidth && ( element.attributes.width = parentWidth );
- parentHeight && ( element.attributes.height = parentHeight );
- }
- },
- // Restore param elements into self-closing.
- param : function( param )
- {
- param.children = [];
- param.isEmpty = true;
- return param;
- },
-
- // Remove empty link but not empty anchor.(#3829)
- a : function( element )
- {
- if ( !( element.children.length ||
- element.attributes.name ||
- element.attributes._cke_saved_name ) )
- {
- 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';
- },
-
- title : function( element )
- {
- element.children[ 0 ].value = element.attributes[ '_cke_title' ];
- }
- },
-
- attributes :
- {
- 'class' : function( value, element )
- {
- // Remove all class names starting with "cke_".
- return CKEDITOR.tools.ltrim( value.replace( /(?:^|\s+)cke_[^\s]*/g, '' ) ) || false;
- }
- },
-
- comment : function( contents )
- {
- // If this is a comment for protected source.
- if ( contents.substr( 0, protectedSourceMarker.length ) == protectedSourceMarker )
- {
- // 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;
- }
- };
-
- var defaultHtmlBlockFilterRules = { elements : {} };
-
- for ( i in blockLikeTags )
- defaultHtmlBlockFilterRules.elements[ i ] = extendBlockForOutput;
-
- if ( CKEDITOR.env.ie )
- {
- // IE outputs style attribute in capital letters. We should convert
- // them back to lower case.
- defaultHtmlFilterRules.attributes.style = function( value, element )
- {
- return value.toLowerCase();
- };
- }
-
- 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' );
- }
-
- function protectElements( html )
- {
- return html.replace( protectElementsRegex, function( match )
- {
- return '<cke:encoded>' + encodeURIComponent( match ) + '</cke:encoded>';
- });
- }
-
- function unprotectElements( html )
- {
- 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:$1$2></cke:$1>' );
- }
-
- function protectRealComments( html )
- {
- return html.replace( /<!--(?!{cke_protected})[\s\S]+?-->/g, function( match )
- {
- return '<!--' + protectedSourceMarker +
- '{C}' +
- encodeURIComponent( match ).replace( /--/g, '%2D%2D' ) +
- '-->';
- });
- }
-
- function unprotectRealComments( html )
- {
- return html.replace( /<!--\{cke_protected\}\{C\}([\s\S]+?)-->/g, function( match, data )
- {
- return decodeURIComponent( data );
- });
- }
-
- function protectSource( data, protectRegexes )
- {
- var protectedHtml = [],
- tempRegex = /<\!--\{cke_temp(comment)?\}(\d*?)-->/g;
-
- var regexes =
- [
- // Script tags will also be forced to be protected, otherwise
- // IE will execute them.
- ( /<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( $, isComment, id )
- {
- return protectedHtml[ id ];
- }
- );
- return '<!--{cke_temp}' + ( protectedHtml.push( match ) - 1 ) + '-->';
- });
- }
- data = data.replace( tempRegex, function( $, isComment, id )
- {
- return '<!--' + protectedSourceMarker +
- ( isComment ? '{C}' : '' ) +
- encodeURIComponent( protectedHtml[ id ] ).replace( /--/g, '%2D%2D' ) +
- '-->';
- }
- );
- return data;
- }
-
- CKEDITOR.plugins.add( 'htmldataprocessor',
- {
- requires : [ 'htmlwriter' ],
-
- init : function( editor )
- {
- var dataProcessor = editor.dataProcessor = new CKEDITOR.htmlDataProcessor( editor );
-
- dataProcessor.writer.forceSimpleAmpersand = editor.config.forceSimpleAmpersand;
-
- dataProcessor.dataFilter.addRules( defaultDataFilterRules );
- dataProcessor.dataFilter.addRules( defaultDataBlockFilterRules );
- dataProcessor.htmlFilter.addRules( defaultHtmlFilterRules );
- dataProcessor.htmlFilter.addRules( defaultHtmlBlockFilterRules );
- }
- });
-
- CKEDITOR.htmlDataProcessor = function( editor )
- {
- this.editor = editor;
-
- this.writer = new CKEDITOR.htmlWriter();
- this.dataFilter = new CKEDITOR.htmlParser.filter();
- this.htmlFilter = new CKEDITOR.htmlParser.filter();
- };
-
- CKEDITOR.htmlDataProcessor.prototype =
- {
- toHtml : function( data, fixForBody )
- {
- // The source data is already HTML, but we need to clean
- // it up and apply the filter.
-
- data = protectSource( data, this.editor.config.protectedSource );
-
- // Before anything, we must protect the URL attributes as the
- // browser may changing them when setting the innerHTML later in
- // the code.
- data = protectAttributes( 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)
- data = protectElementsNames( data );
-
- // All none-IE browsers ignore self-closed custom elements,
- // protecting them into open-close. (#3591)
- data = protectSelfClosingElements( data );
-
- // Call the browser to help us fixing a possibly invalid HTML
- // structure.
- var div = new CKEDITOR.dom.element( 'div' );
- // Add fake character to workaround IE comments bug. (#3801)
- div.setHtml( 'a' + data );
- data = div.getHtml().substr( 1 );
-
- // 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.
- var fragment = CKEDITOR.htmlParser.fragment.fromHtml( data, fixForBody ),
- writer = new CKEDITOR.htmlParser.basicWriter();
-
- fragment.writeHtml( writer, this.dataFilter );
- data = writer.getHtml( true );
-
- // Protect the real comments again.
- data = protectRealComments( data );
-
- return data;
- },
-
- toDataFormat : function( html, fixForBody )
- {
- var writer = this.writer,
- fragment = CKEDITOR.htmlParser.fragment.fromHtml( html, fixForBody );
-
- writer.reset();
-
- fragment.writeHtml( writer, this.htmlFilter );
-
- return writer.getHtml( true );
- }
- };
-})();
-
-/**
- * Whether to force using "&" instead of "&amp;" in elements attributes
- * values. It's not recommended to change this setting for compliance with the
- * W3C XHTML 1.0 standards
- * (<a href="http://www.w3.org/TR/xhtml1/#C_12">C.12, XHTML 1.0</a>).
- * @type Boolean
- * @default false
- * @example
- * config.forceSimpleAmpersand = false;
- */
-CKEDITOR.config.forceSimpleAmpersand = false;
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +(function() +{ + // Regex to scan for at the end of blocks, which are actually placeholders. + // Safari transforms the to \xa0. (#4172) + var tailNbspRegex = /^[\t\r\n ]*(?: |\xa0)$/; + + var protectedSourceMarker = '{cke_protected}'; + + // Return the last non-space child node of the block (#4344). + function lastNoneSpaceChild( block ) + { + var lastIndex = block.children.length, + last = block.children[ lastIndex - 1 ]; + while ( last && last.type == CKEDITOR.NODE_TEXT && !CKEDITOR.tools.trim( last.value ) ) + last = block.children[ --lastIndex ]; + return last; + } + + function trimFillers( block, fromSource ) + { + // If the current node is a block, and if we're converting from source or + // we're not in IE then search for and remove any tailing BR node. + // + // Also, any at the end of blocks are fillers, remove them as well. + // (#2886) + var children = block.children, lastChild = lastNoneSpaceChild( block ); + if ( lastChild ) + { + if ( ( fromSource || !CKEDITOR.env.ie ) && lastChild.type == CKEDITOR.NODE_ELEMENT && lastChild.name == 'br' ) + children.pop(); + if ( lastChild.type == CKEDITOR.NODE_TEXT && tailNbspRegex.test( lastChild.value ) ) + children.pop(); + } + } + + function blockNeedsExtension( block ) + { + var lastChild = lastNoneSpaceChild( block ); + + return !lastChild + || lastChild.type == CKEDITOR.NODE_ELEMENT && lastChild.name == 'br' + // Some of the controls in form needs extension too, + // to move cursor at the end of the form. (#4791) + || block.name == 'form' && lastChild.name == 'input'; + } + + function extendBlockForDisplay( block ) + { + trimFillers( block, true ); + + if ( blockNeedsExtension( block ) ) + { + if ( CKEDITOR.env.ie ) + block.add( new CKEDITOR.htmlParser.text( '\xa0' ) ); + else + block.add( new CKEDITOR.htmlParser.element( 'br', {} ) ); + } + } + + function extendBlockForOutput( block ) + { + trimFillers( block ); + + if ( blockNeedsExtension( block ) ) + block.add( new CKEDITOR.htmlParser.text( '\xa0' ) ); + } + + var dtd = CKEDITOR.dtd; + + // Find out the list of block-like tags that can contain <br>. + var blockLikeTags = CKEDITOR.tools.extend( {}, dtd.$block, dtd.$listItem, dtd.$tableContent ); + for ( var i in blockLikeTags ) + { + if ( ! ( 'br' in dtd[i] ) ) + delete blockLikeTags[i]; + } + // We just avoid filler in <pre> right now. + // TODO: Support filler for <pre>, line break is also occupy line height. + delete blockLikeTags.pre; + var defaultDataFilterRules = + { + elements : {}, + attributeNames : + [ + // Event attributes (onXYZ) must not be directly set. They can become + // active in the editing area (IE|WebKit). + [ ( /^on/ ), '_cke_pa_on' ] + ] + }; + + var defaultDataBlockFilterRules = { elements : {} }; + + for ( i in blockLikeTags ) + defaultDataBlockFilterRules.elements[ i ] = extendBlockForDisplay; + + var defaultHtmlFilterRules = + { + elementNames : + [ + // Remove the "cke:" namespace prefix. + [ ( /^cke:/ ), '' ], + + // Ignore <?xml:namespace> tags. + [ ( /^\?xml:namespace$/ ), '' ] + ], + + attributeNames : + [ + // Attributes saved for changes and protected attributes. + [ ( /^_cke_(saved|pa)_/ ), '' ], + + // All "_cke" attributes are to be ignored. + [ ( /^_cke.*/ ), '' ], + + [ 'hidefocus', '' ] + ], + + elements : + { + $ : function( element ) + { + 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++ ) + { + savedAttributeName = '_cke_saved_' + attributeNames[ i ]; + savedAttributeName in attribs && ( delete attribs[ attributeNames[ i ] ] ); + } + } + + return element; + }, + + embed : function( element ) + { + var parent = element.parent; + + // If the <embed> is child of a <object>, copy the width + // and height attributes from it. + if ( parent && parent.name == 'object' ) + { + var parentWidth = parent.attributes.width, + parentHeight = parent.attributes.height; + parentWidth && ( element.attributes.width = parentWidth ); + parentHeight && ( element.attributes.height = parentHeight ); + } + }, + // Restore param elements into self-closing. + param : function( param ) + { + param.children = []; + param.isEmpty = true; + return param; + }, + + // Remove empty link but not empty anchor.(#3829) + a : function( element ) + { + if ( !( element.children.length || + element.attributes.name || + element.attributes._cke_saved_name ) ) + { + return false; + } + }, + + html : function( element ) + { + delete element.attributes.contenteditable; + delete element.attributes[ 'class' ]; + }, + + 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'; + }, + + title : function( element ) + { + element.children[ 0 ].value = element.attributes[ '_cke_title' ]; + } + }, + + attributes : + { + 'class' : function( value, element ) + { + // Remove all class names starting with "cke_". + return CKEDITOR.tools.ltrim( value.replace( /(?:^|\s+)cke_[^\s]*/g, '' ) ) || false; + } + }, + + comment : function( contents ) + { + // If this is a comment for protected source. + if ( contents.substr( 0, protectedSourceMarker.length ) == protectedSourceMarker ) + { + // 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; + } + }; + + var defaultHtmlBlockFilterRules = { elements : {} }; + + for ( i in blockLikeTags ) + defaultHtmlBlockFilterRules.elements[ i ] = extendBlockForOutput; + + if ( CKEDITOR.env.ie ) + { + // IE outputs style attribute in capital letters. We should convert + // them back to lower case. + defaultHtmlFilterRules.attributes.style = function( value, element ) + { + return value.toLowerCase(); + }; + } + + function protectReadOnly( element ) + { + element.attributes.contenteditable = "false"; + } + function unprotectReadyOnly( element ) + { + delete element.attributes.contenteditable; + } + // Disable form elements editing mode provided by some browers. (#5746) + for ( i in { input : 1, textarea : 1 } ) + { + defaultDataFilterRules.elements[ i ] = protectReadOnly; + defaultHtmlFilterRules.elements[ i ] = unprotectReadyOnly; + } + + 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' ); + } + + function protectElements( html ) + { + return html.replace( protectElementsRegex, function( match ) + { + return '<cke:encoded>' + encodeURIComponent( match ) + '</cke:encoded>'; + }); + } + + function unprotectElements( html ) + { + 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:$1$2></cke:$1>' ); + } + + function protectRealComments( html ) + { + return html.replace( /<!--(?!{cke_protected})[\s\S]+?-->/g, function( match ) + { + return '<!--' + protectedSourceMarker + + '{C}' + + encodeURIComponent( match ).replace( /--/g, '%2D%2D' ) + + '-->'; + }); + } + + function unprotectRealComments( html ) + { + return html.replace( /<!--\{cke_protected\}\{C\}([\s\S]+?)-->/g, function( match, data ) + { + return decodeURIComponent( data ); + }); + } + + function protectSource( data, protectRegexes ) + { + var protectedHtml = [], + tempRegex = /<\!--\{cke_temp(comment)?\}(\d*?)-->/g; + + var regexes = + [ + // Script tags will also be forced to be protected, otherwise + // IE will execute them. + ( /<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( $, isComment, id ) + { + return protectedHtml[ id ]; + } + ); + return '<!--{cke_temp}' + ( protectedHtml.push( match ) - 1 ) + '-->'; + }); + } + data = data.replace( tempRegex, function( $, isComment, id ) + { + return '<!--' + protectedSourceMarker + + ( isComment ? '{C}' : '' ) + + encodeURIComponent( protectedHtml[ id ] ).replace( /--/g, '%2D%2D' ) + + '-->'; + } + ); + return data; + } + + CKEDITOR.plugins.add( 'htmldataprocessor', + { + requires : [ 'htmlwriter' ], + + init : function( editor ) + { + var dataProcessor = editor.dataProcessor = new CKEDITOR.htmlDataProcessor( editor ); + + dataProcessor.writer.forceSimpleAmpersand = editor.config.forceSimpleAmpersand; + + dataProcessor.dataFilter.addRules( defaultDataFilterRules ); + dataProcessor.dataFilter.addRules( defaultDataBlockFilterRules ); + dataProcessor.htmlFilter.addRules( defaultHtmlFilterRules ); + dataProcessor.htmlFilter.addRules( defaultHtmlBlockFilterRules ); + } + }); + + CKEDITOR.htmlDataProcessor = function( editor ) + { + this.editor = editor; + + this.writer = new CKEDITOR.htmlWriter(); + this.dataFilter = new CKEDITOR.htmlParser.filter(); + this.htmlFilter = new CKEDITOR.htmlParser.filter(); + }; + + CKEDITOR.htmlDataProcessor.prototype = + { + toHtml : function( data, fixForBody ) + { + // The source data is already HTML, but we need to clean + // it up and apply the filter. + + data = protectSource( data, this.editor.config.protectedSource ); + + // Before anything, we must protect the URL attributes as the + // browser may changing them when setting the innerHTML later in + // the code. + data = protectAttributes( 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) + data = protectElementsNames( data ); + + // All none-IE browsers ignore self-closed custom elements, + // protecting them into open-close. (#3591) + data = protectSelfClosingElements( data ); + + // Call the browser to help us fixing a possibly invalid HTML + // structure. + var div = new CKEDITOR.dom.element( 'div' ); + // Add fake character to workaround IE comments bug. (#3801) + div.setHtml( 'a' + data ); + data = div.getHtml().substr( 1 ); + + // 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. + var fragment = CKEDITOR.htmlParser.fragment.fromHtml( data, fixForBody ), + writer = new CKEDITOR.htmlParser.basicWriter(); + + fragment.writeHtml( writer, this.dataFilter ); + data = writer.getHtml( true ); + + // Protect the real comments again. + data = protectRealComments( data ); + + return data; + }, + + toDataFormat : function( html, fixForBody ) + { + var writer = this.writer, + fragment = CKEDITOR.htmlParser.fragment.fromHtml( html, fixForBody ); + + writer.reset(); + + fragment.writeHtml( writer, this.htmlFilter ); + + return writer.getHtml( true ); + } + }; +})(); + +/** + * Whether to force using "&" instead of "&amp;" in elements attributes + * values. It's not recommended to change this setting for compliance with the + * W3C XHTML 1.0 standards + * (<a href="http://www.w3.org/TR/xhtml1/#C_12">C.12, XHTML 1.0</a>). + * @type Boolean + * @default false + * @example + * config.forceSimpleAmpersand = false; + */ +CKEDITOR.config.forceSimpleAmpersand = false; diff --git a/_source/plugins/htmlwriter/plugin.js b/_source/plugins/htmlwriter/plugin.js index 4408402..0348855 100644 --- a/_source/plugins/htmlwriter/plugin.js +++ b/_source/plugins/htmlwriter/plugin.js @@ -1,314 +1,314 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-CKEDITOR.plugins.add( 'htmlwriter' );
-
-/**
- * Class used to write HTML data.
- * @constructor
- * @example
- * var writer = new CKEDITOR.htmlWriter();
- * writer.openTag( 'p' );
- * writer.attribute( 'class', 'MyClass' );
- * writer.openTagClose( 'p' );
- * writer.text( 'Hello' );
- * writer.closeTag( 'p' );
- * alert( writer.getHtml() ); "<p class="MyClass">Hello</p>"
- */
-CKEDITOR.htmlWriter = CKEDITOR.tools.createClass(
-{
- base : CKEDITOR.htmlParser.basicWriter,
-
- $ : function()
- {
- // Call the base contructor.
- this.base();
-
- /**
- * The characters to be used for each identation step.
- * @type String
- * @default "\t" (tab)
- * @example
- * // Use two spaces for indentation.
- * editorInstance.dataProcessor.writer.indentationChars = ' ';
- */
- this.indentationChars = '\t';
-
- /**
- * The characters to be used to close "self-closing" elements, like "br" or
- * "img".
- * @type String
- * @default " />"
- * @example
- * // Use HTML4 notation for self-closing elements.
- * editorInstance.dataProcessor.writer.selfClosingEnd = '>';
- */
- this.selfClosingEnd = ' />';
-
- /**
- * The characters to be used for line breaks.
- * @type String
- * @default "\n" (LF)
- * @example
- * // Use CRLF for line breaks.
- * editorInstance.dataProcessor.writer.lineBreakChars = '\r\n';
- */
- this.lineBreakChars = '\n';
-
- this.forceSimpleAmpersand = false;
-
- this.sortAttributes = true;
-
- this._.indent = false;
- this._.indentation = '';
- this._.rules = {};
-
- var dtd = CKEDITOR.dtd;
-
- for ( var e in CKEDITOR.tools.extend( {}, dtd.$nonBodyContent, dtd.$block, dtd.$listItem, dtd.$tableContent ) )
- {
- this.setRules( e,
- {
- indent : true,
- breakBeforeOpen : true,
- breakAfterOpen : true,
- breakBeforeClose : !dtd[ e ][ '#' ],
- 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
- });
- },
-
- proto :
- {
- /**
- * Writes the tag opening part for a opener tag.
- * @param {String} tagName The element name for this tag.
- * @param {Object} attributes The attributes defined for this tag. The
- * attributes could be used to inspect the tag.
- * @example
- * // Writes "<p".
- * writer.openTag( 'p', { class : 'MyClass', id : 'MyId' } );
- */
- openTag : function( tagName, attributes )
- {
- var rules = this._.rules[ tagName ];
-
- if ( this._.indent )
- this.indentation();
- // Do not break if indenting.
- else if ( rules && rules.breakBeforeOpen )
- {
- this.lineBreak();
- this.indentation();
- }
-
- this._.output.push( '<', tagName );
- },
-
- /**
- * Writes the tag closing part for a opener tag.
- * @param {String} tagName The element name for this tag.
- * @param {Boolean} isSelfClose Indicates that this is a self-closing tag,
- * like "br" or "img".
- * @example
- * // Writes ">".
- * writer.openTagClose( 'p', false );
- * @example
- * // Writes " />".
- * writer.openTagClose( 'br', true );
- */
- openTagClose : function( tagName, isSelfClose )
- {
- var rules = this._.rules[ tagName ];
-
- if ( isSelfClose )
- this._.output.push( this.selfClosingEnd );
- else
- {
- this._.output.push( '>' );
-
- if ( rules && rules.indent )
- this._.indentation += this.indentationChars;
- }
-
- if ( rules && rules.breakAfterOpen )
- this.lineBreak();
- },
-
- /**
- * Writes an attribute. This function should be called after opening the
- * tag with {@link #openTagClose}.
- * @param {String} attName The attribute name.
- * @param {String} attValue The attribute value.
- * @example
- * // Writes ' class="MyClass"'.
- * writer.attribute( 'class', 'MyClass' );
- */
- attribute : function( attName, attValue )
- {
-
- if ( typeof attValue == 'string' )
- {
- this.forceSimpleAmpersand && ( attValue = attValue.replace( /&/g, '&' ) );
- // Browsers don't always escape quote in attribute values. (#4683)
- attValue = attValue.replace( /"/g, '"' );
- }
-
- this._.output.push( ' ', attName, '="', attValue, '"' );
- },
-
- /**
- * Writes a closer tag.
- * @param {String} tagName The element name for this tag.
- * @example
- * // Writes "</p>".
- * writer.closeTag( 'p' );
- */
- closeTag : function( tagName )
- {
- var rules = this._.rules[ tagName ];
-
- if ( rules && rules.indent )
- this._.indentation = this._.indentation.substr( this.indentationChars.length );
-
- if ( this._.indent )
- this.indentation();
- // Do not break if indenting.
- else if ( rules && rules.breakBeforeClose )
- {
- this.lineBreak();
- this.indentation();
- }
-
- this._.output.push( '</', tagName, '>' );
-
- if ( rules && rules.breakAfterClose )
- this.lineBreak();
- },
-
- /**
- * Writes text.
- * @param {String} text The text value
- * @example
- * // Writes "Hello Word".
- * writer.text( 'Hello Word' );
- */
- text : function( text )
- {
- if ( this._.indent )
- {
- this.indentation();
- text = CKEDITOR.tools.ltrim( text );
- }
-
- this._.output.push( text );
- },
-
- /**
- * Writes a comment.
- * @param {String} comment The comment text.
- * @example
- * // Writes "<!-- My comment -->".
- * writer.comment( ' My comment ' );
- */
- comment : function( comment )
- {
- if ( this._.indent )
- this.indentation();
-
- this._.output.push( '<!--', comment, '-->' );
- },
-
- /**
- * Writes a line break. It uses the {@link #lineBreakChars} property for it.
- * @example
- * // Writes "\n" (e.g.).
- * writer.lineBreak();
- */
- lineBreak : function()
- {
- if ( this._.output.length > 0 )
- this._.output.push( this.lineBreakChars );
- this._.indent = true;
- },
-
- /**
- * Writes the current indentation chars. It uses the
- * {@link #indentationChars} property, repeating it for the current
- * indentation steps.
- * @example
- * // Writes "\t" (e.g.).
- * writer.indentation();
- */
- indentation : function()
- {
- this._.output.push( this._.indentation );
- this._.indent = false;
- },
-
- /**
- * Sets formatting rules for a give element. The possible rules are:
- * <ul>
- * <li><b>indent</b>: indent the element contents.</li>
- * <li><b>breakBeforeOpen</b>: break line before the opener tag for this element.</li>
- * <li><b>breakAfterOpen</b>: break line after the opener tag for this element.</li>
- * <li><b>breakBeforeClose</b>: break line before the closer tag for this element.</li>
- * <li><b>breakAfterClose</b>: break line after the closer tag for this element.</li>
- * </ul>
- *
- * 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}
- * lists have all the above rules set to "true". Additionaly, the "br"
- * element has the "breakAfterOpen" set to "true".
- * @param {String} tagName The element name to which set the rules.
- * @param {Object} rules An object containing the element rules.
- * @example
- * // Break line before and after "img" tags.
- * writer.setRules( 'img',
- * {
- * breakBeforeOpen : true
- * breakAfterOpen : true
- * });
- * @example
- * // Reset the rules for the "h1" tag.
- * writer.setRules( 'h1', {} );
- */
- setRules : function( tagName, rules )
- {
- var currentRules = this._.rules[ tagName ];
-
- if ( currentRules )
- CKEDITOR.tools.extend( currentRules, rules, true );
- else
- this._.rules[ tagName ] = rules;
- }
- }
-});
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.plugins.add( 'htmlwriter' ); + +/** + * Class used to write HTML data. + * @constructor + * @example + * var writer = new CKEDITOR.htmlWriter(); + * writer.openTag( 'p' ); + * writer.attribute( 'class', 'MyClass' ); + * writer.openTagClose( 'p' ); + * writer.text( 'Hello' ); + * writer.closeTag( 'p' ); + * alert( writer.getHtml() ); "<p class="MyClass">Hello</p>" + */ +CKEDITOR.htmlWriter = CKEDITOR.tools.createClass( +{ + base : CKEDITOR.htmlParser.basicWriter, + + $ : function() + { + // Call the base contructor. + this.base(); + + /** + * The characters to be used for each identation step. + * @type String + * @default "\t" (tab) + * @example + * // Use two spaces for indentation. + * editorInstance.dataProcessor.writer.indentationChars = ' '; + */ + this.indentationChars = '\t'; + + /** + * The characters to be used to close "self-closing" elements, like "br" or + * "img". + * @type String + * @default " />" + * @example + * // Use HTML4 notation for self-closing elements. + * editorInstance.dataProcessor.writer.selfClosingEnd = '>'; + */ + this.selfClosingEnd = ' />'; + + /** + * The characters to be used for line breaks. + * @type String + * @default "\n" (LF) + * @example + * // Use CRLF for line breaks. + * editorInstance.dataProcessor.writer.lineBreakChars = '\r\n'; + */ + this.lineBreakChars = '\n'; + + this.forceSimpleAmpersand = false; + + this.sortAttributes = true; + + this._.indent = false; + this._.indentation = ''; + this._.rules = {}; + + var dtd = CKEDITOR.dtd; + + for ( var e in CKEDITOR.tools.extend( {}, dtd.$nonBodyContent, dtd.$block, dtd.$listItem, dtd.$tableContent ) ) + { + this.setRules( e, + { + indent : true, + breakBeforeOpen : true, + breakAfterOpen : true, + breakBeforeClose : !dtd[ e ][ '#' ], + 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 + }); + }, + + proto : + { + /** + * Writes the tag opening part for a opener tag. + * @param {String} tagName The element name for this tag. + * @param {Object} attributes The attributes defined for this tag. The + * attributes could be used to inspect the tag. + * @example + * // Writes "<p". + * writer.openTag( 'p', { class : 'MyClass', id : 'MyId' } ); + */ + openTag : function( tagName, attributes ) + { + var rules = this._.rules[ tagName ]; + + if ( this._.indent ) + this.indentation(); + // Do not break if indenting. + else if ( rules && rules.breakBeforeOpen ) + { + this.lineBreak(); + this.indentation(); + } + + this._.output.push( '<', tagName ); + }, + + /** + * Writes the tag closing part for a opener tag. + * @param {String} tagName The element name for this tag. + * @param {Boolean} isSelfClose Indicates that this is a self-closing tag, + * like "br" or "img". + * @example + * // Writes ">". + * writer.openTagClose( 'p', false ); + * @example + * // Writes " />". + * writer.openTagClose( 'br', true ); + */ + openTagClose : function( tagName, isSelfClose ) + { + var rules = this._.rules[ tagName ]; + + if ( isSelfClose ) + this._.output.push( this.selfClosingEnd ); + else + { + this._.output.push( '>' ); + + if ( rules && rules.indent ) + this._.indentation += this.indentationChars; + } + + if ( rules && rules.breakAfterOpen ) + this.lineBreak(); + }, + + /** + * Writes an attribute. This function should be called after opening the + * tag with {@link #openTagClose}. + * @param {String} attName The attribute name. + * @param {String} attValue The attribute value. + * @example + * // Writes ' class="MyClass"'. + * writer.attribute( 'class', 'MyClass' ); + */ + attribute : function( attName, attValue ) + { + + if ( typeof attValue == 'string' ) + { + this.forceSimpleAmpersand && ( attValue = attValue.replace( /&/g, '&' ) ); + // Browsers don't always escape special character in attribute values. (#4683, #4719). + attValue = CKEDITOR.tools.htmlEncodeAttr( attValue ); + } + + this._.output.push( ' ', attName, '="', attValue, '"' ); + }, + + /** + * Writes a closer tag. + * @param {String} tagName The element name for this tag. + * @example + * // Writes "</p>". + * writer.closeTag( 'p' ); + */ + closeTag : function( tagName ) + { + var rules = this._.rules[ tagName ]; + + if ( rules && rules.indent ) + this._.indentation = this._.indentation.substr( this.indentationChars.length ); + + if ( this._.indent ) + this.indentation(); + // Do not break if indenting. + else if ( rules && rules.breakBeforeClose ) + { + this.lineBreak(); + this.indentation(); + } + + this._.output.push( '</', tagName, '>' ); + + if ( rules && rules.breakAfterClose ) + this.lineBreak(); + }, + + /** + * Writes text. + * @param {String} text The text value + * @example + * // Writes "Hello Word". + * writer.text( 'Hello Word' ); + */ + text : function( text ) + { + if ( this._.indent ) + { + this.indentation(); + text = CKEDITOR.tools.ltrim( text ); + } + + this._.output.push( text ); + }, + + /** + * Writes a comment. + * @param {String} comment The comment text. + * @example + * // Writes "<!-- My comment -->". + * writer.comment( ' My comment ' ); + */ + comment : function( comment ) + { + if ( this._.indent ) + this.indentation(); + + this._.output.push( '<!--', comment, '-->' ); + }, + + /** + * Writes a line break. It uses the {@link #lineBreakChars} property for it. + * @example + * // Writes "\n" (e.g.). + * writer.lineBreak(); + */ + lineBreak : function() + { + if ( this._.output.length > 0 ) + this._.output.push( this.lineBreakChars ); + this._.indent = true; + }, + + /** + * Writes the current indentation chars. It uses the + * {@link #indentationChars} property, repeating it for the current + * indentation steps. + * @example + * // Writes "\t" (e.g.). + * writer.indentation(); + */ + indentation : function() + { + this._.output.push( this._.indentation ); + this._.indent = false; + }, + + /** + * Sets formatting rules for a give element. The possible rules are: + * <ul> + * <li><b>indent</b>: indent the element contents.</li> + * <li><b>breakBeforeOpen</b>: break line before the opener tag for this element.</li> + * <li><b>breakAfterOpen</b>: break line after the opener tag for this element.</li> + * <li><b>breakBeforeClose</b>: break line before the closer tag for this element.</li> + * <li><b>breakAfterClose</b>: break line after the closer tag for this element.</li> + * </ul> + * + * 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} + * lists have all the above rules set to "true". Additionaly, the "br" + * element has the "breakAfterOpen" set to "true". + * @param {String} tagName The element name to which set the rules. + * @param {Object} rules An object containing the element rules. + * @example + * // Break line before and after "img" tags. + * writer.setRules( 'img', + * { + * breakBeforeOpen : true + * breakAfterOpen : true + * }); + * @example + * // Reset the rules for the "h1" tag. + * writer.setRules( 'h1', {} ); + */ + setRules : function( 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/image/dialogs/image.js b/_source/plugins/image/dialogs/image.js index cc18936..decce28 100644 --- a/_source/plugins/image/dialogs/image.js +++ b/_source/plugins/image/dialogs/image.js @@ -1,1378 +1,1394 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-(function()
-{
- // Load image preview.
- var IMAGE = 1,
- LINK = 2,
- PREVIEW = 4,
- CLEANUP = 8,
- regexGetSize = /^\s*(\d+)((px)|\%)?\s*$/i,
- regexGetSizeOrEmpty = /(^\s*(\d+)((px)|\%)?\s*$)|^$/i,
- pxLengthRegex = /^\d+px$/;
-
- var onSizeChange = function()
- {
- var value = this.getValue(), // This = input element.
- dialog = this.getDialog(),
- aMatch = value.match( regexGetSize ); // Check value
- if ( aMatch )
- {
- if ( aMatch[2] == '%' ) // % is allowed - > unlock ratio.
- switchLockRatio( dialog, false ); // Unlock.
- value = aMatch[1];
- }
-
- // Only if ratio is locked
- if ( dialog.lockRatio )
- {
- var oImageOriginal = dialog.originalElement;
- if ( oImageOriginal.getCustomData( 'isReady' ) == 'true' )
- {
- if ( this.id == 'txtHeight' )
- {
- if ( value && value != '0' )
- value = Math.round( oImageOriginal.$.width * ( value / oImageOriginal.$.height ) );
- if ( !isNaN( value ) )
- dialog.setValueOf( 'info', 'txtWidth', value );
- }
- else //this.id = txtWidth.
- {
- if ( value && value != '0' )
- value = Math.round( oImageOriginal.$.height * ( value / oImageOriginal.$.width ) );
- if ( !isNaN( value ) )
- dialog.setValueOf( 'info', 'txtHeight', value );
- }
- }
- }
- updatePreview( dialog );
- };
-
- var updatePreview = function( dialog )
- {
- //Don't load before onShow.
- if ( !dialog.originalElement || !dialog.preview )
- return 1;
-
- // Read attributes and update imagePreview;
- dialog.commitContent( PREVIEW, dialog.preview );
- 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,
- ratioButton = CKEDITOR.document.getById( 'btnLockSizes' );
-
- if ( oImageOriginal.getCustomData( 'isReady' ) == 'true' )
- {
- if ( value == 'check' ) // Check image ratio and original image ratio.
- {
- var width = dialog.getValueOf( 'info', 'txtWidth' ),
- height = dialog.getValueOf( 'info', 'txtHeight' ),
- originalRatio = oImageOriginal.$.width * 1000 / oImageOriginal.$.height,
- thisRatio = width * 1000 / height;
- dialog.lockRatio = false; // Default: unlock ratio
-
- if ( !width && !height )
- dialog.lockRatio = true;
- else if ( !isNaN( originalRatio ) && !isNaN( thisRatio ) )
- {
- if ( Math.round( originalRatio ) == Math.round( thisRatio ) )
- dialog.lockRatio = true;
- }
- }
- else if ( value != undefined )
- dialog.lockRatio = value;
- else
- dialog.lockRatio = !dialog.lockRatio;
- }
- else if ( value != 'check' ) // I can't lock ratio if ratio is unknown.
- dialog.lockRatio = false;
-
- if ( dialog.lockRatio )
- ratioButton.removeClass( 'cke_btn_unlocked' );
- else
- ratioButton.addClass( 'cke_btn_unlocked' );
-
- var lang = dialog._.editor.lang.image,
- label = lang[ dialog.lockRatio ? 'unlockRatio' : 'lockRatio' ];
-
- ratioButton.setAttribute( 'title', label );
- ratioButton.getFirst().setText( label );
-
- return dialog.lockRatio;
- };
-
- var resetSize = function( dialog )
- {
- var oImageOriginal = dialog.originalElement;
- if ( oImageOriginal.getCustomData( 'isReady' ) == 'true' )
- {
- dialog.setValueOf( 'info', 'txtWidth', oImageOriginal.$.width );
- dialog.setValueOf( 'info', 'txtHeight', oImageOriginal.$.height );
- }
- updatePreview( dialog );
- };
-
- var setupDimension = function( type, element )
- {
- if ( type != IMAGE )
- return;
-
- function checkDimension( size, defaultValue )
- {
- var aMatch = size.match( regexGetSize );
- if ( aMatch )
- {
- if ( aMatch[2] == '%' ) // % is allowed.
- {
- aMatch[1] += '%';
- switchLockRatio( dialog, false ); // Unlock ratio
- }
- return aMatch[1];
- }
- return defaultValue;
- }
-
- var dialog = this.getDialog(),
- value = '',
- dimension = (( this.id == 'txtWidth' )? 'width' : 'height' ),
- size = element.getAttribute( dimension );
-
- if ( size )
- value = checkDimension( size, value );
- value = checkDimension( element.getStyle( dimension ), value );
-
- this.setValue( value );
- };
-
- var imageDialog = function( editor, dialogType )
- {
- var previewPreloader;
-
- var onImgLoadEvent = function()
- {
- // Image is ready.
- var original = this.originalElement;
- original.setCustomData( 'isReady', 'true' );
- original.removeListener( 'load', onImgLoadEvent );
- original.removeListener( 'error', onImgLoadErrorEvent );
- original.removeListener( 'abort', onImgLoadErrorEvent );
-
- // Hide loader
- CKEDITOR.document.getById( 'ImagePreviewLoader' ).setStyle( 'display', 'none' );
-
- // New image -> new domensions
- if ( !this.dontResetSize )
- resetSize( this );
-
- if ( this.firstLoad )
- CKEDITOR.tools.setTimeout( function(){ switchLockRatio( this, 'check' ); }, 0, this );
-
- this.firstLoad = false;
- this.dontResetSize = false;
- };
-
- var onImgLoadErrorEvent = function()
- {
- // Error. Image is not loaded.
- var original = this.originalElement;
- original.removeListener( 'load', onImgLoadEvent );
- original.removeListener( 'error', onImgLoadErrorEvent );
- original.removeListener( 'abort', onImgLoadErrorEvent );
-
- // Set Error image.
- var noimage = CKEDITOR.getUrl( editor.skinPath + 'images/noimage.png' );
-
- if ( this.preview )
- this.preview.setAttribute( 'src', noimage );
-
- // Hide loader
- CKEDITOR.document.getById( 'ImagePreviewLoader' ).setStyle( 'display', 'none' );
- switchLockRatio( this, false ); // Unlock.
- };
- return {
- title : ( dialogType == 'image' ) ? editor.lang.image.title : editor.lang.image.titleButton,
- minWidth : 420,
- minHeight : 310,
- onShow : function()
- {
- this.imageElement = false;
- this.linkElement = false;
-
- // Default: create a new element.
- this.imageEditMode = false;
- this.linkEditMode = false;
-
- this.lockRatio = true;
- this.dontResetSize = false;
- this.firstLoad = true;
- this.addLink = false;
-
- 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', '' );
- this.originalElement.setCustomData( 'isReady', 'false' );
-
- if ( link )
- {
- this.linkElement = link;
- this.linkEditMode = true;
-
- // Look for Image element.
- var linkChildren = link.getChildren();
- if ( linkChildren.count() == 1 ) // 1 child.
- {
- var childTagName = linkChildren.getItem( 0 ).getName();
- if ( childTagName == 'img' || childTagName == 'input' )
- {
- this.imageElement = linkChildren.getItem( 0 );
- if ( this.imageElement.getName() == 'img' )
- this.imageEditMode = 'img';
- else if ( this.imageElement.getName() == 'input' )
- this.imageEditMode = 'input';
- }
- }
- // Fill out all fields.
- if ( dialogType == 'image' )
- this.setupContent( LINK, link );
- }
-
- 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 )
- {
- // 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 );
-
- // 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' ) ) )
- {
- this.preview.removeAttribute( 'src' );
- this.preview.setStyle( 'display', 'none' );
- }
- },
- onOk : function()
- {
- // Edit existing Image.
- if ( this.imageEditMode )
- {
- var imgTagName = this.imageEditMode;
-
- // Image dialog and Input element.
- if ( dialogType == 'image' && imgTagName == 'input' && confirm( editor.lang.image.button2Img ) )
- {
- // Replace INPUT-> IMG
- imgTagName = 'img';
- this.imageElement = editor.document.createElement( 'img' );
- this.imageElement.setAttribute( 'alt', '' );
- editor.insertElement( this.imageElement );
- }
- // ImageButton dialog and Image element.
- else if ( dialogType != 'image' && imgTagName == 'img' && confirm( editor.lang.image.img2Button ))
- {
- // Replace IMG -> INPUT
- imgTagName = 'input';
- this.imageElement = editor.document.createElement( 'input' );
- this.imageElement.setAttributes(
- {
- type : 'image',
- alt : ''
- }
- );
- editor.insertElement( this.imageElement );
- }
- else
- {
- // Restore the original element before all commits.
- this.imageElement = this.cleanImageElement;
- delete this.cleanImageElement;
- }
- }
- else // Create a new image.
- {
- // Image dialog -> create IMG element.
- if ( dialogType == 'image' )
- this.imageElement = editor.document.createElement( 'img' );
- else
- {
- this.imageElement = editor.document.createElement( 'input' );
- this.imageElement.setAttribute ( 'type' ,'image' );
- }
- this.imageElement.setAttribute( 'alt', '' );
- }
-
- // Create a new link.
- if ( !this.linkEditMode )
- this.linkElement = editor.document.createElement( 'a' );
-
- // Set attributes.
- 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 )
- {
- if ( this.addLink )
- {
- //Insert a new Link.
- if ( !this.linkEditMode )
- {
- editor.insertElement(this.linkElement);
- this.linkElement.append(this.imageElement, false);
- }
- else //Link already exists, image not.
- editor.insertElement(this.imageElement );
- }
- else
- editor.insertElement( this.imageElement );
- }
- else // Image already exists.
- {
- //Add a new link element.
- if ( !this.linkEditMode && this.addLink )
- {
- editor.insertElement( this.linkElement );
- this.imageElement.appendTo( this.linkElement );
- }
- //Remove Link, Image exists.
- else if ( this.linkEditMode && !this.addLink )
- {
- editor.getSelection().selectElement( this.linkElement );
- editor.insertElement( this.imageElement );
- }
- }
- },
- onLoad : function()
- {
- if ( dialogType != 'image' )
- this.hidePage( 'Link' ); //Hide Link tab.
- var doc = this._.element.getDocument();
- this.addFocusable( doc.getById( 'btnResetSize' ), 5 );
- this.addFocusable( doc.getById( 'btnLockSizes' ), 5 );
-
- this.commitContent = commitContent;
- },
- onHide : function()
- {
- if ( this.preview )
- this.commitContent( CLEANUP, this.preview );
-
- if ( this.originalElement )
- {
- this.originalElement.removeListener( 'load', onImgLoadEvent );
- this.originalElement.removeListener( 'error', onImgLoadErrorEvent );
- this.originalElement.removeListener( 'abort', onImgLoadErrorEvent );
- this.originalElement.remove();
- this.originalElement = false; // Dialog is closed.
- }
-
- delete this.imageElement;
- },
- contents : [
- {
- id : 'info',
- label : editor.lang.image.infoTab,
- accessKey : 'I',
- elements :
- [
- {
- type : 'vbox',
- padding : 0,
- children :
- [
- {
- type : 'hbox',
- widths : [ '280px', '110px' ],
- align : 'right',
- children :
- [
- {
- id : 'txtUrl',
- type : 'text',
- label : editor.lang.common.url,
- required: true,
- onChange : function()
- {
- var dialog = this.getDialog(),
- newUrl = this.getValue();
-
- //Update original image
- if ( newUrl.length > 0 ) //Prevent from load before onShow
- {
- dialog = this.getDialog();
- var original = dialog.originalElement;
-
- dialog.preview.removeStyle( 'display' );
-
- original.setCustomData( 'isReady', 'false' );
- // Show loader
- var loader = CKEDITOR.document.getById( 'ImagePreviewLoader' );
- if ( loader )
- loader.setStyle( 'display', '' );
-
- original.on( 'load', onImgLoadEvent, dialog );
- original.on( 'error', onImgLoadErrorEvent, dialog );
- original.on( 'abort', onImgLoadErrorEvent, dialog );
- original.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.
- else if ( dialog.preview )
- {
- dialog.preview.removeAttribute( 'src' );
- dialog.preview.setStyle( 'display', 'none' );
- }
- },
- setup : function( type, element )
- {
- if ( type == IMAGE )
- {
- var url = element.getAttribute( '_cke_saved_src' ) || element.getAttribute( 'src' );
- var field = this;
-
- this.getDialog().dontResetSize = true;
-
- field.setValue( url ); // And call this.onChange()
- // Manually set the initial value.(#4191)
- field.setInitValue();
- field.focus();
- }
- },
- commit : function( type, element )
- {
- if ( type == IMAGE && ( this.getValue() || this.isChanged() ) )
- {
- element.setAttribute( '_cke_saved_src', decodeURI( this.getValue() ) );
- element.setAttribute( 'src', decodeURI( this.getValue() ) );
- }
- else if ( type == CLEANUP )
- {
- element.setAttribute( 'src', '' ); // If removeAttribute doesn't work.
- element.removeAttribute( 'src' );
- }
- },
- validate : CKEDITOR.dialog.validate.notEmpty( editor.lang.image.urlMissing )
- },
- {
- type : 'button',
- id : 'browse',
- // v-align with the 'txtUrl' field.
- // TODO: We need something better than a fixed size here.
- style : 'display:inline-block;margin-top:10px;',
- align : 'center',
- label : editor.lang.common.browseServer,
- hidden : true,
- filebrowser : 'info:txtUrl'
- }
- ]
- }
- ]
- },
- {
- id : 'txtAlt',
- type : 'text',
- label : editor.lang.image.alt,
- accessKey : 'A',
- 'default' : '',
- onChange : function()
- {
- updatePreview( this.getDialog() );
- },
- setup : function( type, element )
- {
- if ( type == IMAGE )
- this.setValue( element.getAttribute( 'alt' ) );
- },
- commit : function( type, element )
- {
- if ( type == IMAGE )
- {
- if ( this.getValue() || this.isChanged() )
- element.setAttribute( 'alt', this.getValue() );
- }
- else if ( type == PREVIEW )
- {
- element.setAttribute( 'alt', this.getValue() );
- }
- else if ( type == CLEANUP )
- {
- element.removeAttribute( 'alt' );
- }
- }
- },
- {
- type : 'hbox',
- widths : [ '140px', '240px' ],
- children :
- [
- {
- type : 'vbox',
- padding : 10,
- children :
- [
- {
- type : 'hbox',
- widths : [ '70%', '30%' ],
- children :
- [
- {
- type : 'vbox',
- padding : 1,
- children :
- [
- {
- type : 'text',
- width: '40px',
- id : 'txtWidth',
- labelLayout : 'horizontal',
- label : editor.lang.image.width,
- onKeyUp : onSizeChange,
- onChange : function()
- {
- commitInternally.call( this, 'advanced:txtdlgGenStyle' );
- },
- validate : function()
- {
- var aMatch = this.getValue().match( regexGetSizeOrEmpty );
- if ( !aMatch )
- alert( editor.lang.image.validateWidth );
- return !!aMatch;
- },
- setup : setupDimension,
- commit : function( type, element, internalCommit )
- {
- var value = this.getValue();
- if ( type == IMAGE )
- {
- if ( value )
- element.setStyle( 'width', CKEDITOR.tools.cssLength( value ) );
- else if ( !value && this.isChanged( ) )
- element.removeStyle( 'width' );
-
- !internalCommit && element.removeAttribute( 'width' );
- }
- else if ( type == PREVIEW )
- {
- var aMatch = value.match( regexGetSize );
- if ( !aMatch )
- {
- var oImageOriginal = this.getDialog().originalElement;
- if ( oImageOriginal.getCustomData( 'isReady' ) == 'true' )
- element.setStyle( 'width', oImageOriginal.$.width + 'px');
- }
- else
- element.setStyle( 'width', value + 'px');
- }
- else if ( type == CLEANUP )
- {
- element.removeAttribute( 'width' );
- element.removeStyle( 'width' );
- }
- }
- },
- {
- type : 'text',
- id : 'txtHeight',
- width: '40px',
- labelLayout : 'horizontal',
- label : editor.lang.image.height,
- onKeyUp : onSizeChange,
- onChange : function()
- {
- commitInternally.call( this, 'advanced:txtdlgGenStyle' );
- },
- validate : function()
- {
- var aMatch = this.getValue().match( regexGetSizeOrEmpty );
- if ( !aMatch )
- alert( editor.lang.image.validateHeight );
- return !!aMatch;
- },
- setup : setupDimension,
- commit : function( type, element, internalCommit )
- {
- var value = this.getValue();
- if ( type == IMAGE )
- {
- if ( value )
- 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 )
- {
- var aMatch = value.match( regexGetSize );
- if ( !aMatch )
- {
- var oImageOriginal = this.getDialog().originalElement;
- if ( oImageOriginal.getCustomData( 'isReady' ) == 'true' )
- element.setStyle( 'height', oImageOriginal.$.height + 'px' );
- }
- else
- element.setStyle( 'height', value + 'px' );
- }
- else if ( type == CLEANUP )
- {
- element.removeAttribute( 'height' );
- element.removeStyle( 'height' );
- }
- }
- }
- ]
- },
- {
- type : 'html',
- style : 'margin-top:10px;width:40px;height:40px;',
- onLoad : function()
- {
- // Activate Reset button
- var resetButton = CKEDITOR.document.getById( 'btnResetSize' ),
- ratioButton = CKEDITOR.document.getById( 'btnLockSizes' );
- if ( resetButton )
- {
- resetButton.on( 'click', function(evt)
- {
- resetSize( this );
- evt.data.preventDefault();
- }, this.getDialog() );
- resetButton.on( 'mouseover', function()
- {
- this.addClass( 'cke_btn_over' );
- }, resetButton );
- resetButton.on( 'mouseout', function()
- {
- this.removeClass( 'cke_btn_over' );
- }, resetButton );
- }
- // Activate (Un)LockRatio button
- if ( ratioButton )
- {
- ratioButton.on( 'click', function(evt)
- {
- var locked = switchLockRatio( this ),
- oImageOriginal = this.originalElement,
- width = this.getValueOf( 'info', 'txtWidth' );
-
- if ( oImageOriginal.getCustomData( 'isReady' ) == 'true' && width )
- {
- var height = oImageOriginal.$.height / oImageOriginal.$.width * width;
- if ( !isNaN( height ) )
- {
- this.setValueOf( 'info', 'txtHeight', Math.round( height ) );
- updatePreview( this );
- }
- }
- evt.data.preventDefault();
- }, this.getDialog() );
- ratioButton.on( 'mouseover', function()
- {
- this.addClass( 'cke_btn_over' );
- }, ratioButton );
- ratioButton.on( 'mouseout', function()
- {
- this.removeClass( 'cke_btn_over' );
- }, ratioButton );
- }
- },
- html : '<div>'+
- '<a href="javascript:void(0)" tabindex="-1" title="' + editor.lang.image.unlockRatio +
- '" class="cke_btn_locked" id="btnLockSizes" role="button"><span class="cke_label">' + editor.lang.image.unlockRatio + '</span></a>' +
- '<a href="javascript:void(0)" tabindex="-1" title="' + editor.lang.image.resetSize +
- '" class="cke_btn_reset" id="btnResetSize" role="button"><span class="cke_label">' + editor.lang.image.resetSize + '</span></a>'+
- '</div>'
- }
- ]
- },
- {
- type : 'vbox',
- padding : 1,
- children :
- [
- {
- type : 'text',
- id : 'txtBorder',
- width: '60px',
- labelLayout : 'horizontal',
- label : editor.lang.image.border,
- 'default' : '',
- onKeyUp : function()
- {
- updatePreview( this.getDialog() );
- },
- onChange : function()
- {
- commitInternally.call( this, 'advanced:txtdlgGenStyle' );
- },
- validate : CKEDITOR.dialog.validate.integer( editor.lang.image.validateBorder ),
- setup : function( type, element )
- {
- if ( type == IMAGE )
- {
- var value,
- borderStyle = element.getStyle( 'border-width' );
- borderStyle = borderStyle && borderStyle.match( /^(\d+px)(?: \1 \1 \1)?$/ );
- value = borderStyle && parseInt( borderStyle[ 1 ], 10 );
- isNaN ( parseInt( value, 10 ) ) && ( value = element.getAttribute( 'border' ) );
- this.setValue( value );
- }
- },
- commit : function( type, element, internalCommit )
- {
- var value = parseInt( this.getValue(), 10 );
- if ( type == IMAGE || type == PREVIEW )
- {
- if ( !isNaN( 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-width' );
- element.removeStyle( 'border-style' );
- element.removeStyle( 'border-color' );
- }
- }
- },
- {
- type : 'text',
- id : 'txtHSpace',
- width: '60px',
- labelLayout : 'horizontal',
- label : editor.lang.image.hSpace,
- 'default' : '',
- onKeyUp : function()
- {
- updatePreview( this.getDialog() );
- },
- onChange : function()
- {
- commitInternally.call( this, 'advanced:txtdlgGenStyle' );
- },
- validate : CKEDITOR.dialog.validate.integer( editor.lang.image.validateHSpace ),
- setup : function( type, element )
- {
- if ( type == IMAGE )
- {
- 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;
- isNaN( parseInt( value, 10 ) ) && ( value = element.getAttribute( 'hspace' ) );
-
- this.setValue( value );
- }
- },
- commit : function( type, element, internalCommit )
- {
- var value = parseInt( this.getValue(), 10 );
- if ( type == IMAGE || type == PREVIEW )
- {
- if ( !isNaN( 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 )
- {
- element.removeAttribute( 'hspace' );
- element.removeStyle( 'margin-left' );
- element.removeStyle( 'margin-right' );
- }
- }
- },
- {
- type : 'text',
- id : 'txtVSpace',
- width : '60px',
- labelLayout : 'horizontal',
- label : editor.lang.image.vSpace,
- 'default' : '',
- onKeyUp : function()
- {
- updatePreview( this.getDialog() );
- },
- onChange : function()
- {
- commitInternally.call( this, 'advanced:txtdlgGenStyle' );
- },
- validate : CKEDITOR.dialog.validate.integer( editor.lang.image.validateVSpace ),
- setup : function( type, element )
- {
- if ( type == IMAGE )
- {
- 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;
- isNaN ( parseInt( value, 10 ) ) && ( value = element.getAttribute( 'vspace' ) );
- this.setValue( value );
- }
- },
- commit : function( type, element, internalCommit )
- {
- var value = parseInt( this.getValue(), 10 );
- if ( type == IMAGE || type == PREVIEW )
- {
- if ( !isNaN( 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 )
- {
- element.removeAttribute( 'vspace' );
- element.removeStyle( 'margin-top' );
- element.removeStyle( 'margin-bottom' );
- }
- }
- },
- {
- id : 'cmbAlign',
- type : 'select',
- labelLayout : 'horizontal',
- widths : [ '35%','65%' ],
- style : 'width:90px',
- label : editor.lang.image.align,
- 'default' : '',
- items :
- [
- [ editor.lang.common.notSet , ''],
- [ editor.lang.image.alignLeft , 'left'],
- [ 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 )
- {
- 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, internalCommit )
- {
- var value = this.getValue();
- if ( type == IMAGE || type == PREVIEW )
- {
- if ( value )
- element.setStyle( 'float', value );
- else
- 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.removeStyle( 'float' );
-
- }
- }
- ]
- }
- ]
- },
- {
- type : 'vbox',
- height : '250px',
- children :
- [
- {
- type : 'html',
- style : 'width:95%;',
- html : '<div>' + CKEDITOR.tools.htmlEncode( editor.lang.common.preview ) +'<br>'+
- '<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" 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>'
- }
- ]
- }
- ]
- }
- ]
- },
- {
- id : 'Link',
- label : editor.lang.link.title,
- padding : 0,
- elements :
- [
- {
- id : 'txtUrl',
- type : 'text',
- label : editor.lang.common.url,
- style : 'width: 100%',
- 'default' : '',
- setup : function( type, element )
- {
- if ( type == LINK )
- {
- var href = element.getAttribute( '_cke_saved_href' );
- if ( !href )
- href = element.getAttribute( 'href' );
- this.setValue( href );
- }
- },
- commit : function( type, element )
- {
- if ( type == LINK )
- {
- if ( this.getValue() || this.isChanged() )
- {
- element.setAttribute( '_cke_saved_href', decodeURI( this.getValue() ) );
- element.setAttribute( 'href', 'javascript:void(0)/*' +
- CKEDITOR.tools.getNextNumber() + '*/' );
-
- if ( this.getValue() || !editor.config.image_removeLinkByEmptyURL )
- this.getDialog().addLink = true;
- }
- }
- }
- },
- {
- type : 'button',
- id : 'browse',
- filebrowser :
- {
- action : 'Browse',
- target: 'Link:txtUrl',
- url: editor.config.filebrowserImageBrowseLinkUrl || editor.config.filebrowserBrowseUrl
- },
- style : 'float:right',
- hidden : true,
- label : editor.lang.common.browseServer
- },
- {
- id : 'cmbTarget',
- type : 'select',
- label : editor.lang.common.target,
- 'default' : '',
- items :
- [
- [ editor.lang.common.notSet , ''],
- [ editor.lang.common.targetNew , '_blank'],
- [ editor.lang.common.targetTop , '_top'],
- [ editor.lang.common.targetSelf , '_self'],
- [ editor.lang.common.targetParent , '_parent']
- ],
- setup : function( type, element )
- {
- if ( type == LINK )
- this.setValue( element.getAttribute( 'target' ) );
- },
- commit : function( type, element )
- {
- if ( type == LINK )
- {
- if ( this.getValue() || this.isChanged() )
- element.setAttribute( 'target', this.getValue() );
- }
- }
- }
- ]
- },
- {
- id : 'Upload',
- hidden : true,
- filebrowser : 'uploadButton',
- label : editor.lang.image.upload,
- elements :
- [
- {
- type : 'file',
- id : 'upload',
- label : editor.lang.image.btnUpload,
- style: 'height:40px',
- size : 38
- },
- {
- type : 'fileButton',
- id : 'uploadButton',
- filebrowser : 'info:txtUrl',
- label : editor.lang.image.btnUpload,
- 'for' : [ 'Upload', 'upload' ]
- }
- ]
- },
- {
- id : 'advanced',
- label : editor.lang.common.advancedTab,
- elements :
- [
- {
- type : 'hbox',
- widths : [ '50%', '25%', '25%' ],
- children :
- [
- {
- type : 'text',
- id : 'linkId',
- label : editor.lang.common.id,
- setup : function( type, element )
- {
- if ( type == IMAGE )
- this.setValue( element.getAttribute( 'id' ) );
- },
- commit : function( type, element )
- {
- if ( type == IMAGE )
- {
- if ( this.getValue() || this.isChanged() )
- element.setAttribute( 'id', this.getValue() );
- }
- }
- },
- {
- id : 'cmbLangDir',
- type : 'select',
- style : 'width : 100px;',
- label : editor.lang.common.langDir,
- 'default' : '',
- items :
- [
- [ editor.lang.common.notSet, '' ],
- [ editor.lang.common.langDirLtr, 'ltr' ],
- [ editor.lang.common.langDirRtl, 'rtl' ]
- ],
- setup : function( type, element )
- {
- if ( type == IMAGE )
- this.setValue( element.getAttribute( 'dir' ) );
- },
- commit : function( type, element )
- {
- if ( type == IMAGE )
- {
- if ( this.getValue() || this.isChanged() )
- element.setAttribute( 'dir', this.getValue() );
- }
- }
- },
- {
- type : 'text',
- id : 'txtLangCode',
- label : editor.lang.common.langCode,
- 'default' : '',
- setup : function( type, element )
- {
- if ( type == IMAGE )
- this.setValue( element.getAttribute( 'lang' ) );
- },
- commit : function( type, element )
- {
- if ( type == IMAGE )
- {
- if ( this.getValue() || this.isChanged() )
- element.setAttribute( 'lang', this.getValue() );
- }
- }
- }
- ]
- },
- {
- type : 'text',
- id : 'txtGenLongDescr',
- label : editor.lang.common.longDescr,
- setup : function( type, element )
- {
- if ( type == IMAGE )
- this.setValue( element.getAttribute( 'longDesc' ) );
- },
- commit : function( type, element )
- {
- if ( type == IMAGE )
- {
- if ( this.getValue() || this.isChanged() )
- element.setAttribute( 'longDesc', this.getValue() );
- }
- }
- },
- {
- type : 'hbox',
- widths : [ '50%', '50%' ],
- children :
- [
- {
- type : 'text',
- id : 'txtGenClass',
- label : editor.lang.common.cssClass,
- 'default' : '',
- setup : function( type, element )
- {
- if ( type == IMAGE )
- this.setValue( element.getAttribute( 'class' ) );
- },
- commit : function( type, element )
- {
- if ( type == IMAGE )
- {
- if ( this.getValue() || this.isChanged() )
- element.setAttribute( 'class', this.getValue() );
- }
- }
- },
- {
- type : 'text',
- id : 'txtGenTitle',
- label : editor.lang.common.advisoryTitle,
- 'default' : '',
- onChange : function()
- {
- updatePreview( this.getDialog() );
- },
- setup : function( type, element )
- {
- if ( type == IMAGE )
- this.setValue( element.getAttribute( 'title' ) );
- },
- commit : function( type, element )
- {
- if ( type == IMAGE )
- {
- if ( this.getValue() || this.isChanged() )
- element.setAttribute( 'title', this.getValue() );
- }
- else if ( type == PREVIEW )
- {
- element.setAttribute( 'title', this.getValue() );
- }
- else if ( type == CLEANUP )
- {
- element.removeAttribute( 'title' );
- }
- }
- }
- ]
- },
- {
- type : 'text',
- id : 'txtdlgGenStyle',
- label : editor.lang.common.cssStyle,
- 'default' : '',
- setup : function( type, element )
- {
- if ( type == IMAGE )
- {
- var genStyle = element.getAttribute( 'style' );
- if ( !genStyle && element.$.style.cssText )
- genStyle = element.$.style.cssText;
- this.setValue( genStyle );
-
- var height = element.$.style.height,
- width = element.$.style.width,
- aMatchH = ( height ? height : '' ).match( regexGetSize ),
- aMatchW = ( width ? width : '').match( regexGetSize );
-
- this.attributesInStyle =
- {
- height : !!aMatchH,
- width : !!aMatchW
- };
- }
- },
- 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() );
- }
- }
- }
- ]
- }
- ]
- };
- };
-
- CKEDITOR.dialog.add( 'image', function( editor )
- {
- return imageDialog( editor, 'image' );
- });
-
- CKEDITOR.dialog.add( 'imagebutton', function( editor )
- {
- return imageDialog( editor, 'imagebutton' );
- });
-})();
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +(function() +{ + var imageDialog = function( editor, dialogType ) + { + // Load image preview. + var IMAGE = 1, + LINK = 2, + PREVIEW = 4, + CLEANUP = 8, + regexGetSize = /^\s*(\d+)((px)|\%)?\s*$/i, + regexGetSizeOrEmpty = /(^\s*(\d+)((px)|\%)?\s*$)|^$/i, + pxLengthRegex = /^\d+px$/; + + var onSizeChange = function() + { + var value = this.getValue(), // This = input element. + dialog = this.getDialog(), + aMatch = value.match( regexGetSize ); // Check value + if ( aMatch ) + { + if ( aMatch[2] == '%' ) // % is allowed - > unlock ratio. + switchLockRatio( dialog, false ); // Unlock. + value = aMatch[1]; + } + + // Only if ratio is locked + if ( dialog.lockRatio ) + { + var oImageOriginal = dialog.originalElement; + if ( oImageOriginal.getCustomData( 'isReady' ) == 'true' ) + { + if ( this.id == 'txtHeight' ) + { + if ( value && value != '0' ) + value = Math.round( oImageOriginal.$.width * ( value / oImageOriginal.$.height ) ); + if ( !isNaN( value ) ) + dialog.setValueOf( 'info', 'txtWidth', value ); + } + else //this.id = txtWidth. + { + if ( value && value != '0' ) + value = Math.round( oImageOriginal.$.height * ( value / oImageOriginal.$.width ) ); + if ( !isNaN( value ) ) + dialog.setValueOf( 'info', 'txtHeight', value ); + } + } + } + updatePreview( dialog ); + }; + + var updatePreview = function( dialog ) + { + //Don't load before onShow. + if ( !dialog.originalElement || !dialog.preview ) + return 1; + + // Read attributes and update imagePreview; + dialog.commitContent( PREVIEW, dialog.preview ); + 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; + + // Dialog may already closed. (#5505) + if( !oImageOriginal ) + return null; + + var ratioButton = CKEDITOR.document.getById( btnLockSizesId ); + + if ( oImageOriginal.getCustomData( 'isReady' ) == 'true' ) + { + if ( value == 'check' ) // Check image ratio and original image ratio. + { + var width = dialog.getValueOf( 'info', 'txtWidth' ), + height = dialog.getValueOf( 'info', 'txtHeight' ), + originalRatio = oImageOriginal.$.width * 1000 / oImageOriginal.$.height, + thisRatio = width * 1000 / height; + dialog.lockRatio = false; // Default: unlock ratio + + if ( !width && !height ) + dialog.lockRatio = true; + else if ( !isNaN( originalRatio ) && !isNaN( thisRatio ) ) + { + if ( Math.round( originalRatio ) == Math.round( thisRatio ) ) + dialog.lockRatio = true; + } + } + else if ( value != undefined ) + dialog.lockRatio = value; + else + dialog.lockRatio = !dialog.lockRatio; + } + else if ( value != 'check' ) // I can't lock ratio if ratio is unknown. + dialog.lockRatio = false; + + if ( dialog.lockRatio ) + ratioButton.removeClass( 'cke_btn_unlocked' ); + else + ratioButton.addClass( 'cke_btn_unlocked' ); + + var lang = dialog._.editor.lang.image, + label = lang[ dialog.lockRatio ? 'unlockRatio' : 'lockRatio' ]; + + ratioButton.setAttribute( 'title', label ); + ratioButton.getFirst().setText( label ); + + return dialog.lockRatio; + }; + + var resetSize = function( dialog ) + { + var oImageOriginal = dialog.originalElement; + if ( oImageOriginal.getCustomData( 'isReady' ) == 'true' ) + { + dialog.setValueOf( 'info', 'txtWidth', oImageOriginal.$.width ); + dialog.setValueOf( 'info', 'txtHeight', oImageOriginal.$.height ); + } + updatePreview( dialog ); + }; + + var setupDimension = function( type, element ) + { + if ( type != IMAGE ) + return; + + function checkDimension( size, defaultValue ) + { + var aMatch = size.match( regexGetSize ); + if ( aMatch ) + { + if ( aMatch[2] == '%' ) // % is allowed. + { + aMatch[1] += '%'; + switchLockRatio( dialog, false ); // Unlock ratio + } + return aMatch[1]; + } + return defaultValue; + } + + var dialog = this.getDialog(), + value = '', + dimension = (( this.id == 'txtWidth' )? 'width' : 'height' ), + size = element.getAttribute( dimension ); + + if ( size ) + value = checkDimension( size, value ); + value = checkDimension( element.getStyle( dimension ), value ); + + this.setValue( value ); + }; + + var previewPreloader; + + var onImgLoadEvent = function() + { + // Image is ready. + var original = this.originalElement; + original.setCustomData( 'isReady', 'true' ); + original.removeListener( 'load', onImgLoadEvent ); + original.removeListener( 'error', onImgLoadErrorEvent ); + original.removeListener( 'abort', onImgLoadErrorEvent ); + + // Hide loader + CKEDITOR.document.getById( imagePreviewLoaderId ).setStyle( 'display', 'none' ); + + // New image -> new domensions + if ( !this.dontResetSize ) + resetSize( this ); + + if ( this.firstLoad ) + CKEDITOR.tools.setTimeout( function(){ switchLockRatio( this, 'check' ); }, 0, this ); + + this.firstLoad = false; + this.dontResetSize = false; + }; + + var onImgLoadErrorEvent = function() + { + // Error. Image is not loaded. + var original = this.originalElement; + original.removeListener( 'load', onImgLoadEvent ); + original.removeListener( 'error', onImgLoadErrorEvent ); + original.removeListener( 'abort', onImgLoadErrorEvent ); + + // Set Error image. + var noimage = CKEDITOR.getUrl( editor.skinPath + 'images/noimage.png' ); + + if ( this.preview ) + this.preview.setAttribute( 'src', noimage ); + + // Hide loader + CKEDITOR.document.getById( imagePreviewLoaderId ).setStyle( 'display', 'none' ); + switchLockRatio( this, false ); // Unlock. + }; + + var numbering = function( id ){ return id + CKEDITOR.tools.getNextNumber(); }, + btnLockSizesId = numbering( 'btnLockSizes' ), + btnResetSizeId = numbering( 'btnResetSize' ), + imagePreviewLoaderId = numbering( 'ImagePreviewLoader' ), + imagePreviewBoxId = numbering( 'ImagePreviewBox' ), + previewLinkId = numbering( 'previewLink' ), + previewImageId = numbering( 'previewImage' ); + + return { + title : ( dialogType == 'image' ) ? editor.lang.image.title : editor.lang.image.titleButton, + minWidth : 420, + minHeight : 310, + onShow : function() + { + this.imageElement = false; + this.linkElement = false; + + // Default: create a new element. + this.imageEditMode = false; + this.linkEditMode = false; + + this.lockRatio = true; + this.dontResetSize = false; + this.firstLoad = true; + this.addLink = false; + + var editor = this.getParentEditor(), + sel = this.getParentEditor().getSelection(), + element = sel.getSelectedElement(), + link = element && element.getAscendant( 'a' ); + + //Hide loader. + CKEDITOR.document.getById( imagePreviewLoaderId ).setStyle( 'display', 'none' ); + // Create the preview before setup the dialog contents. + previewPreloader = new CKEDITOR.dom.element( 'img', editor.document ); + this.preview = CKEDITOR.document.getById( previewImageId ); + + // Copy of the image + this.originalElement = editor.document.createElement( 'img' ); + this.originalElement.setAttribute( 'alt', '' ); + this.originalElement.setCustomData( 'isReady', 'false' ); + + if ( link ) + { + this.linkElement = link; + this.linkEditMode = true; + + // Look for Image element. + var linkChildren = link.getChildren(); + if ( linkChildren.count() == 1 ) // 1 child. + { + var childTagName = linkChildren.getItem( 0 ).getName(); + if ( childTagName == 'img' || childTagName == 'input' ) + { + this.imageElement = linkChildren.getItem( 0 ); + if ( this.imageElement.getName() == 'img' ) + this.imageEditMode = 'img'; + else if ( this.imageElement.getName() == 'input' ) + this.imageEditMode = 'input'; + } + } + // Fill out all fields. + if ( dialogType == 'image' ) + this.setupContent( LINK, link ); + } + + 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 ) + { + // 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 ); + + // 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' ) ) ) + { + this.preview.removeAttribute( 'src' ); + this.preview.setStyle( 'display', 'none' ); + } + }, + onOk : function() + { + // Edit existing Image. + if ( this.imageEditMode ) + { + var imgTagName = this.imageEditMode; + + // Image dialog and Input element. + if ( dialogType == 'image' && imgTagName == 'input' && confirm( editor.lang.image.button2Img ) ) + { + // Replace INPUT-> IMG + imgTagName = 'img'; + this.imageElement = editor.document.createElement( 'img' ); + this.imageElement.setAttribute( 'alt', '' ); + editor.insertElement( this.imageElement ); + } + // ImageButton dialog and Image element. + else if ( dialogType != 'image' && imgTagName == 'img' && confirm( editor.lang.image.img2Button )) + { + // Replace IMG -> INPUT + imgTagName = 'input'; + this.imageElement = editor.document.createElement( 'input' ); + this.imageElement.setAttributes( + { + type : 'image', + alt : '' + } + ); + editor.insertElement( this.imageElement ); + } + else + { + // Restore the original element before all commits. + this.imageElement = this.cleanImageElement; + delete this.cleanImageElement; + } + } + else // Create a new image. + { + // Image dialog -> create IMG element. + if ( dialogType == 'image' ) + this.imageElement = editor.document.createElement( 'img' ); + else + { + this.imageElement = editor.document.createElement( 'input' ); + this.imageElement.setAttribute ( 'type' ,'image' ); + } + this.imageElement.setAttribute( 'alt', '' ); + } + + // Create a new link. + if ( !this.linkEditMode ) + this.linkElement = editor.document.createElement( 'a' ); + + // Set attributes. + 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 ) + { + if ( this.addLink ) + { + //Insert a new Link. + if ( !this.linkEditMode ) + { + editor.insertElement(this.linkElement); + this.linkElement.append(this.imageElement, false); + } + else //Link already exists, image not. + editor.insertElement(this.imageElement ); + } + else + editor.insertElement( this.imageElement ); + } + else // Image already exists. + { + //Add a new link element. + if ( !this.linkEditMode && this.addLink ) + { + editor.insertElement( this.linkElement ); + this.imageElement.appendTo( this.linkElement ); + } + //Remove Link, Image exists. + else if ( this.linkEditMode && !this.addLink ) + { + editor.getSelection().selectElement( this.linkElement ); + editor.insertElement( this.imageElement ); + } + } + }, + onLoad : function() + { + if ( dialogType != 'image' ) + this.hidePage( 'Link' ); //Hide Link tab. + var doc = this._.element.getDocument(); + this.addFocusable( doc.getById( btnResetSizeId ), 5 ); + this.addFocusable( doc.getById( btnLockSizesId ), 5 ); + + this.commitContent = commitContent; + }, + onHide : function() + { + if ( this.preview ) + this.commitContent( CLEANUP, this.preview ); + + if ( this.originalElement ) + { + this.originalElement.removeListener( 'load', onImgLoadEvent ); + this.originalElement.removeListener( 'error', onImgLoadErrorEvent ); + this.originalElement.removeListener( 'abort', onImgLoadErrorEvent ); + this.originalElement.remove(); + this.originalElement = false; // Dialog is closed. + } + + delete this.imageElement; + }, + contents : [ + { + id : 'info', + label : editor.lang.image.infoTab, + accessKey : 'I', + elements : + [ + { + type : 'vbox', + padding : 0, + children : + [ + { + type : 'hbox', + widths : [ '280px', '110px' ], + align : 'right', + children : + [ + { + id : 'txtUrl', + type : 'text', + label : editor.lang.common.url, + required: true, + onChange : function() + { + var dialog = this.getDialog(), + newUrl = this.getValue(); + + //Update original image + if ( newUrl.length > 0 ) //Prevent from load before onShow + { + dialog = this.getDialog(); + var original = dialog.originalElement; + + dialog.preview.removeStyle( 'display' ); + + original.setCustomData( 'isReady', 'false' ); + // Show loader + var loader = CKEDITOR.document.getById( imagePreviewLoaderId ); + if ( loader ) + loader.setStyle( 'display', '' ); + + original.on( 'load', onImgLoadEvent, dialog ); + original.on( 'error', onImgLoadErrorEvent, dialog ); + original.on( 'abort', onImgLoadErrorEvent, dialog ); + original.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. + else if ( dialog.preview ) + { + dialog.preview.removeAttribute( 'src' ); + dialog.preview.setStyle( 'display', 'none' ); + } + }, + setup : function( type, element ) + { + if ( type == IMAGE ) + { + var url = element.getAttribute( '_cke_saved_src' ) || element.getAttribute( 'src' ); + var field = this; + + this.getDialog().dontResetSize = true; + + field.setValue( url ); // And call this.onChange() + // Manually set the initial value.(#4191) + field.setInitValue(); + field.focus(); + } + }, + commit : function( type, element ) + { + if ( type == IMAGE && ( this.getValue() || this.isChanged() ) ) + { + element.setAttribute( '_cke_saved_src', decodeURI( this.getValue() ) ); + element.setAttribute( 'src', decodeURI( this.getValue() ) ); + } + else if ( type == CLEANUP ) + { + element.setAttribute( 'src', '' ); // If removeAttribute doesn't work. + element.removeAttribute( 'src' ); + } + }, + validate : CKEDITOR.dialog.validate.notEmpty( editor.lang.image.urlMissing ) + }, + { + type : 'button', + id : 'browse', + // v-align with the 'txtUrl' field. + // TODO: We need something better than a fixed size here. + style : 'display:inline-block;margin-top:10px;', + align : 'center', + label : editor.lang.common.browseServer, + hidden : true, + filebrowser : 'info:txtUrl' + } + ] + } + ] + }, + { + id : 'txtAlt', + type : 'text', + label : editor.lang.image.alt, + accessKey : 'A', + 'default' : '', + onChange : function() + { + updatePreview( this.getDialog() ); + }, + setup : function( type, element ) + { + if ( type == IMAGE ) + this.setValue( element.getAttribute( 'alt' ) ); + }, + commit : function( type, element ) + { + if ( type == IMAGE ) + { + if ( this.getValue() || this.isChanged() ) + element.setAttribute( 'alt', this.getValue() ); + } + else if ( type == PREVIEW ) + { + element.setAttribute( 'alt', this.getValue() ); + } + else if ( type == CLEANUP ) + { + element.removeAttribute( 'alt' ); + } + } + }, + { + type : 'hbox', + widths : [ '140px', '240px' ], + children : + [ + { + type : 'vbox', + padding : 10, + children : + [ + { + type : 'hbox', + widths : [ '70%', '30%' ], + children : + [ + { + type : 'vbox', + padding : 1, + children : + [ + { + type : 'text', + width: '40px', + id : 'txtWidth', + labelLayout : 'horizontal', + label : editor.lang.image.width, + onKeyUp : onSizeChange, + onChange : function() + { + commitInternally.call( this, 'advanced:txtdlgGenStyle' ); + }, + validate : function() + { + var aMatch = this.getValue().match( regexGetSizeOrEmpty ); + if ( !aMatch ) + alert( editor.lang.image.validateWidth ); + return !!aMatch; + }, + setup : setupDimension, + commit : function( type, element, internalCommit ) + { + var value = this.getValue(); + if ( type == IMAGE ) + { + if ( value ) + element.setStyle( 'width', CKEDITOR.tools.cssLength( value ) ); + else if ( !value && this.isChanged( ) ) + element.removeStyle( 'width' ); + + !internalCommit && element.removeAttribute( 'width' ); + } + else if ( type == PREVIEW ) + { + var aMatch = value.match( regexGetSize ); + if ( !aMatch ) + { + var oImageOriginal = this.getDialog().originalElement; + if ( oImageOriginal.getCustomData( 'isReady' ) == 'true' ) + element.setStyle( 'width', oImageOriginal.$.width + 'px'); + } + else + element.setStyle( 'width', value + 'px'); + } + else if ( type == CLEANUP ) + { + element.removeAttribute( 'width' ); + element.removeStyle( 'width' ); + } + } + }, + { + type : 'text', + id : 'txtHeight', + width: '40px', + labelLayout : 'horizontal', + label : editor.lang.image.height, + onKeyUp : onSizeChange, + onChange : function() + { + commitInternally.call( this, 'advanced:txtdlgGenStyle' ); + }, + validate : function() + { + var aMatch = this.getValue().match( regexGetSizeOrEmpty ); + if ( !aMatch ) + alert( editor.lang.image.validateHeight ); + return !!aMatch; + }, + setup : setupDimension, + commit : function( type, element, internalCommit ) + { + var value = this.getValue(); + if ( type == IMAGE ) + { + if ( value ) + 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 ) + { + var aMatch = value.match( regexGetSize ); + if ( !aMatch ) + { + var oImageOriginal = this.getDialog().originalElement; + if ( oImageOriginal.getCustomData( 'isReady' ) == 'true' ) + element.setStyle( 'height', oImageOriginal.$.height + 'px' ); + } + else + element.setStyle( 'height', value + 'px' ); + } + else if ( type == CLEANUP ) + { + element.removeAttribute( 'height' ); + element.removeStyle( 'height' ); + } + } + } + ] + }, + { + type : 'html', + style : 'margin-top:10px;width:40px;height:40px;', + onLoad : function() + { + // Activate Reset button + var resetButton = CKEDITOR.document.getById( btnResetSizeId ), + ratioButton = CKEDITOR.document.getById( btnLockSizesId ); + if ( resetButton ) + { + resetButton.on( 'click', function(evt) + { + resetSize( this ); + evt.data.preventDefault(); + }, this.getDialog() ); + resetButton.on( 'mouseover', function() + { + this.addClass( 'cke_btn_over' ); + }, resetButton ); + resetButton.on( 'mouseout', function() + { + this.removeClass( 'cke_btn_over' ); + }, resetButton ); + } + // Activate (Un)LockRatio button + if ( ratioButton ) + { + ratioButton.on( 'click', function(evt) + { + var locked = switchLockRatio( this ), + oImageOriginal = this.originalElement, + width = this.getValueOf( 'info', 'txtWidth' ); + + if ( oImageOriginal.getCustomData( 'isReady' ) == 'true' && width ) + { + var height = oImageOriginal.$.height / oImageOriginal.$.width * width; + if ( !isNaN( height ) ) + { + this.setValueOf( 'info', 'txtHeight', Math.round( height ) ); + updatePreview( this ); + } + } + evt.data.preventDefault(); + }, this.getDialog() ); + ratioButton.on( 'mouseover', function() + { + this.addClass( 'cke_btn_over' ); + }, ratioButton ); + ratioButton.on( 'mouseout', function() + { + this.removeClass( 'cke_btn_over' ); + }, ratioButton ); + } + }, + html : '<div>'+ + '<a href="javascript:void(0)" tabindex="-1" title="' + editor.lang.image.unlockRatio + + '" class="cke_btn_locked" id="' + btnLockSizesId + '" role="button"><span class="cke_label">' + editor.lang.image.unlockRatio + '</span></a>' + + '<a href="javascript:void(0)" tabindex="-1" title="' + editor.lang.image.resetSize + + '" class="cke_btn_reset" id="' + btnResetSizeId + '" role="button"><span class="cke_label">' + editor.lang.image.resetSize + '</span></a>'+ + '</div>' + } + ] + }, + { + type : 'vbox', + padding : 1, + children : + [ + { + type : 'text', + id : 'txtBorder', + width: '60px', + labelLayout : 'horizontal', + label : editor.lang.image.border, + 'default' : '', + onKeyUp : function() + { + updatePreview( this.getDialog() ); + }, + onChange : function() + { + commitInternally.call( this, 'advanced:txtdlgGenStyle' ); + }, + validate : CKEDITOR.dialog.validate.integer( editor.lang.image.validateBorder ), + setup : function( type, element ) + { + if ( type == IMAGE ) + { + var value, + borderStyle = element.getStyle( 'border-width' ); + borderStyle = borderStyle && borderStyle.match( /^(\d+px)(?: \1 \1 \1)?$/ ); + value = borderStyle && parseInt( borderStyle[ 1 ], 10 ); + isNaN ( parseInt( value, 10 ) ) && ( value = element.getAttribute( 'border' ) ); + this.setValue( value ); + } + }, + commit : function( type, element, internalCommit ) + { + var value = parseInt( this.getValue(), 10 ); + if ( type == IMAGE || type == PREVIEW ) + { + if ( !isNaN( 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-width' ); + element.removeStyle( 'border-style' ); + element.removeStyle( 'border-color' ); + } + } + }, + { + type : 'text', + id : 'txtHSpace', + width: '60px', + labelLayout : 'horizontal', + label : editor.lang.image.hSpace, + 'default' : '', + onKeyUp : function() + { + updatePreview( this.getDialog() ); + }, + onChange : function() + { + commitInternally.call( this, 'advanced:txtdlgGenStyle' ); + }, + validate : CKEDITOR.dialog.validate.integer( editor.lang.image.validateHSpace ), + setup : function( type, element ) + { + if ( type == IMAGE ) + { + 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; + isNaN( parseInt( value, 10 ) ) && ( value = element.getAttribute( 'hspace' ) ); + + this.setValue( value ); + } + }, + commit : function( type, element, internalCommit ) + { + var value = parseInt( this.getValue(), 10 ); + if ( type == IMAGE || type == PREVIEW ) + { + if ( !isNaN( 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 ) + { + element.removeAttribute( 'hspace' ); + element.removeStyle( 'margin-left' ); + element.removeStyle( 'margin-right' ); + } + } + }, + { + type : 'text', + id : 'txtVSpace', + width : '60px', + labelLayout : 'horizontal', + label : editor.lang.image.vSpace, + 'default' : '', + onKeyUp : function() + { + updatePreview( this.getDialog() ); + }, + onChange : function() + { + commitInternally.call( this, 'advanced:txtdlgGenStyle' ); + }, + validate : CKEDITOR.dialog.validate.integer( editor.lang.image.validateVSpace ), + setup : function( type, element ) + { + if ( type == IMAGE ) + { + 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; + isNaN ( parseInt( value, 10 ) ) && ( value = element.getAttribute( 'vspace' ) ); + this.setValue( value ); + } + }, + commit : function( type, element, internalCommit ) + { + var value = parseInt( this.getValue(), 10 ); + if ( type == IMAGE || type == PREVIEW ) + { + if ( !isNaN( 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 ) + { + element.removeAttribute( 'vspace' ); + element.removeStyle( 'margin-top' ); + element.removeStyle( 'margin-bottom' ); + } + } + }, + { + id : 'cmbAlign', + type : 'select', + labelLayout : 'horizontal', + widths : [ '35%','65%' ], + style : 'width:90px', + label : editor.lang.image.align, + 'default' : '', + items : + [ + [ editor.lang.common.notSet , ''], + [ editor.lang.image.alignLeft , 'left'], + [ 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 ) + { + 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, internalCommit ) + { + var value = this.getValue(); + if ( type == IMAGE || type == PREVIEW ) + { + if ( value ) + element.setStyle( 'float', value ); + else + 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.removeStyle( 'float' ); + + } + } + ] + } + ] + }, + { + type : 'vbox', + height : '250px', + children : + [ + { + type : 'html', + style : 'width:95%;', + html : '<div>' + CKEDITOR.tools.htmlEncode( editor.lang.common.preview ) +'<br>'+ + '<div id="' + imagePreviewLoaderId + '" class="ImagePreviewLoader" style="display:none"><div class="loading"> </div></div>'+ + '<div id="' + imagePreviewBoxId + '" class="ImagePreviewBox"><table><tr><td>'+ + '<a href="javascript:void(0)" target="_blank" onclick="return false;" id="' + previewLinkId + '">'+ + '<img id="' + previewImageId + '" alt="" /></a>' + + ( editor.config.image_previewText || + '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.' ) + + '</td></tr></table></div></div>' + } + ] + } + ] + } + ] + }, + { + id : 'Link', + label : editor.lang.link.title, + padding : 0, + elements : + [ + { + id : 'txtUrl', + type : 'text', + label : editor.lang.common.url, + style : 'width: 100%', + 'default' : '', + setup : function( type, element ) + { + if ( type == LINK ) + { + var href = element.getAttribute( '_cke_saved_href' ); + if ( !href ) + href = element.getAttribute( 'href' ); + this.setValue( href ); + } + }, + commit : function( type, element ) + { + if ( type == LINK ) + { + if ( this.getValue() || this.isChanged() ) + { + element.setAttribute( '_cke_saved_href', decodeURI( this.getValue() ) ); + element.setAttribute( 'href', 'javascript:void(0)/*' + + CKEDITOR.tools.getNextNumber() + '*/' ); + + if ( this.getValue() || !editor.config.image_removeLinkByEmptyURL ) + this.getDialog().addLink = true; + } + } + } + }, + { + type : 'button', + id : 'browse', + filebrowser : + { + action : 'Browse', + target: 'Link:txtUrl', + url: editor.config.filebrowserImageBrowseLinkUrl || editor.config.filebrowserBrowseUrl + }, + style : 'float:right', + hidden : true, + label : editor.lang.common.browseServer + }, + { + id : 'cmbTarget', + type : 'select', + label : editor.lang.common.target, + 'default' : '', + items : + [ + [ editor.lang.common.notSet , ''], + [ editor.lang.common.targetNew , '_blank'], + [ editor.lang.common.targetTop , '_top'], + [ editor.lang.common.targetSelf , '_self'], + [ editor.lang.common.targetParent , '_parent'] + ], + setup : function( type, element ) + { + if ( type == LINK ) + this.setValue( element.getAttribute( 'target' ) ); + }, + commit : function( type, element ) + { + if ( type == LINK ) + { + if ( this.getValue() || this.isChanged() ) + element.setAttribute( 'target', this.getValue() ); + } + } + } + ] + }, + { + id : 'Upload', + hidden : true, + filebrowser : 'uploadButton', + label : editor.lang.image.upload, + elements : + [ + { + type : 'file', + id : 'upload', + label : editor.lang.image.btnUpload, + style: 'height:40px', + size : 38 + }, + { + type : 'fileButton', + id : 'uploadButton', + filebrowser : 'info:txtUrl', + label : editor.lang.image.btnUpload, + 'for' : [ 'Upload', 'upload' ] + } + ] + }, + { + id : 'advanced', + label : editor.lang.common.advancedTab, + elements : + [ + { + type : 'hbox', + widths : [ '50%', '25%', '25%' ], + children : + [ + { + type : 'text', + id : 'linkId', + label : editor.lang.common.id, + setup : function( type, element ) + { + if ( type == IMAGE ) + this.setValue( element.getAttribute( 'id' ) ); + }, + commit : function( type, element ) + { + if ( type == IMAGE ) + { + if ( this.getValue() || this.isChanged() ) + element.setAttribute( 'id', this.getValue() ); + } + } + }, + { + id : 'cmbLangDir', + type : 'select', + style : 'width : 100px;', + label : editor.lang.common.langDir, + 'default' : '', + items : + [ + [ editor.lang.common.notSet, '' ], + [ editor.lang.common.langDirLtr, 'ltr' ], + [ editor.lang.common.langDirRtl, 'rtl' ] + ], + setup : function( type, element ) + { + if ( type == IMAGE ) + this.setValue( element.getAttribute( 'dir' ) ); + }, + commit : function( type, element ) + { + if ( type == IMAGE ) + { + if ( this.getValue() || this.isChanged() ) + element.setAttribute( 'dir', this.getValue() ); + } + } + }, + { + type : 'text', + id : 'txtLangCode', + label : editor.lang.common.langCode, + 'default' : '', + setup : function( type, element ) + { + if ( type == IMAGE ) + this.setValue( element.getAttribute( 'lang' ) ); + }, + commit : function( type, element ) + { + if ( type == IMAGE ) + { + if ( this.getValue() || this.isChanged() ) + element.setAttribute( 'lang', this.getValue() ); + } + } + } + ] + }, + { + type : 'text', + id : 'txtGenLongDescr', + label : editor.lang.common.longDescr, + setup : function( type, element ) + { + if ( type == IMAGE ) + this.setValue( element.getAttribute( 'longDesc' ) ); + }, + commit : function( type, element ) + { + if ( type == IMAGE ) + { + if ( this.getValue() || this.isChanged() ) + element.setAttribute( 'longDesc', this.getValue() ); + } + } + }, + { + type : 'hbox', + widths : [ '50%', '50%' ], + children : + [ + { + type : 'text', + id : 'txtGenClass', + label : editor.lang.common.cssClass, + 'default' : '', + setup : function( type, element ) + { + if ( type == IMAGE ) + this.setValue( element.getAttribute( 'class' ) ); + }, + commit : function( type, element ) + { + if ( type == IMAGE ) + { + if ( this.getValue() || this.isChanged() ) + element.setAttribute( 'class', this.getValue() ); + } + } + }, + { + type : 'text', + id : 'txtGenTitle', + label : editor.lang.common.advisoryTitle, + 'default' : '', + onChange : function() + { + updatePreview( this.getDialog() ); + }, + setup : function( type, element ) + { + if ( type == IMAGE ) + this.setValue( element.getAttribute( 'title' ) ); + }, + commit : function( type, element ) + { + if ( type == IMAGE ) + { + if ( this.getValue() || this.isChanged() ) + element.setAttribute( 'title', this.getValue() ); + } + else if ( type == PREVIEW ) + { + element.setAttribute( 'title', this.getValue() ); + } + else if ( type == CLEANUP ) + { + element.removeAttribute( 'title' ); + } + } + } + ] + }, + { + type : 'text', + id : 'txtdlgGenStyle', + label : editor.lang.common.cssStyle, + 'default' : '', + setup : function( type, element ) + { + if ( type == IMAGE ) + { + var genStyle = element.getAttribute( 'style' ); + if ( !genStyle && element.$.style.cssText ) + genStyle = element.$.style.cssText; + this.setValue( genStyle ); + + var height = element.$.style.height, + width = element.$.style.width, + aMatchH = ( height ? height : '' ).match( regexGetSize ), + aMatchW = ( width ? width : '').match( regexGetSize ); + + this.attributesInStyle = + { + height : !!aMatchH, + width : !!aMatchW + }; + } + }, + 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() ); + } + } + } + ] + } + ] + }; + }; + + CKEDITOR.dialog.add( 'image', function( editor ) + { + return imageDialog( editor, 'image' ); + }); + + CKEDITOR.dialog.add( 'imagebutton', function( editor ) + { + return imageDialog( editor, 'imagebutton' ); + }); +})(); diff --git a/_source/plugins/image/plugin.js b/_source/plugins/image/plugin.js index 4d808fe..79e4f72 100644 --- a/_source/plugins/image/plugin.js +++ b/_source/plugins/image/plugin.js @@ -1,64 +1,81 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-/**
- * @file Image plugin
- */
-
-CKEDITOR.plugins.add( 'image',
-{
- init : function( editor )
- {
- var pluginName = 'image';
-
- // Register the dialog.
- CKEDITOR.dialog.add( pluginName, this.path + 'dialogs/image.js' );
-
- // Register the command.
- editor.addCommand( pluginName, new CKEDITOR.dialogCommand( pluginName ) );
-
- // Register the toolbar button.
- editor.ui.addButton( 'Image',
- {
- label : editor.lang.common.image,
- command : pluginName
- });
-
- // If the "menu" plugin is loaded, register the menu items.
- if ( editor.addMenuItems )
- {
- editor.addMenuItems(
- {
- image :
- {
- label : editor.lang.image.menu,
- command : 'image',
- group : 'image'
- }
- });
- }
-
- // If the "contextmenu" plugin is loaded, register the listeners.
- if ( editor.contextMenu )
- {
- editor.contextMenu.addListener( function( element, selection )
- {
- if ( !element || !element.is( 'img' ) || element.getAttribute( '_cke_realelement' ) )
- return null;
-
- return { image : CKEDITOR.TRISTATE_OFF };
- });
- }
- }
-} );
-
-/**
- * Whether to remove links when emptying the link URL field in the image dialog.
- * @type Boolean
- * @default true
- * @example
- * config.image_removeLinkByEmptyURL = false;
- */
-CKEDITOR.config.image_removeLinkByEmptyURL = true;
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @file Image plugin + */ + +CKEDITOR.plugins.add( 'image', +{ + init : function( editor ) + { + var pluginName = 'image'; + + // Register the dialog. + CKEDITOR.dialog.add( pluginName, this.path + 'dialogs/image.js' ); + + // Register the command. + editor.addCommand( pluginName, new CKEDITOR.dialogCommand( pluginName ) ); + + // Register the toolbar button. + editor.ui.addButton( 'Image', + { + label : editor.lang.common.image, + command : pluginName + }); + + editor.on( 'doubleclick', function( evt ) + { + var element = evt.data.element; + + if ( element.is( 'img' ) && !element.getAttribute( '_cke_realelement' ) ) + evt.data.dialog = 'image'; + }); + + // If the "menu" plugin is loaded, register the menu items. + if ( editor.addMenuItems ) + { + editor.addMenuItems( + { + image : + { + label : editor.lang.image.menu, + command : 'image', + group : 'image' + } + }); + } + + // If the "contextmenu" plugin is loaded, register the listeners. + if ( editor.contextMenu ) + { + editor.contextMenu.addListener( function( element, selection ) + { + if ( !element || !element.is( 'img' ) || element.getAttribute( '_cke_realelement' ) ) + return null; + + return { image : CKEDITOR.TRISTATE_OFF }; + }); + } + } +} ); + +/** + * Whether to remove links when emptying the link URL field in the image dialog. + * @type Boolean + * @default true + * @example + * config.image_removeLinkByEmptyURL = false; + */ +CKEDITOR.config.image_removeLinkByEmptyURL = true; + +/** + * Padding text to set off the image in preview area. + * @name CKEDITOR.config.image_previewText + * @type String + * @default "Lorem ipsum dolor..." placehoder text. + * @example + * config.image_previewText = CKEDITOR.tools.repeat( '___ ', 100 ); + */ diff --git a/_source/plugins/indent/plugin.js b/_source/plugins/indent/plugin.js index e9992bd..1cee4cb 100644 --- a/_source/plugins/indent/plugin.js +++ b/_source/plugins/indent/plugin.js @@ -1,349 +1,422 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-/**
- * @file Increse and decrease indent commands.
- */
-
-(function()
-{
- var listNodeNames = { ol : 1, ul : 1 };
-
- function setState( editor, state )
- {
- editor.getCommand( this.name ).setState( state );
- }
-
- function onSelectionChange( evt )
- {
- var elements = evt.data.path.elements,
- listNode, listItem,
- editor = evt.editor;
-
- for ( var i = 0 ; i < elements.length ; i++ )
- {
- if ( elements[i].getName() == 'li' )
- {
- listItem = elements[i];
- continue;
- }
- if ( listNodeNames[ elements[i].getName() ] )
- {
- listNode = elements[i];
- break;
- }
- }
-
- if ( listNode )
- {
- if ( this.name == 'outdent' )
- return setState.call( this, editor, CKEDITOR.TRISTATE_OFF );
- else
- {
- while ( listItem && ( listItem = listItem.getPrevious( CKEDITOR.dom.walker.whitespaces( true ) ) ) )
- {
- if ( listItem.getName && listItem.getName() == 'li' )
- return setState.call( this, editor, CKEDITOR.TRISTATE_OFF );
- }
- return setState.call( this, editor, CKEDITOR.TRISTATE_DISABLED );
- }
- }
-
- if ( !this.useIndentClasses && this.name == 'indent' )
- return setState.call( this, editor, CKEDITOR.TRISTATE_OFF );
-
- var path = evt.data.path,
- firstBlock = path.block || path.blockLimit;
- if ( !firstBlock )
- return setState.call( this, editor, CKEDITOR.TRISTATE_DISABLED );
-
- if ( this.useIndentClasses )
- {
- var indentClass = firstBlock.$.className.match( this.classNameRegex ),
- indentStep = 0;
- if ( indentClass )
- {
- indentClass = indentClass[1];
- indentStep = this.indentClassMap[ indentClass ];
- }
- if ( ( this.name == 'outdent' && !indentStep ) ||
- ( this.name == 'indent' && indentStep == editor.config.indentClasses.length ) )
- return setState.call( this, editor, CKEDITOR.TRISTATE_DISABLED );
- return setState.call( this, editor, CKEDITOR.TRISTATE_OFF );
- }
- else
- {
- var indent = parseInt( firstBlock.getStyle( this.indentCssProperty ), 10 );
- if ( isNaN( indent ) )
- indent = 0;
- if ( indent <= 0 )
- return setState.call( this, editor, CKEDITOR.TRISTATE_DISABLED );
- return setState.call( this, editor, CKEDITOR.TRISTATE_OFF );
- }
- }
-
- function indentList( editor, range, listNode )
- {
- // Our starting and ending points of the range might be inside some blocks under a list item...
- // So before playing with the iterator, we need to expand the block to include the list items.
- var startContainer = range.startContainer,
- endContainer = range.endContainer;
- while ( startContainer && !startContainer.getParent().equals( listNode ) )
- startContainer = startContainer.getParent();
- while ( endContainer && !endContainer.getParent().equals( listNode ) )
- endContainer = endContainer.getParent();
-
- if ( !startContainer || !endContainer )
- return;
-
- // Now we can iterate over the individual items on the same tree depth.
- var block = startContainer,
- itemsToMove = [],
- stopFlag = false;
- while ( !stopFlag )
- {
- if ( block.equals( endContainer ) )
- stopFlag = true;
- itemsToMove.push( block );
- block = block.getNext();
- }
- if ( itemsToMove.length < 1 )
- return;
-
- // Do indent or outdent operations on the array model of the list, not the
- // list's DOM tree itself. The array model demands that it knows as much as
- // possible about the surrounding lists, we need to feed it the further
- // ancestor node that is still a list.
- var listParents = listNode.getParents( true );
- for ( var i = 0 ; i < listParents.length ; i++ )
- {
- if ( listParents[i].getName && listNodeNames[ listParents[i].getName() ] )
- {
- listNode = listParents[i];
- break;
- }
- }
- var indentOffset = this.name == 'indent' ? 1 : -1,
- startItem = itemsToMove[0],
- lastItem = itemsToMove[ itemsToMove.length - 1 ],
- database = {};
-
- // Convert the list DOM tree into a one dimensional array.
- var listArray = CKEDITOR.plugins.list.listToArray( listNode, database );
-
- // Apply indenting or outdenting on the array.
- var baseIndent = listArray[ lastItem.getCustomData( 'listarray_index' ) ].indent;
- for ( i = startItem.getCustomData( 'listarray_index' ) ; i <= lastItem.getCustomData( 'listarray_index' ) ; i++ )
- listArray[i].indent += indentOffset;
- for ( i = lastItem.getCustomData( 'listarray_index' ) + 1 ;
- i < listArray.length && listArray[i].indent > baseIndent ; i++ )
- listArray[i].indent += indentOffset;
-
- // Convert the array back to a DOM forest (yes we might have a few subtrees now).
- // And replace the old list with the new forest.
- var newList = CKEDITOR.plugins.list.arrayToList( listArray, database, null, editor.config.enterMode, 0 );
-
- // Avoid nested <li> after outdent even they're visually same,
- // recording them for later refactoring.(#3982)
- if ( this.name == 'outdent' )
- {
- var parentLiElement;
- if ( ( parentLiElement = listNode.getParent() ) && parentLiElement.is( 'li' ) )
- {
- var children = newList.listNode.getChildren(),
- pendingLis = [],
- count = children.count(),
- child;
-
- for ( i = count - 1 ; i >= 0 ; i-- )
- {
- if ( ( child = children.getItem( i ) ) && child.is && child.is( 'li' ) )
- pendingLis.push( child );
- }
- }
- }
-
- if ( newList )
- newList.listNode.replace( listNode );
-
- // Move the nested <li> to be appeared after the parent.
- if ( pendingLis && pendingLis.length )
- {
- for ( i = 0; i < pendingLis.length ; i++ )
- {
- var li = pendingLis[ i ],
- followingList = li;
-
- // Nest preceding <ul>/<ol> inside current <li> if any.
- while ( ( followingList = followingList.getNext() ) &&
- followingList.is &&
- followingList.getName() in listNodeNames )
- {
- li.append( followingList );
- }
-
- li.insertAfter( parentLiElement );
- }
- }
-
- // Clean up the markers.
- CKEDITOR.dom.element.clearAllMarkers( database );
- }
-
- function indentBlock( editor, range )
- {
- var iterator = range.createIterator(),
- enterMode = editor.config.enterMode;
- iterator.enforceRealBlocks = true;
- iterator.enlargeBr = enterMode != CKEDITOR.ENTER_BR;
- var block;
- while ( ( block = iterator.getNextParagraph() ) )
- {
-
- if ( this.useIndentClasses )
- {
- // Transform current class name to indent step index.
- var indentClass = block.$.className.match( this.classNameRegex ),
- indentStep = 0;
- if ( indentClass )
- {
- indentClass = indentClass[1];
- indentStep = this.indentClassMap[ indentClass ];
- }
-
- // Operate on indent step index, transform indent step index back to class
- // name.
- if ( this.name == 'outdent' )
- indentStep--;
- else
- indentStep++;
- indentStep = Math.min( indentStep, editor.config.indentClasses.length );
- indentStep = Math.max( indentStep, 0 );
- var className = CKEDITOR.tools.ltrim( block.$.className.replace( this.classNameRegex, '' ) );
- if ( indentStep < 1 )
- block.$.className = className;
- else
- block.addClass( editor.config.indentClasses[ indentStep - 1 ] );
- }
- else
- {
- var currentOffset = parseInt( block.getStyle( this.indentCssProperty ), 10 );
- if ( isNaN( currentOffset ) )
- currentOffset = 0;
- currentOffset += ( this.name == 'indent' ? 1 : -1 ) * editor.config.indentOffset;
- currentOffset = Math.max( currentOffset, 0 );
- currentOffset = Math.ceil( currentOffset / editor.config.indentOffset ) * editor.config.indentOffset;
- block.setStyle( this.indentCssProperty, currentOffset ? currentOffset + editor.config.indentUnit : '' );
- if ( block.getAttribute( 'style' ) === '' )
- block.removeAttribute( 'style' );
- }
- }
- }
-
- function indentCommand( editor, name )
- {
- this.name = name;
- this.useIndentClasses = editor.config.indentClasses && editor.config.indentClasses.length > 0;
- if ( this.useIndentClasses )
- {
- this.classNameRegex = new RegExp( '(?:^|\\s+)(' + editor.config.indentClasses.join( '|' ) + ')(?=$|\\s)' );
- this.indentClassMap = {};
- for ( var i = 0 ; i < editor.config.indentClasses.length ; i++ )
- this.indentClassMap[ editor.config.indentClasses[i] ] = i + 1;
- }
- else
- this.indentCssProperty = editor.config.contentsLangDirection == 'ltr' ? 'margin-left' : 'margin-right';
- }
-
- indentCommand.prototype = {
- exec : function( editor )
- {
- var selection = editor.getSelection(),
- range = selection && selection.getRanges()[0];
-
- if ( !selection || !range )
- return;
-
- var bookmarks = selection.createBookmarks( true ),
- nearestListBlock = range.getCommonAncestor();
-
- while ( nearestListBlock && !( nearestListBlock.type == CKEDITOR.NODE_ELEMENT &&
- listNodeNames[ nearestListBlock.getName() ] ) )
- nearestListBlock = nearestListBlock.getParent();
-
- if ( nearestListBlock )
- indentList.call( this, editor, range, nearestListBlock );
- else
- indentBlock.call( this, editor, range );
-
- editor.focus();
- editor.forceNextSelectionCheck();
- selection.selectBookmarks( bookmarks );
- }
- };
-
- CKEDITOR.plugins.add( 'indent',
- {
- init : function( editor )
- {
- // Register commands.
- var indent = new indentCommand( editor, 'indent' ),
- outdent = new indentCommand( editor, 'outdent' );
- editor.addCommand( 'indent', indent );
- editor.addCommand( 'outdent', outdent );
-
- // Register the toolbar buttons.
- editor.ui.addButton( 'Indent',
- {
- label : editor.lang.indent,
- command : 'indent'
- });
- editor.ui.addButton( 'Outdent',
- {
- label : editor.lang.outdent,
- command : 'outdent'
- });
-
- // Register the state changing handlers.
- editor.on( 'selectionChange', CKEDITOR.tools.bind( onSelectionChange, indent ) );
- editor.on( 'selectionChange', CKEDITOR.tools.bind( onSelectionChange, outdent ) );
- },
-
- requires : [ 'domiterator', 'list' ]
- } );
-})();
-
-CKEDITOR.tools.extend( CKEDITOR.config,
- {
- indentOffset : 40,
- indentUnit : 'px',
- indentClasses : null
- });
-
-/**
- * Size of each indentation step
- * @type Number
- * @default 40
- * @example
- * config.indentOffset = 4;
- */
-
- /**
- * Unit for the indentation style
- * @type String
- * @default 'px'
- * @example
- * config.indentUnit = 'em';
- */
-
- /**
- * List of classes to use for indenting the contents. If it's null, no classes will be used
- * and instead the {@link #indentUnit} and {@link #indentOffset} properties will be used.
- * @type Array
- * default null
- * @example
- * // Use the classes 'Indent1', 'Indent2', 'Indent3'
- * config.indentClasses = ['Indent1', 'Indent2', 'Indent3'];
- */
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @file Increse and decrease indent commands. + */ + +(function() +{ + var listNodeNames = { ol : 1, ul : 1 }; + + var isNotWhitespaces = CKEDITOR.dom.walker.whitespaces( true ), + isNotBookmark = CKEDITOR.dom.walker.bookmark( false, true ); + + function setState( editor, state ) + { + editor.getCommand( this.name ).setState( state ); + } + + function onSelectionChange( evt ) + { + var editor = evt.editor; + + var elementPath = evt.data.path, + list = elementPath && elementPath.contains( listNodeNames ); + + if ( list ) + return setState.call( this, editor, CKEDITOR.TRISTATE_OFF ); + + if ( !this.useIndentClasses && this.name == 'indent' ) + return setState.call( this, editor, CKEDITOR.TRISTATE_OFF ); + + var path = evt.data.path, + firstBlock = path.block || path.blockLimit; + if ( !firstBlock ) + return setState.call( this, editor, CKEDITOR.TRISTATE_DISABLED ); + + if ( this.useIndentClasses ) + { + var indentClass = firstBlock.$.className.match( this.classNameRegex ), + indentStep = 0; + if ( indentClass ) + { + indentClass = indentClass[1]; + indentStep = this.indentClassMap[ indentClass ]; + } + if ( ( this.name == 'outdent' && !indentStep ) || + ( this.name == 'indent' && indentStep == editor.config.indentClasses.length ) ) + return setState.call( this, editor, CKEDITOR.TRISTATE_DISABLED ); + return setState.call( this, editor, CKEDITOR.TRISTATE_OFF ); + } + else + { + var indent = parseInt( firstBlock.getStyle( this.indentCssProperty ), 10 ); + if ( isNaN( indent ) ) + indent = 0; + if ( indent <= 0 ) + return setState.call( this, editor, CKEDITOR.TRISTATE_DISABLED ); + return setState.call( this, editor, CKEDITOR.TRISTATE_OFF ); + } + } + + function indentList( editor, range, listNode ) + { + // Our starting and ending points of the range might be inside some blocks under a list item... + // So before playing with the iterator, we need to expand the block to include the list items. + var startContainer = range.startContainer, + endContainer = range.endContainer; + while ( startContainer && !startContainer.getParent().equals( listNode ) ) + startContainer = startContainer.getParent(); + while ( endContainer && !endContainer.getParent().equals( listNode ) ) + endContainer = endContainer.getParent(); + + if ( !startContainer || !endContainer ) + return; + + // Now we can iterate over the individual items on the same tree depth. + var block = startContainer, + itemsToMove = [], + stopFlag = false; + while ( !stopFlag ) + { + if ( block.equals( endContainer ) ) + stopFlag = true; + itemsToMove.push( block ); + block = block.getNext(); + } + if ( itemsToMove.length < 1 ) + return; + + // Do indent or outdent operations on the array model of the list, not the + // list's DOM tree itself. The array model demands that it knows as much as + // possible about the surrounding lists, we need to feed it the further + // ancestor node that is still a list. + var listParents = listNode.getParents( true ); + for ( var i = 0 ; i < listParents.length ; i++ ) + { + if ( listParents[i].getName && listNodeNames[ listParents[i].getName() ] ) + { + listNode = listParents[i]; + break; + } + } + var indentOffset = this.name == 'indent' ? 1 : -1, + startItem = itemsToMove[0], + lastItem = itemsToMove[ itemsToMove.length - 1 ], + database = {}; + + // Convert the list DOM tree into a one dimensional array. + var listArray = CKEDITOR.plugins.list.listToArray( listNode, database ); + + // Apply indenting or outdenting on the array. + var baseIndent = listArray[ lastItem.getCustomData( 'listarray_index' ) ].indent; + for ( i = startItem.getCustomData( 'listarray_index' ); i <= lastItem.getCustomData( 'listarray_index' ); i++ ) + { + listArray[ i ].indent += indentOffset; + // Make sure the newly created sublist get a brand-new element of the same type. (#5372) + var listRoot = listArray[ i ].parent; + listArray[ i ].parent = new CKEDITOR.dom.element( listRoot.getName(), listRoot.getDocument() ); + } + + for ( i = lastItem.getCustomData( 'listarray_index' ) + 1 ; + i < listArray.length && listArray[i].indent > baseIndent ; i++ ) + listArray[i].indent += indentOffset; + + // Convert the array back to a DOM forest (yes we might have a few subtrees now). + // And replace the old list with the new forest. + var newList = CKEDITOR.plugins.list.arrayToList( listArray, database, null, editor.config.enterMode, 0 ); + + // Avoid nested <li> after outdent even they're visually same, + // recording them for later refactoring.(#3982) + if ( this.name == 'outdent' ) + { + var parentLiElement; + if ( ( parentLiElement = listNode.getParent() ) && parentLiElement.is( 'li' ) ) + { + var children = newList.listNode.getChildren(), + pendingLis = [], + count = children.count(), + child; + + for ( i = count - 1 ; i >= 0 ; i-- ) + { + if ( ( child = children.getItem( i ) ) && child.is && child.is( 'li' ) ) + pendingLis.push( child ); + } + } + } + + if ( newList ) + newList.listNode.replace( listNode ); + + // Move the nested <li> to be appeared after the parent. + if ( pendingLis && pendingLis.length ) + { + for ( i = 0; i < pendingLis.length ; i++ ) + { + var li = pendingLis[ i ], + followingList = li; + + // Nest preceding <ul>/<ol> inside current <li> if any. + while ( ( followingList = followingList.getNext() ) && + followingList.is && + followingList.getName() in listNodeNames ) + { + // IE requires a filler NBSP for nested list inside empty list item, + // otherwise the list item will be inaccessiable. (#4476) + if ( CKEDITOR.env.ie && !li.getFirst( function( node ){ return isNotWhitespaces( node ) && isNotBookmark( node ); } ) ) + li.append( range.document.createText( '\u00a0' ) ); + + li.append( followingList ); + } + + li.insertAfter( parentLiElement ); + } + } + + // Clean up the markers. + CKEDITOR.dom.element.clearAllMarkers( database ); + } + + function indentBlock( editor, range ) + { + var iterator = range.createIterator(), + enterMode = editor.config.enterMode; + iterator.enforceRealBlocks = true; + iterator.enlargeBr = enterMode != CKEDITOR.ENTER_BR; + var block; + while ( ( block = iterator.getNextParagraph() ) ) + indentElement.call( this, editor, block ); + } + + function indentElement( editor, element ) + { + if ( this.useIndentClasses ) + { + // Transform current class name to indent step index. + var indentClass = element.$.className.match( this.classNameRegex ), + indentStep = 0; + if ( indentClass ) + { + indentClass = indentClass[1]; + indentStep = this.indentClassMap[ indentClass ]; + } + + // Operate on indent step index, transform indent step index back to class + // name. + if ( this.name == 'outdent' ) + indentStep--; + else + indentStep++; + + if ( indentStep < 0 ) + return false; + + indentStep = Math.min( indentStep, editor.config.indentClasses.length ); + indentStep = Math.max( indentStep, 0 ); + var className = CKEDITOR.tools.ltrim( element.$.className.replace( this.classNameRegex, '' ) ); + if ( indentStep < 1 ) + element.$.className = className; + else + element.addClass( editor.config.indentClasses[ indentStep - 1 ] ); + } + else + { + var currentOffset = parseInt( element.getStyle( this.indentCssProperty ), 10 ); + if ( isNaN( currentOffset ) ) + currentOffset = 0; + currentOffset += ( this.name == 'indent' ? 1 : -1 ) * editor.config.indentOffset; + + if ( currentOffset < 0 ) + return false; + + currentOffset = Math.max( currentOffset, 0 ); + currentOffset = Math.ceil( currentOffset / editor.config.indentOffset ) * editor.config.indentOffset; + element.setStyle( this.indentCssProperty, currentOffset ? currentOffset + editor.config.indentUnit : '' ); + if ( element.getAttribute( 'style' ) === '' ) + element.removeAttribute( 'style' ); + } + + return true; + } + + function indentCommand( editor, name ) + { + this.name = name; + this.useIndentClasses = editor.config.indentClasses && editor.config.indentClasses.length > 0; + if ( this.useIndentClasses ) + { + this.classNameRegex = new RegExp( '(?:^|\\s+)(' + editor.config.indentClasses.join( '|' ) + ')(?=$|\\s)' ); + this.indentClassMap = {}; + for ( var i = 0 ; i < editor.config.indentClasses.length ; i++ ) + this.indentClassMap[ editor.config.indentClasses[i] ] = i + 1; + } + else + this.indentCssProperty = editor.config.contentsLangDirection == 'ltr' ? 'margin-left' : 'margin-right'; + this.startDisabled = name == 'outdent'; + } + + function isListItem( node ) + { + return node.type = CKEDITOR.NODE_ELEMENT && node.is( 'li' ); + } + + indentCommand.prototype = { + exec : function( editor ) + { + var selection = editor.getSelection(), + range = selection && selection.getRanges()[0]; + + var startContainer = range.startContainer, + endContainer = range.endContainer, + rangeRoot = range.getCommonAncestor(), + nearestListBlock = rangeRoot; + + while ( nearestListBlock && !( nearestListBlock.type == CKEDITOR.NODE_ELEMENT && + listNodeNames[ nearestListBlock.getName() ] ) ) + nearestListBlock = nearestListBlock.getParent(); + + // Avoid selection anchors under list root. + // <ul>[<li>...</li>]</ul> => <ul><li>[...]</li></ul> + if ( nearestListBlock && startContainer.type == CKEDITOR.NODE_ELEMENT + && startContainer.getName() in listNodeNames ) + { + var walker = new CKEDITOR.dom.walker( range ); + walker.evaluator = isListItem; + range.startContainer = walker.next(); + } + + if ( nearestListBlock && endContainer.type == CKEDITOR.NODE_ELEMENT + && endContainer.getName() in listNodeNames ) + { + walker = new CKEDITOR.dom.walker( range ); + walker.evaluator = isListItem; + range.endContainer = walker.previous(); + } + + var bookmarks = selection.createBookmarks( true ); + + if ( nearestListBlock ) + { + var firstListItem = nearestListBlock.getFirst( function( node ) + { + return node.type == CKEDITOR.NODE_ELEMENT && node.is( 'li' ); + }), + rangeStart = range.startContainer, + indentWholeList = firstListItem.equals( rangeStart ) || firstListItem.contains( rangeStart ); + + // Indent the entire list if cursor is inside the first list item. (#3893) + if ( !( indentWholeList && indentElement.call( this, editor, nearestListBlock ) ) ) + indentList.call( this, editor, range, nearestListBlock ); + } + else + indentBlock.call( this, editor, range ); + + editor.focus(); + editor.forceNextSelectionCheck(); + selection.selectBookmarks( bookmarks ); + } + }; + + CKEDITOR.plugins.add( 'indent', + { + init : function( editor ) + { + // Register commands. + var indent = new indentCommand( editor, 'indent' ), + outdent = new indentCommand( editor, 'outdent' ); + editor.addCommand( 'indent', indent ); + editor.addCommand( 'outdent', outdent ); + + // Register the toolbar buttons. + editor.ui.addButton( 'Indent', + { + label : editor.lang.indent, + command : 'indent' + }); + editor.ui.addButton( 'Outdent', + { + label : editor.lang.outdent, + command : 'outdent' + }); + + // Register the state changing handlers. + editor.on( 'selectionChange', CKEDITOR.tools.bind( onSelectionChange, indent ) ); + editor.on( 'selectionChange', CKEDITOR.tools.bind( onSelectionChange, outdent ) ); + + // [IE6/7] Raw lists are using margin instead of padding for visual indentation in wysiwyg mode. (#3893) + if ( CKEDITOR.env.ie6Compat || CKEDITOR.env.ie7Compat ) + { + editor.addCss( + "ul,ol" + + "{" + + " margin-left: 0px;" + + " padding-left: 40px;" + + "}" ); + } + }, + + requires : [ 'domiterator', 'list' ] + } ); +})(); + +CKEDITOR.tools.extend( CKEDITOR.config, + { + indentOffset : 40, + indentUnit : 'px', + indentClasses : null + }); + +/** + * Size of each indentation step + * @type Number + * @example + * config.indentOffset = 40; + */ + + /** + * Unit for the indentation style + * @type String + * @example + * config.indentUnit = 'px'; + */ + + /** + * List of classes to use for indenting the contents. + * @type Array + * @example + * // Don't use classes for indenting. (this is the default value) + * config.indentClasses = null; + * @example + * // Use the classes 'Indent1', 'Indent2', 'Indent3' + * config.indentClasses = ['Indent1', 'Indent2', 'Indent3']; + */ + +/** + * Size of each indentation step + * @type Number + * @default 40 + * @example + * config.indentOffset = 4; + */ + + /** + * Unit for the indentation style + * @type String + * @default 'px' + * @example + * config.indentUnit = 'em'; + */ + + /** + * List of classes to use for indenting the contents. If it's null, no classes will be used + * and instead the {@link #indentUnit} and {@link #indentOffset} properties will be used. + * @type Array + * default null + * @example + * // Use the classes 'Indent1', 'Indent2', 'Indent3' + * config.indentClasses = ['Indent1', 'Indent2', 'Indent3']; + */ diff --git a/_source/plugins/link/dialogs/link.js b/_source/plugins/link/dialogs/link.js index c7388ca..696183e 100644 --- a/_source/plugins/link/dialogs/link.js +++ b/_source/plugins/link/dialogs/link.js @@ -1,1421 +1,1413 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-CKEDITOR.dialog.add( 'link', function( editor )
-{
- // Handles the event when the "Target" selection box is changed.
- var targetChanged = function()
- {
- var dialog = this.getDialog(),
- popupFeatures = dialog.getContentElement( 'target', 'popupFeatures' ),
- targetName = dialog.getContentElement( 'target', 'linkTargetName' ),
- value = this.getValue();
-
- if ( !popupFeatures || !targetName )
- return;
-
- popupFeatures = popupFeatures.getElement();
- popupFeatures.hide();
- targetName.setValue( '' );
-
- switch ( value )
- {
- case 'frame' :
- targetName.setLabel( editor.lang.link.targetFrameName );
- targetName.getElement().show();
- break;
- case 'popup' :
- popupFeatures.show();
- targetName.setLabel( editor.lang.link.targetPopupName );
- targetName.getElement().show();
- break;
- default :
- targetName.setValue( value );
- targetName.getElement().hide();
- break;
- }
-
- };
-
- // Handles the event when the "Type" selection box is changed.
- var linkTypeChanged = function()
- {
- var dialog = this.getDialog(),
- partIds = [ 'urlOptions', 'anchorOptions', 'emailOptions' ],
- typeValue = this.getValue(),
- uploadTab = dialog.definition.getContents( 'upload' ),
- uploadInitiallyHidden = uploadTab && uploadTab.hidden;
-
- if ( typeValue == 'url' )
- {
- if ( editor.config.linkShowTargetTab )
- dialog.showPage( 'target' );
- if ( !uploadInitiallyHidden )
- dialog.showPage( 'upload' );
- }
- else
- {
- dialog.hidePage( 'target' );
- if ( !uploadInitiallyHidden )
- dialog.hidePage( 'upload' );
- }
-
- for ( var i = 0 ; i < partIds.length ; i++ )
- {
- var element = dialog.getContentElement( 'info', partIds[i] );
- if ( !element )
- continue;
-
- element = element.getElement().getParent().getParent();
- if ( partIds[i] == typeValue + 'Options' )
- element.show();
- else
- element.hide();
- }
- };
-
- // Loads the parameters in a selected link to the link dialog fields.
- var javascriptProtocolRegex = /^javascript:/,
- emailRegex = /^mailto:([^?]+)(?:\?(.+))?$/,
- emailSubjectRegex = /subject=([^;?:@&=$,\/]*)/,
- emailBodyRegex = /body=([^;?:@&=$,\/]*)/,
- anchorRegex = /^#(.*)$/,
- urlRegex = /^((?: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*/;
- var popupFeaturesRegex = /(?:^|,)([^=]+)=(\d+|yes|no)/gi;
-
- var parseLink = function( editor, element )
- {
- var href = element ? ( element.getAttribute( '_cke_saved_href' ) || element.getAttribute( 'href' ) ) : '',
- javascriptMatch,
- emailMatch,
- anchorMatch,
- urlMatch,
- retval = {};
-
- if ( ( javascriptMatch = href.match( javascriptProtocolRegex ) ) )
- {
- if ( emailProtection == 'encode' )
- {
- href = href.replace( encodedEmailLinkRegex,
- function ( match, protectedAddress, rest )
- {
- return 'mailto:' +
- String.fromCharCode.apply( String, protectedAddress.split( ',' ) ) +
- ( rest && unescapeSingleQuote( rest ) );
- });
- }
- // 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( '@' );
- }
- } );
- }
- }
-
- if ( !retval.type )
- {
- if ( ( anchorMatch = href.match( anchorRegex ) ) )
- {
- retval.type = 'anchor';
- retval.anchor = {};
- retval.anchor.name = retval.anchor.id = anchorMatch[1];
- }
- // Protected email link as encoded string.
- else if ( ( emailMatch = href.match( emailRegex ) ) )
- {
- 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 ] ) );
- }
- // 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];
- }
- else
- retval.type = 'url';
- }
-
- // Load target and popup settings.
- if ( element )
- {
- var target = element.getAttribute( 'target' );
- retval.target = {};
- retval.adv = {};
-
- // IE BUG: target attribute is an empty string instead of null in IE if it's not set.
- if ( !target )
- {
- var onclick = element.getAttribute( '_cke_pa_onclick' ) || element.getAttribute( 'onclick' ),
- onclickMatch = onclick && onclick.match( popupRegex );
- if ( onclickMatch )
- {
- retval.target.type = 'popup';
- retval.target.name = onclickMatch[1];
-
- var featureMatch;
- while ( ( featureMatch = popupFeaturesRegex.exec( onclickMatch[2] ) ) )
- {
- if ( featureMatch[2] == 'yes' || featureMatch[2] == '1' )
- retval.target[ featureMatch[1] ] = true;
- else if ( isFinite( featureMatch[2] ) )
- retval.target[ featureMatch[1] ] = featureMatch[2];
- }
- }
- }
- else
- {
- var targetMatch = target.match( selectableTargets );
- if ( targetMatch )
- retval.target.type = retval.target.name = target;
- else
- {
- retval.target.type = 'frame';
- retval.target.name = target;
- }
- }
-
- var me = this;
- var advAttr = function( inputName, attrName )
- {
- var value = element.getAttribute( attrName );
- if ( value !== null )
- retval.adv[ inputName ] = value || '';
- };
- advAttr( 'advId', 'id' );
- advAttr( 'advLangDir', 'dir' );
- advAttr( 'advAccessKey', 'accessKey' );
- advAttr( 'advName', 'name' );
- advAttr( 'advLangCode', 'lang' );
- advAttr( 'advTabIndex', 'tabindex' );
- advAttr( 'advTitle', 'title' );
- advAttr( 'advContentType', 'type' );
- advAttr( 'advCSSClasses', 'class' );
- advAttr( 'advCharset', 'charset' );
- advAttr( 'advStyles', 'style' );
- }
-
- // Find out whether we have any anchors in the editor.
- // Get all IMG elements in CK document.
- var elements = editor.document.getElementsByTag( 'img' ),
- realAnchors = new CKEDITOR.dom.nodeList( editor.document.$.anchors ),
- anchors = retval.anchors = [];
-
- for ( var i = 0; i < elements.count() ; i++ )
- {
- var item = elements.getItem( i );
- if ( item.getAttribute( '_cke_realelement' ) && item.getAttribute( '_cke_real_element_type' ) == 'anchor' )
- {
- anchors.push( editor.restoreRealElement( item ) );
- }
- }
-
- for ( i = 0 ; i < realAnchors.count() ; i++ )
- anchors.push( realAnchors.getItem( i ) );
-
- for ( i = 0 ; i < anchors.length ; i++ )
- {
- item = anchors[ i ];
- anchors[ i ] = { name : item.getAttribute( 'name' ), id : item.getAttribute( 'id' ) };
- }
-
- // Record down the selected element in the dialog.
- this._.selectedElement = element;
-
- return retval;
- };
-
- var setupParams = function( page, data )
- {
- if ( data[page] )
- this.setValue( data[page][this.id] || '' );
- };
-
- var setupPopupParams = function( data )
- {
- return setupParams.call( this, 'target', data );
- };
-
- var setupAdvParams = function( data )
- {
- return setupParams.call( this, 'adv', data );
- };
-
- var commitParams = function( page, data )
- {
- if ( !data[page] )
- data[page] = {};
-
- data[page][this.id] = this.getValue() || '';
- };
-
- var commitPopupParams = function( data )
- {
- return commitParams.call( this, 'target', data );
- };
-
- var commitAdvParams = function( data )
- {
- 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,
- minHeight : 230,
- contents : [
- {
- id : 'info',
- label : editor.lang.link.info,
- title : editor.lang.link.info,
- elements :
- [
- {
- id : 'linkType',
- type : 'select',
- label : editor.lang.link.type,
- 'default' : 'url',
- items :
- [
- [ editor.lang.link.toUrl, 'url' ],
- [ editor.lang.link.toAnchor, 'anchor' ],
- [ editor.lang.link.toEmail, 'email' ]
- ],
- onChange : linkTypeChanged,
- setup : function( data )
- {
- if ( data.type )
- this.setValue( data.type );
- },
- commit : function( data )
- {
- data.type = this.getValue();
- }
- },
- {
- type : 'vbox',
- id : 'urlOptions',
- children :
- [
- {
- type : 'hbox',
- widths : [ '25%', '75%' ],
- children :
- [
- {
- id : 'protocol',
- type : 'select',
- label : editor.lang.common.protocol,
- 'default' : 'http://',
- style : 'width : 100%;',
- items :
- [
- [ 'http://' ],
- [ 'https://' ],
- [ 'ftp://' ],
- [ 'news://' ],
- [ '<other>', '' ]
- ],
- setup : function( data )
- {
- if ( data.url )
- this.setValue( data.url.protocol || '' );
- },
- commit : function( data )
- {
- if ( !data.url )
- data.url = {};
-
- data.url.protocol = this.getValue();
- }
- },
- {
- type : 'text',
- id : 'url',
- label : editor.lang.common.url,
- required: true,
- onLoad : function ()
- {
- this.allowOnChange = true;
- },
- onKeyUp : function()
- {
- this.allowOnChange = false;
- var protocolCmb = this.getDialog().getContentElement( 'info', 'protocol' ),
- url = this.getValue(),
- urlOnChangeProtocol = /^(http|https|ftp|news):\/\/(?=.)/gi,
- urlOnChangeTestOther = /^((javascript:)|[#\/\.])/gi;
-
- var protocol = urlOnChangeProtocol.exec( url );
- if ( protocol )
- {
- this.setValue( url.substr( protocol[ 0 ].length ) );
- protocolCmb.setValue( protocol[ 0 ].toLowerCase() );
- }
- else if ( urlOnChangeTestOther.test( url ) )
- protocolCmb.setValue( '' );
-
- this.allowOnChange = true;
- },
- onChange : function()
- {
- if ( this.allowOnChange ) // Dont't call on dialog load.
- this.onKeyUp();
- },
- validate : function()
- {
- var dialog = this.getDialog();
-
- if ( dialog.getContentElement( 'info', 'linkType' ) &&
- dialog.getValueOf( 'info', 'linkType' ) != 'url' )
- return true;
-
- if ( this.getDialog().fakeObj ) // Edit Anchor.
- return true;
-
- var func = CKEDITOR.dialog.validate.notEmpty( editor.lang.link.noUrl );
- return func.apply( this );
- },
- setup : function( data )
- {
- this.allowOnChange = false;
- if ( data.url )
- this.setValue( data.url.url );
- this.allowOnChange = true;
-
- },
- commit : function( data )
- {
- // IE will not trigger the onChange event if the mouse has been used
- // to carry all the operations #4724
- this.onChange();
-
- if ( !data.url )
- data.url = {};
-
- data.url.url = this.getValue();
- this.allowOnChange = false;
- }
- }
- ],
- setup : function( data )
- {
- if ( !this.getDialog().getContentElement( 'info', 'linkType' ) )
- this.getElement().show();
- }
- },
- {
- type : 'button',
- id : 'browse',
- hidden : 'true',
- filebrowser : 'info:url',
- label : editor.lang.common.browseServer
- }
- ]
- },
- {
- type : 'vbox',
- id : 'anchorOptions',
- width : 260,
- align : 'center',
- padding : 0,
- children :
- [
- {
- type : 'fieldset',
- id : 'selectAnchorText',
- label : editor.lang.link.selectAnchor,
- setup : function( data )
- {
- if ( data.anchors.length > 0 )
- this.getElement().show();
- else
- this.getElement().hide();
- },
- children :
- [
- {
- type : 'hbox',
- id : 'selectAnchor',
- children :
- [
- {
- type : 'select',
- id : 'anchorName',
- 'default' : '',
- label : editor.lang.link.anchorName,
- style : 'width: 100%;',
- items :
- [
- [ '' ]
- ],
- setup : function( data )
- {
- this.clear();
- this.add( '' );
- for ( var i = 0 ; i < data.anchors.length ; i++ )
- {
- if ( data.anchors[i].name )
- this.add( data.anchors[i].name );
- }
-
- if ( data.anchor )
- this.setValue( data.anchor.name );
-
- var linkType = this.getDialog().getContentElement( 'info', 'linkType' );
- if ( linkType && linkType.getValue() == 'email' )
- this.focus();
- },
- commit : function( data )
- {
- if ( !data.anchor )
- data.anchor = {};
-
- data.anchor.name = this.getValue();
- }
- },
- {
- type : 'select',
- id : 'anchorId',
- 'default' : '',
- label : editor.lang.link.anchorId,
- style : 'width: 100%;',
- items :
- [
- [ '' ]
- ],
- setup : function( data )
- {
- this.clear();
- this.add( '' );
- for ( var i = 0 ; i < data.anchors.length ; i++ )
- {
- if ( data.anchors[i].id )
- this.add( data.anchors[i].id );
- }
-
- if ( data.anchor )
- this.setValue( data.anchor.id );
- },
- commit : function( data )
- {
- if ( !data.anchor )
- data.anchor = {};
-
- data.anchor.id = this.getValue();
- }
- }
- ],
- setup : function( data )
- {
- if ( data.anchors.length > 0 )
- this.getElement().show();
- else
- this.getElement().hide();
- }
- }
- ]
- },
- {
- type : 'html',
- id : 'noAnchors',
- style : 'text-align: center;',
- html : '<div role="label" tabIndex="-1">' + CKEDITOR.tools.htmlEncode( editor.lang.link.noAnchors ) + '</div>',
- // Focus the first element defined in above html.
- focus : true,
- setup : function( data )
- {
- if ( data.anchors.length < 1 )
- this.getElement().show();
- else
- this.getElement().hide();
- }
- }
- ],
- setup : function( data )
- {
- if ( !this.getDialog().getContentElement( 'info', 'linkType' ) )
- this.getElement().hide();
- }
- },
- {
- type : 'vbox',
- id : 'emailOptions',
- padding : 1,
- children :
- [
- {
- type : 'text',
- id : 'emailAddress',
- label : editor.lang.link.emailAddress,
- required : true,
- validate : function()
- {
- var dialog = this.getDialog();
-
- if ( !dialog.getContentElement( 'info', 'linkType' ) ||
- dialog.getValueOf( 'info', 'linkType' ) != 'email' )
- return true;
-
- var func = CKEDITOR.dialog.validate.notEmpty( editor.lang.link.noEmail );
- return func.apply( this );
- },
- setup : function( data )
- {
- if ( data.email )
- this.setValue( data.email.address );
-
- var linkType = this.getDialog().getContentElement( 'info', 'linkType' );
- if ( linkType && linkType.getValue() == 'email' )
- this.select();
- },
- commit : function( data )
- {
- if ( !data.email )
- data.email = {};
-
- data.email.address = this.getValue();
- }
- },
- {
- type : 'text',
- id : 'emailSubject',
- label : editor.lang.link.emailSubject,
- setup : function( data )
- {
- if ( data.email )
- this.setValue( data.email.subject );
- },
- commit : function( data )
- {
- if ( !data.email )
- data.email = {};
-
- data.email.subject = this.getValue();
- }
- },
- {
- type : 'textarea',
- id : 'emailBody',
- label : editor.lang.link.emailBody,
- rows : 3,
- 'default' : '',
- setup : function( data )
- {
- if ( data.email )
- this.setValue( data.email.body );
- },
- commit : function( data )
- {
- if ( !data.email )
- data.email = {};
-
- data.email.body = this.getValue();
- }
- }
- ],
- setup : function( data )
- {
- if ( !this.getDialog().getContentElement( 'info', 'linkType' ) )
- this.getElement().hide();
- }
- }
- ]
- },
- {
- id : 'target',
- label : editor.lang.link.target,
- title : editor.lang.link.target,
- elements :
- [
- {
- type : 'hbox',
- widths : [ '50%', '50%' ],
- children :
- [
- {
- type : 'select',
- id : 'linkTargetType',
- label : editor.lang.common.target,
- 'default' : 'notSet',
- style : 'width : 100%;',
- 'items' :
- [
- [ editor.lang.common.notSet, 'notSet' ],
- [ editor.lang.link.targetFrame, 'frame' ],
- [ editor.lang.link.targetPopup, 'popup' ],
- [ editor.lang.common.targetNew, '_blank' ],
- [ editor.lang.common.targetTop, '_top' ],
- [ editor.lang.common.targetSelf, '_self' ],
- [ editor.lang.common.targetParent, '_parent' ]
- ],
- onChange : targetChanged,
- setup : function( data )
- {
- if ( data.target )
- this.setValue( data.target.type );
- },
- commit : function( data )
- {
- if ( !data.target )
- data.target = {};
-
- data.target.type = this.getValue();
- }
- },
- {
- type : 'text',
- id : 'linkTargetName',
- label : editor.lang.link.targetFrameName,
- 'default' : '',
- setup : function( data )
- {
- if ( data.target )
- this.setValue( data.target.name );
- },
- commit : function( data )
- {
- if ( !data.target )
- data.target = {};
-
- data.target.name = this.getValue();
- }
- }
- ]
- },
- {
- type : 'vbox',
- width : 260,
- align : 'center',
- padding : 2,
- id : 'popupFeatures',
- children :
- [
- {
- type : 'fieldset',
- label : editor.lang.link.popupFeatures,
- children :
- [
- {
- type : 'hbox',
- children :
- [
- {
- type : 'checkbox',
- id : 'resizable',
- label : editor.lang.link.popupResizable,
- setup : setupPopupParams,
- commit : commitPopupParams
- },
- {
- type : 'checkbox',
- id : 'status',
- label : editor.lang.link.popupStatusBar,
- setup : setupPopupParams,
- commit : commitPopupParams
-
- }
- ]
- },
- {
- type : 'hbox',
- children :
- [
- {
- type : 'checkbox',
- id : 'location',
- label : editor.lang.link.popupLocationBar,
- setup : setupPopupParams,
- commit : commitPopupParams
-
- },
- {
- type : 'checkbox',
- id : 'toolbar',
- label : editor.lang.link.popupToolbar,
- setup : setupPopupParams,
- commit : commitPopupParams
-
- }
- ]
- },
- {
- type : 'hbox',
- children :
- [
- {
- type : 'checkbox',
- id : 'menubar',
- label : editor.lang.link.popupMenuBar,
- setup : setupPopupParams,
- commit : commitPopupParams
-
- },
- {
- type : 'checkbox',
- id : 'fullscreen',
- label : editor.lang.link.popupFullScreen,
- setup : setupPopupParams,
- commit : commitPopupParams
-
- }
- ]
- },
- {
- type : 'hbox',
- children :
- [
- {
- type : 'checkbox',
- id : 'scrollbars',
- label : editor.lang.link.popupScrollBars,
- setup : setupPopupParams,
- commit : commitPopupParams
-
- },
- {
- type : 'checkbox',
- id : 'dependent',
- label : editor.lang.link.popupDependent,
- setup : setupPopupParams,
- commit : commitPopupParams
-
- }
- ]
- },
- {
- type : 'hbox',
- children :
- [
- {
- type : 'text',
- widths : [ '30%', '70%' ],
- labelLayout : 'horizontal',
- label : editor.lang.link.popupWidth,
- id : 'width',
- setup : setupPopupParams,
- commit : commitPopupParams
-
- },
- {
- type : 'text',
- labelLayout : 'horizontal',
- widths : [ '55%', '45%' ],
- label : editor.lang.link.popupLeft,
- id : 'left',
- setup : setupPopupParams,
- commit : commitPopupParams
-
- }
- ]
- },
- {
- type : 'hbox',
- children :
- [
- {
- type : 'text',
- labelLayout : 'horizontal',
- widths : [ '30%', '70%' ],
- label : editor.lang.link.popupHeight,
- id : 'height',
- setup : setupPopupParams,
- commit : commitPopupParams
-
- },
- {
- type : 'text',
- labelLayout : 'horizontal',
- label : editor.lang.link.popupTop,
- widths : [ '55%', '45%' ],
- id : 'top',
- setup : setupPopupParams,
- commit : commitPopupParams
-
- }
- ]
- }
- ]
- }
- ]
- }
- ]
- },
- {
- id : 'upload',
- label : editor.lang.link.upload,
- title : editor.lang.link.upload,
- hidden : true,
- filebrowser : 'uploadButton',
- elements :
- [
- {
- type : 'file',
- id : 'upload',
- label : editor.lang.common.upload,
- style: 'height:40px',
- size : 29
- },
- {
- type : 'fileButton',
- id : 'uploadButton',
- label : editor.lang.common.uploadSubmit,
- filebrowser : 'info:url',
- 'for' : [ 'upload', 'upload' ]
- }
- ]
- },
- {
- id : 'advanced',
- label : editor.lang.link.advanced,
- title : editor.lang.link.advanced,
- elements :
- [
- {
- type : 'vbox',
- padding : 1,
- children :
- [
- {
- type : 'hbox',
- widths : [ '45%', '35%', '20%' ],
- children :
- [
- {
- type : 'text',
- id : 'advId',
- label : editor.lang.link.id,
- setup : setupAdvParams,
- commit : commitAdvParams
- },
- {
- type : 'select',
- id : 'advLangDir',
- label : editor.lang.link.langDir,
- 'default' : '',
- style : 'width:110px',
- items :
- [
- [ editor.lang.common.notSet, '' ],
- [ editor.lang.link.langDirLTR, 'ltr' ],
- [ editor.lang.link.langDirRTL, 'rtl' ]
- ],
- setup : setupAdvParams,
- commit : commitAdvParams
- },
- {
- type : 'text',
- id : 'advAccessKey',
- width : '80px',
- label : editor.lang.link.acccessKey,
- maxLength : 1,
- setup : setupAdvParams,
- commit : commitAdvParams
-
- }
- ]
- },
- {
- type : 'hbox',
- widths : [ '45%', '35%', '20%' ],
- children :
- [
- {
- type : 'text',
- label : editor.lang.link.name,
- id : 'advName',
- setup : setupAdvParams,
- commit : commitAdvParams
-
- },
- {
- type : 'text',
- label : editor.lang.link.langCode,
- id : 'advLangCode',
- width : '110px',
- 'default' : '',
- setup : setupAdvParams,
- commit : commitAdvParams
-
- },
- {
- type : 'text',
- label : editor.lang.link.tabIndex,
- id : 'advTabIndex',
- width : '80px',
- maxLength : 5,
- setup : setupAdvParams,
- commit : commitAdvParams
-
- }
- ]
- }
- ]
- },
- {
- type : 'vbox',
- padding : 1,
- children :
- [
- {
- type : 'hbox',
- widths : [ '45%', '55%' ],
- children :
- [
- {
- type : 'text',
- label : editor.lang.link.advisoryTitle,
- 'default' : '',
- id : 'advTitle',
- setup : setupAdvParams,
- commit : commitAdvParams
-
- },
- {
- type : 'text',
- label : editor.lang.link.advisoryContentType,
- 'default' : '',
- id : 'advContentType',
- setup : setupAdvParams,
- commit : commitAdvParams
-
- }
- ]
- },
- {
- type : 'hbox',
- widths : [ '45%', '55%' ],
- children :
- [
- {
- type : 'text',
- label : editor.lang.link.cssClasses,
- 'default' : '',
- id : 'advCSSClasses',
- setup : setupAdvParams,
- commit : commitAdvParams
-
- },
- {
- type : 'text',
- label : editor.lang.link.charset,
- 'default' : '',
- id : 'advCharset',
- setup : setupAdvParams,
- commit : commitAdvParams
-
- }
- ]
- },
- {
- type : 'hbox',
- children :
- [
- {
- type : 'text',
- label : editor.lang.link.styles,
- 'default' : '',
- id : 'advStyles',
- setup : setupAdvParams,
- commit : commitAdvParams
-
- }
- ]
- }
- ]
- }
- ]
- }
- ],
- onShow : function()
- {
- this.fakeObj = false;
-
- var editor = this.getParentEditor(),
- selection = editor.getSelection(),
- ranges = selection.getRanges(),
- element = null,
- me = this;
- // Fill in all the relevant fields if there's already one link selected.
- if ( ranges.length == 1 )
- {
-
- var rangeRoot = ranges[0].getCommonAncestor( true );
- element = rangeRoot.getAscendant( 'a', true );
- if ( element && element.getAttribute( 'href' ) )
- {
- selection.selectElement( element );
- }
- else if ( ( element = rangeRoot.getAscendant( 'img', true ) ) &&
- element.getAttribute( '_cke_real_element_type' ) &&
- element.getAttribute( '_cke_real_element_type' ) == 'anchor' )
- {
- this.fakeObj = element;
- element = editor.restoreRealElement( this.fakeObj );
- selection.selectElement( this.fakeObj );
- }
- else
- element = null;
- }
-
- this.setupContent( parseLink.apply( this, [ editor, element ] ) );
- },
- onOk : function()
- {
- var attributes = { href : 'javascript:void(0)/*' + CKEDITOR.tools.getNextNumber() + '*/' },
- removeAttributes = [],
- data = { href : attributes.href },
- me = this,
- editor = this.getParentEditor();
-
- this.commitContent( data );
-
- // Compose the URL.
- switch ( data.type || 'url' )
- {
- case 'url':
- var protocol = ( data.url && data.url.protocol != undefined ) ? data.url.protocol : 'http://',
- url = ( data.url && data.url.url ) || '';
- attributes._cke_saved_href = ( url.indexOf( '/' ) === 0 ) ? url : protocol + url;
- break;
- case 'anchor':
- var name = ( data.anchor && data.anchor.name ),
- id = ( data.anchor && data.anchor.id );
- attributes._cke_saved_href = '#' + ( name || id || '' );
- break;
- case 'email':
-
- var linkHref,
- email = data.email,
- address = email.address;
-
- switch( emailProtection )
- {
- 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 = linkHref.join( '' );
- break;
- }
-
- // Popups and target.
- if ( data.target )
- {
- if ( data.target.type == 'popup' )
- {
- var onclickList = [ 'window.open(this.href, \'',
- data.target.name || '', '\', \'' ];
- var featureList = [ 'resizable', 'status', 'location', 'toolbar', 'menubar', 'fullscreen',
- 'scrollbars', 'dependent' ];
- var featureLength = featureList.length;
- var addFeature = function( featureName )
- {
- if ( data.target[ featureName ] )
- featureList.push( featureName + '=' + data.target[ featureName ] );
- };
-
- for ( var i = 0 ; i < featureLength ; i++ )
- featureList[i] = featureList[i] + ( data.target[ featureList[i] ] ? '=yes' : '=no' ) ;
- addFeature( 'width' );
- addFeature( 'left' );
- addFeature( 'height' );
- addFeature( 'top' );
-
- onclickList.push( featureList.join( ',' ), '\'); return false;' );
- 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' );
- }
- }
-
- // Advanced attributes.
- if ( data.adv )
- {
- var advAttr = function( inputName, attrName )
- {
- var value = data.adv[ inputName ];
- if ( value )
- attributes[attrName] = value;
- else
- removeAttributes.push( attrName );
- };
-
- if ( this._.selectedElement )
- advAttr( 'advId', 'id' );
- advAttr( 'advLangDir', 'dir' );
- advAttr( 'advAccessKey', 'accessKey' );
- advAttr( 'advName', 'name' );
- advAttr( 'advLangCode', 'lang' );
- advAttr( 'advTabIndex', 'tabindex' );
- advAttr( 'advTitle', 'title' );
- advAttr( 'advContentType', 'type' );
- advAttr( 'advCSSClasses', 'class' );
- advAttr( 'advCharset', 'charset' );
- advAttr( 'advStyles', 'style' );
- }
-
- if ( !this._.selectedElement )
- {
- // Create element if current selection is collapsed.
- var selection = editor.getSelection(),
- ranges = selection.getRanges();
- if ( ranges.length == 1 && ranges[0].collapsed )
- {
- var text = new CKEDITOR.dom.text( attributes._cke_saved_href, editor.document );
- ranges[0].insertNode( text );
- ranges[0].selectNodeContents( text );
- selection.selectRanges( ranges );
- }
-
- // Apply style.
- var style = new CKEDITOR.style( { element : 'a', attributes : attributes } );
- style.type = CKEDITOR.STYLE_INLINE; // need to override... dunno why.
- style.apply( editor.document );
-
- // Id. Apply only to the first link.
- if ( data.adv && data.adv.advId )
- {
- var links = this.getParentEditor().document.$.getElementsByTagName( 'a' );
- for ( i = 0 ; i < links.length ; i++ )
- {
- if ( links[i].href == attributes.href )
- {
- links[i].id = data.adv.advId;
- break;
- }
- }
- }
- }
- else
- {
- // We're only editing an existing link, so just overwrite the attributes.
- var element = this._.selectedElement,
- href = element.getAttribute( '_cke_saved_href' ),
- textView = element.getHtml();
-
- // IE BUG: Setting the name attribute to an existing link doesn't work.
- // Must re-create the link from weired syntax to workaround.
- if ( CKEDITOR.env.ie && attributes.name != element.getAttribute( 'name' ) )
- {
- var newElement = new CKEDITOR.dom.element( '<a name="' + CKEDITOR.tools.htmlEncode( attributes.name ) + '">',
- editor.document );
-
- selection = editor.getSelection();
-
- element.moveChildren( newElement );
- element.copyAttributes( newElement, { name : 1 } );
- newElement.replace( element );
- element = newElement;
-
- selection.selectElement( element );
- }
-
- element.setAttributes( attributes );
- element.removeAttributes( removeAttributes );
- // Update text view when user changes protocol #4612.
- if (href == textView)
- element.setHtml( attributes._cke_saved_href );
- // Make the element display as an anchor if a name has been set.
- if ( element.getAttribute( 'name' ) )
- element.addClass( 'cke_anchor' );
- else
- element.removeClass( 'cke_anchor' );
-
- if ( this.fakeObj )
- editor.createFakeElement( element, 'cke_anchor', 'anchor' ).replace( this.fakeObj );
-
- delete this._.selectedElement;
- }
- },
- onLoad : function()
- {
- if ( !editor.config.linkShowAdvancedTab )
- this.hidePage( 'advanced' ); //Hide Advanded tab.
-
- if ( !editor.config.linkShowTargetTab )
- this.hidePage( 'target' ); //Hide Target tab.
-
- },
- // Inital focus on 'url' field if link is of type URL.
- onFocus : function()
- {
- var linkType = this.getContentElement( 'info', 'linkType' ),
- urlField;
- if ( linkType && linkType.getValue( ) == 'url' )
- {
- urlField = this.getContentElement( 'info', 'url' );
- urlField.select();
- }
- }
- };
-});
-
-/**
- * The e-mail address anti-spam protection option. The protection will be
- * applied when creating or modifying e-mail links through the editor interface.<br>
- * Two methods of protection can be choosed:
- * <ol> <li>The e-mail parts (name, domain and any other query string) are
- * assembled into a function call pattern. Such function must be
- * provided by the developer in the pages that will use the contents.
- * <li>Only the e-mail address is obfuscated into a special string that
- * has no meaning for humans or spam bots, but which is properly
- * rendered and accepted by the browser.</li></ol>
- * Both approaches require JavaScript to be enabled.
- * @name CKEDITOR.config.emailProtection
- * @since 3.1
- * @type String
- * @default '' (empty string = disabled)
- * @example
- * // href="mailto:tester@ckeditor.com?subject=subject&body=body"
- * config.emailProtection = '';
- * @example
- * // 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 = 'encode';
- * @example
- * // href="javascript:mt('tester','ckeditor.com','subject','body')"
- * config.emailProtection = 'mt(NAME,DOMAIN,SUBJECT,BODY)';
- */
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.dialog.add( 'link', function( editor ) +{ + var plugin = CKEDITOR.plugins.link; + // Handles the event when the "Target" selection box is changed. + var targetChanged = function() + { + var dialog = this.getDialog(), + popupFeatures = dialog.getContentElement( 'target', 'popupFeatures' ), + targetName = dialog.getContentElement( 'target', 'linkTargetName' ), + value = this.getValue(); + + if ( !popupFeatures || !targetName ) + return; + + popupFeatures = popupFeatures.getElement(); + popupFeatures.hide(); + targetName.setValue( '' ); + + switch ( value ) + { + case 'frame' : + targetName.setLabel( editor.lang.link.targetFrameName ); + targetName.getElement().show(); + break; + case 'popup' : + popupFeatures.show(); + targetName.setLabel( editor.lang.link.targetPopupName ); + targetName.getElement().show(); + break; + default : + targetName.setValue( value ); + targetName.getElement().hide(); + break; + } + + }; + + // Handles the event when the "Type" selection box is changed. + var linkTypeChanged = function() + { + var dialog = this.getDialog(), + partIds = [ 'urlOptions', 'anchorOptions', 'emailOptions' ], + typeValue = this.getValue(), + uploadTab = dialog.definition.getContents( 'upload' ), + uploadInitiallyHidden = uploadTab && uploadTab.hidden; + + if ( typeValue == 'url' ) + { + if ( editor.config.linkShowTargetTab ) + dialog.showPage( 'target' ); + if ( !uploadInitiallyHidden ) + dialog.showPage( 'upload' ); + } + else + { + dialog.hidePage( 'target' ); + if ( !uploadInitiallyHidden ) + dialog.hidePage( 'upload' ); + } + + for ( var i = 0 ; i < partIds.length ; i++ ) + { + var element = dialog.getContentElement( 'info', partIds[i] ); + if ( !element ) + continue; + + element = element.getElement().getParent().getParent(); + if ( partIds[i] == typeValue + 'Options' ) + element.show(); + else + element.hide(); + } + }; + + // Loads the parameters in a selected link to the link dialog fields. + var javascriptProtocolRegex = /^javascript:/, + emailRegex = /^mailto:([^?]+)(?:\?(.+))?$/, + emailSubjectRegex = /subject=([^;?:@&=$,\/]*)/, + emailBodyRegex = /body=([^;?:@&=$,\/]*)/, + anchorRegex = /^#(.*)$/, + urlRegex = /^((?: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*/; + var popupFeaturesRegex = /(?:^|,)([^=]+)=(\d+|yes|no)/gi; + + var parseLink = function( editor, element ) + { + var href = ( element && ( element.getAttribute( '_cke_saved_href' ) || element.getAttribute( 'href' ) ) ) || '', + javascriptMatch, + emailMatch, + anchorMatch, + urlMatch, + retval = {}; + + if ( ( javascriptMatch = href.match( javascriptProtocolRegex ) ) ) + { + if ( emailProtection == 'encode' ) + { + href = href.replace( encodedEmailLinkRegex, + function ( match, protectedAddress, rest ) + { + return 'mailto:' + + String.fromCharCode.apply( String, protectedAddress.split( ',' ) ) + + ( rest && unescapeSingleQuote( rest ) ); + }); + } + // 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( '@' ); + } + } ); + } + } + + if ( !retval.type ) + { + if ( ( anchorMatch = href.match( anchorRegex ) ) ) + { + retval.type = 'anchor'; + retval.anchor = {}; + retval.anchor.name = retval.anchor.id = anchorMatch[1]; + } + // Protected email link as encoded string. + else if ( ( emailMatch = href.match( emailRegex ) ) ) + { + 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 ] ) ); + } + // 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]; + } + else + retval.type = 'url'; + } + + // Load target and popup settings. + if ( element ) + { + var target = element.getAttribute( 'target' ); + retval.target = {}; + retval.adv = {}; + + // IE BUG: target attribute is an empty string instead of null in IE if it's not set. + if ( !target ) + { + var onclick = element.getAttribute( '_cke_pa_onclick' ) || element.getAttribute( 'onclick' ), + onclickMatch = onclick && onclick.match( popupRegex ); + if ( onclickMatch ) + { + retval.target.type = 'popup'; + retval.target.name = onclickMatch[1]; + + var featureMatch; + while ( ( featureMatch = popupFeaturesRegex.exec( onclickMatch[2] ) ) ) + { + if ( featureMatch[2] == 'yes' || featureMatch[2] == '1' ) + retval.target[ featureMatch[1] ] = true; + else if ( isFinite( featureMatch[2] ) ) + retval.target[ featureMatch[1] ] = featureMatch[2]; + } + } + } + else + { + var targetMatch = target.match( selectableTargets ); + if ( targetMatch ) + retval.target.type = retval.target.name = target; + else + { + retval.target.type = 'frame'; + retval.target.name = target; + } + } + + var me = this; + var advAttr = function( inputName, attrName ) + { + var value = element.getAttribute( attrName ); + if ( value !== null ) + retval.adv[ inputName ] = value || ''; + }; + advAttr( 'advId', 'id' ); + advAttr( 'advLangDir', 'dir' ); + advAttr( 'advAccessKey', 'accessKey' ); + advAttr( 'advName', 'name' ); + advAttr( 'advLangCode', 'lang' ); + advAttr( 'advTabIndex', 'tabindex' ); + advAttr( 'advTitle', 'title' ); + advAttr( 'advContentType', 'type' ); + advAttr( 'advCSSClasses', 'class' ); + advAttr( 'advCharset', 'charset' ); + advAttr( 'advStyles', 'style' ); + } + + // Find out whether we have any anchors in the editor. + // Get all IMG elements in CK document. + var elements = editor.document.getElementsByTag( 'img' ), + realAnchors = new CKEDITOR.dom.nodeList( editor.document.$.anchors ), + anchors = retval.anchors = []; + + for ( var i = 0; i < elements.count() ; i++ ) + { + var item = elements.getItem( i ); + if ( item.getAttribute( '_cke_realelement' ) && item.getAttribute( '_cke_real_element_type' ) == 'anchor' ) + { + anchors.push( editor.restoreRealElement( item ) ); + } + } + + for ( i = 0 ; i < realAnchors.count() ; i++ ) + anchors.push( realAnchors.getItem( i ) ); + + for ( i = 0 ; i < anchors.length ; i++ ) + { + item = anchors[ i ]; + anchors[ i ] = { name : item.getAttribute( 'name' ), id : item.getAttribute( 'id' ) }; + } + + // Record down the selected element in the dialog. + this._.selectedElement = element; + + return retval; + }; + + var setupParams = function( page, data ) + { + if ( data[page] ) + this.setValue( data[page][this.id] || '' ); + }; + + var setupPopupParams = function( data ) + { + return setupParams.call( this, 'target', data ); + }; + + var setupAdvParams = function( data ) + { + return setupParams.call( this, 'adv', data ); + }; + + var commitParams = function( page, data ) + { + if ( !data[page] ) + data[page] = {}; + + data[page][this.id] = this.getValue() || ''; + }; + + var commitPopupParams = function( data ) + { + return commitParams.call( this, 'target', data ); + }; + + var commitAdvParams = function( data ) + { + 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, + minHeight : 230, + contents : [ + { + id : 'info', + label : editor.lang.link.info, + title : editor.lang.link.info, + elements : + [ + { + id : 'linkType', + type : 'select', + label : editor.lang.link.type, + 'default' : 'url', + items : + [ + [ editor.lang.link.toUrl, 'url' ], + [ editor.lang.link.toAnchor, 'anchor' ], + [ editor.lang.link.toEmail, 'email' ] + ], + onChange : linkTypeChanged, + setup : function( data ) + { + if ( data.type ) + this.setValue( data.type ); + }, + commit : function( data ) + { + data.type = this.getValue(); + } + }, + { + type : 'vbox', + id : 'urlOptions', + children : + [ + { + type : 'hbox', + widths : [ '25%', '75%' ], + children : + [ + { + id : 'protocol', + type : 'select', + label : editor.lang.common.protocol, + 'default' : 'http://', + items : + [ + // Force 'ltr' for protocol names in BIDI. (#5433) + [ 'http://\u200E', 'http://' ], + [ 'https://\u200E', 'https://' ], + [ 'ftp://\u200E', 'ftp://' ], + [ 'news://\u200E', 'news://' ], + [ editor.lang.link.other , '' ] + ], + setup : function( data ) + { + if ( data.url ) + this.setValue( data.url.protocol || '' ); + }, + commit : function( data ) + { + if ( !data.url ) + data.url = {}; + + data.url.protocol = this.getValue(); + } + }, + { + type : 'text', + id : 'url', + label : editor.lang.common.url, + required: true, + onLoad : function () + { + this.allowOnChange = true; + }, + onKeyUp : function() + { + this.allowOnChange = false; + var protocolCmb = this.getDialog().getContentElement( 'info', 'protocol' ), + url = this.getValue(), + urlOnChangeProtocol = /^(http|https|ftp|news):\/\/(?=.)/gi, + urlOnChangeTestOther = /^((javascript:)|[#\/\.\?])/gi; + + var protocol = urlOnChangeProtocol.exec( url ); + if ( protocol ) + { + this.setValue( url.substr( protocol[ 0 ].length ) ); + protocolCmb.setValue( protocol[ 0 ].toLowerCase() ); + } + else if ( urlOnChangeTestOther.test( url ) ) + protocolCmb.setValue( '' ); + + this.allowOnChange = true; + }, + onChange : function() + { + if ( this.allowOnChange ) // Dont't call on dialog load. + this.onKeyUp(); + }, + validate : function() + { + var dialog = this.getDialog(); + + if ( dialog.getContentElement( 'info', 'linkType' ) && + dialog.getValueOf( 'info', 'linkType' ) != 'url' ) + return true; + + if ( this.getDialog().fakeObj ) // Edit Anchor. + return true; + + var func = CKEDITOR.dialog.validate.notEmpty( editor.lang.link.noUrl ); + return func.apply( this ); + }, + setup : function( data ) + { + this.allowOnChange = false; + if ( data.url ) + this.setValue( data.url.url ); + this.allowOnChange = true; + + }, + commit : function( data ) + { + // IE will not trigger the onChange event if the mouse has been used + // to carry all the operations #4724 + this.onChange(); + + if ( !data.url ) + data.url = {}; + + data.url.url = this.getValue(); + this.allowOnChange = false; + } + } + ], + setup : function( data ) + { + if ( !this.getDialog().getContentElement( 'info', 'linkType' ) ) + this.getElement().show(); + } + }, + { + type : 'button', + id : 'browse', + hidden : 'true', + filebrowser : 'info:url', + label : editor.lang.common.browseServer + } + ] + }, + { + type : 'vbox', + id : 'anchorOptions', + width : 260, + align : 'center', + padding : 0, + children : + [ + { + type : 'fieldset', + id : 'selectAnchorText', + label : editor.lang.link.selectAnchor, + setup : function( data ) + { + if ( data.anchors.length > 0 ) + this.getElement().show(); + else + this.getElement().hide(); + }, + children : + [ + { + type : 'hbox', + id : 'selectAnchor', + children : + [ + { + type : 'select', + id : 'anchorName', + 'default' : '', + label : editor.lang.link.anchorName, + style : 'width: 100%;', + items : + [ + [ '' ] + ], + setup : function( data ) + { + this.clear(); + this.add( '' ); + for ( var i = 0 ; i < data.anchors.length ; i++ ) + { + if ( data.anchors[i].name ) + this.add( data.anchors[i].name ); + } + + if ( data.anchor ) + this.setValue( data.anchor.name ); + + var linkType = this.getDialog().getContentElement( 'info', 'linkType' ); + if ( linkType && linkType.getValue() == 'email' ) + this.focus(); + }, + commit : function( data ) + { + if ( !data.anchor ) + data.anchor = {}; + + data.anchor.name = this.getValue(); + } + }, + { + type : 'select', + id : 'anchorId', + 'default' : '', + label : editor.lang.link.anchorId, + style : 'width: 100%;', + items : + [ + [ '' ] + ], + setup : function( data ) + { + this.clear(); + this.add( '' ); + for ( var i = 0 ; i < data.anchors.length ; i++ ) + { + if ( data.anchors[i].id ) + this.add( data.anchors[i].id ); + } + + if ( data.anchor ) + this.setValue( data.anchor.id ); + }, + commit : function( data ) + { + if ( !data.anchor ) + data.anchor = {}; + + data.anchor.id = this.getValue(); + } + } + ], + setup : function( data ) + { + if ( data.anchors.length > 0 ) + this.getElement().show(); + else + this.getElement().hide(); + } + } + ] + }, + { + type : 'html', + id : 'noAnchors', + style : 'text-align: center;', + html : '<div role="label" tabIndex="-1">' + CKEDITOR.tools.htmlEncode( editor.lang.link.noAnchors ) + '</div>', + // Focus the first element defined in above html. + focus : true, + setup : function( data ) + { + if ( data.anchors.length < 1 ) + this.getElement().show(); + else + this.getElement().hide(); + } + } + ], + setup : function( data ) + { + if ( !this.getDialog().getContentElement( 'info', 'linkType' ) ) + this.getElement().hide(); + } + }, + { + type : 'vbox', + id : 'emailOptions', + padding : 1, + children : + [ + { + type : 'text', + id : 'emailAddress', + label : editor.lang.link.emailAddress, + required : true, + validate : function() + { + var dialog = this.getDialog(); + + if ( !dialog.getContentElement( 'info', 'linkType' ) || + dialog.getValueOf( 'info', 'linkType' ) != 'email' ) + return true; + + var func = CKEDITOR.dialog.validate.notEmpty( editor.lang.link.noEmail ); + return func.apply( this ); + }, + setup : function( data ) + { + if ( data.email ) + this.setValue( data.email.address ); + + var linkType = this.getDialog().getContentElement( 'info', 'linkType' ); + if ( linkType && linkType.getValue() == 'email' ) + this.select(); + }, + commit : function( data ) + { + if ( !data.email ) + data.email = {}; + + data.email.address = this.getValue(); + } + }, + { + type : 'text', + id : 'emailSubject', + label : editor.lang.link.emailSubject, + setup : function( data ) + { + if ( data.email ) + this.setValue( data.email.subject ); + }, + commit : function( data ) + { + if ( !data.email ) + data.email = {}; + + data.email.subject = this.getValue(); + } + }, + { + type : 'textarea', + id : 'emailBody', + label : editor.lang.link.emailBody, + rows : 3, + 'default' : '', + setup : function( data ) + { + if ( data.email ) + this.setValue( data.email.body ); + }, + commit : function( data ) + { + if ( !data.email ) + data.email = {}; + + data.email.body = this.getValue(); + } + } + ], + setup : function( data ) + { + if ( !this.getDialog().getContentElement( 'info', 'linkType' ) ) + this.getElement().hide(); + } + } + ] + }, + { + id : 'target', + label : editor.lang.link.target, + title : editor.lang.link.target, + elements : + [ + { + type : 'hbox', + widths : [ '50%', '50%' ], + children : + [ + { + type : 'select', + id : 'linkTargetType', + label : editor.lang.common.target, + 'default' : 'notSet', + style : 'width : 100%;', + 'items' : + [ + [ editor.lang.common.notSet, 'notSet' ], + [ editor.lang.link.targetFrame, 'frame' ], + [ editor.lang.link.targetPopup, 'popup' ], + [ editor.lang.common.targetNew, '_blank' ], + [ editor.lang.common.targetTop, '_top' ], + [ editor.lang.common.targetSelf, '_self' ], + [ editor.lang.common.targetParent, '_parent' ] + ], + onChange : targetChanged, + setup : function( data ) + { + if ( data.target ) + this.setValue( data.target.type ); + }, + commit : function( data ) + { + if ( !data.target ) + data.target = {}; + + data.target.type = this.getValue(); + } + }, + { + type : 'text', + id : 'linkTargetName', + label : editor.lang.link.targetFrameName, + 'default' : '', + setup : function( data ) + { + if ( data.target ) + this.setValue( data.target.name ); + }, + commit : function( data ) + { + if ( !data.target ) + data.target = {}; + + data.target.name = this.getValue().replace(/\W/gi, ''); + } + } + ] + }, + { + type : 'vbox', + width : 260, + align : 'center', + padding : 2, + id : 'popupFeatures', + children : + [ + { + type : 'fieldset', + label : editor.lang.link.popupFeatures, + children : + [ + { + type : 'hbox', + children : + [ + { + type : 'checkbox', + id : 'resizable', + label : editor.lang.link.popupResizable, + setup : setupPopupParams, + commit : commitPopupParams + }, + { + type : 'checkbox', + id : 'status', + label : editor.lang.link.popupStatusBar, + setup : setupPopupParams, + commit : commitPopupParams + + } + ] + }, + { + type : 'hbox', + children : + [ + { + type : 'checkbox', + id : 'location', + label : editor.lang.link.popupLocationBar, + setup : setupPopupParams, + commit : commitPopupParams + + }, + { + type : 'checkbox', + id : 'toolbar', + label : editor.lang.link.popupToolbar, + setup : setupPopupParams, + commit : commitPopupParams + + } + ] + }, + { + type : 'hbox', + children : + [ + { + type : 'checkbox', + id : 'menubar', + label : editor.lang.link.popupMenuBar, + setup : setupPopupParams, + commit : commitPopupParams + + }, + { + type : 'checkbox', + id : 'fullscreen', + label : editor.lang.link.popupFullScreen, + setup : setupPopupParams, + commit : commitPopupParams + + } + ] + }, + { + type : 'hbox', + children : + [ + { + type : 'checkbox', + id : 'scrollbars', + label : editor.lang.link.popupScrollBars, + setup : setupPopupParams, + commit : commitPopupParams + + }, + { + type : 'checkbox', + id : 'dependent', + label : editor.lang.link.popupDependent, + setup : setupPopupParams, + commit : commitPopupParams + + } + ] + }, + { + type : 'hbox', + children : + [ + { + type : 'text', + widths : [ '30%', '70%' ], + labelLayout : 'horizontal', + label : editor.lang.link.popupWidth, + id : 'width', + setup : setupPopupParams, + commit : commitPopupParams + + }, + { + type : 'text', + labelLayout : 'horizontal', + widths : [ '55%', '45%' ], + label : editor.lang.link.popupLeft, + id : 'left', + setup : setupPopupParams, + commit : commitPopupParams + + } + ] + }, + { + type : 'hbox', + children : + [ + { + type : 'text', + labelLayout : 'horizontal', + widths : [ '30%', '70%' ], + label : editor.lang.link.popupHeight, + id : 'height', + setup : setupPopupParams, + commit : commitPopupParams + + }, + { + type : 'text', + labelLayout : 'horizontal', + label : editor.lang.link.popupTop, + widths : [ '55%', '45%' ], + id : 'top', + setup : setupPopupParams, + commit : commitPopupParams + + } + ] + } + ] + } + ] + } + ] + }, + { + id : 'upload', + label : editor.lang.link.upload, + title : editor.lang.link.upload, + hidden : true, + filebrowser : 'uploadButton', + elements : + [ + { + type : 'file', + id : 'upload', + label : editor.lang.common.upload, + style: 'height:40px', + size : 29 + }, + { + type : 'fileButton', + id : 'uploadButton', + label : editor.lang.common.uploadSubmit, + filebrowser : 'info:url', + 'for' : [ 'upload', 'upload' ] + } + ] + }, + { + id : 'advanced', + label : editor.lang.link.advanced, + title : editor.lang.link.advanced, + elements : + [ + { + type : 'vbox', + padding : 1, + children : + [ + { + type : 'hbox', + widths : [ '45%', '35%', '20%' ], + children : + [ + { + type : 'text', + id : 'advId', + label : editor.lang.link.id, + setup : setupAdvParams, + commit : commitAdvParams + }, + { + type : 'select', + id : 'advLangDir', + label : editor.lang.link.langDir, + 'default' : '', + style : 'width:110px', + items : + [ + [ editor.lang.common.notSet, '' ], + [ editor.lang.link.langDirLTR, 'ltr' ], + [ editor.lang.link.langDirRTL, 'rtl' ] + ], + setup : setupAdvParams, + commit : commitAdvParams + }, + { + type : 'text', + id : 'advAccessKey', + width : '80px', + label : editor.lang.link.acccessKey, + maxLength : 1, + setup : setupAdvParams, + commit : commitAdvParams + + } + ] + }, + { + type : 'hbox', + widths : [ '45%', '35%', '20%' ], + children : + [ + { + type : 'text', + label : editor.lang.link.name, + id : 'advName', + setup : setupAdvParams, + commit : commitAdvParams + + }, + { + type : 'text', + label : editor.lang.link.langCode, + id : 'advLangCode', + width : '110px', + 'default' : '', + setup : setupAdvParams, + commit : commitAdvParams + + }, + { + type : 'text', + label : editor.lang.link.tabIndex, + id : 'advTabIndex', + width : '80px', + maxLength : 5, + setup : setupAdvParams, + commit : commitAdvParams + + } + ] + } + ] + }, + { + type : 'vbox', + padding : 1, + children : + [ + { + type : 'hbox', + widths : [ '45%', '55%' ], + children : + [ + { + type : 'text', + label : editor.lang.link.advisoryTitle, + 'default' : '', + id : 'advTitle', + setup : setupAdvParams, + commit : commitAdvParams + + }, + { + type : 'text', + label : editor.lang.link.advisoryContentType, + 'default' : '', + id : 'advContentType', + setup : setupAdvParams, + commit : commitAdvParams + + } + ] + }, + { + type : 'hbox', + widths : [ '45%', '55%' ], + children : + [ + { + type : 'text', + label : editor.lang.link.cssClasses, + 'default' : '', + id : 'advCSSClasses', + setup : setupAdvParams, + commit : commitAdvParams + + }, + { + type : 'text', + label : editor.lang.link.charset, + 'default' : '', + id : 'advCharset', + setup : setupAdvParams, + commit : commitAdvParams + + } + ] + }, + { + type : 'hbox', + children : + [ + { + type : 'text', + label : editor.lang.link.styles, + 'default' : '', + id : 'advStyles', + setup : setupAdvParams, + commit : commitAdvParams + + } + ] + } + ] + } + ] + } + ], + onShow : function() + { + this.fakeObj = false; + + var editor = this.getParentEditor(), + selection = editor.getSelection(), + element = null; + + // Fill in all the relevant fields if there's already one link selected. + if ( ( element = plugin.getSelectedLink( editor ) ) && element.hasAttribute( 'href' ) ) + selection.selectElement( element ); + else if ( ( element = selection.getSelectedElement() ) && element.is( 'img' ) + && element.getAttribute( '_cke_real_element_type' ) + && element.getAttribute( '_cke_real_element_type' ) == 'anchor' ) + { + this.fakeObj = element; + element = editor.restoreRealElement( this.fakeObj ); + selection.selectElement( this.fakeObj ); + } + else + element = null; + + this.setupContent( parseLink.apply( this, [ editor, element ] ) ); + }, + onOk : function() + { + var attributes = { href : 'javascript:void(0)/*' + CKEDITOR.tools.getNextNumber() + '*/' }, + removeAttributes = [], + data = { href : attributes.href }, + me = this, + editor = this.getParentEditor(); + + this.commitContent( data ); + + // Compose the URL. + switch ( data.type || 'url' ) + { + case 'url': + var protocol = ( data.url && data.url.protocol != undefined ) ? data.url.protocol : 'http://', + url = ( data.url && data.url.url ) || ''; + attributes._cke_saved_href = ( url.indexOf( '/' ) === 0 ) ? url : protocol + url; + break; + case 'anchor': + var name = ( data.anchor && data.anchor.name ), + id = ( data.anchor && data.anchor.id ); + attributes._cke_saved_href = '#' + ( name || id || '' ); + break; + case 'email': + + var linkHref, + email = data.email, + address = email.address; + + switch( emailProtection ) + { + 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 = linkHref.join( '' ); + break; + } + + // Popups and target. + if ( data.target ) + { + if ( data.target.type == 'popup' ) + { + var onclickList = [ 'window.open(this.href, \'', + data.target.name || '', '\', \'' ]; + var featureList = [ 'resizable', 'status', 'location', 'toolbar', 'menubar', 'fullscreen', + 'scrollbars', 'dependent' ]; + var featureLength = featureList.length; + var addFeature = function( featureName ) + { + if ( data.target[ featureName ] ) + featureList.push( featureName + '=' + data.target[ featureName ] ); + }; + + for ( var i = 0 ; i < featureLength ; i++ ) + featureList[i] = featureList[i] + ( data.target[ featureList[i] ] ? '=yes' : '=no' ) ; + addFeature( 'width' ); + addFeature( 'left' ); + addFeature( 'height' ); + addFeature( 'top' ); + + onclickList.push( featureList.join( ',' ), '\'); return false;' ); + 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' ); + } + } + + // Advanced attributes. + if ( data.adv ) + { + var advAttr = function( inputName, attrName ) + { + var value = data.adv[ inputName ]; + if ( value ) + attributes[attrName] = value; + else + removeAttributes.push( attrName ); + }; + + if ( this._.selectedElement ) + advAttr( 'advId', 'id' ); + advAttr( 'advLangDir', 'dir' ); + advAttr( 'advAccessKey', 'accessKey' ); + advAttr( 'advName', 'name' ); + advAttr( 'advLangCode', 'lang' ); + advAttr( 'advTabIndex', 'tabindex' ); + advAttr( 'advTitle', 'title' ); + advAttr( 'advContentType', 'type' ); + advAttr( 'advCSSClasses', 'class' ); + advAttr( 'advCharset', 'charset' ); + advAttr( 'advStyles', 'style' ); + } + + if ( !this._.selectedElement ) + { + // Create element if current selection is collapsed. + var selection = editor.getSelection(), + ranges = selection.getRanges(); + if ( ranges.length == 1 && ranges[0].collapsed ) + { + var text = new CKEDITOR.dom.text( attributes._cke_saved_href, editor.document ); + ranges[0].insertNode( text ); + ranges[0].selectNodeContents( text ); + selection.selectRanges( ranges ); + } + + // Apply style. + var style = new CKEDITOR.style( { element : 'a', attributes : attributes } ); + style.type = CKEDITOR.STYLE_INLINE; // need to override... dunno why. + style.apply( editor.document ); + + // Id. Apply only to the first link. + if ( data.adv && data.adv.advId ) + { + var links = this.getParentEditor().document.$.getElementsByTagName( 'a' ); + for ( i = 0 ; i < links.length ; i++ ) + { + if ( links[i].href == attributes.href ) + { + links[i].id = data.adv.advId; + break; + } + } + } + } + else + { + // We're only editing an existing link, so just overwrite the attributes. + var element = this._.selectedElement, + href = element.getAttribute( '_cke_saved_href' ), + textView = element.getHtml(); + + // IE BUG: Setting the name attribute to an existing link doesn't work. + // Must re-create the link from weired syntax to workaround. + if ( CKEDITOR.env.ie && attributes.name != element.getAttribute( 'name' ) ) + { + var newElement = new CKEDITOR.dom.element( '<a name="' + CKEDITOR.tools.htmlEncode( attributes.name ) + '">', + editor.document ); + + selection = editor.getSelection(); + + element.moveChildren( newElement ); + element.copyAttributes( newElement, { name : 1 } ); + newElement.replace( element ); + element = newElement; + + selection.selectElement( element ); + } + + element.setAttributes( attributes ); + element.removeAttributes( removeAttributes ); + // Update text view when user changes protocol #4612. + if (href == textView) + element.setHtml( attributes._cke_saved_href ); + // Make the element display as an anchor if a name has been set. + if ( element.getAttribute( 'name' ) ) + element.addClass( 'cke_anchor' ); + else + element.removeClass( 'cke_anchor' ); + + if ( this.fakeObj ) + editor.createFakeElement( element, 'cke_anchor', 'anchor' ).replace( this.fakeObj ); + + delete this._.selectedElement; + } + }, + onLoad : function() + { + if ( !editor.config.linkShowAdvancedTab ) + this.hidePage( 'advanced' ); //Hide Advanded tab. + + if ( !editor.config.linkShowTargetTab ) + this.hidePage( 'target' ); //Hide Target tab. + + }, + // Inital focus on 'url' field if link is of type URL. + onFocus : function() + { + var linkType = this.getContentElement( 'info', 'linkType' ), + urlField; + if ( linkType && linkType.getValue( ) == 'url' ) + { + urlField = this.getContentElement( 'info', 'url' ); + urlField.select(); + } + } + }; +}); + +/** + * The e-mail address anti-spam protection option. The protection will be + * applied when creating or modifying e-mail links through the editor interface.<br> + * Two methods of protection can be choosed: + * <ol> <li>The e-mail parts (name, domain and any other query string) are + * assembled into a function call pattern. Such function must be + * provided by the developer in the pages that will use the contents. + * <li>Only the e-mail address is obfuscated into a special string that + * has no meaning for humans or spam bots, but which is properly + * rendered and accepted by the browser.</li></ol> + * Both approaches require JavaScript to be enabled. + * @name CKEDITOR.config.emailProtection + * @since 3.1 + * @type String + * @default '' (empty string = disabled) + * @example + * // href="mailto:tester@ckeditor.com?subject=subject&body=body" + * config.emailProtection = ''; + * @example + * // 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 = 'encode'; + * @example + * // href="javascript:mt('tester','ckeditor.com','subject','body')" + * config.emailProtection = 'mt(NAME,DOMAIN,SUBJECT,BODY)'; + */ diff --git a/_source/plugins/link/plugin.js b/_source/plugins/link/plugin.js index 7993a0f..ee2c004 100644 --- a/_source/plugins/link/plugin.js +++ b/_source/plugins/link/plugin.js @@ -1,188 +1,229 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-CKEDITOR.plugins.add( 'link',
-{
- init : function( editor )
- {
- // Add the link and unlink buttons.
- editor.addCommand( 'link', new CKEDITOR.dialogCommand( 'link' ) );
- editor.addCommand( 'anchor', new CKEDITOR.dialogCommand( 'anchor' ) );
- editor.addCommand( 'unlink', new CKEDITOR.unlinkCommand() );
- editor.ui.addButton( 'Link',
- {
- label : editor.lang.link.toolbar,
- command : 'link'
- } );
- editor.ui.addButton( 'Unlink',
- {
- label : editor.lang.unlink,
- command : 'unlink'
- } );
- editor.ui.addButton( 'Anchor',
- {
- label : editor.lang.anchor.toolbar,
- command : 'anchor'
- } );
- CKEDITOR.dialog.add( 'link', this.path + 'dialogs/link.js' );
- CKEDITOR.dialog.add( 'anchor', this.path + 'dialogs/anchor.js' );
-
- // Add the CSS styles for anchor placeholders.
- editor.addCss(
- 'img.cke_anchor' +
- '{' +
- 'background-image: url(' + CKEDITOR.getUrl( this.path + 'images/anchor.gif' ) + ');' +
- 'background-position: center center;' +
- 'background-repeat: no-repeat;' +
- 'border: 1px solid #a9a9a9;' +
- 'width: 18px;' +
- 'height: 18px;' +
- '}\n' +
- 'a.cke_anchor' +
- '{' +
- 'background-image: url(' + CKEDITOR.getUrl( this.path + 'images/anchor.gif' ) + ');' +
- 'background-position: 0 center;' +
- 'background-repeat: no-repeat;' +
- 'border: 1px solid #a9a9a9;' +
- 'padding-left: 18px;' +
- '}'
- );
-
- // Register selection change handler for the unlink button.
- editor.on( 'selectionChange', function( evt )
- {
- /*
- * Despite our initial hope, document.queryCommandEnabled() does not work
- * for this in Firefox. So we must detect the state by element paths.
- */
- var command = editor.getCommand( 'unlink' ),
- element = evt.data.path.lastElement.getAscendant( 'a', true );
- if ( element && element.getName() == 'a' && element.getAttribute( 'href' ) )
- command.setState( CKEDITOR.TRISTATE_OFF );
- else
- command.setState( CKEDITOR.TRISTATE_DISABLED );
- } );
-
- // If the "menu" plugin is loaded, register the menu items.
- if ( editor.addMenuItems )
- {
- editor.addMenuItems(
- {
- anchor :
- {
- label : editor.lang.anchor.menu,
- command : 'anchor',
- group : 'anchor'
- },
-
- link :
- {
- label : editor.lang.link.menu,
- command : 'link',
- group : 'link',
- order : 1
- },
-
- unlink :
- {
- label : editor.lang.unlink,
- command : 'unlink',
- group : 'link',
- order : 5
- }
- });
- }
-
- // If the "contextmenu" plugin is loaded, register the listeners.
- if ( editor.contextMenu )
- {
- editor.contextMenu.addListener( function( element, selection )
- {
- if ( !element )
- return null;
-
- var isAnchor = ( element.is( 'img' ) && element.getAttribute( '_cke_real_element_type' ) == 'anchor' );
-
- if ( !isAnchor )
- {
- if ( !( element = element.getAscendant( 'a', true ) ) )
- return null;
-
- isAnchor = ( element.getAttribute( 'name' ) && !element.getAttribute( 'href' ) );
- }
-
- return isAnchor ?
- { anchor : CKEDITOR.TRISTATE_OFF } :
- { link : CKEDITOR.TRISTATE_OFF, unlink : CKEDITOR.TRISTATE_OFF };
- });
- }
- },
-
- afterInit : function( editor )
- {
- // Register a filter to displaying placeholders after mode change.
-
- var dataProcessor = editor.dataProcessor,
- dataFilter = dataProcessor && dataProcessor.dataFilter;
-
- if ( dataFilter )
- {
- dataFilter.addRules(
- {
- elements :
- {
- a : function( element )
- {
- var attributes = element.attributes;
- if ( attributes.name && !attributes.href )
- return editor.createFakeParserElement( element, 'cke_anchor', 'anchor' );
- }
- }
- });
- }
- },
-
- requires : [ 'fakeobjects' ]
-} );
-
-CKEDITOR.unlinkCommand = function(){};
-CKEDITOR.unlinkCommand.prototype =
-{
- /** @ignore */
- exec : function( editor )
- {
- /*
- * execCommand( 'unlink', ... ) in Firefox leaves behind <span> tags at where
- * the <a> was, so again we have to remove the link ourselves. (See #430)
- *
- * TODO: Use the style system when it's complete. Let's use execCommand()
- * as a stopgap solution for now.
- */
- var selection = editor.getSelection(),
- bookmarks = selection.createBookmarks(),
- ranges = selection.getRanges(),
- rangeRoot,
- element;
-
- for ( var i = 0 ; i < ranges.length ; i++ )
- {
- rangeRoot = ranges[i].getCommonAncestor( true );
- element = rangeRoot.getAscendant( 'a', true );
- if ( !element )
- continue;
- ranges[i].selectNodeContents( element );
- }
-
- selection.selectRanges( ranges );
- editor.document.$.execCommand( 'unlink', false, null );
- selection.selectBookmarks( bookmarks );
- }
-};
-
-CKEDITOR.tools.extend( CKEDITOR.config,
-{
- linkShowAdvancedTab : true,
- linkShowTargetTab : true
-} );
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.plugins.add( 'link', +{ + init : function( editor ) + { + // Add the link and unlink buttons. + editor.addCommand( 'link', new CKEDITOR.dialogCommand( 'link' ) ); + editor.addCommand( 'anchor', new CKEDITOR.dialogCommand( 'anchor' ) ); + editor.addCommand( 'unlink', new CKEDITOR.unlinkCommand() ); + editor.ui.addButton( 'Link', + { + label : editor.lang.link.toolbar, + command : 'link' + } ); + editor.ui.addButton( 'Unlink', + { + label : editor.lang.unlink, + command : 'unlink' + } ); + editor.ui.addButton( 'Anchor', + { + label : editor.lang.anchor.toolbar, + command : 'anchor' + } ); + CKEDITOR.dialog.add( 'link', this.path + 'dialogs/link.js' ); + CKEDITOR.dialog.add( 'anchor', this.path + 'dialogs/anchor.js' ); + + // Add the CSS styles for anchor placeholders. + editor.addCss( + 'img.cke_anchor' + + '{' + + 'background-image: url(' + CKEDITOR.getUrl( this.path + 'images/anchor.gif' ) + ');' + + 'background-position: center center;' + + 'background-repeat: no-repeat;' + + 'border: 1px solid #a9a9a9;' + + 'width: 18px;' + + 'height: 18px;' + + '}\n' + + 'a.cke_anchor' + + '{' + + 'background-image: url(' + CKEDITOR.getUrl( this.path + 'images/anchor.gif' ) + ');' + + 'background-position: 0 center;' + + 'background-repeat: no-repeat;' + + 'border: 1px solid #a9a9a9;' + + 'padding-left: 18px;' + + '}' + ); + + // Register selection change handler for the unlink button. + editor.on( 'selectionChange', function( evt ) + { + /* + * Despite our initial hope, document.queryCommandEnabled() does not work + * for this in Firefox. So we must detect the state by element paths. + */ + var command = editor.getCommand( 'unlink' ), + element = evt.data.path.lastElement.getAscendant( 'a', true ); + if ( element && element.getName() == 'a' && element.getAttribute( 'href' ) ) + command.setState( CKEDITOR.TRISTATE_OFF ); + else + command.setState( CKEDITOR.TRISTATE_DISABLED ); + } ); + + editor.on( 'doubleclick', function( evt ) + { + var element = CKEDITOR.plugins.link.getSelectedLink( editor ) || evt.data.element; + + if ( element.is( 'a' ) ) + evt.data.dialog = ( element.getAttribute( 'name' ) && !element.getAttribute( 'href' ) ) ? 'anchor' : 'link'; + else if ( element.is( 'img' ) && element.getAttribute( '_cke_real_element_type' ) == 'anchor' ) + evt.data.dialog = 'anchor'; + }); + + // If the "menu" plugin is loaded, register the menu items. + if ( editor.addMenuItems ) + { + editor.addMenuItems( + { + anchor : + { + label : editor.lang.anchor.menu, + command : 'anchor', + group : 'anchor' + }, + + link : + { + label : editor.lang.link.menu, + command : 'link', + group : 'link', + order : 1 + }, + + unlink : + { + label : editor.lang.unlink, + command : 'unlink', + group : 'link', + order : 5 + } + }); + } + + // If the "contextmenu" plugin is loaded, register the listeners. + if ( editor.contextMenu ) + { + editor.contextMenu.addListener( function( element, selection ) + { + if ( !element ) + return null; + + var isAnchor = ( element.is( 'img' ) && element.getAttribute( '_cke_real_element_type' ) == 'anchor' ); + + if ( !isAnchor ) + { + if ( !( element = CKEDITOR.plugins.link.getSelectedLink( editor ) ) ) + return null; + + isAnchor = ( element.getAttribute( 'name' ) && !element.getAttribute( 'href' ) ); + } + + return isAnchor ? + { anchor : CKEDITOR.TRISTATE_OFF } : + { link : CKEDITOR.TRISTATE_OFF, unlink : CKEDITOR.TRISTATE_OFF }; + }); + } + }, + + afterInit : function( editor ) + { + // Register a filter to displaying placeholders after mode change. + + var dataProcessor = editor.dataProcessor, + dataFilter = dataProcessor && dataProcessor.dataFilter; + + if ( dataFilter ) + { + dataFilter.addRules( + { + elements : + { + a : function( element ) + { + var attributes = element.attributes; + if ( attributes.name && !attributes.href ) + return editor.createFakeParserElement( element, 'cke_anchor', 'anchor' ); + } + } + }); + } + }, + + requires : [ 'fakeobjects' ] +} ); + +CKEDITOR.plugins.link = +{ + /** + * Get the surrounding link element of current selection. + * @param editor + * @example CKEDITOR.plugins.link.getSelectedLink( editor ); + * @since 3.2.1 + * The following selection will all return the link element. + * <pre> + * <a href="#">li^nk</a> + * <a href="#">[link]</a> + * text[<a href="#">link]</a> + * <a href="#">li[nk</a>] + * [<b><a href="#">li]nk</a></b>] + * [<a href="#"><b>li]nk</b></a> + * </pre> + */ + getSelectedLink : function( editor ) + { + var range; + try { range = editor.getSelection().getRanges()[ 0 ]; } + catch( e ) { return null; } + + range.shrink( CKEDITOR.SHRINK_TEXT ); + var root = range.getCommonAncestor(); + return root.getAscendant( 'a', true ); + } +}; + +CKEDITOR.unlinkCommand = function(){}; +CKEDITOR.unlinkCommand.prototype = +{ + /** @ignore */ + exec : function( editor ) + { + /* + * execCommand( 'unlink', ... ) in Firefox leaves behind <span> tags at where + * the <a> was, so again we have to remove the link ourselves. (See #430) + * + * TODO: Use the style system when it's complete. Let's use execCommand() + * as a stopgap solution for now. + */ + var selection = editor.getSelection(), + bookmarks = selection.createBookmarks(), + ranges = selection.getRanges(), + rangeRoot, + element; + + for ( var i = 0 ; i < ranges.length ; i++ ) + { + rangeRoot = ranges[i].getCommonAncestor( true ); + element = rangeRoot.getAscendant( 'a', true ); + if ( !element ) + continue; + ranges[i].selectNodeContents( element ); + } + + selection.selectRanges( ranges ); + editor.document.$.execCommand( 'unlink', false, null ); + selection.selectBookmarks( bookmarks ); + }, + + startDisabled : true +}; + +CKEDITOR.tools.extend( CKEDITOR.config, +{ + linkShowAdvancedTab : true, + linkShowTargetTab : true +} ); diff --git a/_source/plugins/list/plugin.js b/_source/plugins/list/plugin.js index 87ad3cc..3de4edd 100644 --- a/_source/plugins/list/plugin.js +++ b/_source/plugins/list/plugin.js @@ -1,643 +1,656 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-/**
- * @file Insert and remove numbered and bulleted lists.
- */
-
-(function()
-{
- var listNodeNames = { ol : 1, ul : 1 },
- emptyTextRegex = /^[\n\r\t ]*$/;
-
- CKEDITOR.plugins.list = {
- /*
- * Convert a DOM list tree into a data structure that is easier to
- * manipulate. This operation should be non-intrusive in the sense that it
- * does not change the DOM tree, with the exception that it may add some
- * markers to the list item nodes when database is specified.
- */
- listToArray : function( listNode, database, baseArray, baseIndentLevel, grandparentNode )
- {
- if ( !listNodeNames[ listNode.getName() ] )
- return [];
-
- if ( !baseIndentLevel )
- baseIndentLevel = 0;
- if ( !baseArray )
- baseArray = [];
-
- // Iterate over all list items to get their contents and look for inner lists.
- for ( var i = 0, count = listNode.getChildCount() ; i < count ; i++ )
- {
- var listItem = listNode.getChild( i );
-
- // It may be a text node or some funny stuff.
- if ( listItem.$.nodeName.toLowerCase() != 'li' )
- continue;
- var itemObj = { 'parent' : listNode, indent : baseIndentLevel, contents : [] };
- if ( !grandparentNode )
- {
- itemObj.grandparent = listNode.getParent();
- if ( itemObj.grandparent && itemObj.grandparent.$.nodeName.toLowerCase() == 'li' )
- itemObj.grandparent = itemObj.grandparent.getParent();
- }
- else
- itemObj.grandparent = grandparentNode;
-
- if ( database )
- CKEDITOR.dom.element.setMarker( database, listItem, 'listarray_index', baseArray.length );
- baseArray.push( itemObj );
-
- for ( var j = 0, itemChildCount = listItem.getChildCount() ; j < itemChildCount ; j++ )
- {
- var child = listItem.getChild( j );
- if ( child.type == CKEDITOR.NODE_ELEMENT && listNodeNames[ child.getName() ] )
- // Note the recursion here, it pushes inner list items with
- // +1 indentation in the correct order.
- CKEDITOR.plugins.list.listToArray( child, database, baseArray, baseIndentLevel + 1, itemObj.grandparent );
- else
- itemObj.contents.push( child );
- }
- }
- return baseArray;
- },
-
- // Convert our internal representation of a list back to a DOM forest.
- arrayToList : function( listArray, database, baseIndex, paragraphMode )
- {
- if ( !baseIndex )
- baseIndex = 0;
- if ( !listArray || listArray.length < baseIndex + 1 )
- return null;
- var doc = listArray[ baseIndex ].parent.getDocument(),
- retval = new CKEDITOR.dom.documentFragment( doc ),
- rootNode = null,
- currentIndex = baseIndex,
- indentLevel = Math.max( listArray[ baseIndex ].indent, 0 ),
- currentListItem = null,
- paragraphName = ( paragraphMode == CKEDITOR.ENTER_P ? 'p' : 'div' );
- while ( true )
- {
- var item = listArray[ currentIndex ];
- if ( item.indent == indentLevel )
- {
- if ( !rootNode || listArray[ currentIndex ].parent.getName() != rootNode.getName() )
- {
- rootNode = listArray[ currentIndex ].parent.clone( false, true );
- retval.append( rootNode );
- }
- currentListItem = rootNode.append( doc.createElement( 'li' ) );
- for ( var i = 0 ; i < item.contents.length ; i++ )
- currentListItem.append( item.contents[i].clone( true, true ) );
- currentIndex++;
- }
- else if ( item.indent == Math.max( indentLevel, 0 ) + 1 )
- {
- var listData = CKEDITOR.plugins.list.arrayToList( listArray, null, currentIndex, paragraphMode );
- currentListItem.append( listData.listNode );
- currentIndex = listData.nextIndex;
- }
- else if ( item.indent == -1 && !baseIndex && item.grandparent )
- {
- currentListItem;
- if ( listNodeNames[ item.grandparent.getName() ] )
- currentListItem = doc.createElement( 'li' );
- else
- {
- if ( paragraphMode != CKEDITOR.ENTER_BR && item.grandparent.getName() != 'td' )
- currentListItem = doc.createElement( paragraphName );
- else
- currentListItem = new CKEDITOR.dom.documentFragment( doc );
- }
-
- for ( i = 0 ; i < item.contents.length ; i++ )
- currentListItem.append( item.contents[i].clone( true, true ) );
-
- if ( currentListItem.type == CKEDITOR.NODE_DOCUMENT_FRAGMENT
- && currentIndex != listArray.length - 1 )
- {
- if ( currentListItem.getLast()
- && currentListItem.getLast().type == CKEDITOR.NODE_ELEMENT
- && currentListItem.getLast().getAttribute( 'type' ) == '_moz' )
- currentListItem.getLast().remove();
- currentListItem.appendBogus();
- }
-
- if ( currentListItem.type == CKEDITOR.NODE_ELEMENT &&
- currentListItem.getName() == paragraphName &&
- currentListItem.$.firstChild )
- {
- currentListItem.trim();
- var firstChild = currentListItem.getFirst();
- if ( firstChild.type == CKEDITOR.NODE_ELEMENT && firstChild.isBlockBoundary() )
- {
- var tmp = new CKEDITOR.dom.documentFragment( doc );
- currentListItem.moveChildren( tmp );
- currentListItem = tmp;
- }
- }
-
- var currentListItemName = currentListItem.$.nodeName.toLowerCase();
- if ( !CKEDITOR.env.ie && ( currentListItemName == 'div' || currentListItemName == 'p' ) )
- currentListItem.appendBogus();
- retval.append( currentListItem );
- rootNode = null;
- currentIndex++;
- }
- else
- return null;
-
- if ( listArray.length <= currentIndex || Math.max( listArray[ currentIndex ].indent, 0 ) < indentLevel )
- break;
- }
-
- // Clear marker attributes for the new list tree made of cloned nodes, if any.
- if ( database )
- {
- var currentNode = retval.getFirst();
- while ( currentNode )
- {
- if ( currentNode.type == CKEDITOR.NODE_ELEMENT )
- CKEDITOR.dom.element.clearMarkers( database, currentNode );
- currentNode = currentNode.getNextSourceNode();
- }
- }
-
- return { listNode : retval, nextIndex : currentIndex };
- }
- };
-
- function setState( editor, state )
- {
- editor.getCommand( this.name ).setState( state );
- }
-
- function onSelectionChange( evt )
- {
- var path = evt.data.path,
- blockLimit = path.blockLimit,
- elements = path.elements,
- element;
-
- // Grouping should only happen under blockLimit.(#3940).
- for ( var i = 0 ; i < elements.length && ( element = elements[ i ] )
- && !element.equals( blockLimit ); i++ )
- {
- if ( listNodeNames[ elements[i].getName() ] )
- {
- return setState.call( this, evt.editor,
- this.type == elements[i].getName() ? CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF );
- }
- }
-
- return setState.call( this, evt.editor, CKEDITOR.TRISTATE_OFF );
- }
-
- function changeListType( editor, groupObj, database, listsCreated )
- {
- // This case is easy...
- // 1. Convert the whole list into a one-dimensional array.
- // 2. Change the list type by modifying the array.
- // 3. Recreate the whole list by converting the array to a list.
- // 4. Replace the original list with the recreated list.
- var listArray = CKEDITOR.plugins.list.listToArray( groupObj.root, database ),
- selectedListItems = [];
-
- for ( var i = 0 ; i < groupObj.contents.length ; i++ )
- {
- var itemNode = groupObj.contents[i];
- itemNode = itemNode.getAscendant( 'li', true );
- if ( !itemNode || itemNode.getCustomData( 'list_item_processed' ) )
- continue;
- selectedListItems.push( itemNode );
- CKEDITOR.dom.element.setMarker( database, itemNode, 'list_item_processed', true );
- }
-
- var fakeParent = groupObj.root.getDocument().createElement( this.type );
- for ( i = 0 ; i < selectedListItems.length ; i++ )
- {
- var listIndex = selectedListItems[i].getCustomData( 'listarray_index' );
- listArray[listIndex].parent = fakeParent;
- }
- var newList = CKEDITOR.plugins.list.arrayToList( listArray, database, null, editor.config.enterMode );
- var child, length = newList.listNode.getChildCount();
- for ( i = 0 ; i < length && ( child = newList.listNode.getChild( i ) ) ; i++ )
- {
- if ( child.getName() == this.type )
- listsCreated.push( child );
- }
- newList.listNode.replace( groupObj.root );
- }
-
- function createList( editor, groupObj, listsCreated )
- {
- var contents = groupObj.contents,
- doc = groupObj.root.getDocument(),
- listContents = [];
-
- // It is possible to have the contents returned by DomRangeIterator to be the same as the root.
- // e.g. when we're running into table cells.
- // In such a case, enclose the childNodes of contents[0] into a <div>.
- if ( contents.length == 1 && contents[0].equals( groupObj.root ) )
- {
- var divBlock = doc.createElement( 'div' );
- contents[0].moveChildren && contents[0].moveChildren( divBlock );
- contents[0].append( divBlock );
- contents[0] = divBlock;
- }
-
- // Calculate the common parent node of all content blocks.
- var commonParent = groupObj.contents[0].getParent();
- for ( var i = 0 ; i < contents.length ; i++ )
- commonParent = commonParent.getCommonAncestor( contents[i].getParent() );
-
- // We want to insert things that are in the same tree level only, so calculate the contents again
- // by expanding the selected blocks to the same tree level.
- for ( i = 0 ; i < contents.length ; i++ )
- {
- var contentNode = contents[i],
- parentNode;
- while ( ( parentNode = contentNode.getParent() ) )
- {
- if ( parentNode.equals( commonParent ) )
- {
- listContents.push( contentNode );
- break;
- }
- contentNode = parentNode;
- }
- }
-
- if ( listContents.length < 1 )
- return;
-
- // Insert the list to the DOM tree.
- var insertAnchor = listContents[ listContents.length - 1 ].getNext(),
- listNode = doc.createElement( this.type );
-
- listsCreated.push( listNode );
- while ( listContents.length )
- {
- var contentBlock = listContents.shift(),
- listItem = doc.createElement( 'li' );
- contentBlock.moveChildren( listItem );
- contentBlock.remove();
- listItem.appendTo( listNode );
-
- // Append a bogus BR to force the LI to render at full height
- if ( !CKEDITOR.env.ie )
- listItem.appendBogus();
- }
- if ( insertAnchor )
- listNode.insertBefore( insertAnchor );
- else
- listNode.appendTo( commonParent );
- }
-
- function removeList( editor, groupObj, database )
- {
- // This is very much like the change list type operation.
- // Except that we're changing the selected items' indent to -1 in the list array.
- var listArray = CKEDITOR.plugins.list.listToArray( groupObj.root, database ),
- selectedListItems = [];
-
- for ( var i = 0 ; i < groupObj.contents.length ; i++ )
- {
- var itemNode = groupObj.contents[i];
- itemNode = itemNode.getAscendant( 'li', true );
- if ( !itemNode || itemNode.getCustomData( 'list_item_processed' ) )
- continue;
- selectedListItems.push( itemNode );
- CKEDITOR.dom.element.setMarker( database, itemNode, 'list_item_processed', true );
- }
-
- var lastListIndex = null;
- for ( i = 0 ; i < selectedListItems.length ; i++ )
- {
- var listIndex = selectedListItems[i].getCustomData( 'listarray_index' );
- listArray[listIndex].indent = -1;
- lastListIndex = listIndex;
- }
-
- // After cutting parts of the list out with indent=-1, we still have to maintain the array list
- // model's nextItem.indent <= currentItem.indent + 1 invariant. Otherwise the array model of the
- // list cannot be converted back to a real DOM list.
- for ( i = lastListIndex + 1 ; i < listArray.length ; i++ )
- {
- if ( listArray[i].indent > listArray[i-1].indent + 1 )
- {
- var indentOffset = listArray[i-1].indent + 1 - listArray[i].indent;
- var oldIndent = listArray[i].indent;
- while ( listArray[i] && listArray[i].indent >= oldIndent )
- {
- listArray[i].indent += indentOffset;
- i++;
- }
- i--;
- }
- }
-
- var newList = CKEDITOR.plugins.list.arrayToList( listArray, database, null, editor.config.enterMode );
-
- // Compensate <br> before/after the list node if the surrounds are non-blocks.(#3836)
- var docFragment = newList.listNode, boundaryNode, siblingNode;
- function compensateBrs( isStart )
- {
- if ( ( boundaryNode = docFragment[ isStart ? 'getFirst' : 'getLast' ]() )
- && !( boundaryNode.is && boundaryNode.isBlockBoundary() )
- && ( siblingNode = groupObj.root[ isStart ? 'getPrevious' : 'getNext' ]
- ( CKEDITOR.dom.walker.whitespaces( true ) ) )
- && !( siblingNode.is && siblingNode.isBlockBoundary( { br : 1 } ) ) )
- editor.document.createElement( 'br' )[ isStart ? 'insertBefore' : 'insertAfter' ]( boundaryNode );
- }
- compensateBrs( true );
- compensateBrs();
-
- docFragment.replace( groupObj.root );
- }
-
- function listCommand( name, type )
- {
- this.name = name;
- this.type = type;
- }
-
- listCommand.prototype = {
- exec : function( editor )
- {
- editor.focus();
-
- var doc = editor.document,
- selection = editor.getSelection(),
- ranges = selection && selection.getRanges();
-
- // There should be at least one selected range.
- if ( !ranges || ranges.length < 1 )
- return;
-
- // Midas lists rule #1 says we can create a list even in an empty document.
- // But DOM iterator wouldn't run if the document is really empty.
- // So create a paragraph if the document is empty and we're going to create a list.
- if ( this.state == CKEDITOR.TRISTATE_OFF )
- {
- var body = doc.getBody();
- body.trim();
- if ( !body.getFirst() )
- {
- var paragraph = doc.createElement( editor.config.enterMode == CKEDITOR.ENTER_P ? 'p' :
- ( editor.config.enterMode == CKEDITOR.ENTER_DIV ? 'div' : 'br' ) );
- paragraph.appendTo( body );
- ranges = [ new CKEDITOR.dom.range( doc ) ];
- // IE exception on inserting anything when anchor inside <br>.
- if ( paragraph.is( 'br' ) )
- {
- ranges[ 0 ].setStartBefore( paragraph );
- ranges[ 0 ].setEndAfter( paragraph );
- }
- else
- ranges[ 0 ].selectNodeContents( paragraph );
- selection.selectRanges( ranges );
- }
- // Maybe a single range there enclosing the whole list,
- // turn on the list state manually(#4129).
- else
- {
- var range = ranges.length == 1 && ranges[ 0 ],
- enclosedNode = range && range.getEnclosedNode();
- if ( enclosedNode && enclosedNode.is
- && this.type == enclosedNode.getName() )
- {
- setState.call( this, editor, CKEDITOR.TRISTATE_ON );
- }
- }
- }
-
- var bookmarks = selection.createBookmarks( true );
-
- // Group the blocks up because there are many cases where multiple lists have to be created,
- // or multiple lists have to be cancelled.
- var listGroups = [],
- database = {};
-
- while ( ranges.length > 0 )
- {
- range = ranges.shift();
-
- var boundaryNodes = range.getBoundaryNodes(),
- startNode = boundaryNodes.startNode,
- endNode = boundaryNodes.endNode;
-
- if ( startNode.type == CKEDITOR.NODE_ELEMENT && startNode.getName() == 'td' )
- range.setStartAt( boundaryNodes.startNode, CKEDITOR.POSITION_AFTER_START );
-
- if ( endNode.type == CKEDITOR.NODE_ELEMENT && endNode.getName() == 'td' )
- range.setEndAt( boundaryNodes.endNode, CKEDITOR.POSITION_BEFORE_END );
-
- var iterator = range.createIterator(),
- block;
-
- iterator.forceBrBreak = ( this.state == CKEDITOR.TRISTATE_OFF );
-
- 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 = pathElementsCount - 1; i >= 0 && ( element = pathElements[ i ] ); i-- )
- {
- 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
- // no longer be valid. Since paragraphs after the list
- // should belong to a different group of paragraphs before
- // the list. (Bug #1309)
- blockLimit.removeCustomData( 'list_group_object' );
-
- var groupObj = element.getCustomData( 'list_group_object' );
- if ( groupObj )
- groupObj.contents.push( block );
- else
- {
- groupObj = { root : element, contents : [ block ] };
- listGroups.push( groupObj );
- CKEDITOR.dom.element.setMarker( database, element, 'list_group_object', groupObj );
- }
- processedFlag = true;
- break;
- }
- }
-
- if ( processedFlag )
- continue;
-
- // No list ancestor? Group by block limit.
- var root = blockLimit;
- if ( root.getCustomData( 'list_group_object' ) )
- root.getCustomData( 'list_group_object' ).contents.push( block );
- else
- {
- groupObj = { root : root, contents : [ block ] };
- CKEDITOR.dom.element.setMarker( database, root, 'list_group_object', groupObj );
- listGroups.push( groupObj );
- }
- }
- }
-
- // Now we have two kinds of list groups, groups rooted at a list, and groups rooted at a block limit element.
- // We either have to build lists or remove lists, for removing a list does not makes sense when we are looking
- // at the group that's not rooted at lists. So we have three cases to handle.
- var listsCreated = [];
- while ( listGroups.length > 0 )
- {
- groupObj = listGroups.shift();
- if ( this.state == CKEDITOR.TRISTATE_OFF )
- {
- if ( listNodeNames[ groupObj.root.getName() ] )
- changeListType.call( this, editor, groupObj, database, listsCreated );
- else
- createList.call( this, editor, groupObj, listsCreated );
- }
- else if ( this.state == CKEDITOR.TRISTATE_ON && listNodeNames[ groupObj.root.getName() ] )
- removeList.call( this, editor, groupObj, database );
- }
-
- // For all new lists created, merge adjacent, same type lists.
- for ( i = 0 ; i < listsCreated.length ; i++ )
- {
- listNode = listsCreated[i];
- var mergeSibling, listCommand = this;
- ( mergeSibling = function( rtl ){
-
- var sibling = listNode[ rtl ?
- 'getPrevious' : 'getNext' ]( CKEDITOR.dom.walker.whitespaces( true ) );
- if ( sibling && sibling.getName &&
- sibling.getName() == listCommand.type )
- {
- sibling.remove();
- // Move children order by merge direction.(#3820)
- sibling.moveChildren( listNode, rtl ? true : false );
- }
- } )();
- mergeSibling( true );
- }
-
- // Clean up, restore selection and update toolbar button states.
- CKEDITOR.dom.element.clearAllMarkers( database );
- selection.selectBookmarks( bookmarks );
- editor.focus();
- }
- };
-
- 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 )
- {
- // Register commands.
- var numberedListCommand = new listCommand( 'numberedlist', 'ol' ),
- bulletedListCommand = new listCommand( 'bulletedlist', 'ul' );
- editor.addCommand( 'numberedlist', numberedListCommand );
- editor.addCommand( 'bulletedlist', bulletedListCommand );
-
- // Register the toolbar button.
- editor.ui.addButton( 'NumberedList',
- {
- label : editor.lang.numberedlist,
- command : 'numberedlist'
- } );
- editor.ui.addButton( 'BulletedList',
- {
- label : editor.lang.bulletedlist,
- command : 'bulletedlist'
- } );
-
- // Register the state changing handlers.
- editor.on( 'selectionChange', CKEDITOR.tools.bind( onSelectionChange, numberedListCommand ) );
- 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' ]
- } );
-})();
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @file Insert and remove numbered and bulleted lists. + */ + +(function() +{ + var listNodeNames = { ol : 1, ul : 1 }, + emptyTextRegex = /^[\n\r\t ]*$/; + + CKEDITOR.plugins.list = { + /* + * Convert a DOM list tree into a data structure that is easier to + * manipulate. This operation should be non-intrusive in the sense that it + * does not change the DOM tree, with the exception that it may add some + * markers to the list item nodes when database is specified. + */ + listToArray : function( listNode, database, baseArray, baseIndentLevel, grandparentNode ) + { + if ( !listNodeNames[ listNode.getName() ] ) + return []; + + if ( !baseIndentLevel ) + baseIndentLevel = 0; + if ( !baseArray ) + baseArray = []; + + // Iterate over all list items to and look for inner lists. + for ( var i = 0, count = listNode.getChildCount() ; i < count ; i++ ) + { + var listItem = listNode.getChild( i ); + + // It may be a text node or some funny stuff. + if ( listItem.$.nodeName.toLowerCase() != 'li' ) + continue; + + var itemObj = { 'parent' : listNode, indent : baseIndentLevel, element : listItem, contents : [] }; + if ( !grandparentNode ) + { + itemObj.grandparent = listNode.getParent(); + if ( itemObj.grandparent && itemObj.grandparent.$.nodeName.toLowerCase() == 'li' ) + itemObj.grandparent = itemObj.grandparent.getParent(); + } + else + itemObj.grandparent = grandparentNode; + + if ( database ) + CKEDITOR.dom.element.setMarker( database, listItem, 'listarray_index', baseArray.length ); + baseArray.push( itemObj ); + + for ( var j = 0, itemChildCount = listItem.getChildCount(), child; j < itemChildCount ; j++ ) + { + child = listItem.getChild( j ); + if ( child.type == CKEDITOR.NODE_ELEMENT && listNodeNames[ child.getName() ] ) + // Note the recursion here, it pushes inner list items with + // +1 indentation in the correct order. + CKEDITOR.plugins.list.listToArray( child, database, baseArray, baseIndentLevel + 1, itemObj.grandparent ); + else + itemObj.contents.push( child ); + } + } + return baseArray; + }, + + // Convert our internal representation of a list back to a DOM forest. + arrayToList : function( listArray, database, baseIndex, paragraphMode ) + { + if ( !baseIndex ) + baseIndex = 0; + if ( !listArray || listArray.length < baseIndex + 1 ) + return null; + var doc = listArray[ baseIndex ].parent.getDocument(), + retval = new CKEDITOR.dom.documentFragment( doc ), + rootNode = null, + currentIndex = baseIndex, + indentLevel = Math.max( listArray[ baseIndex ].indent, 0 ), + currentListItem = null, + paragraphName = ( paragraphMode == CKEDITOR.ENTER_P ? 'p' : 'div' ); + while ( true ) + { + var item = listArray[ currentIndex ]; + if ( item.indent == indentLevel ) + { + if ( !rootNode || listArray[ currentIndex ].parent.getName() != rootNode.getName() ) + { + rootNode = listArray[ currentIndex ].parent.clone( false, true ); + retval.append( rootNode ); + } + currentListItem = rootNode.append( item.element.clone( false, true ) ); + for ( var i = 0 ; i < item.contents.length ; i++ ) + currentListItem.append( item.contents[i].clone( true, true ) ); + currentIndex++; + } + else if ( item.indent == Math.max( indentLevel, 0 ) + 1 ) + { + var listData = CKEDITOR.plugins.list.arrayToList( listArray, null, currentIndex, paragraphMode ); + currentListItem.append( listData.listNode ); + currentIndex = listData.nextIndex; + } + else if ( item.indent == -1 && !baseIndex && item.grandparent ) + { + currentListItem; + if ( listNodeNames[ item.grandparent.getName() ] ) + currentListItem = item.element.clone( false, true ); + else + { + // Create completely new blocks here, attributes are dropped. + if ( paragraphMode != CKEDITOR.ENTER_BR && item.grandparent.getName() != 'td' ) + currentListItem = doc.createElement( paragraphName ); + else + currentListItem = new CKEDITOR.dom.documentFragment( doc ); + } + + for ( i = 0 ; i < item.contents.length ; i++ ) + currentListItem.append( item.contents[i].clone( true, true ) ); + + if ( currentListItem.type == CKEDITOR.NODE_DOCUMENT_FRAGMENT + && currentIndex != listArray.length - 1 ) + { + if ( currentListItem.getLast() + && currentListItem.getLast().type == CKEDITOR.NODE_ELEMENT + && currentListItem.getLast().getAttribute( 'type' ) == '_moz' ) + currentListItem.getLast().remove(); + currentListItem.appendBogus(); + } + + if ( currentListItem.type == CKEDITOR.NODE_ELEMENT && + currentListItem.getName() == paragraphName && + currentListItem.$.firstChild ) + { + currentListItem.trim(); + var firstChild = currentListItem.getFirst(); + if ( firstChild.type == CKEDITOR.NODE_ELEMENT && firstChild.isBlockBoundary() ) + { + var tmp = new CKEDITOR.dom.documentFragment( doc ); + currentListItem.moveChildren( tmp ); + currentListItem = tmp; + } + } + + var currentListItemName = currentListItem.$.nodeName.toLowerCase(); + if ( !CKEDITOR.env.ie && ( currentListItemName == 'div' || currentListItemName == 'p' ) ) + currentListItem.appendBogus(); + retval.append( currentListItem ); + rootNode = null; + currentIndex++; + } + else + return null; + + if ( listArray.length <= currentIndex || Math.max( listArray[ currentIndex ].indent, 0 ) < indentLevel ) + break; + } + + // Clear marker attributes for the new list tree made of cloned nodes, if any. + if ( database ) + { + var currentNode = retval.getFirst(); + while ( currentNode ) + { + if ( currentNode.type == CKEDITOR.NODE_ELEMENT ) + CKEDITOR.dom.element.clearMarkers( database, currentNode ); + currentNode = currentNode.getNextSourceNode(); + } + } + + return { listNode : retval, nextIndex : currentIndex }; + } + }; + + function setState( editor, state ) + { + editor.getCommand( this.name ).setState( state ); + } + + function onSelectionChange( evt ) + { + var path = evt.data.path, + blockLimit = path.blockLimit, + elements = path.elements, + element; + + // Grouping should only happen under blockLimit.(#3940). + for ( var i = 0 ; i < elements.length && ( element = elements[ i ] ) + && !element.equals( blockLimit ); i++ ) + { + if ( listNodeNames[ elements[i].getName() ] ) + { + return setState.call( this, evt.editor, + this.type == elements[i].getName() ? CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF ); + } + } + + return setState.call( this, evt.editor, CKEDITOR.TRISTATE_OFF ); + } + + function changeListType( editor, groupObj, database, listsCreated ) + { + // This case is easy... + // 1. Convert the whole list into a one-dimensional array. + // 2. Change the list type by modifying the array. + // 3. Recreate the whole list by converting the array to a list. + // 4. Replace the original list with the recreated list. + var listArray = CKEDITOR.plugins.list.listToArray( groupObj.root, database ), + selectedListItems = []; + + for ( var i = 0 ; i < groupObj.contents.length ; i++ ) + { + var itemNode = groupObj.contents[i]; + itemNode = itemNode.getAscendant( 'li', true ); + if ( !itemNode || itemNode.getCustomData( 'list_item_processed' ) ) + continue; + selectedListItems.push( itemNode ); + CKEDITOR.dom.element.setMarker( database, itemNode, 'list_item_processed', true ); + } + + var fakeParent = groupObj.root.getDocument().createElement( this.type ); + for ( i = 0 ; i < selectedListItems.length ; i++ ) + { + var listIndex = selectedListItems[i].getCustomData( 'listarray_index' ); + listArray[listIndex].parent = fakeParent; + } + var newList = CKEDITOR.plugins.list.arrayToList( listArray, database, null, editor.config.enterMode ); + var child, length = newList.listNode.getChildCount(); + for ( i = 0 ; i < length && ( child = newList.listNode.getChild( i ) ) ; i++ ) + { + if ( child.getName() == this.type ) + listsCreated.push( child ); + } + newList.listNode.replace( groupObj.root ); + } + + var headerTagRegex = /^h[1-6]$/; + + function createList( editor, groupObj, listsCreated ) + { + var contents = groupObj.contents, + doc = groupObj.root.getDocument(), + listContents = []; + + // It is possible to have the contents returned by DomRangeIterator to be the same as the root. + // e.g. when we're running into table cells. + // In such a case, enclose the childNodes of contents[0] into a <div>. + if ( contents.length == 1 && contents[0].equals( groupObj.root ) ) + { + var divBlock = doc.createElement( 'div' ); + contents[0].moveChildren && contents[0].moveChildren( divBlock ); + contents[0].append( divBlock ); + contents[0] = divBlock; + } + + // Calculate the common parent node of all content blocks. + var commonParent = groupObj.contents[0].getParent(); + for ( var i = 0 ; i < contents.length ; i++ ) + commonParent = commonParent.getCommonAncestor( contents[i].getParent() ); + + // We want to insert things that are in the same tree level only, so calculate the contents again + // by expanding the selected blocks to the same tree level. + for ( i = 0 ; i < contents.length ; i++ ) + { + var contentNode = contents[i], + parentNode; + while ( ( parentNode = contentNode.getParent() ) ) + { + if ( parentNode.equals( commonParent ) ) + { + listContents.push( contentNode ); + break; + } + contentNode = parentNode; + } + } + + if ( listContents.length < 1 ) + return; + + // Insert the list to the DOM tree. + var insertAnchor = listContents[ listContents.length - 1 ].getNext(), + listNode = doc.createElement( this.type ); + + listsCreated.push( listNode ); + while ( listContents.length ) + { + var contentBlock = listContents.shift(), + listItem = doc.createElement( 'li' ); + + // Preserve heading structure when converting to list item. (#5271) + if ( headerTagRegex.test( contentBlock.getName() ) ) + contentBlock.appendTo( listItem ); + else + { + contentBlock.copyAttributes( listItem ); + contentBlock.moveChildren( listItem ); + contentBlock.remove(); + } + + listItem.appendTo( listNode ); + + // Append a bogus BR to force the LI to render at full height + if ( !CKEDITOR.env.ie ) + listItem.appendBogus(); + } + if ( insertAnchor ) + listNode.insertBefore( insertAnchor ); + else + listNode.appendTo( commonParent ); + } + + function removeList( editor, groupObj, database ) + { + // This is very much like the change list type operation. + // Except that we're changing the selected items' indent to -1 in the list array. + var listArray = CKEDITOR.plugins.list.listToArray( groupObj.root, database ), + selectedListItems = []; + + for ( var i = 0 ; i < groupObj.contents.length ; i++ ) + { + var itemNode = groupObj.contents[i]; + itemNode = itemNode.getAscendant( 'li', true ); + if ( !itemNode || itemNode.getCustomData( 'list_item_processed' ) ) + continue; + selectedListItems.push( itemNode ); + CKEDITOR.dom.element.setMarker( database, itemNode, 'list_item_processed', true ); + } + + var lastListIndex = null; + for ( i = 0 ; i < selectedListItems.length ; i++ ) + { + var listIndex = selectedListItems[i].getCustomData( 'listarray_index' ); + listArray[listIndex].indent = -1; + lastListIndex = listIndex; + } + + // After cutting parts of the list out with indent=-1, we still have to maintain the array list + // model's nextItem.indent <= currentItem.indent + 1 invariant. Otherwise the array model of the + // list cannot be converted back to a real DOM list. + for ( i = lastListIndex + 1 ; i < listArray.length ; i++ ) + { + if ( listArray[i].indent > listArray[i-1].indent + 1 ) + { + var indentOffset = listArray[i-1].indent + 1 - listArray[i].indent; + var oldIndent = listArray[i].indent; + while ( listArray[i] && listArray[i].indent >= oldIndent ) + { + listArray[i].indent += indentOffset; + i++; + } + i--; + } + } + + var newList = CKEDITOR.plugins.list.arrayToList( listArray, database, null, editor.config.enterMode ); + + // Compensate <br> before/after the list node if the surrounds are non-blocks.(#3836) + var docFragment = newList.listNode, boundaryNode, siblingNode; + function compensateBrs( isStart ) + { + if ( ( boundaryNode = docFragment[ isStart ? 'getFirst' : 'getLast' ]() ) + && !( boundaryNode.is && boundaryNode.isBlockBoundary() ) + && ( siblingNode = groupObj.root[ isStart ? 'getPrevious' : 'getNext' ] + ( CKEDITOR.dom.walker.whitespaces( true ) ) ) + && !( siblingNode.is && siblingNode.isBlockBoundary( { br : 1 } ) ) ) + editor.document.createElement( 'br' )[ isStart ? 'insertBefore' : 'insertAfter' ]( boundaryNode ); + } + compensateBrs( true ); + compensateBrs(); + + docFragment.replace( groupObj.root ); + } + + function listCommand( name, type ) + { + this.name = name; + this.type = type; + } + + listCommand.prototype = { + exec : function( editor ) + { + editor.focus(); + + var doc = editor.document, + selection = editor.getSelection(), + ranges = selection && selection.getRanges(); + + // There should be at least one selected range. + if ( !ranges || ranges.length < 1 ) + return; + + // Midas lists rule #1 says we can create a list even in an empty document. + // But DOM iterator wouldn't run if the document is really empty. + // So create a paragraph if the document is empty and we're going to create a list. + if ( this.state == CKEDITOR.TRISTATE_OFF ) + { + var body = doc.getBody(); + body.trim(); + if ( !body.getFirst() ) + { + var paragraph = doc.createElement( editor.config.enterMode == CKEDITOR.ENTER_P ? 'p' : + ( editor.config.enterMode == CKEDITOR.ENTER_DIV ? 'div' : 'br' ) ); + paragraph.appendTo( body ); + ranges = [ new CKEDITOR.dom.range( doc ) ]; + // IE exception on inserting anything when anchor inside <br>. + if ( paragraph.is( 'br' ) ) + { + ranges[ 0 ].setStartBefore( paragraph ); + ranges[ 0 ].setEndAfter( paragraph ); + } + else + ranges[ 0 ].selectNodeContents( paragraph ); + selection.selectRanges( ranges ); + } + // Maybe a single range there enclosing the whole list, + // turn on the list state manually(#4129). + else + { + var range = ranges.length == 1 && ranges[ 0 ], + enclosedNode = range && range.getEnclosedNode(); + if ( enclosedNode && enclosedNode.is + && this.type == enclosedNode.getName() ) + { + setState.call( this, editor, CKEDITOR.TRISTATE_ON ); + } + } + } + + var bookmarks = selection.createBookmarks( true ); + + // Group the blocks up because there are many cases where multiple lists have to be created, + // or multiple lists have to be cancelled. + var listGroups = [], + database = {}; + + while ( ranges.length > 0 ) + { + range = ranges.shift(); + + var boundaryNodes = range.getBoundaryNodes(), + startNode = boundaryNodes.startNode, + endNode = boundaryNodes.endNode; + + if ( startNode.type == CKEDITOR.NODE_ELEMENT && startNode.getName() == 'td' ) + range.setStartAt( boundaryNodes.startNode, CKEDITOR.POSITION_AFTER_START ); + + if ( endNode.type == CKEDITOR.NODE_ELEMENT && endNode.getName() == 'td' ) + range.setEndAt( boundaryNodes.endNode, CKEDITOR.POSITION_BEFORE_END ); + + var iterator = range.createIterator(), + block; + + iterator.forceBrBreak = ( this.state == CKEDITOR.TRISTATE_OFF ); + + 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 = pathElementsCount - 1; i >= 0 && ( element = pathElements[ i ] ); i-- ) + { + 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 + // no longer be valid. Since paragraphs after the list + // should belong to a different group of paragraphs before + // the list. (Bug #1309) + blockLimit.removeCustomData( 'list_group_object' ); + + var groupObj = element.getCustomData( 'list_group_object' ); + if ( groupObj ) + groupObj.contents.push( block ); + else + { + groupObj = { root : element, contents : [ block ] }; + listGroups.push( groupObj ); + CKEDITOR.dom.element.setMarker( database, element, 'list_group_object', groupObj ); + } + processedFlag = true; + break; + } + } + + if ( processedFlag ) + continue; + + // No list ancestor? Group by block limit. + var root = blockLimit; + if ( root.getCustomData( 'list_group_object' ) ) + root.getCustomData( 'list_group_object' ).contents.push( block ); + else + { + groupObj = { root : root, contents : [ block ] }; + CKEDITOR.dom.element.setMarker( database, root, 'list_group_object', groupObj ); + listGroups.push( groupObj ); + } + } + } + + // Now we have two kinds of list groups, groups rooted at a list, and groups rooted at a block limit element. + // We either have to build lists or remove lists, for removing a list does not makes sense when we are looking + // at the group that's not rooted at lists. So we have three cases to handle. + var listsCreated = []; + while ( listGroups.length > 0 ) + { + groupObj = listGroups.shift(); + if ( this.state == CKEDITOR.TRISTATE_OFF ) + { + if ( listNodeNames[ groupObj.root.getName() ] ) + changeListType.call( this, editor, groupObj, database, listsCreated ); + else + createList.call( this, editor, groupObj, listsCreated ); + } + else if ( this.state == CKEDITOR.TRISTATE_ON && listNodeNames[ groupObj.root.getName() ] ) + removeList.call( this, editor, groupObj, database ); + } + + // For all new lists created, merge adjacent, same type lists. + for ( i = 0 ; i < listsCreated.length ; i++ ) + { + listNode = listsCreated[i]; + var mergeSibling, listCommand = this; + ( mergeSibling = function( rtl ){ + + var sibling = listNode[ rtl ? + 'getPrevious' : 'getNext' ]( CKEDITOR.dom.walker.whitespaces( true ) ); + if ( sibling && sibling.getName && + sibling.getName() == listCommand.type ) + { + sibling.remove(); + // Move children order by merge direction.(#3820) + sibling.moveChildren( listNode, rtl ? true : false ); + } + } )(); + mergeSibling( true ); + } + + // Clean up, restore selection and update toolbar button states. + CKEDITOR.dom.element.clearAllMarkers( database ); + selection.selectBookmarks( bookmarks ); + editor.focus(); + } + }; + + 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 ) + { + // Register commands. + var numberedListCommand = new listCommand( 'numberedlist', 'ol' ), + bulletedListCommand = new listCommand( 'bulletedlist', 'ul' ); + editor.addCommand( 'numberedlist', numberedListCommand ); + editor.addCommand( 'bulletedlist', bulletedListCommand ); + + // Register the toolbar button. + editor.ui.addButton( 'NumberedList', + { + label : editor.lang.numberedlist, + command : 'numberedlist' + } ); + editor.ui.addButton( 'BulletedList', + { + label : editor.lang.bulletedlist, + command : 'bulletedlist' + } ); + + // Register the state changing handlers. + editor.on( 'selectionChange', CKEDITOR.tools.bind( onSelectionChange, numberedListCommand ) ); + 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 848cc2a..fc3909b 100644 --- a/_source/plugins/listblock/plugin.js +++ b/_source/plugins/listblock/plugin.js @@ -1,252 +1,257 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-CKEDITOR.plugins.add( 'listblock',
-{
- requires : [ 'panel' ],
-
- onLoad : function()
- {
- CKEDITOR.ui.panel.prototype.addListBlock = function( name, definiton )
- {
- return this.addBlock( name, new CKEDITOR.ui.listBlock( this.getHolderElement(), definiton ) );
- };
-
- CKEDITOR.ui.listBlock = CKEDITOR.tools.createClass(
- {
- base : CKEDITOR.ui.panel.block,
-
- $ : function( blockHolder, blockDefinition )
- {
- blockDefinition = blockDefinition || {};
-
- var attribs = blockDefinition.attributes || ( blockDefinition.attributes = {} );
- ( this.multiSelect = !!blockDefinition.multiSelect ) &&
- ( attribs[ 'aria-multiselectable' ] = true );
- // Provide default role of 'listbox'.
- !attribs.role && ( attribs.role = 'listbox' );
-
- // Call the base contructor.
- this.base.apply( this, arguments );
-
- var keys = this.keys;
- keys[ 40 ] = 'next'; // ARROW-DOWN
- keys[ 9 ] = 'next'; // TAB
- keys[ 38 ] = 'prev'; // ARROW-UP
- keys[ CKEDITOR.SHIFT + 9 ] = 'prev'; // SHIFT + TAB
- keys[ 32 ] = 'click'; // SPACE
-
- this._.pendingHtml = [];
- this._.items = {};
- this._.groups = {};
- },
-
- _ :
- {
- close : function()
- {
- if ( this._.started )
- {
- this._.pendingHtml.push( '</ul>' );
- delete this._.started;
- }
- },
-
- getClick : function()
- {
- if ( !this._.click )
- {
- this._.click = CKEDITOR.tools.addFunction( function( value )
- {
- var marked = true;
-
- if ( this.multiSelect )
- marked = this.toggle( value );
- else
- this.mark( value );
-
- if ( this.onClick )
- this.onClick( value, marked );
- },
- this );
- }
- return this._.click;
- }
- },
-
- proto :
- {
- add : function( value, html, title )
- {
- var pendingHtml = this._.pendingHtml,
- id = 'cke_' + CKEDITOR.tools.getNextNumber();
-
- if ( !this._.started )
- {
- pendingHtml.push( '<ul role="presentation" class=cke_panel_list>' );
- this._.started = 1;
- this._.size = this._.size || 0;
- }
-
- this._.items[ value ] = id;
-
- pendingHtml.push(
- '<li id=', id, ' class=cke_panel_listItem>' +
- '<a id="', id, '_option" _cke_focus=1 hidefocus=true' +
- ' title="', title || value, '"' +
- ' href="javascript:void(\'', value, '\')"' +
- ' onclick="CKEDITOR.tools.callFunction(', this._.getClick(), ',\'', value, '\'); return false;"',
- ' role="option"' +
- ' aria-posinset="' + ++this._.size + '">',
- html || value,
- '</a>' +
- '</li>' );
- },
-
- startGroup : function( title )
- {
- this._.close();
-
- var id = 'cke_' + CKEDITOR.tools.getNextNumber();
-
- this._.groups[ title ] = id;
-
- this._.pendingHtml.push( '<h1 role="presentation" id=', id, ' class=cke_panel_grouptitle>', title, '</h1>' );
- },
-
- commit : function()
- {
- this._.close();
- this.element.appendHtml( this._.pendingHtml.join( '' ) );
-
- var items = this._.items,
- doc = this.element.getDocument();
- for ( var value in items )
- doc.getById( items[ value ] + '_option' ).setAttribute( 'aria-setsize', this._.size );
- delete this._.size;
-
- this._.pendingHtml = [];
- },
-
- toggle : function( value )
- {
- var isMarked = this.isMarked( value );
-
- if ( isMarked )
- this.unmark( value );
- else
- this.mark( value );
-
- return !isMarked;
- },
-
- hideGroup : function( groupTitle )
- {
- var group = this.element.getDocument().getById( this._.groups[ groupTitle ] ),
- list = group && group.getNext();
-
- if ( group )
- {
- group.setStyle( 'display', 'none' );
-
- if ( list && list.getName() == 'ul' )
- list.setStyle( 'display', 'none' );
- }
- },
-
- hideItem : function( value )
- {
- this.element.getDocument().getById( this._.items[ value ] ).setStyle( 'display', 'none' );
- },
-
- showAll : function()
- {
- var items = this._.items,
- groups = this._.groups,
- doc = this.element.getDocument();
-
- for ( var value in items )
- {
- doc.getById( items[ value ] ).setStyle( 'display', '' );
- }
-
- for ( var title in groups )
- {
- var group = doc.getById( groups[ title ] ),
- list = group.getNext();
-
- group.setStyle( 'display', '' );
-
- if ( list && list.getName() == 'ul' )
- list.setStyle( 'display', '' );
- }
- },
-
- mark : function( value )
- {
- if ( !this.multiSelect )
- this.unmarkAll();
-
- var itemId = this._.items[ value ],
- item = this.element.getDocument().getById( itemId );
- item.addClass( 'cke_selected' );
-
- this.element.getDocument().getById( itemId + '_option' ).setAttribute( 'aria-selected', true );
- this.element.setAttribute( 'aria-activedescendant', itemId + '_option' );
- },
-
- unmark : function( value )
- {
- this.element.getDocument().getById( this._.items[ value ] ).removeClass( 'cke_selected' );
- },
-
- unmarkAll : function()
- {
- var items = this._.items,
- doc = this.element.getDocument();
-
- for ( var value in items )
- {
- doc.getById( items[ value ] ).removeClass( 'cke_selected' );
- }
- },
-
- isMarked : function( value )
- {
- return this.element.getDocument().getById( this._.items[ value ] ).hasClass( 'cke_selected' );
- },
-
- focus : function( value )
- {
- this._.focusIndex = -1;
-
- if ( value )
- {
- var selected = this.element.getDocument().getById( this._.items[ value ] ).getFirst();
-
- var links = this.element.getElementsByTag( 'a' ),
- link,
- i = -1;
-
- while ( ( link = links.getItem( ++i ) ) )
- {
- if ( link.equals( selected ) )
- {
- this._.focusIndex = i;
- break;
- }
- }
-
- setTimeout( function()
- {
- selected.focus();
- },
- 0 );
- }
- }
- }
- });
- }
-});
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.plugins.add( 'listblock', +{ + requires : [ 'panel' ], + + onLoad : function() + { + CKEDITOR.ui.panel.prototype.addListBlock = function( name, definition ) + { + return this.addBlock( name, new CKEDITOR.ui.listBlock( this.getHolderElement(), definition ) ); + }; + + CKEDITOR.ui.listBlock = CKEDITOR.tools.createClass( + { + base : CKEDITOR.ui.panel.block, + + $ : function( blockHolder, blockDefinition ) + { + blockDefinition = blockDefinition || {}; + + var attribs = blockDefinition.attributes || ( blockDefinition.attributes = {} ); + ( this.multiSelect = !!blockDefinition.multiSelect ) && + ( attribs[ 'aria-multiselectable' ] = true ); + // Provide default role of 'listbox'. + !attribs.role && ( attribs.role = 'listbox' ); + + // Call the base contructor. + this.base.apply( this, arguments ); + + var keys = this.keys; + keys[ 40 ] = 'next'; // ARROW-DOWN + keys[ 9 ] = 'next'; // TAB + keys[ 38 ] = 'prev'; // ARROW-UP + keys[ CKEDITOR.SHIFT + 9 ] = 'prev'; // SHIFT + TAB + keys[ 32 ] = 'click'; // SPACE + + this._.pendingHtml = []; + this._.items = {}; + this._.groups = {}; + }, + + _ : + { + close : function() + { + if ( this._.started ) + { + this._.pendingHtml.push( '</ul>' ); + delete this._.started; + } + }, + + getClick : function() + { + if ( !this._.click ) + { + this._.click = CKEDITOR.tools.addFunction( function( value ) + { + var marked = true; + + if ( this.multiSelect ) + marked = this.toggle( value ); + else + this.mark( value ); + + if ( this.onClick ) + this.onClick( value, marked ); + }, + this ); + } + return this._.click; + } + }, + + proto : + { + add : function( value, html, title ) + { + var pendingHtml = this._.pendingHtml, + id = 'cke_' + CKEDITOR.tools.getNextNumber(); + + if ( !this._.started ) + { + pendingHtml.push( '<ul role="presentation" class=cke_panel_list>' ); + this._.started = 1; + this._.size = this._.size || 0; + } + + this._.items[ value ] = id; + + pendingHtml.push( + '<li id=', id, ' class=cke_panel_listItem>' + + '<a id="', id, '_option" _cke_focus=1 hidefocus=true' + + ' title="', title || value, '"' + + ' href="javascript:void(\'', value, '\')"' + + ' onclick="CKEDITOR.tools.callFunction(', this._.getClick(), ',\'', value, '\'); return false;"', + ' role="option"' + + ' aria-posinset="' + ++this._.size + '">', + html || value, + '</a>' + + '</li>' ); + }, + + startGroup : function( title ) + { + this._.close(); + + var id = 'cke_' + CKEDITOR.tools.getNextNumber(); + + this._.groups[ title ] = id; + + this._.pendingHtml.push( '<h1 role="presentation" id=', id, ' class=cke_panel_grouptitle>', title, '</h1>' ); + }, + + commit : function() + { + this._.close(); + this.element.appendHtml( this._.pendingHtml.join( '' ) ); + + var items = this._.items, + doc = this.element.getDocument(); + for ( var value in items ) + doc.getById( items[ value ] + '_option' ).setAttribute( 'aria-setsize', this._.size ); + delete this._.size; + + this._.pendingHtml = []; + }, + + toggle : function( value ) + { + var isMarked = this.isMarked( value ); + + if ( isMarked ) + this.unmark( value ); + else + this.mark( value ); + + return !isMarked; + }, + + hideGroup : function( groupTitle ) + { + var group = this.element.getDocument().getById( this._.groups[ groupTitle ] ), + list = group && group.getNext(); + + if ( group ) + { + group.setStyle( 'display', 'none' ); + + if ( list && list.getName() == 'ul' ) + list.setStyle( 'display', 'none' ); + } + }, + + hideItem : function( value ) + { + this.element.getDocument().getById( this._.items[ value ] ).setStyle( 'display', 'none' ); + }, + + showAll : function() + { + var items = this._.items, + groups = this._.groups, + doc = this.element.getDocument(); + + for ( var value in items ) + { + doc.getById( items[ value ] ).setStyle( 'display', '' ); + } + + for ( var title in groups ) + { + var group = doc.getById( groups[ title ] ), + list = group.getNext(); + + group.setStyle( 'display', '' ); + + if ( list && list.getName() == 'ul' ) + list.setStyle( 'display', '' ); + } + }, + + mark : function( value ) + { + if ( !this.multiSelect ) + this.unmarkAll(); + + var itemId = this._.items[ value ], + item = this.element.getDocument().getById( itemId ); + item.addClass( 'cke_selected' ); + + this.element.getDocument().getById( itemId + '_option' ).setAttribute( 'aria-selected', true ); + this.element.setAttribute( 'aria-activedescendant', itemId + '_option' ); + + this.onMark && this.onMark( item ); + }, + + unmark : function( value ) + { + this.element.getDocument().getById( this._.items[ value ] ).removeClass( 'cke_selected' ); + this.onUnmark && this.onUnmark( this._.items[ value ] ); + }, + + unmarkAll : function() + { + var items = this._.items, + doc = this.element.getDocument(); + + for ( var value in items ) + { + doc.getById( items[ value ] ).removeClass( 'cke_selected' ); + } + + this.onUnmark && this.onUnmark(); + }, + + isMarked : function( value ) + { + return this.element.getDocument().getById( this._.items[ value ] ).hasClass( 'cke_selected' ); + }, + + focus : function( value ) + { + this._.focusIndex = -1; + + if ( value ) + { + var selected = this.element.getDocument().getById( this._.items[ value ] ).getFirst(); + + var links = this.element.getElementsByTag( 'a' ), + link, + i = -1; + + while ( ( link = links.getItem( ++i ) ) ) + { + if ( link.equals( selected ) ) + { + this._.focusIndex = i; + break; + } + } + + setTimeout( function() + { + selected.focus(); + }, + 0 ); + } + } + } + }); + } +}); diff --git a/_source/plugins/liststyle/dialogs/liststyle.js b/_source/plugins/liststyle/dialogs/liststyle.js new file mode 100644 index 0000000..6430ac4 --- /dev/null +++ b/_source/plugins/liststyle/dialogs/liststyle.js @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.html or http://ckeditor.com/license + */ + +(function() +{ + function getListElement( editor, listTag ) + { + var range; + try { range = editor.getSelection().getRanges()[ 0 ]; } + catch( e ) { return null; } + + range.shrink( CKEDITOR.SHRINK_TEXT ); + return range.getCommonAncestor().getAscendant( listTag, true ); + } + + var mapListStyle = { + 'a' : 'lower-alpha', + 'A' : 'upper-alpha', + 'i' : 'lower-roman', + 'I' : 'upper-roman', + '1' : 'decimal', + 'disc' : 'disc', + 'circle': 'circle', + 'square' : 'square' + }; + + function listStyle( editor, startupPage ) + { + if ( startupPage == 'bulletedListStyle' ) + { + return { + title : editor.lang.list.bulletedTitle, + minWidth : 300, + minHeight : 50, + contents : + [ + { + id : 'info', + accessKey : 'I', + elements : + [ + { + type : 'select', + label : editor.lang.list.type, + id : 'type', + style : 'width: 150px; margin: auto;', + items : + [ + [ editor.lang.list.notset, '' ], + [ editor.lang.list.circle, 'circle' ], + [ editor.lang.list.disc, 'disc' ], + [ editor.lang.list.square, 'square' ] + ], + setup : function( element ) + { + var value = element.getStyle( 'list-style-type' ) + || mapListStyle[ element.getAttribute( 'type' ) ] + || element.getAttribute( 'type' ) + || ''; + + this.setValue( value ); + }, + commit : function( element ) + { + var value = this.getValue(); + if ( value ) + element.setStyle( 'list-style-type', value ); + else + element.removeStyle( 'list-style-type' ); + } + } + ] + } + ], + onShow: function() + { + var editor = this.getParentEditor(), + element = getListElement( editor, 'ul' ); + + element && this.setupContent( element ); + }, + onOk: function() + { + var editor = this.getParentEditor(), + element = getListElement( editor, 'ul' ); + + element && this.commitContent( element ); + } + }; + } + else if ( startupPage == 'numberedListStyle' ) + { + + var listStyleOptions = + [ + [ editor.lang.list.notset, '' ], + [ editor.lang.list.lowerRoman, 'lower-roman' ], + [ editor.lang.list.upperRoman, 'upper-roman' ], + [ editor.lang.list.lowerAlpha, 'lower-alpha' ], + [ editor.lang.list.upperAlpha, 'upper-alpha' ], + [ editor.lang.list.decimal, 'decimal' ] + ]; + + if ( !CKEDITOR.env.ie || CKEDITOR.env.version > 7 ) + { + listStyleOptions.concat( [ + [ editor.lang.list.armenian, 'armenian' ], + [ editor.lang.list.decimalLeadingZero, 'decimal-leading-zero' ], + [ editor.lang.list.georgian, 'georgian' ], + [ editor.lang.list.lowerGreek, 'lower-greek' ] + ]); + } + + return { + title : editor.lang.list.numberedTitle, + minWidth : 300, + minHeight : 50, + contents : + [ + { + id : 'info', + accessKey : 'I', + elements : + [ + { + type : 'hbox', + widths : [ '25%', '75%' ], + children : + [ + { + label : editor.lang.list.start, + type : 'text', + id : 'start', + setup : function( element ) + { + var value = element.getAttribute( 'start' ) || 1; + value && this.setValue( value ); + }, + commit : function( element ) + { + element.setAttribute( 'start', this.getValue() ); + } + }, + { + type : 'select', + label : editor.lang.list.type, + id : 'type', + style : 'width: 100%;', + items : listStyleOptions, + setup : function( element ) + { + var value = element.getStyle( 'list-style-type' ) + || mapListStyle[ element.getAttribute( 'type' ) ] + || element.getAttribute( 'type' ) + || ''; + + this.setValue( value ); + }, + commit : function( element ) + { + var value = this.getValue(); + if ( value ) + element.setStyle( 'list-style-type', value ); + else + element.removeStyle( 'list-style-type' ); + } + } + ] + } + ] + } + ], + onShow: function() + { + var editor = this.getParentEditor(), + element = getListElement( editor, 'ol' ); + + element && this.setupContent( element ); + }, + onOk: function() + { + var editor = this.getParentEditor(), + element = getListElement( editor, 'ol' ); + + element && this.commitContent( element ); + } + }; + } + } + + CKEDITOR.dialog.add( 'numberedListStyle', function( editor ) + { + return listStyle( editor, 'numberedListStyle' ); + }); + + CKEDITOR.dialog.add( 'bulletedListStyle', function( editor ) + { + return listStyle( editor, 'bulletedListStyle' ); + }); +})(); diff --git a/_source/plugins/liststyle/plugin.js b/_source/plugins/liststyle/plugin.js new file mode 100644 index 0000000..133290f --- /dev/null +++ b/_source/plugins/liststyle/plugin.js @@ -0,0 +1,59 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +(function() +{ + CKEDITOR.plugins.liststyle = + { + init : function( editor ) + { + + editor.addCommand( 'numberedListStyle', new CKEDITOR.dialogCommand( 'numberedListStyle' ) ); + CKEDITOR.dialog.add( 'numberedListStyle', this.path + 'dialogs/liststyle.js' ); + editor.addCommand( 'bulletedListStyle', new CKEDITOR.dialogCommand( 'bulletedListStyle' ) ); + CKEDITOR.dialog.add( 'bulletedListStyle', this.path + 'dialogs/liststyle.js' ); + + //Register map group; + editor.addMenuGroup("list", 108); + // If the "menu" plugin is loaded, register the menu items. + if ( editor.addMenuItems ) + { + editor.addMenuItems( + { + numberedlist : + { + label : editor.lang.list.numberedTitle, + group : 'list', + command: 'numberedListStyle' + }, + bulletedlist : + { + label : editor.lang.list.bulletedTitle, + group : 'list', + command: 'bulletedListStyle' + } + }); + } + + // If the "contextmenu" plugin is loaded, register the listeners. + if ( editor.contextMenu ) + { + editor.contextMenu.addListener( function( element, selection ) + { + if ( !element ) + return null; + + if ( element.getAscendant( 'ol') ) + return { numberedlist: CKEDITOR.TRISTATE_OFF }; + + if ( element.getAscendant( 'ul' ) ) + return { bulletedlist: CKEDITOR.TRISTATE_OFF }; + }); + } + } + }; + + CKEDITOR.plugins.add( 'liststyle', CKEDITOR.plugins.liststyle ); +})(); diff --git a/_source/plugins/maximize/plugin.js b/_source/plugins/maximize/plugin.js index 957d991..ea769f3 100644 --- a/_source/plugins/maximize/plugin.js +++ b/_source/plugins/maximize/plugin.js @@ -1,283 +1,341 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-(function()
-{
- function protectFormStyles( formElement )
- {
- if ( !formElement || formElement.type != CKEDITOR.NODE_ELEMENT || formElement.getName() != 'form' )
- return [];
-
- var hijackRecord = [];
- var hijackNames = [ 'style', 'className' ];
- for ( var i = 0 ; i < hijackNames.length ; i++ )
- {
- var name = hijackNames[i];
- var $node = formElement.$.elements.namedItem( name );
- if ( $node )
- {
- var hijackNode = new CKEDITOR.dom.element( $node );
- hijackRecord.push( [ hijackNode, hijackNode.nextSibling ] );
- hijackNode.remove();
- }
- }
-
- return hijackRecord;
- }
-
- function restoreFormStyles( formElement, hijackRecord )
- {
- if ( !formElement || formElement.type != CKEDITOR.NODE_ELEMENT || formElement.getName() != 'form' )
- return;
-
- if ( hijackRecord.length > 0 )
- {
- for ( var i = hijackRecord.length - 1 ; i >= 0 ; i-- )
- {
- var node = hijackRecord[i][0];
- var sibling = hijackRecord[i][1];
- if ( sibling )
- node.insertBefore( sibling );
- else
- node.appendTo( formElement );
- }
- }
- }
-
- function saveStyles( element, isInsideEditor )
- {
- var data = protectFormStyles( element );
- var retval = {};
-
- var $element = element.$;
-
- if ( !isInsideEditor )
- {
- retval[ 'class' ] = $element.className || '';
- $element.className = '';
- }
-
- retval.inline = $element.style.cssText || '';
- if ( !isInsideEditor ) // Reset any external styles that might interfere. (#2474)
- $element.style.cssText = 'position: static; overflow: visible';
-
- restoreFormStyles( data );
- return retval;
- }
-
- function restoreStyles( element, savedStyles )
- {
- var data = protectFormStyles( element );
- var $element = element.$;
- if ( 'class' in savedStyles )
- $element.className = savedStyles[ 'class' ];
- if ( 'inline' in savedStyles )
- $element.style.cssText = savedStyles.inline;
- restoreFormStyles( data );
- }
-
- function getResizeHandler( mainWindow, editor )
- {
- return function()
- {
- var viewPaneSize = mainWindow.getViewPaneSize();
- editor.resize( viewPaneSize.width, viewPaneSize.height, null, true );
- };
- }
-
- CKEDITOR.plugins.add( 'maximize',
- {
- init : function( editor )
- {
- var lang = editor.lang;
- var mainDocument = CKEDITOR.document;
- var mainWindow = mainDocument.getWindow();
-
- // Saved selection and scroll position for the editing area.
- var savedSelection;
- var savedScroll;
-
- // Saved scroll position for the outer window.
- var outerScroll;
-
- // Saved resize handler function.
- var resizeHandler = getResizeHandler( mainWindow, editor );
-
- // Retain state after mode switches.
- var savedState = CKEDITOR.TRISTATE_OFF;
-
- editor.addCommand( 'maximize',
- {
- modes : { wysiwyg : 1, source : 1 },
- editorFocus : false,
- exec : function()
- {
- var container = editor.container.getChild( 1 );
- var contents = editor.getThemeSpace( 'contents' );
-
- // Save current selection and scroll position in editing area.
- if ( editor.mode == 'wysiwyg' )
- {
- var selection = editor.getSelection();
- savedSelection = selection && selection.getRanges();
- savedScroll = mainWindow.getScrollPosition();
- }
- else
- {
- var $textarea = editor.textarea.$;
- savedSelection = !CKEDITOR.env.ie && [ $textarea.selectionStart, $textarea.selectionEnd ];
- savedScroll = [ $textarea.scrollLeft, $textarea.scrollTop ];
- }
-
- if ( this.state == CKEDITOR.TRISTATE_OFF ) // Go fullscreen if the state is off.
- {
- // Add event handler for resizing.
- mainWindow.on( 'resize', resizeHandler );
-
- // Save the scroll bar position.
- outerScroll = mainWindow.getScrollPosition();
-
- // Save and reset the styles for the entire node tree.
- var currentNode = editor.container;
- while ( ( currentNode = currentNode.getParent() ) )
- {
- currentNode.setCustomData( 'maximize_saved_styles', saveStyles( currentNode ) );
- currentNode.setStyle( 'z-index', editor.config.baseFloatZIndex - 1 );
- }
- contents.setCustomData( 'maximize_saved_styles', saveStyles( contents, true ) );
- container.setCustomData( 'maximize_saved_styles', saveStyles( container, true ) );
-
- // Hide scroll bars.
- if ( CKEDITOR.env.ie )
- {
- mainDocument.$.documentElement.style.overflow =
- mainDocument.getBody().$.style.overflow = 'hidden';
- }
- else
- {
- mainDocument.getBody().setStyles(
- {
- overflow : 'hidden',
- width : '0px',
- height : '0px'
- } );
- }
-
- // 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();
- container.setStyle( 'position', 'absolute' );
- container.$.offsetLeft; // SAFARI BUG: See #2066.
- container.setStyles(
- {
- 'z-index' : editor.config.baseFloatZIndex - 1,
- left : '0px',
- top : '0px'
- } );
- editor.resize( viewPaneSize.width, viewPaneSize.height, null, true );
-
- // Still not top left? Fix it. (Bug #174)
- var offset = container.getDocumentPosition();
- container.setStyles(
- {
- left : ( -1 * offset.x ) + 'px',
- top : ( -1 * offset.y ) + 'px'
- } );
-
- // Add cke_maximized class.
- container.addClass( 'cke_maximized' );
- }
- else if ( this.state == CKEDITOR.TRISTATE_ON ) // Restore from fullscreen if the state is on.
- {
- // Remove event handler for resizing.
- mainWindow.removeListener( 'resize', resizeHandler );
-
- // Restore CSS styles for the entire node tree.
- var editorElements = [ contents, container ];
- for ( var i = 0 ; i < editorElements.length ; i++ )
- {
- restoreStyles( editorElements[i], editorElements[i].getCustomData( 'maximize_saved_styles' ) );
- editorElements[i].removeCustomData( 'maximize_saved_styles' );
- }
-
- currentNode = editor.container;
- while ( ( currentNode = currentNode.getParent() ) )
- {
- restoreStyles( currentNode, currentNode.getCustomData( 'maximize_saved_styles' ) );
- currentNode.removeCustomData( 'maximize_saved_styles' );
- }
-
- // Restore the window scroll position.
- 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' );
-
- // Emit a resize event, because this time the size is modified in
- // restoreStyles.
- editor.fire( 'resize' );
- }
-
- this.toggleState();
-
- // Toggle button label.
- var button = this.uiItems[ 0 ];
- var label = ( this.state == CKEDITOR.TRISTATE_OFF )
- ? lang.maximize : lang.minimize;
- var buttonNode = editor.element.getDocument().getById( button._.id );
- buttonNode.getChild( 1 ).setHtml( label );
- buttonNode.setAttribute( 'title', label );
- buttonNode.setAttribute( 'href', 'javascript:void("' + label + '");' );
-
- // Restore selection and scroll position in editing area.
- if ( editor.mode == 'wysiwyg' )
- {
- if ( savedSelection )
- {
- editor.getSelection().selectRanges(savedSelection);
- var element = editor.getSelection().getStartElement();
- element && element.scrollIntoView( true );
- }
-
- else
- mainWindow.$.scrollTo( savedScroll.x, savedScroll.y );
- }
- else
- {
- if ( savedSelection )
- {
- $textarea.selectionStart = savedSelection[0];
- $textarea.selectionEnd = savedSelection[1];
- }
- $textarea.scrollLeft = savedScroll[0];
- $textarea.scrollTop = savedScroll[1];
- }
-
- savedSelection = savedScroll = null;
- savedState = this.state;
- },
- canUndo : false
- } );
-
- editor.ui.addButton( 'Maximize',
- {
- label : lang.maximize,
- command : 'maximize'
- } );
-
- // Restore the command state after mode change.
- editor.on( 'mode', function()
- {
- editor.getCommand( 'maximize' ).setState( savedState );
- }, null, null, 100 );
- }
- } );
-})();
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +(function() +{ + function protectFormStyles( formElement ) + { + if ( !formElement || formElement.type != CKEDITOR.NODE_ELEMENT || formElement.getName() != 'form' ) + return []; + + var hijackRecord = []; + var hijackNames = [ 'style', 'className' ]; + for ( var i = 0 ; i < hijackNames.length ; i++ ) + { + var name = hijackNames[i]; + var $node = formElement.$.elements.namedItem( name ); + if ( $node ) + { + var hijackNode = new CKEDITOR.dom.element( $node ); + hijackRecord.push( [ hijackNode, hijackNode.nextSibling ] ); + hijackNode.remove(); + } + } + + return hijackRecord; + } + + function restoreFormStyles( formElement, hijackRecord ) + { + if ( !formElement || formElement.type != CKEDITOR.NODE_ELEMENT || formElement.getName() != 'form' ) + return; + + if ( hijackRecord.length > 0 ) + { + for ( var i = hijackRecord.length - 1 ; i >= 0 ; i-- ) + { + var node = hijackRecord[i][0]; + var sibling = hijackRecord[i][1]; + if ( sibling ) + node.insertBefore( sibling ); + else + node.appendTo( formElement ); + } + } + } + + function saveStyles( element, isInsideEditor ) + { + var data = protectFormStyles( element ); + var retval = {}; + + var $element = element.$; + + if ( !isInsideEditor ) + { + retval[ 'class' ] = $element.className || ''; + $element.className = ''; + } + + retval.inline = $element.style.cssText || ''; + if ( !isInsideEditor ) // Reset any external styles that might interfere. (#2474) + $element.style.cssText = 'position: static; overflow: visible'; + + restoreFormStyles( data ); + return retval; + } + + function restoreStyles( element, savedStyles ) + { + var data = protectFormStyles( element ); + var $element = element.$; + if ( 'class' in savedStyles ) + $element.className = savedStyles[ 'class' ]; + if ( 'inline' in savedStyles ) + $element.style.cssText = savedStyles.inline; + restoreFormStyles( data ); + } + + function refreshCursor( editor ) + { + // Refresh all editor instances on the page (#5724). + var all = CKEDITOR.instances; + for ( var i in all ) + { + var one = all[ i ]; + if ( one.mode == 'wysiwyg' ) + { + var body = one.document.getBody(); + // Refresh 'contentEditable' otherwise + // DOM lifting breaks design mode. (#5560) + body.setAttribute( 'contentEditable', false ); + body.setAttribute( 'contentEditable', true ); + } + } + + if ( editor.focusManager.hasFocus ) + { + editor.toolbox.focus(); + editor.focus(); + } + } + + /** + * Adding an iframe shim to this element, OR removing the existing one if already applied. + * Note: This will only affect IE version below 7. + */ + function createIframeShim( element ) + { + if ( !CKEDITOR.env.ie || CKEDITOR.env.version > 6 ) + return null; + + var shim = CKEDITOR.dom.element.createFromHtml( '<iframe frameborder="0" tabindex="-1"' + + ' src="javascript:' + + 'void((function(){' + + 'document.open();' + + ( CKEDITOR.env.isCustomDomain() ? 'document.domain=\'' + this.getDocument().$.domain + '\';' : '' ) + + 'document.close();' + + '})())"' + + ' style="display:block;position:absolute;z-index:-1;' + + 'progid:DXImageTransform.Microsoft.Alpha(opacity=0);' + + '"></iframe>' ); + return element.append( shim, true ); + } + + CKEDITOR.plugins.add( 'maximize', + { + init : function( editor ) + { + var lang = editor.lang; + var mainDocument = CKEDITOR.document; + var mainWindow = mainDocument.getWindow(); + + // Saved selection and scroll position for the editing area. + var savedSelection; + var savedScroll; + + // Saved scroll position for the outer window. + var outerScroll; + + var shim; + + // Saved resize handler function. + function resizeHandler() + { + var viewPaneSize = mainWindow.getViewPaneSize(); + shim && shim.setStyles( { width : viewPaneSize.width + 'px', height : viewPaneSize.height + 'px' } ); + editor.resize( viewPaneSize.width, viewPaneSize.height, null, true ); + } + + // Retain state after mode switches. + var savedState = CKEDITOR.TRISTATE_OFF; + + editor.addCommand( 'maximize', + { + modes : { wysiwyg : 1, source : 1 }, + editorFocus : false, + exec : function() + { + var container = editor.container.getChild( 1 ); + var contents = editor.getThemeSpace( 'contents' ); + + // Save current selection and scroll position in editing area. + if ( editor.mode == 'wysiwyg' ) + { + var selection = editor.getSelection(); + savedSelection = selection && selection.getRanges(); + savedScroll = mainWindow.getScrollPosition(); + } + else + { + var $textarea = editor.textarea.$; + savedSelection = !CKEDITOR.env.ie && [ $textarea.selectionStart, $textarea.selectionEnd ]; + savedScroll = [ $textarea.scrollLeft, $textarea.scrollTop ]; + } + + if ( this.state == CKEDITOR.TRISTATE_OFF ) // Go fullscreen if the state is off. + { + // Add event handler for resizing. + mainWindow.on( 'resize', resizeHandler ); + + // Save the scroll bar position. + outerScroll = mainWindow.getScrollPosition(); + + // Save and reset the styles for the entire node tree. + var currentNode = editor.container; + while ( ( currentNode = currentNode.getParent() ) ) + { + currentNode.setCustomData( 'maximize_saved_styles', saveStyles( currentNode ) ); + currentNode.setStyle( 'z-index', editor.config.baseFloatZIndex - 1 ); + } + contents.setCustomData( 'maximize_saved_styles', saveStyles( contents, true ) ); + container.setCustomData( 'maximize_saved_styles', saveStyles( container, true ) ); + + // Hide scroll bars. + if ( CKEDITOR.env.ie ) + { + mainDocument.$.documentElement.style.overflow = + mainDocument.getBody().$.style.overflow = 'hidden'; + } + else + { + mainDocument.getBody().setStyles( + { + overflow : 'hidden', + width : '0px', + height : '0px' + } ); + } + + // 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(); + container.setStyle( 'position', 'absolute' ); + container.$.offsetLeft; // SAFARI BUG: See #2066. + container.setStyles( + { + 'z-index' : editor.config.baseFloatZIndex - 1, + left : '0px', + top : '0px' + } ); + + shim = createIframeShim( container ); // IE6 select element penetration when maximized. (#4459) + resizeHandler(); + + // Still not top left? Fix it. (Bug #174) + var offset = container.getDocumentPosition(); + container.setStyles( + { + left : ( -1 * offset.x ) + 'px', + top : ( -1 * offset.y ) + 'px' + } ); + + // Fixing positioning editor chrome in Firefox break design mode. (#5149) + CKEDITOR.env.gecko && refreshCursor( editor ); + + // Add cke_maximized class. + container.addClass( 'cke_maximized' ); + } + else if ( this.state == CKEDITOR.TRISTATE_ON ) // Restore from fullscreen if the state is on. + { + // Remove event handler for resizing. + mainWindow.removeListener( 'resize', resizeHandler ); + + // Restore CSS styles for the entire node tree. + var editorElements = [ contents, container ]; + for ( var i = 0 ; i < editorElements.length ; i++ ) + { + restoreStyles( editorElements[i], editorElements[i].getCustomData( 'maximize_saved_styles' ) ); + editorElements[i].removeCustomData( 'maximize_saved_styles' ); + } + + currentNode = editor.container; + while ( ( currentNode = currentNode.getParent() ) ) + { + restoreStyles( currentNode, currentNode.getCustomData( 'maximize_saved_styles' ) ); + currentNode.removeCustomData( 'maximize_saved_styles' ); + } + + // Restore the window scroll position. + 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' ); + + if ( shim ) + { + shim.remove(); + shim = null; + } + + // Emit a resize event, because this time the size is modified in + // restoreStyles. + editor.fire( 'resize' ); + } + + this.toggleState(); + + // Toggle button label. + var button = this.uiItems[ 0 ]; + var label = ( this.state == CKEDITOR.TRISTATE_OFF ) + ? lang.maximize : lang.minimize; + var buttonNode = editor.element.getDocument().getById( button._.id ); + buttonNode.getChild( 1 ).setHtml( label ); + buttonNode.setAttribute( 'title', label ); + buttonNode.setAttribute( 'href', 'javascript:void("' + label + '");' ); + + // Restore selection and scroll position in editing area. + if ( editor.mode == 'wysiwyg' ) + { + if ( savedSelection ) + { + // Fixing positioning editor chrome in Firefox break design mode. (#5149) + CKEDITOR.env.gecko && refreshCursor( editor ); + + editor.getSelection().selectRanges(savedSelection); + var element = editor.getSelection().getStartElement(); + element && element.scrollIntoView( true ); + } + + else + mainWindow.$.scrollTo( savedScroll.x, savedScroll.y ); + } + else + { + if ( savedSelection ) + { + $textarea.selectionStart = savedSelection[0]; + $textarea.selectionEnd = savedSelection[1]; + } + $textarea.scrollLeft = savedScroll[0]; + $textarea.scrollTop = savedScroll[1]; + } + + savedSelection = savedScroll = null; + savedState = this.state; + }, + canUndo : false + } ); + + editor.ui.addButton( 'Maximize', + { + label : lang.maximize, + command : 'maximize' + } ); + + // Restore the command state after mode change. + editor.on( 'mode', function() + { + editor.getCommand( 'maximize' ).setState( savedState ); + }, null, null, 100 ); + } + } ); +})(); diff --git a/_source/plugins/menubutton/plugin.js b/_source/plugins/menubutton/plugin.js index 4797a4e..f453239 100644 --- a/_source/plugins/menubutton/plugin.js +++ b/_source/plugins/menubutton/plugin.js @@ -1,93 +1,94 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-CKEDITOR.plugins.add( 'menubutton',
-{
- requires : [ 'button', 'contextmenu' ],
- beforeInit : function( editor )
- {
- editor.ui.addHandler( CKEDITOR.UI_MENUBUTTON, CKEDITOR.ui.menuButton.handler );
- }
-});
-
-/**
- * Button UI element.
- * @constant
- * @example
- */
-CKEDITOR.UI_MENUBUTTON = 5;
-
-(function()
-{
- var clickFn = function( editor )
- {
- var _ = this._;
-
- // Do nothing if this button is disabled.
- if ( _.state === CKEDITOR.TRISTATE_DISABLED )
- return;
-
- _.previousState = _.state;
-
- // Check if we already have a menu for it, otherwise just create it.
- var menu = _.menu;
- if ( !menu )
- {
- menu = _.menu = new CKEDITOR.plugins.contextMenu( editor );
-
- menu.onHide = CKEDITOR.tools.bind( function()
- {
- this.setState( _.previousState );
- },
- this );
-
- // Initialize the menu items at this point.
- if ( this.onMenu )
- {
- menu.addListener( this.onMenu );
- }
- }
-
- if ( _.on )
- {
- menu.hide();
- return;
- }
-
- this.setState( CKEDITOR.TRISTATE_ON );
-
- menu.show( CKEDITOR.document.getById( this._.id ), 4 );
- };
-
-
- CKEDITOR.ui.menuButton = CKEDITOR.tools.createClass(
- {
- base : CKEDITOR.ui.button,
-
- $ : function( definition )
- {
- // We don't want the panel definition in this object.
- var panelDefinition = definition.panel;
- delete definition.panel;
-
- this.base( definition );
-
- this.hasArrow = true;
-
- this.click = clickFn;
- },
-
- statics :
- {
- handler :
- {
- create : function( definition )
- {
- return new CKEDITOR.ui.menuButton( definition );
- }
- }
- }
- });
-})();
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.plugins.add( 'menubutton', +{ + requires : [ 'button', 'contextmenu' ], + beforeInit : function( editor ) + { + editor.ui.addHandler( CKEDITOR.UI_MENUBUTTON, CKEDITOR.ui.menuButton.handler ); + } +}); + +/** + * Button UI element. + * @constant + * @example + */ +CKEDITOR.UI_MENUBUTTON = 5; + +(function() +{ + var clickFn = function( editor ) + { + var _ = this._; + + // Do nothing if this button is disabled. + if ( _.state === CKEDITOR.TRISTATE_DISABLED ) + return; + + _.previousState = _.state; + + // Check if we already have a menu for it, otherwise just create it. + var menu = _.menu; + if ( !menu ) + { + menu = _.menu = new CKEDITOR.plugins.contextMenu( editor ); + menu.definition.panel.attributes[ 'aria-label' ] = editor.lang.common.options; + + menu.onHide = CKEDITOR.tools.bind( function() + { + this.setState( _.previousState ); + }, + this ); + + // Initialize the menu items at this point. + if ( this.onMenu ) + { + menu.addListener( this.onMenu ); + } + } + + if ( _.on ) + { + menu.hide(); + return; + } + + this.setState( CKEDITOR.TRISTATE_ON ); + + menu.show( CKEDITOR.document.getById( this._.id ), 4 ); + }; + + + CKEDITOR.ui.menuButton = CKEDITOR.tools.createClass( + { + base : CKEDITOR.ui.button, + + $ : function( definition ) + { + // We don't want the panel definition in this object. + var panelDefinition = definition.panel; + delete definition.panel; + + this.base( definition ); + + this.hasArrow = true; + + this.click = clickFn; + }, + + statics : + { + handler : + { + create : function( definition ) + { + return new CKEDITOR.ui.menuButton( definition ); + } + } + } + }); +})(); diff --git a/_source/plugins/pagebreak/plugin.js b/_source/plugins/pagebreak/plugin.js index 3a1c642..3d840fc 100644 --- a/_source/plugins/pagebreak/plugin.js +++ b/_source/plugins/pagebreak/plugin.js @@ -1,98 +1,107 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-/**
- * @file Horizontal Page Break
- */
-
-// Register a plugin named "pagebreak".
-CKEDITOR.plugins.add( 'pagebreak',
-{
- init : function( editor )
- {
- // Register the command.
- editor.addCommand( 'pagebreak', CKEDITOR.plugins.pagebreakCmd );
-
- // Register the toolbar button.
- editor.ui.addButton( 'PageBreak',
- {
- label : editor.lang.pagebreak,
- command : 'pagebreak'
- });
-
- // Add the style that renders our placeholder.
- editor.addCss(
- 'img.cke_pagebreak' +
- '{' +
- 'background-image: url(' + CKEDITOR.getUrl( this.path + 'images/pagebreak.gif' ) + ');' +
- 'background-position: center center;' +
- 'background-repeat: no-repeat;' +
- 'clear: both;' +
- 'display: block;' +
- 'float: none;' +
- 'width: 100%;' +
- 'border-top: #999999 1px dotted;' +
- 'border-bottom: #999999 1px dotted;' +
- 'height: 5px;' +
- 'page-break-after: always;' +
-
- '}' );
- },
-
- afterInit : function( editor )
- {
- // Register a filter to displaying placeholders after mode change.
-
- var dataProcessor = editor.dataProcessor,
- dataFilter = dataProcessor && dataProcessor.dataFilter;
-
- if ( dataFilter )
- {
- dataFilter.addRules(
- {
- elements :
- {
- div : function( element )
- {
- 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;
-
- if ( childStyle && ( /page-break-after\s*:\s*always/i ).test( style ) && ( /display\s*:\s*none/i ).test( childStyle ) )
- return editor.createFakeParserElement( element, 'cke_pagebreak', 'div' );
- }
- }
- });
- }
- },
-
- requires : [ 'fakeobjects' ]
-});
-
-CKEDITOR.plugins.pagebreakCmd =
-{
- exec : function( editor )
- {
- // Create the element that represents a print break.
- var breakObject = CKEDITOR.dom.element.createFromHtml( '<div style="page-break-after: always;"><span style="display: none;"> </span></div>' );
-
- // Creates the fake image used for this element.
- breakObject = editor.createFakeElement( breakObject, 'cke_pagebreak', 'div' );
-
- var ranges = editor.getSelection().getRanges();
-
- for ( var range, i = 0 ; i < ranges.length ; i++ )
- {
- range = ranges[ i ];
-
- if ( i > 0 )
- breakObject = breakObject.clone( true );
-
- range.splitBlock( 'p' );
- range.insertNode( breakObject );
- }
- }
-};
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @file Horizontal Page Break + */ + +// Register a plugin named "pagebreak". +CKEDITOR.plugins.add( 'pagebreak', +{ + init : function( editor ) + { + // Register the command. + editor.addCommand( 'pagebreak', CKEDITOR.plugins.pagebreakCmd ); + + // Register the toolbar button. + editor.ui.addButton( 'PageBreak', + { + label : editor.lang.pagebreak, + command : 'pagebreak' + }); + + // Add the style that renders our placeholder. + editor.addCss( + 'img.cke_pagebreak' + + '{' + + 'background-image: url(' + CKEDITOR.getUrl( this.path + 'images/pagebreak.gif' ) + ');' + + 'background-position: center center;' + + 'background-repeat: no-repeat;' + + 'clear: both;' + + 'display: block;' + + 'float: none;' + + 'width:100%;_width:99.9%;' + + 'border-top: #999999 1px dotted;' + + 'border-bottom: #999999 1px dotted;' + + 'height: 5px;' + + 'page-break-after: always;' + + + '}' ); + }, + + afterInit : function( editor ) + { + // Register a filter to displaying placeholders after mode change. + + var dataProcessor = editor.dataProcessor, + dataFilter = dataProcessor && dataProcessor.dataFilter; + + if ( dataFilter ) + { + dataFilter.addRules( + { + elements : + { + div : function( element ) + { + 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; + + if ( childStyle && ( /page-break-after\s*:\s*always/i ).test( style ) && ( /display\s*:\s*none/i ).test( childStyle ) ) + return editor.createFakeParserElement( element, 'cke_pagebreak', 'div' ); + } + } + }); + } + }, + + requires : [ 'fakeobjects' ] +}); + +CKEDITOR.plugins.pagebreakCmd = +{ + exec : function( editor ) + { + // Create the element that represents a print break. + var breakObject = CKEDITOR.dom.element.createFromHtml( '<div style="page-break-after: always;"><span style="display: none;"> </span></div>' ); + + // Creates the fake image used for this element. + breakObject = editor.createFakeElement( breakObject, 'cke_pagebreak', 'div' ); + + var ranges = editor.getSelection().getRanges(); + + editor.fire( 'saveSnapshot' ); + + for ( var range, i = 0 ; i < ranges.length ; i++ ) + { + range = ranges[ i ]; + + if ( i > 0 ) + breakObject = breakObject.clone( true ); + + range.splitBlock( 'p' ); + range.insertNode( breakObject ); + if ( i == ranges.length - 1 ) + { + range.moveToPosition( breakObject, CKEDITOR.POSITION_AFTER_END ); + range.select(); + } + } + + editor.fire( 'saveSnapshot' ); + } +}; diff --git a/_source/plugins/panel/plugin.js b/_source/plugins/panel/plugin.js index 181fae3..2bc6ba9 100644 --- a/_source/plugins/panel/plugin.js +++ b/_source/plugins/panel/plugin.js @@ -1,379 +1,397 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-CKEDITOR.plugins.add( 'panel',
-{
- beforeInit : function( editor )
- {
- editor.ui.addHandler( CKEDITOR.UI_PANEL, CKEDITOR.ui.panel.handler );
- }
-});
-
-/**
- * Panel UI element.
- * @constant
- * @example
- */
-CKEDITOR.UI_PANEL = 2;
-
-CKEDITOR.ui.panel = function( document, definition )
-{
- // Copy all definition properties to this object.
- if ( definition )
- CKEDITOR.tools.extend( this, definition );
-
- // Set defaults.
- CKEDITOR.tools.extend( this,
- {
- className : '',
- css : []
- });
-
- this.id = CKEDITOR.tools.getNextNumber();
- this.document = document;
-
- this._ =
- {
- blocks : {}
- };
-};
-
-/**
- * Transforms a rich combo definition in a {@link CKEDITOR.ui.richCombo}
- * instance.
- * @type Object
- * @example
- */
-CKEDITOR.ui.panel.handler =
-{
- create : function( definition )
- {
- return new CKEDITOR.ui.panel( definition );
- }
-};
-
-CKEDITOR.ui.panel.prototype =
-{
- renderHtml : function( editor )
- {
- var output = [];
- this.render( editor, output );
- return output.join( '' );
- },
-
- /**
- * Renders the combo.
- * @param {CKEDITOR.editor} editor The editor instance which this button is
- * to be used by.
- * @param {Array} output The output array to which append the HTML relative
- * to this button.
- * @example
- */
- render : function( editor, output )
- {
- var id = 'cke_' + this.id;
-
- output.push(
- '<div class="', editor.skinClass ,'"' +
- ' lang="', editor.langCode, '"' +
- ' role="presentation"' +
- // iframe loading need sometime, keep the panel hidden(#4186).
- ' style="display:none;z-index:' + ( editor.config.baseFloatZIndex + 1 ) + '">' +
- '<div' +
- ' id=', id,
- ' dir=', editor.lang.dir,
- ' role="presentation"' +
- ' class="cke_panel cke_', editor.lang.dir );
-
- if ( this.className )
- output.push( ' ', this.className );
-
- output.push(
- '">' );
-
- if ( this.forceIFrame || this.css.length )
- {
- output.push(
- '<iframe id="', id, '_frame"' +
- ' frameborder="0"' +
- ' role="application" src="javascript:void(' );
-
- output.push(
- // Support for custom document.domain in IE.
- CKEDITOR.env.isCustomDomain() ?
- '(function(){' +
- 'document.open();' +
- 'document.domain=\'' + document.domain + '\';' +
- 'document.close();' +
- '})()'
- :
- '0' );
-
- output.push(
- ')"></iframe>' );
- }
-
- output.push(
- '</div>' +
- '</div>' );
-
- return id;
- },
-
- getHolderElement : function()
- {
- var holder = this._.holder;
-
- if ( !holder )
- {
- if ( this.forceIFrame || this.css.length )
- {
- var iframe = this.document.getById( 'cke_' + this.id + '_frame' ),
- parentDiv = iframe.getParent(),
- dir = parentDiv.getAttribute( 'dir' ),
- className = parentDiv.getParent().getAttribute( 'class' ),
- langCode = parentDiv.getParent().getAttribute( 'lang' ),
- doc = iframe.getFrameDocument();
- // Initialize the IFRAME document body.
- doc.$.open();
-
- // Support for custom document.domain in IE.
- if ( CKEDITOR.env.isCustomDomain() )
- doc.$.domain = document.domain;
-
- var onLoad = CKEDITOR.tools.addFunction( CKEDITOR.tools.bind( function( ev )
- {
- this.isLoaded = true;
- if ( this.onLoad )
- this.onLoad();
- }, this ) );
-
- doc.$.write(
- '<!DOCTYPE html>' +
- '<html dir="' + dir + '" class="' + className + '_container" lang="' + langCode + '">' +
- '<head>' +
- '<style>.' + className + '_container{visibility:hidden}</style>' +
- '</head>' +
- '<body class="cke_' + dir + ' cke_panel_frame ' + CKEDITOR.env.cssClass + '" style="margin:0;padding:0"' +
- ' onload="( window.CKEDITOR || window.parent.CKEDITOR ).tools.callFunction(' + onLoad + ');"></body>' +
- // It looks strange, but for FF2, the styles must go
- // after <body>, so it (body) becames immediatelly
- // available. (#3031)
- CKEDITOR.tools.buildStyleHtml( this.css ) +
- '<\/html>' );
- doc.$.close();
-
- var win = doc.getWindow();
-
- // Register the CKEDITOR global.
- win.$.CKEDITOR = CKEDITOR;
-
- doc.on( 'keydown', function( evt )
- {
- var keystroke = evt.data.getKeystroke(),
- dir = this.document.getById( 'cke_' + this.id ).getAttribute( 'dir' );
-
- // Delegate key processing to block.
- if ( this._.onKeyDown && this._.onKeyDown( keystroke ) === false )
- {
- evt.data.preventDefault();
- return;
- }
-
- // ESC/ARROW-LEFT(ltr) OR ARROW-RIGHT(rtl)
- if ( keystroke == 27 || keystroke == ( dir == 'rtl' ? 39 : 37 ) )
- {
- if ( this.onEscape && this.onEscape( keystroke ) === false )
- evt.data.preventDefault( );
- }
- },
- this );
-
- holder = doc.getBody();
- }
- else
- holder = this.document.getById( 'cke_' + this.id );
-
- this._.holder = holder;
- }
-
- return holder;
- },
-
- addBlock : function( name, block )
- {
- block = this._.blocks[ name ] = block instanceof CKEDITOR.ui.panel.block ? block
- : new CKEDITOR.ui.panel.block( this.getHolderElement(), block );
-
- if ( !this._.currentBlock )
- this.showBlock( name );
-
- return block;
- },
-
- getBlock : function( name )
- {
- return this._.blocks[ name ];
- },
-
- showBlock : function( name )
- {
- var blocks = this._.blocks,
- block = blocks[ name ],
- current = this._.currentBlock,
- holder = this.forceIFrame ?
- this.document.getById( 'cke_' + this.id + '_frame' )
- : this._.holder;
-
- if ( current )
- {
- // Clean up the current block's effects on holder.
- holder.removeAttributes( current.attributes );
- current.hide();
- }
-
- this._.currentBlock = block;
-
- holder.setAttributes( block.attributes );
- CKEDITOR.fire( 'ariaWidget', holder );
-
- // Reset the focus index, so it will always go into the first one.
- block._.focusIndex = -1;
-
- this._.onKeyDown = block.onKeyDown && CKEDITOR.tools.bind( block.onKeyDown, block );
-
- block.show();
-
- return block;
- },
-
- destroy : function()
- {
- this.element && this.element.remove();
- }
-};
-
-CKEDITOR.ui.panel.block = CKEDITOR.tools.createClass(
-{
- $ : function( blockHolder, blockDefinition )
- {
- this.element = blockHolder.append(
- blockHolder.getDocument().createElement( 'div',
- {
- attributes :
- {
- 'tabIndex' : -1,
- 'class' : 'cke_panel_block',
- 'role' : 'presentation'
- },
- styles :
- {
- display : 'none'
- }
- }) );
-
- // Copy all definition properties to this object.
- if ( blockDefinition )
- CKEDITOR.tools.extend( this, blockDefinition );
-
- this.keys = {};
-
- this._.focusIndex = -1;
-
- // Disable context menu for panels.
- this.element.disableContextMenu();
- },
-
- _ : {
-
- /**
- * Mark the item specified by the index as current activated.
- */
- markItem: function( index )
- {
- if ( index == -1 )
- return;
- var links = this.element.getElementsByTag( 'a' );
- var item = links.getItem( this._.focusIndex = index );
-
- // Safari need focus on the iframe window first(#3389), but we need
- // lock the blur to avoid hiding the panel.
- if ( CKEDITOR.env.webkit )
- item.getDocument().getWindow().focus();
- item.focus();
- }
- },
-
- proto :
- {
- show : function()
- {
- this.element.setStyle( 'display', '' );
- },
-
- hide : function()
- {
- if ( !this.onHide || this.onHide.call( this ) !== true )
- this.element.setStyle( 'display', 'none' );
- },
-
- onKeyDown : function( keystroke )
- {
- var keyAction = this.keys[ keystroke ];
- switch ( keyAction )
- {
- // Move forward.
- case 'next' :
- var index = this._.focusIndex,
- links = this.element.getElementsByTag( 'a' ),
- link;
-
- while ( ( link = links.getItem( ++index ) ) )
- {
- // Move the focus only if the element is marked with
- // the _cke_focus and it it's visible (check if it has
- // width).
- if ( link.getAttribute( '_cke_focus' ) && link.$.offsetWidth )
- {
- this._.focusIndex = index;
- link.focus();
- break;
- }
- }
- return false;
-
- // Move backward.
- case 'prev' :
- index = this._.focusIndex;
- links = this.element.getElementsByTag( 'a' );
-
- while ( index > 0 && ( link = links.getItem( --index ) ) )
- {
- // Move the focus only if the element is marked with
- // the _cke_focus and it it's visible (check if it has
- // width).
- if ( link.getAttribute( '_cke_focus' ) && link.$.offsetWidth )
- {
- this._.focusIndex = index;
- link.focus();
- break;
- }
- }
- return false;
-
- case 'click' :
- index = this._.focusIndex;
- link = index >= 0 && this.element.getElementsByTag( 'a' ).getItem( index );
-
- if ( link )
- link.$.click ? link.$.click() : link.$.onclick();
-
- return false;
- }
-
- return true;
- }
- }
-});
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.plugins.add( 'panel', +{ + beforeInit : function( editor ) + { + editor.ui.addHandler( CKEDITOR.UI_PANEL, CKEDITOR.ui.panel.handler ); + } +}); + +/** + * Panel UI element. + * @constant + * @example + */ +CKEDITOR.UI_PANEL = 2; + +CKEDITOR.ui.panel = function( document, definition ) +{ + // Copy all definition properties to this object. + if ( definition ) + CKEDITOR.tools.extend( this, definition ); + + // Set defaults. + CKEDITOR.tools.extend( this, + { + className : '', + css : [] + }); + + this.id = CKEDITOR.tools.getNextNumber(); + this.document = document; + + this._ = + { + blocks : {} + }; +}; + +/** + * Transforms a rich combo definition in a {@link CKEDITOR.ui.richCombo} + * instance. + * @type Object + * @example + */ +CKEDITOR.ui.panel.handler = +{ + create : function( definition ) + { + return new CKEDITOR.ui.panel( definition ); + } +}; + +CKEDITOR.ui.panel.prototype = +{ + renderHtml : function( editor ) + { + var output = []; + this.render( editor, output ); + return output.join( '' ); + }, + + /** + * Renders the combo. + * @param {CKEDITOR.editor} editor The editor instance which this button is + * to be used by. + * @param {Array} output The output array to which append the HTML relative + * to this button. + * @example + */ + render : function( editor, output ) + { + var id = 'cke_' + this.id; + + output.push( + '<div class="', editor.skinClass ,'"' + + ' lang="', editor.langCode, '"' + + ' role="presentation"' + + // iframe loading need sometime, keep the panel hidden(#4186). + ' style="display:none;z-index:' + ( editor.config.baseFloatZIndex + 1 ) + '">' + + '<div' + + ' id=', id, + ' dir=', editor.lang.dir, + ' role="presentation"' + + ' class="cke_panel cke_', editor.lang.dir ); + + if ( this.className ) + output.push( ' ', this.className ); + + output.push( + '">' ); + + if ( this.forceIFrame || this.css.length ) + { + output.push( + '<iframe id="', id, '_frame"' + + ' frameborder="0"' + + ' role="application" src="javascript:void(' ); + + output.push( + // Support for custom document.domain in IE. + CKEDITOR.env.isCustomDomain() ? + '(function(){' + + 'document.open();' + + 'document.domain=\'' + document.domain + '\';' + + 'document.close();' + + '})()' + : + '0' ); + + output.push( + ')"></iframe>' ); + } + + output.push( + '</div>' + + '</div>' ); + + return id; + }, + + getHolderElement : function() + { + var holder = this._.holder; + + if ( !holder ) + { + if ( this.forceIFrame || this.css.length ) + { + var iframe = this.document.getById( 'cke_' + this.id + '_frame' ), + parentDiv = iframe.getParent(), + dir = parentDiv.getAttribute( 'dir' ), + className = parentDiv.getParent().getAttribute( 'class' ), + langCode = parentDiv.getParent().getAttribute( 'lang' ), + doc = iframe.getFrameDocument(); + // Initialize the IFRAME document body. + doc.$.open(); + + // Support for custom document.domain in IE. + if ( CKEDITOR.env.isCustomDomain() ) + doc.$.domain = document.domain; + + var onLoad = CKEDITOR.tools.addFunction( CKEDITOR.tools.bind( function( ev ) + { + this.isLoaded = true; + if ( this.onLoad ) + this.onLoad(); + }, this ) ); + + doc.$.write( + '<!DOCTYPE html>' + + '<html dir="' + dir + '" class="' + className + '_container" lang="' + langCode + '">' + + '<head>' + + '<style>.' + className + '_container{visibility:hidden}</style>' + + '</head>' + + '<body class="cke_' + dir + ' cke_panel_frame ' + CKEDITOR.env.cssClass + '" style="margin:0;padding:0"' + + ' onload="( window.CKEDITOR || window.parent.CKEDITOR ).tools.callFunction(' + onLoad + ');"></body>' + + // It looks strange, but for FF2, the styles must go + // after <body>, so it (body) becames immediatelly + // available. (#3031) + CKEDITOR.tools.buildStyleHtml( this.css ) + + '<\/html>' ); + doc.$.close(); + + var win = doc.getWindow(); + + // Register the CKEDITOR global. + win.$.CKEDITOR = CKEDITOR; + + doc.on( 'keydown', function( evt ) + { + var keystroke = evt.data.getKeystroke(), + dir = this.document.getById( 'cke_' + this.id ).getAttribute( 'dir' ); + + // Delegate key processing to block. + if ( this._.onKeyDown && this._.onKeyDown( keystroke ) === false ) + { + evt.data.preventDefault(); + return; + } + + // ESC/ARROW-LEFT(ltr) OR ARROW-RIGHT(rtl) + if ( keystroke == 27 || keystroke == ( dir == 'rtl' ? 39 : 37 ) ) + { + if ( this.onEscape && this.onEscape( keystroke ) === false ) + evt.data.preventDefault( ); + } + }, + this ); + + holder = doc.getBody(); + } + else + holder = this.document.getById( 'cke_' + this.id ); + + this._.holder = holder; + } + + return holder; + }, + + addBlock : function( name, block ) + { + block = this._.blocks[ name ] = block instanceof CKEDITOR.ui.panel.block ? block + : new CKEDITOR.ui.panel.block( this.getHolderElement(), block ); + + if ( !this._.currentBlock ) + this.showBlock( name ); + + return block; + }, + + getBlock : function( name ) + { + return this._.blocks[ name ]; + }, + + showBlock : function( name ) + { + var blocks = this._.blocks, + block = blocks[ name ], + current = this._.currentBlock, + holder = this.forceIFrame ? + this.document.getById( 'cke_' + this.id + '_frame' ) + : this._.holder; + + // Disable context menu for block panel. + holder.getParent().getParent().disableContextMenu(); + + if ( current ) + { + // Clean up the current block's effects on holder. + holder.removeAttributes( current.attributes ); + current.hide(); + } + + this._.currentBlock = block; + + holder.setAttributes( block.attributes ); + CKEDITOR.fire( 'ariaWidget', holder ); + + // Reset the focus index, so it will always go into the first one. + block._.focusIndex = -1; + + this._.onKeyDown = block.onKeyDown && CKEDITOR.tools.bind( block.onKeyDown, block ); + + block.onMark = function( item ) + { + holder.setAttribute( 'aria-activedescendant', item.getId() + '_option' ); + }; + + block.onUnmark = function() + { + holder.removeAttribute( 'aria-activedescendant' ); + }; + + block.show(); + + return block; + }, + + destroy : function() + { + this.element && this.element.remove(); + } +}; + +CKEDITOR.ui.panel.block = CKEDITOR.tools.createClass( +{ + $ : function( blockHolder, blockDefinition ) + { + this.element = blockHolder.append( + blockHolder.getDocument().createElement( 'div', + { + attributes : + { + 'tabIndex' : -1, + 'class' : 'cke_panel_block', + 'role' : 'presentation' + }, + styles : + { + display : 'none' + } + }) ); + + // Copy all definition properties to this object. + if ( blockDefinition ) + CKEDITOR.tools.extend( this, blockDefinition ); + + if ( !this.attributes.title ) + this.attributes.title = this.attributes[ 'aria-label' ]; + + this.keys = {}; + + this._.focusIndex = -1; + + // Disable context menu for panels. + this.element.disableContextMenu(); + }, + + _ : { + + /** + * Mark the item specified by the index as current activated. + */ + markItem: function( index ) + { + if ( index == -1 ) + return; + var links = this.element.getElementsByTag( 'a' ); + var item = links.getItem( this._.focusIndex = index ); + + // Safari need focus on the iframe window first(#3389), but we need + // lock the blur to avoid hiding the panel. + if ( CKEDITOR.env.webkit ) + item.getDocument().getWindow().focus(); + item.focus(); + + this.onMark && this.onMark( item ); + } + }, + + proto : + { + show : function() + { + this.element.setStyle( 'display', '' ); + }, + + hide : function() + { + if ( !this.onHide || this.onHide.call( this ) !== true ) + this.element.setStyle( 'display', 'none' ); + }, + + onKeyDown : function( keystroke ) + { + var keyAction = this.keys[ keystroke ]; + switch ( keyAction ) + { + // Move forward. + case 'next' : + var index = this._.focusIndex, + links = this.element.getElementsByTag( 'a' ), + link; + + while ( ( link = links.getItem( ++index ) ) ) + { + // Move the focus only if the element is marked with + // the _cke_focus and it it's visible (check if it has + // width). + if ( link.getAttribute( '_cke_focus' ) && link.$.offsetWidth ) + { + this._.focusIndex = index; + link.focus(); + break; + } + } + return false; + + // Move backward. + case 'prev' : + index = this._.focusIndex; + links = this.element.getElementsByTag( 'a' ); + + while ( index > 0 && ( link = links.getItem( --index ) ) ) + { + // Move the focus only if the element is marked with + // the _cke_focus and it it's visible (check if it has + // width). + if ( link.getAttribute( '_cke_focus' ) && link.$.offsetWidth ) + { + this._.focusIndex = index; + link.focus(); + break; + } + } + return false; + + case 'click' : + index = this._.focusIndex; + link = index >= 0 && this.element.getElementsByTag( 'a' ).getItem( index ); + + if ( link ) + link.$.click ? link.$.click() : link.$.onclick(); + + return false; + } + + return true; + } + } +}); diff --git a/_source/plugins/pastefromword/plugin.js b/_source/plugins/pastefromword/plugin.js index a5d3896..8057745 100644 --- a/_source/plugins/pastefromword/plugin.js +++ b/_source/plugins/pastefromword/plugin.js @@ -1,120 +1,122 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-(function()
-{
- CKEDITOR.plugins.add( '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 );
- };
-
- // 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',
- {
- canUndo : false,
- exec : function()
- {
- forceFromWord = 1;
- if ( editor.execCommand( 'paste' ) === false )
- {
- editor.on( 'dialogHide', function ( evt )
- {
- evt.removeListener();
- resetFromWord();
- });
- }
- }
- });
-
- // Register the toolbar button.
- editor.ui.addButton( 'PasteFromWord',
- {
- label : editor.lang.pastefromword.toolbar,
- command : 'pastefromword'
- });
-
- 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 to prompt the user about the clean up of content being pasted from
- * MS Word.
- * @name CKEDITOR.config.pasteFromWordPromptCleanup
- * @since 3.1
- * @type Boolean
- * @default undefined
- * @example
- * config.pasteFromWordPromptCleanup = true;
- */
-
-/**
- * The file that provides the MS Word cleanup function for pasting operations.
- * Note: This is a global configuration shared by all editor instances present
- * in the page.
- * @name CKEDITOR.config.pasteFromWordCleanupFile
- * @since 3.1
- * @type String
- * @default 'default'
- * @example
- * // Load from 'pastefromword' plugin 'filter' sub folder (custom.js file).
- * CKEDITOR.config.pasteFromWordCleanupFile = 'custom';
- */
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ +(function() +{ + CKEDITOR.plugins.add( '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 ); + }; + + // 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', + { + canUndo : false, + exec : function() + { + forceFromWord = 1; + if ( editor.execCommand( 'paste' ) === false ) + { + editor.on( 'dialogHide', function ( evt ) + { + evt.removeListener(); + resetFromWord(); + }); + } + else + resetFromWord(); + } + }); + + // Register the toolbar button. + editor.ui.addButton( 'PasteFromWord', + { + label : editor.lang.pastefromword.toolbar, + command : 'pastefromword' + }); + + 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 to prompt the user about the clean up of content being pasted from + * MS Word. + * @name CKEDITOR.config.pasteFromWordPromptCleanup + * @since 3.1 + * @type Boolean + * @default undefined + * @example + * config.pasteFromWordPromptCleanup = true; + */ + +/** + * The file that provides the MS Word cleanup function for pasting operations. + * Note: This is a global configuration shared by all editor instances present + * in the page. + * @name CKEDITOR.config.pasteFromWordCleanupFile + * @since 3.1 + * @type String + * @default 'default' + * @example + * // Load from 'pastefromword' plugin 'filter' sub folder (custom.js file). + * CKEDITOR.config.pasteFromWordCleanupFile = 'custom'; + */ diff --git a/_source/plugins/pastetext/dialogs/pastetext.js b/_source/plugins/pastetext/dialogs/pastetext.js index 9ff4f74..5ed557f 100644 --- a/_source/plugins/pastetext/dialogs/pastetext.js +++ b/_source/plugins/pastetext/dialogs/pastetext.js @@ -1,77 +1,78 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-(function()
-{
- CKEDITOR.dialog.add( 'pastetext', function( editor )
- {
- return {
- title : editor.lang.pasteText.title,
-
- minWidth : CKEDITOR.env.ie && CKEDITOR.env.quirks ? 368 : 350,
- minHeight : 240,
-
- onShow : function()
- {
- // Reset the textarea value.
- this.getContentElement( 'general', 'content' ).getInputElement().setValue( '' );
- },
-
- onOk : function()
- {
- // Get the textarea value.
- var text = this.getContentElement( 'general', 'content' ).getInputElement().getValue(),
- editor = this.getParentEditor();
-
- setTimeout( function()
- {
- editor.fire( 'paste', { 'text' : text } );
- }, 0 );
- },
-
- contents :
- [
- {
- label : editor.lang.common.generalTab,
- id : 'general',
- elements :
- [
- {
- type : 'html',
- id : 'pasteMsg',
- html : '<div style="white-space:normal;width:340px;">' + editor.lang.clipboard.pasteMsg + '</div>'
- },
- {
- type : 'html',
- id : 'content',
- style : 'width:340px;height:170px',
- html :
- '<textarea style="' +
- 'width:346px;' +
- 'height:170px;' +
- 'resize: none;' +
- 'border:1px solid black;' +
- 'background-color:white">' +
- '</textarea>',
-
- onLoad : function()
- {
- var label = this.getDialog().getContentElement( 'general', 'pasteMsg' ).getElement(),
- input = this.getElement();
-
- input.setAttribute( 'aria-labelledby', label.$.id );
- },
-
- focus : function()
- {
- this.getElement().focus();
- }
- }
- ]
- }
- ]
- };
- });
-})();
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +(function() +{ + CKEDITOR.dialog.add( 'pastetext', function( editor ) + { + return { + title : editor.lang.pasteText.title, + + minWidth : CKEDITOR.env.ie && CKEDITOR.env.quirks ? 368 : 350, + minHeight : 240, + + onShow : function() + { + // Reset the textarea value. + this.getContentElement( 'general', 'content' ).getInputElement().setValue( '' ); + }, + + onOk : function() + { + // Get the textarea value. + var text = this.getContentElement( 'general', 'content' ).getInputElement().getValue(), + editor = this.getParentEditor(); + + setTimeout( function() + { + editor.fire( 'paste', { 'text' : text } ); + }, 0 ); + }, + + contents : + [ + { + label : editor.lang.common.generalTab, + id : 'general', + elements : + [ + { + type : 'html', + id : 'pasteMsg', + html : '<div style="white-space:normal;width:340px;">' + editor.lang.clipboard.pasteMsg + '</div>' + }, + { + type : 'html', + id : 'content', + style : 'width:340px;height:170px', + html : + '<textarea style="' + + 'width:346px;' + + 'height:170px;' + + 'resize: none;' + + 'direction:' + editor.config.contentsLangDirection + ';' + + 'border:1px solid black;' + + 'background-color:white">' + + '</textarea>', + + onLoad : function() + { + var label = this.getDialog().getContentElement( 'general', 'pasteMsg' ).getElement(), + input = this.getElement(); + + input.setAttribute( 'aria-labelledby', label.$.id ); + }, + + focus : function() + { + this.getElement().focus(); + } + } + ] + } + ] + }; + }); +})(); diff --git a/_source/plugins/removeformat/plugin.js b/_source/plugins/removeformat/plugin.js index 97b858a..fe49f50 100644 --- a/_source/plugins/removeformat/plugin.js +++ b/_source/plugins/removeformat/plugin.js @@ -1,132 +1,172 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-CKEDITOR.plugins.add( 'removeformat',
-{
- requires : [ 'selection' ],
-
- init : function( editor )
- {
- editor.addCommand( 'removeFormat', CKEDITOR.plugins.removeformat.commands.removeformat );
- editor.ui.addButton( 'RemoveFormat',
- {
- label : editor.lang.removeFormat,
- command : 'removeFormat'
- });
- }
-});
-
-CKEDITOR.plugins.removeformat =
-{
- commands :
- {
- removeformat :
- {
- exec : function( editor )
- {
- var tagsRegex = editor._.removeFormatRegex ||
- ( editor._.removeFormatRegex = new RegExp( '^(?:' + editor.config.removeFormatTags.replace( /,/g,'|' ) + ')$', 'i' ) );
-
- var removeAttributes = editor._.removeAttributes ||
- ( editor._.removeAttributes = editor.config.removeFormatAttributes.split( ',' ) );
-
- var ranges = editor.getSelection().getRanges();
-
- for ( var i = 0, range ; range = ranges[ i ] ; i++ )
- {
- if ( range.collapsed )
- continue;
-
- range.enlarge( CKEDITOR.ENLARGE_ELEMENT );
-
- // Bookmark the range so we can re-select it after processing.
- var bookmark = range.createBookmark();
-
- // The style will be applied within the bookmark boundaries.
- var startNode = bookmark.startNode;
- var endNode = bookmark.endNode;
-
- // We need to check the selection boundaries (bookmark spans) to break
- // the code in a way that we can properly remove partially selected nodes.
- // For example, removing a <b> style from
- // <b>This is [some text</b> to show <b>the] problem</b>
- // ... where [ and ] represent the selection, must result:
- // <b>This is </b>[some text to show the]<b> problem</b>
- // The strategy is simple, we just break the partial nodes before the
- // removal logic, having something that could be represented this way:
- // <b>This is </b>[<b>some text</b> to show <b>the</b>]<b> problem</b>
-
- var breakParent = function( node )
- {
- // Let's start checking the start boundary.
- var path = new CKEDITOR.dom.elementPath( node );
- var pathElements = path.elements;
-
- for ( var i = 1, pathElement ; pathElement = pathElements[ i ] ; i++ )
- {
- if ( pathElement.equals( path.block ) || pathElement.equals( path.blockLimit ) )
- break;
-
- // If this element can be removed (even partially).
- if ( tagsRegex.test( pathElement.getName() ) )
- node.breakParent( pathElement );
- }
- };
-
- breakParent( startNode );
- breakParent( endNode );
-
- // Navigate through all nodes between the bookmarks.
- var currentNode = startNode.getNextSourceNode( true, CKEDITOR.NODE_ELEMENT );
-
- while ( currentNode )
- {
- // If we have reached the end of the selection, stop looping.
- if ( currentNode.equals( endNode ) )
- break;
-
- // Cache the next node to be processed. Do it now, because
- // currentNode may be removed.
- var nextNode = currentNode.getNextSourceNode( false, CKEDITOR.NODE_ELEMENT );
-
- // This node must not be a fake element.
- if ( !( currentNode.getName() == 'img' && currentNode.getAttribute( '_cke_realelement' ) ) )
- {
- // Remove elements nodes that match with this style rules.
- if ( tagsRegex.test( currentNode.getName() ) )
- currentNode.remove( true );
- else
- currentNode.removeAttributes( removeAttributes );
- }
-
- currentNode = nextNode;
- }
-
- range.moveToBookmark( bookmark );
- }
-
- editor.getSelection().selectRanges( ranges );
- }
- }
- }
-};
-
-/**
- * A comma separated list of elements to be removed when executing the "remove
- " format" command. Note that only inline elements are allowed.
- * @type String
- * @default 'b,big,code,del,dfn,em,font,i,ins,kbd,q,samp,small,span,strike,strong,sub,sup,tt,u,var'
- * @example
- */
-CKEDITOR.config.removeFormatTags = 'b,big,code,del,dfn,em,font,i,ins,kbd,q,samp,small,span,strike,strong,sub,sup,tt,u,var';
-
-/**
- * A comma separated list of elements attributes to be removed when executing
- * the "remove format" command.
- * @type String
- * @default 'class,style,lang,width,height,align,hspace,valign'
- * @example
- */
-CKEDITOR.config.removeFormatAttributes = 'class,style,lang,width,height,align,hspace,valign';
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.plugins.add( 'removeformat', +{ + requires : [ 'selection' ], + + init : function( editor ) + { + editor.addCommand( 'removeFormat', CKEDITOR.plugins.removeformat.commands.removeformat ); + editor.ui.addButton( 'RemoveFormat', + { + label : editor.lang.removeFormat, + command : 'removeFormat' + }); + + editor._.removeFormat = { filters: [] }; + } +}); + +CKEDITOR.plugins.removeformat = +{ + commands : + { + removeformat : + { + exec : function( editor ) + { + var tagsRegex = editor._.removeFormatRegex || + ( editor._.removeFormatRegex = new RegExp( '^(?:' + editor.config.removeFormatTags.replace( /,/g,'|' ) + ')$', 'i' ) ); + + var removeAttributes = editor._.removeAttributes || + ( editor._.removeAttributes = editor.config.removeFormatAttributes.split( ',' ) ); + + var filter = CKEDITOR.plugins.removeformat.filter; + var ranges = editor.getSelection().getRanges(); + + for ( var i = 0, range ; range = ranges[ i ] ; i++ ) + { + if ( range.collapsed ) + continue; + + range.enlarge( CKEDITOR.ENLARGE_ELEMENT ); + + // Bookmark the range so we can re-select it after processing. + var bookmark = range.createBookmark(); + + // The style will be applied within the bookmark boundaries. + var startNode = bookmark.startNode; + var endNode = bookmark.endNode; + + // We need to check the selection boundaries (bookmark spans) to break + // the code in a way that we can properly remove partially selected nodes. + // For example, removing a <b> style from + // <b>This is [some text</b> to show <b>the] problem</b> + // ... where [ and ] represent the selection, must result: + // <b>This is </b>[some text to show the]<b> problem</b> + // The strategy is simple, we just break the partial nodes before the + // removal logic, having something that could be represented this way: + // <b>This is </b>[<b>some text</b> to show <b>the</b>]<b> problem</b> + + var breakParent = function( node ) + { + // Let's start checking the start boundary. + var path = new CKEDITOR.dom.elementPath( node ); + var pathElements = path.elements; + + for ( var i = 1, pathElement ; pathElement = pathElements[ i ] ; i++ ) + { + if ( pathElement.equals( path.block ) || pathElement.equals( path.blockLimit ) ) + break; + + // If this element can be removed (even partially). + if ( tagsRegex.test( pathElement.getName() ) && filter( editor, pathElement ) ) + node.breakParent( pathElement ); + } + }; + + breakParent( startNode ); + breakParent( endNode ); + + // Navigate through all nodes between the bookmarks. + var currentNode = startNode.getNextSourceNode( true, CKEDITOR.NODE_ELEMENT ); + + while ( currentNode ) + { + // If we have reached the end of the selection, stop looping. + if ( currentNode.equals( endNode ) ) + break; + + // Cache the next node to be processed. Do it now, because + // currentNode may be removed. + var nextNode = currentNode.getNextSourceNode( false, CKEDITOR.NODE_ELEMENT ); + + // This node must not be a fake element. + if ( !( currentNode.getName() == 'img' + && currentNode.getAttribute( '_cke_realelement' ) ) + && filter( editor, currentNode ) ) + { + // Remove elements nodes that match with this style rules. + if ( tagsRegex.test( currentNode.getName() ) ) + currentNode.remove( true ); + else + currentNode.removeAttributes( removeAttributes ); + } + + currentNode = nextNode; + } + + range.moveToBookmark( bookmark ); + } + + editor.getSelection().selectRanges( ranges ); + } + } + }, + + /** + * Perform the remove format filters on the passed element. + * @param {CKEDITOR.editor} editor + * @param {CKEDITOR.dom.element} element + */ + filter : function ( editor, element ) + { + var filters = editor._.removeFormat.filters; + for ( var i = 0; i < filters.length; i++ ) + { + if ( filters[ i ]( element ) === false ) + return false; + } + return true; + } +}; + +/** + * Add to a collection of functions to decide whether a specific + * element should be considered as formatting element and thus + * could be removed during <b>removeFormat</b> command, + * Note: Only available with the existence of 'removeformat' plugin. + * @since 3.3 + * @param {Function} func The function to be called, which will be passed a {CKEDITOR.dom.element} element to test. + * @example + * // Don't remove empty span + * editor.addRemoveFormatFilter.push( function( element ) + * { + * return !( element.is( 'span' ) && CKEDITOR.tools.isEmpty( element.getAttributes() ) ); + * }); + */ +CKEDITOR.editor.prototype.addRemoveFormatFilter = function( func ) +{ + this._.removeFormat.filters.push( func ); +}; + +/** + * A comma separated list of elements to be removed when executing the "remove + " format" command. Note that only inline elements are allowed. + * @type String + * @default 'b,big,code,del,dfn,em,font,i,ins,kbd,q,samp,small,span,strike,strong,sub,sup,tt,u,var' + * @example + */ +CKEDITOR.config.removeFormatTags = 'b,big,code,del,dfn,em,font,i,ins,kbd,q,samp,small,span,strike,strong,sub,sup,tt,u,var'; + +/** + * A comma separated list of elements attributes to be removed when executing + * the "remove format" command. + * @type String + * @default 'class,style,lang,width,height,align,hspace,valign' + * @example + */ +CKEDITOR.config.removeFormatAttributes = 'class,style,lang,width,height,align,hspace,valign'; diff --git a/_source/plugins/resize/plugin.js b/_source/plugins/resize/plugin.js index 32faf6f..65ce9db 100644 --- a/_source/plugins/resize/plugin.js +++ b/_source/plugins/resize/plugin.js @@ -1,115 +1,146 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-CKEDITOR.plugins.add( 'resize',
-{
- init : function( editor )
- {
- var config = editor.config;
-
- if ( config.resize_enabled )
- {
- var container = null;
- var origin, startSize;
-
- function dragHandler( evt )
- {
- var dx = evt.data.$.screenX - origin.x;
- var dy = evt.data.$.screenY - origin.y;
- var internalWidth = startSize.width + dx * ( editor.lang.dir == 'rtl' ? -1 : 1 );
- var internalHeight = startSize.height + dy;
-
- editor.resize( Math.max( config.resize_minWidth, Math.min( internalWidth, config.resize_maxWidth ) ),
- Math.max( config.resize_minHeight, Math.min( internalHeight, config.resize_maxHeight ) ) );
- }
-
- function dragEndHandler ( evt )
- {
- CKEDITOR.document.removeListener( 'mousemove', dragHandler );
- CKEDITOR.document.removeListener( 'mouseup', dragEndHandler );
-
- if ( editor.document )
- {
- editor.document.removeListener( 'mousemove', dragHandler );
- editor.document.removeListener( 'mouseup', dragEndHandler );
- }
- }
-
- var mouseDownFn = CKEDITOR.tools.addFunction( function( $event )
- {
- if ( !container )
- container = editor.getResizable();
-
- startSize = { width : container.$.offsetWidth || 0, height : container.$.offsetHeight || 0 };
- origin = { x : $event.screenX, y : $event.screenY };
-
- CKEDITOR.document.on( 'mousemove', dragHandler );
- CKEDITOR.document.on( 'mouseup', dragEndHandler );
-
- if ( editor.document )
- {
- editor.document.on( 'mousemove', dragHandler );
- editor.document.on( 'mouseup', dragEndHandler );
- }
- } );
-
- editor.on( 'themeSpace', function( event )
- {
- if ( event.data.space == 'bottom' )
- {
- event.data.html += '<div class="cke_resizer"' +
- ' title="' + CKEDITOR.tools.htmlEncode( editor.lang.resize ) + '"' +
- ' onmousedown="CKEDITOR.tools.callFunction(' + mouseDownFn + ', event)"' +
- '></div>';
- }
- }, editor, null, 100 );
- }
- }
-} );
-
-/**
- * The minimum editor width, in pixels, when resizing it with the resize handle.
- * @type Number
- * @default 750
- * @example
- * config.resize_minWidth = 500;
- */
-CKEDITOR.config.resize_minWidth = 750;
-
-/**
- * The minimum editor height, in pixels, when resizing it with the resize handle.
- * @type Number
- * @default 250
- * @example
- * config.resize_minHeight = 600;
- */
-CKEDITOR.config.resize_minHeight = 250;
-
-/**
- * The maximum editor width, in pixels, when resizing it with the resize handle.
- * @type Number
- * @default 3000
- * @example
- * config.resize_maxWidth = 750;
- */
-CKEDITOR.config.resize_maxWidth = 3000;
-
-/**
- * The maximum editor height, in pixels, when resizing it with the resize handle.
- * @type Number
- * @default 3000
- * @example
- * config.resize_maxHeight = 600;
- */
-CKEDITOR.config.resize_maxHeight = 3000;
-
-/**
- * Whether to enable the resizing feature. If disabed the resize handler will not be visible.
- * @type Boolean
- * @default true
- * @example
- * config.resize_enabled = false;
- */
-CKEDITOR.config.resize_enabled = true;
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.plugins.add( 'resize', +{ + init : function( editor ) + { + var config = editor.config; + + if ( config.resize_enabled ) + { + var container = null, + origin, + startSize, + resizeHorizontal = ( config.resize_dir == 'both' || config.resize_dir == 'horizontal' ) && + ( config.resize_minWidth != config.resize_maxWidth ), + resizeVertical = ( config.resize_dir == 'both' || config.resize_dir == 'vertical' ) && + ( config.resize_minHeight != config.resize_maxHeight ); + + function dragHandler( evt ) + { + var dx = evt.data.$.screenX - origin.x, + dy = evt.data.$.screenY - origin.y, + width = startSize.width, + height = startSize.height, + internalWidth = width + dx * ( editor.lang.dir == 'rtl' ? -1 : 1 ), + internalHeight = height + dy; + + if ( resizeHorizontal ) + width = Math.max( config.resize_minWidth, Math.min( internalWidth, config.resize_maxWidth ) ); + + if ( resizeVertical ) + height = Math.max( config.resize_minHeight, Math.min( internalHeight, config.resize_maxHeight ) ); + + editor.resize( width, height ); + } + + function dragEndHandler ( evt ) + { + CKEDITOR.document.removeListener( 'mousemove', dragHandler ); + CKEDITOR.document.removeListener( 'mouseup', dragEndHandler ); + + if ( editor.document ) + { + editor.document.removeListener( 'mousemove', dragHandler ); + editor.document.removeListener( 'mouseup', dragEndHandler ); + } + } + + var mouseDownFn = CKEDITOR.tools.addFunction( function( $event ) + { + if ( !container ) + container = editor.getResizable(); + + startSize = { width : container.$.offsetWidth || 0, height : container.$.offsetHeight || 0 }; + origin = { x : $event.screenX, y : $event.screenY }; + + CKEDITOR.document.on( 'mousemove', dragHandler ); + CKEDITOR.document.on( 'mouseup', dragEndHandler ); + + if ( editor.document ) + { + editor.document.on( 'mousemove', dragHandler ); + editor.document.on( 'mouseup', dragEndHandler ); + } + }); + + editor.on( 'destroy', function() { CKEDITOR.tools.removeFunction( mouseDownFn ); } ); + + editor.on( 'themeSpace', function( event ) + { + if ( event.data.space == 'bottom' ) + { + var direction = ''; + if ( resizeHorizontal && !resizeVertical) + direction = ' cke_resizer_horizontal'; + if ( !resizeHorizontal && resizeVertical) + direction = ' cke_resizer_vertical'; + + event.data.html += '<div class="cke_resizer' + direction + '"' + + ' title="' + CKEDITOR.tools.htmlEncode( editor.lang.resize ) + '"' + + ' onmousedown="CKEDITOR.tools.callFunction(' + mouseDownFn + ', event)"' + + '></div>'; + } + }, editor, null, 100 ); + } + } +} ); + +/** + * The minimum editor width, in pixels, when resizing it with the resize handle. + * @type Number + * @default 750 + * @example + * config.resize_minWidth = 500; + */ +CKEDITOR.config.resize_minWidth = 750; + +/** + * The minimum editor height, in pixels, when resizing it with the resize handle. + * @type Number + * @default 250 + * @example + * config.resize_minHeight = 600; + */ +CKEDITOR.config.resize_minHeight = 250; + +/** + * The maximum editor width, in pixels, when resizing it with the resize handle. + * @type Number + * @default 3000 + * @example + * config.resize_maxWidth = 750; + */ +CKEDITOR.config.resize_maxWidth = 3000; + +/** + * The maximum editor height, in pixels, when resizing it with the resize handle. + * @type Number + * @default 3000 + * @example + * config.resize_maxHeight = 600; + */ +CKEDITOR.config.resize_maxHeight = 3000; + +/** + * Whether to enable the resizing feature. If disabled the resize handler will not be visible. + * @type Boolean + * @default true + * @example + * config.resize_enabled = false; + */ +CKEDITOR.config.resize_enabled = true; + +/** + * The directions to which the editor resizing is enabled. Possible values + * are "both", "vertical" and "horizontal". + * @type String + * @default 'both' + * @since 3.3 + * @example + * config.resize_dir = 'vertical'; + */ +CKEDITOR.config.resize_dir = 'both'; diff --git a/_source/plugins/richcombo/plugin.js b/_source/plugins/richcombo/plugin.js index 8c2e81b..39315a8 100644 --- a/_source/plugins/richcombo/plugin.js +++ b/_source/plugins/richcombo/plugin.js @@ -1,367 +1,370 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-CKEDITOR.plugins.add( 'richcombo',
-{
- requires : [ 'floatpanel', 'listblock', 'button' ],
-
- beforeInit : function( editor )
- {
- editor.ui.addHandler( CKEDITOR.UI_RICHCOMBO, CKEDITOR.ui.richCombo.handler );
- }
-});
-
-/**
- * Button UI element.
- * @constant
- * @example
- */
-CKEDITOR.UI_RICHCOMBO = 3;
-
-CKEDITOR.ui.richCombo = CKEDITOR.tools.createClass(
-{
- $ : function( definition )
- {
- // Copy all definition properties to this object.
- CKEDITOR.tools.extend( this, definition,
- // Set defaults.
- {
- title : definition.label,
- modes : { wysiwyg : 1 }
- });
-
- // We don't want the panel definition in this object.
- var panelDefinition = this.panel || {};
- delete this.panel;
-
- this.id = CKEDITOR.tools.getNextNumber();
-
- this.document = ( panelDefinition
- && panelDefinition.parent
- && panelDefinition.parent.getDocument() )
- || CKEDITOR.document;
-
- panelDefinition.className = ( panelDefinition.className || '' ) + ' cke_rcombopanel';
- panelDefinition.block =
- {
- multiSelect : panelDefinition.multiSelect,
- attributes : panelDefinition.attributes
- };
-
- this._ =
- {
- panelDefinition : panelDefinition,
- items : {},
- state : CKEDITOR.TRISTATE_OFF
- };
- },
-
- statics :
- {
- handler :
- {
- create : function( definition )
- {
- return new CKEDITOR.ui.richCombo( definition );
- }
- }
- },
-
- proto :
- {
- renderHtml : function( editor )
- {
- var output = [];
- this.render( editor, output );
- return output.join( '' );
- },
-
- /**
- * Renders the combo.
- * @param {CKEDITOR.editor} editor The editor instance which this button is
- * to be used by.
- * @param {Array} output The output array to which append the HTML relative
- * to this button.
- * @example
- */
- render : function( editor, output )
- {
- var env = CKEDITOR.env;
-
- var id = 'cke_' + this.id;
- var clickFn = CKEDITOR.tools.addFunction( function( $element )
- {
- var _ = this._;
-
- if ( _.state == CKEDITOR.TRISTATE_DISABLED )
- return;
-
- this.createPanel( editor );
-
- if ( _.on )
- {
- _.panel.hide();
- return;
- }
-
- if ( !_.committed )
- {
- _.list.commit();
- _.committed = 1;
- }
-
- var value = this.getValue();
- if ( value )
- _.list.mark( value );
- else
- _.list.unmarkAll();
-
- _.panel.showBlock( this.id, new CKEDITOR.dom.element( $element ), 4 );
- },
- this );
-
- var instance = {
- id : id,
- combo : this,
- focus : function()
- {
- var element = CKEDITOR.document.getById( id ).getChild( 1 );
- element.focus();
- },
- execute : clickFn
- };
-
- editor.on( 'mode', function()
- {
- this.setState( this.modes[ editor.mode ] ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED );
- },
- this );
-
- var keyDownFn = CKEDITOR.tools.addFunction( function( ev, element )
- {
- ev = new CKEDITOR.dom.event( ev );
-
- var keystroke = ev.getKeystroke();
- switch ( keystroke )
- {
- case 13 : // ENTER
- case 32 : // SPACE
- case 40 : // ARROW-DOWN
- // Show panel
- CKEDITOR.tools.callFunction( clickFn, element );
- break;
- default :
- // Delegate the default behavior to toolbar button key handling.
- instance.onkey( instance, keystroke );
- }
-
- // Avoid subsequent focus grab on editor document.
- ev.preventDefault();
- });
-
- output.push(
- '<span class="cke_rcombo">',
- '<span id=', id );
-
- if ( this.className )
- output.push( ' class="', this.className, ' cke_off"');
-
- output.push(
- '>',
- '<span id="' + id+ '_label" class=cke_label>', this.label, '</span>',
- '<a hidefocus=true title="', this.title, '" tabindex="-1"',
- env.gecko && env.version >= 10900 && !env.hc ? '' : ' href="javascript:void(\'' + this.label + '\')"',
- ' role="button" aria-labelledby="', id , '_label" aria-describedby="', id, '_text" aria-haspopup="true"' );
-
- // Some browsers don't cancel key events in the keydown but in the
- // keypress.
- // TODO: Check if really needed for Gecko+Mac.
- if ( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.mac ) )
- {
- output.push(
- ' onkeypress="return false;"' );
- }
-
- // With Firefox, we need to force it to redraw, otherwise it
- // will remain in the focus state.
- if ( CKEDITOR.env.gecko )
- {
- output.push(
- ' onblur="this.style.cssText = this.style.cssText;"' );
- }
-
- output.push(
- ' onkeydown="CKEDITOR.tools.callFunction( ', keyDownFn, ', event, this );"' +
- ' onclick="CKEDITOR.tools.callFunction(', clickFn, ', this); return false;">' +
- '<span>' +
- '<span id="' + id + '_text" class="cke_text cke_inline_label">' + this.label + '</span>' +
- '</span>' +
- '<span class=cke_openbutton>' + ( CKEDITOR.env.hc ? '<span>▼</span>' : '' ) + '</span>' + // BLACK DOWN-POINTING TRIANGLE
- '</a>' +
- '</span>' +
- '</span>' );
-
- if ( this.onRender )
- this.onRender();
-
- return instance;
- },
-
- createPanel : function( editor )
- {
- if ( this._.panel )
- return;
-
- var panelDefinition = this._.panelDefinition,
- panelBlockDefinition = this._.panelDefinition.block,
- panelParentElement = panelDefinition.parent || CKEDITOR.document.getBody(),
- panel = new CKEDITOR.ui.floatPanel( editor, panelParentElement, panelDefinition ),
- list = panel.addListBlock( this.id, panelBlockDefinition ),
- me = this;
-
- panel.onShow = function()
- {
- if ( me.className )
- this.element.getFirst().addClass( me.className + '_panel' );
-
- me.setState( CKEDITOR.TRISTATE_ON );
-
- list.focus( !me.multiSelect && me.getValue() );
-
- me._.on = 1;
-
- if ( me.onOpen )
- me.onOpen();
- };
-
- panel.onHide = function()
- {
- if ( me.className )
- this.element.getFirst().removeClass( me.className + '_panel' );
-
- me.setState( CKEDITOR.TRISTATE_OFF );
-
- me._.on = 0;
-
- if ( me.onClose )
- me.onClose();
- };
-
- panel.onEscape = function()
- {
- panel.hide();
- me.document.getById( 'cke_' + me.id ).getFirst().getNext().focus();
- };
-
- list.onClick = function( value, marked )
- {
- // Move the focus to the main windows, otherwise it will stay
- // into the floating panel, even if invisible, and Safari and
- // Opera will go a bit crazy.
- me.document.getWindow().focus();
-
- if ( me.onClick )
- me.onClick.call( me, value, marked );
-
- if ( marked )
- me.setValue( value, me._.items[ value ] );
- else
- me.setValue( '' );
-
- panel.hide();
- };
-
- this._.panel = panel;
- this._.list = list;
-
- panel.getBlock( this.id ).onHide = function()
- {
- me._.on = 0;
- me.setState( CKEDITOR.TRISTATE_OFF );
- };
-
- if ( this.init )
- this.init();
- },
-
- setValue : function( value, text )
- {
- this._.value = value;
-
- var textElement = this.document.getById( 'cke_' + this.id + '_text' );
-
- if ( !( value || text ) )
- {
- text = this.label;
- textElement.addClass( 'cke_inline_label' );
- }
- else
- textElement.removeClass( 'cke_inline_label' );
-
- textElement.setHtml( typeof text != 'undefined' ? text : value );
- },
-
- getValue : function()
- {
- return this._.value || '';
- },
-
- unmarkAll : function()
- {
- this._.list.unmarkAll();
- },
-
- mark : function( value )
- {
- this._.list.mark( value );
- },
-
- hideItem : function( value )
- {
- this._.list.hideItem( value );
- },
-
- hideGroup : function( groupTitle )
- {
- this._.list.hideGroup( groupTitle );
- },
-
- showAll : function()
- {
- this._.list.showAll();
- },
-
- add : function( value, html, text )
- {
- this._.items[ value ] = text || value;
- this._.list.add( value, html, text );
- },
-
- startGroup : function( title )
- {
- this._.list.startGroup( title );
- },
-
- commit : function()
- {
- this._.list.commit();
- },
-
- setState : function( state )
- {
- if ( this._.state == state )
- return;
-
- this.document.getById( 'cke_' + this.id ).setState( state );
-
- this._.state = state;
- }
- }
-});
-
-CKEDITOR.ui.prototype.addRichCombo = function( name, definition )
-{
- this.add( name, CKEDITOR.UI_RICHCOMBO, definition );
-};
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.plugins.add( 'richcombo', +{ + requires : [ 'floatpanel', 'listblock', 'button' ], + + beforeInit : function( editor ) + { + editor.ui.addHandler( CKEDITOR.UI_RICHCOMBO, CKEDITOR.ui.richCombo.handler ); + } +}); + +/** + * Button UI element. + * @constant + * @example + */ +CKEDITOR.UI_RICHCOMBO = 3; + +CKEDITOR.ui.richCombo = CKEDITOR.tools.createClass( +{ + $ : function( definition ) + { + // Copy all definition properties to this object. + CKEDITOR.tools.extend( this, definition, + // Set defaults. + { + title : definition.label, + modes : { wysiwyg : 1 } + }); + + // We don't want the panel definition in this object. + var panelDefinition = this.panel || {}; + delete this.panel; + + this.id = CKEDITOR.tools.getNextNumber(); + + this.document = ( panelDefinition + && panelDefinition.parent + && panelDefinition.parent.getDocument() ) + || CKEDITOR.document; + + panelDefinition.className = ( panelDefinition.className || '' ) + ' cke_rcombopanel'; + panelDefinition.block = + { + multiSelect : panelDefinition.multiSelect, + attributes : panelDefinition.attributes + }; + + this._ = + { + panelDefinition : panelDefinition, + items : {}, + state : CKEDITOR.TRISTATE_OFF + }; + }, + + statics : + { + handler : + { + create : function( definition ) + { + return new CKEDITOR.ui.richCombo( definition ); + } + } + }, + + proto : + { + renderHtml : function( editor ) + { + var output = []; + this.render( editor, output ); + return output.join( '' ); + }, + + /** + * Renders the combo. + * @param {CKEDITOR.editor} editor The editor instance which this button is + * to be used by. + * @param {Array} output The output array to which append the HTML relative + * to this button. + * @example + */ + render : function( editor, output ) + { + var env = CKEDITOR.env; + + var id = 'cke_' + this.id; + var clickFn = CKEDITOR.tools.addFunction( function( $element ) + { + var _ = this._; + + if ( _.state == CKEDITOR.TRISTATE_DISABLED ) + return; + + this.createPanel( editor ); + + if ( _.on ) + { + _.panel.hide(); + return; + } + + if ( !_.committed ) + { + _.list.commit(); + _.committed = 1; + } + + var value = this.getValue(); + if ( value ) + _.list.mark( value ); + else + _.list.unmarkAll(); + + _.panel.showBlock( this.id, new CKEDITOR.dom.element( $element ), 4 ); + }, + this ); + + var instance = { + id : id, + combo : this, + focus : function() + { + var element = CKEDITOR.document.getById( id ).getChild( 1 ); + element.focus(); + }, + clickFn : clickFn + }; + + editor.on( 'mode', function() + { + this.setState( this.modes[ editor.mode ] ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED ); + }, + this ); + + var keyDownFn = CKEDITOR.tools.addFunction( function( ev, element ) + { + ev = new CKEDITOR.dom.event( ev ); + + var keystroke = ev.getKeystroke(); + switch ( keystroke ) + { + case 13 : // ENTER + case 32 : // SPACE + case 40 : // ARROW-DOWN + // Show panel + CKEDITOR.tools.callFunction( clickFn, element ); + break; + default : + // Delegate the default behavior to toolbar button key handling. + instance.onkey( instance, keystroke ); + } + + // Avoid subsequent focus grab on editor document. + ev.preventDefault(); + }); + + // For clean up + instance.keyDownFn = keyDownFn; + + output.push( + '<span class="cke_rcombo">', + '<span id=', id ); + + if ( this.className ) + output.push( ' class="', this.className, ' cke_off"'); + + output.push( + '>', + '<span id="' + id+ '_label" class=cke_label>', this.label, '</span>', + '<a hidefocus=true title="', this.title, '" tabindex="-1"', + env.gecko && env.version >= 10900 && !env.hc ? '' : ' href="javascript:void(\'' + this.label + '\')"', + ' role="button" aria-labelledby="', id , '_label" aria-describedby="', id, '_text" aria-haspopup="true"' ); + + // Some browsers don't cancel key events in the keydown but in the + // keypress. + // TODO: Check if really needed for Gecko+Mac. + if ( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.mac ) ) + { + output.push( + ' onkeypress="return false;"' ); + } + + // With Firefox, we need to force it to redraw, otherwise it + // will remain in the focus state. + if ( CKEDITOR.env.gecko ) + { + output.push( + ' onblur="this.style.cssText = this.style.cssText;"' ); + } + + output.push( + ' onkeydown="CKEDITOR.tools.callFunction( ', keyDownFn, ', event, this );"' + + ' onclick="CKEDITOR.tools.callFunction(', clickFn, ', this); return false;">' + + '<span>' + + '<span id="' + id + '_text" class="cke_text cke_inline_label">' + this.label + '</span>' + + '</span>' + + '<span class=cke_openbutton>' + ( CKEDITOR.env.hc ? '<span>▼</span>' : '' ) + '</span>' + // BLACK DOWN-POINTING TRIANGLE + '</a>' + + '</span>' + + '</span>' ); + + if ( this.onRender ) + this.onRender(); + + return instance; + }, + + createPanel : function( editor ) + { + if ( this._.panel ) + return; + + var panelDefinition = this._.panelDefinition, + panelBlockDefinition = this._.panelDefinition.block, + panelParentElement = panelDefinition.parent || CKEDITOR.document.getBody(), + panel = new CKEDITOR.ui.floatPanel( editor, panelParentElement, panelDefinition ), + list = panel.addListBlock( this.id, panelBlockDefinition ), + me = this; + + panel.onShow = function() + { + if ( me.className ) + this.element.getFirst().addClass( me.className + '_panel' ); + + me.setState( CKEDITOR.TRISTATE_ON ); + + list.focus( !me.multiSelect && me.getValue() ); + + me._.on = 1; + + if ( me.onOpen ) + me.onOpen(); + }; + + panel.onHide = function() + { + if ( me.className ) + this.element.getFirst().removeClass( me.className + '_panel' ); + + me.setState( CKEDITOR.TRISTATE_OFF ); + + me._.on = 0; + + if ( me.onClose ) + me.onClose(); + }; + + panel.onEscape = function() + { + panel.hide(); + me.document.getById( 'cke_' + me.id ).getFirst().getNext().focus(); + }; + + list.onClick = function( value, marked ) + { + // Move the focus to the main windows, otherwise it will stay + // into the floating panel, even if invisible, and Safari and + // Opera will go a bit crazy. + me.document.getWindow().focus(); + + if ( me.onClick ) + me.onClick.call( me, value, marked ); + + if ( marked ) + me.setValue( value, me._.items[ value ] ); + else + me.setValue( '' ); + + panel.hide(); + }; + + this._.panel = panel; + this._.list = list; + + panel.getBlock( this.id ).onHide = function() + { + me._.on = 0; + me.setState( CKEDITOR.TRISTATE_OFF ); + }; + + if ( this.init ) + this.init(); + }, + + setValue : function( value, text ) + { + this._.value = value; + + var textElement = this.document.getById( 'cke_' + this.id + '_text' ); + + if ( !( value || text ) ) + { + text = this.label; + textElement.addClass( 'cke_inline_label' ); + } + else + textElement.removeClass( 'cke_inline_label' ); + + textElement.setHtml( typeof text != 'undefined' ? text : value ); + }, + + getValue : function() + { + return this._.value || ''; + }, + + unmarkAll : function() + { + this._.list.unmarkAll(); + }, + + mark : function( value ) + { + this._.list.mark( value ); + }, + + hideItem : function( value ) + { + this._.list.hideItem( value ); + }, + + hideGroup : function( groupTitle ) + { + this._.list.hideGroup( groupTitle ); + }, + + showAll : function() + { + this._.list.showAll(); + }, + + add : function( value, html, text ) + { + this._.items[ value ] = text || value; + this._.list.add( value, html, text ); + }, + + startGroup : function( title ) + { + this._.list.startGroup( title ); + }, + + commit : function() + { + this._.list.commit(); + }, + + setState : function( state ) + { + if ( this._.state == state ) + return; + + this.document.getById( 'cke_' + this.id ).setState( state ); + + this._.state = state; + } + } +}); + +CKEDITOR.ui.prototype.addRichCombo = function( name, definition ) +{ + this.add( name, CKEDITOR.UI_RICHCOMBO, definition ); +}; diff --git a/_source/plugins/scayt/dialogs/options.js b/_source/plugins/scayt/dialogs/options.js index 2d5c5ab..6033507 100644 --- a/_source/plugins/scayt/dialogs/options.js +++ b/_source/plugins/scayt/dialogs/options.js @@ -1,533 +1,533 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-CKEDITOR.dialog.add( 'scaytcheck', function( editor )
-{
- var firstLoad = true,
- captions,
- doc = CKEDITOR.document,
- tags = [],
- i,
- contents = [],
- 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',
- label : editor.lang.scayt.optionsTab,
- elements : [
- {
- type : 'html',
- id : 'options',
- html : '<form name="optionsbar"><div class="inner_options">' +
- ' <div class="messagebox"></div>' +
- ' <div style="display:none;">' +
- ' <input type="checkbox" name="options" id="allCaps" />' +
- ' <label for="allCaps" id="label_allCaps"></label>' +
- ' </div>' +
- ' <div style="display:none;">' +
- ' <input name="options" type="checkbox" id="ignoreDomainNames" />' +
- ' <label for="ignoreDomainNames" id="label_ignoreDomainNames"></label>' +
- ' </div>' +
- ' <div style="display:none;">' +
- ' <input name="options" type="checkbox" id="mixedCase" />' +
- ' <label for="mixedCase" id="label_mixedCase"></label>' +
- ' </div>' +
- ' <div style="display:none;">' +
- ' <input name="options" type="checkbox" id="mixedWithDigits" />' +
- ' <label for="mixedWithDigits" id="label_mixedWithDigits"></label>' +
- ' </div>' +
- '</div></form>'
- }
- ]
- },
- {
- id : 'langs',
- label : editor.lang.scayt.languagesTab,
- elements : [
- {
- type : 'html',
- id : 'langs',
- html : '<form name="languagesbar"><div class="inner_langs">' +
- ' <div class="messagebox"></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>'
- }
- ]
- },
- {
- id : 'dictionaries',
- label : editor.lang.scayt.dictionariesTab,
- elements : [
- {
- type : 'html',
- style: '',
- 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>'+
- ' <span class="cke_dialog_ui_labeled_content" >'+
- ' <div class="cke_dialog_ui_input_text">'+
- ' <input id="dic_name" type="text" class="cke_dialog_ui_input_text"/>'+
- ' </div></span></div>'+
- ' <div style="margin:5px auto; width:80%;white-space:normal;">'+
- ' <a style="display:none;" class="cke_dialog_ui_button" href="javascript:void(0)" id="dic_create">'+
- ' </a>' +
- ' <a style="display:none;" class="cke_dialog_ui_button" href="javascript:void(0)" id="dic_delete">'+
- ' </a>' +
- ' <a style="display:none;" class="cke_dialog_ui_button" href="javascript:void(0)" id="dic_rename">'+
- ' </a>' +
- ' <a style="display:none;" class="cke_dialog_ui_button" href="javascript:void(0)" id="dic_restore">'+
- ' </a>' +
- ' </div>' +
- ' <div style="margin:5px auto; width:95%;white-space:normal;" id="dic_info"></div>' +
- '</div></form>'
- }
- ]
- },
- {
- id : 'about',
- label : editor.lang.scayt.aboutTab,
- elements : [
- {
- type : 'html',
- id : 'about',
- style : 'margin: 5px 5px;',
- html : '<div id="scayt_about"></div>'
- }
- ]
- }
- ];
-
- var dialogDefiniton = {
- title : editor.lang.scayt.title,
- minWidth : 360,
- minHeight : 220,
- onShow : function()
- {
- var dialog = this;
- dialog.data = editor.fire( 'scaytDialog', {} );
- dialog.options = dialog.data.scayt_control.option();
- dialog.sLang = dialog.data.scayt_control.sLang;
-
- if ( !dialog.data || !dialog.data.scayt || !dialog.data.scayt_control )
- {
- alert( 'Error loading application service' );
- dialog.hide();
- return;
- }
-
- var stop = 0;
- if ( firstLoad )
- {
- 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 );
-
- dialog.selectPage( dialog.data.tab );
- },
- onOk : function()
- {
- var scayt_control = this.data.scayt_control;
- scayt_control.option( this.options );
- // Setup languge if it was changed.
- var csLang = this.chosed_lang;
- 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 );
-
- 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;
-
-
- var init_with_captions = function()
- {
- var dialog = this,
- lang_list = dialog.data.scayt.getLangList(),
- buttons = [ 'dic_create','dic_delete','dic_rename','dic_restore' ],
- labels = optionsIds,
- i;
-
- // Add buttons titles
- if (userDicActive)
- {
- for ( i in buttons )
- {
- var button = buttons[ i ];
- doc.getById( button ).setHtml( '<span class="cke_dialog_ui_button">' + captions[ 'button_' + button] +'</span>' );
- }
- doc.getById( 'dic_info' ).setHtml( captions[ 'dic_info' ] );
- }
-
-
- // Fill options and dictionary labels.
- if (tags[0] == 1)
- {
- for ( i in labels )
- {
- 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";
- }
- }
- }
-
- var about = '<p>' + captions[ 'about_throwt_image' ] + '</p>'+
- '<p>' + captions[ 'version' ] + dialog.data.scayt.version.toString() + '</p>' +
- '<p>' + captions[ 'about_throwt_copy' ] + '</p>';
-
- doc.getById( 'scayt_about' ).setHtml( about );
-
- // Create languages tab.
- var createOption = function( option, list )
- {
- var label = doc.createElement( 'label' );
- label.setAttribute( 'for', 'cke_option' + option );
- label.setHtml( list[ option ] );
-
- if ( dialog.sLang == option ) // Current.
- dialog.chosed_lang = option;
-
- var div = doc.createElement( 'div' );
- var radio = CKEDITOR.dom.element.createFromHtml( '<input id="cke_option' +
- option + '" type="radio" ' +
- ( dialog.sLang == option ? 'checked="checked"' : '' ) +
- ' value="' + option + '" name="scayt_lang" />' );
-
- radio.on( 'click', function()
- {
- this.$.checked = true;
- dialog.chosed_lang = option;
- });
-
- div.append( radio );
- div.append( label );
-
- return {
- lang : list[ option ],
- code : option,
- radio : div
- };
- };
-
- var langList = [];
- 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 );
-
- 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 );
- }
- }
-
- // 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];
-
- var err_massage = captions["err_dic_create"];
- var suc_massage = captions["succ_dic_create"];
-
- 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
- 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"];
-
- // 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 )
- {
- // try to restore existing dictionary
- var all_buttons = dic_buttons[0] + ',' + dic_buttons[1];
- var err_massage = captions["err_dic_restore"];
- var suc_massage = captions["succ_dic_restore"];
-
- window.scayt.restoreUserDictionary(dic_name,
- function(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 );
- },
- function(arg)
- {
- 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]);
- if ( dic_button )
- dic_button.on( 'click', onDicButtonClick, this );
- }
- };
-
- var reload = function()
- {
- var dialog = this;
- // for enabled options tab
- if (tags[0] == 1){
- var opto = getBOMAllOptions();
-
- // Animate options.
- for ( var k=0,l = opto.length; k<l;k++ )
- {
-
- var i = opto[k].id;
- var checkbox = doc.getById( i );
-
- if ( checkbox )
- {
- 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 )
- {
- window.scayt.getNameUserDictionary(
- function( o )
- {
- 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("");
- });
- 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>') ;
- }
- 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";
- }
-
- }
- function hide_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 = "none";
- }
- }
- function set_dic_name ( dic_name )
- {
- doc.getById('dic_name').$.value= dic_name;
- }
-
- return dialogDefiniton;
-});
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.dialog.add( 'scaytcheck', function( editor ) +{ + var firstLoad = true, + captions, + doc = CKEDITOR.document, + tags = [], + i, + contents = [], + 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', + label : editor.lang.scayt.optionsTab, + elements : [ + { + type : 'html', + id : 'options', + html : '<form name="optionsbar"><div class="inner_options">' + + ' <div class="messagebox"></div>' + + ' <div style="display:none;">' + + ' <input type="checkbox" name="options" id="allCaps" />' + + ' <label for="allCaps" id="label_allCaps"></label>' + + ' </div>' + + ' <div style="display:none;">' + + ' <input name="options" type="checkbox" id="ignoreDomainNames" />' + + ' <label for="ignoreDomainNames" id="label_ignoreDomainNames"></label>' + + ' </div>' + + ' <div style="display:none;">' + + ' <input name="options" type="checkbox" id="mixedCase" />' + + ' <label for="mixedCase" id="label_mixedCase"></label>' + + ' </div>' + + ' <div style="display:none;">' + + ' <input name="options" type="checkbox" id="mixedWithDigits" />' + + ' <label for="mixedWithDigits" id="label_mixedWithDigits"></label>' + + ' </div>' + + '</div></form>' + } + ] + }, + { + id : 'langs', + label : editor.lang.scayt.languagesTab, + elements : [ + { + type : 'html', + id : 'langs', + html : '<form name="languagesbar"><div class="inner_langs">' + + ' <div class="messagebox"></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>' + } + ] + }, + { + id : 'dictionaries', + label : editor.lang.scayt.dictionariesTab, + elements : [ + { + type : 'html', + style: '', + 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>'+ + ' <span class="cke_dialog_ui_labeled_content" >'+ + ' <div class="cke_dialog_ui_input_text">'+ + ' <input id="dic_name" type="text" class="cke_dialog_ui_input_text"/>'+ + ' </div></span></div>'+ + ' <div style="margin:5px auto; width:80%;white-space:normal;">'+ + ' <a style="display:none;" class="cke_dialog_ui_button" href="javascript:void(0)" id="dic_create">'+ + ' </a>' + + ' <a style="display:none;" class="cke_dialog_ui_button" href="javascript:void(0)" id="dic_delete">'+ + ' </a>' + + ' <a style="display:none;" class="cke_dialog_ui_button" href="javascript:void(0)" id="dic_rename">'+ + ' </a>' + + ' <a style="display:none;" class="cke_dialog_ui_button" href="javascript:void(0)" id="dic_restore">'+ + ' </a>' + + ' </div>' + + ' <div style="margin:5px auto; width:95%;white-space:normal;" id="dic_info"></div>' + + '</div></form>' + } + ] + }, + { + id : 'about', + label : editor.lang.scayt.aboutTab, + elements : [ + { + type : 'html', + id : 'about', + style : 'margin: 5px 5px;', + html : '<div id="scayt_about"></div>' + } + ] + } + ]; + + var dialogDefiniton = { + title : editor.lang.scayt.title, + minWidth : 360, + minHeight : 220, + onShow : function() + { + var dialog = this; + dialog.data = editor.fire( 'scaytDialog', {} ); + dialog.options = dialog.data.scayt_control.option(); + dialog.sLang = dialog.data.scayt_control.sLang; + + if ( !dialog.data || !dialog.data.scayt || !dialog.data.scayt_control ) + { + alert( 'Error loading application service' ); + dialog.hide(); + return; + } + + var stop = 0; + if ( firstLoad ) + { + 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 ); + + dialog.selectPage( dialog.data.tab ); + }, + onOk : function() + { + var scayt_control = this.data.scayt_control; + scayt_control.option( this.options ); + // Setup languge if it was changed. + var csLang = this.chosed_lang; + 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 ); + + 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; + + + var init_with_captions = function() + { + var dialog = this, + lang_list = dialog.data.scayt.getLangList(), + buttons = [ 'dic_create','dic_delete','dic_rename','dic_restore' ], + labels = optionsIds, + i; + + // Add buttons titles + if ( userDicActive ) + { + for ( i = 0; i < buttons.length; i++ ) + { + var button = buttons[ i ]; + doc.getById( button ).setHtml( '<span class="cke_dialog_ui_button">' + captions[ 'button_' + button] +'</span>' ); + } + doc.getById( 'dic_info' ).setHtml( captions[ 'dic_info' ] ); + } + + + // Fill options and dictionary labels. + if ( tags[0] == 1 ) + { + for ( i in labels ) + { + 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"; + } + } + } + + var about = '<p><img src="' + window.scayt.getAboutInfo().logoURL + '" /></p>' + + '<p>' + captions[ 'version' ] + window.scayt.getAboutInfo().version.toString() + '</p>' + + '<p>' + captions[ 'about_throwt_copy' ] + '</p>'; + + doc.getById( 'scayt_about' ).setHtml( about ); + + // Create languages tab. + var createOption = function( option, list ) + { + var label = doc.createElement( 'label' ); + label.setAttribute( 'for', 'cke_option' + option ); + label.setHtml( list[ option ] ); + + if ( dialog.sLang == option ) // Current. + dialog.chosed_lang = option; + + var div = doc.createElement( 'div' ); + var radio = CKEDITOR.dom.element.createFromHtml( '<input id="cke_option' + + option + '" type="radio" ' + + ( dialog.sLang == option ? 'checked="checked"' : '' ) + + ' value="' + option + '" name="scayt_lang" />' ); + + radio.on( 'click', function() + { + this.$.checked = true; + dialog.chosed_lang = option; + }); + + div.append( radio ); + div.append( label ); + + return { + lang : list[ option ], + code : option, + radio : div + }; + }; + + var langList = []; + 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 ); + + 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 ); + } + } + + // 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]; + + var err_massage = captions["err_dic_create"]; + var suc_massage = captions["succ_dic_create"]; + + 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 + 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"]; + + // 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 ) + { + // try to restore existing dictionary + var all_buttons = dic_buttons[0] + ',' + dic_buttons[1]; + var err_massage = captions["err_dic_restore"]; + var suc_massage = captions["succ_dic_restore"]; + + window.scayt.restoreUserDictionary(dic_name, + function(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 ); + }, + function(arg) + { + 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]); + if ( dic_button ) + dic_button.on( 'click', onDicButtonClick, this ); + } + }; + + var reload = function() + { + var dialog = this; + // for enabled options tab + if (tags[0] == 1){ + var opto = getBOMAllOptions(); + + // Animate options. + for ( var k=0,l = opto.length; k<l;k++ ) + { + + var i = opto[k].id; + var checkbox = doc.getById( i ); + + if ( checkbox ) + { + 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 ) + { + window.scayt.getNameUserDictionary( + function( o ) + { + 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(""); + }); + 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>') ; + } + 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"; + } + + } + function hide_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 = "none"; + } + } + function set_dic_name ( 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 718aee6..9786b49 100644 --- a/_source/plugins/scayt/plugin.js +++ b/_source/plugins/scayt/plugin.js @@ -1,603 +1,960 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-/**
- * @fileOverview Spell Check As You Type (SCAYT).
- * Button name : Scayt.
- */
-
-(function()
-{
- var commandName = 'scaytcheck',
- 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()
- {
- var editor = this;
-
- var createInstance = function() // Create new instance every time Document is created.
- {
- // Initialise Scayt instance.
- var oParams = {};
- 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:WvF0D4-UtPqN1-43nkD4-NKvUm2-daQqk3-LmNiI-z7Ysb4-mwry24-T8YrS3-Q2tpq2";
- oParams.customDictionaryIds = editor.config.scayt_customDictionaryIds;
- oParams.userDictionaryName = editor.config.scayt_userDictionaryName;
- oParams.sLang = editor.config.scayt_sLang || "en_US";
-
- if ( CKEDITOR._scaytParams )
- {
- for ( var k in CKEDITOR._scaytParams )
- {
- oParams[ k ] = CKEDITOR._scaytParams[ k ];
- }
- }
-
- var scayt_control = new window.scayt( oParams );
-
- // Copy config.
- var lastInstance = plugin.instances[ editor.name ];
- if ( lastInstance )
- {
- scayt_control.sLang = lastInstance.sLang;
- scayt_control.option( lastInstance.option() );
- scayt_control.paused = lastInstance.paused;
- }
-
- 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_paused === false );
- } catch (e) {}
-
- editor.fire( 'showScaytState' );
- };
-
- editor.on( 'contentDom', createInstance );
- editor.on( 'contentDomUnload', function()
- {
- // Remove scripts.
- var scripts = CKEDITOR.document.getElementsByTag( 'script' ),
- scaytIdRegex = /^dojoIoScript(\d+)$/i,
- scaytSrcRegex = /^https?:\/\/svc\.spellchecker\.net\/spellcheck\/script\/ssrv\.cgi/i;
-
- for ( var i=0; i < scripts.count(); i++ )
- {
- var script = scripts.getItem( i ),
- id = script.getId(),
- src = script.getAttribute( 'src' );
-
- if ( id && src && id.match( scaytIdRegex ) && src.match( scaytSrcRegex ))
- script.remove();
- }
- });
-
- editor.on( 'beforeCommandExec', function( ev ) // Disable SCAYT before Source command execution.
- {
- if ( (ev.data.name == 'source' || ev.data.name == 'newpage') && editor.mode == 'wysiwyg' )
- {
- var scayt_instanse = plugin.getScayt( editor );
- if ( scayt_instanse )
- {
- 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()
- {
- if ( plugin.isScaytEnabled( editor ) )
- plugin.getScayt( editor ).refresh();
- });
-
- // Reload spell-checking for current word after insertion completed.
- editor.on( 'insertElement', 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( '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;
- ev.data.scayt_control = plugin.getScayt( editor );
- ev.data.tab = openPage;
- ev.data.scayt = window.scayt;
- });
-
- var dataProcessor = editor.dataProcessor,
- htmlFilter = dataProcessor && dataProcessor.htmlFilter;
- if ( htmlFilter )
- {
- htmlFilter.addRules(
- {
- elements :
- {
- span : function( element )
- {
- if ( element.attributes.scayt_word && element.attributes.scaytid )
- {
- delete element.name; // Write children, but don't write this node.
- return element;
- }
- }
- }
- }
- );
- }
-
- if ( editor.document )
- createInstance();
- };
-
- CKEDITOR.plugins.scayt =
- {
- engineLoaded : false,
- instances : {},
- getScayt : function( editor )
- {
- return this.instances[ editor.name ];
- },
- isScaytReady : function( editor )
- {
- return this.engineLoaded === true &&
- 'undefined' !== typeof window.scayt && this.getScayt( editor );
- },
- isScaytEnabled : function( editor )
- {
- var scayt_instanse = this.getScayt( editor );
- return ( scayt_instanse ) ? scayt_instanse.disabled === false : false;
- },
- loadEngine : function( editor )
- {
- if ( this.engineLoaded === true )
- return onEngineLoad.apply( editor ); // Add new instance.
- else if ( this.engineLoaded == -1 ) // We are waiting.
- return CKEDITOR.on( 'scaytReady', function(){ onEngineLoad.apply( editor );} ); // Use function(){} to avoid rejection as duplicate.
-
- CKEDITOR.on( 'scaytReady', onEngineLoad, editor );
- CKEDITOR.on( 'scaytReady', function()
- {
- this.engineLoaded = true;
- },
- this,
- null,
- 0 ); // First to run.
-
- this.engineLoaded = -1; // Loading in progress.
-
- // compose scayt url
- var protocol = document.location.protocol;
- // Default to 'http' for unknown.
- protocol = protocol.search( /https?:/) != -1? protocol : 'http:';
- var baseUrl = "svc.spellchecker.net/spellcheck3/lf/scayt/scayt21.js";
-
- var scaytUrl = editor.config.scayt_srcUrl || ( protocol + "//" + baseUrl );
- var scaytConfigBaseUrl = plugin.parseUrl( scaytUrl ).path + "/";
-
- CKEDITOR._djScaytConfig =
- {
- baseUrl: scaytConfigBaseUrl,
- addOnLoad:
- [
- function()
- {
- CKEDITOR.fireOnce( "scaytReady" );
- }
- ],
- isDebug: false
- };
- // Append javascript code.
- CKEDITOR.document.getHead().append(
- CKEDITOR.document.createElement( 'script',
- {
- attributes :
- {
- type : 'text/javascript',
- src : scaytUrl
- }
- })
- );
-
- return null;
- },
- parseUrl : function ( data )
- {
- var match;
- if ( data.match && ( match = data.match(/(.*)[\/\\](.*?\.\w+)$/) ) )
- return { path: match[1], file: match[2] };
- else
- return data;
- }
- };
-
- var plugin = CKEDITOR.plugins.scayt;
-
- // Context menu constructing.
- var addButtonCommand = function( editor, buttonName, buttonLabel, commandName, command, menugroup, menuOrder )
- {
- editor.addCommand( commandName, command );
-
- // If the "menu" plugin is loaded, register the menu item.
- editor.addMenuItem( commandName,
- {
- label : buttonLabel,
- command : commandName,
- group : menugroup,
- order : menuOrder
- });
- };
-
- var commandDefinition =
- {
- preserveState : true,
- editorFocus : false,
-
- exec: function( editor )
- {
- if ( plugin.isScaytReady( editor ) )
- {
- var isEnabled = plugin.isScaytEnabled( editor );
-
- this.setState( isEnabled ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_ON );
-
- var scayt_control = plugin.getScayt( editor );
- scayt_control.setDisabled( isEnabled );
- }
- else if ( !editor.config.scayt_autoStartup && plugin.engineLoaded >= 0 ) // Load first time
- {
- this.setState( CKEDITOR.TRISTATE_DISABLED );
-
- editor.on( 'showScaytState', function()
- {
- this.removeListener();
- this.setState( plugin.isScaytEnabled( editor ) ? CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF );
- },
- this);
-
- plugin.loadEngine( editor );
- }
- }
- };
-
- // Add scayt plugin.
- CKEDITOR.plugins.add( 'scayt',
- {
- requires : [ 'menubutton' ],
-
- beforeInit : function( editor )
- {
- // Register own rbc menu group.
- editor.config.menu_groups = 'scayt_suggest,scayt_moresuggest,scayt_control,' + editor.config.menu_groups;
- },
-
- init : function( editor )
- {
- var moreSuggestions = {};
- var mainSuggestions = {};
-
- // Scayt command.
- var command = editor.addCommand( commandName, commandDefinition );
-
- // 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 );
- // combine menu items to render
- var uiMuneItems = {};
-
- // allways added
- uiMuneItems.scaytToggle =
- {
- label : editor.lang.scayt.enable,
- command : commandName,
- group : menuGroup
- };
-
- if (uiTabs[0] == 1)
- uiMuneItems.scaytOptions =
- {
- label : editor.lang.scayt.options,
- group : menuGroup,
- onClick : function()
- {
- openPage = 'options';
- editor.openDialog( commandName );
- }
- };
-
- if (uiTabs[1] == 1)
- uiMuneItems.scaytLangs =
- {
- label : editor.lang.scayt.langs,
- group : menuGroup,
- onClick : function()
- {
- openPage = 'langs';
- editor.openDialog( commandName );
- }
- };
- if (uiTabs[2] == 1)
- uiMuneItems.scaytDict =
- {
- label : editor.lang.scayt.dictionariesTab,
- group : menuGroup,
- onClick : function()
- {
- 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,
- {
- label : editor.lang.scayt.title,
- title : editor.lang.scayt.title,
- className : 'cke_button_scayt',
- onRender: function()
- {
- command.on( 'state', function()
- {
- this.setState( command.state );
- },
- this);
- },
- onMenu : function()
- {
- var isEnabled = plugin.isScaytEnabled( editor );
-
- editor.getMenuItem( 'scaytToggle' ).label = editor.lang.scayt[ isEnabled ? 'disable' : 'enable' ];
-
- return {
- 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
- };
- }
- });
-
- // If the "contextmenu" plugin is loaded, register the listeners.
- if ( editor.contextMenu && editor.addMenuItems )
- {
- editor.contextMenu.addListener( function( element )
- {
- if ( !( plugin.isScaytEnabled( editor ) && element ) )
- return null;
-
- var scayt_control = plugin.getScayt( editor ),
- word = scayt_control.getWord( element.$ );
-
- if ( !word )
- return null;
-
- var sLang = scayt_control.getLang(),
- _r = {},
- items_suggestion = window.scayt.getSuggestion( word, sLang );
- if (!items_suggestion || !items_suggestion.length )
- return null;
- // Remove unused commands and menuitems
- for ( i in moreSuggestions )
- {
- delete editor._.menuItems[ i ];
- delete editor._.commands[ i ];
- }
- for ( i in mainSuggestions )
- {
- delete editor._.menuItems[ i ];
- delete editor._.commands[ i ];
- }
- moreSuggestions = {}; // Reset items.
- mainSuggestions = {};
-
- 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 )
- {
- var commandName = 'scayt_suggestion_' + items_suggestion[i].replace( ' ', '_' );
- var exec = ( function( el, s )
- {
- return {
- exec: function()
- {
- scayt_control.replace(el, s);
- }
- };
- })( element.$, items_suggestion[i] );
-
- 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 if ( moreSuggestionsUnable == "on" )
- {
- addButtonCommand( editor, 'button_' + commandName, items_suggestion[i],
- commandName, exec, 'scayt_moresuggest', i + 1 );
- moreSuggestions[ commandName ] = CKEDITOR.TRISTATE_OFF;
- moreSuggestionsUnableAdded = true;
- }
- }
-
- if ( moreSuggestionsUnableAdded ){
- // Rgister the More suggestions group;
- editor.addMenuItem( 'scayt_moresuggest',
- {
- label : editor.lang.scayt.moreSuggestions,
- group : 'scayt_moresuggest',
- order : 10,
- getItems : function()
- {
- return moreSuggestions;
- }
- });
- mainSuggestions[ 'scayt_moresuggest' ] = CKEDITOR.TRISTATE_OFF;
-
- }
-
- if ( in_array( "all",contextCommands ) || in_array("ignore",contextCommands) )
- {
- 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;
- }
-
- 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;
- }
-
- 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 );
-
- return mainSuggestions;
- });
- }
-
- // Start plugin
- if ( editor.config.scayt_autoStartup )
- {
- var showInitialState = function()
- {
- editor.removeListener( 'showScaytState', showInitialState );
- command.setState( plugin.isScaytEnabled( editor ) ? CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF );
- };
- editor.on( 'showScaytState', showInitialState );
-
- plugin.loadEngine( editor );
- }
- }
- });
-})();
-
-// TODO: Documentation
-// CKEDITOR.config.scayt_maxSuggestions
-// CKEDITOR.config.scayt_autoStartup
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Spell Check As You Type (SCAYT). + * Button name : Scayt. + */ + +(function() +{ + var commandName = 'scaytcheck', + openPage = ''; + + // 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() + { + var editor = this; + + var createInstance = function() // Create new instance every time Document is created. + { + // Initialise Scayt instance. + var oParams = {}; + // Get the iframe. + oParams.srcNodeRef = editor.document.getWindow().$.frameElement; + // syntax : AppName.AppVersion@AppRevision + oParams.assocApp = 'CKEDITOR.' + CKEDITOR.version + '@' + CKEDITOR.revision; + 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.sLang = editor.config.scayt_sLang || 'en_US'; + + // Introduce SCAYT onLoad callback. (#5632) + oParams.onLoad = function() + { + // Draw down word marker to avoid being covered by background-color style.(#5466) + if ( !( CKEDITOR.env.ie && CKEDITOR.env.version < 8 ) ) + this.addStyle( this.selectorCss(), 'padding-bottom: 2px !important;' ); + + // Call scayt_control.focus when SCAYT loaded + // and only if editor has focus and scayt control creates at first time (#5720) + if ( editor.focusManager.hasFocus && !plugin.isControlRestored( editor ) ) + this.focus(); + + }; + + oParams.onBeforeChange = function() + { + if ( plugin.getScayt( editor ) && !editor.checkDirty() ) + setTimeout( function(){ editor.resetDirty(); } ); + }; + + var scayt_custom_params = window.scayt_custom_params; + if ( typeof scayt_custom_params == 'object') + { + for ( var k in scayt_custom_params ) + { + oParams[ k ] = scayt_custom_params[ k ]; + } + } + // needs for restoring a specific scayt control settings + if ( plugin.getControlId(editor) ) + oParams.id = plugin.getControlId(editor); + + var scayt_control = new window.scayt( oParams ); + + scayt_control.afterMarkupRemove.push( function( node ) + { + ( new CKEDITOR.dom.element( node, scayt_control.document ) ).mergeSiblings(); + } ); + + // Copy config. + var lastInstance = plugin.instances[ editor.name ]; + if ( lastInstance ) + { + scayt_control.sLang = lastInstance.sLang; + scayt_control.option( lastInstance.option() ); + scayt_control.paused = lastInstance.paused; + } + + 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( plugin.isPaused( editor ) === false ); + } catch (e) {} + + editor.fire( 'showScaytState' ); + }; + + editor.on( 'contentDom', createInstance ); + editor.on( 'contentDomUnload', function() + { + // Remove scripts. + var scripts = CKEDITOR.document.getElementsByTag( 'script' ), + scaytIdRegex = /^dojoIoScript(\d+)$/i, + scaytSrcRegex = /^https?:\/\/svc\.spellchecker\.net\/spellcheck\/script\/ssrv\.cgi/i; + + for ( var i=0; i < scripts.count(); i++ ) + { + var script = scripts.getItem( i ), + id = script.getId(), + src = script.getAttribute( 'src' ); + + if ( id && src && id.match( scaytIdRegex ) && src.match( scaytSrcRegex )) + script.remove(); + } + }); + + editor.on( 'beforeCommandExec', function( ev ) // Disable SCAYT before Source command execution. + { + if ( (ev.data.name == 'source' || ev.data.name == 'newpage') && editor.mode == 'wysiwyg' ) + { + var scayt_instance = plugin.getScayt( editor ); + if ( scayt_instance ) + { + plugin.setPaused( editor, !scayt_instance.disabled ); + // store a control id for restore a specific scayt control settings + plugin.setControlId( editor, scayt_instance.id ); + scayt_instance.destroy( true ); + delete plugin.instances[ editor.name ]; + } + } + // Catch on source mode switch off (#5720) + else if ( ev.data.name == 'source' && editor.mode == 'source' ) + plugin.markControlRestore( editor ); + }); + + editor.on( 'afterCommandExec', function( ev ) + { + if ( !plugin.isScaytEnabled( editor ) ) + return; + + if ( editor.mode == 'wysiwyg' && ( ev.data.name == 'undo' || ev.data.name == 'redo' ) ) + window.setTimeout( function() { plugin.getScayt( editor ).refresh(); }, 10 ); + }); + + editor.on( 'destroy', function( ev ) + { + var editor = ev.editor, + scayt_instance = plugin.getScayt( editor ); + + // SCAYT instance might already get destroyed by mode switch (#5744). + if ( !scayt_instance ) + return; + + delete plugin.instances[ editor.name ]; + // store a control id for restore a specific scayt control settings + plugin.setControlId( editor, scayt_instance.id ); + scayt_instance.destroy( true ); + }); + + // Listen to data manipulation to reflect scayt markup. + editor.on( 'afterSetData', function() + { + if ( plugin.isScaytEnabled( editor ) ) { + window.setTimeout( function() + { + var instance = plugin.getScayt( editor ); + instance && instance.refresh(); + }, 10 ); + } + }); + + // Reload spell-checking for current word after insertion completed. + editor.on( 'insertElement', 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 ); + + // Return focus to the editor and refresh SCAYT markup (#5573). + window.setTimeout( function() + { + scayt_instance.focus(); + scayt_instance.refresh(); + }, 10 ); + } + }, 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 ); + + // Return focus to the editor (#5573) + // Refresh SCAYT markup + window.setTimeout( function() + { + scayt_instance.focus(); + scayt_instance.refresh(); + }, 10 ); + } + }, this, null, 50 ); + + editor.on( 'scaytDialog', function( ev ) // Communication with dialog. + { + ev.data.djConfig = window.djConfig; + ev.data.scayt_control = plugin.getScayt( editor ); + ev.data.tab = openPage; + ev.data.scayt = window.scayt; + }); + + var dataProcessor = editor.dataProcessor, + htmlFilter = dataProcessor && dataProcessor.htmlFilter; + + if ( htmlFilter ) + { + htmlFilter.addRules( + { + elements : + { + span : function( element ) + { + if ( element.attributes.scayt_word && element.attributes.scaytid ) + { + delete element.name; // Write children, but don't write this node. + return element; + } + } + } + } + ); + } + + // Override Image.equals method avoid CK snapshot module to add SCAYT markup to snapshots. (#5546) + var undoImagePrototype = CKEDITOR.plugins.undo.Image.prototype; + undoImagePrototype.equals = CKEDITOR.tools.override( undoImagePrototype.equals, function( org ) + { + return function( otherImage ) + { + var thisContents = this.contents, + otherContents = otherImage.contents; + var scayt_instance = plugin.getScayt( this.editor ); + // Making the comparison based on content without SCAYT word markers. + if ( scayt_instance && plugin.isScaytReady( this.editor ) ) + { + // scayt::reset might return value undefined. (#5742) + this.contents = scayt_instance.reset( thisContents ) || ''; + otherImage.contents = scayt_instance.reset( otherContents ) || ''; + } + + var retval = org.apply( this, arguments ); + + this.contents = thisContents; + otherImage.contents = otherContents; + return retval; + }; + }); + + if ( editor.document ) + createInstance(); + }; + +CKEDITOR.plugins.scayt = + { + engineLoaded : false, + instances : {}, + // Data storage for SCAYT control, based on editor instances + controlInfo : {}, + setControlInfo : function( editor, o ) + { + if ( editor && editor.name && typeof ( this.controlInfo[ editor.name ] ) != 'object' ) + this.controlInfo[ editor.name ] = {}; + + for ( var infoOpt in o ) + this.controlInfo[ editor.name ][ infoOpt ] = o[ infoOpt ]; + }, + isControlRestored : function ( editor ) + { + if ( editor && + editor.name && + this.controlInfo[ editor.name ] ) + { + return this.controlInfo[ editor.name ].restored ; + } + return false; + }, + markControlRestore : function ( editor ) + { + this.setControlInfo( editor,{ restored:true } ); + }, + setControlId: function (editor, id) + { + this.setControlInfo( editor,{ id:id } ); + }, + getControlId: function (editor) + { + if ( editor && + editor.name && + this.controlInfo[ editor.name ] && + this.controlInfo[ editor.name ].id ) + { + return this.controlInfo[ editor.name ].id; + } + return null; + }, + setPaused: function ( editor , bool ) + { + this.setControlInfo( editor,{ paused:bool } ); + }, + isPaused: function (editor) + { + if ( editor && + editor.name && + this.controlInfo[editor.name] ) + { + return this.controlInfo[editor.name].paused ; + } + return undefined; + }, + getScayt : function( editor ) + { + return this.instances[ editor.name ]; + }, + isScaytReady : function( editor ) + { + return this.engineLoaded === true && + 'undefined' !== typeof window.scayt && this.getScayt( editor ); + }, + isScaytEnabled : function( editor ) + { + var scayt_instance = this.getScayt( editor ); + return ( scayt_instance ) ? scayt_instance.disabled === false : false; + }, + loadEngine : function( editor ) + { + // SCAYT doesn't work with Firefox2, Opera. + if ( CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 || CKEDITOR.env.opera ) + return editor.fire( 'showScaytState' ); + + if ( this.engineLoaded === true ) + return onEngineLoad.apply( editor ); // Add new instance. + else if ( this.engineLoaded == -1 ) // We are waiting. + return CKEDITOR.on( 'scaytReady', function(){ onEngineLoad.apply( editor ); } ); // Use function(){} to avoid rejection as duplicate. + + CKEDITOR.on( 'scaytReady', onEngineLoad, editor ); + CKEDITOR.on( 'scaytReady', function() + { + this.engineLoaded = true; + }, + this, + null, + 0 + ); // First to run. + + this.engineLoaded = -1; // Loading in progress. + + // compose scayt url + var protocol = document.location.protocol; + // Default to 'http' for unknown. + protocol = protocol.search( /https?:/) != -1? protocol : 'http:'; + var baseUrl = 'svc.spellchecker.net/spellcheck31/lf/scayt24/loader__base.js'; + + var scaytUrl = editor.config.scayt_srcUrl || ( protocol + '//' + baseUrl ); + var scaytConfigBaseUrl = plugin.parseUrl( scaytUrl ).path + '/'; + + if( window.scayt == undefined ) + { + CKEDITOR._djScaytConfig = + { + baseUrl: scaytConfigBaseUrl, + addOnLoad: + [ + function() + { + CKEDITOR.fireOnce( 'scaytReady' ); + } + ], + isDebug: false + }; + // Append javascript code. + CKEDITOR.document.getHead().append( + CKEDITOR.document.createElement( 'script', + { + attributes : + { + type : 'text/javascript', + async : 'true', + src : scaytUrl + } + }) + ); + } + else + CKEDITOR.fireOnce( 'scaytReady' ); + + return null; + }, + parseUrl : function ( data ) + { + var match; + if ( data.match && ( match = data.match(/(.*)[\/\\](.*?\.\w+)$/) ) ) + return { path: match[1], file: match[2] }; + else + return data; + } + }; + + var plugin = CKEDITOR.plugins.scayt; + + // Context menu constructing. + var addButtonCommand = function( editor, buttonName, buttonLabel, commandName, command, menugroup, menuOrder ) + { + editor.addCommand( commandName, command ); + + // If the "menu" plugin is loaded, register the menu item. + editor.addMenuItem( commandName, + { + label : buttonLabel, + command : commandName, + group : menugroup, + order : menuOrder + }); + }; + + var commandDefinition = + { + preserveState : true, + editorFocus : false, + + exec: function( editor ) + { + var autoStartup = editor.config.scayt_autoStartup; + autoStartup = ( autoStartup == undefined ) || autoStartup; + + if ( plugin.isScaytReady( editor ) ) + { + var isEnabled = plugin.isScaytEnabled( editor ); + + this.setState( isEnabled ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_ON ); + + var scayt_control = plugin.getScayt( editor ); + // the place where the status of editor focus should be restored + // after there will be ability to store its state before SCAYT button click + // if (storedFocusState is focused ) + // scayt_control.focus(); + // + // now focus is set certainly + scayt_control.focus( ); + scayt_control.setDisabled( isEnabled ); + } + else if ( !autoStartup && plugin.engineLoaded >= 0 ) // Load first time + { + this.setState( CKEDITOR.TRISTATE_DISABLED ); + plugin.loadEngine( editor ); + } + } + }; + + // Add scayt plugin. + CKEDITOR.plugins.add( 'scayt', + { + requires : [ 'menubutton' ], + + beforeInit : function( editor ) + { + var items_order = editor.config.scayt_contextMenuItemsOrder + || 'suggest|moresuggest|control', + items_order_str = ""; + + items_order = items_order.split( '|' ); + + if ( items_order && items_order.length ) + { + for ( var pos in items_order ) + items_order_str += 'scayt_' + items_order[ pos ] + ( items_order.length != parseInt( pos, 10 ) + 1 ? ',' : '' ); + } + + // Register scayt rbc menu group. + if ( editor.config.scayt_contextMenuOntop ) + // Put it on top of all context menu items + editor.config.menu_groups = items_order_str + ',' + editor.config.menu_groups; + else + // Put it down + editor.config.menu_groups = editor.config.menu_groups + ',' +items_order_str; + }, + + init : function( editor ) + { + var moreSuggestions = {}; + var mainSuggestions = {}; + + // Scayt command. + var command = editor.addCommand( commandName, commandDefinition ); + + // 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 to array convert + confuiTabs = confuiTabs.split( ',' ); + // check array length ! always 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 ); + // combine menu items to render + var uiMuneItems = {}; + + // always added + uiMuneItems.scaytToggle = + { + label : editor.lang.scayt.enable, + command : commandName, + group : menuGroup + }; + + if (uiTabs[0] == 1) + uiMuneItems.scaytOptions = + { + label : editor.lang.scayt.options, + group : menuGroup, + onClick : function() + { + openPage = 'options'; + editor.openDialog( commandName ); + } + }; + + if (uiTabs[1] == 1) + uiMuneItems.scaytLangs = + { + label : editor.lang.scayt.langs, + group : menuGroup, + onClick : function() + { + openPage = 'langs'; + editor.openDialog( commandName ); + } + }; + if (uiTabs[2] == 1) + uiMuneItems.scaytDict = + { + label : editor.lang.scayt.dictionariesTab, + group : menuGroup, + onClick : function() + { + openPage = 'dictionaries'; + editor.openDialog( commandName ); + } + }; + // always added + uiMuneItems.scaytAbout = + { + label : editor.lang.scayt.about, + group : menuGroup, + onClick : function() + { + openPage = 'about'; + editor.openDialog( commandName ); + } + } + ; + + uiTabs[3] = 1; // about us tab is always on + plugin.uiTabs = uiTabs; + + editor.addMenuItems( uiMuneItems ); + + editor.ui.add( 'Scayt', CKEDITOR.UI_MENUBUTTON, + { + label : editor.lang.scayt.title, + title : CKEDITOR.env.opera ? editor.lang.scayt.opera_title : editor.lang.scayt.title, + className : 'cke_button_scayt', + onRender: function() + { + command.on( 'state', function() + { + this.setState( command.state ); + }, + this); + }, + onMenu : function() + { + var isEnabled = plugin.isScaytEnabled( editor ); + + editor.getMenuItem( 'scaytToggle' ).label = editor.lang.scayt[ isEnabled ? 'disable' : 'enable' ]; + + return { + 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 + }; + } + }); + + // If the "contextmenu" plugin is loaded, register the listeners. + if ( editor.contextMenu && editor.addMenuItems ) + { + editor.contextMenu.addListener( function( ) + { + if ( !plugin.isScaytEnabled( editor ) ) + return null; + + var scayt_control = plugin.getScayt( editor ), + node = scayt_control.getScaytNode(); + + if ( !node ) + return null; + + var word = scayt_control.getWord( node ); + + if ( !word ) + return null; + + var sLang = scayt_control.getLang(), + _r = {}, + items_suggestion = window.scayt.getSuggestion( word, sLang ); + if ( !items_suggestion || !items_suggestion.length ) + return null; + // Remove unused commands and menuitems + for ( i in moreSuggestions ) + { + delete editor._.menuItems[ i ]; + delete editor._.commands[ i ]; + } + for ( i in mainSuggestions ) + { + delete editor._.menuItems[ i ]; + delete editor._.commands[ i ]; + } + moreSuggestions = {}; // Reset items. + mainSuggestions = {}; + + 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 ) + { + var commandName = 'scayt_suggestion_' + items_suggestion[i].replace( ' ', '_' ); + var exec = ( function( el, s ) + { + return { + exec: function() + { + scayt_control.replace(el, s); + } + }; + })( node, items_suggestion[i] ); + + 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 if ( moreSuggestionsUnable == 'on' ) + { + addButtonCommand( editor, 'button_' + commandName, items_suggestion[i], + commandName, exec, 'scayt_moresuggest', i + 1 ); + moreSuggestions[ commandName ] = CKEDITOR.TRISTATE_OFF; + moreSuggestionsUnableAdded = true; + } + } + + if ( moreSuggestionsUnableAdded ) + { + // Register the More suggestions group; + editor.addMenuItem( 'scayt_moresuggest', + { + label : editor.lang.scayt.moreSuggestions, + group : 'scayt_moresuggest', + order : 10, + getItems : function() + { + return moreSuggestions; + } + }); + mainSuggestions[ 'scayt_moresuggest' ] = CKEDITOR.TRISTATE_OFF; + } + + if ( in_array( 'all', contextCommands ) || in_array( 'ignore', contextCommands) ) + { + var ignore_command = { + exec: function(){ + scayt_control.ignore( node ); + } + }; + addButtonCommand( editor, 'ignore', editor.lang.scayt.ignore, 'scayt_ignore', ignore_command, 'scayt_control', 1 ); + mainSuggestions[ 'scayt_ignore' ] = CKEDITOR.TRISTATE_OFF; + } + + if ( in_array( 'all', contextCommands ) || in_array( 'ignoreall', contextCommands ) ) + { + var ignore_all_command = { + exec: function(){ + scayt_control.ignoreAll( node ); + } + }; + addButtonCommand(editor, 'ignore_all', editor.lang.scayt.ignoreAll, 'scayt_ignore_all', ignore_all_command, 'scayt_control', 2); + mainSuggestions['scayt_ignore_all'] = CKEDITOR.TRISTATE_OFF; + } + + if ( in_array( 'all', contextCommands ) || in_array( 'add', contextCommands ) ) + { + var addword_command = { + exec: function(){ + window.scayt.addWordToUserDictionary( node ); + } + }; + 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 ); + + return mainSuggestions; + }); + } + + var showInitialState = function() + { + editor.removeListener( 'showScaytState', showInitialState ); + + if ( !CKEDITOR.env.opera ) + command.setState( plugin.isScaytEnabled( editor ) ? CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF ); + else + command.setState( CKEDITOR.TRISTATE_DISABLED ); + }; + + editor.on( 'showScaytState', showInitialState ); + + if ( CKEDITOR.env.opera ) + { + editor.on( 'instanceReady', function() + { + showInitialState(); + }); + } + + // Start plugin + var autoStartup = editor.config.scayt_autoStartup; + if ( ( autoStartup == undefined ) || autoStartup ) + { + editor.on( 'instanceReady', function() + { + plugin.loadEngine( editor ); + }); + } + }, + + afterInit : function( editor ) + { + // Prevent word marker line from displaying in elements path and been removed when cleaning format. (#3570) (#4125) + var elementsPathFilters, + scaytFilter = function( element ) + { + if ( element.hasAttribute( 'scaytid' ) ) + return false; + }; + + if ( editor._.elementsPath && ( elementsPathFilters = editor._.elementsPath.filters ) ) + elementsPathFilters.push( scaytFilter ); + + editor.addRemoveFormatFilter && editor.addRemoveFormatFilter( scaytFilter ); + + } + }); +})(); + +/** + * If enabled (true), turns on SCAYT automatically after loading the editor. + * @name CKEDITOR.config.scayt_autoStartup + * @type Boolean + * @default true + * @example + * config.scayt_autoStartup = false; + */ + +/** + * Defines the number of SCAYT suggestions to show in the main context menu. + * The possible values are: + * <ul> + * <li>0 (zero): All suggestions are displayed in the main context menu.</li> + * <li>Positive number: The maximum number of suggestions to shown in context + * menu. Other entries will be shown in "More Suggestions" sub-menu.</li> + * <li>Negative number: No suggestions are shown in the main context menu. All + * entries will be listed in the "Suggestions" sub-menu.</li> + * </ul> + * @name CKEDITOR.config.scayt_maxSuggestions + * @type Number + * @default 5 + * @example + * // Display only three suggestions in the main context menu. + * config.scayt_maxSuggestions = 3; + * @example + * // Do not show the suggestions directly. + * config.scayt_maxSuggestions = -1; + */ + +/** + * Sets the customer ID for SCAYT. Required for migration from free version + * with banner to paid version. + * @name CKEDITOR.config.scayt_customerid + * @type String + * @default '' + * @example + * // Load SCAYT using my customer ID. + * config.scayt_customerid = 'your-encrypted-customer-id'; + */ + +/** + * Enables/disables the "More Suggestions" sub-menu in the context menu. + * The possible values are "on" or "off". + * @name CKEDITOR.config.scayt_moreSuggestions + * @type String + * @default 'on' + * @example + * // Disables the "More Suggestions" sub-menu. + * config.scayt_moreSuggestions = 'off'; + */ + +/** + * Customizes the display of SCAYT context menu commands ("Add Word", "Ignore" + * and "Ignore All"). It must be a string with one or more of the following + * words separated by a pipe ("|"): + * <ul> + * <li>"off": disables all options.</li> + * <li>"all": enables all options.</li> + * <li>"ignore": enables the "Ignore" option.</li> + * <li>"ignoreall": enables the "Ignore All" option.</li> + * <li>"add": enables the "Add Word" option.</li> + * </ul> + * @name CKEDITOR.config.scayt_contextCommands + * @type String + * @default 'all' + * @example + * // Show only "Add Word" and "Ignore All" in the context menu. + * config.scayt_contextCommands = 'add|ignoreall'; + */ + +/** + * Sets the default spellchecking language for SCAYT. + * @name CKEDITOR.config.scayt_sLang + * @type String + * @default 'en_US' + * @example + * // Sets SCAYT to German. + * config.scayt_sLang = 'de_DE'; + */ + +/** + * Sets the visibility of the SCAYT tabs in the settings dialog and toolbar + * button. The value must contain a "1" (enabled) or "0" (disabled) number for + * each of the following entries, in this precise order, separated by a + * comma (","): "Options", "Languages" and "Dictionary". + * @name CKEDITOR.config.scayt_uiTabs + * @type String + * @default '1,1,1' + * @example + * // Hide the "Languages" tab. + * config.scayt_uiTabs = '1,0,1'; + */ + + +/** + * Set the URL to SCAYT core. Required to switch to licensed version of SCAYT application. + * Further details at http://wiki.spellchecker.net/doku.php?id=3rd:wysiwyg:fckeditor:wscckf3l . + * @name CKEDITOR.config.scayt_srcUrl + * @type String + * @default '' + * @example + * config.scayt_srcUrl = "http://my-host/spellcheck/lf/scayt/scayt.js"; + */ + +/** + * Links SCAYT to custom dictionaries. It's a string containing dictionary ids + * separared by commas (","). Available only for licensed version. + * Further details at http://wiki.spellchecker.net/doku.php?id=custom_dictionary_support . + * @name CKEDITOR.config.scayt_customDictionaryIds + * @type String + * @default '' + * @example + * config.scayt_customDictionaryIds = '3021,3456,3478"'; + */ + +/** + * Makes it possible to activate a custom dictionary on SCAYT. The user + * dictionary name must be used. Available only for licensed version. + * @name CKEDITOR.config.scayt_userDictionaryName + * @type String + * @default '' + * @example + * config.scayt_userDictionaryName = 'MyDictionary'; + */ + +/** + * Makes it possible to place the SCAYT context menu items above others. + * @name CKEDITOR.config.scayt_contextMenuOntop + * @type Boolean + * @default false + * @example + * config.scayt_contextMenuOntop = true; + */ + +/** + * Define order of placing of SCAYT context menu items by groups. + * It must be a string with one or more of the following + * words separated by a pipe ("|"): + * <ul> + * <li>'suggest' - main suggestion word list,</li> + * <li>'moresuggest' - more suggestions word list,</li> + * <li>'control' - SCAYT commands, such as 'Ignore' and 'Add Word'</li> + * </ul> + * + * @name CKEDITOR.config.scayt_contextMenuItemsOrder + * @type String + * @default 'suggest|moresuggest|control' + * @example + * config.scayt_contextMenuItemsOrder = 'moresuggest|control|suggest'; + */ diff --git a/_source/plugins/selection/plugin.js b/_source/plugins/selection/plugin.js index 85981fa..d7b4627 100644 --- a/_source/plugins/selection/plugin.js +++ b/_source/plugins/selection/plugin.js @@ -1,1103 +1,1169 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-(function()
-{
- // #### checkSelectionChange : START
-
- // The selection change check basically saves the element parent tree of
- // the current node and check it on successive requests. If there is any
- // change on the tree, then the selectionChange event gets fired.
- function checkSelectionChange()
- {
- try
- {
- // In IE, the "selectionchange" event may still get thrown when
- // releasing the WYSIWYG mode, so we need to check it first.
- var sel = this.getSelection();
- if ( !sel )
- return;
-
- var firstElement = sel.getStartElement();
- var currentPath = new CKEDITOR.dom.elementPath( firstElement );
-
- if ( !currentPath.compare( this._.selectionPreviousPath ) )
- {
- this._.selectionPreviousPath = currentPath;
- this.fire( 'selectionChange', { selection : sel, path : currentPath, element : firstElement } );
- }
- }
- catch (e)
- {}
- }
-
- var checkSelectionChangeTimer,
- checkSelectionChangeTimeoutPending;
-
- function checkSelectionChangeTimeout()
- {
- // Firing the "OnSelectionChange" event on every key press started to
- // be too slow. This function guarantees that there will be at least
- // 200ms delay between selection checks.
-
- checkSelectionChangeTimeoutPending = true;
-
- if ( checkSelectionChangeTimer )
- return;
-
- checkSelectionChangeTimeoutExec.call( this );
-
- checkSelectionChangeTimer = CKEDITOR.tools.setTimeout( checkSelectionChangeTimeoutExec, 200, this );
- }
-
- function checkSelectionChangeTimeoutExec()
- {
- checkSelectionChangeTimer = null;
-
- if ( checkSelectionChangeTimeoutPending )
- {
- // Call this with a timeout so the browser properly moves the
- // selection after the mouseup. It happened that the selection was
- // being moved after the mouseup when clicking inside selected text
- // with Firefox.
- CKEDITOR.tools.setTimeout( checkSelectionChange, 0, this );
-
- checkSelectionChangeTimeoutPending = false;
- }
- }
-
- // #### checkSelectionChange : END
-
- var selectAllCmd =
- {
- exec : function( editor )
- {
- switch ( editor.mode )
- {
- case 'wysiwyg' :
- editor.document.$.execCommand( 'SelectAll', false, null );
- break;
- case 'source' :
- // TODO
- }
- },
- canUndo : false
- };
-
- CKEDITOR.plugins.add( 'selection',
- {
- init : function( editor )
- {
- editor.on( 'contentDom', function()
- {
- var doc = editor.document,
- body = doc.getBody();
-
- if ( CKEDITOR.env.ie )
- {
- // Other browsers don't loose the selection if the
- // editor document loose the focus. In IE, we don't
- // have support for it, so we reproduce it here, other
- // than firing the selection change event.
-
- var savedRange,
- saveEnabled;
-
- // "onfocusin" is fired before "onfocus". It makes it
- // possible to restore the selection before click
- // events get executed.
- body.on( 'focusin', function()
- {
- // If we have saved a range, restore it at this
- // point.
- if ( savedRange )
- {
- // Well not break because of this.
- try
- {
- savedRange.select();
- }
- catch (e)
- {}
-
- savedRange = null;
- }
- });
-
- editor.window.on( 'focus', function()
- {
- // Enable selections to be saved.
- saveEnabled = true;
-
- saveSelection();
- });
-
- body.on( 'beforedeactivate', function()
- {
- // Disable selections from being saved.
- saveEnabled = false;
- });
-
- // IE fires the "selectionchange" event when clicking
- // inside a selection. We don't want to capture that.
- body.on( 'mousedown', disableSave );
- body.on( 'mouseup',
- function()
- {
- saveEnabled = true;
- setTimeout( function()
- {
- saveSelection( true );
- },
- 0 );
- });
-
- body.on( 'keydown', disableSave );
- body.on( 'keyup',
- function()
- {
- saveEnabled = true;
- saveSelection();
- });
-
-
- // IE is the only to provide the "selectionchange"
- // event.
- doc.on( 'selectionchange', saveSelection );
-
- function disableSave()
- {
- saveEnabled = false;
- }
-
- function saveSelection( testIt )
- {
- if ( saveEnabled )
- {
- var doc = editor.document,
- sel = doc && doc.$.selection;
-
- // There is a very specific case, when clicking
- // inside a text selection. In that case, the
- // selection collapses at the clicking point,
- // but the selection object remains in an
- // unknown state, making createRange return a
- // range at the very start of the document. In
- // such situation we have to test the range, to
- // be sure it's valid.
- if ( testIt && sel && sel.type == 'None' )
- {
- // The "InsertImage" command can be used to
- // test whether the selection is good or not.
- // If not, it's enough to give some time to
- // IE to put things in order for us.
- if ( !doc.$.queryCommandEnabled( 'InsertImage' ) )
- {
- CKEDITOR.tools.setTimeout( saveSelection, 50, this, true );
- return;
- }
- }
-
- savedRange = sel && sel.createRange();
-
- checkSelectionChangeTimeout.call( editor );
- }
- }
- }
- else
- {
- // In other browsers, we make the selection change
- // check based on other events, like clicks or keys
- // press.
-
- doc.on( 'mouseup', checkSelectionChangeTimeout, editor );
- doc.on( 'keyup', checkSelectionChangeTimeout, editor );
- }
- });
-
- editor.addCommand( 'selectAll', selectAllCmd );
- editor.ui.addButton( 'SelectAll',
- {
- label : editor.lang.selectAll,
- command : 'selectAll'
- });
-
- editor.selectionChange = checkSelectionChangeTimeout;
- }
- });
-
- /**
- * Gets the current selection from the editing area when in WYSIWYG mode.
- * @returns {CKEDITOR.dom.selection} A selection object or null if not on
- * WYSIWYG mode or no selection is available.
- * @example
- * var selection = CKEDITOR.instances.editor1.<b>getSelection()</b>;
- * alert( selection.getType() );
- */
- CKEDITOR.editor.prototype.getSelection = function()
- {
- return this.document && this.document.getSelection();
- };
-
- CKEDITOR.editor.prototype.forceNextSelectionCheck = function()
- {
- delete this._.selectionPreviousPath;
- };
-
- /**
- * Gets the current selection from the document.
- * @returns {CKEDITOR.dom.selection} A selection object.
- * @example
- * var selection = CKEDITOR.instances.editor1.document.<b>getSelection()</b>;
- * alert( selection.getType() );
- */
- CKEDITOR.dom.document.prototype.getSelection = function()
- {
- var sel = new CKEDITOR.dom.selection( this );
- return ( !sel || sel.isInvalid ) ? null : sel;
- };
-
- /**
- * No selection.
- * @constant
- * @example
- * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_NONE )
- * alert( 'Nothing is selected' );
- */
- CKEDITOR.SELECTION_NONE = 1;
-
- /**
- * Text or collapsed selection.
- * @constant
- * @example
- * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_TEXT )
- * alert( 'Text is selected' );
- */
- CKEDITOR.SELECTION_TEXT = 2;
-
- /**
- * Element selection.
- * @constant
- * @example
- * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_ELEMENT )
- * alert( 'An element is selected' );
- */
- CKEDITOR.SELECTION_ELEMENT = 3;
-
- /**
- * Manipulates the selection in a DOM document.
- * @constructor
- * @example
- */
- CKEDITOR.dom.selection = function( document )
- {
- var lockedSelection = document.getCustomData( 'cke_locked_selection' );
-
- if ( lockedSelection )
- return lockedSelection;
-
- this.document = document;
- this.isLocked = false;
- this._ =
- {
- cache : {}
- };
-
- /**
- * IE BUG: The selection's document may be a different document than the
- * editor document. Return null if that's the case.
- */
- if ( CKEDITOR.env.ie )
- {
- var range = this.getNative().createRange();
- if ( !range
- || ( range.item && range.item(0).ownerDocument != this.document.$ )
- || ( range.parentElement && range.parentElement().ownerDocument != this.document.$ ) )
- {
- this.isInvalid = true;
- }
- }
-
- return this;
- };
-
- var styleObjectElements =
- {
- 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
- };
-
- CKEDITOR.dom.selection.prototype =
- {
- /**
- * Gets the native selection object from the browser.
- * @function
- * @returns {Object} The native selection object.
- * @example
- * var selection = editor.getSelection().<b>getNative()</b>;
- */
- getNative :
- CKEDITOR.env.ie ?
- function()
- {
- return this._.cache.nativeSel || ( this._.cache.nativeSel = this.document.$.selection );
- }
- :
- function()
- {
- return this._.cache.nativeSel || ( this._.cache.nativeSel = this.document.getWindow().$.getSelection() );
- },
-
- /**
- * Gets the type of the current selection. The following values are
- * available:
- * <ul>
- * <li>{@link CKEDITOR.SELECTION_NONE} (1): No selection.</li>
- * <li>{@link CKEDITOR.SELECTION_TEXT} (2): Text is selected or
- * collapsed selection.</li>
- * <li>{@link CKEDITOR.SELECTION_ELEMENT} (3): A element
- * selection.</li>
- * </ul>
- * @function
- * @returns {Number} One of the following constant values:
- * {@link CKEDITOR.SELECTION_NONE}, {@link CKEDITOR.SELECTION_TEXT} or
- * {@link CKEDITOR.SELECTION_ELEMENT}.
- * @example
- * if ( editor.getSelection().<b>getType()</b> == CKEDITOR.SELECTION_TEXT )
- * alert( 'Text is selected' );
- */
- getType :
- CKEDITOR.env.ie ?
- function()
- {
- var cache = this._.cache;
- if ( cache.type )
- return cache.type;
-
- var type = CKEDITOR.SELECTION_NONE;
-
- try
- {
- var sel = this.getNative(),
- ieType = sel.type;
-
- if ( ieType == 'Text' )
- type = CKEDITOR.SELECTION_TEXT;
-
- if ( ieType == 'Control' )
- type = CKEDITOR.SELECTION_ELEMENT;
-
- // It is possible that we can still get a text range
- // object even when type == 'None' is returned by IE.
- // So we'd better check the object returned by
- // createRange() rather than by looking at the type.
- if ( sel.createRange().parentElement )
- type = CKEDITOR.SELECTION_TEXT;
- }
- catch(e) {}
-
- return ( cache.type = type );
- }
- :
- function()
- {
- var cache = this._.cache;
- if ( cache.type )
- return cache.type;
-
- var type = CKEDITOR.SELECTION_TEXT;
-
- var sel = this.getNative();
-
- if ( !sel )
- type = CKEDITOR.SELECTION_NONE;
- else if ( sel.rangeCount == 1 )
- {
- // Check if the actual selection is a control (IMG,
- // TABLE, HR, etc...).
-
- var range = sel.getRangeAt(0),
- startContainer = range.startContainer;
-
- if ( startContainer == range.endContainer
- && startContainer.nodeType == 1
- && ( range.endOffset - range.startOffset ) == 1
- && styleObjectElements[ startContainer.childNodes[ range.startOffset ].nodeName.toLowerCase() ] )
- {
- type = CKEDITOR.SELECTION_ELEMENT;
- }
- }
-
- return ( cache.type = type );
- },
-
- getRanges :
- CKEDITOR.env.ie ?
- ( function()
- {
- // Finds the container and offset for a specific boundary
- // of an IE range.
- var getBoundaryInformation = function( range, start )
- {
- // Creates a collapsed range at the requested boundary.
- range = range.duplicate();
- range.collapse( start );
-
- // Gets the element that encloses the range entirely.
- var parent = range.parentElement();
- var siblings = parent.childNodes;
-
- var testRange;
-
- for ( var i = 0 ; i < siblings.length ; i++ )
- {
- var child = siblings[ i ];
- if ( child.nodeType == 1 )
- {
- testRange = range.duplicate();
-
- testRange.moveToElementText( child );
-
- var comparisonStart = testRange.compareEndPoints( 'StartToStart', range ),
- comparisonEnd = testRange.compareEndPoints( 'EndToStart', range );
-
- testRange.collapse();
-
- if ( comparisonStart > 0 )
- break;
- // When selection stay at the side of certain self-closing elements, e.g. BR,
- // our comparison will never shows an equality. (#4824)
- else if ( !comparisonStart
- || comparisonEnd == 1 && comparisonStart == -1 )
- return { container : parent, offset : i };
- else if ( !comparisonEnd )
- return { container : parent, offset : i + 1 };
-
- testRange = null;
- }
- }
-
- if ( !testRange )
- {
- testRange = range.duplicate();
- testRange.moveToElementText( parent );
- testRange.collapse( false );
- }
-
- testRange.setEndPoint( 'StartToStart', range );
- // IE report line break as CRLF with range.text but
- // only LF with textnode.nodeValue, normalize them to avoid
- // breaking character counting logic below. (#3949)
- var distance = testRange.text.replace( /(\r\n|\r)/g, '\n' ).length;
-
- try
- {
- while ( distance > 0 )
- distance -= siblings[ --i ].nodeValue.length;
- }
- // Measurement in IE could be somtimes wrong because of <select> element. (#4611)
- catch( e )
- {
- distance = 0;
- }
-
-
- if ( distance === 0 )
- {
- return {
- container : parent,
- offset : i
- };
- }
- else
- {
- return {
- container : siblings[ i ],
- offset : -distance
- };
- }
- };
-
- return function()
- {
- var cache = this._.cache;
- if ( cache.ranges )
- return cache.ranges;
-
- // IE doesn't have range support (in the W3C way), so we
- // need to do some magic to transform selections into
- // CKEDITOR.dom.range instances.
-
- var sel = this.getNative(),
- nativeRange = sel && sel.createRange(),
- type = this.getType(),
- range;
-
- if ( !sel )
- return [];
-
- if ( type == CKEDITOR.SELECTION_TEXT )
- {
- range = new CKEDITOR.dom.range( this.document );
-
- var boundaryInfo = getBoundaryInformation( nativeRange, true );
- range.setStart( new CKEDITOR.dom.node( boundaryInfo.container ), boundaryInfo.offset );
-
- boundaryInfo = getBoundaryInformation( nativeRange );
- range.setEnd( new CKEDITOR.dom.node( boundaryInfo.container ), boundaryInfo.offset );
-
- return ( cache.ranges = [ range ] );
- }
- else if ( type == CKEDITOR.SELECTION_ELEMENT )
- {
- var retval = this._.cache.ranges = [];
-
- for ( var i = 0 ; i < nativeRange.length ; i++ )
- {
- var element = nativeRange.item( i ),
- parentElement = element.parentNode,
- j = 0;
-
- range = new CKEDITOR.dom.range( this.document );
-
- for (; j < parentElement.childNodes.length && parentElement.childNodes[j] != element ; j++ )
- { /*jsl:pass*/ }
-
- range.setStart( new CKEDITOR.dom.node( parentElement ), j );
- range.setEnd( new CKEDITOR.dom.node( parentElement ), j + 1 );
- retval.push( range );
- }
-
- return retval;
- }
-
- return ( cache.ranges = [] );
- };
- })()
- :
- function()
- {
- var cache = this._.cache;
- if ( cache.ranges )
- return cache.ranges;
-
- // On browsers implementing the W3C range, we simply
- // tranform the native ranges in CKEDITOR.dom.range
- // instances.
-
- var ranges = [];
- var sel = this.getNative();
-
- if ( !sel )
- return [];
-
- for ( var i = 0 ; i < sel.rangeCount ; i++ )
- {
- var nativeRange = sel.getRangeAt( i );
- var range = new CKEDITOR.dom.range( this.document );
-
- range.setStart( new CKEDITOR.dom.node( nativeRange.startContainer ), nativeRange.startOffset );
- range.setEnd( new CKEDITOR.dom.node( nativeRange.endContainer ), nativeRange.endOffset );
- ranges.push( range );
- }
-
- return ( cache.ranges = ranges );
- },
-
- /**
- * Gets the DOM element in which the selection starts.
- * @returns {CKEDITOR.dom.element} The element at the beginning of the
- * selection.
- * @example
- * var element = editor.getSelection().<b>getStartElement()</b>;
- * alert( element.getName() );
- */
- getStartElement : function()
- {
- var cache = this._.cache;
- if ( cache.startElement !== undefined )
- return cache.startElement;
-
- var node,
- sel = this.getNative();
-
- switch ( this.getType() )
- {
- case CKEDITOR.SELECTION_ELEMENT :
- return this.getSelectedElement();
-
- case CKEDITOR.SELECTION_TEXT :
-
- var range = this.getRanges()[0];
-
- if ( range )
- {
- if ( !range.collapsed )
- {
- range.optimize();
-
- // Decrease the range content to exclude particial
- // selected node on the start which doesn't have
- // visual impact. ( #3231 )
- while ( true )
- {
- 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.isBlockBoundary() )
- range.setStartAfter( startContainer );
- else break;
- }
-
- node = range.startContainer;
-
- if ( node.type != CKEDITOR.NODE_ELEMENT )
- return node.getParent();
-
- node = node.getChild( range.startOffset );
-
- if ( !node || node.type != CKEDITOR.NODE_ELEMENT )
- return range.startContainer;
-
- var child = node.getFirst();
- while ( child && child.type == CKEDITOR.NODE_ELEMENT )
- {
- node = child;
- child = child.getFirst();
- }
-
- return node;
- }
- }
-
- if ( CKEDITOR.env.ie )
- {
- range = sel.createRange();
- range.collapse( true );
-
- node = range.parentElement();
- }
- else
- {
- node = sel.anchorNode;
-
- if ( node && node.nodeType != 1 )
- node = node.parentNode;
- }
- }
-
- return cache.startElement = ( node ? new CKEDITOR.dom.element( node ) : null );
- },
-
- /**
- * Gets the current selected element.
- * @returns {CKEDITOR.dom.element} The selected element. Null if no
- * selection is available or the selection type is not
- * {@link CKEDITOR.SELECTION_ELEMENT}.
- * @example
- * var element = editor.getSelection().<b>getSelectedElement()</b>;
- * alert( element.getName() );
- */
- getSelectedElement : function()
- {
- var cache = this._.cache;
- if ( cache.selectedElement !== undefined )
- return cache.selectedElement;
-
- var node;
-
- if ( this.getType() == CKEDITOR.SELECTION_ELEMENT )
- {
- var sel = this.getNative();
-
- if ( CKEDITOR.env.ie )
- {
- try
- {
- node = sel.createRange().item(0);
- }
- catch(e) {}
- }
- else
- {
- var range = sel.getRangeAt( 0 );
- node = range.startContainer.childNodes[ range.startOffset ];
- }
- }
-
- return cache.selectedElement = ( node ? new CKEDITOR.dom.element( node ) : null );
- },
-
- lock : function()
- {
- // Call all cacheable function.
- this.getRanges();
- this.getStartElement();
- this.getSelectedElement();
-
- // The native selection is not available when locked.
- this._.cache.nativeSel = {};
-
- this.isLocked = true;
-
- // Save this selection inside the DOM document.
- this.document.setCustomData( 'cke_locked_selection', this );
- },
-
- unlock : function( restore )
- {
- var doc = this.document,
- lockedSelection = doc.getCustomData( 'cke_locked_selection' );
-
- if ( lockedSelection )
- {
- doc.setCustomData( 'cke_locked_selection', null );
-
- if ( restore )
- {
- var selectedElement = lockedSelection.getSelectedElement(),
- ranges = !selectedElement && lockedSelection.getRanges();
-
- this.isLocked = false;
- this.reset();
-
- doc.getBody().focus();
-
- if ( selectedElement )
- this.selectElement( selectedElement );
- else
- this.selectRanges( ranges );
- }
- }
-
- if ( !lockedSelection || !restore )
- {
- this.isLocked = false;
- this.reset();
- }
- },
-
- reset : function()
- {
- this._.cache = {};
- },
-
- selectElement : function( element )
- {
- if ( this.isLocked )
- {
- var range = new CKEDITOR.dom.range( this.document );
- range.setStartBefore( element );
- range.setEndAfter( element );
-
- this._.cache.selectedElement = element;
- this._.cache.startElement = element;
- this._.cache.ranges = [ range ];
- this._.cache.type = CKEDITOR.SELECTION_ELEMENT;
-
- return;
- }
-
- if ( CKEDITOR.env.ie )
- {
- this.getNative().empty();
-
- try
- {
- // Try to select the node as a control.
- range = this.document.$.body.createControlRange();
- range.addElement( element.$ );
- range.select();
- }
- catch(e)
- {
- // If failed, select it as a text range.
- range = this.document.$.body.createTextRange();
- range.moveToElementText( element.$ );
- range.select();
- }
-
- this.reset();
- }
- else
- {
- // Create the range for the element.
- range = this.document.$.createRange();
- range.selectNode( element.$ );
-
- // Select the range.
- var sel = this.getNative();
- sel.removeAllRanges();
- sel.addRange( range );
-
- this.reset();
- }
- },
-
- selectRanges : function( ranges )
- {
- if ( this.isLocked )
- {
- this._.cache.selectedElement = null;
- this._.cache.startElement = ranges[ 0 ].getTouchedStartNode();
- this._.cache.ranges = ranges;
- this._.cache.type = CKEDITOR.SELECTION_TEXT;
-
- return;
- }
-
- if ( CKEDITOR.env.ie )
- {
- // IE doesn't accept multiple ranges selection, so we just
- // select the first one.
- if ( ranges[ 0 ] )
- ranges[ 0 ].select();
-
- this.reset();
- }
- else
- {
- var sel = this.getNative();
- sel.removeAllRanges();
-
- for ( var i = 0 ; i < ranges.length ; i++ )
- {
- var range = ranges[ i ];
- var nativeRange = this.document.$.createRange();
- var startContainer = range.startContainer;
-
- // In FF2, if we have a collapsed range, inside an empty
- // element, we must add something to it otherwise the caret
- // will not be visible.
- if ( range.collapsed &&
- ( CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 ) &&
- startContainer.type == CKEDITOR.NODE_ELEMENT &&
- !startContainer.getChildCount() )
- {
- startContainer.appendText( '' );
- }
-
- nativeRange.setStart( startContainer.$, range.startOffset );
- nativeRange.setEnd( range.endContainer.$, range.endOffset );
-
- // Select the range.
- sel.addRange( nativeRange );
- }
-
- this.reset();
- }
- },
-
- createBookmarks : function( serializable )
- {
- var retval = [],
- ranges = this.getRanges(),
- length = ranges.length,
- bookmark;
- for ( var i = 0; i < length ; i++ )
- {
- retval.push( bookmark = ranges[ i ].createBookmark( serializable, true ) );
-
- serializable = bookmark.serializable;
-
- var bookmarkStart = serializable ? this.document.getById( bookmark.startNode ) : bookmark.startNode,
- bookmarkEnd = serializable ? this.document.getById( bookmark.endNode ) : bookmark.endNode;
-
- // Updating the offset values for rest of ranges which have been mangled(#3256).
- for ( var j = i + 1 ; j < length ; j++ )
- {
- var dirtyRange = ranges[ j ],
- rangeStart = dirtyRange.startContainer,
- rangeEnd = dirtyRange.endContainer;
-
- rangeStart.equals( bookmarkStart.getParent() ) && dirtyRange.startOffset++;
- rangeStart.equals( bookmarkEnd.getParent() ) && dirtyRange.startOffset++;
- rangeEnd.equals( bookmarkStart.getParent() ) && dirtyRange.endOffset++;
- rangeEnd.equals( bookmarkEnd.getParent() ) && dirtyRange.endOffset++;
- }
- }
-
- return retval;
- },
-
- createBookmarks2 : function( normalized )
- {
- var bookmarks = [],
- ranges = this.getRanges();
-
- for ( var i = 0 ; i < ranges.length ; i++ )
- bookmarks.push( ranges[i].createBookmark2( normalized ) );
-
- return bookmarks;
- },
-
- selectBookmarks : function( bookmarks )
- {
- var ranges = [];
- for ( var i = 0 ; i < bookmarks.length ; i++ )
- {
- var range = new CKEDITOR.dom.range( this.document );
- range.moveToBookmark( bookmarks[i] );
- ranges.push( range );
- }
- 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 ?
- // V2
- function( forceExpand )
- {
- var collapsed = this.collapsed;
- var isStartMarkerAlone;
- var dummySpan;
-
- var bookmark = this.createBookmark();
-
- // Create marker tags for the start and end boundaries.
- var startNode = bookmark.startNode;
-
- var endNode;
- if ( !collapsed )
- endNode = bookmark.endNode;
-
- // Create the main range which will be used for the selection.
- var ieRange = this.document.$.body.createTextRange();
-
- // Position the range at the start boundary.
- ieRange.moveToElementText( startNode.$ );
- ieRange.moveStart( 'character', 1 );
-
- if ( endNode )
- {
- // Create a tool range for the end.
- var ieRangeEnd = this.document.$.body.createTextRange();
-
- // Position the tool range at the end.
- ieRangeEnd.moveToElementText( endNode.$ );
-
- // Move the end boundary of the main range to match the tool range.
- ieRange.setEndPoint( 'EndToEnd', ieRangeEnd );
- ieRange.moveEnd( 'character', -1 );
- }
- else
- {
- // The isStartMarkerAlone logic comes from V2. It guarantees that the lines
- // 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.
- 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
- // inline elements, like <b></b> (#253).
- // It is also needed when placing the selection right after an inline
- // element to avoid the selection moving inside of it.
- dummySpan = this.document.createElement( 'span' );
- dummySpan.setHtml( '' ); // Zero Width No-Break Space (U+FEFF). See #1359.
- dummySpan.insertBefore( startNode );
-
- if ( isStartMarkerAlone )
- {
- // To expand empty blocks or line spaces after <br>, we need
- // instead to have any char, which will be later deleted using the
- // selection.
- // \ufeff = Zero Width No-Break Space (U+FEFF). (#1359)
- this.document.createText( '\ufeff' ).insertBefore( startNode );
- }
- }
-
- // Remove the markers (reset the position, because of the changes in the DOM tree).
- this.setStartBefore( startNode );
- startNode.remove();
-
- if ( collapsed )
- {
- if ( isStartMarkerAlone )
- {
- // Move the selection start to include the temporary \ufeff.
- ieRange.moveStart( 'character', -1 );
-
- ieRange.select();
-
- // Remove our temporary stuff.
- this.document.$.selection.clear();
- }
- else
- ieRange.select();
-
- dummySpan.remove();
- }
- else
- {
- this.setEndBefore( endNode );
- endNode.remove();
- ieRange.select();
- }
- }
- :
- function()
- {
- var startContainer = this.startContainer;
-
- // If we have a collapsed range, inside an empty element, we must add
- // something to it, otherwise the caret will not be visible.
- if ( this.collapsed && startContainer.type == CKEDITOR.NODE_ELEMENT && !startContainer.getChildCount() )
- startContainer.append( new CKEDITOR.dom.text( '' ) );
-
- var nativeRange = this.document.$.createRange();
- nativeRange.setStart( startContainer.$, this.startOffset );
-
- try
- {
- nativeRange.setEnd( this.endContainer.$, this.endOffset );
- }
- catch ( e )
- {
- // There is a bug in Firefox implementation (it would be too easy
- // otherwise). The new start can't be after the end (W3C says it can).
- // So, let's create a new range and collapse it to the desired point.
- if ( e.toString().indexOf( 'NS_ERROR_ILLEGAL_VALUE' ) >= 0 )
- {
- this.collapse( true );
- nativeRange.setEnd( this.endContainer.$, this.endOffset );
- }
- else
- throw( e );
- }
-
- var selection = this.document.getSelection().getNative();
- selection.removeAllRanges();
- selection.addRange( nativeRange );
- };
-} )();
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +(function() +{ + // #### checkSelectionChange : START + + // The selection change check basically saves the element parent tree of + // the current node and check it on successive requests. If there is any + // change on the tree, then the selectionChange event gets fired. + function checkSelectionChange() + { + try + { + // In IE, the "selectionchange" event may still get thrown when + // releasing the WYSIWYG mode, so we need to check it first. + var sel = this.getSelection(); + if ( !sel ) + return; + + var firstElement = sel.getStartElement(); + var currentPath = new CKEDITOR.dom.elementPath( firstElement ); + + if ( !currentPath.compare( this._.selectionPreviousPath ) ) + { + this._.selectionPreviousPath = currentPath; + this.fire( 'selectionChange', { selection : sel, path : currentPath, element : firstElement } ); + } + } + catch (e) + {} + } + + var checkSelectionChangeTimer, + checkSelectionChangeTimeoutPending; + + function checkSelectionChangeTimeout() + { + // Firing the "OnSelectionChange" event on every key press started to + // be too slow. This function guarantees that there will be at least + // 200ms delay between selection checks. + + checkSelectionChangeTimeoutPending = true; + + if ( checkSelectionChangeTimer ) + return; + + checkSelectionChangeTimeoutExec.call( this ); + + checkSelectionChangeTimer = CKEDITOR.tools.setTimeout( checkSelectionChangeTimeoutExec, 200, this ); + } + + function checkSelectionChangeTimeoutExec() + { + checkSelectionChangeTimer = null; + + if ( checkSelectionChangeTimeoutPending ) + { + // Call this with a timeout so the browser properly moves the + // selection after the mouseup. It happened that the selection was + // being moved after the mouseup when clicking inside selected text + // with Firefox. + CKEDITOR.tools.setTimeout( checkSelectionChange, 0, this ); + + checkSelectionChangeTimeoutPending = false; + } + } + + // #### checkSelectionChange : END + + var selectAllCmd = + { + modes : { wysiwyg : 1, source : 1 }, + exec : function( editor ) + { + switch ( editor.mode ) + { + case 'wysiwyg' : + editor.document.$.execCommand( 'SelectAll', false, null ); + break; + case 'source' : + // Select the contents of the textarea + var textarea = editor.textarea.$ ; + if ( CKEDITOR.env.ie ) + { + textarea.createTextRange().execCommand( 'SelectAll' ) ; + } + else + { + textarea.selectionStart = 0 ; + textarea.selectionEnd = textarea.value.length ; + } + textarea.focus() ; + } + }, + canUndo : false + }; + + CKEDITOR.plugins.add( 'selection', + { + init : function( editor ) + { + editor.on( 'contentDom', function() + { + var doc = editor.document, + body = doc.getBody(); + + if ( CKEDITOR.env.ie ) + { + // Other browsers don't loose the selection if the + // editor document loose the focus. In IE, we don't + // have support for it, so we reproduce it here, other + // than firing the selection change event. + + var savedRange, + saveEnabled; + + // "onfocusin" is fired before "onfocus". It makes it + // possible to restore the selection before click + // events get executed. + body.on( 'focusin', function( evt ) + { + // If there are elements with layout they fire this event but + // it must be ignored to allow edit its contents #4682 + if ( evt.data.$.srcElement.nodeName != 'BODY' ) + return; + + // If we have saved a range, restore it at this + // point. + if ( savedRange ) + { + // Well not break because of this. + try + { + savedRange.select(); + } + catch (e) + {} + + savedRange = null; + } + }); + + body.on( 'focus', function() + { + // Enable selections to be saved. + saveEnabled = true; + + saveSelection(); + }); + + body.on( 'beforedeactivate', function( evt ) + { + // Ignore this event if it's caused by focus switch between + // internal editable control type elements, e.g. layouted paragraph. (#4682) + if ( evt.data.$.toElement ) + return; + + // Disable selections from being saved. + saveEnabled = false; + }); + + // IE before version 8 will leave cursor blinking inside the document after + // editor blurred unless we clean up the selection. (#4716) + if ( CKEDITOR.env.ie && CKEDITOR.env.version < 8 ) + { + doc.getWindow().on( 'blur', function( evt ) + { + editor.document.$.selection.empty(); + }); + } + + // IE fires the "selectionchange" event when clicking + // inside a selection. We don't want to capture that. + body.on( 'mousedown', disableSave ); + body.on( 'mouseup', + function() + { + saveEnabled = true; + setTimeout( function() + { + saveSelection( true ); + }, + 0 ); + }); + + body.on( 'keydown', disableSave ); + body.on( 'keyup', + function() + { + saveEnabled = true; + saveSelection(); + }); + + + // IE is the only to provide the "selectionchange" + // event. + doc.on( 'selectionchange', saveSelection ); + + function disableSave() + { + saveEnabled = false; + } + + function saveSelection( testIt ) + { + if ( saveEnabled ) + { + var doc = editor.document, + sel = editor.getSelection(), + nativeSel = sel && sel.getNative(); + + // There is a very specific case, when clicking + // inside a text selection. In that case, the + // selection collapses at the clicking point, + // but the selection object remains in an + // unknown state, making createRange return a + // range at the very start of the document. In + // such situation we have to test the range, to + // be sure it's valid. + if ( testIt && nativeSel && nativeSel.type == 'None' ) + { + // The "InsertImage" command can be used to + // test whether the selection is good or not. + // If not, it's enough to give some time to + // IE to put things in order for us. + if ( !doc.$.queryCommandEnabled( 'InsertImage' ) ) + { + CKEDITOR.tools.setTimeout( saveSelection, 50, this, true ); + return; + } + } + + // Avoid saving selection from within text input. (#5747) + var parentTag; + if ( nativeSel && nativeSel.type == 'Text' + && ( parentTag = nativeSel.createRange().parentElement().nodeName.toLowerCase() ) + && parentTag in { input: 1, textarea : 1 } ) + { + return; + } + + savedRange = nativeSel && sel.getRanges()[ 0 ]; + + checkSelectionChangeTimeout.call( editor ); + } + } + } + else + { + // In other browsers, we make the selection change + // check based on other events, like clicks or keys + // press. + + doc.on( 'mouseup', checkSelectionChangeTimeout, editor ); + doc.on( 'keyup', checkSelectionChangeTimeout, editor ); + } + }); + + editor.addCommand( 'selectAll', selectAllCmd ); + editor.ui.addButton( 'SelectAll', + { + label : editor.lang.selectAll, + command : 'selectAll' + }); + + editor.selectionChange = checkSelectionChangeTimeout; + } + }); + + /** + * Gets the current selection from the editing area when in WYSIWYG mode. + * @returns {CKEDITOR.dom.selection} A selection object or null if not on + * WYSIWYG mode or no selection is available. + * @example + * var selection = CKEDITOR.instances.editor1.<b>getSelection()</b>; + * alert( selection.getType() ); + */ + CKEDITOR.editor.prototype.getSelection = function() + { + return this.document && this.document.getSelection(); + }; + + CKEDITOR.editor.prototype.forceNextSelectionCheck = function() + { + delete this._.selectionPreviousPath; + }; + + /** + * Gets the current selection from the document. + * @returns {CKEDITOR.dom.selection} A selection object. + * @example + * var selection = CKEDITOR.instances.editor1.document.<b>getSelection()</b>; + * alert( selection.getType() ); + */ + CKEDITOR.dom.document.prototype.getSelection = function() + { + var sel = new CKEDITOR.dom.selection( this ); + return ( !sel || sel.isInvalid ) ? null : sel; + }; + + /** + * No selection. + * @constant + * @example + * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_NONE ) + * alert( 'Nothing is selected' ); + */ + CKEDITOR.SELECTION_NONE = 1; + + /** + * Text or collapsed selection. + * @constant + * @example + * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_TEXT ) + * alert( 'Text is selected' ); + */ + CKEDITOR.SELECTION_TEXT = 2; + + /** + * Element selection. + * @constant + * @example + * if ( editor.getSelection().getType() == CKEDITOR.SELECTION_ELEMENT ) + * alert( 'An element is selected' ); + */ + CKEDITOR.SELECTION_ELEMENT = 3; + + /** + * Manipulates the selection in a DOM document. + * @constructor + * @example + */ + CKEDITOR.dom.selection = function( document ) + { + var lockedSelection = document.getCustomData( 'cke_locked_selection' ); + + if ( lockedSelection ) + return lockedSelection; + + this.document = document; + this.isLocked = false; + this._ = + { + cache : {} + }; + + /** + * IE BUG: The selection's document may be a different document than the + * editor document. Return null if that's the case. + */ + if ( CKEDITOR.env.ie ) + { + var range = this.getNative().createRange(); + if ( !range + || ( range.item && range.item(0).ownerDocument != this.document.$ ) + || ( range.parentElement && range.parentElement().ownerDocument != this.document.$ ) ) + { + this.isInvalid = true; + } + } + + return this; + }; + + var styleObjectElements = + { + 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 + }; + + CKEDITOR.dom.selection.prototype = + { + /** + * Gets the native selection object from the browser. + * @function + * @returns {Object} The native selection object. + * @example + * var selection = editor.getSelection().<b>getNative()</b>; + */ + getNative : + CKEDITOR.env.ie ? + function() + { + return this._.cache.nativeSel || ( this._.cache.nativeSel = this.document.$.selection ); + } + : + function() + { + return this._.cache.nativeSel || ( this._.cache.nativeSel = this.document.getWindow().$.getSelection() ); + }, + + /** + * Gets the type of the current selection. The following values are + * available: + * <ul> + * <li>{@link CKEDITOR.SELECTION_NONE} (1): No selection.</li> + * <li>{@link CKEDITOR.SELECTION_TEXT} (2): Text is selected or + * collapsed selection.</li> + * <li>{@link CKEDITOR.SELECTION_ELEMENT} (3): A element + * selection.</li> + * </ul> + * @function + * @returns {Number} One of the following constant values: + * {@link CKEDITOR.SELECTION_NONE}, {@link CKEDITOR.SELECTION_TEXT} or + * {@link CKEDITOR.SELECTION_ELEMENT}. + * @example + * if ( editor.getSelection().<b>getType()</b> == CKEDITOR.SELECTION_TEXT ) + * alert( 'Text is selected' ); + */ + getType : + CKEDITOR.env.ie ? + function() + { + var cache = this._.cache; + if ( cache.type ) + return cache.type; + + var type = CKEDITOR.SELECTION_NONE; + + try + { + var sel = this.getNative(), + ieType = sel.type; + + if ( ieType == 'Text' ) + type = CKEDITOR.SELECTION_TEXT; + + if ( ieType == 'Control' ) + type = CKEDITOR.SELECTION_ELEMENT; + + // It is possible that we can still get a text range + // object even when type == 'None' is returned by IE. + // So we'd better check the object returned by + // createRange() rather than by looking at the type. + if ( sel.createRange().parentElement ) + type = CKEDITOR.SELECTION_TEXT; + } + catch(e) {} + + return ( cache.type = type ); + } + : + function() + { + var cache = this._.cache; + if ( cache.type ) + return cache.type; + + var type = CKEDITOR.SELECTION_TEXT; + + var sel = this.getNative(); + + if ( !sel ) + type = CKEDITOR.SELECTION_NONE; + else if ( sel.rangeCount == 1 ) + { + // Check if the actual selection is a control (IMG, + // TABLE, HR, etc...). + + var range = sel.getRangeAt(0), + startContainer = range.startContainer; + + if ( startContainer == range.endContainer + && startContainer.nodeType == 1 + && ( range.endOffset - range.startOffset ) == 1 + && styleObjectElements[ startContainer.childNodes[ range.startOffset ].nodeName.toLowerCase() ] ) + { + type = CKEDITOR.SELECTION_ELEMENT; + } + } + + return ( cache.type = type ); + }, + + getRanges : + CKEDITOR.env.ie ? + ( function() + { + // Finds the container and offset for a specific boundary + // of an IE range. + var getBoundaryInformation = function( range, start ) + { + // Creates a collapsed range at the requested boundary. + range = range.duplicate(); + range.collapse( start ); + + // Gets the element that encloses the range entirely. + var parent = range.parentElement(); + var siblings = parent.childNodes; + + var testRange; + + for ( var i = 0 ; i < siblings.length ; i++ ) + { + var child = siblings[ i ]; + if ( child.nodeType == 1 ) + { + testRange = range.duplicate(); + + testRange.moveToElementText( child ); + + var comparisonStart = testRange.compareEndPoints( 'StartToStart', range ), + comparisonEnd = testRange.compareEndPoints( 'EndToStart', range ); + + testRange.collapse(); + + if ( comparisonStart > 0 ) + break; + // When selection stay at the side of certain self-closing elements, e.g. BR, + // our comparison will never shows an equality. (#4824) + else if ( !comparisonStart + || comparisonEnd == 1 && comparisonStart == -1 ) + return { container : parent, offset : i }; + else if ( !comparisonEnd ) + return { container : parent, offset : i + 1 }; + + testRange = null; + } + } + + if ( !testRange ) + { + testRange = range.duplicate(); + testRange.moveToElementText( parent ); + testRange.collapse( false ); + } + + testRange.setEndPoint( 'StartToStart', range ); + // IE report line break as CRLF with range.text but + // only LF with textnode.nodeValue, normalize them to avoid + // breaking character counting logic below. (#3949) + var distance = testRange.text.replace( /(\r\n|\r)/g, '\n' ).length; + + try + { + while ( distance > 0 ) + distance -= siblings[ --i ].nodeValue.length; + } + // Measurement in IE could be somtimes wrong because of <select> element. (#4611) + catch( e ) + { + distance = 0; + } + + + if ( distance === 0 ) + { + return { + container : parent, + offset : i + }; + } + else + { + return { + container : siblings[ i ], + offset : -distance + }; + } + }; + + return function() + { + var cache = this._.cache; + if ( cache.ranges ) + return cache.ranges; + + // IE doesn't have range support (in the W3C way), so we + // need to do some magic to transform selections into + // CKEDITOR.dom.range instances. + + var sel = this.getNative(), + nativeRange = sel && sel.createRange(), + type = this.getType(), + range; + + if ( !sel ) + return []; + + if ( type == CKEDITOR.SELECTION_TEXT ) + { + range = new CKEDITOR.dom.range( this.document ); + + var boundaryInfo = getBoundaryInformation( nativeRange, true ); + range.setStart( new CKEDITOR.dom.node( boundaryInfo.container ), boundaryInfo.offset ); + + boundaryInfo = getBoundaryInformation( nativeRange ); + range.setEnd( new CKEDITOR.dom.node( boundaryInfo.container ), boundaryInfo.offset ); + + return ( cache.ranges = [ range ] ); + } + else if ( type == CKEDITOR.SELECTION_ELEMENT ) + { + var retval = this._.cache.ranges = []; + + for ( var i = 0 ; i < nativeRange.length ; i++ ) + { + var element = nativeRange.item( i ), + parentElement = element.parentNode, + j = 0; + + range = new CKEDITOR.dom.range( this.document ); + + for (; j < parentElement.childNodes.length && parentElement.childNodes[j] != element ; j++ ) + { /*jsl:pass*/ } + + range.setStart( new CKEDITOR.dom.node( parentElement ), j ); + range.setEnd( new CKEDITOR.dom.node( parentElement ), j + 1 ); + retval.push( range ); + } + + return retval; + } + + return ( cache.ranges = [] ); + }; + })() + : + function() + { + var cache = this._.cache; + if ( cache.ranges ) + return cache.ranges; + + // On browsers implementing the W3C range, we simply + // tranform the native ranges in CKEDITOR.dom.range + // instances. + + var ranges = []; + var sel = this.getNative(); + + if ( !sel ) + return []; + + for ( var i = 0 ; i < sel.rangeCount ; i++ ) + { + var nativeRange = sel.getRangeAt( i ); + var range = new CKEDITOR.dom.range( this.document ); + + range.setStart( new CKEDITOR.dom.node( nativeRange.startContainer ), nativeRange.startOffset ); + range.setEnd( new CKEDITOR.dom.node( nativeRange.endContainer ), nativeRange.endOffset ); + ranges.push( range ); + } + + return ( cache.ranges = ranges ); + }, + + /** + * Gets the DOM element in which the selection starts. + * @returns {CKEDITOR.dom.element} The element at the beginning of the + * selection. + * @example + * var element = editor.getSelection().<b>getStartElement()</b>; + * alert( element.getName() ); + */ + getStartElement : function() + { + var cache = this._.cache; + if ( cache.startElement !== undefined ) + return cache.startElement; + + var node, + sel = this.getNative(); + + switch ( this.getType() ) + { + case CKEDITOR.SELECTION_ELEMENT : + return this.getSelectedElement(); + + case CKEDITOR.SELECTION_TEXT : + + var range = this.getRanges()[0]; + + if ( range ) + { + if ( !range.collapsed ) + { + range.optimize(); + + // Decrease the range content to exclude particial + // selected node on the start which doesn't have + // visual impact. ( #3231 ) + while ( true ) + { + 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.isBlockBoundary() ) + range.setStartAfter( startContainer ); + else break; + } + + node = range.startContainer; + + if ( node.type != CKEDITOR.NODE_ELEMENT ) + return node.getParent(); + + node = node.getChild( range.startOffset ); + + if ( !node || node.type != CKEDITOR.NODE_ELEMENT ) + return range.startContainer; + + var child = node.getFirst(); + while ( child && child.type == CKEDITOR.NODE_ELEMENT ) + { + node = child; + child = child.getFirst(); + } + + return node; + } + } + + if ( CKEDITOR.env.ie ) + { + range = sel.createRange(); + range.collapse( true ); + + node = range.parentElement(); + } + else + { + node = sel.anchorNode; + + if ( node && node.nodeType != 1 ) + node = node.parentNode; + } + } + + return cache.startElement = ( node ? new CKEDITOR.dom.element( node ) : null ); + }, + + /** + * Gets the current selected element. + * @returns {CKEDITOR.dom.element} The selected element. Null if no + * selection is available or the selection type is not + * {@link CKEDITOR.SELECTION_ELEMENT}. + * @example + * var element = editor.getSelection().<b>getSelectedElement()</b>; + * alert( element.getName() ); + */ + getSelectedElement : function() + { + var cache = this._.cache; + if ( cache.selectedElement !== undefined ) + return cache.selectedElement; + + var self = this; + + var node = CKEDITOR.tools.tryThese( + // Is it native IE control type selection? + function() + { + return self.getNative().createRange().item( 0 ); + }, + // Figure it out by checking if there's a single enclosed + // node of the range. + function() + { + var range = self.getRanges()[ 0 ], + enclosed, + selected; + + // Check first any enclosed element, e.g. <ul>[<li><a href="#">item</a></li>]</ul> + for ( var i = 2; i && !( ( enclosed = range.getEnclosedNode() ) + && ( enclosed.type == CKEDITOR.NODE_ELEMENT ) + && styleObjectElements[ enclosed.getName() ] + && ( selected = enclosed ) ); i-- ) + { + // Then check any deep wrapped element, e.g. [<b><i><img /></i></b>] + range.shrink( CKEDITOR.SHRINK_ELEMENT ); + } + + return selected.$; + }); + + return cache.selectedElement = ( node ? new CKEDITOR.dom.element( node ) : null ); + }, + + lock : function() + { + // Call all cacheable function. + this.getRanges(); + this.getStartElement(); + this.getSelectedElement(); + + // The native selection is not available when locked. + this._.cache.nativeSel = {}; + + this.isLocked = true; + + // Save this selection inside the DOM document. + this.document.setCustomData( 'cke_locked_selection', this ); + }, + + unlock : function( restore ) + { + var doc = this.document, + lockedSelection = doc.getCustomData( 'cke_locked_selection' ); + + if ( lockedSelection ) + { + doc.setCustomData( 'cke_locked_selection', null ); + + if ( restore ) + { + var selectedElement = lockedSelection.getSelectedElement(), + ranges = !selectedElement && lockedSelection.getRanges(); + + this.isLocked = false; + this.reset(); + + doc.getBody().focus(); + + if ( selectedElement ) + this.selectElement( selectedElement ); + else + this.selectRanges( ranges ); + } + } + + if ( !lockedSelection || !restore ) + { + this.isLocked = false; + this.reset(); + } + }, + + reset : function() + { + this._.cache = {}; + }, + + selectElement : function( element ) + { + if ( this.isLocked ) + { + var range = new CKEDITOR.dom.range( this.document ); + range.setStartBefore( element ); + range.setEndAfter( element ); + + this._.cache.selectedElement = element; + this._.cache.startElement = element; + this._.cache.ranges = [ range ]; + this._.cache.type = CKEDITOR.SELECTION_ELEMENT; + + return; + } + + if ( CKEDITOR.env.ie ) + { + this.getNative().empty(); + + try + { + // Try to select the node as a control. + range = this.document.$.body.createControlRange(); + range.addElement( element.$ ); + range.select(); + } + catch(e) + { + // If failed, select it as a text range. + range = this.document.$.body.createTextRange(); + range.moveToElementText( element.$ ); + range.select(); + } + finally + { + this.document.fire( 'selectionchange' ); + } + + this.reset(); + } + else + { + // Create the range for the element. + range = this.document.$.createRange(); + range.selectNode( element.$ ); + + // Select the range. + var sel = this.getNative(); + sel.removeAllRanges(); + sel.addRange( range ); + + this.reset(); + } + }, + + selectRanges : function( ranges ) + { + if ( this.isLocked ) + { + this._.cache.selectedElement = null; + this._.cache.startElement = ranges[ 0 ].getTouchedStartNode(); + this._.cache.ranges = ranges; + this._.cache.type = CKEDITOR.SELECTION_TEXT; + + return; + } + + if ( CKEDITOR.env.ie ) + { + // IE doesn't accept multiple ranges selection, so we just + // select the first one. + if ( ranges[ 0 ] ) + ranges[ 0 ].select(); + + this.reset(); + } + else + { + var sel = this.getNative(); + sel.removeAllRanges(); + + for ( var i = 0 ; i < ranges.length ; i++ ) + { + var range = ranges[ i ]; + var nativeRange = this.document.$.createRange(); + var startContainer = range.startContainer; + + // In FF2, if we have a collapsed range, inside an empty + // element, we must add something to it otherwise the caret + // will not be visible. + if ( range.collapsed && + ( CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 ) && + startContainer.type == CKEDITOR.NODE_ELEMENT && + !startContainer.getChildCount() ) + { + startContainer.appendText( '' ); + } + + nativeRange.setStart( startContainer.$, range.startOffset ); + nativeRange.setEnd( range.endContainer.$, range.endOffset ); + + // Select the range. + sel.addRange( nativeRange ); + } + + this.reset(); + } + }, + + createBookmarks : function( serializable ) + { + var retval = [], + ranges = this.getRanges(), + length = ranges.length, + bookmark; + for ( var i = 0; i < length ; i++ ) + { + retval.push( bookmark = ranges[ i ].createBookmark( serializable, true ) ); + + serializable = bookmark.serializable; + + var bookmarkStart = serializable ? this.document.getById( bookmark.startNode ) : bookmark.startNode, + bookmarkEnd = serializable ? this.document.getById( bookmark.endNode ) : bookmark.endNode; + + // Updating the offset values for rest of ranges which have been mangled(#3256). + for ( var j = i + 1 ; j < length ; j++ ) + { + var dirtyRange = ranges[ j ], + rangeStart = dirtyRange.startContainer, + rangeEnd = dirtyRange.endContainer; + + rangeStart.equals( bookmarkStart.getParent() ) && dirtyRange.startOffset++; + rangeStart.equals( bookmarkEnd.getParent() ) && dirtyRange.startOffset++; + rangeEnd.equals( bookmarkStart.getParent() ) && dirtyRange.endOffset++; + rangeEnd.equals( bookmarkEnd.getParent() ) && dirtyRange.endOffset++; + } + } + + return retval; + }, + + createBookmarks2 : function( normalized ) + { + var bookmarks = [], + ranges = this.getRanges(); + + for ( var i = 0 ; i < ranges.length ; i++ ) + bookmarks.push( ranges[i].createBookmark2( normalized ) ); + + return bookmarks; + }, + + selectBookmarks : function( bookmarks ) + { + var ranges = []; + for ( var i = 0 ; i < bookmarks.length ; i++ ) + { + var range = new CKEDITOR.dom.range( this.document ); + range.moveToBookmark( bookmarks[i] ); + ranges.push( range ); + } + 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/; +var nonCells = { table:1,tbody:1,tr:1 }; + +CKEDITOR.dom.range.prototype.select = + CKEDITOR.env.ie ? + // V2 + function( forceExpand ) + { + var collapsed = this.collapsed; + var isStartMarkerAlone; + var dummySpan; + + // IE doesn't support selecting the entire table row/cell, move the selection into cells, e.g. + // <table><tbody><tr>[<td>cell</b></td>... => <table><tbody><tr><td>[cell</td>... + if ( this.startContainer.type == CKEDITOR.NODE_ELEMENT && this.startContainer.getName() in nonCells + || this.endContainer.type == CKEDITOR.NODE_ELEMENT && this.endContainer.getName() in nonCells ) + { + this.shrink( CKEDITOR.NODE_ELEMENT, true ); + } + + var bookmark = this.createBookmark(); + + // Create marker tags for the start and end boundaries. + var startNode = bookmark.startNode; + + var endNode; + if ( !collapsed ) + endNode = bookmark.endNode; + + // Create the main range which will be used for the selection. + var ieRange = this.document.$.body.createTextRange(); + + // Position the range at the start boundary. + ieRange.moveToElementText( startNode.$ ); + ieRange.moveStart( 'character', 1 ); + + if ( endNode ) + { + // Create a tool range for the end. + var ieRangeEnd = this.document.$.body.createTextRange(); + + // Position the tool range at the end. + ieRangeEnd.moveToElementText( endNode.$ ); + + // Move the end boundary of the main range to match the tool range. + ieRange.setEndPoint( 'EndToEnd', ieRangeEnd ); + ieRange.moveEnd( 'character', -1 ); + } + else + { + // The isStartMarkerAlone logic comes from V2. It guarantees that the lines + // 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. + 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 + // inline elements, like <b></b> (#253). + // It is also needed when placing the selection right after an inline + // element to avoid the selection moving inside of it. + dummySpan = this.document.createElement( 'span' ); + dummySpan.setHtml( '' ); // Zero Width No-Break Space (U+FEFF). See #1359. + dummySpan.insertBefore( startNode ); + + if ( isStartMarkerAlone ) + { + // To expand empty blocks or line spaces after <br>, we need + // instead to have any char, which will be later deleted using the + // selection. + // \ufeff = Zero Width No-Break Space (U+FEFF). (#1359) + this.document.createText( '\ufeff' ).insertBefore( startNode ); + } + } + + // Remove the markers (reset the position, because of the changes in the DOM tree). + this.setStartBefore( startNode ); + startNode.remove(); + + if ( collapsed ) + { + if ( isStartMarkerAlone ) + { + // Move the selection start to include the temporary \ufeff. + ieRange.moveStart( 'character', -1 ); + + ieRange.select(); + + // Remove our temporary stuff. + this.document.$.selection.clear(); + } + else + ieRange.select(); + + this.moveToPosition( dummySpan, CKEDITOR.POSITION_BEFORE_START ); + dummySpan.remove(); + } + else + { + this.setEndBefore( endNode ); + endNode.remove(); + ieRange.select(); + } + + this.document.fire( 'selectionchange' ); + } + : + function() + { + var startContainer = this.startContainer; + + // If we have a collapsed range, inside an empty element, we must add + // something to it, otherwise the caret will not be visible. + if ( this.collapsed && startContainer.type == CKEDITOR.NODE_ELEMENT && !startContainer.getChildCount() ) + startContainer.append( new CKEDITOR.dom.text( '' ) ); + + var nativeRange = this.document.$.createRange(); + nativeRange.setStart( startContainer.$, this.startOffset ); + + try + { + nativeRange.setEnd( this.endContainer.$, this.endOffset ); + } + catch ( e ) + { + // There is a bug in Firefox implementation (it would be too easy + // otherwise). The new start can't be after the end (W3C says it can). + // So, let's create a new range and collapse it to the desired point. + if ( e.toString().indexOf( 'NS_ERROR_ILLEGAL_VALUE' ) >= 0 ) + { + this.collapse( true ); + nativeRange.setEnd( this.endContainer.$, this.endOffset ); + } + else + throw( e ); + } + + var selection = this.document.getSelection().getNative(); + selection.removeAllRanges(); + selection.addRange( nativeRange ); + }; +} )(); diff --git a/_source/plugins/showborders/plugin.js b/_source/plugins/showborders/plugin.js index 4fc46b6..dc13d24 100644 --- a/_source/plugins/showborders/plugin.js +++ b/_source/plugins/showborders/plugin.js @@ -1,170 +1,167 @@ -/*
-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;
- */
+/* +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 ) + { + 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 dfe0a50..4786655 100644 --- a/_source/plugins/smiley/dialogs/smiley.js +++ b/_source/plugins/smiley/dialogs/smiley.js @@ -1,215 +1,218 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-CKEDITOR.dialog.add( 'smiley', function( editor )
-{
- var config = editor.config,
- lang = editor.lang.smiley,
- images = config.smiley_images,
- columns = 8,
- i;
-
- /**
- * Simulate "this" of a dialog for non-dialog events.
- * @type {CKEDITOR.dialog}
- */
- var dialog;
- var onClick = function( evt )
- {
- var target = evt.data.getTarget(),
- targetName = target.getName();
-
- if ( targetName == 'td' )
- target = target.getChild( [ 0, 0 ] );
- else if ( targetName == 'a' )
- target = target.getChild( 0 );
- else if ( targetName != 'img' )
- return;
-
- var src = target.getAttribute( 'cke_src' ),
- title = target.getAttribute( 'title' );
-
- var img = editor.document.createElement( 'img',
- {
- attributes :
- {
- src : src,
- _cke_saved_src : src,
- title : title,
- alt : title
- }
- });
-
- editor.insertElement( img );
-
- dialog.hide();
- evt.data.preventDefault();
- };
-
- var onKeydown = CKEDITOR.tools.addFunction( function( ev, element )
- {
- ev = new CKEDITOR.dom.event( ev );
- element = new CKEDITOR.dom.element( element );
- var relative, nodeToMove;
-
- var keystroke = ev.getKeystroke();
- switch ( keystroke )
- {
- // UP-ARROW
- case 38 :
- // relative is TR
- if ( ( relative = element.getParent().getParent().getPrevious() ) )
- {
- nodeToMove = relative.getChild( [element.getParent().getIndex(), 0] );
- nodeToMove.focus();
- }
- ev.preventDefault();
- break;
- // DOWN-ARROW
- case 40 :
- // relative is TR
- if ( ( relative = element.getParent().getParent().getNext() ) )
- {
- nodeToMove = relative.getChild( [element.getParent().getIndex(), 0] );
- if ( nodeToMove )
- nodeToMove.focus();
- }
- ev.preventDefault();
- break;
- // ENTER
- // SPACE
- case 32 :
- onClick( { data: ev } );
- ev.preventDefault();
- break;
-
- // RIGHT-ARROW
- case 39 :
- // TAB
- case 9 :
- // relative is TD
- if ( ( relative = element.getParent().getNext() ) )
- {
- nodeToMove = relative.getChild( 0 );
- nodeToMove.focus();
- ev.preventDefault(true);
- }
- // relative is TR
- else if ( ( relative = element.getParent().getParent().getNext() ) )
- {
- nodeToMove = relative.getChild( [0, 0] );
- if ( nodeToMove )
- nodeToMove.focus();
- ev.preventDefault(true);
- }
- break;
-
- // LEFT-ARROW
- case 37 :
- // SHIFT + TAB
- case CKEDITOR.SHIFT + 9 :
- // relative is TD
- if ( ( relative = element.getParent().getPrevious() ) )
- {
- nodeToMove = relative.getChild( 0 );
- nodeToMove.focus();
- ev.preventDefault(true);
- }
- // relative is TR
- else if ( ( relative = element.getParent().getParent().getPrevious() ) )
- {
- nodeToMove = relative.getLast().getChild( 0 );
- nodeToMove.focus();
- ev.preventDefault(true);
- }
- break;
- default :
- // Do not stop not handled events.
- return;
- }
- });
-
- // Build the HTML for the smiley images table.
- var html =
- [
- '<div>' +
- '<span id="smiley_emtions_label" class="cke_voice_label">' + editor.lang.common.options +'</span>',
- '<table role="listbox" aria-labelledby="smiley_emtions_label" style="width:100%;height:100%" cellspacing="2" cellpadding="2"',
- CKEDITOR.env.ie && CKEDITOR.env.quirks ? ' style="position:absolute;"' : '',
- '><tbody>'
- ];
-
- var size = images.length;
- for ( i = 0 ; i < size ; i++ )
- {
- if ( i % columns === 0 )
- html.push( '<tr>' );
-
- html.push(
- '<td class="cke_dark_background cke_hand cke_centered" style="vertical-align: middle;">' +
- '<a href="javascript:void(0)" role="option"',
- ' aria-posinset="' + ( i +1 ) + '"',
- ' aria-setsize="' + size + '"',
- ' aria-labelledby="cke_smile_label_' + i + '"',
- ' class="cke_smile" tabindex="-1" onkeydown="CKEDITOR.tools.callFunction( ', onKeydown, ', event, this );">',
- '<img class="hand" title="', config.smiley_descriptions[i], '"' +
- ' cke_src="', CKEDITOR.tools.htmlEncode( config.smiley_path + images[ i ] ), '" alt="', config.smiley_descriptions[i], '"',
- ' src="', CKEDITOR.tools.htmlEncode( config.smiley_path + images[ i ] ), '"',
- // IE BUG: Below is a workaround to an IE image loading bug to ensure the image sizes are correct.
- ( CKEDITOR.env.ie ? ' onload="this.setAttribute(\'width\', 2); this.removeAttribute(\'width\');" ' : '' ),
- '>' +
- '<span id="cke_smile_label_' + i + '" class="cke_voice_label">' +config.smiley_descriptions[ i ] + '</span>' +
- '</a>',
- '</td>' );
-
- if ( i % columns == columns - 1 )
- html.push( '</tr>' );
- }
-
- if ( i < columns - 1 )
- {
- for ( ; i < columns - 1 ; i++ )
- html.push( '<td></td>' );
- html.push( '</tr>' );
- }
-
- html.push( '</tbody></table></div>' );
-
- var smileySelector =
- {
- type : 'html',
- html : html.join( '' ),
- onLoad : function( event )
- {
- dialog = event.sender;
- },
- focus : function()
- {
- var firstSmile = this.getElement().getElementsByTag( 'a' ).getItem( 0 );
- firstSmile.focus();
- },
- onClick : onClick,
- style : 'width: 100%; height: 100%; border-collapse: separate;'
- };
-
- return {
- title : editor.lang.smiley.title,
- minWidth : 270,
- minHeight : 120,
- contents : [
- {
- id : 'tab1',
- label : '',
- title : '',
- expand : true,
- padding : 0,
- elements : [
- smileySelector
- ]
- }
- ],
- buttons : [ CKEDITOR.dialog.cancelButton ]
- };
-} );
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.dialog.add( 'smiley', function( editor ) +{ + var config = editor.config, + lang = editor.lang.smiley, + images = config.smiley_images, + columns = 8, + i; + + /** + * Simulate "this" of a dialog for non-dialog events. + * @type {CKEDITOR.dialog} + */ + var dialog; + var onClick = function( evt ) + { + var target = evt.data.getTarget(), + targetName = target.getName(); + + if ( targetName == 'td' ) + target = target.getChild( [ 0, 0 ] ); + else if ( targetName == 'a' ) + target = target.getChild( 0 ); + else if ( targetName != 'img' ) + return; + + var src = target.getAttribute( 'cke_src' ), + title = target.getAttribute( 'title' ); + + var img = editor.document.createElement( 'img', + { + attributes : + { + src : src, + _cke_saved_src : src, + title : title, + alt : title + } + }); + + editor.insertElement( img ); + + dialog.hide(); + evt.data.preventDefault(); + }; + + var onKeydown = CKEDITOR.tools.addFunction( function( ev, element ) + { + ev = new CKEDITOR.dom.event( ev ); + element = new CKEDITOR.dom.element( element ); + var relative, nodeToMove; + + var keystroke = ev.getKeystroke(); + var rtl = editor.lang.dir == 'rtl'; + switch ( keystroke ) + { + // UP-ARROW + case 38 : + // relative is TR + if ( ( relative = element.getParent().getParent().getPrevious() ) ) + { + nodeToMove = relative.getChild( [element.getParent().getIndex(), 0] ); + nodeToMove.focus(); + } + ev.preventDefault(); + break; + // DOWN-ARROW + case 40 : + // relative is TR + if ( ( relative = element.getParent().getParent().getNext() ) ) + { + nodeToMove = relative.getChild( [element.getParent().getIndex(), 0] ); + if ( nodeToMove ) + nodeToMove.focus(); + } + ev.preventDefault(); + break; + // ENTER + // SPACE + case 32 : + onClick( { data: ev } ); + ev.preventDefault(); + break; + + // RIGHT-ARROW + case rtl ? 37 : 39 : + // TAB + case 9 : + // relative is TD + if ( ( relative = element.getParent().getNext() ) ) + { + nodeToMove = relative.getChild( 0 ); + nodeToMove.focus(); + ev.preventDefault(true); + } + // relative is TR + else if ( ( relative = element.getParent().getParent().getNext() ) ) + { + nodeToMove = relative.getChild( [0, 0] ); + if ( nodeToMove ) + nodeToMove.focus(); + ev.preventDefault(true); + } + break; + + // LEFT-ARROW + case rtl ? 39 : 37 : + // SHIFT + TAB + case CKEDITOR.SHIFT + 9 : + // relative is TD + if ( ( relative = element.getParent().getPrevious() ) ) + { + nodeToMove = relative.getChild( 0 ); + nodeToMove.focus(); + ev.preventDefault(true); + } + // relative is TR + else if ( ( relative = element.getParent().getParent().getPrevious() ) ) + { + nodeToMove = relative.getLast().getChild( 0 ); + nodeToMove.focus(); + ev.preventDefault(true); + } + break; + default : + // Do not stop not handled events. + return; + } + }); + + // Build the HTML for the smiley images table. + var labelId = 'smiley_emtions_label' + CKEDITOR.tools.getNextNumber(); + var html = + [ + '<div>' + + '<span id="' + labelId + '" class="cke_voice_label">' + lang.options +'</span>', + '<table role="listbox" aria-labelledby="' + labelId + '" style="width:100%;height:100%" cellspacing="2" cellpadding="2"', + CKEDITOR.env.ie && CKEDITOR.env.quirks ? ' style="position:absolute;"' : '', + '><tbody>' + ]; + + var size = images.length; + for ( i = 0 ; i < size ; i++ ) + { + if ( i % columns === 0 ) + html.push( '<tr>' ); + + var smileyLabelId = 'cke_smile_label_' + i + '_' + CKEDITOR.tools.getNextNumber(); + html.push( + '<td class="cke_dark_background cke_hand cke_centered" style="vertical-align: middle;">' + + '<a href="javascript:void(0)" role="option"', + ' aria-posinset="' + ( i +1 ) + '"', + ' aria-setsize="' + size + '"', + ' aria-labelledby="' + smileyLabelId + '"', + ' class="cke_smile" tabindex="-1" onkeydown="CKEDITOR.tools.callFunction( ', onKeydown, ', event, this );">', + '<img class="hand" title="', config.smiley_descriptions[i], '"' + + ' cke_src="', CKEDITOR.tools.htmlEncode( config.smiley_path + images[ i ] ), '" alt="', config.smiley_descriptions[i], '"', + ' src="', CKEDITOR.tools.htmlEncode( config.smiley_path + images[ i ] ), '"', + // IE BUG: Below is a workaround to an IE image loading bug to ensure the image sizes are correct. + ( CKEDITOR.env.ie ? ' onload="this.setAttribute(\'width\', 2); this.removeAttribute(\'width\');" ' : '' ), + '>' + + '<span id="' + smileyLabelId + '" class="cke_voice_label">' +config.smiley_descriptions[ i ] + '</span>' + + '</a>', + '</td>' ); + + if ( i % columns == columns - 1 ) + html.push( '</tr>' ); + } + + if ( i < columns - 1 ) + { + for ( ; i < columns - 1 ; i++ ) + html.push( '<td></td>' ); + html.push( '</tr>' ); + } + + html.push( '</tbody></table></div>' ); + + var smileySelector = + { + type : 'html', + html : html.join( '' ), + onLoad : function( event ) + { + dialog = event.sender; + }, + focus : function() + { + var firstSmile = this.getElement().getElementsByTag( 'a' ).getItem( 0 ); + firstSmile.focus(); + }, + onClick : onClick, + style : 'width: 100%; border-collapse: separate;' + }; + + return { + title : editor.lang.smiley.title, + minWidth : 270, + minHeight : 120, + contents : [ + { + id : 'tab1', + label : '', + title : '', + expand : true, + padding : 0, + elements : [ + smileySelector + ] + } + ], + buttons : [ CKEDITOR.dialog.cancelButton ] + }; +} ); diff --git a/_source/plugins/smiley/plugin.js b/_source/plugins/smiley/plugin.js index d8a9fbf..da1ad54 100644 --- a/_source/plugins/smiley/plugin.js +++ b/_source/plugins/smiley/plugin.js @@ -1,85 +1,84 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-CKEDITOR.plugins.add( 'smiley',
-{
- requires : [ 'dialog' ],
-
- init : function( editor )
- {
- editor.addCommand( 'smiley', new CKEDITOR.dialogCommand( 'smiley' ) );
- editor.ui.addButton( 'Smiley',
- {
- label : editor.lang.smiley.toolbar,
- command : 'smiley'
- });
- CKEDITOR.dialog.add( 'smiley', this.path + 'dialogs/smiley.js' );
- }
-} );
-
-/**
- * The base path used to build the URL for the smiley images. It must end with
- * a slash.
- * @type String
- * @default {@link CKEDITOR.basePath} + 'plugins/smiley/images/'
- * @example
- * config.smiley_path = 'http://www.example.com/images/smileys/';
- * @example
- * config.smiley_path = '/images/smileys/';
- */
-CKEDITOR.config.smiley_path = CKEDITOR.basePath +
- '_source/' + // @Packager.RemoveLine
- 'plugins/smiley/images/';
-
-/**
- * The file names for the smileys to be displayed. These files must be
- * contained inside the URL path defined with the
- * {@link CKEDITOR.config.smiley_path} setting.
- * @type Array
- * @default (see example)
- * @example
- * // This is actually the default value.
- * config.smiley_images = [
- * 'regular_smile.gif','sad_smile.gif','wink_smile.gif','teeth_smile.gif','confused_smile.gif','tounge_smile.gif',
- * 'embaressed_smile.gif','omg_smile.gif','whatchutalkingabout_smile.gif','angry_smile.gif','angel_smile.gif','shades_smile.gif',
- * 'devil_smile.gif','cry_smile.gif','lightbulb.gif','thumbs_down.gif','thumbs_up.gif','heart.gif',
- * 'broken_heart.gif','kiss.gif','envelope.gif'];
- */
-CKEDITOR.config.smiley_images = [
- 'regular_smile.gif','sad_smile.gif','wink_smile.gif','teeth_smile.gif','confused_smile.gif','tounge_smile.gif',
- 'embaressed_smile.gif','omg_smile.gif','whatchutalkingabout_smile.gif','angry_smile.gif','angel_smile.gif','shades_smile.gif',
- 'devil_smile.gif','cry_smile.gif','lightbulb.gif','thumbs_down.gif','thumbs_up.gif','heart.gif',
- 'broken_heart.gif','kiss.gif','envelope.gif'];
-
-/**
- * The description to be used for each of the smileys defined in the
- * {@link CKEDITOR.config.smiley_images} setting. Each entry in this array list
- * must match its relative pair in the {@link CKEDITOR.config.smiley_images}
- * setting.
- * @type Array
- * @default The textual descriptions of smiley.
- * @example
- * // Default settings.
- * config.smiley_descriptions =
- * [
- * 'smiley', 'sad', 'wink', 'laugh', 'frown', 'cheeky', 'blush', 'surprise',
- * 'indecision', 'angry', 'angle', 'cool', 'devil', 'crying', 'enlightened', 'no',
- * 'yes', 'heart', 'broken heart', 'kiss', 'mail'
- * ];
- * @example
- * // Use textual emoticons as description.
- * config.smiley_descriptions =
- * [
- * ':)', ':(', ';)', ':D', ':/', ':P', ':*)', ':-o',
- * ':|', '>:(', 'o:)', '8-)', '>:-)', ';(', '', '', '',
- * '', '', ':-*', ''
- * ];
- */
-CKEDITOR.config.smiley_descriptions =
- [
- 'smiley', 'sad', 'wink', 'laugh', 'frown', 'cheeky', 'blush', 'surprise',
- 'indecision', 'angry', 'angle', 'cool', 'devil', 'crying', 'enlightened', 'no',
- 'yes', 'heart', 'broken heart', 'kiss', 'mail'
- ];
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.plugins.add( 'smiley', +{ + requires : [ 'dialog' ], + + init : function( editor ) + { + editor.config.smiley_path = editor.config.smiley_path || ( this.path + 'images/' ); + editor.addCommand( 'smiley', new CKEDITOR.dialogCommand( 'smiley' ) ); + editor.ui.addButton( 'Smiley', + { + label : editor.lang.smiley.toolbar, + command : 'smiley' + }); + CKEDITOR.dialog.add( 'smiley', this.path + 'dialogs/smiley.js' ); + } +} ); + +/** + * The base path used to build the URL for the smiley images. It must end with + * a slash. + * @name CKEDITOR.config.smiley_path + * @type String + * @default {@link CKEDITOR.basePath} + 'plugins/smiley/images/' + * @example + * config.smiley_path = 'http://www.example.com/images/smileys/'; + * @example + * config.smiley_path = '/images/smileys/'; + */ + +/** + * The file names for the smileys to be displayed. These files must be + * contained inside the URL path defined with the + * {@link CKEDITOR.config.smiley_path} setting. + * @type Array + * @default (see example) + * @example + * // This is actually the default value. + * config.smiley_images = [ + * 'regular_smile.gif','sad_smile.gif','wink_smile.gif','teeth_smile.gif','confused_smile.gif','tounge_smile.gif', + * 'embaressed_smile.gif','omg_smile.gif','whatchutalkingabout_smile.gif','angry_smile.gif','angel_smile.gif','shades_smile.gif', + * 'devil_smile.gif','cry_smile.gif','lightbulb.gif','thumbs_down.gif','thumbs_up.gif','heart.gif', + * 'broken_heart.gif','kiss.gif','envelope.gif']; + */ +CKEDITOR.config.smiley_images = [ + 'regular_smile.gif','sad_smile.gif','wink_smile.gif','teeth_smile.gif','confused_smile.gif','tounge_smile.gif', + 'embaressed_smile.gif','omg_smile.gif','whatchutalkingabout_smile.gif','angry_smile.gif','angel_smile.gif','shades_smile.gif', + 'devil_smile.gif','cry_smile.gif','lightbulb.gif','thumbs_down.gif','thumbs_up.gif','heart.gif', + 'broken_heart.gif','kiss.gif','envelope.gif']; + +/** + * The description to be used for each of the smileys defined in the + * {@link CKEDITOR.config.smiley_images} setting. Each entry in this array list + * must match its relative pair in the {@link CKEDITOR.config.smiley_images} + * setting. + * @type Array + * @default The textual descriptions of smiley. + * @example + * // Default settings. + * config.smiley_descriptions = + * [ + * 'smiley', 'sad', 'wink', 'laugh', 'frown', 'cheeky', 'blush', 'surprise', + * 'indecision', 'angry', 'angel', 'cool', 'devil', 'crying', 'enlightened', 'no', + * 'yes', 'heart', 'broken heart', 'kiss', 'mail' + * ]; + * @example + * // Use textual emoticons as description. + * config.smiley_descriptions = + * [ + * ':)', ':(', ';)', ':D', ':/', ':P', ':*)', ':-o', + * ':|', '>:(', 'o:)', '8-)', '>:-)', ';(', '', '', '', + * '', '', ':-*', '' + * ]; + */ +CKEDITOR.config.smiley_descriptions = + [ + 'smiley', 'sad', 'wink', 'laugh', 'frown', 'cheeky', 'blush', 'surprise', + 'indecision', 'angry', 'angel', 'cool', 'devil', 'crying', 'enlightened', 'no', + 'yes', 'heart', 'broken heart', 'kiss', 'mail' + ]; diff --git a/_source/plugins/sourcearea/plugin.js b/_source/plugins/sourcearea/plugin.js index 92ddce3..05e1592 100644 --- a/_source/plugins/sourcearea/plugin.js +++ b/_source/plugins/sourcearea/plugin.js @@ -1,205 +1,207 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-/**
- * @fileOverview The "sourcearea" plugin. It registers the "source" editing
- * mode, which displays the raw data being edited in the editor.
- */
-
-CKEDITOR.plugins.add( 'sourcearea',
-{
- requires : [ 'editingblock' ],
-
- init : function( editor )
- {
- var sourcearea = CKEDITOR.plugins.sourcearea,
- win = CKEDITOR.document.getWindow();
-
- editor.on( 'editingBlockReady', function()
- {
- var textarea,
- onResize;
-
- editor.addMode( 'source',
- {
- load : function( holderElement, data )
- {
- if ( CKEDITOR.env.ie && CKEDITOR.env.version < 8 )
- holderElement.setStyle( 'position', 'relative' );
-
- // Create the source area <textarea>.
- editor.textarea = textarea = new CKEDITOR.dom.element( 'textarea' );
- textarea.setAttributes(
- {
- dir : 'ltr',
- tabIndex : editor.tabIndex,
- 'role' : 'textbox',
- 'aria-label' : editor.lang.editorTitle.replace( '%1', editor.name )
- });
- textarea.addClass( 'cke_source' );
- textarea.addClass( 'cke_enable_context_menu' );
-
- var styles =
- {
- // IE7 has overflow the <textarea> from wrapping table cell.
- width : CKEDITOR.env.ie7Compat ? '99%' : '100%',
- height : '100%',
- resize : 'none',
- outline : 'none',
- 'text-align' : 'left'
- };
-
- // Having to make <textarea> fixed sized to conque the following bugs:
- // 1. The textarea height/width='100%' doesn't constraint to the 'td' in IE6/7.
- // 2. Unexpected vertical-scrolling behavior happens whenever focus is moving out of editor
- // if text content within it has overflowed. (#4762)
- if ( CKEDITOR.env.ie )
- {
- onResize = function()
- {
- // Holder rectange size is stretched by textarea,
- // so hide it just for a moment.
- textarea.hide();
- textarea.setStyle( 'height', holderElement.$.clientHeight + 'px' );
- textarea.setStyle( 'width', holderElement.$.clientWidth + 'px' );
- // When we have proper holder size, show textarea again.
- textarea.show();
- };
-
- editor.on( 'resize', onResize );
- win.on( 'resize', onResize );
- setTimeout( onResize, 0 );
- }
- else
- {
- // By some yet unknown reason, we must stop the
- // mousedown propagation for the textarea,
- // otherwise it's not possible to place the caret
- // inside of it (non IE).
- textarea.on( 'mousedown', function( evt )
- {
- evt.data.stopPropagation();
- } );
- }
-
- // Reset the holder element and append the
- // <textarea> to it.
- holderElement.setHtml( '' );
- holderElement.append( textarea );
- textarea.setStyles( styles );
-
- editor.fire( 'ariaWidget', textarea );
-
- textarea.on( 'blur', function()
- {
- editor.focusManager.blur();
- });
-
- textarea.on( 'focus', function()
- {
- editor.focusManager.focus();
- });
-
- // The editor data "may be dirty" after this point.
- editor.mayBeDirty = true;
-
- // Set the <textarea> value.
- this.loadData( data );
-
- var keystrokeHandler = editor.keystrokeHandler;
- if ( keystrokeHandler )
- keystrokeHandler.attach( textarea );
-
- setTimeout( function()
- {
- editor.mode = 'source';
- editor.fire( 'mode' );
- },
- ( CKEDITOR.env.gecko || CKEDITOR.env.webkit ) ? 100 : 0 );
- },
-
- loadData : function( data )
- {
- textarea.setValue( data );
- editor.fire( 'dataReady' );
- },
-
- getData : function()
- {
- return textarea.getValue();
- },
-
- getSnapshotData : function()
- {
- return textarea.getValue();
- },
-
- unload : function( holderElement )
- {
- editor.textarea = textarea = null;
-
- if ( onResize )
- {
- editor.removeListener( 'resize', onResize );
- win.removeListener( 'resize', onResize );
- }
-
- if ( CKEDITOR.env.ie && CKEDITOR.env.version < 8 )
- holderElement.removeStyle( 'position' );
- },
-
- focus : function()
- {
- textarea.focus();
- }
- });
- });
-
- editor.addCommand( 'source', sourcearea.commands.source );
-
- if ( editor.ui.addButton )
- {
- editor.ui.addButton( 'Source',
- {
- label : editor.lang.source,
- command : 'source'
- });
- }
-
- editor.on( 'mode', function()
- {
- editor.getCommand( 'source' ).setState(
- editor.mode == 'source' ?
- CKEDITOR.TRISTATE_ON :
- CKEDITOR.TRISTATE_OFF );
- });
- }
-});
-
-/**
- * Holds the definition of commands an UI elements included with the sourcearea
- * plugin.
- * @example
- */
-CKEDITOR.plugins.sourcearea =
-{
- commands :
- {
- source :
- {
- modes : { wysiwyg:1, source:1 },
-
- exec : function( editor )
- {
- if ( editor.mode == 'wysiwyg' )
- editor.fire( 'saveSnapshot' );
- editor.getCommand( 'source' ).setState( CKEDITOR.TRISTATE_DISABLED );
- editor.setMode( editor.mode == 'source' ? 'wysiwyg' : 'source' );
- },
-
- canUndo : false
- }
- }
-};
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview The "sourcearea" plugin. It registers the "source" editing + * mode, which displays the raw data being edited in the editor. + */ + +CKEDITOR.plugins.add( 'sourcearea', +{ + requires : [ 'editingblock' ], + + init : function( editor ) + { + var sourcearea = CKEDITOR.plugins.sourcearea, + win = CKEDITOR.document.getWindow(); + + editor.on( 'editingBlockReady', function() + { + var textarea, + onResize; + + editor.addMode( 'source', + { + load : function( holderElement, data ) + { + if ( CKEDITOR.env.ie && CKEDITOR.env.version < 8 ) + holderElement.setStyle( 'position', 'relative' ); + + // Create the source area <textarea>. + editor.textarea = textarea = new CKEDITOR.dom.element( 'textarea' ); + textarea.setAttributes( + { + dir : 'ltr', + tabIndex : CKEDITOR.env.webkit ? -1 : editor.tabIndex, + 'role' : 'textbox', + 'aria-label' : editor.lang.editorTitle.replace( '%1', editor.name ) + }); + textarea.addClass( 'cke_source' ); + textarea.addClass( 'cke_enable_context_menu' ); + + var styles = + { + // IE7 has overflow the <textarea> from wrapping table cell. + width : CKEDITOR.env.ie7Compat ? '99%' : '100%', + height : '100%', + resize : 'none', + outline : 'none', + 'text-align' : 'left' + }; + + // Having to make <textarea> fixed sized to conque the following bugs: + // 1. The textarea height/width='100%' doesn't constraint to the 'td' in IE6/7. + // 2. Unexpected vertical-scrolling behavior happens whenever focus is moving out of editor + // if text content within it has overflowed. (#4762) + if ( CKEDITOR.env.ie ) + { + onResize = function() + { + // Holder rectange size is stretched by textarea, + // so hide it just for a moment. + textarea.hide(); + textarea.setStyle( 'height', holderElement.$.clientHeight + 'px' ); + textarea.setStyle( 'width', holderElement.$.clientWidth + 'px' ); + // When we have proper holder size, show textarea again. + textarea.show(); + }; + + editor.on( 'resize', onResize ); + win.on( 'resize', onResize ); + setTimeout( onResize, 0 ); + } + else + { + // By some yet unknown reason, we must stop the + // mousedown propagation for the textarea, + // otherwise it's not possible to place the caret + // inside of it (non IE). + textarea.on( 'mousedown', function( evt ) + { + evt.data.stopPropagation(); + } ); + } + + // Reset the holder element and append the + // <textarea> to it. + holderElement.setHtml( '' ); + holderElement.append( textarea ); + textarea.setStyles( styles ); + + editor.fire( 'ariaWidget', textarea ); + + textarea.on( 'blur', function() + { + editor.focusManager.blur(); + }); + + textarea.on( 'focus', function() + { + editor.focusManager.focus(); + }); + + // The editor data "may be dirty" after this point. + editor.mayBeDirty = true; + + // Set the <textarea> value. + this.loadData( data ); + + var keystrokeHandler = editor.keystrokeHandler; + if ( keystrokeHandler ) + keystrokeHandler.attach( textarea ); + + setTimeout( function() + { + editor.mode = 'source'; + editor.fire( 'mode' ); + }, + ( CKEDITOR.env.gecko || CKEDITOR.env.webkit ) ? 100 : 0 ); + }, + + loadData : function( data ) + { + textarea.setValue( data ); + editor.fire( 'dataReady' ); + }, + + getData : function() + { + return textarea.getValue(); + }, + + getSnapshotData : function() + { + return textarea.getValue(); + }, + + unload : function( holderElement ) + { + textarea.clearCustomData(); + editor.textarea = textarea = null; + + if ( onResize ) + { + editor.removeListener( 'resize', onResize ); + win.removeListener( 'resize', onResize ); + } + + if ( CKEDITOR.env.ie && CKEDITOR.env.version < 8 ) + holderElement.removeStyle( 'position' ); + }, + + focus : function() + { + textarea.focus(); + } + }); + }); + + editor.addCommand( 'source', sourcearea.commands.source ); + + if ( editor.ui.addButton ) + { + editor.ui.addButton( 'Source', + { + label : editor.lang.source, + command : 'source' + }); + } + + editor.on( 'mode', function() + { + editor.getCommand( 'source' ).setState( + editor.mode == 'source' ? + CKEDITOR.TRISTATE_ON : + CKEDITOR.TRISTATE_OFF ); + }); + } +}); + +/** + * Holds the definition of commands an UI elements included with the sourcearea + * plugin. + * @example + */ +CKEDITOR.plugins.sourcearea = +{ + commands : + { + source : + { + modes : { wysiwyg:1, source:1 }, + editorFocus : false, + + exec : function( editor ) + { + if ( editor.mode == 'wysiwyg' ) + editor.fire( 'saveSnapshot' ); + editor.getCommand( 'source' ).setState( CKEDITOR.TRISTATE_DISABLED ); + editor.setMode( editor.mode == 'source' ? 'wysiwyg' : 'source' ); + }, + + canUndo : false + } + } +}; diff --git a/_source/plugins/specialchar/dialogs/specialchar.js b/_source/plugins/specialchar/dialogs/specialchar.js index 597f7e1..f933ff4 100644 --- a/_source/plugins/specialchar/dialogs/specialchar.js +++ b/_source/plugins/specialchar/dialogs/specialchar.js @@ -1,373 +1,406 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-CKEDITOR.dialog.add( 'specialchar', function( editor )
-{
- /**
- * Simulate "this" of a dialog for non-dialog events.
- * @type {CKEDITOR.dialog}
- */
- var dialog,
- lang = editor.lang.specialChar;
- var onChoice = function( evt )
- {
- var target, value;
- if ( evt.data )
- target = evt.data.getTarget();
- else
- target = new CKEDITOR.dom.element( evt );
-
- if ( target.getName() == 'a' && ( value = target.getChild( 0 ).getHtml() ) )
- {
- target.removeClass( "cke_light_background" );
- dialog.hide();
- editor.insertHtml( value );
- }
- };
-
- var onClick = CKEDITOR.tools.addFunction( onChoice );
-
- var focusedNode;
-
- var onFocus = function( evt, target )
- {
- var value;
- target = target || evt.data.getTarget();
-
- if ( target.getName() == 'span' )
- target = target.getParent();
-
- if ( target.getName() == 'a' && ( value = target.getChild( 0 ).getHtml() ) )
- {
- // Trigger blur manually if there is focused node.
- if ( focusedNode )
- onBlur( null, focusedNode );
-
- var htmlPreview = dialog.getContentElement( 'info', 'htmlPreview' ).getElement();
-
- dialog.getContentElement( 'info', 'charPreview' ).getElement().setHtml( value );
- htmlPreview.setHtml( CKEDITOR.tools.htmlEncode( value ) );
- target.getParent().addClass( "cke_light_background" );
-
- // Memorize focused node.
- focusedNode = target;
- }
- };
-
- var onBlur = function( evt, target )
- {
- target = target || evt.data.getTarget();
-
- if ( target.getName() == 'span' )
- target = target.getParent();
-
- if ( target.getName() == 'a' )
- {
- dialog.getContentElement( 'info', 'charPreview' ).getElement().setHtml( ' ' );
- dialog.getContentElement( 'info', 'htmlPreview' ).getElement().setHtml( ' ' );
- target.getParent().removeClass( "cke_light_background" );
-
- focusedNode = undefined;
- }
- };
-
- var onKeydown = CKEDITOR.tools.addFunction( function( ev )
- {
- ev = new CKEDITOR.dom.event( ev );
-
- // Get an Anchor element.
- var element = ev.getTarget();
- var relative, nodeToMove;
- var keystroke = ev.getKeystroke();
-
- switch ( keystroke )
- {
- // UP-ARROW
- case 38 :
- // relative is TR
- if ( ( relative = element.getParent().getParent().getPrevious() ) )
- {
- nodeToMove = relative.getChild( [element.getParent().getIndex(), 0] );
- nodeToMove.focus();
- onBlur( null, element );
- onFocus( null, nodeToMove );
- }
- ev.preventDefault();
- break;
- // DOWN-ARROW
- case 40 :
- // relative is TR
- if ( ( relative = element.getParent().getParent().getNext() ) )
- {
- nodeToMove = relative.getChild( [ element.getParent().getIndex(), 0 ] );
- if ( nodeToMove && nodeToMove.type == 1 )
- {
- nodeToMove.focus();
- onBlur( null, element );
- onFocus( null, nodeToMove );
- }
- }
- ev.preventDefault();
- break;
- // SPACE
- // ENTER is already handled as onClick
- case 32 :
- onChoice( { data: ev } );
- ev.preventDefault();
- break;
-
- // RIGHT-ARROW
- case 39 :
- // TAB
- case 9 :
- // relative is TD
- if ( ( relative = element.getParent().getNext() ) )
- {
- nodeToMove = relative.getChild( 0 );
- if ( nodeToMove.type == 1 )
- {
- nodeToMove.focus();
- onBlur( null, element );
- onFocus( null, nodeToMove );
- ev.preventDefault( true );
- }
- else
- onBlur( null, element );
- }
- // relative is TR
- else if ( ( relative = element.getParent().getParent().getNext() ) )
- {
- nodeToMove = relative.getChild( [ 0, 0 ] );
- if ( nodeToMove && nodeToMove.type == 1 )
- {
- nodeToMove.focus();
- onBlur( null, element );
- onFocus( null, nodeToMove );
- ev.preventDefault( true );
- }
- else
- onBlur( null, element );
- }
- break;
-
- // LEFT-ARROW
- case 37 :
- // SHIFT + TAB
- case CKEDITOR.SHIFT + 9 :
- // relative is TD
- if ( ( relative = element.getParent().getPrevious() ) )
- {
- nodeToMove = relative.getChild( 0 );
- nodeToMove.focus();
- onBlur( null, element );
- onFocus( null, nodeToMove );
- ev.preventDefault( true );
- }
- // relative is TR
- else if ( ( relative = element.getParent().getParent().getPrevious() ) )
- {
- nodeToMove = relative.getLast().getChild( 0 );
- nodeToMove.focus();
- onBlur( null, element );
- onFocus( null, nodeToMove );
- ev.preventDefault( true );
- }
- else
- onBlur( null, element );
- break;
- default :
- // Do not stop not handled events.
- return;
- }
- });
-
- return {
- title : lang.title,
- minWidth : 430,
- minHeight : 280,
- buttons : [ CKEDITOR.dialog.cancelButton ],
- charColumns : 17,
- chars :
- [
- '!','"','#','$','%','&',"'",'(',')','*','+','-','.','/',
- '0','1','2','3','4','5','6','7','8','9',':',';',
- '<','=','>','?','@',
- 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O',
- 'P','Q','R','S','T','U','V','W','X','Y','Z',
- '[',']','^','_','`',
- 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p',
- 'q','r','s','t','u','v','w','x','y','z',
- '{','|','}','~','€(EURO SIGN)','‘(LEFT SINGLE QUOTATION MARK)','’(RIGHT SINGLE QUOTATION MARK)','“(LEFT DOUBLE QUOTATION MARK)',
- '”(RIGHT DOUBLE QUOTATION MARK)','–(EN DASH)','—(EM DASH)','¡(INVERTED EXCLAMATION MARK)','¢(CENT SIGN)','£(POUND SIGN)',
- '¤(CURRENCY SIGN)','¥(YEN SIGN)','¦(BROKEN BAR)','§(SECTION SIGN)','¨(DIAERESIS)','©(COPYRIGHT SIGN)','ª(FEMININE ORDINAL INDICATOR)',
- '«(LEFT-POINTING DOUBLE ANGLE QUOTATION MARK)','¬(NOT SIGN)','®(REGISTERED SIGN)','¯(MACRON)','°(DEGREE SIGN)','±(PLUS-MINUS SIGN)','²(SUPERSCRIPT TWO)',
- '³(SUPERSCRIPT THREE)','´(ACUTE ACCENT)','µ(MICRO SIGN)','¶(PILCROW SIGN)','·(MIDDLE DOT)','¸(CEDILLA)',
- '¹(SUPERSCRIPT ONE)','º(MASCULINE ORDINAL INDICATOR)','»(RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK)','¼(VULGAR FRACTION ONE QUARTER)','½(VULGAR FRACTION ONE HALF)','¾(VULGAR FRACTION THREE QUARTERS)',
- '¿(INVERTED QUESTION MARK)','À(LATIN CAPITAL LETTER A WITH GRAVE)','Á(LATIN CAPITAL LETTER A WITH ACUTE)','Â(LATIN CAPITAL LETTER A WITH CIRCUMFLEX)','Ã(LATIN CAPITAL LETTER A WITH TILDE)','Ä(LATIN CAPITAL LETTER A WITH DIAERESIS)',
- 'Å(LATIN CAPITAL LETTER A WITH RING ABOVE)','Æ(LATIN CAPITAL LETTER AE)','Ç(LATIN CAPITAL LETTER C WITH CEDILLA)','È(LATIN CAPITAL LETTER E WITH GRAVE)','É(LATIN CAPITAL LETTER E WITH ACUTE)','Ê(LATIN CAPITAL LETTER E WITH CIRCUMFLEX)',
- 'Ë(LATIN CAPITAL LETTER E WITH DIAERESIS)','Ì(LATIN CAPITAL LETTER I WITH GRAVE)','Í(LATIN CAPITAL LETTER I WITH ACUTE)','Î(LATIN CAPITAL LETTER I WITH CIRCUMFLEX)','Ï(LATIN CAPITAL LETTER I WITH DIAERESIS)','Ð(LATIN CAPITAL LETTER ETH)',
- 'Ñ(LATIN CAPITAL LETTER N WITH TILDE)','Ò(LATIN CAPITAL LETTER O WITH GRAVE)','Ó(LATIN CAPITAL LETTER O WITH ACUTE)','Ô(LATIN CAPITAL LETTER O WITH CIRCUMFLEX)','Õ(LATIN CAPITAL LETTER O WITH TILDE)','Ö(LATIN CAPITAL LETTER O WITH DIAERESIS)',
- '×(MULTIPLICATION SIGN)','Ø(LATIN CAPITAL LETTER O WITH STROKE)','Ù(LATIN CAPITAL LETTER U WITH GRAVE)','Ú(LATIN CAPITAL LETTER U WITH ACUTE)','Û(LATIN CAPITAL LETTER U WITH CIRCUMFLEX)','Ü(LATIN CAPITAL LETTER U WITH DIAERESIS)',
- 'Ý(LATIN CAPITAL LETTER Y WITH ACUTE)','Þ(LATIN CAPITAL LETTER THORN)','ß(LATIN SMALL LETTER SHARP S)','à(LATIN SMALL LETTER A WITH GRAVE)','á(LATIN SMALL LETTER A WITH ACUTE)','â(LATIN SMALL LETTER A WITH CIRCUMFLEX)',
- 'ã(LATIN SMALL LETTER A WITH TILDE)','ä(LATIN SMALL LETTER A WITH DIAERESIS)','å(LATIN SMALL LETTER A WITH RING ABOVE)','æ(LATIN SMALL LETTER AE)','ç(LATIN SMALL LETTER C WITH CEDILLA)','è(LATIN SMALL LETTER E WITH GRAVE)',
- 'é(LATIN SMALL LETTER E WITH ACUTE)','ê(LATIN SMALL LETTER E WITH CIRCUMFLEX)','ë(LATIN SMALL LETTER E WITH DIAERESIS)','ì(LATIN SMALL LETTER I WITH GRAVE)','í(LATIN SMALL LETTER I WITH ACUTE)','î(LATIN SMALL LETTER I WITH CIRCUMFLEX)',
- 'ï(LATIN SMALL LETTER I WITH DIAERESIS)','ð(LATIN SMALL LETTER ETH)','ñ(LATIN SMALL LETTER N WITH TILDE)','ò(LATIN SMALL LETTER O WITH GRAVE)','ó(LATIN SMALL LETTER O WITH ACUTE)','ô(LATIN SMALL LETTER O WITH CIRCUMFLEX)',
- 'õ(LATIN SMALL LETTER O WITH TILDE)','ö(LATIN SMALL LETTER O WITH DIAERESIS)',
- '÷(DIVISION SIGN)','ø(LATIN SMALL LETTER O WITH STROKE)',
- 'ù(LATIN SMALL LETTER U WITH GRAVE)','ú(LATIN SMALL LETTER U WITH ACUTE)',
- 'û(LATIN SMALL LETTER U WITH CIRCUMFLEX)','ü(LATIN SMALL LETTER U WITH DIAERESIS)',
- 'ü(LATIN SMALL LETTER U WITH DIAERESIS)','ý(LATIN SMALL LETTER Y WITH ACUTE)','þ(LATIN SMALL LETTER THORN)','ÿ(LATIN SMALL LETTER Y WITH DIAERESIS)',
- 'Œ(LATIN CAPITAL LIGATURE OE)',
- 'œ(LATIN SMALL LIGATURE OE)','Ŵ(LATIN CAPITAL LETTER W WITH CIRCUMFLEX)',
- 'Ŷ(LATIN CAPITAL LETTER Y WITH CIRCUMFLEX)','ŵ(LATIN SMALL LETTER W WITH CIRCUMFLEX)',
- 'ŷ(LATIN SMALL LETTER Y WITH CIRCUMFLEX)','‚(SINGLE LOW-9 QUOTATION MARK)',
- '‛(SINGLE HIGH-REVERSED-9 QUOTATION MARK)','„(DOUBLE LOW-9 QUOTATION MARK)','…(HORIZONTAL ELLIPSIS)',
- '™(TRADE MARK SIGN)','►(BLACK RIGHT-POINTING POINTER)','•(BULLET)',
- '→(RIGHTWARDS ARROW)','⇒(RIGHTWARDS DOUBLE ARROW)','⇔(LEFT RIGHT DOUBLE ARROW)','♦(BLACK DIAMOND SUIT)','≈(ALMOST EQUAL TO)'
- ],
- onLoad : function()
- {
- var columns = this.definition.charColumns,
- chars = this.definition.chars;
-
- var html = [ '<table role="listbox" aria-labelledby="specialchar_table_label"' +
- ' style="width: 320px; height: 100%; border-collapse: separate;"' +
- ' align="center" cellspacing="2" cellpadding="2" border="0">' ];
-
- var i = 0,
- size = chars.length,
- character,
- charDesc;
-
- while ( i < size )
- {
- html.push( '<tr>' ) ;
-
- for ( var j = 0 ; j < columns ; j++, i++ )
- {
- if ( ( character = chars[ i ] ) )
- {
- charDesc = '';
- character = character.replace( /\((.*?)\)/, function( match, desc )
- {
- charDesc = desc;
- return '';
- } );
-
- // Use character in case description unavailable.
- charDesc = charDesc || character;
-
- html.push(
- '<td class="cke_dark_background" style="cursor: default">' +
- '<a href="javascript: void(0);" role="option"' +
- ' aria-posinset="' + ( i +1 ) + '"',
- ' aria-setsize="' + size + '"',
- ' aria-labelledby="cke_specialchar_label_' + i + '"',
- ' style="cursor: inherit; display: block; height: 1.25em; margin-top: 0.25em; text-align: center;" title="', CKEDITOR.tools.htmlEncode( charDesc ), '"' +
- ' onkeydown="CKEDITOR.tools.callFunction( ' + onKeydown + ', event, this )"' +
- ' onclick="CKEDITOR.tools.callFunction(' + onClick + ', this); return false;"' +
- ' tabindex="-1">' +
- '<span style="margin: 0 auto;cursor: inherit">' +
- character +
- '</span>' +
- '<span class="cke_voice_label" id="cke_specialchar_label_' + i + '">' +
- charDesc +
- '</span></a>');
- }
- else
- html.push( '<td class="cke_dark_background"> ' );
-
- html.push( '</td>' );
- }
- html.push( '</tr>' );
- }
-
- html.push( '</tbody></table>', '<span id="specialchar_table_label" class="cke_voice_label">' + editor.lang.common.options +'</span>' );
-
- this.getContentElement( 'info', 'charContainer' ).getElement().setHtml( html.join( '' ) );
- },
- contents : [
- {
- id : 'info',
- label : editor.lang.common.generalTab,
- title : editor.lang.common.generalTab,
- padding : 0,
- align : 'top',
- elements : [
- {
- type : 'hbox',
- align : 'top',
- widths : [ '320px', '90px' ],
- children :
- [
- {
- type : 'html',
- id : 'charContainer',
- html : '',
- onMouseover : onFocus,
- onMouseout : onBlur,
- focus : function()
- {
- var firstChar = this.getElement().getElementsByTag( 'a' ).getItem( 0 );
- setTimeout(function()
- {
- firstChar.focus();
- onFocus( null, firstChar );
- });
- },
- onShow : function()
- {
- var firstChar = this.getElement().getChild( [ 0, 0, 0, 0, 0 ] );
- setTimeout( function()
- {
- firstChar.focus();
- onFocus( null, firstChar );
- });
- },
- onLoad : function( event )
- {
- dialog = event.sender;
- }
- },
- {
- type : 'hbox',
- align : 'top',
- widths : [ '100%' ],
- children :
- [
- {
- type : 'vbox',
- align : 'top',
- children :
- [
- {
- type : 'html',
- html : '<div></div>'
- },
- {
- type : 'html',
- id : 'charPreview',
- className : 'cke_dark_background',
- style : 'border:1px solid #eeeeee;font-size:28px;height:40px;width:70px;padding-top:9px;font-family:\'Microsoft Sans Serif\',Arial,Helvetica,Verdana;text-align:center;',
- html : '<div> </div>'
- },
- {
- type : 'html',
- id : 'htmlPreview',
- className : 'cke_dark_background',
- style : 'border:1px solid #eeeeee;font-size:14px;height:20px;width:70px;padding-top:2px;font-family:\'Microsoft Sans Serif\',Arial,Helvetica,Verdana;text-align:center;',
- html : '<div> </div>'
- }
- ]
- }
- ]
- }
- ]
- }
- ]
- }
- ]
- };
-} );
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.dialog.add( 'specialchar', function( editor ) +{ + /** + * Simulate "this" of a dialog for non-dialog events. + * @type {CKEDITOR.dialog} + */ + var dialog, + lang = editor.lang.specialChar; + + var insertSpecialChar = function ( specialChar ) + { + var selection = editor.getSelection(), + ranges = selection.getRanges(), + range, textNode; + + editor.fire( 'saveSnapshot' ); + + for ( var i = 0, len = ranges.length ; i < len ; i++ ) + { + range = ranges[ i ]; + range.deleteContents(); + + textNode = CKEDITOR.dom.element.createFromHtml( specialChar ); + range.insertNode( textNode ); + } + + range.moveToPosition( textNode, CKEDITOR.POSITION_AFTER_END ); + range.select(); + + editor.fire( 'saveSnapshot' ); + }; + + var onChoice = function( evt ) + { + var target, value; + if ( evt.data ) + target = evt.data.getTarget(); + else + target = new CKEDITOR.dom.element( evt ); + + if ( target.getName() == 'a' && ( value = target.getChild( 0 ).getHtml() ) ) + { + target.removeClass( "cke_light_background" ); + dialog.hide(); + + // Firefox has bug on insert chars into a element use its own API. (#5170) + if ( CKEDITOR.env.gecko ) + insertSpecialChar( value ); + else + editor.insertHtml( value ); + } + }; + + var onClick = CKEDITOR.tools.addFunction( onChoice ); + + var focusedNode; + + var onFocus = function( evt, target ) + { + var value; + target = target || evt.data.getTarget(); + + if ( target.getName() == 'span' ) + target = target.getParent(); + + if ( target.getName() == 'a' && ( value = target.getChild( 0 ).getHtml() ) ) + { + // Trigger blur manually if there is focused node. + if ( focusedNode ) + onBlur( null, focusedNode ); + + var htmlPreview = dialog.getContentElement( 'info', 'htmlPreview' ).getElement(); + + dialog.getContentElement( 'info', 'charPreview' ).getElement().setHtml( value ); + htmlPreview.setHtml( CKEDITOR.tools.htmlEncode( value ) ); + target.getParent().addClass( "cke_light_background" ); + + // Memorize focused node. + focusedNode = target; + } + }; + + var onBlur = function( evt, target ) + { + target = target || evt.data.getTarget(); + + if ( target.getName() == 'span' ) + target = target.getParent(); + + if ( target.getName() == 'a' ) + { + dialog.getContentElement( 'info', 'charPreview' ).getElement().setHtml( ' ' ); + dialog.getContentElement( 'info', 'htmlPreview' ).getElement().setHtml( ' ' ); + target.getParent().removeClass( "cke_light_background" ); + + focusedNode = undefined; + } + }; + + var onKeydown = CKEDITOR.tools.addFunction( function( ev ) + { + ev = new CKEDITOR.dom.event( ev ); + + // Get an Anchor element. + var element = ev.getTarget(); + var relative, nodeToMove; + var keystroke = ev.getKeystroke(); + var rtl = editor.lang.dir == 'rtl'; + + switch ( keystroke ) + { + // UP-ARROW + case 38 : + // relative is TR + if ( ( relative = element.getParent().getParent().getPrevious() ) ) + { + nodeToMove = relative.getChild( [element.getParent().getIndex(), 0] ); + nodeToMove.focus(); + onBlur( null, element ); + onFocus( null, nodeToMove ); + } + ev.preventDefault(); + break; + // DOWN-ARROW + case 40 : + // relative is TR + if ( ( relative = element.getParent().getParent().getNext() ) ) + { + nodeToMove = relative.getChild( [ element.getParent().getIndex(), 0 ] ); + if ( nodeToMove && nodeToMove.type == 1 ) + { + nodeToMove.focus(); + onBlur( null, element ); + onFocus( null, nodeToMove ); + } + } + ev.preventDefault(); + break; + // SPACE + // ENTER is already handled as onClick + case 32 : + onChoice( { data: ev } ); + ev.preventDefault(); + break; + + // RIGHT-ARROW + case rtl ? 37 : 39 : + // TAB + case 9 : + // relative is TD + if ( ( relative = element.getParent().getNext() ) ) + { + nodeToMove = relative.getChild( 0 ); + if ( nodeToMove.type == 1 ) + { + nodeToMove.focus(); + onBlur( null, element ); + onFocus( null, nodeToMove ); + ev.preventDefault( true ); + } + else + onBlur( null, element ); + } + // relative is TR + else if ( ( relative = element.getParent().getParent().getNext() ) ) + { + nodeToMove = relative.getChild( [ 0, 0 ] ); + if ( nodeToMove && nodeToMove.type == 1 ) + { + nodeToMove.focus(); + onBlur( null, element ); + onFocus( null, nodeToMove ); + ev.preventDefault( true ); + } + else + onBlur( null, element ); + } + break; + + // LEFT-ARROW + case rtl ? 39 : 37 : + // SHIFT + TAB + case CKEDITOR.SHIFT + 9 : + // relative is TD + if ( ( relative = element.getParent().getPrevious() ) ) + { + nodeToMove = relative.getChild( 0 ); + nodeToMove.focus(); + onBlur( null, element ); + onFocus( null, nodeToMove ); + ev.preventDefault( true ); + } + // relative is TR + else if ( ( relative = element.getParent().getParent().getPrevious() ) ) + { + nodeToMove = relative.getLast().getChild( 0 ); + nodeToMove.focus(); + onBlur( null, element ); + onFocus( null, nodeToMove ); + ev.preventDefault( true ); + } + else + onBlur( null, element ); + break; + default : + // Do not stop not handled events. + return; + } + }); + + return { + title : lang.title, + minWidth : 430, + minHeight : 280, + buttons : [ CKEDITOR.dialog.cancelButton ], + charColumns : 17, + chars : + [ + '!','"','#','$','%','&',"'",'(',')','*','+','-','.','/', + '0','1','2','3','4','5','6','7','8','9',':',';', + '<','=','>','?','@', + 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O', + 'P','Q','R','S','T','U','V','W','X','Y','Z', + '[',']','^','_','`', + 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p', + 'q','r','s','t','u','v','w','x','y','z', + '{','|','}','~','€(EURO SIGN)','‘(LEFT SINGLE QUOTATION MARK)','’(RIGHT SINGLE QUOTATION MARK)','“(LEFT DOUBLE QUOTATION MARK)', + '”(RIGHT DOUBLE QUOTATION MARK)','–(EN DASH)','—(EM DASH)','¡(INVERTED EXCLAMATION MARK)','¢(CENT SIGN)','£(POUND SIGN)', + '¤(CURRENCY SIGN)','¥(YEN SIGN)','¦(BROKEN BAR)','§(SECTION SIGN)','¨(DIAERESIS)','©(COPYRIGHT SIGN)','ª(FEMININE ORDINAL INDICATOR)', + '«(LEFT-POINTING DOUBLE ANGLE QUOTATION MARK)','¬(NOT SIGN)','®(REGISTERED SIGN)','¯(MACRON)','°(DEGREE SIGN)','±(PLUS-MINUS SIGN)','²(SUPERSCRIPT TWO)', + '³(SUPERSCRIPT THREE)','´(ACUTE ACCENT)','µ(MICRO SIGN)','¶(PILCROW SIGN)','·(MIDDLE DOT)','¸(CEDILLA)', + '¹(SUPERSCRIPT ONE)','º(MASCULINE ORDINAL INDICATOR)','»(RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK)','¼(VULGAR FRACTION ONE QUARTER)','½(VULGAR FRACTION ONE HALF)','¾(VULGAR FRACTION THREE QUARTERS)', + '¿(INVERTED QUESTION MARK)','À(LATIN CAPITAL LETTER A WITH GRAVE)','Á(LATIN CAPITAL LETTER A WITH ACUTE)','Â(LATIN CAPITAL LETTER A WITH CIRCUMFLEX)','Ã(LATIN CAPITAL LETTER A WITH TILDE)','Ä(LATIN CAPITAL LETTER A WITH DIAERESIS)', + 'Å(LATIN CAPITAL LETTER A WITH RING ABOVE)','Æ(LATIN CAPITAL LETTER AE)','Ç(LATIN CAPITAL LETTER C WITH CEDILLA)','È(LATIN CAPITAL LETTER E WITH GRAVE)','É(LATIN CAPITAL LETTER E WITH ACUTE)','Ê(LATIN CAPITAL LETTER E WITH CIRCUMFLEX)', + 'Ë(LATIN CAPITAL LETTER E WITH DIAERESIS)','Ì(LATIN CAPITAL LETTER I WITH GRAVE)','Í(LATIN CAPITAL LETTER I WITH ACUTE)','Î(LATIN CAPITAL LETTER I WITH CIRCUMFLEX)','Ï(LATIN CAPITAL LETTER I WITH DIAERESIS)','Ð(LATIN CAPITAL LETTER ETH)', + 'Ñ(LATIN CAPITAL LETTER N WITH TILDE)','Ò(LATIN CAPITAL LETTER O WITH GRAVE)','Ó(LATIN CAPITAL LETTER O WITH ACUTE)','Ô(LATIN CAPITAL LETTER O WITH CIRCUMFLEX)','Õ(LATIN CAPITAL LETTER O WITH TILDE)','Ö(LATIN CAPITAL LETTER O WITH DIAERESIS)', + '×(MULTIPLICATION SIGN)','Ø(LATIN CAPITAL LETTER O WITH STROKE)','Ù(LATIN CAPITAL LETTER U WITH GRAVE)','Ú(LATIN CAPITAL LETTER U WITH ACUTE)','Û(LATIN CAPITAL LETTER U WITH CIRCUMFLEX)','Ü(LATIN CAPITAL LETTER U WITH DIAERESIS)', + 'Ý(LATIN CAPITAL LETTER Y WITH ACUTE)','Þ(LATIN CAPITAL LETTER THORN)','ß(LATIN SMALL LETTER SHARP S)','à(LATIN SMALL LETTER A WITH GRAVE)','á(LATIN SMALL LETTER A WITH ACUTE)','â(LATIN SMALL LETTER A WITH CIRCUMFLEX)', + 'ã(LATIN SMALL LETTER A WITH TILDE)','ä(LATIN SMALL LETTER A WITH DIAERESIS)','å(LATIN SMALL LETTER A WITH RING ABOVE)','æ(LATIN SMALL LETTER AE)','ç(LATIN SMALL LETTER C WITH CEDILLA)','è(LATIN SMALL LETTER E WITH GRAVE)', + 'é(LATIN SMALL LETTER E WITH ACUTE)','ê(LATIN SMALL LETTER E WITH CIRCUMFLEX)','ë(LATIN SMALL LETTER E WITH DIAERESIS)','ì(LATIN SMALL LETTER I WITH GRAVE)','í(LATIN SMALL LETTER I WITH ACUTE)','î(LATIN SMALL LETTER I WITH CIRCUMFLEX)', + 'ï(LATIN SMALL LETTER I WITH DIAERESIS)','ð(LATIN SMALL LETTER ETH)','ñ(LATIN SMALL LETTER N WITH TILDE)','ò(LATIN SMALL LETTER O WITH GRAVE)','ó(LATIN SMALL LETTER O WITH ACUTE)','ô(LATIN SMALL LETTER O WITH CIRCUMFLEX)', + 'õ(LATIN SMALL LETTER O WITH TILDE)','ö(LATIN SMALL LETTER O WITH DIAERESIS)', + '÷(DIVISION SIGN)','ø(LATIN SMALL LETTER O WITH STROKE)', + 'ù(LATIN SMALL LETTER U WITH GRAVE)','ú(LATIN SMALL LETTER U WITH ACUTE)', + 'û(LATIN SMALL LETTER U WITH CIRCUMFLEX)','ü(LATIN SMALL LETTER U WITH DIAERESIS)', + 'ü(LATIN SMALL LETTER U WITH DIAERESIS)','ý(LATIN SMALL LETTER Y WITH ACUTE)','þ(LATIN SMALL LETTER THORN)','ÿ(LATIN SMALL LETTER Y WITH DIAERESIS)', + 'Œ(LATIN CAPITAL LIGATURE OE)', + 'œ(LATIN SMALL LIGATURE OE)','Ŵ(LATIN CAPITAL LETTER W WITH CIRCUMFLEX)', + 'Ŷ(LATIN CAPITAL LETTER Y WITH CIRCUMFLEX)','ŵ(LATIN SMALL LETTER W WITH CIRCUMFLEX)', + 'ŷ(LATIN SMALL LETTER Y WITH CIRCUMFLEX)','‚(SINGLE LOW-9 QUOTATION MARK)', + '‛(SINGLE HIGH-REVERSED-9 QUOTATION MARK)','„(DOUBLE LOW-9 QUOTATION MARK)','…(HORIZONTAL ELLIPSIS)', + '™(TRADE MARK SIGN)','►(BLACK RIGHT-POINTING POINTER)','•(BULLET)', + '→(RIGHTWARDS ARROW)','⇒(RIGHTWARDS DOUBLE ARROW)','⇔(LEFT RIGHT DOUBLE ARROW)','♦(BLACK DIAMOND SUIT)','≈(ALMOST EQUAL TO)' + ], + onLoad : function() + { + var columns = this.definition.charColumns, + chars = this.definition.chars; + + var charsTableLabel = 'specialchar_table_label' + CKEDITOR.tools.getNextNumber(); + var html = [ '<table role="listbox" aria-labelledby="' + charsTableLabel + '"' + + ' style="width: 320px; height: 100%; border-collapse: separate;"' + + ' align="center" cellspacing="2" cellpadding="2" border="0">' ]; + + var i = 0, + size = chars.length, + character, + charDesc; + + while ( i < size ) + { + html.push( '<tr>' ) ; + + for ( var j = 0 ; j < columns ; j++, i++ ) + { + if ( ( character = chars[ i ] ) ) + { + charDesc = ''; + character = character.replace( /\((.*?)\)/, function( match, desc ) + { + charDesc = desc; + return ''; + } ); + + // Use character in case description unavailable. + charDesc = charDesc || character; + + var charLabelId = 'cke_specialchar_label_' + i + '_' + CKEDITOR.tools.getNextNumber(); + + html.push( + '<td class="cke_dark_background" style="cursor: default" role="presentation">' + + '<a href="javascript: void(0);" role="option"' + + ' aria-posinset="' + ( i +1 ) + '"', + ' aria-setsize="' + size + '"', + ' aria-labelledby="' + charLabelId + '"', + ' style="cursor: inherit; display: block; height: 1.25em; margin-top: 0.25em; text-align: center;" title="', CKEDITOR.tools.htmlEncode( charDesc ), '"' + + ' onkeydown="CKEDITOR.tools.callFunction( ' + onKeydown + ', event, this )"' + + ' onclick="CKEDITOR.tools.callFunction(' + onClick + ', this); return false;"' + + ' tabindex="-1">' + + '<span style="margin: 0 auto;cursor: inherit">' + + character + + '</span>' + + '<span class="cke_voice_label" id="' + charLabelId + '">' + + charDesc + + '</span></a>'); + } + else + html.push( '<td class="cke_dark_background"> ' ); + + html.push( '</td>' ); + } + html.push( '</tr>' ); + } + + html.push( '</tbody></table>', '<span id="' + charsTableLabel + '" class="cke_voice_label">' + lang.options +'</span>' ); + + this.getContentElement( 'info', 'charContainer' ).getElement().setHtml( html.join( '' ) ); + }, + contents : [ + { + id : 'info', + label : editor.lang.common.generalTab, + title : editor.lang.common.generalTab, + padding : 0, + align : 'top', + elements : [ + { + type : 'hbox', + align : 'top', + widths : [ '320px', '90px' ], + children : + [ + { + type : 'html', + id : 'charContainer', + html : '', + onMouseover : onFocus, + onMouseout : onBlur, + focus : function() + { + var firstChar = this.getElement().getElementsByTag( 'a' ).getItem( 0 ); + setTimeout(function() + { + firstChar.focus(); + onFocus( null, firstChar ); + }); + }, + onShow : function() + { + var firstChar = this.getElement().getChild( [ 0, 0, 0, 0, 0 ] ); + setTimeout( function() + { + firstChar.focus(); + onFocus( null, firstChar ); + }); + }, + onLoad : function( event ) + { + dialog = event.sender; + } + }, + { + type : 'hbox', + align : 'top', + widths : [ '100%' ], + children : + [ + { + type : 'vbox', + align : 'top', + children : + [ + { + type : 'html', + html : '<div></div>' + }, + { + type : 'html', + id : 'charPreview', + className : 'cke_dark_background', + style : 'border:1px solid #eeeeee;font-size:28px;height:40px;width:70px;padding-top:9px;font-family:\'Microsoft Sans Serif\',Arial,Helvetica,Verdana;text-align:center;', + html : '<div> </div>' + }, + { + type : 'html', + id : 'htmlPreview', + className : 'cke_dark_background', + style : 'border:1px solid #eeeeee;font-size:14px;height:20px;width:70px;padding-top:2px;font-family:\'Microsoft Sans Serif\',Arial,Helvetica,Verdana;text-align:center;', + html : '<div> </div>' + } + ] + } + ] + } + ] + } + ] + } + ] + }; +} ); diff --git a/_source/plugins/split/images/split.gif b/_source/plugins/split/images/split.gif Binary files differnew file mode 100644 index 0000000..8d1cffd --- /dev/null +++ b/_source/plugins/split/images/split.gif diff --git a/_source/plugins/split/plugin.js b/_source/plugins/split/plugin.js new file mode 100644 index 0000000..99acbb1 --- /dev/null +++ b/_source/plugins/split/plugin.js @@ -0,0 +1,107 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @file Content Split Marker + */ + +// Register a plugin named "split". +CKEDITOR.plugins.add( 'split', +{ + init : function( editor ) + { + // Register the command. + editor.addCommand( 'split', CKEDITOR.plugins.splitCmd ); + + // Register the toolbar button. + editor.ui.addButton( 'Split', + { + label : editor.lang.split, + command : 'split' + }); + + // Add the style that renders our placeholder. + editor.addCss( + 'img.cke_split' + + '{' + + 'background-image: url(' + CKEDITOR.getUrl( this.path + 'images/split.gif' ) + ');' + + 'background-position: center center;' + + 'background-repeat: no-repeat;' + + 'clear: both;' + + 'display: block;' + + 'float: none;' + + 'width:100%;_width:99.9%;' + + 'border-top: #999999 1px dotted;' + + 'border-bottom: #999999 1px dotted;' + + 'height: 5px;' + + 'page-break-after: always;' + + + '}' ); + }, + + afterInit : function( editor ) + { + // Register a filter to displaying placeholders after mode change. + + var dataProcessor = editor.dataProcessor, + dataFilter = dataProcessor && dataProcessor.dataFilter; + + if ( dataFilter ) + { + dataFilter.addRules( + { + elements : + { + div : function( element ) + { + 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; + + if ( childStyle && ( /page-break-after\s*:\s*always/i ).test( style ) && ( /display\s*:\s*none/i ).test( childStyle ) ) + return editor.createFakeParserElement( element, 'cke_split', 'div' ); + } + } + }); + } + }, + + requires : [ 'fakeobjects' ] +}); + +CKEDITOR.plugins.splitCmd = +{ + exec : function( editor ) + { + // Create the element that represents a print break. + var breakObject = CKEDITOR.dom.element.createFromHtml( '<div style="page-break-after: always;"><span style="display: none;"> </span></div>...split...' ); + + // Creates the fake image used for this element. + breakObject = editor.createFakeElement( breakObject, 'cke_split', 'div' ); + + var ranges = editor.getSelection().getRanges(); + + editor.fire( 'saveSnapshot' ); + + for ( var range, i = 0 ; i < ranges.length ; i++ ) + { + range = ranges[ i ]; + + if ( i > 0 ) + breakObject = breakObject.clone( true ); + + range.splitBlock( 'p' ); + range.insertNode( breakObject ); + if ( i == ranges.length - 1 ) + { + range.moveToPosition( breakObject, CKEDITOR.POSITION_AFTER_END ); + range.select(); + } + } + + editor.fire( 'saveSnapshot' ); + } +}; diff --git a/_source/plugins/styles/plugin.js b/_source/plugins/styles/plugin.js index 85cda5f..55e4ed3 100644 --- a/_source/plugins/styles/plugin.js +++ b/_source/plugins/styles/plugin.js @@ -1,1258 +1,1381 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-CKEDITOR.plugins.add( 'styles',
-{
- requires : [ 'selection' ]
-});
-
-/**
- * Registers a function to be called whenever a style changes its state in the
- * editing area. The current state is passed to the function. The possible
- * states are {@link CKEDITOR.TRISTATE_ON} and {@link CKEDITOR.TRISTATE_OFF}.
- * @param {CKEDITOR.style} The style to be watched.
- * @param {Function} The function to be called when the style state changes.
- * @example
- * // Create a style object for the <b> element.
- * var style = new CKEDITOR.style( { element : 'b' } );
- * var editor = CKEDITOR.instances.editor1;
- * editor.attachStyleStateChange( style, function( state )
- * {
- * if ( state == CKEDITOR.TRISTATE_ON )
- * alert( 'The current state for the B element is ON' );
- * else
- * alert( 'The current state for the B element is OFF' );
- * });
- */
-CKEDITOR.editor.prototype.attachStyleStateChange = function( style, callback )
-{
- // Try to get the list of attached callbacks.
- var styleStateChangeCallbacks = this._.styleStateChangeCallbacks;
-
- // If it doesn't exist, it means this is the first call. So, let's create
- // all the structure to manage the style checks and the callback calls.
- if ( !styleStateChangeCallbacks )
- {
- // Create the callbacks array.
- styleStateChangeCallbacks = this._.styleStateChangeCallbacks = [];
-
- // Attach to the selectionChange event, so we can check the styles at
- // that point.
- this.on( 'selectionChange', function( ev )
- {
- // Loop throw all registered callbacks.
- for ( var i = 0 ; i < styleStateChangeCallbacks.length ; i++ )
- {
- var callback = styleStateChangeCallbacks[ i ];
-
- // Check the current state for the style defined for that
- // callback.
- var currentState = callback.style.checkActive( ev.data.path ) ? CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF;
-
- // If the state changed since the last check.
- if ( callback.state !== currentState )
- {
- // Call the callback function, passing the current
- // state to it.
- callback.fn.call( this, currentState );
-
- // Save the current state, so it can be compared next
- // time.
- callback.state !== currentState;
- }
- }
- });
- }
-
- // Save the callback info, so it can be checked on the next occurence of
- // selectionChange.
- styleStateChangeCallbacks.push( { style : style, fn : callback } );
-};
-
-CKEDITOR.STYLE_BLOCK = 1;
-CKEDITOR.STYLE_INLINE = 2;
-CKEDITOR.STYLE_OBJECT = 3;
-
-(function()
-{
- var blockElements = { address:1,div:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,p:1,pre:1 };
- var objectElements = { a:1,embed:1,hr:1,img:1,li:1,object:1,ol:1,table:1,td:1,tr:1,ul:1 };
-
- var semicolonFixRegex = /\s*(?:;\s*|$)/;
-
- CKEDITOR.style = function( styleDefinition, variablesValues )
- {
- if ( variablesValues )
- {
- styleDefinition = CKEDITOR.tools.clone( styleDefinition );
-
- replaceVariables( styleDefinition.attributes, variablesValues );
- replaceVariables( styleDefinition.styles, variablesValues );
- }
-
- var element = this.element = ( styleDefinition.element || '*' ).toLowerCase();
-
- this.type =
- ( element == '#' || blockElements[ element ] ) ?
- CKEDITOR.STYLE_BLOCK
- : objectElements[ element ] ?
- CKEDITOR.STYLE_OBJECT
- :
- CKEDITOR.STYLE_INLINE;
-
- this._ =
- {
- definition : styleDefinition
- };
- };
-
- CKEDITOR.style.prototype =
- {
- apply : function( document )
- {
- applyStyle.call( this, document, false );
- },
-
- remove : function( document )
- {
- applyStyle.call( this, document, true );
- },
-
- applyToRange : function( range )
- {
- return ( this.applyToRange =
- this.type == CKEDITOR.STYLE_INLINE ?
- applyInlineStyle
- : this.type == CKEDITOR.STYLE_BLOCK ?
- applyBlockStyle
- : null ).call( this, range );
- },
-
- removeFromRange : function( range )
- {
- return ( this.removeFromRange =
- this.type == CKEDITOR.STYLE_INLINE ?
- removeInlineStyle
- : null ).call( this, range );
- },
-
- applyToObject : function( element )
- {
- setupElement( element, this );
- },
-
- /**
- * Get the style state inside an element path. Returns "true" if the
- * element is active in the path.
- */
- checkActive : function( elementPath )
- {
- switch ( this.type )
- {
- case CKEDITOR.STYLE_BLOCK :
- return this.checkElementRemovable( elementPath.block || elementPath.blockLimit, true );
-
- case CKEDITOR.STYLE_INLINE :
-
- var elements = elementPath.elements;
-
- for ( var i = 0, element ; i < elements.length ; i++ )
- {
- element = elements[i];
-
- if ( element == elementPath.block || element == elementPath.blockLimit )
- continue;
-
- if ( this.checkElementRemovable( element, true ) )
- return true;
- }
- }
- return false;
- },
-
- // Checks if an element, or any of its attributes, is removable by the
- // current style definition.
- checkElementRemovable : function( element, fullMatch )
- {
- if ( !element )
- return false;
-
- var def = this._.definition,
- attribs;
-
- // If the element name is the same as the style name.
- if ( element.getName() == this.element )
- {
- // If no attributes are defined in the element.
- if ( !fullMatch && !element.hasAttributes() )
- return true;
-
- attribs = getAttributesForComparison( def );
-
- if ( attribs._length )
- {
- for ( var attName in attribs )
- {
- if ( attName == '_length' )
- continue;
-
- var elementAttr = element.getAttribute( attName ) || '';
- if ( attribs[ attName ] ==
- ( attName == 'style' ?
- normalizeCssText( elementAttr, false ) : elementAttr ) )
- {
- if ( !fullMatch )
- return true;
- }
- else if ( fullMatch )
- return false;
- }
- if ( fullMatch )
- return true;
- }
- else
- return true;
- }
-
- // Check if the element can be somehow overriden.
- var override = getOverrides( this )[ element.getName() ] ;
- if ( override )
- {
- // If no attributes have been defined, remove the element.
- if ( !( attribs = override.attributes ) )
- return true;
-
- for ( var i = 0 ; i < attribs.length ; i++ )
- {
- attName = attribs[i][0];
- var actualAttrValue = element.getAttribute( attName );
- if ( actualAttrValue )
- {
- var attValue = attribs[i][1];
-
- // Remove the attribute if:
- // - The override definition value is null;
- // - The override definition value is a string that
- // matches the attribute value exactly.
- // - The override definition value is a regex that
- // has matches in the attribute value.
- if ( attValue === null ||
- ( typeof attValue == 'string' && actualAttrValue == attValue ) ||
- attValue.test( actualAttrValue ) )
- return true;
- }
- }
- }
- return false;
- }
- };
-
- // Build the cssText based on the styles definition.
- CKEDITOR.style.getStyleText = function( styleDefinition )
- {
- // If we have already computed it, just return it.
- var stylesDef = styleDefinition._ST;
- if ( stylesDef )
- return stylesDef;
-
- stylesDef = styleDefinition.styles;
-
- // Builds the StyleText.
-
- var stylesText = ( styleDefinition.attributes && styleDefinition.attributes[ 'style' ] ) || '';
-
- if ( stylesText.length )
- stylesText = stylesText.replace( semicolonFixRegex, ';' );
-
- for ( var style in stylesDef )
- stylesText += ( style + ':' + stylesDef[ style ] ).replace( semicolonFixRegex, ';' );
-
- // Browsers make some changes to the style when applying them. So, here
- // we normalize it to the browser format.
- if ( stylesText.length )
- stylesText = normalizeCssText( stylesText );
-
- // Return it, saving it to the next request.
- return ( styleDefinition._ST = stylesText );
- };
-
- function applyInlineStyle( range )
- {
- var document = range.document;
-
- if ( range.collapsed )
- {
- // Create the element to be inserted in the DOM.
- var collapsedElement = getElement( this, document );
-
- // Insert the empty element into the DOM at the range position.
- range.insertNode( collapsedElement );
-
- // Place the selection right inside the empty element.
- range.moveToPosition( collapsedElement, CKEDITOR.POSITION_BEFORE_END );
-
- return;
- }
-
- var elementName = this.element;
- var def = this._.definition;
- var isUnknownElement;
-
- // Get the DTD definition for the element. Defaults to "span".
- var dtd = CKEDITOR.dtd[ elementName ] || ( isUnknownElement = true, CKEDITOR.dtd.span );
-
- // Bookmark the range so we can re-select it after processing.
- var bookmark = range.createBookmark();
-
- // Expand the range.
- range.enlarge( CKEDITOR.ENLARGE_ELEMENT );
- range.trim();
-
- // Get the first node to be processed and the last, which concludes the
- // processing.
- var boundaryNodes = range.getBoundaryNodes();
- var firstNode = boundaryNodes.startNode;
- var lastNode = boundaryNodes.endNode.getNextSourceNode( true );
-
- // Probably the document end is reached, we need a marker node.
- if ( !lastNode )
- {
- var marker;
- lastNode = marker = document.createText( '' );
- lastNode.insertAfter( range.endContainer );
- }
- // The detection algorithm below skips the contents inside bookmark nodes, so
- // we'll need to make sure lastNode isn't the inside a bookmark node.
- var lastParent = lastNode.getParent();
- if ( lastParent && lastParent.getAttribute( '_fck_bookmark' ) )
- lastNode = lastParent;
-
- if ( lastNode.equals( firstNode ) )
- {
- // If the last node is the same as the the first one, we must move
- // it to the next one, otherwise the first one will not be
- // processed.
- lastNode = lastNode.getNextSourceNode( true );
-
- // It may happen that there are no more nodes after it (the end of
- // the document), so we must add something there to make our code
- // simpler.
- if ( !lastNode )
- {
- lastNode = marker = document.createText( '' );
- lastNode.insertAfter( firstNode );
- }
- }
-
- var currentNode = firstNode;
-
- var styleRange;
-
- // Indicates that that some useful inline content has been found, so
- // the style should be applied.
- var hasContents;
-
- while ( currentNode )
- {
- var applyStyle = false;
-
- if ( currentNode.equals( lastNode ) )
- {
- currentNode = null;
- applyStyle = true;
- }
- else
- {
- var nodeType = currentNode.type;
- var nodeName = nodeType == CKEDITOR.NODE_ELEMENT ? currentNode.getName() : null;
-
- if ( nodeName && currentNode.getAttribute( '_fck_bookmark' ) )
- {
- currentNode = currentNode.getNextSourceNode( true );
- continue;
- }
-
- // Check if the current node can be a child of the style element.
- if ( !nodeName || ( dtd[ nodeName ]
- && ( currentNode.getPosition( lastNode ) | CKEDITOR.POSITION_PRECEDING | CKEDITOR.POSITION_IDENTICAL | CKEDITOR.POSITION_IS_CONTAINED ) == ( CKEDITOR.POSITION_PRECEDING + CKEDITOR.POSITION_IDENTICAL + CKEDITOR.POSITION_IS_CONTAINED )
- && ( !def.childRule || def.childRule( currentNode ) ) ) )
- {
- var currentParent = currentNode.getParent();
-
- // Check if the style element can be a child of the current
- // node parent or if the element is not defined in the DTD.
- if ( currentParent
- && ( ( currentParent.getDtd() || CKEDITOR.dtd.span )[ elementName ] || isUnknownElement )
- && ( !def.parentRule || def.parentRule( currentParent ) ) )
- {
- // This node will be part of our range, so if it has not
- // been started, place its start right before the node.
- // In the case of an element node, it will be included
- // only if it is entirely inside the range.
- if ( !styleRange && ( !nodeName || !CKEDITOR.dtd.$removeEmpty[ nodeName ] || ( currentNode.getPosition( lastNode ) | CKEDITOR.POSITION_PRECEDING | CKEDITOR.POSITION_IDENTICAL | CKEDITOR.POSITION_IS_CONTAINED ) == ( CKEDITOR.POSITION_PRECEDING + CKEDITOR.POSITION_IDENTICAL + CKEDITOR.POSITION_IS_CONTAINED ) ) )
- {
- styleRange = new CKEDITOR.dom.range( document );
- styleRange.setStartBefore( currentNode );
- }
-
- // Non element nodes, or empty elements can be added
- // completely to the range.
- if ( nodeType == CKEDITOR.NODE_TEXT || ( nodeType == CKEDITOR.NODE_ELEMENT && !currentNode.getChildCount() ) )
- {
- var includedNode = currentNode;
- var parentNode;
-
- // This node is about to be included completelly, but,
- // if this is the last node in its parent, we must also
- // check if the parent itself can be added completelly
- // to the range.
- while ( !includedNode.$.nextSibling
- && ( parentNode = includedNode.getParent(), dtd[ parentNode.getName() ] )
- && ( parentNode.getPosition( firstNode ) | CKEDITOR.POSITION_FOLLOWING | CKEDITOR.POSITION_IDENTICAL | CKEDITOR.POSITION_IS_CONTAINED ) == ( CKEDITOR.POSITION_FOLLOWING + CKEDITOR.POSITION_IDENTICAL + CKEDITOR.POSITION_IS_CONTAINED )
- && ( !def.childRule || def.childRule( parentNode ) ) )
- {
- includedNode = parentNode;
- }
-
- styleRange.setEndAfter( includedNode );
-
- // If the included node still is the last node in its
- // parent, it means that the parent can't be included
- // in this style DTD, so apply the style immediately.
- if ( !includedNode.$.nextSibling )
- applyStyle = true;
-
- if ( !hasContents )
- hasContents = ( nodeType != CKEDITOR.NODE_TEXT || (/[^\s\ufeff]/).test( currentNode.getText() ) );
- }
- }
- else
- applyStyle = true;
- }
- else
- applyStyle = true;
-
- // Get the next node to be processed.
- currentNode = currentNode.getNextSourceNode();
- }
-
- // Apply the style if we have something to which apply it.
- if ( applyStyle && hasContents && styleRange && !styleRange.collapsed )
- {
- // Build the style element, based on the style object definition.
- var styleNode = getElement( this, document );
-
- // Get the element that holds the entire range.
- var parent = styleRange.getCommonAncestor();
-
- // Loop through the parents, removing the redundant attributes
- // from the element to be applied.
- while ( styleNode && parent )
- {
- if ( parent.getName() == elementName )
- {
- for ( var attName in def.attributes )
- {
- if ( styleNode.getAttribute( attName ) == parent.getAttribute( attName ) )
- styleNode.removeAttribute( attName );
- }
-
- for ( var styleName in def.styles )
- {
- if ( styleNode.getStyle( styleName ) == parent.getStyle( styleName ) )
- styleNode.removeStyle( styleName );
- }
-
- if ( !styleNode.hasAttributes() )
- {
- styleNode = null;
- break;
- }
- }
-
- parent = parent.getParent();
- }
-
- if ( styleNode )
- {
- // Move the contents of the range to the style element.
- styleRange.extractContents().appendTo( styleNode );
-
- // Here we do some cleanup, removing all duplicated
- // elements from the style element.
- removeFromInsideElement( this, styleNode );
-
- // Insert it into the range position (it is collapsed after
- // extractContents.
- styleRange.insertNode( styleNode );
-
- // Let's merge our new style with its neighbors, if possible.
- mergeSiblings( styleNode );
-
- // As the style system breaks text nodes constantly, let's normalize
- // things for performance.
- // With IE, some paragraphs get broken when calling normalize()
- // repeatedly. Also, for IE, we must normalize body, not documentElement.
- // IE is also known for having a "crash effect" with normalize().
- // We should try to normalize with IE too in some way, somewhere.
- if ( !CKEDITOR.env.ie )
- styleNode.$.normalize();
- }
-
- // Style applied, let's release the range, so it gets
- // re-initialization in the next loop.
- styleRange = null;
- }
- }
-
- // Remove the temporary marking node.(#4111)
- marker && marker.remove();
- range.moveToBookmark( bookmark );
- }
-
- function removeInlineStyle( range )
- {
- /*
- * Make sure our range has included all "collpased" parent inline nodes so
- * that our operation logic can be simpler.
- */
- range.enlarge( CKEDITOR.ENLARGE_ELEMENT );
-
- var bookmark = range.createBookmark(),
- startNode = bookmark.startNode;
-
- if ( range.collapsed )
- {
-
- var startPath = new CKEDITOR.dom.elementPath( startNode.getParent() ),
- // The topmost element in elementspatch which we should jump out of.
- boundaryElement;
-
-
- for ( var i = 0, element ; i < startPath.elements.length
- && ( element = startPath.elements[i] ) ; i++ )
- {
- /*
- * 1. If it's collaped inside text nodes, try to remove the style from the whole element.
- *
- * 2. Otherwise if it's collapsed on element boundaries, moving the selection
- * outside the styles instead of removing the whole tag,
- * also make sure other inner styles were well preserverd.(#3309)
- */
- if ( element == startPath.block || element == startPath.blockLimit )
- break;
-
- if ( this.checkElementRemovable( element ) )
- {
- var endOfElement = range.checkBoundaryOfElement( element, CKEDITOR.END ),
- startOfElement = !endOfElement && range.checkBoundaryOfElement( element, CKEDITOR.START );
- if ( startOfElement || endOfElement )
- {
- boundaryElement = element;
- boundaryElement.match = startOfElement ? 'start' : 'end';
- }
- else
- {
- /*
- * Before removing the style node, there may be a sibling to the style node
- * that's exactly the same to the one to be removed. To the user, it makes
- * no difference that they're separate entities in the DOM tree. So, merge
- * them before removal.
- */
- mergeSiblings( element );
- removeFromElement( this, element );
-
- }
- }
- }
-
- // Re-create the style tree after/before the boundary element,
- // the replication start from bookmark start node to define the
- // new range.
- if ( boundaryElement )
- {
- var clonedElement = startNode;
- for ( i = 0 ;; i++ )
- {
- var newElement = startPath.elements[ i ];
- if ( newElement.equals( boundaryElement ) )
- break;
- // Avoid copying any matched element.
- else if ( newElement.match )
- continue;
- else
- newElement = newElement.clone();
- newElement.append( clonedElement );
- clonedElement = newElement;
- }
- clonedElement[ boundaryElement.match == 'start' ?
- 'insertBefore' : 'insertAfter' ]( boundaryElement );
- }
- }
- else
- {
- /*
- * Now our range isn't collapsed. Lets walk from the start node to the end
- * node via DFS and remove the styles one-by-one.
- */
- var endNode = bookmark.endNode,
- me = this;
-
- /*
- * Find out the style ancestor that needs to be broken down at startNode
- * and endNode.
- */
- function breakNodes()
- {
- var startPath = new CKEDITOR.dom.elementPath( startNode.getParent() ),
- endPath = new CKEDITOR.dom.elementPath( endNode.getParent() ),
- breakStart = null,
- breakEnd = null;
- for ( var i = 0 ; i < startPath.elements.length ; i++ )
- {
- var element = startPath.elements[ i ];
-
- if ( element == startPath.block || element == startPath.blockLimit )
- break;
-
- if ( me.checkElementRemovable( element ) )
- breakStart = element;
- }
- for ( i = 0 ; i < endPath.elements.length ; i++ )
- {
- element = endPath.elements[ i ];
-
- if ( element == endPath.block || element == endPath.blockLimit )
- break;
-
- if ( me.checkElementRemovable( element ) )
- breakEnd = element;
- }
-
- if ( breakEnd )
- endNode.breakParent( breakEnd );
- if ( breakStart )
- startNode.breakParent( breakStart );
- }
- breakNodes();
-
- // Now, do the DFS walk.
- var currentNode = startNode.getNext();
- while ( !currentNode.equals( endNode ) )
- {
- /*
- * Need to get the next node first because removeFromElement() can remove
- * the current node from DOM tree.
- */
- var nextNode = currentNode.getNextSourceNode();
- if ( currentNode.type == CKEDITOR.NODE_ELEMENT && this.checkElementRemovable( currentNode ) )
- {
- // Remove style from element or overriding element.
- if ( currentNode.getName() == this.element )
- removeFromElement( this, currentNode );
- else
- removeOverrides( currentNode, getOverrides( this )[ currentNode.getName() ] );
-
- /*
- * removeFromElement() may have merged the next node with something before
- * the startNode via mergeSiblings(). In that case, the nextNode would
- * contain startNode and we'll have to call breakNodes() again and also
- * reassign the nextNode to something after startNode.
- */
- if ( nextNode.type == CKEDITOR.NODE_ELEMENT && nextNode.contains( startNode ) )
- {
- breakNodes();
- nextNode = startNode.getNext();
- }
- }
- currentNode = nextNode;
- }
- }
-
- range.moveToBookmark( bookmark );
-}
-
- function applyBlockStyle( range )
- {
- // Serializible bookmarks is needed here since
- // elements may be merged.
- var bookmark = range.createBookmark( true );
-
- var iterator = range.createIterator();
- iterator.enforceRealBlocks = true;
-
- var block;
- var doc = range.document;
- var previousPreBlock;
-
- while ( ( block = iterator.getNextParagraph() ) ) // Only one =
- {
- var newBlock = getElement( this, doc );
- replaceBlock( block, newBlock );
- }
-
- range.moveToBookmark( bookmark );
- }
-
- // Replace the original block with new one, with special treatment
- // for <pre> blocks to make sure content format is well preserved, and merging/splitting adjacent
- // when necessary.(#3188)
- function replaceBlock( block, newBlock )
- {
- var newBlockIsPre = newBlock.is( 'pre' );
- var blockIsPre = block.is( 'pre' );
-
- var isToPre = newBlockIsPre && !blockIsPre;
- var isFromPre = !newBlockIsPre && blockIsPre;
-
- if ( isToPre )
- newBlock = toPre( block, newBlock );
- else if ( isFromPre )
- // Split big <pre> into pieces before start to convert.
- newBlock = fromPres( splitIntoPres( block ), newBlock );
- else
- block.moveChildren( newBlock );
-
- newBlock.replace( block );
-
- if ( newBlockIsPre )
- {
- // Merge previous <pre> blocks.
- mergePre( newBlock );
- }
- }
-
- /**
- * Merge a <pre> block with a previous sibling if available.
- */
- function mergePre( preBlock )
- {
- var previousBlock;
- if ( !( ( previousBlock = preBlock.getPreviousSourceNode( true, CKEDITOR.NODE_ELEMENT ) )
- && previousBlock.is
- && previousBlock.is( 'pre') ) )
- return;
-
- // Merge the previous <pre> block contents into the current <pre>
- // block.
- //
- // Another thing to be careful here is that currentBlock might contain
- // a '\n' at the beginning, and previousBlock might contain a '\n'
- // towards the end. These new lines are not normally displayed but they
- // become visible after merging.
- var mergedHtml = replace( previousBlock.getHtml(), /\n$/, '' ) + '\n\n' +
- replace( preBlock.getHtml(), /^\n/, '' ) ;
-
- // Krugle: IE normalizes innerHTML from <pre>, breaking whitespaces.
- if ( CKEDITOR.env.ie )
- preBlock.$.outerHTML = '<pre>' + mergedHtml + '</pre>';
- else
- preBlock.setHtml( mergedHtml );
-
- previousBlock.remove();
- }
-
- /**
- * Split into multiple <pre> blocks separated by double line-break.
- * @param preBlock
- */
- function splitIntoPres( preBlock )
- {
- // Exclude the ones at header OR at tail,
- // and ignore bookmark content between them.
- var duoBrRegex = /(\S\s*)\n(?:\s|(<span[^>]+_fck_bookmark.*?\/span>))*\n(?!$)/gi,
- blockName = preBlock.getName(),
- splitedHtml = replace( preBlock.getOuterHtml(),
- duoBrRegex,
- function( match, charBefore, bookmark )
- {
- return charBefore + '</pre>' + bookmark + '<pre>';
- } );
-
- var pres = [];
- splitedHtml.replace( /<pre>([\s\S]*?)<\/pre>/gi, function( match, preContent ){
- pres.push( preContent );
- } );
- return pres;
- }
-
- // Wrapper function of String::replace without considering of head/tail bookmarks nodes.
- function replace( str, regexp, replacement )
- {
- var headBookmark = '',
- tailBookmark = '';
-
- str = str.replace( /(^<span[^>]+_fck_bookmark.*?\/span>)|(<span[^>]+_fck_bookmark.*?\/span>$)/gi,
- function( str, m1, m2 ){
- m1 && ( headBookmark = m1 );
- m2 && ( tailBookmark = m2 );
- return '';
- } );
- return headBookmark + str.replace( regexp, replacement ) + tailBookmark;
- }
- /**
- * Converting a list of <pre> into blocks with format well preserved.
- */
- function fromPres( preHtmls, newBlock )
- {
- var docFrag = new CKEDITOR.dom.documentFragment( newBlock.getDocument() );
- for ( var i = 0 ; i < preHtmls.length ; i++ )
- {
- var blockHtml = preHtmls[ i ];
-
- // 1. Trim the first and last line-breaks immediately after and before <pre>,
- // they're not visible.
- blockHtml = blockHtml.replace( /(\r\n|\r)/g, '\n' ) ;
- blockHtml = replace( blockHtml, /^[ \t]*\n/, '' ) ;
- blockHtml = replace( blockHtml, /\n$/, '' ) ;
- // 2. Convert spaces or tabs at the beginning or at the end to
- blockHtml = replace( blockHtml, /^[ \t]+|[ \t]+$/g, function( match, offset, s )
- {
- if ( match.length == 1 ) // one space, preserve it
- return ' ' ;
- else if ( !offset ) // beginning of block
- return CKEDITOR.tools.repeat( ' ', match.length - 1 ) + ' ';
- else // end of block
- return ' ' + CKEDITOR.tools.repeat( ' ', match.length - 1 );
- } ) ;
-
- // 3. Convert \n to <BR>.
- // 4. Convert contiguous (i.e. non-singular) spaces or tabs to
- blockHtml = blockHtml.replace( /\n/g, '<br>' ) ;
- blockHtml = blockHtml.replace( /[ \t]{2,}/g,
- function ( match )
- {
- return CKEDITOR.tools.repeat( ' ', match.length - 1 ) + ' ' ;
- } ) ;
-
- var newBlockClone = newBlock.clone();
- newBlockClone.setHtml( blockHtml );
- docFrag.append( newBlockClone );
- }
- return docFrag;
- }
-
- /**
- * Converting from a non-PRE block to a PRE block in formatting operations.
- */
- function toPre( block, newBlock )
- {
- // First trim the block content.
- var preHtml = block.getHtml();
-
- // 1. Trim head/tail spaces, they're not visible.
- preHtml = replace( preHtml, /(?:^[ \t\n\r]+)|(?:[ \t\n\r]+$)/g, '' );
- // 2. Delete ANSI whitespaces immediately before and after <BR> because
- // they are not visible.
- preHtml = preHtml.replace( /[ \t\r\n]*(<br[^>]*>)[ \t\r\n]*/gi, '$1' );
- // 3. Compress other ANSI whitespaces since they're only visible as one
- // single space previously.
- // 4. Convert to spaces since is no longer needed in <PRE>.
- preHtml = preHtml.replace( /([ \t\n\r]+| )/g, ' ' );
- // 5. Convert any <BR /> to \n. This must not be done earlier because
- // the \n would then get compressed.
- preHtml = preHtml.replace( /<br\b[^>]*>/gi, '\n' );
-
- // Krugle: IE normalizes innerHTML to <pre>, breaking whitespaces.
- if ( CKEDITOR.env.ie )
- {
- var temp = block.getDocument().createElement( 'div' );
- temp.append( newBlock );
- newBlock.$.outerHTML = '<pre>' + preHtml + '</pre>';
- newBlock = temp.getFirst().remove();
- }
- else
- newBlock.setHtml( preHtml );
-
- return newBlock;
- }
-
- // Removes a style from an element itself, don't care about its subtree.
- function removeFromElement( style, element )
- {
- var def = style._.definition,
- attributes = def.attributes,
- styles = def.styles,
- overrides = getOverrides( style );
-
- function removeAttrs()
- {
- for ( var attName in attributes )
- {
- // The 'class' element value must match (#1318).
- if ( attName == 'class' && element.getAttribute( attName ) != attributes[ attName ] )
- continue;
- element.removeAttribute( attName );
- }
- }
-
- // Remove definition attributes/style from the elemnt.
- removeAttrs();
- for ( var styleName in styles )
- element.removeStyle( styleName );
-
- // Now remove override styles on the element.
- attributes = overrides[ element.getName() ];
- if ( attributes )
- removeAttrs();
- removeNoAttribsElement( element );
- }
-
- // Removes a style from inside an element.
- function removeFromInsideElement( style, element )
- {
- var def = style._.definition,
- attribs = def.attributes,
- styles = def.styles,
- overrides = getOverrides( style );
-
- var innerElements = element.getElementsByTag( style.element );
-
- for ( var i = innerElements.count(); --i >= 0 ; )
- removeFromElement( style, innerElements.getItem( i ) );
-
- // Now remove any other element with different name that is
- // defined to be overriden.
- for ( var overrideElement in overrides )
- {
- if ( overrideElement != style.element )
- {
- innerElements = element.getElementsByTag( overrideElement ) ;
- for ( i = innerElements.count() - 1 ; i >= 0 ; i-- )
- {
- var innerElement = innerElements.getItem( i );
- removeOverrides( innerElement, overrides[ overrideElement ] ) ;
- }
- }
- }
-
- }
-
- /**
- * Remove overriding styles/attributes from the specific element.
- * Note: Remove the element if no attributes remain.
- * @param {Object} element
- * @param {Object} overrides
- */
- function removeOverrides( element, overrides )
- {
- var attributes = overrides && overrides.attributes ;
-
- if ( attributes )
- {
- for ( var i = 0 ; i < attributes.length ; i++ )
- {
- var attName = attributes[i][0], actualAttrValue ;
-
- if ( ( actualAttrValue = element.getAttribute( attName ) ) )
- {
- var attValue = attributes[i][1] ;
-
- // Remove the attribute if:
- // - The override definition value is null ;
- // - The override definition valie is a string that
- // matches the attribute value exactly.
- // - The override definition value is a regex that
- // has matches in the attribute value.
- if ( attValue === null ||
- ( attValue.test && attValue.test( actualAttrValue ) ) ||
- ( typeof attValue == 'string' && actualAttrValue == attValue ) )
- element.removeAttribute( attName ) ;
- }
- }
- }
-
- removeNoAttribsElement( element );
- }
-
- // If the element has no more attributes, remove it.
- function removeNoAttribsElement( element )
- {
- // If no more attributes remained in the element, remove it,
- // leaving its children.
- if ( !element.hasAttributes() )
- {
- // Removing elements may open points where merging is possible,
- // so let's cache the first and last nodes for later checking.
- var firstChild = element.getFirst();
- var lastChild = element.getLast();
-
- element.remove( true );
-
- if ( firstChild )
- {
- // Check the cached nodes for merging.
- mergeSiblings( firstChild );
-
- if ( lastChild && !firstChild.equals( lastChild ) )
- mergeSiblings( lastChild );
- }
- }
- }
-
- function mergeSiblings( element )
- {
- if ( !element || element.type != CKEDITOR.NODE_ELEMENT || !CKEDITOR.dtd.$removeEmpty[ element.getName() ] )
- return;
-
- mergeElements( element, element.getNext(), true );
- mergeElements( element, element.getPrevious() );
- }
-
- function mergeElements( element, sibling, isNext )
- {
- if ( sibling && sibling.type == CKEDITOR.NODE_ELEMENT )
- {
- var hasBookmark = sibling.getAttribute( '_fck_bookmark' );
-
- if ( hasBookmark )
- sibling = isNext ? sibling.getNext() : sibling.getPrevious();
-
- if ( sibling && sibling.type == CKEDITOR.NODE_ELEMENT && element.isIdentical( sibling ) )
- {
- // Save the last child to be checked too, to merge things like
- // <b><i></i></b><b><i></i></b> => <b><i></i></b>
- var innerSibling = isNext ? element.getLast() : element.getFirst();
-
- if ( hasBookmark )
- ( isNext ? sibling.getPrevious() : sibling.getNext() ).move( element, !isNext );
-
- sibling.moveChildren( element, !isNext );
- sibling.remove();
-
- // Now check the last inner child (see two comments above).
- if ( innerSibling )
- mergeSiblings( innerSibling );
- }
- }
- }
-
- function getElement( style, targetDocument )
- {
- var el;
-
- var def = style._.definition;
-
- var elementName = style.element;
-
- // The "*" element name will always be a span for this function.
- if ( elementName == '*' )
- elementName = 'span';
-
- // Create the element.
- el = new CKEDITOR.dom.element( elementName, targetDocument );
-
- return setupElement( el, style );
- }
-
- function setupElement( el, style )
- {
- var def = style._.definition;
- var attributes = def.attributes;
- var styles = CKEDITOR.style.getStyleText( def );
-
- // Assign all defined attributes.
- if ( attributes )
- {
- for ( var att in attributes )
- {
- el.setAttribute( att, attributes[ att ] );
- }
- }
-
- // Assign all defined styles.
- if ( styles )
- el.setAttribute( 'style', styles );
-
- return el;
- }
-
- var varRegex = /#\((.+?)\)/g;
- function replaceVariables( list, variablesValues )
- {
- for ( var item in list )
- {
- list[ item ] = list[ item ].replace( varRegex, function( match, varName )
- {
- return variablesValues[ varName ];
- });
- }
- }
-
-
- // Returns an object that can be used for style matching comparison.
- // Attributes names and values are all lowercased, and the styles get
- // merged with the style attribute.
- function getAttributesForComparison( styleDefinition )
- {
- // If we have already computed it, just return it.
- var attribs = styleDefinition._AC;
- if ( attribs )
- return attribs;
-
- attribs = {};
-
- var length = 0;
-
- // Loop through all defined attributes.
- var styleAttribs = styleDefinition.attributes;
- if ( styleAttribs )
- {
- for ( var styleAtt in styleAttribs )
- {
- length++;
- attribs[ styleAtt ] = styleAttribs[ styleAtt ];
- }
- }
-
- // Includes the style definitions.
- var styleText = CKEDITOR.style.getStyleText( styleDefinition );
- if ( styleText )
- {
- if ( !attribs[ 'style' ] )
- length++;
- attribs[ 'style' ] = styleText;
- }
-
- // Appends the "length" information to the object.
- attribs._length = length;
-
- // Return it, saving it to the next request.
- return ( styleDefinition._AC = attribs );
- }
-
- /**
- * Get the the collection used to compare the elements and attributes,
- * defined in this style overrides, with other element. All information in
- * it is lowercased.
- * @param {CKEDITOR.style} style
- */
- function getOverrides( style )
- {
- if ( style._.overrides )
- return style._.overrides;
-
- var overrides = ( style._.overrides = {} ),
- definition = style._.definition.overrides;
-
- if ( definition )
- {
- // The override description can be a string, object or array.
- // Internally, well handle arrays only, so transform it if needed.
- if ( !CKEDITOR.tools.isArray( definition ) )
- definition = [ definition ];
-
- // Loop through all override definitions.
- for ( var i = 0 ; i < definition.length ; i++ )
- {
- var override = definition[i];
- var elementName;
- var overrideEl;
- var attrs;
-
- // If can be a string with the element name.
- if ( typeof override == 'string' )
- elementName = override.toLowerCase();
- // Or an object.
- else
- {
- elementName = override.element ? override.element.toLowerCase() : style.element;
- attrs = override.attributes;
- }
-
- // We can have more than one override definition for the same
- // element name, so we attempt to simply append information to
- // it if it already exists.
- overrideEl = overrides[ elementName ] || ( overrides[ elementName ] = {} );
-
- if ( attrs )
- {
- // The returning attributes list is an array, because we
- // could have different override definitions for the same
- // attribute name.
- var overrideAttrs = ( overrideEl.attributes = overrideEl.attributes || new Array() );
- for ( var attName in attrs )
- {
- // Each item in the attributes array is also an array,
- // where [0] is the attribute name and [1] is the
- // override value.
- overrideAttrs.push( [ attName.toLowerCase(), attrs[ attName ] ] );
- }
- }
- }
- }
-
- return overrides;
- }
-
- function normalizeCssText( unparsedCssText, nativeNormalize )
- {
- var styleText;
- if ( nativeNormalize !== false )
- {
- // Injects the style in a temporary span object, so the browser parses it,
- // retrieving its final format.
- var temp = new CKEDITOR.dom.element( 'span' );
- temp.setAttribute( 'style', unparsedCssText );
- styleText = temp.getAttribute( 'style' ) || '';
- }
- else
- styleText = unparsedCssText;
-
- // Shrinking white-spaces around colon and semi-colon (#4147).
- // Compensate tail semi-colon.
- return styleText.replace( /\s*([;:])\s*/, '$1' )
- .replace( /([^\s;])$/, '$1;')
- .replace( /,\s+/g, ',' ) // Trimming spaces after comma (e.g. font-family name)(#4107).
- .toLowerCase();
- }
-
- function applyStyle( document, remove )
- {
- // Get all ranges from the selection.
- var selection = document.getSelection();
- var ranges = selection.getRanges();
- var func = remove ? this.removeFromRange : this.applyToRange;
-
- // Apply the style to the ranges.
- for ( var i = 0 ; i < ranges.length ; i++ )
- func.call( this, ranges[ i ] );
-
- // Select the ranges again.
- selection.selectRanges( ranges );
- }
-})();
-
-CKEDITOR.styleCommand = function( style )
-{
- this.style = style;
-};
-
-CKEDITOR.styleCommand.prototype.exec = function( editor )
-{
- editor.focus();
-
- var doc = editor.document;
-
- if ( doc )
- {
- if ( this.state == CKEDITOR.TRISTATE_OFF )
- this.style.apply( doc );
- else if ( this.state == CKEDITOR.TRISTATE_ON )
- this.style.remove( doc );
- }
-
- return !!doc;
-};
-
-CKEDITOR.stylesSet = new CKEDITOR.resourceManager( '', 'stylesSet' );
-
-// Backward compatibility (#5025).
-CKEDITOR.addStylesSet = CKEDITOR.tools.bind( CKEDITOR.stylesSet.add, CKEDITOR.stylesSet );
-CKEDITOR.loadStylesSet = function( name, url, callback )
- {
- CKEDITOR.stylesSet.addExternal( name, url, '' );
- CKEDITOR.stylesSet.load( name, callback );
- };
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.plugins.add( 'styles', +{ + requires : [ 'selection' ] +}); + +/** + * Registers a function to be called whenever a style changes its state in the + * editing area. The current state is passed to the function. The possible + * states are {@link CKEDITOR.TRISTATE_ON} and {@link CKEDITOR.TRISTATE_OFF}. + * @param {CKEDITOR.style} The style to be watched. + * @param {Function} The function to be called when the style state changes. + * @example + * // Create a style object for the <b> element. + * var style = new CKEDITOR.style( { element : 'b' } ); + * var editor = CKEDITOR.instances.editor1; + * editor.attachStyleStateChange( style, function( state ) + * { + * if ( state == CKEDITOR.TRISTATE_ON ) + * alert( 'The current state for the B element is ON' ); + * else + * alert( 'The current state for the B element is OFF' ); + * }); + */ +CKEDITOR.editor.prototype.attachStyleStateChange = function( style, callback ) +{ + // Try to get the list of attached callbacks. + var styleStateChangeCallbacks = this._.styleStateChangeCallbacks; + + // If it doesn't exist, it means this is the first call. So, let's create + // all the structure to manage the style checks and the callback calls. + if ( !styleStateChangeCallbacks ) + { + // Create the callbacks array. + styleStateChangeCallbacks = this._.styleStateChangeCallbacks = []; + + // Attach to the selectionChange event, so we can check the styles at + // that point. + this.on( 'selectionChange', function( ev ) + { + // Loop throw all registered callbacks. + for ( var i = 0 ; i < styleStateChangeCallbacks.length ; i++ ) + { + var callback = styleStateChangeCallbacks[ i ]; + + // Check the current state for the style defined for that + // callback. + var currentState = callback.style.checkActive( ev.data.path ) ? CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF; + + // If the state changed since the last check. + if ( callback.state !== currentState ) + { + // Call the callback function, passing the current + // state to it. + callback.fn.call( this, currentState ); + + // Save the current state, so it can be compared next + // time. + callback.state !== currentState; + } + } + }); + } + + // Save the callback info, so it can be checked on the next occurence of + // selectionChange. + styleStateChangeCallbacks.push( { style : style, fn : callback } ); +}; + +CKEDITOR.STYLE_BLOCK = 1; +CKEDITOR.STYLE_INLINE = 2; +CKEDITOR.STYLE_OBJECT = 3; + +(function() +{ + var blockElements = { address:1,div:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,p:1,pre:1 }; + var objectElements = { a:1,embed:1,hr:1,img:1,li:1,object:1,ol:1,table:1,td:1,tr:1,th:1,ul:1,dl:1,dt:1,dd:1,form:1}; + + var semicolonFixRegex = /\s*(?:;\s*|$)/; + + CKEDITOR.style = function( styleDefinition, variablesValues ) + { + if ( variablesValues ) + { + styleDefinition = CKEDITOR.tools.clone( styleDefinition ); + + replaceVariables( styleDefinition.attributes, variablesValues ); + replaceVariables( styleDefinition.styles, variablesValues ); + } + + var element = this.element = ( styleDefinition.element || '*' ).toLowerCase(); + + this.type = + ( element == '#' || blockElements[ element ] ) ? + CKEDITOR.STYLE_BLOCK + : objectElements[ element ] ? + CKEDITOR.STYLE_OBJECT + : + CKEDITOR.STYLE_INLINE; + + this._ = + { + definition : styleDefinition + }; + }; + + CKEDITOR.style.prototype = + { + apply : function( document ) + { + applyStyle.call( this, document, false ); + }, + + remove : function( document ) + { + applyStyle.call( this, document, true ); + }, + + applyToRange : function( range ) + { + return ( this.applyToRange = + this.type == CKEDITOR.STYLE_INLINE ? + applyInlineStyle + : this.type == CKEDITOR.STYLE_BLOCK ? + applyBlockStyle + : this.type == CKEDITOR.STYLE_OBJECT ? + applyObjectStyle + : null ).call( this, range ); + }, + + removeFromRange : function( range ) + { + return ( this.removeFromRange = + this.type == CKEDITOR.STYLE_INLINE ? + removeInlineStyle + : null ).call( this, range ); + }, + + applyToObject : function( element ) + { + setupElement( element, this ); + }, + + /** + * Get the style state inside an element path. Returns "true" if the + * element is active in the path. + */ + checkActive : function( elementPath ) + { + switch ( this.type ) + { + case CKEDITOR.STYLE_BLOCK : + return this.checkElementRemovable( elementPath.block || elementPath.blockLimit, true ); + + case CKEDITOR.STYLE_OBJECT : + case CKEDITOR.STYLE_INLINE : + + var elements = elementPath.elements; + + for ( var i = 0, element ; i < elements.length ; i++ ) + { + element = elements[ i ]; + + if ( this.type == CKEDITOR.STYLE_INLINE + && ( element == elementPath.block || element == elementPath.blockLimit ) ) + continue; + + if( this.type == CKEDITOR.STYLE_OBJECT + && !( element.getName() in objectElements ) ) + continue; + + if ( this.checkElementRemovable( element, true ) ) + return true; + } + } + return false; + }, + + checkApplicable : function( elementPath ) + { + switch ( this.type ) + { + case CKEDITOR.STYLE_INLINE : + case CKEDITOR.STYLE_BLOCK : + break; + + case CKEDITOR.STYLE_OBJECT : + return elementPath.lastElement.getAscendant( this.element, true ); + } + + return true; + }, + + // Checks if an element, or any of its attributes, is removable by the + // current style definition. + checkElementRemovable : function( element, fullMatch ) + { + if ( !element ) + return false; + + var def = this._.definition, + attribs; + + // If the element name is the same as the style name. + if ( element.getName() == this.element ) + { + // If no attributes are defined in the element. + if ( !fullMatch && !element.hasAttributes() ) + return true; + + attribs = getAttributesForComparison( def ); + + if ( attribs._length ) + { + for ( var attName in attribs ) + { + if ( attName == '_length' ) + continue; + + var elementAttr = element.getAttribute( attName ) || ''; + if ( attName == 'style' ? + compareCssText( attribs[ attName ], normalizeCssText( elementAttr, false ) ) + : attribs[ attName ] == elementAttr ) + { + if ( !fullMatch ) + return true; + } + else if ( fullMatch ) + return false; + } + if ( fullMatch ) + return true; + } + else + return true; + } + + // Check if the element can be somehow overriden. + var override = getOverrides( this )[ element.getName() ] ; + if ( override ) + { + // If no attributes have been defined, remove the element. + if ( !( attribs = override.attributes ) ) + return true; + + for ( var i = 0 ; i < attribs.length ; i++ ) + { + attName = attribs[i][0]; + var actualAttrValue = element.getAttribute( attName ); + if ( actualAttrValue ) + { + var attValue = attribs[i][1]; + + // Remove the attribute if: + // - The override definition value is null; + // - The override definition value is a string that + // matches the attribute value exactly. + // - The override definition value is a regex that + // has matches in the attribute value. + if ( attValue === null || + ( typeof attValue == 'string' && actualAttrValue == attValue ) || + attValue.test( actualAttrValue ) ) + return true; + } + } + } + return false; + }, + + // Builds the preview HTML based on the styles definition. + buildPreview : function() + { + var styleDefinition = this._.definition, + html = [], + elementName = styleDefinition.element; + + // Avoid <bdo> in the preview. + if ( elementName == 'bdo' ) + elementName = 'span'; + + html = [ '<', elementName ]; + + // Assign all defined attributes. + var attribs = styleDefinition.attributes; + if ( attribs ) + { + for ( var att in attribs ) + { + html.push( ' ', att, '="', attribs[ att ], '"' ); + } + } + + // Assign the style attribute. + var cssStyle = CKEDITOR.style.getStyleText( styleDefinition ); + if ( cssStyle ) + html.push( ' style="', cssStyle, '"' ); + + html.push( '>', styleDefinition.name, '</', elementName, '>' ); + + return html.join( '' ); + } + }; + + // Build the cssText based on the styles definition. + CKEDITOR.style.getStyleText = function( styleDefinition ) + { + // If we have already computed it, just return it. + var stylesDef = styleDefinition._ST; + if ( stylesDef ) + return stylesDef; + + stylesDef = styleDefinition.styles; + + // Builds the StyleText. + var stylesText = ( styleDefinition.attributes && styleDefinition.attributes[ 'style' ] ) || '', + specialStylesText = ''; + + if ( stylesText.length ) + stylesText = stylesText.replace( semicolonFixRegex, ';' ); + + for ( var style in stylesDef ) + { + var styleVal = stylesDef[ style ], + text = ( style + ':' + styleVal ).replace( semicolonFixRegex, ';' ); + + // Some browsers don't support 'inherit' property value, leave them intact. (#5242) + if ( styleVal == 'inherit' ) + specialStylesText += text; + else + stylesText += text; + } + + // Browsers make some changes to the style when applying them. So, here + // we normalize it to the browser format. + if ( stylesText.length ) + stylesText = normalizeCssText( stylesText ); + + stylesText += specialStylesText; + + // Return it, saving it to the next request. + return ( styleDefinition._ST = stylesText ); + }; + + function applyInlineStyle( range ) + { + var document = range.document; + + if ( range.collapsed ) + { + // Create the element to be inserted in the DOM. + var collapsedElement = getElement( this, document ); + + // Insert the empty element into the DOM at the range position. + range.insertNode( collapsedElement ); + + // Place the selection right inside the empty element. + range.moveToPosition( collapsedElement, CKEDITOR.POSITION_BEFORE_END ); + + return; + } + + var elementName = this.element; + var def = this._.definition; + var isUnknownElement; + + // Get the DTD definition for the element. Defaults to "span". + var dtd = CKEDITOR.dtd[ elementName ] || ( isUnknownElement = true, CKEDITOR.dtd.span ); + + // Bookmark the range so we can re-select it after processing. + var bookmark = range.createBookmark(); + + // Expand the range. + range.enlarge( CKEDITOR.ENLARGE_ELEMENT ); + range.trim(); + + // Get the first node to be processed and the last, which concludes the + // processing. + var boundaryNodes = range.createBookmark(), + firstNode = boundaryNodes.startNode, + lastNode = boundaryNodes.endNode; + + var currentNode = firstNode; + + var styleRange; + + while ( currentNode ) + { + var applyStyle = false; + + if ( currentNode.equals( lastNode ) ) + { + currentNode = null; + applyStyle = true; + } + else + { + var nodeType = currentNode.type; + var nodeName = nodeType == CKEDITOR.NODE_ELEMENT ? currentNode.getName() : null; + + if ( nodeName && currentNode.getAttribute( '_fck_bookmark' ) ) + { + currentNode = currentNode.getNextSourceNode( true ); + continue; + } + + // Check if the current node can be a child of the style element. + if ( !nodeName || ( dtd[ nodeName ] + && ( currentNode.getPosition( lastNode ) | CKEDITOR.POSITION_PRECEDING | CKEDITOR.POSITION_IDENTICAL | CKEDITOR.POSITION_IS_CONTAINED ) == ( CKEDITOR.POSITION_PRECEDING + CKEDITOR.POSITION_IDENTICAL + CKEDITOR.POSITION_IS_CONTAINED ) + && ( !def.childRule || def.childRule( currentNode ) ) ) ) + { + var currentParent = currentNode.getParent(); + + // Check if the style element can be a child of the current + // node parent or if the element is not defined in the DTD. + if ( currentParent + && ( ( currentParent.getDtd() || CKEDITOR.dtd.span )[ elementName ] || isUnknownElement ) + && ( !def.parentRule || def.parentRule( currentParent ) ) ) + { + // This node will be part of our range, so if it has not + // been started, place its start right before the node. + // In the case of an element node, it will be included + // only if it is entirely inside the range. + if ( !styleRange && ( !nodeName || !CKEDITOR.dtd.$removeEmpty[ nodeName ] || ( currentNode.getPosition( lastNode ) | CKEDITOR.POSITION_PRECEDING | CKEDITOR.POSITION_IDENTICAL | CKEDITOR.POSITION_IS_CONTAINED ) == ( CKEDITOR.POSITION_PRECEDING + CKEDITOR.POSITION_IDENTICAL + CKEDITOR.POSITION_IS_CONTAINED ) ) ) + { + styleRange = new CKEDITOR.dom.range( document ); + styleRange.setStartBefore( currentNode ); + } + + // Non element nodes, or empty elements can be added + // completely to the range. + if ( nodeType == CKEDITOR.NODE_TEXT || ( nodeType == CKEDITOR.NODE_ELEMENT && !currentNode.getChildCount() ) ) + { + var includedNode = currentNode; + var parentNode; + + // This node is about to be included completelly, but, + // if this is the last node in its parent, we must also + // check if the parent itself can be added completelly + // to the range. + while ( !includedNode.$.nextSibling + && ( parentNode = includedNode.getParent(), dtd[ parentNode.getName() ] ) + && ( parentNode.getPosition( firstNode ) | CKEDITOR.POSITION_FOLLOWING | CKEDITOR.POSITION_IDENTICAL | CKEDITOR.POSITION_IS_CONTAINED ) == ( CKEDITOR.POSITION_FOLLOWING + CKEDITOR.POSITION_IDENTICAL + CKEDITOR.POSITION_IS_CONTAINED ) + && ( !def.childRule || def.childRule( parentNode ) ) ) + { + includedNode = parentNode; + } + + styleRange.setEndAfter( includedNode ); + + // If the included node still is the last node in its + // parent, it means that the parent can't be included + // in this style DTD, so apply the style immediately. + if ( !includedNode.$.nextSibling ) + applyStyle = true; + + } + } + else + applyStyle = true; + } + else + applyStyle = true; + + // Get the next node to be processed. + currentNode = currentNode.getNextSourceNode(); + } + + // Apply the style if we have something to which apply it. + if ( applyStyle && styleRange && !styleRange.collapsed ) + { + // Build the style element, based on the style object definition. + var styleNode = getElement( this, document ); + + // Get the element that holds the entire range. + var parent = styleRange.getCommonAncestor(); + + // Loop through the parents, removing the redundant attributes + // from the element to be applied. + while ( styleNode && parent ) + { + if ( parent.getName() == elementName ) + { + for ( var attName in def.attributes ) + { + if ( styleNode.getAttribute( attName ) == parent.getAttribute( attName ) ) + styleNode.removeAttribute( attName ); + } + + for ( var styleName in def.styles ) + { + if ( styleNode.getStyle( styleName ) == parent.getStyle( styleName ) ) + styleNode.removeStyle( styleName ); + } + + if ( !styleNode.hasAttributes() ) + { + styleNode = null; + break; + } + } + + parent = parent.getParent(); + } + + if ( styleNode ) + { + // Move the contents of the range to the style element. + styleRange.extractContents().appendTo( styleNode ); + + // Here we do some cleanup, removing all duplicated + // elements from the style element. + removeFromInsideElement( this, styleNode ); + + // Insert it into the range position (it is collapsed after + // extractContents. + styleRange.insertNode( styleNode ); + + // Let's merge our new style with its neighbors, if possible. + styleNode.mergeSiblings(); + + // As the style system breaks text nodes constantly, let's normalize + // things for performance. + // With IE, some paragraphs get broken when calling normalize() + // repeatedly. Also, for IE, we must normalize body, not documentElement. + // IE is also known for having a "crash effect" with normalize(). + // We should try to normalize with IE too in some way, somewhere. + if ( !CKEDITOR.env.ie ) + styleNode.$.normalize(); + } + + // Style applied, let's release the range, so it gets + // re-initialization in the next loop. + styleRange = null; + } + } + + firstNode.remove(); + lastNode.remove(); + range.moveToBookmark( bookmark ); + // Minimize the result range to exclude empty text nodes. (#5374) + range.shrink( CKEDITOR.SHRINK_TEXT ); + } + + function removeInlineStyle( range ) + { + /* + * Make sure our range has included all "collpased" parent inline nodes so + * that our operation logic can be simpler. + */ + range.enlarge( CKEDITOR.ENLARGE_ELEMENT ); + + var bookmark = range.createBookmark(), + startNode = bookmark.startNode; + + if ( range.collapsed ) + { + + var startPath = new CKEDITOR.dom.elementPath( startNode.getParent() ), + // The topmost element in elementspatch which we should jump out of. + boundaryElement; + + + for ( var i = 0, element ; i < startPath.elements.length + && ( element = startPath.elements[i] ) ; i++ ) + { + /* + * 1. If it's collaped inside text nodes, try to remove the style from the whole element. + * + * 2. Otherwise if it's collapsed on element boundaries, moving the selection + * outside the styles instead of removing the whole tag, + * also make sure other inner styles were well preserverd.(#3309) + */ + if ( element == startPath.block || element == startPath.blockLimit ) + break; + + if ( this.checkElementRemovable( element ) ) + { + var endOfElement = range.checkBoundaryOfElement( element, CKEDITOR.END ), + startOfElement = !endOfElement && range.checkBoundaryOfElement( element, CKEDITOR.START ); + if ( startOfElement || endOfElement ) + { + boundaryElement = element; + boundaryElement.match = startOfElement ? 'start' : 'end'; + } + else + { + /* + * Before removing the style node, there may be a sibling to the style node + * that's exactly the same to the one to be removed. To the user, it makes + * no difference that they're separate entities in the DOM tree. So, merge + * them before removal. + */ + element.mergeSiblings(); + removeFromElement( this, element ); + + } + } + } + + // Re-create the style tree after/before the boundary element, + // the replication start from bookmark start node to define the + // new range. + if ( boundaryElement ) + { + var clonedElement = startNode; + for ( i = 0 ;; i++ ) + { + var newElement = startPath.elements[ i ]; + if ( newElement.equals( boundaryElement ) ) + break; + // Avoid copying any matched element. + else if ( newElement.match ) + continue; + else + newElement = newElement.clone(); + newElement.append( clonedElement ); + clonedElement = newElement; + } + clonedElement[ boundaryElement.match == 'start' ? + 'insertBefore' : 'insertAfter' ]( boundaryElement ); + } + } + else + { + /* + * Now our range isn't collapsed. Lets walk from the start node to the end + * node via DFS and remove the styles one-by-one. + */ + var endNode = bookmark.endNode, + me = this; + + /* + * Find out the style ancestor that needs to be broken down at startNode + * and endNode. + */ + function breakNodes() + { + var startPath = new CKEDITOR.dom.elementPath( startNode.getParent() ), + endPath = new CKEDITOR.dom.elementPath( endNode.getParent() ), + breakStart = null, + breakEnd = null; + for ( var i = 0 ; i < startPath.elements.length ; i++ ) + { + var element = startPath.elements[ i ]; + + if ( element == startPath.block || element == startPath.blockLimit ) + break; + + if ( me.checkElementRemovable( element ) ) + breakStart = element; + } + for ( i = 0 ; i < endPath.elements.length ; i++ ) + { + element = endPath.elements[ i ]; + + if ( element == endPath.block || element == endPath.blockLimit ) + break; + + if ( me.checkElementRemovable( element ) ) + breakEnd = element; + } + + if ( breakEnd ) + endNode.breakParent( breakEnd ); + if ( breakStart ) + startNode.breakParent( breakStart ); + } + breakNodes(); + + // Now, do the DFS walk. + var currentNode = startNode.getNext(); + while ( !currentNode.equals( endNode ) ) + { + /* + * Need to get the next node first because removeFromElement() can remove + * the current node from DOM tree. + */ + var nextNode = currentNode.getNextSourceNode(); + if ( currentNode.type == CKEDITOR.NODE_ELEMENT && this.checkElementRemovable( currentNode ) ) + { + // Remove style from element or overriding element. + if ( currentNode.getName() == this.element ) + removeFromElement( this, currentNode ); + else + removeOverrides( currentNode, getOverrides( this )[ currentNode.getName() ] ); + + /* + * removeFromElement() may have merged the next node with something before + * the startNode via mergeSiblings(). In that case, the nextNode would + * contain startNode and we'll have to call breakNodes() again and also + * reassign the nextNode to something after startNode. + */ + if ( nextNode.type == CKEDITOR.NODE_ELEMENT && nextNode.contains( startNode ) ) + { + breakNodes(); + nextNode = startNode.getNext(); + } + } + currentNode = nextNode; + } + } + + range.moveToBookmark( bookmark ); +} + + function applyObjectStyle( range ) + { + var root = range.getCommonAncestor( true, true ), + element = root.getAscendant( this.element, true ); + element && setupElement( element, this ); + } + + function applyBlockStyle( range ) + { + // Serializible bookmarks is needed here since + // elements may be merged. + var bookmark = range.createBookmark( true ); + + var iterator = range.createIterator(); + iterator.enforceRealBlocks = true; + + // make recognize <br /> tag as a separator in ENTER_BR mode (#5121) + if ( this._.enterMode ) + iterator.enlargeBr = ( this._.enterMode != CKEDITOR.ENTER_BR ); + + var block; + var doc = range.document; + var previousPreBlock; + + while ( ( block = iterator.getNextParagraph() ) ) // Only one = + { + var newBlock = getElement( this, doc ); + replaceBlock( block, newBlock ); + } + + range.moveToBookmark( bookmark ); + } + + // Replace the original block with new one, with special treatment + // for <pre> blocks to make sure content format is well preserved, and merging/splitting adjacent + // when necessary.(#3188) + function replaceBlock( block, newBlock ) + { + var newBlockIsPre = newBlock.is( 'pre' ); + var blockIsPre = block.is( 'pre' ); + + var isToPre = newBlockIsPre && !blockIsPre; + var isFromPre = !newBlockIsPre && blockIsPre; + + if ( isToPre ) + newBlock = toPre( block, newBlock ); + else if ( isFromPre ) + // Split big <pre> into pieces before start to convert. + newBlock = fromPres( splitIntoPres( block ), newBlock ); + else + block.moveChildren( newBlock ); + + newBlock.replace( block ); + + if ( newBlockIsPre ) + { + // Merge previous <pre> blocks. + mergePre( newBlock ); + } + } + + /** + * Merge a <pre> block with a previous sibling if available. + */ + function mergePre( preBlock ) + { + var previousBlock; + if ( !( ( previousBlock = preBlock.getPreviousSourceNode( true, CKEDITOR.NODE_ELEMENT ) ) + && previousBlock.is + && previousBlock.is( 'pre') ) ) + return; + + // Merge the previous <pre> block contents into the current <pre> + // block. + // + // Another thing to be careful here is that currentBlock might contain + // a '\n' at the beginning, and previousBlock might contain a '\n' + // towards the end. These new lines are not normally displayed but they + // become visible after merging. + var mergedHtml = replace( previousBlock.getHtml(), /\n$/, '' ) + '\n\n' + + replace( preBlock.getHtml(), /^\n/, '' ) ; + + // Krugle: IE normalizes innerHTML from <pre>, breaking whitespaces. + if ( CKEDITOR.env.ie ) + preBlock.$.outerHTML = '<pre>' + mergedHtml + '</pre>'; + else + preBlock.setHtml( mergedHtml ); + + previousBlock.remove(); + } + + /** + * Split into multiple <pre> blocks separated by double line-break. + * @param preBlock + */ + function splitIntoPres( preBlock ) + { + // Exclude the ones at header OR at tail, + // and ignore bookmark content between them. + var duoBrRegex = /(\S\s*)\n(?:\s|(<span[^>]+_fck_bookmark.*?\/span>))*\n(?!$)/gi, + blockName = preBlock.getName(), + splitedHtml = replace( preBlock.getOuterHtml(), + duoBrRegex, + function( match, charBefore, bookmark ) + { + return charBefore + '</pre>' + bookmark + '<pre>'; + } ); + + var pres = []; + splitedHtml.replace( /<pre\b.*?>([\s\S]*?)<\/pre>/gi, function( match, preContent ){ + pres.push( preContent ); + } ); + return pres; + } + + // Wrapper function of String::replace without considering of head/tail bookmarks nodes. + function replace( str, regexp, replacement ) + { + var headBookmark = '', + tailBookmark = ''; + + str = str.replace( /(^<span[^>]+_fck_bookmark.*?\/span>)|(<span[^>]+_fck_bookmark.*?\/span>$)/gi, + function( str, m1, m2 ){ + m1 && ( headBookmark = m1 ); + m2 && ( tailBookmark = m2 ); + return ''; + } ); + return headBookmark + str.replace( regexp, replacement ) + tailBookmark; + } + /** + * Converting a list of <pre> into blocks with format well preserved. + */ + function fromPres( preHtmls, newBlock ) + { + var docFrag = new CKEDITOR.dom.documentFragment( newBlock.getDocument() ); + for ( var i = 0 ; i < preHtmls.length ; i++ ) + { + var blockHtml = preHtmls[ i ]; + + // 1. Trim the first and last line-breaks immediately after and before <pre>, + // they're not visible. + blockHtml = blockHtml.replace( /(\r\n|\r)/g, '\n' ) ; + blockHtml = replace( blockHtml, /^[ \t]*\n/, '' ) ; + blockHtml = replace( blockHtml, /\n$/, '' ) ; + // 2. Convert spaces or tabs at the beginning or at the end to + blockHtml = replace( blockHtml, /^[ \t]+|[ \t]+$/g, function( match, offset, s ) + { + if ( match.length == 1 ) // one space, preserve it + return ' ' ; + else if ( !offset ) // beginning of block + return CKEDITOR.tools.repeat( ' ', match.length - 1 ) + ' '; + else // end of block + return ' ' + CKEDITOR.tools.repeat( ' ', match.length - 1 ); + } ) ; + + // 3. Convert \n to <BR>. + // 4. Convert contiguous (i.e. non-singular) spaces or tabs to + blockHtml = blockHtml.replace( /\n/g, '<br>' ) ; + blockHtml = blockHtml.replace( /[ \t]{2,}/g, + function ( match ) + { + return CKEDITOR.tools.repeat( ' ', match.length - 1 ) + ' ' ; + } ) ; + + var newBlockClone = newBlock.clone(); + newBlockClone.setHtml( blockHtml ); + docFrag.append( newBlockClone ); + } + return docFrag; + } + + /** + * Converting from a non-PRE block to a PRE block in formatting operations. + */ + function toPre( block, newBlock ) + { + // First trim the block content. + var preHtml = block.getHtml(); + + // 1. Trim head/tail spaces, they're not visible. + preHtml = replace( preHtml, /(?:^[ \t\n\r]+)|(?:[ \t\n\r]+$)/g, '' ); + // 2. Delete ANSI whitespaces immediately before and after <BR> because + // they are not visible. + preHtml = preHtml.replace( /[ \t\r\n]*(<br[^>]*>)[ \t\r\n]*/gi, '$1' ); + // 3. Compress other ANSI whitespaces since they're only visible as one + // single space previously. + // 4. Convert to spaces since is no longer needed in <PRE>. + preHtml = preHtml.replace( /([ \t\n\r]+| )/g, ' ' ); + // 5. Convert any <BR /> to \n. This must not be done earlier because + // the \n would then get compressed. + preHtml = preHtml.replace( /<br\b[^>]*>/gi, '\n' ); + + // Krugle: IE normalizes innerHTML to <pre>, breaking whitespaces. + if ( CKEDITOR.env.ie ) + { + var temp = block.getDocument().createElement( 'div' ); + temp.append( newBlock ); + newBlock.$.outerHTML = '<pre>' + preHtml + '</pre>'; + newBlock = temp.getFirst().remove(); + } + else + newBlock.setHtml( preHtml ); + + return newBlock; + } + + // Removes a style from an element itself, don't care about its subtree. + function removeFromElement( style, element ) + { + var def = style._.definition, + attributes = CKEDITOR.tools.extend( {}, def.attributes, getOverrides( style )[ element.getName() ] ), + styles = def.styles, + // If the style is only about the element itself, we have to remove the element. + removeEmpty = CKEDITOR.tools.isEmpty( attributes ) && CKEDITOR.tools.isEmpty( styles ); + + // Remove definition attributes/style from the elemnt. + for ( var attName in attributes ) + { + // The 'class' element value must match (#1318). + if ( ( attName == 'class' || style._.definition.fullMatch ) + && element.getAttribute( attName ) != normalizeProperty( attName, attributes[ attName ] ) ) + continue; + removeEmpty = element.hasAttribute( attName ); + element.removeAttribute( attName ); + } + + for ( var styleName in styles ) + { + // Full match style insist on having fully equivalence. (#5018) + if ( style._.definition.fullMatch + && element.getStyle( styleName ) != normalizeProperty( styleName, styles[ styleName ], true ) ) + continue; + + removeEmpty = removeEmpty || !!element.getStyle( styleName ); + element.removeStyle( styleName ); + } + + removeEmpty && removeNoAttribsElement( element ); + } + + // Removes a style from inside an element. + function removeFromInsideElement( style, element ) + { + var def = style._.definition, + attribs = def.attributes, + styles = def.styles, + overrides = getOverrides( style ); + + var innerElements = element.getElementsByTag( style.element ); + + for ( var i = innerElements.count(); --i >= 0 ; ) + removeFromElement( style, innerElements.getItem( i ) ); + + // Now remove any other element with different name that is + // defined to be overriden. + for ( var overrideElement in overrides ) + { + if ( overrideElement != style.element ) + { + innerElements = element.getElementsByTag( overrideElement ) ; + for ( i = innerElements.count() - 1 ; i >= 0 ; i-- ) + { + var innerElement = innerElements.getItem( i ); + removeOverrides( innerElement, overrides[ overrideElement ] ) ; + } + } + } + + } + + /** + * Remove overriding styles/attributes from the specific element. + * Note: Remove the element if no attributes remain. + * @param {Object} element + * @param {Object} overrides + */ + function removeOverrides( element, overrides ) + { + var attributes = overrides && overrides.attributes ; + + if ( attributes ) + { + for ( var i = 0 ; i < attributes.length ; i++ ) + { + var attName = attributes[i][0], actualAttrValue ; + + if ( ( actualAttrValue = element.getAttribute( attName ) ) ) + { + var attValue = attributes[i][1] ; + + // Remove the attribute if: + // - The override definition value is null ; + // - The override definition valie is a string that + // matches the attribute value exactly. + // - The override definition value is a regex that + // has matches in the attribute value. + if ( attValue === null || + ( attValue.test && attValue.test( actualAttrValue ) ) || + ( typeof attValue == 'string' && actualAttrValue == attValue ) ) + element.removeAttribute( attName ) ; + } + } + } + + removeNoAttribsElement( element ); + } + + // If the element has no more attributes, remove it. + function removeNoAttribsElement( element ) + { + // If no more attributes remained in the element, remove it, + // leaving its children. + if ( !element.hasAttributes() ) + { + // Removing elements may open points where merging is possible, + // so let's cache the first and last nodes for later checking. + var firstChild = element.getFirst(); + var lastChild = element.getLast(); + + element.remove( true ); + + if ( firstChild ) + { + // Check the cached nodes for merging. + firstChild.type == CKEDITOR.NODE_ELEMENT && firstChild.mergeSiblings(); + + if ( lastChild && !firstChild.equals( lastChild ) + && lastChild.type == CKEDITOR.NODE_ELEMENT ) + lastChild.mergeSiblings(); + } + } + } + + function getElement( style, targetDocument ) + { + var el; + + var def = style._.definition; + + var elementName = style.element; + + // The "*" element name will always be a span for this function. + if ( elementName == '*' ) + elementName = 'span'; + + // Create the element. + el = new CKEDITOR.dom.element( elementName, targetDocument ); + + return setupElement( el, style ); + } + + function setupElement( el, style ) + { + var def = style._.definition; + var attributes = def.attributes; + var styles = CKEDITOR.style.getStyleText( def ); + + // Assign all defined attributes. + if ( attributes ) + { + for ( var att in attributes ) + { + el.setAttribute( att, attributes[ att ] ); + } + } + + // Assign all defined styles. + if ( styles ) + el.setAttribute( 'style', styles ); + + return el; + } + + var varRegex = /#\((.+?)\)/g; + function replaceVariables( list, variablesValues ) + { + for ( var item in list ) + { + list[ item ] = list[ item ].replace( varRegex, function( match, varName ) + { + return variablesValues[ varName ]; + }); + } + } + + + // Returns an object that can be used for style matching comparison. + // Attributes names and values are all lowercased, and the styles get + // merged with the style attribute. + function getAttributesForComparison( styleDefinition ) + { + // If we have already computed it, just return it. + var attribs = styleDefinition._AC; + if ( attribs ) + return attribs; + + attribs = {}; + + var length = 0; + + // Loop through all defined attributes. + var styleAttribs = styleDefinition.attributes; + if ( styleAttribs ) + { + for ( var styleAtt in styleAttribs ) + { + length++; + attribs[ styleAtt ] = styleAttribs[ styleAtt ]; + } + } + + // Includes the style definitions. + var styleText = CKEDITOR.style.getStyleText( styleDefinition ); + if ( styleText ) + { + if ( !attribs[ 'style' ] ) + length++; + attribs[ 'style' ] = styleText; + } + + // Appends the "length" information to the object. + attribs._length = length; + + // Return it, saving it to the next request. + return ( styleDefinition._AC = attribs ); + } + + /** + * Get the the collection used to compare the elements and attributes, + * defined in this style overrides, with other element. All information in + * it is lowercased. + * @param {CKEDITOR.style} style + */ + function getOverrides( style ) + { + if ( style._.overrides ) + return style._.overrides; + + var overrides = ( style._.overrides = {} ), + definition = style._.definition.overrides; + + if ( definition ) + { + // The override description can be a string, object or array. + // Internally, well handle arrays only, so transform it if needed. + if ( !CKEDITOR.tools.isArray( definition ) ) + definition = [ definition ]; + + // Loop through all override definitions. + for ( var i = 0 ; i < definition.length ; i++ ) + { + var override = definition[i]; + var elementName; + var overrideEl; + var attrs; + + // If can be a string with the element name. + if ( typeof override == 'string' ) + elementName = override.toLowerCase(); + // Or an object. + else + { + elementName = override.element ? override.element.toLowerCase() : style.element; + attrs = override.attributes; + } + + // We can have more than one override definition for the same + // element name, so we attempt to simply append information to + // it if it already exists. + overrideEl = overrides[ elementName ] || ( overrides[ elementName ] = {} ); + + if ( attrs ) + { + // The returning attributes list is an array, because we + // could have different override definitions for the same + // attribute name. + var overrideAttrs = ( overrideEl.attributes = overrideEl.attributes || new Array() ); + for ( var attName in attrs ) + { + // Each item in the attributes array is also an array, + // where [0] is the attribute name and [1] is the + // override value. + overrideAttrs.push( [ attName.toLowerCase(), attrs[ attName ] ] ); + } + } + } + } + + return overrides; + } + + function normalizeProperty( name, value, isStyle ) + { + var temp = new CKEDITOR.dom.element( 'span' ); + temp [ isStyle ? 'setStyle' : 'setAttribute' ]( name, value ); + return temp[ isStyle ? 'getStyle' : 'getAttribute' ]( name ); + } + + function normalizeCssText( unparsedCssText, nativeNormalize ) + { + var styleText; + if ( nativeNormalize !== false ) + { + // Injects the style in a temporary span object, so the browser parses it, + // retrieving its final format. + var temp = new CKEDITOR.dom.element( 'span' ); + temp.setAttribute( 'style', unparsedCssText ); + styleText = temp.getAttribute( 'style' ) || ''; + } + else + styleText = unparsedCssText; + + // Shrinking white-spaces around colon and semi-colon (#4147). + // Compensate tail semi-colon. + return styleText.replace( /\s*([;:])\s*/, '$1' ) + .replace( /([^\s;])$/, '$1;') + .replace( /,\s+/g, ',' ) // Trimming spaces after comma (e.g. font-family name)(#4107). + .toLowerCase(); + } + + // Turn inline style text properties into one hash. + function parseStyleText( styleText ) + { + var retval = {}; + styleText + .replace( /"/g, '"' ) + .replace( /\s*([^ :;]+)\s*:\s*([^;]+)\s*(?=;|$)/g, function( match, name, value ) + { + retval[ name ] = value; + } ); + return retval; + } + + function compareCssText( source, target ) + { + typeof source == 'string' && ( source = parseStyleText( source ) ); + typeof target == 'string' && ( target = parseStyleText( target ) ); + for( var name in source ) + { + // Value 'inherit' is treated as a wildcard, + // which will match any value. + if ( !( name in target && + ( target[ name ] == source[ name ] + || source[ name ] == 'inherit' + || target[ name ] == 'inherit' ) ) ) + { + return false; + } + } + return true; + } + + function applyStyle( document, remove ) + { + // Get all ranges from the selection. + var selection = document.getSelection(); + var ranges = selection.getRanges(); + var func = remove ? this.removeFromRange : this.applyToRange; + + // Apply the style to the ranges. + for ( var i = 0 ; i < ranges.length ; i++ ) + func.call( this, ranges[ i ] ); + + // Select the ranges again. + selection.selectRanges( ranges ); + } +})(); + +CKEDITOR.styleCommand = function( style ) +{ + this.style = style; +}; + +CKEDITOR.styleCommand.prototype.exec = function( editor ) +{ + editor.focus(); + + var doc = editor.document; + + if ( doc ) + { + if ( this.state == CKEDITOR.TRISTATE_OFF ) + this.style.apply( doc ); + else if ( this.state == CKEDITOR.TRISTATE_ON ) + this.style.remove( doc ); + } + + return !!doc; +}; + +CKEDITOR.stylesSet = new CKEDITOR.resourceManager( '', 'stylesSet' ); + +// Backward compatibility (#5025). +CKEDITOR.addStylesSet = CKEDITOR.tools.bind( CKEDITOR.stylesSet.add, CKEDITOR.stylesSet ); +CKEDITOR.loadStylesSet = function( name, url, callback ) + { + CKEDITOR.stylesSet.addExternal( name, url, '' ); + CKEDITOR.stylesSet.load( name, callback ); + }; + + +/** + * Gets the current styleSet for this instance + * @param {Function} The function to be called with the styles data. + * @example + * editor.getStylesSet( function( stylesDefinitions ) {} ); + */ +CKEDITOR.editor.prototype.getStylesSet = function( callback ) +{ + if ( !this._.stylesDefinitions ) + { + var editor = this, + // Respect the backwards compatible definition entry + configStyleSet = editor.config.stylesCombo_stylesSet || editor.config.stylesSet || 'default'; + + // #5352 Allow to define the styles directly in the config object + if ( configStyleSet instanceof Array ) + { + editor._.stylesDefinitions = configStyleSet; + callback( configStyleSet ); + return; + } + + var partsStylesSet = configStyleSet.split( ':' ), + styleSetName = partsStylesSet[ 0 ], + externalPath = partsStylesSet[ 1 ], + pluginPath = CKEDITOR.plugins.registered.styles.path; + + CKEDITOR.stylesSet.addExternal( styleSetName, + externalPath ? + partsStylesSet.slice( 1 ).join( ':' ) : + pluginPath + 'styles/' + styleSetName + '.js', '' ); + + CKEDITOR.stylesSet.load( styleSetName, function( stylesSet ) + { + editor._.stylesDefinitions = stylesSet[ styleSetName ]; + callback( editor._.stylesDefinitions ); + } ) ; + } + else + callback( this._.stylesDefinitions ); +}; + +/** + * The "styles definition set" to use in the editor. They will be used in the + * styles combo and the Style selector of the div container. <br> + * The styles may be defined in the page containing the editor, or can be + * loaded on demand from an external file. In the second case, if this setting + * contains only a name, the styles definition file will be loaded from the + * "styles" folder inside the styles plugin folder. + * Otherwise, this setting has the "name:url" syntax, making it + * possible to set the URL from which loading the styles file.<br> + * Previously this setting was available as config.stylesCombo_stylesSet<br> + * @name CKEDITOR.config.stylesSet + * @type String|Array + * @default 'default' + * @since 3.3 + * @example + * // Load from the styles' styles folder (mystyles.js file). + * config.stylesSet = 'mystyles'; + * @example + * // Load from a relative URL. + * config.stylesSet = 'mystyles:/editorstyles/styles.js'; + * @example + * // Load from a full URL. + * config.stylesSet = 'mystyles:http://www.example.com/editorstyles/styles.js'; + * @example + * // Load from a list of definitions. + * config.stylesSet = [ + * { name : 'Strong Emphasis', element : 'strong' }, + * { name : 'Emphasis', element : 'em' }, ... ]; + */ diff --git a/_source/plugins/styles/styles/default.js b/_source/plugins/styles/styles/default.js new file mode 100644 index 0000000..1e8ca6d --- /dev/null +++ b/_source/plugins/styles/styles/default.js @@ -0,0 +1,88 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.stylesSet.add( 'default', +[ + /* Block Styles */ + + // These styles are already available in the "Format" combo, so they are + // not needed here by default. You may enable them to avoid placing the + // "Format" combo in the toolbar, maintaining the same features. + /* + { name : 'Paragraph' , element : 'p' }, + { name : 'Heading 1' , element : 'h1' }, + { name : 'Heading 2' , element : 'h2' }, + { name : 'Heading 3' , element : 'h3' }, + { name : 'Heading 4' , element : 'h4' }, + { name : 'Heading 5' , element : 'h5' }, + { name : 'Heading 6' , element : 'h6' }, + { name : 'Preformatted Text', element : 'pre' }, + { name : 'Address' , element : 'address' }, + */ + + { name : 'Blue Title' , element : 'h3', styles : { 'color' : 'Blue' } }, + { name : 'Red Title' , element : 'h3', styles : { 'color' : 'Red' } }, + + /* Inline Styles */ + + // These are core styles available as toolbar buttons. You may opt enabling + // some of them in the Styles combo, removing them from the toolbar. + /* + { name : 'Strong' , element : 'strong', overrides : 'b' }, + { name : 'Emphasis' , element : 'em' , overrides : 'i' }, + { name : 'Underline' , element : 'u' }, + { name : 'Strikethrough' , element : 'strike' }, + { name : 'Subscript' , element : 'sub' }, + { name : 'Superscript' , element : 'sup' }, + */ + + { name : 'Marker: Yellow' , element : 'span', styles : { 'background-color' : 'Yellow' } }, + { name : 'Marker: Green' , element : 'span', styles : { 'background-color' : 'Lime' } }, + + { name : 'Big' , element : 'big' }, + { name : 'Small' , element : 'small' }, + { name : 'Typewriter' , element : 'tt' }, + + { name : 'Computer Code' , element : 'code' }, + { name : 'Keyboard Phrase' , element : 'kbd' }, + { name : 'Sample Text' , element : 'samp' }, + { name : 'Variable' , element : 'var' }, + + { name : 'Deleted Text' , element : 'del' }, + { name : 'Inserted Text' , element : 'ins' }, + + { name : 'Cited Work' , element : 'cite' }, + { name : 'Inline Quotation' , element : 'q' }, + + { name : 'Language: RTL' , element : 'span', attributes : { 'dir' : 'rtl' } }, + { name : 'Language: LTR' , element : 'span', attributes : { 'dir' : 'ltr' } }, + + /* Object Styles */ + + { + name : 'Image on Left', + element : 'img', + attributes : + { + 'style' : 'padding: 5px; margin-right: 5px', + 'border' : '2', + 'align' : 'left' + } + }, + + { + name : 'Image on Right', + element : 'img', + attributes : + { + 'style' : 'padding: 5px; margin-left: 5px', + 'border' : '2', + 'align' : 'right' + } + }, + + { name : 'Borderless Table', element : 'table', styles: { 'border-style': 'hidden', 'background-color' : '#E6E6FA' } }, + { name : 'Square Bulleted List', element : 'ul', styles : { 'list-style-type' : 'square' } } +]); diff --git a/_source/plugins/stylescombo/plugin.js b/_source/plugins/stylescombo/plugin.js index 2b4716a..b4d9f49 100644 --- a/_source/plugins/stylescombo/plugin.js +++ b/_source/plugins/stylescombo/plugin.js @@ -1,282 +1,205 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-(function()
-{
- var stylesManager;
-
- CKEDITOR.plugins.add( 'stylescombo',
- {
- requires : [ 'richcombo', 'styles' ],
-
- init : function( editor )
- {
- var config = editor.config,
- lang = editor.lang.stylesCombo,
- pluginPath = this.path,
- styles;
-
- if ( !stylesManager )
- stylesManager = CKEDITOR.stylesSet;
-
- var comboStylesSet = config.stylesCombo_stylesSet.split( ':' ),
- styleSetName = comboStylesSet[ 0 ],
- externalPath = comboStylesSet[ 1 ];
-
- stylesManager.addExternal( styleSetName,
- externalPath ?
- comboStylesSet.slice( 1 ).join( ':' ) :
- pluginPath + 'styles/' + styleSetName + '.js', '' );
-
- editor.ui.addRichCombo( 'Styles',
- {
- label : lang.label,
- title : lang.panelTitle,
- className : 'cke_styles',
-
- panel :
- {
- css : editor.skin.editor.css.concat( config.contentsCss ),
- multiSelect : true,
- attributes : { 'aria-label' : lang.panelTitle }
- },
-
- init : function()
- {
- var combo = this;
-
- CKEDITOR.stylesSet.load( styleSetName, function( stylesSet )
- {
- var stylesDefinitions = stylesSet[ styleSetName ],
- style,
- styleName,
- stylesList = [];
-
- styles = {};
-
- // Put all styles into an Array.
- for ( var i = 0 ; i < stylesDefinitions.length ; i++ )
- {
- var styleDefinition = stylesDefinitions[ i ];
-
- styleName = styleDefinition.name;
-
- style = styles[ styleName ] = new CKEDITOR.style( styleDefinition );
- style._name = styleName;
-
- stylesList.push( style );
- }
-
- // Sorts the Array, so the styles get grouped
- // by type.
- stylesList.sort( sortStyles );
-
- // Loop over the Array, adding all items to the
- // combo.
- var lastType;
- for ( i = 0 ; i < stylesList.length ; i++ )
- {
- style = stylesList[ i ];
- styleName = style._name;
-
- var type = style.type;
-
- if ( type != lastType )
- {
- combo.startGroup( lang[ 'panelTitle' + String( type ) ] );
- lastType = type;
- }
-
- combo.add(
- styleName,
- style.type == CKEDITOR.STYLE_OBJECT ? styleName : buildPreview( style._.definition ),
- styleName );
- }
-
- combo.commit();
-
- combo.onOpen();
- });
- },
-
- onClick : function( value )
- {
- editor.focus();
- editor.fire( 'saveSnapshot' );
-
- var style = styles[ value ],
- selection = editor.getSelection();
-
- if ( style.type == CKEDITOR.STYLE_OBJECT )
- {
- var element = selection.getSelectedElement();
- if ( element )
- style.applyToObject( element );
-
- return;
- }
-
- var elementPath = new CKEDITOR.dom.elementPath( selection.getStartElement() );
-
- if ( style.type == CKEDITOR.STYLE_INLINE && style.checkActive( elementPath ) )
- style.remove( editor.document );
- else
- style.apply( editor.document );
-
- editor.fire( 'saveSnapshot' );
- },
-
- onRender : function()
- {
- editor.on( 'selectionChange', function( ev )
- {
- var currentValue = this.getValue();
-
- var elementPath = ev.data.path,
- elements = elementPath.elements;
-
- // For each element into the elements path.
- for ( var i = 0, element ; i < elements.length ; i++ )
- {
- element = elements[i];
-
- // Check if the element is removable by any of
- // the styles.
- for ( var value in styles )
- {
- if ( styles[ value ].checkElementRemovable( element, true ) )
- {
- if ( value != currentValue )
- this.setValue( value );
- return;
- }
- }
- }
-
- // If no styles match, just empty it.
- this.setValue( '' );
- },
- this);
- },
-
- onOpen : function()
- {
- if ( CKEDITOR.env.ie )
- editor.focus();
-
- var selection = editor.getSelection();
-
- var element = selection.getSelectedElement(),
- elementName = element && element.getName(),
- elementPath = new CKEDITOR.dom.elementPath( element || selection.getStartElement() );
-
- var counter = [ 0, 0, 0, 0 ];
- this.showAll();
- this.unmarkAll();
- for ( var name in styles )
- {
- var style = styles[ name ],
- type = style.type;
-
- if ( type == CKEDITOR.STYLE_OBJECT )
- {
- if ( element && style.element == elementName )
- {
- if ( style.checkElementRemovable( element, true ) )
- this.mark( name );
-
- counter[ type ]++;
- }
- else
- this.hideItem( name );
- }
- else
- {
- if ( style.checkActive( elementPath ) )
- this.mark( name );
-
- counter[ type ]++;
- }
- }
-
- if ( !counter[ CKEDITOR.STYLE_BLOCK ] )
- this.hideGroup( lang[ 'panelTitle' + String( CKEDITOR.STYLE_BLOCK ) ] );
-
- if ( !counter[ CKEDITOR.STYLE_INLINE ] )
- this.hideGroup( lang[ 'panelTitle' + String( CKEDITOR.STYLE_INLINE ) ] );
-
- if ( !counter[ CKEDITOR.STYLE_OBJECT ] )
- this.hideGroup( lang[ 'panelTitle' + String( CKEDITOR.STYLE_OBJECT ) ] );
- }
- });
- }
- });
-
- function buildPreview( styleDefinition )
- {
- var html = [];
-
- var elementName = styleDefinition.element;
-
- // Avoid <bdo> in the preview.
- if ( elementName == 'bdo' )
- elementName = 'span';
-
- html = [ '<', elementName ];
-
- // Assign all defined attributes.
- var attribs = styleDefinition.attributes;
- if ( attribs )
- {
- for ( var att in attribs )
- {
- html.push( ' ', att, '="', attribs[ att ], '"' );
- }
- }
-
- // Assign the style attribute.
- var cssStyle = CKEDITOR.style.getStyleText( styleDefinition );
- if ( cssStyle )
- html.push( ' style="', cssStyle, '"' );
-
- html.push( '>', styleDefinition.name, '</', elementName, '>' );
-
- return html.join( '' );
- }
-
- function sortStyles( styleA, styleB )
- {
- var typeA = styleA.type,
- typeB = styleB.type;
-
- return typeA == typeB ? 0 :
- typeA == CKEDITOR.STYLE_OBJECT ? -1 :
- typeB == CKEDITOR.STYLE_OBJECT ? 1 :
- typeB == CKEDITOR.STYLE_BLOCK ? 1 :
- -1;
- }
-})();
-
-/**
- * The "styles definition set" to load into the styles combo. The styles may
- * be defined in the page containing the editor, or can be loaded on demand
- * from an external file when opening the styles combo for the fist time. In
- * the second case, if this setting contains only a name, the styles definition
- * file will be loaded from the "styles" folder inside the stylescombo plugin
- * folder. Otherwise, this setting has the "name:url" syntax, making it
- * possible to set the URL from which loading the styles file.
- * @type string
- * @default 'default'
- * @example
- * // Load from the stylescombo styles folder (mystyles.js file).
- * config.stylesCombo_stylesSet = 'mystyles';
- * @example
- * // Load from a relative URL.
- * config.stylesCombo_stylesSet = 'mystyles:/editorstyles/styles.js';
- * @example
- * // Load from a full URL.
- * config.stylesCombo_stylesSet = 'mystyles:http://www.example.com/editorstyles/styles.js';
- */
-CKEDITOR.config.stylesCombo_stylesSet = 'default';
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +(function() +{ + CKEDITOR.plugins.add( 'stylescombo', + { + requires : [ 'richcombo', 'styles' ], + + init : function( editor ) + { + var config = editor.config, + lang = editor.lang.stylesCombo, + styles = {}, + stylesList = []; + + function loadStylesSet( callback ) + { + editor.getStylesSet( function( stylesDefinitions ) + { + if ( !stylesList.length ) + { + var style, + styleName; + + // Put all styles into an Array. + for ( var i = 0 ; i < stylesDefinitions.length ; i++ ) + { + var styleDefinition = stylesDefinitions[ i ]; + + styleName = styleDefinition.name; + + style = styles[ styleName ] = new CKEDITOR.style( styleDefinition ); + style._name = styleName; + style._.enterMode = config.enterMode; + + stylesList.push( style ); + } + + // Sorts the Array, so the styles get grouped by type. + stylesList.sort( sortStyles ); + } + + callback && callback(); + }); + } + + editor.ui.addRichCombo( 'Styles', + { + label : lang.label, + title : lang.panelTitle, + className : 'cke_styles', + + panel : + { + css : editor.skin.editor.css.concat( config.contentsCss ), + multiSelect : true, + attributes : { 'aria-label' : lang.panelTitle } + }, + + init : function() + { + var combo = this; + + loadStylesSet( function() + { + var style, styleName; + + // Loop over the Array, adding all items to the + // combo. + var lastType; + for ( var i = 0 ; i < stylesList.length ; i++ ) + { + style = stylesList[ i ]; + styleName = style._name; + + var type = style.type; + + if ( type != lastType ) + { + combo.startGroup( lang[ 'panelTitle' + String( type ) ] ); + lastType = type; + } + + combo.add( + styleName, + style.type == CKEDITOR.STYLE_OBJECT ? styleName : style.buildPreview(), + styleName ); + } + + combo.commit(); + + combo.onOpen(); + }); + }, + + onClick : function( value ) + { + editor.focus(); + editor.fire( 'saveSnapshot' ); + + var style = styles[ value ], + selection = editor.getSelection(); + + var elementPath = new CKEDITOR.dom.elementPath( selection.getStartElement() ); + + if ( style.type == CKEDITOR.STYLE_INLINE && style.checkActive( elementPath ) ) + style.remove( editor.document ); + else + style.apply( editor.document ); + + editor.fire( 'saveSnapshot' ); + }, + + onRender : function() + { + editor.on( 'selectionChange', function( ev ) + { + var currentValue = this.getValue(); + + var elementPath = ev.data.path, + elements = elementPath.elements; + + // For each element into the elements path. + for ( var i = 0, element ; i < elements.length ; i++ ) + { + element = elements[i]; + + // Check if the element is removable by any of + // the styles. + for ( var value in styles ) + { + if ( styles[ value ].checkElementRemovable( element, true ) ) + { + if ( value != currentValue ) + this.setValue( value ); + return; + } + } + } + + // If no styles match, just empty it. + this.setValue( '' ); + }, + this); + }, + + onOpen : function() + { + if ( CKEDITOR.env.ie || CKEDITOR.env.webkit ) + editor.focus(); + + var selection = editor.getSelection(); + + var element = selection.getSelectedElement(), + elementPath = new CKEDITOR.dom.elementPath( element || selection.getStartElement() ); + + var counter = [ 0, 0, 0, 0 ]; + this.showAll(); + this.unmarkAll(); + for ( var name in styles ) + { + var style = styles[ name ], + type = style.type; + + if ( style.checkActive( elementPath ) ) + this.mark( name ); + else if ( type == CKEDITOR.STYLE_OBJECT && !style.checkApplicable( elementPath ) ) + { + this.hideItem( name ); + counter[ type ]--; + } + + counter[ type ]++; + } + + if ( !counter[ CKEDITOR.STYLE_BLOCK ] ) + this.hideGroup( lang[ 'panelTitle' + String( CKEDITOR.STYLE_BLOCK ) ] ); + + if ( !counter[ CKEDITOR.STYLE_INLINE ] ) + this.hideGroup( lang[ 'panelTitle' + String( CKEDITOR.STYLE_INLINE ) ] ); + + if ( !counter[ CKEDITOR.STYLE_OBJECT ] ) + this.hideGroup( lang[ 'panelTitle' + String( CKEDITOR.STYLE_OBJECT ) ] ); + } + }); + + editor.on( 'instanceReady', function() { loadStylesSet(); } ); + } + }); + + function sortStyles( styleA, styleB ) + { + var typeA = styleA.type, + typeB = styleB.type; + + return typeA == typeB ? 0 : + typeA == CKEDITOR.STYLE_OBJECT ? -1 : + typeB == CKEDITOR.STYLE_OBJECT ? 1 : + typeB == CKEDITOR.STYLE_BLOCK ? 1 : + -1; + } +})(); diff --git a/_source/plugins/tab/plugin.js b/_source/plugins/tab/plugin.js index c2646fe..5c3f734 100644 --- a/_source/plugins/tab/plugin.js +++ b/_source/plugins/tab/plugin.js @@ -1,261 +1,261 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-(function()
-{
- var meta =
- {
- editorFocus : false,
- modes : { wysiwyg:1, source:1 }
- };
-
- var blurCommand =
- {
- exec : function( editor )
- {
- editor.container.focusNext( true, editor.tabIndex );
- }
- };
-
- var blurBackCommand =
- {
- exec : function( editor )
- {
- editor.container.focusPrevious( true, editor.tabIndex );
- }
- };
-
- CKEDITOR.plugins.add( 'tab',
- {
- requires : [ 'keystrokes' ],
-
- init : function( editor )
- {
- var tabSpaces = editor.config.tabSpaces || 0,
- tabText = '';
-
- while ( tabSpaces-- )
- tabText += '\xa0';
-
- if ( tabText )
- {
- editor.on( 'key', function( ev )
- {
- if ( ev.data.keyCode == 9 ) // TAB
- {
- editor.insertHtml( tabText );
- ev.cancel();
- }
- });
- }
-
- if ( CKEDITOR.env.webkit )
- {
- editor.on( 'key', function( ev )
- {
- var keyCode = ev.data.keyCode;
-
- if ( keyCode == 9 && !tabText ) // TAB
- {
- ev.cancel();
- editor.execCommand( 'blur' );
- }
-
- if ( keyCode == ( CKEDITOR.SHIFT + 9 ) ) // SHIFT+TAB
- {
- editor.execCommand( 'blurBack' );
- ev.cancel();
- }
- });
- }
-
- editor.addCommand( 'blur', CKEDITOR.tools.extend( blurCommand, meta ) );
- editor.addCommand( 'blurBack', CKEDITOR.tools.extend( blurBackCommand, meta ) );
- }
- });
-})();
-
-/**
- * Moves the UI focus to the element following this element in the tabindex
- * order.
- * @example
- * var element = CKEDITOR.document.getById( 'example' );
- * element.focusNext();
- */
-CKEDITOR.dom.element.prototype.focusNext = function( ignoreChildren, indexToUse )
-{
- var $ = this.$,
- curTabIndex = ( indexToUse === undefined ? this.getTabIndex() : indexToUse ),
- passedCurrent, enteredCurrent,
- elected, electedTabIndex,
- element, elementTabIndex;
-
- if ( curTabIndex <= 0 )
- {
- // If this element has tabindex <= 0 then we must simply look for any
- // element following it containing tabindex=0.
-
- element = this.getNextSourceNode( ignoreChildren, CKEDITOR.NODE_ELEMENT );
-
- while ( element )
- {
- if ( element.isVisible() && element.getTabIndex() === 0 )
- {
- elected = element;
- break;
- }
-
- element = element.getNextSourceNode( false, CKEDITOR.NODE_ELEMENT );
- }
- }
- else
- {
- // If this element has tabindex > 0 then we must look for:
- // 1. An element following this element with the same tabindex.
- // 2. The first element in source other with the lowest tabindex
- // that is higher than this element tabindex.
- // 3. The first element with tabindex=0.
-
- element = this.getDocument().getBody().getFirst();
-
- while ( ( element = element.getNextSourceNode( false, CKEDITOR.NODE_ELEMENT ) ) )
- {
- if ( !passedCurrent )
- {
- if ( !enteredCurrent && element.equals( this ) )
- {
- enteredCurrent = true;
-
- // Ignore this element, if required.
- if ( ignoreChildren )
- {
- if ( !( element = element.getNextSourceNode( true, CKEDITOR.NODE_ELEMENT ) ) )
- break;
- passedCurrent = 1;
- }
- }
- else if ( enteredCurrent && !this.contains( element ) )
- passedCurrent = 1;
- }
-
- if ( !element.isVisible() || ( elementTabIndex = element.getTabIndex() ) < 0 )
- continue;
-
- if ( passedCurrent && elementTabIndex == curTabIndex )
- {
- elected = element;
- break;
- }
-
- if ( elementTabIndex > curTabIndex && ( !elected || !electedTabIndex || elementTabIndex < electedTabIndex ) )
- {
- elected = element;
- electedTabIndex = elementTabIndex;
- }
- else if ( !elected && elementTabIndex === 0 )
- {
- elected = element;
- electedTabIndex = elementTabIndex;
- }
- }
- }
-
- if ( elected )
- elected.focus();
-};
-
-/**
- * Moves the UI focus to the element before this element in the tabindex order.
- * @example
- * var element = CKEDITOR.document.getById( 'example' );
- * element.focusPrevious();
- */
-CKEDITOR.dom.element.prototype.focusPrevious = function( ignoreChildren, indexToUse )
-{
- var $ = this.$,
- curTabIndex = ( indexToUse === undefined ? this.getTabIndex() : indexToUse ),
- passedCurrent, enteredCurrent,
- elected,
- electedTabIndex = 0,
- elementTabIndex;
-
- var element = this.getDocument().getBody().getLast();
-
- while ( ( element = element.getPreviousSourceNode( false, CKEDITOR.NODE_ELEMENT ) ) )
- {
- if ( !passedCurrent )
- {
- if ( !enteredCurrent && element.equals( this ) )
- {
- enteredCurrent = true;
-
- // Ignore this element, if required.
- if ( ignoreChildren )
- {
- if ( !( element = element.getPreviousSourceNode( true, CKEDITOR.NODE_ELEMENT ) ) )
- break;
- passedCurrent = 1;
- }
- }
- else if ( enteredCurrent && !this.contains( element ) )
- passedCurrent = 1;
- }
-
- if ( !element.isVisible() || ( elementTabIndex = element.getTabIndex() ) < 0 )
- continue;
-
- if ( curTabIndex <= 0 )
- {
- // If this element has tabindex <= 0 then we must look for:
- // 1. An element before this one containing tabindex=0.
- // 2. The last element with the highest tabindex.
-
- if ( passedCurrent && elementTabIndex === 0 )
- {
- elected = element;
- break;
- }
-
- if ( elementTabIndex > electedTabIndex )
- {
- elected = element;
- electedTabIndex = elementTabIndex;
- }
- }
- else
- {
- // If this element has tabindex > 0 we must look for:
- // 1. An element preceeding this one, with the same tabindex.
- // 2. The last element in source other with the highest tabindex
- // that is lower than this element tabindex.
-
- if ( passedCurrent && elementTabIndex == curTabIndex )
- {
- elected = element;
- break;
- }
-
- if ( elementTabIndex < curTabIndex && ( !elected || elementTabIndex > electedTabIndex ) )
- {
- elected = element;
- electedTabIndex = elementTabIndex;
- }
- }
- }
-
- if ( elected )
- elected.focus();
-};
-
-/**
- * Intructs the editor to add a number of spaces (&nbsp;) to the text when
- * hitting the TAB key. If set to zero, the TAB key will be used to move the
- * cursor focus to the next element in the page, out of the editor focus.
- * @name CKEDITOR.config.tabSpaces
- * @type Number
- * @default 0
- * @example
- * config.tabSpaces = 4;
- */
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +(function() +{ + var meta = + { + editorFocus : false, + modes : { wysiwyg:1, source:1 } + }; + + var blurCommand = + { + exec : function( editor ) + { + editor.container.focusNext( true, editor.tabIndex ); + } + }; + + var blurBackCommand = + { + exec : function( editor ) + { + editor.container.focusPrevious( true, editor.tabIndex ); + } + }; + + CKEDITOR.plugins.add( 'tab', + { + requires : [ 'keystrokes' ], + + init : function( editor ) + { + var tabSpaces = editor.config.tabSpaces || 0, + tabText = ''; + + while ( tabSpaces-- ) + tabText += '\xa0'; + + if ( tabText ) + { + editor.on( 'key', function( ev ) + { + if ( ev.data.keyCode == 9 ) // TAB + { + editor.insertHtml( tabText ); + ev.cancel(); + } + }); + } + + if ( CKEDITOR.env.webkit || CKEDITOR.env.gecko ) + { + editor.on( 'key', function( ev ) + { + var keyCode = ev.data.keyCode; + + if ( keyCode == 9 && !tabText ) // TAB + { + ev.cancel(); + editor.execCommand( 'blur' ); + } + + if ( keyCode == ( CKEDITOR.SHIFT + 9 ) ) // SHIFT+TAB + { + editor.execCommand( 'blurBack' ); + ev.cancel(); + } + }); + } + + editor.addCommand( 'blur', CKEDITOR.tools.extend( blurCommand, meta ) ); + editor.addCommand( 'blurBack', CKEDITOR.tools.extend( blurBackCommand, meta ) ); + } + }); +})(); + +/** + * Moves the UI focus to the element following this element in the tabindex + * order. + * @example + * var element = CKEDITOR.document.getById( 'example' ); + * element.focusNext(); + */ +CKEDITOR.dom.element.prototype.focusNext = function( ignoreChildren, indexToUse ) +{ + var $ = this.$, + curTabIndex = ( indexToUse === undefined ? this.getTabIndex() : indexToUse ), + passedCurrent, enteredCurrent, + elected, electedTabIndex, + element, elementTabIndex; + + if ( curTabIndex <= 0 ) + { + // If this element has tabindex <= 0 then we must simply look for any + // element following it containing tabindex=0. + + element = this.getNextSourceNode( ignoreChildren, CKEDITOR.NODE_ELEMENT ); + + while ( element ) + { + if ( element.isVisible() && element.getTabIndex() === 0 ) + { + elected = element; + break; + } + + element = element.getNextSourceNode( false, CKEDITOR.NODE_ELEMENT ); + } + } + else + { + // If this element has tabindex > 0 then we must look for: + // 1. An element following this element with the same tabindex. + // 2. The first element in source other with the lowest tabindex + // that is higher than this element tabindex. + // 3. The first element with tabindex=0. + + element = this.getDocument().getBody().getFirst(); + + while ( ( element = element.getNextSourceNode( false, CKEDITOR.NODE_ELEMENT ) ) ) + { + if ( !passedCurrent ) + { + if ( !enteredCurrent && element.equals( this ) ) + { + enteredCurrent = true; + + // Ignore this element, if required. + if ( ignoreChildren ) + { + if ( !( element = element.getNextSourceNode( true, CKEDITOR.NODE_ELEMENT ) ) ) + break; + passedCurrent = 1; + } + } + else if ( enteredCurrent && !this.contains( element ) ) + passedCurrent = 1; + } + + if ( !element.isVisible() || ( elementTabIndex = element.getTabIndex() ) < 0 ) + continue; + + if ( passedCurrent && elementTabIndex == curTabIndex ) + { + elected = element; + break; + } + + if ( elementTabIndex > curTabIndex && ( !elected || !electedTabIndex || elementTabIndex < electedTabIndex ) ) + { + elected = element; + electedTabIndex = elementTabIndex; + } + else if ( !elected && elementTabIndex === 0 ) + { + elected = element; + electedTabIndex = elementTabIndex; + } + } + } + + if ( elected ) + elected.focus(); +}; + +/** + * Moves the UI focus to the element before this element in the tabindex order. + * @example + * var element = CKEDITOR.document.getById( 'example' ); + * element.focusPrevious(); + */ +CKEDITOR.dom.element.prototype.focusPrevious = function( ignoreChildren, indexToUse ) +{ + var $ = this.$, + curTabIndex = ( indexToUse === undefined ? this.getTabIndex() : indexToUse ), + passedCurrent, enteredCurrent, + elected, + electedTabIndex = 0, + elementTabIndex; + + var element = this.getDocument().getBody().getLast(); + + while ( ( element = element.getPreviousSourceNode( false, CKEDITOR.NODE_ELEMENT ) ) ) + { + if ( !passedCurrent ) + { + if ( !enteredCurrent && element.equals( this ) ) + { + enteredCurrent = true; + + // Ignore this element, if required. + if ( ignoreChildren ) + { + if ( !( element = element.getPreviousSourceNode( true, CKEDITOR.NODE_ELEMENT ) ) ) + break; + passedCurrent = 1; + } + } + else if ( enteredCurrent && !this.contains( element ) ) + passedCurrent = 1; + } + + if ( !element.isVisible() || ( elementTabIndex = element.getTabIndex() ) < 0 ) + continue; + + if ( curTabIndex <= 0 ) + { + // If this element has tabindex <= 0 then we must look for: + // 1. An element before this one containing tabindex=0. + // 2. The last element with the highest tabindex. + + if ( passedCurrent && elementTabIndex === 0 ) + { + elected = element; + break; + } + + if ( elementTabIndex > electedTabIndex ) + { + elected = element; + electedTabIndex = elementTabIndex; + } + } + else + { + // If this element has tabindex > 0 we must look for: + // 1. An element preceeding this one, with the same tabindex. + // 2. The last element in source other with the highest tabindex + // that is lower than this element tabindex. + + if ( passedCurrent && elementTabIndex == curTabIndex ) + { + elected = element; + break; + } + + if ( elementTabIndex < curTabIndex && ( !elected || elementTabIndex > electedTabIndex ) ) + { + elected = element; + electedTabIndex = elementTabIndex; + } + } + } + + if ( elected ) + elected.focus(); +}; + +/** + * Intructs the editor to add a number of spaces (&nbsp;) to the text when + * hitting the TAB key. If set to zero, the TAB key will be used to move the + * cursor focus to the next element in the page, out of the editor focus. + * @name CKEDITOR.config.tabSpaces + * @type Number + * @default 0 + * @example + * config.tabSpaces = 4; + */ diff --git a/_source/plugins/table/dialogs/table.js b/_source/plugins/table/dialogs/table.js index 87e55df..31b375a 100644 --- a/_source/plugins/table/dialogs/table.js +++ b/_source/plugins/table/dialogs/table.js @@ -1,589 +1,596 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-(function()
-{
- var widthPattern = /^(\d+(?:\.\d+)?)(px|%)$/,
- heightPattern = /^(\d+(?:\.\d+)?)px$/;
-
- var commitValue = function( data )
- {
- var id = this.id;
- if ( !data.info )
- data.info = {};
- data.info[id] = this.getValue();
- };
-
- function tableDialog( editor, command )
- {
- var makeElement = function( name ){ return new CKEDITOR.dom.element( name, editor.document ); };
-
- return {
- title : editor.lang.table.title,
- minWidth : 310,
- minHeight : CKEDITOR.env.ie ? 310 : 280,
- onShow : function()
- {
- // Detect if there's a selected table.
- var selection = editor.getSelection(),
- ranges = selection.getRanges(),
- selectedTable = null;
-
- var rowsInput = this.getContentElement( 'info', 'txtRows' ),
- colsInput = this.getContentElement( 'info', 'txtCols' ),
- widthInput = this.getContentElement( 'info', 'txtWidth' );
- if ( command == 'tableProperties' )
- {
- if ( ( selectedTable = editor.getSelection().getSelectedElement() ) )
- {
- if ( selectedTable.getName() != 'table' )
- selectedTable = null;
- }
- else if ( ranges.length > 0 )
- {
- var rangeRoot = ranges[0].getCommonAncestor( true );
- selectedTable = rangeRoot.getAscendant( 'table', true );
- }
-
- // Save a reference to the selected table, and push a new set of default values.
- this._.selectedElement = selectedTable;
- }
-
- // Enable, disable and select the row, cols, width fields.
- if ( selectedTable )
- {
- this.setupContent( selectedTable );
- rowsInput && rowsInput.disable();
- colsInput && colsInput.disable();
- widthInput && widthInput.select();
- }
- else
- {
- rowsInput && rowsInput.enable();
- colsInput && colsInput.enable();
- rowsInput && rowsInput.select();
- }
- },
- onOk : function()
- {
- if ( this._.selectedElement )
- {
- var selection = editor.getSelection(),
- bms = editor.getSelection().createBookmarks();
- }
-
- var table = this._.selectedElement || makeElement( 'table' ),
- me = this,
- data = {};
-
- this.commitContent( data, table );
-
- if ( data.info )
- {
- var info = data.info;
-
- // Generate the rows and cols.
- if ( !this._.selectedElement )
- {
- var tbody = table.append( makeElement( 'tbody' ) ),
- rows = parseInt( info.txtRows, 10 ) || 0,
- cols = parseInt( info.txtCols, 10 ) || 0;
-
- for ( var i = 0 ; i < rows ; i++ )
- {
- var row = tbody.append( makeElement( 'tr' ) );
- for ( var j = 0 ; j < cols ; j++ )
- {
- var cell = row.append( makeElement( 'td' ) );
- if ( !CKEDITOR.env.ie )
- cell.append( makeElement( 'br' ) );
- }
- }
- }
-
- // Modify the table headers. Depends on having rows and cols generated
- // correctly so it can't be done in commit functions.
-
- // Should we make a <thead>?
- var headers = info.selHeaders;
- if ( !table.$.tHead && ( headers == 'row' || headers == 'both' ) )
- {
- var thead = new CKEDITOR.dom.element( table.$.createTHead() );
- tbody = table.getElementsByTag( 'tbody' ).getItem( 0 );
- var theRow = tbody.getElementsByTag( 'tr' ).getItem( 0 );
-
- // Change TD to TH:
- for ( i = 0 ; i < theRow.getChildCount() ; i++ )
- {
- var th = theRow.getChild( i );
- if ( th.type == CKEDITOR.NODE_ELEMENT )
- {
- th.renameNode( 'th' );
- th.setAttribute( 'scope', 'col' );
- }
- }
- thead.append( theRow.remove() );
- }
-
- if ( table.$.tHead !== null && !( headers == 'row' || headers == 'both' ) )
- {
- // Move the row out of the THead and put it in the TBody:
- thead = new CKEDITOR.dom.element( table.$.tHead );
- tbody = table.getElementsByTag( 'tbody' ).getItem( 0 );
-
- var previousFirstRow = tbody.getFirst();
- while ( thead.getChildCount() > 0 )
- {
- theRow = thead.getFirst();
- for ( i = 0; i < theRow.getChildCount() ; i++ )
- {
- var newCell = theRow.getChild( i );
- if ( newCell.type == CKEDITOR.NODE_ELEMENT )
- {
- newCell.renameNode( 'td' );
- newCell.removeAttribute( 'scope' );
- }
- }
- theRow.insertBefore( previousFirstRow );
- }
- thead.remove();
- }
-
- // Should we make all first cells in a row TH?
- if ( !this.hasColumnHeaders && ( headers == 'col' || headers == 'both' ) )
- {
- for ( row = 0 ; row < table.$.rows.length ; row++ )
- {
- newCell = new CKEDITOR.dom.element( table.$.rows[ row ].cells[ 0 ] );
- newCell.renameNode( 'th' );
- newCell.setAttribute( 'scope', 'row' );
- }
- }
-
- // Should we make all first TH-cells in a row make TD? If 'yes' we do it the other way round :-)
- if ( ( this.hasColumnHeaders ) && !( headers == 'col' || headers == 'both' ) )
- {
- for ( i = 0 ; i < table.$.rows.length ; i++ )
- {
- row = new CKEDITOR.dom.element( table.$.rows[i] );
- if ( row.getParent().getName() == 'tbody' )
- {
- newCell = new CKEDITOR.dom.element( row.$.cells[0] );
- newCell.renameNode( 'td' );
- newCell.removeAttribute( 'scope' );
- }
- }
- }
-
- // Set the width and height.
- var styles = [];
- if ( info.txtHeight )
- table.setStyle( 'height', CKEDITOR.tools.cssLength( info.txtHeight ) );
- else
- table.removeStyle( 'height' );
-
- if ( info.txtWidth )
- {
- var type = info.cmbWidthType || 'pixels';
- table.setStyle( 'width', info.txtWidth + ( type == 'pixels' ? 'px' : '%' ) );
- }
- 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;
- },
- contents : [
- {
- id : 'info',
- label : editor.lang.table.title,
- elements :
- [
- {
- type : 'hbox',
- widths : [ null, null ],
- styles : [ 'vertical-align:top' ],
- children :
- [
- {
- type : 'vbox',
- padding : 0,
- children :
- [
- {
- type : 'text',
- id : 'txtRows',
- 'default' : 3,
- label : editor.lang.table.rows,
- style : 'width:5em',
- validate : function()
- {
- var pass = true,
- value = this.getValue();
- pass = pass && CKEDITOR.dialog.validate.integer()( value )
- && value > 0;
- if ( !pass )
- {
- alert( editor.lang.table.invalidRows );
- this.select();
- }
- return pass;
- },
- setup : function( selectedElement )
- {
- this.setValue( selectedElement.$.rows.length );
- },
- commit : commitValue
- },
- {
- type : 'text',
- id : 'txtCols',
- 'default' : 2,
- label : editor.lang.table.columns,
- style : 'width:5em',
- validate : function()
- {
- var pass = true,
- value = this.getValue();
- pass = pass && CKEDITOR.dialog.validate.integer()( value )
- && value > 0;
- if ( !pass )
- {
- alert( editor.lang.table.invalidCols );
- this.select();
- }
- return pass;
- },
- setup : function( selectedTable )
- {
- this.setValue( selectedTable.$.rows[0].cells.length);
- },
- commit : commitValue
- },
- {
- type : 'html',
- html : ' '
- },
- {
- type : 'select',
- id : 'selHeaders',
- 'default' : '',
- label : editor.lang.table.headers,
- items :
- [
- [ editor.lang.table.headersNone, '' ],
- [ editor.lang.table.headersRow, 'row' ],
- [ editor.lang.table.headersColumn, 'col' ],
- [ editor.lang.table.headersBoth, 'both' ]
- ],
- setup : function( selectedTable )
- {
- // Fill in the headers field.
- var dialog = this.getDialog();
- dialog.hasColumnHeaders = true;
-
- // Check if all the first cells in every row are TH
- for ( var row = 0 ; row < selectedTable.$.rows.length ; row++ )
- {
- // If just one cell isn't a TH then it isn't a header column
- if ( selectedTable.$.rows[row].cells[0].nodeName.toLowerCase() != 'th' )
- {
- dialog.hasColumnHeaders = false;
- break;
- }
- }
-
- // Check if the table contains <thead>.
- if ( ( selectedTable.$.tHead !== null) )
- this.setValue( dialog.hasColumnHeaders ? 'both' : 'row' );
- else
- this.setValue( dialog.hasColumnHeaders ? 'col' : '' );
- },
- commit : commitValue
- },
- {
- type : 'text',
- id : 'txtBorder',
- 'default' : 1,
- label : editor.lang.table.border,
- style : 'width:3em',
- validate : CKEDITOR.dialog.validate['number']( editor.lang.table.invalidBorder ),
- setup : function( selectedTable )
- {
- this.setValue( selectedTable.getAttribute( 'border' ) || '' );
- },
- commit : function( data, selectedTable )
- {
- if ( this.getValue() )
- selectedTable.setAttribute( 'border', this.getValue() );
- else
- selectedTable.removeAttribute( 'border' );
- }
- },
- {
- id : 'cmbAlign',
- type : 'select',
- 'default' : '',
- label : editor.lang.table.align,
- items :
- [
- [ editor.lang.common.notSet , ''],
- [ editor.lang.table.alignLeft , 'left'],
- [ editor.lang.table.alignCenter , 'center'],
- [ editor.lang.table.alignRight , 'right']
- ],
- setup : function( selectedTable )
- {
- this.setValue( selectedTable.getAttribute( 'align' ) || '' );
- },
- commit : function( data, selectedTable )
- {
- if ( this.getValue() )
- selectedTable.setAttribute( 'align', this.getValue() );
- else
- selectedTable.removeAttribute( 'align' );
- }
- }
- ]
- },
- {
- type : 'vbox',
- padding : 0,
- children :
- [
- {
- type : 'hbox',
- widths : [ '5em' ],
- children :
- [
- {
- type : 'text',
- id : 'txtWidth',
- style : 'width:5em',
- label : editor.lang.table.width,
- 'default' : 200,
- validate : CKEDITOR.dialog.validate['number']( editor.lang.table.invalidWidth ),
-
- // Extra labelling of width unit type.
- onLoad : function()
- {
- var widthType = this.getDialog().getContentElement( 'info', 'cmbWidthType' ),
- labelElement = widthType.getElement(),
- inputElement = this.getInputElement(),
- ariaLabelledByAttr = inputElement.getAttribute( 'aria-labelledby' );
-
- inputElement.setAttribute( 'aria-labelledby', [ ariaLabelledByAttr, labelElement.$.id ].join( ' ' ) );
- },
-
- setup : function( selectedTable )
- {
- var widthMatch = widthPattern.exec( selectedTable.$.style.width );
- if ( widthMatch )
- this.setValue( widthMatch[1] );
- else
- this.setValue( '' );
- },
- commit : commitValue
- },
- {
- id : 'cmbWidthType',
- type : 'select',
- label : editor.lang.table.widthUnit,
- labelStyle: 'visibility:hidden',
- 'default' : 'pixels',
- items :
- [
- [ editor.lang.table.widthPx , 'pixels'],
- [ editor.lang.table.widthPc , 'percents']
- ],
- setup : function( selectedTable )
- {
- var widthMatch = widthPattern.exec( selectedTable.$.style.width );
- if ( widthMatch )
- this.setValue( widthMatch[2] == 'px' ? 'pixels' : 'percents' );
- },
- commit : commitValue
- }
- ]
- },
- {
- type : 'hbox',
- widths : [ '5em' ],
- children :
- [
- {
- type : 'text',
- id : 'txtHeight',
- style : 'width:5em',
- label : editor.lang.table.height,
- 'default' : '',
- validate : CKEDITOR.dialog.validate['number']( editor.lang.table.invalidHeight ),
-
- // Extra labelling of height unit type.
- onLoad : function()
- {
- var heightType = this.getDialog().getContentElement( 'info', 'htmlHeightType' ),
- labelElement = heightType.getElement(),
- inputElement = this.getInputElement(),
- ariaLabelledByAttr = inputElement.getAttribute( 'aria-labelledby' );
-
- inputElement.setAttribute( 'aria-labelledby', [ ariaLabelledByAttr, labelElement.$.id ].join( ' ' ) );
- },
-
- setup : function( selectedTable )
- {
- var heightMatch = heightPattern.exec( selectedTable.$.style.height );
- if ( heightMatch )
- this.setValue( heightMatch[1] );
- },
- commit : commitValue
- },
- {
- id : 'htmlHeightType',
- type : 'html',
- html : '<div><br />' + editor.lang.table.widthPx + '</div>'
- }
- ]
- },
- {
- type : 'html',
- html : ' '
- },
- {
- type : 'text',
- id : 'txtCellSpace',
- style : 'width:3em',
- label : editor.lang.table.cellSpace,
- 'default' : 1,
- validate : CKEDITOR.dialog.validate['number']( editor.lang.table.invalidCellSpacing ),
- setup : function( selectedTable )
- {
- this.setValue( selectedTable.getAttribute( 'cellSpacing' ) || '' );
- },
- commit : function( data, selectedTable )
- {
- if ( this.getValue() )
- selectedTable.setAttribute( 'cellSpacing', this.getValue() );
- else
- selectedTable.removeAttribute( 'cellSpacing' );
- }
- },
- {
- type : 'text',
- id : 'txtCellPad',
- style : 'width:3em',
- label : editor.lang.table.cellPad,
- 'default' : 1,
- validate : CKEDITOR.dialog.validate['number']( editor.lang.table.invalidCellPadding ),
- setup : function( selectedTable )
- {
- this.setValue( selectedTable.getAttribute( 'cellPadding' ) || '' );
- },
- commit : function( data, selectedTable )
- {
- if ( this.getValue() )
- selectedTable.setAttribute( 'cellPadding', this.getValue() );
- else
- selectedTable.removeAttribute( 'cellPadding' );
- }
- }
- ]
- }
- ]
- },
- {
- type : 'html',
- align : 'right',
- html : ''
- },
- {
- type : 'vbox',
- padding : 0,
- children :
- [
- {
- type : 'text',
- id : 'txtCaption',
- label : editor.lang.table.caption,
- setup : function( selectedTable )
- {
- var nodeList = selectedTable.getElementsByTag( 'caption' );
- if ( nodeList.count() > 0 )
- {
- var caption = nodeList.getItem( 0 );
- caption = ( caption.getChild( 0 ) && caption.getChild( 0 ).getText() ) || '';
- caption = CKEDITOR.tools.trim( caption );
- this.setValue( caption );
- }
- },
- commit : function( data, table )
- {
- var caption = this.getValue(),
- captionElement = table.getElementsByTag( 'caption' );
- if ( caption )
- {
- if ( captionElement.count() > 0 )
- {
- captionElement = captionElement.getItem( 0 );
- captionElement.setHtml( '' );
- }
- else
- {
- captionElement = new CKEDITOR.dom.element( 'caption', editor.document );
- if ( table.getChildCount() )
- captionElement.insertBefore( table.getFirst() );
- else
- captionElement.appendTo( table );
- }
- captionElement.append( new CKEDITOR.dom.text( caption, editor.document ) );
- }
- else if ( captionElement.count() > 0 )
- {
- for ( var i = captionElement.count() - 1 ; i >= 0 ; i-- )
- captionElement.getItem( i ).remove();
- }
- }
- },
- {
- type : 'text',
- id : 'txtSummary',
- label : editor.lang.table.summary,
- setup : function( selectedTable )
- {
- this.setValue( selectedTable.getAttribute( 'summary' ) || '' );
- },
- commit : function( data, selectedTable )
- {
- if ( this.getValue() )
- selectedTable.setAttribute( 'summary', this.getValue() );
- }
- }
- ]
- }
- ]
- }
- ]
- };
- }
-
- CKEDITOR.dialog.add( 'table', function( editor )
- {
- return tableDialog( editor, 'table' );
- } );
- CKEDITOR.dialog.add( 'tableProperties', function( editor )
- {
- return tableDialog( editor, 'tableProperties' );
- } );
-})();
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +(function() +{ + var widthPattern = /^(\d+(?:\.\d+)?)(px|%)$/, + heightPattern = /^(\d+(?:\.\d+)?)px$/; + + var commitValue = function( data ) + { + var id = this.id; + if ( !data.info ) + data.info = {}; + data.info[id] = this.getValue(); + }; + + function tableDialog( editor, command ) + { + var makeElement = function( name ){ return new CKEDITOR.dom.element( name, editor.document ); }; + + return { + title : editor.lang.table.title, + minWidth : 310, + minHeight : CKEDITOR.env.ie ? 310 : 280, + onShow : function() + { + // Detect if there's a selected table. + var selection = editor.getSelection(), + ranges = selection.getRanges(), + selectedTable = null; + + var rowsInput = this.getContentElement( 'info', 'txtRows' ), + colsInput = this.getContentElement( 'info', 'txtCols' ), + widthInput = this.getContentElement( 'info', 'txtWidth' ); + if ( command == 'tableProperties' ) + { + if ( ( selectedTable = editor.getSelection().getSelectedElement() ) ) + { + if ( selectedTable.getName() != 'table' ) + selectedTable = null; + } + else if ( ranges.length > 0 ) + { + // Webkit could report the following range on cell selection (#4948): + // <table><tr><td>[ </td></tr></table>] + if ( CKEDITOR.env.webkit ) + ranges[ 0 ].shrink( CKEDITOR.NODE_ELEMENT ); + + var rangeRoot = ranges[0].getCommonAncestor( true ); + selectedTable = rangeRoot.getAscendant( 'table', true ); + } + + // Save a reference to the selected table, and push a new set of default values. + this._.selectedElement = selectedTable; + } + + // Enable, disable and select the row, cols, width fields. + if ( selectedTable ) + { + this.setupContent( selectedTable ); + rowsInput && rowsInput.disable(); + colsInput && colsInput.disable(); + widthInput && widthInput.select(); + } + else + { + rowsInput && rowsInput.enable(); + colsInput && colsInput.enable(); + rowsInput && rowsInput.select(); + } + }, + onOk : function() + { + if ( this._.selectedElement ) + { + var selection = editor.getSelection(), + bms = editor.getSelection().createBookmarks(); + } + + var table = this._.selectedElement || makeElement( 'table' ), + me = this, + data = {}; + + this.commitContent( data, table ); + + if ( data.info ) + { + var info = data.info; + + // Generate the rows and cols. + if ( !this._.selectedElement ) + { + var tbody = table.append( makeElement( 'tbody' ) ), + rows = parseInt( info.txtRows, 10 ) || 0, + cols = parseInt( info.txtCols, 10 ) || 0; + + for ( var i = 0 ; i < rows ; i++ ) + { + var row = tbody.append( makeElement( 'tr' ) ); + for ( var j = 0 ; j < cols ; j++ ) + { + var cell = row.append( makeElement( 'td' ) ); + if ( !CKEDITOR.env.ie ) + cell.append( makeElement( 'br' ) ); + } + } + } + + // Modify the table headers. Depends on having rows and cols generated + // correctly so it can't be done in commit functions. + + // Should we make a <thead>? + var headers = info.selHeaders; + if ( !table.$.tHead && ( headers == 'row' || headers == 'both' ) ) + { + var thead = new CKEDITOR.dom.element( table.$.createTHead() ); + tbody = table.getElementsByTag( 'tbody' ).getItem( 0 ); + var theRow = tbody.getElementsByTag( 'tr' ).getItem( 0 ); + + // Change TD to TH: + for ( i = 0 ; i < theRow.getChildCount() ; i++ ) + { + var th = theRow.getChild( i ); + if ( th.type == CKEDITOR.NODE_ELEMENT ) + { + th.renameNode( 'th' ); + th.setAttribute( 'scope', 'col' ); + } + } + thead.append( theRow.remove() ); + } + + if ( table.$.tHead !== null && !( headers == 'row' || headers == 'both' ) ) + { + // Move the row out of the THead and put it in the TBody: + thead = new CKEDITOR.dom.element( table.$.tHead ); + tbody = table.getElementsByTag( 'tbody' ).getItem( 0 ); + + var previousFirstRow = tbody.getFirst(); + while ( thead.getChildCount() > 0 ) + { + theRow = thead.getFirst(); + for ( i = 0; i < theRow.getChildCount() ; i++ ) + { + var newCell = theRow.getChild( i ); + if ( newCell.type == CKEDITOR.NODE_ELEMENT ) + { + newCell.renameNode( 'td' ); + newCell.removeAttribute( 'scope' ); + } + } + theRow.insertBefore( previousFirstRow ); + } + thead.remove(); + } + + // Should we make all first cells in a row TH? + if ( !this.hasColumnHeaders && ( headers == 'col' || headers == 'both' ) ) + { + for ( row = 0 ; row < table.$.rows.length ; row++ ) + { + newCell = new CKEDITOR.dom.element( table.$.rows[ row ].cells[ 0 ] ); + newCell.renameNode( 'th' ); + newCell.setAttribute( 'scope', 'row' ); + } + } + + // Should we make all first TH-cells in a row make TD? If 'yes' we do it the other way round :-) + if ( ( this.hasColumnHeaders ) && !( headers == 'col' || headers == 'both' ) ) + { + for ( i = 0 ; i < table.$.rows.length ; i++ ) + { + row = new CKEDITOR.dom.element( table.$.rows[i] ); + if ( row.getParent().getName() == 'tbody' ) + { + newCell = new CKEDITOR.dom.element( row.$.cells[0] ); + newCell.renameNode( 'td' ); + newCell.removeAttribute( 'scope' ); + } + } + } + + // Set the width and height. + var styles = []; + if ( info.txtHeight ) + table.setStyle( 'height', CKEDITOR.tools.cssLength( info.txtHeight ) ); + else + table.removeStyle( 'height' ); + + if ( info.txtWidth ) + { + var type = info.cmbWidthType || 'pixels'; + table.setStyle( 'width', info.txtWidth + ( type == 'pixels' ? 'px' : '%' ) ); + } + 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; + }, + contents : [ + { + id : 'info', + label : editor.lang.table.title, + elements : + [ + { + type : 'hbox', + widths : [ null, null ], + styles : [ 'vertical-align:top' ], + children : + [ + { + type : 'vbox', + padding : 0, + children : + [ + { + type : 'text', + id : 'txtRows', + 'default' : 3, + label : editor.lang.table.rows, + style : 'width:5em', + validate : function() + { + var pass = true, + value = this.getValue(); + pass = pass && CKEDITOR.dialog.validate.integer()( value ) + && value > 0; + if ( !pass ) + { + alert( editor.lang.table.invalidRows ); + this.select(); + } + return pass; + }, + setup : function( selectedElement ) + { + this.setValue( selectedElement.$.rows.length ); + }, + commit : commitValue + }, + { + type : 'text', + id : 'txtCols', + 'default' : 2, + label : editor.lang.table.columns, + style : 'width:5em', + validate : function() + { + var pass = true, + value = this.getValue(); + pass = pass && CKEDITOR.dialog.validate.integer()( value ) + && value > 0; + if ( !pass ) + { + alert( editor.lang.table.invalidCols ); + this.select(); + } + return pass; + }, + setup : function( selectedTable ) + { + this.setValue( selectedTable.$.rows[0].cells.length); + }, + commit : commitValue + }, + { + type : 'html', + html : ' ' + }, + { + type : 'select', + id : 'selHeaders', + 'default' : '', + label : editor.lang.table.headers, + items : + [ + [ editor.lang.table.headersNone, '' ], + [ editor.lang.table.headersRow, 'row' ], + [ editor.lang.table.headersColumn, 'col' ], + [ editor.lang.table.headersBoth, 'both' ] + ], + setup : function( selectedTable ) + { + // Fill in the headers field. + var dialog = this.getDialog(); + dialog.hasColumnHeaders = true; + + // Check if all the first cells in every row are TH + for ( var row = 0 ; row < selectedTable.$.rows.length ; row++ ) + { + // If just one cell isn't a TH then it isn't a header column + if ( selectedTable.$.rows[row].cells[0].nodeName.toLowerCase() != 'th' ) + { + dialog.hasColumnHeaders = false; + break; + } + } + + // Check if the table contains <thead>. + if ( ( selectedTable.$.tHead !== null) ) + this.setValue( dialog.hasColumnHeaders ? 'both' : 'row' ); + else + this.setValue( dialog.hasColumnHeaders ? 'col' : '' ); + }, + commit : commitValue + }, + { + type : 'text', + id : 'txtBorder', + 'default' : 1, + label : editor.lang.table.border, + style : 'width:3em', + validate : CKEDITOR.dialog.validate['number']( editor.lang.table.invalidBorder ), + setup : function( selectedTable ) + { + this.setValue( selectedTable.getAttribute( 'border' ) || '' ); + }, + commit : function( data, selectedTable ) + { + if ( this.getValue() ) + selectedTable.setAttribute( 'border', this.getValue() ); + else + selectedTable.removeAttribute( 'border' ); + } + }, + { + id : 'cmbAlign', + type : 'select', + 'default' : '', + label : editor.lang.table.align, + items : + [ + [ editor.lang.common.notSet , ''], + [ editor.lang.table.alignLeft , 'left'], + [ editor.lang.table.alignCenter , 'center'], + [ editor.lang.table.alignRight , 'right'] + ], + setup : function( selectedTable ) + { + this.setValue( selectedTable.getAttribute( 'align' ) || '' ); + }, + commit : function( data, selectedTable ) + { + if ( this.getValue() ) + selectedTable.setAttribute( 'align', this.getValue() ); + else + selectedTable.removeAttribute( 'align' ); + } + } + ] + }, + { + type : 'vbox', + padding : 0, + children : + [ + { + type : 'hbox', + widths : [ '5em' ], + children : + [ + { + type : 'text', + id : 'txtWidth', + style : 'width:5em', + label : editor.lang.table.width, + 'default' : 200, + validate : CKEDITOR.dialog.validate['number']( editor.lang.table.invalidWidth ), + + // Extra labelling of width unit type. + onLoad : function() + { + var widthType = this.getDialog().getContentElement( 'info', 'cmbWidthType' ), + labelElement = widthType.getElement(), + inputElement = this.getInputElement(), + ariaLabelledByAttr = inputElement.getAttribute( 'aria-labelledby' ); + + inputElement.setAttribute( 'aria-labelledby', [ ariaLabelledByAttr, labelElement.$.id ].join( ' ' ) ); + }, + + setup : function( selectedTable ) + { + var widthMatch = widthPattern.exec( selectedTable.$.style.width ); + if ( widthMatch ) + this.setValue( widthMatch[1] ); + else + this.setValue( '' ); + }, + commit : commitValue + }, + { + id : 'cmbWidthType', + type : 'select', + label : editor.lang.table.widthUnit, + labelStyle: 'visibility:hidden', + 'default' : 'pixels', + items : + [ + [ editor.lang.table.widthPx , 'pixels'], + [ editor.lang.table.widthPc , 'percents'] + ], + setup : function( selectedTable ) + { + var widthMatch = widthPattern.exec( selectedTable.$.style.width ); + if ( widthMatch ) + this.setValue( widthMatch[2] == 'px' ? 'pixels' : 'percents' ); + }, + commit : commitValue + } + ] + }, + { + type : 'hbox', + widths : [ '5em' ], + children : + [ + { + type : 'text', + id : 'txtHeight', + style : 'width:5em', + label : editor.lang.table.height, + 'default' : '', + validate : CKEDITOR.dialog.validate['number']( editor.lang.table.invalidHeight ), + + // Extra labelling of height unit type. + onLoad : function() + { + var heightType = this.getDialog().getContentElement( 'info', 'htmlHeightType' ), + labelElement = heightType.getElement(), + inputElement = this.getInputElement(), + ariaLabelledByAttr = inputElement.getAttribute( 'aria-labelledby' ); + + inputElement.setAttribute( 'aria-labelledby', [ ariaLabelledByAttr, labelElement.$.id ].join( ' ' ) ); + }, + + setup : function( selectedTable ) + { + var heightMatch = heightPattern.exec( selectedTable.$.style.height ); + if ( heightMatch ) + this.setValue( heightMatch[1] ); + }, + commit : commitValue + }, + { + id : 'htmlHeightType', + type : 'html', + html : '<div><br />' + editor.lang.table.widthPx + '</div>' + } + ] + }, + { + type : 'html', + html : ' ' + }, + { + type : 'text', + id : 'txtCellSpace', + style : 'width:3em', + label : editor.lang.table.cellSpace, + 'default' : 1, + validate : CKEDITOR.dialog.validate['number']( editor.lang.table.invalidCellSpacing ), + setup : function( selectedTable ) + { + this.setValue( selectedTable.getAttribute( 'cellSpacing' ) || '' ); + }, + commit : function( data, selectedTable ) + { + if ( this.getValue() ) + selectedTable.setAttribute( 'cellSpacing', this.getValue() ); + else + selectedTable.removeAttribute( 'cellSpacing' ); + } + }, + { + type : 'text', + id : 'txtCellPad', + style : 'width:3em', + label : editor.lang.table.cellPad, + 'default' : 1, + validate : CKEDITOR.dialog.validate['number']( editor.lang.table.invalidCellPadding ), + setup : function( selectedTable ) + { + this.setValue( selectedTable.getAttribute( 'cellPadding' ) || '' ); + }, + commit : function( data, selectedTable ) + { + if ( this.getValue() ) + selectedTable.setAttribute( 'cellPadding', this.getValue() ); + else + selectedTable.removeAttribute( 'cellPadding' ); + } + } + ] + } + ] + }, + { + type : 'html', + align : 'right', + html : '' + }, + { + type : 'vbox', + padding : 0, + children : + [ + { + type : 'text', + id : 'txtCaption', + label : editor.lang.table.caption, + setup : function( selectedTable ) + { + var nodeList = selectedTable.getElementsByTag( 'caption' ); + if ( nodeList.count() > 0 ) + { + var caption = nodeList.getItem( 0 ); + caption = ( caption.getChild( 0 ) && caption.getChild( 0 ).getText() ) || ''; + caption = CKEDITOR.tools.trim( caption ); + this.setValue( caption ); + } + }, + commit : function( data, table ) + { + var caption = this.getValue(), + captionElement = table.getElementsByTag( 'caption' ); + if ( caption ) + { + if ( captionElement.count() > 0 ) + { + captionElement = captionElement.getItem( 0 ); + captionElement.setHtml( '' ); + } + else + { + captionElement = new CKEDITOR.dom.element( 'caption', editor.document ); + if ( table.getChildCount() ) + captionElement.insertBefore( table.getFirst() ); + else + captionElement.appendTo( table ); + } + captionElement.append( new CKEDITOR.dom.text( caption, editor.document ) ); + } + else if ( captionElement.count() > 0 ) + { + for ( var i = captionElement.count() - 1 ; i >= 0 ; i-- ) + captionElement.getItem( i ).remove(); + } + } + }, + { + type : 'text', + id : 'txtSummary', + label : editor.lang.table.summary, + setup : function( selectedTable ) + { + this.setValue( selectedTable.getAttribute( 'summary' ) || '' ); + }, + commit : function( data, selectedTable ) + { + if ( this.getValue() ) + selectedTable.setAttribute( 'summary', this.getValue() ); + else + selectedTable.removeAttribute( 'summary' ); + } + } + ] + } + ] + } + ] + }; + } + + CKEDITOR.dialog.add( 'table', function( editor ) + { + return tableDialog( editor, 'table' ); + } ); + CKEDITOR.dialog.add( 'tableProperties', function( editor ) + { + return tableDialog( editor, 'tableProperties' ); + } ); +})(); diff --git a/_source/plugins/table/plugin.js b/_source/plugins/table/plugin.js index 2feaeb1..cb212e1 100644 --- a/_source/plugins/table/plugin.js +++ b/_source/plugins/table/plugin.js @@ -1,70 +1,78 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-CKEDITOR.plugins.add( 'table',
-{
- init : function( editor )
- {
- var table = CKEDITOR.plugins.table,
- lang = editor.lang.table;
-
- editor.addCommand( 'table', new CKEDITOR.dialogCommand( 'table' ) );
- editor.addCommand( 'tableProperties', new CKEDITOR.dialogCommand( 'tableProperties' ) );
-
- editor.ui.addButton( 'Table',
- {
- label : lang.toolbar,
- command : 'table'
- });
-
- CKEDITOR.dialog.add( 'table', this.path + 'dialogs/table.js' );
- CKEDITOR.dialog.add( 'tableProperties', this.path + 'dialogs/table.js' );
-
- // If the "menu" plugin is loaded, register the menu items.
- if ( editor.addMenuItems )
- {
- editor.addMenuItems(
- {
- table :
- {
- label : lang.menu,
- command : 'tableProperties',
- group : 'table',
- order : 5
- },
-
- tabledelete :
- {
- label : lang.deleteTable,
- command : 'tableDelete',
- group : 'table',
- order : 1
- }
- } );
- }
-
- // If the "contextmenu" plugin is loaded, register the listeners.
- if ( editor.contextMenu )
- {
- editor.contextMenu.addListener( function( element, selection )
- {
- if ( !element )
- return null;
-
- var isTable = element.is( 'table' ) || element.hasAscendant( 'table' );
-
- if ( isTable )
- {
- return {
- tabledelete : CKEDITOR.TRISTATE_OFF,
- table : CKEDITOR.TRISTATE_OFF
- };
- }
-
- return null;
- } );
- }
- }
-} );
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.plugins.add( 'table', +{ + init : function( editor ) + { + var table = CKEDITOR.plugins.table, + lang = editor.lang.table; + + editor.addCommand( 'table', new CKEDITOR.dialogCommand( 'table' ) ); + editor.addCommand( 'tableProperties', new CKEDITOR.dialogCommand( 'tableProperties' ) ); + + editor.ui.addButton( 'Table', + { + label : lang.toolbar, + command : 'table' + }); + + CKEDITOR.dialog.add( 'table', this.path + 'dialogs/table.js' ); + CKEDITOR.dialog.add( 'tableProperties', this.path + 'dialogs/table.js' ); + + // If the "menu" plugin is loaded, register the menu items. + if ( editor.addMenuItems ) + { + editor.addMenuItems( + { + table : + { + label : lang.menu, + command : 'tableProperties', + group : 'table', + order : 5 + }, + + tabledelete : + { + label : lang.deleteTable, + command : 'tableDelete', + group : 'table', + order : 1 + } + } ); + } + + editor.on( 'doubleclick', function( evt ) + { + var element = evt.data.element; + + if ( element.is( 'table' ) ) + evt.data.dialog = 'tableProperties'; + }); + + // If the "contextmenu" plugin is loaded, register the listeners. + if ( editor.contextMenu ) + { + editor.contextMenu.addListener( function( element, selection ) + { + if ( !element ) + return null; + + var isTable = element.is( 'table' ) || element.hasAscendant( 'table' ); + + if ( isTable ) + { + return { + tabledelete : CKEDITOR.TRISTATE_OFF, + table : CKEDITOR.TRISTATE_OFF + }; + } + + return null; + } ); + } + } +} ); diff --git a/_source/plugins/tabletools/dialogs/tableCell.js b/_source/plugins/tabletools/dialogs/tableCell.js index 074ee83..c4eaf4e 100644 --- a/_source/plugins/tabletools/dialogs/tableCell.js +++ b/_source/plugins/tabletools/dialogs/tableCell.js @@ -1,523 +1,528 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-CKEDITOR.dialog.add( 'cellProperties', function( editor )
- {
- var langTable = editor.lang.table;
- var langCell = langTable.cell;
- var langCommon = editor.lang.common;
- var validate = CKEDITOR.dialog.validate;
- var widthPattern = /^(\d+(?:\.\d+)?)(px|%)$/,
- heightPattern = /^(\d+(?:\.\d+)?)px$/;
- var bind = CKEDITOR.tools.bind;
-
- function spacer()
- {
- return { type : 'html', html : ' ' };
- }
-
- /**
- *
- * @param dialogName
- * @param callback [ childDialog ]
- */
- function getDialogValue( dialogName, callback )
- {
- var onOk = function()
- {
- releaseHandlers( this );
- callback( this );
- };
- var onCancel = function()
- {
- releaseHandlers( this );
- };
- var bindToDialog = function( dialog )
- {
- dialog.on( 'ok', onOk );
- dialog.on( 'cancel', onCancel );
- };
- var releaseHandlers = function( dialog )
- {
- dialog.removeListener( 'ok', onOk );
- dialog.removeListener( 'cancel', onCancel );
- };
- editor.execCommand( dialogName );
- if ( editor._.storedDialogs.colordialog )
- bindToDialog( editor._.storedDialogs.colordialog );
- else
- {
- CKEDITOR.on( 'dialogDefinition', function( e )
- {
- if ( e.data.name != dialogName )
- return;
-
- var definition = e.data.definition;
-
- e.removeListener();
- definition.onLoad = CKEDITOR.tools.override( definition.onLoad, function( orginal )
- {
- return function()
- {
- bindToDialog( this );
- definition.onLoad = orginal;
- if ( typeof orginal == 'function' )
- orginal.call( this );
- };
- } );
- });
- }
- }
-
- return {
- title : langCell.title,
- minWidth : CKEDITOR.env.ie && CKEDITOR.env.quirks ? 550 : 480,
- minHeight : CKEDITOR.env.ie ? ( CKEDITOR.env.quirks ? 180 : 150 ) : 140,
- contents : [
- {
- id : 'info',
- label : langCell.title,
- accessKey : 'I',
- elements :
- [
- {
- type : 'hbox',
- widths : [ '40%', '5%', '40%' ],
- children :
- [
- {
- type : 'vbox',
- padding : 0,
- children :
- [
- {
- type : 'hbox',
- widths : [ '70%', '30%' ],
- children :
- [
- {
- type : 'text',
- id : 'width',
- label : langTable.width,
- widths : [ '71%', '29%' ],
- labelLayout : 'horizontal',
- validate : validate[ 'number' ]( langCell.invalidWidth ),
-
- // Extra labelling of width unit type.
- onLoad : function()
- {
- var widthType = this.getDialog().getContentElement( 'info', 'widthType' ),
- labelElement = widthType.getElement(),
- inputElement = this.getInputElement(),
- ariaLabelledByAttr = inputElement.getAttribute( 'aria-labelledby' );
-
- inputElement.setAttribute( 'aria-labelledby', [ ariaLabelledByAttr, labelElement.$.id ].join( ' ' ) );
- },
-
- setup : function( element )
- {
- var widthAttr = parseInt( element.getAttribute( 'width' ), 10 ),
- widthStyle = parseInt( element.getStyle( 'width' ), 10 );
-
- !isNaN( widthAttr ) && this.setValue( widthAttr );
- !isNaN( widthStyle ) && this.setValue( widthStyle );
- },
- commit : function( element )
- {
- var value = parseInt( this.getValue(), 10 ),
- unit = this.getDialog().getValueOf( 'info', 'widthType' );
-
- if ( !isNaN( value ) )
- element.setStyle( 'width', value + unit );
- else
- element.removeStyle( 'width' );
-
- element.removeAttribute( 'width' );
- },
- 'default' : ''
- },
- {
- type : 'select',
- id : 'widthType',
- labelLayout : 'horizontal',
- widths : [ '0%', '100%' ],
- label : editor.lang.table.widthUnit,
- labelStyle: 'display:none',
- 'default' : 'px',
- items :
- [
- [ langTable.widthPx, 'px' ],
- [ langTable.widthPc, '%' ]
- ],
- setup : function( selectedCell )
- {
- var widthMatch = widthPattern.exec( selectedCell.$.style.width );
- if ( widthMatch )
- this.setValue( widthMatch[2] );
- }
- }
- ]
- },
- {
- type : 'hbox',
- widths : [ '70%', '30%' ],
- children :
- [
- {
- type : 'text',
- id : 'height',
- label : langTable.height,
- 'default' : '',
- widths : [ '71%', '29%' ],
- labelLayout : 'horizontal',
- validate : validate[ 'number' ]( langCell.invalidHeight ),
-
- // Extra labelling of height unit type.
- onLoad : function()
- {
- var heightType = this.getDialog().getContentElement( 'info', 'htmlHeightType' ),
- labelElement = heightType.getElement(),
- inputElement = this.getInputElement(),
- ariaLabelledByAttr = inputElement.getAttribute( 'aria-labelledby' );
-
- inputElement.setAttribute( 'aria-labelledby', [ ariaLabelledByAttr, labelElement.$.id ].join( ' ' ) );
- },
-
- setup : function( element )
- {
- var heightAttr = parseInt( element.getAttribute( 'height' ), 10 ),
- heightStyle = parseInt( element.getStyle( 'height' ), 10 );
-
- !isNaN( heightAttr ) && this.setValue( heightAttr );
- !isNaN( heightStyle ) && this.setValue( heightStyle );
- },
- commit : function( element )
- {
- var value = parseInt( this.getValue(), 10 );
-
- if ( !isNaN( value ) )
- element.setStyle( 'height', CKEDITOR.tools.cssLength( value ) );
- else
- element.removeStyle( 'height' );
-
- element.removeAttribute( 'height' );
- }
- },
- {
- id : 'htmlHeightType',
- type : 'html',
- html : langTable.widthPx
- }
- ]
- },
- spacer(),
- {
- type : 'select',
- id : 'wordWrap',
- labelLayout : 'horizontal',
- label : langCell.wordWrap,
- widths : [ '50%', '50%' ],
- 'default' : 'yes',
- items :
- [
- [ langCell.yes, 'yes' ],
- [ langCell.no, 'no' ]
- ],
- setup : function( element )
- {
- var wordWrapAttr = element.getAttribute( 'noWrap' ),
- wordWrapStyle = element.getStyle( 'white-space' );
-
- if ( wordWrapStyle == 'nowrap' || wordWrapAttr )
- this.setValue( 'no' );
- },
- commit : function( element )
- {
- if ( this.getValue() == 'no' )
- element.setStyle( 'white-space', 'nowrap' );
- else
- element.removeStyle( 'white-space' );
-
- element.removeAttribute( 'noWrap' );
- }
- },
- spacer(),
- {
- type : 'select',
- id : 'hAlign',
- labelLayout : 'horizontal',
- label : langCell.hAlign,
- widths : [ '50%', '50%' ],
- 'default' : '',
- items :
- [
- [ langCommon.notSet, '' ],
- [ langTable.alignLeft, 'left' ],
- [ langTable.alignCenter, 'center' ],
- [ langTable.alignRight, 'right' ]
- ],
- setup : function( element )
- {
- var alignAttr = element.getAttribute( 'align' ),
- textAlignStyle = element.getStyle( 'text-align');
-
- this.setValue( textAlignStyle || alignAttr || '' );
- },
- commit : function( selectedCell )
- {
- var value = this.getValue();
-
- if ( value )
- selectedCell.setStyle( 'text-align', value );
- else
- selectedCell.removeStyle( 'text-align' );
-
- selectedCell.removeAttribute( 'align' );
- }
- },
- {
- type : 'select',
- id : 'vAlign',
- labelLayout : 'horizontal',
- label : langCell.vAlign,
- widths : [ '50%', '50%' ],
- 'default' : '',
- items :
- [
- [ langCommon.notSet, '' ],
- [ langCell.alignTop, 'top' ],
- [ langCell.alignMiddle, 'middle' ],
- [ langCell.alignBottom, 'bottom' ],
- [ langCell.alignBaseline, 'baseline' ]
- ],
- setup : function( element )
- {
- var vAlignAttr = element.getAttribute( 'vAlign' ),
- vAlignStyle = element.getStyle( 'vertical-align' );
-
- switch( vAlignStyle )
- {
- // Ignore all other unrelated style values..
- case 'top':
- case 'middle':
- case 'bottom':
- case 'baseline':
- break;
- default:
- vAlignStyle = '';
- }
-
- this.setValue( vAlignStyle || vAlignAttr || '' );
- },
- commit : function( element )
- {
- var value = this.getValue();
-
- if ( value )
- element.setStyle( 'vertical-align', value );
- else
- element.removeStyle( 'vertical-align' );
-
- element.removeAttribute( 'vAlign' );
- }
- }
- ]
- },
- spacer(),
- {
- type : 'vbox',
- padding : 0,
- children :
- [
- {
- type : 'select',
- id : 'cellType',
- label : langCell.cellType,
- labelLayout : 'horizontal',
- widths : [ '50%', '50%' ],
- 'default' : 'td',
- items :
- [
- [ langCell.data, 'td' ],
- [ langCell.header, 'th' ]
- ],
- setup : function( selectedCell )
- {
- this.setValue( selectedCell.getName() );
- },
- commit : function( selectedCell )
- {
- selectedCell.renameNode( this.getValue() );
- }
- },
- spacer(),
- {
- type : 'text',
- id : 'rowSpan',
- label : langCell.rowSpan,
- labelLayout : 'horizontal',
- widths : [ '50%', '50%' ],
- 'default' : '',
- validate : validate.integer( langCell.invalidRowSpan ),
- setup : function( selectedCell )
- {
- var attrVal = parseInt( selectedCell.getAttribute( 'rowSpan' ), 10 );
- if ( attrVal && attrVal != 1 )
- this.setValue( attrVal );
- },
- commit : function( selectedCell )
- {
- var value = parseInt( this.getValue(), 10 );
- if ( value && value != 1 )
- selectedCell.setAttribute( 'rowSpan', this.getValue() );
- else
- selectedCell.removeAttribute( 'rowSpan' );
- }
- },
- {
- type : 'text',
- id : 'colSpan',
- label : langCell.colSpan,
- labelLayout : 'horizontal',
- widths : [ '50%', '50%' ],
- 'default' : '',
- validate : validate.integer( langCell.invalidColSpan ),
- setup : function( element )
- {
- var attrVal = parseInt( element.getAttribute( 'colSpan' ), 10 );
- if ( attrVal && attrVal != 1 )
- this.setValue( attrVal );
- },
- commit : function( selectedCell )
- {
- var value = parseInt( this.getValue(), 10 );
- if ( value && value != 1 )
- selectedCell.setAttribute( 'colSpan', this.getValue() );
- else
- selectedCell.removeAttribute( 'colSpan' );
- }
- },
- spacer(),
- {
- type : 'hbox',
- padding : 0,
- widths : [ '80%', '20%' ],
- children :
- [
- {
- type : 'text',
- id : 'bgColor',
- label : langCell.bgColor,
- labelLayout : 'horizontal',
- widths : [ '70%', '30%' ],
- 'default' : '',
- setup : function( element )
- {
- var bgColorAttr = element.getAttribute( 'bgColor' ),
- bgColorStyle = element.getStyle( 'background-color' );
-
- this.setValue( bgColorStyle || bgColorAttr );
- },
- commit : function( selectedCell )
- {
- var value = this.getValue();
-
- if ( value )
- selectedCell.setStyle( 'background-color', this.getValue() );
- else
- selectedCell.removeStyle( 'background-color' );
-
- selectedCell.removeAttribute( 'bgColor');
- }
- },
- {
- type : 'button',
- id : 'bgColorChoose',
- label : langCell.chooseColor,
- style : 'margin-left: 10px',
- onClick : function()
- {
- var self = this;
- getDialogValue( 'colordialog', function( colorDialog )
- {
- self.getDialog().getContentElement( 'info', 'bgColor' ).setValue(
- colorDialog.getContentElement( 'picker', 'selectedColor' ).getValue()
- );
- } );
- }
- }
- ]
- },
- spacer(),
- {
- type : 'hbox',
- padding : 0,
- widths : [ '80%', '20%' ],
- children :
- [
- {
- type : 'text',
- id : 'borderColor',
- label : langCell.borderColor,
- labelLayout : 'horizontal',
- widths : [ '70%', '30%' ],
- 'default' : '',
- setup : function( element )
- {
- var borderColorAttr = element.getAttribute( 'borderColor' ),
- borderColorStyle = element.getStyle( 'border-color' );
-
- this.setValue( borderColorStyle || borderColorAttr );
- },
- commit : function( selectedCell )
- {
- var value = this.getValue();
- if ( value )
- selectedCell.setStyle( 'border-color', this.getValue() );
- else
- selectedCell.removeStyle( 'border-color' );
-
- selectedCell.removeAttribute( 'borderColor');
- }
- },
- {
- type : 'button',
- id : 'borderColorChoose',
- label : langCell.chooseColor,
- style : 'margin-left: 10px',
- onClick : function()
- {
- var self = this;
- getDialogValue( 'colordialog', function( colorDialog )
- {
- self.getDialog().getContentElement( 'info', 'borderColor' ).setValue(
- colorDialog.getContentElement( 'picker', 'selectedColor' ).getValue()
- );
- } );
- }
- }
- ]
- }
- ]
- }
- ]
- }
- ]
- }
- ],
- onShow : function()
- {
- this.cells = CKEDITOR.plugins.tabletools.getSelectedCells(
- this._.editor.getSelection() );
- this.setupContent( this.cells[ 0 ] );
- },
- onOk : function()
- {
- var cells = this.cells;
- for ( var i = 0 ; i < cells.length ; i++ )
- this.commitContent( cells[ i ] );
- }
- };
- } );
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.dialog.add( 'cellProperties', function( editor ) + { + var langTable = editor.lang.table; + var langCell = langTable.cell; + var langCommon = editor.lang.common; + var validate = CKEDITOR.dialog.validate; + var widthPattern = /^(\d+(?:\.\d+)?)(px|%)$/, + heightPattern = /^(\d+(?:\.\d+)?)px$/; + var bind = CKEDITOR.tools.bind; + + function spacer() + { + return { type : 'html', html : ' ' }; + } + + /** + * + * @param dialogName + * @param callback [ childDialog ] + */ + function getDialogValue( dialogName, callback ) + { + var onOk = function() + { + releaseHandlers( this ); + callback( this ); + }; + var onCancel = function() + { + releaseHandlers( this ); + }; + var bindToDialog = function( dialog ) + { + dialog.on( 'ok', onOk ); + dialog.on( 'cancel', onCancel ); + }; + var releaseHandlers = function( dialog ) + { + dialog.removeListener( 'ok', onOk ); + dialog.removeListener( 'cancel', onCancel ); + }; + editor.execCommand( dialogName ); + if ( editor._.storedDialogs.colordialog ) + bindToDialog( editor._.storedDialogs.colordialog ); + else + { + CKEDITOR.on( 'dialogDefinition', function( e ) + { + if ( e.data.name != dialogName ) + return; + + var definition = e.data.definition; + + e.removeListener(); + definition.onLoad = CKEDITOR.tools.override( definition.onLoad, function( orginal ) + { + return function() + { + bindToDialog( this ); + definition.onLoad = orginal; + if ( typeof orginal == 'function' ) + orginal.call( this ); + }; + } ); + }); + } + } + + return { + title : langCell.title, + minWidth : CKEDITOR.env.ie && CKEDITOR.env.quirks ? 550 : 480, + minHeight : CKEDITOR.env.ie ? ( CKEDITOR.env.quirks ? 180 : 150 ) : 140, + contents : [ + { + id : 'info', + label : langCell.title, + accessKey : 'I', + elements : + [ + { + type : 'hbox', + widths : [ '40%', '5%', '40%' ], + children : + [ + { + type : 'vbox', + padding : 0, + children : + [ + { + type : 'hbox', + widths : [ '70%', '30%' ], + children : + [ + { + type : 'text', + id : 'width', + label : langTable.width, + widths : [ '71%', '29%' ], + labelLayout : 'horizontal', + validate : validate[ 'number' ]( langCell.invalidWidth ), + + // Extra labelling of width unit type. + onLoad : function() + { + var widthType = this.getDialog().getContentElement( 'info', 'widthType' ), + labelElement = widthType.getElement(), + inputElement = this.getInputElement(), + ariaLabelledByAttr = inputElement.getAttribute( 'aria-labelledby' ); + + inputElement.setAttribute( 'aria-labelledby', [ ariaLabelledByAttr, labelElement.$.id ].join( ' ' ) ); + }, + + setup : function( element ) + { + var widthAttr = parseInt( element.getAttribute( 'width' ), 10 ), + widthStyle = parseInt( element.getStyle( 'width' ), 10 ); + + !isNaN( widthAttr ) && this.setValue( widthAttr ); + !isNaN( widthStyle ) && this.setValue( widthStyle ); + }, + commit : function( element ) + { + var value = parseInt( this.getValue(), 10 ), + unit = this.getDialog().getValueOf( 'info', 'widthType' ); + + if ( !isNaN( value ) ) + element.setStyle( 'width', value + unit ); + else + element.removeStyle( 'width' ); + + element.removeAttribute( 'width' ); + }, + 'default' : '' + }, + { + type : 'select', + id : 'widthType', + labelLayout : 'horizontal', + widths : [ '0%', '100%' ], + label : editor.lang.table.widthUnit, + labelStyle: 'display:none', + 'default' : 'px', + items : + [ + [ langTable.widthPx, 'px' ], + [ langTable.widthPc, '%' ] + ], + setup : function( selectedCell ) + { + var widthMatch = widthPattern.exec( selectedCell.$.style.width ); + if ( widthMatch ) + this.setValue( widthMatch[2] ); + } + } + ] + }, + { + type : 'hbox', + widths : [ '70%', '30%' ], + children : + [ + { + type : 'text', + id : 'height', + label : langTable.height, + 'default' : '', + widths : [ '71%', '29%' ], + labelLayout : 'horizontal', + validate : validate[ 'number' ]( langCell.invalidHeight ), + + // Extra labelling of height unit type. + onLoad : function() + { + var heightType = this.getDialog().getContentElement( 'info', 'htmlHeightType' ), + labelElement = heightType.getElement(), + inputElement = this.getInputElement(), + ariaLabelledByAttr = inputElement.getAttribute( 'aria-labelledby' ); + + inputElement.setAttribute( 'aria-labelledby', [ ariaLabelledByAttr, labelElement.$.id ].join( ' ' ) ); + }, + + setup : function( element ) + { + var heightAttr = parseInt( element.getAttribute( 'height' ), 10 ), + heightStyle = parseInt( element.getStyle( 'height' ), 10 ); + + !isNaN( heightAttr ) && this.setValue( heightAttr ); + !isNaN( heightStyle ) && this.setValue( heightStyle ); + }, + commit : function( element ) + { + var value = parseInt( this.getValue(), 10 ); + + if ( !isNaN( value ) ) + element.setStyle( 'height', CKEDITOR.tools.cssLength( value ) ); + else + element.removeStyle( 'height' ); + + element.removeAttribute( 'height' ); + } + }, + { + id : 'htmlHeightType', + type : 'html', + html : langTable.widthPx + } + ] + }, + spacer(), + { + type : 'select', + id : 'wordWrap', + labelLayout : 'horizontal', + label : langCell.wordWrap, + widths : [ '50%', '50%' ], + 'default' : 'yes', + items : + [ + [ langCell.yes, 'yes' ], + [ langCell.no, 'no' ] + ], + setup : function( element ) + { + var wordWrapAttr = element.getAttribute( 'noWrap' ), + wordWrapStyle = element.getStyle( 'white-space' ); + + if ( wordWrapStyle == 'nowrap' || wordWrapAttr ) + this.setValue( 'no' ); + }, + commit : function( element ) + { + if ( this.getValue() == 'no' ) + element.setStyle( 'white-space', 'nowrap' ); + else + element.removeStyle( 'white-space' ); + + element.removeAttribute( 'noWrap' ); + } + }, + spacer(), + { + type : 'select', + id : 'hAlign', + labelLayout : 'horizontal', + label : langCell.hAlign, + widths : [ '50%', '50%' ], + 'default' : '', + items : + [ + [ langCommon.notSet, '' ], + [ langTable.alignLeft, 'left' ], + [ langTable.alignCenter, 'center' ], + [ langTable.alignRight, 'right' ] + ], + setup : function( element ) + { + var alignAttr = element.getAttribute( 'align' ), + textAlignStyle = element.getStyle( 'text-align'); + + this.setValue( textAlignStyle || alignAttr || '' ); + }, + commit : function( selectedCell ) + { + var value = this.getValue(); + + if ( value ) + selectedCell.setStyle( 'text-align', value ); + else + selectedCell.removeStyle( 'text-align' ); + + selectedCell.removeAttribute( 'align' ); + } + }, + { + type : 'select', + id : 'vAlign', + labelLayout : 'horizontal', + label : langCell.vAlign, + widths : [ '50%', '50%' ], + 'default' : '', + items : + [ + [ langCommon.notSet, '' ], + [ langCell.alignTop, 'top' ], + [ langCell.alignMiddle, 'middle' ], + [ langCell.alignBottom, 'bottom' ], + [ langCell.alignBaseline, 'baseline' ] + ], + setup : function( element ) + { + var vAlignAttr = element.getAttribute( 'vAlign' ), + vAlignStyle = element.getStyle( 'vertical-align' ); + + switch( vAlignStyle ) + { + // Ignore all other unrelated style values.. + case 'top': + case 'middle': + case 'bottom': + case 'baseline': + break; + default: + vAlignStyle = ''; + } + + this.setValue( vAlignStyle || vAlignAttr || '' ); + }, + commit : function( element ) + { + var value = this.getValue(); + + if ( value ) + element.setStyle( 'vertical-align', value ); + else + element.removeStyle( 'vertical-align' ); + + element.removeAttribute( 'vAlign' ); + } + } + ] + }, + spacer(), + { + type : 'vbox', + padding : 0, + children : + [ + { + type : 'select', + id : 'cellType', + label : langCell.cellType, + labelLayout : 'horizontal', + widths : [ '50%', '50%' ], + 'default' : 'td', + items : + [ + [ langCell.data, 'td' ], + [ langCell.header, 'th' ] + ], + setup : function( selectedCell ) + { + this.setValue( selectedCell.getName() ); + }, + commit : function( selectedCell ) + { + selectedCell.renameNode( this.getValue() ); + } + }, + spacer(), + { + type : 'text', + id : 'rowSpan', + label : langCell.rowSpan, + labelLayout : 'horizontal', + widths : [ '50%', '50%' ], + 'default' : '', + validate : validate.integer( langCell.invalidRowSpan ), + setup : function( selectedCell ) + { + var attrVal = parseInt( selectedCell.getAttribute( 'rowSpan' ), 10 ); + if ( attrVal && attrVal != 1 ) + this.setValue( attrVal ); + }, + commit : function( selectedCell ) + { + var value = parseInt( this.getValue(), 10 ); + if ( value && value != 1 ) + selectedCell.setAttribute( 'rowSpan', this.getValue() ); + else + selectedCell.removeAttribute( 'rowSpan' ); + } + }, + { + type : 'text', + id : 'colSpan', + label : langCell.colSpan, + labelLayout : 'horizontal', + widths : [ '50%', '50%' ], + 'default' : '', + validate : validate.integer( langCell.invalidColSpan ), + setup : function( element ) + { + var attrVal = parseInt( element.getAttribute( 'colSpan' ), 10 ); + if ( attrVal && attrVal != 1 ) + this.setValue( attrVal ); + }, + commit : function( selectedCell ) + { + var value = parseInt( this.getValue(), 10 ); + if ( value && value != 1 ) + selectedCell.setAttribute( 'colSpan', this.getValue() ); + else + selectedCell.removeAttribute( 'colSpan' ); + } + }, + spacer(), + { + type : 'hbox', + padding : 0, + widths : [ '80%', '20%' ], + children : + [ + { + type : 'text', + id : 'bgColor', + label : langCell.bgColor, + labelLayout : 'horizontal', + widths : [ '70%', '30%' ], + 'default' : '', + setup : function( element ) + { + var bgColorAttr = element.getAttribute( 'bgColor' ), + bgColorStyle = element.getStyle( 'background-color' ); + + this.setValue( bgColorStyle || bgColorAttr ); + }, + commit : function( selectedCell ) + { + var value = this.getValue(); + + if ( value ) + selectedCell.setStyle( 'background-color', this.getValue() ); + else + selectedCell.removeStyle( 'background-color' ); + + selectedCell.removeAttribute( 'bgColor'); + } + }, + { + type : 'button', + id : 'bgColorChoose', + label : langCell.chooseColor, + style : 'margin-left: 10px', + onClick : function() + { + var self = this; + getDialogValue( 'colordialog', function( colorDialog ) + { + self.getDialog().getContentElement( 'info', 'bgColor' ).setValue( + colorDialog.getContentElement( 'picker', 'selectedColor' ).getValue() + ); + } ); + } + } + ] + }, + spacer(), + { + type : 'hbox', + padding : 0, + widths : [ '80%', '20%' ], + children : + [ + { + type : 'text', + id : 'borderColor', + label : langCell.borderColor, + labelLayout : 'horizontal', + widths : [ '70%', '30%' ], + 'default' : '', + setup : function( element ) + { + var borderColorAttr = element.getAttribute( 'borderColor' ), + borderColorStyle = element.getStyle( 'border-color' ); + + this.setValue( borderColorStyle || borderColorAttr ); + }, + commit : function( selectedCell ) + { + var value = this.getValue(); + if ( value ) + selectedCell.setStyle( 'border-color', this.getValue() ); + else + selectedCell.removeStyle( 'border-color' ); + + selectedCell.removeAttribute( 'borderColor'); + } + }, + { + type : 'button', + id : 'borderColorChoose', + label : langCell.chooseColor, + style : 'margin-left: 10px', + onClick : function() + { + var self = this; + getDialogValue( 'colordialog', function( colorDialog ) + { + self.getDialog().getContentElement( 'info', 'borderColor' ).setValue( + colorDialog.getContentElement( 'picker', 'selectedColor' ).getValue() + ); + } ); + } + } + ] + } + ] + } + ] + } + ] + } + ], + onShow : function() + { + this.cells = CKEDITOR.plugins.tabletools.getSelectedCells( + this._.editor.getSelection() ); + this.setupContent( this.cells[ 0 ] ); + }, + onOk : function() + { + var selection = this._.editor.getSelection(), + bookmarks = selection.createBookmarks(); + + var cells = this.cells; + for ( var i = 0 ; i < cells.length ; i++ ) + this.commitContent( cells[ i ] ); + + selection.selectBookmarks( bookmarks ); + } + }; + } ); diff --git a/_source/plugins/tabletools/plugin.js b/_source/plugins/tabletools/plugin.js index 4a3a004..6471793 100644 --- a/_source/plugins/tabletools/plugin.js +++ b/_source/plugins/tabletools/plugin.js @@ -1,1022 +1,1116 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-(function()
-{
- function removeRawAttribute( $node, attr )
- {
- if ( CKEDITOR.env.ie )
- $node.removeAttribute( attr );
- else
- delete $node[ attr ];
- }
-
- var cellNodeRegex = /^(?:td|th)$/;
-
- function getSelectedCells( selection )
- {
- // Walker will try to split text nodes, which will make the current selection
- // invalid. So save bookmarks before doing anything.
- var bookmarks = selection.createBookmarks();
-
- var ranges = selection.getRanges();
- var retval = [];
- var database = {};
-
- function moveOutOfCellGuard( node )
- {
- // Apply to the first cell only.
- if ( retval.length > 0 )
- return;
-
- // If we are exiting from the first </td>, then the td should definitely be
- // included.
- if ( node.type == CKEDITOR.NODE_ELEMENT && cellNodeRegex.test( node.getName() )
- && !node.getCustomData( 'selected_cell' ) )
- {
- CKEDITOR.dom.element.setMarker( database, node, 'selected_cell', true );
- retval.push( node );
- }
- }
-
- for ( var i = 0 ; i < ranges.length ; i++ )
- {
- var range = ranges[ i ];
-
- if ( range.collapsed )
- {
- // Walker does not handle collapsed ranges yet - fall back to old API.
- var startNode = range.getCommonAncestor();
- var nearestCell = startNode.getAscendant( 'td', true ) || startNode.getAscendant( 'th', true );
- if ( nearestCell )
- retval.push( nearestCell );
- }
- else
- {
- var walker = new CKEDITOR.dom.walker( range );
- var node;
- walker.guard = moveOutOfCellGuard;
-
- while ( ( node = walker.next() ) )
- {
- // If may be possible for us to have a range like this:
- // <td>^1</td><td>^2</td>
- // The 2nd td shouldn't be included.
- //
- // So we have to take care to include a td we've entered only when we've
- // walked into its children.
-
- var parent = node.getParent();
- if ( parent && cellNodeRegex.test( parent.getName() ) && !parent.getCustomData( 'selected_cell' ) )
- {
- CKEDITOR.dom.element.setMarker( database, parent, 'selected_cell', true );
- retval.push( parent );
- }
- }
- }
- }
-
- CKEDITOR.dom.element.clearAllMarkers( database );
-
- // Restore selection position.
- selection.selectBookmarks( bookmarks );
-
- return retval;
- }
-
- function clearRow( $tr )
- {
- // Get the array of row's cells.
- var $cells = $tr.cells;
-
- // Empty all cells.
- for ( var i = 0 ; i < $cells.length ; i++ )
- {
- $cells[ i ].innerHTML = '';
-
- if ( !CKEDITOR.env.ie )
- ( new CKEDITOR.dom.element( $cells[ i ] ) ).appendBogus();
- }
- }
-
- function insertRow( selection, insertBefore )
- {
- // Get the row where the selection is placed in.
- var row = selection.getStartElement().getAscendant( 'tr' );
- if ( !row )
- return;
-
- // Create a clone of the row.
- var newRow = row.clone( true );
-
- // Insert the new row before of it.
- newRow.insertBefore( row );
-
- // Clean one of the rows to produce the illusion of inserting an empty row
- // before or after.
- clearRow( insertBefore ? newRow.$ : row.$ );
- }
-
- function deleteRows( selectionOrRow )
- {
- if ( selectionOrRow instanceof CKEDITOR.dom.selection )
- {
- var cells = getSelectedCells( selectionOrRow ),
- cellsCount = cells.length,
- rowsToDelete = [],
- cursorPosition,
- previousRowIndex,
- nextRowIndex;
-
- // Queue up the rows - it's possible and likely that we have duplicates.
- for ( var i = 0 ; i < cellsCount ; i++ )
- {
- var row = cells[ i ].getParent(),
- rowIndex = row.$.rowIndex;
-
- !i && ( previousRowIndex = rowIndex - 1 );
- rowsToDelete[ rowIndex ] = row;
- i == cellsCount - 1 && ( nextRowIndex = rowIndex + 1 );
- }
-
- var table = row.getAscendant( 'table' ),
- rows = table.$.rows,
- rowCount = rows.length;
-
- // Where to put the cursor after rows been deleted?
- // 1. Into next sibling row if any;
- // 2. Into previous sibling row if any;
- // 3. Into table's parent element if it's the very last row.
- cursorPosition = new CKEDITOR.dom.element(
- nextRowIndex < rowCount && table.$.rows[ nextRowIndex ] ||
- previousRowIndex > 0 && table.$.rows[ previousRowIndex ] ||
- table.$.parentNode );
-
- for ( i = rowsToDelete.length ; i >= 0 ; i-- )
- {
- if ( rowsToDelete[ i ] )
- deleteRows( rowsToDelete[ i ] );
- }
-
- return cursorPosition;
- }
- else if ( selectionOrRow instanceof CKEDITOR.dom.element )
- {
- table = selectionOrRow.getAscendant( 'table' );
-
- if ( table.$.rows.length == 1 )
- table.remove();
- else
- selectionOrRow.remove();
- }
-
- return 0;
- }
-
- function insertColumn( selection, insertBefore )
- {
- // Get the cell where the selection is placed in.
- var startElement = selection.getStartElement();
- var cell = startElement.getAscendant( 'td', true ) || startElement.getAscendant( 'th', true );
-
- if ( !cell )
- return;
-
- // Get the cell's table.
- var table = cell.getAscendant( 'table' );
- var cellIndex = cell.$.cellIndex;
-
- // Loop through all rows available in the table.
- for ( var i = 0 ; i < table.$.rows.length ; i++ )
- {
- var $row = table.$.rows[ i ];
-
- // If the row doesn't have enough cells, ignore it.
- if ( $row.cells.length < ( cellIndex + 1 ) )
- continue;
-
- cell = new CKEDITOR.dom.element( $row.cells[ cellIndex ].cloneNode( false ) );
-
- if ( !CKEDITOR.env.ie )
- cell.appendBogus();
-
- // Get back the currently selected cell.
- var baseCell = new CKEDITOR.dom.element( $row.cells[ cellIndex ] );
- if ( insertBefore )
- cell.insertBefore( baseCell );
- else
- cell.insertAfter( baseCell );
- }
- }
-
- function deleteColumns( selectionOrCell )
- {
- if ( selectionOrCell instanceof CKEDITOR.dom.selection )
- {
- var colsToDelete = getSelectedCells( selectionOrCell );
- for ( var i = colsToDelete.length ; i >= 0 ; i-- )
- {
- if ( colsToDelete[ i ] )
- deleteColumns( colsToDelete[ i ] );
- }
- }
- else if ( selectionOrCell instanceof CKEDITOR.dom.element )
- {
- // Get the cell's table.
- var table = selectionOrCell.getAscendant( 'table' );
-
- // Get the cell index.
- var cellIndex = selectionOrCell.$.cellIndex;
-
- /*
- * Loop through all rows from down to up, coz it's possible that some rows
- * will be deleted.
- */
- for ( i = table.$.rows.length - 1 ; i >= 0 ; i-- )
- {
- // Get the row.
- var row = new CKEDITOR.dom.element( table.$.rows[ i ] );
-
- // If the cell to be removed is the first one and the row has just one cell.
- if ( !cellIndex && row.$.cells.length == 1 )
- {
- deleteRows( row );
- continue;
- }
-
- // Else, just delete the cell.
- if ( row.$.cells[ cellIndex ] )
- row.$.removeChild( row.$.cells[ cellIndex ] );
- }
- }
- }
-
- function insertCell( selection, insertBefore )
- {
- var startElement = selection.getStartElement();
- var cell = startElement.getAscendant( 'td', true ) || startElement.getAscendant( 'th', true );
-
- if ( !cell )
- return;
-
- // Create the new cell element to be added.
- var newCell = cell.clone();
- if ( !CKEDITOR.env.ie )
- newCell.appendBogus();
-
- if ( insertBefore )
- newCell.insertBefore( cell );
- else
- newCell.insertAfter( cell );
- }
-
- function deleteCells( selectionOrCell )
- {
- if ( selectionOrCell instanceof CKEDITOR.dom.selection )
- {
- var cellsToDelete = getSelectedCells( selectionOrCell );
- for ( var i = cellsToDelete.length - 1 ; i >= 0 ; i-- )
- deleteCells( cellsToDelete[ i ] );
- }
- else if ( selectionOrCell instanceof CKEDITOR.dom.element )
- {
- if ( selectionOrCell.getParent().getChildCount() == 1 )
- selectionOrCell.getParent().remove();
- else
- selectionOrCell.remove();
- }
- }
-
- // 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 };
-
- CKEDITOR.plugins.tabletools =
- {
- init : function( editor )
- {
- var lang = editor.lang.table;
-
- editor.addCommand( 'cellProperties', new CKEDITOR.dialogCommand( 'cellProperties' ) );
- CKEDITOR.dialog.add( 'cellProperties', this.path + 'dialogs/tableCell.js' );
-
- editor.addCommand( 'tableDelete',
- {
- exec : function( editor )
- {
- var selection = editor.getSelection();
- var startElement = selection && selection.getStartElement();
- var table = startElement && startElement.getAscendant( 'table', true );
-
- if ( !table )
- return;
-
- // Maintain the selection point at where the table was deleted.
- selection.selectElement( table );
- var range = selection.getRanges()[0];
- range.collapse();
- selection.selectRanges( [ range ] );
-
- // If the table's parent has only one child, remove it as well.
- if ( table.getParent().getChildCount() == 1 )
- table.getParent().remove();
- else
- table.remove();
- }
- } );
-
- editor.addCommand( 'rowDelete',
- {
- exec : function( editor )
- {
- var selection = editor.getSelection();
- placeCursorInCell( deleteRows( selection ) );
- }
- } );
-
- editor.addCommand( 'rowInsertBefore',
- {
- exec : function( editor )
- {
- var selection = editor.getSelection();
- insertRow( selection, true );
- }
- } );
-
- editor.addCommand( 'rowInsertAfter',
- {
- exec : function( editor )
- {
- var selection = editor.getSelection();
- insertRow( selection );
- }
- } );
-
- editor.addCommand( 'columnDelete',
- {
- exec : function( editor )
- {
- var selection = editor.getSelection();
- deleteColumns( selection );
- }
- } );
-
- editor.addCommand( 'columnInsertBefore',
- {
- exec : function( editor )
- {
- var selection = editor.getSelection();
- insertColumn( selection, true );
- }
- } );
-
- editor.addCommand( 'columnInsertAfter',
- {
- exec : function( editor )
- {
- var selection = editor.getSelection();
- insertColumn( selection );
- }
- } );
-
- editor.addCommand( 'cellDelete',
- {
- exec : function( editor )
- {
- var selection = editor.getSelection();
- deleteCells( selection );
- }
- } );
-
- 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 )
- {
- var selection = editor.getSelection();
- insertCell( selection, true );
- }
- } );
-
- editor.addCommand( 'cellInsertAfter',
- {
- exec : function( editor )
- {
- var selection = editor.getSelection();
- insertCell( selection );
- }
- } );
-
- // If the "menu" plugin is loaded, register the menu items.
- if ( editor.addMenuItems )
- {
- editor.addMenuItems(
- {
- tablecell :
- {
- label : lang.cell.menu,
- group : 'tablecell',
- order : 1,
- getItems : function()
- {
- 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
- };
- }
- },
-
- tablecell_insertBefore :
- {
- label : lang.cell.insertBefore,
- group : 'tablecell',
- command : 'cellInsertBefore',
- order : 5
- },
-
- tablecell_insertAfter :
- {
- label : lang.cell.insertAfter,
- group : 'tablecell',
- command : 'cellInsertAfter',
- order : 10
- },
-
- tablecell_delete :
- {
- label : lang.cell.deleteCell,
- group : 'tablecell',
- command : 'cellDelete',
- 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 : 21
- },
-
- tablerow :
- {
- label : lang.row.menu,
- group : 'tablerow',
- order : 1,
- getItems : function()
- {
- return {
- tablerow_insertBefore : CKEDITOR.TRISTATE_OFF,
- tablerow_insertAfter : CKEDITOR.TRISTATE_OFF,
- tablerow_delete : CKEDITOR.TRISTATE_OFF
- };
- }
- },
-
- tablerow_insertBefore :
- {
- label : lang.row.insertBefore,
- group : 'tablerow',
- command : 'rowInsertBefore',
- order : 5
- },
-
- tablerow_insertAfter :
- {
- label : lang.row.insertAfter,
- group : 'tablerow',
- command : 'rowInsertAfter',
- order : 10
- },
-
- tablerow_delete :
- {
- label : lang.row.deleteRow,
- group : 'tablerow',
- command : 'rowDelete',
- order : 15
- },
-
- tablecolumn :
- {
- label : lang.column.menu,
- group : 'tablecolumn',
- order : 1,
- getItems : function()
- {
- return {
- tablecolumn_insertBefore : CKEDITOR.TRISTATE_OFF,
- tablecolumn_insertAfter : CKEDITOR.TRISTATE_OFF,
- tablecolumn_delete : CKEDITOR.TRISTATE_OFF
- };
- }
- },
-
- tablecolumn_insertBefore :
- {
- label : lang.column.insertBefore,
- group : 'tablecolumn',
- command : 'columnInsertBefore',
- order : 5
- },
-
- tablecolumn_insertAfter :
- {
- label : lang.column.insertAfter,
- group : 'tablecolumn',
- command : 'columnInsertAfter',
- order : 10
- },
-
- tablecolumn_delete :
- {
- label : lang.column.deleteColumn,
- group : 'tablecolumn',
- command : 'columnDelete',
- order : 15
- }
- });
- }
-
- // If the "contextmenu" plugin is laoded, register the listeners.
- if ( editor.contextMenu )
- {
- editor.contextMenu.addListener( function( element, selection )
- {
- if ( !element )
- return null;
-
- while ( element )
- {
- if ( element.getName() in contextMenuTags )
- {
- return {
- tablecell : CKEDITOR.TRISTATE_OFF,
- tablerow : CKEDITOR.TRISTATE_OFF,
- tablecolumn : CKEDITOR.TRISTATE_OFF
- };
- }
- element = element.getParent();
- }
-
- return null;
- } );
- }
- },
-
- getSelectedCells : getSelectedCells
-
- };
- CKEDITOR.plugins.add( 'tabletools', CKEDITOR.plugins.tabletools );
-})();
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +(function() +{ + function removeRawAttribute( $node, attr ) + { + if ( CKEDITOR.env.ie ) + $node.removeAttribute( attr ); + else + delete $node[ attr ]; + } + + var cellNodeRegex = /^(?:td|th)$/; + + function getSelectedCells( selection ) + { + // Walker will try to split text nodes, which will make the current selection + // invalid. So save bookmarks before doing anything. + var bookmarks = selection.createBookmarks(); + + var ranges = selection.getRanges(); + var retval = []; + var database = {}; + + function moveOutOfCellGuard( node ) + { + // Apply to the first cell only. + if ( retval.length > 0 ) + return; + + // If we are exiting from the first </td>, then the td should definitely be + // included. + if ( node.type == CKEDITOR.NODE_ELEMENT && cellNodeRegex.test( node.getName() ) + && !node.getCustomData( 'selected_cell' ) ) + { + CKEDITOR.dom.element.setMarker( database, node, 'selected_cell', true ); + retval.push( node ); + } + } + + for ( var i = 0 ; i < ranges.length ; i++ ) + { + var range = ranges[ i ]; + + if ( range.collapsed ) + { + // Walker does not handle collapsed ranges yet - fall back to old API. + var startNode = range.getCommonAncestor(); + var nearestCell = startNode.getAscendant( 'td', true ) || startNode.getAscendant( 'th', true ); + if ( nearestCell ) + retval.push( nearestCell ); + } + else + { + var walker = new CKEDITOR.dom.walker( range ); + var node; + walker.guard = moveOutOfCellGuard; + + while ( ( node = walker.next() ) ) + { + // If may be possible for us to have a range like this: + // <td>^1</td><td>^2</td> + // The 2nd td shouldn't be included. + // + // So we have to take care to include a td we've entered only when we've + // walked into its children. + + var parent = node.getParent(); + if ( parent && cellNodeRegex.test( parent.getName() ) && !parent.getCustomData( 'selected_cell' ) ) + { + CKEDITOR.dom.element.setMarker( database, parent, 'selected_cell', true ); + retval.push( parent ); + } + } + } + } + + CKEDITOR.dom.element.clearAllMarkers( database ); + + // Restore selection position. + selection.selectBookmarks( bookmarks ); + + return retval; + } + + function getFocusElementAfterDelCells( cellsToDelete ) { + var i = 0, + last = cellsToDelete.length - 1, + database = {}, + cell,focusedCell, + tr; + + while ( ( cell = cellsToDelete[ i++ ] ) ) + CKEDITOR.dom.element.setMarker( database, cell, 'delete_cell', true ); + + // 1.first we check left or right side focusable cell row by row; + i = 0; + while ( ( cell = cellsToDelete[ i++ ] ) ) + { + if ( ( focusedCell = cell.getPrevious() ) && !focusedCell.getCustomData( 'delete_cell' ) + || ( focusedCell = cell.getNext() ) && !focusedCell.getCustomData( 'delete_cell' ) ) + { + CKEDITOR.dom.element.clearAllMarkers( database ); + return focusedCell; + } + } + + CKEDITOR.dom.element.clearAllMarkers( database ); + + // 2. then we check the toppest row (outside the selection area square) focusable cell + tr = cellsToDelete[ 0 ].getParent(); + if ( ( tr = tr.getPrevious() ) ) + return tr.getLast(); + + // 3. last we check the lowerest row focusable cell + tr = cellsToDelete[ last ].getParent(); + if ( ( tr = tr.getNext() ) ) + return tr.getChild( 0 ); + + return null; + } + + function clearRow( $tr ) + { + // Get the array of row's cells. + var $cells = $tr.cells; + + // Empty all cells. + for ( var i = 0 ; i < $cells.length ; i++ ) + { + $cells[ i ].innerHTML = ''; + + if ( !CKEDITOR.env.ie ) + ( new CKEDITOR.dom.element( $cells[ i ] ) ).appendBogus(); + } + } + + function insertRow( selection, insertBefore ) + { + // Get the row where the selection is placed in. + var row = selection.getStartElement().getAscendant( 'tr' ); + if ( !row ) + return; + + // Create a clone of the row. + var newRow = row.clone( true ); + + // Insert the new row before of it. + newRow.insertBefore( row ); + + // Clean one of the rows to produce the illusion of inserting an empty row + // before or after. + clearRow( insertBefore ? newRow.$ : row.$ ); + } + + function deleteRows( selectionOrRow ) + { + if ( selectionOrRow instanceof CKEDITOR.dom.selection ) + { + var cells = getSelectedCells( selectionOrRow ), + cellsCount = cells.length, + rowsToDelete = [], + cursorPosition, + previousRowIndex, + nextRowIndex; + + // Queue up the rows - it's possible and likely that we have duplicates. + for ( var i = 0 ; i < cellsCount ; i++ ) + { + var row = cells[ i ].getParent(), + rowIndex = row.$.rowIndex; + + !i && ( previousRowIndex = rowIndex - 1 ); + rowsToDelete[ rowIndex ] = row; + i == cellsCount - 1 && ( nextRowIndex = rowIndex + 1 ); + } + + var table = row.getAscendant( 'table' ), + rows = table.$.rows, + rowCount = rows.length; + + // Where to put the cursor after rows been deleted? + // 1. Into next sibling row if any; + // 2. Into previous sibling row if any; + // 3. Into table's parent element if it's the very last row. + cursorPosition = new CKEDITOR.dom.element( + nextRowIndex < rowCount && table.$.rows[ nextRowIndex ] || + previousRowIndex > 0 && table.$.rows[ previousRowIndex ] || + table.$.parentNode ); + + for ( i = rowsToDelete.length ; i >= 0 ; i-- ) + { + if ( rowsToDelete[ i ] ) + deleteRows( rowsToDelete[ i ] ); + } + + return cursorPosition; + } + else if ( selectionOrRow instanceof CKEDITOR.dom.element ) + { + table = selectionOrRow.getAscendant( 'table' ); + + if ( table.$.rows.length == 1 ) + table.remove(); + else + selectionOrRow.remove(); + } + + return 0; + } + + function insertColumn( selection, insertBefore ) + { + // Get the cell where the selection is placed in. + var startElement = selection.getStartElement(); + var cell = startElement.getAscendant( 'td', true ) || startElement.getAscendant( 'th', true ); + + if ( !cell ) + return; + + // Get the cell's table. + var table = cell.getAscendant( 'table' ); + var cellIndex = cell.$.cellIndex; + + // Loop through all rows available in the table. + for ( var i = 0 ; i < table.$.rows.length ; i++ ) + { + var $row = table.$.rows[ i ]; + + // If the row doesn't have enough cells, ignore it. + if ( $row.cells.length < ( cellIndex + 1 ) ) + continue; + + cell = new CKEDITOR.dom.element( $row.cells[ cellIndex ].cloneNode( false ) ); + + if ( !CKEDITOR.env.ie ) + cell.appendBogus(); + + // Get back the currently selected cell. + var baseCell = new CKEDITOR.dom.element( $row.cells[ cellIndex ] ); + if ( insertBefore ) + cell.insertBefore( baseCell ); + else + cell.insertAfter( baseCell ); + } + } + + function getFocusElementAfterDelCols( cells ) + { + var cellIndexList = [], + table = cells[ 0 ] && cells[ 0 ].getAscendant( 'table' ), + i, length, + targetIndex, targetCell; + + // get the cellIndex list of delete cells + for ( i = 0, length = cells.length; i < length; i++ ) + cellIndexList.push( cells[i].$.cellIndex ); + + // get the focusable column index + cellIndexList.sort(); + for ( i = 1, length = cellIndexList.length; i < length; i++ ) + { + if ( cellIndexList[ i ] - cellIndexList[ i - 1 ] > 1 ) + { + targetIndex = cellIndexList[ i - 1 ] + 1; + break; + } + } + + if ( !targetIndex ) + targetIndex = cellIndexList[ 0 ] > 0 ? ( cellIndexList[ 0 ] - 1 ) + : ( cellIndexList[ cellIndexList.length - 1 ] + 1 ); + + // scan row by row to get the target cell + var rows = table.$.rows; + for ( i = 0, length = rows.length; i < length ; i++ ) + { + targetCell = rows[ i ].cells[ targetIndex ]; + if ( targetCell ) + break; + } + + return targetCell ? new CKEDITOR.dom.element( targetCell ) : table.getPrevious(); + } + + function deleteColumns( selectionOrCell ) + { + if ( selectionOrCell instanceof CKEDITOR.dom.selection ) + { + var colsToDelete = getSelectedCells( selectionOrCell ), + elementToFocus = getFocusElementAfterDelCols( colsToDelete ); + + for ( var i = colsToDelete.length - 1 ; i >= 0 ; i-- ) + { + if ( colsToDelete[ i ] ) + deleteColumns( colsToDelete[ i ] ); + } + + return elementToFocus; + } + else if ( selectionOrCell instanceof CKEDITOR.dom.element ) + { + // Get the cell's table. + var table = selectionOrCell.getAscendant( 'table' ); + if ( !table ) + return null; + + // Get the cell index. + var cellIndex = selectionOrCell.$.cellIndex; + + /* + * Loop through all rows from down to up, coz it's possible that some rows + * will be deleted. + */ + for ( i = table.$.rows.length - 1 ; i >= 0 ; i-- ) + { + // Get the row. + var row = new CKEDITOR.dom.element( table.$.rows[ i ] ); + + // If the cell to be removed is the first one and the row has just one cell. + if ( !cellIndex && row.$.cells.length == 1 ) + { + deleteRows( row ); + continue; + } + + // Else, just delete the cell. + if ( row.$.cells[ cellIndex ] ) + row.$.removeChild( row.$.cells[ cellIndex ] ); + } + } + + return null; + } + + function insertCell( selection, insertBefore ) + { + var startElement = selection.getStartElement(); + var cell = startElement.getAscendant( 'td', true ) || startElement.getAscendant( 'th', true ); + + if ( !cell ) + return; + + // Create the new cell element to be added. + var newCell = cell.clone(); + if ( !CKEDITOR.env.ie ) + newCell.appendBogus(); + + if ( insertBefore ) + newCell.insertBefore( cell ); + else + newCell.insertAfter( cell ); + } + + function deleteCells( selectionOrCell ) + { + if ( selectionOrCell instanceof CKEDITOR.dom.selection ) + { + var cellsToDelete = getSelectedCells( selectionOrCell ); + var table = cellsToDelete[ 0 ] && cellsToDelete[ 0 ].getAscendant( 'table' ); + var cellToFocus = getFocusElementAfterDelCells( cellsToDelete ); + + for ( var i = cellsToDelete.length - 1 ; i >= 0 ; i-- ) + deleteCells( cellsToDelete[ i ] ); + + if ( cellToFocus ) + placeCursorInCell( cellToFocus, true ); + else if ( table ) + table.remove(); + } + else if ( selectionOrCell instanceof CKEDITOR.dom.element ) + { + var tr = selectionOrCell.getParent(); + if ( tr.getChildCount() == 1 ) + tr.remove(); + else + selectionOrCell.remove(); + } + } + + // 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 }; + + CKEDITOR.plugins.tabletools = + { + init : function( editor ) + { + var lang = editor.lang.table; + + editor.addCommand( 'cellProperties', new CKEDITOR.dialogCommand( 'cellProperties' ) ); + CKEDITOR.dialog.add( 'cellProperties', this.path + 'dialogs/tableCell.js' ); + + editor.addCommand( 'tableDelete', + { + exec : function( editor ) + { + var selection = editor.getSelection(); + var startElement = selection && selection.getStartElement(); + var table = startElement && startElement.getAscendant( 'table', true ); + + if ( !table ) + return; + + // Maintain the selection point at where the table was deleted. + selection.selectElement( table ); + var range = selection.getRanges()[0]; + range.collapse(); + selection.selectRanges( [ range ] ); + + // If the table's parent has only one child, remove it,except body,as well.( #5416 ) + var parent = table.getParent(); + if ( parent.getChildCount() == 1 && parent.getName() != 'body' ) + parent.remove(); + else + table.remove(); + } + } ); + + editor.addCommand( 'rowDelete', + { + exec : function( editor ) + { + var selection = editor.getSelection(); + placeCursorInCell( deleteRows( selection ) ); + } + } ); + + editor.addCommand( 'rowInsertBefore', + { + exec : function( editor ) + { + var selection = editor.getSelection(); + insertRow( selection, true ); + } + } ); + + editor.addCommand( 'rowInsertAfter', + { + exec : function( editor ) + { + var selection = editor.getSelection(); + insertRow( selection ); + } + } ); + + editor.addCommand( 'columnDelete', + { + exec : function( editor ) + { + var selection = editor.getSelection(); + var element = deleteColumns( selection ); + element && placeCursorInCell( element, true ); + } + } ); + + editor.addCommand( 'columnInsertBefore', + { + exec : function( editor ) + { + var selection = editor.getSelection(); + insertColumn( selection, true ); + } + } ); + + editor.addCommand( 'columnInsertAfter', + { + exec : function( editor ) + { + var selection = editor.getSelection(); + insertColumn( selection ); + } + } ); + + editor.addCommand( 'cellDelete', + { + exec : function( editor ) + { + var selection = editor.getSelection(); + deleteCells( selection ); + } + } ); + + 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 ) + { + var selection = editor.getSelection(); + insertCell( selection, true ); + } + } ); + + editor.addCommand( 'cellInsertAfter', + { + exec : function( editor ) + { + var selection = editor.getSelection(); + insertCell( selection ); + } + } ); + + // If the "menu" plugin is loaded, register the menu items. + if ( editor.addMenuItems ) + { + editor.addMenuItems( + { + tablecell : + { + label : lang.cell.menu, + group : 'tablecell', + order : 1, + getItems : function() + { + 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 + }; + } + }, + + tablecell_insertBefore : + { + label : lang.cell.insertBefore, + group : 'tablecell', + command : 'cellInsertBefore', + order : 5 + }, + + tablecell_insertAfter : + { + label : lang.cell.insertAfter, + group : 'tablecell', + command : 'cellInsertAfter', + order : 10 + }, + + tablecell_delete : + { + label : lang.cell.deleteCell, + group : 'tablecell', + command : 'cellDelete', + 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 : 21 + }, + + tablerow : + { + label : lang.row.menu, + group : 'tablerow', + order : 1, + getItems : function() + { + return { + tablerow_insertBefore : CKEDITOR.TRISTATE_OFF, + tablerow_insertAfter : CKEDITOR.TRISTATE_OFF, + tablerow_delete : CKEDITOR.TRISTATE_OFF + }; + } + }, + + tablerow_insertBefore : + { + label : lang.row.insertBefore, + group : 'tablerow', + command : 'rowInsertBefore', + order : 5 + }, + + tablerow_insertAfter : + { + label : lang.row.insertAfter, + group : 'tablerow', + command : 'rowInsertAfter', + order : 10 + }, + + tablerow_delete : + { + label : lang.row.deleteRow, + group : 'tablerow', + command : 'rowDelete', + order : 15 + }, + + tablecolumn : + { + label : lang.column.menu, + group : 'tablecolumn', + order : 1, + getItems : function() + { + return { + tablecolumn_insertBefore : CKEDITOR.TRISTATE_OFF, + tablecolumn_insertAfter : CKEDITOR.TRISTATE_OFF, + tablecolumn_delete : CKEDITOR.TRISTATE_OFF + }; + } + }, + + tablecolumn_insertBefore : + { + label : lang.column.insertBefore, + group : 'tablecolumn', + command : 'columnInsertBefore', + order : 5 + }, + + tablecolumn_insertAfter : + { + label : lang.column.insertAfter, + group : 'tablecolumn', + command : 'columnInsertAfter', + order : 10 + }, + + tablecolumn_delete : + { + label : lang.column.deleteColumn, + group : 'tablecolumn', + command : 'columnDelete', + order : 15 + } + }); + } + + // If the "contextmenu" plugin is laoded, register the listeners. + if ( editor.contextMenu ) + { + editor.contextMenu.addListener( function( element, selection ) + { + if ( !element ) + return null; + + while ( element ) + { + if ( element.getName() in contextMenuTags ) + { + return { + tablecell : CKEDITOR.TRISTATE_OFF, + tablerow : CKEDITOR.TRISTATE_OFF, + tablecolumn : CKEDITOR.TRISTATE_OFF + }; + } + element = element.getParent(); + } + + return null; + } ); + } + }, + + getSelectedCells : getSelectedCells + + }; + CKEDITOR.plugins.add( 'tabletools', CKEDITOR.plugins.tabletools ); +})(); diff --git a/_source/plugins/templates/dialogs/templates.js b/_source/plugins/templates/dialogs/templates.js index 98c8feb..6f26a44 100644 --- a/_source/plugins/templates/dialogs/templates.js +++ b/_source/plugins/templates/dialogs/templates.js @@ -1,229 +1,231 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-(function()
-{
- var doc = CKEDITOR.document;
-
- CKEDITOR.dialog.add( 'templates', function( editor )
- {
- // Constructs the HTML view of the specified templates data.
- function renderTemplatesList( container, templatesDefinitions )
- {
- // clear loading wait text.
- container.setHtml( '' );
-
- for ( var i = 0 ; i < templatesDefinitions.length ; i++ )
- {
- var definition = CKEDITOR.getTemplates( templatesDefinitions[ i ] ),
- imagesPath = definition.imagesPath,
- templates = definition.templates,
- count = templates.length;
-
- for ( var j = 0 ; j < count ; j++ )
- {
- var template = templates[ j ],
- item = createTemplateItem( template, imagesPath );
- item.setAttribute( 'aria-posinset', j + 1 );
- item.setAttribute( 'aria-setsize', count );
- container.append( item );
- }
- }
- }
-
- function createTemplateItem( template, imagesPath )
- {
- var item = CKEDITOR.dom.element.createFromHtml(
- '<a href="javascript:void(0)" tabIndex="-1" role="option" >' +
- '<div class="cke_tpl_item"></div>' +
- '</a>' );
-
- // Build the inner HTML of our new item DIV.
- var html = '<table style="width:350px;" class="cke_tpl_preview"><tr>';
-
- if ( template.image && imagesPath )
- html += '<td class="cke_tpl_preview_img"><img src="' + CKEDITOR.getUrl( imagesPath + template.image ) + '"></td>';
-
- html += '<td style="white-space:normal;"><span class="cke_tpl_title">' + template.title + '</span><br/>';
-
- if ( template.description )
- html += '<span>' + template.description + '</span>';
-
- html += '</td></tr></table>';
-
- item.getFirst().setHtml( html );
-
- item.on( 'click', function() { insertTemplate( template.html ); } );
-
- return item;
- }
-
- /**
- * Insert the specified template content into editor.
- * @param {Number} index
- */
- function insertTemplate( html )
- {
- var dialog = CKEDITOR.dialog.getCurrent(),
- isInsert = dialog.getValueOf( 'selectTpl', 'chkInsertOpt' );
-
- 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 );
- setTimeout( function ()
- {
- editor.fire( 'saveSnapshot' );
- }, 0 );
- } );
-
- editor.fire( 'saveSnapshot' );
- editor.setData( html );
- }
- else
- {
- editor.insertHtml( html );
- dialog.hide();
- }
- }
-
- function keyNavigation( evt )
- {
- var target = evt.data.getTarget(),
- position = listContainer.getPosition( target );
-
- // Keyboard navigation for template list.
- if ( position > CKEDITOR.POSITION_CONTAINS )
- {
- var keystroke = evt.data.getKeystroke(),
- items = listContainer.getElementsByTag( 'a' ),
- focusItem;
-
- if ( items )
- {
- switch ( keystroke )
- {
- case 40 : // ARROW-DOWN
- focusItem = target.getNext();
- break;
-
- case 38 : // ARROW-UP
- focusItem = target.getPrevious();
- break;
-
- case 13 : // ENTER
- case 32 : // SPACE
- target.fire( 'click' );
- }
-
- if ( focusItem )
- {
- focusItem.focus();
- evt.data.preventDefault();
- }
- }
- }
- }
-
- // Load skin at first.
- CKEDITOR.skins.load( editor, 'templates' );
-
- var listContainer;
-
- return {
- title :editor.lang.templates.title,
-
- minWidth : CKEDITOR.env.ie ? 440 : 400,
- minHeight : 340,
-
- contents :
- [
- {
- id :'selectTpl',
- label : editor.lang.templates.title,
- elements :
- [
- {
- type : 'vbox',
- padding : 5,
- children :
- [
- {
- type : 'html',
- html :
- '<span>' +
- editor.lang.templates.selectPromptMsg +
- '</span>'
- },
- {
- id : "templatesList",
- type : 'html',
- focus: function()
- {
- // Move focus to the first list item if available.
- try { this.getElement().getElementsByTag( 'a' ).getItem( 0 ).focus(); }
- catch( er ){}
- },
- html :
- '<div class="cke_tpl_list" tabIndex="-1" role="listbox" aria-labelledby="cke_tpl_list_label">' +
- '<div class="cke_tpl_loading"><span></span></div>' +
- '</div>' +
- '<span class="cke_voice_label" id="cke_tpl_list_label">' + editor.lang.common.options+ '</span>'
- },
- {
- id : 'chkInsertOpt',
- type : 'checkbox',
- label : editor.lang.templates.insertOption,
- 'default' : editor.config.templates_replaceContent
- }
- ]
- }
- ]
- }
- ],
-
- buttons : [ CKEDITOR.dialog.cancelButton ],
-
- onShow : function()
- {
- var templatesListField = this.getContentElement( 'selectTpl' , 'templatesList' );
- listContainer = templatesListField.getElement();
-
- CKEDITOR.loadTemplates( editor.config.templates_files, function()
- {
- var templates = editor.config.templates.split( ',' );
-
- if ( templates.length )
- {
- renderTemplatesList( listContainer, templates );
- templatesListField.focus();
- }
- else
- {
- listContainer.setHtml(
- '<div class="cke_tpl_empty">' +
- '<span>' + editor.lang.templates.emptyListMsg + '</span>' +
- '</div>' );
- }
- });
-
- this._.element.on( 'keydown', keyNavigation );
- },
-
- onHide : function ()
- {
- this._.element.removeListener( 'keydown', keyNavigation );
- }
- };
- });
-})();
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +(function() +{ + var doc = CKEDITOR.document; + + CKEDITOR.dialog.add( 'templates', function( editor ) + { + // Constructs the HTML view of the specified templates data. + function renderTemplatesList( container, templatesDefinitions ) + { + // clear loading wait text. + container.setHtml( '' ); + + for ( var i = 0 ; i < templatesDefinitions.length ; i++ ) + { + var definition = CKEDITOR.getTemplates( templatesDefinitions[ i ] ), + imagesPath = definition.imagesPath, + templates = definition.templates, + count = templates.length; + + for ( var j = 0 ; j < count ; j++ ) + { + var template = templates[ j ], + item = createTemplateItem( template, imagesPath ); + item.setAttribute( 'aria-posinset', j + 1 ); + item.setAttribute( 'aria-setsize', count ); + container.append( item ); + } + } + } + + function createTemplateItem( template, imagesPath ) + { + var item = CKEDITOR.dom.element.createFromHtml( + '<a href="javascript:void(0)" tabIndex="-1" role="option" >' + + '<div class="cke_tpl_item"></div>' + + '</a>' ); + + // Build the inner HTML of our new item DIV. + var html = '<table style="width:350px;" class="cke_tpl_preview" role="presentation"><tr>'; + + if ( template.image && imagesPath ) + html += '<td class="cke_tpl_preview_img"><img src="' + CKEDITOR.getUrl( imagesPath + template.image ) + '"' + ( CKEDITOR.env.ie6Compat? ' onload="this.width=this.width"' : '' ) + ' alt="" title=""></td>'; + + html += '<td style="white-space:normal;"><span class="cke_tpl_title">' + template.title + '</span><br/>'; + + if ( template.description ) + html += '<span>' + template.description + '</span>'; + + html += '</td></tr></table>'; + + item.getFirst().setHtml( html ); + + item.on( 'click', function() { insertTemplate( template.html ); } ); + + return item; + } + + /** + * Insert the specified template content into editor. + * @param {Number} index + */ + function insertTemplate( html ) + { + var dialog = CKEDITOR.dialog.getCurrent(), + isInsert = dialog.getValueOf( 'selectTpl', 'chkInsertOpt' ); + + 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 ); + setTimeout( function () + { + editor.fire( 'saveSnapshot' ); + }, 0 ); + } ); + + editor.fire( 'saveSnapshot' ); + editor.setData( html ); + } + else + { + editor.insertHtml( html ); + dialog.hide(); + } + } + + function keyNavigation( evt ) + { + var target = evt.data.getTarget(), + onList = listContainer.equals( target ); + + // Keyboard navigation for template list. + if ( onList || listContainer.contains( target ) ) + { + var keystroke = evt.data.getKeystroke(), + items = listContainer.getElementsByTag( 'a' ), + focusItem; + + if ( items ) + { + // Focus not yet onto list items? + if ( onList ) + focusItem = items.getItem( 0 ); + else + { + switch ( keystroke ) + { + case 40 : // ARROW-DOWN + focusItem = target.getNext(); + break; + + case 38 : // ARROW-UP + focusItem = target.getPrevious(); + break; + + case 13 : // ENTER + case 32 : // SPACE + target.fire( 'click' ); + } + } + + if ( focusItem ) + { + focusItem.focus(); + evt.data.preventDefault(); + } + } + } + } + + // Load skin at first. + CKEDITOR.skins.load( editor, 'templates' ); + + var listContainer; + + var templateListLabelId = 'cke_tpl_list_label_' + CKEDITOR.tools.getNextNumber(); + return { + title :editor.lang.templates.title, + + minWidth : CKEDITOR.env.ie ? 440 : 400, + minHeight : 340, + + contents : + [ + { + id :'selectTpl', + label : editor.lang.templates.title, + elements : + [ + { + type : 'vbox', + padding : 5, + children : + [ + { + type : 'html', + html : + '<span>' + + editor.lang.templates.selectPromptMsg + + '</span>' + }, + { + id : "templatesList", + type : 'html', + focus: true, + html : + '<div class="cke_tpl_list" tabIndex="-1" role="listbox" aria-labelledby="' + templateListLabelId+ '">' + + '<div class="cke_tpl_loading"><span></span></div>' + + '</div>' + + '<span class="cke_voice_label" id="' + templateListLabelId + '">' + editor.lang.templates.options+ '</span>' + }, + { + id : 'chkInsertOpt', + type : 'checkbox', + label : editor.lang.templates.insertOption, + 'default' : editor.config.templates_replaceContent + } + ] + } + ] + } + ], + + buttons : [ CKEDITOR.dialog.cancelButton ], + + onShow : function() + { + var templatesListField = this.getContentElement( 'selectTpl' , 'templatesList' ); + listContainer = templatesListField.getElement(); + + CKEDITOR.loadTemplates( editor.config.templates_files, function() + { + var templates = editor.config.templates.split( ',' ); + + if ( templates.length ) + { + renderTemplatesList( listContainer, templates ); + templatesListField.focus(); + } + else + { + listContainer.setHtml( + '<div class="cke_tpl_empty">' + + '<span>' + editor.lang.templates.emptyListMsg + '</span>' + + '</div>' ); + } + }); + + this._.element.on( 'keydown', keyNavigation ); + }, + + onHide : function () + { + this._.element.removeListener( 'keydown', keyNavigation ); + } + }; + }); +})(); diff --git a/_source/plugins/templates/templates/default.js b/_source/plugins/templates/templates/default.js index 53b8a42..69f8ec5 100644 --- a/_source/plugins/templates/templates/default.js +++ b/_source/plugins/templates/templates/default.js @@ -1,94 +1,94 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-// Register a templates definition set named "default".
-CKEDITOR.addTemplates( 'default',
-{
- // The name of sub folder which hold the shortcut preview images of the
- // templates.
- imagesPath : CKEDITOR.getUrl( CKEDITOR.plugins.getPath( 'templates' ) + 'templates/images/' ),
-
- // The templates definitions.
- templates :
- [
- {
- title: 'Image and Title',
- image: 'template1.gif',
- description: 'One main image with a title and text that surround the image.',
- html:
- '<h3>' +
- '<img style="margin-right: 10px" height="100" width="100" align="left"/>' +
- 'Type the title here'+
- '</h3>' +
- '<p>' +
- 'Type the text here' +
- '</p>'
- },
- {
- title: 'Strange Template',
- image: 'template2.gif',
- description: 'A template that defines two colums, each one with a title, and some text.',
- html:
- '<table cellspacing="0" cellpadding="0" style="width:100%" border="0">' +
- '<tr>' +
- '<td style="width:50%">' +
- '<h3>Title 1</h3>' +
- '</td>' +
- '<td></td>' +
- '<td style="width:50%">' +
- '<h3>Title 2</h3>' +
- '</td>' +
- '</tr>' +
- '<tr>' +
- '<td>' +
- 'Text 1' +
- '</td>' +
- '<td></td>' +
- '<td>' +
- 'Text 2' +
- '</td>' +
- '</tr>' +
- '</table>' +
- '<p>' +
- 'More text goes here.' +
- '</p>'
- },
- {
- title: 'Text and Table',
- image: 'template3.gif',
- description: 'A title with some text and a table.',
- html:
- '<div style="width: 80%">' +
- '<h3>' +
- 'Title goes here' +
- '</h3>' +
- '<table style="float: right" cellspacing="0" cellpadding="0" style="width:150px" border="1">' +
- '<caption style="border:solid 1px black">' +
- '<strong>Table title</strong>' +
- '</caption>' +
- '</tr>' +
- '<tr>' +
- '<td> </td>' +
- '<td> </td>' +
- '<td> </td>' +
- '</tr>' +
- '<tr>' +
- '<td> </td>' +
- '<td> </td>' +
- '<td> </td>' +
- '</tr>' +
- '<tr>' +
- '<td> </td>' +
- '<td> </td>' +
- '<td> </td>' +
- '</tr>' +
- '</table>' +
- '<p>' +
- 'Type the text here' +
- '</p>' +
- '</div>'
- }
- ]
-});
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +// Register a templates definition set named "default". +CKEDITOR.addTemplates( 'default', +{ + // The name of sub folder which hold the shortcut preview images of the + // templates. + imagesPath : CKEDITOR.getUrl( CKEDITOR.plugins.getPath( 'templates' ) + 'templates/images/' ), + + // The templates definitions. + templates : + [ + { + title: 'Image and Title', + image: 'template1.gif', + description: 'One main image with a title and text that surround the image.', + html: + '<h3>' + + '<img style="margin-right: 10px" height="100" width="100" align="left"/>' + + 'Type the title here'+ + '</h3>' + + '<p>' + + 'Type the text here' + + '</p>' + }, + { + title: 'Strange Template', + image: 'template2.gif', + description: 'A template that defines two colums, each one with a title, and some text.', + html: + '<table cellspacing="0" cellpadding="0" style="width:100%" border="0">' + + '<tr>' + + '<td style="width:50%">' + + '<h3>Title 1</h3>' + + '</td>' + + '<td></td>' + + '<td style="width:50%">' + + '<h3>Title 2</h3>' + + '</td>' + + '</tr>' + + '<tr>' + + '<td>' + + 'Text 1' + + '</td>' + + '<td></td>' + + '<td>' + + 'Text 2' + + '</td>' + + '</tr>' + + '</table>' + + '<p>' + + 'More text goes here.' + + '</p>' + }, + { + title: 'Text and Table', + image: 'template3.gif', + description: 'A title with some text and a table.', + html: + '<div style="width: 80%">' + + '<h3>' + + 'Title goes here' + + '</h3>' + + '<table style="width:150px;float: right" cellspacing="0" cellpadding="0" border="1">' + + '<caption style="border:solid 1px black">' + + '<strong>Table title</strong>' + + '</caption>' + + '</tr>' + + '<tr>' + + '<td> </td>' + + '<td> </td>' + + '<td> </td>' + + '</tr>' + + '<tr>' + + '<td> </td>' + + '<td> </td>' + + '<td> </td>' + + '</tr>' + + '<tr>' + + '<td> </td>' + + '<td> </td>' + + '<td> </td>' + + '</tr>' + + '</table>' + + '<p>' + + 'Type the text here' + + '</p>' + + '</div>' + } + ] +}); diff --git a/_source/plugins/toolbar/plugin.js b/_source/plugins/toolbar/plugin.js index 4b59f6d..ada234f 100644 --- a/_source/plugins/toolbar/plugin.js +++ b/_source/plugins/toolbar/plugin.js @@ -1,474 +1,479 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-/**
- * @fileOverview The "toolbar" plugin. Renders the default toolbar interface in
- * the editor.
- */
-
-(function()
-{
- var toolbox = function()
- {
- this.toolbars = [];
- this.focusCommandExecuted = false;
- };
-
- toolbox.prototype.focus = function()
- {
- for ( var t = 0, toolbar ; toolbar = this.toolbars[ t++ ] ; )
- {
- for ( var i = 0, item ; item = toolbar.items[ i++ ] ; )
- {
- if ( item.focus )
- {
- item.focus();
- return;
- }
- }
- }
- };
-
- var commands =
- {
- toolbarFocus :
- {
- modes : { wysiwyg : 1, source : 1 },
-
- exec : function( editor )
- {
- if ( editor.toolbox )
- {
- editor.toolbox.focusCommandExecuted = true;
-
- // Make the first button focus accessible. (#3417)
- if ( CKEDITOR.env.ie )
- setTimeout( function(){ editor.toolbox.focus(); }, 100 );
- else
- editor.toolbox.focus();
- }
- }
- }
- };
-
- CKEDITOR.plugins.add( 'toolbar',
- {
- init : function( editor )
- {
- var itemKeystroke = function( item, keystroke )
- {
- var next, nextToolGroup, groupItemsCount;
-
- switch ( keystroke )
- {
- case 39 : // RIGHT-ARROW
- case 9 : // TAB
- do
- {
- // Look for the previous item in the toolbar.
- next = item.next;
-
- if ( !next )
- {
- nextToolGroup = item.toolbar.next;
- groupItemsCount = nextToolGroup && nextToolGroup.items.length;
-
- // Bypass the empty toolgroups.
- while ( groupItemsCount === 0 )
- {
- nextToolGroup = nextToolGroup.next;
- groupItemsCount = nextToolGroup && nextToolGroup.items.length;
- }
-
- if ( nextToolGroup )
- next = nextToolGroup.items[ 0 ];
- }
-
- item = next;
- }
- while ( item && !item.focus )
-
- // If available, just focus it, otherwise focus the
- // first one.
- if ( item )
- item.focus();
- else
- editor.toolbox.focus();
-
- return false;
-
- case 37 : // LEFT-ARROW
- case CKEDITOR.SHIFT + 9 : // SHIFT + TAB
- do
- {
- // Look for the previous item in the toolbar.
- next = item.previous;
-
- if ( !next )
- {
- nextToolGroup = item.toolbar.previous;
- groupItemsCount = nextToolGroup && nextToolGroup.items.length;
-
- // Bypass the empty toolgroups.
- while ( groupItemsCount === 0 )
- {
- nextToolGroup = nextToolGroup.previous;
- groupItemsCount = nextToolGroup && nextToolGroup.items.length;
- }
-
- if ( nextToolGroup )
- next = nextToolGroup.items[ groupItemsCount - 1 ];
- }
-
- item = next;
- }
- while ( item && !item.focus )
-
- // If available, just focus it, otherwise focus the
- // last one.
- if ( item )
- item.focus();
- else
- {
- var lastToolbarItems = editor.toolbox.toolbars[ editor.toolbox.toolbars.length - 1 ].items;
- lastToolbarItems[ lastToolbarItems.length - 1 ].focus();
- }
-
- return false;
-
- case 27 : // ESC
- editor.focus();
- return false;
-
- case 13 : // ENTER
- case 32 : // SPACE
- item.execute();
- return false;
- }
- return true;
- };
-
- editor.on( 'themeSpace', function( event )
- {
- if ( event.data.space == editor.config.toolbarLocation )
- {
- editor.toolbox = new toolbox();
-
- var labelId = 'cke_' + CKEDITOR.tools.getNextNumber();
-
- var output = [ '<div class="cke_toolbox" role="toolbar" aria-labelledby="', labelId, '"' ],
- expanded = editor.config.toolbarStartupExpanded !== false,
- groupStarted;
-
- output.push( expanded ? '>' : ' style="display:none">' );
-
- // Sends the ARIA label.
- output.push( '<span id="', labelId, '" class="cke_voice_label">', editor.lang.toolbar, '</span>' );
-
- var toolbars = editor.toolbox.toolbars,
- toolbar =
- ( editor.config.toolbar instanceof Array ) ?
- editor.config.toolbar
- :
- editor.config[ 'toolbar_' + editor.config.toolbar ];
-
- for ( var r = 0 ; r < toolbar.length ; r++ )
- {
- var row = toolbar[ r ];
-
- // It's better to check if the row object is really
- // available because it's a common mistake to leave
- // an extra comma in the toolbar definition
- // settings, which leads on the editor not loading
- // at all in IE. (#3983)
- if ( !row )
- continue;
-
- var toolbarId = 'cke_' + CKEDITOR.tools.getNextNumber(),
- toolbarObj = { id : toolbarId, items : [] };
-
- if ( groupStarted )
- {
- output.push( '</div>' );
- groupStarted = 0;
- }
-
- if ( row === '/' )
- {
- output.push( '<div class="cke_break"></div>' );
- continue;
- }
-
- output.push( '<span id="', toolbarId, '" class="cke_toolbar" role="presentation"><span class="cke_toolbar_start"></span>' );
-
- // Add the toolbar to the "editor.toolbox.toolbars"
- // array.
- var index = toolbars.push( toolbarObj ) - 1;
-
- // Create the next/previous reference.
- if ( index > 0 )
- {
- toolbarObj.previous = toolbars[ index - 1 ];
- toolbarObj.previous.next = toolbarObj;
- }
-
- // Create all items defined for this toolbar.
- for ( var i = 0 ; i < row.length ; i++ )
- {
- var item,
- itemName = row[ i ];
-
- if ( itemName == '-' )
- item = CKEDITOR.ui.separator;
- else
- item = editor.ui.create( itemName );
-
- if ( item )
- {
- if ( item.canGroup )
- {
- if ( !groupStarted )
- {
- output.push( '<span class="cke_toolgroup" role="presentation">' );
- groupStarted = 1;
- }
- }
- else if ( groupStarted )
- {
- output.push( '</span>' );
- groupStarted = 0;
- }
-
- var itemObj = item.render( editor, output );
- index = toolbarObj.items.push( itemObj ) - 1;
-
- if ( index > 0 )
- {
- itemObj.previous = toolbarObj.items[ index - 1 ];
- itemObj.previous.next = itemObj;
- }
-
- itemObj.toolbar = toolbarObj;
- itemObj.onkey = itemKeystroke;
-
- /*
- * Fix for #3052:
- * Prevent JAWS from focusing the toolbar after document load.
- */
- itemObj.onfocus = function()
- {
- if ( !editor.toolbox.focusCommandExecuted )
- editor.focus();
- };
- }
- }
-
- if ( groupStarted )
- {
- output.push( '</span>' );
- groupStarted = 0;
- }
-
- output.push( '<span class="cke_toolbar_end"></span></span>' );
- }
-
- output.push( '</div>' );
-
- if ( editor.config.toolbarCanCollapse )
- {
- var collapserFn = CKEDITOR.tools.addFunction(
- function()
- {
- editor.execCommand( 'toolbarCollapse' );
- } );
-
- var collapserId = 'cke_' + CKEDITOR.tools.getNextNumber();
-
- editor.addCommand( 'toolbarCollapse',
- {
- exec : function( editor )
- {
- var collapser = CKEDITOR.document.getById( collapserId );
- var toolbox = collapser.getPrevious();
- var contents = editor.getThemeSpace( 'contents' );
- var toolboxContainer = toolbox.getParent();
- var contentHeight = parseInt( contents.$.style.height, 10 );
- var previousHeight = toolboxContainer.$.offsetHeight;
- var collapsed = !toolbox.isVisible();
-
- if ( !collapsed )
- {
- 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 );
- }
-
- // Update collapser symbol.
- collapser.getFirst().setText( collapsed ?
- '\u25B2' : // BLACK UP-POINTING TRIANGLE
- '\u25C0' ); // BLACK LEFT-POINTING TRIANGLE
-
- var dy = toolboxContainer.$.offsetHeight - previousHeight;
- contents.setStyle( 'height', ( contentHeight - dy ) + 'px' );
-
- editor.fire( 'resize' );
- },
-
- modes : { wysiwyg : 1, source : 1 }
- } );
-
- output.push( '<a title="' + ( expanded ? editor.lang.toolbarCollapse : editor.lang.toolbarExpand )
- + '" id="' + collapserId + '" tabIndex="-1" class="cke_toolbox_collapser' );
-
- if ( !expanded )
- output.push( ' cke_toolbox_collapser_min' );
-
- output.push( '" onclick="CKEDITOR.tools.callFunction(' + collapserFn + ')">',
- '<span>▲</span>', // BLACK UP-POINTING TRIANGLE
- '</a>' );
- }
-
- event.data.html += output.join( '' );
- }
- });
-
- editor.addCommand( 'toolbarFocus', commands.toolbarFocus );
- }
- });
-})();
-
-/**
- * The UI element that renders a toolbar separator.
- * @type Object
- * @example
- */
-CKEDITOR.ui.separator =
-{
- render : function( editor, output )
- {
- output.push( '<span class="cke_separator" role="separator"></span>' );
- return {};
- }
-};
-
-/**
- * The "theme space" to which rendering the toolbar. For the default theme,
- * the recommended options are "top" and "bottom".
- * @type String
- * @default 'top'
- * @see CKEDITOR.config.theme
- * @example
- * config.toolbarLocation = 'bottom';
- */
-CKEDITOR.config.toolbarLocation = 'top';
-
-/**
- * The toolbar definition. It is an array of toolbars (strips),
- * each one being also an array, containing a list of UI items.
- * Note that this setting is composed by "toolbar_" added by the toolbar name,
- * which in this case is called "Basic". This second part of the setting name
- * can be anything. You must use this name in the
- * {@link CKEDITOR.config.toolbar} setting, so you instruct the editor which
- * toolbar_(name) setting to you.
- * @type Array
- * @example
- * // Defines a toolbar with only one strip containing the "Source" button, a
- * // separator and the "Bold" and "Italic" buttons.
- * <b>config.toolbar_Basic =
- * [
- * [ 'Source', '-', 'Bold', 'Italic' ]
- * ]</b>;
- * config.toolbar = 'Basic';
- */
-CKEDITOR.config.toolbar_Basic =
-[
- ['Bold', 'Italic', '-', 'NumberedList', 'BulletedList', '-', 'Link', 'Unlink','-','About']
-];
-
-/**
- * This is the default toolbar definition used by the editor. It contains all
- * editor features.
- * @type Array
- * @default (see example)
- * @example
- * // This is actually the default value.
- * config.toolbar_Full =
- * [
- * ['Source','-','Save','NewPage','Preview','-','Templates'],
- * ['Cut','Copy','Paste','PasteText','PasteFromWord','-','Print', 'SpellChecker', 'Scayt'],
- * ['Undo','Redo','-','Find','Replace','-','SelectAll','RemoveFormat'],
- * ['Form', 'Checkbox', 'Radio', 'TextField', 'Textarea', 'Select', 'Button', 'ImageButton', 'HiddenField'],
- * '/',
- * ['Bold','Italic','Underline','Strike','-','Subscript','Superscript'],
- * ['NumberedList','BulletedList','-','Outdent','Indent','Blockquote'],
- * ['JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock'],
- * ['Link','Unlink','Anchor'],
- * ['Image','Flash','Table','HorizontalRule','Smiley','SpecialChar','PageBreak'],
- * '/',
- * ['Styles','Format','Font','FontSize'],
- * ['TextColor','BGColor'],
- * ['Maximize', 'ShowBlocks','-','About']
- * ];
- */
-CKEDITOR.config.toolbar_Full =
-[
- ['Source','-','Save','NewPage','Preview','-','Templates'],
- ['Cut','Copy','Paste','PasteText','PasteFromWord','-','Print', 'SpellChecker', 'Scayt'],
- ['Undo','Redo','-','Find','Replace','-','SelectAll','RemoveFormat'],
- ['Form', 'Checkbox', 'Radio', 'TextField', 'Textarea', 'Select', 'Button', 'ImageButton', 'HiddenField'],
- '/',
- ['Bold','Italic','Underline','Strike','-','Subscript','Superscript'],
- ['NumberedList','BulletedList','-','Outdent','Indent','Blockquote','CreateDiv'],
- ['JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock'],
- ['Link','Unlink','Anchor'],
- ['Image','Flash','Table','HorizontalRule','Smiley','SpecialChar','PageBreak'],
- '/',
- ['Styles','Format','Font','FontSize'],
- ['TextColor','BGColor'],
- ['Maximize', 'ShowBlocks','-','About']
-];
-
-/**
- * The toolbox (alias toolbar) definition. It is a toolbar name or an array of
- * toolbars (strips), each one being also an array, containing a list of UI items.
- * @type Array|String
- * @default 'Full'
- * @example
- * // Defines a toolbar with only one strip containing the "Source" button, a
- * // separator and the "Bold" and "Italic" buttons.
- * config.toolbar =
- * [
- * [ 'Source', '-', 'Bold', 'Italic' ]
- * ];
- * @example
- * // Load toolbar_Name where Name = Basic.
- * config.toolbar = 'Basic';
- */
-CKEDITOR.config.toolbar = 'Full';
-
-/**
- * Whether the toolbar can be collapsed by the user. If disabled, the collapser
- * button will not be displayed.
- * @type Boolean
- * @default true
- * @example
- * config.toolbarCanCollapse = false;
- */
-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;
- */
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview The "toolbar" plugin. Renders the default toolbar interface in + * the editor. + */ + +(function() +{ + var toolbox = function() + { + this.toolbars = []; + this.focusCommandExecuted = false; + }; + + toolbox.prototype.focus = function() + { + for ( var t = 0, toolbar ; toolbar = this.toolbars[ t++ ] ; ) + { + for ( var i = 0, item ; item = toolbar.items[ i++ ] ; ) + { + if ( item.focus ) + { + item.focus(); + return; + } + } + } + }; + + var commands = + { + toolbarFocus : + { + modes : { wysiwyg : 1, source : 1 }, + + exec : function( editor ) + { + if ( editor.toolbox ) + { + editor.toolbox.focusCommandExecuted = true; + + // Make the first button focus accessible. (#3417) + if ( CKEDITOR.env.ie ) + setTimeout( function(){ editor.toolbox.focus(); }, 100 ); + else + editor.toolbox.focus(); + } + } + } + }; + + CKEDITOR.plugins.add( 'toolbar', + { + init : function( editor ) + { + var itemKeystroke = function( item, keystroke ) + { + var next, nextToolGroup, groupItemsCount; + var rtl = editor.lang.dir == 'rtl'; + + switch ( keystroke ) + { + case rtl ? 37 : 39 : // RIGHT-ARROW + case 9 : // TAB + do + { + // Look for the next item in the toolbar. + next = item.next; + + if ( !next ) + { + nextToolGroup = item.toolbar.next; + groupItemsCount = nextToolGroup && nextToolGroup.items.length; + + // Bypass the empty toolgroups. + while ( groupItemsCount === 0 ) + { + nextToolGroup = nextToolGroup.next; + groupItemsCount = nextToolGroup && nextToolGroup.items.length; + } + + if ( nextToolGroup ) + next = nextToolGroup.items[ 0 ]; + } + + item = next; + } + while ( item && !item.focus ) + + // If available, just focus it, otherwise focus the + // first one. + if ( item ) + item.focus(); + else + editor.toolbox.focus(); + + return false; + + case rtl ? 39 : 37 : // LEFT-ARROW + case CKEDITOR.SHIFT + 9 : // SHIFT + TAB + do + { + // Look for the previous item in the toolbar. + next = item.previous; + + if ( !next ) + { + nextToolGroup = item.toolbar.previous; + groupItemsCount = nextToolGroup && nextToolGroup.items.length; + + // Bypass the empty toolgroups. + while ( groupItemsCount === 0 ) + { + nextToolGroup = nextToolGroup.previous; + groupItemsCount = nextToolGroup && nextToolGroup.items.length; + } + + if ( nextToolGroup ) + next = nextToolGroup.items[ groupItemsCount - 1 ]; + } + + item = next; + } + while ( item && !item.focus ) + + // If available, just focus it, otherwise focus the + // last one. + if ( item ) + item.focus(); + else + { + var lastToolbarItems = editor.toolbox.toolbars[ editor.toolbox.toolbars.length - 1 ].items; + lastToolbarItems[ lastToolbarItems.length - 1 ].focus(); + } + + return false; + + case 27 : // ESC + editor.focus(); + return false; + + case 13 : // ENTER + case 32 : // SPACE + item.execute(); + return false; + } + return true; + }; + + editor.on( 'themeSpace', function( event ) + { + if ( event.data.space == editor.config.toolbarLocation ) + { + editor.toolbox = new toolbox(); + + var labelId = 'cke_' + CKEDITOR.tools.getNextNumber(); + + var output = [ '<div class="cke_toolbox" role="toolbar" aria-labelledby="', labelId, '"' ], + expanded = editor.config.toolbarStartupExpanded !== false, + groupStarted; + + output.push( expanded ? '>' : ' style="display:none">' ); + + // Sends the ARIA label. + output.push( '<span id="', labelId, '" class="cke_voice_label">', editor.lang.toolbar, '</span>' ); + + var toolbars = editor.toolbox.toolbars, + toolbar = + ( editor.config.toolbar instanceof Array ) ? + editor.config.toolbar + : + editor.config[ 'toolbar_' + editor.config.toolbar ]; + + for ( var r = 0 ; r < toolbar.length ; r++ ) + { + var row = toolbar[ r ]; + + // It's better to check if the row object is really + // available because it's a common mistake to leave + // an extra comma in the toolbar definition + // settings, which leads on the editor not loading + // at all in IE. (#3983) + if ( !row ) + continue; + + var toolbarId = 'cke_' + CKEDITOR.tools.getNextNumber(), + toolbarObj = { id : toolbarId, items : [] }; + + if ( groupStarted ) + { + output.push( '</div>' ); + groupStarted = 0; + } + + if ( row === '/' ) + { + output.push( '<div class="cke_break"></div>' ); + continue; + } + + output.push( '<span id="', toolbarId, '" class="cke_toolbar" role="presentation"><span class="cke_toolbar_start"></span>' ); + + // Add the toolbar to the "editor.toolbox.toolbars" + // array. + var index = toolbars.push( toolbarObj ) - 1; + + // Create the next/previous reference. + if ( index > 0 ) + { + toolbarObj.previous = toolbars[ index - 1 ]; + toolbarObj.previous.next = toolbarObj; + } + + // Create all items defined for this toolbar. + for ( var i = 0 ; i < row.length ; i++ ) + { + var item, + itemName = row[ i ]; + + if ( itemName == '-' ) + item = CKEDITOR.ui.separator; + else + item = editor.ui.create( itemName ); + + if ( item ) + { + if ( item.canGroup ) + { + if ( !groupStarted ) + { + output.push( '<span class="cke_toolgroup" role="presentation">' ); + groupStarted = 1; + } + } + else if ( groupStarted ) + { + output.push( '</span>' ); + groupStarted = 0; + } + + var itemObj = item.render( editor, output ); + index = toolbarObj.items.push( itemObj ) - 1; + + if ( index > 0 ) + { + itemObj.previous = toolbarObj.items[ index - 1 ]; + itemObj.previous.next = itemObj; + } + + itemObj.toolbar = toolbarObj; + itemObj.onkey = itemKeystroke; + + /* + * Fix for #3052: + * Prevent JAWS from focusing the toolbar after document load. + */ + itemObj.onfocus = function() + { + if ( !editor.toolbox.focusCommandExecuted ) + editor.focus(); + }; + } + } + + if ( groupStarted ) + { + output.push( '</span>' ); + groupStarted = 0; + } + + output.push( '<span class="cke_toolbar_end"></span></span>' ); + } + + output.push( '</div>' ); + + if ( editor.config.toolbarCanCollapse ) + { + var collapserFn = CKEDITOR.tools.addFunction( + function() + { + editor.execCommand( 'toolbarCollapse' ); + } ); + + editor.on( 'destroy', function () { + CKEDITOR.tools.removeFunction( collapserFn ); + } ); + + var collapserId = 'cke_' + CKEDITOR.tools.getNextNumber(); + + editor.addCommand( 'toolbarCollapse', + { + exec : function( editor ) + { + var collapser = CKEDITOR.document.getById( collapserId ); + var toolbox = collapser.getPrevious(); + var contents = editor.getThemeSpace( 'contents' ); + var toolboxContainer = toolbox.getParent(); + var contentHeight = parseInt( contents.$.style.height, 10 ); + var previousHeight = toolboxContainer.$.offsetHeight; + var collapsed = !toolbox.isVisible(); + + if ( !collapsed ) + { + 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 ); + } + + // Update collapser symbol. + collapser.getFirst().setText( collapsed ? + '\u25B2' : // BLACK UP-POINTING TRIANGLE + '\u25C0' ); // BLACK LEFT-POINTING TRIANGLE + + var dy = toolboxContainer.$.offsetHeight - previousHeight; + contents.setStyle( 'height', ( contentHeight - dy ) + 'px' ); + + editor.fire( 'resize' ); + }, + + modes : { wysiwyg : 1, source : 1 } + } ); + + output.push( '<a title="' + ( expanded ? editor.lang.toolbarCollapse : editor.lang.toolbarExpand ) + + '" id="' + collapserId + '" tabIndex="-1" class="cke_toolbox_collapser' ); + + if ( !expanded ) + output.push( ' cke_toolbox_collapser_min' ); + + output.push( '" onclick="CKEDITOR.tools.callFunction(' + collapserFn + ')">', + '<span>▲</span>', // BLACK UP-POINTING TRIANGLE + '</a>' ); + } + + event.data.html += output.join( '' ); + } + }); + + editor.addCommand( 'toolbarFocus', commands.toolbarFocus ); + } + }); +})(); + +/** + * The UI element that renders a toolbar separator. + * @type Object + * @example + */ +CKEDITOR.ui.separator = +{ + render : function( editor, output ) + { + output.push( '<span class="cke_separator" role="separator"></span>' ); + return {}; + } +}; + +/** + * The "theme space" to which rendering the toolbar. For the default theme, + * the recommended options are "top" and "bottom". + * @type String + * @default 'top' + * @see CKEDITOR.config.theme + * @example + * config.toolbarLocation = 'bottom'; + */ +CKEDITOR.config.toolbarLocation = 'top'; + +/** + * The toolbar definition. It is an array of toolbars (strips), + * each one being also an array, containing a list of UI items. + * Note that this setting is composed by "toolbar_" added by the toolbar name, + * which in this case is called "Basic". This second part of the setting name + * can be anything. You must use this name in the + * {@link CKEDITOR.config.toolbar} setting, so you instruct the editor which + * toolbar_(name) setting to you. + * @type Array + * @example + * // Defines a toolbar with only one strip containing the "Source" button, a + * // separator and the "Bold" and "Italic" buttons. + * <b>config.toolbar_Basic = + * [ + * [ 'Source', '-', 'Bold', 'Italic' ] + * ]</b>; + * config.toolbar = 'Basic'; + */ +CKEDITOR.config.toolbar_Basic = +[ + ['Bold', 'Italic', '-', 'NumberedList', 'BulletedList', '-', 'Link', 'Unlink','-','About'] +]; + +/** + * This is the default toolbar definition used by the editor. It contains all + * editor features. + * @type Array + * @default (see example) + * @example + * // This is actually the default value. + * config.toolbar_Full = + * [ + * ['Source','-','Save','NewPage','Preview','-','Templates'], + * ['Cut','Copy','Paste','PasteText','PasteFromWord','-','Print', 'SpellChecker', 'Scayt'], + * ['Undo','Redo','-','Find','Replace','-','SelectAll','RemoveFormat'], + * ['Form', 'Checkbox', 'Radio', 'TextField', 'Textarea', 'Select', 'Button', 'ImageButton', 'HiddenField'], + * '/', + * ['Bold','Italic','Underline','Strike','-','Subscript','Superscript'], + * ['NumberedList','BulletedList','-','Outdent','Indent','Blockquote','CreateDiv'], + * ['JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock'], + * ['Link','Unlink','Anchor'], + * ['Image','Flash','Table','HorizontalRule','Smiley','SpecialChar','PageBreak'], + * '/', + * ['Styles','Format','Font','FontSize'], + * ['TextColor','BGColor'], + * ['Maximize', 'ShowBlocks','-','About'] + * ]; + */ +CKEDITOR.config.toolbar_Full = +[ + ['Source','-','Save','NewPage','Preview','-','Templates'], + ['Cut','Copy','Paste','PasteText','PasteFromWord','-','Print', 'SpellChecker', 'Scayt'], + ['Undo','Redo','-','Find','Replace','-','SelectAll','RemoveFormat'], + ['Form', 'Checkbox', 'Radio', 'TextField', 'Textarea', 'Select', 'Button', 'ImageButton', 'HiddenField'], + '/', + ['Bold','Italic','Underline','Strike','-','Subscript','Superscript'], + ['NumberedList','BulletedList','-','Outdent','Indent','Blockquote','CreateDiv'], + ['JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock'], + ['Link','Unlink','Anchor'], + ['Image','Flash','Table','HorizontalRule','Smiley','SpecialChar','PageBreak'], + '/', + ['Styles','Format','Font','FontSize'], + ['TextColor','BGColor'], + ['Maximize', 'ShowBlocks','-','About'] +]; + +/** + * The toolbox (alias toolbar) definition. It is a toolbar name or an array of + * toolbars (strips), each one being also an array, containing a list of UI items. + * @type Array|String + * @default 'Full' + * @example + * // Defines a toolbar with only one strip containing the "Source" button, a + * // separator and the "Bold" and "Italic" buttons. + * config.toolbar = + * [ + * [ 'Source', '-', 'Bold', 'Italic' ] + * ]; + * @example + * // Load toolbar_Name where Name = Basic. + * config.toolbar = 'Basic'; + */ +CKEDITOR.config.toolbar = 'Full'; + +/** + * Whether the toolbar can be collapsed by the user. If disabled, the collapser + * button will not be displayed. + * @type Boolean + * @default true + * @example + * config.toolbarCanCollapse = false; + */ +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; + */ diff --git a/_source/plugins/uicolor/dialogs/uicolor.js b/_source/plugins/uicolor/dialogs/uicolor.js index 92d42e5..aad0fc7 100644 --- a/_source/plugins/uicolor/dialogs/uicolor.js +++ b/_source/plugins/uicolor/dialogs/uicolor.js @@ -1,204 +1,205 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-CKEDITOR.dialog.add( 'uicolor', function( editor )
-{
- var dialog, picker, pickerContents,
- // Actual UI color value.
- uiColor = editor.getUiColor();
-
- function setNewPickerColor( color )
- {
- // Convert HEX representation to RGB, stripping # char.
- if ( /^#/.test( color ) )
- color = window.YAHOO.util.Color.hex2rgb( color.substr( 1 ) );
- picker.setValue( color, true );
- // Refresh picker UI.
- picker.refresh( 'cke_uicolor_picker' );
- }
-
- function setNewUiColor( color, force )
- {
- if ( force || dialog._.contents.tab1.livePeview.getValue() )
- editor.setUiColor( color );
- // Write new config string into textbox.
- dialog._.contents.tab1.configBox.setValue(
- 'config.uiColor = "#' + picker.get( "hex" ) + '"'
- );
- }
-
- pickerContents =
- {
- id : 'yuiColorPicker',
- type : 'html',
- html : "<div id='cke_uicolor_picker' style='width: 360px; height: 200px; position: relative;'></div>",
- onLoad : function( event )
- {
- var url = CKEDITOR.getUrl(
- '_source/' + // @Packager.RemoveLine
- 'plugins/uicolor/yui/'
- );
-
- // Create new color picker widget.
- picker = new window.YAHOO.widget.ColorPicker( "cke_uicolor_picker",
- {
- showhsvcontrols : true,
- showhexcontrols : true,
- images :
- {
- PICKER_THUMB : url + "assets/picker_thumb.png",
- HUE_THUMB : url + "assets/hue_thumb.png"
- }
- });
-
- // Set actual UI color to the picker.
- if ( uiColor )
- setNewPickerColor( uiColor );
-
- // Subscribe to the rgbChange event.
- picker.on( "rgbChange", function()
- {
- // Reset predefined box.
- dialog._.contents.tab1.predefined.setValue( '' );
- setNewUiColor( '#' + picker.get( 'hex' ) );
- });
-
- // Fix input class names.
- var inputs = new CKEDITOR.dom.nodeList( picker.getElementsByTagName( 'input' ) );
- for ( var i = 0; i < inputs.count() ; i++ )
- inputs.getItem( i ).addClass( 'cke_dialog_ui_input_text' );
- }
- };
-
- var skipPreviewChange = true;
-
- return {
- title : editor.lang.uicolor.title,
- minWidth : 360,
- minHeight : 320,
- onLoad : function()
- {
- dialog = this;
- this.setupContent();
-
- // #3808
- if ( CKEDITOR.env.ie7Compat )
- dialog.parts.contents.setStyle( 'overflow', 'hidden' );
- },
- contents : [
- {
- id : 'tab1',
- label : '',
- title : '',
- expand : true,
- padding : 0,
- elements : [
- pickerContents,
- {
- id : 'tab1',
- type : 'vbox',
- children :
- [
- {
- id : 'livePeview',
- type : 'checkbox',
- label : editor.lang.uicolor.preview,
- 'default' : 1,
- onLoad : function()
- {
- skipPreviewChange = true;
- },
- onChange : function()
- {
- if ( skipPreviewChange )
- return;
- var on = this.getValue(),
- color = on ? '#' + picker.get( 'hex' ) : uiColor;
- setNewUiColor( color, true );
- }
- },
- {
- type : 'hbox',
- children :
- [
- {
- id : 'predefined',
- type : 'select',
- 'default' : '',
- label : editor.lang.uicolor.predefined,
- items :
- [
- [ '' ],
- [ 'Light blue', '#9AB8F3' ],
- [ 'Sand', '#D2B48C' ],
- [ 'Metallic', '#949AAA' ],
- [ 'Purple', '#C2A3C7' ],
- [ 'Olive', '#A2C980' ],
- [ 'Happy green', '#9BD446' ],
- [ 'Jezebel Blue', '#14B8C4' ],
- [ 'Burn', '#FF893A' ],
- [ 'Easy red', '#FF6969' ],
- [ 'Pisces 3', '#48B4F2' ],
- [ 'Aquarius 5', '#487ED4' ],
- [ 'Absinthe', '#A8CF76' ],
- [ 'Scrambled Egg', '#C7A622' ],
- [ 'Hello monday', '#8E8D80' ],
- [ 'Lovely sunshine', '#F1E8B1' ],
- [ 'Recycled air', '#B3C593' ],
- [ 'Down', '#BCBCA4' ],
- [ 'Mark Twain', '#CFE91D' ],
- [ 'Specks of dust', '#D1B596' ],
- [ 'Lollipop', '#F6CE23' ]
- ],
- onChange : function()
- {
- var color = this.getValue();
- if ( color )
- {
- setNewPickerColor( color );
- setNewUiColor( color );
- // Refresh predefined preview box.
- CKEDITOR.document.getById( 'predefinedPreview' ).setStyle( 'background', color );
- }
- else
- CKEDITOR.document.getById( 'predefinedPreview' ).setStyle( 'background', '' );
- },
- onShow : function()
- {
- var color = editor.getUiColor();
- if ( color )
- this.setValue( color );
- }
- },
- {
- id : 'predefinedPreview',
- type : 'html',
- html : '<div id="cke_uicolor_preview" style="border: 1px solid black; padding: 3px; width: 30px;">' +
- '<div id="predefinedPreview" style="width: 30px; height: 30px;"> </div>' +
- '</div>'
- }
- ]
- },
- {
- id : 'configBox',
- type : 'text',
- label : editor.lang.uicolor.config,
- onShow : function()
- {
- var color = editor.getUiColor();
- if ( color )
- this.setValue(
- 'config.uiColor = "' + color + '"'
- );
- }
- }
- ]
- }
- ]
- }
- ],
- buttons : [ CKEDITOR.dialog.okButton ]
- };
-} );
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.dialog.add( 'uicolor', function( editor ) +{ + var dialog, picker, pickerContents, + // Actual UI color value. + uiColor = editor.getUiColor(), + pickerId = 'cke_uicolor_picker' + CKEDITOR.tools.getNextNumber(); + + function setNewPickerColor( color ) + { + // Convert HEX representation to RGB, stripping # char. + if ( /^#/.test( color ) ) + color = window.YAHOO.util.Color.hex2rgb( color.substr( 1 ) ); + picker.setValue( color, true ); + // Refresh picker UI. + picker.refresh( pickerId ); + } + + function setNewUiColor( color, force ) + { + if ( force || dialog._.contents.tab1.livePeview.getValue() ) + editor.setUiColor( color ); + // Write new config string into textbox. + dialog._.contents.tab1.configBox.setValue( + 'config.uiColor = "#' + picker.get( "hex" ) + '"' + ); + } + + pickerContents = + { + id : 'yuiColorPicker', + type : 'html', + html : "<div id='" + pickerId + "' class='cke_uicolor_picker' style='width: 360px; height: 200px; position: relative;'></div>", + onLoad : function( event ) + { + var url = CKEDITOR.getUrl( + '_source/' + // @Packager.RemoveLine + 'plugins/uicolor/yui/' + ); + + // Create new color picker widget. + picker = new window.YAHOO.widget.ColorPicker( pickerId, + { + showhsvcontrols : true, + showhexcontrols : true, + images : + { + PICKER_THUMB : url + "assets/picker_thumb.png", + HUE_THUMB : url + "assets/hue_thumb.png" + } + }); + + // Set actual UI color to the picker. + if ( uiColor ) + setNewPickerColor( uiColor ); + + // Subscribe to the rgbChange event. + picker.on( "rgbChange", function() + { + // Reset predefined box. + dialog._.contents.tab1.predefined.setValue( '' ); + setNewUiColor( '#' + picker.get( 'hex' ) ); + }); + + // Fix input class names. + var inputs = new CKEDITOR.dom.nodeList( picker.getElementsByTagName( 'input' ) ); + for ( var i = 0; i < inputs.count() ; i++ ) + inputs.getItem( i ).addClass( 'cke_dialog_ui_input_text' ); + } + }; + + var skipPreviewChange = true; + + return { + title : editor.lang.uicolor.title, + minWidth : 360, + minHeight : 320, + onLoad : function() + { + dialog = this; + this.setupContent(); + + // #3808 + if ( CKEDITOR.env.ie7Compat ) + dialog.parts.contents.setStyle( 'overflow', 'hidden' ); + }, + contents : [ + { + id : 'tab1', + label : '', + title : '', + expand : true, + padding : 0, + elements : [ + pickerContents, + { + id : 'tab1', + type : 'vbox', + children : + [ + { + id : 'livePeview', + type : 'checkbox', + label : editor.lang.uicolor.preview, + 'default' : 1, + onLoad : function() + { + skipPreviewChange = true; + }, + onChange : function() + { + if ( skipPreviewChange ) + return; + var on = this.getValue(), + color = on ? '#' + picker.get( 'hex' ) : uiColor; + setNewUiColor( color, true ); + } + }, + { + type : 'hbox', + children : + [ + { + id : 'predefined', + type : 'select', + 'default' : '', + label : editor.lang.uicolor.predefined, + items : + [ + [ '' ], + [ 'Light blue', '#9AB8F3' ], + [ 'Sand', '#D2B48C' ], + [ 'Metallic', '#949AAA' ], + [ 'Purple', '#C2A3C7' ], + [ 'Olive', '#A2C980' ], + [ 'Happy green', '#9BD446' ], + [ 'Jezebel Blue', '#14B8C4' ], + [ 'Burn', '#FF893A' ], + [ 'Easy red', '#FF6969' ], + [ 'Pisces 3', '#48B4F2' ], + [ 'Aquarius 5', '#487ED4' ], + [ 'Absinthe', '#A8CF76' ], + [ 'Scrambled Egg', '#C7A622' ], + [ 'Hello monday', '#8E8D80' ], + [ 'Lovely sunshine', '#F1E8B1' ], + [ 'Recycled air', '#B3C593' ], + [ 'Down', '#BCBCA4' ], + [ 'Mark Twain', '#CFE91D' ], + [ 'Specks of dust', '#D1B596' ], + [ 'Lollipop', '#F6CE23' ] + ], + onChange : function() + { + var color = this.getValue(); + if ( color ) + { + setNewPickerColor( color ); + setNewUiColor( color ); + // Refresh predefined preview box. + CKEDITOR.document.getById( 'predefinedPreview' ).setStyle( 'background', color ); + } + else + CKEDITOR.document.getById( 'predefinedPreview' ).setStyle( 'background', '' ); + }, + onShow : function() + { + var color = editor.getUiColor(); + if ( color ) + this.setValue( color ); + } + }, + { + id : 'predefinedPreview', + type : 'html', + html : '<div id="cke_uicolor_preview" style="border: 1px solid black; padding: 3px; width: 30px;">' + + '<div id="predefinedPreview" style="width: 30px; height: 30px;"> </div>' + + '</div>' + } + ] + }, + { + id : 'configBox', + type : 'text', + label : editor.lang.uicolor.config, + onShow : function() + { + var color = editor.getUiColor(); + if ( color ) + this.setValue( + 'config.uiColor = "' + color + '"' + ); + } + } + ] + } + ] + } + ], + buttons : [ CKEDITOR.dialog.okButton ] + }; +} ); diff --git a/_source/plugins/uicolor/yui/assets/yui.css b/_source/plugins/uicolor/yui/assets/yui.css index f0f59b0..1bb7c75 100644 --- a/_source/plugins/uicolor/yui/assets/yui.css +++ b/_source/plugins/uicolor/yui/assets/yui.css @@ -1,15 +1,15 @@ -/*
-Copyright (c) 2009, Yahoo! Inc. All rights reserved.
-Code licensed under the BSD License:
-http://developer.yahoo.net/yui/license.txt
-version: 2.7.0
-*/
-.yui-h-slider,.yui-v-slider{position:relative;}.yui-h-slider .yui-slider-thumb,.yui-v-slider .yui-slider-thumb{position:absolute;cursor:default;}.yui-skin-sam .yui-h-slider{background:url(bg-h.gif) no-repeat 5px 0;height:28px;width:228px;}.yui-skin-sam .yui-h-slider .yui-slider-thumb{top:4px;}.yui-skin-sam .yui-v-slider{background:url(bg-v.gif) no-repeat 12px 0;height:228px;width:48px;}
-
-/*
-Copyright (c) 2009, Yahoo! Inc. All rights reserved.
-Code licensed under the BSD License:
-http://developer.yahoo.net/yui/license.txt
-version: 2.7.0
-*/
-#cke_uicolor_picker .yui-picker-panel{background:#e3e3e3;border-color:#888;}#cke_uicolor_picker .yui-picker-panel .hd{background-color:#ccc;font-size:100%;line-height:100%;border:1px solid #e3e3e3;font-weight:bold;overflow:hidden;padding:6px;color:#000;}#cke_uicolor_picker .yui-picker-panel .bd{background:#e8e8e8;margin:1px;height:200px;}#cke_uicolor_picker .yui-picker-panel .ft{background:#e8e8e8;margin:1px;padding:1px;}#cke_uicolor_picker .yui-picker{position:relative;}#cke_uicolor_picker .yui-picker-hue-thumb{cursor:default;width:18px;height:18px;top:-8px;left:-2px;z-index:9;position:absolute;}#cke_uicolor_picker .yui-picker-hue-bg{-moz-outline:none;outline:0 none;position:absolute;left:200px;height:183px;width:14px;background:url(hue_bg.png) no-repeat;top:4px;}#cke_uicolor_picker .yui-picker-bg{-moz-outline:none;outline:0 none;position:absolute;top:4px;left:4px;height:182px;width:182px;background-color:#F00;background-image:url(picker_mask.png);}*html #cke_uicolor_picker .yui-picker-bg{background-image:none;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='picker_mask.png',sizingMethod='scale');}#cke_uicolor_picker .yui-picker-mask{position:absolute;z-index:1;top:0;left:0;}#cke_uicolor_picker .yui-picker-thumb{cursor:default;width:11px;height:11px;z-index:9;position:absolute;top:-4px;left:-4px;}#cke_uicolor_picker .yui-picker-swatch{position:absolute;left:240px;top:4px;height:60px;width:55px;border:1px solid #888;}#cke_uicolor_picker .yui-picker-websafe-swatch{position:absolute;left:304px;top:4px;height:24px;width:24px;border:1px solid #888;}#cke_uicolor_picker .yui-picker-controls{position:absolute;top:72px;left:226px;font:1em monospace;}#cke_uicolor_picker .yui-picker-controls .hd{background:transparent;border-width:0!important;}#cke_uicolor_picker .yui-picker-controls .bd{height:100px;border-width:0!important;}#cke_uicolor_picker .yui-picker-controls ul{float:left;padding:0 2px 0 0;margin:0;}#cke_uicolor_picker .yui-picker-controls li{padding:2px;list-style:none;margin:0;}#cke_uicolor_picker .yui-picker-controls input{font-size:.85em;width:2.4em;}#cke_uicolor_picker .yui-picker-hex-controls{clear:both;padding:2px;}#cke_uicolor_picker .yui-picker-hex-controls input{width:4.6em;}#cke_uicolor_picker .yui-picker-controls a{font:1em arial,helvetica,clean,sans-serif;display:block;*display:inline-block;padding:0;color:#000;}
+/* +Copyright (c) 2009, Yahoo! Inc. All rights reserved. +Code licensed under the BSD License: +http://developer.yahoo.net/yui/license.txt +version: 2.7.0 +*/ +.yui-h-slider,.yui-v-slider{position:relative;}.yui-h-slider .yui-slider-thumb,.yui-v-slider .yui-slider-thumb{position:absolute;cursor:default;}.yui-skin-sam .yui-h-slider{background:url(bg-h.gif) no-repeat 5px 0;height:28px;width:228px;}.yui-skin-sam .yui-h-slider .yui-slider-thumb{top:4px;}.yui-skin-sam .yui-v-slider{background:url(bg-v.gif) no-repeat 12px 0;height:228px;width:48px;} + +/* +Copyright (c) 2009, Yahoo! Inc. All rights reserved. +Code licensed under the BSD License: +http://developer.yahoo.net/yui/license.txt +version: 2.7.0 +*/ +.cke_uicolor_picker .yui-picker-panel{background:#e3e3e3;border-color:#888;}.cke_uicolor_picker .yui-picker-panel .hd{background-color:#ccc;font-size:100%;line-height:100%;border:1px solid #e3e3e3;font-weight:bold;overflow:hidden;padding:6px;color:#000;}.cke_uicolor_picker .yui-picker-panel .bd{background:#e8e8e8;margin:1px;height:200px;}.cke_uicolor_picker .yui-picker-panel .ft{background:#e8e8e8;margin:1px;padding:1px;}.cke_uicolor_picker .yui-picker{position:relative;}.cke_uicolor_picker .yui-picker-hue-thumb{cursor:default;width:18px;height:18px;top:-8px;left:-2px;z-index:9;position:absolute;}.cke_uicolor_picker .yui-picker-hue-bg{-moz-outline:none;outline:0 none;position:absolute;left:200px;height:183px;width:14px;background:url(hue_bg.png) no-repeat;top:4px;}.cke_uicolor_picker .yui-picker-bg{-moz-outline:none;outline:0 none;position:absolute;top:4px;left:4px;height:182px;width:182px;background-color:#F00;background-image:url(picker_mask.png);}*html .cke_uicolor_picker .yui-picker-bg{background-image:none;filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='picker_mask.png',sizingMethod='scale');}.cke_uicolor_picker .yui-picker-mask{position:absolute;z-index:1;top:0;left:0;}.cke_uicolor_picker .yui-picker-thumb{cursor:default;width:11px;height:11px;z-index:9;position:absolute;top:-4px;left:-4px;}.cke_uicolor_picker .yui-picker-swatch{position:absolute;left:240px;top:4px;height:60px;width:55px;border:1px solid #888;}.cke_uicolor_picker .yui-picker-websafe-swatch{position:absolute;left:304px;top:4px;height:24px;width:24px;border:1px solid #888;}.cke_uicolor_picker .yui-picker-controls{position:absolute;top:72px;left:226px;font:1em monospace;}.cke_uicolor_picker .yui-picker-controls .hd{background:transparent;border-width:0!important;}.cke_uicolor_picker .yui-picker-controls .bd{height:100px;border-width:0!important;}.cke_uicolor_picker .yui-picker-controls ul{float:left;padding:0 2px 0 0;margin:0;}.cke_uicolor_picker .yui-picker-controls li{padding:2px;list-style:none;margin:0;}.cke_uicolor_picker .yui-picker-controls input{font-size:.85em;width:2.4em;}.cke_uicolor_picker .yui-picker-hex-controls{clear:both;padding:2px;}.cke_uicolor_picker .yui-picker-hex-controls input{width:4.6em;}.cke_uicolor_picker .yui-picker-controls a{font:1em arial,helvetica,clean,sans-serif;display:block;*display:inline-block;padding:0;color:#000;} diff --git a/_source/plugins/undo/plugin.js b/_source/plugins/undo/plugin.js index e843d23..635d6e4 100644 --- a/_source/plugins/undo/plugin.js +++ b/_source/plugins/undo/plugin.js @@ -1,519 +1,555 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-/**
- * @fileOverview Undo/Redo system for saving shapshot for document modification
- * and other recordable changes.
- */
-
-(function()
-{
- CKEDITOR.plugins.add( 'undo',
- {
- requires : [ 'selection', 'wysiwygarea' ],
-
- init : function( editor )
- {
- var undoManager = new UndoManager( editor );
-
- var undoCommand = editor.addCommand( 'undo',
- {
- exec : function()
- {
- if ( undoManager.undo() )
- {
- editor.selectionChange();
- this.fire( 'afterUndo' );
- }
- },
- state : CKEDITOR.TRISTATE_DISABLED,
- canUndo : false
- });
-
- var redoCommand = editor.addCommand( 'redo',
- {
- exec : function()
- {
- if ( undoManager.redo() )
- {
- editor.selectionChange();
- this.fire( 'afterRedo' );
- }
- },
- state : CKEDITOR.TRISTATE_DISABLED,
- canUndo : false
- });
-
- undoManager.onChange = function()
- {
- undoCommand.setState( undoManager.undoable() ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED );
- redoCommand.setState( undoManager.redoable() ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED );
- };
-
- function recordCommand( event )
- {
- // If the command hasn't been marked to not support undo.
- if ( undoManager.enabled && event.data.command.canUndo !== false )
- undoManager.save();
- }
-
- // We'll save snapshots before and after executing a command.
- editor.on( 'beforeCommandExec', recordCommand );
- editor.on( 'afterCommandExec', recordCommand );
-
- // Save snapshots before doing custom changes.
- editor.on( 'saveSnapshot', function()
- {
- undoManager.save();
- });
-
- // Registering keydown on every document recreation.(#3844)
- editor.on( 'contentDom', function()
- {
- editor.document.on( 'keydown', function( event )
- {
- // Do not capture CTRL hotkeys.
- if ( !event.data.$.ctrlKey && !event.data.$.metaKey )
- undoManager.type( event );
- });
- });
-
- // Always save an undo snapshot - the previous mode might have
- // changed editor contents.
- editor.on( 'beforeModeUnload', function()
- {
- editor.mode == 'wysiwyg' && undoManager.save( true );
- });
-
- // Make the undo manager available only in wysiwyg mode.
- editor.on( 'mode', function()
- {
- undoManager.enabled = editor.mode == 'wysiwyg';
- undoManager.onChange();
- });
-
- editor.ui.addButton( 'Undo',
- {
- label : editor.lang.undo,
- command : 'undo'
- });
-
- editor.ui.addButton( 'Redo',
- {
- label : editor.lang.redo,
- command : 'redo'
- });
-
- editor.resetUndo = function()
- {
- // Reset the undo stack.
- undoManager.reset();
-
- // Create the first image.
- editor.fire( 'saveSnapshot' );
- };
- }
- });
-
- // Gets a snapshot image which represent the current document status.
- function Image( editor )
- {
- var contents = editor.getSnapshot(),
- selection = contents && editor.getSelection();
-
- // In IE, we need to remove the expando attributes.
- CKEDITOR.env.ie && contents && ( contents = contents.replace( /\s+_cke_expando=".*?"/g, '' ) );
-
- this.contents = contents;
- this.bookmarks = selection && selection.createBookmarks2( true );
- }
-
- // Attributes that browser may changing them when setting via innerHTML.
- var protectedAttrs = /\b(?:href|src|name)="[^"]*?"/gi;
-
- Image.prototype =
- {
- equals : function( otherImage, contentOnly )
- {
- 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 )
- return true;
-
- var bookmarksA = this.bookmarks,
- bookmarksB = otherImage.bookmarks;
-
- if ( bookmarksA || bookmarksB )
- {
- if ( !bookmarksA || !bookmarksB || bookmarksA.length != bookmarksB.length )
- return false;
-
- for ( var i = 0 ; i < bookmarksA.length ; i++ )
- {
- var bookmarkA = bookmarksA[ i ],
- bookmarkB = bookmarksB[ i ];
-
- if (
- bookmarkA.startOffset != bookmarkB.startOffset ||
- bookmarkA.endOffset != bookmarkB.endOffset ||
- !CKEDITOR.tools.arrayCompare( bookmarkA.start, bookmarkB.start ) ||
- !CKEDITOR.tools.arrayCompare( bookmarkA.end, bookmarkB.end ) )
- {
- return false;
- }
- }
- }
-
- return true;
- }
- };
-
- /**
- * @constructor Main logic for Redo/Undo feature.
- */
- function UndoManager( editor )
- {
- this.editor = editor;
-
- // Reset the undo stack.
- 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 =
- {
- /**
- * Process undo system regard keystrikes.
- * @param {CKEDITOR.dom.event} event
- */
- type : function( event )
- {
- 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 navigationKeyCodes,
- wasReset = this.lastKeystroke in navigationKeyCodes,
-
- // Keystrokes which just introduce new contents.
- isContent = ( !isEditingKey && !isReset ),
-
- // Create undo snap for every different modifier key.
- modifierSnapshot = ( isEditingKey && !sameAsLastEditingKey ),
- // Create undo snap on the following cases:
- // 1. Just start to type .
- // 2. Typing some content after a modifier.
- // 3. Typing some content after make a visible selection.
- startedTyping = !( isModifierKey || this.typing )
- || ( isContent && ( wasEditingKey || wasReset ) );
-
- if ( startedTyping || modifierSnapshot )
- {
- var beforeTypeImage = new Image( this.editor );
-
- // Use setTimeout, so we give the necessary time to the
- // browser to insert the character into the DOM.
- CKEDITOR.tools.setTimeout( function()
- {
- var currentSnapshot = this.editor.getSnapshot();
-
- // In IE, we need to remove the expando attributes.
- if ( CKEDITOR.env.ie )
- currentSnapshot = currentSnapshot.replace( /\s+_cke_expando=".*?"/g, '' );
-
- 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 ) )
- // Drop future snapshots.
- this.snapshots.splice( this.index + 1, this.snapshots.length - this.index - 1 );
-
- this.hasUndo = true;
- this.hasRedo = false;
-
- this.typesCount = 1;
- this.modifiersCount = 1;
-
- this.onChange();
- }
- },
- 0, this
- );
- }
-
- this.lastKeystroke = keystroke;
-
- // Create undo snap after typed too much (over 25 times).
- if ( isEditingKey )
- {
- this.typesCount = 0;
- this.modifiersCount++;
-
- if ( this.modifiersCount > 25 )
- {
- this.save( false, null, false );
- this.modifiersCount = 1;
- }
- }
- else if ( !isReset )
- {
- this.modifiersCount = 0;
- this.typesCount++;
-
- if ( this.typesCount > 25 )
- {
- this.save( false, null, false );
- this.typesCount = 1;
- }
- }
-
- },
-
- reset : function() // Reset the undo stack.
- {
- /**
- * Remember last pressed key.
- */
- this.lastKeystroke = 0;
-
- /**
- * Stack for all the undo and redo snapshots, they're always created/removed
- * in consistency.
- */
- this.snapshots = [];
-
- /**
- * Current snapshot history index.
- */
- this.index = -1;
-
- this.limit = this.editor.config.undoStackSize;
-
- this.currentImage = null;
-
- this.hasUndo = false;
- this.hasRedo = false;
-
- this.resetType();
- },
-
- /**
- * Reset all states about typing.
- * @see UndoManager.type
- */
- resetType : function()
- {
- this.typing = false;
- delete this.lastKeystroke;
- this.typesCount = 0;
- this.modifiersCount = 0;
- },
- fireChange : function()
- {
- this.hasUndo = !!this.getNextImage( true );
- this.hasRedo = !!this.getNextImage( false );
- // Reset typing
- this.resetType();
- this.onChange();
- },
-
- /**
- * Save a snapshot of document image for later retrieve.
- */
- save : function( onContentOnly, image, autoFireChange )
- {
- var snapshots = this.snapshots;
-
- // Get a content image.
- if ( !image )
- image = new Image( this.editor );
-
- // Do nothing if it was not possible to retrieve an image.
- if ( image.contents === false )
- return false;
-
- // Check if this is a duplicate. In such case, do nothing.
- if ( this.currentImage && image.equals( this.currentImage, onContentOnly ) )
- return false;
-
- // Drop future snapshots.
- snapshots.splice( this.index + 1, snapshots.length - this.index - 1 );
-
- // If we have reached the limit, remove the oldest one.
- if ( snapshots.length == this.limit )
- snapshots.shift();
-
- // Add the new image, updating the current index.
- this.index = snapshots.push( image ) - 1;
-
- this.currentImage = image;
-
- if ( autoFireChange !== false )
- this.fireChange();
- return true;
- },
-
- restoreImage : function( image )
- {
- this.editor.loadSnapshot( image.contents );
-
- if ( image.bookmarks )
- this.editor.getSelection().selectBookmarks( image.bookmarks );
- else if ( CKEDITOR.env.ie )
- {
- // IE BUG: If I don't set the selection to *somewhere* after setting
- // document contents, then IE would create an empty paragraph at the bottom
- // the next time the document is modified.
- var $range = this.editor.document.getBody().$.createTextRange();
- $range.collapse( true );
- $range.select();
- }
-
- this.index = image.index;
-
- // Update current image with the actual editor
- // content, since actualy content may differ from
- // the original snapshot due to dom change. (#4622)
- this.snapshots.splice( this.index, 1, ( this.currentImage = new Image( this.editor ) ) );
-
- this.fireChange();
- },
-
- // Get the closest available image.
- getNextImage : function( isUndo )
- {
- var snapshots = this.snapshots,
- currentImage = this.currentImage,
- image, i;
-
- if ( currentImage )
- {
- if ( isUndo )
- {
- for ( i = this.index - 1 ; i >= 0 ; i-- )
- {
- image = snapshots[ i ];
- if ( !currentImage.equals( image, true ) )
- {
- image.index = i;
- return image;
- }
- }
- }
- else
- {
- for ( i = this.index + 1 ; i < snapshots.length ; i++ )
- {
- image = snapshots[ i ];
- if ( !currentImage.equals( image, true ) )
- {
- image.index = i;
- return image;
- }
- }
- }
- }
-
- return null;
- },
-
- /**
- * Check the current redo state.
- * @return {Boolean} Whether the document has previous state to
- * retrieve.
- */
- redoable : function()
- {
- return this.enabled && this.hasRedo;
- },
-
- /**
- * Check the current undo state.
- * @return {Boolean} Whether the document has future state to restore.
- */
- undoable : function()
- {
- return this.enabled && this.hasUndo;
- },
-
- /**
- * Perform undo on current index.
- */
- undo : function()
- {
- if ( this.undoable() )
- {
- this.save( true );
-
- var image = this.getNextImage( true );
- if ( image )
- return this.restoreImage( image ), true;
- }
-
- return false;
- },
-
- /**
- * Perform redo on current index.
- */
- redo : function()
- {
- if ( this.redoable() )
- {
- // Try to save. If no changes have been made, the redo stack
- // will not change, so it will still be redoable.
- this.save( true );
-
- // If instead we had changes, we can't redo anymore.
- if ( this.redoable() )
- {
- var image = this.getNextImage( false );
- if ( image )
- return this.restoreImage( image ), true;
- }
- }
-
- return false;
- }
- };
-})();
-
-/**
- * The number of undo steps to be saved. The higher this setting value the more
- * memory is used for it.
- * @type Number
- * @default 20
- * @example
- * config.undoStackSize = 50;
- */
-CKEDITOR.config.undoStackSize = 20;
-
-/**
- * Fired when the editor is about to save an undo snapshot. This event can be
- * fired by plugins and customizations to make the editor saving undo snapshots.
- * @name CKEDITOR.editor#saveSnapshot
- * @event
- */
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Undo/Redo system for saving shapshot for document modification + * and other recordable changes. + */ + +(function() +{ + CKEDITOR.plugins.add( 'undo', + { + requires : [ 'selection', 'wysiwygarea' ], + + init : function( editor ) + { + var undoManager = new UndoManager( editor ); + + var undoCommand = editor.addCommand( 'undo', + { + exec : function() + { + if ( undoManager.undo() ) + { + editor.selectionChange(); + this.fire( 'afterUndo' ); + } + }, + state : CKEDITOR.TRISTATE_DISABLED, + canUndo : false + }); + + var redoCommand = editor.addCommand( 'redo', + { + exec : function() + { + if ( undoManager.redo() ) + { + editor.selectionChange(); + this.fire( 'afterRedo' ); + } + }, + state : CKEDITOR.TRISTATE_DISABLED, + canUndo : false + }); + + undoManager.onChange = function() + { + undoCommand.setState( undoManager.undoable() ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED ); + redoCommand.setState( undoManager.redoable() ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED ); + }; + + function recordCommand( event ) + { + // If the command hasn't been marked to not support undo. + if ( undoManager.enabled && event.data.command.canUndo !== false ) + undoManager.save(); + } + + // We'll save snapshots before and after executing a command. + editor.on( 'beforeCommandExec', recordCommand ); + editor.on( 'afterCommandExec', recordCommand ); + + // Save snapshots before doing custom changes. + editor.on( 'saveSnapshot', function() + { + undoManager.save(); + }); + + // Registering keydown on every document recreation.(#3844) + editor.on( 'contentDom', function() + { + editor.document.on( 'keydown', function( event ) + { + // Do not capture CTRL hotkeys. + if ( !event.data.$.ctrlKey && !event.data.$.metaKey ) + undoManager.type( event ); + }); + }); + + // Always save an undo snapshot - the previous mode might have + // changed editor contents. + editor.on( 'beforeModeUnload', function() + { + editor.mode == 'wysiwyg' && undoManager.save( true ); + }); + + // Make the undo manager available only in wysiwyg mode. + editor.on( 'mode', function() + { + undoManager.enabled = editor.mode == 'wysiwyg'; + undoManager.onChange(); + }); + + editor.ui.addButton( 'Undo', + { + label : editor.lang.undo, + command : 'undo' + }); + + editor.ui.addButton( 'Redo', + { + label : editor.lang.redo, + command : 'redo' + }); + + editor.resetUndo = function() + { + // Reset the undo stack. + undoManager.reset(); + + // Create the first image. + editor.fire( 'saveSnapshot' ); + }; + + /** + * Update the undo stacks with any subsequent DOM changes after this call. + * @name CKEDITOR.editor#updateUndo + * @example + * function() + * { + * editor.fire( 'updateSnapshot' ); + * ... + * // Ask to include subsequent (in this call stack) DOM changes to be + * // considered as part of the first snapshot. + * editor.fire( 'updateSnapshot' ); + * editor.document.body.append(...); + * ... + * } + */ + editor.on( 'updateSnapshot', function() + { + if ( undoManager.currentImage && new Image( editor ).equals( undoManager.currentImage ) ) + setTimeout( function () { undoManager.update(); }, 0 ); + }); + } + }); + + CKEDITOR.plugins.undo = {}; + + /** + * Undo snapshot which represents the current document status. + * @name CKEDITOR.plugins.undo.Image + * @param editor The editor instance on which the image is created. + */ + var Image = CKEDITOR.plugins.undo.Image = function( editor ) + { + this.editor = editor; + var contents = editor.getSnapshot(), + selection = contents && editor.getSelection(); + + // In IE, we need to remove the expando attributes. + CKEDITOR.env.ie && contents && ( contents = contents.replace( /\s+_cke_expando=".*?"/g, '' ) ); + + this.contents = contents; + this.bookmarks = selection && selection.createBookmarks2( true ); + }; + + // Attributes that browser may changing them when setting via innerHTML. + var protectedAttrs = /\b(?:href|src|name)="[^"]*?"/gi; + + Image.prototype = + { + equals : function( otherImage, contentOnly ) + { + + 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 ) + return true; + + var bookmarksA = this.bookmarks, + bookmarksB = otherImage.bookmarks; + + if ( bookmarksA || bookmarksB ) + { + if ( !bookmarksA || !bookmarksB || bookmarksA.length != bookmarksB.length ) + return false; + + for ( var i = 0 ; i < bookmarksA.length ; i++ ) + { + var bookmarkA = bookmarksA[ i ], + bookmarkB = bookmarksB[ i ]; + + if ( + bookmarkA.startOffset != bookmarkB.startOffset || + bookmarkA.endOffset != bookmarkB.endOffset || + !CKEDITOR.tools.arrayCompare( bookmarkA.start, bookmarkB.start ) || + !CKEDITOR.tools.arrayCompare( bookmarkA.end, bookmarkB.end ) ) + { + return false; + } + } + } + + return true; + } + }; + + /** + * @constructor Main logic for Redo/Undo feature. + */ + function UndoManager( editor ) + { + this.editor = editor; + + // Reset the undo stack. + 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 = + { + /** + * Process undo system regard keystrikes. + * @param {CKEDITOR.dom.event} event + */ + type : function( event ) + { + 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 navigationKeyCodes, + wasReset = this.lastKeystroke in navigationKeyCodes, + + // Keystrokes which just introduce new contents. + isContent = ( !isEditingKey && !isReset ), + + // Create undo snap for every different modifier key. + modifierSnapshot = ( isEditingKey && !sameAsLastEditingKey ), + // Create undo snap on the following cases: + // 1. Just start to type . + // 2. Typing some content after a modifier. + // 3. Typing some content after make a visible selection. + startedTyping = !( isModifierKey || this.typing ) + || ( isContent && ( wasEditingKey || wasReset ) ); + + if ( startedTyping || modifierSnapshot ) + { + var beforeTypeImage = new Image( this.editor ); + + // Use setTimeout, so we give the necessary time to the + // browser to insert the character into the DOM. + CKEDITOR.tools.setTimeout( function() + { + var currentSnapshot = this.editor.getSnapshot(); + + // In IE, we need to remove the expando attributes. + if ( CKEDITOR.env.ie ) + currentSnapshot = currentSnapshot.replace( /\s+_cke_expando=".*?"/g, '' ); + + 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 ) ) + // Drop future snapshots. + this.snapshots.splice( this.index + 1, this.snapshots.length - this.index - 1 ); + + this.hasUndo = true; + this.hasRedo = false; + + this.typesCount = 1; + this.modifiersCount = 1; + + this.onChange(); + } + }, + 0, this + ); + } + + this.lastKeystroke = keystroke; + + // Create undo snap after typed too much (over 25 times). + if ( isEditingKey ) + { + this.typesCount = 0; + this.modifiersCount++; + + if ( this.modifiersCount > 25 ) + { + this.save( false, null, false ); + this.modifiersCount = 1; + } + } + else if ( !isReset ) + { + this.modifiersCount = 0; + this.typesCount++; + + if ( this.typesCount > 25 ) + { + this.save( false, null, false ); + this.typesCount = 1; + } + } + + }, + + reset : function() // Reset the undo stack. + { + /** + * Remember last pressed key. + */ + this.lastKeystroke = 0; + + /** + * Stack for all the undo and redo snapshots, they're always created/removed + * in consistency. + */ + this.snapshots = []; + + /** + * Current snapshot history index. + */ + this.index = -1; + + this.limit = this.editor.config.undoStackSize; + + this.currentImage = null; + + this.hasUndo = false; + this.hasRedo = false; + + this.resetType(); + }, + + /** + * Reset all states about typing. + * @see UndoManager.type + */ + resetType : function() + { + this.typing = false; + delete this.lastKeystroke; + this.typesCount = 0; + this.modifiersCount = 0; + }, + fireChange : function() + { + this.hasUndo = !!this.getNextImage( true ); + this.hasRedo = !!this.getNextImage( false ); + // Reset typing + this.resetType(); + this.onChange(); + }, + + /** + * Save a snapshot of document image for later retrieve. + */ + save : function( onContentOnly, image, autoFireChange ) + { + var snapshots = this.snapshots; + + // Get a content image. + if ( !image ) + image = new Image( this.editor ); + + // Do nothing if it was not possible to retrieve an image. + if ( image.contents === false ) + return false; + + // Check if this is a duplicate. In such case, do nothing. + if ( this.currentImage && image.equals( this.currentImage, onContentOnly ) ) + return false; + + // Drop future snapshots. + snapshots.splice( this.index + 1, snapshots.length - this.index - 1 ); + + // If we have reached the limit, remove the oldest one. + if ( snapshots.length == this.limit ) + snapshots.shift(); + + // Add the new image, updating the current index. + this.index = snapshots.push( image ) - 1; + + this.currentImage = image; + + if ( autoFireChange !== false ) + this.fireChange(); + return true; + }, + + restoreImage : function( image ) + { + this.editor.loadSnapshot( image.contents ); + + if ( image.bookmarks ) + this.editor.getSelection().selectBookmarks( image.bookmarks ); + else if ( CKEDITOR.env.ie ) + { + // IE BUG: If I don't set the selection to *somewhere* after setting + // document contents, then IE would create an empty paragraph at the bottom + // the next time the document is modified. + var $range = this.editor.document.getBody().$.createTextRange(); + $range.collapse( true ); + $range.select(); + } + + this.index = image.index; + + // Update current image with the actual editor + // content, since actualy content may differ from + // the original snapshot due to dom change. (#4622) + this.update(); + this.fireChange(); + }, + + // Get the closest available image. + getNextImage : function( isUndo ) + { + var snapshots = this.snapshots, + currentImage = this.currentImage, + image, i; + + if ( currentImage ) + { + if ( isUndo ) + { + for ( i = this.index - 1 ; i >= 0 ; i-- ) + { + image = snapshots[ i ]; + if ( !currentImage.equals( image, true ) ) + { + image.index = i; + return image; + } + } + } + else + { + for ( i = this.index + 1 ; i < snapshots.length ; i++ ) + { + image = snapshots[ i ]; + if ( !currentImage.equals( image, true ) ) + { + image.index = i; + return image; + } + } + } + } + + return null; + }, + + /** + * Check the current redo state. + * @return {Boolean} Whether the document has previous state to + * retrieve. + */ + redoable : function() + { + return this.enabled && this.hasRedo; + }, + + /** + * Check the current undo state. + * @return {Boolean} Whether the document has future state to restore. + */ + undoable : function() + { + return this.enabled && this.hasUndo; + }, + + /** + * Perform undo on current index. + */ + undo : function() + { + if ( this.undoable() ) + { + this.save( true ); + + var image = this.getNextImage( true ); + if ( image ) + return this.restoreImage( image ), true; + } + + return false; + }, + + /** + * Perform redo on current index. + */ + redo : function() + { + if ( this.redoable() ) + { + // Try to save. If no changes have been made, the redo stack + // will not change, so it will still be redoable. + this.save( true ); + + // If instead we had changes, we can't redo anymore. + if ( this.redoable() ) + { + var image = this.getNextImage( false ); + if ( image ) + return this.restoreImage( image ), true; + } + } + + return false; + }, + + /** + * Update the last snapshot of the undo stack with the current editor content. + */ + update : function() + { + this.snapshots.splice( this.index, 1, ( this.currentImage = new Image( this.editor ) ) ); + } + }; +})(); + +/** + * The number of undo steps to be saved. The higher this setting value the more + * memory is used for it. + * @type Number + * @default 20 + * @example + * config.undoStackSize = 50; + */ +CKEDITOR.config.undoStackSize = 20; + +/** + * Fired when the editor is about to save an undo snapshot. This event can be + * fired by plugins and customizations to make the editor saving undo snapshots. + * @name CKEDITOR.editor#saveSnapshot + * @event + */ diff --git a/_source/plugins/wysiwygarea/plugin.js b/_source/plugins/wysiwygarea/plugin.js index c256646..968a2e1 100644 --- a/_source/plugins/wysiwygarea/plugin.js +++ b/_source/plugins/wysiwygarea/plugin.js @@ -1,851 +1,978 @@ -/*
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
-For licensing, see LICENSE.html or http://ckeditor.com/license
-*/
-
-/**
- * @fileOverview The "wysiwygarea" plugin. It registers the "wysiwyg" editing
- * mode, which handles the main editing area space.
- */
-
-(function()
-{
- // 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[^>]*>| |\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;
-
- if ( this.dataProcessor )
- data = this.dataProcessor.toHtml( data );
-
- if ( CKEDITOR.env.ie )
- {
- var selIsLocked = selection.isLocked;
-
- if ( selIsLocked )
- selection.unlock();
-
- var $sel = selection.getNative();
- if ( $sel.type == 'Control' )
- $sel.clear();
- $sel.createRange().pasteHTML( data );
-
- if ( selIsLocked )
- this.getSelection().lock();
- }
- else
- this.document.$.execCommand( 'inserthtml', false, data );
-
- CKEDITOR.tools.setTimeout( function()
- {
- this.fire( 'saveSnapshot' );
- }, 0, this );
- }
- }
-
- function onInsertElement( evt )
- {
- if ( this.mode == 'wysiwyg' )
- {
- this.focus();
- this.fire( 'saveSnapshot' );
-
- var element = evt.data,
- elementName = element.getName(),
- isBlock = CKEDITOR.dtd.$block[ elementName ];
-
- var selection = this.getSelection(),
- ranges = selection.getRanges();
-
- var selIsLocked = selection.isLocked;
-
- if ( selIsLocked )
- selection.unlock();
-
- var range, clone, lastElement, bookmark;
-
- for ( var i = ranges.length - 1 ; i >= 0 ; i-- )
- {
- range = ranges[ i ];
-
- // Remove the original contents.
- range.deleteContents();
-
- clone = !i && element || element.clone( true );
-
- // If we're inserting a block at dtd-violated position, split
- // the parent blocks until we reach blockLimit.
- var current, dtd;
- if ( isBlock )
- {
- while ( ( current = range.getCommonAncestor( false, true ) )
- && ( 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)
- else if ( range.checkStartOfBlock()
- && range.checkEndOfBlock() )
- {
- range.setStartBefore( current );
- range.collapse( true );
- current.remove();
- }
- else
- range.splitBlock();
- }
- }
-
- // Insert the new node.
- range.insertNode( clone );
-
- // Save the last element reference so we can make the
- // selection later.
- if ( !lastElement )
- lastElement = clone;
- }
-
- range.moveToPosition( lastElement, CKEDITOR.POSITION_AFTER_END );
-
- var next = lastElement.getNextSourceNode( true );
- if ( next && next.type == CKEDITOR.NODE_ELEMENT )
- range.moveToElementEditStart( next );
-
- selection.selectRanges( [ range ] );
-
- if ( selIsLocked )
- this.getSelection().lock();
-
- // Save snaps after the whole execution completed.
- // This's a workaround for make DOM modification's happened after
- // 'insertElement' to be included either, e.g. Form-based dialogs' 'commitContents'
- // call.
- CKEDITOR.tools.setTimeout( function(){
- this.fire( 'saveSnapshot' );
- }, 0, this );
- }
- }
-
- // DOM modification here should not bother dirty flag.(#4385)
- function restoreDirty( editor )
- {
- if ( !editor.checkDirty() )
- 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)
- */
- function onSelectionChangeFixBody( evt )
- {
- var editor = evt.editor,
- path = evt.data.path,
- blockLimit = path.blockLimit,
- selection = evt.data.selection,
- range = selection.getRanges()[0],
- body = editor.document.getBody(),
- enterMode = editor.config.enterMode;
-
- // When enterMode set to block, we'll establing new paragraph only if we're
- // selecting inline contents right under body. (#3657)
- if ( enterMode != CKEDITOR.ENTER_BR
- && range.collapsed
- && blockLimit.getName() == 'body'
- && !path.block )
- {
- restoreDirty( editor );
- var fixedBlock = range.fixBlock( true,
- editor.config.enterMode == CKEDITOR.ENTER_DIV ? 'div' : 'p' );
-
- // For IE, we should remove any filler node which was introduced before.
- if ( CKEDITOR.env.ie )
- {
- var first = fixedBlock.getFirst( isNotEmpty );
- first && isNbsp( first ) && first.remove();
- }
-
- // 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 ( 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();
- }
-
- // 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 );
- if ( !CKEDITOR.env.ie )
- body.appendBogus();
- else
- body.append( editor.document.createText( '\xa0' ) );
- }
- }
-
- CKEDITOR.plugins.add( 'wysiwygarea',
- {
- requires : [ 'editingblock' ],
-
- init : function( editor )
- {
- var fixForBody = ( editor.config.enterMode != CKEDITOR.ENTER_BR )
- ? editor.config.enterMode == CKEDITOR.ENTER_DIV ? 'div' : 'p' : false;
-
- var frameLabel = editor.lang.editorTitle.replace( '%1', editor.name );
-
- editor.on( 'editingBlockReady', function()
- {
- var mainElement,
- iframe,
- isLoadingData,
- isPendingFocus,
- frameLoaded,
- fireMode;
-
-
- // Support for custom document.domain in IE.
- var isCustomDomain = CKEDITOR.env.isCustomDomain();
-
- // Creates the iframe that holds the editable document.
- var createIFrame = function( data )
- {
- if ( iframe )
- iframe.remove();
-
- frameLoaded = 0;
-
- var setDataFn = !CKEDITOR.env.gecko && CKEDITOR.tools.addFunction( function( doc )
- {
- CKEDITOR.tools.removeFunction( setDataFn );
- doc.write( data );
- });
-
- var srcScript =
- 'document.open();' +
-
- // The document domain must be set any time we
- // call document.open().
- ( isCustomDomain ? ( 'document.domain="' + document.domain + '";' ) : '' ) +
-
- // With FF, it's better to load the data on
- // iframe.load. (#3894,#4058)
- // But in FF, we still need the open()-close() call
- // to avoid HTTPS warnings.
- ( CKEDITOR.env.gecko ? '' : ( 'parent.CKEDITOR.tools.callFunction(' + setDataFn + ',document);' ) ) +
-
- 'document.close();';
-
- iframe = CKEDITOR.dom.element.createFromHtml( '<iframe' +
- ' style="width:100%;height:100%"' +
- ' frameBorder="0"' +
- ' src="javascript:void(function(){' + encodeURIComponent( srcScript ) + '}())"' +
- ' tabIndex="' + editor.tabIndex + '"' +
- ' allowTransparency="true"' +
- '></iframe>' );
-
- // With FF, it's better to load the data on iframe.load. (#3894,#4058)
- CKEDITOR.env.gecko && iframe.on( 'load', function( ev )
- {
- ev.removeListener();
-
- var doc = iframe.getFrameDocument().$;
-
- doc.open();
- doc.write( data );
- doc.close();
- });
-
- mainElement.append( iframe );
- };
-
- // 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" cke_temp="1">' +
- ( isCustomDomain ? ( 'document.domain="' + document.domain + '";' ) : '' ) +
- 'parent.CKEDITOR._["contentDomReady' + editor.name + '"]( window );' +
- '</script>';
-
- // Editing area bootstrap code.
- var contentDomReady = function( domWindow )
- {
- if ( frameLoaded )
- return;
- frameLoaded = 1;
-
- editor.fire( 'ariaWidget', iframe );
-
- var domDocument = domWindow.document,
- body = domDocument.body;
-
- // Remove this script from the DOM.
- var script = domDocument.getElementById( "cke_actscrpt" );
- script.parentNode.removeChild( script );
-
- delete CKEDITOR._[ 'contentDomReady' + editor.name ];
-
- body.spellcheck = !editor.config.disableNativeSpellChecker;
-
- if ( CKEDITOR.env.ie )
- {
- // Don't display the focus border.
- body.hideFocus = true;
-
- // Disable and re-enable the body to avoid IE from
- // taking the editing focus at startup. (#141 / #523)
- body.disabled = true;
- body.contentEditable = true;
- body.removeAttribute( 'disabled' );
- }
- else
- domDocument.designMode = 'on';
-
- // IE, Opera and Safari may not support it and throw
- // errors.
- try { domDocument.execCommand( 'enableObjectResizing', false, !editor.config.disableObjectResizing ) ; } catch(e) {}
- try { domDocument.execCommand( 'enableInlineTableEditing', false, !editor.config.disableNativeTableHandles ) ; } catch(e) {}
-
- domWindow = editor.window = new CKEDITOR.dom.window( domWindow );
- domDocument = editor.document = new CKEDITOR.dom.document( domDocument );
-
- // Gecko/Webkit need some help when selecting control type elements. (#3448)
- if ( !( CKEDITOR.env.ie || CKEDITOR.env.opera) )
- {
- domDocument.on( 'mousedown', function( ev )
- {
- var control = ev.data.getTarget();
- if ( control.is( 'img', 'hr', 'input', 'textarea', 'select' ) )
- editor.getSelection().selectElement( control );
- } );
- }
-
- // Webkit: avoid from editing form control elements content.
- if ( CKEDITOR.env.webkit )
- {
- // Prevent from tick checkbox/radiobox/select
- domDocument.on( 'click', function( ev )
- {
- if ( ev.data.getTarget().is( 'input', 'select' ) )
- ev.data.preventDefault();
- } );
-
- // Prevent from editig textfield/textarea value.
- domDocument.on( 'mouseup', function( ev )
- {
- if ( ev.data.getTarget().is( 'input', 'textarea' ) )
- ev.data.preventDefault();
- } );
- }
-
- // 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;
-
- focusTarget.on( 'blur', function()
- {
- editor.focusManager.blur();
- });
-
- focusTarget.on( 'focus', function()
- {
- // Gecko need a key event to 'wake up' the editing
- // ability when document is empty.(#3864)
- if ( CKEDITOR.env.gecko )
- {
- var first = body;
- while ( first.firstChild )
- first = first.firstChild;
-
- if ( !first.nextSibling
- && ( 'BR' == first.tagName )
- && first.hasAttribute( '_moz_editor_bogus_node' ) )
- {
- var keyEventSimulate = domDocument.$.createEvent( "KeyEvents" );
- keyEventSimulate.initKeyEvent( 'keypress', true, true, domWindow.$, false,
- false, false, false, 0, 32 );
- domDocument.$.dispatchEvent( keyEventSimulate );
- var bogusText = domDocument.getBody().getFirst() ;
- // Compensate the line maintaining <br> if enterMode is not block.
- if ( editor.config.enterMode == CKEDITOR.ENTER_BR )
- domDocument.createElement( 'br', { attributes: { '_moz_dirty' : "" } } )
- .replace( bogusText );
- else
- bogusText.remove();
- }
- }
-
- editor.focusManager.focus();
- });
-
- var keystrokeHandler = editor.keystrokeHandler;
- if ( keystrokeHandler )
- keystrokeHandler.attach( domDocument );
-
- if ( CKEDITOR.env.ie )
- {
- // Override keystrokes which should have deletion behavior
- // on control types in IE . (#4047)
- domDocument.on( 'keydown', function( evt )
- {
- var keyCode = evt.data.getKeystroke();
-
- // Backspace OR Delete.
- if ( keyCode in { 8 : 1, 46 : 1 } )
- {
- var sel = editor.getSelection(),
- control = sel.getSelectedElement();
-
- if ( control )
- {
- // Make undo snapshot.
- editor.fire( 'saveSnapshot' );
-
- // Delete any element that 'hasLayout' (e.g. hr,table) in IE8 will
- // break up the selection, safely manage it here. (#4795)
- var bookmark = sel.getRanges()[ 0 ].createBookmark();
- // Remove the control manually.
- control.remove();
- sel.selectBookmarks( [ bookmark ] );
-
- editor.fire( 'saveSnapshot' );
-
- evt.data.preventDefault();
- }
- }
- } );
-
- // 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.config.browserContextMenuOnCtrl !== false );
-
- setTimeout( function()
- {
- editor.fire( 'contentDom' );
-
- if ( fireMode )
- {
- editor.mode = 'wysiwyg';
- editor.fire( 'mode' );
- fireMode = false;
- }
-
- isLoadingData = false;
-
- if ( isPendingFocus )
- {
- editor.focus();
- isPendingFocus = false;
- }
- setTimeout( function()
- {
- editor.fire( 'dataReady' );
- }, 0 );
-
- /*
- * IE BUG: IE might have rendered the iframe with invisible contents.
- * (#3623). Push some inconsequential CSS style changes to force IE to
- * refresh it.
- *
- * Also, for some unknown reasons, short timeouts (e.g. 100ms) do not
- * fix the problem. :(
- */
- if ( CKEDITOR.env.ie )
- {
- setTimeout( function()
- {
- if ( editor.document )
- {
- var $body = editor.document.$.body;
- $body.runtimeStyle.marginBottom = '0px';
- $body.runtimeStyle.marginBottom = '';
- }
- }, 1000 );
- }
- },
- 0 );
- };
-
- editor.addMode( 'wysiwyg',
- {
- load : function( holderElement, data, isSnapshot )
- {
- mainElement = holderElement;
-
- if ( CKEDITOR.env.ie && CKEDITOR.env.quirks )
- holderElement.setStyle( 'position', 'relative' );
-
- // The editor data "may be dirty" after this
- // point.
- editor.mayBeDirty = true;
-
- fireMode = true;
-
- if ( isSnapshot )
- this.loadSnapshotData( data );
- else
- this.loadData( data );
- },
-
- loadData : function( data )
- {
- 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 );
-
- if ( fullPage )
- {
- // Check if the <body> tag is available.
- if ( !(/<body[\s|>]/).test( data ) )
- data = '<body>' + data;
-
- // 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>' ) ;
-
- // 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
- {
- data =
- config.docType +
- '<html dir="' + config.contentsLangDirection + '">' +
- '<title>' + frameLabel + '</title>' +
- '<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 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 ( config.ignoreEmptyParagraph )
- data = data.replace( emptyParagraphRegexp, '' );
-
- if ( docType )
- data = docType + '\n' + data;
-
- return data;
- },
-
- getSnapshotData : function()
- {
- return iframe.getFrameDocument().getBody().getHtml();
- },
-
- loadSnapshotData : function( data )
- {
- iframe.getFrameDocument().getBody().setHtml( data );
- },
-
- unload : function( holderElement )
- {
- editor.window = editor.document = iframe = mainElement = isPendingFocus = null;
-
- editor.fire( 'contentDomUnload' );
- },
-
- focus : function()
- {
- if ( isLoadingData )
- isPendingFocus = true;
- 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();
- }
- }
- });
-
- editor.on( 'insertHtml', onInsertHtml, null, null, 20 );
- editor.on( 'insertElement', onInsertElement, null, null, 20 );
- // Auto fixing on some document structure weakness to enhance usabilities. (#3190 and #3189)
- editor.on( 'selectionChange', onSelectionChangeFixBody, null, null, 1 );
- });
-
- var titleBackup;
- // Setting voice label as window title, backup the original one
- // and restore it before running into use.
- editor.on( 'contentDom', function ()
- {
- var title = editor.document.getElementsByTag( 'title' ).getItem( 0 );
- title.setAttribute( '_cke_title', editor.document.$.title );
- editor.document.$.title = frameLabel;
- });
-
-
- // 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(
- // Use 'span' instead of anything else to fly under the screen-reader radar. (#5049)
- '<span tabindex="-1" style="position:absolute; left:-10000" role="presentation"></span>' ) );
-
- ieFocusGrabber.on( 'focus', function()
- {
- editor.focus();
- } );
- } );
- }
- }
- });
-
- // Fixing Firefox 'Back-Forward Cache' break design mode. (#4514)
- if ( CKEDITOR.env.gecko )
- {
- ( function ()
- {
- var body = document.body;
-
- if ( !body )
- window.addEventListener( 'load', arguments.callee, false );
- else
- {
- body.setAttribute( 'onpageshow', body.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';
- }
- }
- } ) + ')' );
- }
- } )();
-
- }
-})();
-
-/**
- * Disables the ability of resize objects (image and tables) in the editing
- * area.
- * @type Boolean
- * @default false
- * @example
- * config.disableObjectResizing = true;
- */
-CKEDITOR.config.disableObjectResizing = false;
-
-/**
- * Disables the "table tools" offered natively by the browser (currently
- * Firefox only) to make quick table editing operations, like adding or
- * deleting rows and columns.
- * @type Boolean
- * @default true
- * @example
- * config.disableNativeTableHandles = false;
- */
-CKEDITOR.config.disableNativeTableHandles = true;
-
-/**
- * Disables the built-in spell checker while typing natively available in the
- * browser (currently Firefox and Safari only).<br /><br />
- *
- * Even if word suggestions will not appear in the CKEditor context menu, this
- * feature is useful to help quickly identifying misspelled words.<br /><br />
- *
- * This setting is currently compatible with Firefox only due to limitations in
- * other browsers.
- * @type Boolean
- * @default true
- * @example
- * config.disableNativeSpellChecker = false;
- */
-CKEDITOR.config.disableNativeSpellChecker = true;
-
-/**
- * Whether the editor must output an empty value ("") if it's contents is made
- * by an empty paragraph only.
- * @type Boolean
- * @default true
- * @example
- * config.ignoreEmptyParagraph = false;
- */
-CKEDITOR.config.ignoreEmptyParagraph = true;
-
-/**
- * Fired when data is loaded and ready for retrieval in an editor instance.
- * @name CKEDITOR.editor#dataReady
- * @event
- */
+/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview The "wysiwygarea" plugin. It registers the "wysiwyg" editing + * mode, which handles the main editing area space. + */ + +(function() +{ + // 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[^>]*>| |\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; + + if ( this.dataProcessor ) + data = this.dataProcessor.toHtml( data ); + + if ( CKEDITOR.env.ie ) + { + var selIsLocked = selection.isLocked; + + if ( selIsLocked ) + selection.unlock(); + + var $sel = selection.getNative(); + if ( $sel.type == 'Control' ) + $sel.clear(); + $sel.createRange().pasteHTML( data ); + + if ( selIsLocked ) + this.getSelection().lock(); + } + else + this.document.$.execCommand( 'inserthtml', false, data ); + + CKEDITOR.tools.setTimeout( function() + { + this.fire( 'saveSnapshot' ); + }, 0, this ); + } + } + + function onInsertElement( evt ) + { + if ( this.mode == 'wysiwyg' ) + { + this.focus(); + this.fire( 'saveSnapshot' ); + + var element = evt.data, + elementName = element.getName(), + isBlock = CKEDITOR.dtd.$block[ elementName ]; + + var selection = this.getSelection(), + ranges = selection.getRanges(); + + var selIsLocked = selection.isLocked; + + if ( selIsLocked ) + selection.unlock(); + + var range, clone, lastElement, bookmark; + + for ( var i = ranges.length - 1 ; i >= 0 ; i-- ) + { + range = ranges[ i ]; + + // Remove the original contents. + range.deleteContents(); + + clone = !i && element || element.clone( true ); + + // If we're inserting a block at dtd-violated position, split + // the parent blocks until we reach blockLimit. + var current, dtd; + if ( isBlock ) + { + while ( ( current = range.getCommonAncestor( false, true ) ) + && ( 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) + else if ( range.checkStartOfBlock() + && range.checkEndOfBlock() ) + { + range.setStartBefore( current ); + range.collapse( true ); + current.remove(); + } + else + range.splitBlock(); + } + } + + // Insert the new node. + range.insertNode( clone ); + + // Save the last element reference so we can make the + // selection later. + if ( !lastElement ) + lastElement = clone; + } + + range.moveToPosition( lastElement, CKEDITOR.POSITION_AFTER_END ); + + var next = lastElement.getNextSourceNode( true ); + if ( next && next.type == CKEDITOR.NODE_ELEMENT ) + range.moveToElementEditStart( next ); + + selection.selectRanges( [ range ] ); + + if ( selIsLocked ) + this.getSelection().lock(); + + // Save snaps after the whole execution completed. + // This's a workaround for make DOM modification's happened after + // 'insertElement' to be included either, e.g. Form-based dialogs' 'commitContents' + // call. + CKEDITOR.tools.setTimeout( function(){ + this.fire( 'saveSnapshot' ); + }, 0, this ); + } + } + + // DOM modification here should not bother dirty flag.(#4385) + function restoreDirty( editor ) + { + if ( !editor.checkDirty() ) + 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)$/ ); + } + + function restoreSelection( selection ) + { + if ( selection.isLocked ) + { + selection.unlock(); + setTimeout( function() { selection.lock(); }, 0 ); + } + } + + function isBlankParagraph( block ) + { + return block.getOuterHtml().match( emptyParagraphRegexp ); + } + + isNotWhitespace = CKEDITOR.dom.walker.whitespaces( true ); + + /** + * Auto-fixing block-less content by wrapping paragraph (#3190), prevent + * non-exitable-block by padding extra br.(#3189) + */ + function onSelectionChangeFixBody( evt ) + { + var editor = evt.editor, + path = evt.data.path, + blockLimit = path.blockLimit, + selection = evt.data.selection, + range = selection.getRanges()[0], + body = editor.document.getBody(), + enterMode = editor.config.enterMode; + + // When enterMode set to block, we'll establing new paragraph only if we're + // selecting inline contents right under body. (#3657) + if ( enterMode != CKEDITOR.ENTER_BR + && range.collapsed + && blockLimit.getName() == 'body' + && !path.block ) + { + editor.fire( 'updateSnapshot' ); + restoreDirty( editor ); + CKEDITOR.env.ie && restoreSelection( selection ); + + var fixedBlock = range.fixBlock( true, + editor.config.enterMode == CKEDITOR.ENTER_DIV ? 'div' : 'p' ); + + // For IE, we should remove any filler node which was introduced before. + if ( CKEDITOR.env.ie ) + { + var first = fixedBlock.getFirst( isNotEmpty ); + first && isNbsp( first ) && first.remove(); + } + + // If the fixed block is actually blank and is already followed by an exitable blank + // block, we should revert the fix and move into the existed one. (#3684) + if ( isBlankParagraph( fixedBlock ) ) + { + var previousElement = fixedBlock.getPrevious( isNotWhitespace ), + nextElement = fixedBlock.getNext( isNotWhitespace ); + + if ( previousElement && previousElement.getName + && !( previousElement.getName() in nonExitableElementNames ) + && isBlankParagraph( previousElement ) + && range.moveToElementEditStart( previousElement ) + || nextElement && nextElement.getName + && !( nextElement.getName() in nonExitableElementNames ) + && isBlankParagraph( nextElement ) + && range.moveToElementEditStart( nextElement ) ) + { + fixedBlock.remove(); + } + } + + range.select(); + // Notify non-IE that selection has changed. + if ( !CKEDITOR.env.ie ) + editor.selectionChange(); + } + + // 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 walkerRange = new CKEDITOR.dom.range( editor.document ), + walker = new CKEDITOR.dom.walker( walkerRange ); + walkerRange.selectNodeContents( body ); + walker.evaluator = function( node ) + { + return node.type == CKEDITOR.NODE_ELEMENT && ( node.getName() in nonExitableElementNames ); + }; + walker.guard = function( node, isMoveout ) + { + return !( ( node.type == CKEDITOR.NODE_TEXT && isNotWhitespace( node ) ) || isMoveout ); + }; + + if ( walker.previous() ) + { + editor.fire( 'updateSnapshot' ); + restoreDirty( editor ); + CKEDITOR.env.ie && restoreSelection( selection ); + + var paddingBlock; + if ( enterMode != CKEDITOR.ENTER_BR ) + paddingBlock = body.append( new CKEDITOR.dom.element( enterMode == CKEDITOR.ENTER_P ? 'p' : 'div' ) ); + else + paddingBlock = body; + + if ( !CKEDITOR.env.ie ) + paddingBlock.appendBogus(); + } + } + + CKEDITOR.plugins.add( 'wysiwygarea', + { + requires : [ 'editingblock' ], + + init : function( editor ) + { + var fixForBody = ( editor.config.enterMode != CKEDITOR.ENTER_BR ) + ? editor.config.enterMode == CKEDITOR.ENTER_DIV ? 'div' : 'p' : false; + + var frameLabel = editor.lang.editorTitle.replace( '%1', editor.name ); + + var contentDomReadyHandler; + editor.on( 'editingBlockReady', function() + { + var mainElement, + iframe, + isLoadingData, + isPendingFocus, + frameLoaded, + fireMode; + + + // Support for custom document.domain in IE. + var isCustomDomain = CKEDITOR.env.isCustomDomain(); + + // Creates the iframe that holds the editable document. + var createIFrame = function( data ) + { + if ( iframe ) + iframe.remove(); + + + var srcScript = + 'document.open();' + + + // The document domain must be set any time we + // call document.open(). + ( isCustomDomain ? ( 'document.domain="' + document.domain + '";' ) : '' ) + + + 'document.close();'; + + iframe = CKEDITOR.dom.element.createFromHtml( '<iframe' + + ' style="width:100%;height:100%"' + + ' frameBorder="0"' + + ' title="' + frameLabel + '"' + + // With IE, the custom domain has to be taken care at first, + // for other browers, the 'src' attribute should be left empty to + // trigger iframe's 'load' event. + ' src="' + ( CKEDITOR.env.ie ? 'javascript:void(function(){' + encodeURIComponent( srcScript ) + '}())' : '' ) + '"' + + ' tabIndex="' + ( CKEDITOR.env.webkit? -1 : editor.tabIndex ) + '"' + + ' allowTransparency="true"' + + '></iframe>' ); + + // With FF, it's better to load the data on iframe.load. (#3894,#4058) + iframe.on( 'load', function( ev ) + { + frameLoaded = 1; + ev.removeListener(); + + var doc = iframe.getFrameDocument().$; + + // Don't leave any history log in IE. (#5657) + doc.open( "text/html","replace" ); + doc.write( data ); + doc.close(); + }); + + mainElement.append( iframe ); + }; + + // The script that launches the bootstrap logic on 'domReady', so the document + // is fully editable even before the editing iframe is fully loaded (#4455). + contentDomReadyHandler = CKEDITOR.tools.addFunction( contentDomReady ); + var activationScript = + '<script id="cke_actscrpt" type="text/javascript" cke_temp="1">' + + ( isCustomDomain ? ( 'document.domain="' + document.domain + '";' ) : '' ) + + 'window.parent.CKEDITOR.tools.callFunction( ' + contentDomReadyHandler + ', window );' + + '</script>'; + + // Editing area bootstrap code. + function contentDomReady( domWindow ) + { + if ( !frameLoaded ) + return; + frameLoaded = 0; + + editor.fire( 'ariaWidget', iframe ); + + var domDocument = domWindow.document, + body = domDocument.body; + + // Remove this script from the DOM. + var script = domDocument.getElementById( "cke_actscrpt" ); + script.parentNode.removeChild( script ); + + body.spellcheck = !editor.config.disableNativeSpellChecker; + + if ( CKEDITOR.env.ie ) + { + // Don't display the focus border. + body.hideFocus = true; + + // Disable and re-enable the body to avoid IE from + // taking the editing focus at startup. (#141 / #523) + body.disabled = true; + body.contentEditable = true; + body.removeAttribute( 'disabled' ); + } + else + { + // Avoid opening design mode in a frame window thread, + // which will cause host page scrolling.(#4397) + setTimeout( function() + { + // Prefer 'contentEditable' instead of 'designMode'. (#3593) + if ( CKEDITOR.env.gecko && CKEDITOR.env.version >= 10900 + || CKEDITOR.env.opera ) + domDocument.$.body.contentEditable = true; + else if ( CKEDITOR.env.webkit ) + domDocument.$.body.parentNode.contentEditable = true; + else + domDocument.$.designMode = 'on'; + }, 0 ); + } + + // Gecko need a key event to 'wake up' the editing + // ability when document is empty.(#3864) + if ( CKEDITOR.env.gecko && !body.childNodes.length ) + { + setTimeout( function() + { + restoreDirty( editor ); + + // Simulating keyboard character input by dispatching a keydown of white-space text. + var keyEventSimulate = domDocument.$.createEvent( "KeyEvents" ); + keyEventSimulate.initKeyEvent( 'keypress', true, true, domWindow.$, false, + false, false, false, 0, 32 ); + domDocument.$.dispatchEvent( keyEventSimulate ); + + // Restore the original document status by placing the cursor before a bogus br created (#5021). + domDocument.createElement( 'br', { attributes: { '_moz_editor_bogus_node' : 'TRUE', '_moz_dirty' : "" } } ) + .replace( domDocument.getBody().getFirst() ); + var nativeRange = new CKEDITOR.dom.range( domDocument ); + nativeRange.setStartAt( new CKEDITOR.dom.element( body ) , CKEDITOR.POSITION_AFTER_START ); + nativeRange.select(); + }, 0 ); + } + + // IE, Opera and Safari may not support it and throw + // errors. + try { domDocument.execCommand( 'enableObjectResizing', false, !editor.config.disableObjectResizing ) ; } catch(e) {} + try { domDocument.execCommand( 'enableInlineTableEditing', false, !editor.config.disableNativeTableHandles ) ; } catch(e) {} + + domWindow = editor.window = new CKEDITOR.dom.window( domWindow ); + domDocument = editor.document = new CKEDITOR.dom.document( domDocument ); + + domDocument.on( 'dblclick', function( evt ) + { + var element = evt.data.getTarget(), + data = { element : element, dialog : '' }; + editor.fire( 'doubleclick', data ); + data.dialog && editor.openDialog( data.dialog ); + }); + + // Gecko/Webkit need some help when selecting control type elements. (#3448) + if ( !( CKEDITOR.env.ie || CKEDITOR.env.opera) ) + { + domDocument.on( 'mousedown', function( ev ) + { + var control = ev.data.getTarget(); + if ( control.is( 'img', 'hr', 'input', 'textarea', 'select' ) ) + editor.getSelection().selectElement( control ); + } ); + } + + // Webkit: avoid from editing form control elements content. + if ( CKEDITOR.env.webkit ) + { + // Prevent from tick checkbox/radiobox/select + domDocument.on( 'click', function( ev ) + { + if ( ev.data.getTarget().is( 'input', 'select' ) ) + ev.data.preventDefault(); + } ); + + // Prevent from editig textfield/textarea value. + domDocument.on( 'mouseup', function( ev ) + { + if ( ev.data.getTarget().is( 'input', 'textarea' ) ) + ev.data.preventDefault(); + } ); + } + + // 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' + || CKEDITOR.env.gecko + || CKEDITOR.env.opera ) + { + 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 ) ) + { + if ( CKEDITOR.env.gecko && CKEDITOR.env.version >= 10900 ) + blinkCursor(); + focusGrabber.focus(); + } + } ); + } + + domWindow.on( 'blur', function() + { + editor.focusManager.blur(); + }); + + domWindow.on( 'focus', function() + { + var doc = editor.document; + + if ( CKEDITOR.env.gecko && CKEDITOR.env.version >= 10900 ) + blinkCursor(); + else if ( CKEDITOR.env.opera ) + doc.getBody().focus(); + else if ( CKEDITOR.env.webkit ) + { + // Selection will get lost after move focus + // to document element, save it first. + var sel = editor.getSelection(), + type = sel.getType(), + range = ( type != CKEDITOR.SELECTION_NONE ) && sel.getRanges()[ 0 ]; + + doc.getDocumentElement().focus(); + range && range.select(); + } + + editor.focusManager.focus(); + }); + + var keystrokeHandler = editor.keystrokeHandler; + if ( keystrokeHandler ) + keystrokeHandler.attach( domDocument ); + + if ( CKEDITOR.env.ie ) + { + domDocument.getDocumentElement().addClass( domDocument.$.compatMode ); + // Override keystrokes which should have deletion behavior + // on control types in IE . (#4047) + domDocument.on( 'keydown', function( evt ) + { + var keyCode = evt.data.getKeystroke(); + + // Backspace OR Delete. + if ( keyCode in { 8 : 1, 46 : 1 } ) + { + var sel = editor.getSelection(), + control = sel.getSelectedElement(); + + if ( control ) + { + // Make undo snapshot. + editor.fire( 'saveSnapshot' ); + + // Delete any element that 'hasLayout' (e.g. hr,table) in IE8 will + // break up the selection, safely manage it here. (#4795) + var bookmark = sel.getRanges()[ 0 ].createBookmark(); + // Remove the control manually. + control.remove(); + sel.selectBookmarks( [ bookmark ] ); + + editor.fire( 'saveSnapshot' ); + + evt.data.preventDefault(); + } + } + } ); + + // 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.config.browserContextMenuOnCtrl !== false ); + + setTimeout( function() + { + editor.fire( 'contentDom' ); + + if ( fireMode ) + { + editor.mode = 'wysiwyg'; + editor.fire( 'mode' ); + fireMode = false; + } + + isLoadingData = false; + + if ( isPendingFocus ) + { + editor.focus(); + isPendingFocus = false; + } + setTimeout( function() + { + editor.fire( 'dataReady' ); + }, 0 ); + + /* + * IE BUG: IE might have rendered the iframe with invisible contents. + * (#3623). Push some inconsequential CSS style changes to force IE to + * refresh it. + * + * Also, for some unknown reasons, short timeouts (e.g. 100ms) do not + * fix the problem. :( + */ + if ( CKEDITOR.env.ie ) + { + setTimeout( function() + { + if ( editor.document ) + { + var $body = editor.document.$.body; + $body.runtimeStyle.marginBottom = '0px'; + $body.runtimeStyle.marginBottom = ''; + } + }, 1000 ); + } + }, + 0 ); + } + + editor.addMode( 'wysiwyg', + { + load : function( holderElement, data, isSnapshot ) + { + mainElement = holderElement; + + if ( CKEDITOR.env.ie && CKEDITOR.env.quirks ) + holderElement.setStyle( 'position', 'relative' ); + + // The editor data "may be dirty" after this + // point. + editor.mayBeDirty = true; + + fireMode = true; + + if ( isSnapshot ) + this.loadSnapshotData( data ); + else + this.loadData( data ); + }, + + loadData : function( data ) + { + 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 ); + + if ( fullPage ) + { + // Check if the <body> tag is available. + if ( !(/<body[\s|>]/).test( data ) ) + data = '<body>' + data; + + // 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>' ) ; + else if ( !(/<title[\s|>]/).test( data ) ) + data = data.replace( /<head[^>]*>/, '$&<title></title>' ) ; + + // 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 + { + data = + config.docType + + '<html dir="' + config.contentsLangDirection + '"' + + ' lang="' + ( config.contentsLanguage || editor.langCode ) + '">' + + '<head>' + + '<title>' + frameLabel + '</title>' + + baseTag + + headExtra + + '</head>' + + '<body' + ( config.bodyId ? ' id="' + config.bodyId + '"' : '' ) + + ( config.bodyClass ? ' class="' + config.bodyClass + '"' : '' ) + + '>' + + data + + '</html>'; + } + + data += activationScript; + + + // The iframe is recreated on each call of setData, so we need to clear DOM objects + this.onDispose(); + createIFrame( data ); + }, + + getData : function() + { + 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 ( config.ignoreEmptyParagraph ) + data = data.replace( emptyParagraphRegexp, '' ); + + if ( docType ) + data = docType + '\n' + data; + + return data; + }, + + getSnapshotData : function() + { + return iframe.getFrameDocument().getBody().getHtml(); + }, + + loadSnapshotData : function( data ) + { + iframe.getFrameDocument().getBody().setHtml( data ); + }, + + onDispose : function() + { + if ( !editor.document ) + return; + + editor.document.getDocumentElement().clearCustomData(); + editor.document.getBody().clearCustomData(); + + editor.window.clearCustomData(); + editor.document.clearCustomData(); + + iframe.clearCustomData(); + }, + + unload : function( holderElement ) + { + this.onDispose(); + + editor.window = editor.document = iframe = mainElement = isPendingFocus = null; + + editor.fire( 'contentDomUnload' ); + }, + + focus : function() + { + if ( isLoadingData ) + isPendingFocus = true; + else if ( editor.window ) + { + editor.window.focus(); + + editor.selectionChange(); + } + } + }); + + editor.on( 'insertHtml', onInsertHtml, null, null, 20 ); + editor.on( 'insertElement', onInsertElement, null, null, 20 ); + // Auto fixing on some document structure weakness to enhance usabilities. (#3190 and #3189) + editor.on( 'selectionChange', onSelectionChangeFixBody, null, null, 1 ); + }); + + var titleBackup; + // Setting voice label as window title, backup the original one + // and restore it before running into use. + editor.on( 'contentDom', function () + { + var title = editor.document.getElementsByTag( 'title' ).getItem( 0 ); + title.setAttribute( '_cke_title', editor.document.$.title ); + editor.document.$.title = frameLabel; + }); + + // IE8 stricts mode doesn't have 'contentEditable' in effect + // on element unless it has layout. (#5562) + if ( CKEDITOR.env.ie8Compat ) + { + editor.addCss( 'html.CSS1Compat [contenteditable=false]{ min-height:0 !important;}' ); + + var selectors = []; + for ( var tag in CKEDITOR.dtd.$removeEmpty ) + selectors.push( 'html.CSS1Compat ' + tag + '[contenteditable=false]' ); + editor.addCss( selectors.join( ',' ) + '{ display:inline-block;}' ); + } + + // Switch on design mode for a short while and close it after then. + function blinkCursor( retry ) + { + CKEDITOR.tools.tryThese( + function() + { + editor.document.$.designMode = 'on'; + setTimeout( function () + { + editor.document.$.designMode = 'off'; + editor.document.getBody().focus(); + }, 50 ); + }, + function() + { + // The above call is known to fail when parent DOM + // tree layout changes may break design mode. (#5782) + // Refresh the 'contentEditable' is a cue to this. + editor.document.$.designMode = 'off'; + var body = editor.document.getBody(); + body.setAttribute( 'contentEditable', false ); + body.setAttribute( 'contentEditable', true ); + // Try it again once.. + !retry && blinkCursor( 1 ); + }); + } + + // Create an invisible element to grab focus. + if ( CKEDITOR.env.gecko || CKEDITOR.env.ie || CKEDITOR.env.opera ) + { + var focusGrabber; + editor.on( 'uiReady', function() + { + focusGrabber = editor.container.append( CKEDITOR.dom.element.createFromHtml( + // Use 'span' instead of anything else to fly under the screen-reader radar. (#5049) + '<span tabindex="-1" style="position:absolute; left:-10000" role="presentation"></span>' ) ); + + focusGrabber.on( 'focus', function() + { + editor.focus(); + } ); + } ); + editor.on( 'destroy', function() + { + CKEDITOR.tools.removeFunction( contentDomReadyHandler ); + focusGrabber.clearCustomData(); + } ); + } + + // Disable form elements editing mode provided by some browers. (#5746) + editor.on( 'insertElement', function ( evt ) + { + var element = evt.data; + if ( element.type = CKEDITOR.NODE_ELEMENT + && ( element.is( 'input' ) || element.is( 'textarea' ) ) ) + { + element.setAttribute( 'contentEditable', false ); + } + }); + + } + }); + + // Fixing Firefox 'Back-Forward Cache' break design mode. (#4514) + if ( CKEDITOR.env.gecko ) + { + ( function () + { + var body = document.body; + + if ( !body ) + window.addEventListener( 'load', arguments.callee, false ); + else + { + var currentHandler = body.getAttribute( 'onpageshow' ); + body.setAttribute( 'onpageshow', ( currentHandler ? currentHandler + ';' : '') + + 'event.persisted && (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";' + + ' }' + + '}' + + '})();' ); + } + } )(); + + } +})(); + +/** + * Disables the ability of resize objects (image and tables) in the editing + * area. + * @type Boolean + * @default false + * @example + * config.disableObjectResizing = true; + */ +CKEDITOR.config.disableObjectResizing = false; + +/** + * Disables the "table tools" offered natively by the browser (currently + * Firefox only) to make quick table editing operations, like adding or + * deleting rows and columns. + * @type Boolean + * @default true + * @example + * config.disableNativeTableHandles = false; + */ +CKEDITOR.config.disableNativeTableHandles = true; + +/** + * Disables the built-in spell checker while typing natively available in the + * browser (currently Firefox and Safari only).<br /><br /> + * + * Even if word suggestions will not appear in the CKEditor context menu, this + * feature is useful to help quickly identifying misspelled words.<br /><br /> + * + * This setting is currently compatible with Firefox only due to limitations in + * other browsers. + * @type Boolean + * @default true + * @example + * config.disableNativeSpellChecker = false; + */ +CKEDITOR.config.disableNativeSpellChecker = true; + +/** + * Whether the editor must output an empty value ("") if it's contents is made + * by an empty paragraph only. + * @type Boolean + * @default true + * @example + * config.ignoreEmptyParagraph = false; + */ +CKEDITOR.config.ignoreEmptyParagraph = true; + +/** + * Fired when data is loaded and ready for retrieval in an editor instance. + * @name CKEDITOR.editor#dataReady + * @event + */ |
