text: /**
   * @class eli18n
   * Javascript applications localization 
   *
   * @param Object o - class options. Object. {textdomain : 'имя_группы_сообщений', messages : {textdomain1 : {}[, textdomain2 : {}]...}}
   *
   * Usage:
   * 
   * var msgs = { Hello : 'Превэд', 'Hello %user' : 'Превед %user' };
   * //load messages and set default textdomain
   * var translator = new eli18n( {textdomain : 'test', messages : {test : msgs}} )
   * window.console.log(translator.translate('Hello'));
   * window.console.log(translator.format('Hello %user', {user : 'David Blain'}))
   * // create new textdomain
   * translator.load({test2 : {'Goodbye' : 'Ja, deva mata!'} })
   * // and use it, without changing default one
   * window.console.log(translator.translate('Goodbye', 'test2'));
   *
   * @author:    Dmitry (dio) Levashov dio@std42.ru
   * license:   BSD license
   **/
  function eli18n(o) {
  	
  	/**
  	 * Get/set default textdomain
  	 *
  	 * @param String d new textdomain name
  	 * @return String  default textdomain
  	 **/
  	this.textdomain = function(d) {
  		return this.messages[d] ? this._domain = d : this._domain;
  	}
  	
  	o && o.messages   && this.load(o.messages);
  	o && o.textdomain && this.textdomain(o.textdomain);
  }
  
  eli18n.prototype = new function() {
  	
  	/**
  	 * @var Object messages (key - messages in English or message handler, value - message in selected language)
  	 **/
  	this.messages = {};
  	/**
  	 * @var String default textdomain
  	 **/
  	this._domain   = '';
  	
  	/**
  	 * Load new messages
  	 *
  	 * @param Object msgs - messages (key - textdomain name, value - messages Object)
  	 * @return Object this
  	 **/
  	this.load = function(msgs) {
  		if (typeof(msgs) == 'object') {
  			for (var d in msgs) {
  				var _msgs = msgs[d];
  				if (typeof(_msgs) == 'object') {
  					if (!this.messages[d]) {
  						this.messages[d] = {}; 
  					}
  					for (var k in _msgs) {
  						if (typeof(_msgs[k]) == 'string') {
  							this.messages[d][k] = _msgs[k];
  						}
  					}
  				}
  			}
  		}
  		return this;
  	}
  
  	/**
  	 * Return translated message, if message exists in required or default textdomain, otherwise returns original message
  	 *
  	 * @param  String msg - message
  	 * @param  String d - textdomain. If empty, default textdomain will be used
  	 * @return String translated message
  	 **/
  	this.translate = function(msg, d) {
  		var d = d && this.messages[d] ? d : this._domain;
  		return this.messages[d] && this.messages[d][msg] ? this.messages[d][msg] : msg;
  		
  	}
  	
  	/**
  	 * Translate message and replace placeholders (%placeholder)
  	 *
  	 * @param  String  msg - message
  	 * @param  Object  replacement for placeholders (keys - placeholders name without leading %, values - replacements)
  	 * @param  String  d - textdomain. If empty, default textdomain will be used
  	 * @return String  translated message
  	 **/
  	this.format = function(msg, data, d) {
  		msg = this.translate(msg, d);
  		if (typeof(data) == 'object') {
  			for (var i in data) {
  				msg = msg.replace('%'+i, this.translate(data[i], d));
  			}
  		}
  		return msg;
  	}
  }
  /**
   * @class elDialogForm
   * Wraper for jquery.ui.dialog and jquery.ui.tabs
   *  Create form in dialog. You can decorate it as you wish - with tabs or/and tables
   *
   * Usage:
   *   var d = new elDialogForm(opts)
   *   d.append(['Field name: ', $('<input type="text" name="f1" />')])
   *		.separator()
   *		.append(['Another field name: ', $('<input type="text" name="f2" />')])
   *      .open()
   * will create dialog with pair text field separated by horizontal rule
   * Calling append() with 2 additional arguments ( d.append([..], null, true)) 
   *  - will create table in dialog and put text inputs and labels in table cells
   *
   * Dialog with tabs:
   *   var d = new elDialogForm(opts)
   *   d.tab('first', 'First tab label)
   * 	  .tab('second', 'Second tab label)
   *    .append(['Field name: ', $('<input type="text" name="f1" />')], 'first', true)  - add label and input to first tab in table (table will create automagicaly)
   *    .append(['Field name 2: ', $('<input type="text" name="f2" />')], 'second', true)  - same in secon tab
   *
   * Options:
   *   class     - css class for dialog
   *   submit    - form submit event callback. Accept 2 args - event and this object
   *   ajaxForm  - arguments for ajaxForm, if needed (dont forget include jquery.form.js)
   *   tabs      - arguments for ui.tabs
   *   dialog    - arguments for ui.dialog
   *   name      - hidden text field in wich selected value will saved
   *
   * Notice!
   * When close dialog, it will destroing insead of dialog('close'). Reason - strange bug with tabs in dialog on secondary opening. 
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   *
   **/
  
  function elDialogForm(o) {
  	var self = this;
  	
  	var defaults = {
  		'class'   : 'el-dialogform',
  		submit    : function(e, d) { d.close(); },
  		form      : { action : window.location.href,	method : 'post'	},
  		ajaxForm  : null,
  		validate  : null,
  		spinner   : 'Loading',
  		tabs      : { active: 0, selected : 0 },
  		tabPrefix : 'el-df-tab-',
  		dialog    : {
  			title     : 'dialog',
  			autoOpen  : false,
  			modal     : true,
  			resizable : false,
  			closeOnEscape : true,
  			buttons  : {
  				Cancel : function() { self.close(); },
  				Ok     : function() { self.form.trigger('submit'); }
  			}
  		}
  	};
  
  	this.opts = jQuery.extend(true, {}, defaults, o);
  	
  	this.opts.dialog.close = function() { 
  		self.close(); 
  	}
  
  	// this.opts.dialog.autoOpen = true;
  	if (this.opts.rtl) {
  		this.opts['class'] += ' el-dialogform-rtl';
  	}
  	
  	if (o && o.dialog && o.dialog.buttons && typeof(o.dialog.buttons) == 'object') {
  		this.opts.dialog.buttons = o.dialog.buttons;
  	}
  
  	this.ul     = null;
  	this.tabs   = {};
  	this._table = null;
  
  	this.dialog = jQuery('<div />').addClass(this.opts['class']).dialog(this.opts.dialog);
  
  	this.message = jQuery('<div class="el-dialogform-message rounded-5" />').hide().appendTo(this.dialog);
  	this.error   = jQuery('<div class="el-dialogform-error rounded-5" />').hide().appendTo(this.dialog);
  	this.spinner = jQuery('<div class="spinner" />').hide().appendTo(this.dialog);
  	this.content = jQuery('<div class="el-dialogform-content" />').appendTo(this.dialog)
  	this.form   = jQuery('<form />').attr(this.opts.form).appendTo(this.content);
  
  	if (this.opts.submit) {
  		this.form.bind('submit', function(e) { self.opts.submit(e, self) })
  	}
  	if (this.opts.ajaxForm && jQuery.fn.ajaxForm) {
  		this.form.ajaxForm(this.opts.ajaxForm);
  	}
  	if (this.opts.validate) {
  		this.form.validate(this.opts.validate);
  	}
  	
  	this.option = function(name, value) {
  		return this.dialog.dialog('option', name, value)
  	}
  	
  	this.showError = function(msg, hideContent) {
  		this.hideMessage();
  		this.hideSpinner();
  		this.error.html(msg).show();
  		hideContent && this.content.hide();
  		return this;
  	}
  	
  	this.hideError= function() {
  		this.error.text('').hide();
  		this.content.show();
  		return this;		
  	}
  	
  	this.showSpinner = function(txt) {
  		this.error.hide();
  		this.message.hide();
  		this.content.hide();
  		this.spinner.text(txt||this.opts.spinner).show();
  		this.option('buttons', {});
  		return this;		
  	}
  	
  	this.hideSpinner = function() {
  		this.content.show();
  		this.spinner.hide();
  		return this;		
  	}
  	
  	this.showMessage = function(txt, hideContent) {
  		this.hideError();
  		this.hideSpinner();
  		this.message.html(txt||'').show();
  		hideContent && this.content.hide();
  		return this;
  	}
  	
  	this.hideMessage = function() {
  		this.message.hide();
  		this.content.show();
  		return this;		
  	}
  	
  	/**
  	 * Create new tab
  	 * @param string id    - tab id
  	 * @param string title - tab name
  	 * @return elDialogForm	
  	**/
  	this.tab = function(id, title) {
  		id = this.opts.tabPrefix+id;
  		
  		if (!this.ul) {
  			this.ul = jQuery('<ul />').prependTo(this.form);
  		}
  		jQuery('<li />').append(jQuery('<a />').attr('href', '#'+id).html(title)).appendTo(this.ul);
  		this.tabs[id] = {tab : jQuery('<div />').attr('id', id).addClass('tab').appendTo(this.form), table : null};
  		return this;
  	}
  	
  	/**
  	 * Create new table
  	 * @param string id  tab id, if set - table will create in tab, otherwise - in dialog
  	 * @return elDialogForm	
  	**/
  	this.table = function(id) {
  		id = id && id.indexOf(this.opts.tabPrefix) == -1 ? this.opts.tabPrefix+id : id;
  		if (id && this.tabs && this.tabs[id]) {
  			this.tabs[id].table = jQuery('<table />').appendTo(this.tabs[id].tab);
  		} else {
  			this._table = jQuery('<table />').appendTo(this.form); 
  		}
  		return this;
  	}
  	
  	/**
  	 * Append html, dom nodes or jQuery objects to dialog or tab
  	 * @param array|object|string  data object(s) to append to dialog
  	 * @param string               tid  tab id, if adding to tab
  	 * @param bool                 t    if true - data will added in table (creating automagicaly)
  	 * @return elDialogForm	
  	**/
  	this.append = function(data, tid, t) {
  		tid = tid ? 'el-df-tab-'+tid : '';
  
  		if (!data) {
  			return this;
  		}
  		
  		if (tid && this.tabs[tid]) {
  			if (t) {
  				!this.tabs[tid].table && this.table(tid);
  				var tr = jQuery('<tr />').appendTo(this.tabs[tid].table);
  				if (!jQuery.isArray(data)) {
  					tr.append(jQuery('<td />').append(data));
  				} else {
  					for (var i=0; i < data.length; i++) {
  						tr.append(jQuery('<td />').append(data[i]));
  					};
  				}
  			} else {
  				if (!jQuery.isArray(data)) {
  					this.tabs[tid].tab.append(data)
  				} else {
  					for (var i=0; i < data.length; i++) {
  						this.tabs[tid].tab.append(data[i]);
  					};
  				}
  			}
  			
  		} else {
  			if (!t) {
  				if (!jQuery.isArray(data)) {
  					this.form.append(data);
  				} else {
  					for (var i=0; i < data.length; i++) {
  						this.form.append(data[i]);
  					};
  				}
  			} else {
  				if (!this._table) {
  					this.table();
  				}
  				var tr = jQuery('<tr />').appendTo(this._table);
  				if (!jQuery.isArray(data)) {
  					tr.append(jQuery('<td />').append(data));
  				} else {
  					for (var i=0; i < data.length; i++) {
  						tr.append(jQuery('<td />').append(data[i]));
  					};
  				}
  			}
  		}
  		return this;
  	}
  	
  	/**
  	 * Append separator (div class="separator") to dialog or tab
  	 * @param  string tid  tab id, if adding to tab
  	 * @return elDialogForm	
  	**/
  	this.separator = function(tid) {
  		tid = 'el-df-tab-'+tid;
  		if (this.tabs && this.tabs[tid]) {
  			this.tabs[tid].tab.append(jQuery('<div />').addClass('separator'));
  			this.tabs[tid].table && this.table(tid);
  		} else {
  			this.form.append(jQuery('<div />').addClass('separator'));
  		}
  		return this;
  	}
  	
  	/**
  	 * Open dialog window
  	 * @return elDialogForm	
  	**/
  	this.open = function() {
  		var self = this;
  		
  		this.ul && this.form.tabs(this.opts.tabs);
  
  		setTimeout(function() {
  			self.dialog.find(':text')
  				.keydown(function(e) {
  					if (e.keyCode == 13) {
  						e.preventDefault()
  						self.form.submit();
  					}
  				})
  				.filter(':first')[0].focus()
  		}, 200);
  
  		this.dialog.dialog('open');
  
  		return this;
  	}
  	
  	/**
  	 * Close dialog window and destroy content
  	 * @return void	
  	**/
  	this.close = function() {
  		if (typeof(this.opts.close) == 'function') {
  			this.opts.close();
  		}
  		this.dialog.dialog('destroy')//.remove();
  	}
  	
  }
  
  /**
   * elColorPicker. JQuery plugin
   * Create drop-down colors palette.
   *
   * Usage:
   * $(selector).elColorPicker(opts)
   *
   * set color after init:
   * var c = $(selector).elColorPicker(opts)
   * c.val('#ffff99)
   *
   * Get selected color:
   * var color = c.val();
   *
   * Notice!
   *   Palette created only after first click on element (lazzy loading)
   *
   * Options:
   *   colors - colors array (by default display 256 web safe colors)
   *   color  - current (selected) color
   *   class - css class for display "button" (element on wich plugin was called)
   *   paletteClass - css class for colors palette
   *   palettePosition - string indicate where palette will created:
   *      'inner' - palette will attach to element (acceptable in most cases)
   *      'outer' - palette will attach to document.body. 
   *                Use, when create color picker inside element with overflow == 'hidden', for example in ui.dialog
   *   update - function wich update button view on select color (by default set selected color as background)
   *   change - callback, called when color was selected (by default write color to console.log)
   *   name   - hidden text field in wich selected color value will saved
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   *
   **/
  (function($) {
  
  	$.fn.elColorPicker = function(o) {
  		var self     = this;
  		var opts     = $.extend({}, $.fn.elColorPicker.defaults, o);
  		this.hidden  = $('<input type="hidden" />').attr('name', opts.name).val(opts.color||'').appendTo(this);
  		this.palette = null;
  		this.preview = null;
  		this.input   = null;
  
  		function setColor(c) {
  			self.val(c);
  			opts.change && opts.change(self.val());
  			self.palette.slideUp();
  		}
  
  		function init() {
  			self.palette  = $('<div />').addClass(opts.paletteClass+' rounded-3');
  			for (var i=0; i < opts.colors.length; i++) {
  				$('<div />')
  					.addClass('color')
  					.css('background-color', opts.colors[i])
  					.attr({title : opts.colors[i], unselectable : 'on'})
  					.appendTo(self.palette)
  					.mouseenter(function() {
  						var v = $(this).attr('title');
  						self.input.val(v);
  						self.preview.css('background-color', v);
  					})
  					.click(function(e) {
  						e.stopPropagation(); 
  						setColor($(this).attr('title'));
  					});
  			};
  			self.input = $('<input type="text" />')
  				.addClass('rounded-3')
  				.attr('size', 8)
  				.click(function(e) {
  					e.stopPropagation();
  					$(this).focus();
  				})
  				.keydown(function(e) {
  					if (e.ctrlKey || e.metaKey) {
  						return true;
  					}
  					var k = e.keyCode;
  					// on esc - close palette
  					if (k == 27) {
  						return self.mouseleave();
  					}
  					// allow input only hex color value
  					if (k!=8 && k != 13 && k!=46 && k!=37 && k != 39 && (k<48 || k>57) && (k<65 || k > 70)) {
  						return false;
  					}
  					var c = $(this).val();
  					if (c.length == 7 || c.length == 0) {
  						if (k == 13) {
  							e.stopPropagation();
  							e.preventDefault();
  							setColor(c);
  							self.palette.slideUp();
  						}
  						if (e.keyCode != 8 && e.keyCode != 46 && k!=37 && k != 39) {
  							return false;
  						}
  					}
  				})
  				.keyup(function(e) {
  					var c = $(this).val(); 
  					c.length == 7 && /^#[0-9abcdef]{6}$/i.test(c) && self.val(c);
  				});
  				
  			self.preview = $('<div />')
  				.addClass('preview rounded-3')
  				.click(function(e) {
  					e.stopPropagation();
  					setColor(self.input.val());
  				});
  			
  			self.palette
  				.append($('<div />').addClass('clearfix'))
  				.append($('<div />').addClass('panel').append(self.input).append(self.preview));
  			
  			if (opts.palettePosition == 'outer') {
  				self.palette.hide()
  					.appendTo(self.parents('body').eq(0))
  					.mouseleave(function() {
  						if (!self.palette.is(':animated')) {
  						$(this).slideUp();
  						self.val(self.val());
  					}
  					});
  				self.mouseleave(function(e) {
  					if (e.relatedTarget != self.palette.get(0)) {
  						if (!self.palette.is(':animated')) {
  							self.palette.slideUp();
  							self.val(self.val());
  						}
  						
  					}
  				})
  			} else {
  				self.append(self.palette.hide())
  					.mouseleave(function(e) {
  						self.palette.slideUp();
  						self.val(self.val());
  					});
  			}
  			self.val(self.val());
  		}
  		
  		this.empty().addClass(opts['class']+' rounded-3')
  			.css({'position' : 'relative', 'background-color' : opts.color||''})
  		.click(function(e) { 
  			if (!self.hasClass('disabled')) {
  				!self.palette && init();
  				if (opts.palettePosition == 'outer' && self.palette.css('display') == 'none') {
  					var o = $(this).offset();
  					var w = self.palette.width();
  					var l = self.parents('body').width() - o.left >= w ? o.left : o.left + $(this).outerWidth() - w;
  					self.palette.css({left : l+'px', top : o.top+$(this).height()+1+'px'});
  				}
  				self.palette.slideToggle();
  			}
  		});
  		
  		this.val = function(v) {
  			if (!v && v!=='') {
  				return this.hidden.val();
  			} else {
  				this.hidden.val(v);
  				if (opts.update) {
  					opts.update(this.hidden.val());
  				} else {
  					this.css('background-color', v);
  				}
  				
  				if (self.palette) {
  					self.preview.css('background-color', v);
  					self.input.val(v);
  				}
  			}
  			return this;
  		}
  		
  		return this;
  	}
  
  	$.fn.elColorPicker.defaults = {
  		'class'         : 'el-colorpicker',
  		paletteClass    : 'el-palette',
  		palettePosition : 'inner',
  		name            : 'color',
  		color           : '',
  		update          : null,
  		change          : function(c) {  },
  		colors          : [
  			'#ffffff', '#cccccc', '#999999', '#666666', '#333333', '#000000', 
  			'#ffcccc', '#cc9999', '#996666', '#663333', '#330000', 
  			'#ff9999', '#cc6666', '#cc3333', '#993333', '#660000', 
  			'#ff6666', '#ff3333', '#ff0000', '#cc0000', '#990000',
  			'#ff9966', '#ff6633', '#ff3300', '#cc3300', '#993300',
  			'#ffcc99', '#cc9966', '#cc6633', '#996633', '#663300',
  			'#ff9933', '#ff6600', '#ff9900', '#cc6600', '#cc9933',
  			'#ffcc66', '#ffcc33', '#ffcc00', '#cc9900', '#996600',
  			'#ffffcc', '#cccc99', '#999966', '#666633', '#333300',
  			'#ffff99', '#cccc66', '#cccc33', '#999933', '#666600',
  			'#ffff66', '#ffff33', '#ffff00', '#cccc00', '#999900',
  			'#ccff66', '#ccff33', '#ccff00', '#99cc00', '#669900',
  			'#ccff99', '#99cc66', '#99cc33', '#669933', '#336600',
  			'#99ff33', '#99ff00', '#66ff00', '#66cc00', '#66cc33',
  			'#99ff66', '#66ff33', '#33ff00', '#33cc00', '#339900',
  			'#ccffcc', '#99cc99', '#669966', '#336633', '#003300',
  			'#99ff99', '#66cc66', '#33cc33', '#339933', '#006600',
  			'#66ff66', '#33ff33', '#00ff00', '#00cc00', '#009900',
  			'#66ff99', '#33ff66', '#00ff33', '#00cc33', '#009933',			
  			'#99ffcc', '#66cc99', '#33cc66', '#339966', '#006633',						
  			'#33ff99', '#00ff66', '#00ff99', '#00cc66', '#33cc99',						
  			'#66ffcc', '#33ffcc', '#00ffcc', '#00cc99', '#009966',						
  			'#ccffff', '#99cccc', '#669999', '#336666', '#003333',						
  			'#99ffff', '#66cccc', '#33cccc', '#339999', '#006666',						
  			'#66cccc', '#33ffff', '#00ffff', '#00cccc', '#009999',						
  			'#66ccff', '#33ccff', '#00ccff', '#0099cc', '#006699',																		
  			'#99ccff', '#6699cc', '#3399cc', '#336699', '#003366',						
  			'#3399ff', '#0099ff', '#0066ff', '#066ccc', '#3366cc',																		
  			'#6699ff', '#3366ff', '#0033ff', '#0033cc', '#003399',						
  			'#ccccff', '#9999cc', '#666699', '#333366', '#000033',																		
  			'#9999ff', '#6666cc', '#3333cc', '#333399', '#000066',																		
  			'#6666ff', '#3333ff', '#0000ff', '#0000cc', '#009999',																		
  			'#9966ff', '#6633ff', '#3300ff', '#3300cc', '#330099',																		
  			'#cc99ff', '#9966cc', '#6633cc', '#663399', '#330066',
  			'#9933ff', '#6600ff', '#9900ff', '#6600cc', '#9933cc',			
  			'#cc66ff', '#cc33ff', '#cc00ff', '#9900cc', '#660099',
  			'#ffccff', '#cc99cc', '#996699', '#663366', '#330033',			
  			'#ff99ff', '#cc66cc', '#cc33cc', '#993399', '#660066',
  			'#ff66ff', '#ff33ff', '#ff00ff', '#cc00cc', '#990099',			
  			'#ff66cc', '#ff33cc', '#ff00cc', '#cc0099', '#990066',
  			'#ff99cc', '#cc6699', '#cc3399', '#993366', '#660033',			
  			'#ff3399', '#ff0099', '#ff0066', '#cc0066', '#cc3366',
  			'#ff6699', '#ff3366', '#ff0033', '#cc0033', '#990033'		
  			]
  	};
  
  })(jQuery);
  /**
   * jQuery plugin. Create group of text input, elSelect and elColorPicker. 
   * Allow input border-width, border-style and border-color. Used in elRTE
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   **/
  (function($) {
  	
  	$.fn.elBorderSelect = function(o) {
  		
  		var $self = this;
  		var self  = this.eq(0);
  		var opts  = $.extend({}, $.fn.elBorderSelect.defaults, o);
  		var width = $('<input type="text" />')
  			.attr({'name' : opts.name+'[width]', size : 3}).css('text-align', 'right')
  			.change(function() { $self.change(); });
  		
  		var color = $('<div />').css('position', 'relative')
  			.elColorPicker({
  				'class'         : 'el-colorpicker ui-icon ui-icon-pencil',
  				name            : opts.name+'[color]', 
  				palettePosition : 'outer',
  				change          : function() { $self.change(); }
  			});
  		
  		
  		var style = $('<div />').elSelect({
  			tpl       : '<div style="border-bottom:4px %val #000;width:100%;margin:7px 0"> </div>',
  			tpls      : { '' : '%label'},
  			maxHeight : opts.styleHeight || null,
  			select    : function() { $self.change(); },
  			src       : {
  				''       : 'none',
  				solid    : 'solid',
  				dashed   : 'dashed',
  				dotted   : 'dotted',
  				'double' : 'double',
  				groove   : 'groove',
  				ridge    : 'ridge',
  				inset    : 'inset',
  				outset   : 'outset'
  			}
  		});
  		
  		self.empty()
  			.addClass(opts['class'])
  			.attr('name', opts.name||'')
  			.append(
  				$('<table />').attr('cellspacing', 0).append(
  					$('<tr />')
  						.append($('<td />').append(width).append(' px'))
  						.append($('<td />').append(style))
  						.append($('<td />').append(color))
  				)
  			);
  		
  		function rgb2hex(str) {
  		    function hex(x)  {
  		    	hexDigits = ["0", "1", "2", "3", "4", "5", "6", "7", "8","9", "a", "b", "c", "d", "e", "f"];
  		        return !x  ? "00" : hexDigits[(x - x % 16) / 16] + hexDigits[x% 16];
  		    }
  			var rgb = (str||'').match(/\(([0-9]{1,3}),\s*([0-9]{1,3}),\s*([0-9]{1,3})\)/); 
  			return rgb ? "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]) : '';
  		}
  		
  		function toPixels(num) {
  			if (!num) {
  				return num;
  			}
  			var m = num.match(/([0-9]+\.?[0-9]*)\s*(px|pt|em|%)/);
  			if (m) {
  				num  = m[1];
  				unit = m[2];
  			} 
  			if (num[0] == '.') {
  				num = '0'+num;
  			}
  			num = parseFloat(num);
  
  			if (isNaN(num)) {
  				return '';
  			}
  			var base = parseInt($(document.body).css('font-size')) || 16;
  			switch (unit) {
  				case 'em': return parseInt(num*base);
  				case 'pt': return parseInt(num*base/12);
  				case '%' : return parseInt(num*base/100);
  			}
  			return num;
  		}
  		
  		this.change = function() {
  			opts.change && opts.change(this.val());
  		}
  		
  		this.val = function(v) {
  			var w, s, c, b, m;
  			
  			if (!v && v !== '') {
  				w = parseInt(width.val());
  				w = !isNaN(w) ? w+'px' : '';
  				s = style.val();
  				c = color.val();
  				return {
  					width : w,
  					style : s,
  					color : c,
  					css   : $.trim(w+' '+s+' '+c)
  				}
  			} else {
  				b = '';
  				if (v.nodeName || v.css) {
  					if (!v.css) {
  						v = $(v);					
  					}
  					b = v.css('border');
  					if ((b = v.css('border'))) {
  						w = s = c = b;
  					} else {
  						w = v.css('border-width');
  						s = v.css('border-style');
  						c = v.css('border-color');
  					}
  
  				} else {
  					w = v.width||'';
  					s = v.style||'';
  					c = v.color||'';
  				}
  				if (!c) { // fix IE8
  					c = '#000000';
  				}
  
  				width.val(toPixels(w));
  				m = s ? s.match(/(solid|dashed|dotted|double|groove|ridge|inset|outset)/i) :'';
  				style.val(m ? m[1] : '');
  				color.val(c.indexOf('#') === 0 ? c : rgb2hex(c)); // TODO IE8ここでnullpoでてる
  				return this;
  			}
  		}
  		
  		this.val(opts.value);
  		return this;
  	}
  	
  	$.fn.elBorderSelect.defaults = {
  		name      : 'el-borderselect',
  		'class'   : 'el-borderselect',
  		value     : {},
  		change    : null
  	}
  	
  })(jQuery);
  /**
   * jQuery plugin. Create group of text input fields and selects for setting padding/margin. Used in elRTE
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   **/
  (function($) {
  	
  	$.fn.elPaddingInput = function(o) {
  		var self = this;
  		var opts = $.extend({}, $.fn.elPaddingInput.defaults, {name : this.attr('name')}, o);
  		this.regexps = {
  			main   : new RegExp(opts.type == 'padding' ? 'padding\s*:\s*([^;"]+)'        : 'margin\s*:\s*([^;"]+)',       'im'),
  			left   : new RegExp(opts.type == 'padding' ? 'padding-left\s*:\s*([^;"]+)'   : 'margin-left\s*:\s*([^;"]+)',  'im'),
  			top    : new RegExp(opts.type == 'padding' ? 'padding-top\s*:\s*([^;"]+)'    : 'margin-top\s*:\s*([^;"]+)',    'im'),
  			right  : new RegExp(opts.type == 'padding' ? 'padding-right\s*:\s*([^;"]+)'  : 'margin-right\s*:\s*([^;"]+)',  'im'),
  			bottom : new RegExp(opts.type == 'padding' ? 'padding-bottom\s*:\s*([^;"]+)' : 'margin-bottom\s*:\s*([^;"]+)', 'im')
  		};
  			
  		$.each(['left', 'top', 'right', 'bottom'], function() {
  			
  			self[this] = $('<input type="text" />')
  				.attr('size', 3)
  				.css('text-align', 'right')
  				.css('border-'+this, '2px solid red')
  				.bind('change', function() { $(this).val(parseNum($(this).val())); change(); })
  				.attr('name', opts.name+'['+this+']');
  		});
  		$.each(['uleft', 'utop', 'uright', 'ubottom'], function() {
  			self[this] = $('<select />')
  				.append('<option value="px">px</option>')
  				.append('<option value="em">em</option>')
  				.append('<option value="pt">pt</option>')
  				.bind('change', function() { change(); })
  				.attr('name', opts.name+'['+this+']');
  			if (opts.percents) {
  				self[this].append('<option value="%">%</option>');
  			}
  		});
  		
  		this.empty().addClass(opts['class'])
  			.append(this.left).append(this.uleft).append(' x ')
  			.append(this.top).append(this.utop).append(' x ')
  			.append(this.right).append(this.uright).append(' x ')
  			.append(this.bottom).append(this.ubottom);
  			
  		this.val = function(v) {
  			if (!v && v!=='') {
  				var l = parseNum(this.left.val());
  				var t = parseNum(this.top.val());
  				var r = parseNum(this.right.val());
  				var b = parseNum(this.bottom.val());
  				var ret = {
  					left   : l=='auto' || l==0 ? l : (l!=='' ? l+this.uleft.val()   : ''), 
  					top    : t=='auto' || t==0 ? t : (t!=='' ? t+this.utop.val()    : ''),
  					right  : r=='auto' || r==0 ? r : (r!=='' ? r+this.uright.val()  : ''),
  					bottom : b=='auto' || b==0 ? b : (b!=='' ? b+this.ubottom.val() : ''),
  					css    : ''
  				};
  				if (ret.left!=='' && ret.right!=='' && ret.top!=='' && ret.bottom!=='') {
  					if (ret.left == ret.right && ret.top == ret.bottom) {
  						ret.css = ret.top+' '+ret.left;
  					} else{
  						ret.css = ret.top+' '+ret.right+' '+ret.bottom+' '+ret.left;
  					}
  				}
  				
  				return ret;
  			} else {
  				
  				if (v.nodeName || v.css) {
  					if (!v.css) {
  						v = $(v);
  					}
  					var val   = {left : '', top : '', right: '', bottom : ''};
  					var style = (v.attr('style')||'').toLowerCase();
  
  					if (style) {
  						style   = $.trim(style);
  						var m = style.match(this.regexps.main);
  						if (m) {
  							var tmp    = $.trim(m[1]).replace(/\s+/g, ' ').split(' ', 4);
  							val.top    = tmp[0];
  							val.right  = tmp[1] && tmp[1]!=='' ? tmp[1] : val.top;
  							val.bottom = tmp[2] && tmp[2]!=='' ? tmp[2] : val.top;
  							val.left   = tmp[3] && tmp[3]!=='' ? tmp[3] : val.right;
  						} else {
  							$.each(['left', 'top', 'right', 'bottom'], function() {
  								var name = this.toString();
  								m = style.match(self.regexps[name]);
  								if (m) {
  									val[name] = m[1];
  								}
  							});
  						}
  					}
  					var v = val;
  				} 
  
  				$.each(['left', 'top', 'right', 'bottom'], function() {
  					var name = this.toString();
  					self[name].val('');
  					self['u'+name].val();
  					if (typeof(v[name]) != 'undefined' && v[name] !== null) {
  						v[name] = v[name].toString();
  						var _v = parseNum(v[name]);
  						self[name].val(_v);
  						var m = v[name].match(/(px|em|pt|%)/i);
  						self['u'+name].val(m ? m[1] : 'px');
  					}
  				});
  				return this;
  			}
  		}
  			
  		function parseNum(num) {
  			num = $.trim(num.toString());
  			if (num[0] == '.') { 
  				num = '0'+num;
  			}
  			n = parseFloat(num);
  			return !isNaN(n) ? n : (num == 'auto' ? num : '');
  		}
  			
  		function change() {
  			opts.change && opts.change(self);
  		}
  		
  		this.val(opts.value);
  		
  		return this;
  	}
  	
  	$.fn.elPaddingInput.defaults = {
  		name     : 'el-paddinginput',
  		'class'  : 'el-paddinginput',
  		type     : 'padding',
  		value    : {},
  		percents : true,
  		change   : null
  	}
  	
  })(jQuery);
  /**
   * elSelect JQuery plugin
   * Replacement for select input
   * Allow to put any html and css decoration in drop-down list
   *
   * Usage:
   *   $(selector).elSelect(opts)
   *
   * set value after init:
   *   var c = $(selector).elSelect(opts)
   *   c.val('some value')
   *
   * Get selected value:
   *   var val = c.val();
   *
   * Notice!
   *   1. When called on multiply elements, elSelect create drop-down list only for fist element
   *   2. Elements list created only after first click on element (lazzy loading)
   *
   * Options:
   *   src       - object with pairs value:label to create drop-down list 
   *   value     - current (selected) value
   *   class     - css class for display "button" (element on wich plugin was called)
   *   listClass - css class for drop down elements list
   *   select    - callback, called when value was selected (by default write value to console.log)
   *   name      - hidden text field in wich selected value will saved
   *   maxHeight - elements list max height (if height greater - scroll will appear)
   *   tpl       - template for element in list (contains 2 vars: %var - for src key, %label - for src[val] )
   *   labelTpl  - template for label (current selected element) (contains 2 placeholders: %var - for src key, %label - for src[val] )
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   **/
  (function($) {
  	
  	$.fn.elSelect = function(o) {
  		var $self    = this;
  		var self     = this.eq(0);
  		var opts     = $.extend({}, $.fn.elSelect.defaults, o);
  		var hidden   = $('<input type="hidden" />').attr('name', opts.name);
  		var label    = $('<label />').attr({unselectable : 'on'}).addClass('rounded-left-3');
  		var list     = null;
  		var ieWidth  = null;
  
  		if (self.get(0).nodeName == 'SELECT') {
  			opts.src = {};
  			self.children('option').each(function() {
  				opts.src[$(this).val()] = $(this).text();
  			});
  			opts.value = self.val();
  			opts.name  = self.attr('name');
  			self.replaceWith((self = $('<div />')));
  		}
  		
  		if (!opts.value || !opts.src[opts.val]) {
  			opts.value = null;
  			var i = 0;
  			for (var v in opts.src) {
  				if (i++ == 0) {
  					opts.value = v;
  				}
  			}
  		}
  
  		this.val = function(v) {
  			if (!v && v!=='') {
  				return hidden.val();
  			} else {
  				if (opts.src[v]) {
  					hidden.val(v);
  					updateLabel(v);
  					if (list) {
  						list.children().each(function() {
  							if ($(this).attr('name') == v) {
  								$(this).addClass('active');
  							} else {
  								$(this).removeClass('active');
  							}
  						});
  					}
  				}
  				return this;
  			}
  		}
  	
  		// update label content
  		function updateLabel(v) {
  			var tpl = opts.labelTpl || opts.tpls[v] || opts.tpl;
  			label.html(tpl.replace(/%val/g, v).replace(/%label/, opts.src[v])).children().attr({unselectable : 'on'});
  		}
  		
  		// init "select"
  		self.empty()
  			.addClass(opts['class']+' rounded-3')
  			.attr({unselectable : 'on'})
  			.append(hidden)
  			.append(label)
  			.hover(
  				function() { $(this).addClass('hover') },
  				function() { $(this).removeClass('hover') }
  			)
  			.click(function(e) {
  				!list && init();
  				list.slideToggle();
  				// stupid ie inherit width from parent
  				if ($.browser.msie && !ieWidth) { 
  					list.children().each(function() {
  						ieWidth = Math.max(ieWidth, $(this).width());
  					});
  					if (ieWidth > list.width()) {
  						list.width(ieWidth+40);
  					}
  				}
  			});
  			
  		this.val(opts.value);
  	
  		// create drop-down list
  		function init() {
  			// not ul because of ie is stupid with mouseleave in it :(
  			list = $('<div />')
  				.addClass(opts.listClass+' rounded-3')
  				.hide()
  				.appendTo(self.mouseleave(function(e) { list.slideUp(); }));
  
  			for (var v in opts.src) {
  				var tpl = opts.tpls[v] || opts.tpl; 
  				$('<div />')
  					.attr('name', v)
  					.append( $(tpl.replace(/%val/g, v).replace(/%label/g, opts.src[v])).attr({unselectable : 'on'}) )
  					.appendTo(list)
  					.hover(
  						function() { $(this).addClass('hover') },
  						function() { $(this).removeClass('hover') }
  					)
  					.click(function(e) {
  						e.stopPropagation();
  						e.preventDefault();
  						
  						var v = $(this).attr('name');
  						$self.val(v);
  						opts.select(v);
  						list.slideUp();
  					});
  			};
  			
  			var w = self.outerWidth();
  			if (list.width() < w) {
  				list.width(w);
  			}
  			
  			var h = list.height();
  			if (opts.maxHeight>0 && h>opts.maxHeight) {
  				list.height(opts.maxHeight);
  			}
  			
  			$self.val(hidden.val());
  		}
  		
  		return this;
  	}
  	
  	$.fn.elSelect.defaults = {
  		name      : 'el-select',
  		'class'   : 'el-select',
  		listClass : 'list',
  		labelTpl  : null,
  		tpl       : '<%val>%label</%val>',
  		tpls      : {},
  		value     : null,
  		src       : {},
  		select    : function(v) {  window.console &&  window.console.log && window.console.log('selected: '+v); },
  		maxHeight : 410
  	}
  	
  })(jQuery);
  /*
   * elRTE - WSWING editor for web
   *
   * Usage:
   * var opts = {
   *	.... // see elRTE.options.js
   * }
   * var editor = new elRTE($('#my-id').get(0), opts)
   * or
   * $('#my-id').elrte(opts)
   *
   * $('#my-id) may be textarea or any DOM Element with text
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * Copyright: Studio 42, http://www.std42.ru
   */
  (function($) {
  
  elRTE = function(target, opts) {
  	if (!target || !target.nodeName) {
  		return alert('elRTE: argument "target" is not DOM Element');
  	}
  	var self       = this, html;
  	this.version   = '1.3';
  	this.build     = '2011-06-23';
  	this.options   = $.extend(true, {}, this.options, opts);
  	this.browser   = $.browser;
  	this.target    = $(target);
  	
  	this.lang      = (''+this.options.lang);
  	this._i18n     = new eli18n({textdomain : 'rte', messages : { rte : this.i18Messages[this.lang] || {}} });
  	this.rtl       = !!(/^(ar|fa|he)$/.test(this.lang) && this.i18Messages[this.lang]);
  	
  	if (this.rtl) {
  		this.options.cssClass += ' el-rte-rtl';
  	}
  	this.toolbar   = $('<div class="toolbar"/>');
  	this.iframe    = document.createElement('iframe');
  	this.iframe.setAttribute('frameborder', 0); // fixes IE border
  
  	// this.source    = $('<textarea />').hide();
  	this.workzone  = $('<div class="workzone"/>').append(this.iframe).append(this.source);
  	this.statusbar = $('<div class="statusbar"/>');
  	this.tabsbar   = $('<div class="tabsbar"/>');
  	this.editor    = $('<div class="'+this.options.cssClass+'" />').append(this.toolbar).append(this.workzone).append(this.statusbar).append(this.tabsbar);
  	
  	this.doc       = null;
  	this.$doc      = null;
  	this.window    = null;
  	
  	this.utils     = new this.utils(this);
  	this.dom       = new this.dom(this);
  	this.filter    = new this.filter(this)
  	
  	/**
  	 * Sync iframes/textareas height with workzone height 
  	 *
  	 * @return void
  	 */
  	this.updateHeight = function() {
  		self.workzone.add(self.iframe).add(self.source).height(self.workzone.height());
  	}
  	
  	/**
  	 * Turn editor resizable on/off if allowed
  	 *
  	 * @param  Boolean 
  	 * @return void
  	 **/
  	this.resizable = function(r) {
  		var self = this;
  		if (this.options.resizable && $.fn.resizable) {
  			if (r) {
  				this.editor.resizable({handles : 'se', alsoResize : this.workzone, minWidth :300, minHeight : 200 }).bind('resize', self.updateHeight);
  			} else {
  				this.editor.resizable('destroy').unbind('resize', self.updateHeight);
  			}
  		}
  	}
  	
  	/* attach editor to document */
  	this.editor.insertAfter(target);
  	/* init editor textarea */
  	var content = '';
  	if (target.nodeName == 'TEXTAREA') {
  		this.source = this.target;
  		this.source.insertAfter(this.iframe).hide();
  		content = this.target.val();
  	} else {
  		this.source = $('<textarea />').insertAfter(this.iframe).hide();
  		content = this.target.hide().html();
  	}
  	this.source.attr('name', this.target.attr('name')||this.target.attr('id'));
  	content = $.trim(content);
  	if (!content) {
  		content = ' ';
  	}
  
  	/* add tabs */
  	if (this.options.allowSource) {
  		this.tabsbar.append('<div class="tab editor rounded-bottom-7 active">'+self.i18n('Editor')+'</div><div class="tab source rounded-bottom-7">'+self.i18n('Source')+'</div><div class="clearfix" style="clear:both"/>')
  			.children('.tab').click(function(e) {
  				if (!$(this).hasClass('active')) {
  					self.tabsbar.children('.tab').toggleClass('active');
  					self.workzone.children().toggle();
  
  					if ($(this).hasClass('editor')) {
  						self.updateEditor();
  						self.window.focus();
  						self.ui.update(true);
  					} else {
  						self.updateSource();
  						self.source.focus();
  						if (self.source[0].setSelectionRange) {
  							self.source[0].setSelectionRange(0, 0);
  						} else {
  							// TODO under IE9
  						}
  						self.ui.disable();
  						self.statusbar.empty();
  					}
  				}
  			});
  	}
  	
  	this.window = this.iframe.contentWindow;
  	this.doc    = this.iframe.contentWindow.document;
  	this.$doc   = $(this.doc);
  	
  	/* put content into iframe */
  	//html = '<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
  	html = '<!doctype html><html lang="ja"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
  	$.each(self.options.cssfiles, function() {
  		html += '<link rel="stylesheet" type="text/css" href="'+this+'" />';
  	});
  	this.doc.open();
  	var s = this.filter.wysiwyg(content),
  		cl = this.rtl ? ' class="el-rte-rtl"' : '';
  	this.doc.write(self.options.doctype+html+'</head><body'+cl+'>'+(s)+'</body></html>');
  	this.doc.close();
  	
  	/* make iframe editable */
  	/*
  	if ($.browser.msie) {
  		this.doc.body.contentEditable = true;
  	} else {
  		try { this.doc.designMode = "on"; } 
  		catch(e) { }
  		this.doc.execCommand('styleWithCSS', false, this.options.styleWithCSS);
  	}
  	*/
  	if ('isContentEditable' in document.createElement('span')) {
  		this.doc.body.contentEditable = true;
  		//this.doc.execCommand('styleWithCSS', false, this.options.styleWithCSS);
  		this.setStyleWithCSS(this.doc, this.options.styleWithCSS);
  	} else {
  		try { this.doc.designMode = "on"; }
  		catch(e) { }
  		//this.doc.execCommand('styleWithCSS', false, this.options.styleWithCSS);
  		this.setStyleWithCSS(this.doc, this.options.styleWithCSS);
  	}
  	
  	if (this.options.height>0) {
  		this.workzone.height(this.options.height);
  	}
  	if (this.options.width>0) {
  		this.editor.width(this.options.width);
  	}
  	
  	this.updateHeight();
  	this.resizable(true);
  	this.window.focus();
  	
  	this.history = new this.history(this);
  	
  	/* init selection object */
  	this.selection = new this.selection(this);
  	/* init buttons */
  	this.ui = new this.ui(this);
  	
  	
  	/* bind updateSource to parent form submit */
  	this.target.parents('form').bind('submit.elfinder', function(e) {
  		self.source.parents('form').find('[name="el-select"]').remove()
  		self.beforeSave();
  	});
  	
  	// on tab press - insert \t and prevent move focus
  	this.source.bind('keydown', function(e) {
  		if (e.keyCode == 9) {
  			e.preventDefault();
  				
  			if ($.browser.msie) {
  				var r = document.selection.createRange();
  				r.text = "\t"+r.text;
  				this.focus();
  			} else {
  				var before = this.value.substr(0, this.selectionStart),
  					after = this.value.substr(this.selectionEnd);
  				this.value = before+"\t"+after;
  				this.setSelectionRange(before.length+1, before.length+1);
  			}
  		}
  	});
  	
  	$(this.doc.body).bind('dragend', function(e) {
  		setTimeout(function() {
  			try {
  				self.window.focus();
  				var bm = self.selection.getBookmark();
  				self.selection.moveToBookmark(bm);
  				self.ui.update();
  			} catch(e) { }
  			
  			
  		}, 200);
  		
  	});
  	
  	this.typing = false;
  	this.lastKey = null;
  	/* update buttons on click and keyup */
  	this.$doc.bind('mouseup', function() {
  		self.typing = false;
  		self.lastKey = null;
  		self.ui.update();
  	})
  	.bind('keyup', function(e) {
  		if ((e.keyCode >= 8 && e.keyCode <= 13) || (e.keyCode>=32 && e.keyCode<= 40) || e.keyCode == 46 || (e.keyCode >=96 && e.keyCode <= 111)) {
  			self.ui.update();
  		}
  	})
  	.bind('keydown', function(e) {
  		if ((e.metaKey || e.ctrlKey) && e.keyCode == 65) {
  			self.ui.update();
  		} else if (e.keyCode == 13) {
  			var n = self.selection.getNode();
  			// self.log(n)
  			if (self.dom.selfOrParent(n, /^PRE$/)) {
  				self.selection.insertNode(self.doc.createTextNode("\r\n"));
  				return false;
  			} else if ($.browser.safari && e.shiftKey) {
  				self.selection.insertNode(self.doc.createElement('br'))
  				return false;
  			}
  		}
  
  		if ((e.keyCode>=48 && e.keyCode <=57) || e.keyCode==61 || e.keyCode == 109 || (e.keyCode>=65 && e.keyCode<=90) || e.keyCode==188 ||e.keyCode==190 || e.keyCode==191 || (e.keyCode>=219 && e.keyCode<=222)) {
  			if (!self.typing) {
  				self.history.add(true);
  			}
  			self.typing = true;
  			self.lastKey = null;
  		} else if (e.keyCode == 8 || e.keyCode == 46 || e.keyCode == 32 || e.keyCode == 13) {
  			if (e.keyCode != self.lastKey) {
  				self.history.add(true);
  			}
  			self.lastKey = e.keyCode;
  			self.typing = false;
  		}
  		
  		if (e.keyCode == 32 && $.browser.opera) {
  			self.selection.insertNode(self.doc.createTextNode(" "));
  			return false
  		}
  	})
  	.bind('paste', function(e) {
  		if (!self.options.allowPaste) {
  			// paste denied 
  			e.stopPropagation();
  			e.preventDefault();
  		} else {
  			var n = $(self.dom.create('div'))[0],
  				r = self.doc.createTextNode('_');
  			self.history.add(true);
  			self.typing = true;
  			self.lastKey = null;
  			n.appendChild(r);
  			self.selection.deleteContents().insertNode(n);
  			self.selection.select(r);
  			setTimeout(function() {
  				if (n.parentNode) {
  					// clean sandbox content
  					$(n).html(self.filter.proccess('paste', $(n).html()));
  					r = n.lastChild;
  					self.dom.unwrap(n);
  					if (r) {
  						self.selection.select(r);
  						self.selection.collapse(false);
  					}
  				} else {
  					// smth wrong - clean all doc
  					n.parentNode && n.parentNode.removeChild(n);
  					self.val(self.filter.proccess('paste', self.filter.wysiwyg2wysiwyg($(self.doc.body).html())));
  					self.selection.select(self.doc.body.firstChild);
  					self.selection.collapse(true);
  				}
  				$(self.doc.body).mouseup(); // to activate history buutons
  			}, 15);
  		}
  	});
  	
  	if ($.browser.msie) {
  		this.$doc.bind('keyup', function(e) {
  			if (e.keyCode == 86 && (e.metaKey||e.ctrlKey)) {
  				self.history.add(true);
  				self.typing = true;
  				self.lastKey = null;
  				self.selection.saveIERange();
  				self.val(self.filter.proccess('paste', self.filter.wysiwyg2wysiwyg($(self.doc.body).html())));
  				self.selection.restoreIERange();
  				$(self.doc.body).mouseup();
  				this.ui.update();
  			}
  		});
  	}
  	
  	if ($.browser.safari) {
  		this.$doc.bind('click', function(e) {
  			$(self.doc.body).find('.elrte-webkit-hl').removeClass('elrte-webkit-hl');
  			if (e.target.nodeName == 'IMG') {
  				$(e.target).addClass('elrte-webkit-hl');
  			}
  		}).bind('keyup', function(e) {
  			$(self.doc.body).find('.elrte-webkit-hl').removeClass('elrte-webkit-hl');
  		})
  	}
  	
  	this.window.focus();
  	
  	this.destroy = function() {
  		this.updateSource();
  		this.target.is('textarea')
  			? this.target.val($.trim(this.source.val()))
  			: this.target.html($.trim(this.source.val()));
  		this.editor.remove();
  		this.target.show().parents('form').unbind('submit.elfinder');
  	}
  	
  }
  
  /**
   * Return message translated to selected language
   *
   * @param  string  msg  message text in english
   * @return string
   **/
  elRTE.prototype.i18n = function(msg) {
  	return this._i18n.translate(msg);
  }
  
  
  
  /**
   * Display editor
   *
   * @return void
   **/
  elRTE.prototype.open = function() {
  	this.editor.show();
  }
  
  /**
   * Hide editor and display elements on wich editor was created
   *
   * @return void
   **/
  elRTE.prototype.close = function() {
  	this.editor.hide();
  }
  
  elRTE.prototype.updateEditor = function() {
  	this.val(this.source.val());
  }
  
  elRTE.prototype.updateSource = function() {
  	this.source.val(this.filter.source($(this.doc.body).html()));
  }
  
  /**
   * Return edited text
   *
   * @return String
   **/
  elRTE.prototype.val = function(v) {
  	if (typeof(v) == 'string') {
  		v = ''+v;
  		if (this.source.is(':visible')) {
  			this.source.val(this.filter.source2source(v));
  		} else {
  			if ($.browser.msie) {
  				this.doc.body.innerHTML = '<br />'+this.filter.wysiwyg(v);
  				this.doc.body.removeChild(this.doc.body.firstChild);
  			} else {
  				this.doc.body.innerHTML = this.filter.wysiwyg(v);
  			}
  			
  		}
  	} else {
  		if (this.source.is(':visible')) {
  			return this.filter.source2source(this.source.val()).trim();
  		} else {
  			//return this.filter.source($(this.doc.body).html()).trim();
  			return this.filter.source($(this.doc.body).html());
  		}
  	}
  }
  
  elRTE.prototype.beforeSave = function() {
  	this.source.val($.trim(this.val())||'');
  }
  
  /**
   * Submit form
   *
   * @return void
   **/
  elRTE.prototype.save = function() {
  	this.beforeSave();
  	this.editor.parents('form').submit();
  }
  
  elRTE.prototype.log = function(msg) {
  	if (window.console && window.console.log) {
  		window.console.log(msg);
  	}
  }
  
  /* IE11対応 */
  elRTE.prototype.setStyleWithCSS = function(target, val) {
  	/*
      try {
          target.execCommand("styleWithCSS", 0, false);
      } catch (e) {
          try {
              Editor.execCommand("useCSS", 0, true);
          } catch (e) {
              try {
                  Editor.execCommand('styleWithCSS', false, false);
              }
              catch (e) {
              }
          }
      }
      */
      try {
      	target.execCommand('styleWithCSS', false, val);
      } catch (e1) {
      	try {
      		this.log(e1);
      		target.execCommand('styleWithCSS', 0, val);
      	} catch (e2) {
      		try {
      			this.log(e2);
      			target.execCommand('useCSS', 0, !val);
      		} catch (e3) {
      			this.log(e3);
      		}
      	}
      }
  }
  
  elRTE.prototype.i18Messages = {};
  
  $.fn.elrte = function(o, v) { 
  	var cmd = typeof(o) == 'string' ? o : '', ret;
  	
  	this.each(function() {
  		if (!this.elrte) {
  			this.elrte = new elRTE(this, typeof(o) == 'object' ? o : {});
  		}
  		switch (cmd) {
  			case 'open':
  			case 'show':
  				this.elrte.open();
  				break;
  			case 'close':
  			case 'hide':
  				this.elrte.close();
  				break;
  			case 'updateSource':
  				this.elrte.updateSource();
  				break;
  			case 'destroy':
  				this.elrte.destroy();
  		}
  	});
  	
  	if (cmd == 'val') {
  		if (!this.length) {
  			return '';
  		} else if (this.length == 1) {
  			return v || v === '' ? this[0].elrte.val(v) : this[0].elrte.val();
  		} else {
  			ret = {}
  			this.each(function() {
  				ret[this.elrte.source.attr('name')] = this.elrte.val();
  			});
  			return ret;
  		}
  	}
  	return this;
  }
  
  })(jQuery);
  /*
   * DOM utilites for elRTE 
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   */
  (function($) {
  elRTE.prototype.dom = function(rte) {
  	this.rte = rte;
  	var self = this;
  	this.regExp = {
  		textNodes         : /^(A|ABBR|ACRONYM|ADDRESS|B|BDO|BIG|BLOCKQUOTE|CAPTION|CENTER|CITE|CODE|DD|DEL|DFN|DIV|DT|EM|FIELDSET|FONT|H[1-6]|I|INS|KBD|LABEL|LEGEND|LI|MARQUEE|NOBR|NOEMBED|P|PRE|Q|SAMP|SMALL|SPAN|STRIKE|STRONG|SUB|SUP|TD|TH|TT|VAR)$/,
  		textContainsNodes : /^(A|ABBR|ACRONYM|ADDRESS|B|BDO|BIG|BLOCKQUOTE|CAPTION|CENTER|CITE|CODE|DD|DEL|DFN|DIV|DL|DT|EM|FIELDSET|FONT|H[1-6]|I|INS|KBD|LABEL|LEGEND|LI|MARQUEE|NOBR|NOEMBED|OL|P|PRE|Q|SAMP|SMALL|SPAN|STRIKE|STRONG|SUB|SUP|TABLE|THEAD|TBODY|TFOOT|TD|TH|TR|TT|UL|VAR)$/,
  		block             : /^(APPLET|BLOCKQUOTE|BR|CAPTION|CENTER|COL|COLGROUP|DD|DIV|DL|DT|H[1-6]|EMBED|FIELDSET|LI|MARQUEE|NOBR|OBJECT|OL|P|PRE|TABLE|THEAD|TBODY|TFOOT|TD|TH|TR|UL)$/,
  		selectionBlock    : /^(APPLET|BLOCKQUOTE|BR|CAPTION|CENTER|COL|COLGROUP|DD|DIV|DL|DT|H[1-6]|EMBED|FIELDSET|LI|MARQUEE|NOBR|OBJECT|OL|P|PRE|TD|TH|TR|UL)$/,		
  		header            : /^H[1-6]$/,
  		formElement       : /^(FORM|INPUT|HIDDEN|TEXTAREA|SELECT|BUTTON)$/
  	};
  	
  	/********************************************************/
  	/*                      Утилиты                         */
  	/********************************************************/	
  	
  	/**
  	 * Возвращает body редактируемого документа
  	 *
  	 * @return Element
  	 **/
  	this.root = function() {
  		return this.rte.body;
  	}
  
  	this.create = function(t) {
  		return this.rte.doc.createElement(t);
  	}
  
  	/**
  	 * Return node for bookmark with unique ID
  	 *
  	 * @return DOMElement
  	 **/
  	this.createBookmark = function() {
  		var b = this.rte.doc.createElement('span');
  		b.id = 'elrte-bm-'+Math.random().toString().substr(2);
  		$(b).addClass('elrtebm elrte-protected');
  		return b;
  	}
  
  	/**
  	 * Вовращает индекс элемента внутри родителя
  	 *
  	 * @param  Element n  нода
  	 * @return integer
  	 **/
  	this.indexOf = function(n) {
  		var ndx = 0;
  		n = $(n);
  		while ((n = n.prev()) && n.length) {
  			ndx++;
  		}
  		return ndx;
  	}
  	
  	/**
  	 * Вовращает значение аттрибута в нижнем регистре (ох уж этот IE)
  	 *
  	 * @param  Element n  нода
  	 * @param  String  attr имя аттрибута
  	 * @return string
  	 **/
  	this.attr = function(n, attr) {
  		if (!n) {
  			return "";
  		}
  		var v = '';
  		if (n.nodeType == 1) {
  			v = $(n).attr(attr);
  			if (v && attr != 'src' && attr != 'href' && attr != 'title' && attr != 'alt') {
  				v = v.toString().toLowerCase();
  			}
  		} 
  		return v||'';
  	}
  	
  	/**
  	 * Вовращает ближайший общий контейнер для 2-х эл-тов
  	 *
  	 * @param  Element n  нода1
  	 * @param  Element n  нода2
  	 * @return Element
  	 **/
  	this.findCommonAncestor = function(n1, n2) {
  		if (!n1 || !n2) {
  			return this.rte.log('dom.findCommonAncestor invalid arguments');
  		}
  		if (n1 == n2) {
  			return n1;
  		} else if (n1.nodeName == 'BODY' || n2.nodeName == 'BODY') {
  			return this.rte.doc.body;
  		}
  		var p1 = $(n1).parents(), p2 = $(n2).parents(), l  = p2.length-1, c  = p2[l];
  		for (var i = p1.length - 1; i >= 0; i--, l--){
  			if (p1[i] == p2[l]) {
  				c = p1[i];
  			} else {
  				break;
  			}
  		};
  		return c;
  	}
  	/**
  	 * Вовращает TRUE, если нода пустая
  	 * пустой считаем ноды:
  	 *  - текстовые эл-ты, содержащие пустую строку или тег br
  	 *  - текстовые ноды с пустой строкой
  	 *
  	 * @param  DOMElement n  нода
  	 * @return bool
  	 **/
  	this.isEmpty = function(n) {
  		if (n.nodeType == 1) {
  			return this.regExp.textNodes.test(n.nodeName) ? $.trim($(n).text()).length == 0 : false;
  		} else if (n.nodeType == 3) {
  			return /^(TABLE|THEAD|TFOOT|TBODY|TR|UL|OL|DL)$/.test(n.parentNode.nodeName)
  				|| n.nodeValue == ''
  				|| ($.trim(n.nodeValue).length== 0 && !(n.nextSibling && n.previousSibling && n.nextSibling.nodeType==1 && n.previousSibling.nodeType==1 && !this.regExp.block.test(n.nextSibling.nodeName) && !this.regExp.block.test(n.previousSibling.nodeName) ));
  		}
  		return true;
  	}
  
  	/********************************************************/
  	/*                  Перемещение по DOM                  */
  	/********************************************************/
  
  	/**
  	 * Вовращает следующую соседнюю ноду (не включаются текстовые ноды не создающие значимые пробелы между инлайн элементами)
  	 *
  	 * @param  DOMElement n  нода
  	 * @return DOMElement
  	 **/
  	this.next = function(n) {
  		while (n.nextSibling && (n = n.nextSibling)) {
  			if (n.nodeType == 1 || (n.nodeType == 3 && !this.isEmpty(n))) {
  				return n;
  			}
  		}
  		return null;
  	}
  
  	/**
  	 * Вовращает предыдующую соседнюю ноду (не включаются текстовые ноды не создающие значимые пробелы между инлайн элементами)
  	 *
  	 * @param  DOMElement n  нода
  	 * @return DOMElement
  	 **/
  	this.prev = function(n) {
  		while (n.previousSibling && (n = n.previousSibling)) {
  			if (n.nodeType == 1 || (n.nodeType ==3 && !this.isEmpty(n))) {
  				return n;
  			}
  		}
  		return null;
  	}
  
  	this.isPrev = function(n, prev) {
  		while ((n = this.prev(n))) {
  			if (n == prev) {
  				return true;
  			}
  		}
  		return false;
  	}
  
  	/**
  	 * Вовращает все следующие соседнии ноды (не включаются текстовые ноды не создающие значимые пробелы между инлайн элементами)
  	 *
  	 * @param  DOMElement n  нода
  	 * @return Array
  	 **/
  	this.nextAll = function(n) {
  		var ret = [];
  		while ((n = this.next(n))) {
  			ret.push(n);
  		}
  		return ret;
  	}
  	
  	/**
  	 * Вовращает все предыдующие соседнии ноды (не включаются текстовые ноды не создающие значимые пробелы между инлайн элементами)
  	 *
  	 * @param  DOMElement n  нода
  	 * @return Array
  	 **/
  	this.prevAll = function(n) {
  		var ret = [];
  		while ((n = this.prev(n))) {
  			ret.push(n);
  		}
  		return ret;
  	}
  	
  	/**
  	 * Вовращает все следующие соседнии inline ноды (не включаются текстовые ноды не создающие значимые пробелы между инлайн элементами)
  	 *
  	 * @param  DOMElement n  нода
  	 * @return Array
  	 **/
  	this.toLineEnd = function(n) {
  		var ret = [];
  		while ((n = this.next(n)) && n.nodeName != 'BR' && n.nodeName != 'HR' && this.isInline(n)) {
  			ret.push(n);
  		}
  		return ret;
  	}
  	
  	/**
  	 * Вовращает все предыдующие соседнии inline ноды (не включаются текстовые ноды не создающие значимые пробелы между инлайн элементами)
  	 *
  	 * @param  DOMElement n  нода
  	 * @return Array
  	 **/
  	this.toLineStart = function(n) {
  		var ret = [];
  		while ((n = this.prev(n)) && n.nodeName != 'BR' && n.nodeName != 'HR' && this.isInline(n) ) {
  			ret.unshift(n);
  		}
  		return ret;
  	}
  	
  	/**
  	 * Вовращает TRUE, если нода - первый непустой эл-т внутри родителя
  	 *
  	 * @param  Element n  нода
  	 * @return bool
  	 **/
  	this.isFirstNotEmpty = function(n) {
  		while ((n = this.prev(n))) {
  			if (n.nodeType == 1 || (n.nodeType == 3 && $.trim(n.nodeValue)!='' ) ) {
  				return false;
  			}
  		}
  		return true;
  	}
  	
  	/**
  	 * Вовращает TRUE, если нода - последний непустой эл-т внутри родителя
  	 *
  	 * @param  Element n  нода
  	 * @return bool
  	 **/
  	this.isLastNotEmpty = function(n) {
  		while ((n = this.next(n))) {
  			if (!this.isEmpty(n)) {
  				return false;
  			}
  		}
  		return true;
  	}
  	
  	/**
  	 * Вовращает TRUE, если нода - единственный непустой эл-т внутри родителя
  	 *
  	 * @param  DOMElement n  нода
  	 * @return bool
  	 **/
  	this.isOnlyNotEmpty = function(n) {
  		return this.isFirstNotEmpty(n) && this.isLastNotEmpty(n);
  	}
  	
  	/**
  	 * Вовращает последний непустой дочерний эл-т ноды или FALSE
  	 *
  	 * @param  Element n  нода
  	 * @return Element
  	 **/
  	this.findLastNotEmpty = function(n) {
  		this.rte.log('findLastNotEmpty Who is here 0_o');
  		if (n.nodeType == 1 && (l = n.lastChild)) {
  			if (!this.isEmpty(l)) {
  				return l;
  			}
  			while (l.previousSibling && (l = l.previousSibling)) {
  				if (!this.isEmpty(l)) {
  					return l;
  				}
  			}
  		}
  		return false;
  	}
  	
  	/**
  	 * Возвращает TRUE, если нода "inline" 
  	 *
  	 * @param  DOMElement n  нода
  	 * @return bool
  	 **/
  	this.isInline = function(n) {
  		if (n.nodeType == 3) {
  			return true;
  		} else if (n.nodeType == 1) {
  			n = $(n);
  			var d = n.css('display');
  			var f = n.css('float');
  			return d == 'inline' || d == 'inline-block' || f == 'left' || f == 'right';
  		}
  		return true;
  	}
  	
  	
  	/********************************************************/
  	/*                  Поиск элементов                     */
  	/********************************************************/
  	
  	this.is = function(n, f) {
  		if (n && n.nodeName) {
  
  			if (typeof(f) == 'string') {
  				f = this.regExp[f]||/.?/;
  			}
  
  			if (f instanceof RegExp && n.nodeName) {
  				return f.test(n.nodeName);
  			} else if (typeof(f) == 'function') {
  				return f(n);
  			}
  		}
  		
  		return false;
  	}
  	
  	/**
  	 * Вовращает элемент(ы) отвечающие условиям поиска
  	 *
  	 * @param  DOMElement||Array  n       нода
  	 * @param  RegExp||String     filter  фильтр условия поиска (RegExp или имя ключа this.regExp или *)
  	 * @return DOMElement||Array
  	 **/
  	this.filter = function(n, filter) {
  		var ret = [], i;
  		if (!n.push) {
  			return this.is(n, filter) ? n : null;
  		}
  		for (i=0; i < n.length; i++) {
  			if (this.is(n[i], filter)) {
  				ret.push(n[i]);
  			}
  		};
  		return ret;
  	}
  	
  	
  	/**
  	 * Вовращает массив родительских элементов, отвечающих условиям поиска
  	 *
  	 * @param  DOMElement      n  нода, родителей, которой ищем
  	 * @param  RegExp||String  filter   фильтр условия поиска (RegExp или имя ключа this.regExp или *)
  	 * @return Array
  	 **/
  	this.parents = function(n, filter) {
  		var ret = [];
  
  		while (n && (n = n.parentNode) && n.nodeName != 'BODY' && n.nodeName != 'HTML') {
  			if (this.is(n, filter)) {
  				ret.push(n);
  			}
  		}
  		return ret;
  	}
  	
  	/**
  	 * Вовращает ближайший родительский эл-т, отвечающий условиям поиска
  	 *
  	 * @param  DOMElement     n  нода, родителя, которой ищем
  	 * @param  RegExp||String f   фильтр условия поиска (RegExp или имя ключа this.regExp или *)
  	 * @return DOMElement
  	 **/
  	this.parent = function(n, f) { 
  		return this.parents(n, f)[0] || null; 
  	}
  	
  	/**
  	 * Вовращает или саму ноду или ее ближайшего родителя, если выполняются условия sf для самой ноды или pf для родителя
  	 *
  	 * @param  DOMElement     n  нода, родителя, которой ищем
  	 * @param  RegExp||String sf   фильтр условия для самой ноды
  	* @param  RegExp||String  pf   фильтр условия для родителя
  	 * @return DOMElement
  	 **/
  	this.selfOrParent = function(n, sf, pf) {
  		return this.is(n, sf) ? n : this.parent(n, pf||sf);
  	}
  	
  	/**
  	 * Вовращает родительскую ноду - ссылку
  	 *
  	 * @param  Element n  нода
  	 * @return Element
  	 **/
  	this.selfOrParentLink = function(n) {
  		n = this.selfOrParent(n, /^A$/);
  		return n && n.href ? n : null;
  	}
  
  	/**
  	 * Вовращает TRUE, если нода -  anchor
  	 *
  	 * @param  Element n  нода
  	 * @return bool
  	 **/
  	this.selfOrParentAnchor = function(n) {
  		n = this.selfOrParent(n, /^A$/);
  		return n && !n.href && n.name ? n : null;
  	}
  
  	/**
  	 * Вовращает массив дочерних ссылок
  	 *
  	 * @param  DOMElement n  нода
  	 * @return Array
  	 **/
  	this.childLinks = function(n) {
  		var res = [];
  		$('a[href]', n).each(function() { res.push(this); });
  		return res;
  	}
  	
  	this.selectionHas = function(f) {
  		var n = this.rte.selection.cloneContents(), i;
  		if (n && n.childNodes && n.childNodes.length) {
  			for (i=0; i < n.childNodes.length; i++) {
  				if (typeof(f) == 'function') {
  					if (f(n.childNodes[i])) {
  						return true;
  					}
  				} else if (n instanceof RegExp) {
  					if (f.test(n.childNodes[i].nodeName)) {
  						return true;
  					}
  				}
  			};
  		}
  		
  		return false;
  	}
  	/********************************************************/
  	/*                    Изменения DOM                     */
  	/********************************************************/
  	
  	/**
  	 * Оборачивает одну ноду другой
  	 *
  	 * @param  DOMElement n  оборачиваемая нода
  	 * @param  DOMElement w  нода обертка или имя тега
  	 * @return DOMElement
  	 **/
  	this.wrap = function(n, w) {
  		n = $.isArray(n) ? n : [n];
  		w = w.nodeName ? w : this.create(w);
  		
  		if (n[0] && n[0].nodeType && n[0].parentNode) {
  			w = n[0].parentNode.insertBefore(w, n[0]);
  			$(n).each(function() {
  				if (this!=w) {
  					w.appendChild(this);
  				}
  			});
  		}
  		
  		return w;
  	}
  	
  	/**
  	 * Replace node with its contents
  	 *
  	 * @param  DOMElement n node
  	 * @return void
  	 **/
  	this.unwrap = function(n) {
  		if (n && n.parentNode) {
  			while (n.firstChild) {
  				n.parentNode.insertBefore(n.firstChild, n);
  			}
  			n.parentNode.removeChild(n);
  			
  		}
  	}
  	
  	/**
  	 * Оборачивает все содержимое ноды
  	 *
  	 * @param  DOMElement n  оборачиваемая нода
  	 * @param  DOMElement w  нода обертка или имя тега
  	 * @return DOMElement
  	 **/
  	this.wrapContents = function(n, w) {
  		w = w.nodeName ? w : this.create(w);
  		for (var i=0; i < n.childNodes.length; i++) {
  			w.appendChild(n.childNodes[i]);
  		};
  		n.appendChild(w);
  		return w;
  	}
  	
  	this.cleanNode = function(n) {
  
  		if (n.nodeType != 1) {
  			return;
  		}
  		if (/^(P|LI)$/.test(n.nodeName) && (l = this.findLastNotEmpty(n)) && l.nodeName == 'BR') {
  			$(l).remove();
  		}
  		$n = $(n);
  		$n.children().each(function() {
  			this.cleanNode(this);
  		});
  		if (n.nodeName != 'BODY' && !/^(TABLE|TR|TD)$/.test(n) && this.isEmpty(n)) {
  			return $n.remove();
  		}
  		if ($n.attr('style') === '') {
  			$n.removeAttr('style');
  		}
  		if (this.rte.browser.safari && $n.hasClass('Apple-span')) {
  			$n.removeClass('Apple-span');
  		}
  		/*
  		if (n.nodeName == 'SPAN' && !$n.attr('style') && !$n.attr('class') && !$n.attr('id')) {
  			$n.replaceWith($n.html());
  		}
  		*/
  	}
  	
  	this.cleanChildNodes = function(n) {
  		var cmd = this.cleanNode;
  		$(n).children().each(function() { cmd(this); });
  	}
  	
  	/********************************************************/
  	/*                       Таблицы                        */
  	/********************************************************/
  	
  	this.tableMatrix = function(n) {
  		var mx = [];
  		if (n && n.nodeName == 'TABLE') {
  			var max = 0;
  			function _pos(r) {
  				for (var i=0; i<=max; i++) {
  					if (!mx[r][i]) {
  						return i;
  					}
  				};
  			}
  			
  			$(n).find('tr').each(function(r) {
  				if (!$.isArray(mx[r])) {
  					mx[r] = [];
  				}
  				
  				$(this).children('td,th').each(function() {
  					var w = parseInt($(this).attr('colspan')||1);
  					var h = parseInt($(this).attr('rowspan')||1);
  					var i = _pos(r);
  					for (var y=0; y<h; y++) {
  						for (var x=0; x<w; x++) {
  							var _y = r+y;
  							if (!$.isArray(mx[_y])) {
  								mx[_y] = [];
  							}
  							var d = x==0 && y==0 ? this : (y==0 ? x : "-");
  							mx[_y][i+x] = d;
  						}
  					};
  					max= Math.max(max, mx[r].length);
  				});
  			});
  		}
  		return mx;
  	}
  	
  	this.indexesOfCell = function(n, tbm) {
  		for (var rnum=0; rnum < tbm.length; rnum++) {
  			for (var cnum=0; cnum < tbm[rnum].length; cnum++) {
  				if (tbm[rnum][cnum] == n) {
  					return [rnum, cnum];
  				}
  				
  			};
  		};
  	}
  	
  	this.fixTable = function(n) {
  		if (n && n.nodeName == 'TABLE') {
  			var tb = $(n);
  			//tb.find('tr:empty').remove();
  			var mx = this.tableMatrix(n);
  			var x  = 0;
  			$.each(mx, function() {
  				x = Math.max(x, this.length);
  			});
  			if (x==0) {
  				return tb.remove();
  			}
  			// for (var i=0; i<mx.length; i++) {
  			// 	this.rte.log(mx[i]);
  			// }
  			
  			for (var r=0; r<mx.length; r++) {
  				var l = mx[r].length;
  				//this.rte.log(r+' : '+l)
  				
  				if (l==0) {
  					//this.rte.log('remove: '+tb.find('tr').eq(r))
  					tb.find('tr').eq(r).remove();
  //					tb.find('tr').eq(r).append('<td>remove</td>')
  				} else if (l<x) {
  					var cnt = x-l;
  					var row = tb.find('tr').eq(r);
  					for (i=0; i<cnt; i++) {
  						row.append('<td>&nbsp;</td>');
  					}
  				}
  			}
  			
  		}
  	}
  	
  	this.tableColumn = function(n, ext, fix) {
  		n      = this.selfOrParent(n, /^TD|TH$/);
  		var tb = this.selfOrParent(n, /^TABLE$/);
  		ret    = [];
  		info   = {offset : [], delta : []};
  		if (n && tb) {
  			fix && this.fixTable(tb);
  			var mx = this.tableMatrix(tb);
  			var _s = false;
  			var x;
  			for (var r=0; r<mx.length; r++) {
  				for (var _x=0; _x<mx[r].length; _x++) {
  					if (mx[r][_x] == n) {
  						x = _x;
  						_s = true;
  						break;
  					}
  				}
  				if (_s) {
  					break;
  				}
  			}
  			
  			// this.rte.log('matrix');
  			// for (var i=0; i<mx.length; i++) {
  			// 	this.rte.log(mx[i]);
  			// }
  			if (x>=0) {
  				for(var r=0; r<mx.length; r++) {
  					var tmp = mx[r][x]||null;
  					if (tmp) {
  						if (tmp.nodeName) {
  							ret.push(tmp);
  							if (ext) {
  								info.delta.push(0);
  								info.offset.push(x);
  							}
  						} else {
  							var d = parseInt(tmp);
  							if (!isNaN(d) && mx[r][x-d] && mx[r][x-d].nodeName) {
  								ret.push(mx[r][x-d]);
  								if (ext) {
  									info.delta.push(d);
  									info.offset.push(x);
  								}
  							}
  						}
  					}
  				}
  			}
  		}
  		return !ext ? ret : {column : ret, info : info};
  	}
  }
  
  })(jQuery);
  (function($) {
  	/**
  	 * @class Filter - clean editor content
  	 * @param elRTE editor instance
  	 * @author Dmitry (dio) Levashov, dio@std42.ru
  	 */
  	elRTE.prototype.filter = function(rte) {
  		var self = this,
  			n = $('<span/>').addClass('elrtetesturl').appendTo(document.body)[0];
  		// media replacement image base url
  		this.url = (typeof(n.currentStyle )!= "undefined" ? n.currentStyle['backgroundImage'] : document.defaultView.getComputedStyle(n, null)['backgroundImage']).replace(/^url\((['"]?)([\s\S]+\/)[\s\S]+\1\)$/i, "$2");
  		$(n).remove();
  
  		this.rte = rte;
  		// flag - return xhtml tags?
  		this.xhtml = /xhtml/i.test(rte.options.doctype);
  		// boolean attributes
  		this.boolAttrs = rte.utils.makeObject('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected'.split(','));
  		// tag regexp
  		this.tagRegExp = /<(\/?)([\w:]+)((?:\s+[a-z\-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*\/?>/g;
  		// this.tagRegExp = /<(\/?)([\w:]+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*\/?>/g;		
  		// opened tag regexp
  		this.openTagRegExp = /<([\w:]+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*\/?>/g;
  		// attributes regexp
  		this.attrRegExp = /([\w:-]+)(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^\s]+))?/g;
  		// script tag regexp
  		this.scriptRegExp = /<script([^>]*)>([\s\S]*?)<\/script>/gi;
  		// style tag regexp
  		this.styleRegExp = /(<style([^>]*)>[\s\S]*?<\/style>)/gi;
  		// link tag regexp
  		this.linkRegExp = /(<link([^>]+)>)/gi;
  		// cdata regexp
  		this.cdataRegExp = /<!\[CDATA\[([\s\S]+)\]\]>/g;
  		// object tag regexp
  		this.objRegExp = /<object([^>]*)>([\s\S]*?)<\/object>/gi;
  		// embed tag regexp
  		this.embRegExp = /<(embed)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*>/gi;
  		// param tag regexp
  		this.paramRegExp = /<(param)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*>/gi;
  		// iframe tag regexp
  		this.iframeRegExp = /<iframe([^>]*)>([\s\S]*?)<\/iframe>/gi;
  
  		// yandex maps regexp
  		this.yMapsRegExp = /<div\s+([^>]*id\s*=\s*('|")?YMapsID[^>]*)>/gi;
  		// google maps regexp
  		this.gMapsRegExp = /<iframe\s+([^>]*src\s*=\s*"http:\/\/maps\.google\.\w+[^>]*)>([\s\S]*?)<\/iframe>/gi;
  		// video hostings url regexp
  		this.videoHostRegExp = /^(http:\/\/[\w\.]*)?(youtube|vimeo|rutube).*/i;
  		// elrte services classes regexp
  		this.serviceClassRegExp = /<(\w+)([^>]*class\s*=\s*"[^>]*elrte-[^>]*)>\s*(<\/\1>)?/gi;
  		this.pagebreakRegExp = /<(\w+)([^>]*style\s*=\s*"[^>]*page-break[^>]*)>\s*(<\/\1>)?/gi;
  		
  		this.pbRegExp = new RegExp('<!-- pagebreak -->', 'gi');
  		// allowed tags
  		this.allowTags = rte.options.allowTags.length ? rte.utils.makeObject(rte.options.allowTags) : null;
  		// denied tags
  		this.denyTags = rte.options.denyTags.length ? rte.utils.makeObject(rte.options.denyTags) : null;
  		// deny attributes
  		this.denyAttr = rte.options.denyAttr ? rte.utils.makeObject(rte.options.denyAttr) : null;
  		// deny attributes for pasted html
  		this.pasteDenyAttr = rte.options.pasteDenyAttr ? rte.utils.makeObject(rte.options.pasteDenyAttr) : null;
  		// font sizes to convert size attr into css property
  		this.fontSize = ['medium', 'xx-small', 'small', 'medium','large','x-large','xx-large' ];
  		// font families regexp to detect family by font name
  		this.fontFamily = {
  			'sans-serif' : /^(arial|tahoma|verdana)$/i,
  			'serif'      : /^(times|times new roman)$/i,
  			'monospace'  : /^courier$/i
  		}
  		// scripts storage
  		this.scripts = {};
  		// cached chains of rules
  		this._chains = {};
  		
  		// cache chains
  		$.each(this.chains, function(n) {
  			self._chains[n] = [];
  			$.each(this, function(i, r) {
  				typeof(self.rules[r]) == 'function' && self._chains[n].push(self.rules[r]);
  			});
  		});
  
  		/**
  		 * filtering through required chain
  		 *
  		 * @param  String  chain name
  		 * @param  String  html-code
  		 * @return String
  		 **/
  		this.proccess = function(chain, html) {
  			// remove whitespace at the begin and end
  			html = $.trim(html).replace(/^\s*(&nbsp;)+/gi, '').replace(/(&nbsp;|<br[^>]*>)+\s*$/gi, '');
  			// pass html through chain
  			$.each(this._chains[chain]||[], function() {
  				html = this.call(self, html);
  			});
  			html = html.replace(/\t/g, '  ').replace(/\r/g, '').replace(/\s*\n\s*\n+/g, "\n")+'  ';
  			return $.trim(html) ? html : '&nbsp;';
  		}
  		
  		/**
  		 * wrapper for "wysiwyg" chain filtering
  		 *
  		 * @param  String  
  		 * @return String
  		 **/
  		this.wysiwyg = function(html) {
  			return this.proccess('wysiwyg', html);
  		}
  		
  		/**
  		 * wrapper for "source" chain filtering
  		 *
  		 * @param  String  
  		 * @return String
  		 **/
  		this.source = function(html) {
  			return this.proccess('source', html);
  		}
  		
  		/**
  		 * wrapper for "source2source" chain filtering
  		 *
  		 * @param  String  
  		 * @return String
  		 **/
  		this.source2source = function(html) {
  			return this.proccess('source2source', html);
  		}
  		
  		/**
  		 * wrapper for "wysiwyg2wysiwyg" chain filtering
  		 *
  		 * @param  String  
  		 * @return String
  		 **/
  		this.wysiwyg2wysiwyg = function(html) {
  			return this.proccess('wysiwyg2wysiwyg', html);
  		}
  		
  		/**
  		 * Parse attributes from string into object
  		 *
  		 * @param  String  string of attributes  
  		 * @return Object
  		 **/
  		this.parseAttrs = function(s) {
  			var a = {},
  				b = this.boolAttrs,
  				m = s.match(this.attrRegExp),
  				t, n, v;
  				// this.rte.log(s)
  			// this.rte.log(m)
  			m && $.each(m, function(i, s) {
  				t = s.split('=');
  				n = $.trim(t[0]).toLowerCase();
  
  				if (t.length>2) {
  					t.shift();
  					v = t.join('=');
  				} else {
  					v = b[n] ||t[1]||'';
  				}
  				a[n] = $.trim(v).replace(/^('|")(.*)(\1)$/, "$2");
  			});
  
  			a.style = this.rte.utils.parseStyle(a.style);
  			// rte.log(a.style)
  			a['class'] = this.rte.utils.parseClass(a['class']||'')
  			return a;
  		}
  		
  		/**
  		 * Restore attributes string from hash
  		 *
  		 * @param  Object  attributes hash
  		 * @return String
  		 **/
  		this.serializeAttrs = function(a, c) {
  			var s = [], self = this;
  
  			$.each(a, function(n, v) {
  				if (n=='style') {
  					v = self.rte.utils.serializeStyle(v, c);
  				} else if (n=='class') {
  					// self.rte.log(v)
  					// self.rte.log(self.rte.utils.serializeClass(v))
  					v = self.rte.utils.serializeClass(v);
  				} 
  				v && s.push(n+'="'+v+'"');
  			});
  			return s.join(' ');
  		}
  		
  		/**
  		 * Remove/replace denied attributes/style properties
  		 *
  		 * @param  Object  attributes hash
  		 * @param  String  tag name to wich attrs belongs 
  		 * @return Object
  		 **/
  		this.cleanAttrs = function(a, t) {
  			var self = this, ra = this.replaceAttrs;
  
  			// remove safari and mso classes
  			$.each(a['class'], function(n) {
  				/^(Apple-style-span|mso\w+)$/i.test(n) && delete a['class'][n];
  			});
  
  			function value(v) {
  				return v+(/\d$/.test(v) ? 'px' : '');
  			}
  
  			$.each(a, function(n, v) {
  				// replace required attrs with css
  				ra[n] && ra[n].call(self, a, t);
  				// remove/fix mso styles
  				if (n == 'style') {
  					$.each(v, function(sn, sv) {
  						switch (sn) {
  							case "mso-padding-alt":
  							case "mso-padding-top-alt":
  							case "mso-padding-right-alt":
  							case "mso-padding-bottom-alt":
  							case "mso-padding-left-alt":
  							case "mso-margin-alt":
  							case "mso-margin-top-alt":
  							case "mso-margin-right-alt":
  							case "mso-margin-bottom-alt":
  							case "mso-margin-left-alt":
  							case "mso-table-layout-alt":
  							case "mso-height":
  							case "mso-width":
  							case "mso-vertical-align-alt":
  								a.style[sn.replace(/^mso-|-alt$/g, '')] = value(sv);
  								delete a.style[sn];
  								break;
  
  							case "horiz-align":
  								a.style['text-align'] = sv;
  								delete a.style[sn];
  								break;
  
  							case "vert-align":
  								a.style['vertical-align'] = sv;
  								delete a.style[sn];
  								break;
  
  							case "font-color":
  							case "mso-foreground":
  								a.style.color = sv;
  								delete a.style[sn];
  							break;
  
  							case "mso-background":
  							case "mso-highlight":
  								a.style.background = sv;
  								delete a.style[sn];
  								break;
  
  							case "mso-default-height":
  								a.style['min-height'] = value(sv);
  								delete a.style[sn];
  								break;
  
  							case "mso-default-width":
  								a.style['min-width'] = value(sv);
  								delete a.style[sn];
  								break;
  
  							case "mso-padding-between-alt":
  								a.style['border-collapse'] = 'separate';
  								a.style['border-spacing'] = value(sv);
  								delete a.style[sn];
  								break;
  
  							case "text-line-through":
  								if (sv.match(/(single|double)/i)) {
  									a.style['text-decoration'] = 'line-through';
  								}
  								delete a.style[sn];
  								break;
  
  							case "mso-zero-height":
  								if (sv == 'yes') {
  									a.style.display = 'none';
  								}
  								delete a.style[sn];
  								break;
  
  							case 'font-weight':
  								if (sv == 700) {
  									a.style['font-weight'] = 'bold';
  								}
  								break;
  
  							default:
  								if (sn.match(/^(mso|column|font-emph|lang|layout|line-break|list-image|nav|panose|punct|row|ruby|sep|size|src|tab-|table-border|text-(?!align|decor|indent|trans)|top-bar|version|vnd|word-break)/)) {
  									delete a.style[sn]
  								}
  						}
  					});
  				}
  			});
  			return a;
  		}
  		
  	}
  
  	// rules to replace tags
  	elRTE.prototype.filter.prototype.replaceTags = {
  		b         : { tag : 'strong' },
  		big       : { tag : 'span', style : {'font-size' : 'large'} },
  		center    : { tag : 'div',  style : {'text-align' : 'center'} },
  		i         : { tag : 'em' },
  		font      : { tag : 'span' },
  		nobr      : { tag : 'span', style : {'white-space' : 'nowrap'} },
  		menu      : { tag : 'ul' },
  		plaintext : { tag : 'pre' },
  		s         : { tag : 'strike' },
  		small     : { tag : 'span', style : {'font-size' : 'small'}},
  		u         : { tag : 'span', style : {'text-decoration' : 'underline'} },
  		xmp       : { tag : 'pre' }
  	}
  	
  	// rules to replace attributes
  	elRTE.prototype.filter.prototype.replaceAttrs = {
  		align : function(a, n) {
  			switch (n) {
  				case 'img':
  					a.style[a.align.match(/(left|right)/) ? 'float' : 'vertical-align'] = a.align;
  					break;
  				
  				case 'table':
  					if (a.align == 'center') {
  						a.style['margin-left'] = a.style['margin-right'] = 'auto';
  					} else {
  						a.style['float'] = a.align;
  					}
  					break;
  					
  				default:
  					a.style['text-align'] = a.align;
  			}
  			delete a.align;
  		},
  		border : function(a) {
  			!a.style['border-width'] && (a.style['border-width'] = (parseInt(a.border)||1)+'px');
  			!a.style['border-style'] && (a.style['border-style'] = 'solid');
  			delete a.border;
  		},
  		bordercolor : function(a) {
  			!a.style['border-color'] && (a.style['border-color'] = a.bordercolor);
  			delete a.bordercolor;
  		},
  		background : function(a) {
  			!a.style['background-image'] && (a.style['background-image'] = 'url('+a.background+')');
  			delete a.background;
  		},
  		bgcolor : function(a) {
  			!a.style['background-color'] && (a.style['background-color'] = a.bgcolor);
  			delete a.bgcolor;
  		},
  		clear : function(a) {
  			a.style.clear = a.clear == 'all' ? 'both' : a.clear;
  			delete a.clear;
  		},
  		color : function(a) {
  			!a.style.color && (a.style.color = a.color);
  			delete a.color;
  		},
  		face : function(a) {
  			var f = a.face.toLowerCase();
  			$.each(this.fontFamily, function(n, r) {
  				if (f.match(r)) {
  					a.style['font-family'] = f+','+n;
  				}
  			});
  			delete a.face;
  		},
  		hspace : function(a, n) {
  			if (n == 'img') {
  				var v = parseInt(a.hspace)||0;
  				!a.style['margin-left'] && (a.style['margin-left'] = v+'px');
  				!a.style['margin-right'] && (a.style['margin-right'] = v+'px')
  				delete a.hspace;
  			}
  		},
  		size : function(a, n) {
  			if (n != 'input') {
  				a.style['font-size'] = this.fontSize[parseInt(a.size)||0]||'medium';
  				delete a.size;
  			}
  		},
  		valign : function(a) {
  			if (!a.style['vertical-align']) {
  				a.style['vertical-align'] = a.valign;
  			}
  			delete a.valign;
  		},
  		vspace : function(a, n) {
  			if (n == 'img') {
  				var v = parseInt(a.vspace)||0;
  				!a.style['margin-top'] && (a.style['margin-top'] = v+'px');
  				!a.style['margin-bottom'] && (a.style['margin-bottom'] = v+'px')
  				delete a.hspace;
  			}
  		}
  	}
  	
  	// rules collection
  	elRTE.prototype.filter.prototype.rules = {
  		/**
  		 * If this.rte.options.allowTags is set - remove all except this ones
  		 *
  		 * @param String  html code
  		 * @return String
  		 **/
  		allowedTags : function(html) {
  			var a = this.allowTags;
  			
  			return a ? html.replace(this.tagRegExp, function(t, c, n) { return a[n.toLowerCase()] ? t : ''; }) : html;
  		},
  		/**
  		 * If this.rte.options.denyTags is set - remove all deny tags
  		 *
  		 * @param String  html code
  		 * @return String
  		 **/
  		deniedTags : function(html) {
  			var d = this.denyTags; 
  
  			return d ? html.replace(this.tagRegExp, function(t, c, n) { return d[n.toLowerCase()] ? '' : t }) : html;
  		},
  		
  		/**
  		 * Replace not allowed tags/attributes
  		 *
  		 * @param String  html code
  		 * @return String
  		 **/
  		clean : function(html) {
  			var self = this, 
  				rt   = this.replaceTags,
  				ra   = this.replaceAttrs, 
  				da   = this.denyAttr,
  				n;
  			
  			
  			html = html.replace(/<!DOCTYPE([\s\S]*)>/gi, '')
  				.replace(/<p [^>]*class="?MsoHeading"?[^>]*>(.*?)<\/p>/gi, "<p><strong>$1</strong></p>")
  				.replace(/<span\s+style\s*=\s*"\s*mso-spacerun\s*:\s*yes\s*;?\s*"\s*>([\s&nbsp;]*)<\/span>/gi, "$1")
  				.replace(/(<p[^>]*>\s*<\/p>|<p[^>]*\/>)/gi, '<br>')
  				.replace(/(<\/p>)(?:\s*<br\s*\/?>\s*|\s*&nbsp;\s*)+\s*(<p[^>]*>)/gi, function(t, b, e) {
  					return b+"\n"+e;
  				})
  				.replace(this.tagRegExp, function(t, c, n, a) {
  					n = n.toLowerCase();
  					
  					if (c) {
  						return '</'+(rt[n] ? rt[n].tag : n)+'>';
  					}
  					// self.rte.log(t)
  					// create attributes hash and clean it
  					a = self.cleanAttrs(self.parseAttrs(a||''), n);
  					// self.rte.log(a)
  					if (rt[n]) {
  						rt[n].style && $.extend(a.style, rt[n].style);
  						n = rt[n].tag;
  					}
  					
  					da && $.each(a, function(na) {
  						if (da[na]) {
  							delete a[na];
  						}
  					});
  					a = self.serializeAttrs(a);
  					// self.rte.log(a)
  					return '<'+n+(a?' ':'')+a+'>';
  				});
  				
  			
  			n = $('<div>'+html+'</div>');
  			
  			// remove empty spans and merge nested spans
  			n.find('span:not([id]):not([class])').each(function() {
  				/*
  				var t = $(this);
  				
  				if (!t.attr('style')) {
  					
  					$.trim(t.html()).length ? self.rte.dom.unwrap(this) : t.remove();
  					// t.children().length ? self.rte.dom.unwrap(this) : t.remove();
  				}
  				*/
  			}).end().find('span span:only-child').each(function() {
  				/*
  				var t   = $(this), 
  					p   = t.parent().eq(0), 
  					tid = t.attr('id'), 
  					pid = p.attr('id'), id, s, c;
  
  				if (self.rte.dom.isOnlyNotEmpty(this) && (!tid || !pid)) {
  					c = $.trim(p.attr('class')+' '+t.attr('class'))
  					c && p.attr('class', c);
  					s = self.rte.utils.serializeStyle($.extend(self.rte.utils.parseStyle($(this).attr('style')||''), self.rte.utils.parseStyle($(p).attr('style')||'')));
  					s && p.attr('style', s);
  					id = tid||pid;
  					id && p.attr('id', id);
  					this.firstChild ? $(this.firstChild).unwrap() : t.remove();
  				}
  				*/
  			})
  			.end().find('a[name]').each(function() {
  				$(this).addClass('elrte-protected elrte-anchor');
  			});
  			
  			return n.html()	
  		},
  		
  		/**
  		 * Clean pasted html
  		 *
  		 * @param String  html code
  		 * @return String
  		 **/
  		cleanPaste : function(html) {
  			var self = this, d = this.pasteDenyAttr;
  
  			html = html
  				.replace(this.scriptRegExp, '')
  				.replace(this.styleRegExp, '')
  				.replace(this.linkRegExp, '')
  				.replace(this.cdataRegExp, '')
  				.replace(/\<\!--[\s\S]*?--\>/g, '');
  			
  			if (this.rte.options.pasteOnlyText) {
  				html = html.replace(this.tagRegExp, function(t, c, n) {
  					return /br/i.test(n) || (c && /h[1-6]|p|ol|ul|li|div|blockquote|tr/i) ? '<br>' : '';
  				}).replace(/(&nbsp;|<br[^>]*>)+\s*$/gi, '');
  			} else if (d) {
  				html = html.replace(this.openTagRegExp, function(t, n, a) {
  					a = self.parseAttrs(a);
  					$.each(a, function(an) {
  						if (d[an]) {
  							delete a[an];
  						}
  					});
  					a = self.serializeAttrs(a, true);
  					return '<'+n+(a?' ':'')+a+'>';
  				});
  			}
  			return html; 
  		},
  		
  		/**
  		 * Replace script/style/media etc with placeholders
  		 *
  		 * @param String  html code
  		 * @return String
  		 **/
  		replace : function(html) {
  			var self = this, r = this.rte.options.replace||[], n;
  
  			// custom replaces if set
  			if (r.length) {
  				$.each(r, function(i, f) {
  					if (typeof(f) == 'function') {
  						html = f.call(self, html);
  					}
  				});
  			}
  
  			/**
  			 * Return media replacement - img html code
  			 *
  			 * @param Object  object to store in rel attr
  			 * @param String  media mime-type
  			 * @return String
  			 **/
  			function img(o, t) {
  				var s = src(),
  					c = s && self.videoHostRegExp.test(s) ? s.replace(self.videoHostRegExp, "$2") : t.replace(/^\w+\/(.+)/, "$1"),
  					w = parseInt((o.obj ? o.obj.width || o.obj.style.width : 0)||(o.embed ? o.embed.width || o.embed.style.width : 0))||150,
  					h = parseInt((o.obj ? o.obj.height || o.obj.style.height : 0)||(o.embed ? o.embed.height || o.embed.style.height : 0))||100,
  					id = 'media'+Math.random().toString().substring(2),
  					style ='',
  					l;
  
  				// find media src
  				function src() {
  					if (o.embed && o.embed.src) {
  						return o.embed.src;
  					}
  					if (o.params && o.params.length) {
  						l = o.params.length;
  						while (l--) {
  							if (o.params[l].name == 'src' || o.params[l].name == 'movie') {
  								return o.params[l].value;
  							}
  						}
  					}
  				}
  				if (o.obj && o.obj.style && o.obj.style['float']) {
  					style = ' style="float:'+o.obj.style['float']+'"';
  				}
  				self.scripts[id] = o;
  				return '<img src="'+self.url+'pixel.gif" class="elrte-media elrte-media-'+c+' elrte-protected" title="'+(s ? self.rte.utils.encode(s) : '')+'" rel="'+id+'" width="'+w+'" height="'+h+'"'+style+'>';
  			}
  			
  			html = html
  				.replace(this.styleRegExp, "<!-- ELRTE_COMMENT$1 -->")
  				.replace(this.linkRegExp,  "<!-- ELRTE_COMMENT$1-->")
  				.replace(this.cdataRegExp, "<!--[CDATA[$1]]-->")
  				.replace(this.scriptRegExp, function(t, a, s) {
  					var id;
  					if (self.denyTags.script) {
  						return '';
  					}
  					id = 'script'+Math.random().toString().substring(2);
  					a = self.parseAttrs(a);
  					!a.type && (a.type = 'text/javascript');
  					self.scripts[id] = '<script '+self.serializeAttrs(a)+">"+s+"</script>";
  					return '<!-- ELRTE_SCRIPT:'+(id)+' -->';
  				})
  				.replace(this.yMapsRegExp, function(t, a) {
  					a = self.parseAttrs(a);
  					a['class']['elrte-yandex-maps'] = 'elrte-yandex-maps';
  					a['class']['elrte-protected'] = 'elrte-protected';
  					return '<div '+self.serializeAttrs(a)+'>';
  				})
  				.replace(this.gMapsRegExp, function(t, a) {
  					var id = 'gmaps'+Math.random().toString().substring(2), w, h;
  					a = self.parseAttrs(a);
  					w = parseInt(a.width||a.style.width||100);
  					h = parseInt(a.height||a.style.height||100);
  					self.scripts[id] = t;
  					return '<img src="'+self.url+'pixel.gif" class="elrte-google-maps elrte-protected" id="'+id+'" style="width:'+w+'px;height:'+h+'px">';
  				})
  				.replace(this.objRegExp, function(t, a, c) {
  					var m = c.match(self.embRegExp),
  						o = { obj : self.parseAttrs(a), embed : m && m.length ? self.parseAttrs(m[0].substring(7)) : null, params : [] },
  						i = self.rte.utils.mediaInfo(o.embed ? o.embed.type||'' : '', o.obj.classid||'');
  					
  					if (i) {
  						if ((m = c.match(self.paramRegExp))) {
  							$.each(m, function(i, p) {
  								o.params.push(self.parseAttrs(p.substring(6)));
  							});
  						}
  						!o.obj.classid  && (o.obj.classid  = i.classid[0]);
  						!o.obj.codebase && (o.obj.codebase = i.codebase);
  						o.embed && !o.embed.type && (o.embed.type = i.type);
  						// ie bug with empty attrs
  						o.obj.width == '1' && delete o.obj.width;
  						o.obj.height == '1' && delete o.obj.height;
  						if (o.embed) {
  							o.embed.width == '1' && delete o.embed.width;
  							o.embed.height == '1' && delete o.embed.height;
  						}
  						return img(o, i.type);
  					}
  					return t;
  				})
  				.replace(this.embRegExp, function(t, n, a) {
  					var a = self.parseAttrs(a),
  						i = self.rte.utils.mediaInfo(a.type||'');
  					// ie bug with empty attrs
  					a.width == '1' && delete a.width;
  					a.height == '1' && delete a.height;
  					return i ? img({ embed : a }, i.type) : t;
  				})
  				.replace(this.iframeRegExp, function(t, a) {
  					var a = self.parseAttrs(a);
  					var w = a.style.width || (parseInt(a.width) > 1 ? parseInt(a.width)+'px' : '100px');
  					var h = a.style.height || (parseInt(a.height) > 1 ? parseInt(a.height)+'px' : '100px');
  					var id = 'iframe'+Math.random().toString().substring(2);
  					self.scripts[id] = t;
  					var img = '<img id="'+id+'" src="'+self.url+'pixel.gif" class="elrte-protected elrte-iframe" style="width:'+w+'; height:'+h+'">';
  					return img;
  				})
  				.replace(this.vimeoRegExp, function(t, n, a) {
  					a = self.parseAttrs(a);
  					delete a.frameborder;
  					a.width == '1' && delete a.width;
  					a.height == '1' && delete a.height;
  					a.type = 'application/x-shockwave-flash';
  					return img({ embed : a }, 'application/x-shockwave-flash');
  				})
  				.replace(/<\/(embed|param)>/gi, '')
  				.replace(this.pbRegExp, function() {
  					return '<img src="'+self.url+'pixel.gif" class="elrte-protected elrte-pagebreak">';
  				});
  
  
  			n = $('<div>'+html+'</div>');
  			
  			// remove empty spans and merge nested spans
  			// n.find('span:not([id]):not([class])').each(function() {
  			// 	var t = $(this);
  			// 	
  			// 	if (!t.attr('style')) {
  			// 		$.trim(t.html()).length ? self.rte.dom.unwrap(this) : t.remove();
  			// 		// t.children().length ? self.rte.dom.unwrap(this) : t.remove();
  			// 	}
  			// }).end().find('span span:only-child').each(function() {
  			// 	var t   = $(this), 
  			// 		p   = t.parent().eq(0), 
  			// 		tid = t.attr('id'), 
  			// 		pid = p.attr('id'), id, s, c;
  			// 
  			// 	if (self.rte.dom.is(this, 'onlyChild') && (!tid || !pid)) {
  			// 		c = $.trim(p.attr('class')+' '+t.attr('class'))
  			// 		c && p.attr('class', c);
  			// 		s = self.rte.utils.serializeStyle($.extend(self.rte.utils.parseStyle($(this).attr('style')||''), self.rte.utils.parseStyle($(p).attr('style')||'')));
  			// 		s && p.attr('style', s);
  			// 		id = tid||pid;
  			// 		id && p.attr('id', id);
  			// 		this.firstChild ? $(this.firstChild).unwrap() : t.remove();
  			// 	}
  			// })
  			// .end().find('a[name]').each(function() {
  			// 	$(this).addClass('elrte-anchor');
  			// });
  
  
  			if (!this.rte.options.allowTextNodes) {
  				// wrap inline nodes with p
  				var dom = this.rte.dom,
  					nodes = [],
  					w = [];
  				
  				if ($.browser.msie) {
  					for (var i = 0; i<n[0].childNodes.length; i++) {
  						nodes.push(n[0].childNodes[i])
  					}
  				} else {
  					nodes = Array.prototype.slice.call(n[0].childNodes);
  				}
  				
  
  				function wrap() {
  					if (w.length && dom.filter(w, 'notEmpty').length) {
  						dom.wrap(w, document.createElement('p'));
  					}
  					w = [];
  				}	
  				$.each(nodes, function(i, n) {
  					if (dom.is(n, 'block')) {
  						wrap();
  					} else {
  						if (w.length && n.previousSibling != w[w.length-1]) {
  							wrap();
  						}
  						w.push(n);
  					}
  				});
  				wrap();
  			}
  			
  			return n.html();
  		},
  		/**
  		 * Restore script/style/media etc from placeholders
  		 *
  		 * @param String  html code
  		 * @return String
  		 **/
  		restore : function(html) {
  			var self =this, r = this.rte.options.restore||[];
  
  			// custom restore if set
  			if (r.length) {
  				$.each(r, function(i, f) {
  					if (typeof(f) == 'function') {
  						html = f.call(self, html);
  					}
  				});
  			}
  			
  			html = html
  				.replace(/\<\!--\[CDATA\[([\s\S]*?)\]\]--\>/gi, "<![CDATA[$1]]>")
  				.replace(/\<\!--\s*ELRTE_SCRIPT\:\s*(script\d+)\s*--\>/gi, function(t, n) {
  					if (self.scripts[n]) {
  						t = self.scripts[n];
  						delete self.scripts[n];
  					}
  					return t||'';
  				})
  				.replace(/\<\!-- ELRTE_COMMENT([\s\S]*?) --\>/gi, "$1")
  				.replace(this.serviceClassRegExp, function(t, n, a, e) {
  
  					var a = self.parseAttrs(a), j, o = '';
  					// alert(t)
  					if (a['class']['elrte-google-maps']) {
  						var t = '';
  						if (self.scripts[a.id]) {
  							t = self.scripts[a.id];
  							delete self.scripts[a.id]
  						}
  						return t;
  					} else if (a['class']['elrte-iframe']) {
  						return self.scripts[a.id] || '';
  					} else if (a['class']['elrtebm']) {
  						return '';
  					} else if (a['class']['elrte-media']) {
  						// alert(a.rel)
  						// return ''
  						// j = a.rel ? JSON.parse(self.rte.utils.decode(a.rel)) : {};
  						j = self.scripts[a.rel]||{};
  						j.params && $.each(j.params, function(i, p) {
  							o += '<param '+self.serializeAttrs(p)+">\n";
  						});
  						j.embed && (o+='<embed '+self.serializeAttrs(j.embed)+">");
  						j.obj && (o = '<object '+self.serializeAttrs(j.obj)+">\n"+o+"\n</object>\n");
  						return o||t;
  					} else if (a['class']['elrte-pagebreak']) {
  						return '<!-- pagebreak -->';
  					}
  					$.each(a['class'], function(n) {
  						if (/^elrte-\w+/i.test(n)) {
  							delete(a['class'][n]);
  						}
  						// /^elrte\w+/i.test(n) && delete(a['class'][n]); 
  					});
  					return '<'+n+' '+self.serializeAttrs(a)+'>'+(e||'');
  
  				});
  			
  			return html;
  		},
  		/**
  		 * compact styles and move tags and attributes names in lower case(for ie&opera)
  		 *
  		 * @param String  html code
  		 * return String
  		 **/
  		compactStyles : function(html) {
  			var self = this;
  
  			return html.replace(this.tagRegExp, function(t, c, n, a) {
  				a = !c && a ? self.serializeAttrs(self.parseAttrs(a), true) : '';
  				return '<'+c+n.toLowerCase()+(a?' ':'')+a+'>';
  			});
  		},
  		/**
  		 * return xhtml tags
  		 *
  		 * @param String  html code
  		 * return String
  		 **/
  		xhtmlTags : function(html) {
  			return this.xhtml ? html.replace(/<(img|hr|br|embed|param|link|area)([^>]*\/*)>/gi, "<$1$2 />") : html;
  		}
  	}
  	
  	/**
  	 * Chains configuration
  	 * Default chains 
  	 * wysiwyg - proccess html from source for wysiwyg editor mode
  	 * source  - proccess html from wysiwyg for source editor mode
  	 * paste   - clean pasted html
  	 * wysiwyg2wysiwyg - ciclyc rule to clean html from wysiwyg for wysiwyg paste
  	 * source2source - ciclyc rule to clean html from source for source paste
  	 * deniedTags is in the end of chain to protect google maps iframe from removed
  	 **/
  	elRTE.prototype.filter.prototype.chains = {
  		wysiwyg         : ['replace', 'clean', 'allowedTags', 'deniedTags', 'compactStyles'],
  		source          : ['clean', 'allowedTags', 'restore', 'compactStyles', 'xhtmlTags'],
  		paste           : ['clean', 'allowedTags', 'cleanPaste', 'replace', 'deniedTags', 'compactStyles'],
  		wysiwyg2wysiwyg : ['clean', 'allowedTags', 'restore', 'replace', 'deniedTags', 'compactStyles'],
  		source2source   : ['clean', 'allowedTags', 'replace', 'deniedTags', 'restore', 'compactStyles', 'xhtmlTags']
  	}
  	
  
  	
  })(jQuery);
  (function($) {
  elRTE.prototype.history = function(rte) {
  	this.rte = rte;
  	this._prev = []
  	this._next = [];
  
  	this.add = function() {
  		if (this.rte.options.historyLength>0 && this._prev.length>= this.rte.options.historyLength) {
  			this._prev.slice(this.rte.options.historyLength);
  		}
  		var b = this.rte.selection.getBookmark();
  		this._prev.push([$(this.rte.doc.body).html(), b]);
  		this.rte.selection.moveToBookmark(b);
  		// this._prev.push($(this.rte.doc.body).html());
  		this._next = [];
  	}
  	
  	this.back = function() {
  		
  		if (this._prev.length) {
  			var b = this.rte.selection.getBookmark(), 
  				data = this._prev.pop();
  			this._next.push([$(this.rte.doc.body).html(), b]);
  			
  			$(this.rte.doc.body).html(data[0]);
  			this.rte.selection.moveToBookmark(data[1]);
  		}
  	}
  
  	this.fwd = function() {
  		if (this._next.length) {
  			var b = this.rte.selection.getBookmark(), 
  				data = this._next.pop();
  			this._prev.push([$(this.rte.doc.body).html(), b]);
  			
  			$(this.rte.doc.body).html(data[0]);
  			this.rte.selection.moveToBookmark(data[1]);
  		}
  	}
  	
  	this.canBack = function() {
  		return this._prev.length;
  	}
  	
  	this.canFwd = function() {
  		return this._next.length;
  	}
  
  }
  })(jQuery);/*
   * elRTE configuration
   *
   * @param doctype         - doctype for editor iframe
   * @param cssClass        - css class for editor
   * @param cssFiles        - array of css files, witch will inlude in iframe
   * @param height          - not used now (may be deleted in future)
   * @param lang            - interface language (requires file in i18n dir)
   * @param toolbar         - name of toolbar to load
   * @param absoluteURLs    - convert files and images urls to absolute or not
   * @param allowSource     - is source editing allowing
   * @param stripWhiteSpace - strip лишние whitespaces/tabs or not
   * @param styleWithCSS    - use style=... instead of strong etc.
   * @param fmAllow         - allow using file manger (elFinder)
   * @param fmOpen          - callback for open file manager
   * @param buttons         - object with pairs of buttons classes names and titles (when create new button, you have to add iys name here)
   * @param panels          - named groups of buttons
   * @param panelNames      - title of panels (required for one planned feature)
   * @param toolbars        - named redy to use toolbals (you may combine your own toolbar)
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * Copyright: Studio 42, http://www.std42.ru
   */
  (function($) {
  elRTE.prototype.options   = {
  	doctype         : '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">',
  	cssClass        : 'el-rte',
  	cssfiles        : [],
  	height          : null,
  	resizable       : true,
  	lang            : 'en',
  	toolbar         : 'normal',
  	absoluteURLs    : true,
  	allowSource     : true,
  	stripWhiteSpace : true,
  	styleWithCSS    : false,
  	fmAllow         : true,
  	fmOpen          : null,
  	/* if set all other tag will be removed */
  	allowTags : [],
  	/* if set this tags will be removed */
  	denyTags : ['applet', 'base', 'basefont', 'bgsound', 'blink', 'body', 'col', 'colgroup', 'isindex', 'frameset', 'html', 'head', 'meta', 'marquee', 'noframes', 'noembed', 'o:p', 'title', 'xml'],
  	denyAttr : [],
  	/* on paste event this attributes will removed from pasted html */
  	pasteDenyAttr : ['id', 'name', 'class', 'style', 'language', 'onclick', 'ondblclick', 'onhover', 'onkeup', 'onkeydown', 'onkeypress'],
  	/* If false - all text nodes will be wrapped by paragraph tag */
  	allowTextNodes : true,
  	/* allow browser specific styles like -moz|-webkit|-o */
  	allowBrowsersSpecStyles : false,
  	/* allow paste content into editor */
  	allowPaste : true,
  	/* if true - only text will be pasted (not in ie) */
  	pasteOnlyText : false,
  	/* user replacement rules */
  	replace : [],
  	/* user restore rules */
  	restore : [],
  	pagebreak : '<div style="page-break-after: always;"></div>', //'<!-- pagebreak -->',
  	buttons         : {
  		'save'                : 'Save',
  		'copy'                : 'Copy',
  		'cut'                 : 'Cut',
  		'css'                 : 'Css style and class',
  		'paste'               : 'Paste',
  		'pastetext'           : 'Paste only text',
  		'pasteformattext'     : 'Paste formatted text',
  		'removeformat'        : 'Clean format', 
  		'undo'                : 'Undo last action',
  		'redo'                : 'Redo previous action',
  		'bold'                : 'Bold',
  		'italic'              : 'Italic',
  		'underline'           : 'Underline',
  		'strikethrough'       : 'Strikethrough',
  		'superscript'         : 'Superscript',
  		'subscript'           : 'Subscript',
  		'justifyleft'         : 'Align left',
  		'justifyright'        : 'Ailgn right',
  		'justifycenter'       : 'Align center',
  		'justifyfull'         : 'Align full',
  		'indent'              : 'Indent',
  		'outdent'             : 'Outdent',
  		'rtl' : 'Right to left',
  		'ltr' : 'Left to right',
  		'forecolor'           : 'Font color',
  		'hilitecolor'         : 'Background color',
  		'formatblock'         : 'Format',
  		'fontsize'            : 'Font size',
  		'fontname'            : 'Font',
  		'insertorderedlist'   : 'Ordered list',
  		'insertunorderedlist' : 'Unordered list',
  		'horizontalrule'      : 'Horizontal rule',
  		'blockquote'          : 'Blockquote',
  		'div'                 : 'Block element (DIV)',
  		'link'                : 'Link',
  		'unlink'              : 'Delete link',
  		'anchor'              : 'Bookmark',
  		'image'               : 'Image',
  		'pagebreak'           : 'Page break',
  		'smiley'              : 'Smiley',
  		'flash'               : 'Flash',
  		'table'               : 'Table',
  		'tablerm'             : 'Delete table',
  		'tableprops'          : 'Table properties',
  		'tbcellprops'         : 'Table cell properties',
  		'tbrowbefore'         : 'Insert row before',
  		'tbrowafter'          : 'Insert row after',
  		'tbrowrm'             : 'Delete row',
  		'tbcolbefore'         : 'Insert column before',
  		'tbcolafter'          : 'Insert column after',
  		'tbcolrm'             : 'Delete column',
  		'tbcellsmerge'        : 'Merge table cells',
  		'tbcellsplit'         : 'Split table cell',
  		'docstructure'        : 'Toggle display document structure',
  		'elfinder'            : 'Open file manager',
  		'fullscreen'          : 'Toggle full screen mode',
  		'nbsp'                : 'Non breakable space',
  		'stopfloat'           : 'Stop element floating',
  		'about'               : 'About this software'
  	},
  	panels      : {
  		eol        : [], // special panel, insert's a new line in toolbar
  		save       : ['save'],
  		copypaste  : ['copy', 'cut', 'paste', 'pastetext', 'pasteformattext', 'removeformat', 'docstructure'],
  		undoredo   : ['undo', 'redo'],
  		style      : ['bold', 'italic', 'underline', 'strikethrough', 'subscript', 'superscript'],
  		colors     : ['forecolor', 'hilitecolor'],
  		alignment  : ['justifyleft', 'justifycenter', 'justifyright', 'justifyfull'],
  		indent     : ['outdent', 'indent'],
  		format     : ['formatblock', 'fontsize', 'fontname'],
  		lists      : ['insertorderedlist', 'insertunorderedlist'],
  		elements   : ['horizontalrule', 'blockquote', 'div', 'stopfloat', 'css', 'nbsp', 'smiley', 'pagebreak'],
  		direction  : ['ltr', 'rtl'],
  		links      : ['link', 'unlink', 'anchor'],
  		images     : ['image'],
  		media      : ['image', 'flash'],		
  		tables     : ['table', 'tableprops', 'tablerm',  'tbrowbefore', 'tbrowafter', 'tbrowrm', 'tbcolbefore', 'tbcolafter', 'tbcolrm', 'tbcellprops', 'tbcellsmerge', 'tbcellsplit'],
  		elfinder   : ['elfinder'],
  		fullscreen : ['fullscreen', 'about']
  	},
  	toolbars    : {
  		tiny     : ['style'],
  		compact  : ['save', 'undoredo', 'style', 'alignment', 'lists', 'links', 'fullscreen'],
  		normal   : ['save', 'copypaste', 'undoredo', 'style', 'alignment', 'colors', 'indent', 'lists', 'links', 'elements', 'images', 'fullscreen'],
  		complete : ['save', 'copypaste', 'undoredo', 'style', 'alignment', 'colors', 'format', 'indent', 'lists', 'links', 'elements', 'media', 'fullscreen'],
  		maxi     : ['save', 'copypaste', 'undoredo', 'elfinder', 'style', 'alignment', 'direction', 'colors', 'format', 'indent', 'lists', 'links', 'elements', 'media', 'tables', 'fullscreen'],
  		eldorado : ['save', 'copypaste', 'elfinder', 'undoredo', 'style', 'alignment', 'colors', 'format', 'indent', 'lists', 'links', 'elements', 'media', 'tables', 'fullscreen']
  		
  	},
  	panelNames : {
  		save      : 'Save',
  		copypaste : 'Copy/Pase',
  		undoredo  : 'Undo/Redo',
  		style     : 'Text styles',
  		colors    : 'Colors',
  		alignment : 'Alignment',
  		indent    : 'Indent/Outdent',
  		format    : 'Text format',
  		lists     : 'Lists',
  		elements  : 'Misc elements',
  		direction : 'Script direction',
  		links     : 'Links',
  		images    : 'Images',
  		media     : 'Media',
  		tables    : 'Tables',
  		elfinder  : 'File manager (elFinder)'
  	}
  };
  })(jQuery);
  /**
   * @class selection  - elRTE utils for working with text selection
   *
   * @param  elRTE  rte  объект-редактор
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   **/
  (function($) {
  elRTE.prototype.selection = function(rte) {
  	this.rte      = rte;
  	var self      = this;
  	this.w3cRange = null;
  	var start, end, node, bm;
  	
  	$(this.rte.doc)
  		.keyup(function(e) {
  			if (e.ctrlKey || e.metaKey || (e.keyCode >= 8 && e.keyCode <= 13) || (e.keyCode>=32 && e.keyCode<= 40) || e.keyCode == 46 || (e.keyCode >=96 && e.keyCode <= 111)) {
  				self.cleanCache();
  			}
  		})
  		.mousedown(function(e) {
  			// self.rte.log(e)
  			if (e.target.nodeName == 'HTML') {
  				start = self.rte.doc.body;
  			} else {
  				start = e.target;
  			}
  			end   = node = null;
  		})
  		.mouseup(function(e) {
  			if (e.target.nodeName == 'HTML') {
  				end = self.rte.doc.body;
  			} else {
  				end = e.target;
  			}
  			end  = e.target;
  			node = null;
  		}).click();
  		
  	/**
  	 * возвращает selection
  	 *
  	 * @return  Selection
  	 **/
  	function selection() {
  		return self.rte.window.getSelection ? self.rte.window.getSelection() : self.rte.window.document.selection;
  	}
  	
  	/**
  	 * Вспомогательная функция
  	 * Возвращает самого верхнего родителя, отвечающего условию - текущая нода - его единственная непустая дочерняя нода
  	 *
  	 * @param   DOMElement  n нода, для которой ищем родителя
  	 * @param   DOMElement  p если задана - нода, выше которой не поднимаемся
  	 * @param   String      s строна поиска (left||right||null)
  	 * @return  DOMElement
  	 **/
  	function realSelected(n, p, s) {
  		while (n.nodeName != 'BODY' && n.parentNode && n.parentNode.nodeName != 'BODY' && (p ? n!== p && n.parentNode != p : 1) && ((s=='left' && self.rte.dom.isFirstNotEmpty(n)) || (s=='right' && self.rte.dom.isLastNotEmpty(n)) || (self.rte.dom.isFirstNotEmpty(n) && self.rte.dom.isLastNotEmpty(n))) ) {
  			n = n.parentNode;
  		}
  		return n;
  	}
  	
  	/**
  	 * Возвращает TRUE, если выделение "схлопнуто"
  	 *
  	 * @return  bool
  	 **/
  	this.collapsed = function() {
  		return this.getRangeAt().isCollapsed();
  	}
  	
  	/**
  	 * "Схлопывает" выделение 
  	 *
  	 * @param   bool  toStart  схлопнуть к начальной точке
  	 * @return  void
  	 **/
  	this.collapse = function(st) {
  		var s = selection(),
  			r = this.getRangeAt();
  		r.collapse(st?true:false);
  		if (!$.browser.msie) {
  			s.removeAllRanges();
  			s.addRange(r);
  			
  		}
  		return this;
  	}
  	
  	/**
  	 * Возвращает TextRange
  	 * Для нормальных браузеров - нативный range
  	 * для "самизнаетечего" - эмуляцию w3c range
  	 *
  	 * @return  range|w3cRange
  	 **/
  	this.getRangeAt = function(updateW3cRange) {
  		if (this.rte.browser.msie) {
  			if (!this.w3cRange) {
  				this.w3cRange = new this.rte.w3cRange(this.rte);
  			}
  			updateW3cRange && this.w3cRange.update();
  			return this.w3cRange;
  		}
  		
  		var s = selection();
  		var r = s.rangeCount > 0 ? s.getRangeAt(0) : this.rte.doc.createRange();
  		r.getStart = function() {
  			return this.startContainer.nodeType==1 && this.startContainer.childNodes.length > 0
  				? this.startContainer.childNodes[Math.min(this.startOffset, this.startContainer.childNodes.length-1)] 
  				: this.startContainer;
  		}
  		
  		r.getEnd = function() {
  			return this.endContainer.nodeType==1 && this.startContainer.childNodes.length > 0
  				? this.endContainer.childNodes[ Math.min(this.startOffset == this.endOffset ? this.endOffset : this.endOffset-1, this.endContainer.childNodes.length-1)] 
  				: this.endContainer;
  		}
  		r.isCollapsed = function() {
  			return this.collapsed;
  		}
  		return r;
  	}
  	
  	this.saveIERange = function() {
  		if ($.browser.msie) {
  			bm = this.getRangeAt().getBookmark();
  		}
  	}
  	
  	this.restoreIERange = function() {
  		$.browser.msie && bm && this.getRangeAt().moveToBookmark(bm);
  	}
  	
  	this.cloneContents = function() {
  		var n = this.rte.dom.create('div'), r, c, i;
  		if ($.browser.msie) {
  			try { 
  				r = this.rte.window.document.selection.createRange(); 
  			} catch(e) { 
  				r = this.rte.doc.body.createTextRange(); 
  			}
  			$(n).html(r.htmlText);
  		} else {
  			c = this.getRangeAt().cloneContents();
  			for (i=0; i<c.childNodes.length; i++) {
  				n.appendChild(c.childNodes[i].cloneNode(true));
  			}
  		}
  		return n;
  	}
  	
  	/**
  	 * Выделяет ноды
  	 *
  	 * @param   DOMNode  s  нода начала выделения
  	 * @param   DOMNode  e  нода конца выделения
  	 * @return  selection
  	 **/
  	this.select = function(s, e) {
  		e = e||s;
  		
  		if (this.rte.browser.msie) {
  			var r  = this.rte.doc.body.createTextRange(),
  				r1 = r.duplicate(),
  				r2 = r.duplicate();
  			
  			r1.moveToElementText(s);
  			r2.moveToElementText(e);
  			r.setEndPoint('StartToStart', r1);
  			r.setEndPoint('EndToEnd',     r2);
  			r.select();
  		} else {
  			
  			var sel = selection(),
  				r = this.getRangeAt();
  			r.setStartBefore(s);
  			r.setEndAfter(e);
  			sel.removeAllRanges();
  			if (this.rte.dom.next(s) == e) { // for IE11
  				r.collapse(true);
  			}
  			sel.addRange(r);
  		}
  		return this.cleanCache();
  	}
  	
  	/**
  	 * Выделяет содержимое ноды
  	 *
  	 * @param   Element  n  нода
  	 * @return  selection
  	 **/
  	this.selectContents = function(n) {
  		var r = this.getRangeAt();
  		if (n && n.nodeType == 1) {
  			if (this.rte.browser.msie) {
  				r.range();
  				r.r.moveToElementText(n.parentNode);
  				r.r.select();
  			} else {
  				try {
  					r.selectNodeContents(n);
  				} catch (e) {
  					return this.rte.log('unable select node contents '+n);
  				}
  				var s = selection();
  				s.removeAllRanges();
  				s.addRange(r);
  			}
  		}
  		return this;
  	}
  	
  	this.deleteContents = function() {
  		if (!$.browser.msie) {
  			this.getRangeAt().deleteContents();
  		}
  		return this;
  	}
  	
  	/**
  	 * Вставляет ноду в текущее выделение
  	 *
  	 * @param   Element  n  нода
  	 * @return  selection
  	 **/
  	this.insertNode = function(n, collapse) {
  		if (collapse && !this.collapsed()) {
  			this.collapse();
  		}
  
  		if (this.rte.browser.msie) {
  			var html = n.nodeType == 3 ? n.nodeValue : $(this.rte.dom.create('span')).append($(n)).html();
  			 var r = this.getRangeAt();
  			r.insertNode(html);
  		} else {
  			var r = this.getRangeAt();
  			r.insertNode(n);
  			r.setStartAfter(n);
  			r.setEndAfter(n);
  			var s = selection();
  			s.removeAllRanges();
  			s.addRange(r);
  		}
  		return this.cleanCache();
  	}
  
  	/**
  	 * Вставляет html в текущее выделение
  	 *
  	 * @param   Element  n  нода
  	 * @return  selection
  	 **/
  	this.insertHtml = function(html, collapse) {
  		if (collapse && !this.collapsed()) {
  			this.collapse();
  		}
  		
  		if (this.rte.browser.msie) {
  			this.getRangeAt().range().pasteHTML(html);
  		} else {
  			var n = $(this.rte.dom.create('span')).html(html||'').get(0);
  			this.insertNode(n);
  			$(n).replaceWith($(n).html());
  		}
  		return this.cleanCache();
  	}
  
  	/**
  	 * Вставляет ноду в текущее выделение
  	 *
  	 * @param   Element  n  нода
  	 * @return  selection
  	 **/
  	this.insertText = function(text, collapse) {
  		var n = this.rte.doc.createTextNode(text);
  		return this.insertHtml(n.nodeValue);
  	}
  
  	this.getBookmark = function() {
  		this.rte.window.focus();
  		var r, r1, r2, _s, _e,
  			s = this.rte.dom.createBookmark(),
  			e = this.rte.dom.createBookmark();
  			
  		
  		
  		if ($.browser.msie) {
  			try { 
  				r = this.rte.window.document.selection.createRange(); 
  			} catch(e) { 
  				r = this.rte.doc.body.createTextRange(); 
  			}
  			
  			if (r.item) {
  				var n = r.item(0);
  				r = this.rte.doc.body.createTextRange();
  				r.moveToElementText(n);
  			}
  			
  			r1 = r.duplicate();
  			r2 = r.duplicate();
  			_s = this.rte.dom.create('span');
  			_e = this.rte.dom.create('span');
  
  			_s.appendChild(s);
  			_e.appendChild(e);
  
  			r1.collapse(true);
  			r1.pasteHTML(_s.innerHTML);
  			r2.collapse(false);
  			r2.pasteHTML(_e.innerHTML);
  		} else {
  				var sel = selection();
  				var r = sel.rangeCount > 0 ? sel.getRangeAt(0) : this.rte.doc.createRange();
  				
  			// r  = this.getRangeAt();
  			r1 = r.cloneRange();
  			r2 = r.cloneRange();
  			
  			// this.insertNode(this.rte.dom.create('hr'))
  			// return
  			r2.collapse(false);
  			r2.insertNode(e);
  			r1.collapse(true);
  			r1.insertNode(s);
  			this.select(s, e);
  		}
  		
  		return [s.id, e.id];
  	}
  
  	this.moveToBookmark = function(b) {
  		this.rte.window.focus();
  		
  		if (b && b.length==2) {
  			var s = this.rte.doc.getElementById(b[0]),
  				e = this.rte.doc.getElementById(b[1]),
  				sel, r;
  			if (s && e) {
  				this.select(s, e);
  				if (this.rte.dom.next(s) == e) {
  					this.collapse(true);
  				}
  				if (!$.browser.msie) {
  					sel = selection();
  					r = sel.rangeCount > 0 ? sel.getRangeAt(0) : this.rte.doc.createRange();
  					sel.removeAllRanges();
  					sel.addRange(r);
  				}
  				s.parentNode.removeChild(s);
  				e.parentNode.removeChild(e);
  			}
  		}
  		return this;	
  	}
  
  	this.removeBookmark = function(b) {
  		this.rte.window.focus();
  		if (b.length==2) {
  			var s = this.rte.doc.getElementById(b[0]),
  				e = this.rte.doc.getElementById(b[1]);
  			if (s && e) {
  				s.parentNode.removeChild(s);
  				e.parentNode.removeChild(e);
  			}
  		}
  	}
  
  	/**
  	 * Очищает кэш
  	 *
  	 * @return  selection
  	 **/
  	this.cleanCache = function() {
  		start = end = node = null;
  		return this;
  	}
  
  	
  	/**
  	 * Возвращает ноду начала выделения
  	 *
  	 * @return  DOMElement
  	 **/
  	this.getStart = function() {
  		if (!start) {
  			var r = this.getRangeAt();
  			start = r.getStart();
  		}
  		return start;
  	}
  	
  	/**
  	 * Возвращает ноду конца выделения
  	 *
  	 * @return  DOMElement
  	 **/
  	this.getEnd = function() {
  		if (!end) {
  			var r = this.getRangeAt();
  			end = r.getEnd();
  		}
  		return end;
  	}
  
  	/**
  	 * Возвращает выбраную ноду (общий контейнер всех выбранных нод)
  	 *
  	 * @return  Element
  	 **/
  	this.getNode = function() {
  		if (!node) {
  			node = this.rte.dom.findCommonAncestor(this.getStart(), this.getEnd());
  		}
  		return node;
  	}
  
  	
  	/**
  	 * Возвращает массив выбранных нод
  	 *
  	 * @param   Object  o  параметры получения и обработки выбраных нод
  	 * @return  Array
  	 **/
  	this.selected = function(o) {
  		var opts = {
  			collapsed : false,  // вернуть выделение, даже если оно схлопнуто
  			blocks    : false,  // блочное выделение
  			filter    : false,  // фильтр результатов
  			wrap      : 'text', // что оборачиваем
  			tag       : 'span'  // во что оборачиваем
  		}
  		opts = $.extend({}, opts, o);
  		
  		// блочное выделение - ищем блочную ноду, но не таблицу
  		if (opts.blocks) {
  			var n  = this.getNode(), _n = null;
  			if (_n = this.rte.dom.selfOrParent(n, 'selectionBlock') ) {
  				return [_n];
  			} 
  		}
  
  		var sel    = this.selectedRaw(opts.collapsed, opts.blocks);
  		var ret    = [];
  		var buffer = [];
  		var ndx    = null;
  
  		// оборачиваем ноды в буффере
  		function wrap() {
  			
  			function allowParagraph() {
  				for (var i=0; i < buffer.length; i++) {
  					if (buffer[i].nodeType == 1 && (self.rte.dom.selfOrParent(buffer[i], /^P$/) || $(buffer[i]).find('p').length>0)) {
  						return false;
  					}
  				};
  				return true;
  			} 
  			
  			if (buffer.length>0) {
  				var tag  = opts.tag == 'p' && !allowParagraph() ? 'div' : opts.tag;
  				var n    = self.rte.dom.wrap(buffer, tag);
  				ret[ndx] = n;
  				ndx      = null;
  				buffer   = [];
  			}
  		}
  		
  		// добавляем ноды в буффер
  		function addToBuffer(n) {
  			if (n.nodeType == 1) {
  				if (/^(THEAD|TFOOT|TBODY|COL|COLGROUP|TR)$/.test(n.nodeName)) {
  					$(n).find('td,th').each(function() {
  						var tag = opts.tag == 'p' && $(this).find('p').length>0 ? 'div' : opts.tag;
  						var n = self.rte.dom.wrapContents(this, tag);
  						return ret.push(n);
  					})
  				} else if (/^(CAPTION|TD|TH|LI|DT|DD)$/.test(n.nodeName)) {
  					var tag = opts.tag == 'p' && $(n).find('p').length>0 ? 'div' : opts.tag;
  					var n = self.rte.dom.wrapContents(n, tag);
  					return ret.push(n);
  				} 
  			} 
  			var prev = buffer.length>0 ? buffer[buffer.length-1] : null;
  			if (prev && prev != self.rte.dom.prev(n)) {
  				wrap();
  			}
  			buffer.push(n); 
  			if (ndx === null) {
  				ndx = ret.length;
  				ret.push('dummy'); // заглушка для оборачиваемых элементов
  			}
  		}
  		
  		if (sel.nodes.length>0) {
  			
  			for (var i=0; i < sel.nodes.length; i++) {
  				var n = sel.nodes[i];
  					// первую и посл текстовые ноды разрезаем, если необходимо
  					 if (n.nodeType == 3 && (i==0 || i == sel.nodes.length-1) && $.trim(n.nodeValue).length>0) {
  						if (i==0 && sel.so>0) {
  							n = n.splitText(sel.so);
  						}
  						if (i == sel.nodes.length-1 && sel.eo>0) {
  							n.splitText(i==0 && sel.so>0 ? sel.eo - sel.so : sel.eo);
  						}
  					}
  
  					switch (opts.wrap) {
  						// оборачиваем только текстовые ноды с br
  						case 'text':
  							if ((n.nodeType == 1 && n.nodeName == 'BR') || (n.nodeType == 3 && $.trim(n.nodeValue).length>0)) {
  								addToBuffer(n);
  							} else if (n.nodeType == 1) {
  								ret.push(n);
  							}
  							break;
  						// оборачиваем все инлайн элементы	
  						case 'inline':
  							if (this.rte.dom.isInline(n)) {
  								addToBuffer(n);
  							} else if (n.nodeType == 1) {
  								
  								ret.push(n);
  							}
  							break;
  						// оборачиваем все	
  						case 'all':
  							if (n.nodeType == 1 || !this.rte.dom.isEmpty(n)) {
  								addToBuffer(n);
  							}
  							break;
  						// ничего не оборачиваем
  						default:
  							if (n.nodeType == 1 || !this.rte.dom.isEmpty(n)) {
  								ret.push(n);
  							}
  					}
  			};
  			wrap();
  		}
  	
  		if (ret.length) {
  			this.rte.window.focus();
  			
  			this.select(ret[0], ret[ret.length-1]);
  		}	
  		return opts.filter ? this.rte.dom.filter(ret, opts.filter) : ret;
  	}
  
  	this.dump = function(ca, s, e, so, eo) {
  		var r = this.getRangeAt();
  		this.rte.log('commonAncestorContainer');
  		this.rte.log(ca || r.commonAncestorContainer);
  		// this.rte.log('commonAncestorContainer childs num')
  		// this/rte.log((ca||r.commonAncestorContainer).childNodes.length)
  		this.rte.log('startContainer');
  		this.rte.log(s || r.startContainer);
  		this.rte.log('startOffset: '+(so>=0 ? so : r.startOffset));
  		this.rte.log('endContainer');
  		this.rte.log(e||r.endContainer);
  		this.rte.log('endOffset: '+(eo>=0 ? eo : r.endOffset));
  	}
  
  	/**
  	 * Возвращает массив выбранных нод, как есть
  	 *
  	 * @param   bool           возвращать если выделение схлопнуто
  	 * @param   bool           "блочное" выделение (текстовые ноды включаются полностью, не зависимо от offset)
  	 * @return  Array
  	 **/
  	this.selectedRaw = function(collapsed, blocks) {
  		var res = {so : null, eo : null, nodes : []};
  		var r   = this.getRangeAt(true);
  		var ca  = r.commonAncestorContainer;
  		var s, e;  // start & end nodes
  		var sf  = false; // start node fully selected
  		var ef  = false; // end node fully selected
  		
  		// возвращает true, если нода не текстовая или выделена полностью
  		function isFullySelected(n, s, e) {
  			if (n.nodeType == 3) {
  				e = e>=0 ? e : n.nodeValue.length;
  				return (s==0 && e==n.nodeValue.length) || $.trim(n.nodeValue).length == $.trim(n.nodeValue.substring(s, e)).length;
  			} 
  			return true;
  		}
  		
  		// возвращает true, если нода пустая или в ней не выделено ни одного непробельного символа
  		function isEmptySelected(n, s, e) {
  			if (n.nodeType == 1) {
  				return self.rte.dom.isEmpty(n);
  			} else if (n.nodeType == 3) {
  				return $.trim(n.nodeValue.substring(s||0, e>=0 ? e : n.nodeValue.length)).length == 0;
  			} 
  			return true;
  		}
  		
  		
  		//this.dump()
  		// начальная нода
  		if (r.startContainer.nodeType == 1) {
  			if (r.startOffset<r.startContainer.childNodes.length) {
  				s = r.startContainer.childNodes[r.startOffset];
  				res.so = s.nodeType == 1 ? null : 0;
  			} else {
  				s = r.startContainer.childNodes[r.startOffset-1];
  				res.so = s.nodeType == 1 ? null : s.nodeValue.length;
  			}
  		} else {
  			s = r.startContainer;
  			res.so = r.startOffset;
  		} 
  		
  		// выделение схлопнуто
  		if (r.collapsed) {
  			if (collapsed) {
  				//  блочное выделение
  				if (blocks) {
  					s = realSelected(s);
  					if (!this.rte.dom.isEmpty(s) || (s = this.rte.dom.next(s))) {
  						res.nodes = [s];
  					} 
  					
  					// добавляем инлайн соседей 
  					if (this.rte.dom.isInline(s)) {
  						res.nodes = this.rte.dom.toLineStart(s).concat(res.nodes, this.rte.dom.toLineEnd(s));
  					}
  					
  					// offset для текстовых нод
  					if (res.nodes.length>0) {
  						res.so = res.nodes[0].nodeType == 1 ? null : 0;
  						res.eo = res.nodes[res.nodes.length-1].nodeType == 1 ? null : res.nodes[res.nodes.length-1].nodeValue.length;
  					}
  					
  				} else if (!this.rte.dom.isEmpty(s)) {
  					res.nodes = [s];
  				}
  				
  			}
  			return res;
  		}
  		
  		// конечная нода
  		if (r.endContainer.nodeType == 1) {
  			e = r.endContainer.childNodes[r.endOffset-1];
  			res.eo = e.nodeType == 1 ? null : e.nodeValue.length;
  		} else {
  			e = r.endContainer;
  			res.eo = r.endOffset;
  		} 
  		// this.rte.log('select 1')
  		//this.dump(ca, s, e, res.so, res.eo)
  		
  		// начальная нода выделена полностью - поднимаемся наверх по левой стороне
  		if (s.nodeType == 1 || blocks || isFullySelected(s, res.so, s.nodeValue.length)) {
  //			this.rte.log('start text node is fully selected')
  			s = realSelected(s, ca, 'left');
  			sf = true;
  			res.so = s.nodeType == 1 ? null : 0;
  		}
  		// конечная нода выделена полностью - поднимаемся наверх по правой стороне
  		if (e.nodeType == 1 || blocks || isFullySelected(e, 0,  res.eo)) {
  //			this.rte.log('end text node is fully selected')
  			e = realSelected(e, ca, 'right');
  			ef = true;
  			res.eo = e.nodeType == 1 ? null : e.nodeValue.length;
  		}
  
  		// блочное выделение - если ноды не элементы - поднимаемся к родителю, но ниже контейнера
  		if (blocks) {
  			if (s.nodeType != 1 && s.parentNode != ca && s.parentNode.nodeName != 'BODY') {
  				s = s.parentNode;
  				res.so = null;
  			}
  			if (e.nodeType != 1 && e.parentNode != ca && e.parentNode.nodeName != 'BODY') {
  				e = e.parentNode;
  				res.eo = null;
  			}
  		}
  
  		// если контенер выделен полностью, поднимаемся наверх насколько можно
  		if (s.parentNode == e.parentNode && s.parentNode.nodeName != 'BODY' && (sf && this.rte.dom.isFirstNotEmpty(s)) && (ef && this.rte.dom.isLastNotEmpty(e))) {
  //			this.rte.log('common parent')
  			s = e = s.parentNode;
  			res.so = s.nodeType == 1 ? null : 0;
  			res.eo = e.nodeType == 1 ? null : e.nodeValue.length;
  		}
  		// начальная нода == конечной ноде
  		if (s == e) {
  //			this.rte.log('start is end')
  			if (!this.rte.dom.isEmpty(s)) {
  				res.nodes.push(s);
  			}
  			return res;
  		}
  		 // this.rte.log('start 2')
  		  //this.dump(ca, s, e, res.so, res.eo)
  		
  		// находим начальную и конечную точки - ноды из иерархии родителей начальной и конечно ноды, у которых родитель - контейнер
  		var sp = s;
  		while (sp.nodeName != 'BODY' && sp.parentNode !== ca && sp.parentNode.nodeName != 'BODY') {
  			sp = sp.parentNode;
  		}
  		//this.rte.log(s.nodeName)
  		// this.rte.log('start point')
  		// this.rte.log(sp)
  		
  		var ep = e;
  //		this.rte.log(ep)
  		while (ep.nodeName != 'BODY' && ep.parentNode !== ca && ep.parentNode.nodeName != 'BODY') {
  			// this.rte.log(ep)
  			ep = ep.parentNode;
  		}
  		// this.rte.log('end point')
  		// this.rte.log(ep)
  		
  		
  		//  если начальная нода не пустая - добавляем ее
  		if (!isEmptySelected(s, res.so, s.nodeType==3 ? s.nodeValue.length : null)) {
  			res.nodes.push(s);
  		}
  		// поднимаемся от начальной ноды до начальной точки
  		var n = s;
  		while (n !== sp) {
  			var _n = n;
  			while ((_n = this.rte.dom.next(_n))) {
  					res.nodes.push(_n);
  			}
  			n = n.parentNode;
  		}
  		// от начальной точки до конечной точки
  		n = sp;
  		while ((n = this.rte.dom.next(n)) && n!= ep ) {
  //			this.rte.log(n)
  			res.nodes.push(n);
  		}
  		// поднимаемся от конечной ноды до конечной точки, результат переворачиваем
  		var tmp = [];
  		n = e;
  		while (n !== ep) {
  			var _n = n;
  			while ((_n = this.rte.dom.prev(_n))) {
  				tmp.push(_n);
  			}
  			n = n.parentNode;
  		}
  		if (tmp.length) {
  			res.nodes = res.nodes.concat(tmp.reverse());
  		}
  		//  если конечная нода не пустая и != начальной - добавляем ее
  		if (!isEmptySelected(e, 0, e.nodeType==3 ? res.eo : null)) {
  			res.nodes.push(e);
  		}
  		
  		if (blocks) {
  			// добавляем инлайн соседей слева
  			if (this.rte.dom.isInline(s)) {
  				res.nodes = this.rte.dom.toLineStart(s).concat(res.nodes);
  				res.so    = res.nodes[0].nodeType == 1 ? null : 0;
  			}
  			// добавляем инлайн соседей справа
  			if (this.rte.dom.isInline(e)) {
  				res.nodes = res.nodes.concat(this.rte.dom.toLineEnd(e));
  				res.eo    = res.nodes[res.nodes.length-1].nodeType == 1 ? null : res.nodes[res.nodes.length-1].nodeValue.length;
  			}
  		}
  		
  		// все радуются! :)
  		return res;
  	}
  	
  }
  
  })(jQuery);/**
   * @class elRTE User interface controller
   *
   * @param  elRTE  rte объект-редактор
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * @todo: this.domElem.removeClass('disabled') - move to ui.update;
   * @todo: add dom and selection as button members
   * Copyright: Studio 42, http://www.std42.ru
   **/
  (function($) {
  elRTE.prototype.ui = function(rte) {
  	this.rte      = rte;
  	this._buttons = [];
  	var self      = this,
  		tb        = this.rte.options.toolbars[rte.options.toolbar && rte.options.toolbars[rte.options.toolbar] ? rte.options.toolbar : 'normal'],
  		tbl       = tb.length,
  		p, pname, pl, n, c, b, i;
  
  	// add prototype to all buttons
  	for (i in this.buttons) {
  		if (this.buttons.hasOwnProperty(i) && i != 'button') {
  			this.buttons[i].prototype = this.buttons.button.prototype;
  		}
  	}
  
  	// create buttons and put on toolbar
  	while (tbl--) {
  		first = (tbl == 0 ? true : false);
  		if (tb[tbl - 1] == 'eol') { first = true; }
  
  		pname = tb[tbl];
  
  		// special 'end of line' panel, starts next panel on a new line
  		if (pname == 'eol') {
  			$(this.rte.doc.createElement('br')).prependTo(this.rte.toolbar);
  			continue;
  		}
  
  		p = $('<ul class="panel-'+pname+(first ? ' first' : '')+'" />').prependTo(this.rte.toolbar);
  		p.bind('mousedown', function(e) {
  			e.preventDefault();
  		})
  		pl = this.rte.options.panels[pname].length;
  		while (pl--) {
  			n = this.rte.options.panels[pname][pl];
  			c = this.buttons[n] || this.buttons.button;
  			this._buttons.push((b = new c(this.rte, n)));
  			p.prepend(b.domElem);
  		}
  	}
  
  	this.update();
  
  	this.disable = function() {
  		$.each(self._buttons, function() {
  			!this.active && this.domElem.addClass('disabled');
  		});
  	}
  
  }
  
  /**
   * Обновляет кнопки - вызывает метод update() для каждой кнопки
   *
   * @return void
   **/
  elRTE.prototype.ui.prototype.update = function(cleanCache) {
  	cleanCache && this.rte.selection.cleanCache();
  	var n    = this.rte.selection.getNode(),
  		p    = this.rte.dom.parents(n, '*'),
  		rtl = this.rte.rtl,
  		sep  = rtl ? ' &laquo; ' : ' &raquo; ',
  		path = '', name, i;
  
  	function _name(n) {
  		var name = n.nodeName.toLowerCase();
  		n = $(n)
  		if (name == 'img') {
  			if (n.hasClass('elrte-media')) {
  				name = 'media';
  			} else if (n.hasClass('elrte-google-maps')) {
  				name = 'google map';
  			} else if (n.hasClass('elrte-yandex-maps')) {
  				name = 'yandex map';
  			} else if (n.hasClass('elrte-pagebreak')) {
  				name = 'pagebreak';
  			}
  		}
  		return name;
  	}
  
  	if (n && n.nodeType == 1 && n.nodeName != 'BODY') {
  		p.unshift(n);
  	}
  
  	if (!rtl) {
  		p = p.reverse();
  	}
  
  	for (i=0; i < p.length; i++) {
  		path += (i>0 ? sep : '')+_name(p[i]);
  	}
  
  	this.rte.statusbar.html(path);
  	$.each(this._buttons, function() {
  		this.update();
  	});
  	this.rte.window.focus();
  }
  
  
  
  elRTE.prototype.ui.prototype.buttons = {
  
  	/**
  	 * @class кнопка на toolbar редактора
  	 * реализует поведение по умолчанию и является родителем для других кнопок
  	 *
  	 * @param  elRTE  rte   объект-редактор
  	 * @param  String name  название кнопки (команда исполняемая document.execCommand())
  	 **/
  	button : function(rte, name) {
  		var self     = this;
  		this.rte     = rte;
  		this.active = false;
  		this.name    = name;
  		this.val     = null;
  		this.domElem = $('<li style="-moz-user-select:-moz-none" class="'+name+' rounded-3" name="'+name+'" title="'+this.rte.i18n(this.rte.options.buttons[name] || name)+'" unselectable="on" />')
  			.hover(
  				function() { $(this).addClass('hover'); },
  				function() { $(this).removeClass('hover'); }
  			)
  			.click( function(e) {
  				e.stopPropagation();
  				e.preventDefault();
  				if (!$(this).hasClass('disabled')) {
  					// try{
  						self.command();
  					// } catch(e) {
  					// 	self.rte.log(e)
  					// }
  					
  				}
  				self.rte.window.focus();
  			});
  	}
  }
  
  /**
   * Обработчик нажатия на кнопку на тулбаре. Выполнение команды или открытие окна|меню и тд
   *
   * @return void
   **/
  elRTE.prototype.ui.prototype.buttons.button.prototype.command = function() {
  	this.rte.history.add();
  	try {
  		this.rte.doc.execCommand(this.name, false, this.val);
  	} catch(e) {
  		return this.rte.log('commands failed: '+this.name);
  	}
  
  	this.rte.ui.update(true);
  }
  
  /**
   * Обновляет состояние кнопки
   *
   * @return void
   **/
  elRTE.prototype.ui.prototype.buttons.button.prototype.update = function() {
  	try {
  		if (!this.rte.doc.queryCommandEnabled(this.name)) {
  			return this.domElem.addClass('disabled');
  		} else {
  			this.domElem.removeClass('disabled');
  		}
  	} catch (e) {
  		return;
  	}
  	try {
  		if (this.rte.doc.queryCommandState(this.name)) {
  			this.domElem.addClass('active');
  		} else {
  			this.domElem.removeClass('active');
  		}
  	} catch (e) { }
  }
  
  })(jQuery);
  /*
   * Misc utils for elRTE
   *
   * @param Object rte - editor
   * @todo Подумать, что из этого реально нужно и навести порядок. Возможно часть перенести в ellib
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * Copyright: Studio 42, http://www.std42.ru
   */
  (function($) {
  elRTE.prototype.utils = function(rte) {
  	this.rte     = rte;
  	this.url     = null;
  	// domo arigato, Steave, http://blog.stevenlevithan.com/archives/parseuri
  	this.reg     = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/;
  	this.baseURL = '';
  	this.path    = '';
  	/**
  	 * entities map
  	 **/
  	this.entities = {'&' : '&amp;', '"' : '&quot;', '<' : '&lt;', '>' : '&gt;'};
  	/**
  	 * entities regexp
  	 **/
  	this.entitiesRegExp = /[<>&\"]/g;
  	/**
  	 * media info
  	 **/
  	this.media = [{ 
  			type     : 'application/x-shockwave-flash', 
  			classid  : ['clsid:d27cdb6e-ae6d-11cf-96b8-444553540000'], 
  			codebase : 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0' 
  		}, { 
  			type     : 'application/x-director', 
  			classid  : ['clsid:166b1bca-3f9c-11cf-8075-444553540000'], 
  			codebase : 'http://download.macromedia.com/pub/shockwave/cabs/director/sw.cab#version=8,5,1,0' 
  		}, { 
  			type     : 'application/x-mplayer2', 
  			classid  : ['clsid:6bf52a52-394a-11d3-b153-00c04f79faa6', 'clsid:22d6f312-b0f6-11d0-94ab-0080c74c7e95', 'clsid:05589fa1-c356-11ce-bf01-00aa0055595a'], 
  			codebase : 'http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf.cab#Version=5,1,52,701' 
  		}, { 
  			type     : 'video/quicktime', 
  			classid  : ['clsid:02bf25d5-8c17-4b23-bc80-d3488abddc6b'], 
  			codebase : 'http://www.apple.com/qtactivex/qtplugin.cab#version=6,0,2,0' 
  		}, { 
  			type     : 'audio/x-pn-realaudio-plugin', 
  			classid  : ['clsid:cfcdaa03-8be4-11cf-b84b-0020afbbccfa'], 
  			codebase : 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0'
  		}];
  	
  	// rgb color regexp
  	this.rgbRegExp = /\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*/i;
  	// regexp to detect color in border/background properties
  	this.colorsRegExp = /aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow|rgb\s*\([^\)]+\)/i;
  	// web safe colors
  	this.colors = {
  		aqua    : '#00ffff',
  		black   : '#000000',
  		blue    : '#0000ff',
  		fuchsia : '#ff00ff',
  		gray    : '#808080',
  		green   : '#008000',
  		lime    : '#00ff00',
  		maroon  : '#800000',
  		navy    : '#000080',
  		olive   : '#808000',
  		orange  : '#ffa500',
  		purple  : '#800080',
  		red     : '#ff0000',
  		silver  : '#c0c0c0',
  		teal    : '#008080',
  		white   : '#fffffff',
  		yellow  : '#ffff00'
  	}
  	
  	var self     = this;
  	
  	this.rgb2hex = function(str) {
  		return this.color2Hex(''+str)
  	}
  	
  	this.toPixels = function(num) {
  		var m = num.match(/([0-9]+\.?[0-9]*)\s*(px|pt|em|%)/);
  		if (m) {
  			num  = m[1];
  			unit = m[2];
  		} 
  		if (num[0] == '.') {
  			num = '0'+num;
  		}
  		num = parseFloat(num);
  
  		if (isNaN(num)) {
  			return '';
  		}
  		var base = parseInt($(document.body).css('font-size')) || 16;
  		switch (unit) {
  			case 'em': return parseInt(num*base);
  			case 'pt': return parseInt(num*base/12);
  			case '%' : return parseInt(num*base/100);
  		}
  		return num;
  	}
  	
  	// TODO: add parse rel path ../../etc
  	this.absoluteURL = function(url) {
  		!this.url && this._url();
  		url = $.trim(url);
  		if (!url) {
  			return '';
  		}
  		// ссылки на якоря не переводим в абс
  		if (url[0] == '#') {
  			return url;
  		}
  		var u = this.parseURL(url);
  
  		if (!u.host && !u.path && !u.anchor) {
  			//this.rte.log('Invalid URL: '+url)
  			return '';
  		}
  		if (!this.rte.options.absoluteURLs) { 
  			return url;
  		}
  		if (u.protocol) {
  			//this.rte.log('url already absolute: '+url);
  			return url;
  		}
  		if (u.host && (u.host.indexOf('.')!=-1 || u.host == 'localhost')) {
  			//this.rte.log('no protocol');
  			return this.url.protocol+'://'+url;
  		}
  		if (url[0] == '/') {
  			url = this.baseURL+url;
  		} else {
  			if (url.indexOf('./') == 0) {
  				url = url.substring(2);
  			}
  			url = this.baseURL+this.path+url;
  		}
  		return url;
  	}
  	
  	this.parseURL = function(url) {
  		var u   = url.match(this.reg);
  		var ret = {};
  		$.each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(i) {
  			ret[this] = u[i];
  		});
  		if (!ret.host.match(/[a-z0-9]/i)) {
  			ret.host = '';
  		}
  		return ret;
  	}
  	
  	this.trimEventCallback = function(c) {
  		c = c ? c.toString() : '';
  		return $.trim(c.replace(/\r*\n/mg, '').replace(/^function\s*on[a-z]+\s*\(\s*event\s*\)\s*\{(.+)\}$/igm, '$1'));
  	}
  	
  	this._url = function() {
  		this.url     = this.parseURL(window.location.href);
  		this.baseURL = this.url.protocol+'://'+(this.url.userInfo ?  parts.userInfo+'@' : '')+this.url.host+(this.url.port ? ':'+this.url.port : '');
  		this.path    = !this.url.file ? this.url.path : this.url.path.substring(0, this.url.path.length - this.url.file.length);
  	}
  
  
  	/**
  	 * Create object (map) from array
  	 *
  	 * @param   Array
  	 * @return  Object
  	 **/
  	this.makeObject = function(o) {
  		var m = {};
  		$.each(o, function(i, e) {
  			m[e] = e;
  		});
  		return m;
  	}
  
  	/**
  	 * Encode entities in string
  	 *
  	 * @param   String
  	 * @return  String
  	 **/
  	this.encode = function(s) {
  		var e = this.entities;
  		return (''+s).replace(this.entitiesRegExp, function(c) {
  			return e[c];
  		});
  	}
  	
  	/**
  	 * Decode entities in string
  	 *
  	 * @param   String
  	 * @return  String
  	 **/
  	this.decode = function(s) {
  		return $('<div/>').html(s||'').text();
  	}
  	
  	/**
  	 * Parse style string into object
  	 *
  	 * @param   String
  	 * @return  Object
  	 **/
  	this.parseStyle = function(s) {
  		var st = {}, a = this.rte.options.allowBrowsersSpecStyles, t, n, v, p;
  		
  		if (typeof(s) == 'string' && s.length) {
  			
  			$.each(s.replace(/&quot;/gi, "'").split(';'), function(i, str) {
  				if ((p = str.indexOf(':')) !== -1) {
  					n = $.trim(str.substr(0, p));
  					v = $.trim(str.substr(p+1))
  					if (n == 'color' || n == 'background-color') {
  						v = v.toLowerCase();
  					}
  					if (n && v && (a || n.substring(0, 1) != '-')) {
  						st[n] = v;
  					}
  				}
  			});
  		}
  		return st;
  	}
  	
  	
  	/**
  	 * Compact some style properties and convert colors in hex
  	 *
  	 * @param   Object
  	 * @return  Object
  	 **/
  	this.compactStyle = function(s) {
  		var self = this;
  
  		if (s.border == 'medium none') {
  			delete s.border;
  		}
  		
  		$.each(s, function(n, v) {
  			if (/color$/i.test(n)) {
  				s[n] = self.color2Hex(v);
  			} else if (/^(border|background)$/i.test(n)) {
  				s[n] = v.replace(self.colorsRegExp, function(m) {
  					return self.color2Hex(m);
  				});
  			}
  		});
  		
  		if (s['border-width']) {
  			s.border = s['border-width']+' '+(s['border-style']||'solid')+' '+(s['border-color']||'#000');
  			delete s['border-width'];
  			delete s['border-style'];
  			delete s['border-color'];
  		}
  		
  		if (s['background-image']) {
  			s.background = (s['background-color']+' ')||''+s['background-image']+' '+s['background-position']||'0 0'+' '+s['background-repeat']||'repeat';
  			delete s['background-image'];
  			delete['background-image'];
  			delete['background-position'];
  			delete['background-repeat'];
  		}
  		
  		if (s['margin-top'] && s['margin-right'] && s['margin-bottom'] && s['margin-left']) {
  			s.margin = s['margin-top']+' '+s['margin-right']+' '+s['margin-bottom']+' '+s['margin-left'];
  			delete s['margin-top'];
  			delete s['margin-right'];
  			delete s['margin-bottom'];
  			delete s['margin-left'];
  		}
  		
  		if (s['padding-top'] && s['padding-right'] && s['padding-bottom'] && s['padding-left']) {
  			s.padding = s['padding-top']+' '+s['padding-right']+' '+s['padding-bottom']+' '+s['padding-left'];
  			delete s['padding-top'];
  			delete s['padding-right'];
  			delete s['padding-bottom'];
  			delete s['padding-left'];
  		}
  		
  		if (s['list-style-type'] || s['list-style-position'] || s['list-style-image']) {
  			s['list-style'] = $.trim(s['list-style-type']||' '+s['list-style-position']||''+s['list-style-image']||'');
  			delete s['list-style-type'];
  			delete s['list-style-position'];
  			delete s['list-style-image'];
  		}
  		
  		return s;
  	}
  	
  	/**
  	 * Serialize style object into string
  	 *
  	 * @param   Object  style map
  	 * @param   Boolean flag - compact style?
  	 * @return  String
  	 **/
  	this.serializeStyle = function(o, c) {
  		var s = [];
  		// c=true
  		$.each(c ? this.compactStyle(o) : o, function(n, v) {
  			v && s.push(n+':'+v);
  		});
  		return s.join(';');
  	}
  
  	/**
  	 * Parse class string into object
  	 *
  	 * @param   String
  	 * @return  Object
  	 **/
  	this.parseClass = function(c) {
  		c = $.trim(c); 
  		// this.rte.log(c)
  		return c.length ? this.makeObject(c.split(/\s+/)) : {};
  		//return c.length ? c.split(/\s+/) : [];
  	}
  
  	/**
  	 * Serialize class object into string
  	 *
  	 * @param   Object
  	 * @return  String
  	 **/
  	this.serializeClass = function(c) {
  		// return c.join(' ')
  		var s = [];
  		// this.rte.log(c)
  		var rte = this.rte
  		$.each(c, function(n) {
  			s.push(n);
  			// rte.log(typeof(n))
  		});
  		return s.join(' ');
  	}
  	
  	/**
  	 * Return required media type info
  	 *
  	 * @param   String  mimetype
  	 * @param   String  classid
  	 * @return  Object
  	 **/
  	this.mediaInfo = function(t, c) {
  		var l = this.media.length;
  		
  		while (l--) {
  			if (t === this.media[l].type || (c && $.inArray(c, this.media[l].classid) != -1)) {
  				return this.media[l];
  			}
  		}
  	}
  
  	/**
  	 * Return color hex value
  	 *
  	 * @param   String   color name or rgb
  	 * @return  String
  	 **/
  	this.color2Hex = function(c) {
  		var m;
  		
  		c = c||'';
  		
  		if (c.indexOf('#') === 0) {
  			return c;
  		}
  		
  		
  		function hex(s) {
  			s = parseInt(s).toString(16);
  			return s.length > 1 ? s : '0' + s; 
  		};
  		
  		if (this.colors[c]) {
  			return this.colors[c];
  		}
  		if ((m = c.match(this.rgbRegExp))) {
  			return '#'+hex(m[1])+hex(m[2])+hex(m[3]);
  		}
  		return '';
  	}
  	
  
  	
  	
  }
  
  })(jQuery);/**
   * @class w3cRange  - w3c text range emulation for "strange" browsers
   *
   * @param  elRTE  rte  объект-редактор
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * Copyright: Studio 42, http://www.std42.ru
   **/
  (function($) {
  elRTE.prototype.w3cRange = function(rte) {
  	var self                     = this;
  	this.rte                     = rte;
  	this.r                       = null;
  	this.collapsed               = true;
  	this.startContainer          = null;
  	this.endContainer            = null;
  	this.startOffset             = 0;
  	this.endOffset               = 0;
  	this.commonAncestorContainer = null;
  	
  	this.range = function() {
  		try { 
  			this.r = this.rte.window.document.selection.createRange(); 
  		} catch(e) { 
  			this.r = this.rte.doc.body.createTextRange(); 
  		}
  		return this.r;
  	}
  	
  	this.insertNode = function(html) {
  		this.range();
  		self.r.collapse(false)
  		var r = self.r.duplicate();
  		r.pasteHTML(html);
  	}
  	
  	this.getBookmark = function() {
  		this.range();
  		if (this.r.item) {
  			var n = this.r.item(0);
  			this.r = this.rte.doc.body.createTextRange();
  			this.r.moveToElementText(n);
  		}
  		return this.r.getBookmark();
  	}
  	
  	this.moveToBookmark = function(bm) {
  		this.rte.window.focus();
  		this.range().moveToBookmark(bm);
  		this.r.select();
  	}
  	
  	/**
  	 * Обновляет данные о выделенных нодах
  	 *
  	 * @return void
  	 **/
  	this.update = function() {
  
  		function _findPos(start) {
  			var marker = '\uFEFF';
  			var ndx = offset = 0;
  			var r = self.r.duplicate();
  			r.collapse(start);
  			var p = r.parentElement();
  			if (!p || p.nodeName == 'HTML') {
  				return {parent : self.rte.doc.body, ndx : ndx, offset : offset};
  			}
  
  			r.pasteHTML(marker); // TODO IE11ダメっぽい
  			
  			childs = p.childNodes;
  			for (var i=0; i < childs.length; i++) {
  				var n = childs[i];
  				if (i>0 && (n.nodeType!==3 || childs[i-1].nodeType !==3)) {
  					ndx++;
  				}
  				if (n.nodeType !== 3) {
  					offset = 0;
  				} else {
  					var pos = n.nodeValue.indexOf(marker);
  					if (pos !== -1) {
  						offset += pos;
  						break;
  					}
  					offset += n.nodeValue.length;
  				}
  			};
  			r.moveStart('character', -1);
  			r.text = '';
  			return {parent : p, ndx : Math.min(ndx, p.childNodes.length-1), offset : offset};
  		}
  
  		this.range();
  		this.startContainer = this.endContainer = null;
  
  		if (this.r.item) {
  			this.collapsed = false;
  			var i = this.r.item(0);
  			this.setStart(i.parentNode, this.rte.dom.indexOf(i));
  			this.setEnd(i.parentNode, this.startOffset+1);
  		} else {
  			this.collapsed = this.r.boundingWidth == 0;
  			var start = _findPos(true); 
  			var end   = _findPos(false);
  			
  			start.parent.normalize();
  			end.parent.normalize();
  			start.ndx = Math.min(start.ndx, start.parent.childNodes.length-1);
  			end.ndx = Math.min(end.ndx, end.parent.childNodes.length-1);
  			if (start.parent.childNodes[start.ndx].nodeType && start.parent.childNodes[start.ndx].nodeType == 1) { // TODO IE8エラー
  				this.setStart(start.parent, start.ndx);
  			} else {
  				this.setStart(start.parent.childNodes[start.ndx], start.offset);
  			}
  			if (end.parent.childNodes[end.ndx].nodeType && end.parent.childNodes[end.ndx].nodeType == 1) { // TODO IE8エラー
  				this.setEnd(end.parent, end.ndx);
  			} else {
  				this.setEnd(end.parent.childNodes[end.ndx], end.offset);
  			}
  			// this.dump();
  			this.select();
  		}
  		return this;
  	}
  	
  	this.isCollapsed = function() {
  		this.range();
  		this.collapsed = this.r.item ? false : this.r.boundingWidth == 0;
  		return this.collapsed;
  	}
  	
  	/**
  	 * "Схлопывает" выделение
  	 *
  	 * @param  bool  toStart - схлопывать выделение к началу или к концу
  	 * @return void
  	 **/
  	this.collapse = function(toStart) {
  		this.range();
  		if (this.r.item) {
  			var n = this.r.item(0);
  			this.r = this.rte.doc.body.createTextRange();
  			this.r.moveToElementText(n);
  		}
  		this.r.collapse(toStart);
  		this.r.select();
  		this.collapsed = true;
  	}
  
  	this.getStart = function() {
  		this.range();
  		if (this.r.item) {
  			return this.r.item(0);
  		}
  		var r = this.r.duplicate();
  		r.collapse(true);
  		var s = r.parentElement();
  		return s && s.nodeName == 'BODY' ? s.firstChild : s;
  	}
  	
  	
  	this.getEnd = function() {
  		this.range();
  		if (this.r.item) {
  			return this.r.item(0);
  		}
  		var r = this.r.duplicate();
  		r.collapse(false);
  		var e = r.parentElement();
  		return e && e.nodeName == 'BODY' ? e.lastChild : e;
  	}
  
  	
  	/**
  	 * Устанавливает начaло выделения на указаную ноду
  	 *
  	 * @param  Element  node    нода
  	 * @param  Number   offset  отступ от начала ноды
  	 * @return void
  	 **/
  	this.setStart = function(node, offset) {
  		this.startContainer = node;
  		this.startOffset    = offset;
  		if (this.endContainer) {
  			this.commonAncestorContainer = this.rte.dom.findCommonAncestor(this.startContainer, this.endContainer);
  		}
  	}
  	
  	/**
  	 * Устанавливает конец выделения на указаную ноду
  	 *
  	 * @param  Element  node    нода
  	 * @param  Number   offset  отступ от конца ноды
  	 * @return void
  	 **/
  	this.setEnd = function(node, offset) {
  		this.endContainer = node;
  		this.endOffset    = offset;
  		if (this.startContainer) {
  			this.commonAncestorContainer = this.rte.dom.findCommonAncestor(this.startContainer, this.endContainer);
  		}
  	}
  	
  	/**
  	 * Устанавливает начaло выделения перед указаной нодой
  	 *
  	 * @param  Element  node    нода
  	 * @return void
  	 **/
  	this.setStartBefore = function(n) {
  		if (n.parentNode) {
  			this.setStart(n.parentNode, this.rte.dom.indexOf(n));
  		}
  	}
  	
  	/**
  	 * Устанавливает начaло выделения после указаной ноды
  	 *
  	 * @param  Element  node    нода
  	 * @return void
  	 **/
  	this.setStartAfter = function(n) {
  		if (n.parentNode) {
  			this.setStart(n.parentNode, this.rte.dom.indexOf(n)+1);
  		}
  	}
  	
  	/**
  	 * Устанавливает конец выделения перед указаной нодой
  	 *
  	 * @param  Element  node    нода
  	 * @return void
  	 **/
  	this.setEndBefore = function(n) {
  		if (n.parentNode) {
  			this.setEnd(n.parentNode, this.rte.dom.indexOf(n));
  		}
  	}
  	
  	/**
  	 * Устанавливает конец выделения после указаной ноды
  	 *
  	 * @param  Element  node    нода
  	 * @return void
  	 **/
  	this.setEndAfter = function(n) {
  		if (n.parentNode) {
  			this.setEnd(n.parentNode, this.rte.dom.indexOf(n)+1);
  		}
  	}
  	
  	/**
  	 * Устанавливает новое выделение после изменений
  	 *
  	 * @return void
  	 **/
  	this.select = function() {
  		// thanks tinymice authors
  		function getPos(n, o) {
  			if (n.nodeType != 3) {
  				return -1;
  			}
  			var c   ='\uFEFF';
  			var val = n.nodeValue;
  			var r   = self.rte.doc.body.createTextRange();
  			n.nodeValue = val.substring(0, o) + c + val.substring(o);
  			r.moveToElementText(n.parentNode);
  			r.findText(c);
  			var p = Math.abs(r.moveStart('character', -0xFFFFF));
  			n.nodeValue = val;
  			return p;
  		};
  		
  		this.r = this.rte.doc.body.createTextRange(); 
  		var so = this.startOffset;
  		var eo = this.endOffset;
  		var s = this.startContainer.nodeType == 1 
  			? this.startContainer.childNodes[Math.min(so, this.startContainer.childNodes.length - 1)]
  			: this.startContainer;
  		var e = this.endContainer.nodeType == 1 
  			? this.endContainer.childNodes[Math.min(so == eo ? eo : eo - 1, this.endContainer.childNodes.length - 1)]
  			: this.endContainer;
  
  		if (this.collapsed) {
  			if (s.nodeType == 3) {
  				var p = getPos(s, so);
  				this.r.move('character', p);
  			} else {
  				this.r.moveToElementText(s);
  				this.r.collapse(true);
  			}
  		} else {
  			var r  = this.rte.doc.body.createTextRange(); 
  			var sp = getPos(s, so);
  			var ep = getPos(e, eo);
  			if (s.nodeType == 3) {
  				this.r.move('character', sp);
  			} else {
  				this.r.moveToElementText(s);
  			}
  			if (e.nodeType == 3) {
  				r.move('character', ep);
  			} else {
  				r.moveToElementText(e);
  			}
  			this.r.setEndPoint('EndToEnd', r);
  		}
  		
  		try {
  			this.r.select();
  		} catch(e) {
  			
  		}
  		if (r) {
  			r = null;
  		}
  	}
  	
  	this.dump = function() {
  		this.rte.log('collapsed: '+this.collapsed);
  		//this.rte.log('commonAncestorContainer: '+this.commonAncestorContainer.nodeName||'#text')
  		this.rte.log('startContainer: '+(this.startContainer ? this.startContainer.nodeName : 'non'));
  		this.rte.log('startOffset: '+this.startOffset);
  		this.rte.log('endContainer: '+(this.endContainer ? this.endContainer.nodeName : 'none'));
  		this.rte.log('endOffset: '+this.endOffset);
  	}
  	
  }
  })(jQuery);
  (function($) {
  	elRTE.prototype.ui.prototype.buttons.about = function(rte, name)  {
  		
  		this.constructor.prototype.constructor.call(this, rte, name);
  		this.active  = true;
  		this.command = function() {
  			var opts, d, txt;
  			
  			opts = {
  				rtl : rte.rtl,
  				submit : function(e, d) {  d.close(); },
  				dialog : {
  					width : 560,
  					title : this.rte.i18n('About this software'),
  					buttons     : {
  						Ok : function() { $(this).dialog('destroy'); }
  					}
  				}
  			}
  
  			txt = '<div class="elrte-logo"></div><h3>'+this.rte.i18n('About elRTE')+'</h3><br clear="all"/>'
  				+'<div class="elrte-ver">'+this.rte.i18n('Version')+': '+this.rte.version+' ('+this.rte.build+')</div>' 
  				+'<div class="elrte-ver">jQuery: '+$('<div/>').jquery+'</div>' 
  				+'<div class="elrte-ver">jQueryUI: '+$.ui.version+'</div>' 
  				+'<div class="elrte-ver">'+this.rte.i18n('Licence')+': BSD Licence</div>'
  				+'<p>'
  				+this.rte.i18n('elRTE is an open-source JavaScript based WYSIWYG HTML-editor.')+'<br/>'
  				+this.rte.i18n('Main goal of the editor - simplify work with text and formating (HTML) on sites, blogs, forums and other online services.')+'<br/>'
  				+this.rte.i18n('You can use it in any commercial or non-commercial projects.')
  				+'</p>'
  				+'<h4>'+this.rte.i18n('Authors')+'</h4>'
  				+'<table class="elrte-authors">'
  				+'<tr><td>Dmitry (dio) Levashov &lt;dio@std42.ru&gt;</td><td>'+this.rte.i18n('Chief developer')+'</td></tr>'
  				+'<tr><td>Troex Nevelin &lt;troex@fury.scancode.ru&gt;</td><td>'+this.rte.i18n('Developer, tech support')+'</td></tr>'
  				+'<tr><td>Valentin Razumnyh &lt;content@std42.ru&gt;</td><td>'+this.rte.i18n('Interface designer')+'</td></tr>'
  				+'<tr><td>Tawfek Daghistani &lt;tawfekov@gmail.com&gt;</td><td>'+this.rte.i18n('RTL support')+'</td></tr>'
  				+(this.rte.options.lang != 'en' ? '<tr><td>'+this.rte.i18n('_translator')+'</td><td>'+this.rte.i18n('_translation')+'</td></tr>' : '')
  				+'</table>'
  				+'<div class="elrte-copy">Copyright &copy; 2009-2011, <a href="http://www.std42.ru">Studio 42</a></div>'
  				+'<div class="elrte-copy">'+this.rte.i18n('For more information about this software visit the')+' <a href="http://elrte.org">'+this.rte.i18n('elRTE website')+'.</a></div>'
  				+'<div class="elrte-copy">Twitter: <a href="http://twitter.com/elrte_elfinder">elrte_elfinder</a></div>';
  			
  			d = new elDialogForm(opts);
  			d.append(txt);
  			d.open();
  		}
  		
  		this.update = function() {
  			this.domElem.removeClass('disabled');
  		}
  		
  	}
  	
  })(jQuery);
  /**
   * @class кнопка - Закладка (открывает диалоговое окно)
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки 
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   **/
  (function($) {
  elRTE.prototype.ui.prototype.buttons.anchor = function(rte, name) {
  	this.constructor.prototype.constructor.call(this, rte, name);
  	this.input = $('<input type="text" />').attr('name', 'anchor').attr('size', '16')
  	var self = this;
  	
  	this.command = function() {
  		var opts = {
  			rtl : this.rte.rtl,
  			submit : function(e, d) { e.stopPropagation(); e.preventDefault(); d.close(); self.set();  },
  			dialog : {
  				title : this.rte.i18n('Bookmark')
  			}
  		}
  
  		
  		this.anchor = this.rte.dom.selfOrParentAnchor(this.rte.selection.getEnd()) || rte.dom.create('a');
  		!this.rte.selection.collapsed() && this.rte.selection.collapse(false);
  		this.input.val($(this.anchor).addClass('elrte-anchor').attr('name'));
  		this.rte.selection.saveIERange();
  		var d = new elDialogForm(opts);
  		d.append([this.rte.i18n('Bookmark name'), this.input], null, true).open();
  		setTimeout(function() { self.input.focus()}, 20);
  	}
  	
  	this.update = function() {
  		var n = this.rte.selection.getNode();
  		if (this.rte.dom.selfOrParentLink(n)) {
  			this.domElem.addClass('disabled');
  		} else if (this.rte.dom.selfOrParentAnchor(n)) {
  			this.domElem.removeClass('disabled').addClass('active');
  		} else {
  			this.domElem.removeClass('disabled').removeClass('active');
  		}
  	}
  	
  	this.set = function() {
  		var n = $.trim(this.input.val());
  
  		if (n) {
  			this.rte.history.add();
  			if (!this.anchor.parentNode) {
  				this.rte.selection.insertHtml('<a name="'+n+'" title="'+this.rte.i18n('Bookmark')+': '+n+'" class="elrte-anchor"></a>');
  			} else {
  				this.anchor.name = n;
  				this.anchor.title = this.rte.i18n('Bookmark')+': '+n;
  			}
  		} else if (this.anchor.parentNode) {
  			this.rte.history.add();
  			this.anchor.parentNode.removeChild(this.anchor);
  		}
  	}
  }
  })(jQuery);
  /**
   * @class кнопка - Цитата
   * Если выделение схлопнуто и находится внутри цитаты - она удаляется
   * Новые цитаты создаются только из несхлопнутого выделения
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки 
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * @copyright: Studio 42, http://www.std42.ru
   **/
  (function($) {
  elRTE.prototype.ui.prototype.buttons.blockquote = function(rte, name) {
  	this.constructor.prototype.constructor.call(this, rte, name);
  	
  	this.command = function() {
  		var n, nodes;
  		this.rte.history.add();
  		if (this.rte.selection.collapsed() && (n = this.rte.dom.selfOrParent(this.rte.selection.getNode(), /^BLOCKQUOTE$/))) {
  			$(n).replaceWith($(n).html());
  		} else {
  			nodes = this.rte.selection.selected({wrap : 'all', tag : 'blockquote'});
  			nodes.length && this.rte.selection.select(nodes[0], nodes[nodes.length-1]);
  		}
  		this.rte.ui.update(true);
  	}
  	
  	this.update = function() {
  		if (this.rte.selection.collapsed()) {
  			if (this.rte.dom.selfOrParent(this.rte.selection.getNode(), /^BLOCKQUOTE$/)) {
  				this.domElem.removeClass('disabled').addClass('active');
  			} else {
  				this.domElem.addClass('disabled').removeClass('active');
  			}
  		} else {
  			this.domElem.removeClass('disabled active');
  		}
  	}
  }
  })(jQuery);
  /**
   * @class кнопки "копировать/вырезать/вставить" 
   * в firefox показывает предложение нажать Ctl+c, в остальных - копирует
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки 
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * @copyright: Studio 42, http://www.std42.ru
   **/
  (function($) {
  elRTE.prototype.ui.prototype.buttons.copy = function(rte, name) {
  	this.constructor.prototype.constructor.call(this, rte, name);
  	
  	this.command = function() {
  		
  		if (this.rte.browser.mozilla) {
  			try {
  				this.rte.doc.execCommand(this.name, false, null);
  			} catch (e) {
  				var s = ' Ctl + C';
  				if (this.name == 'cut') {
  					s = ' Ctl + X';
  				} else if (this.name == 'paste') {
  					s = ' Ctl + V';
  				}
  				var opts = {
  					dialog : {
  						title   : this.rte.i18n('Warning'),
  						buttons : { Ok : function() { $(this).dialog('close'); } }
  					}
  				}
  
  				var d = new elDialogForm(opts);
  				d.append(this.rte.i18n('This operation is disabled in your browser on security reason. Use shortcut instead.')+': '+s).open();
  			}
  		} else {
  			this.constructor.prototype.command.call(this);
  		}
  	}
  }
  
  elRTE.prototype.ui.prototype.buttons.cut   = elRTE.prototype.ui.prototype.buttons.copy;
  elRTE.prototype.ui.prototype.buttons.paste = elRTE.prototype.ui.prototype.buttons.copy;
  
  })(jQuery);(function($) {
  	
  	elRTE.prototype.ui.prototype.buttons.css = function(rte, name) {
  		var self = this;
  		this.constructor.prototype.constructor.call(this, rte, name);
  		this.cssStyle  = $('<input type="text" size="42" name="style" />');
  		this.cssClass  = $('<input type="text" size="42" name="class" />');
  		this.elementID = $('<input type="text" size="42" name="id" />');
  		
  		this.command = function() {
  			var n = this.node(), opts;
  			this.rte.selection.saveIERange();
  			if (n) {
  				var opts = {
  					
  					submit : function(e, d) { e.stopPropagation(); e.preventDefault(); d.close(); self.set();  },
  					dialog : {
  						title : this.rte.i18n('Style'),
  						width : 450,
  						resizable : true,
  						modal : true
  					}
  				}
  				this.cssStyle.val($(n).attr('style'));
  				this.cssClass.val($(n).attr('class'));
  				this.elementID.val($(n).attr('id'));
  				var d = new elDialogForm(opts);
  				d.append([this.rte.i18n('Css style'), this.cssStyle],  null, true)
  				d.append([this.rte.i18n('Css class'), this.cssClass],  null, true)
  				d.append([this.rte.i18n('ID'),        this.elementID], null, true)
  				d.open();
  				setTimeout(function() { self.cssStyle.focus() }, 20)
  			}
  		}
  		
  		this.set = function() {
  			var n = this.node();
  			this.rte.selection.restoreIERange();
  			if (n) {
  				$(n).attr('style', this.cssStyle.val());
  				$(n).attr('class', this.cssClass.val());
  				$(n).attr('id',    this.elementID.val());
  				this.rte.ui.update();
  			}
  		}
  		
  		this.node = function() {
  			var n = this.rte.selection.getNode();
  			if (n.nodeType == 3) {
  				n = n.parentNode;
  			}
  			return n.nodeType == 1 && n.nodeName != 'BODY' ? n : null;
  		}
  		
  		this.update = function() {
  			this.domElem.toggleClass('disabled', this.node()?false:true);
  		}
  		
  	}
  	
  })(jQuery);
  
  (function($) {
  	/**
  	 * @class button - right to left direction (not work yet with text nodes in body)
  	 *
  	 * @param  elRTE  rte   объект-редактор
  	 * @param  String name  название кнопки 
  	 *
  	 * @author:    Dmitry Levashov (dio) dio@std42.ru
  	 * Copyright: Studio 42, http://www.std42.ru
  	 **/
  	elRTE.prototype.ui.prototype.buttons.rtl = function(rte, name)  {
  		this.constructor.prototype.constructor.call(this, rte, name);
  		var self = this;
  	
  		this.command = function() {
  			var n = this.rte.selection.getNode(), self = this;
  			if ($(n).attr('dir') == 'rtl' || $(n).parents('[dir="rtl"]').length || $(n).find('[dir="rtl"]').length) {
  				$(n).removeAttr('dir');
  				$(n).parents('[dir="rtl"]').removeAttr('dir');
  				$(n).find('[dir="rtl"]').removeAttr('dir');
  			} else {
  				if (this.rte.dom.is(n, 'textNodes') && this.rte.dom.is(n, 'block')) {
  					$(n).attr('dir', 'rtl');
  				} else {
  					$.each(this.rte.dom.parents(n, 'textNodes'), function(i, n) {
  						if (self.rte.dom.is(n, 'block')) {
  							$(n).attr('dir', 'rtl');
  							return false;
  						}
  					});
  				}
  			}
  			this.rte.ui.update();
  		}
  
  		this.update = function() {
  			var n = this.rte.selection.getNode();
  			this.domElem.removeClass('disabled');
  			if ($(n).attr('dir') == 'rtl' || $(n).parents('[dir="rtl"]').length || $(n).find('[dir="rtl"]').length) {
  				this.domElem.addClass('active');
  			} else {
  				this.domElem.removeClass('active');
  			}
  		}
  	}
  	
  	/**
  	 * @class button - left to right direction (not work yet with text nodes in body)
  	 *
  	 * @param  elRTE  rte   объект-редактор
  	 * @param  String name  название кнопки 
  	 *
  	 * @author:    Dmitry Levashov (dio) dio@std42.ru
  	 * Copyright: Studio 42, http://www.std42.ru
  	 **/
  	elRTE.prototype.ui.prototype.buttons.ltr = function(rte, name)  {
  		this.constructor.prototype.constructor.call(this, rte, name);
  		var self = this;
  	
  		this.command = function() {
  			var n = this.rte.selection.getNode(), self = this;
  			if ($(n).attr('dir') == 'ltr' || $(n).parents('[dir="ltr"]').length || $(n).find('[dir="ltr"]').length) {
  				$(n).removeAttr('dir');
  				$(n).parents('[dir="ltr"]').removeAttr('dir');
  				$(n).find('[dir="ltr"]').removeAttr('dir');
  			} else {
  				if (this.rte.dom.is(n, 'textNodes') && this.rte.dom.is(n, 'block')) {
  					$(n).attr('dir', 'ltr');
  				} else {
  					$.each(this.rte.dom.parents(n, 'textNodes'), function(i, n) {
  						if (self.rte.dom.is(n, 'block')) {
  							$(n).attr('dir', 'ltr');
  							return false;
  						}
  					});
  				}
  			}
  			this.rte.ui.update();
  		}
  
  		this.update = function() {
  			var n = this.rte.selection.getNode();
  			this.domElem.removeClass('disabled');
  			if ($(n).attr('dir') == 'ltr' || $(n).parents('[dir="ltr"]').length || $(n).find('[dir="ltr"]').length) {
  				this.domElem.addClass('active');
  			} else {
  				this.domElem.removeClass('active');
  			}
  		}
  	}
  	
  })(jQuery);/**
   * @class кнопка - DIV
   * Если выделение схлопнуто и находится внутри div'a - он удаляется
   * Новые div'ы создаются только из несхлопнутого выделения
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки 
   * 
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * copyright: Studio 42, http://www.std42.ru
   **/
  (function($) {
  elRTE.prototype.ui.prototype.buttons.div = function(rte, name) {
  	this.constructor.prototype.constructor.call(this, rte, name);
  	
  	this.command = function() {
  		var n, nodes;
  		this.rte.history.add();
  
  		if (this.rte.selection.collapsed()) {
  			n = this.rte.dom.selfOrParent(this.rte.selection.getNode(), /^DIV$/);
  			if (n) {
  				$(n).replaceWith($(n).html());
  			}
  		} else {
  			nodes = this.rte.selection.selected({wrap : 'all', tag : 'div'});
  			nodes.length && this.rte.selection.select(nodes[0], nodes[nodes.length-1]);
  		}
  		this.rte.ui.update(true);
  	}
  	
  	this.update = function() {
  		if (this.rte.selection.collapsed()) {
  			if (this.rte.dom.selfOrParent(this.rte.selection.getNode(), /^DIV$/)) {
  				this.domElem.removeClass('disabled').addClass('active');
  			} else {
  				this.domElem.addClass('disabled active');
  			}
  		} else {
  			this.domElem.removeClass('disabled active');
  		}
  	}
  }
  })(jQuery);
  /**
   * @class кнопка - Включение/выключение показа структуры документа
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки 
   * 
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * @copyright: Studio 42, http://www.std42.ru
   **/
  (function($) {
  elRTE.prototype.ui.prototype.buttons.docstructure = function(rte, name) {
  	this.constructor.prototype.constructor.call(this, rte, name);
  	
  	this.command = function() {
  		this.domElem.toggleClass('active');
  		$(this.rte.doc.body).toggleClass('el-rte-structure');
  	}
  	this.command();
  	
  	this.update = function() {	
  		this.domElem.removeClass('disabled');
  	}
  }
  })(jQuery);
  /**
   * @class button - open elfinder window (not needed for image or link buttons).Used in ELDORADO.CMS for easy file manipulations.
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки 
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * @copyright: Studio 42, http://www.std42.ru
   **/
  (function($) {
  elRTE.prototype.ui.prototype.buttons.elfinder = function(rte, name) {
  	this.constructor.prototype.constructor.call(this, rte, name);
  	var self = this,
  		rte = this.rte;
  	this.command = function() {
  		if (self.rte.options.fmAllow && typeof(self.rte.options.fmOpen) == 'function') {
  			self.rte.options.fmOpen( function(url) { 
  				var name = decodeURIComponent(url.split('/').pop().replace(/\+/g, " "));
  				
  				if (rte.selection.collapsed()) {
  					rte.selection.insertHtml('<a href="'+url+'" >'+name+'</a>');
  				} else {
  					rte.doc.execCommand('createLink', false, url);
  				}
  				
  			} );
  		}
  	}
  	
  	this.update = function() {
  		if (self.rte.options.fmAllow && typeof(self.rte.options.fmOpen) == 'function') {
  			this.domElem.removeClass('disabled');
  		} else {
  			this.domElem.addClass('disabled');
  		}
  	}
  }
  
  })(jQuery);
  (function($) {
  	
  	elRTE.prototype.ui.prototype.buttons.flash = function(rte, name) {
  		this.constructor.prototype.constructor.call(this, rte, name);
  		var self = this;
  		this.swf = null;
  		this.placeholder = null;
  		this.src = {
  			url    : $('<input type="text" name="url" />').css('width', '99%'),
  			type   : $('<select name="type"/>')
  						.append('<option value="application/x-shockwave-flash">Flash</option>')
  						.append('<option value="video/quicktime">Quicktime movie</option>')
  						.append('<option value="application/x-mplayer2">Windows media</option>'),
  			width  : $('<input type="text" />').attr('size', 5).css('text-align', 'right'),
  			height : $('<input type="text" />').attr('size', 5).css('text-align', 'right'),
  			wmode  : $('<select />')
  						.append($('<option />').val('').text(this.rte.i18n('Not set', 'dialogs')))
  						.append($('<option />').val('transparent').text(this.rte.i18n('Transparent'))),
  			align  : $('<select />')
  						.append($('<option />').val('').text(this.rte.i18n('Not set', 'dialogs')))
  						.append($('<option />').val('left'       ).text(this.rte.i18n('Left')))
  						.append($('<option />').val('right'      ).text(this.rte.i18n('Right')))
  						.append($('<option />').val('top'        ).text(this.rte.i18n('Top')))
  						.append($('<option />').val('text-top'   ).text(this.rte.i18n('Text top')))
  						.append($('<option />').val('middle'     ).text(this.rte.i18n('middle')))
  						.append($('<option />').val('baseline'   ).text(this.rte.i18n('Baseline')))
  						.append($('<option />').val('bottom'     ).text(this.rte.i18n('Bottom')))
  						.append($('<option />').val('text-bottom').text(this.rte.i18n('Text bottom'))),
  			margin : $('<div />')
  		}
  		
  		this.command = function() {
  
  			var n = this.rte.selection.getEnd(), opts, url='', w='', h='', f, a, d, mid, o, wm;
  			this.rte.selection.saveIERange();
  			this.src.margin.elPaddingInput({ type : 'margin' });
  			this.placeholder = null;
  			this.swf = null;
  			if ($(n).hasClass('elrte-media') && (mid = $(n).attr('rel')) &&  this.rte.filter.scripts[mid]) {
  				this.placeholder = $(n);
  				o = this.rte.filter.scripts[mid];
  				url = '';
  				if (o.embed && o.embed.src) {
  					url = o.embed.src;
  				}
  				if (o.params && o.params.length) {
  					l = o.params.length;
  					while (l--) {
  						if (o.params[l].name == 'src' || o.params[l].name == 'movie') {
  							url =  o.params[l].value;
  						}
  					}
  				}
  				
  				if (o.embed) {
  					w = o.embed.width||parseInt(o.embed.style.width)||'';
  					h = o.embed.height||parseInt(o.embed.style.height)||'';
  					wm = o.embed.wmode||'';
  				} else if (o.obj) {
  					w = o.obj.width||parseInt(o.obj.style.width)||'';
  					h = o.obj.height||parseInt(o.obj.style.height)||'';
  					wm = o.obj.wmode||'';
  				}
  				
  				if (o.obj) {
  					f = o.obj.style['float']||'';
  					a = o.obj.style['vertical-align']||'';
  				} else if (o.embed) {
  					f = o.embed.style['float']||'';
  					a = o.embed.style['vertical-align']||'';
  				}
  				this.src.margin.val(n);
  				this.src.type.val(o.embed ? o.embed.type : '');
  			}
  			if ($(n).hasClass('elrte-swf-placeholder')) {
  				this.placeholder = $(n);
  				url = $(n).attr('rel');
  				w = parseInt($(n).css('width'))||'';
  				h = parseInt($(n).css('height'))||'';
  				f = $(n).css('float');
  				a = $(n).css('vertical-align');
  				this.src.margin.val(n);
  				this.src.wmode.val($(n).attr('wmode'));
  			} 
  			this.src.url.val(url);
  			this.src.width.val(w);
  			this.src.height.val(h);
  			this.src.align.val(f||a);
  			this.src.wmode.val(wm);
  			
  
  			
  
  			var opts = {
  				rtl : this.rte.rtl,
  				submit : function(e, d) { e.stopPropagation(); e.preventDefault(); self.set(); d.close(); },
  				dialog : {
  					width    : 580,
  					position : 'top',
  					title    : this.rte.i18n('Flash')
  				}
  			}
  			var d = new elDialogForm(opts);
  			
  			if (this.rte.options.fmAllow && this.rte.options.fmOpen) {
  				var src = $('<span />').append(this.src.url.css('width', '85%'))
  						.append(
  							$('<span />').addClass('ui-state-default ui-corner-all')
  								.css({'float' : 'right', 'margin-right' : '3px'})
  								.attr('title', self.rte.i18n('Open file manger'))
  								.append($('<span />').addClass('ui-icon ui-icon-folder-open'))
  									.click( function() {
  										self.rte.options.fmOpen( function(url) { self.src.url.val(url).change(); } );
  									})
  									.hover(function() {$(this).addClass('ui-state-hover')}, function() { $(this).removeClass('ui-state-hover')})
  							);
  			} else {
  				var src = this.src.url;
  			}
  			
  			d.append([this.rte.i18n('URL'), src], null, true);
  			d.append([this.rte.i18n('Type'), this.src.type], null, true);
  			d.append([this.rte.i18n('Size'), $('<span />').append(this.src.width).append(' x ').append(this.src.height).append(' px')], null, true)
  			d.append([this.rte.i18n('Wmode'), this.src.wmode], null, true);
  			d.append([this.rte.i18n('Alignment'), this.src.align], null, true);
  			d.append([this.rte.i18n('Margins'), this.src.margin], null, true);
  			
  			
  			
  			d.open();
  			// setTimeout( function() {self.src.url.focus()}, 100)
  			
  			
  			var fs = $('<fieldset />').append($('<legend />').text(this.rte.i18n('Preview')))
  			d.append(fs, 'main');
  			var frame = document.createElement('iframe');
  			$(frame).attr('src', '#').addClass('el-rte-preview').appendTo(fs);
  			html = this.rte.options.doctype+'<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /></head><body style="padding:0;margin:0;font-size:9px"> Proin elit arcu, rutrum commodo, vehicula tempus, commodo a, risus. Curabitur nec arcu. Donec sollicitudin mi sit amet mauris. Nam elementum quam ullamcorper ante. Etiam aliquet massa et lorem. Mauris dapibus lacus auctor risus. Aenean tempor ullamcorper leo. Vivamus sed magna quis ligula eleifend adipiscing. Duis orci. Aliquam sodales tortor vitae ipsum. Aliquam nulla. Duis aliquam molestie erat. Ut et mauris vel pede varius sollicitudin</body></html>';
  			frame.contentWindow.document.open();
  			frame.contentWindow.document.write(html);
  			frame.contentWindow.document.close();
  			this.frame = frame.contentWindow.document;
  			this.preview = $(frame.contentWindow.document.body);
  			 				 
  			this.src.type.change(function() {
  				self.src.url.change();
  			});
  
  			this.src.width.change(function() {
  				if (self.swf) {
  					var w = parseInt($(this).val())||'';
  					$(this).val(w);
  					self.swf.css('width', w);
  					self.swf.children('embed').css('width', w);
  				} else {
  					$(this).val('');
  				}
  			});
  
  			this.src.height.change(function() {
  				if (self.swf) {
  					var h = parseInt($(this).val())||'';
  					$(this).val(h);
  					self.swf.css('height', h);
  					self.swf.children('embed').css('height', h);
  				} else {
  					$(this).val('');
  				}
  			});
  			
  			this.src.wmode.change(function() {
  				if (self.swf) {
  					var wm = $(this).val();
  					if (wm) {
  						self.swf.attr('wmode', wm);
  						self.swf.children('embed').attr('wmode', wm);
  					} else {
  						self.swf.removeAttr('wmode');
  						self.swf.children('embed').removeAttr('wmode');
  					}
  				}
  			});
  			
  			this.src.align.change(function() {
  				var v = $(this).val(), f = v=='left' || v=='right';
  				if (self.swf) {
  					self.swf.css({
  						'float' : f ? v : '',
  						'vertical-align' : f ? '' : v
  					});
  				} else {
  					$(this).val('');
  				}
  			});
  			
  			this.src.margin.change(function() {
  				if (self.swf) {
  					var m = self.src.margin.val();
  					if (m.css) {
  						self.swf.css('margin', m.css);
  					} else {
  						self.swf.css('margin-top', m.top);
  						self.swf.css('margin-right', m.right);
  						self.swf.css('margin-bottom', m.bottom);
  						self.swf.css('margin-left', m.left);						
  					}
  				}
  			});
  			
  			this.src.url.change(function() {
  				var url = self.rte.utils.absoluteURL($(this).val()), i, swf;
  				if (url) {
  					i = self.rte.utils.mediaInfo(self.src.type.val());
  					if (!i) {
  						i = self.rte.util.mediaInfo('application/x-shockwave-flash');
  					}
  					swf = '<object classid="'+i.classid+'" codebase="'+i.codebase+'"><param name="src" value="'+url+'" /><embed quality="high" src="'+url+'" type="'+i.type+'"></object>';
  					self.preview.children('object').remove().end().prepend(swf);
  					self.swf = self.preview.children('object').eq(0);
  				} else if (self.swf){
  					self.swf.remove();
  					self.swf = null;
  				}
  				self.src.width.trigger('change');
  				self.src.height.trigger('change');
  				self.src.align.trigger('change');
  
  			}).trigger('change');
  		};
  		
  		this.set = function() {
  			self.swf = null
  			var url = this.rte.utils.absoluteURL(this.src.url.val()),
  				w = parseInt(this.src.width.val()) || '',
  				h = parseInt(this.src.height.val()) || '',
  				wm = this.src.wmode.val(),
  				a = this.src.align.val(),
  				f = a == 'left' || a == 'right' ? a : '',
  				mid = this.placeholder ? this.placeholder.attr('rel') : '', o, _o, c, 
  				m = this.src.margin.val(), margin;
  
  			
  			
  			if (!url) {
  				if (this.placeholder) {
  					this.placeholder.remove();
  					delete this.rte.filter.scripts[mid];
  				}
  			} else {
  				i = self.rte.utils.mediaInfo(self.src.type.val());
  				if (!i) {
  					i = self.rte.util.mediaInfo('application/x-shockwave-flash');
  				}
  				c = this.rte.filter.videoHostRegExp.test(url) ? url.replace(this.rte.filter.videoHostRegExp, "$2") : i.type.replace(/^\w+\/(.+)/, "$1");
  
  				o = {
  					obj : {
  						classid : i.classid[0],
  						codebase : i.codebase,
  						style : {}
  					},
  					params :[ { name : 'src', value : url } ],
  					embed :{
  						src : url,
  						type : i.type,
  						quality : 'high',
  						wmode : wm,
  						style : {}
  					}
  				};
  				
  				if (w) {
  					o.obj.width = w;
  					o.embed.width = w;
  				}
  				if (h) {
  					o.obj.height = h;
  					o.embed.height = h;
  				}
  				if (f) {
  					o.obj.style['float'] = f;
  				} else if (a) {
  					o.obj.style['vertical-align'] = a;
  				}
  				
  				if (m.css) {
  					margin = { margin : m.css };
  				} else {
  					margin = {
  						'margin-top' : m.top,
  						'margin-right' : m.right,
  						'margin-bottom' : m.bottom,
  						'margin-left' : m.left
  					};
  				}
  				
  				o.obj.style = $.extend({}, o.obj.style, margin);
  				
  				if (this.placeholder && mid) {
  					_o = this.rte.filter.scripts[mid]||{};
  
  					o = $.extend(true, _o, o);
  					delete o.obj.style.width;
  					delete o.obj.style.height;
  					delete o.embed.style.width;
  					delete o.embed.style.height;
  					this.rte.filter.scripts[mid] = o;
  					this.placeholder.removeAttr('class');
  				} else {
  					var id = 'media'+Math.random().toString().substring(2);
  					this.rte.filter.scripts[id] = o;
  					this.placeholder = $(this.rte.dom.create('img')).attr('rel', id).attr('src', this.rte.filter.url+'pixel.gif');
  					var ins = true;
  				}
  				this.placeholder.attr('title', this.rte.utils.encode(url)).attr('width', w||150).attr('height', h||100).addClass('elrte-protected elrte-media elrte-media-'+c).css(o.obj.style);
  				if (f) {
  					this.placeholder.css('float', f).css('vertical-align', '');
  				} else if (a) {
  					this.placeholder.css('float', '').css('vertical-align', a);
  				} else {
  					this.placeholder.css('float', '').css('vertical-align', '');
  				}
  				
  				if (ins) {
  					this.rte.window.focus();
  					this.rte.selection.restoreIERange();
  					this.rte.selection.insertNode(this.placeholder.get(0));
  				}
  			}
  		}
  		
  		this.update = function() {
  			this.domElem.removeClass('disabled');
  			var n = this.rte.selection.getNode();
  			this.domElem.toggleClass('active', n && n.nodeName == 'IMG' && $(n).hasClass('elrte-media'))
  			
  		}
  		
  		
  	}
  })(jQuery);/**
   * @class drop-down menu - font-family for selected text
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки 
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * @copyright: Studio 42, http://www.std42.ru
   **/
  (function($) {
  elRTE.prototype.ui.prototype.buttons.fontname = function(rte, name) {
  	this.constructor.prototype.constructor.call(this, rte, name);
  	var self = this;
  	var opts = {
  		tpl      : '<span style="font-family:%val">%label</span>',
  		select   : function(v) { self.set(v); },
  		src      : {
  			''                                              : this.rte.i18n('Font'),
  			'andale mono,sans-serif'                        : 'Andale Mono',
  			'arial,helvetica,sans-serif'                    : 'Arial',
  			'arial black,gadget,sans-serif'                 : 'Arial Black',
  			'book antiqua,palatino,sans-serif'              : 'Book Antiqua',
  			'comic sans ms,cursive'                         : 'Comic Sans MS',
  			'courier new,courier,monospace'                 : 'Courier New',
  			'georgia,palatino,serif'                        : 'Georgia',
  			'helvetica,sans-serif'                          : 'Helvetica',
  			'impact,sans-serif'                             : 'Impact',
  			'lucida console,monaco,monospace'               : 'Lucida console',
  			'lucida sans unicode,lucida grande,sans-serif'  : 'Lucida grande',
  			'tahoma,sans-serif'                             : 'Tahoma',
  			'times new roman,times,serif'                   : 'Times New Roman',
  			'trebuchet ms,lucida grande,verdana,sans-serif' : 'Trebuchet MS',
  			'verdana,geneva,sans-serif'                     : 'Verdana'
  		}
  	}
  	
  	this.select = this.domElem.elSelect(opts);
  	
  	this.command = function() {
  	}
  	
  	this.set = function(size) {
  		this.rte.history.add();
  		var nodes = this.rte.selection.selected({filter : 'textContainsNodes'});
  		$.each(nodes, function() {
  			$this = /^(THEAD|TFOOT|TBODY|COL|COLGROUP|TR)$/.test(this.nodeName) ? $(this).find('td,th') : $(this);
  			$(this).css('font-family', size).find('[style]').css('font-family', '');
  		});
  		this.rte.ui.update();
  	}
  	
  	this.update = function() {
  		this.domElem.removeClass('disabled'); 
  		var n = this.rte.selection.getNode();
  		if (n.nodeType != 1) {
  			n = n.parentNode;
  		}
  		var v = $(n).css('font-family');
  		v = v ? v.toString().toLowerCase().replace(/,\s+/g, ',').replace(/'|"/g, '') : '';
  		this.select.val(opts.src[v] ? v : '');
  	}
  }
  
  })(jQuery);/**
   * @class drop-down menu - font size for selected text
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки 
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * @copyright: Studio 42, http://www.std42.ru
   **/
  (function($) {
  elRTE.prototype.ui.prototype.buttons.fontsize = function(rte, name) {
  	this.constructor.prototype.constructor.call(this, rte, name);
  	var self = this;
  	var opts = {
  		labelTpl : '%label',
  		tpl      : '<span style="font-size:%val;line-height:1.2em">%label</span>',
  		select   : function(v) { self.set(v); },
  		src      : {
  			''         : this.rte.i18n('Font size'),
  			'xx-small' : this.rte.i18n('Small (8pt)'), 
  			'x-small'  : this.rte.i18n('Small (10px)'), 
  			'small'    : this.rte.i18n('Small (12pt)'), 
  			'medium'   : this.rte.i18n('Normal (14pt)'),
  			'large'    : this.rte.i18n('Large (18pt)'),
  			'x-large'  : this.rte.i18n('Large (24pt)'),
  			'xx-large' : this.rte.i18n('Large (36pt)')
  		}
  	}
  	
  	this.select = this.domElem.elSelect(opts);
  	
  	this.command = function() {
  	}
  	
  	this.set = function(size) {
  		this.rte.history.add();
  		var nodes = this.rte.selection.selected({filter : 'textContainsNodes'});
  		$.each(nodes, function() {
  			$this = /^(THEAD|TFOOT|TBODY|COL|COLGROUP|TR)$/.test(this.nodeName) ? $(this).find('td,th') : $(this);
  			$this.css('font-size', size).find("[style]").css('font-size', '');
  		});
  		this.rte.ui.update();
  	}
  	
  	this.update = function() {
  		this.domElem.removeClass('disabled');
  		var n = this.rte.selection.getNode();
  		this.select.val((m = this.rte.dom.attr(n, 'style').match(/font-size:\s*([^;]+)/i)) ? m[1] : '');
  	}
  }
  
  })(jQuery);/**
   * @class color pallete for text color and background
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки 
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * @copyright: Studio 42, http://www.std42.ru
   **/
  (function($) {
  elRTE.prototype.ui.prototype.buttons.forecolor = function(rte, name) {
  	var self = this;
  	this.constructor.prototype.constructor.call(this, rte, name);
  	var opts = {
  		'class' : '',
  		palettePosition : 'outer',
  		color   : this.defaultColor,
  		update  : function(c) { self.indicator.css('background-color', c); },
  		change  : function(c) { self.set(c) }
  	}
  	
  	this.defaultColor = this.name == 'forecolor' ? '#000000' : '#ffffff';
  	this.picker       = this.domElem.elColorPicker(opts);
  	this.indicator    = $('<div />').addClass('color-indicator').prependTo(this.domElem);
  	
  	this.command = function() {
  	}
  	
  	this.set = function(c) {
  		if (!this.rte.selection.collapsed()) {
  			this.rte.history.add();
  			var nodes = this.rte.selection.selected({collapse : false, wrap : 'text'}),
  				css   = this.name == 'forecolor' ? 'color' : 'background-color';			
  			$.each(nodes, function() {
  				if (/^(THEAD|TBODY|TFOOT|TR)$/.test(this.nodeName)) {
  					$(this).find('td,th').each(function() {
  						$(this).css(css, c).find('*').css(css, '');
  					})
  				} else {
  					$(this).css(css, c).find('*').css(css, '');
  				}
  			});
  			this.rte.ui.update(true);
  		}
  	}
  	
  	this.update = function() {
  		this.domElem.removeClass('disabled');
  		var n = this.rte.selection.getNode();
  		this.picker.val(this.rte.utils.rgb2hex($(n.nodeType != 1 ? n.parentNode : n).css(this.name == 'forecolor' ? 'color' : 'background-color'))||this.defaultColor)
  	}
  }
  
  elRTE.prototype.ui.prototype.buttons.hilitecolor = elRTE.prototype.ui.prototype.buttons.forecolor;
  
  })(jQuery);/**
   * @class drop-down menu - formatting text block
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки 
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * @copyright: Studio 42, http://www.std42.ru
   **/
  (function($) {
  elRTE.prototype.ui.prototype.buttons.formatblock = function(rte, name) {
  	this.constructor.prototype.constructor.call(this, rte, name);
  
  	var cmd = this.rte.browser.msie 
  		? function(v) { self.val = v; self.constructor.prototype.command.call(self); }
  		: function(v) { self.ieCommand(v); } 
  	var self = this;
  	var opts = {
  		labelTpl : '%label',
  		tpls     : {'' : '%label'},
  		select   : function(v) { self.formatBlock(v); },
  		src      : {
  			'span'    : this.rte.i18n('Format'),
  			'h1'      : this.rte.i18n('Heading 1'),
  			'h2'      : this.rte.i18n('Heading 2'),
  			'h3'      : this.rte.i18n('Heading 3'),
  			'h4'      : this.rte.i18n('Heading 4'),
  			'h5'      : this.rte.i18n('Heading 5'),
  			'h6'      : this.rte.i18n('Heading 6'),
  			'p'       : this.rte.i18n('Paragraph'),
  			'address' : this.rte.i18n('Address'),
  			'pre'     : this.rte.i18n('Preformatted'),
  			'div'     : this.rte.i18n('Normal (DIV)')
  		}
  	}
  
  	this.select = this.domElem.elSelect(opts);
  	
  	this.command = function() {
  
  	}
  	
  	this.formatBlock = function(v) {
  
  		function format(n, tag) {
  			
  			function replaceChilds(p) {
  				$(p).find('h1,h2,h3,h4,h5,h6,p,address,pre').each(function() {
  					$(this).replaceWith($(this).html());
  				});
  				return p;
  			}
  			
  			if (/^(LI|DT|DD|TD|TH|CAPTION)$/.test(n.nodeName)) {
  				!self.rte.dom.isEmpty(n) && self.rte.dom.wrapContents(replaceChilds(n), tag);
  			} else if (/^(UL|OL|DL|TABLE)$/.test(n.nodeName)) {
  				self.rte.dom.wrap(n, tag);
  			} else {
  				!self.rte.dom.isEmpty(n) && $(replaceChilds(n)).replaceWith( $(self.rte.dom.create(tag)).html($(n).html()));
  			}
  			
  		}
  		this.rte.history.add();
  
  		var tag = v.toUpperCase(),
  			i, n, $n,
  			c = this.rte.selection.collapsed(),
  			bm = this.rte.selection.getBookmark(),
  			nodes = this.rte.selection.selected({
  				collapsed : true,
  				blocks    : true,
  				filter    : 'textContainsNodes',
  				wrap      : 'inline',
  				tag       : 'span'
  			})
  			l = nodes.length,
  			s = $(nodes[0]).prev(),
  			e = $(nodes[nodes.length-1]).next();
  
  		while (l--) {
  			n = nodes[l];
  			$n = $(n);
  			if (tag == 'DIV' || tag == 'SPAN') {
  				if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(n.nodeName)) {
  					$n.replaceWith($(this.rte.dom.create('div')).html($n.html()||''));
  				}
  			} else {
  				if (/^(THEAD|TBODY|TFOOT|TR)$/.test(n.nodeName)) {
  					$n.find('td,th').each(function() { format(this, tag); });
  				} else if (n.nodeName != tag) {
  					format(n, tag);
  				}
  			}
  		}
  
  		this.rte.selection.moveToBookmark(bm);
  
  		this.rte.ui.update(true);
  	}
  	
  	this.update = function() {
  		this.domElem.removeClass('disabled');
  		var n = this.rte.dom.selfOrParent(this.rte.selection.getNode(), /^(H[1-6]|P|ADDRESS|PRE)$/);
  		this.select.val(n ? n.nodeName.toLowerCase() : 'span');
  	}
  }
  })(jQuery);
  /**
   * @class button - switch to fullscreen mode and back
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки 
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * @copyright: Studio 42, http://www.std42.ru
   **/
  (function($) {
  elRTE.prototype.ui.prototype.buttons.fullscreen = function(rte, name) {
  	var self     = this;
  	this.constructor.prototype.constructor.call(this, rte, name);
  	this.active  = true;
  	this.editor = rte.editor;
  	this.wz = rte.workzone;
  	this.height  = 0;
  	this.delta = 0;
  	this._class = 'el-fullscreen';
  	
  	setTimeout(function() {
  		self.height  = self.wz.height();
  		self.delta   = self.editor.outerHeight()-self.height;
  	}, 50);
  	
  	
  	/**
  	 * Update editor height on window resize in fullscreen view
  	 *
  	 **/
  	function resize() {
  		self.wz.height($(window).height()-self.delta);
  		self.rte.updateHeight();
  	}
  	
  	this.command = function() {
  		var w = $(window),
  			e = this.editor,
  			p = e.parents().filter(function(i, n) { return  !/^(html|body)$/i.test(n.nodeName) && $(n).css('position') == 'relative'; }),
  			wz = this.wz,
  			c = this._class,
  			f = e.hasClass(c),
  			rte = this.rte,
  			s = this.rte.selection,
  			m = $.browser.mozilla,
  			b, h;
  
  		function save() {
  			if (m) {
  				b = s.getBookmark();
  			}
  		}
  		
  		function restore() {
  			if (m) {
  				self.wz.children().toggle();
  				self.rte.source.focus();
  				self.wz.children().toggle();
  				s.moveToBookmark(b);
  			}
  		}
  
  		save();
  		p.css('position', f ? 'relative' : 'static');	
  		
  		if (f) {
  			e.removeClass(c);
  			wz.height(this.height);
  			w.unbind('resize', resize);
  			this.domElem.removeClass('active');
  		} else {
  			e.addClass(c).removeAttr('style');
  			wz.height(w.height() - this.delta).css('width', '100%');
  			w.bind('resize', resize);
  			this.domElem.addClass('active');
  		}
  		rte.updateHeight();	
  		rte.resizable(f);
  		restore();
  		
  	}
  	
  	this.update = function() {
  		this.domElem.removeClass('disabled');
  	}
  }
  })(jQuery);
  /**
   * @class button - horizontal rule (open dialog window)
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки 
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * @copyright: Studio 42, http://www.std42.ru
   **/
  (function($) {
  elRTE.prototype.ui.prototype.buttons.horizontalrule = function(rte, name) {
  	this.constructor.prototype.constructor.call(this, rte, name);
  	var self = this;
  	var bookmarks = null;
  	this.src = {
  		width   : $('<input type="text" />').attr({'name' : 'width', 'size' : 4}).css('text-align', 'right'),
  		wunit   : $('<select />').attr('name', 'wunit')
  					.append($('<option />').val('%').text('%'))
  					.append($('<option />').val('px').text('px'))
  					.val('%'),
  		height  : $('<input type="text" />').attr({'name' : 'height', 'size' : 4}).css('text-align', 'right'),
  		bg      : $('<div />'),
  		border  : $('<div />'),
  		'class' : $('<input type="text" />').css('width', '100%'),
  		style   : $('<input type="text" />').css('width', '100%')
  	}
  	
  	this.command = function() {
  		this.src.bg.elColorPicker({palettePosition : 'outer', 'class' : 'el-colorpicker ui-icon ui-icon-pencil'});
  		
  		var n   = this.rte.selection.getEnd();
  		this.hr = n.nodeName == 'HR' ? $(n) : $(rte.doc.createElement('hr')).css({width : '100%', height : '1px'});
  		bookmarks = self.rte.selection.getBookmark();
  		this.src.border.elBorderSelect({styleHeight : 73, value : this.hr});
  		
  		var _w  = this.hr.css('width') || this.hr.attr('width');
  		this.src.width.val(parseInt(_w) || 100);
  		this.src.wunit.val(_w.indexOf('px') != -1 ? 'px' : '%');
  		
  		this.src.height.val( this.rte.utils.toPixels(this.hr.css('height') || this.hr.attr('height')) || 1) ;
  		
  		this.src.bg.val(this.rte.utils.color2Hex(this.hr.css('background-color')));
  		this.src['class'].val(this.rte.dom.attr(this.hr, 'class'));
  		this.src.style.val(this.rte.dom.attr(this.hr, 'style'));
  		
  		var opts = {
  			rtl : this.rte.rtl,
  			submit : function(e, d) { e.stopPropagation(); e.preventDefault(); self.set(); d.close(); },
  			dialog : {
  				title : this.rte.i18n('Horizontal rule')
  			},
  			close : function() {
  				bookmarks && self.rte.selection.moveToBookmark(bookmarks);
  			}
  		}
  
  		var d = new elDialogForm(opts);
  		d.append([this.rte.i18n('Width'),          $('<span />').append(this.src.width).append(this.src.wunit) ], null, true)
  			.append([this.rte.i18n('Height'),      $('<span />').append(this.src.height).append(' px')], null, true)
  			.append([this.rte.i18n('Border'),      this.src.border], null, true)
  			.append([this.rte.i18n('Background'),  this.src.bg], null, true)
  			.append([this.rte.i18n('Css class'),   this.src['class']], null, true)
  			.append([this.rte.i18n('Css style'),   this.src.style], null, true)
  			.open();
  	}
  	
  	this.update = function() {
  		this.domElem.removeClass('disabled');
  		if (this.rte.selection.getEnd() && this.rte.selection.getEnd().nodeName == 'HR') {
  			this.domElem.addClass('active');
  		} else {
  			this.domElem.removeClass('active');
  		}
  	}
  	
  	this.set = function() {
  		this.rte.history.add();
  		bookmarks && self.rte.selection.moveToBookmark(bookmarks);
  		!this.hr.parentNode && this.rte.selection.insertNode(this.hr.get(0));
  		var attr = {
  			noshade : true,
  			style   : this.src.style.val()
  		}
  		var b = this.src.border.val();
  		var css = {
  			width  : (parseInt(this.src.width.val()) || 100)+this.src.wunit.val(),
  			height : parseInt(this.src.height.val()) || 1,
  			'background-color' : this.src.bg.val(),
  			border : b.width && b.style ? b.width+' '+b.style+' '+b.color : ''
  		}
  
  		this.hr.removeAttr('class')
  			.removeAttr('style')
  			.removeAttr('width')
  			.removeAttr('height')
  			.removeAttr('align')
  			.attr(attr)
  			.css(css);
  		
  		if (this.src['class'].val()) {
  			this.hr.attr('class', this.src['class'].val());	
  		}
  		this.rte.ui.update()
  	}
  	
  }
  })(jQuery);
  /**
   * @class button - insert/edit image (open dialog window)
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки 
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * Copyright: Studio 42, http://www.std42.ru
   **/
  (function($) {
  elRTE.prototype.ui.prototype.buttons.image = function(rte, name) {
  	this.constructor.prototype.constructor.call(this, rte, name);
  	var self = this,
  		rte  = self.rte,
  		proportion = 0,
  		width = 0,
  		height = 0,
  		bookmarks = null,
  		reset = function(nosrc) {
  			$.each(self.src, function(i, elements) {
  				$.each(elements, function(n, el) {
  					if (n == 'src' && nosrc) {
  						return;
  					}
  					el.val('');
  				});
  			});
  		},
  		values = function(img) {
  			$.each(self.src, function(i, elements) {
  				$.each(elements, function(n, el) {
  					var val, w, c, s, border;
  					
  					if (n == 'width') {
  						val = img.width();
  					} else if (n == 'height') {
  						val = img.height();
  					} else if (n == 'border') {
  						/*
  						val = '';
  						border = img.css('border') || rte.utils.parseStyle(img.attr('style')).border || '';
  
  						if (border) {
  							w = border.match(/(\d(px|em|%))/);
  							c = border.match(/(#[a-z0-9]+)/);
  							val = {
  								width : w ? w[1] : border,
  								style : border,
  								color : rte.utils.color2Hex(c ? c[1] : border)
  							}
  						} 
  						*/
  					} else if (n == 'margin') {
  						val = img;
  					} else if (n == 'align') { 
  						val = img.css('float');
  
  						if (val != 'left' && val != 'right') {
  							val = img.css('vertical-align');
  						}
  					 }else {
  						val = img.attr(n)||'';
  					}
  					
  					if (i == 'events') {
  						val = rte.utils.trimEventCallback(val);
  					}
  					
  					if (n == 'border') {
  						el.val(img);
  					} else {
  						el.val(val);
  					}
  				});
  			});
  		},
  		preview = function() {
  			var src = self.src.main.src.val();
  			
  			reset(true);
  			
  			if (!src) {
  				self.preview.children('img').remove();
  				self.prevImg = null;
  			} else {
  				if (self.prevImg) {
  					self.prevImg
  						.removeAttr('src')
  						.removeAttr('style')
  						.removeAttr('class')
  						.removeAttr('id')
  						.removeAttr('title')
  						.removeAttr('alt')
  						.removeAttr('longdesc');
  						
  					$.each(self.src.events, function(name, input) {
  						self.prevImg.removeAttr(name);
  					});
  				} else {
  					self.prevImg = $('<img/>').prependTo(self.preview);
  				}
  				self.prevImg.load(function() {
  					self.prevImg.unbind('load');
  					setTimeout(function() {
  						width      = self.prevImg.width();
  						height     = self.prevImg.height();
  						proportion = (width/height).toFixed(2);
  						self.src.main.width.val(width);
  						self.src.main.height.val(height);
  						
  					}, 100);
  				})
  				.attr('src', src);
  			}
  			
  		},
  		size = function(e) {
  			var w = parseInt(self.src.main.width.val())||0,
  				h = parseInt(self.src.main.height.val())||0;
  				
  			if (self.prevImg) {
  				if (w && h) {
  					if (e.target === self.src.main.width[0]) {
  						h = parseInt(w/proportion);
  					} else {
  						w = parseInt(h*proportion);
  					}
  				} else {
  					w = width;
  					h = height;
  				}
  				self.src.main.height.val(h);
  				self.src.main.width.val(w);
  				self.prevImg.width(w).height(h);
  				self.src.adv.style.val(self.prevImg.attr('style'));
  			}
  		}
  		;
  	
  	this.img     = null;
  	this.prevImg = null;
  	this.preview = $('<div class="elrte-image-preview"/>').text('Proin elit arcu, rutrum commodo, vehicula tempus, commodo a, risus. Curabitur nec arcu. Donec sollicitudin mi sit amet mauris. Nam elementum quam ullamcorper ante. Etiam aliquet massa et lorem. Mauris dapibus lacus auctor risus. Aenean tempor ullamcorper leo. Vivamus sed magna quis ligula eleifend adipiscing. Duis orci. Aliquam sodales tortor vitae ipsum. Aliquam nulla. Duis aliquam molestie erat. Ut et mauris vel pede varius sollicitudin');
  	
  	this.init = function() {	
  		this.labels = {
  			main   : 'Properies',
  			link   : 'Link',
  			adv    : 'Advanced',
  			events : 'Events',
  			id       : 'ID',
  			'class'  : 'Css class',
  			style    : 'Css style',
  			longdesc : 'Detail description URL',
  			href    : 'URL',
  			target  : 'Open in',
  			title   : 'Title'
  		}
  		
  		this.src = {
  			main : {
  				src    : $('<input type="text" />').css('width', '100%').change(preview),
  				title  : $('<input type="text" />').css('width', '100%'),
  				alt    : $('<input type="text" />').css('width', '100%'),
  				width  : $('<input type="text" />').attr('size', 5).css('text-align', 'right').change(size),
  				height : $('<input type="text" />').attr('size', 5).css('text-align', 'right').change(size),
  				margin : $('<div />').elPaddingInput({
  					type : 'margin', 
  					change : function() {
  						var margin = self.src.main.margin.val();
  					
  						if (self.prevImg) {
  							if (margin.css) {
  								self.prevImg.css('margin', margin.css)
  							} else {
  								self.prevImg.css({
  									'margin-left'   : margin.left,
  									'margin-top'    : margin.top,
  									'margin-right'  : margin.right,
  									'margin-bottom' : margin.bottom
  								});
  							}
  						}
  					} 
  				}), 
  				align  : $('<select />').css('width', '100%')
  							.append($('<option />').val('').text(this.rte.i18n('Not set', 'dialogs')))
  							.append($('<option />').val('left'       ).text(this.rte.i18n('Left')))
  							.append($('<option />').val('right'      ).text(this.rte.i18n('Right')))
  							.append($('<option />').val('top'        ).text(this.rte.i18n('Top')))
  							.append($('<option />').val('text-top'   ).text(this.rte.i18n('Text top')))
  							.append($('<option />').val('middle'     ).text(this.rte.i18n('middle')))
  							.append($('<option />').val('baseline'   ).text(this.rte.i18n('Baseline')))
  							.append($('<option />').val('bottom'     ).text(this.rte.i18n('Bottom')))
  							.append($('<option />').val('text-bottom').text(this.rte.i18n('Text bottom')))
  							.change(function() {
  								var val = $(this).val(),
  									css = {
  										'float' : '',
  										'vertical-align' : ''
  									};
  								if (self.prevImg) {
  									if (val == 'left' || val == 'right') {
  										css['float'] = val;
  										css['vertical-align'] = '';
  									} else if (val) {
  										css['float'] = '';
  										css['vertical-align'] = val;
  									} 
  									self.prevImg.css(css);
  								}
  							})
  						,
  				border : $('<div />').elBorderSelect({
  					name : 'border',
  					change : function() {
  						var border = self.src.main.border.val();
  						if (self.prevImg) {
  							self.prevImg.css('border', border.width ? border.width+' '+border.style+' '+border.color : '');
  						}
  					}
  				})
  			},
  
  			adv : {},
  			events : {}
  		}
  		
  		$.each(['id', 'class', 'style', 'longdesc'], function(i, name) {
  			self.src.adv[name] = $('<input type="text" style="width:100%" />');
  		});
  		
  		this.src.adv['class'].change(function() {
  			if (self.prevImg) {
  				self.prevImg.attr('class', $(this).val());
  			}
  		});
  		
  		this.src.adv.style.change(function() {
  			if (self.prevImg) {
  				self.prevImg.attr('style', $(this).val());
  				values(self.prevImg);
  			}
  		});
  		
  		$.each(
  			['onblur', 'onfocus', 'onclick', 'ondblclick', 'onmousedown', 'onmouseup', 'onmouseover', 'onmouseout', 'onmouseleave', 'onkeydown', 'onkeypress', 'onkeyup'], 
  			function() {
  				self.src.events[this] = $('<input type="text"  style="width:100%"/>');
  		});
  	}
  	
  	this.command = function() {
  		!this.src && this.init();
  		
  		var img, 
  			opts = {
  				rtl : rte.rtl,
  				submit : function(e, d) { 
  					e.stopPropagation(); 
  					e.preventDefault(); 
  					self.set(); 
  
  					dialog.close(); 
  				},
  				close : function() {
  
  					bookmarks && rte.selection.moveToBookmark(bookmarks)
  				},
  				dialog : {
  					autoOpen  : false,
  					width     : 500,
  					position  : 'top',
  					title     : rte.i18n('Image'),
  					resizable : true,
  					open      : function() {
  						$.fn.resizable && $(this).parents('.ui-dialog:first').resizable('option', 'alsoResize', '.elrte-image-preview');
  					}
  				}
  			},
  			dialog = new elDialogForm(opts),
  			fm = !!rte.options.fmOpen,
  			src = fm
  				? $('<div class="elrte-image-src-fm"><span class="ui-state-default ui-corner-all"><span class="ui-icon ui-icon-folder-open"/></span></div>')
  					.append(this.src.main.src.css('width', '87%'))
  				: this.src.main.src;
  			
  			;
  		
  		reset();
  		this.preview.children('img').remove();
  		this.prevImg = null;
  		img = rte.selection.getEnd();
  		
  		this.img = img.nodeName == 'IMG' && !$(img).is('.elrte-protected')
  			? $(img)
  			: $('<img/>');
  		
  		bookmarks = rte.selection.getBookmark();
  
  		if (fm) {
  			src.children('.ui-state-default')
  				.click( function() {
  					rte.options.fmOpen( function(url) { self.src.main.src.val(url).change() } );
  				})
  				.hover(function() {
  					$(this).toggleClass('ui-state-hover');
  				});
  		}
  		
  		dialog.tab('main', this.rte.i18n('Properies'))
  			.append([this.rte.i18n('Image URL'), src],                 'main', true)
  			.append([this.rte.i18n('Title'),     this.src.main.title], 'main', true)
  			.append([this.rte.i18n('Alt text'),  this.src.main.alt],   'main', true)
  			.append([this.rte.i18n('Size'), $('<span />').append(this.src.main.width).append(' x ').append(this.src.main.height).append(' px')], 'main', true)
  			.append([this.rte.i18n('Alignment'), this.src.main.align],  'main', true)
  			.append([this.rte.i18n('Margins'),   this.src.main.margin], 'main', true)
  			.append([this.rte.i18n('Border'),    this.src.main.border], 'main', true)
  		
  		dialog.append($('<fieldset><legend>'+this.rte.i18n('Preview')+'</legend></fieldset>').append(this.preview), 'main');
  		
  		
  		
  		$.each(this.src, function(tabname, elements) {
  		
  			if (tabname == 'main') {
  				return;
  			}
  			dialog.tab(tabname, rte.i18n(self.labels[tabname]));
  			
  			$.each(elements, function(name, el) {
  				self.src[tabname][name].val(tabname == 'events' ? rte.utils.trimEventCallback(self.img.attr(name)) : self.img.attr(name)||'');
  				dialog.append([rte.i18n(self.labels[name] || name), self.src[tabname][name]], tabname, true);
  			});
  		});
  		
  		dialog.open();		
  		
  		if (this.img.attr('src')) {
  			values(this.img);
  			this.prevImg = this.img.clone().prependTo(this.preview);
  			proportion   = (this.img.width()/this.img.height()).toFixed(2);
  			width        = parseInt(this.img.width());
  			height       = parseInt(this.img.height());
  		}
  	}
  		
  	this.set = function() {
  		var src = this.src.main.src.val(),
  			link;
  		
  		this.rte.history.add();
  		bookmarks && rte.selection.moveToBookmark(bookmarks);
  		
  		if (!src) {
  			link = rte.dom.selfOrParentLink(this.img[0]);
  			link && link.remove();
  			return this.img.remove();
  		}
  		
  		!this.img[0].parentNode && (this.img = $(this.rte.doc.createElement('img')));
  		
  		this.img.attr('src', src)
  			.attr('style', this.src.adv.style.val());
  		
  		$.each(this.src, function(i, elements) {
  			$.each(elements, function(name, el) {
  				var val = el.val(), style;
  				
  				switch (name) {
  					case 'width':
  						self.img.css('width', val);
  						break;
  					case 'height':
  						self.img.css('height', val);
  						break;
  					case 'align':
  						self.img.css(val == 'left' || val == 'right' ? 'float' : 'vertical-align', val);
  						break;
  					case 'margin':
  						if (val.css) {
  							self.img.css('margin', val.css);
  						} else {
  							self.img.css({
  								'margin-left'   : val.left,
  								'margin-top'    : val.top,
  								'margin-right'  : val.right,
  								'margin-bottom' : val.bottom
  							});
  						}
  						break;
  					case 'border':
  						if (!val.width) {
  							val = '';
  						} else {
  							/*
  							val = 'border:'+val.css+';'+$.trim((self.img.attr('style')||'').replace(/border\-[^;]+;?/ig, ''));
  							name = 'style';
  							self.img.attr('style', val)
  							*/
  							self.img.css('border', val.css);
  							return;
  						}
  
  						break;
  					case 'src':
  					case 'style':
  						return;
  					default:
  						val ? self.img.attr(name, val) : self.img.removeAttr(name);
  				}
  			});
  		});
  		
  		!this.img[0].parentNode && rte.selection.insertNode(this.img[0]);
  		this.rte.ui.update();
  	}
  
  	this.update = function() {
  		this.domElem.removeClass('disabled');
  		var n = this.rte.selection.getEnd(),
  			$n = $(n);
  		if (n.nodeName == 'IMG' && !$n.hasClass('elrte-protected')) {
  			this.domElem.addClass('active');
  		} else {
  			this.domElem.removeClass('active');
  		}
  	}
  	
  }
  })(jQuery);
  /**
   * @class Увеличение отступа
   * списки - если выделен один элемент - увеличивается вложенность списка, в остальных случаях - padding у родительского ul|ol
   * Если таблица выделена полностью - ей добавляется margin, если частично - увеличивается padding для ячеек
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки 
   *
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * @copyright: Studio 42, http://www.std42.ru
   **/
  (function($) {
  elRTE.prototype.ui.prototype.buttons.indent = function(rte, name) {
  	this.constructor.prototype.constructor.call(this, rte, name);
  	var self = this;
  	
  	this.command = function() {
  		this.rte.history.add();
  		var nodes = this.rte.selection.selected({collapsed : true, blocks : true, wrap : 'inline', tag : 'p'});
  
  		function indent(n) {
  			var css = /(IMG|HR|TABLE|EMBED|OBJECT)/.test(n.nodeName) ? 'margin-left' : 'padding-left';
  			var val = self.rte.dom.attr(n, 'style').indexOf(css) != -1 ? parseInt($(n).css(css))||0 : 0;
  			$(n).css(css, val+40+'px');
  		}
  		
  		for (var i=0; i < nodes.length; i++) {
  			if (/^(TABLE|THEAD|TFOOT|TBODY|COL|COLGROUP|TR)$/.test(nodes[i].nodeName)) {
  				$(nodes[i]).find('td,th').each(function() {
  					indent(this);
  				});
  			} else if (/^LI$/.test(nodes[i].nodeName)) {
  				var n = $(nodes[i]);
  				$(this.rte.dom.create(nodes[i].parentNode.nodeName))
  					.append($(this.rte.dom.create('li')).html(n.html()||'')).appendTo(n.html('&nbsp;'));
  			} else {
  				indent(nodes[i]);
  			}
  		};
  		this.rte.ui.update();
  	}
  	
  	this.update = function() {
  		this.domElem.removeClass('disabled');
  	}
  
  }
  })(jQuery);
  /**
   * @class button - justify text
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки 
   *
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * @copyright: Studio 42, http://www.std42.ru
   **/
  (function($) {
  elRTE.prototype.ui.prototype.buttons.justifyleft = function(rte, name) {
  	this.constructor.prototype.constructor.call(this, rte, name);
  	this.align = this.name == 'justifyfull' ? 'justify' : this.name.replace('justify', '');
  
  	this.command = function() {
  		var s = this.rte.selection.selected({collapsed:true, blocks : true, tag : 'div'}),
  			l = s.length;
  		l && this.rte.history.add();
  		while (l--) {
  			this.rte.dom.filter(s[l], 'textNodes') && $(s[l]).css('text-align', this.align);
  		}
  		this.rte.ui.update();
  	}
  	
  	this.update = function() {
  		var s = this.rte.selection.getNode(), 
  			n = s.nodeName == 'BODY' ? s : this.rte.dom.selfOrParent(s, 'textNodes')||(s.parentNode && s.parentNode.nodeName == 'BODY' ? s.parentNode : null);
  		if (n) {
  			this.domElem.removeClass('disabled').toggleClass('active', $(n).css('text-align') == this.align);
  		} else {
  			this.domElem.addClass('disabled');
  		}
  	}
  	
  }
  
  elRTE.prototype.ui.prototype.buttons.justifycenter = elRTE.prototype.ui.prototype.buttons.justifyleft;
  elRTE.prototype.ui.prototype.buttons.justifyright  = elRTE.prototype.ui.prototype.buttons.justifyleft;
  elRTE.prototype.ui.prototype.buttons.justifyfull   = elRTE.prototype.ui.prototype.buttons.justifyleft;
  
  })(jQuery);
  /**
   * @class button - insert/edit link (open dialog window)
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки 
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * Copyright: Studio 42, http://www.std42.ru
   **/
  (function($) {
  elRTE.prototype.ui.prototype.buttons.link = function(rte, name) {
  	this.constructor.prototype.constructor.call(this, rte, name);
  	var self = this;
  	this.img = false;
  	
  	this.bm;
  	
  	function init() {
  		self.labels = {
  			id        : 'ID',
  			'class'   : 'Css class',
  			style     : 'Css style',
  			dir       : 'Script direction',
  			lang      : 'Language',
  			charset   : 'Charset',
  			type      : 'Target MIME type',
  			rel       : 'Relationship page to target (rel)',
  			rev       : 'Relationship target to page (rev)',
  			tabindex  : 'Tab index',
  			accesskey : 'Access key'
  		}
  		self.src = {
  			main : {
  				href   : $('<input type="text" />'),
  				title  : $('<input type="text" />'),
  				anchor : $('<select />').attr('name', 'anchor'),
  				target : $('<select />')
  					.append($('<option />').text(self.rte.i18n('In this window')).val(''))
  					.append($('<option />').text(self.rte.i18n('In new window (_blank)')).val('_blank'))
  				// 	.append($('<option />').text(self.rte.i18n('In new parent window (_parent)')).val('_parent'))
  				// 	.append($('<option />').text(self.rte.i18n('In top frame (_top)')).val('_top'))
  			},
  
  			popup : {
  				use        : $('<input type="checkbox" />'),
  				url        : $('<input type="text" />'    ).val('http://'),
  				name       : $('<input type="text" />'    ),
  				width      : $('<input type="text" />'    ).attr({size : 6, title : self.rte.i18n('Width')} ).css('text-align', 'right'),
  				height     : $('<input type="text" />'    ).attr({size : 6, title : self.rte.i18n('Height')}).css('text-align', 'right'),
  				left       : $('<input type="text" />'    ).attr({size : 6, title : self.rte.i18n('Left')}  ).css('text-align', 'right'),
  				top        : $('<input type="text" />'    ).attr({size : 6, title : self.rte.i18n('Top')}   ).css('text-align', 'right'),
  				location   : $('<input type="checkbox" />'),				
  				menubar    : $('<input type="checkbox" />'),
  				toolbar    : $('<input type="checkbox" />'),
  				scrollbars : $('<input type="checkbox" />'),
  				status     : $('<input type="checkbox" />'),
  				resizable  : $('<input type="checkbox" />'),
  				dependent  : $('<input type="checkbox" />'),
  				retfalse   : $('<input type="checkbox" />').attr('checked', true)
  			},
  
  			adv : {
  				id        : $('<input type="text" />'),
  				'class'   : $('<input type="text" />'),
  				style     : $('<input type="text" />'),
  				dir       : $('<select />')
  							.append($('<option />').text(self.rte.i18n('Not set')).val(''))
  							.append($('<option />').text(self.rte.i18n('Left to right')).val('ltr'))
  							.append($('<option />').text(self.rte.i18n('Right to left')).val('rtl')),
  				lang      : $('<input type="text" />'),
  				charset   : $('<input type="text" />'),
  				type      : $('<input type="text" />'),
  				rel       : $('<input type="text" />'),
  				rev       : $('<input type="text" />'),
  				tabindex  : $('<input type="text" />'),
  				accesskey : $('<input type="text" />')
  			},
  			events : {}
  		}
  
  		$.each(
  			['onblur', 'onfocus', 'onclick', 'ondblclick', 'onmousedown', 'onmouseup', 'onmouseover', 'onmouseout', 'onmouseleave', 'onkeydown', 'onkeypress', 'onkeyup'], 
  			function() {
  				self.src.events[this] = $('<input type="text" />');
  		});
  
  		$.each(self.src, function() {
  			for (var n in this) {
  				// this[n].attr('name', n);
  				var t = this[n].attr('type');
  				if (!t || (t == 'text'  && !this[n].attr('size')) ) {
  					this[n].css('width', '100%');
  				}
  			}
  		});
  		
  	}
  	
  	this.command = function() {
  		var n = this.rte.selection.getNode(),
  			sel, i, v, opts, l, r, link, href, s;
  		
  		!this.src && init();
  		// this.rte.selection.saveIERange();
  
  		this.bm = this.rte.selection.getBookmark();
  
  		function isLink(n) { return n.nodeName == 'A' && n.href; }
  		
  		this.link = this.rte.dom.selfOrParentLink(n);
  		
  		if (!this.link) {
  			sel = $.browser.msie ? this.rte.selection.selected() : this.rte.selection.selected({wrap : false});
  			if (sel.length) {
  				for (i=0; i < sel.length; i++) {
  					if (isLink(sel[i])) {
  						this.link = sel[i];
  						break;
  					}
  				};
  				if (!this.link) {
  					this.link = this.rte.dom.parent(sel[0], isLink) || this.rte.dom.parent(sel[sel.length-1], isLink);
  				}
  			}
  		}
  		
  		this.link = this.link ? $(this.link) : $(this.rte.doc.createElement('a'));
  		this.img = n.nodeName == 'IMG' ? n : null;
  		this.updatePopup();
  		
  		this.src.main.anchor.empty();
  		$('a[href!=""][name]', this.rte.doc).each(function() {
  			var n = $(this).attr('name');
  			self.src.main.anchor.append($('<option />').val(n).text(n));
  		});
  		if (this.src.main.anchor.children().length) {
  			this.src.main.anchor.prepend($('<option />').val('').text(this.rte.i18n('Select bookmark')) )
  				.change(function() {
  					var v = $(this).val();
  					if (v) {
  						self.src.main.href.val('#'+v);
  					}
  				});
  		}
  		
  		opts = {
  			rtl : this.rte.rtl,
  			submit : function(e, d) { e.stopPropagation(); e.preventDefault(); self.set(); d.close(); },
  			tabs : { show : function(e, ui) { if (ui.index==3) { self.updateOnclick(); } } },
  			close : function() {self.rte.browser.msie && self.rte.selection.restoreIERange(); },
  			dialog : {
  				width : 'auto',
  				width : 430,
  				title : this.rte.i18n('Link')
  				
  			}
  		}
  
  		d = new elDialogForm(opts);
  
  		l = $('<div />')
  			.append( $('<label />').append(this.src.popup.location).append(this.rte.i18n('Location bar')))
  			.append( $('<label />').append(this.src.popup.menubar).append(this.rte.i18n('Menu bar')))
  			.append( $('<label />').append(this.src.popup.toolbar).append(this.rte.i18n('Toolbar')))				
  			.append( $('<label />').append(this.src.popup.scrollbars).append(this.rte.i18n('Scrollbars')));
  		r = $('<div />')
  			.append( $('<label />').append(this.src.popup.status).append(this.rte.i18n('Status bar')))
  			.append( $('<label />').append(this.src.popup.resizable).append(this.rte.i18n('Resizable')))
  			.append( $('<label />').append(this.src.popup.dependent).append(this.rte.i18n('Depedent')))				
  			.append( $('<label />').append(this.src.popup.retfalse).append(this.rte.i18n('Add return false')));
  
  		d.tab('main', this.rte.i18n('Properies'))
  			.tab('popup',  this.rte.i18n('Popup'))
  			.tab('adv',    this.rte.i18n('Advanced'))
  			.tab('events', this.rte.i18n('Events'))
  			.append($('<label />').append(this.src.popup.use).append(this.rte.i18n('Open link in popup window')), 'popup')
  			.separator('popup')
  			.append([this.rte.i18n('URL'),  this.src.popup.url],  'popup', true)
  			.append([this.rte.i18n('Window name'), this.src.popup.name], 'popup', true)
  			.append([this.rte.i18n('Window size'), $('<span />').append(this.src.popup.width).append(' x ').append(this.src.popup.height).append(' px')], 'popup', true)
  			.append([this.rte.i18n('Window position'), $('<span />').append(this.src.popup.left).append(' x ').append(this.src.popup.top).append(' px')], 'popup', true)				
  			.separator('popup')
  			.append([l, r], 'popup', true);
  
  		link = this.link.get(0);
  		href = this.rte.dom.attr(link, 'href');
  		this.src.main.href.val(href).change(function() {
  			$(this).val(self.rte.utils.absoluteURL($(this).val()));
  		});
  		
  		if (this.rte.options.fmAllow && this.rte.options.fmOpen) {
  			var s = $('<span />').append(this.src.main.href.css('width', '87%'))
  				.append(
  					$('<span />').addClass('ui-state-default ui-corner-all')
  						.css({'float' : 'right', 'margin-right' : '3px'})
  						.attr('title', self.rte.i18n('Open file manger'))
  						.append($('<span />').addClass('ui-icon ui-icon-folder-open'))
  							.click( function() {
  								self.rte.options.fmOpen( function(url) { self.src.main.href.val(url).change(); } );
  							})
  							.hover(function() {$(this).addClass('ui-state-hover')}, function() { $(this).removeClass('ui-state-hover')})
  				);
  			d.append([this.rte.i18n('Link URL'), s], 'main', true);
  		} else {
  			d.append([this.rte.i18n('Link URL'), this.src.main.href], 'main', true);
  		}
  		this.src.main.href.change();
  		
  		d.append([this.rte.i18n('Title'), this.src.main.title.val(this.rte.dom.attr(link, 'title'))], 'main', true);
  		if (this.src.main.anchor.children().length) {
  			d.append([this.rte.i18n('Bookmark'), this.src.main.anchor.val(href)], 'main', true)
  		}
  		if (!(this.rte.options.doctype.match(/xhtml/) && this.rte.options.doctype.match(/strict/))) {
  			d.append([this.rte.i18n('Target'), this.src.main.target.val(this.link.attr('target')||'')], 'main', true);
  		}
  		
  
  
  		for (var n in this.src.adv) {
  			this.src.adv[n].val(this.rte.dom.attr(link, n));
  			d.append([this.rte.i18n(this.labels[n] ? this.labels[n] : n), this.src.adv[n]], 'adv', true);
  		}
  		for (var n in this.src.events) {
  			var v = this.rte.utils.trimEventCallback(this.rte.dom.attr(link, n));
  			this.src.events[n].val(v);
  			d.append([this.rte.i18n(this.labels[n] ? this.labels[n] : n), this.src.events[n]], 'events', true);
  		}
  		
  		this.src.popup.use.change(function() {
  			var c = $(this).attr('checked');
  			$.each(self.src.popup, function() {
  				if ($(this).attr('name') != 'use') {
  					if (c) {
  						$(this).removeAttr('disabled');
  					} else {
  						$(this).attr('disabled', true);
  					}
  				}
  			})
  		});
  		this.src.popup.use.change();
  
  		d.open();
  	}
  	
  	this.update = function() {
  		var n = this.rte.selection.getNode();
  		
  		// var t = this.rte.dom.selectionHas(function(n) { return n.nodeName == 'A' && n.href; });
  		// this.rte.log(t)
  		
  		if (this.rte.dom.selfOrParentLink(n)) {
  			this.domElem.removeClass('disabled').addClass('active');
  		} else if (this.rte.dom.selectionHas(function(n) { return n.nodeName == 'A' && n.href; })) {
  			this.domElem.removeClass('disabled').addClass('active');
  		} else if (!this.rte.selection.collapsed() || n.nodeName == 'IMG') {
  			this.domElem.removeClass('disabled active');
  		} else {
  			this.domElem.addClass('disabled').removeClass('active');
  		}
  	}
  	
  	this.updatePopup = function() {
  		var onclick = ''+this.link.attr('onclick');
  		// onclick = onclick ? $.trim(onclick.toString()) : ''
  		if ( onclick.length>0 && (m = onclick.match(/window.open\('([^']+)',\s*'([^']*)',\s*'([^']*)'\s*.*\);\s*(return\s+false)?/))) {
  			this.src.popup.use.attr('checked', 'on')
  			this.src.popup.url.val(m[1]);
  			this.src.popup.name.val(m[2]);
  
  			if ( /location=yes/.test(m[3]) ) {
  				this.src.popup.location.attr('checked', true);
  			}
  			if ( /menubar=yes/.test(m[3]) ) {
  				this.src.popup.menubar.attr('checked', true);
  			}
  			if ( /toolbar=yes/.test(m[3]) ) {
  				this.src.popup.toolbar.attr('checked', true);
  			}
  			if ( /scrollbars=yes/.test(m[3]) ) {
  				this.src.popup.scrollbars.attr('checked', true);
  			}
  			if ( /status=yes/.test(m[3]) ) {
  				this.src.popup.status.attr('checked', true);
  			}
  			if ( /resizable=yes/.test(m[3]) ) {
  				this.src.popup.resizable.attr('checked', true);
  			}
  			if ( /dependent=yes/.test(m[3]) ) {
  				this.src.popup.dependent.attr('checked', true);
  			}
  			if ((_m = m[3].match(/width=([^,]+)/))) {
  				this.src.popup.width.val(_m[1]);
  			}
  			if ((_m = m[3].match(/height=([^,]+)/))) {
  				this.src.popup.height.val(_m[1]);
  			}
  			if ((_m = m[3].match(/left=([^,]+)/))) {
  				this.src.popup.left.val(_m[1]);
  			}
  			if ((_m = m[3].match(/top=([^,]+)/))) {
  				this.src.popup.top.val(_m[1]);
  			}
  			if (m[4]) {
  				this.src.popup.retfalse.attr('checked', true);
  			}
  		} else {
  			$.each(this.src.popup, function() {
  				var $this = $(this);
  				if ($this.attr('type') == 'text') {
  					$this.val($this.attr('name') == 'url' ? 'http://' : '');
  				} else {
  					if ($this.attr('name') == 'retfalse') {
  						this.attr('checked', true);
  					} else {
  						$this.removeAttr('checked');
  					}
  				}
  			});
  		}
  		
  	}
  	
  	this.updateOnclick = function () {
  		var url = this.src.popup.url.val();
  		if (this.src.popup.use.attr('checked') && url) {
  			var params = '';
  			if (this.src.popup.location.attr('checked')) {
  				params += 'location=yes,';
  			}
  			if (this.src.popup.menubar.attr('checked')) {
  				params += 'menubar=yes,';
  			}
  			if (this.src.popup.toolbar.attr('checked')) {
  				params += 'toolbar=yes,';
  			}
  			if (this.src.popup.scrollbars.attr('checked')) {
  				params += 'scrollbars=yes,';
  			}
  			if (this.src.popup.status.attr('checked')) {
  				params += 'status=yes,';
  			}
  			if (this.src.popup.resizable.attr('checked')) {
  				params += 'resizable=yes,';
  			}
  			if (this.src.popup.dependent.attr('checked')) {
  				params += 'dependent=yes,';
  			}
  			if (this.src.popup.width.val()) {
  				params += 'width='+this.src.popup.width.val()+',';
  			}
  			if (this.src.popup.height.val()) {
  				params += 'height='+this.src.popup.height.val()+',';
  			}
  			if (this.src.popup.left.val()) {
  				params += 'left='+this.src.popup.left.val()+',';
  			}
  			if (this.src.popup.top.val()) {
  				params += 'top='+this.src.popup.top.val()+',';
  			}
  			if (params.length>0) {
  				params = params.substring(0, params.length-1)
  			}
  			var retfalse = this.src.popup.retfalse.attr('checked') ? 'return false;' : '';
  			var onclick = "window.open('"+url+"', '"+$.trim(this.src.popup.name.val())+"', '"+params+"'); "+retfalse;
  			this.src.events.onclick.val(onclick);
  			if (!this.src.main.href.val()) {
  				this.src.main.href.val('#');
  			}
  		} else {
  			var v = this.src.events.onclick.val();
  			v = v.replace(/window\.open\([^\)]+\)\s*;?\s*return\s*false\s*;?/i, '');
  			this.src.events.onclick.val(v);
  		}
  	}
  	
  	this.set = function() {
  		var href, fakeURL;
  		this.updateOnclick();
  		this.rte.selection.moveToBookmark(this.bm);
  		// this.rte.selection.restoreIERange();
  		this.rte.history.add();
  		href = this.rte.utils.absoluteURL(this.src.main.href.val());
  		if (!href) {
  			// this.link.parentNode && this.rte.doc.execCommand('unlink', false, null);
  			var bm = this.rte.selection.getBookmark();
  			this.rte.dom.unwrap(this.link[0]);
  			this.rte.selection.moveToBookmark(bm);
  
  		} else {
  				if (this.img && this.img.parentNode) {
  					this.link = $(this.rte.dom.create('a')).attr('href', href);
  					this.rte.dom.wrap(this.img, this.link[0]);
  				} else if (!this.link[0].parentNode) {
  					fakeURL = '#--el-editor---'+Math.random();
  					this.rte.doc.execCommand('createLink', false, fakeURL);
  					this.link = $('a[href="'+fakeURL+'"]', this.rte.doc);
  					this.link.each(function() {
  						var $this = $(this);
  
  						// удаляем ссылки вокруг пустых элементов
  						if (!$.trim($this.html()) && !$.trim($this.text())) {
  							$this.replaceWith($this.text()); //  сохраняем пробелы :)
  						}
  					});
  				}
  
  			this.src.main.href.val(href);
  			for (var tab in this.src) {
  				if (tab != 'popup') {
  					for (var n in this.src[tab]) {
  						if (n != 'anchors') {
  							var v = $.trim(this.src[tab][n].val());
  							if (v) {
  								this.link.attr(n, v);
  							} else {
  								this.link.removeAttr(n);
  							}
  						}
  					}
  				}
  			};
  
  
  			this.img && this.rte.selection.select(this.img);
  		}
  		this.rte.ui.update(true);
  	}
  	
  }
  
  })(jQuery);
  /**
   * @class button - insert non breakable space
   * Если выделение схлопнуто и находится внутри div'a - он удаляется
   * Новые div'ы создаются только из несхлопнутого выделения
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки 
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * @copyright: Studio 42, http://www.std42.ru
   **/
  (function($) {
  elRTE.prototype.ui.prototype.buttons.nbsp = function(rte, name) {
  	this.constructor.prototype.constructor.call(this, rte, name);
  	
  	this.command = function() {
  		this.rte.history.add();
  		this.rte.selection.insertHtml('&nbsp;', true);
  		this.rte.window.focus();
  		this.rte.ui.update();
  	}
  	
  	this.update = function() {
  		this.domElem.removeClass('disabled');
  	}
  }
  })(jQuery);
  
  /**
   * @class button - outdent text
   * уменьшает padding/margin/самомнение ;)
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки 
   * @todo decrease lists nesting level!
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * @copyright: Studio 42, http://www.std42.ru
   **/
  (function($) {
  elRTE.prototype.ui.prototype.buttons.outdent = function(rte, name) {
  	this.constructor.prototype.constructor.call(this, rte, name);
  	var self = this;
  
  	this.command = function() {
  		var v = this.find();
  		if (v.node) {
  			this.rte.history.add();
  			$(v.node).css(v.type, (v.val>40 ? v.val-40 : 0)+'px');
  			this.rte.ui.update();
  		}
  	}
  	
  	this.find = function(n) {
  		function checkNode(n) {
  			var ret = {type : '', val : 0};
  			var s;
  			if ((s = self.rte.dom.attr(n, 'style'))) {
  				ret.type = s.indexOf('padding-left') != -1
  					? 'padding-left'
  					: (s.indexOf('margin-left') != -1 ? 'margin-left' : '');
  				ret.val = ret.type ? parseInt($(n).css(ret.type))||0 : 0;
  			}
  			return ret;
  		}
  		
  		var n = this.rte.selection.getNode();
  		var ret = checkNode(n);
  		if (ret.val) {
  			ret.node = n;
  		} else {
  			$.each(this.rte.dom.parents(n, '*'), function() {
  				ret = checkNode(this);
  				if (ret.val) {
  					ret.node = this;
  					return ret;
  				}
  			})
  		}
  		return ret;
  	}
  	
  	this.update = function() {
  		var v = this.find();
  		if (v.node) {
  			this.domElem.removeClass('disabled');
  		} else {
  			this.domElem.addClass('disabled');
  		}
  	}
  
  	
  }
  
  })(jQuery);
  (function($) {
  	elRTE.prototype.ui.prototype.buttons.pagebreak = function(rte, name) {
  		this.constructor.prototype.constructor.call(this, rte, name);
  		
  		// prevent resize
  		$(this.rte.doc.body).bind('mousedown', function(e) {
  			if ($(e.target).hasClass('elrte-pagebreak')) {
  				e.preventDefault();
  			}
  		})
  		
  		this.command = function() {
  			this.rte.selection.insertHtml('<img src="'+this.rte.filter.url+'pixel.gif" class="elrte-protected elrte-pagebreak"/>', false);
  		}
  		
  		this.update = function() {
  			this.domElem.removeClass('disabled');
  		}
  	}
  	
  })(jQuery);/**
   * @class button - insert formatted text (open dialog window)
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки 
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * @copyright: Studio 42, http://www.std42.ru
   **/
  (function($) {
  elRTE.prototype.ui.prototype.buttons.pasteformattext = function(rte, name) {
  	this.constructor.prototype.constructor.call(this, rte, name);
  	this.iframe = $(document.createElement('iframe')).addClass('el-rte-paste-input');
  	this.doc    = null;
  	var self    = this;
  	
  	this.command = function() {
  		this.rte.selection.saveIERange();
  		var self = this,
  			opts = {
  			submit : function(e, d) {
  				e.stopPropagation();
  				e.preventDefault();
  				self.paste();
  				d.close();
  			},
  			dialog : {
  				width : 500,
  				title : this.rte.i18n('Paste formatted text')
  			}
  		},
  		d = new elDialogForm(opts);
  		d.append(this.iframe).open();
  		this.doc = this.iframe.get(0).contentWindow.document;
  		html = this.rte.options.doctype
  			+'<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
  		html += '</head><body> <br /> </body></html>';	
  		
  		this.doc.open();
  		this.doc.write(html);
  		this.doc.close();
  
  		/* IE11対応
  		if (!this.rte.browser.msie) {
  			try { this.doc.designMode = "on"; } 
  			catch(e) { }
  		} else {
  			this.doc.body.contentEditable = true;
  		}
  		*/
  		if ('isContentEditable' in document.createElement('span')) {
  			this.doc.body.contentEditable = true;
  		} else {
  			try { this.doc.designMode = "on"; }
  			catch(e) { }
  		}		
  		
  		setTimeout(function() { self.iframe[0].contentWindow.focus(); }, 50);
  	}
  	
  	this.paste = function() {
  		$(this.doc.body).find('[class]').removeAttr('class');
  		var html = $.trim($(this.doc.body).html());
  		if (html) {
  			this.rte.history.add();
  			this.rte.selection.restoreIERange();
  			this.rte.selection.insertHtml(this.rte.filter.wysiwyg2wysiwyg(this.rte.filter.proccess('paste', html)));
  			this.rte.ui.update(true);
  		}
  	}
  
  	this.update = function() {
  		this.domElem.removeClass('disabled');
  	}
  }
  })(jQuery);
  
  /**
   * @class кнопка "вставить только текст" 
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки 
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * @copyright: Studio 42, http://www.std42.ru
   **/
  (function($) {
  elRTE.prototype.ui.prototype.buttons.pastetext = function(rte, name) {
  	this.constructor.prototype.constructor.call(this, rte, name);
  	this.input = $('<textarea />').addClass('el-rte-paste-input');
  	var self   = this;
  	
  	this.command = function() {
  		this.rte.browser.msie && this.rte.selection.saveIERange();
  		var opts = {
  			submit : function(e, d) {
  				e.stopPropagation();
  				e.preventDefault();
  				self.paste();
  				d.close();
  			},
  			dialog : {
  				width : 500,
  				title : this.rte.i18n('Paste only text')
  			}
  		}
  		var d = new elDialogForm(opts);
  		d.append(this.input).open();
  	}
  	
  	this.paste = function() {
  		var txt = $.trim(this.input.val());
  		if (txt) {
  			this.rte.history.add();
  			this.rte.browser.msie && this.rte.selection.restoreIERange();
  			this.rte.selection.insertText(txt.replace(/\r?\n/g, '<br />'), true);
  			this.rte.ui.update(true);
  		}
  		this.input.val('');
  	}
  
  	this.update = function() {
  		this.domElem.removeClass('disabled');
  	}
  	
  }
  })(jQuery);
  /**
   * @class button - save editor content (submit form)
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки 
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * @copyright: Studio 42, http://www.std42.ru
   **/
  (function($) {
  elRTE.prototype.ui.prototype.buttons.save = function(rte, name) {
  	this.constructor.prototype.constructor.call(this, rte, name);
  	this.active = true;
  	
  	this.command = function() {
  		this.rte.save();
  	}
  	
  	this.update = function() { }
  }
  })(jQuery);
  /**
   * @class button - insert smiley (open dialog window)
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки
   *
   * @author:    eSabbath
   *
   **/
  (function($) { 
  elRTE.prototype.ui.prototype.buttons.smiley = function(rte, name) {
  	this.constructor.prototype.constructor.call(this, rte, name);
  	var self = this;
  	this.img = null;
  	this.url = this.rte.filter.url+'smileys/';
  	this.smileys = {
  		'smile' : 'smile.png',
  		'happy' : 'happy.png',
  		'tongue' : 'tongue.png',
  		'surprised' : 'surprised.png',
  		'waii' : 'waii.png',
  		'wink' : 'wink.png',
  		'evilgrin' : 'evilgrin.png',
  		'grin' : 'grin.png',
  		
  		
  		'unhappy' : 'unhappy.png'
  	};
  	this.width = 120;
  
  	this.command = function() {
  		var self = this, url = this.url, d, opts, img;
  
  		this.rte.browser.msie && this.rte.selection.saveIERange();
  
  		opts = {
  			dialog : {
  				height  : 120,
  				width   : this.width,
  				title   : this.rte.i18n('Smiley'),
  				buttons : {}
  			}
  		}
  		d = new elDialogForm(opts);
  		$.each(this.smileys, function(name, img) {
  			d.append($('<img src="'+url+img+'" title="'+name+'" id="'+name+'" class="el-rte-smiley"/>').click(function() { self.set(this.id, d);	}));
  		});
  		d.open();
  	}
  
  	this.update = function() {
  		this.domElem.removeClass('disabled');
  		this.domElem.removeClass('active');
  	}
  
  	this.set = function(s, d) {
  		this.rte.browser.msie && this.rte.selection.restoreIERange();
  		if (this.smileys[s]) {
  			this.img = $(this.rte.doc.createElement('img'));
  			this.img.attr({
  				src    : this.url + this.smileys[s],
  				title  : s,
  				alt    : s
  			});
  			this.rte.selection.insertNode(this.img.get(0));
  			this.rte.ui.update();
  		}
  		d.close();
  	}
  }
  })(jQuery);
  /**
   * @class button - stops elements floating. Insert div with style="clear:all"
   * Если выделение схлопнуто и находится внутри div'a с аттрибутом или css clear - он удаляется
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки 
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * @copyright: Studio 42, http://www.std42.ru
   **/
  (function($) {
  elRTE.prototype.ui.prototype.buttons.stopfloat = function(rte, name) {
  	this.constructor.prototype.constructor.call(this, rte, name);
  
  	this.find = function() {
  		if (this.rte.selection.collapsed()) {
  			var n = this.rte.dom.selfOrParent(this.rte.selection.getEnd(), /^DIV$/);
  			if (n && (this.rte.dom.attr(n, 'clear') || $(n).css('clear') != 'none')) {
  				return n;
  			}
  		}
  	}
  	
  	this.command = function() {
  		var n;
  		if ((n = this.find())) {
  			var n = $(n);
  			this.rte.history.add();
  			if (!n.children().length && !$.trim(n.text()).length) {
  				n.remove();
  			} else {
  				n.removeAttr('clear').css('clear', '');
  			}
  		} else {
  			this.rte.history.add();
  			this.rte.selection.insertNode($(this.rte.dom.create('div')).css('clear', 'both').get(0), true);
  		}
  		this.rte.ui.update(true);
  	}
  	
  	this.update = function() {
  		this.domElem.removeClass('disabled');
  		if (this.find()) {
  			this.domElem.addClass('active');
  		} else {
  			this.domElem.removeClass('active');
  		}
  	}
  }
  
  })(jQuery);/**
   * @class button - create/edit table (open dialog window)
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки 
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * Copyright: Studio 42, http://www.std42.ru
   **/
  (function($) {
  elRTE.prototype.ui.prototype.buttons.table = function(rte, name) {
  	this.constructor.prototype.constructor.call(this, rte, name);
  	var self    = this;
  	this.src    = null;
  	this.labels = null;
  	
  	function init() {
  		self.labels = {
  			main      : 'Properies',
  			adv       : 'Advanced',
  			events    : 'Events',
  			id        : 'ID',
  			'class'   : 'Css class',
  			style     : 'Css style',
  			dir       : 'Script direction',
  			summary   : 'Summary',
  			lang      : 'Language',
  			href      : 'URL'
  		}
  		
  		self.src = {
  			main : {
  				caption : $('<input type="text" />'),
  				rows    : $('<input type="text" />').attr('size', 5).val(2),
  				cols    : $('<input type="text" />').attr('size', 5).val(2),
  				width   : $('<input type="text" />').attr('size', 5),
  				wunit   : $('<select />')
  							.append($('<option />').val('%').text('%'))
  							.append($('<option />').val('px').text('px')),				
  				height  : $('<input type="text" />').attr('size', 5),	
  				hunit   : $('<select />')
  							.append($('<option />').val('%').text('%'))
  							.append($('<option />').val('px').text('px')),	
  				align   : $('<select />')
  							.append($('<option />').val('').text(self.rte.i18n('Not set')))
  							.append($('<option />').val('left').text(self.rte.i18n('Left')))
  							.append($('<option />').val('center').text(self.rte.i18n('Center')))	
  							.append($('<option />').val('right').text(self.rte.i18n('Right'))),	
  				spacing : $('<input type="text" />').attr('size', 5),	
  				padding : $('<input type="text" />').attr('size', 5),
  				border  : $('<div />'),
  				// frame   : $('<select />')
  				// 			.append($('<option />').val('void').text(self.rte.i18n('No')))
  				// 			.append($('<option />').val('border').text(self.rte.i18n('Yes'))),
  				rules   : $('<select />')
  							.append($('<option />').val('none').text(self.rte.i18n('No')))
  							.append($('<option />').val('all').text(self.rte.i18n('Cells')))
  							.append($('<option />').val('groups').text(self.rte.i18n('Groups')))
  							.append($('<option />').val('rows').text(self.rte.i18n('Rows')))
  							.append($('<option />').val('cols').text(self.rte.i18n('Columns'))),
  				margin  : $('<div />'),
  				bg      : $('<div />'),
  				bgimg   : $('<input type="text" />').css('width', '90%')
  			},
  			
  			adv : {
  				id        : $('<input type="text" />'),
  				summary   : $('<input type="text" />'),
  				'class'   : $('<input type="text" />'),
  				style     : $('<input type="text" />'),
  				dir       : $('<select />')
  								.append($('<option />').text(self.rte.i18n('Not set')).val(''))
  								.append($('<option />').text(self.rte.i18n('Left to right')).val('ltr'))
  								.append($('<option />').text(self.rte.i18n('Right to left')).val('rtl')),
  				lang      : $('<input type="text" />')
  			},
  			
  			events : {}
  		}
  		
  		$.each(self.src, function() {
  			for (var n in this) {
  				this[n].attr('name', n);
  				var t = this[n].get(0).nodeName; 
  				if (t == 'INPUT' && n != 'bgimg') {
  					this[n].css(this[n].attr('size') ? {'text-align' : 'right'} : {width : '100%'});
  				} else if (t == 'SELECT' && n!='wunit' && n!='hunit') {
  					this[n].css('width', '100%');
  				}
  			}
  		});
  		
  		$.each(
  			['onblur', 'onfocus', 'onclick', 'ondblclick', 'onmousedown', 'onmouseup', 'onmouseover', 'onmouseout', 'onmouseleave', 'onkeydown', 'onkeypress', 'onkeyup'], 
  			function() {
  				self.src.events[this] = $('<input type="text" />').css('width', '100%');
  		});
  		
  		self.src.main.align.change(function() {
  			var v = $(this).val();
  			if (v == 'center') {
  				self.src.main.margin.val({left : 'auto', right : 'auto'});
  			} else {
  				var m = self.src.main.margin.val();
  				if (m.left == 'auto' && m.right == 'auto') {
  					self.src.main.margin.val({left : '', right : ''});
  				}
  			}
  		});
  		
  		self.src.main.bgimg.change(function() {
  			var t = $(this);
  			t.val(self.rte.utils.absoluteURL(t.val()));
  		})
  		
  	}
  	
  	this.command = function() {
  		var n = this.rte.dom.selfOrParent(this.rte.selection.getNode(), /^TABLE$/);
  		
  		if (this.name == 'table') {
  			this.table = $(this.rte.doc.createElement('table'));	
  		} else {
  			this.table = n ? $(n) : $(this.rte.doc.createElement('table'));					
  		}
  		
  		!this.src && init();
  		this.src.main.border.elBorderSelect({styleHeight : 117});
  		this.src.main.bg.elColorPicker({palettePosition : 'outer', 'class' : 'el-colorpicker ui-icon ui-icon-pencil'});
  		this.src.main.margin.elPaddingInput({ type : 'margin', value : this.table});
  		
  		if (this.table.parents().length) {
  			this.src.main.rows.val('').attr('disabled', true);
  			this.src.main.cols.val('').attr('disabled', true);
  		} else {
  			this.src.main.rows.val(2).removeAttr('disabled');
  			this.src.main.cols.val(2).removeAttr('disabled');
  		}
  		
  		var w = this.table.css('width') || this.table.attr('width');
  		this.src.main.width.val(parseInt(w)||'');
  		this.src.main.wunit.val(w.indexOf('px') != -1 ? 'px' : '%');
  		
  		var h = this.table.css('height') || this.table.attr('height');	
  		this.src.main.height.val(parseInt(h)||'');
  		this.src.main.hunit.val(h && h.indexOf('px') != -1 ? 'px' : '%');
  
  		var f = this.table.css('float');
  		this.src.main.align.val('');
  		if (f == 'left' || f == 'right') {
  			this.src.main.align.val(f);
  		} else {
  			var ml = this.table.css('margin-left');
  			var mr = this.table.css('margin-right');
  			if (ml == 'auto' && mr == 'auto') {
  				this.src.main.align.val('center');
  			}
  		}
  
  		this.src.main.border.val(this.table);
  		//this.src.main.frame.val(this.table.attr('frame'));
  		this.src.main.rules.val(this.rte.dom.attr(this.table.get(0), 'rules'));
  
  		this.src.main.bg.val(this.table.css('background-color'));
  		var bgimg = (this.table.css('background-image')||'').replace(/url\(([^\)]+)\)/i, "$1");
  		this.src.main.bgimg.val(bgimg!='none' ? bgimg : '');
  
  		var opts = {
  			rtl : this.rte.rtl,
  			submit : function(e, d) { e.stopPropagation(); e.preventDefault(); self.set(); d.close(); },
  			dialog : {
  				width : 530,
  				title : this.rte.i18n('Table')
  			}
  		}
  		var d = new elDialogForm(opts);
  		
  		for (var tab in this.src) {
  			d.tab(tab, this.rte.i18n(this.labels[tab]));
  			if (tab == 'main') {
  				var t1 = $('<table />')
  					.append($('<tr />').append('<td>'+this.rte.i18n('Rows')+'</td>').append($('<td />').append(this.src.main.rows)))
  					.append($('<tr />').append('<td>'+this.rte.i18n('Columns')+'</td>').append($('<td />').append(this.src.main.cols)));
  				var t2 = $('<table />')
  					.append($('<tr />').append('<td>'+this.rte.i18n('Width')+'</td>').append($('<td />').append(this.src.main.width).append(this.src.main.wunit)))
  					.append($('<tr />').append('<td>'+this.rte.i18n('Height')+'</td>').append($('<td />').append(this.src.main.height).append(this.src.main.hunit)));
  				var t3 = $('<table />')
  					.append($('<tr />').append('<td>'+this.rte.i18n('Spacing')+'</td>').append($('<td />').append(this.src.main.spacing.val(this.table.attr('cellspacing')||''))))
  					.append($('<tr />').append('<td>'+this.rte.i18n('Padding')+'</td>').append($('<td />').append(this.src.main.padding.val(this.table.attr('cellpadding')||''))));
  				
  				d.append([this.rte.i18n('Caption'), this.src.main.caption.val(this.table.find('caption').eq(0).text() || '')], 'main', true)
  					.separator('main')
  					.append([t1, t2, t3], 'main', true)
  					.separator('main')
  					.append([this.rte.i18n('Border'),        this.src.main.border], 'main', true)
  					//.append([this.rte.i18n('Frame'),       this.src.main.frame], 'main', true)
  					.append([this.rte.i18n('Inner borders'), this.src.main.rules], 'main', true)
  					.append([this.rte.i18n('Alignment'),     this.src.main.align], 'main', true)
  					.append([this.rte.i18n('Margins'),       this.src.main.margin], 'main', true)
  					.append([this.rte.i18n('Background'),    $('<span />').append($('<span />').css({'float' : 'left', 'margin-right' : '3px'}).append(this.src.main.bg)).append(this.src.main.bgimg)], 'main', true)
  			} else {
  				for (var name in this.src[tab]) {
  					var v = this.rte.dom.attr(this.table, name);
  					if (tab == 'events') {
  						v = this.rte.utils.trimEventCallback(v);
  					} 
  					d.append([this.rte.i18n(this.labels[name] ? this.labels[name] : name), this.src[tab][name].val(v)], tab, true);
  				}
  			}
  		}
  		
  		d.open();
  	}
  	
  	this.set = function() {
  		
  		if (!this.table.parents().length) {
  			var r = parseInt(this.src.main.rows.val()) || 0;
  			var c = parseInt(this.src.main.cols.val()) || 0;
  			if (r<=0 || c<=0) {
  				return;
  			}
  			this.rte.history.add(); 
  			var b = $(this.rte.doc.createElement('tbody')).appendTo(this.table);
  			
  			for (var i=0; i < r; i++) {
  				var tr = '<tr>';
  				for (var j=0; j < c; j++) {
  					tr += '<td>&nbsp;</td>';
  				}
  				b.append(tr+'</tr>');
  			};
  			
  			// var tr = $(this.rte.doc.createElement('tr'));
  			// 
  			// for (var i=0; i < c; i++) {
  			// 	tr.append($(this.rte.doc.createElement('td')).html('&nbsp;'));
  			// };
  			// 
  			// for (var i=0; i<r; i++) {
  			// 	b.append(tr.clone(true));
  			// };
  			
  			// this.rte.selection.insertNode(this.table.get(0), true);
  		} else {
  			this.table
  				.removeAttr('width')
  				.removeAttr('height')
  				.removeAttr('border')
  				.removeAttr('align')
  				.removeAttr('bordercolor')
  				.removeAttr('bgcolor')
  				.removeAttr('cellspacing')
  				.removeAttr('cellpadding')
  				.removeAttr('frame')
  				.removeAttr('rules')
  				.removeAttr('style');
  		}
  		
  		var cap = $.trim(this.src.main.caption.val());
  		if (cap) {
  			if (!this.table.children('caption').length) {
  				this.table.prepend('<caption />' );
  			}
  			this.table.children('caption').text(cap);
  		} else {
  			this.table.children('caption').remove();
  		}
  		
  		
  		for (var tab in this.src) {
  			if (tab != 'main') {
  				for (var n in this.src[tab]) {
  					var v = $.trim(this.src[tab][n].val());
  					if (v) {
  						this.table.attr(n, v);
  					} else {
  						this.table.removeAttr(n);
  					}
  				}
  			}
  		}
  		var spacing, padding, rules;
  
  		if ((spacing = parseInt(this.src.main.spacing.val())) && spacing>=0) {
  			this.table.attr('cellspacing', spacing);
  		} 
  
  		if ((padding = parseInt(this.src.main.padding.val())) && padding>=0) {
  			this.table.attr('cellpadding', padding);
  		} 
  		
  		if ((rules = this.src.main.rules.val())) {
  			this.table.attr('rules', rules);
  		}
  		
  		var
  			w = parseInt(this.src.main.width.val()) || '',
  			h = parseInt(this.src.main.height.val()) || '',
  			i = $.trim(this.src.main.bgimg.val()),
  			b = this.src.main.border.val(),
  			m = this.src.main.margin.val(),
  			f = this.src.main.align.val();
  		this.table.css({
  			width              : w ? w+this.src.main.wunit.val() : '',
  			height             : h ? h+this.src.main.hunit.val() : '',
  			border             : $.trim(b.width+' '+b.style+' '+b.color),
  			'background-color' : this.src.main.bg.val(),
  			'background-image' : i ? 'url('+i+')' : ''
  		});	
  		if (m.css) {
  			this.table.css('margin', m.css);
  		} else {
  			this.table.css({
  				'margin-top'    : m.top,
  				'margin-right'  : m.right,
  				'margin-bottom' : m.bottom,
  				'margin-left'   : m.left
  			});
  		}
  		if ((f=='left' || f=='right') && this.table.css('margin-left')!='auto'  && this.table.css('margin-right')!='auto') {
  			this.table.css('float', f);
  		}
  		if (!this.table.attr('style')) {
  			this.table.removeAttr('style');
  		}
  		if (!this.table.parents().length) {
  			this.rte.selection.insertNode(this.table.get(0), true);
  		}
  		this.rte.ui.update();
  	}
  	
  	this.update = function() {
  		this.domElem.removeClass('disabled');
  		if (this.name == 'tableprops' && !this.rte.dom.selfOrParent(this.rte.selection.getNode(), /^TABLE$/)) {
  			this.domElem.addClass('disabled').removeClass('active');
  		}
  	}
  	
  }
  
  elRTE.prototype.ui.prototype.buttons.tableprops = elRTE.prototype.ui.prototype.buttons.table;
  
  })(jQuery);
  
  /**
   * @class button - remove table
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки 
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * @copyright: Studio 42, http://www.std42.ru
   **/
  (function($) {
  elRTE.prototype.ui.prototype.buttons.tablerm = function(rte, name) {
  	this.constructor.prototype.constructor.call(this, rte, name);
  	
  	this.command = function() {
  		var t = this.rte.dom.parent(this.rte.selection.getNode(), /^TABLE$/);
  		// t && $(t).remove();
  		if (t) {
  			this.rte.history.add();
  			$(t).remove();
  		}
  		this.rte.ui.update(true);
  	}
  	
  	this.update = function() {
  		if (this.rte.dom.parent(this.rte.selection.getNode(), /^TABLE$/)) {
  			this.domElem.removeClass('disabled');
  		} else {
  			this.domElem.addClass('disabled');
  		}
  	}
  }
  })(jQuery);
  /**
   * @class button - table cell properties
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки 
   *
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * @copyright: Studio 42, http://www.std42.ru
   **/
  (function($) {
  elRTE.prototype.ui.prototype.buttons.tbcellprops = function(rte, name) {
  	this.constructor.prototype.constructor.call(this, rte, name);
  	var self = this;
  	this.src = null;
  	this.labels = null;
  	
  	function init() {
  		self.labels = {
  			main    : 'Properies',
  			adv     : 'Advanced',
  			events  : 'Events',
  			id      : 'ID',
  			'class' : 'Css class',
  			style   : 'Css style',
  			dir     : 'Script direction',
  			lang    : 'Language'
  		}
  		
  		self.src = {
  			main : {
  				type    : $('<select />').css('width', '100%')
  							.append($('<option />').val('td').text(self.rte.i18n('Data')))
  							.append($('<option />').val('th').text(self.rte.i18n('Header'))),
  				width   : $('<input type="text" />').attr('size', 4),
  				wunit   : $('<select />')
  							.append($('<option />').val('%').text('%'))
  							.append($('<option />').val('px').text('px')),				
  				height  : $('<input type="text" />').attr('size', 4),	
  				hunit   : $('<select />')
  							.append($('<option />').val('%').text('%'))
  							.append($('<option />').val('px').text('px')),	
  				align   : $('<select />').css('width', '100%')
  							.append($('<option />').val('').text(self.rte.i18n('Not set')))
  							.append($('<option />').val('left').text(self.rte.i18n('Left')))
  							.append($('<option />').val('center').text(self.rte.i18n('Center')))	
  							.append($('<option />').val('right').text(self.rte.i18n('Right')))
  							.append($('<option />').val('justify').text(self.rte.i18n('Justify'))),	
  				border  : $('<div />'),
  				padding  : $('<div />'),
  				bg      : $('<div />'),
  				bgimg   : $('<input type="text" />').css('width', '90%'),
  				apply   : $('<select />').css('width', '100%')
  							.append($('<option />').val('').text(self.rte.i18n('Current cell')))
  							.append($('<option />').val('row').text(self.rte.i18n('All cells in row')))
  							.append($('<option />').val('column').text(self.rte.i18n('All cells in column')))	
  							.append($('<option />').val('table').text(self.rte.i18n('All cells in table')))
  			},
  			
  			adv : {
  				id        : $('<input type="text" />'),
  				'class'   : $('<input type="text" />'),
  				style     : $('<input type="text" />'),
  				dir       : $('<select />').css('width', '100%')
  								.append($('<option />').text(self.rte.i18n('Not set')).val(''))
  								.append($('<option />').text(self.rte.i18n('Left to right')).val('ltr'))
  								.append($('<option />').text(self.rte.i18n('Right to left')).val('rtl')),
  				lang      : $('<input type="text" />')
  			},
  			
  			events : {}
  		}
  		
  		$.each(self.src, function() {
  			for (var n in this) {
  				this[n].attr('name', n);
  				if (this[n].attr('type') == 'text' && !this[n].attr('size') && n!='bgimg') {
  					this[n].css('width', '100%')
  				}
  			}
  		});
  		
  		$.each(
  			['onblur', 'onfocus', 'onclick', 'ondblclick', 'onmousedown', 'onmouseup', 'onmouseover', 'onmouseout', 'onmouseleave', 'onkeydown', 'onkeypress', 'onkeyup'], 
  			function() {
  				self.src.events[this] = $('<input type="text" />').css('width', '100%');
  		});
  		
  	}
  	
  	this.command = function() {
  		!this.src && init();
  		this.cell = this.rte.dom.selfOrParent(this.rte.selection.getNode(), /^(TD|TH)$/);
  		if (!this.cell) {
  			return;
  		}
  		this.src.main.type.val(this.cell.nodeName.toLowerCase());
  		this.cell = $(this.cell);
  		this.src.main.border.elBorderSelect({styleHeight : 117, value : this.cell});
  		this.src.main.bg.elColorPicker({palettePosition : 'outer', 'class' : 'el-colorpicker ui-icon ui-icon-pencil'});
  		this.src.main.padding.elPaddingInput({ value : this.cell});
  		
  		var w = this.cell.css('width') || this.cell.attr('width');
  		this.src.main.width.val(parseInt(w)||'');
  		this.src.main.wunit.val(w.indexOf('px') != -1 ? 'px' : '%');
  		
  		var h = this.cell.css('height') || this.cell.attr('height');	
  		this.src.main.height.val(parseInt(h)||'');
  		this.src.main.hunit.val(h.indexOf('px') != -1 ? 'px' : '%');
  		
  		this.src.main.align.val(this.cell.attr('align') || this.cell.css('text-align'));
  		this.src.main.bg.val(this.cell.css('background-color'));
  		var bgimg = this.cell.css('background-image');
  		this.src.main.bgimg.val(bgimg && bgimg!='none' ? bgimg.replace(/url\(([^\)]+)\)/i, "$1") : '');
  		this.src.main.apply.val('');
  		
  		var opts = {
  			rtl : this.rte.rtl,
  			submit : function(e, d) { e.stopPropagation(); e.preventDefault(); self.set(); d.close(); },
  			dialog : {
  				width : 520,
  				title : this.rte.i18n('Table cell properties')
  			}
  		}
  		var d = new elDialogForm(opts);
  		for (var tab in this.src) {
  			d.tab(tab, this.rte.i18n(this.labels[tab]));
  			
  			if (tab == 'main') {
  				d.append([this.rte.i18n('Width'),              $('<span />').append(this.src.main.width).append(this.src.main.wunit)],  'main', true)
  					.append([this.rte.i18n('Height'),          $('<span />').append(this.src.main.height).append(this.src.main.hunit)], 'main', true)
  					.append([this.rte.i18n('Table cell type'), this.src.main.type],    'main', true)
  					.append([this.rte.i18n('Border'),          this.src.main.border],  'main', true)
  					.append([this.rte.i18n('Alignment'),       this.src.main.align],   'main', true)
  					.append([this.rte.i18n('Paddings'),        this.src.main.padding], 'main', true)
  					.append([this.rte.i18n('Background'),      $('<span />').append($('<span />').css({'float' : 'left', 'margin-right' : '3px'}).append(this.src.main.bg)).append(this.src.main.bgimg)],  'main', true)
  					.append([this.rte.i18n('Apply to'),        this.src.main.apply],   'main', true);
  			} else {
  				for (var name in this.src[tab]) {
  					var v = this.cell.attr(name) || '';
  					if (tab == 'events') {
  						v = this.rte.utils.trimEventCallback(v);
  					} 
  					d.append([this.rte.i18n(this.labels[name] ? this.labels[name] : name), this.src[tab][name].val(v)], tab, true);
  				}
  			}
  		}
  		d.open()
  	}
  	
  	this.set = function() {
  		// $(t).remove();
  		var target = this.cell,
  			apply  = this.src.main.apply.val();
  		switch (this.src.main.apply.val()) {
  			case 'row':
  				target = this.cell.parent('tr').children('td,th');
  				break;
  				
  			case 'column':
  				target = $(this.rte.dom.tableColumn(this.cell.get(0)));
  				break;
  				
  			case 'table':
  				target = this.cell.parents('table').find('td,th');
  				break;
  		}
  
  		for (var tab in this.src) {
  			if (tab != 'main') {
  				for (var n in this.src[tab]) {
  					var v = $.trim(this.src[tab][n].val());
  					if (v) {
  						target.attr(n, v);
  					} else {
  						target.removeAttr(n);
  					}
  				}
  			}
  		}
  		
  		target.removeAttr('width')
  			.removeAttr('height')
  			.removeAttr('border')
  			.removeAttr('align')
  			.removeAttr('bordercolor')
  			.removeAttr('bgcolor');
  			
  		var t = this.src.main.type.val();
  		var w = parseInt(this.src.main.width.val()) || '';
  		var h = parseInt(this.src.main.height.val()) || '';
  		var i = $.trim(this.src.main.bgimg.val());
  		var b = this.src.main.border.val();
  		var css = {
  			'width'            : w ? w+this.src.main.wunit.val() : '',
  			'height'           : h ? h+this.src.main.hunit.val() : '',
  			'background-color' : this.src.main.bg.val(),
  			'background-image' : i ? 'url('+i+')' : '',
  			'border'           : $.trim(b.width+' '+b.style+' '+b.color),
  			'text-align'       : this.src.main.align.val() || ''
  		};
  		var p = this.src.main.padding.val();
  		if (p.css) {
  			css.padding = p.css;
  		} else {
  			css['padding-top']    = p.top;
  			css['padding-right']  = p.right;
  			css['padding-bottom'] = p.bottom;
  			css['padding-left']   = p.left;
  		}
  		
  		target = target.get();
  
  		$.each(target, function() {
  			var type = this.nodeName.toLowerCase();
  			var $this = $(this);
  			if (type != t) {
  				
  				var attr = {}
  				for (var i in self.src.adv) {
  					var v = $this.attr(i)
  					if (v) {
  						attr[i] = v.toString();
  					}
  				}
  				for (var i in self.src.events) {
  					var v = $this.attr(i)
  					if (v) {
  						attr[i] = v.toString();
  					}
  				}
  				var colspan = $this.attr('colspan')||1;
  				var rowspan = $this.attr('rowspan')||1;
  				if (colspan>1) {
  					attr.colspan = colspan;
  				}
  				if (rowspan>1) {
  					attr.rowspan = rowspan;
  				}
  				
  				$this.replaceWith($('<'+t+' />').html($this.html()).attr(attr).css(css) );
  				
  			} else {
  				$this.css(css);
  			}
  		});
  
  		this.rte.ui.update();
  	}
  	
  	this.update = function() {
  		if (this.rte.dom.parent(this.rte.selection.getNode(), /^TABLE$/)) {
  			this.domElem.removeClass('disabled');
  		} else {
  			this.domElem.addClass('disabled');
  		}
  	}
  	
  }
  
  })(jQuery);/**
   * @class button - table cells merge
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки 
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * @copyright: Studio 42, http://www.std42.ru
   **/
  (function($) {
  elRTE.prototype.ui.prototype.buttons.tbcellsmerge = function(rte, name) {
  	this.constructor.prototype.constructor.call(this, rte, name);
  	var self = this;
  	
  	function selectedCells() {
  		var c1 = self.rte.dom.selfOrParent(self.rte.selection.getStart(), /^(TD|TH)$/);
  		var c2 = self.rte.dom.selfOrParent(self.rte.selection.getEnd(), /^(TD|TH)$/);		
  		if (c1 && c2 && c1!=c2 && $(c1).parents('table').get(0) == $(c2).parents('table').get(0)) {
  			return [c1, c2];
  		}
  		return null;
  	}
  	
  	this.command = function() {
  		var cells = selectedCells();
  
  		if (cells) {
  			
  			var _s  = this.rte.dom.indexOf($(cells[0]).parent('tr').get(0));
  			var _e  = this.rte.dom.indexOf($(cells[1]).parent('tr').get(0));
  			var ro  = Math.min(_s, _e); // row offset
  			var rl  = Math.max(_s, _e) - ro + 1; // row length
  			var _c1 = this.rte.dom.tableColumn(cells[0], true, true); 
  			var _c2 = this.rte.dom.tableColumn(cells[1], true);
  			var _i1 = $.inArray(cells[0], _c1.column); 
  			var _i2 = $.inArray(cells[1], _c2.column);
  			
  			var colBegin = _c1.info.offset[_i1] < _c2.info.offset[_i2]  ? _c1 : _c2;
  			var colEnd   = _c1.info.offset[_i1] >= _c2.info.offset[_i2] ? _c1 : _c2;
  			var length   = 0;
  			var target   = null;
  			var html     = '';
  			this.rte.history.add();
  			var rows = $($(cells[0]).parents('table').eq(0).find('tr').get().slice(ro, ro+rl))
  				.each( function(i) {
  					var _l = html.length;
  					var accept = false;
  					$(this).children('td,th').each(function() {
  						var $this   = $(this);
  						var inBegin = $.inArray(this, colBegin.column);
  						var inEnd   = $.inArray(this, colEnd.column);
  						
  						if (inBegin!=-1 || inEnd!=-1) {
  							accept = inBegin!=-1 && inEnd==-1;
  							var len = parseInt($this.attr('colspan')||1)
  							if (i == 0) {
  								length += len;
  							}
  							
  							if (inBegin!=-1 && i>0) {
  								var delta = colBegin.info.delta[inBegin];
  								if (delta>0) {
  									if ($this.css('text-align') == 'left') {
  										var cell = $this.clone(true);
  										$this.html('&nbsp;');
  									} else {
  										var cell = $this.clone().html('&nbsp;');
  									}
  									cell.removeAttr('colspan').removeAttr('id').insertBefore(this);
  									if (delta>1) {
  										cell.attr('colspan', delta);
  									}
  								}
  							}
  							
  							if (inEnd!=-1) {
  								var delta = colEnd.info.delta[inEnd];
  								if (len-delta>1) {
  									var cp = len-delta-1;
  									if ($this.css('text-align') == 'right') {
  										var cell = $this.clone(true);
  										$this.html('&nbsp;');
  									} else {
  										var cell = $this.clone().html('&nbsp;');
  									}
  									cell.removeAttr('colspan').removeAttr('id').insertAfter(this);
  									if (cp>1) {
  										cell.attr('colspan', cp);
  									}
  								}
  							}
  							if (!target) {
  								target = $this;
  							} else {
  								html += $this.html();
  								$this.remove();
  							}
  						} else if (accept) {
  							if (i == 0) {
  								length += parseInt($this.attr('colspan')||1);
  							}
  							html += $this.html();
  							$this.remove();
  							
  
  						}
  					})
  					html += _l!=html.length ? '<br />' : '';
  				});
  
  			target.removeAttr('colspan').removeAttr('rowspan').html(target.html()+html)
  			if (length>1) {
  				target.attr('colspan', length);
  			}
  			if (rl>1) {
  				target.attr('rowspan', rl);
  			}
  			// sometimes when merge cells with different rowspans we get "lost" cells in rows 
  			// this add cells if needed
  			this.rte.dom.fixTable($(cells[0]).parents('table').get(0));
  		}
  	}
  	
  	this.update = function() {
  		if (selectedCells()) {
  			this.domElem.removeClass('disabled');
  		} else {
  			this.domElem.addClass('disabled');
  		}
  	}
  }
  })(jQuery);
  /**
   * @class button - split merged cell
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки
   * @todo split not merged cell 
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * @copyright: Studio 42, http://www.std42.ru 
   **/
  (function($) {
  elRTE.prototype.ui.prototype.buttons.tbcellsplit = function(rte, name) {
  	this.constructor.prototype.constructor.call(this, rte, name);
  	
  	this.command = function() {
  		var n = this.rte.dom.selfOrParent(this.rte.selection.getNode(), /^(TD|TH)$/);
  		if (n) {
  			this.rte.history.add();
  			var colspan = parseInt(this.rte.dom.attr(n, 'colspan'));
  			var rowspan = parseInt(this.rte.dom.attr(n, 'rowspan'));
  			if (colspan>1 || rowspan>1) {
  				var cnum = colspan-1;
  				var rnum = rowspan-1;
  				var tb   = this.rte.dom.parent(n, /^TABLE$/);
  				var tbm  = this.rte.dom.tableMatrix(tb);
  				
  				// ячейки в текущем ряду
  				if (cnum) {
  					for (var i=0; i<cnum; i++) {
  						$(this.rte.dom.create(n.nodeName)).html('&nbsp;').insertAfter(n);
  					}
  				}
  				if (rnum) {
  					var ndx  = this.rte.dom.indexesOfCell(n, tbm)
  					var rndx = ndx[0];
  					var cndx = ndx[1];
  					// ячейки в следущих рядах
  					for (var r=rndx+1; r < rndx+rnum+1; r++) {
  						var cell;
  						
  						if (!tbm[r][cndx].nodeName) {
  							if (tbm[r][cndx-1].nodeName) {
  								cell = tbm[r][cndx-1];
  							} else {
  								for (var i=cndx-1; i>=0; i--) {
  									if (tbm[r][i].nodeName) {
  										cell =tbm[r][i];
  										break;
  									}
  								}
  							}
  							if (cell) {
  								for (var i=0; i<= cnum; i++) {
  									$(this.rte.dom.create(cell.nodeName)).html('&nbsp;').insertAfter(cell);
  								}
  							}
  						}
  					};
  				}
  				$(n).removeAttr('colspan').removeAttr('rowspan');
  				this.rte.dom.fixTable(tb);
  			}
  		}
  		this.rte.ui.update(true);
  	}
  	
  	this.update = function() {
  		var n = this.rte.dom.selfOrParent(this.rte.selection.getNode(), /^(TD|TH)$/);
  		if (n && (parseInt(this.rte.dom.attr(n, 'colspan'))>1 || parseInt(this.rte.dom.attr(n, 'rowspan'))>1)) {
  			this.domElem.removeClass('disabled');
  		} else {
  			this.domElem.addClass('disabled');
  		}
  	}
  }
  
  })(jQuery);
  /**
   * @class button - Insert new column in table(before or after current)
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки 
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * @copyright: Studio 42, http://www.std42.ru
   **/
  (function($) {
  elRTE.prototype.ui.prototype.buttons.tbcolbefore = function(rte, name) {
  	this.constructor.prototype.constructor.call(this, rte, name);
  	var self = this;
  	
  	this.command = function() {
  		var self = this;
  		var cells = this.rte.dom.tableColumn(this.rte.selection.getNode(), false, true);
  		if (cells.length) {
  			this.rte.history.add();
  			$.each(cells, function() {
  				var $this = $(this);
  				var cp = parseInt($this.attr('colspan')||1)
  				if (cp >1) {
  					$this.attr('colspan', cp+1);
  				} else {
  					var c = $(self.rte.dom.create(this.nodeName)).html('&nbsp;');
  					if (self.name == 'tbcolbefore') {
  						c.insertBefore(this);
  					} else {
  						c.insertAfter(this);
  					}
  				}
  			});
  			this.rte.ui.update();
  		}
  	}
  	
  	this.update = function() {
  		if (this.rte.dom.selfOrParent(this.rte.selection.getNode(), /^(TD|TH)$/)) {
  			this.domElem.removeClass('disabled');
  		} else {
  			this.domElem.addClass('disabled');
  		}
  	}
  }
  
  elRTE.prototype.ui.prototype.buttons.tbcolafter = elRTE.prototype.ui.prototype.buttons.tbcolbefore;
  
  })(jQuery);
  /**
   * @class button - remove table colunm
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки 
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * @copyright: Studio 42, http://www.std42.ru
   **/
  (function($) {
  elRTE.prototype.ui.prototype.buttons.tbcolrm = function(rte, name) {
  	this.constructor.prototype.constructor.call(this, rte, name);
  	var self = this;
  	
  	this.command = function() {
  		var n     = this.rte.selection.getNode();
  		var c     = this.rte.dom.selfOrParent(n, /^(TD|TH)$/);
  		var prev  = $(c).prev('td,th').get(0);
  		var next  = $(c).next('td,th').get(0);			
  		var tb    = this.rte.dom.parent(n, /^TABLE$/);
  		var cells = this.rte.dom.tableColumn(n, false, true);
  
  		if (cells.length) {
  			this.rte.history.add();
  			$.each(cells, function() {
  				var $this = $(this);
  				var cp    = parseInt($this.attr('colspan')||1);
  				if ( cp>1 ) {
  					$this.attr('colspan', cp-1);
  				} else {
  					$this.remove();
  				}
  			});
  			this.rte.dom.fixTable(tb);
  			if (prev || next) {
  				this.rte.selection.selectContents(prev ? prev : next).collapse(true);
  			}
  			this.rte.ui.update(true);
  		}
  	}
  	
  	this.update = function() {
  		if (this.rte.dom.selfOrParent(this.rte.selection.getNode(), /^(TD|TH)$/)) {
  			this.domElem.removeClass('disabled');
  		} else {
  			this.domElem.addClass('disabled');
  		}
  	}
  }
  
  })(jQuery);
  /**
   * @class меню - Новый ряд в таблице
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки 
   **/
  elRTE.prototype.ui.prototype.buttons.tbrowbefore = function(rte, name) {
  	this.constructor.prototype.constructor.call(this, rte, name);
  	
  	this.command = function() {
  		var n  = this.rte.selection.getNode();
  		var c  = this.rte.dom.selfOrParent(n, /^(TD|TH)$/);
  		var r  = this.rte.dom.selfOrParent(c, /^TR$/);
  		var mx = this.rte.dom.tableMatrix(this.rte.dom.selfOrParent(c, /^TABLE$/));
  
  		if (c && r && mx) {
  			this.rte.history.add();
  			var before = this.name == 'tbrowbefore';
  			var ro     = $(r).prevAll('tr').length;
  			var cnt    = 0;
  			var mdf    = [];
  			
  			function _find(x, y) {
  				while (y>0) {
  					y--;
  					if (mx[y] && mx[y][x] && mx[y][x].nodeName) {
  						return mx[y][x];
  					}
  				}
  			}
  			
  			for (var i=0; i<mx[ro].length; i++) {
  				if (mx[ro][i] && mx[ro][i].nodeName) {
  					var cell    = $(mx[ro][i]);
  					var colspan = parseInt(cell.attr('colspan')||1);
  					if (parseInt(cell.attr('rowspan')||1) > 1) {
  						if (before) {
  							cnt += colspan;
  						} else {
  							mdf.push(cell);
  						}
  					} else {
  						cnt += colspan;
  					}
  				} else if (mx[ro][i] == '-') {
  					cell = _find(i, ro);
  					cell && mdf.push($(cell));
  				}
  			}
  			var row = $(this.rte.dom.create('tr'));
  			for (var i=0; i<cnt; i++) {
  				row.append('<td>&nbsp;</td>');
  			}
  			if (before) {
  				row.insertBefore(r);
  			} else {
  				row.insertAfter(r);
  			}
  			$.each(mdf, function() {
  				$(this).attr('rowspan', parseInt($(this).attr('rowspan')||1)+1);
  			});
  			this.rte.ui.update();
  		}
  	}
  	
  	this.update = function() {
  		if (this.rte.dom.selfOrParent(this.rte.selection.getNode(), /^TR$/)) {
  			this.domElem.removeClass('disabled');
  		} else {
  			this.domElem.addClass('disabled');
  		}
  	}
  }
  
  elRTE.prototype.ui.prototype.buttons.tbrowafter = elRTE.prototype.ui.prototype.buttons.tbrowbefore;
  /**
   * @class button - remove table row
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки 
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * @copyright: Studio 42, http://www.std42.ru
   **/
  (function($) {
  elRTE.prototype.ui.prototype.buttons.tbrowrm = function(rte, name) {
  	this.constructor.prototype.constructor.call(this, rte, name);
  	var self = this;
  	this.command = function() {
  		
  		var n  = this.rte.selection.getNode(),
  			c  = this.rte.dom.selfOrParent(n, /^(TD|TH)$/),
  			r  = this.rte.dom.selfOrParent(c, /^TR$/),
  			tb = this.rte.dom.selfOrParent(c, /^TABLE$/),
  			mx = this.rte.dom.tableMatrix(tb);
  		
  		if (c && r && mx.length) {
  			this.rte.history.add();
  			if (mx.length==1) {
  				$(tb).remove();
  				return this.rte.ui.update();
  			}
  			var mdf = [];
  			var ro  = $(r).prevAll('tr').length;
  			
  			function _find(x, y) {
  				while (y>0) {
  					y--;
  					if (mx[y] && mx[y][x] && mx[y][x].nodeName) {
  						return mx[y][x];
  					}
  				}
  			}
  			
  			// move cell with rowspan>1 to next row
  			function _move(cell, x) {
  				y = ro+1;
  				var sibling= null;
  				if (mx[y]) {
  					for (var _x=0; _x<x; _x++) {
  						if (mx[y][_x] && mx[y][_x].nodeName) {
  							sibling = mx[y][_x];
  						}
  					};
  					
  					cell = cell.remove();
  					if (sibling) {
  						cell.insertAfter(sibling);
  					} else {
  						cell.prependTo($(r).next('tr').eq(0));
  					}
  				}
  			}
  			
  			function _cursorPos(column) {
  				for (var i = 0; i<column.length; i++) {
  					if (column[i] == c) {
  						return i<column.length-1 ? column[i+1] : column[i-1];
  					}
  				}
  			}
  			
  			for (var i=0; i<mx[ro].length; i++) {
  				var cell = null;
  				var move = false;
  				if (mx[ro][i] && mx[ro][i].nodeName) {
  					cell = mx[ro][i];
  					move = true;
  				} else if (mx[ro][i] == '-' && (cell = _find(i, ro))) {
  					move = false;
  				}
  				if (cell) {
  					cell = $(cell);
  					var rowspan = parseInt(cell.attr('rowspan')||1);
  					if (rowspan>1) {
  						cell.attr('rowspan', rowspan-1);
  						move && _move(cell, i, ro);
  					} 
  				}
  			};
  			
  			var _c = _cursorPos(this.rte.dom.tableColumn(c));
  			if (_c) {
  				this.rte.selection.selectContents(_c).collapse(true);
  			}
  
  			$(r).remove();
  		}
  		this.rte.ui.update();
  	}
  	
  	this.update = function() {
  		if (this.rte.dom.selfOrParent(this.rte.selection.getNode(), /^TR$/)) {
  			this.domElem.removeClass('disabled');
  		} else {
  			this.domElem.addClass('disabled');
  		}
  	}
  }
  
  })(jQuery);/**
   * @class кнопка - отмена повтор действий
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки 
   * 
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * @copyright: Studio 42, http://www.std42.ru
   **/
  (function($) {
  	elRTE.prototype.ui.prototype.buttons.undo = function(rte, name) {
  		this.constructor.prototype.constructor.call(this, rte, name);
  	
  		this.command = function() {
  			if (this.name == 'undo' && this.rte.history.canBack()) {
  				this.rte.history.back();
  				this.rte.ui.update();
  			} else if (this.name == 'redo' && this.rte.history.canFwd()) {
  				this.rte.history.fwd();
  				this.rte.ui.update();
  			}
  		}
  	
  		this.update = function() {
  			this.domElem.toggleClass('disabled', this.name == 'undo' ? !this.rte.history.canBack() : !this.rte.history.canFwd());
  		}
  	}
  
  	elRTE.prototype.ui.prototype.buttons.redo = elRTE.prototype.ui.prototype.buttons.undo;
  
  })(jQuery);/**
   * @class button - remove link
   *
   * @param  elRTE  rte   объект-редактор
   * @param  String name  название кнопки
   *
   * @author:    Dmitry Levashov (dio) dio@std42.ru
   * @copyright: Studio 42, http://www.std42.ru 
   **/
  (function($) {
  
  	elRTE.prototype.ui.prototype.buttons.unlink = function(rte, name) {
  		this.constructor.prototype.constructor.call(this, rte, name);
  
  		this.command = function() {
  
  			var n = this.rte.selection.getNode(), 
  				l = this.rte.dom.selfOrParentLink(n);
  
  			function isLink(n) { return n.nodeName == 'A' && n.href; }
  
  			if (!l) {
  
  				var sel = $.browser.msie ? this.rte.selection.selected() : this.rte.selection.selected({wrap : false});
  				if (sel.length) {
  					for (var i=0; i < sel.length; i++) {
  						if (isLink(sel[i])) {
  							l = sel[i];
  							break;
  						}
  					};
  					if (!l) {
  						l = this.rte.dom.parent(sel[0], isLink) || this.rte.dom.parent(sel[sel.length-1], isLink);
  					}
  				}
  			}
  
  			if (l) {
  				this.rte.history.add();
  				this.rte.selection.select(l);
  				this.rte.doc.execCommand('unlink', false, null);
  				this.rte.ui.update(true);
  			}
  		
  		}
  	
  		this.update = function() {
  			var n = this.rte.selection.getNode();
  			if (this.rte.dom.selfOrParentLink(n)) {
  				this.domElem.removeClass('disabled').addClass('active');
  			} else if (this.rte.dom.selectionHas(function(n) { return n.nodeName == 'A' && n.href; })) {
  				this.domElem.removeClass('disabled').addClass('active');
  			} else {
  				this.domElem.addClass('disabled').removeClass('active');
  			}
  		}
  	}
  
  })(jQuery);
  
  