Added: ofbiz/branches/jquery/framework/images/webapp/images/jquery/plugins/elrteEditor/elrte.full.js
URL: http://svn.apache.org/viewvc/ofbiz/branches/jquery/framework/images/webapp/images/jquery/plugins/elrteEditor/elrte.full.js?rev=1036195&view=auto ============================================================================== --- ofbiz/branches/jquery/framework/images/webapp/images/jquery/plugins/elrteEditor/elrte.full.js (added) +++ ofbiz/branches/jquery/framework/images/webapp/images/jquery/plugins/elrteEditor/elrte.full.js Wed Nov 17 20:13:57 2010 @@ -0,0 +1,8647 @@ +/*! + * elRTE WYSIWYG HTML-editor + * Version 1.1 (2010-09-20) + * http://elrte.org + * + * Copyright 2010, Studio 42 Ltd. + * Licensed under a 3 clauses BSD license + */ + +/** + * @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 [hidden email] + * 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) [hidden email] + * + **/ + +function elDialogForm(o) { + var self = this; + + var defaults = { + 'class' : 'el-dialogform', + submit : function(e, d) { window.console && window.console.log && window.console.log('submit called'); d.close(); }, + form : { action : window.location.href, method : 'post' }, + ajaxForm : null, + validate : null, + spinner : 'Loading', + tabs : { active: 0 }, + tabPrefix : 'el-df-tab-', + dialog : { + title : 'dialog', + autoOpen : false, + modal : true, + resizable : false, + buttons : { + Cancel : function() { self.close(); }, + Ok : function() { self.form.trigger('submit'); } + } + } + }; + + this.opts = jQuery.extend(true, defaults, o, {dialog : { autoOpen : false, close : function() { self.close(); } }}); + + 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() { + this.ul && this.form.tabs(this.opts.tabs); + this.form.find(':text').keyup(function(e) { + if (e.keyCode == 13) { + self.form.submit(); + } + }); + + this.dialog.attr('unselectable', 'on').dialog('open'); + var self = this; + if (this.form && this.form.find(':text').length) { + setTimeout(function() { self.form.find(':text')[0].focus(); }, 20); + } + + 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) [hidden email] + * + **/ +(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(); + }) + .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() { + $(this).slideUp(); + self.val(self.val()); + }) + self.mouseleave(function(e) { + if (e.relatedTarget != self.palette.get(0)) { + 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) [hidden email] + **/ +(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) { + if (!v && v !== '') { + var w = parseInt(width.val()); + return {width : !isNaN(w) ? w+'px' : '', style : style.val(), color : color.val()}; + } else { + var m, w, s, c, b = ''; + if (v.nodeName || v.css) { + if (!v.css) { + v = $(v); + } + var 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||''; + } + + width.val(toPixels(w)); + var m = s ? s.match(/(solid|dashed|dotted|double|groove|ridge|inset|outset)/i) :''; + style.val(m ? m[1] : ''); + color.val(rgb2hex(c)); + 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) [hidden email] + **/ +(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(); + 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) [hidden email] + **/ +(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) [hidden email] + * 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.1'; + this.build = '2010-09-20'; + this.options = $.extend(true, {}, this.options, opts); + this.browser = $.browser; + this.target = $(target); + + this.lang = (''+this.options.lang).toLowerCase(); + 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.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 ($.browser.msie) { + // @todo + } else { + self.source[0].setSelectionRange(0, 0); + } + 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" />'; + $.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 (this.options.height>0) { + this.workzone.height(this.options.height); + + } + 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', 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); + } + } + }); + + /* update buttons on click and keyup */ + this.$doc.bind('mouseup', function() { + self.ui.update(); + }) + .bind('dragstart', function(e) { + e.preventDefault(); + e.stopPropagation(); + }) + .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.log('keyup '+e.keyCode) + 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; + } + } + }) + + this.typing = false; + this.lastKey = null; + + this.$doc.bind('keydown', function(e) { + 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; + } + + }) + .bind('mouseup', function() { + self.typing = false; + self.lastKey = null; + }) + .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.resizable(true) + this.window.focus(); + // this.log(this.editor.parents('form').find('[name="el-select"]')) + + +} + +/** + * 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()); + } else { + return this.filter.source($(this.doc.body).html()); + } + } +} + +elRTE.prototype.beforeSave = function() { + this.source.val(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); + } + +} + +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; + } + }); + + if (cmd == 'val') { + if (!this.length) { + return ''; + } else if (this.length == 1) { + return 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) [hidden email] + */ +(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'); + 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) { + var v = ''; + if (n.nodeType == 1) { + v = $(n).attr(attr); + if (v && attr != 'src' && attr != 'href') { + 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) { + 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> </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, [hidden email] + */ + 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+\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; + this.vimeoRegExp = /<(iframe)\s+([^>]*src\s*=\s*"http:\/\/[^"]+vimeo\.com\/\w+[^>]*)>([\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*( )+/gi, '').replace(/( |<br[^>]*>)+\s*$/gi, ''); + // pass html through chain + $.each(this._chains[chain]||[], function() { + html = this.call(self, html); + }); + return html.replace(/\t/g, ' ').replace(/\r/g, '').replace(/\s*\n\s*\n+/g, "\n")+' '; + } + + /** + * 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; + + 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); + 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') { + 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; + + return 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 ]*)<\/span>/gi, "$1") + .replace(/(<p[^>]*>\s*<\/p>|<p[^>]*\/>)/gi, '<br>') + .replace(this.tagRegExp, function(t, c, n, a) { + n = n.toLowerCase(); + + if (c) { + return '</'+(rt[n] ? rt[n].tag : n)+'>'; + } + + // create attributes hash and clean it + a = self.cleanAttrs(self.parseAttrs(a||''), n); + + 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); + return '<'+n+(a?' ':'')+a+'>'; + }); + }, + + /** + * 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(/( |<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.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')) { + 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(); + } + }); + + + + 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++) { [... 5792 lines stripped ...] |
Free forum by Nabble | Edit this page |