Index: /CKReleaser/trunk/_source/includes/io.js
===================================================================
--- /CKReleaser/trunk/_source/includes/io.js	(revision 4588)
+++ /CKReleaser/trunk/_source/includes/io.js	(revision 4589)
@@ -192,7 +192,7 @@
 		},
 
-		copy : function( sourceLocation, targetLocation )
-		{
-			if ( CKRELEASER.release.isIgnoredPath( sourceLocation.getAbsolutePath() ) )
+		copy : function( sourceLocation, targetLocation, extraIgnoredPaths )
+		{
+			if ( CKRELEASER.release.isIgnoredPath( sourceLocation.getAbsolutePath(), extraIgnoredPaths ) )
 				return;
 
@@ -210,5 +210,10 @@
 				for ( var i = 0 ; i < children.length ; i++ )
 				{
-					this.copy( new File( sourceLocation, children[i] ), new File( targetLocation, children[i] ) );
+					this.copy( new File( sourceLocation, children[i] ), new File( targetLocation, children[i] ), extraIgnoredPaths );
+				}
+
+				if ( !targetLocation.list().length )
+				{
+					targetLocation['delete']();
 				}
 			}
Index: /CKReleaser/trunk/_source/includes/releaser.js
===================================================================
--- /CKReleaser/trunk/_source/includes/releaser.js	(revision 4588)
+++ /CKReleaser/trunk/_source/includes/releaser.js	(revision 4589)
@@ -35,6 +35,15 @@
 	}
 
-	release.prototype.isIgnoredPath = function( path )
-	{
+	release.prototype.isIgnoredPath = function( path, extraIgnoredPaths )
+	{
+		if ( extraIgnoredPaths && extraIgnoredPaths.length )
+		{
+			for ( var i = 0 ; i < extraIgnoredPaths.length ; i++ )
+			{
+				if ( path.replace( "\\", "/" ).endsWith( extraIgnoredPaths[i] ) )
+					return true;
+			}
+		}
+
 		if ( !this.ignore )
 			return false;
@@ -89,11 +98,25 @@
 	function copyFiles()
 	{
+		var sourceLocation, targetLocation;
+
 		for ( var i = 0 ; i < CKRELEASER.release.copy.length ; i++ )
 		{
 			try
 			{
-				var sourceLocation = new File( CKRELEASER.releaseDir, CKRELEASER.release.copy[i].source );
-				var targetLocation = new File( CKRELEASER.releaseDir, CKRELEASER.release.copy[i].target );
-				CKRELEASER.io.copy( sourceLocation, targetLocation );
+				var ignoredFiles = [];
+				if ( CKRELEASER.release.copy[i].ignore )
+				{
+					var packFile = new File( CKRELEASER.sourceDir, CKRELEASER.release.copy[i].ignore.sourcePackage );
+					if ( !packFile.exists() )
+						throw 'Package file ' + CKRELEASER.release.packages[i] + ' defined in ' + CKRELEASER.releaseFile
+						  + ' not found (' + packFile.getCanonicalPath() + ')';
+					
+					var object = loadFile( packFile.getCanonicalPath() );
+					ignoredFiles = eval('object.' + CKRELEASER.release.copy[i].ignore.files) || [];
+				}
+
+				sourceLocation = new File( CKRELEASER.releaseDir, CKRELEASER.release.copy[i].source );
+				targetLocation = new File( CKRELEASER.releaseDir, CKRELEASER.release.copy[i].target );
+				CKRELEASER.io.copy( sourceLocation, targetLocation, ignoredFiles );
 			}
 			catch ( e )
@@ -335,4 +358,27 @@
 	}
 
+	function loadFile( filePath )
+	{
+		var file = new File( filePath );
+
+		var releaseCode = 'var object = { ' + CKRELEASER.io.readFile( file ) + '\n};';
+		var cx = Context.enter(), scope = cx.initStandardObjects();
+
+		try
+		{
+			cx.evaluateString( scope, releaseCode, file.getName(), 1, null );
+		}
+		catch ( e )
+		{
+			if ( CKRELEASER.verbose && typeof ( e.rhinoException ) != 'undefined' )
+				e.rhinoException.printStackTrace();
+
+			error( '\nParsing release file failed:\n    Error: ' + e.message + '\n    File: ' + file.getAbsolutePath()
+					+ '\n    Line: ' + e.lineNumber );
+		}
+
+		return scope.object;
+	}
+
 	function fixLineEndings( file )
 	{
@@ -351,25 +397,9 @@
 	CKRELEASER.releaser.prototype =
 	{
+		copyFiles : copyFiles,
+
 		loadDefinitionFile : function( filePath )
 		{
-			var file = new File( filePath );
-
-			var releaseCode = 'var release = { ' + CKRELEASER.io.readFile( file ) + '\n};';
-			var cx = Context.enter(), scope = cx.initStandardObjects();
-
-			try
-			{
-				cx.evaluateString( scope, releaseCode, file.getName(), 1, null );
-			}
-			catch ( e )
-			{
-				if ( CKRELEASER.verbose && typeof ( e.rhinoException ) != 'undefined' )
-					e.rhinoException.printStackTrace();
-
-				error( '\nParsing release file failed:\n    Error: ' + e.message + '\n    File: ' + file.getAbsolutePath()
-						+ '\n    Line: ' + e.lineNumber );
-			}
-
-			this.loadDefinition( scope.release );
+			this.loadDefinition( loadFile( filePath ) );
 		},
 
Index: /CKReleaser/trunk/test/_assets/plugins/font/plugin.js
===================================================================
--- /CKReleaser/trunk/test/_assets/plugins/font/plugin.js	(revision 4589)
+++ /CKReleaser/trunk/test/_assets/plugins/font/plugin.js	(revision 4589)
@@ -0,0 +1,227 @@
+/*
+Copyright (c) 2003-2009, 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 vars = {};
+			var parts = names[ i ].split( '/' );
+
+			var name = names[ i ] = parts[ 0 ];
+			vars[ styleType ] = values[ i ] = parts[ 1 ] || name;
+
+			styles[ name ] = new CKEDITOR.style( styleDefinition, vars );
+		}
+
+		editor.ui.addRichCombo( comboName,
+			{
+				label : lang.label,
+				title : lang.panelTitle,
+				voiceLabel : lang.voiceLabel,
+				className : 'cke_' + ( styleType == 'size' ? 'fontSize' : 'font' ),
+				multiSelect : false,
+
+				panel :
+				{
+					css : [ CKEDITOR.getUrl( editor.skinPath + 'editor.css' ) ].concat( config.contentsCss ),
+					voiceLabel : lang.panelVoiceLabel
+				},
+
+				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 } } ]
+	};
Index: /CKReleaser/trunk/test/_assets/plugins/format/plugin.js
===================================================================
--- /CKReleaser/trunk/test/_assets/plugins/format/plugin.js	(revision 4589)
+++ /CKReleaser/trunk/test/_assets/plugins/format/plugin.js	(revision 4589)
@@ -0,0 +1,191 @@
+/*
+Copyright (c) 2003-2009, 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,
+				voiceLabel : lang.voiceLabel,
+				className : 'cke_format',
+				multiSelect : false,
+
+				panel :
+				{
+					css : [ CKEDITOR.getUrl( editor.skinPath + 'editor.css' ) ].concat( config.contentsCss ),
+					voiceLabel : lang.panelVoiceLabel
+				},
+
+				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 );
+
+					editor.fire( 'saveSnapshot' );
+				},
+
+				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' };
Index: /CKReleaser/trunk/test/_assets/plugins/forms/dialogs/button.js
===================================================================
--- /CKReleaser/trunk/test/_assets/plugins/forms/dialogs/button.js	(revision 4589)
+++ /CKReleaser/trunk/test/_assets/plugins/forms/dialogs/button.js	(revision 4589)
@@ -0,0 +1,135 @@
+/*
+Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.dialog.add( 'button', function( editor )
+{
+	return {
+		title : editor.lang.button.title,
+		minWidth : 350,
+		minHeight : 150,
+		onShow : function()
+		{
+			delete this.button;
+			var element = this.getParentEditor().getSelection().getSelectedElement();
+			if ( element && element.getName() == "input" )
+			{
+				var type = element.getAttribute( 'type' );
+				if ( type == "button" || type == "reset" || type == "submit" )
+				{
+					this.button = element;
+					this.setupContent( element );
+				}
+			}
+		},
+		onOk : function()
+		{
+			var editor,
+				element = this.button,
+				isInsertMode = !element;
+
+			if ( isInsertMode )
+			{
+				editor = this.getParentEditor();
+				element = editor.document.createElement( 'input' );
+			}
+
+			if ( isInsertMode )
+				editor.insertElement( element );
+			this.commitContent( { element : element } );
+		},
+		contents : [
+			{
+				id : 'info',
+				label : editor.lang.button.title,
+				title : editor.lang.button.title,
+				elements : [
+					{
+						id : '_cke_saved_name',
+						type : 'text',
+						label : editor.lang.common.name,
+						'default' : '',
+						setup : function( element )
+						{
+							this.setValue(
+									element.getAttribute( '_cke_saved_name' ) ||
+									element.getAttribute( 'name' ) ||
+									'' );
+						},
+						commit : function( data )
+						{
+							var element = data.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.button.text,
+						accessKey : 'V',
+						'default' : '',
+						setup : function( element )
+						{
+							this.setValue( element.getAttribute( 'value' ) || '' );
+						},
+						commit : function( data )
+						{
+							var element = data.element;
+
+							if ( this.getValue() )
+								element.setAttribute( 'value', this.getValue() );
+							else
+								element.removeAttribute( 'value' );
+						}
+					},
+					{
+						id : 'type',
+						type : 'select',
+						label : editor.lang.button.type,
+						'default' : 'button',
+						accessKey : 'T',
+						items :
+						[
+							[ editor.lang.button.typeBtn, 'button' ],
+							[ editor.lang.button.typeSbm, 'submit' ],
+							[ editor.lang.button.typeRst, 'reset' ]
+						],
+						setup : function( element )
+						{
+							this.setValue( element.getAttribute( 'type' ) || '' );
+						},
+						commit : function( data )
+						{
+							var element = data.element;
+
+							if ( CKEDITOR.env.ie )
+							{
+								var elementType = element.getAttribute( 'type' );
+								var currentType = this.getValue();
+
+								if ( currentType != elementType )
+								{
+									var replace = CKEDITOR.dom.element.createFromHtml( '<input type="' + currentType +
+										'"></input>', editor.document );
+									element.copyAttributes( replace, { type : 1 } );
+									replace.replace( element );
+									editor.getSelection().selectElement( replace );
+									data.element = replace;
+								}
+							}
+							else
+								element.setAttribute( 'type', this.getValue() );
+						}
+					}
+				]
+			}
+		]
+	};
+});
Index: /CKReleaser/trunk/test/_assets/plugins/forms/dialogs/checkbox.js
===================================================================
--- /CKReleaser/trunk/test/_assets/plugins/forms/dialogs/checkbox.js	(revision 4589)
+++ /CKReleaser/trunk/test/_assets/plugins/forms/dialogs/checkbox.js	(revision 4589)
@@ -0,0 +1,138 @@
+/*
+Copyright (c) 2003-2009, 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 )
+						{
+							this.setValue( element.getAttribute( 'value' ) || '' );
+						},
+						commit : function( data )
+						{
+							var element = data.element;
+
+							if ( this.getValue() )
+								element.setAttribute( 'value', this.getValue() );
+							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"' : '' )
+										   + '></input>', editor.document );
+									element.copyAttributes( replace, { type : 1, checked : 1 } );
+									replace.replace( element );
+									editor.getSelection().selectElement( replace );
+									data.element = replace;
+								}
+							}
+							else
+							{
+								if ( this.getValue() )
+									element.setAttribute( 'checked', this.getValue() );
+								else
+									element.removeAttribute( 'checked' );
+							}
+						}
+					}
+				]
+			}
+		]
+	};
+});
Index: /CKReleaser/trunk/test/_assets/plugins/forms/dialogs/form.js
===================================================================
--- /CKReleaser/trunk/test/_assets/plugins/forms/dialogs/form.js	(revision 4589)
+++ /CKReleaser/trunk/test/_assets/plugins/forms/dialogs/form.js	(revision 4589)
@@ -0,0 +1,177 @@
+/*
+Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+CKEDITOR.dialog.add( 'form', function( editor )
+{
+	var autoAttributes =
+	{
+		action : 1,
+		id : 1,
+		method : 1,
+		enctype : 1,
+		target : 1
+	};
+
+	return {
+		title : editor.lang.form.title,
+		minWidth : 350,
+		minHeight : 200,
+		onShow : function()
+		{
+			delete this.form;
+
+			var element = this.getParentEditor().getSelection().getStartElement();
+			var form = element && element.getAscendant( 'form', true );
+			if ( form )
+			{
+				this.form = form;
+				this.setupContent( form );
+			}
+		},
+		onOk : function()
+		{
+			var editor,
+				element = this.form,
+				isInsertMode = !element;
+
+			if ( isInsertMode )
+			{
+				editor = this.getParentEditor();
+				element = editor.document.createElement( 'form' );
+				element.append( editor.document.createElement( 'br' ) );
+			}
+
+			if ( isInsertMode )
+				editor.insertElement( element );
+			this.commitContent( element );
+		},
+		onLoad : function()
+		{
+			function autoSetup( element )
+			{
+				this.setValue( element.getAttribute( this.id ) || '' );
+			}
+
+			function autoCommit( element )
+			{
+				if ( this.getValue() )
+					element.setAttribute( this.id, this.getValue() );
+				else
+					element.removeAttribute( this.id );
+			}
+
+			this.foreach( function( contentObj )
+				{
+					if ( autoAttributes[ contentObj.id ] )
+					{
+						contentObj.setup = autoSetup;
+						contentObj.commit = autoCommit;
+					}
+				} );
+		},
+		contents : [
+			{
+				id : 'info',
+				label : editor.lang.form.title,
+				title : editor.lang.form.title,
+				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( element )
+						{
+							if ( this.getValue() )
+								element.setAttribute( '_cke_saved_name', this.getValue() );
+							else
+							{
+								element.removeAttribute( '_cke_saved_name' );
+								element.removeAttribute( 'name' );
+							}
+						}
+					},
+					{
+						id : 'action',
+						type : 'text',
+						label : editor.lang.form.action,
+						'default' : '',
+						accessKey : 'A'
+					},
+					{
+						type : 'hbox',
+						widths : [ '45%', '55%' ],
+						children :
+						[
+							{
+								id : 'id',
+								type : 'text',
+								label : editor.lang.common.id,
+								'default' : '',
+								accessKey : 'I'
+							},
+							{
+								id : 'enctype',
+								type : 'select',
+								label : editor.lang.form.encoding,
+								style : 'width:100%',
+								accessKey : 'E',
+								'default' : '',
+								items :
+								[
+									[ '' ],
+									[ 'text/plain' ],
+									[ 'multipart/form-data' ],
+									[ 'application/x-www-form-urlencoded' ]
+								]
+							}
+						]
+					},
+					{
+						type : 'hbox',
+						widths : [ '45%', '55%' ],
+						children :
+						[
+							{
+								id : 'target',
+								type : 'select',
+								label : editor.lang.form.target,
+								style : 'width:100%',
+								accessKey : 'M',
+								'default' : '',
+								items :
+								[
+									[ editor.lang.form.targetNotSet, '' ],
+									[ editor.lang.form.targetNew, '_blank' ],
+									[ editor.lang.form.targetTop, '_top' ],
+									[ editor.lang.form.targetSelf, '_self' ],
+									[ editor.lang.form.targetParent, '_parent' ]
+								]
+							},
+							{
+								id : 'method',
+								type : 'select',
+								label : editor.lang.form.method,
+								accessKey : 'M',
+								'default' : 'GET',
+								items :
+								[
+									[ 'GET', 'get' ],
+									[ 'POST', 'post' ]
+								]
+							}
+						]
+					}
+				]
+			}
+		]
+	};
+});
Index: /CKReleaser/trunk/test/_assets/plugins/forms/plugin.js
===================================================================
--- /CKReleaser/trunk/test/_assets/plugins/forms/plugin.js	(revision 4589)
+++ /CKReleaser/trunk/test/_assets/plugins/forms/plugin.js	(revision 4589)
@@ -0,0 +1,193 @@
+/*
+Copyright (c) 2003-2009, 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 };
+					}
+				});
+		}
+	},
+	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 );
+	};
+}
Index: /CKReleaser/trunk/test/_assets/plugins/horizontalrule/plugin.js
===================================================================
--- /CKReleaser/trunk/test/_assets/plugins/horizontalrule/plugin.js	(revision 4589)
+++ /CKReleaser/trunk/test/_assets/plugins/horizontalrule/plugin.js	(revision 4589)
@@ -0,0 +1,36 @@
+/*
+Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+
+/**
+ * @file Horizontal Rule plugin.
+ */
+
+(function()
+{
+	var horizontalruleCmd =
+	{
+		canUndo : false,    // The undo snapshot will be handled by 'insertElement'.
+		exec : function( editor )
+		{
+			editor.insertElement( editor.document.createElement( 'hr' ) );
+		}
+	};
+
+	var pluginName = 'horizontalrule';
+
+	// Register a plugin named "horizontalrule".
+	CKEDITOR.plugins.add( pluginName,
+	{
+		init : function( editor )
+		{
+			editor.addCommand( pluginName, horizontalruleCmd );
+			editor.ui.addButton( 'HorizontalRule',
+				{
+					label : editor.lang.horizontalrule,
+					command : pluginName
+				});
+		}
+	});
+})();
Index: /CKReleaser/trunk/test/_assets/plugins/specialchar/dialogs/specialchar.js
===================================================================
--- /CKReleaser/trunk/test/_assets/plugins/specialchar/dialogs/specialchar.js	(revision 4589)
+++ /CKReleaser/trunk/test/_assets/plugins/specialchar/dialogs/specialchar.js	(revision 4589)
@@ -0,0 +1,362 @@
+/*
+Copyright (c) 2003-2009, 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;
+	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( '&nbsp;' );
+			dialog.getContentElement( 'info', 'htmlPreview' ).getElement().setHtml( '&nbsp;' );
+			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 )
+		{
+			// RIGHT-ARROW
+			case 39 :
+				// 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();
+				break;
+			// LEFT-ARROW
+			case 37 :
+				// relative is TD
+				if ( ( relative = element.getParent().getPrevious() ) )
+				{
+					nodeToMove = relative.getChild( 0 );
+					nodeToMove.focus();
+					onBlur( null, element );
+					onFocus( null, nodeToMove );
+				}
+				ev.preventDefault();
+				break;
+			// 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;
+			// 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;
+			// 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 : editor.lang.specialChar.title,
+		minWidth : 430,
+		minHeight : 280,
+		buttons : [ CKEDITOR.dialog.cancelButton ],
+		charColumns : 17,
+		chars :
+			[
+				'!','&quot;','#','$','%','&amp;',"'",'(',')','*','+','-','.','/',
+				'0','1','2','3','4','5','6','7','8','9',':',';',
+				'&lt;','=','&gt;','?','@',
+				'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;','&lsquo;','&rsquo;','&rsquo;','&ldquo;',
+				'&rdquo;','&ndash;','&mdash;','&iexcl;','&cent;','&pound;',
+				'&curren;','&yen;','&brvbar;','&sect;','&uml;','&copy;','&ordf;',
+				'&laquo;','&not;','&reg;','&macr;','&deg;','&plusmn;','&sup2;',
+				'&sup3;','&acute;','&micro;','&para;','&middot;','&cedil;',
+				'&sup1;','&ordm;','&raquo;','&frac14;','&frac12;','&frac34;',
+				'&iquest;','&Agrave;','&Aacute;','&Acirc;','&Atilde;','&Auml;',
+				'&Aring;','&AElig;','&Ccedil;','&Egrave;','&Eacute;','&Ecirc;',
+				'&Euml;','&Igrave;','&Iacute;','&Icirc;','&Iuml;','&ETH;',
+				'&Ntilde;','&Ograve;','&Oacute;','&Ocirc;','&Otilde;','&Ouml;',
+				'&times;','&Oslash;','&Ugrave;','&Uacute;','&Ucirc;','&Uuml;',
+				'&Yacute;','&THORN;','&szlig;','&agrave;','&aacute;','&acirc;',
+				'&atilde;','&auml;','&aring;','&aelig;','&ccedil;','&egrave;',
+				'&eacute;','&ecirc;','&euml;','&igrave;','&iacute;','&icirc;',
+				'&iuml;','&eth;','&ntilde;','&ograve;','&oacute;','&ocirc;',
+				'&otilde;','&ouml;','&divide;','&oslash;','&ugrave;','&uacute;',
+				'&ucirc;','&uuml;','&uuml;','&yacute;','&thorn;','&yuml;',
+				'&OElig;','&oelig;','&#372;','&#374','&#373','&#375;','&sbquo;',
+				'&#8219;','&bdquo;','&hellip;','&trade;','&#9658;','&bull;',
+				'&rarr;','&rArr;','&hArr;','&diams;','&asymp;'
+			],
+		onLoad :  function()
+		{
+			var columns = this.definition.charColumns,
+				chars = this.definition.chars;
+
+			var html = [ '<table style="width: 320px; height: 100%; border-collapse: separate;" align="center" cellspacing="2" cellpadding="2" border="0">' ];
+
+			var i = 0 ;
+			while ( i < chars.length )
+			{
+				html.push( '<tr>' ) ;
+
+				for( var j = 0 ; j < columns ; j++, i++ )
+				{
+					if ( chars[ i ] )
+					{
+						html.push(
+							'<td class="cke_dark_background" style="cursor: default">' +
+							'<a href="javascript: void(0);" style="cursor: inherit; display: block; height: 1.25em; margin-top: 0.25em; text-align: center;" title="', chars[i].replace( /&/g, '&amp;' ), '"' +
+							' onkeydown="CKEDITOR.tools.callFunction( ' + onKeydown + ', event, this )"' +
+							' onclick="CKEDITOR.tools.callFunction(' + onClick + ', this); return false;"' +
+							' tabindex="-1">' +
+							'<span style="margin: 0 auto;cursor: inherit">' +
+							chars[i] +
+							'</span></a>');
+					}
+					else
+						html.push( '<td class="cke_dark_background">&nbsp;' );
+
+					html.push( '</td>' );
+				}
+				html.push( '</tr>' );
+			}
+
+			html.push( '</tbody></table>' );
+
+			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().getChild( [0, 0, 0, 0, 0] );
+									setTimeout(function()
+									{
+										firstChar.focus();
+										onFocus( null, firstChar );
+									});
+								},
+								// Needed only for webkit.
+								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',
+												style : 'border:1px solid #eeeeee;background-color:#EAEAD1;font-size:28px;height:40px;width:70px;padding-top:9px;font-family:\'Microsoft Sans Serif\',Arial,Helvetica,Verdana;text-align:center;',
+												html : '<div>&nbsp;</div>'
+											},
+											{
+												type : 'html',
+												id : 'htmlPreview',
+												style : 'border:1px solid #eeeeee;background-color:#EAEAD1;font-size:14px;height:20px;width:70px;padding-top:2px;font-family:\'Microsoft Sans Serif\',Arial,Helvetica,Verdana;text-align:center;',
+												html : '<div>&nbsp;</div>'
+											}
+										]
+									}
+								]
+							}
+						]
+					}
+				]
+			}
+		]
+	};
+} );
Index: /CKReleaser/trunk/test/_assets/plugins/specialchar/plugin.js
===================================================================
--- /CKReleaser/trunk/test/_assets/plugins/specialchar/plugin.js	(revision 4589)
+++ /CKReleaser/trunk/test/_assets/plugins/specialchar/plugin.js	(revision 4589)
@@ -0,0 +1,29 @@
+/*
+Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.
+For licensing, see LICENSE.html or http://ckeditor.com/license
+*/
+
+/**
+ * @file Special Character plugin
+ */
+
+CKEDITOR.plugins.add( 'specialchar',
+{
+	init : function( editor )
+	{
+		var pluginName = 'specialchar';
+
+		// Register the dialog.
+		CKEDITOR.dialog.add( pluginName, this.path + 'dialogs/specialchar.js' );
+
+		// Register the command.
+		editor.addCommand( pluginName, new CKEDITOR.dialogCommand( pluginName ) );
+
+		// Register the toolbar button.
+		editor.ui.addButton( 'SpecialChar',
+			{
+				label : editor.lang.specialChar.toolbar,
+				command : pluginName
+			});
+	}
+} );
Index: /CKReleaser/trunk/test/test.js
===================================================================
--- /CKReleaser/trunk/test/test.js	(revision 4588)
+++ /CKReleaser/trunk/test/test.js	(revision 4589)
@@ -53,5 +53,5 @@
 			error( "Can't create temp directory: " + tempDir );
 
-		var tests = [ 'directives', 'skins', 'samples', 'bom', 'lineendings' ];
+		var tests = [ 'directives', 'skins', 'samples', 'bom', 'lineendings', 'plugins' ];
 
 		for ( var i = 0 ; i < tests.length ; i++ )
@@ -284,5 +284,64 @@
 		}
 	}
-	
+
+	function listFiles( file )
+	{
+		var result = [];
+
+		if ( file.isDirectory() )
+		{
+			var children = file.list();
+			if ( !children.length )
+			{
+				result.push( file );
+			}
+			else
+			{
+				for ( var i = 0 ; i < children.length ; i++ )
+				{
+					result.push( listFiles( new File( file, children[i] ) ) );
+				}
+			}
+		}
+		else
+		{
+			result.push( file );
+		}
+
+		return result;
+	}
+
+	function testCopyFiles()
+	{
+		print( "\nTesting copying files...\n" );
+		var releaser = new CKRELEASER.releaser();
+		CKRELEASER.sourceDir = ".";
+		CKRELEASER.releaseDir = ".";
+		CKRELEASER.release.ignore = ['.svn'];
+		CKRELEASER.release.copy = 
+		[
+			{
+				source : '_assets/plugins',
+				target : 'tmp/plugins',
+				ignore : {
+					sourcePackage : 'test.pack',
+					files : 'packages[1].files'
+				}
+			}
+		];
+		releaser.copyFiles();
+		var files = listFiles(new File(".", "tmp/plugins"));
+		var validResult = [
+			'./tmp/plugins/forms/dialogs/button.js',
+			'./tmp/plugins/forms/dialogs/checkbox.js',
+			'./tmp/plugins/forms/dialogs/form.js',
+			'./tmp/plugins/forms/plugin.js',
+			'./tmp/plugins/horizontalrule/plugin.js',
+			'./tmp/plugins/specialchar/dialogs/specialchar.js'];
+
+		assertEquals( files.length, 3, "Comparing plugins directories (same number of subfolders/files?)" );
+		var areEqual = files.toString().replace(/\\/g, "/") == validResult.toString();
+		assertEquals( true, areEqual, "Comparing plugins directories (are equal?)" );
+	}
 	prepareTempDirs();
 	testDirectives();
@@ -292,4 +351,5 @@
 	testBom();
 	testLineEndings();
+	testCopyFiles();
 
 	print( '' );
Index: /CKReleaser/trunk/test/test.pack
===================================================================
--- /CKReleaser/trunk/test/test.pack	(revision 4589)
+++ /CKReleaser/trunk/test/test.pack	(revision 4589)
@@ -0,0 +1,102 @@
+/*
+ * CKPackager - Sample Package file
+ */
+
+header :
+	'/*'																			+ '\n' +
+	'Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.'	+ '\n' +
+	'For licensing, see LICENSE.html or http://ckeditor.com/license'				+ '\n' +
+	'*/'																			+ '\n' +
+	'\n',
+
+noCheck : false,
+
+constants :
+	{
+		'CKEDITOR.ELEMENT_MODE_NONE' : 0,
+		'CKEDITOR.ELEMENT_MODE_REPLACE' : 1,
+		'CKEDITOR.ELEMENT_MODE_APPENDTO' : 2,
+		'CKEDITOR.CTRL' : 1000,
+		'CKEDITOR.SHIFT' : 2000,
+		'CKEDITOR.ALT' : 4000,
+		'CKEDITOR.NODE_ELEMENT' : 1,
+		'CKEDITOR.NODE_TEXT' : 3,
+		'CKEDITOR.NODE_COMMENT' : 8,
+		'CKEDITOR.NODE_DOCUMENT_FRAGMENT' : 11,
+		'CKEDITOR.POSITION_IDENTICAL' : 0,
+		'CKEDITOR.POSITION_DISCONNECTED' : 1,
+		'CKEDITOR.POSITION_FOLLOWING' : 2,
+		'CKEDITOR.POSITION_PRECEDING' : 4,
+		'CKEDITOR.POSITION_IS_CONTAINED' : 8,
+		'CKEDITOR.POSITION_CONTAINS' : 16,
+		'CKEDITOR.ENTER_P' : 1,
+		'CKEDITOR.ENTER_BR' : 2,
+		'CKEDITOR.ENTER_DIV' : 3,
+		'CKEDITOR.TRISTATE_ON' : 1,
+		'CKEDITOR.TRISTATE_OFF' : 2,
+		'CKEDITOR.TRISTATE_DISABLED' : 0,
+		'CKEDITOR.POSITION_AFTER_START' : 1,
+		'CKEDITOR.POSITION_BEFORE_END' : 2,
+		'CKEDITOR.POSITION_BEFORE_START' : 3,
+		'CKEDITOR.POSITION_AFTER_END' : 4,
+		'CKEDITOR.ENLARGE_ELEMENT' : 1,
+		'CKEDITOR.ENLARGE_BLOCK_CONTENTS' : 2,
+		'CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS' : 3,
+		'CKEDITOR.START' : 1,
+		'CKEDITOR.END' : 2,
+		'CKEDITOR.STARTEND' : 3,
+		'CKEDITOR.UI_BUTTON' : 1,
+		'CKEDITOR.STYLE_BLOCK' : 1,
+		'CKEDITOR.STYLE_INLINE' : 2,
+		'CKEDITOR.STYLE_OBJECT' : 3,
+		'CKEDITOR.UI_PANELBUTTON' : 4,
+		'CKEDITOR.SELECTION_NONE' : 1,
+		'CKEDITOR.SELECTION_TEXT' : 2,
+		'CKEDITOR.SELECTION_ELEMENT' : 3,
+		'CKEDITOR.UI_RICHCOMBO' : 3,
+		'CKEDITOR.UI_MENUBUTTON' : 5,
+		'CKEDITOR.DIALOG_RESIZE_NONE' : 0,
+		'CKEDITOR.DIALOG_RESIZE_WIDTH' : 1,
+		'CKEDITOR.DIALOG_RESIZE_HEIGHT' : 2,
+		'CKEDITOR.DIALOG_RESIZE_BOTH' : 3,
+		'CKEDITOR.VALIDATE_OR' : 1,
+		'CKEDITOR.VALIDATE_AND' : 2,
+		'CKEDITOR.UI_PANEL' : 2
+	},
+
+packages :
+	[
+		{
+			output : 'ckeditor_basic.js',
+			wrap : true,
+			files :
+				[
+					'_source/core/ckeditor_base.js',
+					'_source/core/event.js',
+					'_source/core/editor_basic.js',
+					'_source/core/env.js',
+					'_source/core/ckeditor_basic.js'
+				]
+		},
+
+		{
+			output : 'ckeditor.js',
+			wrap : true,
+			files :
+				[
+					'_source/core/dom/text.js',
+					'_source/core/dom/documentfragment.js',
+					'_source/core/dom/walker.js',
+					'_source/core/dom/range.js',
+					'_source/core/_bootstrap.js',
+					'plugins/about/plugin.js',
+					'plugins/format/plugin.js',
+					'plugins/font/plugin.js',
+					'plugins/basicstyles/plugin.js',
+					'plugins/specialchar/plugin.js',
+					'_source/skins/kama/skin.js',
+					'_source/themes/default/theme.js'
+				]
+		}
+
+	]
