Updated Redactor 9.2 -> 10.0
authorAlexander Ebert <ebert@woltlab.com>
Sat, 4 Oct 2014 13:49:34 +0000 (15:49 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Sat, 4 Oct 2014 13:49:34 +0000 (15:49 +0200)
There are still some small glitches and I have not yet overhauled the API access from our own scripts (e.g. mentions). If something related to the editor is now broken, blame me.

12 files changed:
wcfsetup/install/files/js/3rdParty/redactor/plugins/table.js [new file with mode: 0644]
wcfsetup/install/files/js/3rdParty/redactor/plugins/wbbcode.js
wcfsetup/install/files/js/3rdParty/redactor/plugins/wbutton.js
wcfsetup/install/files/js/3rdParty/redactor/plugins/wfontcolor.js
wcfsetup/install/files/js/3rdParty/redactor/plugins/wfontfamily.js
wcfsetup/install/files/js/3rdParty/redactor/plugins/wfontsize.js
wcfsetup/install/files/js/3rdParty/redactor/plugins/wmonkeypatch.js
wcfsetup/install/files/js/3rdParty/redactor/plugins/wupload.js
wcfsetup/install/files/js/3rdParty/redactor/plugins/wutil.js
wcfsetup/install/files/js/3rdParty/redactor/redactor.js
wcfsetup/install/files/js/3rdParty/redactor/redactor.min.js
wcfsetup/install/files/style/redactor.less

diff --git a/wcfsetup/install/files/js/3rdParty/redactor/plugins/table.js b/wcfsetup/install/files/js/3rdParty/redactor/plugins/table.js
new file mode 100644 (file)
index 0000000..dfc4831
--- /dev/null
@@ -0,0 +1,330 @@
+if (!RedactorPlugins) var RedactorPlugins = {};
+
+RedactorPlugins.table = function()
+{
+       return {
+               getTemplate: function()
+               {
+                       return String()
+                       + '<section id="redactor-modal-table-insert">'
+                               + '<label>' + this.lang.get('rows') + '</label>'
+                               + '<input type="text" size="5" value="2" id="redactor-table-rows" />'
+                               + '<label>' + this.lang.get('columns') + '</label>'
+                               + '<input type="text" size="5" value="3" id="redactor-table-columns" />'
+                       + '</section>';
+               },
+               init: function()
+               {
+
+                       var dropdown = {};
+
+                       dropdown.insert_table = { title: this.lang.get('insert_table'), func: this.table.show };
+                       dropdown.insert_row_above = { title: this.lang.get('insert_row_above'), func: this.table.addRowAbove };
+                       dropdown.insert_row_below = { title: this.lang.get('insert_row_below'), func: this.table.addRowBelow };
+                       dropdown.insert_column_left = { title: this.lang.get('insert_column_left'), func: this.table.addColumnLeft };
+                       dropdown.insert_column_right = { title: this.lang.get('insert_column_right'), func: this.table.addColumnRight };
+                       dropdown.add_head = { title: this.lang.get('add_head'), func: this.table.addHead };
+                       dropdown.delete_head = { title: this.lang.get('delete_head'), func: this.table.deleteHead };
+                       dropdown.delete_column = { title: this.lang.get('delete_column'), func: this.table.deleteColumn };
+                       dropdown.delete_row = { title: this.lang.get('delete_row'), func: this.table.deleteRow };
+                       dropdown.delete_table = { title: this.lang.get('delete_table'), func: this.table.deleteTable };
+
+                       this.observe.addButton('td', 'table');
+                       this.observe.addButton('th', 'table');
+
+                       var button = this.button.addBefore('link', 'table', this.lang.get('table'));
+                       this.button.addDropdown(button, dropdown);
+               },
+               show: function()
+               {
+                       this.modal.addTemplate('table', this.table.getTemplate());
+
+                       this.modal.load('table', this.lang.get('insert_table'), 300);
+                       this.modal.createCancelButton();
+
+                       var button = this.modal.createActionButton(this.lang.get('insert'));
+                       button.on('click', this.table.insert);
+
+                       this.selection.save();
+                       this.modal.show();
+
+                       $('#redactor-table-rows').focus();
+
+               },
+               insert: function()
+               {
+
+                       var rows = $('#redactor-table-rows').val(),
+                               columns = $('#redactor-table-columns').val(),
+                               $tableBox = $('<div>'),
+                               tableId = Math.floor(Math.random() * 99999),
+                               $table = $('<table id="table' + tableId + '"><tbody></tbody></table>'),
+                               i, $row, z, $column;
+
+                       for (i = 0; i < rows; i++)
+                       {
+                               $row = $('<tr>');
+
+                               for (z = 0; z < columns; z++)
+                               {
+                                       $column = $('<td>' + this.opts.invisibleSpace + '</td>');
+
+                                       // set the focus to the first td
+                                       if (i === 0 && z === 0)
+                                       {
+                                               $column.append(this.selection.getMarker());
+                                       }
+
+                                       $($row).append($column);
+                               }
+
+                               $table.append($row);
+                       }
+
+                       $tableBox.append($table);
+                       var html = $tableBox.html();
+
+
+                       this.modal.close();
+                       this.selection.restore();
+
+                       if (this.table.getTable()) return;
+
+                       this.buffer.set();
+
+                       var current = this.selection.getBlock() || this.selection.getCurrent();
+                       if (current && current.tagName != 'BODY')
+                       {
+                               if (current.tagName == 'LI') current = $(current).closest('ul, ol');
+                               $(current).after(html);
+                       }
+                       else
+                       {
+                               this.insert.html(html);
+                       }
+
+                       this.selection.restore();
+
+                       var table = this.$editor.find('#table' + tableId);
+
+                       if (!this.opts.linebreaks && (this.utils.browser('mozilla') || this.utils.browser('msie')))
+                       {
+                               var $next = table.next();
+                               if ($next.length === 0)
+                               {
+                                        table.after(this.opts.emptyHtml);
+                               }
+                       }
+
+                       this.observe.buttons();
+
+                       table.find('span.redactor-selection-marker').remove();
+                       table.removeAttr('id');
+
+                       this.code.sync();
+                       this.core.setCallback('insertedTable', table);
+               },
+               getTable: function()
+               {
+                       var $table = $(this.selection.getParent()).closest('table');
+
+                       if (!this.utils.isRedactorParent($table)) return false;
+                       if ($table.size() === 0) return false;
+
+                       return $table;
+               },
+               restoreAfterDelete: function($table)
+               {
+                       this.selection.restore();
+                       $table.find('span.redactor-selection-marker').remove();
+                       this.code.sync();
+               },
+               deleteTable: function()
+               {
+                       var $table = this.table.getTable();
+                       if (!$table) return;
+
+                       this.buffer.set();
+
+
+                       var $next = $table.next();
+                       if (!this.opts.linebreaks && $next.length !== 0)
+                       {
+                               this.caret.setStart($next);
+                       }
+                       else
+                       {
+                               this.caret.setAfter($table);
+                       }
+
+
+                       $table.remove();
+
+                       this.code.sync();
+               },
+               deleteRow: function()
+               {
+                       var $table = this.table.getTable();
+                       if (!$table) return;
+
+                       var $current = $(this.selection.getCurrent());
+
+                       this.buffer.set();
+
+                       var $current_tr = $current.closest('tr');
+                       var $focus_tr = $current_tr.prev().length ? $current_tr.prev() : $current_tr.next();
+                       if ($focus_tr.length)
+                       {
+                               var $focus_td = $focus_tr.children('td, th').first();
+                               if ($focus_td.length) $focus_td.prepend(this.selection.getMarker());
+                       }
+
+                       $current_tr.remove();
+                       this.table.restoreAfterDelete($table);
+               },
+               deleteColumn: function()
+               {
+                       var $table = this.table.getTable();
+                       if (!$table) return;
+
+                       this.buffer.set();
+
+                       var $current = $(this.selection.getCurrent());
+                       var $current_td = $current.closest('td, th');
+                       var index = $current_td[0].cellIndex;
+
+                       $table.find('tr').each($.proxy(function(i, elem)
+                       {
+                               var $elem = $(elem);
+                               var focusIndex = index - 1 < 0 ? index + 1 : index - 1;
+                               if (i === 0) $elem.find('td, th').eq(focusIndex).prepend(this.selection.getMarker());
+
+                               $elem.find('td, th').eq(index).remove();
+
+                       }, this));
+
+                       this.table.restoreAfterDelete($table);
+               },
+               addHead: function()
+               {
+                       var $table = this.table.getTable();
+                       if (!$table) return;
+
+                       this.buffer.set();
+
+                       if ($table.find('thead').size() !== 0)
+                       {
+                               this.table.deleteHead();
+                               return;
+                       }
+
+                       var tr = $table.find('tr').first().clone();
+                       tr.find('td').html(this.opts.invisibleSpace);
+                       $thead = $('<thead></thead>').append(tr);
+                       $table.prepend($thead);
+
+                       this.code.sync();
+
+               },
+               deleteHead: function()
+               {
+                       var $table = this.table.getTable();
+                       if (!$table) return;
+
+                       var $thead = $table.find('thead');
+                       if ($thead.size() === 0) return;
+
+                       this.buffer.set();
+
+                       $thead.remove();
+                       this.code.sync();
+               },
+               addRowAbove: function()
+               {
+                       this.table.addRow('before');
+               },
+               addRowBelow: function()
+               {
+                       this.table.addRow('after');
+               },
+               addColumnLeft: function()
+               {
+                       this.table.addColumn('before');
+               },
+               addColumnRight: function()
+               {
+                       this.table.addColumn('after');
+               },
+               addRow: function(type)
+               {
+                       var $table = this.table.getTable();
+                       if (!$table) return;
+
+                       this.buffer.set();
+
+                       var $current = $(this.selection.getCurrent());
+                       var $current_tr = $current.closest('tr');
+                       var new_tr = $current_tr.clone();
+
+                       new_tr.find('th').replaceWith(function()
+                       {
+                               var $td = $('<td>');
+                               $td[0].attributes = this.attributes;
+
+                               return $td.append($(this).contents());
+                       });
+
+                       new_tr.find('td').html(this.opts.invisibleSpace);
+
+                       if (type == 'after')
+                       {
+                               $current_tr.after(new_tr);
+                       }
+                       else
+                       {
+                               $current_tr.before(new_tr);
+                       }
+
+                       this.code.sync();
+               },
+               addColumn: function (type)
+               {
+                       var $table = this.table.getTable();
+                       if (!$table) return;
+
+                       var index = 0;
+                       var current = $(this.selection.getCurrent());
+
+                       this.buffer.set();
+
+                       var $current_tr = current.closest('tr');
+                       var $current_td = current.closest('td, th');
+
+                       $current_tr.find('td, th').each($.proxy(function(i, elem)
+                       {
+                               if ($(elem)[0] === $current_td[0]) index = i;
+
+                       }, this));
+
+                       $table.find('tr').each($.proxy(function(i, elem)
+                       {
+                               var $current = $(elem).find('td, th').eq(index);
+
+                               var td = $current.clone();
+                               td.html(this.opts.invisibleSpace);
+
+                               if (type == 'after')
+                               {
+                                       $current.after(td);
+                               }
+                               else
+                               {
+                                       $current.before(td);
+                               }
+
+                       }, this));
+
+                       this.code.sync();
+               }
+       };
+};
\ No newline at end of file
index 5f737288b56ca6cfa0e320d693320ede22ae8feb..cb1322e93d5b9b33a01f4c8306a3aeeac017f67f 100644 (file)
@@ -7,1234 +7,1284 @@ if (!RedactorPlugins) var RedactorPlugins = {};
  * @copyright  2001-2014 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  */
-RedactorPlugins.wbbcode = {
-       /**
-        * Initializes the RedactorPlugins.wbbcode plugin.
-        */
-       init: function() {
-               var $identifier = this.$source.wcfIdentify();
-               
-               this.opts.initCallback = $.proxy(function() {
-                       // use stored editor contents
-                       var $content = $.trim(this.getOption('wOriginalValue'));
-                       if ($content.length) {
-                               this.toggle();
-                               this.$source.val($content);
-                               this.toggle();
-                       }
+RedactorPlugins.wbbcode = function() {
+       "use strict";
+       
+       return {
+               /**
+                * Initializes the RedactorPlugins.wbbcode plugin.
+                */
+               init: function() {
+                       var $identifier = this.$textarea.wcfIdentify();
                        
-                       delete this.opts.wOriginalValue;
-               }, this);
-               
-               this.opts.pasteBeforeCallback = $.proxy(this._wPasteBeforeCallback, this);
-               this.opts.pasteAfterCallback = $.proxy(this._wPasteAfterCallback, this);
-               
-               var $mpSyncClean = this.syncClean;
-               var self = this;
-               this.syncClean = function(html) {
-                       html = html.replace(/<p><br([^>]+)?><\/p>/g, '<p>@@@wcf_empty_line@@@</p>');
-                       return $mpSyncClean.call(self, html);
-               };
-               
-               if (this.getOption('wAutosaveOnce')) {
-                       this._saveTextToStorage();
-                       delete this.opts.wAutosaveOnce;
-               }
-               
-               // we do not support table heads
-               var $tableButton = this.buttonGet('table');
-               if ($tableButton.length) {
-                       var $addHead = $tableButton.data('dropdown').children('a.redactor_dropdown_add_head');
+                       this.opts.initCallback = $.proxy(function() {
+                               // use stored editor contents
+                               var $content = $.trim(this.wutil.getOption('wOriginalValue'));
+                               if ($content.length) {
+                                       this.toggle();
+                                       this.$textarea.val($content);
+                                       this.toggle();
+                               }
+                               
+                               delete this.opts.wOriginalValue;
+                       }, this);
                        
-                       // drop divider
-                       $addHead.prev().remove();
+                       this.opts.pasteBeforeCallback = $.proxy(this.wbbcode._pasteBeforeCallback, this);
+                       this.opts.pasteCallback = $.proxy(this.wbbcode._pasteCallback, this);
                        
-                       // drop 'delete head'
-                       $addHead.next().remove();
+                       var $mpCleanOnSync = this.clean.onSync;
+                       this.clean.onSync = function(html) {
+                               html = html.replace(/<p><br([^>]+)?><\/p>/g, '<p>@@@wcf_empty_line@@@</p>');
+                               return $mpCleanOnSync.call(self, html);
+                       };
                        
-                       // drop 'add head'
-                       $addHead.remove();
+                       if (this.wutil.getOption('wAutosaveOnce')) {
+                               this.wutil._saveTextToStorage();
+                               delete this.opts.wAutosaveOnce;
+                       }
                        
-                       // toggle dropdown options
-                       $tableButton.click($.proxy(this._tableButtonClick, this));
-               }
-               
-               // handle 'insert quote' button
-               WCF.System.Event.addListener('com.woltlab.wcf.redactor', 'insertBBCode_quote_' + $identifier, $.proxy(function(data) {
-                       data.cancel = true;
+                       // we do not support table heads
+                       var $tableButton = this.button.get('table');
+                       if ($tableButton.length) {
+                               var $dropdown = $tableButton.data('dropdown');
+                               
+                               // remove head add/delete
+                               $dropdown.find('.redactor-dropdown-add_head').parent().remove();
+                               $dropdown.find('.redactor-dropdown-delete_head').parent().remove();
+                               
+                               // add visual divider
+                               $('<li class="dropdownDivider" />').insertBefore($dropdown.find('.redactor-dropdown-delete_table').parent());
+                               
+                               // toggle dropdown options
+                               $tableButton.click($.proxy(this.wbbcode._tableButtonClick, this));
+                       }
                        
-                       this._handleInsertQuote();
-               }, this));
-               
-               // handle keydown
-               WCF.System.Event.addListener('com.woltlab.wcf.redactor', 'keydown_' + $identifier, $.proxy(this._wKeydownCallback, this));
-               WCF.System.Event.addListener('com.woltlab.wcf.redactor', 'keyup_' + $identifier, $.proxy(this._wKeyupCallback, this));
-       },
-       
-       /**
-        * Toggles features within the table dropdown.
-        * 
-        * @param       object          event
-        */
-       _tableButtonClick: function(event) {
-               var $button = $(event.currentTarget);
-               if (!$button.hasClass('dropact')) {
-                       return;
-               }
-               
-               var $current = this.getBlock() || this.getCurrent();
-               var $dropdown = $button.data('dropdown');
-               
-               // within table
-               $dropdown.children('li').show();
-               var $insertTable = $dropdown.find('> li > .redactor_dropdown_insert_table').parent();
-               if ($current.tagName == 'TD') {
-                       $insertTable.hide().next().hide();
-               }
-               else {
-                       $insertTable.nextAll().hide();
-               }
-       },
-       
-       /**
-        * Inserts a smiley, optionally trying to register a new smiley.
-        * 
-        * @param       string          smileyCode
-        * @param       string          smileyPath
-        * @param       boolean         registerSmiley
-        */
-       insertSmiley: function(smileyCode, smileyPath, registerSmiley) {
-               if (registerSmiley) {
-                       this.registerSmiley(smileyCode, smileyPath);
-               }
-               
-               if (this.opts.visual) {
-                       this.bufferSet();
+                       // handle 'insert quote' button
+                       WCF.System.Event.addListener('com.woltlab.wcf.redactor', 'insertBBCode_quote_' + $identifier, $.proxy(function(data) {
+                               data.cancel = true;
+                               
+                               this.wbbcode._handleInsertQuote();
+                       }, this));
                        
-                       this.$editor.focus();
+                       // handle keydown
+                       WCF.System.Event.addListener('com.woltlab.wcf.redactor', 'keydown_' + $identifier, $.proxy(this.wbbcode._keydownCallback, this));
+                       WCF.System.Event.addListener('com.woltlab.wcf.redactor', 'keyup_' + $identifier, $.proxy(this.wbbcode._keyupCallback, this));
                        
-                       this.insertHtml('&nbsp;<img src="' + smileyPath + '" class="smiley" alt="' + smileyCode + '" />&nbsp;');
+                       // disable automatic synchronization
+                       this.code.sync = function() { };
                        
-                       if (this.opts.air) this.$air.fadeOut(100);
-                       this.sync();
-               }
-               else {
-                       this.insertAtCaret(' ' + smileyCode + ' ');
-               }
-       },
-       
-       /**
-        * Registers a new smiley, returns false if the smiley code is already registered.
-        * 
-        * @param       string          smileyCode
-        * @param       string          smileyPath
-        * @return      boolean
-        */
-       registerSmiley: function(smileyCode, smileyPath) {
-               if (__REDACTOR_SMILIES[smileyCode]) {
-                       return false;
-               }
-               
-               __REDACTOR_SMILIES[smileyCode] = smileyPath;
-               
-               return true;
-       },
-       
-       /**
-        * Overwrites $.Redactor.toggle() to transform the source mode into a BBCode view.
-        * 
-        * @see         $.Redactor.toggle()
-        * @param       string          direct
-        */
-       toggle: function(direct) {
-               if (this.opts.visual) {
-                       this.sync(undefined, true);
-                       this.toggleCode(direct);
-                       this.$source.val(this.convertFromHtml(this.$source.val()));
-                       
-                       this.buttonGet('html').children('i').removeClass('fa-square-o').addClass('fa-square');
-               }
-               else {
-                       this.$source.val(this.convertToHtml(this.$source.val()));
-                       this.toggleVisual();
-                       this._observeQuotes();
-                       
-                       this.buttonGet('html').children('i').removeClass('fa-square').addClass('fa-square-o');
-               }
-       },
-       
-       /**
-        * Converts source contents from HTML into BBCode.
-        * 
-        * @param       string          html
-        */
-       convertFromHtml: function(html) {
-               WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'beforeConvertFromHtml', { html: html });
-               
-               // revert conversion of special characters
-               html = html.replace(/&trade;/gi, '\u2122');
-               html = html.replace(/&copy;/gi, '\u00a9');
-               html = html.replace(/&hellip;/gi, '\u2026');
-               html = html.replace(/&mdash;/gi, '\u2014');
-               html = html.replace(/&dash;/gi, '\u2010');
-               
-               // drop all new lines
-               html = html.replace(/\r?\n/g, '');
-               
-               // convert paragraphs into single lines
-               var $parts = html.split(/(<\/?(?:div|p)>)/);
-               var $tmp = '';
-               var $buffer = '';
-               for (var $i = 0; $i < $parts.length; $i++) {
-                       var $part = $parts[$i];
-                       if ($part == '<p>' || $part == '<div>') {
-                               continue;
-                       }
-                       else if ($part == '</p>' || $part == '</div>') {
-                               $buffer = $.trim($buffer);
-                               if ($buffer != '@@@wcf_empty_line@@@') {
-                                       $buffer += "\n";
-                               }
-                               
-                               $tmp += $buffer;
-                               $buffer = '';
-                       }
-                       else {
-                               if ($i == 0 || $i + 1 == $parts.length) {
-                                       $tmp += $part;
+                       this.code.toggle = (function() {
+                               if (this.opts.visual) {
+                                       this.code.startSync();
+                                       
+                                       this.code.showCode();
+                                       this.$textarea.val(this.wbbcode.convertFromHtml(this.$textarea.val()));
+                                       
+                                       this.button.get('html').children('i').removeClass('fa-square-o').addClass('fa-square');
                                }
                                else {
-                                       $buffer += $part;
+                                       this.$textarea.val(this.wbbcode.convertToHtml(this.$textarea.val()));
+                                       this.code.showVisual();
+                                       this.wbbcode._observeQuotes();
+                                       
+                                       this.button.get('html').children('i').removeClass('fa-square').addClass('fa-square-o');
                                }
+                       }).bind(this);
+               },
+               
+               /**
+                * Toggles features within the table dropdown.
+                * 
+                * @param       object          event
+                */
+               _tableButtonClick: function(event) {
+                       var $button = $(event.currentTarget);
+                       if (!$button.hasClass('dropact')) {
+                               return;
                        }
-               }
-               
-               if ($buffer) {
-                       $tmp += $buffer;
-                       $buffer = '';
-               }
-               
-               html = $tmp;
-               
-               html = html.replace(/@@@wcf_empty_line@@@/g, '\n');
-               html = html.replace(/\n\n$/, '\n');
-               
-               // convert all <br> into \n
-               html = html.replace(/<br>$/, '');
-               html = html.replace(/<br>/g, '\n');
-               
-               // drop <br>, they are pointless because the editor already adds a newline after them
-               html = html.replace(/<br>/g, '');
-               html = html.replace(/&nbsp;/gi, " ");
-               
-               // [quote]
-               html = html.replace(/<blockquote class="quoteBox" cite="([^"]+)?" data-author="([^"]+)?">\n?<div[^>]+>\n?<header(?:[^>]*?)>[\s\S]*?<\/header>/gi, function(match, link, author, innerContent) {
-                       var $quote;
                        
-                       if (author) author = WCF.String.unescapeHTML(author);
-                       if (link) link = WCF.String.unescapeHTML(link);
+                       var $current = this.selection.getBlock() || this.selection.getCurrent();
+                       var $dropdown = $button.data('dropdown');
                        
-                       if (link) {
-                               $quote = "[quote='" + author + "','" + link + "']";
+                       // within table
+                       $dropdown.children('li').show();
+                       var $insertTable = $dropdown.find('> li > .redactor-dropdown-insert_table').parent();
+                       if ($current.tagName == 'TD') {
+                               $insertTable.hide();
+                       }
+                       else {
+                               $insertTable.nextAll().hide();
+                       }
+               },
+               
+               /**
+                * Inserts a smiley, optionally trying to register a new smiley.
+                * 
+                * @param       string          smileyCode
+                * @param       string          smileyPath
+                * @param       boolean         registerSmiley
+                */
+               insertSmiley: function(smileyCode, smileyPath, registerSmiley) {
+                       if (registerSmiley) {
+                               this.registerSmiley(smileyCode, smileyPath);
                        }
-                       else if (author) {
-                               $quote = "[quote='" + author + "']";
+                       
+                       if (this.opts.visual) {
+                               this.bufferSet();
+                               
+                               this.$editor.focus();
+                               
+                               this.insertHtml('&nbsp;<img src="' + smileyPath + '" class="smiley" alt="' + smileyCode + '" />&nbsp;');
                        }
                        else {
-                               $quote = "[quote]";
+                               this.insertAtCaret(' ' + smileyCode + ' ');
+                       }
+               },
+               
+               /**
+                * Registers a new smiley, returns false if the smiley code is already registered.
+                * 
+                * @param       string          smileyCode
+                * @param       string          smileyPath
+                * @return      boolean
+                */
+               registerSmiley: function(smileyCode, smileyPath) {
+                       if (__REDACTOR_SMILIES[smileyCode]) {
+                               return false;
                        }
                        
-                       return $quote;
-               });
-               html = html.replace(/(?:\n*)<\/blockquote>\n?/gi, '[/quote]\n');
-               
-               // [email]
-               html = html.replace(/<a [^>]*?href=(["'])mailto:(.+?)\1.*?>([\s\S]+?)<\/a>/gi, '[email=$2]$3[/email]');
-               
-               // [url]
-               html = html.replace(/<a [^>]*?href=(["'])(.+?)\1.*?>([\s\S]+?)<\/a>/gi, function(match, x, url, text) {
-                       if (url == text) return '[url]' + url + '[/url]';
+                       __REDACTOR_SMILIES[smileyCode] = smileyPath;
                        
-                       return "[url='" + url + "']" + text + "[/url]";
-               });
-               
-               // [b]
-               html = html.replace(/<(?:b|strong)>/gi, '[b]');
-               html = html.replace(/<\/(?:b|strong)>/gi, '[/b]');
-               
-               // [i]
-               html = html.replace(/<(?:i|em)>/gi, '[i]');
-               html = html.replace(/<\/(?:i|em)>/gi, '[/i]');
-               
-               // [u]
-               html = html.replace(/<u>/gi, '[u]');
-               html = html.replace(/<\/u>/gi, '[/u]');
-               
-               // [s]
-               html = html.replace(/<(?:s(trike)?|del)>/gi, '[s]');
-               html = html.replace(/<\/(?:s(trike)?|del)>/gi, '[/s]');
-               
-               // [sub]
-               html = html.replace(/<sub>/gi, '[sub]');
-               html = html.replace(/<\/sub>/gi, '[/sub]');
-               
-               // [sup]
-               html = html.replace(/<sup>/gi, '[sup]');
-               html = html.replace(/<\/sup>/gi, '[/sup]');
-               
-               // smileys
-               html = html.replace(/<img [^>]*?alt="([^"]+?)" class="smiley".*?> ?/gi, '$1 '); // firefox
-               html = html.replace(/<img [^>]*?class="smiley" alt="([^"]+?)".*?> ?/gi, '$1 '); // chrome, ie
-               
-               // attachments
-               html = html.replace(/<img [^>]*?class="redactorEmbeddedAttachment" data-attachment-id="(\d+)"( style="float: (left|right)")?>/gi, function(match, attachmentID, styleTag, alignment) {
-                       if (alignment) {
-                               return '[attach=' + attachmentID + ',' + alignment + '][/attach]';
+                       return true;
+               },
+               
+               /**
+                * Converts source contents from HTML into BBCode.
+                * 
+                * @param       string          html
+                */
+               convertFromHtml: function(html) {
+                       WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'beforeConvertFromHtml', { html: html });
+                       
+                       // revert conversion of special characters
+                       html = html.replace(/&trade;/gi, '\u2122');
+                       html = html.replace(/&copy;/gi, '\u00a9');
+                       html = html.replace(/&hellip;/gi, '\u2026');
+                       html = html.replace(/&mdash;/gi, '\u2014');
+                       html = html.replace(/&dash;/gi, '\u2010');
+                       
+                       // drop all new lines
+                       html = html.replace(/\r?\n/g, '');
+                       
+                       // remove empty links
+                       html = html.replace(/<a[^>]*?><\/a>/g, '');
+                       
+                       // drop empty paragraphs
+                       html = html.replace(/<p><\/p>/g, '');
+                       
+                       // remove <br> right in front of </p> (does not match <p><br></p> since it has been converted already)
+                       html = html.replace(/<br( \/)?><\/p>/g, '');
+                       
+                       // convert paragraphs into single lines
+                       var $parts = html.split(/(<\/?(?:div|p)>)/);
+                       var $tmp = '';
+                       var $buffer = '';
+                       for (var $i = 0; $i < $parts.length; $i++) {
+                               var $part = $parts[$i];
+                               if ($part == '<p>' || $part == '<div>') {
+                                       continue;
+                               }
+                               else if ($part == '</p>' || $part == '</div>') {
+                                       $buffer = $.trim($buffer);
+                                       if ($buffer != '@@@wcf_empty_line@@@') {
+                                               $buffer += "\n";
+                                       }
+                                       
+                                       $tmp += $buffer;
+                                       $buffer = '';
+                               }
+                               else {
+                                       if ($i == 0 || $i + 1 == $parts.length) {
+                                               $tmp += $part;
+                                       }
+                                       else {
+                                               $buffer += $part;
+                                       }
+                               }
                        }
                        
-                       return '[attach=' + attachmentID + '][/attach]';
-               });
-               
-               // [img]
-               html = html.replace(/<img [^>]*?src=(["'])([^"']+?)\1 style="float: (left|right)[^"]*".*?>/gi, "[img='$2',$3][/img]");
-               html = html.replace(/<img [^>]*?src=(["'])([^"']+?)\1.*?>/gi, '[img]$2[/img]');
-               
-               // handle [color], [size], [font] and [tt]
-               var $components = html.split(/(<\/?span[^>]*>)/);
-               
-               var $buffer = [ ];
-               var $openElements = [ ];
-               var $result = '';
-               
-               for (var $i = 0; $i < $components.length; $i++) {
-                       var $value = $components[$i];
+                       if ($buffer) {
+                               $tmp += $buffer;
+                               $buffer = '';
+                       }
+                       
+                       html = $tmp;
+                       
+                       html = html.replace(/@@@wcf_empty_line@@@/g, '\n');
+                       html = html.replace(/\n\n$/, '\n');
+                       
+                       // convert all <br> into \n
+                       html = html.replace(/<br>$/, '');
+                       html = html.replace(/<br>/g, '\n');
                        
-                       if ($value == '</span>') {
-                               var $opening = $openElements.pop();
-                               var $tmp = $opening.start + $buffer.pop() + $opening.end;
+                       // drop <br>, they are pointless because the editor already adds a newline after them
+                       html = html.replace(/<br>/g, '');
+                       html = html.replace(/&nbsp;/gi, " ");
+                       
+                       // [quote]
+                       html = html.replace(/<blockquote class="quoteBox" cite="([^"]+)?" data-author="([^"]+)?">\n?<div[^>]+>\n?<header(?:[^>]*?)>[\s\S]*?<\/header>/gi, function(match, link, author, innerContent) {
+                               var $quote;
+                               
+                               if (author) author = WCF.String.unescapeHTML(author);
+                               if (link) link = WCF.String.unescapeHTML(link);
                                
-                               if ($buffer.length) {
-                                       $buffer[$buffer.length - 1] += $tmp;
+                               if (link) {
+                                       $quote = "[quote='" + author + "','" + link + "']";
+                               }
+                               else if (author) {
+                                       $quote = "[quote='" + author + "']";
                                }
                                else {
-                                       $result += $tmp;
+                                       $quote = "[quote]";
                                }
-                       }
-                       else {
-                               if ($value.match(/^<span style="([^"]+)">/)) {
-                                       var $style = RegExp.$1;
-                                       var $start;
-                                       var $end;
+                               
+                               return $quote;
+                       });
+                       html = html.replace(/(?:\n*)<\/blockquote>\n?/gi, '[/quote]\n');
+                       
+                       // [email]
+                       html = html.replace(/<a [^>]*?href=(["'])mailto:(.+?)\1.*?>([\s\S]+?)<\/a>/gi, '[email=$2]$3[/email]');
+                       
+                       // remove empty links
+                       html = html.replace(/<a[^>]+><\/a>/, '');
+                       
+                       // [url]
+                       html = html.replace(/<a [^>]*?href=(["'])(.+?)\1.*?>([\s\S]+?)<\/a>/gi, function(match, x, url, text) {
+                               if (url == text) return '[url]' + url + '[/url]';
+                               
+                               return "[url='" + url + "']" + text + "[/url]";
+                       });
+                       
+                       // [b]
+                       html = html.replace(/<(?:b|strong)>/gi, '[b]');
+                       html = html.replace(/<\/(?:b|strong)>/gi, '[/b]');
+                       
+                       // [i]
+                       html = html.replace(/<(?:i|em)>/gi, '[i]');
+                       html = html.replace(/<\/(?:i|em)>/gi, '[/i]');
+                       
+                       // [u]
+                       html = html.replace(/<u>/gi, '[u]');
+                       html = html.replace(/<\/u>/gi, '[/u]');
+                       
+                       // [s]
+                       html = html.replace(/<(?:s(trike)?|del)>/gi, '[s]');
+                       html = html.replace(/<\/(?:s(trike)?|del)>/gi, '[/s]');
+                       
+                       // [sub]
+                       html = html.replace(/<sub>/gi, '[sub]');
+                       html = html.replace(/<\/sub>/gi, '[/sub]');
+                       
+                       // [sup]
+                       html = html.replace(/<sup>/gi, '[sup]');
+                       html = html.replace(/<\/sup>/gi, '[/sup]');
+                       
+                       // smileys
+                       html = html.replace(/<img [^>]*?alt="([^"]+?)" class="smiley".*?> ?/gi, '$1 '); // firefox
+                       html = html.replace(/<img [^>]*?class="smiley" alt="([^"]+?)".*?> ?/gi, '$1 '); // chrome, ie
+                       
+                       // attachments
+                       html = html.replace(/<img [^>]*?class="redactorEmbeddedAttachment" data-attachment-id="(\d+)"( style="float: (left|right)")?>/gi, function(match, attachmentID, styleTag, alignment) {
+                               if (alignment) {
+                                       return '[attach=' + attachmentID + ',' + alignment + '][/attach]';
+                               }
+                               
+                               return '[attach=' + attachmentID + '][/attach]';
+                       });
+                       
+                       // [img]
+                       html = html.replace(/<img [^>]*?src=(["'])([^"']+?)\1 style="float: (left|right)[^"]*".*?>/gi, "[img='$2',$3][/img]");
+                       html = html.replace(/<img [^>]*?src=(["'])([^"']+?)\1.*?>/gi, '[img]$2[/img]');
+                       
+                       // handle [color], [size], [font] and [tt]
+                       var $components = html.split(/(<\/?span[^>]*>)/);
+                       
+                       var $buffer = [ ];
+                       var $openElements = [ ];
+                       var $result = '';
+                       var $pixelToPoint = {
+                               11: 8, 
+                               13: 10,
+                               16: 12,
+                               19: 14,
+                               24: 18,
+                               29: 22,
+                               32: 24,
+                               48: 36
+                       };
+                       
+                       for (var $i = 0; $i < $components.length; $i++) {
+                               var $value = $components[$i];
+                               
+                               if ($value == '</span>') {
+                                       var $opening = $openElements.pop();
+                                       var $tmp = $opening.start + $buffer.pop() + $opening.end;
                                        
-                                       if ($style.match(/^color: ?rgb\((\d{1,3}), ?(\d{1,3}), ?(\d{1,3})\);?$/i)) {
-                                               var $r = RegExp.$1;
-                                               var $g = RegExp.$2;
-                                               var $b = RegExp.$3;
-                                               
-                                               var $hex = ("0123456789ABCDEF".charAt(($r - $r % 16) / 16) + '' + "0123456789ABCDEF".charAt($r % 16)) + '' + ("0123456789ABCDEF".charAt(($g - $g % 16) / 16) + '' + "0123456789ABCDEF".charAt($g % 16)) + '' + ("0123456789ABCDEF".charAt(($b - $b % 16) / 16) + '' + "0123456789ABCDEF".charAt($b % 16));
-                                               $start = '[color=#' + $hex + ']';
-                                               $end = '[/color]';
-                                       }
-                                       else if ($style.match(/^color: ?(.*?);?$/i)) {
-                                               $start = '[color=' + RegExp.$1 + ']';
-                                               $end = '[/color]';
-                                       }
-                                       else if ($style.match(/^font-size: ?(\d+)pt;?$/i)) {
-                                               $start = '[size=' + RegExp.$1 + ']';
-                                               $end = '[/size]';
-                                       }
-                                       else if ($style.match(/^font-family: ?(.*?);?$/)) {
-                                               $start = '[font=' + RegExp.$1.replace(/'/g, '') + ']';
-                                               $end = '[/font]';
+                                       if ($buffer.length) {
+                                               $buffer[$buffer.length - 1] += $tmp;
                                        }
                                        else {
-                                               $start = '<span style="' + $style + '">';
-                                               $end = '</span>';
+                                               $result += $tmp;
                                        }
-                                       
-                                       $buffer[$buffer.length] = '';
-                                       $openElements[$buffer.length] = {
-                                               start: $start,
-                                               end: $end
-                                       };
-                               }
-                               else if ($value.match(/^<span class="inlineCode">/)) {
-                                       $buffer[$buffer.length] = '';
-                                       $openElements[$buffer.length] = {
-                                               start: '[tt]',
-                                               end: '[/tt]'
-                                       };
                                }
                                else {
-                                       if ($buffer.length) {
-                                               $buffer[$buffer.length - 1] += $value;
+                                       if ($value.match(/^<span/)) {
+                                               if ($value.match(/^<span(?:.*?)style="([^"]+)"(?:[^>]*?)>/)) {
+                                                       var $style = RegExp.$1;
+                                                       var $start;
+                                                       var $end;
+                                                       
+                                                       if ($style.match(/color: ?rgb\((\d{1,3}), ?(\d{1,3}), ?(\d{1,3})\);?/i)) {
+                                                               var $r = RegExp.$1;
+                                                               var $g = RegExp.$2;
+                                                               var $b = RegExp.$3;
+                                                               
+                                                               var $hex = ("0123456789ABCDEF".charAt(($r - $r % 16) / 16) + '' + "0123456789ABCDEF".charAt($r % 16)) + '' + ("0123456789ABCDEF".charAt(($g - $g % 16) / 16) + '' + "0123456789ABCDEF".charAt($g % 16)) + '' + ("0123456789ABCDEF".charAt(($b - $b % 16) / 16) + '' + "0123456789ABCDEF".charAt($b % 16));
+                                                               $start = '[color=#' + $hex + ']';
+                                                               $end = '[/color]';
+                                                       }
+                                                       else if ($style.match(/color: ?(.*?);?/i)) {
+                                                               $start = '[color=' + RegExp.$1 + ']';
+                                                               $end = '[/color]';
+                                                       }
+                                                       else if ($style.match(/font-size: ?(\d+)(pt|px);?/i)) {
+                                                               if (RegExp.$2 == 'pt') {
+                                                                       $start = '[size=' + RegExp.$1 + ']';
+                                                                       $end = '[/size]';
+                                                               }
+                                                               else {
+                                                                       if ($pixelToPoint[RegExp.$1]) {
+                                                                               $start = '[size=' + $pixelToPoint[RegExp.$1] + ']';
+                                                                               $end = '[/size]';
+                                                                       }
+                                                                       else {
+                                                                               // unsupported size
+                                                                               $start = '';
+                                                                               $end = '';
+                                                                       }
+                                                               }
+                                                       }
+                                                       else if ($style.match(/font-family: ?(.*?);?/)) {
+                                                               $start = '[font=' + RegExp.$1.replace(/'/g, '') + ']';
+                                                               $end = '[/font]';
+                                                       }
+                                                       else {
+                                                               $start = '<span style="' + $style + '">';
+                                                               $end = '</span>';
+                                                       }
+                                                       
+                                                       $buffer[$buffer.length] = '';
+                                                       $openElements[$buffer.length] = {
+                                                               start: $start,
+                                                               end: $end
+                                                       };
+                                               }
+                                               else if ($value.match(/^<span class="inlineCode">/)) {
+                                                       $buffer[$buffer.length] = '';
+                                                       $openElements[$buffer.length] = {
+                                                               start: '[tt]',
+                                                               end: '[/tt]'
+                                                       };
+                                               }
+                                               else {
+                                                       // unrecognized span, ignore
+                                                       $buffer[$buffer.length] = '';
+                                                       $openElements[$buffer.length] = {
+                                                               start: '',
+                                                               end: ''
+                                                       };
+                                               }
                                        }
                                        else {
-                                               $result += $value;
+                                               if ($buffer.length) {
+                                                       $buffer[$buffer.length - 1] += $value;
+                                               }
+                                               else {
+                                                       $result += $value;
+                                               }
                                        }
                                }
                        }
-               }
-               
-               html = $result;
-               
-               // [align]
-               html = html.replace(/<(div|p) style="text-align: ?(left|center|right|justify);? ?">([\s\S]*?)\n/gi, function(match, tag, alignment, content) {
-                       return '[align=' + alignment + ']' + $.trim(content) + '[/align]';
-               });
-               
-               // [*]
-               html = html.replace(/<li>/gi, '[*]');
-               html = html.replace(/<\/li>/gi, '');
-               
-               // [list]
-               html = html.replace(/<ul>/gi, '[list]');
-               html = html.replace(/<(ol|ul style="list-style-type: decimal")>/gi, '[list=1]');
-               html = html.replace(/<ul style="list-style-type: (none|circle|square|disc|decimal|lower-roman|upper-roman|decimal-leading-zero|lower-greek|lower-latin|upper-latin|armenian|georgian)">/gi, '[list=$1]');
-               html = html.replace(/<\/(ul|ol)>/gi, '[/list]');
-               
-               // [table]
-               html = html.replace(/<table[^>]*>/gi, '[table]\n');
-               html = html.replace(/<\/table>/gi, '[/table]\n');
-               
-               // remove <tbody>
-               html = html.replace(/<tbody>([\s\S]*?)<\/tbody>/, function(match, p1) {
-                       return $.trim(p1);
-               });
-               
-               // remove empty <tr>s
-               html = html.replace(/<tr><\/tr>/gi, '');
-               // [tr]
-               html = html.replace(/<tr>/gi, '[tr]\n');
-               html = html.replace(/<\/tr>/gi, '[/tr]\n');
-               
-               // [td]+[align]
-               html = html.replace(/<td style="text-align: ?(left|center|right|justify);? ?">([\s\S]*?)<\/td>/gi, "[td][align=$1]$2[/align][/td]");
-               
-               // [td]
-               html = html.replace(/(\t)*<td>(\t)*/gi, '[td]');
-               html = html.replace(/(\t)*<\/td>/gi, '[/td]\n');
-               
-               // cache redactor's selection markers
-               var $cachedMarkers = { };
-               html.replace(/<span id="selection-marker-\d+" class="redactor-selection-marker"><\/span>/, function(match) {
-                       var $key = match.hashCode();
-                       $cachedMarkers[$key] = match.replace(/\$/g, '$$$$');
-                       return '@@' + $key + '@@';
-               });
-               
-               WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'convertFromHtml', { html: html });
-               
-               // Remove remaining tags.
-               html = html.replace(/<[^(<|>)]+>/g, '');
-               
-               // insert redactor's selection markers
-               if ($.getLength($cachedMarkers)) {
-                       for (var $key in $cachedMarkers) {
-                               var $regex = new RegExp('@@' + $key + '@@', 'g');
-                               data = data.replace($regex, $cachedMarkers[$key]);
-                       }
-               }
-               
-               // Restore <, > and &
-               html = html.replace(/&lt;/g, '<');
-               html = html.replace(/&gt;/g, '>');
-               html = html.replace(/&amp;/g, '&');
-               
-               // Restore ( and )
-               html = html.replace(/%28/g, '(');
-               html = html.replace(/%29/g, ')');
-               
-               // Restore %20
-               //html = html.replace(/%20/g, ' ');
-               
-               // cache source code tags to preserve leading tabs
-               var $cachedCodes = { };
-               for (var $i = 0, $length = __REDACTOR_SOURCE_BBCODES.length; $i < $length; $i++) {
-                       var $bbcode = __REDACTOR_SOURCE_BBCODES[$i];
                        
-                       var $regExp = new RegExp('\\[' + $bbcode + '([\\S\\s]+?)\\[\\/' + $bbcode + '\\]', 'gi');
-                       html = html.replace($regExp, function(match) {
-                               var $key = match.hashCode();
-                               $cachedCodes[$key] = match.replace(/\$/g, '$$$$');
-                               return '@@' + $key + '@@';
+                       html = $result;
+                       
+                       // [align]
+                       html = html.replace(/<(div|p) style="text-align: ?(left|center|right|justify);? ?">([\s\S]*?)\n/gi, function(match, tag, alignment, content) {
+                               return '[align=' + alignment + ']' + $.trim(content) + '[/align]';
                        });
-               }
-               
-               WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'afterConvertFromHtml', { html: html });
-               
-               // insert codes
-               if ($.getLength($cachedCodes)) {
-                       for (var $key in $cachedCodes) {
-                               var $regex = new RegExp('@@' + $key + '@@', 'g');
-                               html = html.replace($regex, $cachedCodes[$key]);
-                       }
-               }
-               
-               // remove all leading and trailing whitespaces, but add one empty line at the end
-               html = $.trim(html);
-               html += "\n";
-               
-               return html;
-       },
-       
-       /**
-        * Converts source contents from BBCode to HTML.
-        * 
-        * @param       string          data
-        */
-       convertToHtml: function(data) {
-               WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'beforeConvertToHtml', { data: data });
-               
-               // remove 0x200B (unicode zero width space)
-               data = this.removeZeroWidthSpace(data);
-               
-               // Convert & to its HTML entity.
-               data = data.replace(/&/g, '&amp;');
-               
-               // Convert < and > to their HTML entities.
-               data = data.replace(/</g, '&lt;');
-               data = data.replace(/>/g, '&gt;');
-               
-               // cache source code tags
-               var $cachedCodes = { };
-               for (var $i = 0, $length = __REDACTOR_SOURCE_BBCODES.length; $i < $length; $i++) {
-                       var $bbcode = __REDACTOR_SOURCE_BBCODES[$i];
                        
-                       var $regExp = new RegExp('\\[' + $bbcode + '([\\S\\s]+?)\\[\\/' + $bbcode + '\\]', 'gi');
-                       data = data.replace($regExp, function(match) {
+                       // [*]
+                       html = html.replace(/<li>/gi, '[*]');
+                       html = html.replace(/<\/li>/gi, '');
+                       
+                       // [list]
+                       html = html.replace(/<ul>/gi, '[list]');
+                       html = html.replace(/<(ol|ul style="list-style-type: decimal")>/gi, '[list=1]');
+                       html = html.replace(/<ul style="list-style-type: (none|circle|square|disc|decimal|lower-roman|upper-roman|decimal-leading-zero|lower-greek|lower-latin|upper-latin|armenian|georgian)">/gi, '[list=$1]');
+                       html = html.replace(/<\/(ul|ol)>/gi, '[/list]');
+                       
+                       // [table]
+                       html = html.replace(/<table[^>]*>/gi, '[table]\n');
+                       html = html.replace(/<\/table>/gi, '[/table]\n');
+                       
+                       // remove <tbody>
+                       html = html.replace(/<tbody>([\s\S]*?)<\/tbody>/, function(match, p1) {
+                               return $.trim(p1);
+                       });
+                       
+                       // remove empty <tr>s
+                       html = html.replace(/<tr><\/tr>/gi, '');
+                       // [tr]
+                       html = html.replace(/<tr>/gi, '[tr]\n');
+                       html = html.replace(/<\/tr>/gi, '[/tr]\n');
+                       
+                       // [td]+[align]
+                       html = html.replace(/<td style="text-align: ?(left|center|right|justify);? ?">([\s\S]*?)<\/td>/gi, "[td][align=$1]$2[/align][/td]");
+                       
+                       // [td]
+                       html = html.replace(/(\t)*<td>(\t)*/gi, '[td]');
+                       html = html.replace(/(\t)*<\/td>/gi, '[/td]\n');
+                       
+                       // cache redactor's selection markers
+                       var $cachedMarkers = { };
+                       html.replace(/<span id="selection-marker-\d+" class="redactor-selection-marker"><\/span>/, function(match) {
                                var $key = match.hashCode();
-                               $cachedCodes[$key] = match.replace(/\$/g, '$$$$');
+                               $cachedMarkers[$key] = match.replace(/\$/g, '$$$$');
                                return '@@' + $key + '@@';
                        });
-               }
-               
-               // [url]
-               data = data.replace(/\[url\]([^"]+?)\[\/url]/gi, '<a href="$1">$1</a>');
-               data = data.replace(/\[url\='([^'"]+)'](.+?)\[\/url]/gi, '<a href="$1">$2</a>');
-               data = data.replace(/\[url\=([^'"\]]+)](.+?)\[\/url]/gi, '<a href="$1">$2</a>');
-               
-               // [email]
-               data = data.replace(/\[email\]([^"]+?)\[\/email]/gi, '<a href="mailto:$1">$1</a>');
-               data = data.replace(/\[email\=([^"\]]+)](.+?)\[\/email]/gi, '<a href="mailto:$1">$2</a>');
-               
-               // [b]
-               data = data.replace(/\[b\](.*?)\[\/b]/gi, '<b>$1</b>');
-               
-               // [i]
-               data = data.replace(/\[i\](.*?)\[\/i]/gi, '<i>$1</i>');
-               
-               // [u]
-               data = data.replace(/\[u\](.*?)\[\/u]/gi, '<u>$1</u>');
-               
-               // [s]
-               data = data.replace(/\[s\](.*?)\[\/s]/gi, '<strike>$1</strike>');
-               
-               // [sub]
-               data = data.replace(/\[sub\](.*?)\[\/sub]/gi, '<sub>$1</sub>');
-               
-               // [sup]
-               data = data.replace(/\[sup\](.*?)\[\/sup]/gi, '<sup>$1</sup>');
                        
-               // [img]
-               data = data.replace(/\[img\]([^"]+?)\[\/img\]/gi,'<img src="$1" />');
-               data = data.replace(/\[img='?([^"]*?)'?,'?(left|right)'?\]\[\/img\]/gi,'<img src="$1" style="float: $2" />');
-               data = data.replace(/\[img='?([^"]*?)'?\]\[\/img\]/gi,'<img src="$1" />');
-               
-               // [size]
-               data = data.replace(/\[size=(\d+)\](.*?)\[\/size\]/gi,'<span style="font-size: $1pt">$2</span>');
-               
-               // [color]
-               data = data.replace(/\[color=([#a-z0-9]*?)\](.*?)\[\/color\]/gi,'<span style="color: $1">$2</span>');
-               
-               // [font]
-               data = data.replace(/\[font='?([a-z,\- ]*?)'?\](.*?)\[\/font\]/gi,'<span style="font-family: $1">$2</span>');
-               
-               // [align]
-               data = data.replace(/\[align=(left|right|center|justify)\](.*?)\[\/align\]/gi,'<div style="text-align: $1">$2</div>');
-               
-               // [*]
-               data = data.replace(/\[\*\](.*?)(?=\[\*\]|\[\/list\])/gi,'<li>$1</li>');
-               
-               // [list]
-               data = data.replace(/\[list\]/gi, '<ul>');
-               data = data.replace(/\[list=1\]/gi, '<ul style="list-style-type: decimal">');
-               data = data.replace(/\[list=a\]/gi, '<ul style="list-style-type: lower-latin">');
-               data = data.replace(/\[list=(none|circle|square|disc|decimal|lower-roman|upper-roman|decimal-leading-zero|lower-greek|lower-latin|upper-latin|armenian|georgian)\]/gi, '<ul style="list-style-type: $1">');
-               data = data.replace(/\[\/list]/gi, '</ul>');
-               
-               // trim whitespaces within [table]
-               data = data.replace(/\[table\]([\S\s]*?)\[\/table\]/gi, function(match, p1) {
-                       return '[table]' + $.trim(p1) + '[/table]';
-               });
-               
-               // [table]
-               data = data.replace(/\[table\]/gi, '<table border="1" cellspacing="1" cellpadding="1" style="width: 500px;">');
-               data = data.replace(/\[\/table\]/gi, '</table>');
-               // [tr]
-               data = data.replace(/\[tr\]/gi, '<tr>');
-               data = data.replace(/\[\/tr\]/gi, '</tr>');
-               // [td]
-               data = data.replace(/\[td\]/gi, '<td>');
-               data = data.replace(/\[\/td\]/gi, '</td>');
-               
-               // trim whitespaces within <td>
-               data = data.replace(/<td>([\S\s]*?)<\/td>/gi, function(match, p1) {
-                       var $tdContent = $.trim(p1);
-                       if (!$tdContent.length) {
-                               // unicode zero-width space
-                               $tdContent = '&#8203;';
+                       WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'convertFromHtml', { html: html });
+                       
+                       // Remove remaining tags.
+                       html = html.replace(/<[^(<|>)]+>/g, '');
+                       
+                       // insert redactor's selection markers
+                       if ($.getLength($cachedMarkers)) {
+                               for (var $key in $cachedMarkers) {
+                                       var $regex = new RegExp('@@' + $key + '@@', 'g');
+                                       data = data.replace($regex, $cachedMarkers[$key]);
+                               }
                        }
                        
-                       return '<td>' + $tdContent + '</td>';
-               });
-               
-               // attachments
-               var $attachmentUrl = this.getOption('wAttachmentUrl');
-               if ($attachmentUrl) {
-                       var $imageAttachmentIDs = this._getImageAttachmentIDs();
+                       // Restore <, > and &
+                       html = html.replace(/&lt;/g, '<');
+                       html = html.replace(/&gt;/g, '>');
+                       html = html.replace(/&amp;/g, '&');
+                       
+                       // Restore ( and )
+                       html = html.replace(/%28/g, '(');
+                       html = html.replace(/%29/g, ')');
                        
-                       data = data.replace(/\[attach=(\d+)(,[^\]]*)?\]\[\/attach\]/g, function(match, attachmentID, alignment) {
-                               attachmentID = parseInt(attachmentID);
+                       // Restore %20
+                       //html = html.replace(/%20/g, ' ');
+                       
+                       // cache source code tags to preserve leading tabs
+                       var $cachedCodes = { };
+                       for (var $i = 0, $length = __REDACTOR_SOURCE_BBCODES.length; $i < $length; $i++) {
+                               var $bbcode = __REDACTOR_SOURCE_BBCODES[$i];
                                
-                               if (WCF.inArray(attachmentID, $imageAttachmentIDs)) {
-                                       var $style = '';
-                                       if (alignment) {
-                                               if (alignment.match(/^,'?(left|right)'?/)) {
-                                                       $style = ' style="float: ' + RegExp.$1 + '"';
-                                               }
-                                       }
-                                       
-                                       return '<img src="' + $attachmentUrl.replace(/987654321/, attachmentID) + '" class="redactorEmbeddedAttachment" data-attachment-id="' + attachmentID + '"' + $style + ' />';
+                               var $regExp = new RegExp('\\[' + $bbcode + '([\\S\\s]+?)\\[\\/' + $bbcode + '\\]', 'gi');
+                               html = html.replace($regExp, function(match) {
+                                       var $key = match.hashCode();
+                                       $cachedCodes[$key] = match.replace(/\$/g, '$$$$');
+                                       return '@@' + $key + '@@';
+                               });
+                       }
+                       
+                       WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'afterConvertFromHtml', { html: html });
+                       
+                       // insert codes
+                       if ($.getLength($cachedCodes)) {
+                               for (var $key in $cachedCodes) {
+                                       var $regex = new RegExp('@@' + $key + '@@', 'g');
+                                       html = html.replace($regex, $cachedCodes[$key]);
                                }
-                               
-                               return match;
-                       });
-               }
-               
-               // smileys
-               for (var smileyCode in __REDACTOR_SMILIES) {
-                       $smileyCode = smileyCode.replace(/</g, '&lt;').replace(/>/g, '&gt;');
-                       var regExp = new RegExp('(\\s|>|^)' + WCF.String.escapeRegExp($smileyCode) + '(?=\\s|<|$)', 'gi');
-                       data = data.replace(regExp, '$1<img src="' + __REDACTOR_SMILIES[smileyCode] + '" class="smiley" alt="' + $smileyCode + '" />');
-               }
-               
-               // remove "javascript:"
-               data = data.replace(/(javascript):/gi, '$1<span></span>:');
-               
-               // unify line breaks
-               data = data.replace(/(\r|\r\n)/g, "\n");
-               
-               // extract [quote] bbcodes to prevent line break handling below
-               var $cachedQuotes = [ ];
-               var $knownQuotes = [ ];
-               for (var $i = 0; $i < 5; $i++) {
-                       var $foundQuotes = false;
+                       }
                        
-                       data = data.replace(/\[quote.*?\]((?!\[quote)[\s\S])*?\[\/quote\]/gi, function(match) {
-                               var $key = match.hashCode();
-                               $cachedQuotes.push({
-                                       hashCode: $key,
-                                       content: match.replace(/\$/g, '$$$$')
+                       // remove all leading and trailing whitespaces, but add one empty line at the end
+                       html = $.trim(html);
+                       html += "\n";
+                       
+                       return html;
+               },
+               
+               /**
+                * Converts source contents from BBCode to HTML.
+                * 
+                * @param       string          data
+                */
+               convertToHtml: function(data) {
+                       WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'beforeConvertToHtml', { data: data });
+                       
+                       // remove 0x200B (unicode zero width space)
+                       data = this.wutil.removeZeroWidthSpace(data);
+                       
+                       // Convert & to its HTML entity.
+                       data = data.replace(/&/g, '&amp;');
+                       
+                       // Convert < and > to their HTML entities.
+                       data = data.replace(/</g, '&lt;');
+                       data = data.replace(/>/g, '&gt;');
+                       
+                       // cache source code tags
+                       var $cachedCodes = { };
+                       for (var $i = 0, $length = __REDACTOR_SOURCE_BBCODES.length; $i < $length; $i++) {
+                               var $bbcode = __REDACTOR_SOURCE_BBCODES[$i];
+                               
+                               var $regExp = new RegExp('\\[' + $bbcode + '([\\S\\s]+?)\\[\\/' + $bbcode + '\\]', 'gi');
+                               data = data.replace($regExp, function(match) {
+                                       var $key = match.hashCode();
+                                       $cachedCodes[$key] = match.replace(/\$/g, '$$$$');
+                                       return '@@' + $key + '@@';
                                });
-                               $knownQuotes.push($key.toString());
+                       }
+                       
+                       // [url]
+                       data = data.replace(/\[url\]([^"]+?)\[\/url]/gi, '<a href="$1">$1</a>');
+                       data = data.replace(/\[url\='([^'"]+)'](.+?)\[\/url]/gi, '<a href="$1">$2</a>');
+                       data = data.replace(/\[url\=([^'"\]]+)](.+?)\[\/url]/gi, '<a href="$1">$2</a>');
+                       
+                       // [email]
+                       data = data.replace(/\[email\]([^"]+?)\[\/email]/gi, '<a href="mailto:$1">$1</a>');
+                       data = data.replace(/\[email\=([^"\]]+)](.+?)\[\/email]/gi, '<a href="mailto:$1">$2</a>');
+                       
+                       // [b]
+                       data = data.replace(/\[b\](.*?)\[\/b]/gi, '<b>$1</b>');
+                       
+                       // [i]
+                       data = data.replace(/\[i\](.*?)\[\/i]/gi, '<i>$1</i>');
+                       
+                       // [u]
+                       data = data.replace(/\[u\](.*?)\[\/u]/gi, '<u>$1</u>');
+                       
+                       // [s]
+                       data = data.replace(/\[s\](.*?)\[\/s]/gi, '<strike>$1</strike>');
+                       
+                       // [sub]
+                       data = data.replace(/\[sub\](.*?)\[\/sub]/gi, '<sub>$1</sub>');
+                       
+                       // [sup]
+                       data = data.replace(/\[sup\](.*?)\[\/sup]/gi, '<sup>$1</sup>');
                                
-                               $foundQuotes = true;
+                       // [img]
+                       data = data.replace(/\[img\]([^"]+?)\[\/img\]/gi,'<img src="$1" />');
+                       data = data.replace(/\[img='?([^"]*?)'?,'?(left|right)'?\]\[\/img\]/gi,'<img src="$1" style="float: $2" />');
+                       data = data.replace(/\[img='?([^"]*?)'?\]\[\/img\]/gi,'<img src="$1" />');
+                       
+                       // [size]
+                       data = data.replace(/\[size=(\d+)\](.*?)\[\/size\]/gi,'<span style="font-size: $1pt">$2</span>');
+                       
+                       // [color]
+                       data = data.replace(/\[color=([#a-z0-9]*?)\](.*?)\[\/color\]/gi,'<span style="color: $1">$2</span>');
+                       
+                       // [font]
+                       data = data.replace(/\[font='?([a-z,\- ]*?)'?\](.*?)\[\/font\]/gi,'<span style="font-family: $1">$2</span>');
+                       
+                       // [align]
+                       data = data.replace(/\[align=(left|right|center|justify)\](.*?)\[\/align\]/gi,'<div style="text-align: $1">$2</div>');
+                       
+                       // [*]
+                       data = data.replace(/\[\*\](.*?)(?=\[\*\]|\[\/list\])/gi,'<li>$1</li>');
+                       
+                       // [list]
+                       data = data.replace(/\[list\]/gi, '<ul>');
+                       data = data.replace(/\[list=1\]/gi, '<ul style="list-style-type: decimal">');
+                       data = data.replace(/\[list=a\]/gi, '<ul style="list-style-type: lower-latin">');
+                       data = data.replace(/\[list=(none|circle|square|disc|decimal|lower-roman|upper-roman|decimal-leading-zero|lower-greek|lower-latin|upper-latin|armenian|georgian)\]/gi, '<ul style="list-style-type: $1">');
+                       data = data.replace(/\[\/list]/gi, '</ul>');
+                       
+                       // trim whitespaces within [table]
+                       data = data.replace(/\[table\]([\S\s]*?)\[\/table\]/gi, function(match, p1) {
+                               return '[table]' + $.trim(p1) + '[/table]';
+                       });
+                       
+                       // [table]
+                       data = data.replace(/\[table\]/gi, '<table border="1" cellspacing="1" cellpadding="1" style="width: 500px;">');
+                       data = data.replace(/\[\/table\]/gi, '</table>');
+                       // [tr]
+                       data = data.replace(/\[tr\]/gi, '<tr>');
+                       data = data.replace(/\[\/tr\]/gi, '</tr>');
+                       // [td]
+                       data = data.replace(/\[td\]/gi, '<td>');
+                       data = data.replace(/\[\/td\]/gi, '</td>');
+                       
+                       // trim whitespaces within <td>
+                       data = data.replace(/<td>([\S\s]*?)<\/td>/gi, function(match, p1) {
+                               var $tdContent = $.trim(p1);
+                               if (!$tdContent.length) {
+                                       // unicode zero-width space
+                                       $tdContent = '&#8203;';
+                               }
                                
-                               return '@@' + $key + '@@';
+                               return '<td>' + $tdContent + '</td>';
                        });
                        
-                       // we found no more quotes
-                       if (!$foundQuotes) {
-                               break;
+                       // attachments
+                       var $attachmentUrl = this.wutil.getOption('wAttachmentUrl');
+                       if ($attachmentUrl) {
+                               var $imageAttachmentIDs = this.wbbcode._getImageAttachmentIDs();
+                               
+                               data = data.replace(/\[attach=(\d+)(,[^\]]*)?\]\[\/attach\]/g, function(match, attachmentID, alignment) {
+                                       attachmentID = parseInt(attachmentID);
+                                       
+                                       if (WCF.inArray(attachmentID, $imageAttachmentIDs)) {
+                                               var $style = '';
+                                               if (alignment) {
+                                                       if (alignment.match(/^,'?(left|right)'?/)) {
+                                                               $style = ' style="float: ' + RegExp.$1 + '"';
+                                                       }
+                                               }
+                                               
+                                               return '<img src="' + $attachmentUrl.replace(/987654321/, attachmentID) + '" class="redactorEmbeddedAttachment" data-attachment-id="' + attachmentID + '"' + $style + ' />';
+                                       }
+                                       
+                                       return match;
+                               });
+                       }
+                       
+                       // smileys
+                       for (var smileyCode in __REDACTOR_SMILIES) {
+                               var $smileyCode = smileyCode.replace(/</g, '&lt;').replace(/>/g, '&gt;');
+                               var regExp = new RegExp('(\\s|>|^)' + WCF.String.escapeRegExp($smileyCode) + '(?=\\s|<|$)', 'gi');
+                               data = data.replace(regExp, '$1<img src="' + __REDACTOR_SMILIES[smileyCode] + '" class="smiley" alt="' + $smileyCode + '" />');
                        }
-               }
-               
-               // add newlines before and after [quote] tags
-               data = data.replace(/(\[quote.*?\])/gi, '$1\n');
-               data = data.replace(/(\[\/quote\])/gi, '\n$1');
-               
-               // drop trailing line breaks
-               data = data.replace(/\n*$/, '');
-               
-               // convert line breaks into <p></p> or empty lines to <p><br></p>
-               var $tmp = data.split("\n");
-               data = '';
-               for (var $i = 0, $length = $tmp.length; $i < $length; $i++) {
-                       var $line = $.trim($tmp[$i]);
-                       
-                       if ($line.match(/^<([a-z]+)/)) {
-                               data += $line;
+                       
+                       // remove "javascript:"
+                       data = data.replace(/(javascript):/gi, '$1<span></span>:');
+                       
+                       // unify line breaks
+                       data = data.replace(/(\r|\r\n)/g, "\n");
+                       
+                       // extract [quote] bbcodes to prevent line break handling below
+                       var $cachedQuotes = [ ];
+                       var $knownQuotes = [ ];
+                       for (var $i = 0; $i < 5; $i++) {
+                               var $foundQuotes = false;
                                
-                               if (!this.opts.rBlockTest.test(RegExp.$1.toUpperCase())) {
-                                       data += '<br>';
+                               data = data.replace(/\[quote.*?\]((?!\[quote)[\s\S])*?\[\/quote\]/gi, function(match) {
+                                       var $key = match.hashCode();
+                                       $cachedQuotes.push({
+                                               hashCode: $key,
+                                               content: match.replace(/\$/g, '$$$$')
+                                       });
+                                       $knownQuotes.push($key.toString());
+                                       
+                                       $foundQuotes = true;
+                                       
+                                       return '@@' + $key + '@@';
+                               });
+                               
+                               // we found no more quotes
+                               if (!$foundQuotes) {
+                                       break;
                                }
                        }
-                       else {
-                               if (!$line) {
-                                       $line = '<br>';
+                       
+                       // add newlines before and after [quote] tags
+                       data = data.replace(/(\[quote.*?\])/gi, '$1\n');
+                       data = data.replace(/(\[\/quote\])/gi, '\n$1');
+                       
+                       // drop trailing line breaks
+                       data = data.replace(/\n*$/, '');
+                       
+                       // convert line breaks into <p></p> or empty lines to <p><br></p>
+                       var $tmp = data.split("\n");
+                       console.debug($tmp);
+                       data = '';
+                       for (var $i = 0, $length = $tmp.length; $i < $length; $i++) {
+                               var $line = $.trim($tmp[$i]);
+                               
+                               if ($line.match(/^<([a-z]+)/)) {
+                                       if (!this.reIsBlock.test(RegExp.$1.toUpperCase())) {
+                                               $line = '<p>' + $line + '</p>';
+                                       }
+                                       
+                                       data += $line;
                                }
-                               else if ($line.match(/^@@([0-9\-]+)@@$/)) {
-                                       if (WCF.inArray(RegExp.$1, $knownQuotes)) {
-                                               // prevent quote being nested inside a <p> block
-                                               data += $line;
-                                               continue;
+                               else {
+                                       if (!$line) {
+                                               $line = '<br>';
                                        }
+                                       else if ($line.match(/^@@([0-9\-]+)@@$/)) {
+                                               if (WCF.inArray(RegExp.$1, $knownQuotes)) {
+                                                       // prevent quote being nested inside a <p> block
+                                                       data += $line;
+                                                       continue;
+                                               }
+                                       }
+                                       
+                                       data += '<p>' + $line + '</p>';
                                }
-                               
-                               data += '<p>' + $line + '</p>';
                        }
-               }
-               
-               // insert codes
-               if ($.getLength($cachedCodes)) {
-                       for (var $key in $cachedCodes) {
-                               var $regex = new RegExp('@@' + $key + '@@', 'g');
-                               data = data.replace($regex, $cachedCodes[$key]);
+                       
+                       console.debug(data);
+                       
+                       // insert codes
+                       if ($.getLength($cachedCodes)) {
+                               for (var $key in $cachedCodes) {
+                                       var $regex = new RegExp('@@' + $key + '@@', 'g');
+                                       data = data.replace($regex, $cachedCodes[$key]);
+                               }
+                               
+                               // [tt]
+                               data = data.replace(/\[tt\](.*?)\[\/tt\]/gi, '<span class="inlineCode">$1</span>');
                        }
                        
-                       // [tt]
-                       data = data.replace(/\[tt\](.*?)\[\/tt\]/gi, '<span class="inlineCode">$1</span>');
-               }
-               
-               // preserve leading whitespaces in [code] tags
-               data = data.replace(/\[code\][\S\s]*?\[\/code\]/, '<pre>$&</pre>');
-               
-               // insert quotes
-               if ($cachedQuotes.length) {
-                       // [quote]
-                       var $unquoteString = function(quotedString) {
-                               return quotedString.replace(/^['"]/, '').replace(/['"]$/, '');
-                       };
+                       // preserve leading whitespaces in [code] tags
+                       data = data.replace(/\[code\][\S\s]*?\[\/code\]/, '<pre>$&</pre>');
                        
-                       var self = this;
-                       var $transformQuote = function(quote) {
-                               return quote.replace(/\[quote([^\]]+)?\]([\S\s]*)\[\/quote\]?/gi, $.proxy(function(match, attributes, innerContent) {
-                                       var $author = '';
-                                       var $link = '';
-                                       
-                                       if (attributes) {
-                                               attributes = attributes.substr(1);
-                                               attributes = attributes.split(',');
+                       // insert quotes
+                       if ($cachedQuotes.length) {
+                               // [quote]
+                               var $unquoteString = function(quotedString) {
+                                       return quotedString.replace(/^['"]/, '').replace(/['"]$/, '');
+                               };
+                               
+                               var self = this;
+                               var $transformQuote = function(quote) {
+                                       return quote.replace(/\[quote([^\]]+)?\]([\S\s]*)\[\/quote\]?/gi, function(match, attributes, innerContent) {
+                                               var $author = '';
+                                               var $link = '';
                                                
-                                               switch (attributes.length) {
-                                                       case 1:
-                                                               $author = attributes[0];
-                                                       break;
+                                               if (attributes) {
+                                                       attributes = attributes.substr(1);
+                                                       attributes = attributes.split(',');
+                                                       
+                                                       switch (attributes.length) {
+                                                               case 1:
+                                                                       $author = attributes[0];
+                                                               break;
+                                                               
+                                                               case 2:
+                                                                       $author = attributes[0];
+                                                                       $link = attributes[1];
+                                                               break;
+                                                       }
                                                        
-                                                       case 2:
-                                                               $author = attributes[0];
-                                                               $link = attributes[1];
-                                                       break;
+                                                       $author = WCF.String.escapeHTML($unquoteString($.trim($author)));
+                                                       $link = WCF.String.escapeHTML($unquoteString($.trim($link)));
                                                }
                                                
-                                               $author = WCF.String.escapeHTML($unquoteString($.trim($author)));
-                                               $link = WCF.String.escapeHTML($unquoteString($.trim($link)));
-                                       }
-                                       
-                                       var $quote = '<blockquote class="quoteBox" cite="' + $link + '" data-author="' + $author + '">'
-                                               + '<div class="container containerPadding">'
-                                                       + '<header contenteditable="false">'
-                                                               + '<h3>'
-                                                                       + self._buildQuoteHeader($author, $link)
-                                                               + '</h3>'
-                                                               + '<a class="redactorQuoteEdit"></a>'
-                                                       + '</header>';
-                                       
-                                       innerContent = $.trim(innerContent);
-                                       var $tmp = '';
-                                       
-                                       if (innerContent.length) {
-                                               var $lines = innerContent.split('\n');
-                                               for (var $i = 0; $i < $lines.length; $i++) {
-                                                       var $line = $lines[$i];
-                                                       if ($line.length === 0) {
-                                                               $line = self.opts.invisibleSpace;
-                                                       }
-                                                       else if ($line.match(/^@@([0-9\-]+)@@$/)) {
-                                                               if (WCF.inArray(RegExp.$1, $knownQuotes)) {
-                                                                       // prevent quote being nested inside a <div> block
-                                                                       $tmp += $line;
-                                                                       continue;
+                                               var $quote = '<blockquote class="quoteBox" cite="' + $link + '" data-author="' + $author + '">'
+                                                       + '<div class="container containerPadding">'
+                                                               + '<header contenteditable="false">'
+                                                                       + '<h3>'
+                                                                               + self.wbbcode._buildQuoteHeader($author, $link)
+                                                                       + '</h3>'
+                                                                       + '<a class="redactorQuoteEdit"></a>'
+                                                               + '</header>';
+                                               
+                                               innerContent = $.trim(innerContent);
+                                               var $tmp = '';
+                                               
+                                               if (innerContent.length) {
+                                                       var $lines = innerContent.split('\n');
+                                                       for (var $i = 0; $i < $lines.length; $i++) {
+                                                               var $line = $lines[$i];
+                                                               if ($line.length === 0) {
+                                                                       $line = self.opts.invisibleSpace;
                                                                }
+                                                               else if ($line.match(/^@@([0-9\-]+)@@$/)) {
+                                                                       if (WCF.inArray(RegExp.$1, $knownQuotes)) {
+                                                                               // prevent quote being nested inside a <div> block
+                                                                               $tmp += $line;
+                                                                               continue;
+                                                                       }
+                                                               }
+                                                               
+                                                               $tmp += '<div>' + $line + '</div>';
                                                        }
-                                                       
-                                                       $tmp += '<div>' + $line + '</div>';
                                                }
-                                       }
-                                       else {
-                                               $tmp = '<div>' + self.opts.invisibleSpace + '</div>';
-                                       }
-                                       
-                                       $quote += $tmp;
-                                       $quote += '</blockquote>';
-                                       
-                                       return $quote;
-                               }, this));
+                                               else {
+                                                       $tmp = '<div>wcf' + self.opts.invisibleSpace + '</div>';
+                                               }
+                                               
+                                               $quote += $tmp;
+                                               $quote += '</div></blockquote>';
+                                               
+                                               return $quote;
+                                       });
+                               };
+                               
+                               // reinsert quotes in reverse order, adding the most outer quotes first
+                               for (var $i = $cachedQuotes.length - 1; $i >= 0; $i--) {
+                                       var $cachedQuote = $cachedQuotes[$i];
+                                       var $regex = new RegExp('@@' + $cachedQuote.hashCode + '@@', 'g');
+                                       data = data.replace($regex, $transformQuote($cachedQuote.content));
+                               }
+                       }
+                       
+                       WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'afterConvertToHtml', { data: data });
+                       
+                       return data;
+               },
+               
+               /**
+                * Converts certain HTML elements prior to paste in order to preserve formattings.
+                * 
+                * @param       string          html
+                * @return      string
+                */
+               _pasteBeforeCallback: function(html) {
+                       var $levels = {
+                               1: 24,
+                               2: 22,
+                               3: 18,
+                               4: 14,
+                               5: 12,
+                               6: 10
+                       };
+                       
+                       // replace <h1> ... </h6> tags
+                       html = html.replace(/<h([1-6])[^>]+>/g, function(match, level) {
+                               return '[size=' + $levels[level] + ']';
+                       });
+                       html = html.replace(/<\/h[1-6]>/g, '[/size]');
+                       
+                       // convert block-level elements
+                       html = html.replace(/<(article|header)[^>]+>/g, '<div>');
+                       html = html.replace(/<\/(article|header)>/g, '</div>');
+                       
+                       // replace nested elements e.g. <div><p>...</p></div>
+                       html = html.replace(/<(div|p)([^>]+)?><(div|p)([^>]+)?>/g, '<p>');
+                       html = html.replace(/<\/(div|p)><\/(div|p)>/g, '</p>');
+                       html = html.replace(/<(div|p)><br><\/(div|p)>/g, '<p>');
+                       
+                       WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'beforePaste', { html: html });
+                       
+                       return html;
+               },
+               
+               /**
+                * Restores and fixes formatting before inserting pasted HTML into the editor.
+                * 
+                * @param       string          html
+                * @return      string
+                */
+               _pasteCallback: function(html) {
+                       // replace <p>...</p> with <p>...</p><p><br></p>
+                       html = html.replace(/<p>([\s\S]*?)<\/p>/g, function(match, content) {
+                               if (content.match(/<br( \/)?>$/)) {
+                                       return match;
+                               }
+                               
+                               return '<p>' + content + '</p><p><br></p>';
+                       });
+                       
+                       // restore font size
+                       html = html.replace(/\[size=(\d+)\]/g, '<p><span style="font-size: $1pt">');
+                       html = html.replace(/\[\/size\]/g, '</span></p>');
+                       
+                       WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'afterPaste', { html: html });
+                       
+                       return html;
+                       
+                       // TODO
+                       
+                       // handle pasting of images in Firefox
+                       html = html.replace(/<img([^>]+)>/g, function(match, content) {
+                               match = match.replace(/data-mozilla-paste-image="0"/, 'data-mozilla-paste-image="0" style="display:none"');
+                               return match;
+                       });
+                       
+                       
+                       
+                       return html;
+               },
+               
+               /**
+                * Inserts an attachment with live preview.
+                * 
+                * @param       integer         attachmentID
+                */
+               insertAttachment: function(attachmentID) {
+                       attachmentID = parseInt(attachmentID);
+                       var $attachmentUrl = this.getOption('wAttachmentUrl');
+                       var $bbcode = '[attach=' + attachmentID + '][/attach]';
+                       
+                       var $imageAttachmentIDs = this.wbbcode._getImageAttachmentIDs();
+                       
+                       if ($attachmentUrl && WCF.inArray(attachmentID, $imageAttachmentIDs)) {
+                               this.insertDynamic(
+                                       '<img src="' + $attachmentUrl.replace(/987654321/, attachmentID) + '" class="redactorEmbeddedAttachment" data-attachment-id="' + attachmentID + '" />',
+                                       $bbcode
+                               );
+                       }
+                       else {
+                               this.insertDynamic($bbcode);
+                       }
+               },
+               
+               /**
+                * Returns a list of attachments representing an image.
+                * 
+                * @return      array<integer>
+                */
+               _getImageAttachmentIDs: function() {
+                       // WCF.Attachment.Upload may have no been initialized yet, fallback to static data
+                       var $imageAttachmentIDs = this.wutil.getOption('wAttachmentImageIDs') || [ ];
+                       if ($imageAttachmentIDs.length) {
+                               delete this.opts.wAttachmentImageIDs;
+                               
+                               return $imageAttachmentIDs;
+                       }
+                       
+                       var $data = {
+                               imageAttachmentIDs: [ ]
                        };
+                       WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'getImageAttachments_' + this.$textarea.wcfIdentify(), $data);
                        
-                       // reinsert quotes in reverse order, adding the most outer quotes first
-                       for (var $i = $cachedQuotes.length - 1; $i >= 0; $i--) {
-                               var $cachedQuote = $cachedQuotes[$i];
-                               var $regex = new RegExp('@@' + $cachedQuote.hashCode + '@@', 'g');
-                               data = data.replace($regex, $transformQuote($cachedQuote.content));
+                       return $data.imageAttachmentIDs;
+               },
+               
+               /**
+                * Handles up/down/delete/backspace key for quote boxes.
+                * 
+                * @param       object          data
+                */
+               _keydownCallback: function(data) {
+                       switch (data.event.which) {
+                               case $.ui.keyCode.BACKSPACE:
+                               case $.ui.keyCode.DELETE:
+                               case $.ui.keyCode.DOWN:
+                               case $.ui.keyCode.ENTER:
+                               case $.ui.keyCode.TAB:
+                               case $.ui.keyCode.UP:
+                                       // handle keys
+                               break;
+                               
+                               default:
+                                       return;
+                               break;
                        }
-               }
-               
-               WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'afterConvertToHtml', { data: data });
-               
-               return data;
-       },
-       
-       /**
-        * Converts certain HTML elements prior to paste in order to preserve formattings.
-        * 
-        * @param       string          html
-        * @return      string
-        */
-       _wPasteBeforeCallback: function(html) {
-               var $levels = {
-                       1: 24,
-                       2: 22,
-                       3: 18,
-                       4: 14,
-                       5: 12,
-                       6: 10
-               };
-               
-               // replace <h1> ... </h6> tags
-               html = html.replace(/<h([1-6])[^>]+>/g, function(match, level) {
-                       return '[size=' + $levels[level] + ']';
-               });
-               html = html.replace(/<\/h[1-6]>/g, '[/size]');
-               
-               // convert block-level elements
-               html = html.replace(/<(article|header)[^>]+>/g, '<div>');
-               html = html.replace(/<\/(article|header)>/g, '</div>');
-               
-               // replace nested elements e.g. <div><p>...</p></div>
-               html = html.replace(/<(div|p)([^>]+)?><(div|p)([^>]+)?>/g, '<p>');
-               html = html.replace(/<\/(div|p)><\/(div|p)>/g, '</p>@@@wcf_break@@@');
-               html = html.replace(/<(div|p)><br><\/(div|p)>/g, '@@@wcf_break@@@');
-               
-               WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'beforePaste', { html: html });
-               
-               return html;
-       },
-       
-       /**
-        * Restores and fixes formatting before inserting pasted HTML into the editor.
-        * 
-        * @param       string          html
-        * @return      string
-        */
-       _wPasteAfterCallback: function(html) {
-               // replace <p /> with <p>...<br></p>
-               html = html.replace(/<p>([\s\S]*?)<\/p>/g, '<p>$1<br></p>');
-               
-               // drop <header />
-               html = html.replace(/<header[^>]*>/g, '');
-               html = html.replace(/<\/header>/g, '');
-               
-               html = html.replace(/<div>(.*?)<\/div>/g, '<p>$1</p>');
-               
-               // drop lonely divs
-               html = html.replace(/<\/?div>/g, '');
-               
-               html = html.replace(/@@@wcf_break@@@/g, '<p><br></p>');
-               
-               // drop lonely <p> opening tags
-               html = html.replace(/<p><p>/g, '<p>');
-               
-               // restore font size
-               html = html.replace(/\[size=(\d+)\]/g, '<p><br></p><p><inline style="font-size: $1pt">');
-               html = html.replace(/\[\/size\]/g, '</inline></p><p><br></p>');
-               
-               // handle pasting of images in Firefox
-               html = html.replace(/<img([^>]+)>/g, function(match, content) {
-                       match = match.replace(/data-mozilla-paste-image="0"/, 'data-mozilla-paste-image="0" style="display:none"');
-                       return match;
-               });
-               
-               WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'afterPaste', { html: html });
-               
-               return html;
-       },
-       
-       /**
-        * Inserts an attachment with live preview.
-        * 
-        * @param       integer         attachmentID
-        */
-       insertAttachment: function(attachmentID) {
-               attachmentID = parseInt(attachmentID);
-               var $attachmentUrl = this.getOption('wAttachmentUrl');
-               var $bbcode = '[attach=' + attachmentID + '][/attach]';
-               
-               var $imageAttachmentIDs = this._getImageAttachmentIDs();
-               
-               if ($attachmentUrl && WCF.inArray(attachmentID, $imageAttachmentIDs)) {
-                       this.insertDynamic(
-                               '<img src="' + $attachmentUrl.replace(/987654321/, attachmentID) + '" class="redactorEmbeddedAttachment" data-attachment-id="' + attachmentID + '" />',
-                               $bbcode
-                       );
-               }
-               else {
-                       this.insertDynamic($bbcode);
-               }
-       },
-       
-       /**
-        * Returns a list of attachments representing an image.
-        * 
-        * @return      array<integer>
-        */
-       _getImageAttachmentIDs: function() {
-               // WCF.Attachment.Upload may have no been initialized yet, fallback to static data
-               var $imageAttachmentIDs = this.getOption('wAttachmentImageIDs') || [ ];
-               if ($imageAttachmentIDs.length) {
-                       delete this.opts.wAttachmentImageIDs;
-                       
-                       return $imageAttachmentIDs;
-               }
-               
-               var $data = {
-                       imageAttachmentIDs: [ ]
-               };
-               WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'getImageAttachments_' + this.$source.wcfIdentify(), $data);
-               
-               return $data.imageAttachmentIDs;
-       },
-       
-       /**
-        * Handles up/down/delete/backspace key for quote boxes.
-        * 
-        * @param       object          data
-        */
-       _wKeydownCallback: function(data) {
-               switch (data.event.which) {
-                       case $.ui.keyCode.BACKSPACE:
-                       case $.ui.keyCode.DELETE:
-                       case $.ui.keyCode.DOWN:
-                       case $.ui.keyCode.UP:
-                               // handle keys
-                       break;
-                       
-                       default:
-                               return;
-                       break;
-               }
-               
-               var $current = $(this.getCurrent());
-               var $parent = this.getParent();
-               $parent = ($parent) ? $($parent) : $parent;
-               var $quote = ($parent) ? $parent.closest('blockquote.quoteBox', this.$editor.get()[0]) : { length: 0 };
-               
-               switch (data.event.which) {
-                       // backspace key
-                       case $.ui.keyCode.BACKSPACE:
-                               if (this.isCaret()) {
-                                       if ($quote.length) {
-                                               // check if quote is empty
-                                               var $isEmpty = true;
-                                               $quote.find('div > div').each(function() {
-                                                       if ($(this).text().replace(/\u200B/, '').length) {
-                                                               $isEmpty = false;
-                                                               return false;
+                       
+                       this.selection.get();
+                       var $current = $(this.selection.getCurrent());
+                       var $parent = this.selection.getParent();
+                       $parent = ($parent) ? $($parent) : $parent;
+                       var $quote = ($parent) ? $parent.closest('blockquote.quoteBox', this.$editor.get()[0]) : { length: 0 };
+                       
+                       switch (data.event.which) {
+                               // backspace key
+                               case $.ui.keyCode.BACKSPACE:
+                                       if (this.wutil.isCaret()) {
+                                               if ($quote.length) {
+                                                       // check if quote is empty
+                                                       var $isEmpty = true;
+                                                       $quote.find('div > div').each(function() {
+                                                               if ($(this).text().replace(/\u200B/, '').length) {
+                                                                       $isEmpty = false;
+                                                                       return false;
+                                                               }
+                                                       });
+                                                       
+                                                       if ($isEmpty) {
+                                                               // expand selection and prevent delete
+                                                               var $selection = window.getSelection();
+                                                               if ($selection.rangeCount) $selection.removeAllRanges();
+                                                               
+                                                               var $quoteRange = document.createRange();
+                                                               $quoteRange.selectNode($quote[0]);
+                                                               $selection.addRange($quoteRange);
+                                                               
+                                                               data.cancel = true;
                                                        }
-                                               });
-                                               
-                                               if ($isEmpty) {
+                                               }
+                                       }
+                               break;
+                               
+                               // delete key
+                               case $.ui.keyCode.DELETE:
+                                       if (this.wutil.isCaret()) {
+                                               if (this.wutil.isEndOfElement($current[0]) && $current.next('blockquote').length) {
                                                        // expand selection and prevent delete
                                                        var $selection = window.getSelection();
                                                        if ($selection.rangeCount) $selection.removeAllRanges();
                                                        
                                                        var $quoteRange = document.createRange();
-                                                       $quoteRange.selectNode($quote[0]);
+                                                       $quoteRange.selectNode($current.next()[0]);
                                                        $selection.addRange($quoteRange);
                                                        
                                                        data.cancel = true;
                                                }
                                        }
-                               }
-                               else {
-                                       // check if selection contains a quote, turn on buffer if true
-                                       var $contents = this.getRange().cloneContents();
-                                       if (this.containsTag($contents, 'BLOCKQUOTE')) {
-                                               this.bufferSet();
-                                       }
-                               }
-                       break;
-                       
-                       // delete key
-                       case $.ui.keyCode.DELETE:
-                               if (this.isCaret()) {
-                                       if (this.isEndOfElement($current[0]) && $current.next('blockquote').length) {
-                                               // expand selection and prevent delete
-                                               var $selection = window.getSelection();
-                                               if ($selection.rangeCount) $selection.removeAllRanges();
-                                               
-                                               var $quoteRange = document.createRange();
-                                               $quoteRange.selectNode($current.next()[0]);
-                                               $selection.addRange($quoteRange);
-                                               
-                                               data.cancel = true;
-                                       }
-                               }
-                               else {
-                                       // check if selection contains a quote, turn on buffer if true
-                                       var $contents = this.getRange().cloneContents();
-                                       if (this.containsTag($contents, 'BLOCKQUOTE')) {
-                                               this.bufferSet();
-                                       }
-                               }
-                       break;
-                       
-                       // arrow down
-                       case $.ui.keyCode.DOWN:
-                               if ($current.next('blockquote.quoteBox').length) {
-                                       this.selectionStart($current.next().find('> div > div:first'));
-                                       
-                                       data.cancel = true;
-                               }
-                               else if ($parent) {
-                                       if ($parent.next('blockquote.quoteBox').length) {
-                                               this.selectionStart($parent.next().find('> div > div:first'));
+                               break;
+                               
+                               // arrow down
+                               case $.ui.keyCode.DOWN:
+                                       if ($current.next('blockquote').length) {
+                                               this.caret.setStart($current.next().find('> div > div:first'));
                                                
                                                data.cancel = true;
                                        }
-                                       else if ($quote.length) {
-                                               var $container = $current.closest('div', $quote[0]);
-                                               if (!$container.next().length) {
-                                                       // check if there is an element after the quote
-                                                       if ($quote.next().length) {
-                                                               this.setSelectionStart($quote.next());
-                                                       }
-                                                       else {
-                                                               this.insertingAfterLastElement($quote);
-                                                       }
+                                       else if ($parent) {
+                                               if ($parent.next('blockquote').length) {
+                                                       this.caret.setStart($parent.next().find('> div > div:first'));
                                                        
                                                        data.cancel = true;
                                                }
-                                       } 
-                               }
-                       break;
-                       
-                       // arrow up
-                       case $.ui.keyCode.UP:
-                               if (!$parent || !$quote.length) {
-                                       return;
-                               }
+                                               else if ($quote.length) {
+                                                       var $container = $current.closest('div', $quote[0]);
+                                                       if (!$container.next().length) {
+                                                               // check if there is an element after the quote
+                                                               if ($quote.next().length) {
+                                                                       this.caret.setStart($quote.next());
+                                                               }
+                                                               else {
+                                                                       this.wutil.setCaretAfter($quote);
+                                                               }
+                                                               
+                                                               data.cancel = true;
+                                                       }
+                                               } 
+                                       }
+                               break;
                                
-                               var $container = $current.closest('div', $quote[0]);
-                               var $prev = $container.prev();
-                               if ($prev[0].tagName === 'DIV') {
-                                       return;
-                               }
-                               else if ($prev[0].tagName === 'BLOCKQUOTE') {
-                                       // set focus to quote text rather than the element itself
-                                       return;
-                                       //this.selectionEnd($prev.find('> div > div:last'));
-                               }
+                               // enter
+                               case $.ui.keyCode.ENTER:
+                                       if ($quote.length) {
+                                               // prevent Redactor's default behavior for <blockquote>
+                                               this.keydown.blockquote = false;
+                                               this.keydown.enterWithinBlockquote = true;
+                                       }
+                               break;
                                
-                               var $previousElement = $quote.prev();
-                               if ($previousElement.length === 0) {
-                                       var $node = $(this.opts.emptyHtml);
-                                       $node.insertBefore($quote);
-                                       this.selectionStart($node);
-                               }
-                               else {
-                                       if ($previousElement[0].tagName === 'BLOCKQUOTE') {
+                               // tab
+                               case $.ui.keyCode.TAB:
+                                       var $block = this.selection.getBlock();
+                                       if (!$block || $block.tagName !== 'LI') {
+                                               // disallow indent/outdent outside of lists
+                                               data.cancel = true;
+                                       }
+                               break;
+                               
+                               // arrow up
+                               case $.ui.keyCode.UP:
+                                       if (!$parent || !$quote.length) {
+                                               return;
+                                       }
+                                       
+                                       var $container = $current.closest('div', $quote[0]);
+                                       var $prev = $container.prev();
+                                       if ($prev[0].tagName === 'DIV') {
+                                               return;
+                                       }
+                                       else if ($prev[0].tagName === 'BLOCKQUOTE') {
+                                               // TODO
                                                // set focus to quote text rather than the element itself
-                                               this.selectionEnd($previousElement.find('> div > div:last'));
+                                               return;
+                                               //this.selectionEnd($prev.find('> div > div:last'));
+                                       }
+                                       
+                                       var $previousElement = $quote.prev();
+                                       if ($previousElement.length === 0) {
+                                               this.wutil.setCaretBefore($quote);
                                        }
                                        else {
-                                               // focus is wrong if the previous element is empty (e.g. only a newline present)
-                                               if ($.trim($previousElement.html()) == '') {
-                                                       $previousElement.html(this.opts.invisibleSpace);
+                                               if ($previousElement[0].tagName === 'BLOCKQUOTE') {
+                                                       // set focus to quote text rather than the element itself
+                                                       this.caret.sendEnd($previousElement.find('> div > div:last'));
+                                               }
+                                               else {
+                                                       // focus is wrong if the previous element is empty (e.g. only a newline present)
+                                                       if ($.trim($previousElement.html()) == '') {
+                                                               $previousElement.html(this.opts.invisibleSpace);
+                                                       }
+                                                       
+                                                       this.caret.setEnd($previousElement);
                                                }
-                                               
-                                               this.selectionEnd($previousElement);
                                        }
+                                       
+                                       data.cancel = true;
+                               break;
+                       }
+               },
+               
+               /**
+                * Handles quote deletion.
+                * 
+                * @param       object          data
+                */
+               _keyupCallback: function(data) {
+                       if (data.event.which !== $.ui.keyCode.BACKSPACE && data.event.which !== $.ui.keyCode.DELETE) {
+                               return;
+                       }
+                       
+                       // check for empty <blockquote
+                       this.$editor.find('blockquote').each(function(index, blockquote) {
+                               var $blockquote = $(blockquote);
+                               if (!$blockquote.find('> div > header').length) {
+                                       $blockquote.remove();
                                }
+                       });
+               },
+               
+               /**
+                * Initializes source editing for quotes.
+                */
+               _observeQuotes: function() {
+                       this.$editor.find('.redactorQuoteEdit:not(.jsRedactorQuoteEdit)').addClass('jsRedactorQuoteEdit').click($.proxy(this.wbbcode._observeQuotesClick, this));
+               },
+               
+               /**
+                * Handles clicks on the 'edit quote' link.
+                * 
+                * @param       object          event
+                */
+               _observeQuotesClick: function(event) {
+                       var $header = $(event.currentTarget).closest('header');
+                       var $tooltip = $('<span class="redactor-link-tooltip" />');
+                       
+                       $('<a href="#">' + WCF.Language.get('wcf.bbcode.quote.edit') + '</a>').click($.proxy(function(e) {
+                               e.preventDefault();
                                
-                               data.cancel = true;
-                       break;
-               }
-       },
-       
-       /**
-        * Handles quote deletion.
-        * 
-        * @param       object          data
-        */
-       _wKeyupCallback: function(data) {
-               if (data.event.which !== $.ui.keyCode.BACKSPACE && data.event.which !== $.ui.keyCode.DELETE) {
-                       return;
-               }
-               
-               // check for empty <blockquote>
-               this.$editor.find('blockquote').each(function(index, blockquote) {
-                       var $blockquote = $(blockquote);
-                       if (!$blockquote.find('> div > header').length) {
-                               $blockquote.remove();
-                       }
-               });
-       },
-       
-       /**
-        * Initializes source editing for quotes.
-        */
-       _observeQuotes: function() {
-               this.$editor.find('.redactorQuoteEdit:not(.jsRedactorQuoteEdit)').addClass('jsRedactorQuoteEdit').click($.proxy(this._observeQuotesClick, this));
-       },
-       
-       /**
-        * Handles clicks on the 'edit quote' link.
-        * 
-        * @param       object          event
-        */
-       _observeQuotesClick: function(event) {
-               var $header = $(event.currentTarget).closest('header');
-               var $tooltip = $('<span class="redactor-link-tooltip" />');
-               
-               $('<a href="#">' + WCF.Language.get('wcf.bbcode.quote.edit') + '</a>').click($.proxy(function(e) {
-                       e.preventDefault();
+                               this._openQuoteEditOverlay($(event.currentTarget).closest('blockquote.quoteBox'), false);
+                               $('.redactor-link-tooltip').remove();
+                       }, this)).appendTo($tooltip);
+                       
+                       var $offset = $header.offset();
+                       $tooltip.css({
+                               left: $offset.left + 'px',
+                               top: ($offset.top + 20) + 'px'
+                       });
                        
-                       this._openQuoteEditOverlay($(event.currentTarget).closest('blockquote.quoteBox'), false);
                        $('.redactor-link-tooltip').remove();
-               }, this)).appendTo($tooltip);
-               
-               var $offset = $header.offset();
-               $tooltip.css({
-                       left: $offset.left + 'px',
-                       top: ($offset.top + 20) + 'px'
-               });
-               
-               $('.redactor-link-tooltip').remove();
-               $tooltip.appendTo(document.body);
-       },
-       
-       /**
-        * Opens the quote source edit dialog.
-        * 
-        * @param       jQuery          quote
-        * @param       boolean         insertQuote
-        */
-       _openQuoteEditOverlay: function(quote, insertQuote) {
-               if (insertQuote) {
-                       this.modalInit(WCF.Language.get('wcf.bbcode.quote.insert'), this.opts.modal_quote, 300, $.proxy(function() {
-                               $('#redactorEditQuote').click($.proxy(function() {
+                       $tooltip.appendTo(document.body);
+                       
+                       // prevent the cursor being placed in the quote header
+                       $('<div contenteditable="true" />').appendTo(document.body).focus().remove();
+               },
+               
+               /**
+                * Opens the quote source edit dialog.
+                * 
+                * @param       jQuery          quote
+                * @param       boolean         insertQuote
+                */
+               _openQuoteEditOverlay: function(quote, insertQuote) {
+                       this.modal.load('quote', WCF.Language.get('wcf.bbcode.quote.' + (insertQuote ? 'insert' : 'edit')), 400);
+                       
+                       var $button = this.modal.createActionButton(this.lang.get('save'));
+                       if (insertQuote) {
+                               $button.click($.proxy(function() {
                                        var $author = $('#redactorQuoteAuthor').val();
                                        var $link = WCF.String.escapeHTML($('#redactorQuoteLink').val());
                                        
-                                       this.insertQuoteBBCode($author, $link);
+                                       this.wbbcode.insertQuoteBBCode($author, $link);
                                        
-                                       this.modalClose();
+                                       this.modal.close();
                                }, this));
-                       }, this));
-               }
-               else {
-                       this.modalInit(WCF.Language.get('wcf.bbcode.quote.edit'), this.opts.modal_quote, 300, $.proxy(function() {
-                               if (!insertQuote) {
-                                       $('#redactorQuoteAuthor').val(quote.data('author'));
-                                       
-                                       // do not use prop() here, an empty cite attribute would yield the page URL instead
-                                       $('#redactorQuoteLink').val(WCF.String.unescapeHTML(quote.attr('cite')));
-                               }
+                       }
+                       else {
+                               $('#redactorQuoteAuthor').val(quote.data('author'));
+                               
+                               // do not use prop() here, an empty cite attribute would yield the page URL instead
+                               $('#redactorQuoteLink').val(WCF.String.unescapeHTML(quote.attr('cite')));
                                
-                               $('#redactorEditQuote').click($.proxy(function() {
+                               $button.click($.proxy(function() {
                                        var $author = $('#redactorQuoteAuthor').val();
                                        quote.data('author', $author);
                                        quote.attr('data-author', $author);
                                        quote.prop('cite', WCF.String.escapeHTML($('#redactorQuoteLink').val()));
                                        
-                                       this._updateQuoteHeader(quote);
+                                       this.wbbcode._updateQuoteHeader(quote);
                                        
                                        this.modalClose();
                                }, this));
-                       }, this));
-               }
-       },
-       
-       /**
-        * Updates the quote's source.
-        * 
-        * @param       jQuery          quote
-        */
-       _updateQuoteHeader: function(quote) {
-               var $author = quote.data('author');
-               var $link = quote.attr('cite');
-               if ($link) $link = WCF.String.escapeHTML($link);
-               
-               quote.find('> div > header > h3').empty().append(this._buildQuoteHeader($author, $link));       
-       },
-       
-       /**
-        * Inserts the quote BBCode.
-        * 
-        * @param       string          author
-        * @param       string          link
-        * @param       string          html
-        * @param       string          plainText
-        */
-       insertQuoteBBCode: function(author, link, html, plainText) {
-               var $bbcode = '[quote]';
-               if (author) {
-                       if (link) {
-                               $bbcode = "[quote='" + author + "','" + link + "']";
                        }
-                       else {
-                               $bbcode = "[quote='" + author + "']";
+                       
+                       this.modal.show();
+               },
+               
+               /**
+                * Updates the quote's source.
+                * 
+                * @param       jQuery          quote
+                */
+               _updateQuoteHeader: function(quote) {
+                       var $author = quote.data('author');
+                       var $link = quote.attr('cite');
+                       if ($link) $link = WCF.String.escapeHTML($link);
+                       
+                       quote.find('> div > header > h3').empty().append(this.wbbcode._buildQuoteHeader($author, $link));       
+               },
+               
+               /**
+                * Inserts the quote BBCode.
+                * 
+                * @param       string          author
+                * @param       string          link
+                * @param       string          html
+                * @param       string          plainText
+                */
+               insertQuoteBBCode: function(author, link, html, plainText) {
+                       var $bbcode = '[quote]';
+                       if (author) {
+                               if (link) {
+                                       $bbcode = "[quote='" + author + "','" + link + "']";
+                               }
+                               else {
+                                       $bbcode = "[quote='" + author + "']";
+                               }
                        }
-               }
-               
-               if (plainText) $bbcode += plainText;
-               $bbcode += '[/quote]';
-               
-               if (this.inWysiwygMode()) {
-                       $bbcode = this.convertToHtml($bbcode);
-                       this.insertHtml($bbcode);
                        
-                       this._observeQuotes();
+                       if (plainText) $bbcode += plainText;
+                       $bbcode += '[/quote]';
                        
-                       this.$toolbar.find('a.re-__wcf_quote').addClass('redactor_button_disabled');
-               }
-               else {
-                       this.insertAtCaret($bbcode);
-               }
-       },
-       
-       /**
-        * Builds the quote source HTML.
-        * 
-        * @param       string          author
-        * @param       string          link
-        * @return      string
-        */
-       _buildQuoteHeader: function(author, link) {
-               var $header = '';
-               // author is empty, check if link was provided and use it instead
-               if (!author && link) {
-                       author = link;
-                       link = '';
-               }
-               
-               if (author) {
-                       if (link) $header += '<a href="' + link + '">';
+                       if (this.wutil.inWysiwygMode()) {
+                               $bbcode = this.wbbcode.convertToHtml($bbcode);
+                               this.insert.html($bbcode, false);
+                               
+                               this.wbbcode._observeQuotes();
+                               
+                               this.$toolbar.find('a.re-__wcf_quote').addClass('redactor-button-disabled');
+                       }
+                       else {
+                               this.wutil.insertAtCaret($bbcode);
+                       }
+               },
+               
+               /**
+                * Builds the quote source HTML.
+                * 
+                * @param       string          author
+                * @param       string          link
+                * @return      string
+                */
+               _buildQuoteHeader: function(author, link) {
+                       var $header = '';
+                       // author is empty, check if link was provided and use it instead
+                       if (!author && link) {
+                               author = link;
+                               link = '';
+                       }
                        
-                       $header += WCF.Language.get('wcf.bbcode.quote.title.javascript', { quoteAuthor: WCF.String.unescapeHTML(author) });
+                       if (author) {
+                               if (link) $header += '<a href="' + link + '">';
+                               
+                               $header += WCF.Language.get('wcf.bbcode.quote.title.javascript', { quoteAuthor: WCF.String.unescapeHTML(author) });
+                               
+                               if (link) $header += '</a>';
+                       }
+                       else {
+                               $header = '<small>' + WCF.Language.get('wcf.bbcode.quote.title.clickToSet') + '</small>';
+                       }
                        
-                       if (link) $header += '</a>';
-               }
-               else {
-                       $header = '<small>' + WCF.Language.get('wcf.bbcode.quote.title.clickToSet') + '</small>';
-               }
+                       return $header;
+               },
                
-               return $header;
-       },
-       
-       _handleInsertQuote: function() {
-               this._openQuoteEditOverlay(null, true);
-       }
+               _handleInsertQuote: function() {
+                       this.wbbcode._openQuoteEditOverlay(null, true);
+               }
+       };
 };
index 38d53eefe31fe315af291c1c62a2dac11ae45e08..6347648d35696efa8c8a24780d66ace211442e78 100644 (file)
@@ -7,139 +7,170 @@ if (!RedactorPlugins) var RedactorPlugins = {};
  * @copyright  2001-2014 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  */
-RedactorPlugins.wbutton = {
-       /**
-        * list of button names and their associated bbcode tag
-        * @var object<string>
-        */
-       _bbcodes: { },
+RedactorPlugins.wbutton = function() {
+       "use strict";
        
-       /**
-        * Initializes the RedactorPlugins.wbutton plugin.
-        */
-       init: function() {
-               this._bbcodes = { };
+       return {
+               /**
+                * list of button names and their associated bbcode tag
+                * @var object<string>
+                */
+               _bbcodes: { },
                
-               for (var $i = 0, $length = __REDACTOR_BUTTONS.length; $i < $length; $i++) {
-                       this._addBBCodeButton(__REDACTOR_BUTTONS[$i]);
-               }
-               
-               // this list contains overrides for built-in buttons, if a button is not present
-               // Redactor's own icon will be used instead. This solves the problem of FontAwesome
-               // not providing an icon for everything we need (especially the core stuff)
-               var $faIcons = {
-                       'html': 'fa-square-o',
-                       'bold': 'fa-bold',
-                       'italic': 'fa-italic',
-                       'underline': 'fa-underline',
-                       'deleted': 'fa-strikethrough',
-                       'subscript': 'fa-subscript',
-                       'superscript': 'fa-superscript',
-                       'orderedlist': 'fa-list-ol',
-                       'unorderedlist': 'fa-list-ul',
-                       'outdent': 'fa-outdent',
-                       'indent': 'fa-indent',
-                       'link': 'fa-link',
-                       'alignment': 'fa-align-left',
-                       'table': 'fa-table'
-               };
-               
-               var $buttons = this.getOption('buttons');
-               var $lastButton = '';
-               for (var $i = 0, $length = $buttons.length; $i < $length; $i++) {
-                       var $button = $buttons[$i];
+               /**
+                * Initializes the RedactorPlugins.wbutton plugin.
+                */
+               init: function() {
+                       this._bbcodes = { };
                        
-                       if ($button == 'separator') {
-                               this.buttonGet($lastButton).parent().addClass('separator');
-                               
-                               continue;
+                       for (var $i = 0, $length = __REDACTOR_BUTTONS.length; $i < $length; $i++) {
+                               this.wbutton._addBBCodeButton(__REDACTOR_BUTTONS[$i]);
                        }
                        
-                       // check if button does not exist
-                       var $buttonObj = this.buttonGet($button);
-                       if ($buttonObj.length) {
-                               if ($faIcons[$button]) {
-                                       this.buttonAwesome($button, $faIcons[$button]);
+                       // this list contains overrides for built-in buttons, if a button is not present
+                       // Redactor's own icon will be used instead. This solves the problem of FontAwesome
+                       // not providing an icon for everything we need (especially the core stuff)
+                       var $faIcons = {
+                               'html': 'fa-square-o',
+                               'bold': 'fa-bold',
+                               'italic': 'fa-italic',
+                               'underline': 'fa-underline',
+                               'deleted': 'fa-strikethrough',
+                               'subscript': 'fa-subscript',
+                               'superscript': 'fa-superscript',
+                               'orderedlist': 'fa-list-ol',
+                               'unorderedlist': 'fa-list-ul',
+                               'outdent': 'fa-outdent',
+                               'indent': 'fa-indent',
+                               'link': 'fa-link',
+                               'alignment': 'fa-align-left',
+                               'table': 'fa-table'
+                       };
+                       
+                       var $buttons = this.wutil.getOption('buttons');
+                       var $lastButton = '';
+                       for (var $i = 0, $length = $buttons.length; $i < $length; $i++) {
+                               var $button = $buttons[$i];
+                               if ($button == 'separator') {
+                                       this.button.get($lastButton).parent().addClass('separator');
+                                       
+                                       continue;
                                }
-                       }
-                       else {
-                               this._addCoreButton($button, ($faIcons[$button] ? $faIcons[$button] : null), $lastButton);
+                               
+                               // check if button does not exist
+                               var $buttonObj = this.button.get($button);
+                               if ($buttonObj.length) {
+                                       if ($faIcons[$button]) {
+                                               this.button.setAwesome($button, $faIcons[$button]);
+                                       }
+                               }
+                               else {
+                                       this.wbutton._addCoreButton($button, ($faIcons[$button] ? $faIcons[$button] : null), $lastButton);
+                               }
+                               
+                               $lastButton = $button;
                        }
                        
-                       $lastButton = $button;
-               }
-       },
-       
-       _addCoreButton: function(buttonName, faIcon, insertAfter) {
-               var $button = this.buttonBuild(buttonName, {
-                       title: buttonName,
-                       exec: buttonName
-               }, false);
-               $('<li />').append($button).insertAfter(this.buttonGet(insertAfter).parent());
-               
-               if (faIcon !== null) {
-                       this.buttonAwesome(buttonName, faIcon);
-               }
-       },
-       
-       /**
-        * Adds a custom button.
-        * 
-        * @param       object<string>          data
-        */
-       _addBBCodeButton: function(data) {
-               var $buttonName = '__wcf_' + data.name;
-               var $button = this.buttonAdd($buttonName, data.label, this._insertBBCode);
-               this._bbcodes[$buttonName] = data.name;
+                       // handle image insert
+                       this.button.addCallback(this.button.get('image'), $.proxy(this.wbutton.insertImage, this));
+               },
                
-               // FontAwesome class name
-               if (data.icon.match(/^fa\-[a-z\-]+$/)) {
-                       this.buttonAwesome($buttonName, data.icon);
-               }
-               else {
-                       // image reference
-                       $button.css('background-image', 'url(' + __REDACTOR_ICON_PATH + data.icon + ')');
-               }
-       },
-       
-       /**
-        * Inserts the specified BBCode.
-        * 
-        * @param       string          buttonName
-        * @param       jQuery          buttonDOM
-        * @param       object          buttonObj
-        * @param       object          event
-        */
-       _insertBBCode: function(buttonName, buttonDOM, buttonObj, event) {
-               var $bbcode = this._bbcodes[buttonName];
-               var $eventData = {
-                       buttonName: buttonName,
-                       buttonDOM: buttonDOM,
-                       buttonObj: buttonObj,
-                       event: event,
-                       cancel: false
-               };
+               _addCoreButton: function(buttonName, faIcon, insertAfter) {
+                       var $buttonObj = { title: buttonName };
+                       if (buttonName === 'subscript' || buttonName === 'superscript') {
+                               $buttonObj.command = buttonName;
+                       }
+                       
+                       var $button = this.button.build(buttonName, $buttonObj);
+                       $('<li />').append($button).insertAfter(this.button.get(insertAfter).parent());
+                       
+                       if (faIcon !== null) {
+                               this.button.setAwesome(buttonName, faIcon);
+                       }
+               },
                
-               WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'insertBBCode_' + $bbcode + '_' + this.$source.wcfIdentify(), $eventData);
+               /**
+                * Adds a custom button.
+                * 
+                * @param       object<string>          data
+                */
+               _addBBCodeButton: function(data) {
+                       var $buttonName = '__wcf_' + data.name;
+                       var $button = this.button.add($buttonName, data.label);
+                       this.button.addCallback($button, this.wbutton._insertBBCode);
+                       
+                       this._bbcodes[$buttonName] = data.name;
+                       
+                       // FontAwesome class name
+                       if (data.icon.match(/^fa\-[a-z\-]+$/)) {
+                               this.button.setAwesome($buttonName, data.icon);
+                       }
+                       else {
+                               // image reference
+                               $button.css('background-image', 'url(' + __REDACTOR_ICON_PATH + data.icon + ')');
+                       }
+               },
                
-               if ($eventData.cancel === false) {
-                       var $selectedHtml = this.getSelectionHtml();
+               /**
+                * Inserts the specified BBCode.
+                * 
+                * @param       string          buttonName
+                */
+               _insertBBCode: function(buttonName) {
+                       var $bbcode = this._bbcodes[buttonName];
+                       var $eventData = {
+                               buttonName: buttonName,
+                               cancel: false
+                       };
                        
-                       if ($bbcode === 'tt') {
-                               var $parent = (this.getParent()) ? $(this.getParent()) : null;
-                               if ($parent && $parent.closest('inline.inlineCode', this.$editor.get()[0]).length) {
-                                       this.inlineRemoveClass('inlineCode');
+                       WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'insertBBCode_' + $bbcode + '_' + this.$textarea.wcfIdentify(), $eventData);
+                       
+                       if ($eventData.cancel === false) {
+                               var $selectedHtml = this.selection.getHtml();
+                               
+                               // TODO: this behaves pretty weird at this time, fix or remove
+                               if (false && $bbcode === 'tt') {
+                                       var $parent = (this.selection.getParent()) ? $(this.selection.getParent()) : null;
+                                       if ($parent && $parent.closest('inline.inlineCode', this.$editor.get()[0]).length) {
+                                               this.inline.toggleClass('inlineCode');
+                                       }
+                                       else {
+                                               this.inline.toggleClass('inlineCode');
+                                       }
                                }
                                else {
-                                       this.inlineSetClass('inlineCode');
+                                       this.insert.html('[' + $bbcode + ']' + $selectedHtml + '[/' + $bbcode + ']');
                                }
                        }
-                       else {
-                               this.insertHtml('[' + $bbcode + ']' + $selectedHtml + '[/' + $bbcode + ']');
+                       
+                       event.preventDefault();
+                       return false;
+               },
+               
+               insertImage: function() {
+                       this.image.show();
+               },
+               
+               _insertImage: function() {
+                       var $source = $('#redactor-image-link-source');
+                       var $url = $source.val().trim();
+                       if ($url.length) {
+                               this.buffer.set();
+                               
+                               var $align = $('#redactor-image-align').val();
+                               var $style = '';
+                               if ($align === 'left' || $align === 'right') {
+                                       $style = ' style="float: ' + $align + '"';
+                               }
+                               
+                               this.insert.html('<img src="' + $url + '"' + $style + '>', false);
+                               
+                               this.modal.close();
+                               this.observe.images();
+                       }
+                       else if (!$source.next('small.innerError')) {
+                               $('<small class="innerError">' + WCF.Language.get('wcf.global.form.error.empty') + '</small>').insertAfter($source);
                        }
                }
-               
-               event.preventDefault();
-               return false;
-       }
+       };
 };
index c54efce60e4c9dd158e53fd369c97f2c9456684d..901fb5597d5a2662afe26b4494be0716c6added2 100644 (file)
@@ -7,68 +7,65 @@ if (!RedactorPlugins) var RedactorPlugins = {};
  * @copyright  2001-2014 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  */
-RedactorPlugins.wfontcolor = {
-       /**
-        * Initializes the RedactorPlugins.wfontcolor plugin.
-        */
-       init: function() {
-               var $dropdown = this._createFontColorDropdown();
-               
-               this.buttonReplace('fontcolor', 'fontcolor', this.opts.curLang.fontcolor, $.proxy(function(btnName, $button, btnObject, e) {
-                       this.dropdownShow(e, btnName);
-               }, this));
-               this.buttonGet('fontcolor').data('dropdown', $dropdown);
-       },
+RedactorPlugins.wfontcolor = function() {
+       "use strict";
        
-       /**
-        * Creates the font color dropdown.
-        */
-       _createFontColorDropdown: function() {
-               var $dropdown = $('<div class="redactor_dropdown redactor_dropdown_box_fontcolor dropdownMenu" style="display: none;">');
-               var $colors = [
-                       '#000000', '#800000', '#8B4513', '#2F4F4F', '#008080', '#000080', '#4B0082', '#696969',
-                       '#B22222', '#A52A2A', '#DAA520', '#006400', '#40E0D0', '#0000CD', '#800080', '#808080',
-                       '#FF0000', '#FF8C00', '#FFD700', '#008000', '#00FFFF', '#0000FF', '#EE82EE', '#A9A9A9',
-                       '#FFA07A', '#FFA500', '#FFFF00', '#00FF00', '#AFEEEE', '#ADD8E6', '#DDA0DD', '#D3D3D3',
-                       '#FFF0F5', '#FAEBD7', '#FFFFE0', '#F0FFF0', '#F0FFFF', '#F0F8FF', '#E6E6FA', '#FFFFFF'
-               ];
+       return {
+               /**
+                * Initializes the RedactorPlugins.wfontcolor plugin.
+                */
+               init: function() {
+                       var $dropdown = this.button.addDropdown(this.button.get('fontcolor'));
+                       this.wfontcolor._createDropdown($dropdown);
+               },
                
-               var $container = $('<li class="redactorColorPallet" />');
-               for (var $i = 0, $length = $colors.length; $i < $length; $i++) {
-                       var $color = $colors[$i];
+               /**
+                * Creates the font color dropdown.
+                * 
+                * @param       jQuery          dropdown
+                */
+               _createDropdown: function(dropdown) {
+                       var $colors = [
+                               '#000000', '#800000', '#8B4513', '#2F4F4F', '#008080', '#000080', '#4B0082', '#696969',
+                               '#B22222', '#A52A2A', '#DAA520', '#006400', '#40E0D0', '#0000CD', '#800080', '#808080',
+                               '#FF0000', '#FF8C00', '#FFD700', '#008000', '#00FFFF', '#0000FF', '#EE82EE', '#A9A9A9',
+                               '#FFA07A', '#FFA500', '#FFFF00', '#00FF00', '#AFEEEE', '#ADD8E6', '#DDA0DD', '#D3D3D3',
+                               '#FFF0F5', '#FAEBD7', '#FFFFE0', '#F0FFF0', '#F0FFFF', '#F0F8FF', '#E6E6FA', '#FFFFFF'
+                       ];
                        
-                       var $swatch = $('<a href="#" />').data('color', $color).css('background-color', $color);
-                       $container.append($swatch);
-                       $swatch.click($.proxy(this._onColorPick, this));
-               }
-               
-               var $elNone = $('<a href="#" />').html(this.opts.curLang.none).data('color', 'none');
-               $elNone.click($.proxy(this._onColorPick, this));
-               
-               $dropdown.append($container);
-               $dropdown.append($('<li class="dropdownDivider" />'));
-               $dropdown.append($elNone);
-               $elNone.wrap('<li />');
-               
-               $(this.$toolbar).append($dropdown);
-               
-               return $dropdown;
-       },
-       
-       /**
-        * Handles click on a specific text color.
-        * 
-        * @param       object          event
-        */
-       _onColorPick: function(event) {
-               event.preventDefault();
+                       var $container = $('<li class="redactorColorPallet" />');
+                       for (var $i = 0, $length = $colors.length; $i < $length; $i++) {
+                               var $color = $colors[$i];
+                               
+                               var $swatch = $('<a href="#" title="' + $color + '" />').data('color', $color).css('background-color', $color);
+                               $container.append($swatch);
+                               $swatch.click($.proxy(this.wfontcolor._onColorPick, this));
+                       }
+                       
+                       var $elNone = $('<a href="#" />').html(this.opts.curLang.none).data('color', 'none');
+                       $elNone.click($.proxy(this.wfontcolor._onColorPick, this));
+                       
+                       $container.appendTo(dropdown);
+                       $('<li class="dropdownDivider" />').appendTo(dropdown);
+                       $elNone.appendTo(dropdown);
+                       $elNone.wrap('<li />');
+               },
                
-               var $color = $(event.currentTarget).data('color');
-               if ($color === 'none') {
-                       this.inlineRemoveStyle('color');
-               }
-               else {
-                       this.inlineSetStyle('color', $color);
+               /**
+                * Handles click on a specific text color.
+                * 
+                * @param       object          event
+                */
+               _onColorPick: function(event) {
+                       event.preventDefault();
+                       
+                       var $color = $(event.currentTarget).data('color');
+                       if ($color === 'none') {
+                               this.inline.remoteStyleRule('color');
+                       }
+                       else {
+                               this.inline.format('span', 'style', 'color: ' + $color + ';');
+                       }
                }
-       }
-};
\ No newline at end of file
+       };
+};
index 747459cac384034e7e3b242403456e6daa3c79c6..f758c63de572169063c055aef4b999922a7c95a5 100644 (file)
@@ -7,56 +7,53 @@ if (!RedactorPlugins) var RedactorPlugins = {};
  * @copyright  2001-2014 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  */
-RedactorPlugins.wfontfamily = {
-       /**
-        * Initializes the RedactorPlugins.wfontsize plugin.
-        */
-       init: function () {
-               var $dropdown = this._createFontFamilyDropdown();
-               
-               this.buttonReplace('fontfamily', 'wfontfamily', WCF.Language.get('wcf.bbcode.button.fontFamily'), $.proxy(function(btnName, $button, btnObject, e) {
-                       this.dropdownShow(e, btnName);
-               }, this));
-               this.buttonGet('wfontfamily').addClass('re-fontfamily').data('dropdown', $dropdown);
-       },
+RedactorPlugins.wfontfamily = function() {
+       "use strict";
        
-       /**
-        * Creates the font family dropdown.
-        */
-       _createFontFamilyDropdown: function() {
-               var $dropdown = $('<div class="redactor_dropdown redactor_dropdown_box_wfontfamily dropdownMenu" style="display: none;">');
-               var $fonts = {
-                       'Arial': "Arial, Helvetica, sans-serif",
-                       'Comic Sans MS': "Comic Sans MS, cursive",
-                       'Courier New': "Consolas, Courier New, Courier, monospace",
-                       'Georgia': "Georgia, serif",
-                       'Lucida Sans Unicode': "Lucida Sans Unicode, Lucida Grande, sans-serif",
-                       'Tahoma': "Tahoma, Geneva, sans-serif",
-                       'Times New Roman': "Times New Roman, Times, serif",
-                       'Trebuchet MS': "Trebuchet MS, Helvetica, sans-serif",
-                       'Verdana': "Verdana, Geneva, sans-serif"
-               };
-               var self = this;
-               $.each($fonts, function(title, fontFamily) {
-                       var $listItem = $('<li><a href="#">' + title + '</a></li>').appendTo($dropdown);
-                       var $item = $listItem.children('a').data('fontFamily', fontFamily).css('font-family', fontFamily);
-                       $item.click(function() {
+       return {
+               /**
+                * Initializes the RedactorPlugins.wfontsize plugin.
+                */
+               init: function () {
+                       var $dropdown = this.button.addDropdown(this.button.get('fontfamily'));
+                       this.wfontfamily._createDropdown($dropdown);
+               },
+               
+               /**
+                * Creates the font family dropdown.
+                * 
+                * @param       jQuery          dropdown
+                */
+               _createDropdown: function(dropdown) {
+                       var $fonts = {
+                               'Arial': "Arial, Helvetica, sans-serif",
+                               'Comic Sans MS': "Comic Sans MS, cursive",
+                               'Courier New': "Consolas, Courier New, Courier, monospace",
+                               'Georgia': "Georgia, serif",
+                               'Lucida Sans Unicode': "Lucida Sans Unicode, Lucida Grande, sans-serif",
+                               'Tahoma': "Tahoma, Geneva, sans-serif",
+                               'Times New Roman': "Times New Roman, Times, serif",
+                               'Trebuchet MS': "Trebuchet MS, Helvetica, sans-serif",
+                               'Verdana': "Verdana, Geneva, sans-serif"
+                       };
+                       var self = this;
+                       $.each($fonts, function(title, fontFamily) {
+                               var $listItem = $('<li><a href="#">' + title + '</a></li>').appendTo(dropdown);
+                               var $item = $listItem.children('a').data('fontFamily', fontFamily).css('font-family', fontFamily);
+                               $item.click(function() {
+                                       event.preventDefault();
+                                       
+                                       self.inline.format('span', 'style', 'font-family: ' + $(this).data('fontFamily') + ';');
+                               });
+                       });
+                       
+                       $('<li class="dropdownDivider" />').appendTo(dropdown);
+                       var $listItem = $('<li><a href="#">None</a></li>').appendTo(dropdown);
+                       $listItem.children('a').click(function() {
                                event.preventDefault();
                                
-                               self.inlineSetStyle('font-family', $(this).data('fontFamily'));
+                               self.inline.removeStyleRule('font-family');
                        });
-               });
-               
-               $('<li class="dropdownDivider" />').appendTo($dropdown);
-               var $listItem = $('<li><a href="#">None</a></li>').appendTo($dropdown);
-               $listItem.children('a').click(function() {
-                       event.preventDefault();
-                       
-                       self.inlineRemoveStyle('font-family');
-               });
-               
-               $(this.$toolbar).append($dropdown);
-               
-               return $dropdown;
-       }
-};
\ No newline at end of file
+               }
+       };
+};
index f822b3419f582730c8bd0500917812dc43d113a9..48382b29a3da40a1f8b525b792d17e7212effb72 100644 (file)
@@ -7,51 +7,48 @@ if (!RedactorPlugins) var RedactorPlugins = {};
  * @copyright  2001-2014 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  */
-RedactorPlugins.wfontsize = {
-       /**
-        * Initializes the RedactorPlugins.wfontsize plugin.
-        */
-       init: function () {
-               var $dropdown = this._createFontSizeDropdown();
-               
-               this.buttonReplace('fontsize', 'wfontsize', WCF.Language.get('wcf.bbcode.button.fontSize'), $.proxy(function(btnName, $button, btnObject, e) {
-                       this.dropdownShow(e, btnName);
-               }, this));
-               this.buttonGet('wfontsize').addClass('re-fontsize').data('dropdown', $dropdown);
-       },
+RedactorPlugins.wfontsize = function() {
+       "use strict";
        
-       /**
-        * Creates the font size dropdown.
-        */
-       _createFontSizeDropdown: function() {
-               var $dropdown = $('<div class="redactor_dropdown redactor_dropdown_box_wfontsize dropdownMenu" style="display: none;">');
-               var $fontSizes = [ 8, 10, 12, 14, 18, 24, 36 ];
-               var self = this;
-               for (var $i = 0; $i < $fontSizes.length; $i++) {
-                       var $fontSize = $fontSizes[$i];
-                       var $listItem = $('<li><a href="#">' + $fontSize + '</a></li>').appendTo($dropdown);
-                       var $item = $listItem.children('a').data('fontSize', $fontSize).css('font-size', $fontSize + 'pt');
-                       if ($fontSize > 18) {
-                               $item.css('line-height', '1em');
+       return {
+               /**
+                * Initializes the RedactorPlugins.wfontsize plugin.
+                */
+               init: function () {
+                       var $dropdown = this.button.addDropdown(this.button.get('fontsize'));
+                       this.wfontsize._createDropdown($dropdown);
+               },
+               
+               /**
+                * Creates the font size dropdown.
+                * 
+                * @param       jQuery          dropdown
+                */
+               _createDropdown: function(dropdown) {
+                       var $fontSizes = [ 8, 10, 12, 14, 18, 24, 36 ];
+                       var self = this;
+                       for (var $i = 0; $i < $fontSizes.length; $i++) {
+                               var $fontSize = $fontSizes[$i];
+                               var $listItem = $('<li><a href="#">' + $fontSize + '</a></li>').appendTo(dropdown);
+                               var $item = $listItem.children('a').data('fontSize', $fontSize).css('font-size', $fontSize + 'pt');
+                               if ($fontSize > 18) {
+                                       $item.css('line-height', '1em');
+                               }
+                               
+                               $item.click(function() {
+                                       event.preventDefault();
+                                       
+                                       self.inline.format('span', 'style', 'font-size: ' + $(this).data('fontSize') + 'pt;');
+                               });
                        }
                        
-                       $item.click(function() {
+                       $('<li class="dropdownDivider" />').appendTo(dropdown);
+                       var $listItem = $('<li><a href="#">None</a></li>').appendTo(dropdown);
+                       $listItem.children('a').click(function() {
                                event.preventDefault();
                                
-                               self.inlineSetStyle('font-size', $(this).data('fontSize') + 'pt');
+                               self.inline.removeStyleRule('font-size');
                        });
                }
-               
-               $('<li class="dropdownDivider" />').appendTo($dropdown);
-               var $listItem = $('<li><a href="#">None</a></li>').appendTo($dropdown);
-               $listItem.children('a').click(function() {
-                       event.preventDefault();
-                       
-                       self.inlineRemoveStyle('font-size');
-               });
-               
-               $(this.$toolbar).append($dropdown);
-               
-               return $dropdown;
-       }
+       };
 };
index 1295a51fc567ddcad09d5f99dc54e87c15bac49f..3e04b682339b4635414c1672c758fa96b28ea0b8 100644 (file)
@@ -14,557 +14,519 @@ if (!RedactorPlugins) var RedactorPlugins = {};
  * @copyright  2001-2014 WoltLab GmbH, 2009-2014 Imperavi LLC.
  * @license    http://imperavi.com/redactor/license/
  */
-RedactorPlugins.wmonkeypatch = {
-       /**
-        * Initializes the RedactorPlugins.wmonkeypatch plugin.
-        */
-       init: function() {
-               var self = this;
-               var $identifier = this.$source.wcfIdentify();
-               
-               var $mpIndentingStart = this.indentingStart;
-               this.indentingStart = function(cmd) {
-                       if (self.mpIndentingStart(cmd)) {
-                               $mpIndentingStart.call(self, cmd);
-                       }
-               };
-               
-               // keydown w/ event aborting through callback
-               var $mpBuildEventKeydown = this.buildEventKeydown;
-               this.buildEventKeydown = function(e) {
-                       var $eventData = {
-                               cancel: false,
-                               event: e
-                       };
+RedactorPlugins.wmonkeypatch = function() {
+       "use strict";
+       
+       return {
+               /**
+                * Initializes the RedactorPlugins.wmonkeypatch plugin.
+                */
+               init: function() {
+                       // module overrides
+                       this.wmonkeypatch.button();
+                       this.wmonkeypatch.clean();
+                       this.wmonkeypatch.dropdown();
+                       this.wmonkeypatch.image();
+                       this.wmonkeypatch.insert();
+                       this.wmonkeypatch.keydown();
+                       this.wmonkeypatch.modal();
+                       this.wmonkeypatch.paste();
+                       this.wmonkeypatch.observe();
+                       this.wmonkeypatch.utils();
                        
-                       WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'keydown_' + $identifier, $eventData);
+                       // templates
+                       this.wmonkeypatch.rebuildTemplates();
                        
-                       if ($eventData.cancel !== true) {
-                               return $mpBuildEventKeydown.call(self, e);
-                       }
+                       // events and callbacks
+                       this.wmonkeypatch.bindEvents();
                        
-                       return false;
-               };
+               },
                
-               // keyup w/ event aborting through callback
-               var $mpBuildEventKeyup = this.buildEventKeyup;
-               this.buildEventKeyup = function(e) {
-                       var $eventData = {
-                               cancel: false,
-                               event: e
-                       };
+               /**
+                * Setups event listeners and callbacks.
+                */
+               bindEvents: function() {
+                       var $identifier = this.$textarea.wcfIdentify();
                        
-                       WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'keyup_' + $identifier, $eventData);
+                       // keydown
+                       this.wutil.setOption('keydownCallback', function(event) {
+                               var $data = {
+                                       cancel: false,
+                                       event: event
+                               };
+                               
+                               WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'keydown_' + $identifier, $data);
+                               
+                               return ($data.cancel ? false : true);
+                       });
                        
-                       if ($eventData.cancel !== true) {
-                               return $mpBuildEventKeyup.call(self, e);
-                       }
+                       // keyup
+                       this.wutil.setOption('keyupCallback', function(event) {
+                               var $data = {
+                                       cancel: false,
+                                       event: event
+                               };
+                               
+                               WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'keyup_' + $identifier, $data);
+                               
+                               return ($data.cancel ? false : true);
+                       });
                        
-                       return false;
-               };
+                       // buttons response
+                       if (this.opts.activeButtons) {
+                               this.$editor.off('mouseup.redactor keyup.redactor focus.redactor');
+                               this.$editor.on('mouseup.redactor keyup.redactor focus.redactor', $.proxy(this.observe.buttons, this));
+                       }
+               },
                
-               var $mpToggleCode = this.toggleCode;
-               this.toggleCode = function(direct) {
-                       var $height = self.normalize(self.$editor.css('height'));
-                       
-                       $mpToggleCode.call(self, direct);
-                       
-                       self.$source.height($height);
-               };
+               /**
+                * Partially overwrites the 'button' module.
+                * 
+                *  - consistent display of dropdowns
+                */
+               button: function() {
+                       // button.addDropdown
+                       var $mpAddDropdown = this.button.addDropdown;
+                       this.button.addDropdown = (function($btn, dropdown) {
+                               var $dropdown = $mpAddDropdown.call(this, $btn, dropdown);
+                               
+                               if (!dropdown) {
+                                       $dropdown.addClass('dropdownMenu');
+                               }
+                               
+                               return $dropdown;
+                       }).bind(this);
+               },
                
-               var $mpModalInit = this.modalInit;
-               this.modalInit = function(title, content, width, callback) {
-                       self.mpModalInit();
-                       
-                       $mpModalInit.call(self, title, content, width, callback);
-               };
+               /**
+                * Partially overwrites the 'clean' module.
+                * 
+                *  - convert <div> to <p> during paste
+                */
+               clean: function() {
+                       var $mpOnPaste = this.clean.onPaste;
+                       this.clean.onPaste = (function(html, setMode) {
+                               this.opts.replaceDivs = true;
+                               
+                               html = $mpOnPaste.call(this, html, setMode);
+                               
+                               this.opts.replaceDivs = false;
+                               
+                               return html;
+                       }).bind(this);
+               },
                
-               var $mpModalShowOnDesktop = this.modalShowOnDesktop;
-               this.modalShowOnDesktop = function() {
-                       $mpModalShowOnDesktop.call(self);
+               /**
+                * Partially overwrites the 'dropdown' module.
+                * 
+                *  - emulate WCF-like dropdowns.
+                */
+               dropdown: function() {
+                       // dropdown.build
+                       this.dropdown.build = (function(name, $dropdown, dropdownObject) {
+                               $dropdown.addClass('dropdownMenu');
+                               
+                               $.each(dropdownObject, (function(btnName, btnObject) {
+                                       if (btnName == 'dropdownDivider') {
+                                               $('<li class="dropdownDivider" />').appendTo($dropdown);
+                                       }
+                                       else {
+                                               var $listItem = $('<li />');
+                                               var $item = $('<a href="#" class="redactor-dropdown-' + btnName + '">' + btnObject.title + '</a>');
+                                               
+                                               $item.on('click', $.proxy(function(e) {
+                                                       var type = 'func';
+                                                       var callback = btnObject.func;
+                                                       if (btnObject.command) {
+                                                               type = 'command';
+                                                               callback = btnObject.command;
+                                                       }
+                                                       else if (btnObject.dropdown) {
+                                                               type = 'dropdown';
+                                                               callback = btnObject.dropdown;
+                                                       }
+                                                       
+                                                       this.button.onClick(e, btnName, type, callback);
+                                                       
+                                               }, this));
+                                               
+                                               $item.appendTo($listItem);
+                                               $listItem.appendTo($dropdown);
+                                       }
+                               }).bind(this));
+                               
+                               /*$dropdown.children('a').each(function() {
+                                       $(this).wrap('li');
+                               });*/
+                       }).bind(this);
                        
-                       $(document.body).css('overflow', false);
-               };
+                       // dropdown.show
+                       var $mpShow = this.dropdown.show;
+                       this.dropdown.show = $.proxy(function(e, key) {
+                               $mpShow.call(this, e, key);
+                               
+                               this.button.get(key).data('dropdown').off('mouseover mouseout');
+                       }, this);
+               },
                
-               var $mpDestroy = this.destroy;
-               this.destroy = function() {
-                       self.callback('destroy', false, { });
+               /**
+                * Partially overwrites the 'image' module.
+                * 
+                *  - WCF-like dialog behavior
+                *  - resolves an existing issue in Redactor 10.0 related to 'imageEditable' and 'imageResize' = false
+                */
+               image: function() {
+                       // image.setEditable
+                       this.image.setEditable = (function($image) {
+                               // TODO: remove this entire function once the issue with the option 'imageEditable' has been resolved by Imperavi
+                               if (!this.opts.imageEditable) return;
+                               
+                               $image.on('dragstart', $.proxy(this.image.onDrag, this));
+                               
+                               $image.on('mousedown', $.proxy(this.image.hideResize, this));
+                               $image.on('click touchstart', $.proxy(function(e) {
+                                       this.observe.image = $image;
+                                       
+                                       if (this.$editor.find('#redactor-image-box').size() !== 0) return false;
+                                       
+                                       // resize
+                                       if (!this.opts.imageEditable && !this.opts.imageResizable) return;
+                                       
+                                       this.image.resizer = this.image.loadEditableControls($image);
+                                       if (this.image.resizer === false) {
+                                               // work-around, this.image.hideResize() is not aware of this.image.resizer = false (but legally possible!)
+                                               this.image.resizer = $();
+                                       }
+                                       else {
+                                               this.image.resizer.on('mousedown.redactor touchstart.redactor', $.proxy(function(e) {
+                                                       e.preventDefault();
+                                                       
+                                                       this.image.resizeHandle = {
+                                                               x : e.pageX,
+                                                               y : e.pageY,
+                                                               el : $image,
+                                                               ratio: $image.width() / $image.height(),
+                                                               h: $image.height()
+                                                       };
+                                                       
+                                                       e = e.originalEvent || e;
+                                                       
+                                                       if (e.targetTouches) {
+                                                               this.image.resizeHandle.x = e.targetTouches[0].pageX;
+                                                               this.image.resizeHandle.y = e.targetTouches[0].pageY;
+                                                       }
+                                                       
+                                                       this.image.startResize();
+                                               }, this));
+                                       }
+                                       
+                                       $(document).on('click.redactor-image-resize-hide', $.proxy(this.image.hideResize, this));
+                                       this.$editor.on('click.redactor-image-resize-hide', $.proxy(this.image.hideResize, this));
+                               }, this));
+                       }).bind(this);
                        
-                       $mpDestroy.call(self);
-               };
-               
-               var $mpSync = this.sync;
-               this.sync = function(e, forceSync) {
-                       if (forceSync === true) {
-                               $mpSync.call(self, e);
-                       }
-               };
-               
-               // handle indent/outdent
-               var $mpButtonActiveObserver = this.buttonActiveObserver;
-               this.buttonActiveObserver = function(e, btnName) {
-                       $mpButtonActiveObserver.call(self, e, btnName);
+                       // image.show
+                       this.image.show = (function() {
+                               this.modal.load('image', this.lang.get('image'), 0);
+                               var $button = this.modal.createActionButton(this.lang.get('insert'));
+                               $button.click($.proxy(this.wbutton._insertImage, this));
+                               
+                               this.selection.save();
+                               this.modal.show();
+                       }).bind(this);
                        
-                       self.mpButtonActiveObserver(e, btnName);
-               };
-               if (this.opts.activeButtons) {
-                       this.$editor.off('mouseup.redactor keyup.redactor').on('mouseup.redactor keyup.redactor', $.proxy(this.buttonActiveObserver, this));
-               }
-               this.$toolbar.find('a.re-indent, a.re-outdent').addClass('redactor_button_disabled');
-               
-               // image editing
-               var $mpImageResizeControls = this.imageResizeControls;
-               this.imageResizeControls = function($image) {
-                       if (!$image.data('attachmentID')) {
-                               $mpImageResizeControls.call(self, $image);
-                       }
+                       // image.showEdit
+                       this.image.showEdit = (function(image) {
+                               this.modal.load('imageEdit', this.lang.get('edit'), 0);
+                               this.image.buttonSave = this.modal.createActionButton(this.lang.get('save'));
+                               
+                               this.image.buttonSave.click((function() {
+                                       this.image.update(image);
+                               }).bind(this));
+                               
+                               // set overlay values
+                               $('#redactor-image-link-source').val(image.attr('src'));
+                               $('#redactor-image-align').val(image.css('float'));
+                               
+                               this.modal.show();
+                       }).bind(this);
                        
-                       return false;
-               };
+                       // image.update
+                       this.image.update = (function(image) {
+                               this.image.hideResize();
+                               this.buffer.set();
+                               
+                               image.attr('src', $('#redactor-image-link-source').val());
+                               this.image.setFloating(image);
+                               
+                               this.modal.close();
+                               this.observe.images();
+                       }).bind(this);
+               },
                
-               var $mpImageEdit = this.imageEdit;
-               this.imageEdit = function(image) {
-                       $mpImageEdit.call(self, image);
+               /**
+                * Partially overwrites the 'insert' module.
+                * 
+                *  - fixes insertion in an empty editor w/o prior focus until the issue has been resolved by Imperavi
+                */
+               insert: function() {
+                       var $focusEditor = (function() {
+                               var $html = this.$editor.html();
+                               if (this.utils.isEmpty($html)) {
+                                       this.$editor.focus();
+                                       
+                                       this.caret.setEnd(this.$editor.children('p:eq(0)'));
+                               }
+                       }).bind(this);
                        
-                       $('#redactor_image_source').val($(image).prop('src'));
-               };
+                       // insert.html
+                       var $mpHtml = this.insert.html;
+                       this.insert.html = (function(html, clean) {
+                               $focusEditor();
+                               
+                               $mpHtml.call(this, html, clean);
+                       }).bind(this);
+               },
                
-               var $mpImageSave = this.imageSave;
-               this.imageSave = function(el) {
-                       $(el).prop('src', $('#redactor_image_source').val());
+               /**
+                * Partially overwrites the 'keydown' module.
+                * 
+                *  - improve behavior in quotes
+                *  - allow indentation for lists only
+                */
+               keydown: function() {
+                       this.keydown.enterWithinBlockquote = false;
                        
-                       $mpImageSave.call(self, el);
-               };
-               
-               // backspace
-               var $mpBuildEventKeydownBackspace = this.buildEventKeydownBackspace;
-               this.buildEventKeydownBackspace = function(e, current, parent) {
-                       if ($mpBuildEventKeydownBackspace.call(self, e, current, parent) !== false) {
-                               return self.mpBuildEventKeydownBackspace(e, current, parent);
-                       }
+                       // keydown.onTab
+                       this.keydown.onTab = (function(e, key) {
+                               e.preventDefault();
+                               
+                               if (e.metaKey && key === 219) this.indent.decrease();
+                               else if (e.metaKey && key === 221) this.indent.increase();
+                               else if (!e.shiftKey) this.indent.increase();
+                               else this.indent.decrease();
+                               
+                               return false;
+                       }).bind(this);
                        
-                       return false;
-               };
-               
-               this.setOption('modalOpenedCallback', $.proxy(this.modalOpenedCallback, this));
-               this.setOption('dropdownShowCallback', $.proxy(this.dropdownShowCallback, this));
-               
-               this.modalTemplatesInit();
-       },
-       
-       cleanRemoveSpaces: function(html, buffer) {
-               return html;
-       },
-       
-       /**
-        * Enable/Disable the 'Indent'/'Outdent' for lists/outside lists.
-        * 
-        * @param       object          e
-        * @param       string          btnName
-        */
-       mpButtonActiveObserver: function(e, btnName) {
-               var parent = this.getParent();
-               parent = (parent === false) ? null : $(parent);
+                       // keydown.replaceDivToParagraph
+                       var $mpReplaceDivToParagraph = this.keydown.replaceDivToParagraph;
+                       this.keydown.replaceDivToParagraph = (function() {
+                               if (this.keydown.enterWithinBlockquote) {
+                                       // do nothing and prevent replacement
+                                       this.keydown.enterWithinBlockquote = false;
+                               }
+                               else {
+                                       $mpReplaceDivToParagraph.call(this);
+                               }
+                       }).bind(this);
+               },
                
-               var self = this;
-               var $editor = this.$editor.get()[0];
-               var $toggleButtons = function(searchFor, buttonSelector, inverse, className, skipInSourceMode) {
-                       var $buttons = self.$toolbar.find(buttonSelector);
-                       if (parent && parent.closest(searchFor, $editor).length != 0) {
-                               $buttons[(inverse ? 'removeClass' : 'addClass')](className);
-                       }
-                       else {
-                               if (skipInSourceMode && !self.opts.visual) {
-                                       return;
+               /**
+                * Partially overwrites the 'modal' module.
+                * 
+                *  - delegate modal creation and handling to $.ui.wcfDialog.
+                */
+               modal: function() {
+                       // modal.dialog
+                       this.modal.dialog = null;
+                       
+                       // modal.addTemplate
+                       var $mpAddTemplate = this.modal.addTemplate;
+                       this.modal.addTemplate = (function(name, template) {
+                               // overrides the 'table' template
+                               if (name !== 'table') {
+                                       $mpAddTemplate.call(this, name, template);
                                }
+                       }).bind(this);
+                       
+                       // modal.build
+                       this.modal.build = function() { /* does nothing */ };
+                       
+                       // modal.load
+                       this.modal.load = (function(templateName, title, width) {
+                               this.modal.templateName = templateName;
+                               this.modal.title = title;
                                
-                               $buttons[(inverse ? 'addClass' : 'removeClass')](className);
-                       }
-               };
-               
-               $toggleButtons('ul', 'a.re-indent, a.re-outdent', true, 'redactor_button_disabled');
-               $toggleButtons('inline.inlineCode', 'a.re-__wcf_tt', false, 'redactor_act');
-               $toggleButtons('blockquote.quoteBox', 'a.re-__wcf_quote', false, 'redactor_button_disabled', true);
-               $toggleButtons('sub', 'a.re-subscript', false, 'redactor_act');
-               $toggleButtons('sup', 'a.re-superscript', false, 'redactor_act');
-       },
-       
-       /**
-        * Overwrites $.Redactor.inlineRemoveStyle() to drop empty <inline> elements.
-        * 
-        * @see         $.Redactor.inlineRemoveStyle()
-        * @param       string          rule
-        */
-       inlineRemoveStyle: function(rule) {
-               this.selectionSave();
-               
-               this.inlineEachNodes(function(node) {
-                       $(node).css(rule, '');
-                       this.removeEmptyAttr(node, 'style');
-               });
-               
-               // WoltLab modifications START
-               // drop all <inline> elements without an actual attribute
-               this.$editor.find('inline').each(function(index, inlineElement) {
-                       if (!inlineElement.attributes.length) {
-                               var $inlineElement = $(inlineElement);
-                               $inlineElement.replaceWith($inlineElement.html());
-                       }
-               });
-               // WoltLab modifications END
-               
-               this.selectionRestore();
-               this.sync();
-       },
-       
-       /**
-        * Overwrites $.Redactor.inlineMethods() to fix calls to inlineSetClass().
-        * 
-        * @see         $.Redactor.inlineMethods()
-        * @param       string          type
-        * @param       string          attr
-        * @param       string          value
-        */
-       inlineMethods: function(type, attr, value) {
-               this.bufferSet();
-               this.selectionSave();
-
-               var range = this.getRange();
-               var el = this.getElement();
-
-               if ((range.collapsed || range.startContainer === range.endContainer) && el && !this.nodeTestBlocks(el))
-               {
-                       $(el)[type](attr, value);
-               }
-               else
-               {
-                       var cmd, arg = value;
-                       switch (attr)
-                       {
-                               case 'font-size':
-                                       cmd = 'fontSize';
-                                       arg = 4;
-                               break;
-                               case 'font-family':
-                                       cmd = 'fontName';
-                               break;
-                               case 'color':
-                                       cmd = 'foreColor';
-                               break;
-                               case 'background-color':
-                                       cmd = 'backColor';
-                               break;
-                       }
+                               this.modal.dialog = $('<div />').hide().appendTo(document.body);
+                               this.modal.dialog.html(this.modal.getTemplate(this.modal.templateName));
+                               
+                               this.$modalFooter = null;
+                       }).bind(this);
                        
-                       // WoltLab modifications START
-                       if (type === 'addClass') {
-                               cmd = 'fontSize';
-                               arg = 4;
-                       }
-                       // WoltLab modifications END
-
-                       this.document.execCommand(cmd, false, arg);
-
-                       var fonts = this.$editor.find('font');
-                       $.each(fonts, $.proxy(function(i, s)
-                       {
-                               this.inlineSetMethods(type, s, attr, value);
-
-                       }, this));
-
-               }
-
-               this.selectionRestore();
-               this.sync();
-       },
-       
-       /**
-        * Drops the indentation if not within a list.
-        * 
-        * @param       string          cmd
-        */
-       mpIndentingStart: function(cmd) {
-               if (this.getBlock().tagName == 'LI') {
-                       return true;
-               }
-               
-               return false;
-       },
-       
-       /**
-        * Provides WCF-like overlays.
-        */
-       modalTemplatesInit: function() {
-               this.setOption('modal_image',
-                       '<fieldset>'
-                               + '<dl>'
-                                       + '<dt><label for="redactor_file_link">' + this.opts.curLang.image_web_link + '</label></dt>'
-                                       + '<dd><input type="text" name="redactor_image_source" id="redactor_image_source" class="long"  /></dd>'
-                               + '</dl>'
-                               + '<dl>'
-                                       + '<dt><label for="redactor_form_image_align">' + this.opts.curLang.image_position + '</label></dt>'
-                                       + '<dd>'
-                                               + '<select id="redactor_form_image_align">'
-                                                       + '<option value="none">' + this.opts.curLang.none + '</option>'
-                                                       + '<option value="left">' + this.opts.curLang.left + '</option>'
-                                                       + '<option value="right">' + this.opts.curLang.right + '</option>'
-                                               + '</select>'
-                                       + '</dd>'
-                               + '</dl>'
-                       + '</fieldset>'
-                       + '<div class="formSubmit">'
-                               + '<button id="redactor_upload_btn">' + this.opts.curLang.insert + '</button>'
-                       + '</div>'
-               );
-               
-               this.setOption('modal_image_edit', this.getOption('modal_image').replace(
-                       '<button id="redactor_upload_btn">' + this.opts.curLang.insert + '</button>',
-                       '<button id="redactorSaveBtn">' + this.opts.curLang.save + '</button>'
-               ));
-               
-               this.setOption('modal_link',
-                       '<fieldset>'
-                               + '<dl>'
-                                       + '<dt><label for="redactor_link_url">URL</label></dt>'
-                                       + '<dd><input type="text" id="redactor_link_url" class="long" /></dd>'
-                               + '</dl>'
-                               + '<dl>'
-                                       + '<dt><label for="redactor_link_url_text">' + this.opts.curLang.text + '</label></dt>'
-                                       + '<dd><input type="text" id="redactor_link_url_text" class="long" /></dd>'
-                               + '</dl>'
-                       + '</fieldset>'
-                       + '<div class="formSubmit">'
-                               + '<button id="redactor_insert_link_btn">' + this.opts.curLang.insert + '</button>'
-                       + '</div>'
-               );
-               
-               this.setOption('modal_table',
-                       '<fieldset>'
-                               + '<dl>'
-                                       + '<dt><label for="redactor_table_rows">' + this.opts.curLang.rows + '</label></dt>'
-                                       + '<dd><input type="number" size="5" value="2" min="0" id="redactor_table_rows" class="tiny" /></dd>'
-                               + '</dl>'
-                               + '<dl>'
-                                       + '<dt><label for="redactor_table_columns">' + this.opts.curLang.columns + '</label></dt>'
-                                       + '<dd><input type="number" size="5" value="3" min="0" id="redactor_table_columns" class="tiny" /></dd>'
-                               + '</dl>'
-                       + '</fieldset>'
-                       + '<div class="formSubmit">'
-                               + '<button id="redactor_insert_table_btn">' + this.opts.curLang.insert + '</button>'
-                       + '</div>'
-               );
-               
-               this.setOption('modal_quote',
-                       '<fieldset>'
-                               + '<dl>'
-                                       + '<dt><label for="redactorQuoteAuthor">' + WCF.Language.get('wcf.bbcode.quote.edit.author') + '</label></dt>'
-                                       + '<dd><input type="text" id="redactorQuoteAuthor" class="long" /></dd>'
-                               + '</dl>'
-                               + '<dl>'
-                                       + '<dt><label for="redactorQuoteLink">' + WCF.Language.get('wcf.bbcode.quote.edit.link') + '</label></dt>'
-                                       + '<dd><input type="text" id="redactorQuoteLink" class="long" /></dd>'
-                               + '</dl>'
-                       + '</fieldset>'
-                       + '<div class="formSubmit">'
-                               + '<button id="redactorEditQuote">' + this.opts.curLang.save + '</button>'
-                       + '</div>'
-               );
-       },
-       
-       mpModalInit: function() {
-               // modal overlay
-               if (!$('#redactor_modal_overlay').length) {
-                       this.$overlay = $('<div id="redactor_modal_overlay" class="dialogOverlay" />').css({ height: '100%', zIndex: 50000 }).hide().appendTo(document.body);
-               }
-               
-               if (!$('#redactor_modal').length) {
-                       this.$modal = $('<div id="redactor_modal" class="dialogContainer" />').css({ display: 'none', zIndex: 50001 }).appendTo(document.body);
-                       $('<header class="dialogTitlebar"><span id="redactor_modal_header" class="dialogTitle" /><a id="redactor_modal_close" class="dialogCloseButton" /></header>').appendTo(this.$modal);
-                       $('<div class="dialogContent"><div id="redactor_modal_inner" /></div>').appendTo(this.$modal);
-               }
-               
-               this.$modal.children('.dialogContent').removeClass('dialogForm');
-       },
-       
-       modalOpenedCallback: function() {
-               // handle positioning of form submit controls
-               var $heightDifference = 0;
-               if (this.$modal.find('.formSubmit').length) {
-                       $heightDifference = this.$modal.find('.formSubmit').outerHeight();
+                       // modal.show
+                       this.modal.show = (function() {
+                               this.modal.dialog.wcfDialog({
+                                       onClose: $.proxy(this.modal.close, this),
+                                       title: this.modal.title
+                               });
+                       }).bind(this);
                        
-                       this.$modal.children('.dialogContent').addClass('dialogForm').css({ marginBottom: $heightDifference + 'px' });
-               }
-               else {
-                       this.$modal.children('.dialogContent').removeClass('dialogForm').css({ marginBottom: '0px' });
-               }
-               
-               // fix position
-               if (!this.isMobile()) {
-                       var $dimensions = this.$modal.getDimensions('outer');
-                       this.$modal.css({
-                               marginLeft: -1 * Math.round($dimensions.width / 2) + 'px',
-                               marginTop: -1 * Math.round($dimensions.height / 2) + 'px'
-                       });
-               }
-       },
-       
-       dropdownShowCallback: function(data) {
-               if (!data.dropdown.hasClass('dropdownMenu')) {
-                       data.dropdown.addClass('dropdownMenu');
-                       data.dropdown.children('.redactor_separator_drop').replaceWith('<li class="dropdownDivider" />');
-                       data.dropdown.children('a').wrap('<li />');
-               }
-       },
-       
-       /**
-        * Overwrites $.Redactor.inlineEachNodes(), the original method compares "selectionHtml"
-        * and "parentHtml" to check if the callback should be invoked. In some cases the "parentHtml"
-        * may contain a trailing unicode zero-width space and the comparision will fail, even though
-        * the "entire" node is selected.
-        * 
-        * @see $.Redactor.inlineEachNodes()
-        */
-       inlineEachNodes: function(callback) {
-               var range = this.getRange(),
-                       node = this.getElement(),
-                       nodes = this.getNodes(),
-                       collapsed;
-
-               if (range.collapsed || range.startContainer === range.endContainer && node)
-               {
-                       nodes = $(node);
-                       collapsed = true;
-               }
-
-               $.each(nodes, $.proxy(function(i, node)
-               {
-                       if (!collapsed && node.tagName !== 'INLINE')
-                       {
-                               var selectionHtml = this.getSelectionText();
-                               var parentHtml = $(node).parent().text();
-                               // if parentHtml contains a trailing 0x200B, the comparison will most likely fail
-                               var selected = this.removeZeroWidthSpace(selectionHtml) == this.removeZeroWidthSpace(parentHtml);
-
-                               if (selected && node.parentNode.tagName === 'INLINE' && !$(node.parentNode).hasClass('redactor_editor'))
-                               {
-                                       node = node.parentNode;
+                       // modal.createButton
+                       var $mpCreateButton = this.modal.createButton;
+                       this.modal.createButton = (function(label, className) {
+                               if (this.$modalFooter === null) {
+                                       this.$modalFooter = $('<div class="formSubmit" />').appendTo(this.modal.dialog);
+                                       this.modal.dialog.addClass('dialogForm');
                                }
-                               else return;
-                       }
-                       callback.call(this, node);
-
-               }, this ) );
-       },
-       
-       /**
-        * Overwrites $.Redactor.imageCallbackLink() to provide proper image insert behavior.
-        * 
-        * @see $.Redactor.imageCallbackLink()
-        */
-       imageCallbackLink: function() {
-               var $src = $.trim($('#redactor_image_source').val());
-               if ($src.length) {
-                       var $float = '';
-                       var $alignment = $('#redactor_form_image_align').val();
-                       switch ($alignment) {
-                               case 'left':
-                                       $float = ' style="float: left;"';
-                               break;
                                
-                               case 'right':
-                                       $float = ' style="float: right;"';
-                               break;
-                       }
+                               return $mpCreateButton.call(this, label, className);
+                       }).bind(this);
                        
-                       var $data = '<img id="image-marker" src="' + $src + '"' + $float + ' />';
+                       // modal.close
+                       this.modal.close = (function() {
+                               this.modal.dialog.wcfDialog('close');
+                               this.modal.dialog.remove();
+                       }).bind(this);
                        
-                       this.imageInsert($data, true);
-               }
-               else {
-                       this.modalClose();
-               }
-       },
-       
-       /**
-        * Overwrites $.Redactor.observeLinks() to prevent quote headers being recognized as ordinary URLs.
-        * 
-        * @see $.Redactor.observeLinks()
-        */
-       observeLinks: function() {
-               this.$editor.find('a:not(.redactorQuoteEdit)').on('click', $.proxy(this.linkObserver, this));
+                       // modal.createCancelButton
+                       this.modal.createCancelButton = function() { return $(); };
+                       
+                       // modal.createDeleteButton
+                       this.modal.createDeleteButton = function() { return $(); };
+               },
                
-               this.$editor.on('click.redactor', $.proxy(function(e)
-               {
-                       this.linkObserverTooltipClose(e);
-               }, this));
+               /**
+                * Partially overwrites the 'paste' module.
+                * 
+                *  - prevent screwed up, pasted HTML from placing text nodes (and inline elements) in the editor's direct root 
+                */
+               paste: function() {
+                       var $fixDOM = (function() {
+                               var $current = this.$editor[0].childNodes[0];
+                               var $nextSibling = $current;
+                               var $p = null;
+                               
+                               while ($nextSibling) {
+                                       $current = $nextSibling;
+                                       $nextSibling = $current.nextSibling;
+                                       
+                                       if ($current.nodeType === Element.ELEMENT_NODE) {
+                                               if (this.reIsBlock.test($current.tagName)) {
+                                                       $p = null;
+                                               }
+                                               else {
+                                                       if ($p === null) {
+                                                               $p = $('<p />').insertBefore($current);
+                                                       }
+                                                       
+                                                       $p.append($current);
+                                               }
+                                       }
+                                       else if ($current.nodeType === Element.TEXT_NODE) {
+                                               if ($p === null) {
+                                                       $p = $('<p />').insertBefore($current);
+                                               }
+                                               
+                                               $p.append($current);
+                                       }
+                               }
+                       }).bind(this);
+                       
+                       // paste.insert
+                       var $mpInsert = this.paste.insert;
+                       this.paste.insert = (function(html) {
+                               $mpInsert.call(this, html);
+                               
+                               setTimeout($fixDOM, 20);
+                       }).bind(this);
+               },
                
-               $(document).on('click.redactor', $.proxy(function(e)
-               {
-                       this.linkObserverTooltipClose(e);
-               }, this));
-       },
-       
-       /**
-        * Overwrites $.Redactor.observeImages() to prevent smileys being recognized as ordinary images.
-        * 
-        * @see $.Redactor.observeImages()
-        */
-       observeImages: function() {
-               if (this.opts.observeImages === false) return false;
-
-               this.$editor.find('img:not(.smiley)').each($.proxy(function(i, elem)
-               {
-                       if (this.browser('msie')) $(elem).attr('unselectable', 'on');
-
-                       var parent = $(elem).parent();
-                       if (!parent.hasClass('royalSlider') && !parent.hasClass('fotorama'))
-                       {
-                               this.imageResize(elem);
-                       }
-
-               }, this));
-
-               // royalSlider and fotorama
-               this.$editor.find('.fotorama, .royalSlider').on('click', $.proxy(this.editGallery, this));
-
-       },
-       
-       /**
-        * Handles deletion of quotes in design mode.
-        * 
-        * @param       object          event
-        * @param       object          current
-        * @param       object          parent
-        * @return      boolean
-        */
-       mpBuildEventKeydownBackspace: function(event, current, parent) {
-               var $value = $.trim((current.textContent) ? current.textContent : current.innerText);
+               /**
+                * Partially overwrites the 'observe' module.
+                * 
+                *  - handles custom button active states.
+                */
+               observe: function() {
+                       var $toggleButtons = (function(parent, searchFor, buttonSelector, inverse, className, skipInSourceMode) {
+                               var $buttons = this.$toolbar.find(buttonSelector);
+                               if (parent && parent.closest(searchFor, this.$editor[0]).length != 0) {
+                                       $buttons[(inverse ? 'removeClass' : 'addClass')](className);
+                               }
+                               else {
+                                       if (skipInSourceMode && !this.opts.visual) {
+                                               return;
+                                       }
+                                       
+                                       $buttons[(inverse ? 'addClass' : 'removeClass')](className);
+                               }
+                       }).bind(this);
+                       
+                       var $mpButtons = this.observe.buttons;
+                       this.observe.buttons = (function(e, btnName) {
+                               $mpButtons.call(this, e, btnName);
+                               
+                               var parent = this.selection.getParent();
+                               parent = (parent === false) ? null : $(parent);
+                               
+                               $toggleButtons(parent, 'ul, ol', 'a.re-indent, a.re-outdent', true, 'redactor-button-disabled');
+                               //$toggleButtons(parent, 'inline.inlineCode', 'a.re-__wcf_tt', false, 'redactor-act');
+                               $toggleButtons(parent, 'blockquote.quoteBox', 'a.re-__wcf_quote', false, 'redactor-button-disabled', true);
+                               $toggleButtons(parent, 'sub', 'a.re-subscript', false, 'redactor-act');
+                               $toggleButtons(parent, 'sup', 'a.re-superscript', false, 'redactor-act');
+                       }).bind(this);
+               },
+               
+               /**
+                * Partially overwrites the 'utils' module.
+                * 
+                *  - prevent removing of empty paragraphs/divs
+                */
+               utils: function() {
+                       this.utils.removeEmpty = function(i, s) { /* does nothing */ };
+               },
                
-               if ($value == '' && parent.parentNode && parent.parentNode.tagName == 'BLOCKQUOTE') {
-                       var $parentNode = parent.parentNode.parentNode;
-                       $(parent.parentNode).remove();
-                       this.selectionStart($parentNode);
+               /**
+                * Rebuilds certain templates provided by Redactor to better integrate into WCF.
+                */
+               rebuildTemplates: function() {
+                       // template: image
+                       this.opts.modal.image =
+                               '<fieldset id="redactor-modal-image-edit">'
+                                       + '<dl>'
+                                               + '<dt><label for="redactor-image-link-source">' + this.lang.get('link') + '</label></dt>'
+                                               + '<dd><input type="text" id="redactor-image-link-source" class="long"  /></dd>'
+                                       + '</dl>'
+                                       + '<dl>'
+                                               + '<dt><label for="redactor-image-align">' + this.opts.curLang.image_position + '</label></dt>'
+                                               + '<dd>'
+                                                       + '<select id="redactor-image-align">'
+                                                               + '<option value="none">' + this.lang.get('none') + '</option>'
+                                                               + '<option value="left">' + this.lang.get('left') + '</option>'
+                                                               + '<option value="right">' + this.lang.get('right') + '</option>'
+                                                       + '</select>'
+                                               + '</dd>'
+                                       + '</dl>'
+                               + '</fieldset>';
                        
-                       return false;
+                       // template: imageEdit
+                       this.opts.modal.imageEdit = this.opts.modal.image;
+                       
+                       // template: quote
+                       this.opts.modal.quote =
+                               '<fieldset>'
+                                       + '<dl>'
+                                               + '<dt><label for="redactorQuoteAuthor">' + WCF.Language.get('wcf.bbcode.quote.edit.author') + '</label></dt>'
+                                               + '<dd><input type="text" id="redactorQuoteAuthor" class="long" /></dd>'
+                                       + '</dl>'
+                                       + '<dl>'
+                                               + '<dt><label for="redactorQuoteLink">' + WCF.Language.get('wcf.bbcode.quote.edit.link') + '</label></dt>'
+                                               + '<dd><input type="text" id="redactorQuoteLink" class="long" /></dd>'
+                                       + '</dl>'
+                               + '</fieldset>';
+                       
+                       // template: table
+                       this.opts.modal.table =
+                               '<fieldset id="redactor-modal-table-insert">'
+                                       + '<dl>'
+                                               + '<dt><label for="redactor-table-rows">' + this.lang.get('rows') + '</label></dt>'
+                                               + '<dd><input type="number" size="5" value="2" min="1" id="redactor-table-rows" class="tiny" /></dd>'
+                                       + '</dl>'
+                                       + '<dl>'
+                                               + '<dt><label for="redactor-table-columns">' + this.lang.get('columns') + '</label></dt>'
+                                               + '<dd><input type="number" size="5" value="3" min="1" id="redactor-table-columns" class="tiny" /></dd>'
+                                       + '</dl>'
+                               + '</fieldset>';
                }
-       },
-       
-       /**
-        * Overwrites $.Redactor.cleanGetTabs() to prevent HTML indentation.
-        * 
-        * @see $.Redactor.cleanGetTabs()
-        */
-       cleanGetTabs: function() {
-               return '';
-       },
-       
-       /**
-        * Overwrites $.Redactor.cleanRemoveEmptyTags() to prevent empty tags being discarded, they
-        * are required to properly transform HTML -> BBCode.
-        * 
-        * @see $.Redactor.cleanRemoveEmptyTags()
-        */
-       cleanRemoveEmptyTags: function(html) {
-               return html;
-       }
+       };
 };
index daaeeacbd79a641a736091366f0fcc8260ede913..c66aa50d18fef5c817ac54223c13470f6cd70014 100644 (file)
@@ -7,210 +7,214 @@ if (!RedactorPlugins) var RedactorPlugins = {};
  * @copyright  2001-2014 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  */
-RedactorPlugins.wupload = {
-       _boundGlobalUploadEvents: false,
-       _wUploadDropArea: { },
-       _timer: null,
-       _isDragging: false,
-       _isFile: false,
+RedactorPlugins.wupload = function() {
+       "use strict";
        
-       /**
-        * Initializes the RedactorPlugins.wupload plugin.
-        */
-       init: function() {
-               var $namespace = '.redactor_' + this.$source.wcfIdentify();
-               $(document).on('dragover' + $namespace, $.proxy(this._dragOver, this));
-               $(document).on('dragleave' + $namespace, $.proxy(this._dragLeave, this));
-               $(document).on('drop' + $namespace, $.proxy(function(event) {
-                       event.preventDefault();
-                       
-                       this._revertDropArea(undefined, this.$source.wcfIdentify());
-               }, this));
+       return {
+               _boundGlobalUploadEvents: false,
+               _wUploadDropArea: { },
+               _timer: null,
+               _isDragging: false,
+               _isFile: false,
                
-               if (!this._boundGlobalUploadEvents) {
-                       this._boundGlobalUploadEvents = true;
+               /**
+                * Initializes the RedactorPlugins.wupload plugin.
+                */
+               init: function() {
+                       var $namespace = '.redactor_' + this.$textarea.wcfIdentify();
+                       $(document).on('dragover' + $namespace, $.proxy(this._dragOver, this));
+                       $(document).on('dragleave' + $namespace, $.proxy(this._dragLeave, this));
+                       $(document).on('drop' + $namespace, $.proxy(function(event) {
+                               event.preventDefault();
+                               
+                               this._revertDropArea(undefined, this.$textarea.wcfIdentify());
+                       }, this));
                        
-                       $(document).on('dragend', function(event) { event.preventDefault(); });
-               }
-               
-               WCF.System.Event.addListener('com.woltlab.wcf.attachment', 'autoInsert_' + this.$source.wcfIdentify(), $.proxy(this.insertPastedImageAttachment, this));
-       },
-       
-       /**
-        * Handles an actively dragged object.
-        * 
-        * @param       object          event
-        */
-       _dragOver: function(event) {
-               event = event.originalEvent;
-               
-               if (!event.dataTransfer || !event.dataTransfer.types) {
-                       return;
-               }
-               
-               // IE and WebKit set 'Files', Firefox sets 'application/x-moz-file' for files being dragged
-               // and Safari just provides 'Files' along with a huge list of other stuff
-               this._isFile = false;
-               if (event.dataTransfer.types[0] === 'application/x-moz-file') {
-                       this._isFile = true;
-               }
-               else {
-                       for (var $i = 0; $i < event.dataTransfer.types.length; $i++) {
-                               if (event.dataTransfer.types[$i] === 'Files') {
-                                       this._isFile = true;
-                                       break;
-                               }
+                       if (!this._boundGlobalUploadEvents) {
+                               this._boundGlobalUploadEvents = true;
+                               
+                               $(document).on('dragend', function(event) { event.preventDefault(); });
                        }
-               }
-               
-               if (!this._isFile) {
-                       return;
-               }
-               
-               this._isFile = true;
-               event.preventDefault();
+                       
+                       WCF.System.Event.addListener('com.woltlab.wcf.attachment', 'autoInsert_' + this.$textarea.wcfIdentify(), $.proxy(this.insertPastedImageAttachment, this));
+               },
                
-               if (!this._isDragging) {
-                       var $containerID = this.$source.wcfIdentify();
+               /**
+                * Handles an actively dragged object.
+                * 
+                * @param       object          event
+                */
+               _dragOver: function(event) {
+                       event = event.originalEvent;
                        
-                       if (this._wUploadDropArea[$containerID] === undefined) {
-                               this._wUploadDropArea[$containerID] = $('<div class="redactorDropArea">' + WCF.Language.get('wcf.attachment.dragAndDrop.dropHere') + '</div>').hide().appendTo(document.body);
-                               this._wUploadDropArea[$containerID].on('dragover', $.proxy(this._hoverDropArea, this)).on('dragleave', $.proxy(this._revertDropArea, this)).on('drop', $.proxy(this._drop, this));
+                       if (!event.dataTransfer || !event.dataTransfer.types) {
+                               return;
                        }
                        
-                       // adjust dimensions
-                       var $dimensions = (this.inWysiwygMode()) ? this.$editor.getDimensions('outer') : this.$source.getDimensions('outer');
-                       var $position = (this.inWysiwygMode()) ? this.$editor.getOffsets('offset') : this.$source.getOffsets('offset');
+                       // IE and WebKit set 'Files', Firefox sets 'application/x-moz-file' for files being dragged
+                       // and Safari just provides 'Files' along with a huge list of other stuff
+                       this._isFile = false;
+                       if (event.dataTransfer.types[0] === 'application/x-moz-file') {
+                               this._isFile = true;
+                       }
+                       else {
+                               for (var $i = 0; $i < event.dataTransfer.types.length; $i++) {
+                                       if (event.dataTransfer.types[$i] === 'Files') {
+                                               this._isFile = true;
+                                               break;
+                                       }
+                               }
+                       }
                        
-                       this._wUploadDropArea[$containerID].css({
-                               height: $dimensions.height + 'px',
-                               left: $position.left + 'px',
-                               lineHeight: $dimensions.height + 'px',
-                               top: $position.top + 'px',
-                               width: $dimensions.width + 'px'
-                       }).show();
+                       if (!this._isFile) {
+                               return;
+                       }
                        
-                       this._isDragging = true;
-               }
-               
-               event.preventDefault();
-       },
-       
-       /**
-        * Visualizes the drop area being hovered.
-        * 
-        * @param       object          event
-        */
-       _hoverDropArea: function(event) {
-               this._wUploadDropArea[this.$source.wcfIdentify()].addClass('active').text(WCF.Language.get('wcf.attachment.dragAndDrop.dropNow'));
-       },
-       
-       /**
-        * Reverts the drop area into the initial state.
-        * 
-        * @param       object          event
-        * @param       string          containerID
-        */
-       _revertDropArea: function(event, containerID) {
-               if (!this._isFile) {
-                       return;
-               }
-               
-               var $containerID = containerID || this.$source.wcfIdentify();
-               this._wUploadDropArea[$containerID].removeClass('active').text(WCF.Language.get('wcf.attachment.dragAndDrop.dropHere'));
-               
-               if (containerID) {
-                       this._wUploadDropArea[$containerID].hide();
-               }
-       },
-       
-       /**
-        * Handles the object no longer being dragged.
-        * 
-        * This event can fires whenever an object is hovering over a different element, there is
-        * a delay of 100ms before the dragging will be checked again to prevent flicker.
-        */
-       _dragLeave: function() {
-               if (!this._isDragging || !this._isFile) {
-                       return;
-               }
-               
-               if (this._timer === null) {
-                       var self = this;
-                       this._timer = new WCF.PeriodicalExecuter(function(pe) {
-                               pe.stop();
+                       this._isFile = true;
+                       event.preventDefault();
+                       
+                       if (!this._isDragging) {
+                               var $containerID = this.$textarea.wcfIdentify();
                                
-                               if (!self._isDragging) {
-                                       self._wUploadDropArea[self.$source.wcfIdentify()].hide();
+                               if (this._wUploadDropArea[$containerID] === undefined) {
+                                       this._wUploadDropArea[$containerID] = $('<div class="redactorDropArea">' + WCF.Language.get('wcf.attachment.dragAndDrop.dropHere') + '</div>').hide().appendTo(document.body);
+                                       this._wUploadDropArea[$containerID].on('dragover', $.proxy(this._hoverDropArea, this)).on('dragleave', $.proxy(this._revertDropArea, this)).on('drop', $.proxy(this._drop, this));
                                }
-                       }, 100);
-               }
-               else {
-                       this._timer.resume();
-               }
+                               
+                               // adjust dimensions
+                               var $dimensions = (this.inWysiwygMode()) ? this.$editor.getDimensions('outer') : this.$textarea.getDimensions('outer');
+                               var $position = (this.inWysiwygMode()) ? this.$editor.getOffsets('offset') : this.$textarea.getOffsets('offset');
+                               
+                               this._wUploadDropArea[$containerID].css({
+                                       height: $dimensions.height + 'px',
+                                       left: $position.left + 'px',
+                                       lineHeight: $dimensions.height + 'px',
+                                       top: $position.top + 'px',
+                                       width: $dimensions.width + 'px'
+                               }).show();
+                               
+                               this._isDragging = true;
+                       }
+                       
+                       event.preventDefault();
+               },
                
-               this._isDragging = false;
-       },
-       
-       /**
-        * Handles the drop of the dragged object.
-        * 
-        * @param       object          event
-        */
-       _drop: function(event) {
-               if (!this._isFile) {
-                       return;
-               }
+               /**
+                * Visualizes the drop area being hovered.
+                * 
+                * @param       object          event
+                */
+               _hoverDropArea: function(event) {
+                       this._wUploadDropArea[this.$textarea.wcfIdentify()].addClass('active').text(WCF.Language.get('wcf.attachment.dragAndDrop.dropNow'));
+               },
                
-               event = event.originalEvent || event;
+               /**
+                * Reverts the drop area into the initial state.
+                * 
+                * @param       object          event
+                * @param       string          containerID
+                */
+               _revertDropArea: function(event, containerID) {
+                       if (!this._isFile) {
+                               return;
+                       }
+                       
+                       var $containerID = containerID || this.$textarea.wcfIdentify();
+                       this._wUploadDropArea[$containerID].removeClass('active').text(WCF.Language.get('wcf.attachment.dragAndDrop.dropHere'));
+                       
+                       if (containerID) {
+                               this._wUploadDropArea[$containerID].hide();
+                       }
+               },
                
-               if (event.dataTransfer && event.dataTransfer.files.length) {
-                       event.preventDefault();
+               /**
+                * Handles the object no longer being dragged.
+                * 
+                * This event can fires whenever an object is hovering over a different element, there is
+                * a delay of 100ms before the dragging will be checked again to prevent flicker.
+                */
+               _dragLeave: function() {
+                       if (!this._isDragging || !this._isFile) {
+                               return;
+                       }
                        
-                       // reset overlay
-                       var $containerID = this.$source.wcfIdentify();
-                       this._revertDropArea(undefined, $containerID);
+                       if (this._timer === null) {
+                               var self = this;
+                               this._timer = new WCF.PeriodicalExecuter(function(pe) {
+                                       pe.stop();
+                                       
+                                       if (!self._isDragging) {
+                                               self._wUploadDropArea[self.$source.wcfIdentify()].hide();
+                                       }
+                               }, 100);
+                       }
+                       else {
+                               this._timer.resume();
+                       }
                        
-                       for (var $i = 0; $i < event.dataTransfer.files.length; $i++) {
-                               var $file = event.dataTransfer.files[$i];
-                               if ($file.type) {
-                                       WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'upload_' + $containerID, { file: $file });
-                               }
+                       this._isDragging = false;
+               },
+               
+               /**
+                * Handles the drop of the dragged object.
+                * 
+                * @param       object          event
+                */
+               _drop: function(event) {
+                       if (!this._isFile) {
+                               return;
                        }
-               }
-       },
-       
-       /**
-        * Overwrites $.Redactor.pasteClipboardUploadMozilla() to upload files as attachments.
-        * 
-        * @see         $.Redactor.pasteClipboardUploadMozilla()
-        */
-       pasteClipboardUploadMozilla: function() {
-               this.$editor.find('img[data-mozilla-paste-image]').each($.proxy(function(index, image) {
-                       var $image = $(image);
-                       var $src = $image.prop('src').split(',');
-                       var $contentType = $src[0].split(';')[0].split(':')[1];
-                       var $data = $src[1]; // raw base64
                        
-                       var $eventData = {
-                               blob: WCF.base64toBlob($data, $contentType),
-                               uploadID: null
-                       };
-                       WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'upload_' + this.$source.wcfIdentify(), $eventData);
+                       event = event.originalEvent || event;
                        
-                       // drop image
-                       $image.replaceWith('<span class="redactor-pastedImageFromClipboard-' + $eventData.uploadID + '" />');
-               }, this));
-       },
-       
-       /**
-        * Inserts the attachment at the placeholder location.
-        * 
-        * @param       object          data
-        */
-       insertPastedImageAttachment: function(data) {
-               var $placeholder = this.$editor.find('span.redactor-pastedImageFromClipboard-' + data.uploadID);
-               $placeholder.before(data.attachment);
-               $placeholder.remove();
-       }
+                       if (event.dataTransfer && event.dataTransfer.files.length) {
+                               event.preventDefault();
+                               
+                               // reset overlay
+                               var $containerID = this.$textarea.wcfIdentify();
+                               this._revertDropArea(undefined, $containerID);
+                               
+                               for (var $i = 0; $i < event.dataTransfer.files.length; $i++) {
+                                       var $file = event.dataTransfer.files[$i];
+                                       if ($file.type) {
+                                               WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'upload_' + $containerID, { file: $file });
+                                       }
+                               }
+                       }
+               },
+               
+               /**
+                * Overwrites $.Redactor.pasteClipboardUploadMozilla() to upload files as attachments.
+                * 
+                * @see         $.Redactor.pasteClipboardUploadMozilla()
+                */
+               pasteClipboardUploadMozilla: function() {
+                       this.$editor.find('img[data-mozilla-paste-image]').each($.proxy(function(index, image) {
+                               var $image = $(image);
+                               var $src = $image.prop('src').split(',');
+                               var $contentType = $src[0].split(';')[0].split(':')[1];
+                               var $data = $src[1]; // raw base64
+                               
+                               var $eventData = {
+                                       blob: WCF.base64toBlob($data, $contentType),
+                                       uploadID: null
+                               };
+                               WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'upload_' + this.$textarea.wcfIdentify(), $eventData);
+                               
+                               // drop image
+                               $image.replaceWith('<span class="redactor-pastedImageFromClipboard-' + $eventData.uploadID + '" />');
+                       }, this));
+               },
+               
+               /**
+                * Inserts the attachment at the placeholder location.
+                * 
+                * @param       object          data
+                */
+               insertPastedImageAttachment: function(data) {
+                       var $placeholder = this.$editor.find('span.redactor-pastedImageFromClipboard-' + data.uploadID);
+                       $placeholder.before(data.attachment);
+                       $placeholder.remove();
+               }
+       };
 };
index bc5612dacc91d9b9dd3dc95c552d3ca4f21175d2..5a61101238baddd4a055be9b7775b797ca5bb76e 100644 (file)
@@ -7,457 +7,470 @@ if (!RedactorPlugins) var RedactorPlugins = {};
  * @copyright  2001-2014 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  */
-RedactorPlugins.wutil = {
-       /**
-        * autosave worker process
-        * @var WCF.PeriodicalExecuter
-        */
-       _autosaveWorker: null,
+RedactorPlugins.wutil = function() {
+       "use strict";
        
-       /**
-        * Initializes the RedactorPlugins.wutil plugin.
-        */
-       init: function() {
-               // convert HTML to BBCode upon submit
-               this.$source.parents('form').submit($.proxy(this.submit, this));
+       return {
+               /**
+                * autosave worker process
+                * @var WCF.PeriodicalExecuter
+                */
+               _autosaveWorker: null,
                
-               if (this.getOption('wautosave').active) {
-                       this.autosaveEnable();
+               /**
+                * Initializes the RedactorPlugins.wutil plugin.
+                */
+               init: function() {
+                       // convert HTML to BBCode upon submit
+                       this.$textarea.parents('form').submit($.proxy(this.submit, this));
                        
-                       if (this.getOption('wautosave').saveOnInit || this.$source.data('saveOnInit')) {
-                               this.setOption('wAutosaveOnce', true);
-                       }
-                       else {
-                               this.autosaveRestore();
+                       if (this.wutil.getOption('wautosave').active) {
+                               this.wutil.autosaveEnable();
+                               
+                               if (this.wutil.getOption('wautosave').saveOnInit || this.$textarea.data('saveOnInit')) {
+                                       this.wutil.setOption('wAutosaveOnce', true);
+                               }
+                               else {
+                                       this.wutil.autosaveRestore();
+                               }
                        }
-               }
-               
-               // prevent Redactor's own autosave
-               this.setOption('autosave', false);
-               
-               // disable autosave on destroy
-               var $mpDestroy = this.destroy;
-               var self = this;
-               this.destroy = function() {
-                       self.autosaveDisable();
-                       $mpDestroy.call(self);
-               };
-       },
-       
-       /**
-        * Allows inserting of text contents in Redactor's source area.
-        * 
-        * @param       string          string
-        * @return      boolean
-        */
-       insertAtCaret: function(string) {
-               if (this.opts.visual) {
-                       console.debug("insertAtCaret() failed: Editor is in WYSIWYG-mode.");
-                       return false;
-               }
-               
-               this.$source.focus();
-               var $position = this.$source.getCaret();
-               if ($position == -1) {
-                       console.debug("insertAtCaret() failed: Source is not input[type=text], input[type=password] or textarea.");
-               }
-               
-               var $content = this.$source.val();
-               $content = $content.substr(0, $position) + string + $content.substr($position);
-               this.$source.val($content);
+                       
+                       // prevent Redactor's own autosave
+                       this.wutil.setOption('autosave', false);
+                       
+                       // disable autosave on destroy
+                       var $mpDestroy = this.destroy;
+                       var self = this;
+                       this.destroy = function() {
+                               self.autosaveDisable();
+                               $mpDestroy.call(self);
+                       };
+               },
                
-               return true;
-       },
-       
-       /**
-        * Inserts content into the editor depending if it is in wysiwyg or plain mode. If 'plainValue' is
-        * null or undefined, the value from 'html' will be taken instead.
-        * 
-        * @param       string          html
-        * @param       string          plainValue
-        */
-       insertDynamic: function(html, plainValue) {
-               if (this.inWysiwygMode()) {
-                       this.insertHtml(html);
-               }
-               else {
-                       if (plainValue === undefined || plainValue === null) {
-                               plainValue = html;
+               /**
+                * Allows inserting of text contents in Redactor's source area.
+                * 
+                * @param       string          string
+                * @return      boolean
+                */
+               insertAtCaret: function(string) {
+                       if (this.opts.visual) {
+                               console.debug("insertAtCaret() failed: Editor is in WYSIWYG-mode.");
+                               return false;
                        }
                        
-                       this.insertAtCaret(plainValue);
-               }
-       },
-       
-       /**
-        * Sets an option value after initialization.
-        * 
-        * @param       string          key
-        * @param       mixed           value
-        */
-       setOption: function(key, value) {
-               this.opts[key] = value;
-       },
-       
-       /**
-        * Reads an option value, returns null if key is unknown.
-        * 
-        * @param       string          key
-        * @return      mixed
-        */
-       getOption: function(key) {
-               if (this.opts[key]) {
-                       return this.opts[key];
-               }
+                       this.$textarea.focus();
+                       var $position = this.$textarea.getCaret();
+                       if ($position == -1) {
+                               console.debug("insertAtCaret() failed: Source is not input[type=text], input[type=password] or textarea.");
+                       }
+                       
+                       var $content = this.$textarea.val();
+                       $content = $content.substr(0, $position) + string + $content.substr($position);
+                       this.$textarea.val($content);
+                       
+                       return true;
+               },
                
-               return null;
-       },
-       
-       /**
-        * Returns true if editor is in source mode.
-        * 
-        * @return      boolean
-        */
-       inPlainMode: function() {
-               return !this.opts.visual;
-       },
-       
-       /**
-        * Returns true if editor is in WYSIWYG mode.
-        * 
-        * @return      boolean
-        */
-       inWysiwygMode: function() {
-               return (this.opts.visual);
-       },
-       
-       /**
-        * Replaces all ranges from the current selection with the provided one.
-        * 
-        * @param       DOMRange        range
-        */
-       replaceRangesWith: function(range) {
-               getSelection().removeAllRanges();
-               getSelection().addRange(range);
-       },
-       
-       /**
-        * Returns text using BBCodes.
-        * 
-        * @return      string
-        */
-       getText: function() {
-               if (this.inWysiwygMode()) {
-                       this.wSync();
-               }
+               /**
+                * Inserts content into the editor depending if it is in wysiwyg or plain mode. If 'plainValue' is
+                * null or undefined, the value from 'html' will be taken instead.
+                * 
+                * @param       string          html
+                * @param       string          plainValue
+                */
+               insertDynamic: function(html, plainValue) {
+                       if (this.inWysiwygMode()) {
+                               this.insertHtml(html);
+                       }
+                       else {
+                               if (plainValue === undefined || plainValue === null) {
+                                       plainValue = html;
+                               }
+                               
+                               this.insertAtCaret(plainValue);
+                       }
+               },
                
-               return this.$source.val();
-       },
-       
-       /**
-        * Converts HTML to BBCode upon submit.
-        */
-       submit: function() {
-               if (this.inWysiwygMode()) {
-                       this.wSync();
-               }
+               /**
+                * Sets an option value after initialization.
+                * 
+                * @param       string          key
+                * @param       mixed           value
+                */
+               setOption: function(key, value) {
+                       this.opts[key] = value;
+               },
                
-               this.autosavePurge();
-       },
-       
-       /**
-        * Resets the editor's contents.
-        */
-       reset: function() {
-               if (this.inWysiwygMode()) {
-                       this.$editor.html('<p>' + this.opts.invisibleSpace + '</p>');
-                       this.sync();
-               }
-               else {
-                       this.$source.val('');
-               }
+               /**
+                * Reads an option value, returns null if key is unknown.
+                * 
+                * @param       string          key
+                * @return      mixed
+                */
+               getOption: function(key) {
+                       if (this.opts[key]) {
+                               return this.opts[key];
+                       }
+                       
+                       return null;
+               },
                
-               WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'reset', { wysiwygContainerID: this.$source.wcfIdentify() });
-       },
-       
-       /**
-        * Enables automatic saving every minute.
-        * 
-        * @param       string          key
-        */
-       autosaveEnable: function(key) {
-               if (!this.getOption('wautosave').active) {
-                       this.setOption('wautosave', {
-                               active: true,
-                               key: key
-                       });
-               }
+               /**
+                * Returns true if editor is in source mode.
+                * 
+                * @return      boolean
+                */
+               inPlainMode: function() {
+                       return !this.opts.visual;
+               },
                
-               if (this._autosaveWorker === null) {
-                       this._autosaveWorker = new WCF.PeriodicalExecuter($.proxy(this._saveTextToStorage, this), 60 * 1000);
-               }
+               /**
+                * Returns true if editor is in WYSIWYG mode.
+                * 
+                * @return      boolean
+                */
+               inWysiwygMode: function() {
+                       return (this.opts.visual);
+               },
                
-               return true;
-       },
-       
-       /**
-        * Saves current editor text to local browser storage.
-        */
-       _saveTextToStorage: function() {
-               localStorage.setItem(this.getOption('wautosave').key, this.getText());
-       },
-       
-       /**
-        * Disables automatic saving.
-        */
-       autosaveDisable: function() {
-               if (!this.getOption('wautosave').active) {
-                       return false;
-               }
+               /**
+                * Replaces all ranges from the current selection with the provided one.
+                * 
+                * @param       DOMRange        range
+                */
+               replaceRangesWith: function(range) {
+                       getSelection().removeAllRanges();
+                       getSelection().addRange(range);
+               },
                
-               this._autosaveWorker.stop();
-               this._autosaveWorker = null;
+               /**
+                * Returns text using BBCodes.
+                * 
+                * @return      string
+                */
+               getText: function() {
+                       if (this.inWysiwygMode()) {
+                               this.wSync();
+                       }
+                       
+                       return this.$textarea.val();
+               },
                
-               this.setOption('wautosave', {
-                       active: false,
-                       key: ''
-               });
+               /**
+                * Converts HTML to BBCode upon submit.
+                */
+               submit: function() {
+                       if (this.inWysiwygMode()) {
+                               this.wSync();
+                       }
+                       
+                       this.autosavePurge();
+               },
                
-               return true;
-       },
-       
-       /**
-        * Attempts to purge saved text.
-        * 
-        * @param       string          key
-        */
-       autosavePurge: function() {
-               localStorage.removeItem(this.getOption('wautosave').key);
-       },
-       
-       /**
-        * Attempts to restore a saved text.
-        */
-       autosaveRestore: function() {
-               var $options = this.getOption('wautosave');
-               var $text = localStorage.getItem($options.key);
-               if ($text !== null) {
+               /**
+                * Resets the editor's contents.
+                */
+               reset: function() {
                        if (this.inWysiwygMode()) {
-                               this.setOption('wOriginalValue', $text);
+                               this.$editor.html('<p>' + this.opts.invisibleSpace + '</p>');
+                               this.sync();
                        }
                        else {
-                               this.$source.val($text);
+                               this.$textarea.val('');
+                       }
+                       
+                       WCF.System.Event.fireEvent('com.woltlab.wcf.redactor', 'reset', { wysiwygContainerID: this.$textarea.wcfIdentify() });
+               },
+               
+               /**
+                * Enables automatic saving every minute.
+                * 
+                * @param       string          key
+                */
+               autosaveEnable: function(key) {
+                       if (!this.wutil.getOption('wautosave').active) {
+                               this.wutil.setOption('wautosave', {
+                                       active: true,
+                                       key: key
+                               });
+                       }
+                       
+                       if (this._autosaveWorker === null) {
+                               this._autosaveWorker = new WCF.PeriodicalExecuter($.proxy(this._saveTextToStorage, this), 60 * 1000);
                        }
                        
                        return true;
-               }
+               },
                
-               return false;
-       },
-       
-       /**
-        * Replaces one button with a new one.
-        * 
-        * @param       string          target
-        * @param       string          key
-        * @param       string          title
-        * @param       object          callback
-        * @param       object          dropdown
-        * @return      jQuery
-        */
-       buttonReplace: function(target, key, title, callback, dropdown) {
-               var $target = this.buttonGet(target);
+               /**
+                * Saves current editor text to local browser storage.
+                */
+               _saveTextToStorage: function() {
+                       localStorage.setItem(this.getOption('wautosave').key, this.getText());
+               },
                
-               var $button = this.buttonAddAfter(target, key, title, callback, dropdown);
-               if ($target.parent().hasClass('separator')) {
-                       $button.parent().addClass('separator');
-               }
+               /**
+                * Disables automatic saving.
+                */
+               autosaveDisable: function() {
+                       if (!this.getOption('wautosave').active) {
+                               return false;
+                       }
+                       
+                       this._autosaveWorker.stop();
+                       this._autosaveWorker = null;
+                       
+                       this.setOption('wautosave', {
+                               active: false,
+                               key: ''
+                       });
+                       
+                       return true;
+               },
                
-               $target.parent().remove();
+               /**
+                * Attempts to purge saved text.
+                * 
+                * @param       string          key
+                */
+               autosavePurge: function() {
+                       localStorage.removeItem(this.getOption('wautosave').key);
+               },
                
-               return $button;
-       },
-       
-       /**
-        * Removes the unicode zero-width space (0x200B).
-        * 
-        * @param       string          string
-        * @return      string
-        */
-       removeZeroWidthSpace: function(string) {
-               var $string = '';
+               /**
+                * Attempts to restore a saved text.
+                */
+               autosaveRestore: function() {
+                       var $options = this.wutil.getOption('wautosave');
+                       var $text = localStorage.getItem($options.key);
+                       if ($text !== null) {
+                               if (this.wutil.inWysiwygMode()) {
+                                       this.wutil.setOption('wOriginalValue', $text);
+                               }
+                               else {
+                                       this.$textarea.val($text);
+                               }
+                               
+                               return true;
+                       }
+                       
+                       return false;
+               },
                
-               for (var $i = 0, $length = string.length; $i < $length; $i++) {
-                       var $byte = string.charCodeAt($i).toString(16);
-                       if ($byte != '200b') {
-                               $string += string[$i];
+               /**
+                * Replaces one button with a new one.
+                * 
+                * @param       string          target
+                * @param       string          key
+                * @param       string          title
+                * @param       object          callback
+                * @param       object          dropdown
+                * @return      jQuery
+                */
+               buttonReplace: function(target, key, title, callback, dropdown) {
+                       var $target = this.buttonGet(target);
+                       
+                       var $button = this.buttonAddAfter(target, key, title, callback, dropdown);
+                       if ($target.parent().hasClass('separator')) {
+                               $button.parent().addClass('separator');
                        }
-               }
+                       
+                       $target.parent().remove();
+                       
+                       return $button;
+               },
                
-               return $string;
-       },
-       
-       /**
-        * Synchronizes editor's source textarea.
-        */
-       wSync: function() {
-               this.sync(undefined, true);
-               this.$source.val(this.convertFromHtml(this.cleanHtml(this.$source.val())));
-       },
-       
-       /**
-        * Returns source textarea object.
-        * 
-        * @return      jQuery
-        */
-       getSource: function() {
-               return this.$source;
-       },
-       
-       /**
-        * Returns editor instance name.
-        * 
-        * @return      string
-        */
-       getName: function() {
-               return this.$source.wcfIdentify();
-       },
-       
-       /**
-        * Sets the selection after the last direct children of the editor.
-        */
-       selectionEndOfEditor: function() {
-               this.selectionEnd(this.$editor.children(':last')[0]);
-       },
-       
-       /**
-        * Returns true if current selection is just a caret or false if selection spans content.
-        * 
-        * @param       Range           range
-        * @return      boolean
-        */
-       isCaret: function(range) {
-               var $range = (range) ? range : this.getRange();
+               /**
+                * Removes the unicode zero-width space (0x200B).
+                * 
+                * @param       string          string
+                * @return      string
+                */
+               removeZeroWidthSpace: function(string) {
+                       var $string = '';
+                       
+                       for (var $i = 0, $length = string.length; $i < $length; $i++) {
+                               var $byte = string.charCodeAt($i).toString(16);
+                               if ($byte != '200b') {
+                                       $string += string[$i];
+                               }
+                       }
+                       
+                       return $string;
+               },
                
-               return $range.collapsed;
-       },
-       
-       /**
-        * Returns true if current selection is just a caret and it is the last possible offset
-        * within the given element.
-        * 
-        * @param       Element         element
-        * @return      boolean
-        */
-       isEndOfElement: function(element) {
-               var $range = this.getRange();
+               /**
+                * Synchronizes editor's source textarea.
+                */
+               wSync: function() {
+                       this.sync(undefined, true);
+                       this.$textarea.val(this.convertFromHtml(this.cleanHtml(this.$textarea.val())));
+               },
                
-               // range is not a plain caret
-               if (!this.isCaret($range)) {
-                       console.debug("case#1");
-                       return false;
-               }
+               /**
+                * Returns source textarea object.
+                * 
+                * @return      jQuery
+                */
+               getSource: function() {
+                       return this.$textarea;
+               },
                
-               if ($range.endContainer.nodeType === Element.TEXT_NODE) {
-                       // caret is not at the end
-                       if ($range.endOffset < $range.endContainer.length) {
-                               console.debug("case#2");
-                               return false;
-                       }
-               }
+               /**
+                * Returns editor instance name.
+                * 
+                * @return      string
+                */
+               getName: function() {
+                       return this.$textarea.wcfIdentify();
+               },
                
-               // range is not within the provided element
-               if (!this.isNodeWithin($range.endContainer, element)) {
-                       console.debug("case#3");
-                       return false;
-               }
+               /**
+                * Sets the selection after the last direct children of the editor.
+                */
+               selectionEndOfEditor: function() {
+                       this.selectionEnd(this.$editor.children(':last')[0]);
+               },
+               
+               /**
+                * Returns true if current selection is just a caret or false if selection spans content.
+                * 
+                * @return      boolean
+                */
+               isCaret: function() {
+                       this.selection.get();
+                       
+                       return this.range.collapsed;
+               },
                
-               var $current = $range.endContainer;
-               while ($current !== element) {
-                       // end of range is not the last element
-                       if ($current.nextSibling) {
-                               console.debug("case#4");
+               /**
+                * Returns true if current selection is just a caret and it is the last possible offset
+                * within the given element.
+                * 
+                * @param       Element         element
+                * @return      boolean
+                */
+               isEndOfElement: function(element) {
+                       this.selection.get();
+                       
+                       // range is not a plain caret
+                       if (!this.wutil.isCaret()) {
                                return false;
                        }
                        
-                       $current = $current.parentNode;
-               }
-               
-               return true;
-       },
-       
-       /**
-        * Returns true if the provided node is a direct or indirect child of the target element. This
-        * method works similar to jQuery's $.contains() but works recursively.
-        * 
-        * @param       Element         node
-        * @param       Element         element
-        * @return      boolean
-        */
-       isNodeWithin: function(node, element) {
-               var $node = $(node);
-               while ($node[0] !== this.$editor[0]) {
-                       if ($node[0] === element) {
-                               return true;
+                       if (this.range.endContainer.nodeType === Element.TEXT_NODE) {
+                               // caret is not at the end
+                               if (this.range.endOffset < this.range.endContainer.length) {
+                                       return false;
+                               }
                        }
                        
-                       $node = $node.parent();
-               }
+                       // range is not within the provided element
+                       if (!this.wutil.isNodeWithin(this.range.endContainer, element)) {
+                               return false;
+                       }
+                       
+                       var $current = this.range.endContainer;
+                       while ($current !== element) {
+                               // end of range is not the last element
+                               if ($current.nextSibling) {
+                                       return false;
+                               }
+                               
+                               $current = $current.parentNode;
+                       }
+                       
+                       return true;
+               },
                
-               return false;
-       },
-       
-       /**
-        * Returns true if the given node equals the provided tagName or contains it.
-        * 
-        * @param       Element         node
-        * @param       string          tagName
-        * @return      boolean
-        */
-       containsTag: function(node, tagName) {
-               switch (node.nodeType) {
-                       case Element.ELEMENT_NODE:
-                               if (node.tagName === tagName) {
+               /**
+                * Returns true if the provided node is a direct or indirect child of the target element. This
+                * method works similar to jQuery's $.contains() but works recursively.
+                * 
+                * @param       Element         node
+                * @param       Element         element
+                * @return      boolean
+                */
+               isNodeWithin: function(node, element) {
+                       var $node = $(node);
+                       while ($node[0] !== this.$editor[0]) {
+                               if ($node[0] === element) {
                                        return true;
                                }
                                
-                       // fall through
-                       case Element.DOCUMENT_FRAGMENT_NODE:
-                               for (var $i = 0; $i < node.childNodes.length; $i++) {
-                                       if (this.containsTag(node.childNodes[$i], tagName)) {
+                               $node = $node.parent();
+                       }
+                       
+                       return false;
+               },
+               
+               /**
+                * Returns true if the given node equals the provided tagName or contains it.
+                * 
+                * @param       Element         node
+                * @param       string          tagName
+                * @return      boolean
+                */
+               containsTag: function(node, tagName) {
+                       switch (node.nodeType) {
+                               case Element.ELEMENT_NODE:
+                                       if (node.tagName === tagName) {
                                                return true;
                                        }
-                               }
+                                       
+                               // fall through
+                               case Element.DOCUMENT_FRAGMENT_NODE:
+                                       for (var $i = 0; $i < node.childNodes.length; $i++) {
+                                               if (this.wutil.containsTag(node.childNodes[$i], tagName)) {
+                                                       return true;
+                                               }
+                                       }
+                                       
+                                       return false;
+                               break;
                                
-                               return false;
-                       break;
+                               default:
+                                       return false;
+                               break;
+                       }
+               },
+               
+               /**
+                * Replaces the current content with the provided value.
+                * 
+                * @param       string          value
+                */
+               replaceText: function(value) {
+                       var $wasInWysiwygMode = false;
+                       var $offsetTop = $(document).scrollTop();
+                       if (this.inWysiwygMode()) {
+                               this.toggle();
+                               $wasInWysiwygMode = true;
+                       }
                        
-                       default:
-                               return false;
-                       break;
-               }
-       },
-       
-       /**
-        * Replaces the current content with the provided value.
-        * 
-        * @param       string          value
-        */
-       replaceText: function(value) {
-               var $wasInWysiwygMode = false;
-               var $offsetTop = $(document).scrollTop();
-               if (this.inWysiwygMode()) {
-                       this.toggle();
-                       $wasInWysiwygMode = true;
-               }
+                       this.$textarea.val(value);
+                       
+                       if ($wasInWysiwygMode) {
+                               this.toggle();
+                               
+                               // restore scrolling since editor receives the focus
+                               $(document).scrollTop($offsetTop);
+                       }
+               },
                
-               this.$source.val(value);
+               setCaretBefore: function(element) {
+                       this.wutil._setCaret(element, true);
+               },
                
-               if ($wasInWysiwygMode) {
-                       this.toggle();
-                       
-                       // restore scrolling since editor receives the focus
-                       $(document).scrollTop($offsetTop);
+               setCaretAfter: function(element) {
+                       this.wutil._setCaret(element, false);
+               },
+               
+               _setCaret: function(element, setBefore) {
+                       var $node = $(this.opts.emptyHtml);
+                       $node[(setBefore ? 'insertBefore' : 'insertAfter')](element);
+                       this.caret.setStart($node[0]);
                }
-       }
+       };
 };
index cb431e00d76147cba321dd782a61b36d1849b475..8fbf104ed4b620e08d439180d34dc0d0a9ec15ac 100644 (file)
@@ -1,6 +1,6 @@
 /*
-       Redactor v9.2.6
-       Updated: Jul 19, 2014
+       Redactor v10.0
+       Updated: September 24, 2014
 
        http://imperavi.com/redactor/
 
@@ -9,28 +9,26 @@
 
        Usage: $('#content').redactor();
 */
+
 (function($)
 {
-       var uuid = 0;
+       'use strict';
 
-       "use strict";
-
-       var Range = function(range)
+       if (!Function.prototype.bind)
        {
-               this[0] = range.startOffset;
-               this[1] = range.endOffset;
-
-               this.range = range;
-
-               return this;
-       };
+               Function.prototype.bind = function(scope)
+               {
+                       var fn = this;
+                       return function()
+                       {
+                               return fn.apply(scope);
+                       };
+               };
+       }
 
-       Range.prototype.equals = function()
-       {
-               return this[0] === this[1];
-       };
+       var uuid = 0;
 
-       var reUrlYoutube = /https?:\/\/(?:[0-9A-Z-]+\.)?(?:youtu\.be\/|youtube\.com\S*[^\w\-\s])([\w\-]{11})(?=[^\w\-]|$)(?![?=&+%\w.-]*(?:['"][^<>]*>|<\/a>))[?=&+%\w.-]*/ig;
+       var reUrlYoutube = /https?:\/\/(?:[0-9A-Z-]+\.)?(?:youtu\.be\/|youtube\.com\S*[^\w\-\s])([\w\-]{11})(?=[^\w\-]|$)(?![?=&+%\w.\-]*(?:['"][^<>]*>|<\/a>))[?=&+%\w.-]*/ig;
        var reUrlVimeo = /https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/;
 
        // Plugin
                        this.each(function()
                        {
                                var instance = $.data(this, 'redactor');
-                               if (typeof instance !== 'undefined' && $.isFunction(instance[options]))
+                               var func;
+
+                               if (options.search(/\./) != '-1')
+                               {
+                                       func = options.split('.');
+                                       if (typeof instance[func[0]] != 'undefined')
+                                       {
+                                               func = instance[func[0]][func[1]];
+                                       }
+                               }
+                               else
+                               {
+                                       func = instance[options];
+                               }
+
+                               if (typeof instance !== 'undefined' && $.isFunction(func))
+                               {
+                                       var methodVal = func.apply(instance, args);
+                                       if (methodVal !== undefined && methodVal !== instance)
+                                       {
+                                               val.push(methodVal);
+                                       }
+                               }
+                               else
                                {
-                                       var methodVal = instance[options].apply(instance, args);
-                                       if (methodVal !== undefined && methodVal !== instance) val.push(methodVal);
+                                       $.error('No such method "' + options + '" for Redactor');
                                }
-                               else return $.error('No such method "' + options + '" for Redactor');
                        });
                }
                else
                {
                        this.each(function()
                        {
-                               if (!$.data(this, 'redactor')) $.data(this, 'redactor', Redactor(this, options));
+                               $.data(this, 'redactor', {});
+                               $.data(this, 'redactor', Redactor(this, options));
                        });
                }
 
                return new Redactor.prototype.init(el, options);
        }
 
+       // Functionality
        $.Redactor = Redactor;
-       $.Redactor.VERSION = '9.2.6';
+       $.Redactor.VERSION = '10.0';
+       $.Redactor.modules = ['core', 'build', 'lang', 'toolbar', 'button', 'dropdown', 'code',
+                                                 'clean', 'tidy', 'paragraphize', 'tabifier', 'focus', 'placeholder', 'autosave', 'buffer', 'indent', 'alignment', 'paste',
+                                                 'keydown', 'keyup', 'shortcuts', 'line', 'list', 'block', 'inline', 'insert', 'caret', 'selection', 'observe',
+                                                 'link', 'image', 'file', 'modal', 'progress', 'upload', 'utils'];
+
        $.Redactor.opts = {
 
-                       // settings
-                       rangy: false,
-
-                       iframe: false,
-                       fullpage: false,
-                       css: false, // url
-
-                       lang: 'en',
-                       direction: 'ltr', // ltr or rtl
-
-                       placeholder: false,
-
-                       typewriter: false,
-                       wym: false,
-                       mobile: true,
-                       cleanup: true,
-                       tidyHtml: true,
-                       pastePlainText: false,
-                       removeEmptyTags: true,
-                       cleanSpaces: true,
-                       cleanFontTag: true,
-                       templateVars: false,
-                       xhtml: false,
-
-                       visual: true,
-                       focus: false,
-                       tabindex: false,
-                       autoresize: true,
-                       minHeight: false,
-                       maxHeight: false,
-                       shortcuts: {
-                               'ctrl+m, meta+m': "this.execCommand('removeFormat', false)",
-                               'ctrl+b, meta+b': "this.execCommand('bold', false)",
-                               'ctrl+i, meta+i': "this.execCommand('italic', false)",
-                               'ctrl+h, meta+h': "this.execCommand('superscript', false)",
-                               'ctrl+l, meta+l': "this.execCommand('subscript', false)",
-                               'ctrl+k, meta+k': "this.linkShow()",
-                               'ctrl+shift+7': "this.execCommand('insertorderedlist', false)",
-                               'ctrl+shift+8': "this.execCommand('insertunorderedlist', false)"
-                       },
-                       shortcutsAdd: false,
-
-                       autosave: false, // false or url
-                       autosaveInterval: 60, // seconds
-
-                       plugins: false, // array
-
-                       //linkAnchor: true,
-                       //linkEmail: true,
-                       linkProtocol: 'http://',
-                       linkNofollow: false,
-                       linkSize: 50,
-                       predefinedLinks: false, // json url (ex. /some-url.json ) or false
-
-                       imageFloatMargin: '10px',
-                       imageGetJson: false, // json url (ex. /some-images.json ) or false
-
-                       dragUpload: true, // false
-                       imageTabLink: true,
-                       imageUpload: false, // url
-                       imageUploadParam: 'file', // input name
-                       imageResizable: true,
-
-                       fileUpload: false, // url
-                       fileUploadParam: 'file', // input name
-                       clipboardUpload: true, // or false
-                       clipboardUploadUrl: false, // url
-
-                       dnbImageTypes: ['image/png', 'image/jpeg', 'image/gif'], // or false
-
-                       s3: false,
-                       uploadFields: false,
-
-                       observeImages: true,
-                       observeLinks: true,
-
-                       modalOverlay: true,
-
-                       tabSpaces: false, // true or number of spaces
-                       tabFocus: true,
-
-                       air: false,
-                       airButtons: ['formatting', 'bold', 'italic', 'deleted', 'unorderedlist', 'orderedlist', 'outdent', 'indent'],
-
-                       toolbar: true,
-                       toolbarFixed: false,
-                       toolbarFixedTarget: document,
-                       toolbarFixedTopOffset: 0, // pixels
-                       toolbarFixedBox: false,
-                       toolbarExternal: false, // ID selector
-                       toolbarOverflow: false,
-                       buttonSource: true,
-
-                       buttons: ['html', 'formatting', 'bold', 'italic', 'deleted', 'unorderedlist', 'orderedlist',
-                                         'outdent', 'indent', 'image', 'video', 'file', 'table', 'link', 'alignment', '|',
-                                         'horizontalrule'], // 'underline', 'alignleft', 'aligncenter', 'alignright', 'justify'
-                       buttonsHideOnMobile: [],
-
-                       activeButtons: ['deleted', 'italic', 'bold', 'underline', 'unorderedlist', 'orderedlist',
-                                                       'alignleft', 'aligncenter', 'alignright', 'justify', 'table'],
-                       activeButtonsStates: {
-                               b: 'bold',
-                               strong: 'bold',
-                               i: 'italic',
-                               em: 'italic',
-                               del: 'deleted',
-                               strike: 'deleted',
-                               ul: 'unorderedlist',
-                               ol: 'orderedlist',
-                               u: 'underline',
-                               tr: 'table',
-                               td: 'table',
-                               table: 'table'
-                       },
-
-                       formattingTags: ['p', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
-
-                       linebreaks: false,
-                       paragraphy: true,
-                       convertDivs: true,
-                       convertLinks: true,
-                       convertImageLinks: false,
-                       convertVideoLinks: false,
-                       formattingPre: false,
-                       phpTags: false,
-
-                       allowedTags: false,
-                       deniedTags: ['html', 'head', 'link', 'body', 'meta', 'script', 'style', 'applet'],
-
-                       boldTag: 'strong',
-                       italicTag: 'em',
-
-                       // private
-                       indentValue: 20,
-                       buffer: [],
-                       rebuffer: [],
-                       textareamode: false,
-                       emptyHtml: '<p>&#x200b;</p>',
-                       invisibleSpace: '&#x200b;',
-                       rBlockTest: /^(P|H[1-6]|LI|ADDRESS|SECTION|HEADER|FOOTER|ASIDE|ARTICLE)$/i,
-                       alignmentTags: ['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'DD', 'DL', 'DT', 'DIV', 'TD',
-                                                               'BLOCKQUOTE', 'OUTPUT', 'FIGCAPTION', 'ADDRESS', 'SECTION',
-                                                               'HEADER', 'FOOTER', 'ASIDE', 'ARTICLE'],
-                       ownLine: ['area', 'body', 'head', 'hr', 'i?frame', 'link', 'meta', 'noscript', 'style', 'script', 'table', 'tbody', 'thead', 'tfoot'],
-                       contOwnLine: ['li', 'dt', 'dt', 'h[1-6]', 'option', 'script'],
-                       newLevel: ['blockquote', 'div', 'dl', 'fieldset', 'form', 'frameset', 'map', 'ol', 'p', 'pre', 'select', 'td', 'th', 'tr', 'ul'],
-                       blockLevelElements: ['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'DD', 'DL', 'DT', 'DIV', 'LI',
-                                                               'BLOCKQUOTE', 'OUTPUT', 'FIGCAPTION', 'PRE', 'ADDRESS', 'SECTION',
-                                                               'HEADER', 'FOOTER', 'ASIDE', 'ARTICLE', 'TD'],
-
-
-                       // lang
-                       langs: {
-                               en: {
-                                       html: 'HTML',
-                                       video: 'Insert Video',
-                                       image: 'Insert Image',
-                                       table: 'Table',
-                                       link: 'Link',
-                                       link_insert: 'Insert link',
-                                       link_edit: 'Edit link',
-                                       unlink: 'Unlink',
-                                       formatting: 'Formatting',
-                                       paragraph: 'Normal text',
-                                       quote: 'Quote',
-                                       code: 'Code',
-                                       header1: 'Header 1',
-                                       header2: 'Header 2',
-                                       header3: 'Header 3',
-                                       header4: 'Header 4',
-                                       header5: 'Header 5',
-                                       bold: 'Bold',
-                                       italic: 'Italic',
-                                       fontcolor: 'Font Color',
-                                       backcolor: 'Back Color',
-                                       unorderedlist: 'Unordered List',
-                                       orderedlist: 'Ordered List',
-                                       outdent: 'Outdent',
-                                       indent: 'Indent',
-                                       cancel: 'Cancel',
-                                       insert: 'Insert',
-                                       save: 'Save',
-                                       _delete: 'Delete',
-                                       insert_table: 'Insert Table',
-                                       insert_row_above: 'Add Row Above',
-                                       insert_row_below: 'Add Row Below',
-                                       insert_column_left: 'Add Column Left',
-                                       insert_column_right: 'Add Column Right',
-                                       delete_column: 'Delete Column',
-                                       delete_row: 'Delete Row',
-                                       delete_table: 'Delete Table',
-                                       rows: 'Rows',
-                                       columns: 'Columns',
-                                       add_head: 'Add Head',
-                                       delete_head: 'Delete Head',
-                                       title: 'Title',
-                                       image_position: 'Position',
-                                       none: 'None',
-                                       left: 'Left',
-                                       right: 'Right',
-                                       center: 'Center',
-                                       image_web_link: 'Image Web Link',
-                                       text: 'Text',
-                                       mailto: 'Email',
-                                       web: 'URL',
-                                       video_html_code: 'Video Embed Code',
-                                       file: 'Insert File',
-                                       upload: 'Upload',
-                                       download: 'Download',
-                                       choose: 'Choose',
-                                       or_choose: 'Or choose',
-                                       drop_file_here: 'Drop file here',
-                                       align_left: 'Align text to the left',
-                                       align_center: 'Center text',
-                                       align_right: 'Align text to the right',
-                                       align_justify: 'Justify text',
-                                       horizontalrule: 'Insert Horizontal Rule',
-                                       deleted: 'Deleted',
-                                       anchor: 'Anchor',
-                                       link_new_tab: 'Open link in new tab',
-                                       underline: 'Underline',
-                                       alignment: 'Alignment',
-                                       filename: 'Name (optional)',
-                                       edit: 'Edit'
-                               }
+               // settings
+               lang: 'en',
+               direction: 'ltr', // ltr or rtl
+
+               plugins: false, // array
+
+               focus: false,
+               focusEnd: false,
+
+               placeholder: false,
+
+               visual: true,
+               tabindex: false,
+
+               minHeight: false,
+               maxHeight: false,
+
+               linebreaks: false,
+               replaceDivs: true,
+               paragraphize: true,
+               cleanStyleOnEnter: false,
+               enterKey: true,
+
+               cleanOnPaste: true,
+               cleanSpaces: true,
+               pastePlainText: false,
+
+               autosave: false, // false or url
+               autosaveName: false,
+               autosaveInterval: 60, // seconds
+               autosaveOnChange: false,
+
+               linkTooltip: true,
+               linkProtocol: 'http',
+               linkNofollow: false,
+               linkSize: 50,
+
+               imageEditable: true,
+               imageLink: true,
+               imagePosition: true,
+               imageFloatMargin: '10px',
+               imageResizable: true,
+
+               imageUpload: false,
+               imageUploadParam: 'file',
+
+               uploadImageField: false,
+
+               dragImageUpload: true,
+               clipboardImageUpload: false,
+
+               fileUpload: false,
+               fileUploadParam: 'file',
+
+               dragFileUpload: true,
+
+               s3: false,
+
+               convertLinks: true,
+               convertUrlLinks: true,
+               convertImageLinks: true,
+               convertVideoLinks: true,
+
+               preSpaces: 4, // or false
+               tabAsSpaces: false, // true or number of spaces
+               tabFocus: true,
+
+               scrollTarget: false,
+
+               toolbar: true,
+               toolbarFixed: true,
+               toolbarFixedTarget: document,
+               toolbarExternal: false, // ID selector
+               toolbarOverflow: false,
+
+               buttonSource: false,
+               buttons: ['html', 'formatting', 'bold', 'italic', 'deleted', 'unorderedlist', 'orderedlist',
+                                 'outdent', 'indent', 'image', 'file', 'link', 'alignment', 'horizontalrule'], // + 'underline'
+
+               buttonsHide: [],
+               buttonsHideOnMobile: [],
+
+               formatting: ['p', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
+               formattingAdd: false,
+
+               tabifier: true,
+
+               deniedTags: ['html', 'head', 'link', 'body', 'meta', 'script', 'style', 'applet'],
+               allowedTags: false, // or array
+
+               removeComments: false,
+               replaceTags: [
+                       ['strike', 'del']
+               ],
+               replaceStyles: [
+            ['font-weight:\\s?bold', "strong"],
+            ['font-style:\\s?italic', "em"],
+            ['text-decoration:\\s?underline', "u"],
+            ['text-decoration:\\s?line-through', 'del']
+        ],
+        removeDataAttr: false,
+
+               removeAttr: false, // or multi array
+               allowedAttr: false, // or multi array
+
+               removeWithoutAttr: ['span'], // or false
+               removeEmpty: ['p'], // or false;
+
+               activeButtons: ['deleted', 'italic', 'bold', 'underline', 'unorderedlist', 'orderedlist',
+                                               'alignleft', 'aligncenter', 'alignright', 'justify'],
+               activeButtonsStates: {
+                       b: 'bold',
+                       strong: 'bold',
+                       i: 'italic',
+                       em: 'italic',
+                       del: 'deleted',
+                       strike: 'deleted',
+                       ul: 'unorderedlist',
+                       ol: 'orderedlist',
+                       u: 'underline'
+               },
+
+               shortcuts: {
+                       'ctrl+shift+m, meta+shift+m': { func: 'inline.removeFormat' },
+                       'ctrl+b, meta+b': { func: 'inline.format', params: ['bold'] },
+                       'ctrl+i, meta+i': { func: 'inline.format', params: ['italic'] },
+                       'ctrl+h, meta+h': { func: 'inline.format', params: ['superscript'] },
+                       'ctrl+l, meta+l': { func: 'inline.format', params: ['subscript'] },
+                       'ctrl+k, meta+k': { func: 'link.show' },
+                       'ctrl+shift+7':   { func: 'list.toggle', params: ['orderedlist'] },
+                       'ctrl+shift+8':   { func: 'list.toggle', params: ['unorderedlist'] }
+               },
+               shortcutsAdd: false,
+
+               // private
+               buffer: [],
+               rebuffer: [],
+               emptyHtml: '<p>&#x200b;</p>',
+               invisibleSpace: '&#x200b;',
+               imageTypes: ['image/png', 'image/jpeg', 'image/gif'],
+               indentValue: 20,
+               verifiedTags:           ['a', 'img', 'b', 'strong', 'sub', 'sup', 'i', 'em', 'u', 'small', 'strike', 'del', 'cite', 'ul', 'ol', 'li'], // and for span tag special rule
+               inlineTags:             ['strong', 'b', 'u', 'em', 'i', 'code', 'del', 'ins', 'samp', 'kbd', 'sup', 'sub', 'mark', 'var', 'cite', 'small'],
+               alignmentTags:          ['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6',  'DL', 'DT', 'DD', 'DIV', 'TD', 'BLOCKQUOTE', 'OUTPUT', 'FIGCAPTION', 'ADDRESS', 'SECTION', 'HEADER', 'FOOTER', 'ASIDE', 'ARTICLE'],
+               blockLevelElements: ['PRE', 'UL', 'OL', 'LI'],
+
+
+               // lang
+               langs: {
+                       en: {
+                               html: 'HTML',
+                               video: 'Insert Video',
+                               image: 'Insert Image',
+                               table: 'Table',
+                               link: 'Link',
+                               link_insert: 'Insert link',
+                               link_edit: 'Edit link',
+                               unlink: 'Unlink',
+                               formatting: 'Formatting',
+                               paragraph: 'Normal text',
+                               quote: 'Quote',
+                               code: 'Code',
+                               header1: 'Header 1',
+                               header2: 'Header 2',
+                               header3: 'Header 3',
+                               header4: 'Header 4',
+                               header5: 'Header 5',
+                               bold: 'Bold',
+                               italic: 'Italic',
+                               fontcolor: 'Font Color',
+                               backcolor: 'Back Color',
+                               unorderedlist: 'Unordered List',
+                               orderedlist: 'Ordered List',
+                               outdent: 'Outdent',
+                               indent: 'Indent',
+                               cancel: 'Cancel',
+                               insert: 'Insert',
+                               save: 'Save',
+                               _delete: 'Delete',
+                               insert_table: 'Insert Table',
+                               insert_row_above: 'Add Row Above',
+                               insert_row_below: 'Add Row Below',
+                               insert_column_left: 'Add Column Left',
+                               insert_column_right: 'Add Column Right',
+                               delete_column: 'Delete Column',
+                               delete_row: 'Delete Row',
+                               delete_table: 'Delete Table',
+                               rows: 'Rows',
+                               columns: 'Columns',
+                               add_head: 'Add Head',
+                               delete_head: 'Delete Head',
+                               title: 'Title',
+                               image_position: 'Position',
+                               none: 'None',
+                               left: 'Left',
+                               right: 'Right',
+                               center: 'Center',
+                               image_web_link: 'Image Web Link',
+                               text: 'Text',
+                               mailto: 'Email',
+                               web: 'URL',
+                               video_html_code: 'Video Embed Code or Youtube/Vimeo Link',
+                               file: 'Insert File',
+                               upload: 'Upload',
+                               download: 'Download',
+                               choose: 'Choose',
+                               or_choose: 'Or choose',
+                               drop_file_here: 'Drop file here',
+                               align_left: 'Align text to the left',
+                               align_center: 'Center text',
+                               align_right: 'Align text to the right',
+                               align_justify: 'Justify text',
+                               horizontalrule: 'Insert Horizontal Rule',
+                               deleted: 'Deleted',
+                               anchor: 'Anchor',
+                               link_new_tab: 'Open link in new tab',
+                               underline: 'Underline',
+                               alignment: 'Alignment',
+                               filename: 'Name (optional)',
+                               edit: 'Edit'
                        }
+               }
        };
 
        // Functionality
                        DELETE: 46,
                        DOWN: 40,
                        ENTER: 13,
+                       SPACE: 32,
                        ESC: 27,
                        TAB: 9,
                        CTRL: 17,
                        META: 91,
+                       SHIFT: 16,
+                       ALT: 18,
                        LEFT: 37,
                        LEFT_WIN: 91
                },
                // Initialization
                init: function(el, options)
                {
-                       this.rtePaste = false;
-                       this.$element = this.$source = $(el);
+                       this.$element = $(el);
                        this.uuid = uuid++;
 
-                       // clonning options
-                       var opts = $.extend(true, {}, $.Redactor.opts);
+                       // if paste event detected = true
+                       this.rtePaste = false;
+                       this.$pasteBox = false;
 
-                       // current settings
-                       this.opts = $.extend(
-                               {},
-                               opts,
-                               this.$element.data(),
-                               options
-                       );
+                       this.loadOptions(options);
+                       this.loadModules();
 
-                       this.start = true;
-                       this.dropdowns = [];
+                       // formatting storage
+                       this.formatting = {};
 
-                       // get sizes
-                       this.sourceHeight = this.$source.css('height');
-                       this.sourceWidth = this.$source.css('width');
+                       // block level tags
+                       $.merge(this.opts.blockLevelElements, this.opts.alignmentTags);
+                       this.reIsBlock = new RegExp('^(' + this.opts.blockLevelElements.join('|' ) + ')$', 'i');
 
-                       // dependency of the editor modes
-                       if (this.opts.fullpage) this.opts.iframe = true;
-                       if (this.opts.linebreaks) this.opts.paragraphy = false;
-                       if (this.opts.paragraphy) this.opts.linebreaks = false;
-                       if (this.opts.toolbarFixedBox) this.opts.toolbarFixed = true;
+                       // setup allowed and denied tags
+                       this.tidy.setupAllowed();
 
-                       // the alias for iframe mode
-                       this.document = document;
-                       this.window = window;
+                       // load lang
+                       this.lang.load();
 
-                       // selection saved
-                       this.savedSel = false;
+                       // extend shortcuts
+                       $.extend(this.opts.shortcuts, this.opts.shortcutsAdd);
 
-                       // clean setup
-                       this.cleanlineBefore = new RegExp('^<(/?' + this.opts.ownLine.join('|/?' ) + '|' + this.opts.contOwnLine.join('|') + ')[ >]');
-                       this.cleanlineAfter = new RegExp('^<(br|/?' + this.opts.ownLine.join('|/?' ) + '|/' + this.opts.contOwnLine.join('|/') + ')[ >]');
-                       this.cleannewLevel = new RegExp('^</?(' + this.opts.newLevel.join('|' ) + ')[ >]');
+                       // start callback
+                       this.core.setCallback('start');
 
-                       // block level
-                       this.rTestBlock = new RegExp('^(' + this.opts.blockLevelElements.join('|' ) + ')$', 'i');
+                       // build
+                       this.start = true;
+                       this.build.run();
+               },
 
-                       // setup formatting permissions
-                       if (this.opts.linebreaks === false)
+               loadOptions: function(options)
+               {
+                       this.opts = $.extend(
+                               {},
+                               $.extend(true, {}, $.Redactor.opts),
+                               this.$element.data(),
+                               options
+                       );
+               },
+               getModuleMethods: function(object)
+               {
+                       return Object.getOwnPropertyNames(object).filter(function(property)
                        {
-                               if (this.opts.allowedTags !== false)
-                               {
-                                       var arrSearch = ['strong', 'em', 'del'];
-                                       var arrAdd = ['b', 'i', 'strike'];
-
-                                       if ($.inArray('p', this.opts.allowedTags) === '-1') this.opts.allowedTags.push('p');
-
-                                       for (i in arrSearch)
-                                       {
-                                               if ($.inArray(arrSearch[i], this.opts.allowedTags) != '-1') this.opts.allowedTags.push(arrAdd[i]);
-                                       }
-                               }
-
-                               if (this.opts.deniedTags !== false)
-                               {
-                                       var pos = $.inArray('p', this.opts.deniedTags);
-                                       if (pos !== '-1') this.opts.deniedTags.splice(pos, pos);
-                               }
-                       }
-
-                       // ie & opera
-                       if (this.browser('msie') || this.browser('opera'))
+                               return typeof object[property] == 'function';
+                       });
+               },
+               loadModules: function()
+               {
+                       var len = $.Redactor.modules.length;
+                       for (var i = 0; i < len; i++)
                        {
-                               this.opts.buttons = this.removeFromArrayByValue(this.opts.buttons, 'horizontalrule');
+                               this.bindModuleMethods($.Redactor.modules[i]);
                        }
+               },
+               bindModuleMethods: function(module)
+               {
+                       if (typeof this[module] == 'undefined') return;
 
-                       // load lang
-                       this.opts.curLang = this.opts.langs[this.opts.lang];
-
-                       // extend shortcuts
-                       $.extend(this.opts.shortcuts, this.opts.shortcutsAdd);
-
-                       // init placeholder
-                       this.placeholderInit();
+                       // init module
+                       this[module] = this[module]();
 
-                       // Build
-                       this.buildStart();
+                       var methods = this.getModuleMethods(this[module]);
+                       var len = methods.length;
 
+                       // bind methods
+                       for (var z = 0; z < len; z++)
+                       {
+                               this[module][methods[z]] = this[module][methods[z]].bind(this);
+                       }
                },
-               toolbarInit: function(lang)
+
+               core: function()
                {
                        return {
-                               html:
+                               getObject: function()
                                {
-                                       title: lang.html,
-                                       func: 'toggle'
+                                       return $.extend({}, this);
                                },
-                               formatting:
+                               getEditor: function()
                                {
-                                       title: lang.formatting,
-                                       func: 'show',
-                                       dropdown:
-                                       {
-                                               p:
-                                               {
-                                                       title: lang.paragraph,
-                                                       func: 'formatBlocks'
-                                               },
-                                               blockquote:
-                                               {
-                                                       title: lang.quote,
-                                                       func: 'formatQuote',
-                                                       className: 'redactor_format_blockquote'
-                                               },
-                                               pre:
-                                               {
-                                                       title: lang.code,
-                                                       func: 'formatBlocks',
-                                                       className: 'redactor_format_pre'
-                                               },
-                                               h1:
-                                               {
-                                                       title: lang.header1,
-                                                       func: 'formatBlocks',
-                                                       className: 'redactor_format_h1'
-                                               },
-                                               h2:
-                                               {
-                                                       title: lang.header2,
-                                                       func: 'formatBlocks',
-                                                       className: 'redactor_format_h2'
-                                               },
-                                               h3:
-                                               {
-                                                       title: lang.header3,
-                                                       func: 'formatBlocks',
-                                                       className: 'redactor_format_h3'
-                                               },
-                                               h4:
-                                               {
-                                                       title: lang.header4,
-                                                       func: 'formatBlocks',
-                                                       className: 'redactor_format_h4'
-                                               },
-                                               h5:
-                                               {
-                                                       title: lang.header5,
-                                                       func: 'formatBlocks',
-                                                       className: 'redactor_format_h5'
-                                               }
-                                       }
+                                       return this.$editor;
                                },
-                               bold:
+                               getBox: function()
                                {
-                                       title: lang.bold,
-                                       exec: 'bold'
+                                       return this.$box;
                                },
-                               italic:
+                               getElement: function()
                                {
-                                       title: lang.italic,
-                                       exec: 'italic'
+                                       return this.$element;
                                },
-                               deleted:
+                               getTextarea: function()
                                {
-                                       title: lang.deleted,
-                                       exec: 'strikethrough'
+                                       return this.$textarea;
                                },
-                               underline:
+                               getToolbar: function()
                                {
-                                       title: lang.underline,
-                                       exec: 'underline'
+                                       return (this.$toolbar) ? this.$toolbar : false;
                                },
-                               unorderedlist:
+                               addEvent: function(name)
                                {
-                                       title: '&bull; ' + lang.unorderedlist,
-                                       exec: 'insertunorderedlist'
+                                       this.core.event = name;
                                },
-                               orderedlist:
+                               getEvent: function()
                                {
-                                       title: '1. ' + lang.orderedlist,
-                                       exec: 'insertorderedlist'
+                                       return this.core.event;
                                },
-                               outdent:
+                               setCallback: function(type, e, data)
                                {
-                                       title: '< ' + lang.outdent,
-                                       func: 'indentingOutdent'
+                                       var callback = this.opts[type + 'Callback'];
+                                       if ($.isFunction(callback))
+                                       {
+                                               return (typeof data == 'undefined') ? callback.call(this, e) : callback.call(this, e, data);
+                                       }
+                                       else
+                                       {
+                                               return (typeof data == 'undefined') ? e : data;
+                                       }
                                },
-                               indent:
+                               destroy: function()
                                {
-                                       title: '> ' + lang.indent,
-                                       func: 'indentingIndent'
-                               },
-                               image:
+                                       this.core.setCallback('destroy');
+
+                                       // off events and remove data
+                                       this.$element.off('.redactor').removeData('redactor');
+                                       this.$editor.off('.redactor');
+
+                                       // common
+                                       this.$editor.removeClass('redactor-editor redactor-linebreaks redactor-placeholder');
+                                       this.$editor.removeAttr('contenteditable');
+
+                                       var html = this.code.get();
+
+                                       if (this.build.isTextarea())
+                                       {
+                                               this.$box.after(this.$element);
+                                               this.$box.remove();
+                                               this.$element.val(html).show();
+                                       }
+                                       else
+                                       {
+                                               this.$box.after(this.$editor);
+                                               this.$box.remove();
+                                               this.$element.html(html).show();
+                                       }
+
+                                       // paste box
+                                       if (this.$pasteBox) this.$pasteBox.remove();
+
+                                       // modal
+                                       if (this.$modalBox) this.$modalBox.remove();
+                                       if (this.$modalOverlay) this.$modalOverlay.remove();
+
+                                       // buttons tooltip
+                                       $('.redactor-toolbar-tooltip').remove();
+
+                                       // autosave
+                                       clearInterval(this.autosaveInterval);
+
+                               }
+                       };
+               },
+               build: function()
+               {
+                       return {
+                               run: function()
                                {
-                                       title: lang.image,
-                                       func: 'imageShow'
+
+                                       this.build.createContainerBox();
+                                       this.build.loadContent();
+                                       this.build.loadEditor();
+                                       this.build.enableEditor();
+                                       this.build.setCodeAndCall();
+
                                },
-                               video:
+                               isTextarea: function()
                                {
-                                       title: lang.video,
-                                       func: 'videoShow'
+                                       return (this.$element[0].tagName === 'TEXTAREA');
                                },
-                               file:
+                               createContainerBox: function()
                                {
-                                       title: lang.file,
-                                       func: 'fileShow'
+                                       this.$box = $('<div class="redactor-box" />');
                                },
-                               table:
+                               createTextarea: function()
                                {
-                                       title: lang.table,
-                                       func: 'show',
-                                       dropdown:
-                                       {
-                                               insert_table:
-                                               {
-                                                       title: lang.insert_table,
-                                                       func: 'tableShow'
-                                               },
-                                               separator_drop1:
-                                               {
-                                                       name: 'separator'
-                                               },
-                                               insert_row_above:
-                                               {
-                                                       title: lang.insert_row_above,
-                                                       func: 'tableAddRowAbove'
-                                               },
-                                               insert_row_below:
-                                               {
-                                                       title: lang.insert_row_below,
-                                                       func: 'tableAddRowBelow'
-                                               },
-                                               insert_column_left:
-                                               {
-                                                       title: lang.insert_column_left,
-                                                       func: 'tableAddColumnLeft'
-                                               },
-                                               insert_column_right:
-                                               {
-                                                       title: lang.insert_column_right,
-                                                       func: 'tableAddColumnRight'
-                                               },
-                                               separator_drop2:
-                                               {
-                                                       name: 'separator'
-                                               },
-                                               add_head:
-                                               {
-                                                       title: lang.add_head,
-                                                       func: 'tableAddHead'
-                                               },
-                                               delete_head:
-                                               {
-                                                       title: lang.delete_head,
-                                                       func: 'tableDeleteHead'
-                                               },
-                                               separator_drop3:
-                                               {
-                                                       name: 'separator'
-                                               },
-                                               delete_column:
-                                               {
-                                                       title: lang.delete_column,
-                                                       func: 'tableDeleteColumn'
-                                               },
-                                               delete_row:
-                                               {
-                                                       title: lang.delete_row,
-                                                       func: 'tableDeleteRow'
-                                               },
-                                               delete_table:
-                                               {
-                                                       title: lang.delete_table,
-                                                       func: 'tableDeleteTable'
-                                               }
-                                       }
+                                       this.$textarea = $('<textarea />').attr('name', this.build.getTextareaName());
                                },
-                               link: {
-                                       title: lang.link,
-                                       func: 'show',
-                                       dropdown:
+                               getTextareaName: function()
+                               {
+                                       var name = this.$element.attr('id');
+                                       if (typeof(name) == 'undefined')
                                        {
-                                               link:
-                                               {
-                                                       title: lang.link_insert,
-                                                       func: 'linkShow'
-                                               },
-                                               unlink:
-                                               {
-                                                       title: lang.unlink,
-                                                       exec: 'unlink'
-                                               }
+                                               name = 'content-' + this.uuid;
                                        }
+
+                                       return name;
                                },
-                               alignment:
+                               loadContent: function()
                                {
-                                       title: lang.alignment,
-                                       func: 'show',
-                                       dropdown:
-                                       {
-                                               alignleft:
-                                               {
-                                                       title: lang.align_left,
-                                                       func: 'alignmentLeft'
-                                               },
-                                               aligncenter:
-                                               {
-                                                       title: lang.align_center,
-                                                       func: 'alignmentCenter'
-                                               },
-                                               alignright:
-                                               {
-                                                       title: lang.align_right,
-                                                       func: 'alignmentRight'
-                                               },
-                                               justify:
-                                               {
-                                                       title: lang.align_justify,
-                                                       func: 'alignmentJustify'
-                                               }
-                                       }
+                                       var func = (this.build.isTextarea()) ? 'val' : 'html';
+                                       this.content = $.trim(this.$element[func]());
                                },
-                               alignleft:
+                               enableEditor: function()
                                {
-                                       title: lang.align_left,
-                                       func: 'alignmentLeft'
+                                       this.$editor.attr({ 'contenteditable': true, 'dir': this.opts.direction });
                                },
-                               aligncenter:
+                               loadEditor: function()
                                {
-                                       title: lang.align_center,
-                                       func: 'alignmentCenter'
+                                       var func = (this.build.isTextarea()) ? 'fromTextarea' : 'fromElement';
+                                       this.build[func]();
                                },
-                               alignright:
+                               fromTextarea: function()
                                {
-                                       title: lang.align_right,
-                                       func: 'alignmentRight'
+                                       this.$editor = $('<div />');
+                                       this.$textarea = this.$element;
+                                       this.$box.insertAfter(this.$element).append(this.$editor).append(this.$element);
+                                       this.$editor.addClass('redactor-editor');
+
+                                       this.$element.hide();
                                },
-                               alignjustify:
+                               fromElement: function()
                                {
-                                       title: lang.align_justify,
-                                       func: 'alignmentJustify'
+                                       this.$editor = this.$element;
+                                       this.build.createTextarea();
+                                       this.$box.insertAfter(this.$editor).append(this.$editor).append(this.$textarea);
+                                       this.$editor.addClass('redactor-editor');
+
+                                       this.$textarea.hide();
                                },
-                               horizontalrule:
+                               setCodeAndCall: function()
                                {
-                                       exec: 'inserthorizontalrule',
-                                       title: lang.horizontalrule
-                               }
+                                       // set code
+                                       this.code.set(this.content);
 
-                       }
-               },
+                                       this.build.setOptions();
+                                       this.build.callEditor();
 
-               // CALLBACKS
-               callback: function(type, event, data)
-               {
-                       var callback = this.opts[ type + 'Callback' ];
-                       if ($.isFunction(callback))
-                       {
-                               if (event === false) return callback.call(this, data);
-                               else return callback.call(this, event, data);
-                       }
-                       else return data;
-               },
+                                       // code mode
+                                       if (!this.opts.visual)
+                                       {
+                                               setTimeout($.proxy(this.code.showCode, this), 200);
+                                       }
+                               },
+                               callEditor: function()
+                               {
+                                       this.build.disableMozillaEditing();
+                                       this.build.setEvents();
+                                       this.build.setHelpers();
 
+                                       // load toolbar
+                                       if (this.opts.toolbar)
+                                       {
+                                               this.opts.toolbar = this.toolbar.init();
+                                               this.toolbar.build();
+                                       }
 
-               // DESTROY
-               destroy: function()
-               {
-                       clearInterval(this.autosaveInterval);
+                                       // modal templates init
+                                       this.modal.loadTemplates();
 
-                       $(window).off('.redactor');
-                       this.$source.off('redactor-textarea');
-                       this.$element.off('.redactor').removeData('redactor');
+                                       // plugins
+                                       this.build.plugins();
 
-                       var html = this.get();
+                                       // observers
+                                       setTimeout($.proxy(this.observe.load, this), 4);
 
-                       if (this.opts.textareamode)
-                       {
-                               this.$box.after(this.$source);
-                               this.$box.remove();
-                               this.$source.val(html).show();
-                       }
-                       else
-                       {
-                               var $elem = this.$editor;
-                               if (this.opts.iframe) $elem = this.$element;
+                                       // init callback
+                                       this.core.setCallback('init');
+                               },
+                               setOptions: function()
+                               {
+                                       // textarea direction
+                                       $(this.$textarea).attr('dir', this.opts.direction);
 
-                               this.$box.after($elem);
-                               this.$box.remove();
+                                       if (this.opts.linebreaks) this.$editor.addClass('redactor-linebreaks');
 
-                               $elem.removeClass('redactor_editor').removeClass('redactor_editor_wym').removeAttr('contenteditable').html(html).show();
-                       }
+                                       if (this.opts.tabindex) this.$editor.attr('tabindex', this.opts.tabindex);
 
-                       if (this.opts.toolbarExternal)
-                       {
-                               $(this.opts.toolbarExternal).html('');
-                       }
+                                       if (this.opts.minHeight) this.$editor.css('minHeight', this.opts.minHeight);
+                                       if (this.opts.maxHeight) this.$editor.css('maxHeight', this.opts.maxHeight);
 
-                       if (this.opts.air)
-                       {
-                               $('#redactor_air_' + this.uuid).remove();
-                       }
-               },
+                               },
+                               setEvents: function()
+                               {
+                                       // drop
+                                       this.$editor.on('drop.redactor', $.proxy(function(e)
+                                       {
+                                               e = e.originalEvent || e;
 
-               // API GET
-               getObject: function()
-               {
-                       return $.extend({}, this);
-               },
-               getEditor: function()
-               {
-                       return this.$editor;
-               },
-               getBox: function()
-               {
-                       return this.$box;
-               },
-               getIframe: function()
-               {
-                       return (this.opts.iframe) ? this.$frame : false;
-               },
-               getToolbar: function()
-               {
-                       return (this.$toolbar) ? this.$toolbar : false;
-               },
+                                               if (window.FormData === undefined || !e.dataTransfer) return true;
 
-               // CODE GET & SET
-               get: function()
-               {
-                       return this.$source.val();
-               },
-               getCodeIframe: function()
-               {
-                       this.$editor.removeAttr('contenteditable').removeAttr('dir');
-                       var html = this.outerHtml(this.$frame.contents().children());
-                       this.$editor.attr({ 'contenteditable': true, 'dir': this.opts.direction });
+                                           var length = e.dataTransfer.files.length;
+                                           if (length === 0) return true;
+                                               else
+                                               {
+                                                   e.preventDefault();
 
-                       return html;
-               },
-               set: function(html, strip, placeholderRemove)
-               {
-                       html = html.toString();
-                       html = html.replace(/\$/g, '&#36;');
+                                                       if (this.opts.dragImageUpload || this.opts.dragFileUpload)
+                                                       {
+                                                               var files = e.dataTransfer.files;
+                                                               this.upload.directUpload(files[0], e);
+                                                       }
+                                               }
 
-                       if (this.opts.fullpage) this.setCodeIframe(html);
-                       else this.setEditor(html, strip);
+                                               setTimeout($.proxy(this.clean.clearUnverified, this), 1);
 
-                       if (html == '') placeholderRemove = false;
-                       if (placeholderRemove !== false) this.placeholderRemoveFromEditor();
-               },
-               setEditor: function(html, strip)
-               {
+                                               this.core.setCallback('drop', e);
 
-                       if (strip !== false)
-                       {
-                               html = this.cleanSavePreCode(html);
+                                       }, this));
 
-                               html = this.cleanStripTags(html);
-                               html = this.cleanConvertProtected(html);
-                               html = this.cleanConvertInlineTags(html, true);
 
-                               if (this.opts.linebreaks === false)     html = this.cleanConverters(html);
-                               else html = html.replace(/<p(.*?)>([\w\W]*?)<\/p>/gi, '$2<br>');
-                       }
+                                       // click
+                                       this.$editor.on('click.redactor', $.proxy(function(e)
+                                       {
+                                               var type = 'click';
+                                               if ((this.core.getEvent() == 'click' || this.core.getEvent() == 'arrow'))
+                                               {
+                                                       type = false;
+                                               }
 
-                       // $ fix
-                       html = html.replace(/&amp;#36;/g, '$');
+                                               this.core.addEvent(type);
+                                               this.utils.disableSelectAll();
+                                               this.core.setCallback('click', e);
 
-                       html = this.cleanEmpty(html);
+                                       }, this));
 
-                       this.$editor.html(html);
+                                       // paste
+                                       this.$editor.on('paste.redactor', $.proxy(this.paste.init, this));
 
-                       // set no editable
-                       this.setNonEditable();
-                       this.setSpansVerified();
+                                       // keydown
+                                       this.$editor.on('keydown.redactor', $.proxy(this.keydown.init, this));
 
-                       this.sync();
-               },
-               setCodeIframe: function(html)
-               {
-                       var doc = this.iframePage();
-                       this.$frame[0].src = "about:blank";
+                                       // keyup
+                                       this.$editor.on('keyup.redactor', $.proxy(this.keyup.init, this));
 
-                       html = this.cleanConvertProtected(html);
-                       html = this.cleanConvertInlineTags(html);
-                       html = this.cleanRemoveSpaces(html);
+                                       // textarea keydown
+                                       if ($.isFunction(this.opts.codeKeydownCallback))
+                                       {
+                                               this.$textarea.on('keydown.redactor-textarea', $.proxy(this.opts.codeKeydownCallback, this));
+                                       }
 
-                       doc.open();
-                       doc.write(html);
-                       doc.close();
+                                       // textarea keyup
+                                       if ($.isFunction(this.opts.codeKeyupCallback))
+                                       {
+                                               this.$textarea.on('keyup.redactor-textarea', $.proxy(this.opts.codeKeyupCallback, this));
+                                       }
 
-                       // redefine editor for fullpage mode
-                       if (this.opts.fullpage)
-                       {
-                               this.$editor = this.$frame.contents().find('body').attr({ 'contenteditable': true, 'dir': this.opts.direction });
-                       }
+                                       // focus
+                                       if ($.isFunction(this.opts.focusCallback))
+                                       {
+                                               this.$editor.on('focus.redactor', $.proxy(this.opts.focusCallback, this));
+                                       }
 
-                       // set no editable
-                       this.setNonEditable();
-                       this.setSpansVerified();
-                       this.sync();
+                                       var clickedElement;
+                                       $(document).on('mousedown', function(e) {
+                                               clickedElement = $(e.target);
+                                       });
 
-               },
-               setFullpageOnInit: function(html)
-               {
-                       this.fullpageDoctype = html.match(/^<\!doctype[^>]*>/i);
-                       if (this.fullpageDoctype && this.fullpageDoctype.length == 1)
-                       {
-                               html = html.replace(/^<\!doctype[^>]*>/i, '');
-                       }
+                                       // blur
+                                       this.$editor.on('blur.redactor', $.proxy(function(e)
+                                       {
+                                               if (this.rtePaste) return;
 
-                       html = this.cleanSavePreCode(html, true);
-                       html = this.cleanConverters(html);
-                       html = this.cleanEmpty(html);
+                                               var $el = $(clickedElement);
+                                               if (!$el.hasClass('redactor-toolbar, redactor-dropdown') && !$el.is('#redactor-modal') && $el.parents('.redactor-toolbar, .redactor-dropdown, #redactor-modal').size() === 0)
+                                               {
+                                                       this.utils.disableSelectAll();
+                                                       if ($.isFunction(this.opts.blurCallback)) this.core.setCallback('blur', e);
+                                               }
+                                       }, this));
+                               },
+                               setHelpers: function()
+                               {
+                                       // autosave
+                                       this.autosave.enable();
 
-                       // set code
-                       this.$editor.html(html);
+                                       // placeholder
+                                       this.placeholder.enable();
 
-                       // set no editable
-                       this.setNonEditable();
-                       this.setSpansVerified();
-                       this.sync();
-               },
-               setFullpageDoctype: function()
-               {
-                       if (this.fullpageDoctype && this.fullpageDoctype.length == 1)
-                       {
-                               var source = this.fullpageDoctype[0] + '\n' + this.$source.val();
-                               this.$source.val(source);
-                       }
-               },
-               setSpansVerified: function()
-               {
-                       var spans = this.$editor.find('span');
-                       var replacementTag = 'inline';
-
-                       $.each(spans, function() {
-                               var outer = this.outerHTML;
-
-                               // Replace opening tag
-                               var regex = new RegExp('<' + this.tagName, 'gi');
-                               var newTag = outer.replace(regex, '<' + replacementTag);
-
-                               // Replace closing tag
-                               regex = new RegExp('</' + this.tagName, 'gi');
-                               newTag = newTag.replace(regex, '</' + replacementTag);
-
-                               $(this).replaceWith(newTag);
-                       });
-
-               },
-               setSpansVerifiedHtml: function(html)
-               {
-                       html = html.replace(/<span(.*?)>/, '<inline$1>');
-                       return html.replace(/<\/span>/, '</inline>');
-               },
-               setNonEditable: function()
-               {
-                       this.$editor.find('.noneditable').attr('contenteditable', false);
-               },
-
-               // SYNC
-               sync: function(e)
-               {
-                       var html = '';
-
-                       this.cleanUnverified();
-
-                       if (this.opts.fullpage) html = this.getCodeIframe();
-                       else html = this.$editor.html();
+                                       // focus
+                                       if (this.opts.focus) setTimeout($.proxy(this.focus.setStart, this), 100);
+                                       if (this.opts.focusEnd) setTimeout($.proxy(this.focus.setEnd, this), 100);
 
-                       html = this.syncClean(html);
-                       html = this.cleanRemoveEmptyTags(html);
-
-                       // is there a need to synchronize
-                       var source = this.cleanRemoveSpaces(this.$source.val(), false);
-                       var editor = this.cleanRemoveSpaces(html, false);
-
-                       if (source == editor)
-                       {
-                               // do not sync
-                               return false;
-                       }
-                       // fix second level up ul, ol
-                       html = html.replace(/<\/li><(ul|ol)>([\w\W]*?)<\/(ul|ol)>/gi, '<$1>$2</$1></li>');
-
-                       if ($.trim(html) === '<br>') html = '';
-
-                       // xhtml
-                       if (this.opts.xhtml)
-                       {
-                               var xhtmlTags = ['br', 'hr', 'img', 'link', 'input', 'meta'];
-                               $.each(xhtmlTags, function(i,s)
+                               },
+                               plugins: function()
                                {
-                                       html = html.replace(new RegExp('<' + s + '(.*?[^\/$]?)>', 'gi'), '<' + s + '$1 />');
-                               });
-
-                       }
-
-                       // before callback
-                       html = this.callback('syncBefore', false, html);
-
-                       this.$source.val(html);
-                       this.setFullpageDoctype();
-
-                       // onchange & after callback
-                       this.callback('syncAfter', false, html);
-
-                       if (this.start === false)
-                       {
+                                       if (!this.opts.plugins) return;
+                                       if (!RedactorPlugins) return;
 
-                               if (typeof e != 'undefined')
-                               {
-                                       switch(e.which)
+                                       $.each(this.opts.plugins, $.proxy(function(i, s)
                                        {
-                                       case 37: // left
-                                       break;
-                                       case 38: // up
-                                       break;
-                                       case 39: // right
-                                       break;
-                                       case 40: // down
-                                       break;
-
-                                               default: this.callback('change', false, html);
-                                       }
-                               }
-                               else
-                               {
-                                       this.callback('change', false, html);
-                               }
-                       }
-
-               },
-               syncClean: function(html)
-               {
-                       if (!this.opts.fullpage) html = this.cleanStripTags(html);
-
-                       // trim
-                       html = $.trim(html);
-
-                       // removeplaceholder
-                       html = this.placeholderRemoveFromCode(html);
-
-                       // remove space
-                       html = html.replace(/&#x200b;/gi, '');
-                       html = html.replace(/&#8203;/gi, '');
-                       html = html.replace(/<\/a>&nbsp;/gi, '<\/a> ');
-                       html = html.replace(/\u200B/g, '');
-
-                       if (html == '<p></p>' || html == '<p> </p>' || html == '<p>&nbsp;</p>')
-                       {
-                               html = '';
-                       }
-
-                       // link nofollow
-                       if (this.opts.linkNofollow)
-                       {
-                               html = html.replace(/<a(.*?)rel="nofollow"(.*?)>/gi, '<a$1$2>');
-                               html = html.replace(/<a(.*?)>/gi, '<a$1 rel="nofollow">');
-                       }
-
-                       // php code fix
-                       html = html.replace('<!--?php', '<?php');
-                       html = html.replace('?-->', '?>');
-
-                       // revert no editable
-                       html = html.replace(/<(.*?)class="noeditable"(.*?) contenteditable="false"(.*?)>/gi, '<$1class="noeditable"$2$3>');
-
-                       html = html.replace(/ data-tagblock=""/gi, '');
-                       html = html.replace(/<br\s?\/?>\n?<\/(P|H[1-6]|LI|ADDRESS|SECTION|HEADER|FOOTER|ASIDE|ARTICLE)>/gi, '</$1>');
-
-                       // remove image resize
-                       html = html.replace(/<span(.*?)id="redactor-image-box"(.*?)>([\w\W]*?)<img(.*?)><\/span>/gi, '$3<img$4>');
-                       html = html.replace(/<span(.*?)id="redactor-image-resizer"(.*?)>(.*?)<\/span>/gi, '');
-                       html = html.replace(/<span(.*?)id="redactor-image-editter"(.*?)>(.*?)<\/span>/gi, '');
+                                               if (RedactorPlugins[s])
+                                               {
+                                                       if (!$.isFunction(RedactorPlugins[s])) return;
 
-                       // remove empty lists
-                       html = html.replace(/<(ul|ol)>\s*\t*\n*<\/(ul|ol)>/gi, '');
+                                                       this[s] = RedactorPlugins[s]();
 
-                       // remove font
-                       if (this.opts.cleanFontTag)
-                       {
-                               html = html.replace(/<font(.*?)>([\w\W]*?)<\/font>/gi, '$2');
-                       }
+                                                       var methods = this.getModuleMethods(this[s]);
+                                                       var len = methods.length;
 
-                       // remove spans
-                       html = html.replace(/<span(.*?)>([\w\W]*?)<\/span>/gi, '$2');
-                       html = html.replace(/<inline>([\w\W]*?)<\/inline>/gi, '$1');
-                       html = html.replace(/<inline>/gi, '<span>');
-                       html = html.replace(/<inline /gi, '<span ');
-                       html = html.replace(/<\/inline>/gi, '</span>');
+                                                       // bind methods
+                                                       for (var z = 0; z < len; z++)
+                                                       {
+                                                               this[s][methods[z]] = this[s][methods[z]].bind(this);
+                                                       }
 
-                       if (this.opts.removeEmptyTags)
-                       {
-                               html = html.replace(/<span>([\w\W]*?)<\/span>/gi, '$1');
-                       }
+                                                       if ($.isFunction(this[s].init)) this[s].init();
+                                               }
 
-                       html = html.replace(/<span(.*?)class="redactor_placeholder"(.*?)>([\w\W]*?)<\/span>/gi, '');
-                       html = html.replace(/<img(.*?)contenteditable="false"(.*?)>/gi, '<img$1$2>');
+                                       }, this));
 
-                       // special characters
-                       html = html.replace(/&/gi, '&');
-                       html = html.replace(/\u2122/gi, '&trade;');
-                       html = html.replace(/\u00a9/gi, '&copy;');
-                       html = html.replace(/\u2026/gi, '&hellip;');
-                       html = html.replace(/\u2014/gi, '&mdash;');
-                       html = html.replace(/\u2010/gi, '&dash;');
 
-                       html = this.cleanReConvertProtected(html);
+                               },
+                               disableMozillaEditing: function()
+                               {
+                                       if (!this.utils.browser('mozilla')) return;
 
-                       return html;
+                                       // FF fix
+                                       try {
+                                               document.execCommand('enableObjectResizing', false, false);
+                                               document.execCommand('enableInlineTableEditing', false, false);
+                                       } catch (e) {}
+                               }
+                       };
                },
-
-
-
-               // BUILD
-               buildStart: function()
+               lang: function()
                {
-                       // content
-                       this.content = '';
-
-                       // container
-                       this.$box = $('<div class="redactor_box" />');
-
-                       // textarea test
-                       if (this.$source[0].tagName === 'TEXTAREA') this.opts.textareamode = true;
-
-                       // mobile
-                       if (this.opts.mobile === false && this.isMobile())
-                       {
-                               this.buildMobile();
-                       }
-                       else
-                       {
-                               // get the content at the start
-                               this.buildContent();
-
-                               if (this.opts.iframe)
+                       return {
+                               load: function()
                                {
-                                       // build as iframe
-                                       this.opts.autoresize = false;
-                                       this.iframeStart();
-                               }
-                               else if (this.opts.textareamode) this.buildFromTextarea();
-                               else this.buildFromElement();
-
-                               // options and final setup
-                               if (!this.opts.iframe)
+                                       this.opts.curLang = this.opts.langs[this.opts.lang];
+                               },
+                               get: function(name)
                                {
-                                       this.buildOptions();
-                                       this.buildAfter();
+                                       return (typeof this.opts.curLang[name] != 'undefined') ? this.opts.curLang[name] : '';
                                }
-                       }
-               },
-               buildMobile: function()
-               {
-                       if (!this.opts.textareamode)
-                       {
-                               this.$editor = this.$source;
-                               this.$editor.hide();
-                               this.$source = this.buildCodearea(this.$editor);
-                               this.$source.val(this.content);
-                       }
-
-                       this.$box.insertAfter(this.$source).append(this.$source);
-               },
-               buildContent: function()
-               {
-                       if (this.opts.textareamode) this.content = $.trim(this.$source.val());
-                       else this.content = $.trim(this.$source.html());
-               },
-               buildFromTextarea: function()
-               {
-                       this.$editor = $('<div />');
-                       this.$box.insertAfter(this.$source).append(this.$editor).append(this.$source);
-
-                       // enable
-                       this.buildAddClasses(this.$editor);
-                       this.buildEnable();
-               },
-               buildFromElement: function()
-               {
-                       this.$editor = this.$source;
-                       this.$source = this.buildCodearea(this.$editor);
-                       this.$box.insertAfter(this.$editor).append(this.$editor).append(this.$source);
-
-                       // enable
-                       this.buildEnable();
-               },
-               buildCodearea: function($source)
-               {
-                       return $('<textarea />').attr('name', $source.attr('id')).css('height', this.sourceHeight);
-               },
-               buildAddClasses: function(el)
-               {
-                       // append textarea classes to editable layer
-                       $.each(this.$source.get(0).className.split(/\s+/), function(i,s)
-                       {
-                               el.addClass('redactor_' + s);
-                       });
-               },
-               buildEnable: function()
-               {
-                       this.$editor.addClass('redactor_editor').attr({ 'contenteditable': true, 'dir': this.opts.direction });
-                       this.$source.attr('dir', this.opts.direction).hide();
-
-                       // set code
-                       this.set(this.content, true, false);
+                       };
                },
-               buildOptions: function()
+               toolbar: function()
                {
-                       var $source = this.$editor;
-                       if (this.opts.iframe) $source = this.$frame;
-
-                       // options
-                       if (this.opts.tabindex) $source.attr('tabindex', this.opts.tabindex);
-
-                       if (this.opts.minHeight) $source.css('min-height', this.opts.minHeight + 'px');
-                       // FF fix bug with line-height rendering
-                       else if (this.browser('mozilla') && this.opts.linebreaks)
-                       {
-                               this.$editor.css('min-height', '45px');
-                       }
-                       // FF fix bug with line-height rendering
-                       if (this.browser('mozilla') && this.opts.linebreaks)
-                       {
-                               this.$editor.css('padding-bottom', '10px');
-                       }
+                       return {
+                               init: function()
+                               {
+                                       return {
+                                               html:
+                                               {
+                                                       title: this.lang.get('html'),
+                                                       func: 'code.toggle'
+                                               },
+                                               formatting:
+                                               {
+                                                       title: this.lang.get('formatting'),
+                                                       dropdown:
+                                                       {
+                                                               p:
+                                                               {
+                                                                       title: this.lang.get('paragraph'),
+                                                                       func: 'block.format'
+                                                               },
+                                                               blockquote:
+                                                               {
+                                                                       title: this.lang.get('quote'),
+                                                                       func: 'block.format'
+                                                               },
+                                                               pre:
+                                                               {
+                                                                       title: this.lang.get('code'),
+                                                                       func: 'block.format'
+                                                               },
+                                                               h1:
+                                                               {
+                                                                       title: this.lang.get('header1'),
+                                                                       func: 'block.format'
+                                                               },
+                                                               h2:
+                                                               {
+                                                                       title: this.lang.get('header2'),
+                                                                       func: 'block.format'
+                                                               },
+                                                               h3:
+                                                               {
+                                                                       title: this.lang.get('header3'),
+                                                                       func: 'block.format'
+                                                               },
+                                                               h4:
+                                                               {
+                                                                       title: this.lang.get('header4'),
+                                                                       func: 'block.format'
+                                                               },
+                                                               h5:
+                                                               {
+                                                                       title: this.lang.get('header5'),
+                                                                       func: 'block.format'
+                                                               }
+                                                       }
+                                               },
+                                               bold:
+                                               {
+                                                       title: this.lang.get('bold'),
+                                                       func: 'inline.format'
+                                               },
+                                               italic:
+                                               {
+                                                       title: this.lang.get('italic'),
+                                                       func: 'inline.format'
+                                               },
+                                               deleted:
+                                               {
+                                                       title: this.lang.get('deleted'),
+                                                       func: 'inline.format'
+                                               },
+                                               underline:
+                                               {
+                                                       title: this.lang.get('underline'),
+                                                       func: 'inline.format'
+                                               },
+                                               unorderedlist:
+                                               {
+                                                       title: '&bull; ' + this.lang.get('unorderedlist'),
+                                                       func: 'list.toggle'
+                                               },
+                                               orderedlist:
+                                               {
+                                                       title: '1. ' + this.lang.get('orderedlist'),
+                                                       func: 'list.toggle'
+                                               },
+                                               outdent:
+                                               {
+                                                       title: '< ' + this.lang.get('outdent'),
+                                                       func: 'indent.decrease'
+                                               },
+                                               indent:
+                                               {
+                                                       title: '> ' + this.lang.get('indent'),
+                                                       func: 'indent.increase'
+                                               },
+                                               image:
+                                               {
+                                                       title: this.lang.get('image'),
+                                                       func: 'image.show'
+                                               },
+                                               file:
+                                               {
+                                                       title: this.lang.get('file'),
+                                                       func: 'file.show'
+                                               },
+                                               link:
+                                               {
+                                                       title: this.lang.get('link'),
+                                                       dropdown:
+                                                       {
+                                                               link:
+                                                               {
+                                                                       title: this.lang.get('link_insert'),
+                                                                       func: 'link.show'
+                                                               },
+                                                               unlink:
+                                                               {
+                                                                       title: this.lang.get('unlink'),
+                                                                       func: 'link.unlink'
+                                                               }
+                                                       }
+                                               },
+                                               alignment:
+                                               {
+                                                       title: this.lang.get('alignment'),
+                                                       dropdown:
+                                                       {
+                                                               left:
+                                                               {
+                                                                       title: this.lang.get('align_left'),
+                                                                       func: 'alignment.left'
+                                                               },
+                                                               center:
+                                                               {
+                                                                       title: this.lang.get('align_center'),
+                                                                       func: 'alignment.center'
+                                                               },
+                                                               right:
+                                                               {
+                                                                       title: this.lang.get('align_right'),
+                                                                       func: 'alignment.right'
+                                                               },
+                                                               justify:
+                                                               {
+                                                                       title: this.lang.get('align_justify'),
+                                                                       func: 'alignment.justify'
+                                                               }
+                                                       }
+                                               },
+                                               horizontalrule:
+                                               {
+                                                       title: this.lang.get('horizontalrule'),
+                                                       func: 'line.insert'
+                                               }
+                                       };
+                               },
+                               build: function()
+                               {
+                                       this.toolbar.hideButtons();
+                                       this.toolbar.hideButtonsOnMobile();
+                                       this.toolbar.isButtonSourceNeeded();
 
+                                       if (this.opts.buttons.length === 0) return;
 
-                       if (this.opts.maxHeight)
-                       {
-                               this.opts.autoresize = false;
-                               this.sourceHeight = this.opts.maxHeight;
-                       }
-                       if (this.opts.wym) this.$editor.addClass('redactor_editor_wym');
-                       if (this.opts.typewriter) this.$editor.addClass('redactor-editor-typewriter');
-                       if (!this.opts.autoresize) $source.css('height', this.sourceHeight);
+                                       this.$toolbar = this.toolbar.createContainer();
 
-               },
-               buildAfter: function()
-               {
-                       this.start = false;
+                                       this.toolbar.setOverflow();
+                                       this.toolbar.append();
+                                       this.toolbar.setFormattingTags();
+                                       this.toolbar.loadButtons();
+                                       this.toolbar.setTabindex();
+                                       this.toolbar.setFixed();
 
-                       // load toolbar
-                       if (this.opts.toolbar)
-                       {
-                               this.opts.toolbar = this.toolbarInit(this.opts.curLang);
-                               this.toolbarBuild();
-                       }
+                                       // buttons response
+                                       if (this.opts.activeButtons)
+                                       {
+                                               this.$editor.on('mouseup.redactor keyup.redactor focus.redactor', $.proxy(this.observe.buttons, this));
+                                       }
 
-                       // modal templates
-                       this.modalTemplatesInit();
+                               },
+                               createContainer: function()
+                               {
+                                       return $('<ul>').addClass('redactor-toolbar').attr('id', 'redactor-toolbar-' + this.uuid);
+                               },
+                               setFormattingTags: function()
+                               {
+                                       $.each(this.opts.toolbar.formatting.dropdown, $.proxy(function (i, s)
+                                       {
+                                               if ($.inArray(i, this.opts.formatting) == -1) delete this.opts.toolbar.formatting.dropdown[i];
+                                       }, this));
 
-                       // plugins
-                       this.buildPlugins();
+                               },
+                               loadButtons: function()
+                               {
+                                       $.each(this.opts.buttons, $.proxy(function(i, btnName)
+                                       {
+                                               if (!this.opts.toolbar[btnName]) return;
 
-                       // enter, tab, etc.
-                       this.buildBindKeyboard();
+                                               if (this.opts.fileUpload === false && btnName === 'file') return true;
+                                               if (this.opts.imageUpload === false && btnName === 'image') return true;
 
-                       // autosave
-                       if (this.opts.autosave) this.autosave();
+                                               var btnObject = this.opts.toolbar[btnName];
+                                               this.$toolbar.append($('<li>').append(this.button.build(btnName, btnObject)));
 
-                       // observers
-                       setTimeout($.proxy(this.observeStart, this), 4);
+                                       }, this));
+                               },
+                               append: function()
+                               {
+                                       if (this.opts.toolbarExternal)
+                                       {
+                                               this.$toolbar.addClass('redactor-toolbar-external');
+                                               $(this.opts.toolbarExternal).html(this.$toolbar);
+                                       }
+                                       else
+                                       {
+                                               this.$box.prepend(this.$toolbar);
+                                       }
+                               },
+                               setFixed: function()
+                               {
+                                       if (this.utils.isMobile()) return;
+                                       if (this.opts.toolbarExternal) return;
+                                       if (!this.opts.toolbarFixed) return;
 
-                       // FF fix
-                       if (this.browser('mozilla'))
-                       {
-                               try {
-                                       this.document.execCommand('enableObjectResizing', false, false);
-                                       this.document.execCommand('enableInlineTableEditing', false, false);
-                               } catch (e) {}
-                       }
+                                       this.toolbar.observeScroll();
+                                       $(this.opts.toolbarFixedTarget).on('scroll.redactor', $.proxy(this.toolbar.observeScroll, this));
 
-                       // focus
-                       if (this.opts.focus) setTimeout($.proxy(this.focus, this), 100);
+                               },
+                               setTabindex: function()
+                               {
+                                       this.$toolbar.find('a').attr('tabindex', '-1');
+                               },
+                               setOverflow: function()
+                               {
+                                       if (this.utils.isMobile() && this.opts.toolbarOverflow)
+                                       {
+                                               this.$toolbar.addClass('redactor-toolbar-overflow');
+                                       }
+                               },
+                               isButtonSourceNeeded: function()
+                               {
+                                       if (this.opts.buttonSource) return;
 
-                       // code mode
-                       if (!this.opts.visual)
-                       {
-                               setTimeout($.proxy(function()
+                                       var index = this.opts.buttons.indexOf('html');
+                                       this.opts.buttons.splice(index, 1);
+                               },
+                               hideButtons: function()
                                {
-                                       this.opts.visual = true;
-                                       this.toggle(false);
+                                       if (this.opts.buttonsHide.length === 0) return;
 
-                               }, this), 200);
-                       }
+                                       $.each(this.opts.buttonsHide, $.proxy(function(i, s)
+                                       {
+                                               var index = this.opts.buttons.indexOf(s);
+                                               this.opts.buttons.splice(index, 1);
 
-                       // init callback
-                       this.callback('init');
-               },
-               buildBindKeyboard: function()
-               {
-                       this.dblEnter = 0;
+                                       }, this));
+                               },
+                               hideButtonsOnMobile: function()
+                               {
+                                       if (!this.utils.isMobile() && this.opts.buttonsHideOnMobile.length === 0) return;
 
-                       if (this.opts.dragUpload && (this.opts.imageUpload !== false || this.opts.s3 !== false))
-                       {
-                               this.$editor.on('drop.redactor', $.proxy(this.buildEventDrop, this));
-                       }
+                                       $.each(this.opts.buttonsHideOnMobile, $.proxy(function(i, s)
+                                       {
+                                               var index = this.opts.buttons.indexOf(s);
+                                               this.opts.buttons.splice(index, 1);
 
-                       this.$editor.on('click.redactor', $.proxy(function()
-                       {
-                               this.selectall = false;
+                                       }, this));
+                               },
+                               observeScroll: function()
+                               {
+                                       var scrollTop = $(this.opts.toolbarFixedTarget).scrollTop();
+                                       var boxTop = 1;
 
-                       }, this));
+                                       if (this.opts.toolbarFixedTarget === document)
+                                       {
+                                               boxTop = this.$box.offset().top;
+                                       }
 
-                       this.$editor.on('input.redactor', $.proxy(this.sync, this));
-                       this.$editor.on('paste.redactor', $.proxy(this.buildEventPaste, this));
-                       this.$editor.on('keydown.redactor', $.proxy(this.buildEventKeydown, this));
-                       this.$editor.on('keyup.redactor', $.proxy(this.buildEventKeyup, this));
+                                       if (scrollTop > boxTop)
+                                       {
+                                               this.toolbar.observeScrollEnable(scrollTop, boxTop);
+                                       }
+                                       else
+                                       {
+                                               this.toolbar.observeScrollDisable();
+                                       }
+                               },
+                               observeScrollEnable: function(scrollTop, boxTop)
+                               {
+                                       var top = scrollTop - boxTop;
+                                       var left = 0;
+                                       var end = boxTop + this.$box.height() + 30;
+                                       var width = this.$box.innerWidth();
 
-                       // textarea callback
-                       if ($.isFunction(this.opts.textareaKeydownCallback))
-                       {
-                               this.$source.on('keydown.redactor-textarea', $.proxy(this.opts.textareaKeydownCallback, this));
-                       }
+                                       this.$toolbar.addClass('toolbar-fixed-box');
+                                       this.$toolbar.css({
+                                               position: 'absolute',
+                                               width: width,
+                                               top: top + 'px',
+                                               left: left
+                                       });
 
-                       // focus callback
-                       if ($.isFunction(this.opts.focusCallback))
-                       {
-                               this.$editor.on('focus.redactor', $.proxy(this.opts.focusCallback, this));
-                       }
+                                       this.toolbar.setDropdownsFixed();
+                                       this.$toolbar.css('visibility', (scrollTop < end) ? 'visible' : 'hidden');
+                               },
+                               observeScrollDisable: function()
+                               {
+                                       this.$toolbar.css({
+                                               position: 'relative',
+                                               width: 'auto',
+                                               top: 0,
+                                               left: 0,
+                                               visibility: 'visible'
+                                       });
 
-                       var clickedElement;
-                       $(document).mousedown(function(e) {
-                               clickedElement = $(e.target);
-                       });
+                                       this.toolbar.unsetDropdownsFixed();
+                                       this.$toolbar.removeClass('toolbar-fixed-box');
 
-                       // blur callback
-                       this.$editor.on('blur.redactor', $.proxy(function(e)
-                       {
-                               if (!$(clickedElement).hasClass('redactor_toolbar') && $(clickedElement).parents('.redactor_toolbar').size() == 0)
+                               },
+                               setDropdownsFixed: function()
                                {
-                                       this.selectall = false;
-                                       if ($.isFunction(this.opts.blurCallback)) this.callback('blur', e);
+                                       var self = this;
+                                       $('.redactor-dropdown').each(function()
+                                       {
+                                               $(this).css({ position: 'fixed', top: self.$toolbar.innerHeight() });
+                                       });
+                               },
+                               unsetDropdownsFixed: function()
+                               {
+                                       var self = this;
+                                       $('.redactor-dropdown').each(function()
+                                       {
+                                               var top = (self.$toolbar.innerHeight() + self.$toolbar.offset().top) + 'px';
+                                               $(this).css({ position: 'absolute', top: top });
+                                       });
                                }
-                       }, this));
-
+                       };
                },
-               buildEventDrop: function(e)
+               button: function()
                {
-                       e = e.originalEvent || e;
-
-                       if (window.FormData === undefined || !e.dataTransfer) return true;
+                       return {
+                               build: function(btnName, btnObject)
+                               {
+                                       var $button = $('<a href="#" class="re-icon re-' + btnName + '" rel="' + btnName + '" />');
 
-                   var length = e.dataTransfer.files.length;
-                   if (length == 0) return true;
+                                       if (btnObject.func || btnObject.command || btnObject.dropdown)
+                                       {
+                                               $button.on('touchstart click', $.proxy(function(e)
+                                               {
+                                                       if ($button.hasClass('redactor-button-disabled')) return false;
 
-                   e.preventDefault();
+                                                       var type = 'func';
+                                                       var callback = btnObject.func;
+                                                       if (btnObject.command)
+                                                       {
+                                                               type = 'command';
+                                                               callback = btnObject.command;
+                                                       }
+                                                       else if (btnObject.dropdown)
+                                                       {
+                                                               type = 'dropdown';
+                                                               callback = false;
+                                                       }
 
-               var file = e.dataTransfer.files[0];
+                                                       this.button.onClick(e, btnName, type, callback);
 
-               if (this.opts.dnbImageTypes !== false && this.opts.dnbImageTypes.indexOf(file.type) == -1)
-               {
-                       return true;
-               }
+                                               }, this));
+                                       }
 
-                       this.bufferSet();
+                                       // dropdown
+                                       if (btnObject.dropdown)
+                                       {
+                                               var $dropdown = $('<div class="redactor-dropdown redactor-dropdown-box-' + btnName + '" style="display: none;">');
+                                               $button.data('dropdown', $dropdown);
+                                               this.dropdown.build(btnName, $dropdown, btnObject.dropdown);
+                                       }
 
-                       this.showProgressBar();
+                                       // tooltip
+                                       if (this.utils.isDesktop())
+                                       {
+                                               this.button.createTooltip($button, btnName, btnObject.title);
+                                       }
 
-                       if (this.opts.s3 === false)
-                       {
-                               this.dragUploadAjax(this.opts.imageUpload, file, true, e, this.opts.imageUploadParam);
-                       }
-                       else
-                       {
-                               this.s3uploadFile(file);
-                       }
+                                       return $button;
+                               },
+                               createTooltip: function($button, name, title)
+                               {
+                                       var $tooltip = $('<span>').addClass('redactor-toolbar-tooltip redactor-toolbar-tooltip-' + name).hide().html(title);
+                                       $tooltip.appendTo('body');
 
+                                       $button.on('mouseover', function()
+                                       {
+                                               if ($(this).hasClass('redactor-button-disabled')) return;
 
-               },
-               buildEventPaste: function(e)
-               {
-                       var oldsafari = false;
-                       if (this.browser('webkit') && navigator.userAgent.indexOf('Chrome') === -1)
-                       {
-                               var arr = this.browser('version').split('.');
-                               if (arr[0] < 536) oldsafari = true;
-                       }
+                                               var pos = $button.offset();
+                                               var height = $button.innerHeight();
+                                               var width = $button.innerWidth();
 
-                       if (oldsafari) return true;
+                                               $tooltip.show();
+                                               $tooltip.css({
+                                                       top: (pos.top + height) + 'px',
+                                                       left: (pos.left + width/2 - $tooltip.innerWidth()/2) + 'px'
+                                               });
+                                       });
 
-                       // paste except opera (not webkit)
-                       if (this.browser('opera')) return true;
+                                       $button.on('mouseout', function()
+                                       {
+                                               $tooltip.hide();
+                                       });
 
-                       // clipboard upload
-                       if (this.opts.clipboardUpload && this.buildEventClipboardUpload(e)) return true;
+                               },
+                               onClick: function(e, btnName, type, callback)
+                               {
+                                       e.preventDefault();
 
-                       if (this.opts.cleanup)
-                       {
-                               this.rtePaste = true;
+                                       if (this.utils.browser('msie')) e.returnValue = false;
 
-                               this.selectionSave();
+                                       if (type == 'command')
+                                       {
+                                               this.inline.format(callback);
+                                       }
+                                       else if (type == 'dropdown')
+                                       {
+                                               this.dropdown.show(e, btnName);
+                                       }
+                                       else
+                                       {
+                                               var func;
+                                               if ($.isFunction(callback))
+                                               {
+                                                       callback.call(this, btnName);
+                                                       this.observe.buttons(e, btnName);
+                                               }
+                                               else if (callback.search(/\./) != '-1')
+                                               {
+                                                       func = callback.split('.');
+                                                       if (typeof this[func[0]] != 'undefined')
+                                                       {
+                                                               this[func[0]][func[1]](btnName);
+                                                               this.observe.buttons(e, btnName);
+                                                       }
+                                               }
+                                               else
+                                               {
+                                                       this[callback](btnName);
+                                                       this.observe.buttons(e, btnName);
+                                               }
+                                       }
 
-                               if (!this.selectall)
+                               },
+                               get: function(key)
+                               {
+                                       return this.$toolbar.find('a.re-' + key);
+                               },
+                               setActive: function(key)
                                {
-                                       if (this.opts.autoresize === true && this.fullscreen !== true)
+                                       this.button.get(key).addClass('redactor-act');
+                               },
+                               setInactive: function(key)
+                               {
+                                       this.button.get(key).removeClass('redactor-act');
+                               },
+                               setInactiveAll: function(key)
+                               {
+                                       if (typeof key == 'undefined')
                                        {
-                                               this.$editor.height(this.$editor.height());
-                                               this.saveScroll = this.document.body.scrollTop;
+                                               this.$toolbar.find('a.re-icon').removeClass('redactor-act');
                                        }
                                        else
                                        {
-                                               this.saveScroll = this.$editor.scrollTop();
+                                               this.$toolbar.find('a.re-icon').not('.re-' + key).removeClass('redactor-act');
                                        }
-                               }
-
-                               var frag = this.extractContent();
+                               },
+                               setActiveInVisual: function()
+                               {
+                                       this.$toolbar.find('a.re-icon').not('a.re-html').removeClass('redactor-button-disabled');
+                               },
+                               setInactiveInCode: function()
+                               {
+                                       this.$toolbar.find('a.re-icon').not('a.re-html').addClass('redactor-button-disabled');
+                               },
+                               changeIcon: function(key, classname)
+                               {
+                                       this.button.get(key).addClass('re-' + classname);
+                               },
+                               removeIcon: function(key, classname)
+                               {
+                                       this.button.get(key).removeClass('re-' + classname);
+                               },
+                               setAwesome: function(key, name)
+                               {
+                                       var $button = this.button.get(key);
+                                       $button.removeClass('redactor-btn-image').addClass('fa-redactor-btn');
+                                       $button.html('<i class="fa ' + name + '"></i>');
+                               },
+                               addCallback: function($btn, callback)
+                               {
+                                       var type = (callback == 'dropdown') ? 'dropdown' : 'func';
+                                       var key = $btn.attr('rel');
+                                       $btn.on('touchstart click', $.proxy(function(e)
+                                       {
+                                               if ($btn.hasClass('redactor-button-disabled')) return false;
+                                               this.button.onClick(e, key, type, callback);
 
-                               setTimeout($.proxy(function()
+                                       }, this));
+                               },
+                               addDropdown: function($btn, dropdown)
                                {
-                                       var pastedFrag = this.extractContent();
-                                       this.$editor.append(frag);
+                                       var key = $btn.attr('rel');
+                                       this.button.addCallback($btn, 'dropdown');
 
-                                       this.selectionRestore();
+                                       var $dropdown = $('<div class="redactor-dropdown redactor-dropdown-box-' + key + '" style="display: none;">');
+                                       $btn.data('dropdown', $dropdown);
 
-                                       var html = this.getFragmentHtml(pastedFrag);
-                                       this.pasteClean(html);
+                                       if (dropdown)
+                                       {
+                                               this.dropdown.build(key, $dropdown, dropdown);
+                                       }
 
-                                       if (this.opts.autoresize === true && this.fullscreen !== true) this.$editor.css('height', 'auto');
+                                       return $dropdown;
+                               },
+                               add: function(key, title)
+                               {
+                                       if (!this.opts.toolbar) return;
 
-                               }, this), 1);
-                       }
-               },
-               buildEventClipboardUpload: function(e)
-               {
-                       var event = e.originalEvent || e;
-                       this.clipboardFilePaste = false;
+                                       var btn = this.button.build(key, { title: title });
+                                       btn.addClass('redactor-btn-image');
 
+                                       this.$toolbar.append($('<li>').append(btn));
 
-                       if (typeof(event.clipboardData) === 'undefined') return false;
-                       if (event.clipboardData.items)
-                       {
-                               var file = event.clipboardData.items[0].getAsFile();
-                               if (file !== null)
+                                       return btn;
+                               },
+                               addFirst: function(key, title)
                                {
-                                       this.bufferSet();
-                                       this.clipboardFilePaste = true;
-
-                                       var reader = new FileReader();
-                                       reader.onload = $.proxy(this.pasteClipboardUpload, this);
-                               reader.readAsDataURL(file);
-
-                               return true;
-                               }
-                       }
+                                       if (!this.opts.toolbar) return;
 
-                       return false;
+                                       var btn = this.button.build(key, { title: title });
+                                       this.$toolbar.prepend($('<li>').append(btn));
 
-               },
-               buildEventKeydown: function(e)
-               {
-                       if (this.rtePaste) return false;
-
-                       var key = e.which;
-                       var ctrl = e.ctrlKey || e.metaKey;
-                       var parent = this.getParent();
-                       var current = this.getCurrent();
-                       var block = this.getBlock();
-                       var pre = false;
-
-                       this.callback('keydown', e);
-
-                       /*
-                               firefox cmd+left/Cmd+right browser back/forward fix -
-                               http://joshrhoderick.wordpress.com/2010/05/05/how-firefoxs-command-key-bug-kills-usability-on-the-mac/
-                       */
-                       if (this.browser('mozilla') && "modify" in window.getSelection())
-                       {
-                               if ((ctrl) && (e.keyCode===37 || e.keyCode===39))
+                                       return btn;
+                               },
+                               addAfter: function(afterkey, key, title)
                                {
-                                       var selection = this.getSelection();
-                                       var lineOrWord = (e.metaKey ? "line" : "word");
-                                       if (e.keyCode===37)
-                                       {
-                                               selection.modify("extend","left",lineOrWord);
-                                               if (!e.shiftKey)
-                                               {
-                                                       selection.collapseToStart();
-                                               }
-                                       }
-                                       if (e.keyCode===39)
-                                       {
-                                               selection.modify("extend","right",lineOrWord);
-                                               if (!e.shiftKey)
-                                               {
-                                                       selection.collapseToEnd();
-                                               }
-                                       }
+                                       if (!this.opts.toolbar) return;
 
-                                       e.preventDefault();
-                               }
-                       }
+                                       var btn = this.button.build(key, { title: title });
+                                       var $btn = this.button.get(afterkey);
 
+                                       if ($btn.size() !== 0) $btn.parent().after($('<li>').append(btn));
+                                       else this.$toolbar.append($('<li>').append(btn));
 
-                       this.imageResizeHide(false);
+                                       return btn;
+                               },
+                               addBefore: function(beforekey, key, title)
+                               {
+                                       if (!this.opts.toolbar) return;
 
-                       // pre & down
-                       if ((parent && $(parent).get(0).tagName === 'PRE') || (current && $(current).get(0).tagName === 'PRE'))
-                       {
-                               pre = true;
-                               if (key === this.keyCode.DOWN) this.insertAfterLastElement(block);
-                       }
+                                       var btn = this.button.build(key, { title: title });
+                                       var $btn = this.button.get(beforekey);
 
-                       // down
-                       if (key === this.keyCode.DOWN)
-                       {
-                               if (parent && $(parent)[0].tagName === 'BLOCKQUOTE') this.insertAfterLastElement(parent);
-                               if (current && $(current)[0].tagName === 'BLOCKQUOTE') this.insertAfterLastElement(current);
+                                       if ($btn.size() !== 0) $btn.parent().before($('<li>').append(btn));
+                                       else this.$toolbar.append($('<li>').append(btn));
 
-                               if (parent && $(parent)[0].tagName === 'P' && $(parent).parent()[0].tagName == 'BLOCKQUOTE')
+                                       return btn;
+                               },
+                               remove: function(key)
                                {
-                                       this.insertAfterLastElement(parent, $(parent).parent()[0]);
+                                       this.button.get(key).remove();
                                }
-                               if (current && $(current)[0].tagName === 'P' && parent && $(parent)[0].tagName == 'BLOCKQUOTE')
+                       };
+               },
+               dropdown: function()
+               {
+                       return {
+                               build: function(name, $dropdown, dropdownObject)
                                {
-                                       this.insertAfterLastElement(current, parent);
-                               }
-                       }
+                                       if (name == 'formatting' && this.opts.formattingAdd)
+                                       {
+                                               $.each(this.opts.formattingAdd, $.proxy(function(i,s)
+                                               {
+                                                       var name = s.tag;
+                                                       if (typeof s.class != 'undefined')
+                                                       {
+                                                               name = name + '-' + s.class;
+                                                       }
 
-                       // shortcuts setup
-                       this.shortcuts(e, key);
+                                                       s.type = (this.utils.isBlockTag(s.tag)) ? 'block' : 'inline';
+                                                       var func = (s.type == 'inline') ? 'inline.formatting' : 'block.formatting';
 
-                       // buffer setup
-                       if (ctrl && key === 90 && !e.shiftKey && !e.altKey) // z key
-                       {
-                               e.preventDefault();
-                               if (this.opts.buffer.length) this.bufferUndo();
-                               else this.document.execCommand('undo', false, false);
-                               return;
-                       }
-                       // undo
-                       else if (ctrl && key === 90 && e.shiftKey && !e.altKey)
-                       {
-                               e.preventDefault();
-                               if (this.opts.rebuffer.length != 0) this.bufferRedo();
-                               else this.document.execCommand('redo', false, false);
-                               return;
-                       }
+                                                       if (this.opts.linebreaks && s.type == 'block' && s.tag == 'p') return;
 
-                       // space
-                       if (key == 32)
-                       {
-                               this.bufferSet();
-                       }
+                                                       this.formatting[name] = {
+                                                               tag: s.tag,
+                                                               style: s.style,
+                                                               'class': s.class,
+                                                               attr: s.attr,
+                                                               data: s.data
+                                                       };
 
-                       // select all
-                       if (ctrl && key === 65)
-                       {
-                               this.bufferSet();
-                               this.selectall = true;
-                       }
-                       else if (key != this.keyCode.LEFT_WIN && !ctrl)
-                       {
-                               this.selectall = false;
-                       }
+                                                       dropdownObject[name] = {
+                                                               func: func,
+                                                               title: s.title
+                                                       };
 
-                       // enter
-                       if (key == this.keyCode.ENTER && !e.shiftKey && !e.ctrlKey && !e.metaKey)
-                       {
-                               // remove selected content on enter
-                               var range = this.getRange();
-                               if (range && range.collapsed === false)
-                               {
-                                       sel = this.getSelection();
-                                       if (sel.rangeCount)
-                                       {
-                                               range.deleteContents();
-                                       }
-                               }
+                                               }, this));
 
-                               // In ie, opera in the tables are created paragraphs, fix it.
-                               if (this.browser('msie') && (parent.nodeType == 1 && (parent.tagName == 'TD' || parent.tagName == 'TH')))
-                               {
-                                       e.preventDefault();
-                                       this.bufferSet();
-                                       this.insertNode(document.createElement('br'));
-                                       this.callback('enter', e);
-                                       return false;
-                               }
+                                       }
 
-                               // blockquote exit
-                               if (block && (block.tagName == 'BLOCKQUOTE' || $(block).parent()[0].tagName == 'BLOCKQUOTE'))
-                               {
-                                       if (this.isEndOfElement())
+                                       $.each(dropdownObject, $.proxy(function(btnName, btnObject)
                                        {
-                                               if (this.dblEnter == 1)
+                                               var $item = $('<a href="#" class="redactor-dropdown-' + btnName + '">' + btnObject.title + '</a>');
+                                               if (name == 'formatting') $item.addClass('redactor-formatting-' + btnName);
+
+                                               $item.on('click', $.proxy(function(e)
                                                {
-                                                       var element;
-                                                       var last;
-                                                       if (block.tagName == 'BLOCKQUOTE')
+                                                       var type = 'func';
+                                                       var callback = btnObject.func;
+                                                       if (btnObject.command)
                                                        {
-                                                               last = 'br';
-                                                               element = block;
+                                                               type = 'command';
+                                                               callback = btnObject.command;
                                                        }
-                                                       else
+                                                       else if (btnObject.dropdown)
                                                        {
-                                                               last = 'p';
-                                                               element = $(block).parent()[0];
+                                                               type = 'dropdown';
+                                                               callback = btnObject.dropdown;
                                                        }
 
-                                                       e.preventDefault();
-                                                       this.insertingAfterLastElement(element);
-                                                       this.dblEnter = 0;
+                                                       this.button.onClick(e, btnName, type, callback);
 
-                                                       if (last == 'p')
-                                                       {
-                                                               $(block).parent().find('p').last().remove();
-                                                       }
-                                                       else
-                                                       {
-                                                               var tmp = $.trim($(block).html());
-                                                               $(block).html(tmp.replace(/<br\s?\/?>$/i, ''));
-                                                       }
+                                               }, this));
 
-                                                       return;
-                                               }
-                                               else this.dblEnter++;
-                                       }
-                                       else this.dblEnter++;
-                               }
+                                               $dropdown.append($item);
 
-                               // pre
-                               if (pre === true)
-                               {
-                                       return this.buildEventKeydownPre(e, current);
-                               }
-                               else
+                                       }, this));
+                               },
+                               show: function(e, key)
                                {
-                                       if (!this.opts.linebreaks)
+                                       if (!this.opts.visual)
                                        {
-                                               // lists exit
-                                               if (block && block.tagName == 'LI')
-                                               {
-                                                       var listCurrent = this.getBlock();
-                                                       if (listCurrent !== false || listCurrent.tagName === 'LI')
-                                                       {
-                                                               var listText = $.trim($(block).text());
-                                                               var listCurrentText = $.trim($(listCurrent).text());
-                                                               if (listText == ''
-                                                                       && listCurrentText == ''
-                                                                       && $(listCurrent).next('li').size() == 0
-                                                                       && $(listCurrent).parents('li').size() == 0)
-                                                               {
-                                                                       this.bufferSet();
+                                               e.preventDefault();
+                                               return false;
+                                       }
+
+                                       var $button = this.button.get(key);
+
+                                       // Always re-append it to the end of <body> so it always has the highest sub-z-index.
+                                       var $dropdown = $button.data('dropdown').appendTo(document.body);
+
+                                       if ($button.hasClass('dropact'))
+                                       {
+                                               this.dropdown.hideAll();
+                                       }
+                                       else
+                                       {
+                                               this.dropdown.hideAll();
+                                               this.core.setCallback('dropdownShow', { dropdown: $dropdown, key: key, button: $button });
 
-                                                                       var $list = $(listCurrent).closest('ol, ul');
-                                                                       $(listCurrent).remove();
-                                                                       var node = $('<p>' + this.opts.invisibleSpace + '</p>');
-                                                                       $list.after(node);
-                                                                       this.selectionStart(node);
+                                               this.button.setActive(key);
 
-                                                                       this.sync();
-                                                                       this.callback('enter', e);
-                                                                       return false;
-                                                               }
-                                                       }
+                                               $button.addClass('dropact');
 
-                                               }
+                                               var keyPosition = $button.offset();
 
-                                               // replace div to p
-                                               if (block && this.opts.rBlockTest.test(block.tagName))
+                                               // fix right placement
+                                               var dropdownWidth = $dropdown.width();
+                                               if ((keyPosition.left + dropdownWidth) > $(document).width())
                                                {
-                                                       // hit enter
-                                                       this.bufferSet();
-
-                                                       setTimeout($.proxy(function()
-                                                       {
-                                                               var blockElem = this.getBlock();
-                                                               if (blockElem.tagName === 'DIV' && !$(blockElem).hasClass('redactor_editor'))
-                                                               {
-                                                                       var node = $('<p>' + this.opts.invisibleSpace + '</p>');
-                                                                       $(blockElem).replaceWith(node);
-                                                                       this.selectionStart(node);
-                                                               }
+                                                       keyPosition.left -= dropdownWidth;
+                                               }
 
-                                                       }, this), 1);
+                                               var left = keyPosition.left + 'px';
+                                               if (this.$toolbar.hasClass('toolbar-fixed-box'))
+                                               {
+                                                       $dropdown.css({ position: 'fixed', left: left, top: this.$toolbar.innerHeight() }).show();
                                                }
-                                               else if (block === false)
+                                               else
                                                {
-                                                       // hit enter
-                                                       this.bufferSet();
-                                                       var node = $('<p>' + this.opts.invisibleSpace + '</p>');
-                                                       this.insertNode(node[0]);
-                                                       this.selectionStart(node);
-                                                       this.callback('enter', e);
-                                                       return false;
+                                                       var top = ($button.innerHeight() + keyPosition.top) + 'px';
+
+                                                       $dropdown.css({ position: 'absolute', left: left, top: top }).show();
                                                }
 
+
+                                               this.core.setCallback('dropdownShown', { dropdown: $dropdown, key: key, button: $button });
                                        }
 
-                                       if (this.opts.linebreaks)
-                                       {
-                                               // replace div to br
-                                               if (block && this.opts.rBlockTest.test(block.tagName))
-                                               {
-                                                       // hit enter
-                                                       this.bufferSet();
+                                       $(document).one('click', $.proxy(this.dropdown.hide, this));
+                                       this.$editor.one('click', $.proxy(this.dropdown.hide, this));
 
-                                                       setTimeout($.proxy(function()
-                                                       {
-                                                               var blockElem = this.getBlock();
-                                                               if ((blockElem.tagName === 'DIV' || blockElem.tagName === 'P') && !$(blockElem).hasClass('redactor_editor'))
-                                                               {
-                                                                       this.replaceLineBreak(blockElem);
-                                                               }
+                                       $dropdown.on('mouseover', function() { $('html').css('overflow', 'hidden'); });
+                                       $dropdown.on('mouseout', function() { $('html').css('overflow', ''); });
 
-                                                       }, this), 1);
-                                               }
-                                               else
-                                               {
-                                                       return this.buildEventKeydownInsertLineBreak(e);
-                                               }
-                                       }
+                                       e.stopPropagation();
+                               },
+                               hideAll: function()
+                               {
+                                       this.$toolbar.find('a.dropact').removeClass('redactor-act').removeClass('dropact');
 
-                                       // blockquote, figcaption
-                                       if (block.tagName == 'BLOCKQUOTE' || block.tagName == 'FIGCAPTION')
+                                       $('.redactor-dropdown').hide();
+                                       this.core.setCallback('dropdownHide');
+                               },
+                               hide: function (e)
+                               {
+                                       var $dropdown = $(e.target);
+                                       if (!$dropdown.hasClass('dropact'))
                                        {
-                                               return this.buildEventKeydownInsertLineBreak(e);
+                                               $dropdown.removeClass('dropact');
+                                               this.dropdown.hideAll();
                                        }
-
                                }
+                       };
+               },
+               code: function()
+               {
+                       return {
+                               set: function(html)
+                               {
+                                       html = $.trim(html.toString());
 
-                               this.callback('enter', e);
-                       }
-                       else if (key === this.keyCode.ENTER && (e.ctrlKey || e.shiftKey)) // Shift+Enter or Ctrl+Enter
-                       {
-                               this.bufferSet();
+                                       // clean
+                                       html = this.clean.onSet(html);
 
-                               e.preventDefault();
-                               this.insertLineBreak();
-                       }
+                                       this.$editor.html(html);
+                                       this.code.sync();
 
-                       // tab (cmd + [)
-                       if ((key === this.keyCode.TAB || e.metaKey && key === 219) && this.opts.shortcuts)
-                       {
-                               return this.buildEventKeydownTab(e, pre, key);
-                       }
+                                       setTimeout($.proxy(this.buffer.add, this), 15);
+                                       if (this.start === false) this.observe.load();
 
-                       // delete zero-width space before the removing
-                       if (key === this.keyCode.BACKSPACE) this.buildEventKeydownBackspace(e, current, parent);
+                               },
+                               get: function()
+                               {
+                                       var code = this.$textarea.val();
 
-               },
-               buildEventKeydownPre: function(e, current)
-               {
-                       e.preventDefault();
-                       this.bufferSet();
-                       var html = $(current).parent().text();
-                       this.insertNode(document.createTextNode('\n'));
-                       if (html.search(/\s$/) == -1)
-                       {
-                               this.insertNode(document.createTextNode('\n'));
-                       }
+                                       // indent code
+                                       code = this.tabifier.get(code);
 
-                       this.sync();
-                       this.callback('enter', e);
-                       return false;
-               },
-               buildEventKeydownTab: function(e, pre, key)
-               {
-                       if (!this.opts.tabFocus) return true;
-                       if (this.isEmpty(this.get()) && this.opts.tabSpaces === false) return true;
+                                       return code;
+                               },
+                               sync: function()
+                               {
+                                       setTimeout($.proxy(this.code.startSync, this), 10);
+                               },
+                               startSync: function()
+                               {
+                                       var html = this.$editor.html();
 
-                       e.preventDefault();
+                                       // is there a need to synchronize
+                                       if (this.code.syncCode && this.code.syncCode == html)
+                                       {
+                                               // do not sync
+                                               return;
+                                       }
 
-                       if (pre === true && !e.shiftKey)
-                       {
-                               this.bufferSet();
-                               this.insertNode(document.createTextNode('\t'));
-                               this.sync();
-                               return false;
+                                       // save code
+                                       this.code.syncCode = html;
 
-                       }
-                       else if (this.opts.tabSpaces !== false)
-                       {
-                               this.bufferSet();
-                               this.insertNode(document.createTextNode(Array(this.opts.tabSpaces + 1).join('\u00a0')));
-                               this.sync();
-                               return false;
-                       }
-                       else
-                       {
-                               if (!e.shiftKey) this.indentingIndent();
-                               else this.indentingOutdent();
-                       }
+                                       // before clean callback
+                                       html = this.core.setCallback('syncBefore', html);
 
-                       return false;
-               },
-               buildEventKeydownBackspace: function(e, current, parent)
-               {
-                       // remove empty list in table
-                       if (parent && current && parent.parentNode.tagName == 'TD'
-                               && parent.tagName == 'UL' && current.tagName == 'LI' && $(parent).children('li').size() == 1)
-                       {
-                               var text = $(current).text().replace(/[\u200B-\u200D\uFEFF]/g, '');
-                               if (text == '')
-                               {
-                                       var node = parent.parentNode;
-                                       $(parent).remove();
-                                       this.selectionStart(node);
-                                       this.sync();
-                                       return false;
-                               }
-                       }
+                                       // clean
+                                       html = this.clean.onSync(html);
 
-                       if (typeof current.tagName !== 'undefined' && /^(H[1-6])$/i.test(current.tagName))
-                       {
-                               var node;
-                               if (this.opts.linebreaks === false) node = $('<p>' + this.opts.invisibleSpace + '</p>');
-                               else node = $('<br>' + this.opts.invisibleSpace);
+                                       // set code
+                                       this.$textarea.val(html);
 
-                               $(current).replaceWith(node);
-                               this.selectionStart(node);
-                               this.sync();
-                       }
+                                       // after sync callback
+                                       this.core.setCallback('sync', html);
 
-                       if (typeof current.nodeValue !== 'undefined' && current.nodeValue !== null)
-                       {
-                               if (current.remove && current.nodeType === 3 && current.nodeValue.match(/[^\u200B]/g) == null)
-                               {
-                                       $(current).prev().remove();
-                                       this.sync();
-                               }
-                       }
-               },
-               buildEventKeydownInsertLineBreak: function(e)
-               {
-                       this.bufferSet();
-                       e.preventDefault();
-                       this.insertLineBreak();
-                       this.callback('enter', e);
-                       return;
-               },
-               buildEventKeyup: function(e)
-               {
-                       if (this.rtePaste) return false;
+                                       if (this.start === false)
+                                       {
+                                               this.core.setCallback('change', html);
+                                       }
 
-                       var key = e.which;
-                       var parent = this.getParent();
-                       var current = this.getCurrent();
+                                       this.start = false;
 
-                       // replace to p before / after the table or body
-                       if (!this.opts.linebreaks && current.nodeType == 3 && (parent == false || parent.tagName == 'BODY'))
-                       {
-                               var node = $('<p>').append($(current).clone());
-                               $(current).replaceWith(node);
-                               var next = $(node).next();
-                               if (typeof(next[0]) !== 'undefined' && next[0].tagName == 'BR')
+                                       // autosave on change
+                                       this.autosave.onChange();
+                               },
+                               toggle: function()
                                {
-                                       next.remove();
-                               }
-
-                               this.selectionEnd(node);
-                       }
+                                       if (this.opts.visual)
+                                       {
+                                               this.code.showCode();
+                                       }
+                                       else
+                                       {
+                                               this.code.showVisual();
+                                       }
+                               },
+                               showCode: function()
+                               {
+                                       this.code.coords = this.caret.getCoords();
+                                       var scroll = $(window).scrollTop();
 
-                       // convert links
-                       if ((this.opts.convertLinks || this.opts.convertImageLinks || this.opts.convertVideoLinks) && key === this.keyCode.ENTER)
-                       {
-                               this.buildEventKeyupConverters();
-                       }
+                                       var height = this.$editor.innerHeight();
 
-                       // if empty
-                       if (key === this.keyCode.DELETE || key === this.keyCode.BACKSPACE)
-                       {
-                               return this.formatEmpty(e);
-                       }
+                                       this.$editor.hide();
 
-                       this.callback('keyup', e);
-                       this.sync(e);
-               },
-               buildEventKeyupConverters: function()
-               {
-                       this.formatLinkify(this.opts.linkProtocol, this.opts.convertLinks, this.opts.convertImageLinks, this.opts.convertVideoLinks, this.opts.linkSize);
+                                       var html = this.$textarea.val();
+                                       this.modified = this.clean.removeSpaces(html);
 
-                       setTimeout($.proxy(function()
-                       {
-                               if (this.opts.convertImageLinks) this.observeImages();
-                               if (this.opts.observeLinks) this.observeLinks();
-                       }, this), 5);
-               },
-               buildPlugins: function()
-               {
-                       if (!this.opts.plugins ) return;
+                                       // indent code
+                                       html = this.tabifier.get(html);
 
-                       $.each(this.opts.plugins, $.proxy(function(i, s)
-                       {
-                               if (RedactorPlugins[s])
-                               {
-                                       $.extend(this, RedactorPlugins[s]);
-                                       if ($.isFunction( RedactorPlugins[ s ].init)) this.init();
-                               }
+                                       this.$textarea.val(html).height(height).show().focus();
+                                       this.$textarea.on('keydown.redactor-textarea-indenting', this.code.textareaIndenting);
 
-                       }, this ));
-               },
+                                       $(window).scrollTop(scroll);
 
-               // IFRAME
-               iframeStart: function()
-               {
-                       this.iframeCreate();
+                                       this.opts.visual = false;
 
-                       if (this.opts.textareamode) this.iframeAppend(this.$source);
-                       else
-                       {
-                               this.$sourceOld = this.$source.hide();
-                               this.$source = this.buildCodearea(this.$sourceOld);
-                               this.iframeAppend(this.$sourceOld);
-                       }
-               },
-               iframeAppend: function(el)
-               {
-                       this.$source.attr('dir', this.opts.direction).hide();
-                       this.$box.insertAfter(el).append(this.$frame).append(this.$source);
-               },
-               iframeCreate: function()
-               {
-                       this.$frame = $('<iframe style="width: 100%;" frameborder="0" />').one('load', $.proxy(function()
-                       {
-                               if (this.opts.fullpage)
+                                       this.button.setInactiveInCode();
+                                       this.button.setActive('html');
+                                       this.core.setCallback('source', html);
+                               },
+                               showVisual: function()
                                {
-                                       this.iframePage();
+                                       if (this.opts.visual) return;
 
-                                       if (this.content === '') this.content = this.opts.invisibleSpace;
+                                       var html = this.$textarea.hide().val();
+
+                                       if (this.modified !== this.clean.removeSpaces(html))
+                                       {
+                                               this.code.set(html);
+                                       }
 
-                                       this.$frame.contents()[0].write(this.content);
-                                       this.$frame.contents()[0].close();
+                                       this.$editor.show();
 
-                                       var timer = setInterval($.proxy(function()
+                                       if (!this.utils.isEmpty(html))
                                        {
-                                               if (this.$frame.contents().find('body').html())
-                                               {
-                                                       clearInterval(timer);
-                                                       this.iframeLoad();
-                                               }
+                                               this.placeholder.remove();
+                                       }
 
-                                       }, this), 0);
-                               }
-                               else this.iframeLoad();
+                                       this.$editor.focus();
+                                       this.caret.setToPoint(this.code.coords.x, this.code.coords.y);
 
-                       }, this));
-               },
-               iframeDoc: function()
-               {
-                       return this.$frame[0].contentWindow.document;
-               },
-               iframePage: function()
-               {
-                       var doc = this.iframeDoc();
-                       if (doc.documentElement) doc.removeChild(doc.documentElement);
+                                       if (this.focus.isFocused())
+                                       {
+                                               this.focus.setStart();
+                                       }
 
-                       return doc;
-               },
-               iframeAddCss: function(css)
-               {
-                       css = css || this.opts.css;
+                                       this.$textarea.off('keydown.redactor-textarea-indenting');
 
-                       if (this.isString(css))
-                       {
-                               this.$frame.contents().find('head').append('<link rel="stylesheet" href="' + css + '" />');
-                       }
+                                       this.button.setActiveInVisual();
+                                       this.button.setInactive('html');
 
-                       if ($.isArray(css))
-                       {
-                               $.each(css, $.proxy(function(i, url)
+                                       this.observe.load();
+                                       this.opts.visual = true;
+                               },
+                               textareaIndenting: function(e)
                                {
-                                       this.iframeAddCss(url);
+                                       if (e.keyCode !== 9) return true;
 
-                               }, this));
-                       }
+                                       var $el = this.$textarea;
+                                       var start = $el.get(0).selectionStart;
+                                       $el.val($el.val().substring(0, start) + "\t" + $el.val().substring($el.get(0).selectionEnd));
+                                       $el.get(0).selectionStart = $el.get(0).selectionEnd = start + 1;
+
+                                       return false;
+                               }
+                       };
                },
-               iframeLoad: function()
+               clean: function()
                {
-                       this.$editor = this.$frame.contents().find('body').attr({ 'contenteditable': true, 'dir': this.opts.direction });
+                       return {
+                               onSet: function(html)
+                               {
+                                       html = this.clean.savePreCode(html);
 
-                       // set document & window
-                       if (this.$editor[0])
-                       {
-                               this.document = this.$editor[0].ownerDocument;
-                               this.window = this.document.defaultView || window;
-                       }
+                                       // replace dollar sign to entity
+                                       html = html.replace(/\$/g, '&#36;');
+                                       html = html.replace(/”/g, '"');
+                                       html = html.replace(/‘/g, '\'');
+                                       html = html.replace(/’/g, '\'');
 
-                       // iframe css
-                       this.iframeAddCss();
+                                       if (this.opts.replaceDivs) html = this.clean.replaceDivs(html);
+                                       if (this.opts.linebreaks)  html = this.clean.replaceParagraphsToBr(html);
 
-                       if (this.opts.fullpage)
-                       {
-                               this.setFullpageOnInit(this.$source.val());
-                       }
-                       else this.set(this.content, true, false);
+                                       // save form tag
+                                       html = this.clean.saveFormTags(html);
 
-                       this.buildOptions();
-                       this.buildAfter();
-               },
+                                       // convert font tag to span
+                                       html = html.replace(/<font(.*?)style="(.*?)"(.*?)>([\w\W]*?)<\/font>/gi, '<span style="$2">$4</span>');
 
-               // PLACEHOLDER
-               placeholderInit: function()
-               {
-                       if (this.opts.placeholder !== false)
-                       {
-                               this.placeholderText = this.opts.placeholder;
-                               this.opts.placeholder = true;
-                       }
-                       else
-                       {
-                               if (typeof this.$element.attr('placeholder') == 'undefined' || this.$element.attr('placeholder') == '')
-                               {
-                                       this.opts.placeholder = false;
-                               }
-                               else
-                               {
-                                       this.placeholderText = this.$element.attr('placeholder');
-                                       this.opts.placeholder = true;
-                               }
-                       }
-               },
-               placeholderStart: function(html)
-               {
-                       if (this.opts.placeholder === false)
-                       {
-                               return false;
-                       }
+                                       // remove font tag
+                                       html = html.replace(/<font(.*?[^<])>/gi, '');
+                                       html = html.replace(/<\/font>/gi, '');
 
-                       if (this.isEmpty(html))
-                       {
-                               this.opts.focus = false;
-                               this.placeholderOnFocus();
-                               this.placeholderOnBlur();
+                                       // tidy html
+                                       html = this.tidy.load(html);
 
-                               return this.placeholderGet();
-                       }
-                       else
-                       {
-                               this.placeholderOnBlur();
-                       }
+                                       // paragraphize
+                                       if (this.opts.paragraphize) html = this.paragraphize.load(html);
 
-                       return false;
-               },
-               placeholderOnFocus: function()
-               {
-                       this.$editor.on('focus.redactor_placeholder', $.proxy(this.placeholderFocus, this));
-               },
-               placeholderOnBlur: function()
-               {
-                       this.$editor.on('blur.redactor_placeholder', $.proxy(this.placeholderBlur, this));
-               },
-               placeholderGet: function()
-               {
-                       var ph = $('<span class="redactor_placeholder">').data('redactor', 'verified')
-                       .attr('contenteditable', false).text(this.placeholderText);
+                                       // verified
+                                       html = this.clean.setVerified(html);
 
-                       if (this.opts.linebreaks === false)
-                       {
-                               return $('<p>').append(ph);
-                       }
-                       else return ph;
-               },
-               placeholderBlur: function()
-               {
-                       var html = this.get();
-                       if (this.isEmpty(html))
-                       {
-                               this.placeholderOnFocus();
-                               this.$editor.html(this.placeholderGet());
-                       }
-               },
-               placeholderFocus: function()
-               {
-                       this.$editor.find('span.redactor_placeholder').remove();
+                                       // convert inline tags
+                                       html = this.clean.convertInline(html);
 
-                       var html = '';
-                       if (this.opts.linebreaks === false)
-                       {
-                               html = this.opts.emptyHtml;
-                       }
+                                       return html;
+                               },
+                               onSync: function(html)
+                               {
+                                       // remove spaces
+                                       html = html.replace(/[\u200B-\u200D\uFEFF]/g, '');
+                                       html = html.replace(/&#x200b;/gi, '');
+                                       html = html.replace(/&nbsp;/gi, ' ');
 
-                       this.$editor.off('focus.redactor_placeholder');
-                       this.$editor.html(html);
+                                       if (html.search(/^<p>(||\s||&nbsp;)<\/p>$/i) != -1)
+                                       {
+                                               return '';
+                                       }
 
-                       if (this.opts.linebreaks === false)
-                       {
-                               // place the cursor inside emptyHtml
-                               this.selectionStart(this.$editor.children()[0]);
-                       }
-                       else
-                       {
-                               this.focus();
-                       }
+                                       // restore form tag
+                                       html = this.clean.restoreFormTags(html);
+
+                                       var chars = {
+                                               '\u2122': '&trade;',
+                                               '\u00a9': '&copy;',
+                                               '\u2026': '&hellip;',
+                                               '\u2014': '&mdash;',
+                                               '\u2010': '&dash;'
+                                       };
+                                       // replace special characters
+                                       $.each(chars, function(i,s)
+                                       {
+                                               html = html.replace(new RegExp(i, 'g'), s);
+                                       });
 
-                       this.sync();
-               },
-               placeholderRemoveFromEditor: function()
-               {
-                       this.$editor.find('span.redactor_placeholder').remove();
-                       this.$editor.off('focus.redactor_placeholder');
-               },
-               placeholderRemoveFromCode: function(html)
-               {
-                       return html.replace(/<span class="redactor_placeholder"(.*?)>(.*?)<\/span>/i, '');
-               },
+                                       // remove br in the of li
+                                       html = html.replace(new RegExp('<br\\s?/?></li>', 'gi'), '</li>');
+                                       html = html.replace(new RegExp('</li><br\\s?/?>', 'gi'), '</li>');
 
-               // SHORTCUTS
-               shortcuts: function(e, key)
-               {
+                                       // remove verified
+                                       html = html.replace(new RegExp('<div(.*?) data-tagblock="redactor"(.*?[^>])>', 'gi'), '<div$1$2>');
+                                       html = html.replace(new RegExp('<(.*?) data-verified="redactor"(.*?[^>])>', 'gi'), '<$1$2>');
+                                       html = html.replace(new RegExp('<span(.*?) rel="(.*?)"(.*?[^>])>', 'gi'), '<span$1$3>');
+                                       html = html.replace(new RegExp('<img(.*?) rel="(.*?)"(.*?[^>])>', 'gi'), '<img$1$3>');
+                                       html = html.replace(new RegExp('<span class="redactor-invisible-space">(.*?)</span>', 'gi'), '$1');
+                                       html = html.replace(/ data-save-url="(.*?[^>])"/gi, '');
 
-                       // disable browser's hot keys for bold and italic
-                       if (!this.opts.shortcuts)
-                       {
-                               if ((e.ctrlKey || e.metaKey) && (key === 66 || key === 73))
-                               {
-                                       e.preventDefault();
-                               }
+                                       // remove image resize
+                                       html = html.replace(/<span(.*?)id="redactor-image-box"(.*?[^>])>([\w\W]*?)<img(.*?)><\/span>/gi, '$3<img$4>');
+                                       html = html.replace(/<span(.*?)id="redactor-image-resizer"(.*?[^>])>(.*?)<\/span>/gi, '');
+                                       html = html.replace(/<span(.*?)id="redactor-image-editter"(.*?[^>])>(.*?)<\/span>/gi, '');
 
-                               return false;
-                       }
+                                       // tidy html
+                                       html = this.tidy.load(html);
 
-                       $.each(this.opts.shortcuts, $.proxy(function(str, command)
-                       {
-                               var keys = str.split(',');
-                               for (var i in keys)
-                               {
-                                       if (typeof keys[i] === 'string')
+                                       // link nofollow
+                                       if (this.opts.linkNofollow)
                                        {
-                                               this.shortcutsHandler(e, $.trim(keys[i]), $.proxy(function()
-                                               {
-                                                       eval(command);
-                                               }, this));
+                                               html = html.replace(/<a(.*?)rel="nofollow"(.*?[^>])>/gi, '<a$1$2>');
+                                               html = html.replace(/<a(.*?[^>])>/gi, '<a$1 rel="nofollow">');
                                        }
 
-                               }
+                                       // reconvert inline
+                                       html = html.replace(/<(.*?) data-redactor-tag="(.*?)"(.*?[^>])>/gi, '<$1$3>');
+                                       html = html.replace(/<(.*?) data-redactor-class="(.*?)"(.*?[^>])>/gi, '<$1$3>');
+                                       html = html.replace(/<(.*?) data-redactor-style="(.*?)"(.*?[^>])>/gi, '<$1$3>');
+                                       html = html.replace(new RegExp('<(.*?) data-verified="redactor"(.*?[^>])>', 'gi'), '<$1$2>');
+                                       html = html.replace(new RegExp('<(.*?) data-verified="redactor">', 'gi'), '<$1>');
 
-                       }, this));
+                                       return html;
+                               },
+                               onPaste: function(html, setMode)
+                               {
+                                       html = $.trim(html);
 
+                                       html = html.replace(/\$/g, '&#36;');
+                                       html = html.replace(/”/g, '"');
+                                       html = html.replace(/“/g, '"');
+                                       html = html.replace(/‘/g, '\'');
+                                       html = html.replace(/’/g, '\'');
 
-               },
-               shortcutsHandler: function(e, keys, origHandler)
-               {
-                       // based on https://github.com/jeresig/jquery.hotkeys
-                       var hotkeysSpecialKeys =
-                       {
-                               8: "backspace", 9: "tab", 10: "return", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause",
-                               20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home",
-                               37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", 59: ";", 61: "=",
-                               96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7",
-                               104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/",
-                               112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8",
-                               120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 173: "-", 186: ";", 187: "=",
-                               188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'"
-                       };
+                                       // convert dirty spaces
+                                       html = html.replace(/<span class="Apple-converted-space">&nbsp;<\/span>/gi, ' ');
+                                       html = html.replace(/<span class="Apple-tab-span"[^>]*>\t<\/span>/gi, '\t');
+                                       html = html.replace(/<span[^>]*>(\s|&nbsp;)<\/span>/gi, ' ');
 
+                                       if (this.opts.pastePlainText)
+                                       {
+                                               return this.clean.getPlainText(html);
+                                       }
 
-                       var hotkeysShiftNums =
-                       {
-                               "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&",
-                               "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<",
-                               ".": ">",  "/": "?",  "\\": "|"
-                       };
+                                       if (!this.utils.isSelectAll() && typeof setMode == 'undefined')
+                                       {
+                                               if (this.utils.isCurrentOrParent(['FIGCAPTION', 'A']))
+                                               {
+                                                       return this.clean.getPlainText(html, false);
+                                               }
 
-                       keys = keys.toLowerCase().split(" ");
-                       var special = hotkeysSpecialKeys[e.keyCode],
-                               character = String.fromCharCode( e.which ).toLowerCase(),
-                               modif = "", possible = {};
+                                               if (this.utils.isCurrentOrParent('PRE'))
+                                               {
+                                                       return this.clean.getPreCode(html);
+                                               }
 
-                       $.each([ "alt", "ctrl", "meta", "shift"], function(index, specialKey)
-                       {
-                               if (e[specialKey + 'Key'] && special !== specialKey)
-                               {
-                                       modif += specialKey + '+';
-                               }
-                       });
+                                               if (this.utils.isCurrentOrParent(['BLOCKQUOTE', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6']))
+                                               {
+                                                       html = this.clean.getOnlyImages(html);
 
+                                                       if (!this.utils.browser('msie'))
+                                                       {
+                                                               var block = this.selection.getBlock();
+                                                               if (block && block.tagName == 'P')
+                                                               {
+                                                                       html = html.replace(/<img(.*?)>/gi, '<p><img$1></p>');
+                                                               }
+                                                       }
 
-                       if (special)
-                       {
-                               possible[modif + special] = true;
-                       }
+                                                       return html;
+                                               }
 
-                       if (character)
-                       {
-                               possible[modif + character] = true;
-                               possible[modif + hotkeysShiftNums[character]] = true;
+                                               if (this.utils.isCurrentOrParent(['TD']))
+                                               {
+                                                       html = this.clean.onPasteTidy(html, 'td');
 
-                               // "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
-                               if (modif === "shift+")
-                               {
-                                       possible[hotkeysShiftNums[character]] = true;
-                               }
-                       }
+                                                       if (this.opts.linebreaks) html = this.clean.replaceParagraphsToBr(html);
 
-                       for (var i = 0, l = keys.length; i < l; i++)
-                       {
-                               if (possible[keys[i]])
-                               {
-                                       e.preventDefault();
-                                       return origHandler.apply(this, arguments);
-                               }
-                       }
-               },
+                                                       html = this.clean.replaceDivsToBr(html);
 
-               // FOCUS
-               focus: function()
-               {
-                       if (!this.browser('opera'))
-                       {
-                               this.window.setTimeout($.proxy(this.focusSet, this, true), 1);
-                       }
-                       else
-                       {
-                               this.$editor.focus();
-                       }
-               },
-               focusWithSaveScroll: function()
-               {
-                       if (this.browser('msie'))
-                       {
-                               var top = this.document.documentElement.scrollTop;
-                       }
+                                                       return html;
+                                               }
 
-                       this.$editor.focus();
 
-                       if (this.browser('msie'))
-                       {
-                               this.document.documentElement.scrollTop = top;
-                       }
-               },
-               focusEnd: function()
-               {
-                       if (!this.browser('mozilla'))
-                       {
-                               this.focusSet();
-                       }
-                       else
-                       {
-                               if (this.opts.linebreaks === false)
-                               {
-                                       var last = this.$editor.children().last();
+                                               if (this.utils.isCurrentOrParent(['LI']))
+                                               {
+                                                       return this.clean.onPasteTidy(html, 'li');
+                                               }
+                                       }
 
-                                       this.$editor.focus();
-                                       this.selectionEnd(last);
-                               }
-                               else
-                               {
-                                       this.focusSet();
-                               }
-                       }
-               },
-               focusSet: function(collapse, element)
-               {
-                       this.$editor.focus();
 
-                       if (typeof element == 'undefined')
-                       {
-                               element = this.$editor[0];
-                       }
+                                       html = this.clean.isSingleLine(html, setMode);
 
-                       var range = this.getRange();
-                       range.selectNodeContents(element);
+                                       if (!this.clean.singleLine)
+                                       {
+                                               if (this.opts.linebreaks)  html = this.clean.replaceParagraphsToBr(html);
+                                               if (this.opts.replaceDivs) html = this.clean.replaceDivs(html);
 
-                       // collapse - controls the position of focus: the beginning (true), at the end (false).
-                       range.collapse(collapse || false);
+                                               html = this.clean.saveFormTags(html);
+                                       }
 
-                       var sel = this.getSelection();
-                       sel.removeAllRanges();
-                       sel.addRange(range);
-               },
+                                       html = this.clean.onPasteIeFixLinks(html);
+                                       html = this.clean.onPasteWord(html);
+                                       html = this.clean.onPasteExtra(html);
 
-               // TOGGLE
-               toggle: function(direct)
-               {
-                       if (this.opts.visual) this.toggleCode(direct);
-                       else this.toggleVisual();
-               },
-               toggleVisual: function()
-               {
-                       var html = this.$source.hide().val();
-                       if (typeof this.modified !== 'undefined')
-                       {
-                               var modified = this.modified.replace(/\n/g, '');
+                                       html = this.clean.onPasteTidy(html, 'all');
 
-                               var thtml = html.replace(/\n/g, '');
-                               thtml = this.cleanRemoveSpaces(thtml, false);
+                                       // paragraphize
+                                       if (!this.clean.singleLine && this.opts.paragraphize)
+                                       {
+                                               html = this.paragraphize.load(html);
+                                       }
 
-                               this.modified = this.cleanRemoveSpaces(modified, false) !== thtml;
-                       }
+                                       html = this.clean.removeDirtyStyles(html);
+                                       html = this.clean.onPasteRemoveSpans(html);
+                                       html = this.clean.onPasteRemoveEmpty(html);
 
-                       if (this.modified)
-                       {
-                               // don't remove the iframe even if cleared all.
-                               if (this.opts.fullpage && html === '')
-                               {
-                                       this.setFullpageOnInit(html);
-                               }
-                               else
+                                       html = this.clean.convertInline(html);
+
+                                       return html;
+                               },
+                               onPasteWord: function(html)
                                {
-                                       this.set(html);
-                                       if (this.opts.fullpage)
+                                       // comments
+                                       html = html.replace(/<!--[\s\S]*?-->/gi, '');
+
+                                       // style
+                                       html = html.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '');
+
+                                       // shapes
+                                       html = html.replace(/<img(.*?)v:shapes=(.*?)>/gi, '');
+                                       html = html.replace(/src="file\:\/\/(.*?)"/, 'src=""');
+
+                                       // list
+                                       html = html.replace(/<p(.*?)class="MsoListParagraphCxSpFirst"([\w\W]*?)<\/p>/gi, '<ul><li$2</li>');
+                                       html = html.replace(/<p(.*?)class="MsoListParagraphCxSpMiddle"([\w\W]*?)<\/p>/gi, '<li$2</li>');
+                                       html = html.replace(/<p(.*?)class="MsoListParagraphCxSpLast"([\w\W]*?)<\/p>/gi, '<li$2</li></ul>');
+                                       // one line
+                                       html = html.replace(/<p(.*?)class="MsoListParagraph"([\w\W]*?)<\/p>/gi, '<ul><li$2</li></ul>');
+                                       // remove ms word's bullet
+                                       html = html.replace(/·/g, '');
+                                       html = html.replace(/<p class="Mso(.*?)"/gi, '<p');
+
+                                       // classes
+                                       html = html.replace(/ class=\"(mso[^\"]*)\"/gi, "");
+                                       html = html.replace(/ class=(mso\w+)/gi, "");
+
+                                       // remove ms word tags
+                                       html = html.replace(/<o:p(.*?)>([\w\W]*?)<\/o:p>/gi, '$2');
+
+                                       // remove nbsp
+                                       if (this.opts.cleanSpaces)
                                        {
-                                               this.buildBindKeyboard();
+                                               html = html.replace(/(\s|&nbsp;)+/g, ' ');
                                        }
-                               }
 
-                               this.callback('change', false, html);
-                       }
+                                       // ms word break lines
+                                       html = html.replace(/\n/g, ' ');
 
-                       if (this.opts.iframe) this.$frame.show();
-                       else this.$editor.show();
+                                       // ms word lists break lines
+                                       html = html.replace(/<p>\n?<li>/gi, '<li>');
 
-                       if (this.opts.fullpage) this.$editor.attr('contenteditable', true );
+                                       return html;
+                               },
+                               onPasteExtra: function(html)
+                               {
+                                       // remove google docs markers
+                                       html = html.replace(/<b\sid="internal-source-marker(.*?)">([\w\W]*?)<\/b>/gi, "$2");
+                                       html = html.replace(/<b(.*?)id="docs-internal-guid(.*?)">([\w\W]*?)<\/b>/gi, "$3");
 
-                       this.$source.off('keydown.redactor-textarea-indenting');
+                                       // google docs styles
+                                       html = html.replace(/<span[^>]*(font-style: italic; font-weight: bold|font-weight: bold; font-style: italic)[^>]*>/gi, '<span style="font-weight: bold;"><span style="font-style: italic;">');
+                                       html = html.replace(/<span[^>]*font-style: italic[^>]*>/gi, '<span style="font-style: italic;">');
+                                       html = html.replace(/<span[^>]*font-weight: bold[^>]*>/gi, '<span style="font-weight: bold;">');
+                                       html = html.replace(/<span[^>]*text-decoration: underline[^>]*>/gi, '<span style="text-decoration: underline;">');
 
-                       this.$editor.focus();
-                       this.selectionRestore();
+                                       html = html.replace(/<img>/gi, '');
+                                       html = html.replace(/\n{3,}/gi, '\n');
+                                       html = html.replace(/<font(.*?)>([\w\W]*?)<\/font>/gi, '$2');
 
-                       this.observeStart();
-                       this.buttonActiveVisual();
-                       this.buttonInactive('html');
-                       this.opts.visual = true;
+                                       // remove dirty p
+                                       html = html.replace(/<p><p>/gi, '<p>');
+                                       html = html.replace(/<\/p><\/p>/gi, '</p>');
+                                       html = html.replace(/<li>(\s*|\t*|\n*)<p>/gi, '<li>');
+                                       html = html.replace(/<\/p>(\s*|\t*|\n*)<\/li>/gi, '</li>');
 
+                                       // remove space between paragraphs
+                                       html = html.replace(/<\/p>\s<p/gi, '<\/p><p');
 
-               },
-               toggleCode: function(direct)
-               {
-                       if (direct !== false) this.selectionSave();
+                                       // remove safari local images
+                                       html = html.replace(/<img src="webkit-fake-url\:\/\/(.*?)"(.*?)>/gi, '');
 
-                       var height = null;
-                       if (this.opts.iframe)
-                       {
-                               height = this.$frame.height();
-                               if (this.opts.fullpage) this.$editor.removeAttr('contenteditable');
-                               this.$frame.hide();
-                       }
-                       else
-                       {
-                               height = this.$editor.innerHeight();
-                               this.$editor.hide();
-                       }
+                                       // bullets
+                                       html = html.replace(/<p>•([\w\W]*?)<\/p>/gi, '<li>$1</li>');
 
-                       var html = this.$source.val();
+                                       // FF fix
+                                       if (this.utils.browser('mozilla'))
+                                       {
+                                               html = html.replace(/<br\s?\/?>$/gi, '');
+                                       }
 
-                       // tidy html
-                       if (html !== '' && this.opts.tidyHtml)
-                       {
-                               this.$source.val(this.cleanHtml(html));
-                       }
+                                       return html;
+                               },
+                               onPasteTidy: function(html, type)
+                               {
+                                       // remove all tags except these
+                                       var tags = ['span', 'a', 'pre', 'blockquote', 'small', 'em', 'strong', 'code', 'kbd', 'mark', 'address', 'cite', 'var', 'samp', 'dfn', 'sup', 'sub', 'b', 'i', 'u', 'del',
+                                                               'ol', 'ul', 'li', 'dl', 'dt', 'dd', 'p', 'br', 'video', 'audio', 'embed', 'param', 'object', 'img', 'table',
+                                                               'td', 'th', 'tr', 'tbody', 'tfoot', 'thead', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
+                                       var tagsEmpty = false;
+                                       var attrAllowed =  [
+                                                       ['a', '*'],
+                                                       ['img', ['src', 'alt']],
+                                                       ['span', ['class', 'rel', 'data-verified']],
+                                                       ['video', '*'],
+                                                       ['audio', '*'],
+                                                       ['embed', '*'],
+                                                       ['object', '*'],
+                                                       ['param', '*'],
+                                                       ['source', '*']
+                                               ];
+
+                                       if (type == 'all')
+                                       {
+                                               tagsEmpty = ['p', 'span', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
+                                               attrAllowed =  [
+                                                       ['table', 'class'],
+                                                       ['td', ['colspan', 'rowspan']],
+                                                       ['a', '*'],
+                                                       ['img', ['src', 'alt', 'data-redactor-inserted-image']],
+                                                       ['span', ['class', 'rel', 'data-verified']],
+                                                       ['video', '*'],
+                                                       ['audio', '*'],
+                                                       ['embed', '*'],
+                                                       ['object', '*'],
+                                                       ['param', '*'],
+                                                       ['source', '*']
+                                               ];
+                                       }
+                                       else if (type == 'td')
+                                       {
+                                               // remove all tags except these and remove all table tags: tr, td etc
+                                               tags = ['ul', 'ol', 'li', 'span', 'a', 'small', 'em', 'strong', 'code', 'kbd', 'mark', 'cite', 'var', 'samp', 'dfn', 'sup', 'sub', 'b', 'i', 'u', 'del',
+                                                               'ol', 'ul', 'li', 'dl', 'dt', 'dd', 'br', 'video', 'audio', 'embed', 'param', 'object', 'img', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
 
-                       this.modified = html;
+                                       }
+                                       else if (type == 'li')
+                                       {
+                                               // only inline tags and ul, ol, li
+                                               tags = ['ul', 'ol', 'li', 'span', 'a', 'small', 'em', 'strong', 'code', 'kbd', 'mark', 'cite', 'var', 'samp', 'dfn', 'sup', 'sub', 'b', 'i', 'u', 'del', 'br',
+                                                               'video', 'audio', 'embed', 'param', 'object', 'img'];
+                                       }
 
-                       this.$source.height(height).show().focus();
+                                       var options = {
+                                               deniedTags: false,
+                                               allowedTags: tags,
+                                               removeComments: true,
+                                               removePhp: true,
+                                               removeAttr: false,
+                                               allowedAttr: attrAllowed,
+                                               removeEmpty: tagsEmpty
+                                       };
 
-                       // textarea indenting
-                       this.$source.on('keydown.redactor-textarea-indenting', this.textareaIndenting);
 
-                       this.buttonInactiveVisual();
-                       this.buttonActive('html');
-                       this.opts.visual = false;
-               },
-               textareaIndenting: function(e)
-               {
-                       if (e.keyCode === 9)
-                       {
-                               var $el = $(this);
-                               var start = $el.get(0).selectionStart;
-                               $el.val($el.val().substring(0, start) + "\t" + $el.val().substring($el.get(0).selectionEnd));
-                               $el.get(0).selectionStart = $el.get(0).selectionEnd = start + 1;
-                               return false;
-                       }
-               },
+                                       return this.tidy.load(html, options);
 
-               // AUTOSAVE
-               autosave: function()
-               {
-                       var savedHtml = false;
-                       this.autosaveInterval = setInterval($.proxy(function()
-                       {
-                               var html = this.get();
-                               if (savedHtml !== html)
+                               },
+                               onPasteRemoveEmpty: function(html)
                                {
-                                       var name = this.$source.attr('name');
-                                       $.ajax({
-                                               url: this.opts.autosave,
-                                               type: 'post',
-                                               data: 'name=' + name + '&' + name + '=' + escape(encodeURIComponent(html)),
-                                               success: $.proxy(function(data)
-                                               {
-                                                       var json = $.parseJSON(data);
-                                                       if (typeof json.error == 'undefined')
-                                                       {
-                                                               // success
-                                                               this.callback('autosave', false, json);
-                                                       }
-                                                       else
-                                                       {
-                                                               // error
-                                                               this.callback('autosaveError', false, json);
-                                                       }
+                                       html = html.replace(/<(p|h[1-6])>(|\s|\n|\t|<br\s?\/?>)<\/(p|h[1-6])>/gi, '');
 
-                                                       savedHtml = html;
+                                       // remove br in the end
+                                       if (!this.opts.linebreaks) html = html.replace(/<br>$/i, '');
 
-                                               }, this)
-                                       });
-                               }
-                       }, this), this.opts.autosaveInterval*1000);
-               },
+                                       return html;
+                               },
+                               onPasteRemoveSpans: function(html)
+                               {
+                                       html = html.replace(/<span>(.*?)<\/span>/gi, '$1');
+                                       html = html.replace(/<span[^>]*>\s|&nbsp;<\/span>/gi, ' ');
 
-               // TOOLBAR
-               toolbarBuild: function()
-               {
-                       // hide on mobile
-                       if (this.isMobile() && this.opts.buttonsHideOnMobile.length > 0)
-                       {
-                               $.each(this.opts.buttonsHideOnMobile, $.proxy(function(i, s)
+                                       return html;
+                               },
+                               onPasteIeFixLinks: function(html)
                                {
-                                       var index = this.opts.buttons.indexOf(s);
-                                       this.opts.buttons.splice(index, 1);
+                                       if (!this.utils.browser('msie')) return html;
 
-                               }, this));
-                       }
+                                       var tmp = $.trim(html);
+                                       if (tmp.search(/^<a(.*?)>(.*?)<\/a>$/i) === 0)
+                                       {
+                                               html = html.replace(/^<a(.*?)>(.*?)<\/a>$/i, "$2");
+                                       }
 
-                       // extend buttons
-                       if (this.opts.air)
-                       {
-                               this.opts.buttons = this.opts.airButtons;
-                       }
-                       else
-                       {
-                               if (!this.opts.buttonSource)
+                                       return html;
+                               },
+                               isSingleLine: function(html, setMode)
                                {
-                                       var index = this.opts.buttons.indexOf('html');
-                                       this.opts.buttons.splice(index, 1);
-                               }
-                       }
+                                       this.clean.singleLine = false;
 
-                       // formatting tags
-                       if (this.opts.toolbar)
-                       {
-                               $.each(this.opts.toolbar.formatting.dropdown, $.proxy(function (i, s)
+                                       if (!this.utils.isSelectAll() && typeof setMode == 'undefined')
+                                       {
+                                               var blocks = this.opts.blockLevelElements.join('|').replace('P|', '').replace('DIV|', '');
+
+                                               var matchBlocks = html.match(new RegExp('</(' + blocks + ')>', 'gi'));
+                                               var matchContainers = html.match(/<\/(p|div)>/gi);
+
+                                               if (!matchBlocks && (matchContainers === null || (matchContainers && matchContainers.length <= 1)))
+                                               {
+                                                       var matchBR = html.match(/<br\s?\/?>/gi);
+                                                       var matchIMG = html.match(/<img(.*?[^>])>/gi);
+                                                       if (!matchBR && !matchIMG)
+                                                       {
+                                                               this.clean.singleLine = true;
+                                                               html = html.replace(/<\/?(p|div)(.*?)>/gi, '');
+                                                       }
+                                               }
+                                       }
+
+                                       return html;
+                               },
+                               stripTags: function(input, allowed)
                                {
-                                       if ($.inArray(i, this.opts.formattingTags ) == '-1') delete this.opts.toolbar.formatting.dropdown[i];
+                                   allowed = (((allowed || '') + '').toLowerCase().match(/<[a-z][a-z0-9]*>/g) || []).join('');
+                                   var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi;
 
-                               }, this));
-                       }
+                                   return input.replace(tags, function ($0, $1) {
+                                       return allowed.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : '';
+                                   });
+                               },
+                               savePreCode: function(html)
+                               {
+                                       var pre = html.match(/<(pre|code)(.*?)>([\w\W]*?)<\/(pre|code)>/gi);
+                                       if (pre !== null)
+                                       {
+                                               $.each(pre, $.proxy(function(i,s)
+                                               {
+                                                       var arr = s.match(/<(pre|code)(.*?)>([\w\W]*?)<\/(pre|code)>/i);
 
-                       // if no buttons don't create a toolbar
-                       if (this.opts.buttons.length === 0) return false;
+                                                       arr[3] = arr[3].replace(/<br\s?\/?>/g, '\n');
+                                                       arr[3] = arr[3].replace(/&nbsp;/g, ' ');
 
-                       // air enable
-                       this.airEnable();
+                                                       if (this.opts.preSpaces)
+                                                       {
+                                                               arr[3] = arr[3].replace(/\t/g, Array(this.opts.preSpaces + 1).join(' '));
+                                                       }
 
-                       // toolbar build
-                       this.$toolbar = $('<ul>').addClass('redactor_toolbar').attr('id', 'redactor_toolbar_' + this.uuid);
+                                                       arr[3] = this.clean.encodeEntities(arr[3]);
 
-                       if (this.opts.typewriter)
-                       {
-                               this.$toolbar.addClass('redactor-toolbar-typewriter');
-                       }
+                                                       // $ fix
+                                                       arr[3] = arr[3].replace(/\$/g, '&#36;');
 
-                       if (this.opts.toolbarOverflow && this.isMobile())
-                       {
-                               this.$toolbar.addClass('redactor-toolbar-overflow');
-                       }
+                                                       html = html.replace(s, '<' + arr[1] + arr[2] + '>' + arr[3] + '</' + arr[1] + '>');
 
-                       if (this.opts.air)
-                       {
-                               // air box
-                               this.$air = $('<div class="redactor_air">').attr('id', 'redactor_air_' + this.uuid).hide();
-                               this.$air.append(this.$toolbar);
-                               $('body').append(this.$air);
-                       }
-                       else
-                       {
-                               if (this.opts.toolbarExternal)
+                                               }, this));
+                                       }
+
+                                       return html;
+                               },
+                               getTextFromHtml: function(html)
                                {
-                                       this.$toolbar.addClass('redactor-toolbar-external');
-                                       $(this.opts.toolbarExternal).html(this.$toolbar);
-                               }
-                               else this.$box.prepend(this.$toolbar);
-                       }
+                                       html = html.replace(/<br\s?\/?>|<\/H[1-6]>|<\/p>|<\/div>|<\/li>|<\/td>/gi, '\n');
 
-                       $.each(this.opts.buttons, $.proxy(function(i, btnName)
-                       {
-                               if (this.opts.toolbar[btnName])
+                                       var tmp = document.createElement('div');
+                                       tmp.innerHTML = html;
+                                       html = tmp.textContent || tmp.innerText;
+
+                                       return $.trim(html);
+                               },
+                               getPlainText: function(html, paragraphize)
                                {
-                                       var btnObject = this.opts.toolbar[btnName];
-                                       if (this.opts.fileUpload === false && btnName === 'file') return true;
-                                       this.$toolbar.append( $('<li>').append(this.buttonBuild(btnName, btnObject)));
-                               }
+                                       html = this.clean.getTextFromHtml(html);
+                                       html = html.replace(/\n/g, '<br />');
 
-                       }, this));
+                                       if (this.opts.paragraphize && typeof paragraphize == 'undefined')
+                                       {
+                                               html = this.clean.paragraphize(html);
+                                       }
 
-                       this.$toolbar.find('a').attr('tabindex', '-1');
+                                       return html;
+                               },
+                               getPreCode: function(html)
+                               {
+                                       html = html.replace(/<img(.*?) style="(.*?)"(.*?[^>])>/gi, '<img$1$3>');
+                                       html = html.replace(/<img(.*?)>/gi, '&lt;img$1&gt;');
+                                       html = this.clean.getTextFromHtml(html);
 
-                       // fixed
-                       if (this.opts.toolbarFixed)
-                       {
-                               this.toolbarObserveScroll();
-                               $(this.opts.toolbarFixedTarget).on('scroll.redactor', $.proxy(this.toolbarObserveScroll, this));
-                       }
+                                       if (this.opts.preSpaces)
+                                       {
+                                               html = html.replace(/\t/g, Array(this.opts.preSpaces + 1).join(' '));
+                                       }
 
-                       // buttons response
-                       if (this.opts.activeButtons)
-                       {
-                               this.$editor.on('mouseup.redactor keyup.redactor', $.proxy(this.buttonActiveObserver, this));
-                       }
-               },
-               toolbarObserveScroll: function()
-               {
-                       var scrollTop = $(this.opts.toolbarFixedTarget).scrollTop();
+                                       html = this.clean.encodeEntities(html);
 
-                       var boxTop = 0;
-                       var left = 0;
-                       var end = 0;
+                                       return html;
+                               },
+                               getOnlyImages: function(html)
+                               {
+                                       html = html.replace(/<img(.*?)>/gi, '[img$1]');
 
-                       if (this.opts.toolbarFixedTarget === document)
-                       {
-                               boxTop = this.$box.offset().top;
-                       }
-                       else
-                       {
-                               boxTop = 1;
-                       }
+                                       // remove all tags
+                                       html = html.replace(/<(.*?)>/gi, '');
 
-                       end = boxTop + this.$box.height() + 40;
+                                       html = html.replace(/\[img(.*?)\]/gi, '<img$1>');
 
-                       if (scrollTop > boxTop)
-                       {
-                               var width = '100%';
-                               if (this.opts.toolbarFixedBox)
+                                       return html;
+                               },
+                               getOnlyLinksAndImages: function(html)
                                {
-                                       left = this.$box.offset().left;
-                                       width = this.$box.innerWidth();
-                                       this.$toolbar.addClass('toolbar_fixed_box');
-                               }
+                                       html = html.replace(/<a(.*?)href="(.*?)"(.*?)>([\w\W]*?)<\/a>/gi, '[a href="$2"]$4[/a]');
+                                       html = html.replace(/<img(.*?)>/gi, '[img$1]');
 
-                               this.toolbarFixed = true;
+                                       // remove all tags
+                                       html = html.replace(/<(.*?)>/gi, '');
 
-                               if (this.opts.toolbarFixedTarget === document)
+                                       html = html.replace(/\[a href="(.*?)"\]([\w\W]*?)\[\/a\]/gi, '<a href="$1">$2</a>');
+                                       html = html.replace(/\[img(.*?)\]/gi, '<img$1>');
+
+                                       return html;
+                               },
+                               encodeEntities: function(str)
                                {
-                                       this.$toolbar.css({
-                                               position: 'fixed',
-                                               width: width,
-                                               zIndex: 10005,
-                                               top: this.opts.toolbarFixedTopOffset + 'px',
-                                               left: left
-                                       });
-                               }
-                               else
+                                       str = String(str).replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"');
+                                       return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
+                               },
+                               removeDirtyStyles: function(html)
                                {
-                                       this.$toolbar.css({
-                                               position: 'absolute',
-                                               width: width,
-                                               zIndex: 10005,
-                                               top: (this.opts.toolbarFixedTopOffset + scrollTop) + 'px',
-                                               left: 0
-                                       });
-                               }
+                                       if (this.utils.browser('msie')) return html;
 
-                               if (scrollTop < end) this.$toolbar.css('visibility', 'visible');
-                               else this.$toolbar.css('visibility', 'hidden');
-                       }
-                       else
-                       {
-                               this.toolbarFixed = false;
-                               this.$toolbar.css({
-                                       position: 'relative',
-                                       width: 'auto',
-                                       top: 0,
-                                       left: left
-                               });
-
-                               if (this.opts.toolbarFixedBox) this.$toolbar.removeClass('toolbar_fixed_box');
-                       }
-               },
+                                       var div = document.createElement('div');
+                                       div.innerHTML = html;
 
-               // AIR
-               airEnable: function()
-               {
-                       if (!this.opts.air) return;
+                                       this.clean.clearUnverifiedRemove($(div));
 
-                       this.$editor.on('mouseup.redactor keyup.redactor', this, $.proxy(function(e)
-                       {
-                               var text = this.getSelectionText();
+                                       html = div.innerHTML;
+                                       $(div).remove();
 
-                               if (e.type === 'mouseup' && text != '') this.airShow(e);
-                               if (e.type === 'keyup' && e.shiftKey && text != '')
+                                       return html;
+                               },
+                               clearUnverified: function()
                                {
-                                       var $focusElem = $(this.getElement(this.getSelection().focusNode)), offset = $focusElem.offset();
-                                       offset.height = $focusElem.height();
-                                       this.airShow(offset, true);
-                               }
+                                       if (this.utils.browser('msie')) return;
 
-                       }, this));
-               },
-               airShow: function (e, keyboard)
-               {
-                       if (!this.opts.air) return;
+                                       this.clean.clearUnverifiedRemove(this.$editor);
 
-                       this.selectionSave();
+                                       var headers = this.$editor.find('h1, h2, h3, h4, h5, h6');
+                                       headers.find('span').removeAttr('style');
+                                       headers.find(this.opts.verifiedTags.join(', ')).removeAttr('style');
 
-                       var left, top;
-                       $('.redactor_air').hide();
+                                       this.code.sync();
+                               },
+                               clearUnverifiedRemove: function($editor)
+                               {
+                                       $editor.find(this.opts.verifiedTags.join(', ')).removeAttr('style');
+                                       $editor.find('span').not('[data-verified="redactor"]').removeAttr('style');
 
-                       if (keyboard)
-                       {
-                               left = e.left;
-                               top = e.top + e.height + 14;
+                                       $editor.find('span[data-verified="redactor"], img[data-verified="redactor"]').each(function(i, s)
+                                       {
+                                               var $s = $(s);
+                                               $s.attr('style', $s.attr('rel'));
+                                       });
 
-                               if (this.opts.iframe)
+                               },
+                               setVerified: function(html)
                                {
-                                       top += this.$box.position().top - $(this.document).scrollTop();
-                                       left += this.$box.position().left;
-                               }
-                       }
-                       else
-                       {
-                               var width = this.$air.innerWidth();
+                                       if (this.utils.browser('msie')) return html;
 
-                               left = e.clientX;
-                               if ($(this.document).width() < (left + width)) left -= width;
+                                       html = html.replace(new RegExp('<img(.*?[^>])>', 'gi'), '<img$1 data-verified="redactor">');
+                                       html = html.replace(new RegExp('<span(.*?)>', 'gi'), '<span$1 data-verified="redactor">');
+
+                                       var matches = html.match(new RegExp('<(span|img)(.*?)style="(.*?)"(.*?[^>])>', 'gi'));
+                                       if (matches)
+                                       {
+                                               var len = matches.length;
+                                               for (var i = 0; i < len; i++)
+                                               {
+                                                       var newTag = matches[i].replace(/style="(.*?)"/i, 'style="$1" rel="$1"');
+                                                       html = html.replace(new RegExp(matches[i], 'gi'), newTag);
+                                               }
+                                       }
 
-                               top = e.clientY + 14;
-                               if (this.opts.iframe)
+                                       return html;
+                               },
+                               convertInline: function(html)
                                {
-                                       top += this.$box.position().top;
-                                       left += this.$box.position().left;
-                               }
-                               else top += $( this.document ).scrollTop();
-                       }
+                                       var $div = $('<div />').html(html);
 
-                       this.$air.css({
-                               left: left + 'px',
-                               top: top + 'px'
-                       }).show();
+                                       var tags = this.opts.inlineTags;
+                                       tags.push('span');
 
-                       this.airBindHide();
-               },
-               airBindHide: function()
-               {
-                       if (!this.opts.air) return;
+                                       $div.find(tags.join(',')).each(function()
+                                       {
+                                               var $el = $(this);
+                                               var tag = this.tagName.toLowerCase();
+                                               $el.attr('data-redactor-tag', tag);
 
-                       var hideHandler = $.proxy(function(doc)
-                       {
-                               $(doc).on('mousedown.redactor', $.proxy(function(e)
+                                               if (tag == 'span')
+                                               {
+                                                       if ($el.attr('style')) $el.attr('data-redactor-style', $el.attr('style'));
+                                                       else if ($el.attr('class')) $el.attr('data-redactor-class', $el.attr('class'));
+                                               }
+
+                                       });
+
+                                       html = $div.html();
+                                       $div.remove();
+
+                                       return html;
+                               },
+                               normalizeLists: function()
                                {
-                                       if ($( e.target ).closest(this.$toolbar).length === 0)
+                                       this.$editor.find('li').each(function(i,s)
                                        {
-                                               this.$air.fadeOut(100);
-                                               this.selectionRemove();
-                                               $(doc).off(e);
-                                       }
+                                               var $next = $(s).next();
+                                               if ($next.length !== 0 && ($next[0].tagName == 'UL' || $next[0].tagName == 'OL'))
+                                               {
+                                                       $(s).append($next);
+                                               }
 
-                               }, this)).on('keydown.redactor', $.proxy(function(e)
+                                       });
+                               },
+                               removeSpaces: function(html)
+                               {
+                                       html = html.replace(/\n/g, '');
+                                       html = html.replace(/[\t]*/g, '');
+                                       html = html.replace(/\n\s*\n/g, "\n");
+                                       html = html.replace(/^[\s\n]*/g, ' ');
+                                       html = html.replace(/[\s\n]*$/g, ' ');
+                                       html = html.replace( />\s{2,}</g, '> <'); // between inline tags can be only one space
+                                       html = html.replace(/\n\n/g, "\n");
+                                       html = html.replace(/[\u200B-\u200D\uFEFF]/g, '');
+
+                                       return html;
+                               },
+                               replaceDivs: function(html)
                                {
-                                       if (e.which === this.keyCode.ESC)
+                                       if (this.opts.linebreaks)
                                        {
-                                               this.getSelection().collapseToStart();
+                                               html = html.replace(/<div><br\s?\/?><\/div>/gi, '<br />');
+                                               html = html.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '$2<br />');
+                                       }
+                                       else
+                                       {
+                                               html = html.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '<p$1>$2</p>');
                                        }
 
-                                       this.$air.fadeOut(100);
-                                       $(doc).off(e);
+                                       return html;
+                               },
+                               replaceDivsToBr: function(html)
+                               {
+                                       html = html.replace(/<div\s(.*?)>/gi, '<p>');
+                                       html = html.replace(/<div><br\s?\/?><\/div>/gi, '<br /><br />');
+                                       html = html.replace(/<div>([\w\W]*?)<\/div>/gi, '$1<br /><br />');
 
-                               }, this));
-                       }, this);
+                                       return html;
+                               },
+                               replaceParagraphsToBr: function(html)
+                               {
+                                       html = html.replace(/<p\s(.*?)>/gi, '<p>');
+                                       html = html.replace(/<p><br\s?\/?><\/p>/gi, '<br />');
+                                       html = html.replace(/<p>([\w\W]*?)<\/p>/gi, '$1<br /><br />');
+                                       html = html.replace(/(<br\s?\/?>){1,}\n?<\/blockquote>/gi, '</blockquote>');
 
-                       // Hide the toolbar at events in all documents (iframe)
-                       hideHandler(document);
-                       if (this.opts.iframe) hideHandler(this.document);
+                                       return html;
+                               },
+                               saveFormTags: function(html)
+                               {
+                                       return html.replace(/<form(.*?)>([\w\W]*?)<\/form>/gi, '<section$1 rel="redactor-form-tag">$2</section>');
+                               },
+                               restoreFormTags: function(html)
+                               {
+                                       return html.replace(/<section(.*?) rel="redactor-form-tag"(.*?)>([\w\W]*?)<\/section>/gi, '<form$1$2>$3</form>');
+                               }
+                       };
                },
-               airBindMousemoveHide: function()
+               tidy: function()
                {
-                       if (!this.opts.air) return;
-
-                       var hideHandler = $.proxy(function(doc)
-                       {
-                               $(doc).on('mousemove.redactor', $.proxy(function(e)
+                       return {
+                               setupAllowed: function()
                                {
-                                       if ($( e.target ).closest(this.$toolbar).length === 0)
-                                       {
-                                               this.$air.fadeOut(100);
-                                               $(doc).off(e);
-                                       }
+                                       if (this.opts.allowedTags) this.opts.deniedTags = false;
+                                       if (this.opts.allowedAttr) this.opts.removeAttr = false;
 
-                               }, this));
-                       }, this);
+                                       if (this.opts.linebreaks) return;
 
-                       // Hide the toolbar at events in all documents (iframe)
-                       hideHandler(document);
-                       if (this.opts.iframe) hideHandler(this.document);
-               },
+                                       var tags = ['p', 'section'];
+                                       if (this.opts.allowedTags) this.tidy.addToAllowed(tags);
+                                       if (this.opts.deniedTags) this.tidy.removeFromDenied(tags);
 
-               // DROPDOWNS
-               dropdownBuild: function($dropdown, dropdownObject)
-               {
-                       $.each(dropdownObject, $.proxy(function(btnName, btnObject)
-                       {
-                               if (!btnObject.className) btnObject.className = '';
+                               },
+                               addToAllowed: function(tags)
+                               {
+                                       var len = tags.length;
+                                       for (var i = 0; i < len; i++)
+                                       {
+                                               if ($.inArray(tags[i], this.opts.allowedTags) == -1)
+                                               {
+                                                       this.opts.allowedTags.push(tags[i]);
+                                               }
+                                       }
+                               },
+                               removeFromDenied: function(tags)
+                               {
+                                       var len = tags.length;
+                                       for (var i = 0; i < len; i++)
+                                       {
+                                               var pos = $.inArray(tags[i], this.opts.deniedTags);
+                                               if (pos != -1)
+                                               {
+                                                       this.opts.deniedTags.splice(pos, 1);
+                                               }
+                                       }
+                               },
+                               load: function(html, options)
+                               {
+                                       this.tidy.settings = {
+                                               deniedTags: this.opts.deniedTags,
+                                               allowedTags: this.opts.allowedTags,
+                                               removeComments: this.opts.removeComments,
+                                               replaceTags: this.opts.replaceTags,
+                                               replaceStyles: this.opts.replaceStyles,
+                                               removeDataAttr: this.opts.removeDataAttr,
+                                               removeAttr: this.opts.removeAttr,
+                                               allowedAttr: this.opts.allowedAttr,
+                                               removeWithoutAttr: this.opts.removeWithoutAttr,
+                                               removeEmpty: this.opts.removeEmpty
+                                       };
+
+                                       $.extend(this.tidy.settings, options);
+
+                                       html = this.tidy.removeComments(html);
+                                       html = this.tidy.replaceTags(html);
+
+                                       // create container
+                                       this.tidy.$div = $('<div />').append(html);
+
+                                       // clean
+                                       this.tidy.replaceStyles();
+                                       this.tidy.removeTags();
+
+                                       this.tidy.removeAttr();
+                                       this.tidy.removeEmpty();
+                                       this.tidy.removeParagraphsInLists();
+                                       this.tidy.removeDataAttr();
+                                       this.tidy.removeWithoutAttr();
+
+                                       html = this.tidy.$div.html();
+                                       this.tidy.$div.remove();
+
+                                       return html;
+                               },
+                               removeComments: function(html)
+                               {
+                                       if (!this.tidy.settings.removeComments) return html;
 
-                               var $item;
-                               if (btnObject.name === 'separator') $item = $('<a class="redactor_separator_drop">');
-                               else
+                                       return html.replace(/<!--[\s\S]*?-->/gi, '');
+                               },
+                               replaceTags: function(html)
                                {
-                                       $item = $('<a href="#" class="' + btnObject.className + ' redactor_dropdown_' + btnName + '">' + btnObject.title + '</a>');
-                                       $item.on('click', $.proxy(function(e)
+                                       if (!this.tidy.settings.replaceTags) return html;
+
+                                       var len = this.tidy.settings.replaceTags.length;
+                                       for (var i = 0; i < len; i++)
                                        {
-                                               if (this.opts.air)
-                                               {
-                                                       this.selectionRestore();
-                                               }
+                                               var re = new RegExp('<' + this.tidy.settings.replaceTags[i][0] + '(.*?[^>])>', 'gi');
+                                               html = html.replace(re, '<' + this.tidy.settings.replaceTags[i][1] + '$1>');
 
-                                               if (e.preventDefault) e.preventDefault();
-                                               if (this.browser('msie')) e.returnValue = false;
+                                               re = new RegExp('</' + this.tidy.settings.replaceTags[i][0] + '>', 'gi');
+                                               html = html.replace(re, '</' + this.tidy.settings.replaceTags[i][1] + '>');
+                                       }
 
-                                               if (btnObject.callback) btnObject.callback.call(this, btnName, $item, btnObject, e);
-                                               if (btnObject.exec) this.execCommand(btnObject.exec, btnName);
-                                               if (btnObject.func) this[btnObject.func](btnName);
 
-                                               this.buttonActiveObserver();
-                                               if (this.opts.air) this.$air.fadeOut(100);
+                                       return html;
+                               },
+                               replaceStyles: function()
+                               {
+                                       if (!this.tidy.settings.replaceStyles) return;
 
+                                       var len = this.tidy.settings.replaceStyles.length;
+                                       this.tidy.$div.find('span').each($.proxy(function(n,s)
+                                       {
+                                               var $el = $(s);
+                                               var style = $el.attr('style');
+                                               for (var i = 0; i < len; i++)
+                                               {
+                                                       if (style && style.match(new RegExp('^' + this.tidy.settings.replaceStyles[i][0], 'i')))
+                                                       {
+                                                               var tagName = this.tidy.settings.replaceStyles[i][1];
+                                                               $el.replaceWith(function()
+                                                               {
+                                                                       var tag = document.createElement(tagName);
+                                                                       return $(tag).append($(this).contents());
+                                                               });
+                                                       }
+                                               }
 
                                        }, this));
-                               }
-
-                               $dropdown.append($item);
 
-                       }, this));
-               },
-               dropdownShow: function(e, key)
-               {
-                       if (!this.opts.visual)
-                       {
-                               e.preventDefault();
-                               return false;
-                       }
-
-                       var $button = this.buttonGet(key);
-
-                       // Always re-append it to the end of <body> so it always has the highest sub-z-index.
-                       var $dropdown  = $button.data('dropdown').appendTo(document.body);
-
-                       if ($button.hasClass('dropact')) this.dropdownHideAll();
-                       else
-                       {
-                               this.dropdownHideAll();
-                               this.callback('dropdownShow', { dropdown: $dropdown, key: key, button: $button });
-
-                               this.buttonActive(key);
-                               $button.addClass('dropact');
-
-                               var keyPosition = $button.offset();
-
-                               // fix right placement
-                               var dropdownWidth = $dropdown.width();
-                               if ((keyPosition.left + dropdownWidth) > $(document).width())
+                               },
+                               removeTags: function()
                                {
-                                       keyPosition.left -= dropdownWidth;
-                               }
-
-                               var left = keyPosition.left + 'px';
-                               var btnHeight = $button.innerHeight();
-
-                               var position = 'absolute';
-                               var top = (btnHeight + this.opts.toolbarFixedTopOffset) + 'px';
-
-                               if (this.opts.toolbarFixed && this.toolbarFixed) position = 'fixed';
-                               else top = keyPosition.top + btnHeight + 'px';
+                                       if (!this.tidy.settings.deniedTags && this.tidy.settings.allowedTags)
+                                       {
+                                               this.tidy.$div.find('*').not(this.tidy.settings.allowedTags.join(',')).contents().unwrap();
+                                       }
 
-                               $dropdown.css({ position: position, left: left, top: top }).show();
-                               this.callback('dropdownShown', { dropdown: $dropdown, key: key, button: $button });
-                       }
+                                       if (this.tidy.settings.deniedTags)
+                                       {
+                                               this.tidy.$div.find(this.tidy.settings.deniedTags.join(',')).contents().unwrap();
+                                       }
+                               },
+                               removeAttr: function()
+                               {
+                                       var len;
+                                       if (!this.tidy.settings.removeAttr && this.tidy.settings.allowedAttr)
+                                       {
 
+                                               var allowedAttrTags = [], allowedAttrData = [];
+                                               len = this.tidy.settings.allowedAttr.length;
+                                               for (var i = 0; i < len; i++)
+                                               {
+                                                       allowedAttrTags.push(this.tidy.settings.allowedAttr[i][0]);
+                                                       allowedAttrData.push(this.tidy.settings.allowedAttr[i][1]);
+                                               }
 
-                       var hdlHideDropDown = $.proxy(function(e)
-                       {
-                               this.dropdownHide(e, $dropdown);
 
-                       }, this);
+                                               this.tidy.$div.find('*').each($.proxy(function(n,s)
+                                               {
+                                                       var $el = $(s);
+                                                       var pos = $.inArray($el[0].tagName.toLowerCase(), allowedAttrTags);
+                                                       var attributesRemove = this.tidy.removeAttrGetRemoves(pos, allowedAttrData, $el);
 
-                       $(document).one('click', hdlHideDropDown);
-                       this.$editor.one('click', hdlHideDropDown);
-                       this.$editor.one('touchstart', hdlHideDropDown);
+                                                       if (attributesRemove)
+                                                       {
+                                                               $.each(attributesRemove, function(z,f) {
+                                                                       $el.removeAttr(f);
+                                                               });
+                                                       }
+                                               }, this));
+                                       }
 
+                                       if (this.tidy.settings.removeAttr)
+                                       {
+                                               len = this.tidy.settings.removeAttr.length;
+                                               for (var i = 0; i < len; i++)
+                                               {
+                                                       var attrs = this.tidy.settings.removeAttr[i][1];
+                                                       if ($.isArray(attrs)) attrs = attrs.join(' ');
 
-                       e.stopPropagation();
-                       this.focusWithSaveScroll();
-               },
-               dropdownHideAll: function()
-               {
-                       this.$toolbar.find('a.dropact').removeClass('redactor_act').removeClass('dropact');
-                       $('.redactor_dropdown').hide();
-                       this.callback('dropdownHide');
-               },
-               dropdownHide: function (e, $dropdown)
-               {
-                       if (!$(e.target).hasClass('dropact'))
-                       {
-                               $dropdown.removeClass('dropact');
-                               this.dropdownHideAll();
-                       }
-               },
+                                                       this.tidy.$div.find(this.tidy.settings.removeAttr[i][0]).removeAttr(attrs);
+                                               }
+                                       }
 
-               // BUTTONS
-               buttonBuild: function(btnName, btnObject, buttonImage)
-               {
-                       var $button = $('<a href="javascript:;" title="' + btnObject.title + '" tabindex="-1" class="re-icon re-' + btnName + '"></a>');
+                               },
+                               removeAttrGetRemoves: function(pos, allowed, $el)
+                               {
+                                       var attributesRemove = [];
 
-                       if (typeof buttonImage != 'undefined')
-                       {
-                               $button.addClass('redactor-btn-image');
-                       }
+                                       // remove all attrs
+                                       if (pos == -1)
+                                       {
+                                               $.each($el[0].attributes, function(i, item)
+                                               {
+                                                       attributesRemove.push(item.name);
+                                               });
 
-                       $button.on('click', $.proxy(function(e)
-                       {
-                               if (e.preventDefault) e.preventDefault();
-                               if (this.browser('msie')) e.returnValue = false;
+                                       }
+                                       // allow all attrs
+                                       else if (allowed[pos] == '*')
+                                       {
+                                               attributesRemove = [];
+                                       }
+                                       // allow specific attrs
+                                       else
+                                       {
+                                               $.each($el[0].attributes, function(i, item)
+                                               {
+                                                       if ($.isArray(allowed[pos]))
+                                                       {
+                                                               if ($.inArray(item.name, allowed[pos]) == -1)
+                                                               {
+                                                                       attributesRemove.push(item.name);
+                                                               }
+                                                       }
+                                                       else if (allowed[pos] != item.name)
+                                                       {
+                                                               attributesRemove.push(item.name);
+                                                       }
 
-                               if ($button.hasClass('redactor_button_disabled')) return false;
+                                               });
+                                       }
 
-                               if (this.isFocused() === false && !btnObject.exec)
+                                       return attributesRemove;
+                               },
+                               removeAttrs: function (el, regex)
                                {
-                                       this.focusWithSaveScroll();
-                               }
+                                       regex = new RegExp(regex, "g");
+                                       return el.each(function()
+                                       {
+                                               var self = $(this);
+                                               var len = this.attributes.length - 1;
+                                               for (var i = len; i >= 0; i--)
+                                               {
+                                                       var item = this.attributes[i];
+                                                       if (item && item.specified && item.name.search(regex)>=0)
+                                                       {
+                                                               self.removeAttr(item.name);
+                                                       }
+                                               }
+                                       });
+                               },
+                               removeEmpty: function()
+                               {
+                                       if (!this.tidy.settings.removeEmpty) return;
 
-                               if (btnObject.exec)
+                                       this.tidy.$div.find(this.tidy.settings.removeEmpty.join(',')).each(function()
+                                       {
+                                               var $el = $(this);
+                                               var text = $el.text();
+                                               text = text.replace(/[\u200B-\u200D\uFEFF]/g, '');
+                                               text = text.replace(/&nbsp;/gi, '');
+                                               text = text.replace(/\s/g, '');
+
+                               if (text === '' && $el.children().length === 0)
+                               {
+                                       $el.remove();
+                               }
+                                       });
+                               },
+                               removeParagraphsInLists: function()
+                               {
+                                       this.tidy.$div.find('li p').contents().unwrap();
+                               },
+                               removeDataAttr: function()
                                {
-                                       this.focusWithSaveScroll();
+                                       if (!this.tidy.settings.removeDataAttr) return;
 
-                                       this.execCommand(btnObject.exec, btnName);
-                                       this.airBindMousemoveHide();
+                                       var tags = this.tidy.settings.removeDataAttr;
+                                       if ($.isArray(this.tidy.settings.removeDataAttr)) tags = this.tidy.settings.removeDataAttr.join(',');
 
-                               }
-                               else if (btnObject.func && btnObject.func !== 'show')
-                               {
-                                       this[btnObject.func](btnName);
-                                       this.airBindMousemoveHide();
+                                       this.tidy.removeAttrs(this.tidy.$div.find(tags), '^(data-)');
 
-                               }
-                               else if (btnObject.callback)
+                               },
+                               removeWithoutAttr: function()
                                {
-                                       btnObject.callback.call(this, btnName, $button, btnObject, e);
-                                       this.airBindMousemoveHide();
+                                       if (!this.tidy.settings.removeWithoutAttr) return;
 
+                                       this.tidy.$div.find(this.tidy.settings.removeWithoutAttr.join(',')).each(function()
+                                       {
+                                               if (this.attributes.length === 0)
+                                               {
+                                                       $(this).contents().unwrap();
+                                               }
+                                       });
                                }
-                               else if (btnObject.dropdown)
+                       };
+               },
+               paragraphize: function()
+               {
+                       return {
+                               load: function(html)
                                {
-                                       this.dropdownShow(e, btnName);
-                               }
+                                       if (this.opts.linebreaks) return html;
+                                       if (html === '' || html === '<p></p>') return this.opts.emptyHtml;
 
-                               this.buttonActiveObserver(false, btnName);
+                                       this.paragraphize.blocks = ['table', 'div', 'pre', 'form', 'ul', 'ol', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'dl', 'blockquote', 'figcaption',
+                                       'address', 'section', 'header', 'footer', 'aside', 'article', 'object', 'style', 'script', 'iframe', 'select', 'input', 'textarea',
+                                       'button', 'option', 'map', 'area', 'math', 'hr', 'fieldset', 'legend', 'hgroup', 'nav', 'figure', 'details', 'menu', 'summary', 'p'];
 
-                       }, this));
+                                       html = html + "\n";
 
-                       // dropdown
-                       if (btnObject.dropdown)
-                       {
-                               var $dropdown = $('<div class="redactor_dropdown redactor_dropdown_box_' + btnName + '" style="display: none;">');
-                               $button.data('dropdown', $dropdown);
-                               this.dropdownBuild($dropdown, btnObject.dropdown);
-                       }
+                                       this.paragraphize.safes = [];
+                                       this.paragraphize.z = 0;
 
-                       return $button;
-               },
-               buttonGet: function(key)
-               {
-                       if (!this.opts.toolbar) return false;
-                       return $(this.$toolbar.find('a.re-' + key));
-               },
-               buttonTagToActiveState: function(buttonName, tagName)
-               {
-                       this.opts.activeButtons.push(buttonName);
-                       this.opts.activeButtonsStates[tagName] = buttonName;
-               },
-               buttonActiveToggle: function(key)
-               {
-                       var btn = this.buttonGet(key);
+                                       html = html.replace(/(<br\s?\/?>){1,}\n?<\/blockquote>/gi, '</blockquote>');
 
-                       if (btn.hasClass('redactor_act'))
-                       {
-                               this.buttonInactive(key);
-                       }
-                       else
-                       {
-                               this.buttonActive(key);
-                       }
-               },
-               buttonActive: function(key)
-               {
-                       var btn = this.buttonGet(key);
-                       btn.addClass('redactor_act');
-               },
-               buttonInactive: function(key)
-               {
-                       var btn = this.buttonGet(key);
-                       btn.removeClass('redactor_act');
-               },
-               buttonInactiveAll: function(btnName)
-               {
-                       this.$toolbar.find('a.re-icon').not('.re-' + btnName).removeClass('redactor_act');
-               },
-               buttonActiveVisual: function()
-               {
-                       this.$toolbar.find('a.re-icon').not('a.re-html').removeClass('redactor_button_disabled');
-               },
-               buttonInactiveVisual: function()
-               {
-                       this.$toolbar.find('a.re-icon').not('a.re-html').addClass('redactor_button_disabled');
-               },
-               buttonChangeIcon: function (key, classname)
-               {
-                       this.buttonGet(key).addClass('re-' + classname);
-               },
-               buttonRemoveIcon: function(key, classname)
-               {
-                       this.buttonGet(key).removeClass('re-' + classname);
-               },
-               buttonAwesome: function(key, name)
-               {
-                       var button = this.buttonGet(key);
-                       button.removeClass('redactor-btn-image');
-                       button.addClass('fa-redactor-btn');
-                       button.html('<i class="fa ' + name + '"></i>');
-               },
-               buttonAdd: function(key, title, callback, dropdown)
-               {
-                       if (!this.opts.toolbar) return;
-                       var btn = this.buttonBuild(key, { title: title, callback: callback, dropdown: dropdown }, true);
+                                       html = this.paragraphize.getSafes(html);
+                                       html = this.paragraphize.getSafesComments(html);
+                                       html = this.paragraphize.replaceBreaksToNewLines(html);
+                                       html = this.paragraphize.replaceBreaksToParagraphs(html);
+                                       html = this.paragraphize.clear(html);
+                                       html = this.paragraphize.restoreSafes(html);
 
-                       this.$toolbar.append($('<li>').append(btn));
+                                       html = html.replace(new RegExp('<br\\s?/?>\n?<(' + this.paragraphize.blocks.join('|') + ')(.*?[^>])>', 'gi'), '<p><br /></p>\n<$1$2>');
 
-                       return btn;
-               },
-               buttonAddFirst: function(key, title, callback, dropdown)
-               {
-                       if (!this.opts.toolbar) return;
-                       var btn = this.buttonBuild(key, { title: title, callback: callback, dropdown: dropdown }, true);
-                       this.$toolbar.prepend($('<li>').append(btn));
-               },
-               buttonAddAfter: function(afterkey, key, title, callback, dropdown)
-               {
-                       if (!this.opts.toolbar) return;
-                       var btn = this.buttonBuild(key, { title: title, callback: callback, dropdown: dropdown }, true);
-                       var $btn = this.buttonGet(afterkey);
+                                       return $.trim(html);
+                               },
+                               getSafes: function(html)
+                               {
+                                       var $div = $('<div />').append(html);
 
-                       if ($btn.size() !== 0) $btn.parent().after($('<li>').append(btn));
-                       else this.$toolbar.append($('<li>').append(btn));
+                                       // remove paragraphs in blockquotes
+                                       $div.find('blockquote p').replaceWith(function()
+                                       {
+                                               return $(this).append('<br />').contents();
+                                       });
 
-                       return btn;
-               },
-               buttonAddBefore: function(beforekey, key, title, callback, dropdown)
-               {
-                       if (!this.opts.toolbar) return;
-                       var btn = this.buttonBuild(key, { title: title, callback: callback, dropdown: dropdown }, true);
-                       var $btn = this.buttonGet(beforekey);
+                                       html = $div.html();
 
-                       if ($btn.size() !== 0) $btn.parent().before($('<li>').append(btn));
-                       else this.$toolbar.append($('<li>').append(btn));
+                                       $div.find(this.paragraphize.blocks.join(', ')).each($.proxy(function(i,s)
+                                       {
+                                               this.paragraphize.z++;
+                                               this.paragraphize.safes[this.paragraphize.z] = s.outerHTML;
+                                               html = html.replace(s.outerHTML, '\n{replace' + this.paragraphize.z + '}');
 
-                       return btn;
-               },
-               buttonRemove: function (key)
-               {
-                       var $btn = this.buttonGet(key);
-                       $btn.remove();
-               },
-               buttonActiveObserver: function(e, btnName)
-               {
-                       var parent = this.getParent();
-                       this.buttonInactiveAll(btnName);
+                                       }, this));
 
-                       if (e === false && btnName !== 'html')
-                       {
-                               if ($.inArray(btnName, this.opts.activeButtons) != -1)
+                                       return html;
+                               },
+                               getSafesComments: function(html)
                                {
-                                       this.buttonActiveToggle(btnName);
-                               }
-                               return;
-                       }
+                                       var commentsMatches = html.match(/<!--([\w\W]*?)-->/gi);
 
-                       if (parent && parent.tagName === 'A') this.$toolbar.find('a.redactor_dropdown_link').text(this.opts.curLang.link_edit);
-                       else this.$toolbar.find('a.redactor_dropdown_link').text(this.opts.curLang.link_insert);
+                                       if (!commentsMatches) return html;
 
-                       $.each(this.opts.activeButtonsStates, $.proxy(function(key, value)
-                       {
-                               if ($(parent).closest(key, this.$editor.get()[0]).length != 0)
-                               {
-                                       this.buttonActive(value);
-                               }
+                                       $.each(commentsMatches, $.proxy(function(i,s)
+                                       {
+                                               this.paragraphize.z++;
+                                               this.paragraphize.safes[this.paragraphize.z] = s;
+                                               html = html.replace(s, '\n{replace' + this.paragraphize.z + '}');
+                                       }, this));
 
-                       }, this));
+                                       return html;
+                               },
+                               restoreSafes: function(html)
+                               {
+                                       $.each(this.paragraphize.safes, function(i,s)
+                                       {
+                                               html = html.replace('{replace' + i + '}', s);
+                                       });
 
-                       var $parent = $(parent).closest(this.opts.alignmentTags.toString().toLowerCase(), this.$editor[0]);
-                       if ($parent.length)
-                       {
-                               var align = $parent.css('text-align');
-                               if (align == '')
+                                       return html;
+                               },
+                               replaceBreaksToParagraphs: function(html)
                                {
-                                       align = 'left';
-                               }
+                                       var htmls = html.split(new RegExp('\n', 'g'), -1);
 
-                               this.buttonActive('align' + align);
-                       }
-               },
+                                       html = '';
+                                       if (htmls)
+                                       {
+                                               var len = htmls.length;
+                                               for (var i = 0; i < len; i++)
+                                               {
+                                                       if (!htmls.hasOwnProperty(i)) return;
 
-               // EXEC
-               execPasteFrag: function(html)
-               {
-                       var sel = this.getSelection();
-                       if (sel.getRangeAt && sel.rangeCount)
-                       {
-                               var range = this.getRange();
-                               range.deleteContents();
+                                                       if (htmls[i].search('{replace') == -1)
+                                                       {
+                                                               htmls[i] = htmls[i].replace(/<p>\n\t?<\/p>/gi, '');
+                                                               htmls[i] = htmls[i].replace(/<p><\/p>/gi, '');
 
-                               var el = this.document.createElement("div");
-                               el.innerHTML = html;
+                                                               if (htmls[i] !== '')
+                                                               {
+                                                                       html += '<p>' +  htmls[i].replace(/^\n+|\n+$/g, "") + "</p>";
+                                                               }
+                                                       }
+                                                       else html += htmls[i];
+                                               }
+                                       }
 
-                               var frag = this.document.createDocumentFragment(), node, lastNode;
-                               while ((node = el.firstChild))
+                                       return html;
+                               },
+                               replaceBreaksToNewLines: function(html)
                                {
-                                       lastNode = frag.appendChild(node);
-                               }
+                                       html = html.replace(/<br \/>\s*<br \/>/gi, "\n\n");
+                                       html = html.replace(/<br\s?\/?>\n?<br\s?\/?>/gi, "\n<br /><br />");
 
-                               var firstNode = frag.firstChild;
-                               range.insertNode(frag);
+                                       html = html.replace(new RegExp("\r\n", 'g'), "\n");
+                                       html = html.replace(new RegExp("\r", 'g'), "\n");
+                                       html = html.replace(new RegExp("/\n\n+/"), 'g', "\n\n");
 
-                               if (lastNode)
+                                       return html;
+                               },
+                               clear: function(html)
                                {
-                                       range = range.cloneRange();
-                                       range.setStartAfter(lastNode);
-                                       range.collapse(true);
-                               }
-                               sel.removeAllRanges();
-                               sel.addRange(range);
-                       }
-               },
-               exec: function(cmd, param, sync)
-               {
-                       if (cmd === 'formatblock' && this.browser('msie'))
-                       {
-                               param = '<' + param + '>';
-                       }
+                                       html = html.replace(new RegExp('</blockquote></p>', 'gi'), '</blockquote>');
+                                       html = html.replace(new RegExp('<p></blockquote>', 'gi'), '</blockquote>');
+                                       html = html.replace(new RegExp('<p><blockquote>', 'gi'), '<blockquote>');
+                                       html = html.replace(new RegExp('<blockquote></p>', 'gi'), '<blockquote>');
 
-                       if (cmd === 'inserthtml' && this.browser('msie'))
-                       {
-                               if (!this.isIe11())
-                               {
-                                       this.focusWithSaveScroll();
-                                       this.document.selection.createRange().pasteHTML(param);
-                               }
-                               else this.execPasteFrag(param);
-                       }
-                       else
-                       {
-                               this.document.execCommand(cmd, false, param);
-                       }
+                                       html = html.replace(new RegExp('<p><p ', 'gi'), '<p ');
+                                       html = html.replace(new RegExp('<p><p>', 'gi'), '<p>');
+                                       html = html.replace(new RegExp('</p></p>', 'gi'), '</p>');
+                                       html = html.replace(new RegExp('<p>\\s?</p>', 'gi'), '');
+                                       html = html.replace(new RegExp("\n</p>", 'gi'), '</p>');
+                                       html = html.replace(new RegExp('<p>\t?\t?\n?<p>', 'gi'), '<p>');
+                                       html = html.replace(new RegExp('<p>\t*</p>', 'gi'), '');
 
-                       if (sync !== false) this.sync();
-                       this.callback('execCommand', cmd, param);
+                                       return html;
+                               }
+                       };
                },
-               execCommand: function(cmd, param, sync)
+               tabifier: function()
                {
-                       if (!this.opts.visual)
-                       {
-                               this.$source.focus();
-                               return false;
-                       }
-
-                       if (   cmd === 'bold'
-                               || cmd === 'italic'
-                               || cmd === 'underline'
-                               || cmd === 'strikethrough')
-                       {
-                               this.bufferSet();
-                       }
-
-
-                       if (cmd === 'superscript' || cmd === 'subscript')
-                       {
-                               var parent = this.getParent();
-                               if (parent.tagName === 'SUP' || parent.tagName === 'SUB')
+                       return {
+                               get: function(code)
                                {
-                                       this.inlineRemoveFormatReplace(parent);
-                               }
-                       }
-
-                       if (cmd === 'inserthtml')
-                       {
-                               this.insertHtml(param, sync);
-                               this.callback('execCommand', cmd, param);
-                               return;
-                       }
+                                       if (!this.opts.tabifier) return code;
 
-                       // Stop formatting pre
-                       if (this.currentOrParentIs('PRE') && !this.opts.formattingPre) return false;
+                                       // clean setup
+                                       var ownLine = ['area', 'body', 'head', 'hr', 'i?frame', 'link', 'meta', 'noscript', 'style', 'script', 'table', 'tbody', 'thead', 'tfoot'];
+                                       var contOwnLine = ['li', 'dt', 'dt', 'h[1-6]', 'option', 'script'];
+                                       var newLevel = ['blockquote', 'div', 'dl', 'fieldset', 'form', 'frameset', 'map', 'ol', 'p', 'pre', 'select', 'td', 'th', 'tr', 'ul'];
 
-                       // Lists
-                       if (cmd === 'insertunorderedlist' || cmd === 'insertorderedlist') return this.execLists(cmd, param);
+                                       this.tabifier.lineBefore = new RegExp('^<(/?' + ownLine.join('|/?' ) + '|' + contOwnLine.join('|') + ')[ >]');
+                                       this.tabifier.lineAfter = new RegExp('^<(br|/?' + ownLine.join('|/?' ) + '|/' + contOwnLine.join('|/') + ')[ >]');
+                                       this.tabifier.newLevel = new RegExp('^</?(' + newLevel.join('|' ) + ')[ >]');
 
-                       // Unlink
-                       if (cmd === 'unlink') return this.execUnlink(cmd, param);
+                                       var i = 0,
+                                       codeLength = code.length,
+                                       point = 0,
+                                       start = null,
+                                       end = null,
+                                       tag = '',
+                                       out = '',
+                                       cont = '';
 
-                       // Usual exec
-                       this.exec(cmd, param, sync);
+                                       this.tabifier.cleanlevel = 0;
 
-                       // Line
-                       if (cmd === 'inserthorizontalrule') this.$editor.find('hr').removeAttr('id');
+                                       for (; i < codeLength; i++)
+                                       {
+                                               point = i;
 
-               },
-               execUnlink: function(cmd, param)
-               {
-                       this.bufferSet();
+                                               // if no more tags, copy and exit
+                                               if (-1 == code.substr(i).indexOf( '<' ))
+                                               {
+                                                       out += code.substr(i);
 
-                       var link = this.currentOrParentIs('A');
-                       if (link)
-                       {
-                               $(link).replaceWith($(link).text());
+                                                       return this.tabifier.finish(out);
+                                               }
 
-                               this.sync();
-                               this.callback('execCommand', cmd, param);
-                               return;
-                       }
-               },
-               execLists: function(cmd, param)
-               {
-                       this.bufferSet();
+                                               // copy verbatim until a tag
+                                               while (point < codeLength && code.charAt(point) != '<')
+                                               {
+                                                       point++;
+                                               }
 
-                       var parent = this.getParent();
-                       var $list = $(parent).closest('ol, ul');
+                                               if (i != point)
+                                               {
+                                                       cont = code.substr(i, point - i);
+                                                       if (!cont.match(/^\s{2,}$/g))
+                                                       {
+                                                               if ('\n' == out.charAt(out.length - 1)) out += this.tabifier.getTabs();
+                                                               else if ('\n' == cont.charAt(0))
+                                                               {
+                                                                       out += '\n' + this.tabifier.getTabs();
+                                                                       cont = cont.replace(/^\s+/, '');
+                                                               }
 
-                       if (!this.isParentRedactor($list) && $list.size() != 0)
-                       {
-                               $list = false;
-                       }
+                                                               out += cont;
+                                                       }
 
-                       var remove = false;
+                                                       if (cont.match(/\n/)) out += '\n' + this.tabifier.getTabs();
+                                               }
 
-                       if ($list && $list.length)
-                       {
-                               remove = true;
-                               var listTag = $list[0].tagName;
-                               if ((cmd === 'insertunorderedlist' && listTag === 'OL')
-                               || (cmd === 'insertorderedlist' && listTag === 'UL'))
-                               {
-                                       remove = false;
-                               }
-                       }
+                                               start = point;
 
-                       this.selectionSave();
+                                               // find the end of the tag
+                                               while (point < codeLength && '>' != code.charAt(point))
+                                               {
+                                                       point++;
+                                               }
 
-                       // remove lists
-                       if (remove)
-                       {
+                                               tag = code.substr(start, point - start);
+                                               i = point;
 
-                               var nodes = this.getNodes();
-                               var elems = this.getBlocks(nodes);
+                                               var t;
 
-                               if (typeof nodes[0] != 'undefined' && nodes.length > 1 && nodes[0].nodeType == 3)
-                               {
-                                       // fix the adding the first li to the array
-                                       elems.unshift(this.getBlock());
-                               }
+                                               if ('!--' == tag.substr(1, 3))
+                                               {
+                                                       if (!tag.match(/--$/))
+                                                       {
+                                                               while ('-->' != code.substr(point, 3))
+                                                               {
+                                                                       point++;
+                                                               }
+                                                               point += 2;
+                                                               tag = code.substr(start, point - start);
+                                                               i = point;
+                                                       }
 
-                               var data = '', replaced = '';
-                               $.each(elems, $.proxy(function(i,s)
-                               {
-                                       if (s.tagName == 'LI')
-                                       {
-                                               var $s = $(s);
-                                               var cloned = $s.clone();
-                                               cloned.find('ul', 'ol').remove();
+                                                       if ('\n' != out.charAt(out.length - 1)) out += '\n';
 
-                                               if (this.opts.linebreaks === false)
+                                                       out += this.tabifier.getTabs();
+                                                       out += tag + '>\n';
+                                               }
+                                               else if ('!' == tag[1])
                                                {
-                                                       data += this.outerHtml($('<p>').append(cloned.contents()));
+                                                       out = this.tabifier.placeTag(tag + '>', out);
                                                }
-                                               else
+                                               else if ('?' == tag[1])
                                                {
-                                                       var clonedHtml = cloned.html().replace(/<br\s?\/?>$/i, '');
-                                                       data += clonedHtml + '<br>';
+                                                       out += tag + '>\n';
                                                }
+                                               else if (t = tag.match(/^<(script|style|pre)/i))
+                                               {
+                                                       t[1] = t[1].toLowerCase();
+                                                       tag = this.tabifier.cleanTag(tag);
+                                                       out = this.tabifier.placeTag(tag, out);
+                                                       end = String(code.substr(i + 1)).toLowerCase().indexOf('</' + t[1]);
 
-                                               if (i == 0)
+                                                       if (end)
+                                                       {
+                                                               cont = code.substr(i + 1, end);
+                                                               i += end;
+                                                               out += cont;
+                                                       }
+                                               }
+                                               else
                                                {
-                                                       $s.addClass('redactor-replaced').empty();
-                                                       replaced = this.outerHtml($s);
+                                                       tag = this.tabifier.cleanTag(tag);
+                                                       out = this.tabifier.placeTag(tag, out);
                                                }
-                                               else $s.remove();
                                        }
 
-                               }, this));
+                                       return this.tabifier.finish(out);
+                               },
+                               getTabs: function()
+                               {
+                                       var s = '';
+                                       for ( var j = 0; j < this.tabifier.cleanlevel; j++ )
+                                       {
+                                               s += '\t';
+                                       }
 
+                                       return s;
+                               },
+                               finish: function(code)
+                               {
+                                       code = code.replace(/\n\s*\n/g, '\n');
+                                       code = code.replace(/^[\s\n]*/, '');
+                                       code = code.replace(/[\s\n]*$/, '');
+                                       code = code.replace(/<script(.*?)>\n<\/script>/gi, '<script$1></script>');
 
-                               html = this.$editor.html().replace(replaced, '</' + listTag + '>' + data + '<' + listTag + '>');
+                                       this.tabifier.cleanlevel = 0;
 
-                               this.$editor.html(html);
-                               this.$editor.find(listTag + ':empty').remove();
+                                       return code;
+                               },
+                               cleanTag: function (tag)
+                               {
+                                       var tagout = '';
+                                       tag = tag.replace(/\n/g, ' ');
+                                       tag = tag.replace(/\s{2,}/g, ' ');
+                                       tag = tag.replace(/^\s+|\s+$/g, ' ');
 
-                       }
+                                       var suffix = '';
+                                       if (tag.match(/\/$/))
+                                       {
+                                               suffix = '/';
+                                               tag = tag.replace(/\/+$/, '');
+                                       }
 
-                       // insert lists
-                       else
-                       {
-                               var firstParent = $(this.getParent()).closest('td');
+                                       var m;
+                                       while (m = /\s*([^= ]+)(?:=((['"']).*?\3|[^ ]+))?/.exec(tag))
+                                       {
+                                               if (m[2]) tagout += m[1].toLowerCase() + '=' + m[2];
+                                               else if (m[1]) tagout += m[1].toLowerCase();
+
+                                               tagout += ' ';
+                                               tag = tag.substr(m[0].length);
+                                       }
 
-                               if (this.browser('msie') && !this.isIe11() && this.opts.linebreaks)
+                                       return tagout.replace(/\s*$/, '') + suffix + '>';
+                               },
+                               placeTag: function (tag, out)
                                {
-                                       var wrapper = this.selectionWrap('div');
-                                       var wrapperHtml = $(wrapper).html();
-                                       var tmpList = $('<ul>');
-                                       if (cmd == 'insertorderedlist')
+                                       var nl = tag.match(this.tabifier.newLevel);
+                                       if (tag.match(this.tabifier.lineBefore) || nl)
                                        {
-                                               tmpList = $('<ol>');
+                                               out = out.replace(/\s*$/, '');
+                                               out += '\n';
                                        }
 
-                                       var tmpLi = $('<li>');
+                                       if (nl && '/' == tag.charAt(1)) this.tabifier.cleanlevel--;
+                                       if ('\n' == out.charAt(out.length - 1)) out += this.tabifier.getTabs();
+                                       if (nl && '/' != tag.charAt(1)) this.tabifier.cleanlevel++;
 
-                                       if ($.trim(wrapperHtml) == '')
-                                       {
-                                               tmpLi.append(wrapperHtml + '<span id="selection-marker-1">' + this.opts.invisibleSpace + '</span>');
-                                               tmpList.append(tmpLi);
-                                               this.$editor.find('#selection-marker-1').replaceWith(tmpList);
-                                       }
-                                       else
+                                       out += tag;
+
+                                       if (tag.match(this.tabifier.lineAfter) || tag.match(this.tabifier.newLevel))
                                        {
-                                               tmpLi.append(wrapperHtml);
-                                               tmpList.append(tmpLi);
-                                               $(wrapper).replaceWith(tmpList);
+                                               out = out.replace(/ *$/, '');
+                                               out += '\n';
                                        }
+
+                                       return out;
                                }
-                               else
+                       };
+               },
+               focus: function()
+               {
+                       return {
+                               setStart: function()
                                {
-                                       this.document.execCommand(cmd);
-                               }
+                                       this.$editor.focus();
 
-                               var parent = this.getParent();
-                               var $list = $(parent).closest('ol, ul');
+                                       var first = this.$editor.children().first();
 
-                               if (this.opts.linebreaks === false)
-                               {
-                                       var listText = $.trim($list.text());
-                                       if (listText == '')
+                                       if (first.size() === 0) return;
+                                       if (first[0].length === 0 || first[0].tagName == 'BR' || first[0].nodeType == 3)
                                        {
-                                               $list.children('li').find('br').remove();
-                                               $list.children('li').append('<span id="selection-marker-1">' + this.opts.invisibleSpace + '</span>');
+                                               return;
                                        }
-                               }
 
-                               if (firstParent.size() != 0)
-                               {
-                                       $list.wrapAll('<td>');
-                               }
+                                       if (first[0].tagName == 'UL' || first[0].tagName == 'OL')
+                                       {
+                                               first = first.find('li').first();
+                                       }
 
-                               if ($list.length)
-                               {
-                                       // remove block-element list wrapper
-                                       var $listParent = $list.parent();
-                                       if (this.isParentRedactor($listParent) && $listParent[0].tagName != 'LI' && this.nodeTestBlocks($listParent[0]))
+                                       if (this.opts.linebreaks && !this.utils.isBlockTag(first[0].tagName))
                                        {
-                                               $listParent.replaceWith($listParent.contents());
+                                               this.selection.get();
+                                               this.range.setStart(this.$editor[0], 0);
+                                               this.range.setEnd(this.$editor[0], 0);
+                                               this.selection.addRange();
+
+                                               return;
                                        }
-                               }
 
-                               if (this.browser('mozilla'))
+                                       // if node is tag
+                                       this.caret.setStart(first);
+                               },
+                               setEnd: function()
                                {
-                                       this.$editor.focus();
-                               }
-                       }
+                                       if (this.utils.browser('mozilla') || this.utils.browser('msie'))
+                                       {
+                                               var last = this.$editor.children().last();
+                                               this.caret.setEnd(last);
+                                       }
+                                       else
+                                       {
+                                               this.selection.get();
 
-                       this.selectionRestore();
-                       this.$editor.find('#selection-marker-1').removeAttr('id');
-                       this.sync();
-                       this.callback('execCommand', cmd, param);
-                       return;
-               },
+                                               try {
+                                                       this.range.selectNodeContents(this.$editor[0]);
+                                                       this.range.collapse(false);
 
-               // INDENTING
-               indentingIndent: function()
-               {
-                       this.indentingStart('indent');
-               },
-               indentingOutdent: function()
-               {
-                       this.indentingStart('outdent');
-               },
-               indentingStart: function(cmd)
-               {
-                       this.bufferSet();
+                                                       this.selection.addRange();
+                                               }
+                                               catch (e) {}
+                                       }
 
-                       if (cmd === 'indent')
-                       {
-                               var block = this.getBlock();
+                               },
+                               isFocused: function()
+                               {
+                                       var focusNode = document.getSelection().focusNode;
+                                       if (focusNode === null) return false;
 
-                               this.selectionSave();
+                                       if (this.opts.linebreaks && $(focusNode.parentNode).hasClass('redactor-linebreaks')) return true;
+                                       else if (!this.utils.isRedactorParent(focusNode.parentNode)) return false;
 
-                               if (block && block.tagName == 'LI')
+                                       return this.$editor.is(':focus');
+                               }
+                       };
+               },
+               placeholder: function()
+               {
+                       return {
+                               enable: function()
                                {
-                                       // li
-                                       var parent = this.getParent();
+                                       if (!this.placeholder.is()) return;
 
-                                       var $list = $(parent).closest('ol, ul');
-                                       var listTag = $list[0].tagName;
+                                       this.$editor.attr('placeholder', this.$element.attr('placeholder'));
 
-                                       var elems = this.getBlocks();
+                                       this.placeholder.toggle();
+                                       this.$editor.on('keyup.redactor-placeholder', $.proxy(this.placeholder.toggle, this));
 
-                                       $.each(elems, function(i,s)
+                               },
+                               toggle: function()
+                               {
+                                       var func = 'removeClass';
+                                       if (this.utils.isEmpty(this.$editor.html(), false)) func = 'addClass';
+                                       this.$editor[func]('redactor-placeholder');
+                               },
+                               remove: function()
+                               {
+                                       this.$editor.removeClass('redactor-placeholder');
+                               },
+                               is: function()
+                               {
+                                       if (this.opts.placeholder)
                                        {
-                                               if (s.tagName == 'LI')
-                                               {
-                                                       var $prev = $(s).prev();
-                                                       if ($prev.size() != 0 && $prev[0].tagName == 'LI')
-                                                       {
-                                                               var $childList = $prev.children('ul, ol');
-                                                               if ($childList.size() == 0)
-                                                               {
-                                                                       $prev.append($('<' + listTag + '>').append(s));
-                                                               }
-                                                               else $childList.append(s);
-                                                       }
-                                               }
-                                       });
+                                               return this.$element.attr('placeholder', this.opts.placeholder);
+                                       }
+                                       else
+                                       {
+                                               return !(typeof this.$element.attr('placeholder') == 'undefined' || this.$element.attr('placeholder') === '');
+                                       }
                                }
-                               // linebreaks
-                               else if (block === false && this.opts.linebreaks === true)
+                       };
+               },
+               autosave: function()
+               {
+                       return {
+                               enable: function()
                                {
-                                       this.exec('formatBlock', 'blockquote');
-                                       var newblock = this.getBlock();
-                                       var block = $('<div data-tagblock="">').html($(newblock).html());
-                                       $(newblock).replaceWith(block);
+                                       if (!this.opts.autosave) return;
 
-                                       var left = this.normalize($(block).css('margin-left')) + this.opts.indentValue;
-                                       $(block).css('margin-left', left + 'px');
-                               }
-                               else
-                               {
-                                       // all block tags
-                                       var elements = this.getBlocks();
-                                       $.each(elements, $.proxy(function(i, elem)
+                                       this.autosave.html = false;
+                                       this.autosave.name = (this.opts.autosaveName) ? this.opts.autosaveName : this.$textarea.attr('name');
+
+                                       if (!this.opts.autosaveOnChange)
                                        {
-                                               var $el = false;
+                                               this.autosaveInterval = setInterval($.proxy(this.autosave.load, this), this.opts.autosaveInterval * 1000);
+                                       }
+                               },
+                               onChange: function()
+                               {
+                                       if (!this.opts.autosaveOnChange) return;
 
-                                               if (elem.tagName === 'TD') return;
+                                       this.autosave.load();
+                               },
+                               load: function()
+                               {
+                                       var html = this.code.get();
+                                       if (this.autosave.html === html) return;
+                                       if (this.utils.isEmpty(html)) return;
 
-                                               if ($.inArray(elem.tagName, this.opts.alignmentTags) !== -1)
-                                               {
-                                                       $el = $(elem);
-                                               }
-                                               else
+                                       $.ajax({
+                                               url: this.opts.autosave,
+                                               type: 'post',
+                                               data: 'name=' + this.autosave.name + '&' + this.autosave.name + '=' + escape(encodeURIComponent(html)),
+                                               success: $.proxy(function(data)
                                                {
-                                                       $el = $(elem).closest(this.opts.alignmentTags.toString().toLowerCase(), this.$editor[0]);
-                                               }
-
-                                               var left = this.normalize($el.css('margin-left')) + this.opts.indentValue;
-                                               $el.css('margin-left', left + 'px');
+                                                       this.autosave.success(data, html);
 
-                                       }, this));
-                               }
-
-                               this.selectionRestore();
+                                               }, this)
+                                       });
+                               },
+                               success: function(data, html)
+                               {
+                                       var json;
+                                       try
+                                       {
+                                               json = $.parseJSON(data);
+                                       }
+                                       catch(e)
+                                       {
+                                               //data has already been parsed
+                                               json = data;
+                                       }
 
-                       }
-                       // outdent
-                       else
-                       {
-                               this.selectionSave();
+                                       var callbackName = (typeof json.error == 'undefined') ? 'autosave' :  'autosaveError';
 
-                               var block = this.getBlock();
-                               if (block && block.tagName == 'LI')
+                                       this.core.setCallback(callbackName, this.autosave.name, json);
+                                       this.autosave.html = html;
+                               },
+                               disable: function()
                                {
-                                       // li
-                                       var elems = this.getBlocks();
-                                       var index = 0;
-
-                                       this.insideOutdent(block, index, elems);
+                                       clearInterval(this.autosaveInterval);
                                }
-                               else
+                       };
+               },
+               buffer: function()
+               {
+                       return {
+                               set: function(type)
                                {
-                                       // all block tags
-                                       var elements = this.getBlocks();
-                                       $.each(elements, $.proxy(function(i, elem)
+                                       if (typeof type == 'undefined' || type == 'undo')
                                        {
-                                               var $el = false;
-
-                                               if ($.inArray(elem.tagName, this.opts.alignmentTags) !== -1)
-                                               {
-                                                       $el = $(elem);
-                                               }
-                                               else
-                                               {
-                                                       $el = $(elem).closest(this.opts.alignmentTags.toString().toLowerCase(), this.$editor[0]);
-                                               }
+                                               this.buffer.setUndo();
+                                       }
+                                       else
+                                       {
+                                               this.buffer.setRedo();
+                                       }
+                               },
+                               setUndo: function()
+                               {
+                                       this.selection.save();
+                                       this.opts.buffer.push(this.$editor.html());
+                                       this.selection.restore();
+                               },
+                               setRedo: function()
+                               {
+                                       this.selection.save();
+                                       this.opts.rebuffer.push(this.$editor.html());
+                                       this.selection.restore();
+                               },
+                               getUndo: function()
+                               {
+                                       this.$editor.html(this.opts.buffer.pop());
+                               },
+                               getRedo: function()
+                               {
+                                       this.$editor.html(this.opts.rebuffer.pop());
+                               },
+                               add: function()
+                               {
+                                       this.opts.buffer.push(this.$editor.html());
+                               },
+                               undo: function()
+                               {
+                                       if (this.opts.buffer.length === 0) return;
 
-                                               var left = this.normalize($el.css('margin-left')) - this.opts.indentValue;
-                                               if (left <= 0)
-                                               {
-                                                       // linebreaks
-                                                       if (this.opts.linebreaks === true && typeof($el.data('tagblock')) !== 'undefined')
-                                                       {
-                                                               $el.replaceWith($el.html() + '<br>');
-                                                       }
-                                                       // all block tags
-                                                       else
-                                                       {
-                                                               $el.css('margin-left', '');
-                                                               this.removeEmptyAttr($el, 'style');
-                                                       }
-                                               }
-                                               else
-                                               {
-                                                       $el.css('margin-left', left + 'px');
-                                               }
+                                       this.buffer.set('redo');
+                                       this.buffer.getUndo();
 
-                                       }, this));
-                               }
+                                       this.selection.restore();
 
+                                       setTimeout($.proxy(this.observe.load, this), 50);
+                               },
+                               redo: function()
+                               {
+                                       if (this.opts.rebuffer.length === 0) return;
 
-                               this.selectionRestore();
-                       }
+                                       this.buffer.set('undo');
+                                       this.buffer.getRedo();
 
-                       this.sync();
+                                       this.selection.restore();
 
+                                       setTimeout($.proxy(this.observe.load, this), 50);
+                               }
+                       };
                },
-               insideOutdent: function (li, index, elems)
+               indent: function()
                {
-                       if (li && li.tagName == 'LI')
-                       {
-                               var $parent = $(li).parent().parent();
-                               if ($parent.size() != 0 && $parent[0].tagName == 'LI')
-                               {
-                                       $parent.after(li);
-                               }
-                               else
+                       return {
+                               increase: function()
                                {
-                                       if (typeof elems[index] != 'undefined')
-                                       {
-                                               li = elems[index];
-                                               index++;
+                                       // focus
+                                       if (!this.utils.browser('msie')) this.$editor.focus();
 
-                                               this.insideOutdent(li, index, elems);
+                                       this.buffer.set();
+                                       this.selection.save();
+
+                                       var block = this.selection.getBlock();
+
+                                       if (block && block.tagName == 'LI')
+                                       {
+                                               this.indent.increaseLists();
+                                       }
+                                       else if (block === false && this.opts.linebreaks)
+                                       {
+                                               this.indent.increaseText();
                                        }
                                        else
                                        {
-                                               this.execCommand('insertunorderedlist');
+                                               this.indent.increaseBlocks();
                                        }
-                               }
-                       }
-               },
 
-               // ALIGNMENT
-               alignmentLeft: function()
-               {
-                       this.alignmentSet('', 'JustifyLeft');
-               },
-               alignmentRight: function()
-               {
-                       this.alignmentSet('right', 'JustifyRight');
-               },
-               alignmentCenter: function()
-               {
-                       this.alignmentSet('center', 'JustifyCenter');
-               },
-               alignmentJustify: function()
-               {
-                       this.alignmentSet('justify', 'JustifyFull');
-               },
-               alignmentSet: function(type, cmd)
-               {
-                       this.bufferSet();
-
-                       if (this.oldIE())
-                       {
-                               this.document.execCommand(cmd, false, false);
-                               return true;
-                       }
-
-                       this.selectionSave();
+                                       this.selection.restore();
+                                       this.code.sync();
+                               },
+                               increaseLists: function()
+                               {
+                                       document.execCommand('indent');
 
-                       var block = this.getBlock();
-                       if (!block && this.opts.linebreaks)
-                       {
-                               // one element
-                               this.exec('formatblock', 'div');
+                                       this.indent.fixEmptyIndent();
+                                       this.clean.normalizeLists();
+                                       this.clean.clearUnverified();
+                               },
+                               increaseBlocks: function()
+                               {
+                                       $.each(this.selection.getBlocks(), $.proxy(function(i, elem)
+                                       {
+                                               if (elem.tagName === 'TD' || elem.tagName === 'TH') return;
 
-                               var newblock = this.getBlock();
-                               var block = $('<div data-tagblock="">').html($(newblock).html());
-                               $(newblock).replaceWith(block);
+                                               var $el = this.utils.getAlignmentElement(elem);
 
-                               $(block).css('text-align', type);
-                               this.removeEmptyAttr(block, 'style');
+                                               var left = this.utils.normalize($el.css('margin-left')) + this.opts.indentValue;
+                                               $el.css('margin-left', left + 'px');
 
-                               if (type == '' && typeof($(block).data('tagblock')) !== 'undefined')
+                                       }, this));
+                               },
+                               increaseText: function()
                                {
-                                       $(block).replaceWith($(block).html());
-                               }
-                       }
-                       else
-                       {
-                               var elements = this.getBlocks();
-                               $.each(elements, $.proxy(function(i, elem)
+                                       var wrapper = this.selection.wrap('div');
+                                       $(wrapper).attr('data-tagblock', 'redactor');
+                                       $(wrapper).css('margin-left', this.opts.indentValue + 'px');
+                               },
+                               decrease: function()
                                {
-                                       var $el = false;
+                                       this.buffer.set();
+                                       this.selection.save();
 
-                                       if ($.inArray(elem.tagName, this.opts.alignmentTags) !== -1)
+                                       var block = this.selection.getBlock();
+                                       if (block && block.tagName == 'LI')
                                        {
-                                               $el = $(elem);
+                                               this.indent.decreaseLists();
                                        }
                                        else
                                        {
-                                               $el = $(elem).closest(this.opts.alignmentTags.toString().toLowerCase(), this.$editor[0]);
-                                       }
-
-                                       if ($el)
-                                       {
-                                               $el.css('text-align', type);
-                                               this.removeEmptyAttr($el, 'style');
+                                               this.indent.decreaseBlocks();
                                        }
 
-                               }, this));
-                       }
-
-                       this.selectionRestore();
-                       this.sync();
-               },
+                                       this.selection.restore();
+                                       this.code.sync();
+                               },
+                               decreaseLists: function ()
+                               {
+                                       document.execCommand('outdent');
 
-               // CLEAN
-               cleanEmpty: function(html)
-               {
-                       var ph = this.placeholderStart(html);
-                       if (ph !== false) return ph;
+                                       var current = this.selection.getCurrent();
 
-                       if (this.opts.linebreaks === false)
-                       {
-                               if (html === '') html = this.opts.emptyHtml;
-                               else if (html.search(/^<hr\s?\/?>$/gi) !== -1) html = '<hr>' + this.opts.emptyHtml;
-                       }
+                                       var $item = $(current).closest('li');
+                                       var $parent = $item.parent();
+                                       if ($item.size() !== 0 && $parent.size() !== 0 && $parent[0].tagName == 'LI')
+                                       {
+                                               $parent.after($item);
+                                       }
 
-                       return html;
-               },
-               cleanConverters: function(html)
-               {
-                       // convert div to p
-                       if (this.opts.convertDivs && !this.opts.gallery)
-                       {
-                               html = html.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '<p$1>$2</p>');
-                       }
+                                       this.indent.fixEmptyIndent();
 
-                       if (this.opts.paragraphy) html = this.cleanParagraphy(html);
+                                       if (!this.opts.linebreaks && $item.size() === 0)
+                                       {
+                                               document.execCommand('formatblock', false, 'p');
+                                               this.$editor.find('ul, ol, blockquote, p').each($.proxy(this.utils.removeEmpty, this));
+                                       }
 
-                       return html;
-               },
-               cleanConvertProtected: function(html)
-               {
-                       if (this.opts.templateVars)
-                       {
-                               html = html.replace(/\{\{(.*?)\}\}/gi, '<!-- template double $1 -->');
-                               html = html.replace(/\{(.*?)\}/gi, '<!-- template $1 -->');
-                       }
+                                       this.clean.clearUnverified();
+                               },
+                               decreaseBlocks: function()
+                               {
+                                       $.each(this.selection.getBlocks(), $.proxy(function(i, elem)
+                                       {
+                                               var $el = this.utils.getAlignmentElement(elem);
+                                               var left = this.utils.normalize($el.css('margin-left')) - this.opts.indentValue;
 
-                       html = html.replace(/<script(.*?)>([\w\W]*?)<\/script>/gi, '<title type="text/javascript" style="display: none;" class="redactor-script-tag"$1>$2</title>');
-                       html = html.replace(/<style(.*?)>([\w\W]*?)<\/style>/gi, '<section$1 style="display: none;" rel="redactor-style-tag">$2</section>');
-                       html = html.replace(/<form(.*?)>([\w\W]*?)<\/form>/gi, '<section$1 rel="redactor-form-tag">$2</section>');
+                                               if (left <= 0)
+                                               {
+                                                       if (this.opts.linebreaks && typeof($el.data('tagblock')) !== 'undefined')
+                                                       {
+                                                               $el.replaceWith($el.html() + '<br />');
+                                                       }
+                                                       else
+                                                       {
+                                                               $el.css('margin-left', '');
+                                                               this.utils.removeEmptyAttr($el, 'style');
+                                                       }
+                                               }
+                                               else
+                                               {
+                                                       $el.css('margin-left', left + 'px');
+                                               }
 
-                       // php tags convertation
-                       if (this.opts.phpTags) html = html.replace(/<\?php([\w\W]*?)\?>/gi, '<section style="display: none;" rel="redactor-php-tag">$1</section>');
-                       else html = html.replace(/<\?php([\w\W]*?)\?>/gi, '');
+                                       }, this));
+                               },
+                               fixEmptyIndent: function()
+                               {
+                                       var block = this.selection.getBlock();
 
-                       return html;
+                                       if (this.range.collapsed && block && block.tagName == 'LI' && this.utils.isEmpty($(block).text()))
+                                       {
+                                               var $block = $(block);
+                                               $block.find('span').not('.redactor-selection-marker').contents().unwrap();
+                                               $block.append('<br>');
+                                       }
+                               }
+                       };
                },
-               cleanReConvertProtected: function(html)
+               alignment: function()
                {
-                       if (this.opts.templateVars)
-                       {
-                               html = html.replace(/<!-- template double (.*?) -->/gi, '{{$1}}');
-                               html = html.replace(/<!-- template (.*?) -->/gi, '{$1}');
-                       }
-
-                       html = html.replace(/<title type="text\/javascript" style="display: none;" class="redactor-script-tag"(.*?)>([\w\W]*?)<\/title>/gi, '<script$1 type="text/javascript">$2</script>');
-                       html = html.replace(/<section(.*?) style="display: none;" rel="redactor-style-tag">([\w\W]*?)<\/section>/gi, '<style$1>$2</style>');
-                       html = html.replace(/<section(.*?)rel="redactor-form-tag"(.*?)>([\w\W]*?)<\/section>/gi, '<form$1$2>$3</form>');
+                       return {
+                               left: function()
+                               {
+                                       this.alignment.set('');
+                               },
+                               right: function()
+                               {
+                                       this.alignment.set('right');
+                               },
+                               center: function()
+                               {
+                                       this.alignment.set('center');
+                               },
+                               justify: function()
+                               {
+                                       this.alignment.set('justify');
+                               },
+                               set: function(type)
+                               {
+                                       if (!this.utils.browser('msie')) this.$editor.focus();
 
-                       // php tags convertation
-                       if (this.opts.phpTags) html = html.replace(/<section style="display: none;" rel="redactor-php-tag">([\w\W]*?)<\/section>/gi, '<?php\r\n$1\r\n?>');
+                                       this.buffer.set();
+                                       this.selection.save();
 
-                       return html;
-               },
-               cleanRemoveSpaces: function(html, buffer)
-               {
-                       if (buffer !== false)
-                       {
-                               var buffer = []
-                               var matches = html.match(/<(pre|style|script|title)(.*?)>([\w\W]*?)<\/(pre|style|script|title)>/gi);
-                               if (matches === null) matches = [];
+                                       this.alignment.blocks = this.selection.getBlocks();
+                                       if (this.opts.linebreaks && this.alignment.blocks[0] === false)
+                                       {
+                                               this.alignment.setText(type);
+                                       }
+                                       else
+                                       {
+                                               this.alignment.setBlocks(type);
+                                       }
 
-                               if (this.opts.phpTags)
+                                       this.selection.restore();
+                                       this.code.sync();
+                               },
+                               setText: function(type)
                                {
-                                       var phpMatches = html.match(/<\?php([\w\W]*?)\?>/gi);
-                                       if (phpMatches) matches = $.merge(matches, phpMatches);
-                               }
-
-                               if (matches)
+                                       var wrapper = this.selection.wrap('div');
+                                       $(wrapper).attr('data-tagblock', 'redactor');
+                                       $(wrapper).css('text-align', type);
+                               },
+                               setBlocks: function(type)
                                {
-                                       $.each(matches, function(i, s)
+                                       $.each(this.alignment.blocks, $.proxy(function(i, el)
                                        {
-                                               html = html.replace(s, 'buffer_' + i);
-                                               buffer.push(s);
-                                       });
-                               }
-                       }
+                                               var $el = this.utils.getAlignmentElement(el);
 
-                       html = html.replace(/\n/g, ' ');
-                       html = html.replace(/[\t]*/g, '');
-                       html = html.replace(/\n\s*\n/g, "\n");
-                       html = html.replace(/^[\s\n]*/g, ' ');
-                       html = html.replace(/[\s\n]*$/g, ' ');
-                       html = html.replace( />\s{2,}</g, '> <'); // between inline tags can be only one space
+                                               if (!$el) return;
 
-                       html = this.cleanReplacer(html, buffer);
+                                               if (type === '' && typeof($el.data('tagblock')) !== 'undefined')
+                                               {
+                                                       $el.replaceWith($el.html());
+                                               }
+                                               else
+                                               {
+                                                       $el.css('text-align', type);
+                                                       this.utils.removeEmptyAttr($el, 'style');
+                                               }
 
-                       html = html.replace(/\n\n/g, "\n");
 
-                       return html;
+                                       }, this));
+                               }
+                       };
                },
-               cleanReplacer: function(html, buffer)
+               paste: function()
                {
-                       if (buffer === false) return html;
-
-                       $.each(buffer, function(i,s)
-                       {
-                               html = html.replace('buffer_' + i, s);
-                       });
+                       return {
+                               init: function(e)
+                               {
+                                       if (!this.opts.cleanOnPaste) return;
 
-                       return html;
-               },
-               cleanRemoveEmptyTags: function(html)
-               {
-                       // remove zero width-space
-                       html = html.replace(/[\u200B-\u200D\uFEFF]/g, '');
+                                       // clipboard upload
+                                       if (this.opts.clipboardImageUpload && this.paste.detectEventClipboardUpload(e)) return;
 
-                       var etagsInline = ["<b>\\s*</b>", "<b>&nbsp;</b>", "<em>\\s*</em>"]
-                       var etags = ["<pre></pre>", "<blockquote>\\s*</blockquote>", "<dd></dd>", "<dt></dt>", "<ul></ul>", "<ol></ol>", "<li></li>", "<table></table>", "<tr></tr>", "<span>\\s*<span>", "<span>&nbsp;<span>", "<p>\\s*</p>", "<p></p>", "<p>&nbsp;</p>",  "<p>\\s*<br>\\s*</p>", "<div>\\s*</div>", "<div>\\s*<br>\\s*</div>"];
+                                       this.rtePaste = true;
 
-                       if (this.opts.removeEmptyTags)
-                       {
-                               etags = etags.concat(etagsInline);
-                       }
-                       else etags = etagsInline;
+                                       this.buffer.set();
+                                       this.selection.save();
+                                       this.utils.saveScroll();
 
-                       var len = etags.length;
-                       for (var i = 0; i < len; ++i)
-                       {
-                               html = html.replace(new RegExp(etags[i], 'gi'), "");
-                       }
+                                       this.paste.createPasteBox();
 
-                       return html;
-               },
-               cleanParagraphy: function(html)
-               {
-                       html = $.trim(html);
+                                       $(window).on('scroll.redactor-freeze', $.proxy(function()
+                                       {
+                                               $(window).scrollTop(this.saveBodyScroll);
 
-                       if (this.opts.linebreaks === true) return html;
-                       if (html === '' || html === '<p></p>') return this.opts.emptyHtml;
+                                       }, this));
 
-                       html = html + "\n";
+                                       setTimeout($.proxy(function()
+                                       {
+                                               var html = this.$pasteBox.html();
+                                               this.$pasteBox.remove();
 
-                       if (this.opts.removeEmptyTags === false)
-                       {
-                               return html;
-                       }
+                                               this.selection.restore();
+                                               this.utils.restoreScroll();
 
-                       var safes = [];
-                       var matches = html.match(/<(table|div|pre|object)(.*?)>([\w\W]*?)<\/(table|div|pre|object)>/gi);
-                       if (!matches) matches = [];
+                                               this.paste.insert(html);
 
-                       var commentsMatches = html.match(/<!--([\w\W]*?)-->/gi);
-                       if (commentsMatches) matches = $.merge(matches, commentsMatches);
+                                               $(window).off('scroll.redactor-freeze');
 
-                       if (this.opts.phpTags)
-                       {
-                               var phpMatches = html.match(/<section(.*?)rel="redactor-php-tag">([\w\W]*?)<\/section>/gi);
-                               if (phpMatches) matches = $.merge(matches, phpMatches);
-                       }
+                                       }, this), 1);
 
-                       if (matches)
-                       {
-                               $.each(matches, function(i,s)
+                               },
+                               createPasteBox: function()
                                {
-                                       safes[i] = s;
-                                       html = html.replace(s, '{replace' + i + '}\n');
-                               });
-                       }
-
-                       html = html.replace(/<br \/>\s*<br \/>/gi, "\n\n");
-                       html = html.replace(/<br><br>/gi, "\n\n");
-
-                       function R(str, mod, r)
-                       {
-                               return html.replace(new RegExp(str, mod), r);
-                       }
+                                       this.$pasteBox = $('<div>').html(' ').attr('contenteditable', 'true').css({ position: 'fixed', width: 0, top: 0, left: '-9999px' });
 
-                       var blocks = '(comment|html|body|head|title|meta|style|script|link|iframe|table|thead|tfoot|caption|col|colgroup|tbody|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|select|option|form|map|area|blockquote|address|math|style|p|h[1-6]|hr|fieldset|legend|section|article|aside|hgroup|header|footer|nav|figure|figcaption|details|menu|summary)';
+                                       this.$pasteBox.appendTo(document.body);
+                                       this.$pasteBox.focus();
+                               },
+                               insert: function(html)
+                               {
+                                       html = this.core.setCallback('pasteBefore', html);
 
-                       html = R('(<' + blocks + '[^>]*>)', 'gi', "\n$1");
-                       html = R('(</' + blocks + '>)', 'gi', "$1\n\n");
-                       html = R("\r\n", 'g', "\n");
-                       html = R("\r", 'g', "\n");
-                       html = R("/\n\n+/", 'g', "\n\n");
+                                       // clean
+                                       html = (this.utils.isSelectAll()) ? this.clean.onPaste(html, false) : this.clean.onPaste(html);
 
-                       var htmls = html.split(new RegExp('\n\s*\n', 'g'), -1);
+                                       html = this.core.setCallback('paste', html);
 
-                       html = '';
-                       for (var i in htmls)
-                       {
-                               if (htmls.hasOwnProperty(i))
-                {
-                                       if (htmls[i].search('{replace') == -1)
+                                       if (this.utils.isSelectAll())
                                        {
-                                               htmls[i] = htmls[i].replace(/<p>\n\t?<\/p>/gi, '');
-                                               htmls[i] = htmls[i].replace(/<p><\/p>/gi, '');
-
-                                               if (htmls[i] != '')
-                                               {
-                                                       html += '<p>' +  htmls[i].replace(/^\n+|\n+$/g, "") + "</p>";
-                                               }
+                                               this.insert.set(html, false);
+                                       }
+                                       else
+                                       {
+                                               this.insert.html(html, false);
                                        }
-                                       else html += htmls[i];
-                               }
-                       }
 
-                       html = R('<p><p>', 'gi', '<p>');
-                       html = R('</p></p>', 'gi', '</p>');
+                                       this.utils.disableSelectAll();
+                                       this.rtePaste = false;
 
-                       html = R('<p>\s?</p>', 'gi', '');
+                                       // FF specific
+                                       if (this.utils.browser('mozilla')) setTimeout($.proxy(this.paste.clipboardUploadInMozilla, this), 100);
+                                       setTimeout($.proxy(this.clean.clearUnverified, this), 10);
 
-                       html = R('<p>([^<]+)</(div|address|form)>', 'gi', "<p>$1</p></$2>");
+                               },
+                               // clipboard
+                               detectEventClipboardUpload: function(e)
+                               {
+                                       e = e.originalEvent || e;
 
-                       html = R('<p>(</?' + blocks + '[^>]*>)</p>', 'gi', "$1");
-                       html = R("<p>(<li.+?)</p>", 'gi', "$1");
-                       html = R('<p>\s?(</?' + blocks + '[^>]*>)', 'gi', "$1");
+                                       if (typeof(e.clipboardData) === 'undefined') return;
+                                       if (!e.clipboardData.items || !e.clipboardData.items.length) return;
 
-                       html = R('(</?' + blocks + '[^>]*>)\s?</p>', 'gi', "$1");
-                       html = R('(</?' + blocks + '[^>]*>)\s?<br />', 'gi', "$1");
-                       html = R('<br />(\s*</?(?:p|li|div|dl|dd|dt|th|pre|td|ul|ol)[^>]*>)', 'gi', '$1');
-                       html = R("\n</p>", 'gi', '</p>');
+                                       var file = e.clipboardData.items[0].getAsFile();
+                                       if (file === null) return false;
 
-                       html = R('<li><p>', 'gi', '<li>');
-                       html = R('</p></li>', 'gi', '</li>');
-                       html = R('</li><p>', 'gi', '</li>');
-                       //html = R('</ul><p>(.*?)</li>', 'gi', '</ul></li>');
-                       // html = R('</ol><p>', 'gi', '</ol>');
-                       html = R('<p>\t?\n?<p>', 'gi', '<p>');
-                       html = R('</dt><p>', 'gi', '</dt>');
-                       html = R('</dd><p>', 'gi', '</dd>');
-                       html = R('<br></p></blockquote>', 'gi', '</blockquote>');
-                       html = R('<p>\t*</p>', 'gi', '');
+                                       var reader = new FileReader();
+                                       reader.onload = $.proxy(this.paste.insertFromClipboard, this);
+                               reader.readAsDataURL(file);
 
-                       // restore safes
-                       $.each(safes, function(i,s)
-                       {
-                               html = html.replace('{replace' + i + '}', s);
-                       });
+                               return true;
+                               },
+                               clipboardUploadInMozilla: function()
+                               {
+                                       var imgs = this.$editor.find('img');
+                                       $.each(imgs, $.proxy(function(i,s)
+                                       {
+                                               if (s.src.search(/^data\:image/i) == -1) return;
 
-                       return $.trim(html);
-               },
-               cleanConvertInlineTags: function(html, set)
-               {
-                       var boldTag = 'strong';
-                       if (this.opts.boldTag === 'b') boldTag = 'b';
+                                               var $s = $(s);
+                                               var arr = s.src.split(",");
+                                               var postData = {
+                                                       'clipboard': 1,
+                                                       'contentType': arr[0].split(";")[0].split(":")[1],
+                                                       'data': arr[1] // raw base64
+                                               };
 
-                       var italicTag = 'em';
-                       if (this.opts.italicTag === 'i') italicTag = 'i';
 
-                       html = html.replace(/<span style="font-style: italic;">([\w\W]*?)<\/span>/gi, '<' + italicTag + '>$1</' + italicTag + '>');
-                       html = html.replace(/<span style="font-weight: bold;">([\w\W]*?)<\/span>/gi, '<' + boldTag + '>$1</' + boldTag + '>');
+                                               // append hidden fields
+                                               if (this.opts.uploadImageFields && typeof this.opts.uploadImageFields === 'object')
+                                               {
+                                                       $.each(this.opts.uploadImageFields, $.proxy(function(k, v)
+                                                       {
+                                                               if (v !== null && v.toString().indexOf('#') === 0) v = $(v).val();
+                                                               postData[k] = v;
 
-                       // bold, italic, del
-                       if (this.opts.boldTag === 'strong') html = html.replace(/<b>([\w\W]*?)<\/b>/gi, '<strong>$1</strong>');
-                       else html = html.replace(/<strong>([\w\W]*?)<\/strong>/gi, '<b>$1</b>');
+                                                       }, this));
+                                               }
 
-                       if (this.opts.italicTag === 'em') html = html.replace(/<i>([\w\W]*?)<\/i>/gi, '<em>$1</em>');
-                       else html = html.replace(/<em>([\w\W]*?)<\/em>/gi, '<i>$1</i>');
+                                               $.post(this.opts.imageUpload, postData,
+                                               $.proxy(function(data)
+                                               {
+                                                       var json = (typeof data === 'string' ? $.parseJSON(data) : data);
+                                               $s.attr('src', json.filelink);
 
-                       html = html.replace(/<span style="text-decoration: underline;">([\w\W]*?)<\/span>/gi, '<u>$1</u>');
+                                               this.code.sync();
 
-                       if (set !== true) html = html.replace(/<strike>([\w\W]*?)<\/strike>/gi, '<del>$1</del>');
-                       else html = html.replace(/<del>([\w\W]*?)<\/del>/gi, '<strike>$1</strike>');
+                                                       // upload callback
+                                                       this.core.setCallback('imageUpload', $s, json);
+                                                       this.observe.images();
 
-                       return html;
-               },
-               cleanStripTags: function(html)
-               {
-                       if (html == '' || typeof html == 'undefined') return html;
+                                               }, this));
 
-                       var allowed = false;
-                       if (this.opts.allowedTags !== false) allowed = true;
 
-                       var arr = allowed === true ? this.opts.allowedTags : this.opts.deniedTags;
+                                       }, this));
+                               },
+                               insertFromClipboard: function(e)
+                               {
+                                       var result = e.target.result;
+                                       var arr = result.split(",");
 
-                       var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi;
-                       html = html.replace(tags, function ($0, $1)
-                       {
-                               if (allowed === true) return $.inArray($1.toLowerCase(), arr) > '-1' ? $0 : '';
-                               else return $.inArray($1.toLowerCase(), arr) > '-1' ? '' : $0;
-                       });
+                                       var formData = !!window.FormData ? new FormData() : null;
+                                       if (!window.FormData) return;
+
+                                       this.buffer.set();
 
-                       html = this.cleanConvertInlineTags(html);
+                                       this.upload.direct = true;
+                                       this.upload.type = 'image';
+                                       this.upload.url = this.opts.imageUpload;
+                                       this.upload.callback = this.image.insert;
 
-                       return html;
+                                       formData.append('clipboard', 1);
+                                       formData.append('contentType', arr[0].split(";")[0].split(":")[1]);
+                                       formData.append('data', arr[1]); // raw base64
+
+                                       this.upload.sendData(formData, e);
+                               }
 
+                       };
                },
-               cleanSavePreCode: function(html, encode)
+               keydown: function()
                {
-                       var pre = html.match(/<(pre|code)(.*?)>([\w\W]*?)<\/(pre|code)>/gi);
-                       if (pre !== null)
-                       {
-                               $.each(pre, $.proxy(function(i,s)
+                       return {
+                               init: function(e)
                                {
-                                       var arr = s.match(/<(pre|code)(.*?)>([\w\W]*?)<\/(pre|code)>/i);
+                                       if (this.rtePaste) return;
 
-                                       arr[3] = arr[3].replace(/&nbsp;/g, ' ');
+                                       var key = e.which;
+                                       var arrow = (key >= 37 && key <= 40);
 
-                                       if (encode !== false) arr[3] = this.cleanEncodeEntities(arr[3]);
+                                       this.keydown.ctrl = e.ctrlKey || e.metaKey;
+                                       this.keydown.current = this.selection.getCurrent();
+                                       this.keydown.parent = this.selection.getParent();
+                                       this.keydown.block = this.selection.getBlock();
 
-                                       // $ fix
-                                       arr[3] = arr[3].replace(/\$/g, '&#36;');
+                               // detect tags
+                                       this.keydown.pre = this.utils.isTag(this.keydown.current, 'pre');
+                                       this.keydown.blockquote = this.utils.isTag(this.keydown.current, 'blockquote');
+                                       this.keydown.figcaption = this.utils.isTag(this.keydown.current, 'figcaption');
 
-                                       html = html.replace(s, '<' + arr[1] + arr[2] + '>' + arr[3] + '</' + arr[1] + '>');
+                                       // shortcuts setup
+                                       this.shortcuts.init(e, key);
 
-                               }, this));
-                       }
+                                       this.keydown.checkEvents(arrow, key);
+                                       this.keydown.setupBuffer(e, key);
+                                       this.keydown.addArrowsEvent(arrow);
+                                       this.keydown.setupSelectAll(e, key);
 
-                       return html;
-               },
-               cleanEncodeEntities: function(str)
-               {
-                       str = String(str).replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"');
-                       return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
-               },
-               cleanUnverified: function()
-               {
-                       // label, abbr, mark, meter, code, q, dfn, ins, time, kbd, var
-                       var $elem = this.$editor.find('li, img, a, b, strong, sub, sup, i, em, u, small, strike, del, span, cite');
+                                       // callback
+                                       var keydownStop = this.core.setCallback('keydown', e);
+                                       if (keydownStop === false)
+                                       {
+                                               e.preventDefault();
+                                               return false;
+                                       }
+
+                                       // down
+                                       if (this.opts.enterKey && key === this.keyCode.DOWN)
+                                       {
+                                               this.keydown.onArrowDown();
+                                       }
 
-                       $elem.filter('[style*="background-color: transparent;"][style*="line-height"]')
-                       .css('background-color', '')
-                       .css('line-height', '');
+                                       // turn off enter key
+                                       if (!this.opts.enterKey && key === this.keyCode.ENTER)
+                                       {
+                                               e.preventDefault();
+                                               // remove selected
+                                               if (!this.range.collapsed) this.range.deleteContents();
+                                               return;
+                                       }
 
-                       $elem.filter('[style*="background-color: transparent;"]')
-                       .css('background-color', '');
+                                       // on enter
+                                       if (key == this.keyCode.ENTER && !e.shiftKey && !e.ctrlKey && !e.metaKey)
+                                       {
+                                               var stop = this.core.setCallback('enter', e);
+                                               if (stop === false)
+                                               {
+                                                       e.preventDefault();
+                                                       return false;
+                                               }
 
-                       $elem.css('line-height', '');
+                                               if (this.keydown.blockquote && this.keydown.exitFromBlockquote(e) === true)
+                                               {
+                                                       return false;
+                                               }
 
-                       $.each($elem, $.proxy(function(i,s)
-                       {
-                               this.removeEmptyAttr(s, 'style');
-                       }, this));
+                                               var current;
+                                               if (this.keydown.pre)
+                                               {
+                                                       return this.keydown.insertNewLine(e);
+                                               }
+                                               else if (this.keydown.blockquote || this.keydown.figcaption)
+                                               {
+                                                       current = this.selection.getCurrent();
+                                                       var $next = $(current).next();
 
-                       var $elem2 = this.$editor.find('b, strong, i, em, u, strike, del');
-                       $elem2.css('font-size', '');
+                                                       if ($next.size() !== 0 && $next[0].tagName == 'BR')
+                                                       {
+                                                               return this.keydown.insertBreakLine(e);
+                                                       }
+                                                       else if (this.utils.isEndOfElement() && (current && current != 'SPAN'))
+                                                       {
+                                                               return this.keydown.insertDblBreakLine(e);
+                                                       }
+                                                       else
+                                                       {
+                                                               return this.keydown.insertBreakLine(e);
+                                                       }
+                                               }
+                                               else if (this.opts.linebreaks && !this.keydown.block)
+                                               {
+                                                       current = this.selection.getCurrent();
+                                                       if (current !== 0 && $(current).hasClass('redactor-invisible-space'))
+                                                       {
+                                                               $(current).remove();
+                                                               return this.keydown.insertDblBreakLine(e);
+                                                       }
+                                                       else
+                                                       {
+                                                               return this.keydown.insertBreakLine(e);
+                                                       }
+                                               }
+                                               else if (this.opts.linebreaks && this.keydown.block)
+                                               {
+                                                       setTimeout($.proxy(this.keydown.replaceDivToBreakLine, this), 1);
+                                               }
+                                               // paragraphs
+                                               else if (!this.opts.linebreaks && this.keydown.block)
+                                               {
+                                                       setTimeout($.proxy(this.keydown.replaceDivToParagraph, this), 1);
+                                               }
+                                               else if (!this.opts.linebreaks && !this.keydown.block)
+                                               {
+                                                       return this.keydown.insertParagraph(e);
+                                               }
+                                       }
 
-                       $.each($elem2, $.proxy(function(i,s)
-                       {
-                               this.removeEmptyAttr(s, 'style');
-                       }, this));
 
-                       // When we paste text in Safari is wrapping inserted div (remove it)
-                       this.$editor.find('div[style="text-align: -webkit-auto;"]').contents().unwrap();
+                                       // Shift+Enter or Ctrl+Enter
+                                       if (key === this.keyCode.ENTER && (e.ctrlKey || e.shiftKey))
+                                       {
+                                               return this.keydown.onShiftEnter(e);
+                                       }
 
-                       // Remove all styles in ul, ol, li
-                       this.$editor.find('ul, ol, li').removeAttr('style');
-               },
 
+                                       // tab or cmd + [
+                                       if (key === this.keyCode.TAB || e.metaKey && key === 221 || e.metaKey && key === 219)
+                                       {
+                                               return this.keydown.onTab(e, key);
+                                       }
 
-               // TEXTAREA CODE FORMATTING
-               cleanHtml: function(code)
-               {
-                       var i = 0,
-                       codeLength = code.length,
-                       point = 0,
-                       start = null,
-                       end = null,
-                       tag = '',
-                       out = '',
-                       cont = '';
-
-                       this.cleanlevel = 0;
-
-                       for (; i < codeLength; i++)
-                       {
-                               point = i;
 
-                               // if no more tags, copy and exit
-                               if (-1 == code.substr(i).indexOf( '<' ))
-                               {
-                                       out += code.substr(i);
+                                       // image delete and backspace
+                                       if (key === this.keyCode.BACKSPACE || key === this.keyCode.DELETE)
+                                       {
+                                               var nodes = this.selection.getNodes();
+                                               if (nodes)
+                                               {
+                                                       var len = nodes.length;
+                                                       var last;
+                                                       for (var i = 0; i < len; i++)
+                                                       {
+                                                               var children = $(nodes[i]).children('img');
+                                                               if (children.size() !== 0)
+                                                               {
+                                                                       var self = this;
+                                                                       $.each(children, function(z,s)
+                                                                       {
+                                                                               var $s = $(s);
+                                                                               if ($s.css('float') != 'none') return;
 
-                                       return this.cleanFinish(out);
-                               }
+                                                                               // image delete callback
+                                                                               self.core.setCallback('imageDelete', s.src, $s);
+                                                                               last = s;
+                                                                       });
+                                                               }
+                                                               else if (nodes[i].tagName == 'IMG')
+                                                               {
+                                                                       if (last != nodes[i])
+                                                                       {
+                                                                               // image delete callback
+                                                                               this.core.setCallback('imageDelete', nodes[i].src, $(nodes[i]));
+                                                                               last = nodes[i];
+                                                                       }
+                                                               }
+                                                       }
+                                               }
+                                       }
 
-                               // copy verbatim until a tag
-                               while (point < codeLength && code.charAt(point) != '<')
-                               {
-                                       point++;
-                               }
+                                       // backspace
+                                       if (key === this.keyCode.BACKSPACE)
+                                       {
+                                               this.keydown.removeInvisibleSpace();
+                                               this.keydown.removeEmptyListInTable(e);
+                                       }
 
-                               if (i != point)
+                                       this.code.sync();
+                               },
+                               checkEvents: function(arrow, key)
                                {
-                                       cont = code.substr(i, point - i);
-                                       if (!cont.match(/^\s{2,}$/g))
+                                       if (!arrow && (this.core.getEvent() == 'click' || this.core.getEvent() == 'arrow'))
                                        {
-                                               if ('\n' == out.charAt(out.length - 1)) out += this.cleanGetTabs();
-                                               else if ('\n' == cont.charAt(0))
+                                               this.core.addEvent(false);
+
+                                               if (this.keydown.checkKeyEvents(key))
                                                {
-                                                       out += '\n' + this.cleanGetTabs();
-                                                       cont = cont.replace(/^\s+/, '');
+                                                       this.buffer.set();
                                                }
-
-                                               out += cont;
                                        }
+                               },
+                               checkKeyEvents: function(key)
+                               {
+                                       var k = this.keyCode;
+                                       var keys = [k.BACKSPACE, k.DELETE, k.ENTER, k.SPACE, k.ESC, k.TAB, k.CTRL, k.META, k.ALT, k.SHIFT];
 
-                                       if (cont.match(/\n/)) out += '\n' + this.cleanGetTabs();
-                               }
-
-                               start = point;
+                                       return ($.inArray(key, keys) == -1) ? true : false;
 
-                               // find the end of the tag
-                               while (point < codeLength && '>' != code.charAt(point))
+                               },
+                               addArrowsEvent: function(arrow)
                                {
-                                       point++;
-                               }
-
-                               tag = code.substr(start, point - start);
-                               i = point;
+                                       if (!arrow) return;
 
-                               var t;
+                                       if ((this.core.getEvent() == 'click' || this.core.getEvent() == 'arrow'))
+                                       {
+                                               this.core.addEvent(false);
+                                               return;
+                                       }
 
-                               if ('!--' == tag.substr(1, 3))
+                                   this.core.addEvent('arrow');
+                               },
+                               setupBuffer: function(e, key)
                                {
-                                       if (!tag.match(/--$/))
+                                       if (this.keydown.ctrl && key === 90 && !e.shiftKey && !e.altKey && this.opts.buffer.length) // z key
+                                       {
+                                               e.preventDefault();
+                                               this.buffer.undo();
+                                               return;
+                                       }
+                                       // undo
+                                       else if (this.keydown.ctrl && key === 90 && e.shiftKey && !e.altKey && this.opts.rebuffer.length !== 0)
+                                       {
+                                               e.preventDefault();
+                                               this.buffer.redo();
+                                               return;
+                                       }
+                                       else if (!this.keydown.ctrl)
                                        {
-                                               while ('-->' != code.substr(point, 3))
+                                               if (key == this.keyCode.BACKSPACE || key == this.keyCode.DELETE || (key == this.keyCode.ENTER && !e.ctrlKey && !e.shiftKey) || key == this.keyCode.SPACE)
                                                {
-                                                       point++;
+                                                       this.buffer.set();
+                                               }
+                                       }
+                               },
+                               setupSelectAll: function(e, key)
+                               {
+                                       if (this.keydown.ctrl && key === 65)
+                                       {
+                                               this.utils.enableSelectAll();
+                                       }
+                                       else if (key != this.keyCode.LEFT_WIN && !this.keydown.ctrl)
+                                       {
+                                               this.utils.disableSelectAll();
+                                       }
+                               },
+                               onArrowDown: function()
+                               {
+                                       var tags = [this.keydown.blockquote, this.keydown.pre, this.keydown.figcaption];
+
+                                       for (var i = 0; i < tags.length; i++)
+                                       {
+                                               if (tags[i])
+                                               {
+                                                       this.keydown.insertAfterLastElement(tags[i]);
+                                                       return false;
                                                }
-                                               point += 2;
-                                               tag = code.substr(start, point - start);
-                                               i = point;
                                        }
+                               },
+                               onShiftEnter: function(e)
+                               {
+                                       this.buffer.set();
 
-                                       if ('\n' != out.charAt(out.length - 1)) out += '\n';
+                                       if (this.keydown.blockquote && this.utils.isEndOfElement())
+                                       {
+                                               return this.keydown.insertDblBreakLine(e);
+                                       }
 
-                                       out += this.cleanGetTabs();
-                                       out += tag + '>\n';
-                               }
-                               else if ('!' == tag[1])
+                                       return this.keydown.insertBreakLine(e);
+                               },
+                               onTab: function(e, key)
                                {
-                                       out = this.placeTag(tag + '>', out);
-                               }
-                               else if ('?' == tag[1])
+                                       if (!this.opts.tabFocus) return true;
+                                       if (this.utils.isEmpty(this.code.get()) && this.opts.tabAsSpaces === false) return true;
+
+                                       e.preventDefault();
+
+                                       var node;
+                                       if (this.keydown.pre && !e.shiftKey)
+                                       {
+                                               node = (this.opts.preSpaces) ? document.createTextNode(Array(this.opts.preSpaces + 1).join('\u00a0')) : document.createTextNode('\t');
+                                               this.insert.node(node);
+                                               this.code.sync();
+                                       }
+                                       else if (this.opts.tabAsSpaces !== false)
+                                       {
+                                               node = document.createTextNode(Array(this.opts.tabAsSpaces + 1).join('\u00a0'));
+                                               this.insert.node(node);
+                                               this.code.sync();
+                                       }
+                                       else
+                                       {
+                                               if (e.metaKey && key === 219) this.indent.decrease();
+                                               else if (e.metaKey && key === 221) this.indent.increase();
+                                               else if (!e.shiftKey) this.indent.increase();
+                                               else this.indent.decrease();
+                                       }
+
+                                       return false;
+                               },
+                               replaceDivToBreakLine: function()
                                {
-                                       out += tag + '>\n';
-                               }
-                               else if (t = tag.match(/^<(script|style|pre)/i))
+                                       var blockElem = this.selection.getBlock();
+                                       var blockHtml = blockElem.innerHTML.replace(/<br\s?\/?>/gi, '');
+                                       if ((blockElem.tagName === 'DIV' || blockElem.tagName === 'P') && blockHtml === '' && !$(blockElem).hasClass('redactor-editor'))
+                                       {
+                                               var br = document.createElement('br');
+
+                                               $(blockElem).replaceWith(br);
+                                               this.caret.setBefore(br);
+
+                                               this.code.sync();
+
+                                               return false;
+                                       }
+                               },
+                               replaceDivToParagraph: function()
                                {
-                                       t[1] = t[1].toLowerCase();
-                                       tag = this.cleanTag(tag);
-                                       out = this.placeTag(tag, out);
-                                       end = String(code.substr(i + 1)).toLowerCase().indexOf('</' + t[1]);
+                                       var blockElem = this.selection.getBlock();
+                                       var blockHtml = blockElem.innerHTML.replace(/<br\s?\/?>/gi, '');
+                                       if (blockElem.tagName === 'DIV' && blockHtml === '' && !$(blockElem).hasClass('redactor-editor'))
+                                       {
+                                               var p = document.createElement('p');
+                                               p.innerHTML = this.opts.invisibleSpace;
+
+                                               $(blockElem).replaceWith(p);
+                                               this.caret.setStart(p);
 
-                                       if (end)
+                                               this.code.sync();
+
+                                               return false;
+                                       }
+                                       else if (this.opts.cleanStyleOnEnter && blockElem.tagName == 'P')
                                        {
-                                               cont = code.substr(i + 1, end);
-                                               i += end;
-                                               out += cont;
+                                               $(blockElem).removeAttr('class').removeAttr('style');
                                        }
-                               }
-                               else
+                               },
+                               insertParagraph: function(e)
                                {
-                                       tag = this.cleanTag(tag);
-                                       out = this.placeTag(tag, out);
-                               }
-                       }
-
-                       return this.cleanFinish(out);
-               },
-               cleanGetTabs: function()
-               {
-                       var s = '';
-                       for ( var j = 0; j < this.cleanlevel; j++ )
-                       {
-                               s += '\t';
-                       }
+                                       e.preventDefault();
 
-                       return s;
-               },
-               cleanFinish: function(code)
-               {
-                       code = code.replace(/\n\s*\n/g, '\n');
-                       code = code.replace(/^[\s\n]*/, '');
-                       code = code.replace(/[\s\n]*$/, '');
-                       code = code.replace(/<script(.*?)>\n<\/script>/gi, '<script$1></script>');
+                                       this.selection.get();
 
-                       this.cleanlevel = 0;
+                                       var p = document.createElement('p');
+                                       p.innerHTML = this.opts.invisibleSpace;
 
-                       return code;
-               },
-               cleanTag: function (tag)
-               {
-                       var tagout = '';
-                       tag = tag.replace(/\n/g, ' ');
-                       tag = tag.replace(/\s{2,}/g, ' ');
-                       tag = tag.replace(/^\s+|\s+$/g, ' ');
+                                       this.range.deleteContents();
+                                       this.range.insertNode(p);
 
-                       var suffix = '';
-                       if (tag.match(/\/$/))
-                       {
-                               suffix = '/';
-                               tag = tag.replace(/\/+$/, '');
-                       }
+                                       this.caret.setStart(p);
 
-                       var m;
-                       while (m = /\s*([^= ]+)(?:=((['"']).*?\3|[^ ]+))?/.exec(tag))
-                       {
-                               if (m[2]) tagout += m[1].toLowerCase() + '=' + m[2];
-                               else if (m[1]) tagout += m[1].toLowerCase();
+                                       this.code.sync();
 
-                               tagout += ' ';
-                               tag = tag.substr(m[0].length);
-                       }
+                                       return false;
+                               },
+                               exitFromBlockquote: function(e)
+                               {
+                                       if (!this.utils.isEndOfElement()) return;
 
-                       return tagout.replace(/\s*$/, '') + suffix + '>';
-               },
-               placeTag: function (tag, out)
-               {
-                       var nl = tag.match(this.cleannewLevel);
-                       if (tag.match(this.cleanlineBefore) || nl)
-                       {
-                               out = out.replace(/\s*$/, '');
-                               out += '\n';
-                       }
+                                       var tmp = $.trim($(this.keydown.block).html());
+                                       if (tmp.search(/(<br\s?\/?>){2}$/i) != -1)
+                                       {
+                                               e.preventDefault();
 
-                       if (nl && '/' == tag.charAt(1)) this.cleanlevel--;
-                       if ('\n' == out.charAt(out.length - 1)) out += this.cleanGetTabs();
-                       if (nl && '/' != tag.charAt(1)) this.cleanlevel++;
+                                               if (this.opts.linebreaks)
+                                               {
+                                                       var br = document.createElement('br');
+                                                       $(this.keydown.blockquote).after(br);
 
-                       out += tag;
+                                                       this.caret.setBefore(br);
+                                                       $(this.keydown.block).html(tmp.replace(/<br\s?\/?>$/i, ''));
+                                               }
+                                               else
+                                               {
+                                                       var node = $(this.opts.emptyHtml);
+                                                       $(this.keydown.blockquote).after(node);
+                                                       this.caret.setStart(node);
+                                               }
 
-                       if (tag.match(this.cleanlineAfter) || tag.match(this.cleannewLevel))
-                       {
-                               out = out.replace(/ *$/, '');
-                               out += '\n';
-                       }
+                                               return true;
 
-                       return out;
-               },
+                                       }
 
-               // FORMAT
-               formatEmpty: function(e)
-               {
-                       var html = $.trim(this.$editor.html());
+                                       return;
 
-                       if (this.opts.linebreaks)
-                       {
-                               if (html == '')
+                               },
+                               insertAfterLastElement: function(element)
                                {
-                                       e.preventDefault();
-                                       this.$editor.html('');
-                                       this.focus();
-                               }
-                       }
-                       else
-                       {
-                               html = html.replace(/<br\s?\/?>/i, '');
-                               var thtml = html.replace(/<p>\s?<\/p>/gi, '');
+                                       if (!this.utils.isEndOfElement()) return;
+
+                                       this.buffer.set();
+
+                                       if (this.opts.linebreaks)
+                                       {
+                                               var contents = $('<div>').append($.trim(this.$editor.html())).contents();
+                                               var last = contents.last()[0];
+                                               if (last.tagName == 'SPAN' && last.innerHTML === '')
+                                               {
+                                                       last = contents.prev()[0];
+                                               }
+
+                                               if (this.utils.getOuterHtml(last) != this.utils.getOuterHtml(element)) return;
+
+                                               var br = document.createElement('br');
+                                               $(element).after(br);
+                                               this.caret.setAfter(br);
+
+                                       }
+                                       else
+                                       {
+                                               if (this.$editor.contents().last()[0] !== element) return;
 
-                               if (html === '' || thtml === '')
+                                               var node = $(this.opts.emptyHtml);
+                                               $(element).after(node);
+                                               this.caret.setStart(node);
+                                       }
+                               },
+                               insertNewLine: function(e)
                                {
                                        e.preventDefault();
 
-                                       var node = $(this.opts.emptyHtml).get(0);
-                                       this.$editor.html(node);
-                                       this.focus();
-                               }
-                       }
+                                       var node = document.createTextNode('\n');
 
-                       this.sync();
-               },
-               formatBlocks: function(tag)
-               {
-                       if (this.browser('mozilla') && this.isFocused())
-                       {
-                               this.$editor.focus();
-                       }
+                                       this.selection.get();
 
-                       this.bufferSet();
+                                       this.range.deleteContents();
+                                       this.range.insertNode(node);
 
-                       var nodes = this.getBlocks();
-                       this.selectionSave();
+                                       this.caret.setAfter(node);
 
-                       $.each(nodes, $.proxy(function(i, node)
-                       {
-                               if (node.tagName !== 'LI')
+                                       this.code.sync();
+
+                                       return false;
+                               },
+                               insertBreakLine: function(e)
+                               {
+                                       return this.keydown.insertBreakLineProcessing(e);
+                               },
+                               insertDblBreakLine: function(e)
+                               {
+                                       return this.keydown.insertBreakLineProcessing(e, true);
+                               },
+                               insertBreakLineProcessing: function(e, dbl)
                                {
-                                       var parent = $(node).parent();
+                                       e.stopPropagation();
 
-                                       if (tag === 'p')
+                                       this.selection.get();
+                                       var br1 = document.createElement('br');
+
+                                       this.range.deleteContents();
+                                       this.range.insertNode(br1);
+
+                                       if (dbl === true)
                                        {
-                                               if ((node.tagName === 'P'
-                                               && parent.size() != 0
-                                               && parent[0].tagName === 'BLOCKQUOTE')
-                                               ||
-                                               node.tagName === 'BLOCKQUOTE')
-                                               {
-                                                       this.formatQuote();
-                                                       return;
-                                               }
-                                               else if (this.opts.linebreaks)
-                                               {
-                                                       if (node && node.tagName.search(/H[1-6]/) == 0)
-                                                       {
-                                                               $(node).replaceWith(node.innerHTML + '<br>');
-                                                       }
-                                                       else return;
-                                               }
-                                               else
-                                               {
-                                                       this.formatBlock(tag, node);
-                                               }
+                                               var br2 = document.createElement('br');
+                                               this.range.insertNode(br2);
+                                               this.caret.setAfter(br2);
                                        }
                                        else
                                        {
-                                               this.formatBlock(tag, node);
+                                               this.caret.setAfter(br1);
                                        }
-                               }
-
-                       }, this));
 
-                       this.selectionRestore();
-                       this.sync();
-               },
-               formatBlock: function(tag, block)
-               {
-                       if (block === false) block = this.getBlock();
-                       if (block === false && this.opts.linebreaks === true)
-                       {
-                               this.execCommand('formatblock', tag);
-                               return true;
-                       }
+                                       this.code.sync();
 
-                       var contents = '';
-                       if (tag !== 'pre')
-                       {
-                               contents = $(block).contents();
-                       }
-                       else
-                       {
-                               //contents = this.cleanEncodeEntities($(block).text());
-                               contents = $(block).html();
-                               if ($.trim(contents) === '')
+                                       return false;
+                               },
+                               removeInvisibleSpace: function()
                                {
-                                       contents = '<span id="selection-marker-1"></span>';
-                               }
-                       }
+                                       var $current = $(this.keydown.current);
+                                       if ($current.text().search(/^\u200B$/g) === 0)
+                                       {
+                                               $current.remove();
+                                       }
+                               },
+                               removeEmptyListInTable: function(e)
+                               {
+                                       var $current = $(this.keydown.current);
+                                       var $parent = $(this.keydown.parent);
+                                       var td = $current.closest('td');
 
-                       if (block.tagName === 'PRE') tag = 'p';
+                                       if (td.size() !== 0 && $current.closest('li') && $parent.children('li').size() === 1)
+                                       {
+                                               if (!this.utils.isEmpty($current.text())) return;
 
-                       if (this.opts.linebreaks === true && tag === 'p')
-                       {
-                               $(block).replaceWith($('<div>').append(contents).html() + '<br>');
-                       }
-                       else
-                       {
-                               var parent = this.getParent();
+                                               e.preventDefault();
 
-                               var node = $('<' + tag + '>').append(contents);
-                               $(block).replaceWith(node);
+                                               $current.remove();
+                                               $parent.remove();
 
-                               if (parent && parent.tagName == 'TD')
-                               {
-                                       $(node).wrapAll('<td>');
+                                               this.caret.setStart(td);
+                                       }
                                }
-                       }
+                       };
                },
-               formatChangeTag: function(fromElement, toTagName, save)
+               keyup: function()
                {
-                       if (save !== false) this.selectionSave();
-
-                       var newElement = $('<' + toTagName + '/>');
-                       $(fromElement).replaceWith(function() { return newElement.append($(this).contents()); });
+                       return {
+                               init: function(e)
+                               {
+                                       if (this.rtePaste) return;
 
-                       if (save !== false) this.selectionRestore();
+                                       var key = e.which;
 
-                       return newElement;
-               },
+                                       this.keyup.current = this.selection.getCurrent();
+                                       this.keyup.parent = this.selection.getParent();
+                                       var $parent = this.utils.isRedactorParent($(this.keyup.parent).parent());
 
-               // QUOTE
-               formatQuote: function()
-               {
-                       if (this.browser('mozilla') && this.isFocused())
-                       {
-                               this.$editor.focus();
-                       }
+                                       // callback
+                                       var keyupStop = this.core.setCallback('keyup', e);
+                                       if (keyupStop === false)
+                                       {
+                                               e.preventDefault();
+                                               return false;
+                                       }
 
-                       this.bufferSet();
+                                       // replace to p before / after the table or body
+                                       if (!this.opts.linebreaks && this.keyup.current.nodeType == 3 && this.keyup.current.length <= 1 && (this.keyup.parent === false || this.keyup.parent.tagName == 'BODY'))
+                                       {
+                                               this.keyup.replaceToParagraph();
+                                       }
 
-                       // paragraphy
-                       if (this.opts.linebreaks === false)
-                       {
-                               this.selectionSave();
+                                       if ($(this.keyup.parent).hasClass('redactor-invisible-space') && ($parent === false || $parent[0].tagName == 'BODY'))
+                                       {
+                                               $(this.keyup.parent).contents().unwrap();
+                                               this.keyup.replaceToParagraph();
+                                       }
 
-                               var blocks = this.getBlocks();
+                                       // linkify
+                                       if (this.opts.convertLinks && (this.opts.convertUrlLinks || this.opts.convertImageLinks || this.opts.convertVideoLinks) && key === this.keyCode.ENTER)
+                                       {
+                                               this.formatLinkify(this.opts.linkProtocol, this.opts.convertLinks, this.opts.convertUrlLinks, this.opts.convertImageLinks, this.opts.convertVideoLinks, this.opts.linkSize);
 
-                               var blockquote = false;
-                               var blocksLen = blocks.length;
-                               if (blocks)
-                               {
-                                       var data = '';
-                                       var replaced = '';
-                                       var replace = false;
-                                       var paragraphsOnly = true;
+                                               this.observe.load();
+                                               this.code.sync();
+                                       }
 
-                                       $.each(blocks, function(i,s)
+                                       if (key === this.keyCode.DELETE || key === this.keyCode.BACKSPACE)
                                        {
-                                               if (s.tagName !== 'P') paragraphsOnly = false;
-                                       });
+                                               // clear unverified
+                                               this.clean.clearUnverified();
 
-                                       $.each(blocks, $.proxy(function(i,s)
-                                       {
-                                               if (s.tagName === 'BLOCKQUOTE')
+                                               if (this.observe.image)
                                                {
-                                                       this.formatBlock('p', s, false);
-                                               }
-                                               else if (s.tagName === 'P')
-                                               {
-                                                       blockquote = $(s).parent();
-                                                       // from blockquote
-                                                       if (blockquote[0].tagName == 'BLOCKQUOTE')
-                                                       {
-                                                               var count = $(blockquote).children('p').size();
+                                                       e.preventDefault();
 
-                                                               // one
-                                                               if (count == 1)
-                                                               {
-                                                                       $(blockquote).replaceWith(s);
-                                                               }
-                                                               // all
-                                                               else if (count == blocksLen)
-                                                               {
-                                                                       replace = 'blockquote';
-                                                                       data += this.outerHtml(s);
-                                                               }
-                                                               // some
-                                                               else
-                                                               {
-                                                                       replace = 'html';
-                                                                       data += this.outerHtml(s);
+                                                       this.image.hideResize();
 
-                                                                       if (i == 0)
-                                                                       {
-                                                                               $(s).addClass('redactor-replaced').empty();
-                                                                               replaced = this.outerHtml(s);
-                                                                       }
-                                                                       else $(s).remove();
-                                                               }
-                                                       }
-                                                       // to blockquote
-                                                       else
-                                                       {
-                                                               if (paragraphsOnly === false || blocks.length == 1)
-                                                               {
-                                                                       this.formatBlock('blockquote', s, false);
-                                                               }
-                                                               else
-                                                               {
-                                                                       replace = 'paragraphs';
-                                                                       data += this.outerHtml(s);
-                                                               }
-                                                       }
+                                                       this.buffer.set();
+                                                       this.image.remove(this.observe.image);
+                                                       this.observe.image = false;
 
-                                               }
-                                               else if (s.tagName !== 'LI')
-                                               {
-                                                       this.formatBlock('blockquote', s, false);
+                                                       return false;
                                                }
 
-                                       }, this));
+                                               // remove empty paragraphs
+                                               this.$editor.find('p').each($.proxy(this.utils.removeEmpty, this));
 
-                                       if (replace)
-                                       {
-                                               if (replace == 'paragraphs')
-                                               {
-                                                       $(blocks[0]).replaceWith('<blockquote>' + data + '</blockquote>');
-                                                       $(blocks).remove();
-                                               }
-                                               else if (replace == 'blockquote')
-                                               {
-                                                       $(blockquote).replaceWith(data);
-                                               }
-                                               else if (replace == 'html')
+                                               // remove invisible space
+                                               if (this.keyup.current && this.keyup.current.tagName == 'DIV' && this.utils.isEmpty(this.keyup.current.innerHTML))
                                                {
-                                                       var html = this.$editor.html().replace(replaced, '</blockquote>' + data + '<blockquote>');
-
-                                                       this.$editor.html(html);
-                                                       this.$editor.find('blockquote').each(function()
+                                                       if (this.opts.linebreaks)
                                                        {
-                                                               if ($.trim($(this).html()) == '') $(this).remove();
-                                                       })
+                                                               $(this.keyup.current).after(this.selection.getMarkerAsHtml());
+                                                               this.selection.restore();
+                                                               $(this.keyup.current).remove();
+                                                       }
                                                }
+
+                                               // if empty
+                                               return this.keyup.formatEmpty(e);
+                                       }
+                               },
+                               replaceToParagraph: function()
+                               {
+                                       var $current = $(this.keyup.current);
+                                       var node = $('<p>').append($current.clone());
+                                       $current.replaceWith(node);
+                                       var next = $(node).next();
+                                       if (typeof(next[0]) !== 'undefined' && next[0].tagName == 'BR')
+                                       {
+                                               next.remove();
                                        }
-                               }
 
-                               this.selectionRestore();
-                       }
-                       // linebreaks
-                       else
-                       {
-                               var block = this.getBlock();
-                               if (block.tagName === 'BLOCKQUOTE')
+                                       this.caret.setEnd(node);
+                               },
+                               formatEmpty: function(e)
                                {
-                                       this.selectionSave();
+                                       var html = $.trim(this.$editor.html());
 
-                                       var html = $.trim($(block).html());
-                                       var selection = $.trim(this.getSelectionHtml());
+                                       if (!this.utils.isEmpty(html)) return;
 
-                                       html = html.replace(/<span(.*?)id="selection-marker(.*?)<\/span>/gi, '');
+                                       e.preventDefault();
 
-                                       if (html == selection)
+                                       if (this.opts.linebreaks)
                                        {
-                                               $(block).replaceWith($(block).html() + '<br>');
+                                               if (!this.utils.browser('msie')) this.$editor.html('<br />');
+                                               this.focus.setStart();
                                        }
                                        else
                                        {
-                                               // replace
-                                               this.inlineFormat('tmp');
-                                               var tmp = this.$editor.find('tmp');
-                                               tmp.empty();
-
-                                               var newhtml = this.$editor.html().replace('<tmp></tmp>', '</blockquote><span id="selection-marker-1">' + this.opts.invisibleSpace + '</span>' + selection + '<blockquote>');
+                                               html = '<p><br /></p>';
 
-                                               this.$editor.html(newhtml);
-                                               tmp.remove();
-                                               this.$editor.find('blockquote').each(function()
-                                               {
-                                                       if ($.trim($(this).html()) == '') $(this).remove();
-                                               })
+                                               this.$editor.html(html);
+                                               this.focus.setStart();
                                        }
 
-                                       this.selectionRestore();
-                                       this.$editor.find('span#selection-marker-1').attr('id', false);
-                               }
-                               else
-                               {
-                                       var wrapper = this.selectionWrap('blockquote');
-                                       var html = $(wrapper).html();
-
-                                       var blocksElemsRemove = ['ul', 'ol', 'table', 'tr', 'tbody', 'thead', 'tfoot', 'dl'];
-                                       $.each(blocksElemsRemove, function(i,s)
-                                       {
-                                               html = html.replace(new RegExp('<' + s + '(.*?)>', 'gi'), '');
-                                               html = html.replace(new RegExp('</' + s + '>', 'gi'), '');
-                                       });
-
-                                       var blocksElems = this.opts.blockLevelElements;
-                                       $.each(blocksElems, function(i,s)
-                                       {
-                                               html = html.replace(new RegExp('<' + s + '(.*?)>', 'gi'), '');
-                                               html = html.replace(new RegExp('</' + s + '>', 'gi'), '<br>');
-                                       });
+                                       this.code.sync();
 
-                                       $(wrapper).html(html);
-                                       this.selectionElement(wrapper);
-                                       var next = $(wrapper).next();
-                                       if (next.size() != 0 && next[0].tagName === 'BR')
-                                       {
-                                               next.remove();
-                                       }
+                                       return false;
                                }
-                       }
-
-                       this.sync();
-               },
-
-               // BLOCK
-               blockRemoveAttr: function(attr, value)
-               {
-                       var nodes = this.getBlocks();
-                       $(nodes).removeAttr(attr);
-
-                       this.sync();
+                       };
                },
-               blockSetAttr: function(attr, value)
+               shortcuts: function()
                {
-                       var nodes = this.getBlocks();
-                       $(nodes).attr(attr, value);
+                       return {
+                               init: function(e, key)
+                               {
+                                       // disable browser's hot keys for bold and italic
+                                       if (!this.opts.shortcuts)
+                                       {
+                                               if ((e.ctrlKey || e.metaKey) && (key === 66 || key === 73)) e.preventDefault();
+                                               return false;
+                                       }
 
-                       this.sync();
-               },
-               blockRemoveStyle: function(rule)
-               {
-                       var nodes = this.getBlocks();
-                       $(nodes).css(rule, '');
-                       this.removeEmptyAttr(nodes, 'style');
+                                       $.each(this.opts.shortcuts, $.proxy(function(str, command)
+                                       {
+                                               var keys = str.split(',');
+                                               var len = keys.length;
+                                               for (var i = 0; i < len; i++)
+                                               {
+                                                       if (typeof keys[i] === 'string')
+                                                       {
+                                                               this.shortcuts.handler(e, $.trim(keys[i]), $.proxy(function()
+                                                               {
+                                                                       var func;
+                                                                       if (command.func.search(/\./) != '-1')
+                                                                       {
+                                                                               func = command.func.split('.');
+                                                                               if (typeof this[func[0]] != 'undefined')
+                                                                               {
+                                                                                       this[func[0]][func[1]].apply(this, command.params);
+                                                                               }
+                                                                       }
+                                                                       else
+                                                                       {
+                                                                               this[command.func].apply(this, command.params);
+                                                                       }
 
-                       this.sync();
-               },
-               blockSetStyle: function (rule, value)
-               {
-                       var nodes = this.getBlocks();
-                       $(nodes).css(rule, value);
+                                                               }, this));
+                                                       }
 
-                       this.sync();
-               },
-               blockRemoveClass: function(className)
-               {
-                       var nodes = this.getBlocks();
-                       $(nodes).removeClass(className);
-                       this.removeEmptyAttr(nodes, 'class');
+                                               }
 
-                       this.sync();
-               },
-               blockSetClass: function(className)
-               {
-                       var nodes = this.getBlocks();
-                       $(nodes).addClass(className);
+                                       }, this));
+                               },
+                               handler: function(e, keys, origHandler)
+                               {
+                                       // based on https://github.com/jeresig/jquery.hotkeys
+                                       var hotkeysSpecialKeys =
+                                       {
+                                               8: "backspace", 9: "tab", 10: "return", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause",
+                                               20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home",
+                                               37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", 59: ";", 61: "=",
+                                               96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7",
+                                               104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/",
+                                               112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8",
+                                               120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 173: "-", 186: ";", 187: "=",
+                                               188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'"
+                                       };
+
+
+                                       var hotkeysShiftNums =
+                                       {
+                                               "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&",
+                                               "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<",
+                                               ".": ">",  "/": "?",  "\\": "|"
+                                       };
 
-                       this.sync();
-               },
+                                       keys = keys.toLowerCase().split(" ");
+                                       var special = hotkeysSpecialKeys[e.keyCode],
+                                               character = String.fromCharCode( e.which ).toLowerCase(),
+                                               modif = "", possible = {};
 
-               // INLINE
-               inlineRemoveClass: function(className)
-               {
-                       this.selectionSave();
+                                       $.each([ "alt", "ctrl", "meta", "shift"], function(index, specialKey)
+                                       {
+                                               if (e[specialKey + 'Key'] && special !== specialKey)
+                                               {
+                                                       modif += specialKey + '+';
+                                               }
+                                       });
 
-                       this.inlineEachNodes(function(node)
-                       {
-                               $(node).removeClass(className);
-                               this.removeEmptyAttr(node, 'class');
-                       });
 
-                       this.selectionRestore();
-                       this.sync();
-               },
-               inlineSetClass: function(className)
-               {
-                       var current = this.getCurrent();
-                       if (!$(current).hasClass(className)) this.inlineMethods('addClass', className);
-               },
-               inlineRemoveStyle: function (rule)
-               {
-                       this.selectionSave();
+                                       if (special) possible[modif + special] = true;
+                                       if (character)
+                                       {
+                                               possible[modif + character] = true;
+                                               possible[modif + hotkeysShiftNums[character]] = true;
 
-                       this.inlineEachNodes(function(node)
-                       {
-                               $(node).css(rule, '');
-                               this.removeEmptyAttr(node, 'style');
-                       });
+                                               // "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
+                                               if (modif === "shift+")
+                                               {
+                                                       possible[hotkeysShiftNums[character]] = true;
+                                               }
+                                       }
 
-                       this.selectionRestore();
-                       this.sync();
-               },
-               inlineSetStyle: function(rule, value)
-               {
-                       this.inlineMethods('css', rule, value);
+                                       for (var i = 0, len = keys.length; i < len; i++)
+                                       {
+                                               if (possible[keys[i]])
+                                               {
+                                                       e.preventDefault();
+                                                       return origHandler.apply(this, arguments);
+                                               }
+                                       }
+                               }
+                       };
                },
-               inlineRemoveAttr: function (attr)
+               line: function()
                {
-                       this.selectionSave();
-
-                       var range = this.getRange(), node = this.getElement(), nodes = this.getNodes();
+                       return {
+                               insert: function()
+                               {
+                                       this.buffer.set();
 
-                       if (range.collapsed || range.startContainer === range.endContainer && node)
-                       {
-                               nodes = $( node );
-                       }
+                                       var blocks = this.selection.getBlocks();
+                                       if (blocks[0] !== false && this.line.isExceptLastOrFirst(blocks))
+                                       {
+                                               if (!this.utils.browser('msie')) this.$editor.focus();
+                                               return;
+                                       }
 
-                       $(nodes).removeAttr(attr);
+                                       if (this.utils.browser('msie'))
+                                       {
+                                               this.line.insertInIe();
+                                       }
+                                       else
+                                       {
+                                               this.line.insertInOthersBrowsers();
+                                       }
+                               },
+                               isExceptLastOrFirst: function(blocks)
+                               {
+                                       var exceptTags = ['li', 'td', 'th', 'blockquote', 'figcaption', 'pre', 'dl', 'dt', 'dd'];
 
-                       this.inlineUnwrapSpan();
+                                       var first = blocks[0].tagName.toLowerCase();
+                                       var last = this.selection.getLastBlock();
 
-                       this.selectionRestore();
-                       this.sync();
-               },
-               inlineSetAttr: function(attr, value)
-               {
-                       this.inlineMethods('attr', attr, value );
-               },
-               inlineMethods: function(type, attr, value)
-               {
-                       this.bufferSet();
-                       this.selectionSave();
+                                       last = (typeof last == 'undefined') ? first : last.tagName.toLowerCase();
 
-                       var range = this.getRange()
-                       var el = this.getElement();
+                                       var firstFound = $.inArray(first, exceptTags) != -1;
+                                       var lastFound = $.inArray(last, exceptTags) != -1;
 
-                       if ((range.collapsed || range.startContainer === range.endContainer) && el && !this.nodeTestBlocks(el))
-                       {
-                               $(el)[type](attr, value);
-                       }
-                       else
-                       {
-                               var cmd, arg = value;
-                               switch (attr)
-                               {
-                                       case 'font-size':
-                                               cmd = 'fontSize';
-                                               arg = 4;
-                                       break;
-                                       case 'font-family':
-                                               cmd = 'fontName';
-                                       break;
-                                       case 'color':
-                                               cmd = 'foreColor';
-                                       break;
-                                       case 'background-color':
-                                               cmd = 'backColor';
-                                       break;
-                               }
+                                       if ((firstFound && lastFound) || firstFound)
+                                       {
+                                               return true;
+                                       }
+                               },
+                               insertInIe: function()
+                               {
+                                       this.utils.saveScroll();
+                                       this.buffer.set();
 
-                               this.document.execCommand(cmd, false, arg);
+                                       this.insert.node(document.createElement('hr'));
 
-                               var fonts = this.$editor.find('font');
-                               $.each(fonts, $.proxy(function(i, s)
+                                       this.utils.restoreScroll();
+                                       this.code.sync();
+                               },
+                               insertInOthersBrowsers: function()
                                {
-                                       this.inlineSetMethods(type, s, attr, value);
+                                       this.buffer.set();
 
-                               }, this));
+                                       var extra = '<p id="redactor-insert-line"><br /></p>';
+                                       if (this.opts.linebreaks) extra = '<br id="redactor-insert-line">';
 
-                       }
+                                       document.execCommand('insertHTML', false, '<hr>' + extra);
 
-                       this.selectionRestore();
-                       this.sync();
+                                       this.line.setFocus();
+                                       this.code.sync();
+                               },
+                               setFocus: function()
+                               {
+                                       var node = this.$editor.find('#redactor-insert-line');
+                                       var next = $(node).next()[0];
+
+                                       if (next)
+                                       {
+                                               this.caret.setAfter(node);
+                                               node.remove();
+                                       }
+                                       else
+                                       {
+                                               node.removeAttr('id');
+                                       }
+                               }
+                       };
                },
-               inlineSetMethods: function(type, s, attr, value)
+               list: function()
                {
-                       var parent = $(s).parent(), el;
+                       return {
+                               toggle: function(cmd)
+                               {
+                                       if (!this.utils.browser('msie')) this.$editor.focus();
 
-                       var selectionHtml = this.getSelectionText();
-                       var parentHtml = $(parent).text();
-                       var selected = selectionHtml == parentHtml;
+                                       this.buffer.set();
+                                       this.selection.save();
 
-                       if (selected && parent && parent[0].tagName === 'INLINE' && parent[0].attributes.length != 0)
-                       {
-                               el = parent;
-                               $(s).replaceWith($(s).html());
-                       }
-                       else
-                       {
-                               el = $('<inline>').append($(s).contents());
-                               $(s).replaceWith(el);
-                       }
+                                       var parent = this.selection.getParent();
+                                       var $list = $(parent).closest('ol, ul');
 
+                                       if (!this.utils.isRedactorParent($list) && $list.size() !== 0)
+                                       {
+                                               $list = false;
+                                       }
 
-                       $(el)[type](attr, value);
+                                       var isUnorderedCmdOrdered, isOrderedCmdUnordered;
+                                       var remove = false;
+                                       if ($list && $list.length)
+                                       {
+                                               remove = true;
+                                               var listTag = $list[0].tagName;
 
-                       return el;
-               },
-               // Sort elements and execute callback
-               inlineEachNodes: function(callback)
-               {
-                       var range = this.getRange(),
-                               node = this.getElement(),
-                               nodes = this.getNodes(),
-                               collapsed;
+                                               isUnorderedCmdOrdered = (cmd === 'orderedlist' && listTag === 'UL');
+                                               isOrderedCmdUnordered = (cmd === 'unorderedlist' && listTag === 'OL');
+                                       }
 
-                       if (range.collapsed || range.startContainer === range.endContainer && node)
-                       {
-                               nodes = $(node);
-                               collapsed = true;
-                       }
+                                       if (isUnorderedCmdOrdered)
+                                       {
+                                               this.utils.replaceToTag($list, 'ol');
+                                       }
+                                       else if (isOrderedCmdUnordered)
+                                       {
+                                               this.utils.replaceToTag($list, 'ul');
+                                       }
+                                       else
+                                       {
+                                               if (remove)
+                                               {
+                                                       this.list.remove(cmd);
+                                               }
+                                               else
+                                               {
+                                                       this.list.insert(cmd);
+                                               }
+                                       }
 
-                       $.each(nodes, $.proxy(function(i, node)
-                       {
-                               if (!collapsed && node.tagName !== 'INLINE')
+
+                                       this.selection.restore();
+                                       this.code.sync();
+                               },
+                               insert: function(cmd)
                                {
-                                       var selectionHtml = this.getSelectionText();
-                                       var parentHtml = $(node).parent().text();
-                                       var selected = selectionHtml == parentHtml;
+                                       if (this.utils.browser('msie') && this.opts.linebreaks)
+                                       {
+                                               this.list.insertInIe(cmd);
+                                       }
+                                       else
+                                       {
+                                               document.execCommand('insert' + cmd);
+                                       }
+
+                                       var parent = this.selection.getParent();
+                                       var $list = $(parent).closest('ol, ul');
 
-                                       if (selected && node.parentNode.tagName === 'INLINE' && !$(node.parentNode).hasClass('redactor_editor'))
+                                       if (!this.opts.linebreaks && this.utils.isEmpty($list.find('li').text()))
                                        {
-                                               node = node.parentNode;
+                                               var $children = $list.children('li');
+                                               $children.find('br').remove();
+                                               $children.append(this.selection.getMarkerAsHtml());
                                        }
-                                       else return;
-                               }
-                               callback.call(this, node);
 
-                       }, this ) );
-               },
-               inlineUnwrapSpan: function()
-               {
-                       var $spans = this.$editor.find('inline');
+                                       if ($list.length)
+                                       {
+                                               // remove block-element list wrapper
+                                               var $listParent = $list.parent();
+                                               if (this.utils.isRedactorParent($listParent) && $listParent[0].tagName != 'LI' && this.utils.isBlock($listParent[0]))
+                                               {
+                                                       $listParent.replaceWith($listParent.contents());
+                                               }
+                                       }
 
-                       $.each($spans, $.proxy(function(i, span)
-                       {
-                               var $span = $(span);
+                                       if (!this.utils.browser('msie'))
+                                       {
+                                               this.$editor.focus();
+                                       }
 
-                               if ($span.attr('class') === undefined && $span.attr('style') === undefined)
+                                       this.clean.clearUnverified();
+                               },
+                               insertInIe: function(cmd)
                                {
-                                       $span.contents().unwrap();
-                               }
+                                       var wrapper = this.selection.wrap('div');
+                                       var wrapperHtml = $(wrapper).html();
 
-                       }, this));
-               },
-               inlineFormat: function(tag)
-               {
-                       this.selectionSave();
+                                       var tmpList = (cmd == 'orderedlist') ? $('<ol>') : $('<ul>');
+                                       var tmpLi = $('<li>');
 
-                       this.document.execCommand('fontSize', false, 4 );
+                                       if ($.trim(wrapperHtml) === '')
+                                       {
+                                               tmpLi.append(this.selection.getMarkerAsHtml());
+                                               tmpList.append(tmpLi);
+                                               this.$editor.find('#selection-marker-1').replaceWith(tmpList);
+                                       }
+                                       else
+                                       {
+                                               var items = wrapperHtml.split(/<br\s?\/?>/gi);
+                                               if (items)
+                                               {
+                                                       for (var i = 0; i < items.length; i++)
+                                                       {
+                                                               if ($.trim(items[i]) !== '')
+                                                               {
+                                                                       tmpList.append($('<li>').html(items[i]));
+                                                               }
+                                                       }
+                                               }
+                                               else
+                                               {
+                                                       tmpLi.append(wrapperHtml);
+                                                       tmpList.append(tmpLi);
+                                               }
 
-                       var fonts = this.$editor.find('font');
-                       var last;
-                       $.each(fonts, function(i, s)
-                       {
-                               var el = $('<' + tag + '/>').append($(s).contents());
-                               $(s).replaceWith(el);
-                               last = el;
-                       });
+                                               $(wrapper).replaceWith(tmpList);
+                                       }
+                               },
+                               remove: function(cmd)
+                               {
+                                       document.execCommand('insert' + cmd);
 
-                       this.selectionRestore();
+                                       var $current = $(this.selection.getCurrent());
 
-                       this.sync();
-               },
-               inlineRemoveFormat: function(tag)
-               {
-                       this.selectionSave();
+                                       this.indent.fixEmptyIndent();
 
-                       var utag = tag.toUpperCase();
-                       var nodes = this.getNodes();
-                       var parent = $(this.getParent()).parent();
+                                       if (!this.opts.linebreaks && $current.closest('li, th, td').size() === 0)
+                                       {
+                                               document.execCommand('formatblock', false, 'p');
+                                               this.$editor.find('ul, ol, blockquote, p').each($.proxy(this.utils.removeEmpty, this));
+                                       }
 
-                       $.each(nodes, function(i, s)
-                       {
-                               if (s.tagName === utag) this.inlineRemoveFormatReplace(s);
-                       });
+                                       var $table = $(this.selection.getCurrent()).closest('table');
+                                       var $prev = $table.prev();
+                                       if (!this.opts.linebreaks && $table.size() !== 0 && $prev.size() !== 0 && $prev[0].tagName == 'BR')
+                                       {
+                                               $prev.remove();
+                                       }
 
-                       if (parent && parent[0].tagName === utag) this.inlineRemoveFormatReplace(parent);
+                                       this.clean.clearUnverified();
 
-                       this.selectionRestore();
-                       this.sync();
+                               }
+                       };
                },
-               inlineRemoveFormatReplace: function(el)
+               block: function()
                {
-                       $(el).replaceWith($(el).contents());
-               },
+                       return {
+                               formatting: function(name)
+                               {
+                                       var type, value;
 
+                                       if (typeof this.formatting[name].data != 'undefined') type = 'data';
+                                       else if (typeof this.formatting[name].attr != 'undefined') type = 'attr';
+                                       else if (typeof this.formatting[name].class != 'undefined') type = 'class';
 
-               // INSERT
-               insertHtml: function (html, sync)
-               {
-                       var current = this.getCurrent();
-                       var parent = current.parentNode;
+                                       if (type) value = this.formatting[name][type];
 
-                       this.focusWithSaveScroll();
+                                       this.block.format(this.formatting[name].tag, type, value);
 
-                       this.bufferSet();
+                               },
+                               format: function(tag, type, value)
+                               {
+                                       if (tag == 'quote') tag = 'blockquote';
 
-                       var $html = $('<div>').append($.parseHTML(html));
-                       html = $html.html();
+                                       var formatTags = ['p', 'pre', 'blockquote', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
+                                       if ($.inArray(tag, formatTags) == -1) return;
 
-                       html = this.cleanRemoveEmptyTags(html);
+                                       this.block.isRemoveInline = (tag == 'pre' || tag.search(/h[1-6]/i) != -1);
 
-                       // Update value
-                       $html = $('<div>').append($.parseHTML(html));
-                       var currBlock = this.getBlock();
+                                       // focus
+                                       if (!this.utils.browser('msie')) this.$editor.focus();
 
-                       if ($html.contents().length == 1)
-                       {
-                               var htmlTagName = $html.contents()[0].tagName;
+                                       this.block.blocks = this.selection.getBlocks();
 
-                               // If the inserted and received text tags match
-                               if (htmlTagName != 'P' && htmlTagName == currBlock.tagName || htmlTagName == 'PRE')
-                               {
-                                       //html = $html.html();
-                                       $html = $('<div>').append(html);
-                               }
-                       }
+                                       this.block.blocksSize = this.block.blocks.length;
+                                       this.block.type = type;
+                                       this.block.value = value;
 
-                       if (this.opts.linebreaks)
-                       {
-                               html = html.replace(/<p(.*?)>([\w\W]*?)<\/p>/gi, '$2<br>');
-                       }
+                                       this.buffer.set();
+                                       this.selection.save();
 
-                       // add text in a paragraph
-                       if (!this.opts.linebreaks && $html.contents().length == 1 && $html.contents()[0].nodeType == 3
-                               && (this.getRangeSelectedNodes().length > 2 || (!current || current.tagName == 'BODY' && !parent || parent.tagName == 'HTML')))
-                       {
-                               html = '<p>' + html + '</p>';
-                       }
+                                       this.block.set(tag);
 
-                       html = this.setSpansVerifiedHtml(html);
+                                       this.selection.restore();
+                                       this.code.sync();
 
-                       if ($html.contents().length > 1 && currBlock
-                       || $html.contents().is('p, :header, ul, ol, li, div, table, td, blockquote, pre, address, section, header, footer, aside, article'))
-                       {
-                               if (this.browser('msie'))
+                               },
+                               set: function(tag)
                                {
-                                       if (!this.isIe11())
+                                       this.selection.get();
+                                       this.block.containerTag = this.range.commonAncestorContainer.tagName;
+
+                                       if (this.range.collapsed)
                                        {
-                                               this.document.selection.createRange().pasteHTML(html);
+                                               this.block.setCollapsed(tag);
                                        }
                                        else
                                        {
-                                               this.execPasteFrag(html);
+                                               this.block.setMultiple(tag);
                                        }
-                               }
-                               else
-                               {
-                                       this.document.execCommand('inserthtml', false, html);
-                               }
-                       }
-                       else this.insertHtmlAdvanced(html, false);
-
-                       if (this.selectall)
-                       {
-                               this.window.setTimeout($.proxy(function()
+                               },
+                               setCollapsed: function(tag)
                                {
-                                       if (!this.opts.linebreaks) this.selectionEnd(this.$editor.contents().last());
-                                       else this.focusEnd();
+                                       var block = this.block.blocks[0];
+                                       if (block === false) return;
 
-                               }, this), 1);
-                       }
+                                       if (block.tagName == 'LI')
+                                       {
+                                               if (tag != 'blockquote') return;
 
-                       this.observeStart();
+                                               this.block.formatListToBlockquote();
+                                               return;
+                                       }
 
-                       // set no editable
-                       this.setNonEditable();
+                                       var isContainerTable = (this.block.containerTag  == 'TD' || this.block.containerTag  == 'TH');
+                                       if (isContainerTable)
+                                       {
+                                               if (!this.opts.linebreaks && tag == 'p')
+                                               {
+                                                       document.execCommand('formatblock', false, '<' + tag + '>');
 
-                       if (sync !== false) this.sync();
-               },
-               insertHtmlAdvanced: function(html, sync)
-               {
-                       html = this.setSpansVerifiedHtml(html);
+                                                       block = this.selection.getBlock();
+                                                       this.block.toggle($(block));
+                                               }
+                                       }
+                                       else if (block.tagName.toLowerCase() != tag)
+                                       {
+                                               if (this.opts.linebreaks && tag == 'p')
+                                               {
+                                                       $(block).prepend('<br>').append('<br>');
+                                                       this.utils.replaceWithContents(block);
+                                               }
+                                               else
+                                               {
+                                                       var $formatted = this.utils.replaceToTag(block, tag);
 
-                       var sel = this.getSelection();
+                                                       this.block.toggle($formatted);
 
-                       if (sel.getRangeAt && sel.rangeCount)
-                       {
-                               var range = sel.getRangeAt(0);
-                               range.deleteContents();
+                                                       if (tag != 'p' && tag != 'blockquote') $formatted.find('img').remove();
+                                                       if (this.block.isRemoveInline) this.utils.removeInlineTags($formatted);
+                                                       if (tag == 'p' || this.block.headTag) $formatted.find('p').contents().unwrap();
+                                               }
+                                       }
+                                       else if (tag == 'blockquote' && block.tagName.toLowerCase() == tag)
+                                       {
+                                               // blockquote off
+                                               if (this.opts.linebreaks)
+                                               {
+                                                       $(block).prepend('<br>').append('<br>');
+                                                       this.utils.replaceWithContents(block);
+                                               }
+                                               else
+                                               {
+                                                       var $el = this.utils.replaceToTag(block, 'p');
+                                                       this.block.toggle($el);
+                                               }
+                                       }
+                                       else if (block.tagName.toLowerCase() == tag)
+                                       {
+                                               this.block.toggle($(block));
+                                       }
 
-                               var el = document.createElement('div');
-                               el.innerHTML = html;
-                               var frag = document.createDocumentFragment(), node, lastNode;
-                               while ((node = el.firstChild))
+                               },
+                               setMultiple: function(tag)
                                {
-                                       lastNode = frag.appendChild(node);
-                               }
+                                       var block = this.block.blocks[0];
+                                       var isContainerTable = (this.block.containerTag  == 'TD' || this.block.containerTag  == 'TH');
 
-                               range.insertNode(frag);
+                                       if (block !== false && this.block.blocksSize === 1)
+                                       {
+                                               if (block.tagName.toLowerCase() == tag &&  tag == 'blockquote')
+                                               {
+                                                       // blockquote off
+                                                       if (this.opts.linebreaks)
+                                                       {
+                                                               $(block).prepend('<br>').append('<br>');
+                                                               this.utils.replaceWithContents(block);
+                                                       }
+                                                       else
+                                                       {
+                                                               var $el = this.utils.replaceToTag(block, 'p');
+                                                               this.block.toggle($el);
+                                                       }
+                                               }
+                                               else if (block.tagName == 'LI')
+                                               {
+                                                       if (tag != 'blockquote') return;
 
-                               if (lastNode)
-                               {
-                                       range = range.cloneRange();
-                                       range.setStartAfter(lastNode);
-                                       range.collapse(true);
-                                       sel.removeAllRanges();
-                                       sel.addRange(range);
-                               }
-                       }
+                                                       this.block.formatListToBlockquote();
+                                               }
+                                               else if (this.block.containerTag == 'BLOCKQUOTE')
+                                               {
+                                                       this.block.formatBlockquote(tag);
+                                               }
+                                               else if (this.opts.linebreaks && ((isContainerTable) || (this.range.commonAncestorContainer != block)))
+                                               {
+                                                       this.block.formatWrap(tag);
+                                               }
+                                               else
+                                               {
+                                                       if (this.opts.linebreaks && tag == 'p')
+                                                       {
+                                                               $(block).prepend('<br>').append('<br>');
+                                                               this.utils.replaceWithContents(block);
+                                                       }
+                                                       else
+                                                       {
 
-                       if (sync !== false)
-                       {
-                               this.sync();
-                       }
+                                                               var $formatted = this.utils.replaceToTag(block, tag);
 
-               },
-               insertBeforeCursor: function(html)
-               {
-                       html = this.setSpansVerifiedHtml(html);
+                                                               this.block.toggle($formatted);
 
-                       var node = $(html);
+                                                               if (this.block.isRemoveInline) this.utils.removeInlineTags($formatted);
+                                                               if (tag == 'p' || this.block.headTag) $formatted.find('p').contents().unwrap();
+                                                       }
+                                               }
+                                       }
+                                       else
+                                       {
+                                               if (this.opts.linebreaks || tag != 'p')
+                                               {
+                                                       if (tag == 'blockquote')
+                                                       {
+                                                               var count = 0;
+                                                               for (var i = 0; i < this.block.blocksSize; i++)
+                                                               {
+                                                                       if (this.block.blocks[i].tagName == 'BLOCKQUOTE') count++;
+                                                               }
+
+                                                               // only blockquote selected
+                                                               if (count == this.block.blocksSize)
+                                                               {
+                                                                       $.each(this.block.blocks, $.proxy(function(i,s)
+                                                                       {
+                                                                               if (this.opts.linebreaks)
+                                                                               {
+                                                                                       $(s).prepend('<br>').append('<br>');
+                                                                                       this.utils.replaceWithContents(s);
+                                                                               }
+                                                                               else
+                                                                               {
+                                                                                       this.utils.replaceToTag(s, 'p');
+                                                                               }
+
+                                                                       }, this));
+
+                                                                       return;
+                                                               }
+
+                                                       }
 
-                       var space = document.createElement("span");
-                       space.innerHTML = "\u200B";
+                                                       this.block.formatWrap(tag);
+                                               }
+                                               else
+                                               {
+                                                       var classSize = 0;
+                                                       var toggleType = false;
+                                                       if (this.block.type == 'class')
+                                                       {
+                                                               toggleType = 'toggle';
+                                                               classSize = $(this.block.blocks).filter('.' + this.block.value).size();
 
-                       var range = this.getRange();
-                       range.insertNode(space);
-                       range.insertNode(node[0]);
-                       range.collapse(false);
+                                                               if (this.block.blocksSize == classSize) toggleType = 'toggle';
+                                                               else if (this.block.blocksSize > classSize) toggleType = 'set';
+                                                               else if (classSize === 0) toggleType = 'set';
 
-                       var sel = this.getSelection();
-                       sel.removeAllRanges();
-                       sel.addRange(range);
+                                                       }
 
-                       this.sync();
-               },
-               insertText: function(html)
-               {
-                       var $html = $($.parseHTML(html));
+                                                       var exceptTags = ['ul', 'ol', 'li', 'td', 'th', 'dl', 'dt', 'dd'];
+                                                       $.each(this.block.blocks, $.proxy(function(i,s)
+                                                       {
+                                                               if ($.inArray(s.tagName.toLowerCase(), exceptTags) != -1) return;
 
-                       if ($html.length) html = $html.text();
+                                                               var $formatted = this.utils.replaceToTag(s, tag);
 
-                       this.focusWithSaveScroll();
+                                                               if (toggleType)
+                                                               {
+                                                                       if (toggleType == 'toggle') this.block.toggle($formatted);
+                                                                       else if (toggleType == 'remove') this.block.remove($formatted);
+                                                                       else if (toggleType == 'set')  this.block.set2($formatted);
+                                                               }
+                                                               else this.block.toggle($formatted);
 
-                       if (this.browser('msie'))
-                       {
-                               if (!this.isIe11())
-                               {
-                                       this.document.selection.createRange().pasteHTML(html);
-                               }
-                               else
-                               {
-                                       this.execPasteFrag(html);
-                               }
-                       }
-                       else
-                       {
-                               this.document.execCommand('inserthtml', false, html);
-                       }
+                                                               if (tag != 'p' && tag != 'blockquote') $formatted.find('img').remove();
+                                                               if (this.block.isRemoveInline) this.utils.removeInlineTags($formatted);
+                                                               if (tag == 'p' || this.block.headTag) $formatted.find('p').contents().unwrap();
 
-                       this.sync();
-               },
-               insertNode: function(node)
-               {
-                       node = node[0] || node;
 
-                       if (node.tagName == 'SPAN')
-                       {
-                               var replacementTag = 'inline';
+                                                       }, this));
+                                               }
+                                       }
+                               },
+                               toggle: function($el)
+                               {
+                                       if (this.block.type == 'class')
+                                       {
+                                               $el.toggleClass(this.block.value);
+                                               return;
+                                       }
+                                       else if (this.block.type == 'attr' || this.block.type == 'data')
+                                       {
+                                               if ($el.attr(this.block.value.name) == this.block.value.value)
+                                               {
+                                                       $el.removeAttr(this.block.value.name);
+                                               }
+                                               else
+                                               {
+                                                       $el.attr(this.block.value.name, this.block.value.value);
+                                               }
 
-                           var outer = node.outerHTML;
+                                               return;
+                                       }
+                                       else
+                                       {
+                                               $el.removeAttr('style class');
+                                               return;
+                                       }
+                               },
+                               remove: function($el)
+                               {
+                                       $el.removeClass(this.block.value);
+                               },
+                               formatListToBlockquote: function()
+                               {
+                                       var block = $(this.block.blocks[0]).closest('ul, ol');
 
-                           // Replace opening tag
-                           var regex = new RegExp('<' + node.tagName, 'i');
-                           var newTag = outer.replace(regex, '<' + replacementTag);
+                                       $(block).find('ul, ol').contents().unwrap();
+                                       $(block).find('li').append($('<br>')).contents().unwrap();
 
-                           // Replace closing tag
-                           regex = new RegExp('</' + node.tagName, 'i');
-                           newTag = newTag.replace(regex, '</' + replacementTag);
-                           node = $(newTag)[0];
-                       }
+                                       var $el = this.utils.replaceToTag(block, 'blockquote');
+                                       this.block.toggle($el);
+                               },
+                               formatBlockquote: function(tag)
+                               {
+                                       document.execCommand('outdent');
+                                       document.execCommand('formatblock', false, tag);
 
-                       var sel = this.getSelection();
-                       if (sel.getRangeAt && sel.rangeCount)
-                       {
-                               // with delete contents
-                               range = sel.getRangeAt(0);
-                               range.deleteContents();
-                               range.insertNode(node);
-                               range.setEndAfter(node);
-                               range.setStartAfter(node);
-                               sel.removeAllRanges();
-                               sel.addRange(range);
-                       }
+                                       this.clean.clearUnverified();
+                                       this.$editor.find('p:empty').remove();
 
-                       return node;
-               },
-               insertNodeToCaretPositionFromPoint: function(e, node)
-               {
-                       var range;
-                       var x = e.clientX, y = e.clientY;
-                       if (this.document.caretPositionFromPoint)
-                       {
-                           var pos = this.document.caretPositionFromPoint(x, y);
-                           range = this.getRange();
-                           range.setStart(pos.offsetNode, pos.offset);
-                           range.collapse(true);
-                           range.insertNode(node);
-                       }
-                       else if (this.document.caretRangeFromPoint)
-                       {
-                           range = this.document.caretRangeFromPoint(x, y);
-                           range.insertNode(node);
-                       }
-                       else if (typeof document.body.createTextRange != "undefined")
-                       {
-                       range = this.document.body.createTextRange();
-                       range.moveToPoint(x, y);
-                       var endRange = range.duplicate();
-                       endRange.moveToPoint(x, y);
-                       range.setEndPoint("EndToEnd", endRange);
-                       range.select();
-                       }
+                                       var formatted = this.selection.getBlock();
 
-               },
-               insertAfterLastElement: function(element, parent)
-               {
-                       if (typeof(parent) != 'undefined') element = parent;
+                                       if (tag != 'p')
+                                       {
+                                               $(formatted).find('img').remove();
+                                       }
 
-                       if (this.isEndOfElement())
-                       {
-                               if (this.opts.linebreaks)
-                               {
-                                       var contents = $('<div>').append($.trim(this.$editor.html())).contents();
-                                       var last = contents.last()[0];
-                                       if (last.tagName == 'SPAN' && last.innerHTML == '')
+                                       if (!this.opts.linebreaks)
                                        {
-                                               last = contents.prev()[0];
+                                               this.block.toggle($(formatted));
                                        }
 
-                                       if (this.outerHtml(last) != this.outerHtml(element))
+                                       this.$editor.find('ul, ol, tr, blockquote, p').each($.proxy(this.utils.removeEmpty, this));
+
+                                       if (this.opts.linebreaks && tag == 'p')
                                        {
-                                               return false;
+                                               this.utils.replaceWithContents(formatted);
                                        }
-                               }
-                               else
+
+                               },
+                               formatWrap: function(tag)
                                {
-                                       if (this.$editor.contents().last()[0] !== element)
+                                       if (this.block.containerTag == 'UL' || this.block.containerTag == 'OL')
                                        {
-                                               return false;
+                                               if (tag == 'blockquote')
+                                               {
+                                                       this.block.formatListToBlockquote();
+                                               }
+                                               else
+                                               {
+                                                       return;
+                                               }
                                        }
-                               }
 
-                               this.insertingAfterLastElement(element);
-                       }
-               },
-               insertingAfterLastElement: function(element)
-               {
-                       this.bufferSet();
+                                       var formatted = this.selection.wrap(tag);
+                                       if (formatted === false) return;
 
-                       if (this.opts.linebreaks === false)
-                       {
-                               var node = $(this.opts.emptyHtml);
-                               $(element).after(node);
-                               this.selectionStart(node);
-                       }
-                       else
-                       {
-                               var node = $('<span id="selection-marker-1">' + this.opts.invisibleSpace + '</span>', this.document)[0];
-                               $(element).after(node);
-                               $(node).after(this.opts.invisibleSpace);
-                               this.selectionRestore();
-                               this.$editor.find('span#selection-marker-1').removeAttr('id');
-                       }
-               },
-               insertLineBreak: function(twice)
-               {
-                       this.selectionSave();
+                                       var $formatted = $(formatted);
 
-                       var br = '<br>';
-                       if (twice == true)
-                       {
-                               br = '<br><br>';
-                       }
+                                       this.block.formatTableWrapping($formatted);
 
-                       if (this.browser('mozilla'))
-                       {
-                               var span = $('<span>').html(this.opts.invisibleSpace);
-                               this.$editor.find('#selection-marker-1').before(br).before(span).before(this.opts.invisibleSpace);
+                                       var $elements = $formatted.find(this.opts.blockLevelElements.join(',') + ', td, table, thead, tbody, tfoot, th, tr');
+
+                                       if ((this.opts.linebreaks && tag == 'p') || tag == 'pre' || tag == 'blockquote')
+                                       {
+                                               $elements.append('<br />');
+                                       }
 
-                               this.setCaretAfter(span[0]);
-                               span.remove();
+                                       $elements.contents().unwrap();
 
-                               this.selectionRemoveMarkers();
-                       }
-                       else
-                       {
-                               var parent = this.getParent();
-                               if (parent && parent.tagName === 'A')
-                               {
-                                       var offset = this.getCaretOffset(parent);
+                                       if (tag != 'p' && tag != 'blockquote') $formatted.find('img').remove();
 
-                                       var text = $.trim($(parent).text()).replace(/\n\r\n/g, '');
-                                       var len = text.length;
+                                       $.each(this.block.blocks, $.proxy(this.utils.removeEmpty, this));
 
-                                       if (offset == len)
+                                       $formatted.append(this.selection.getMarker(2));
+
+                                       if (!this.opts.linebreaks)
                                        {
-                                               this.selectionRemoveMarkers();
+                                               this.block.toggle($formatted);
+                                       }
 
-                                               var node = $('<span id="selection-marker-1">' + this.opts.invisibleSpace + '</span>', this.document)[0];
-                                               $(parent).after(node);
-                                               $(node).before(br + (this.browser('webkit') ? this.opts.invisibleSpace : ''));
-                                               this.selectionRestore();
+                                       this.$editor.find('ul, ol, tr, blockquote, p').each($.proxy(this.utils.removeEmpty, this));
+                                       $formatted.find('blockquote:empty').remove();
 
-                                               return true;
+                                       if (this.block.isRemoveInline)
+                                       {
+                                               this.utils.removeInlineTags($formatted);
                                        }
 
-                               }
+                                       if (this.opts.linebreaks && tag == 'p')
+                                       {
+                                               this.utils.replaceWithContents($formatted);
+                                       }
 
-                               this.$editor.find('#selection-marker-1').before(br + (this.browser('webkit') ? this.opts.invisibleSpace : ''));
-                               this.selectionRestore();
-                       }
-               },
-               insertDoubleLineBreak: function()
-               {
-                       this.insertLineBreak(true);
-               },
-               replaceLineBreak: function(element)
-               {
-                       var node = $('<br>' + this.opts.invisibleSpace);
-                       $(element).replaceWith(node);
-                       this.selectionStart(node);
-               },
+                               },
+                               formatTableWrapping: function($formatted)
+                               {
+                                       if ($formatted.closest('table').size() === 0) return;
 
-               // PASTE
-               pasteClean: function(html)
-               {
-                       html = this.callback('pasteBefore', false, html);
+                                       if ($formatted.closest('tr').size() === 0) $formatted.wrap('<tr>');
+                                       if ($formatted.closest('td').size() === 0) $formatted.wrap('<td>');
+                               },
+                               removeData: function(name, value)
+                               {
+                                       var blocks = this.selection.getBlocks();
+                                       $(blocks).removeAttr('data-' + name);
 
-                       // ie10 fix paste links
-                       if (this.browser('msie'))
-                       {
-                               var tmp = $.trim(html);
-                               if (tmp.search(/^<a(.*?)>(.*?)<\/a>$/i) == 0)
+                                       this.code.sync();
+                               },
+                               setData: function(name, value)
                                {
-                                       html = html.replace(/^<a(.*?)>(.*?)<\/a>$/i, "$2");
-                               }
-                       }
+                                       var blocks = this.selection.getBlocks();
+                                       $(blocks).attr('data-' + name, value);
 
-                       if (this.opts.pastePlainText)
-                       {
-                               var tmp = this.document.createElement('div');
+                                       this.code.sync();
+                               },
+                               toggleData: function(name, value)
+                               {
+                                       var blocks = this.selection.getBlocks();
+                                       $.each(blocks, function()
+                                       {
+                                               if ($(this).attr('data-' + name))
+                                               {
+                                                       $(this).removeAttr('data-' + name);
+                                               }
+                                               else
+                                               {
+                                                       $(this).attr('data-' + name, value);
+                                               }
+                                       });
+                               },
+                               removeAttr: function(attr, value)
+                               {
+                                       var blocks = this.selection.getBlocks();
+                                       $(blocks).removeAttr(attr);
 
-                               html = html.replace(/<br>|<\/H[1-6]>|<\/p>|<\/div>/gi, '\n');
+                                       this.code.sync();
+                               },
+                               setAttr: function(attr, value)
+                               {
+                                       var blocks = this.selection.getBlocks();
+                                       $(blocks).attr(attr, value);
 
-                               tmp.innerHTML = html;
-                               html = tmp.textContent || tmp.innerText;
+                                       this.code.sync();
+                               },
+                               toggleAttr: function(attr, value)
+                               {
+                                       var blocks = this.selection.getBlocks();
+                                       $.each(blocks, function()
+                                       {
+                                               if ($(this).attr(name))
+                                               {
+                                                       $(this).removeAttr(name);
+                                               }
+                                               else
+                                               {
+                                                       $(this).attr(name, value);
+                                               }
+                                       });
+                               },
+                               removeClass: function(className)
+                               {
+                                       var blocks = this.selection.getBlocks();
+                                       $(blocks).removeClass(className);
 
-                               html = $.trim(html);
-                               html = html.replace('\n', '<br>');
-                               html = this.cleanParagraphy(html);
+                                       this.utils.removeEmptyAttr(blocks, 'class');
 
-                               this.pasteInsert(html);
-                               return false;
-                       }
+                                       this.code.sync();
+                               },
+                               setClass: function(className)
+                               {
+                                       var blocks = this.selection.getBlocks();
+                                       $(blocks).addClass(className);
 
-                       // clean up table
-                       var tablePaste = false;
-                       if (this.currentOrParentIs('TD'))
-                       {
-                               tablePaste = true;
-                               var blocksElems = this.opts.blockLevelElements;
-                               blocksElems.push('tr');
-                               blocksElems.push('table');
-                               $.each(blocksElems, function(i,s)
-                               {
-                                       html = html.replace(new RegExp('<' + s + '(.*?)>', 'gi'), '');
-                                       html = html.replace(new RegExp('</' + s + '>', 'gi'), '<br>');
-                               });
-                       }
+                                       this.code.sync();
+                               },
+                               toggleClass: function(className)
+                               {
+                                       var blocks = this.selection.getBlocks();
+                                       $(blocks).toggleClass(className);
 
-                       // clean up pre
-                       if (this.currentOrParentIs('PRE'))
-                       {
-                               html = this.pastePre(html);
-                               this.pasteInsert(html);
-                               return true;
-                       }
+                                       this.code.sync();
+                               }
+                       };
+               },
+               inline: function()
+               {
+                       return {
+                               formatting: function(name)
+                               {
+                                       var type, value;
 
-                       // ms words shapes
-                       html = html.replace(/<img(.*?)v:shapes=(.*?)>/gi, '');
+                                       if (typeof this.formatting[name].style != 'undefined') type = 'style';
+                                       else if (typeof this.formatting[name].class != 'undefined') type = 'class';
 
-                       // ms word list
-                       html = html.replace(/<p(.*?)class="MsoListParagraphCxSpFirst"([\w\W]*?)<\/p>/gi, '<ul><li$2</li>');
-                       html = html.replace(/<p(.*?)class="MsoListParagraphCxSpMiddle"([\w\W]*?)<\/p>/gi, '<li$2</li>');
-                       html = html.replace(/<p(.*?)class="MsoListParagraphCxSpLast"([\w\W]*?)<\/p>/gi, '<li$2</li></ul>');
-                       // one line
-                       html = html.replace(/<p(.*?)class="MsoListParagraph"([\w\W]*?)<\/p>/gi, '<ul><li$2</li></ul>');
-                       // remove ms word's bullet
-                       html = html.replace(/·/g, '');
+                                       if (type) value = this.formatting[name][type];
 
-                       // remove comments and php tags
-                       html = html.replace(/<!--[\s\S]*?-->|<\?(?:php)?[\s\S]*?\?>/gi, '');
+                                       this.inline.format(this.formatting[name].tag, type, value);
 
-                       // remove nbsp
-                       if (this.opts.cleanSpaces === true)
-                       {
-                               html = html.replace(/(&nbsp;){2,}/gi, '&nbsp;');
-                               html = html.replace(/&nbsp;/gi, ' ');
-                       }
+                               },
+                               format: function(tag, type, value)
+                               {
+                                       // Stop formatting pre
+                                       if (this.utils.isCurrentOrParent('PRE')) return;
 
-                       // remove google docs marker
-                       html = html.replace(/<b\sid="internal-source-marker(.*?)">([\w\W]*?)<\/b>/gi, "$2");
-                       html = html.replace(/<b(.*?)id="docs-internal-guid(.*?)">([\w\W]*?)<\/b>/gi, "$3");
+                                       var tags = ['b', 'bold', 'i', 'italic', 'underline', 'strikethrough', 'deleted', 'superscript', 'subscript'];
+                                       var replaced = ['strong', 'strong', 'em', 'em', 'u', 'del', 'del', 'sup', 'sub'];
 
+                                       for (var i = 0; i < tags.length; i++)
+                                       {
+                                               if (tag == tags[i]) tag = replaced[i];
+                                       }
 
-                       html = html.replace(/<span[^>]*(font-style: italic; font-weight: bold|font-weight: bold; font-style: italic)[^>]*>/gi, '<span style="font-weight: bold;"><span style="font-style: italic;">');
-                       html = html.replace(/<span[^>]*font-style: italic[^>]*>/gi, '<span style="font-style: italic;">');
-                       html = html.replace(/<span[^>]*font-weight: bold[^>]*>/gi, '<span style="font-weight: bold;">');
-                       html = html.replace(/<span[^>]*text-decoration: underline[^>]*>/gi, '<span style="text-decoration: underline;">');
+                                       this.inline.type = type || false;
+                                       this.inline.value = value || false;
 
-                       // strip tags
-                       //html = this.cleanStripTags(html);
+                                       this.buffer.set();
+                                       this.$editor.focus();
 
+                                       this.selection.get();
 
+                                       if (this.range.collapsed)
+                                       {
+                                               this.inline.formatCollapsed(tag);
+                                       }
+                                       else
+                                       {
+                                               this.inline.formatMultiple(tag);
+                                       }
+                               },
+                               formatCollapsed: function(tag)
+                               {
+                                       var current = this.selection.getCurrent();
+                                       var $parent = $(current).closest(tag + '[data-redactor-tag=' + tag + ']');
 
-                       // prevert
-                       html = html.replace(/<td>\u200b*<\/td>/gi, '[td]');
-                       html = html.replace(/<td>&nbsp;<\/td>/gi, '[td]');
-                       html = html.replace(/<td><br><\/td>/gi, '[td]');
-                       html = html.replace(/<td(.*?)colspan="(.*?)"(.*?)>([\w\W]*?)<\/td>/gi, '[td colspan="$2"]$4[/td]');
-                       html = html.replace(/<td(.*?)rowspan="(.*?)"(.*?)>([\w\W]*?)<\/td>/gi, '[td rowspan="$2"]$4[/td]');
-                       html = html.replace(/<a(.*?)href="(.*?)"(.*?)>([\w\W]*?)<\/a>/gi, '[a href="$2"]$4[/a]');
-                       html = html.replace(/<iframe(.*?)>([\w\W]*?)<\/iframe>/gi, '[iframe$1]$2[/iframe]');
-                       html = html.replace(/<video(.*?)>([\w\W]*?)<\/video>/gi, '[video$1]$2[/video]');
-                       html = html.replace(/<audio(.*?)>([\w\W]*?)<\/audio>/gi, '[audio$1]$2[/audio]');
-                       html = html.replace(/<embed(.*?)>([\w\W]*?)<\/embed>/gi, '[embed$1]$2[/embed]');
-                       html = html.replace(/<object(.*?)>([\w\W]*?)<\/object>/gi, '[object$1]$2[/object]');
-                       html = html.replace(/<param(.*?)>/gi, '[param$1]');
+                                       // inline there is
+                                       if ($parent.size() !== 0)
+                                       {
+                                               this.caret.setAfter($parent[0]);
 
-                       html = html.replace(/<img(.*?)>/gi, '[img$1]');
+                                               // remove empty
+                                               if (this.utils.isEmpty($parent.text())) $parent.remove();
 
-                       // remove classes
-                       html = html.replace(/ class="(.*?)"/gi, '');
+                                               this.code.sync();
 
-                       // remove all attributes
-                       html = html.replace(/<(\w+)([\w\W]*?)>/gi, '<$1>');
+                                               return;
+                                       }
 
-                       // remove empty
-                       if (this.opts.linebreaks)
-                       {
-                               // prevent double linebreaks when an empty line in RTF has bold or underlined formatting associated with it
-                               html = html.replace(/<strong><\/strong>/gi, '');
-                               html = html.replace(/<u><\/u>/gi, '');
+                                       // create empty inline
+                                       var node = $('<' + tag + '>').attr('data-verified', 'redactor').attr('data-redactor-tag', tag);
+                                       node.html(this.opts.invisibleSpace);
 
-                               if (this.opts.cleanFontTag)
-                               {
-                                       html = html.replace(/<font(.*?)>([\w\W]*?)<\/font>/gi, '$2');
-                               }
+                                       node = this.inline.setFormat(node);
 
-                               html = html.replace(/<[^\/>][^>]*>(\s*|\t*|\n*|&nbsp;|<br>)<\/[^>]+>/gi, '<br>');
-                       }
-                       else
-                       {
-                               html = html.replace(/<[^\/>][^>]*>(\s*|\t*|\n*|&nbsp;|<br>)<\/[^>]+>/gi, '');
-                       }
+                                       this.insert.node(node);
+                                       this.caret.setEnd(node);
 
-                       html = html.replace(/<div>\s*?\t*?\n*?(<ul>|<ol>|<p>)/gi, '$1');
-
-                       // revert
-                       html = html.replace(/\[td colspan="(.*?)"\]([\w\W]*?)\[\/td\]/gi, '<td colspan="$1">$2</td>');
-                       html = html.replace(/\[td rowspan="(.*?)"\]([\w\W]*?)\[\/td\]/gi, '<td rowspan="$1">$2</td>');
-                       html = html.replace(/\[td\]/gi, '<td>&nbsp;</td>');
-                       html = html.replace(/\[a href="(.*?)"\]([\w\W]*?)\[\/a\]/gi, '<a href="$1">$2</a>');
-                       html = html.replace(/\[iframe(.*?)\]([\w\W]*?)\[\/iframe\]/gi, '<iframe$1>$2</iframe>');
-                       html = html.replace(/\[video(.*?)\]([\w\W]*?)\[\/video\]/gi, '<video$1>$2</video>');
-                       html = html.replace(/\[audio(.*?)\]([\w\W]*?)\[\/audio\]/gi, '<audio$1>$2</audio>');
-                       html = html.replace(/\[embed(.*?)\]([\w\W]*?)\[\/embed\]/gi, '<embed$1>$2</embed>');
-                       html = html.replace(/\[object(.*?)\]([\w\W]*?)\[\/object\]/gi, '<object$1>$2</object>');
-                       html = html.replace(/\[param(.*?)\]/gi, '<param$1>');
-                       html = html.replace(/\[img(.*?)\]/gi, '<img$1>');
-
-                       // convert div to p
-                       if (this.opts.convertDivs)
-                       {
-                               html = html.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '<p>$2</p>');
-                               html = html.replace(/<\/div><p>/gi, '<p>');
-                               html = html.replace(/<\/p><\/div>/gi, '</p>');
-                               html = html.replace(/<p><\/p>/gi, '<br />');
-                       }
-                       else
-                       {
-                               html = html.replace(/<div><\/div>/gi, '<br />');
-                       }
+                                       this.code.sync();
 
-                       // strip tags
-                       html = this.cleanStripTags(html);
+                                       return;
+                               },
+                               formatMultiple: function(tag)
+                               {
+                                       this.inline.formatConvert(tag);
 
-                       if (this.currentOrParentIs('LI'))
-                       {
-                               html = html.replace(/<p>([\w\W]*?)<\/p>/gi, '$1<br>');
-                       }
-                       else if (tablePaste === false)
-                       {
-                               html = this.cleanParagraphy(html);
-                       }
+                                       this.selection.save();
+                                       document.execCommand('strikethrough');
 
-                       // remove span
-                       html = html.replace(/<span(.*?)>([\w\W]*?)<\/span>/gi, '$2');
+                                       this.$editor.find('strike').each($.proxy(function(i,s)
+                                       {
+                                               var $el = $(s);
 
-                       // remove empty
-                       html = html.replace(/<img>/gi, '');
-                       html = html.replace(/<[^\/>][^>][^img|param|source|td][^<]*>(\s*|\t*|\n*| |<br>)<\/[^>]+>/gi, '');
+                                               this.inline.formatRemoveSameChildren($el, tag);
 
-                       html = html.replace(/\n{3,}/gi, '\n');
+                                               var $span;
+                                               if (this.inline.type)
+                                               {
+                                                       $span = $('<span>').attr('data-redactor-tag', tag).attr('data-verified', 'redactor');
+                                                       $span = this.inline.setFormat($span);
+                                               }
+                                               else
+                                               {
+                                                       $span = $('<' + tag + '>').attr('data-redactor-tag', tag).attr('data-verified', 'redactor');
+                                               }
 
-                       // remove dirty p
-                       html = html.replace(/<p><p>/gi, '<p>');
-                       html = html.replace(/<\/p><\/p>/gi, '</p>');
+                                               $el.replaceWith($span.html($el.contents()));
 
-                       html = html.replace(/<li>(\s*|\t*|\n*)<p>/gi, '<li>');
-                       html = html.replace(/<\/p>(\s*|\t*|\n*)<\/li>/gi, '</li>');
+                                               if (tag == 'span')
+                                               {
+                                                       var $parent = $span.parent();
+                                                       if ($parent && $parent[0].tagName == 'SPAN' && this.inline.type == 'style')
+                                                       {
+                                                               var arr = this.inline.value.split(';');
 
-                       if (this.opts.linebreaks === true)
-                       {
-                               html = html.replace(/<p(.*?)>([\w\W]*?)<\/p>/gi, '$2<br>');
-                       }
+                                                               for (var z = 0; z < arr.length; z++)
+                                                               {
+                                                                       if (arr[z] === '') return;
+                                                                       var style = arr[z].split(':');
+                                                                       $parent.css(style[0], '');
 
-                       // remove empty finally
-                       html = html.replace(/<[^\/>][^>][^img|param|source|td][^<]*>(\s*|\t*|\n*| |<br>)<\/[^>]+>/gi, '');
+                                                                       if (this.utils.removeEmptyAttr($parent, 'style'))
+                                                                       {
+                                                                               $parent.replaceWith($parent.contents());
+                                                                       }
 
-                       // remove safari local images
-                       html = html.replace(/<img src="webkit-fake-url\:\/\/(.*?)"(.*?)>/gi, '');
+                                                               }
 
-                       // remove p in td
-                       html = html.replace(/<td(.*?)>(\s*|\t*|\n*)<p>([\w\W]*?)<\/p>(\s*|\t*|\n*)<\/td>/gi, '<td$1>$3</td>');
+                                                       }
+                                               }
 
-                       // remove divs
-                       if (this.opts.convertDivs)
-                       {
-                               html = html.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '$2');
-                               html = html.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '$2');
-                       }
+                                       }, this));
 
-                       // FF specific
-                       this.pasteClipboardMozilla = false;
-                       if (this.browser('mozilla'))
-                       {
-                               if (this.opts.clipboardUpload)
-                               {
-                                       var matches = html.match(/<img src="data:image(.*?)"(.*?)>/gi);
-                                       if (matches !== null)
+                                       if (tag != 'del')
                                        {
-                                               this.pasteClipboardMozilla = matches;
-                                               for (k in matches)
+                                               var self = this;
+                                               this.$editor.find('inline').each(function(i,s)
                                                {
-                                                       var img = matches[k].replace('<img', '<img data-mozilla-paste-image="' + k + '" ');
-                                                       html = html.replace(matches[k], img);
-                                               }
+                                                       self.utils.replaceToTag(s, 'del');
+                                               });
                                        }
-                               }
-
-                               // FF fix
-                               while (/<br>$/gi.test(html))
-                               {
-                                       html = html.replace(/<br>$/gi, '');
-                               }
-                       }
 
-                       // bullets again
-                       html = html.replace(/<p>•([\w\W]*?)<\/p>/gi, '<li>$1</li>');
+                                       this.selection.restore();
+                                       this.code.sync();
 
-                       // ie inserts a blank font tags when pasting
-                       if (this.browser('msie'))
-                       {
-                               while (/<font>([\w\W]*?)<\/font>/gi.test(html))
+                               },
+                               formatRemoveSameChildren: function($el, tag)
                                {
-                                       html = html.replace(/<font>([\w\W]*?)<\/font>/gi, '$1');
-                               }
-                       }
+                                       $el.children(tag).each(function()
+                                       {
+                                               var $child = $(this);
+                                               if (!$child.hasClass('redactor-selection-marker'))
+                                               {
+                                                       $child.contents().unwrap();
+                                               }
+                                       });
+                               },
+                               formatConvert: function(tag)
+                               {
+                                       this.selection.save();
 
-                       // remove table paragraphs
-                       if (tablePaste === false)
-                       {
-                               html = html.replace(/<td(.*?)>([\w\W]*?)<p(.*?)>([\w\W]*?)<\/td>/gi, '<td$1>$2$4</td>');
-                               html = html.replace(/<td(.*?)>([\w\W]*?)<\/p>([\w\W]*?)<\/td>/gi, '<td$1>$2$3</td>');
-                               html = html.replace(/<td(.*?)>([\w\W]*?)<p(.*?)>([\w\W]*?)<\/td>/gi, '<td$1>$2$4</td>');
-                               html = html.replace(/<td(.*?)>([\w\W]*?)<\/p>([\w\W]*?)<\/td>/gi, '<td$1>$2$3</td>');
-                       }
+                                       var find = '';
+                                       if (this.inline.type == 'class') find = '[data-redactor-class=' + this.inline.value + ']';
+                                       else if (this.inline.type == 'style')
+                                       {
+                                               find = '[data-redactor-style="' + this.inline.value + '"]';
+                                       }
 
-                       // ms word break lines
-                       html = html.replace(/\n/g, ' ');
+                                       if (tag != 'del')
+                                       {
+                                               var self = this;
+                                               this.$editor.find('del').each(function(i,s)
+                                               {
+                                                       self.utils.replaceToTag(s, 'inline');
+                                               });
+                                       }
 
-                       // ms word lists break lines
-                       html = html.replace(/<p>\n?<li>/gi, '<li>');
+                                       this.$editor.find('[data-redactor-tag="' + tag + '"]' + find).each(function()
+                                       {
+                                               if (find === '' && tag == 'span' && this.tagName.toLowerCase() == tag) return;
 
-                       this.pasteInsert(html);
+                                               var $el = $(this);
+                                               $el.replaceWith($('<strike />').html($el.contents()));
 
-               },
-               pastePre: function(s)
-               {
-                       s = s.replace(/<br>|<\/H[1-6]>|<\/p>|<\/div>/gi, '\n');
+                                       });
 
-                       var tmp = this.document.createElement('div');
-                       tmp.innerHTML = s;
-                       return this.cleanEncodeEntities(tmp.textContent || tmp.innerText);
-               },
-               pasteInsert: function(html)
-               {
-                       html = this.callback('pasteAfter', false, html);
+                                       this.selection.restore();
+                               },
+                               setFormat: function(node)
+                               {
+                                       switch (this.inline.type)
+                                       {
+                                               case 'class':
 
-                       if (this.selectall)
-                       {
-                               this.$editor.html(html);
-                               this.selectionRemove();
-                               this.focusEnd();
-                               this.sync();
-                       }
-                       else
-                       {
-                               this.insertHtml(html);
-                       }
+                                                       if (node.hasClass(this.inline.value))
+                                                       {
+                                                               node.removeClass(this.inline.value);
+                                                               node.removeAttr('data-redactor-class');
+                                                       }
+                                                       else
+                                                       {
+                                                               node.addClass(this.inline.value);
+                                                               node.attr('data-redactor-class', this.inline.value);
+                                                       }
 
-                       this.selectall = false;
 
-                       setTimeout($.proxy(function()
-                       {
-                               this.rtePaste = false;
+                                               break;
+                                               case 'style':
 
-                               // FF specific
-                               if (this.browser('mozilla'))
-                               {
-                                       this.$editor.find('p:empty').remove()
-                               }
-                               if (this.pasteClipboardMozilla !== false)
-                               {
-                                       this.pasteClipboardUploadMozilla();
-                               }
+                                                       node[0].style.cssText = this.inline.value;
+                                                       node.attr('data-redactor-style', this.inline.value);
 
-                       }, this), 100);
+                                               break;
+                                       }
 
-                       if (this.opts.autoresize && this.fullscreen !== true)
-                       {
-                               $(this.document.body).scrollTop(this.saveScroll);
-                       }
-                       else
-                       {
-                               this.$editor.scrollTop(this.saveScroll);
-                       }
-               },
-               pasteClipboardAppendFields: function(postData)
-               {
-                       // append hidden fields
-                       if (this.opts.uploadFields !== false && typeof this.opts.uploadFields === 'object')
-                       {
-                               $.each(this.opts.uploadFields, $.proxy(function(k, v)
+                                       return node;
+                               },
+                               removeStyle: function()
                                {
-                                       if (v != null && v.toString().indexOf('#') === 0) v = $(v).val();
-                                       postData[k] = v;
+                                       this.buffer.set();
+                                       var current = this.selection.getCurrent();
+                                       var nodes = this.selection.getInlines();
 
-                               }, this));
-                       }
+                                       this.selection.save();
 
-                       return postData;
-               },
-               pasteClipboardUploadMozilla: function()
-               {
-                       var imgs = this.$editor.find('img[data-mozilla-paste-image]');
-                       $.each(imgs, $.proxy(function(i,s)
-                       {
-                               var $s = $(s);
-                               var arr = s.src.split(",");
-                               var postData = {
-                                       'contentType': arr[0].split(";")[0].split(":")[1],
-                                       'data': arr[1] // raw base64
-                               };
+                                       if (current && current.tagName === 'SPAN')
+                                       {
+                                               var $s = $(current);
 
-                               // append hidden fields
-                               postData = this.pasteClipboardAppendFields(postData);
+                                               $s.removeAttr('style');
+                                               if ($s[0].attributes.length === 0)
+                                               {
+                                                       $s.replaceWith($s.contents());
+                                               }
+                                       }
 
-                               $.post(this.opts.clipboardUploadUrl, postData,
-                               $.proxy(function(data)
-                               {
-                                       var json = (typeof data === 'string' ? $.parseJSON(data) : data);
-                               $s.attr('src', json.filelink);
-                               $s.removeAttr('data-mozilla-paste-image');
+                                       $.each(nodes, $.proxy(function(i,s)
+                                       {
+                                               var $s = $(s);
+                                               if ($.inArray(s.tagName.toLowerCase(), this.opts.inlineTags) != -1 && !$s.hasClass('redactor-selection-marker'))
+                                               {
+                                                       $s.removeAttr('style');
+                                                       if ($s[0].attributes.length === 0)
+                                                       {
+                                                               $s.replaceWith($s.contents());
+                                                       }
+                                               }
+                                       }, this));
 
-                               this.sync();
+                                       this.selection.restore();
+                                       this.code.sync();
 
-                                       // upload callback
-                                       this.callback('imageUpload', $s, json);
+                               },
+                               removeStyleRule: function(name)
+                               {
+                                       this.buffer.set();
+                                       var parent = this.selection.getParent();
+                                       var nodes = this.selection.getInlines();
 
-                               }, this));
+                                       this.selection.save();
 
-                       }, this));
-               },
-               pasteClipboardUpload: function(e)
-               {
-               var result = e.target.result;
-                       var arr = result.split(",");
-                       var postData = {
-                               'contentType': arr[0].split(";")[0].split(":")[1],
-                               'data': arr[1] // raw base64
-                       };
+                                       if (parent && parent.tagName === 'SPAN')
+                                       {
+                                               var $s = $(parent);
 
+                                               $s.css(name, '');
+                                               this.utils.removeEmptyAttr($s, 'style');
+                                               if ($s[0].attributes.length === 0)
+                                               {
+                                                       $s.replaceWith($s.contents());
+                                               }
+                                       }
 
-                       if (this.opts.clipboardUpload)
-                       {
-                               // append hidden fields
-                               postData = this.pasteClipboardAppendFields(postData);
+                                       $.each(nodes, $.proxy(function(i,s)
+                                       {
+                                               var $s = $(s);
+                                               if ($.inArray(s.tagName.toLowerCase(), this.opts.inlineTags) != -1 && !$s.hasClass('redactor-selection-marker'))
+                                               {
+                                                       $s.css(name, '');
+                                                       this.utils.removeEmptyAttr($s, 'style');
+                                                       if ($s[0].attributes.length === 0)
+                                                       {
+                                                               $s.replaceWith($s.contents());
+                                                       }
+                                               }
+                                       }, this));
 
-                               $.post(this.opts.clipboardUploadUrl, postData,
-                               $.proxy(function(data)
+                                       this.selection.restore();
+                                       this.code.sync();
+                               },
+                               removeFormat: function()
                                {
-                                       var json = (typeof data === 'string' ? $.parseJSON(data) : data);
+                                       this.buffer.set();
+                                       var current = this.selection.getCurrent();
 
-                                       var html = '<img src="' + json.filelink + '" id="clipboard-image-marker" />';
-                                       this.execCommand('inserthtml', html, false);
+                                       this.selection.save();
 
-                                       var image = $(this.$editor.find('img#clipboard-image-marker'));
+                                       document.execCommand('removeFormat');
 
-                                       if (image.length) image.removeAttr('id');
-                                       else image = false;
+                                       if (current && current.tagName === 'SPAN')
+                                       {
+                                               $(current).replaceWith($(current).contents());
+                                       }
 
-                                       this.sync();
 
-                                       // upload callback
-                                       if (image)
+                                       $.each(this.selection.getNodes(), $.proxy(function(i,s)
                                        {
-                                               this.callback('imageUpload', image, json);
-                                       }
+                                               var $s = $(s);
+                                               if ($.inArray(s.tagName.toLowerCase(), this.opts.inlineTags) != -1 && !$s.hasClass('redactor-selection-marker'))
+                                               {
+                                                       $s.replaceWith($s.contents());
+                                               }
+                                       }, this));
 
+                                       this.selection.restore();
+                                       this.code.sync();
 
-                               }, this));
-                       }
-                       else
-                       {
-                       this.insertHtml('<img src="' + result + '" />');
-               }
+                               },
+                               toggleClass: function(className)
+                               {
+                                       this.inline.format('span', 'class', className);
+                               },
+                               toggleStyle: function(value)
+                               {
+                                       this.inline.format('span', 'style', value);
+                               }
+                       };
                },
-
-               // BUFFER
-               bufferSet: function(selectionSave)
+               insert: function()
                {
-                       if (selectionSave !== false)
-                       {
-                               this.selectionSave();
-                       }
-
-                       this.opts.buffer.push(this.$editor.html());
+                       return {
+                               set: function(html, clean)
+                               {
+                                       this.placeholder.remove();
 
-                       if (selectionSave !== false)
-                       {
-                               this.selectionRemoveMarkers('buffer');
-                       }
+                                       html = this.clean.setVerified(html);
 
-               },
-               bufferUndo: function()
-               {
-                       if (this.opts.buffer.length === 0)
-                       {
-                               this.focusWithSaveScroll();
-                               return;
-                       }
+                                       if (typeof clean == 'undefined')
+                                       {
+                                               html = this.clean.onPaste(html, false);
+                                       }
 
-                       // rebuffer
-                       this.selectionSave();
-                       this.opts.rebuffer.push(this.$editor.html());
-                       this.selectionRestore(false, true);
+                                       this.$editor.html(html);
+                                       this.selection.remove();
+                                       this.focus.setEnd();
+                                       this.clean.normalizeLists();
+                                       this.code.sync();
+                                       this.observe.load();
 
-                       this.$editor.html(this.opts.buffer.pop());
+                                       if (typeof clean == 'undefined')
+                                       {
+                                               setTimeout($.proxy(this.clean.clearUnverified, this), 10);
+                                       }
+                               },
+                               text: function(text)
+                               {
+                                       this.placeholder.remove();
 
-                       this.selectionRestore();
-                       setTimeout($.proxy(this.observeStart, this), 100);
-               },
-               bufferRedo: function()
-               {
-                       if (this.opts.rebuffer.length === 0)
-                       {
-                               this.focusWithSaveScroll();
-                               return false;
-                       }
+                                       text = text.toString();
+                                       text = $.trim(text);
+                                       text = this.clean.getPlainText(text, false);
 
-                       // buffer
-                       this.selectionSave();
-                       this.opts.buffer.push(this.$editor.html());
-                       this.selectionRestore(false, true);
+                                       this.$editor.focus();
 
-                       this.$editor.html(this.opts.rebuffer.pop());
-                       this.selectionRestore(true);
-                       setTimeout($.proxy(this.observeStart, this), 4);
-               },
+                                       if (this.utils.browser('msie'))
+                                       {
+                                               this.insert.htmlIe(text);
+                                       }
+                                       else
+                                       {
+                                               this.selection.get();
 
-               // OBSERVE
-               observeStart: function()
-               {
-                       this.observeImages();
+                                               this.range.deleteContents();
+                                               var el = document.createElement("div");
+                                               el.innerHTML = text;
+                                               var frag = document.createDocumentFragment(), node, lastNode;
+                                               while ((node = el.firstChild))
+                                               {
+                                                       lastNode = frag.appendChild(node);
+                                               }
 
-                       if (this.opts.observeLinks) this.observeLinks();
-               },
-               observeLinks: function()
-               {
-                       this.$editor.find('a').on('click', $.proxy(this.linkObserver, this));
+                                               this.range.insertNode(frag);
 
-                       this.$editor.on('click.redactor', $.proxy(function(e)
-                       {
-                               this.linkObserverTooltipClose(e);
+                                               if (lastNode)
+                                               {
+                                                       var range = this.range.cloneRange();
+                                                       range.setStartAfter(lastNode);
+                                                       range.collapse(true);
+                                                       this.sel.removeAllRanges();
+                                                       this.sel.addRange(range);
+                                               }
+                                       }
 
-                       }, this));
+                                       this.code.sync();
+                                       this.clean.clearUnverified();
+                               },
+                               html: function(html, clean)
+                               {
+                                       this.placeholder.remove();
 
-                       $(document).on('click.redactor', $.proxy(function(e)
-                       {
-                               this.linkObserverTooltipClose(e);
+                                       if (typeof clean == 'undefined') clean = true;
 
-                       }, this));
-               },
-               observeImages: function()
-               {
-                       if (this.opts.observeImages === false) return false;
+                                       this.$editor.focus();
 
-                       this.$editor.find('img').each($.proxy(function(i, elem)
-                       {
-                               if (this.browser('msie')) $(elem).attr('unselectable', 'on');
+                                       html = this.clean.setVerified(html);
 
-                               var parent = $(elem).parent();
-                               if (!parent.hasClass('royalSlider') && !parent.hasClass('fotorama'))
-                               {
-                                       this.imageResize(elem);
-                               }
+                                       if (clean)
+                                       {
+                                               html = this.clean.onPaste(html);
+                                       }
 
-                       }, this));
+                                       if (this.utils.browser('msie'))
+                                       {
+                                               this.insert.htmlIe(html);
+                                       }
+                                       else
+                                       {
+                                               if (this.clean.singleLine) this.insert.execHtml(html);
+                                               else document.execCommand('insertHTML', null, html);
 
-                       // royalSlider and fotorama
-                       this.$editor.find('.fotorama, .royalSlider').on('click', $.proxy(this.editGallery, this));
+                                               this.insert.htmlFixMozilla();
+                                       }
 
-               },
-               linkObserver: function(e)
-               {
-                       var $link = $(e.target);
+                                       this.clean.normalizeLists();
 
-                       var parent = $(e.target).parent();
-                       if (parent.hasClass('royalSlider') || parent.hasClass('fotorama'))
-                       {
-                               return;
-                       }
+                                       // remove empty paragraphs finaly
+                                       if (!this.opts.linebreaks)
+                                       {
+                                               this.$editor.find('p').each($.proxy(this.utils.removeEmpty, this));
+                                       }
 
-                       if ($link.size() == 0 || $link[0].tagName !== 'A') return;
+                                       this.code.sync();
+                                       this.observe.load();
 
-                       var pos = $link.offset();
-                       if (this.opts.iframe)
-                       {
-                               var posFrame = this.$frame.offset();
-                               pos.top = posFrame.top + (pos.top - $(this.document).scrollTop());
-                               pos.left += posFrame.left;
-                       }
+                                       if (clean)
+                                       {
+                                               this.clean.clearUnverified();
+                                       }
 
-                       var tooltip = $('<span class="redactor-link-tooltip"></span>');
+                               },
+                               htmlFixMozilla: function()
+                               {
+                                       // FF inserts empty p when content was selected dblclick
+                                       if (!this.utils.browser('mozilla')) return;
 
-                       var href = $link.attr('href');
-                       if (href === undefined)
-                       {
-                               href = '';
-                       }
+                                       var $next = $(this.selection.getBlock()).next();
+                                       if ($next.length > 0 && $next[0].tagName == 'P' && $next.html() === '')
+                                       {
+                                               $next.remove();
+                                       }
 
-                       if (href.length > 24) href = href.substring(0, 24) + '...';
+                               },
+                               htmlIe: function(html)
+                               {
+                                       if (this.utils.isIe11())
+                                       {
+                                               var parent = this.utils.isCurrentOrParent('P');
+                                               var $html = $('<div>').append(html);
+                                               var blocksMatch = $html.contents().is('p, :header, dl, ul, ol, div, table, td, blockquote, pre, address, section, header, footer, aside, article');
 
-                       var aLink = $('<a href="' + $link.attr('href') + '" target="_blank">' + href + '</a>').on('click', $.proxy(function(e)
-                       {
-                               this.linkObserverTooltipClose(false);
-                       }, this));
+                                               if (parent && blocksMatch) this.insert.ie11FixInserting(parent, html);
+                                               else this.insert.ie11PasteFrag(html);
 
-                       var aEdit = $('<a href="#">' + this.opts.curLang.edit + '</a>').on('click', $.proxy(function(e)
-                       {
-                               e.preventDefault();
-                               this.linkShow();
-                               this.linkObserverTooltipClose(false);
+                                               return;
+                                       }
 
-                       }, this));
+                                       document.selection.createRange().pasteHTML(html);
 
-                       var aUnlink = $('<a href="#">' + this.opts.curLang.unlink + '</a>').on('click', $.proxy(function(e)
-                       {
-                               e.preventDefault();
-                               this.execCommand('unlink');
-                               this.linkObserverTooltipClose(false);
+                               },
+                               execHtml: function(html)
+                               {
+                                       html = this.clean.setVerified(html);
 
-                       }, this));
+                                       this.selection.get();
 
+                                       this.range.deleteContents();
 
-                       tooltip.append(aLink);
-                       tooltip.append(' | ');
-                       tooltip.append(aEdit);
-                       tooltip.append(' | ');
-                       tooltip.append(aUnlink);
-                       tooltip.css({
-                               top: (pos.top + 20) + 'px',
-                               left: pos.left + 'px'
-                       });
+                                       var el = document.createElement('div');
+                                       el.innerHTML = html;
 
-                       $('.redactor-link-tooltip').remove();
-                       $('body').append(tooltip);
-               },
-               linkObserverTooltipClose: function(e)
-               {
-                       if (e !== false && e.target.tagName == 'A') return false;
-                       $('.redactor-link-tooltip').remove();
-               },
+                                       var frag = document.createDocumentFragment(), node, lastNode;
+                                       while ((node = el.firstChild))
+                                       {
+                                               lastNode = frag.appendChild(node);
+                                       }
 
-               // SELECTION
-               getSelection: function()
-               {
-                       if (!this.opts.rangy) return this.document.getSelection();
-                       else // rangy
-                       {
-                               if (!this.opts.iframe) return rangy.getSelection();
-                               else return rangy.getSelection(this.$frame[0]);
-                       }
-               },
-               getRange: function()
-               {
-                       if (!this.opts.rangy)
-                       {
-                               if (this.document.getSelection)
-                               {
-                                       var sel = this.getSelection();
-                                       if (sel.getRangeAt && sel.rangeCount) return sel.getRangeAt(0);
-                               }
+                                       this.range.insertNode(frag);
 
-                               return this.document.createRange();
-                       }
-                       else // rangy
-                       {
-                               if (!this.opts.iframe) return rangy.createRange();
-                               else return rangy.createRange(this.iframeDoc());
-                       }
-               },
-               selectionElement: function(node)
-               {
-                       this.setCaret(node);
-               },
-               selectionStart: function(node)
-               {
-                       this.selectionSet(node[0] || node, 0, null, 0);
-               },
-               selectionEnd: function(node)
-               {
-                       this.selectionSet(node[0] || node, 1, null, 1);
-               },
-               selectionSet: function(orgn, orgo, focn, foco)
-               {
-                       if (focn == null) focn = orgn;
-                       if (foco == null) foco = orgo;
+                                       this.range.collapse(true);
+                                       this.caret.setAfter(lastNode);
 
-                       var sel = this.getSelection();
-                       if (!sel) return;
+                               },
+                               node: function(node)
+                               {
+                                       node = node[0] || node;
 
-                       if (orgn.tagName == 'P' && orgn.innerHTML == '')
-                       {
-                               orgn.innerHTML = this.opts.invisibleSpace;
-                       }
+                                       this.selection.get();
+                                       this.range.deleteContents();
+                                       this.range.insertNode(node);
+                                       this.range.collapse(false);
+                                       this.selection.addRange();
 
-                       if (orgn.tagName == 'BR' && this.opts.linebreaks === false)
-                       {
-                               var par = $(this.opts.emptyHtml)[0];
-                               $(orgn).replaceWith(par);
-                               orgn = par;
-                               focn = orgn;
-                       }
+                                       return node;
+                               },
+                               nodeToPoint: function(node, x, y)
+                               {
+                                       node = node[0] || node;
 
-                       var range = this.getRange();
-                       range.setStart(orgn, orgo);
-                       range.setEnd(focn, foco );
+                                       this.selection.get();
 
-                       try {
-                               sel.removeAllRanges();
-                       } catch (e) {}
+                                       var range;
+                                       if (document.caretPositionFromPoint)
+                                       {
+                                           var pos = document.caretPositionFromPoint(x, y);
 
-                       sel.addRange(range);
-               },
-               selectionWrap: function(tag)
-               {
-                       tag = tag.toLowerCase();
+                                           this.range.setStart(pos.offsetNode, pos.offset);
+                                           this.range.collapse(true);
+                                           this.range.insertNode(node);
+                                       }
+                                       else if (document.caretRangeFromPoint)
+                                       {
+                                           range = document.caretRangeFromPoint(x, y);
+                                           range.insertNode(node);
+                                       }
+                                       else if (typeof document.body.createTextRange != "undefined")
+                                       {
+                                       range = document.body.createTextRange();
+                                       range.moveToPoint(x, y);
+                                       var endRange = range.duplicate();
+                                       endRange.moveToPoint(x, y);
+                                       range.setEndPoint("EndToEnd", endRange);
+                                       range.select();
+                                       }
+                               },
+                               nodeToCaretPositionFromPoint: function(e, node)
+                               {
+                                       node = node[0] || node;
 
-                       var block = this.getBlock();
-                       if (block)
-                       {
-                               var wrapper = this.formatChangeTag(block, tag);
-                               this.sync();
-                               return wrapper;
-                       }
+                                       var range;
+                                       var x = e.clientX, y = e.clientY;
+                                       if (document.caretPositionFromPoint)
+                                       {
+                                           var pos = document.caretPositionFromPoint(x, y);
+                                           var sel = document.getSelection();
+                                           range = sel.getRangeAt(0);
+                                           range.setStart(pos.offsetNode, pos.offset);
+                                           range.collapse(true);
+                                           range.insertNode(node);
+                                       }
+                                       else if (document.caretRangeFromPoint)
+                                       {
+                                           range = document.caretRangeFromPoint(x, y);
+                                           range.insertNode(node);
+                                       }
+                                       else if (typeof document.body.createTextRange != "undefined")
+                                       {
+                                       range = document.body.createTextRange();
+                                       range.moveToPoint(x, y);
+                                       var endRange = range.duplicate();
+                                       endRange.moveToPoint(x, y);
+                                       range.setEndPoint("EndToEnd", endRange);
+                                       range.select();
+                                       }
 
-                       var sel = this.getSelection();
-                       var range = sel.getRangeAt(0);
-                       var wrapper = document.createElement(tag);
-                       wrapper.appendChild(range.extractContents());
-                       range.insertNode(wrapper);
+                               },
+                               ie11FixInserting: function(parent, html)
+                               {
+                                       var node = document.createElement('span');
+                                       node.className = 'redactor-ie-paste';
+                                       this.insert.node(node);
 
-                       this.selectionElement(wrapper);
+                                       var parHtml = $(parent).html();
 
-                       return wrapper;
-               },
-               selectionAll: function()
-               {
-                       var range = this.getRange();
-                       range.selectNodeContents(this.$editor[0]);
+                                       parHtml = '<p>' + parHtml.replace(/<span class="redactor-ie-paste"><\/span>/gi, '</p>' + html + '<p>') + '</p>';
+                                       $(parent).replaceWith(parHtml);
+                               },
+                               ie11PasteFrag: function(html)
+                               {
+                                       this.selection.get();
+                                       this.range.deleteContents();
 
-                       var sel = this.getSelection();
-                       sel.removeAllRanges();
-                       sel.addRange(range);
-               },
-               selectionRemove: function()
-               {
-                       this.getSelection().removeAllRanges();
-               },
-               getCaretOffset: function (element)
-               {
-                       var caretOffset = 0;
+                                       var el = document.createElement("div");
+                                       el.innerHTML = html;
 
-                       var range = this.getRange();
-                       var preCaretRange = range.cloneRange();
-                       preCaretRange.selectNodeContents(element);
-                       preCaretRange.setEnd(range.endContainer, range.endOffset);
-                       caretOffset = $.trim(preCaretRange.toString()).length;
+                                       var frag = document.createDocumentFragment(), node, lastNode;
+                                       while ((node = el.firstChild))
+                                       {
+                                               lastNode = frag.appendChild(node);
+                                       }
 
-                       return caretOffset;
-               },
-               getCaretOffsetRange: function()
-               {
-                       return new Range(this.getSelection().getRangeAt(0));
+                                       this.range.insertNode(frag);
+                               }
+                       };
                },
-               setCaret: function (el, start, end)
+               caret: function()
                {
-                       if (typeof end === 'undefined') end = start;
-                       el = el[0] || el;
+                       return {
+                               setStart: function(node)
+                               {
+                                       // inline tag
+                                       if (!this.utils.isBlock(node))
+                                       {
+                                               var space = this.utils.createSpaceElement();
 
-                       var range = this.getRange();
-                       range.selectNodeContents(el);
+                                               $(node).prepend(space);
+                                               this.caret.setEnd(space);
+                                       }
+                                       else
+                                       {
+                                               this.caret.set(node, 0, node, 0);
+                                       }
+                               },
+                               setEnd: function(node)
+                               {
+                                       this.caret.set(node, 1, node, 1);
+                               },
+                               set: function(orgn, orgo, focn, foco)
+                               {
+                                       // focus
+                                       if (!this.utils.browser('msie')) this.$editor.focus();
 
-                       var textNodes = this.getTextNodesIn(el);
-                       var foundStart = false;
-                       var charCount = 0, endCharCount;
+                                       orgn = orgn[0] || orgn;
+                                       focn = focn[0] || focn;
 
-                       if (textNodes.length == 1 && start)
-                       {
-                               range.setStart(textNodes[0], start);
-                               range.setEnd(textNodes[0], end);
-                       }
-                       else
-                       {
-                               for (var i = 0, textNode; textNode = textNodes[i++];)
-                               {
-                                       endCharCount = charCount + textNode.length;
-                                       if (!foundStart && start >= charCount && (start < endCharCount || (start == endCharCount && i < textNodes.length)))
+                                       if (this.utils.isBlockTag(orgn.tagName) && orgn.innerHTML === '')
                                        {
-                                               range.setStart(textNode, start - charCount);
-                                               foundStart = true;
+                                               orgn.innerHTML = this.opts.invisibleSpace;
                                        }
 
-                                       if (foundStart && end <= endCharCount)
+                                       if (orgn.tagName == 'BR' && this.opts.linebreaks === false)
                                        {
-                                               range.setEnd( textNode, end - charCount );
-                                               break;
+                                               var par = $(this.opts.emptyHtml)[0];
+                                               $(orgn).replaceWith(par);
+                                               orgn = par;
+                                               focn = orgn;
                                        }
 
-                                       charCount = endCharCount;
-                               }
-                       }
-
-                       var sel = this.getSelection();
-                       sel.removeAllRanges();
-                       sel.addRange( range );
-               },
-               setCaretAfter: function(node)
-               {
-                       this.$editor.focus();
+                                       this.selection.get();
 
-                       node = node[0] || node;
+                                       try {
+                                               this.range.setStart(orgn, orgo);
+                                               this.range.setEnd(focn, foco);
+                                       }
+                                       catch (e) {}
 
-                       var range = this.document.createRange()
+                                       this.selection.addRange();
+                               },
+                               setAfter: function(node)
+                               {
+                                       var tag = $(node)[0].tagName;
 
-                       var start = 1;
-                       var end = -1;
+                                       // inline tag
+                                       if (tag != 'BR' && !this.utils.isBlock(node))
+                                       {
+                                               var space = this.utils.createSpaceElement();
 
-                       range.setStart(node, start)
-                       range.setEnd(node, end + 2)
+                                               $(node).after(space);
+                                               this.caret.setEnd(space);
+                                       }
+                                       else
+                                       {
+                                               if (tag != 'BR' && this.utils.browser('msie'))
+                                               {
+                                                       this.caret.setStart($(node).next());
+                                               }
+                                               else
+                                               {
+                                                       this.caret.setAfterOrBefore(node, 'after');
+                                               }
+                                       }
+                               },
+                               setBefore: function(node)
+                               {
+                                       // block tag
+                                       if (this.utils.isBlock(node))
+                                       {
+                                               this.caret.setEnd($(node).prev());
+                                       }
+                                       else
+                                       {
+                                               this.caret.setAfterOrBefore(node, 'before');
+                                       }
+                               },
+                               setAfterOrBefore: function(node, type)
+                               {
+                                       // focus
+                                       if (!this.utils.browser('msie')) this.$editor.focus();
 
+                                       node = node[0] || node;
 
-                       var selection = this.window.getSelection()
-                       var cursorRange = this.document.createRange()
+                                       this.selection.get();
 
-                       var emptyElement = this.document.createTextNode('\u200B')
-                       $(node).after(emptyElement)
+                                       if (type == 'after')
+                                       {
+                                               try {
 
-                       cursorRange.setStartAfter(emptyElement)
+                                                       this.range.setStartAfter(node);
+                                                       this.range.setEndAfter(node);
+                                               }
+                                               catch (e) {}
+                                       }
+                                       else
+                                       {
+                                               try {
+                                                       this.range.setStartBefore(node);
+                                                       this.range.setEndBefore(node);
+                                               }
+                                               catch (e) {}
+                                       }
 
-                       selection.removeAllRanges()
-                       selection.addRange(cursorRange)
-                       $(emptyElement).remove();
-               },
-               getTextNodesIn: function (node)
-               {
-                       var textNodes = [];
 
-                       if (node.nodeType == 3) textNodes.push(node);
-                       else
-                       {
-                               var children = node.childNodes;
-                               for (var i = 0, len = children.length; i < len; ++i)
+                                       this.range.collapse(false);
+                                       this.selection.addRange();
+                               },
+                               getOffset: function(node)
                                {
-                                       textNodes.push.apply(textNodes, this.getTextNodesIn(children[i]));
-                               }
-                       }
-
-                       return textNodes;
-               },
-
-               // GET ELEMENTS
-               getCurrent: function()
-               {
-                       var el = false;
-                       var sel = this.getSelection();
+                                       node = node[0] || node;
 
-                       if (sel && sel.rangeCount > 0)
-                       {
-                               el = sel.getRangeAt(0).startContainer;
-                               //el = sel.getRangeAt(0).commonAncestorContainer;
-                       }
+                                       this.selection.get();
 
-                       return this.isParentRedactor(el);
-               },
-               getParent: function(elem)
-               {
-                       elem = elem || this.getCurrent();
-                       if (elem) return this.isParentRedactor( $( elem ).parent()[0] );
-                       else return false;
-               },
-               getBlock: function(node)
-               {
-                       if (typeof node === 'undefined') node = this.getCurrent();
+                                       var cloned = this.range.cloneRange();
+                                       cloned.selectNodeContents(node);
+                                       cloned.setEnd(this.range.endContainer, this.range.endOffset);
 
-                       while (node)
-                       {
-                               if (this.nodeTestBlocks(node))
+                                       return $.trim(cloned.toString()).length;
+                               },
+                               setToPoint: function(startX, startY)
                                {
-                                       if ($(node).hasClass('redactor_editor')) return false;
-                                       return node;
-                               }
 
-                               node = node.parentNode;
-                       }
+                                       if (startX === 0 && startY === 0)
+                                       {
+                                               this.focus.setStart();
+                                               return;
+                                       }
 
-                       return false;
-               },
-               getBlocks: function(nodes)
-               {
-                       var newnodes = [];
-                       if (typeof nodes == 'undefined')
-                       {
-                               var range = this.getRange();
-                               if (range && range.collapsed === true) return [this.getBlock()];
-                               var nodes = this.getNodes(range);
-                       }
+                                       var start, range = null;
+                                       if (document.caretPositionFromPoint)
+                                       {
+                                               start = document.caretPositionFromPoint(startX, startY);
 
-                       $.each(nodes, $.proxy(function(i,node)
-                       {
-                               if (this.opts.iframe === false && $(node).parents('div.redactor_editor').size() == 0) return false;
-                               if (this.nodeTestBlocks(node)) newnodes.push(node);
+                                               range = document.createRange();
 
-                       }, this));
+                                               range.setStart(start.offsetNode, start.offset);
+                                               range.collapse(true);
+                                       }
+                                       // WebKit
+                                       else if (document.caretRangeFromPoint)
+                                       {
+                                               start = document.caretRangeFromPoint(startX, startY);
+                                               range = document.createRange();
 
-                       if (newnodes.length === 0) newnodes = [this.getBlock()];
+                                               if (start !== null)
+                                               {
+                                                       range.setStart(start.startContainer, start.startOffset);
+                                                       range.collapse(true);
+                                               }
+                                               else
+                                               {
+                                                       range = null;
+                                               }
 
-                       return newnodes;
-               },
-               isInlineNode: function(node)
-               {
-                       if (node.nodeType != 1) return false;
+                                       }
+                                       // IE
+                                       else if (document.body.createTextRange)
+                                       {
+                                           range = document.body.createTextRange();
+                                           range.moveToPoint(startX, startY);
+                                               range.select();
+                                       }
 
-                       return !this.rTestBlock.test(node.nodeName);
-               },
-               nodeTestBlocks: function(node)
-               {
-                       return node.nodeType == 1 && this.rTestBlock.test(node.nodeName);
-               },
-               tagTestBlock: function(tag)
-               {
-                       return this.rTestBlock.test(tag);
-               },
-               getNodes: function(range, tag)
-               {
-                       if (typeof range == 'undefined' || range == false) var range = this.getRange();
-                       if (range && range.collapsed === true)
-                       {
-                               if (typeof tag === 'undefined' && this.tagTestBlock(tag))
-                               {
-                                       var block = this.getBlock();
-                                       if (block.tagName == tag) return [block];
-                                       else return [];
-                               }
-                               else
-                               {
-                                       return [this.getCurrent()];
-                               }
-                       }
+                                       if (range !== null)
+                                       {
+                                               var sel = document.getSelection();
+                                               sel.removeAllRanges();
+                                               sel.addRange(range);
+                                       }
 
-                       var nodes = [], finalnodes = [];
 
-                       var sel = this.document.getSelection();
-                       if (!sel.isCollapsed) nodes = this.getRangeSelectedNodes(sel.getRangeAt(0));
+                                       if (!this.focus.isFocused())
+                                       {
+                                               this.focus.setStart();
+                                       }
 
-                       $.each(nodes, $.proxy(function(i,node)
-                       {
-                               if (this.opts.iframe === false && $(node).parents('div.redactor_editor').size() == 0) return false;
+                                       return;
 
-                               if (typeof tag === 'undefined')
+                               },
+                               getCoords: function()
                                {
-                                       if ($.trim(node.textContent) != '')
+                                       if (!this.focus.isFocused())
                                        {
-                                               finalnodes.push(node);
+                                               this.$editor.focus();
                                        }
-                               }
-                               else if (node.tagName == tag)
-                               {
-                                       finalnodes.push(node);
-                               }
 
-                       }, this));
+                                       var sel = document.selection, range, rect;
+                                       var x = 0, y = 0;
 
-                       if (finalnodes.length == 0)
-                       {
-                               if (typeof tag === 'undefined' && this.tagTestBlock(tag))
-                               {
-                                       var block = this.getBlock();
-                                       if (block.tagName == tag) return finalnodes.push(block);
-                                       else return [];
-                               }
-                               else
-                               {
-                                       finalnodes.push(this.getCurrent());
-                               }
-                       }
+                                       if (sel)
+                                       {
+                                               if (sel.type != "Control")
+                                               {
+                                                       range = sel.createRange();
+                                                       range.collapse(true);
+                                                       x = range.boundingLeft;
+                                                       y = range.boundingTop;
+                                               }
+                                       }
+                                       else if (document.getSelection)
+                                       {
+                                               sel = document.getSelection();
 
-                       // last element filtering
-                       var last = finalnodes[finalnodes.length-1];
-                       if (this.nodeTestBlocks(last))
-                       {
-                               finalnodes = finalnodes.slice(0, -1);
-                       }
+                                               try
+                                               {
+                                                       range = sel.getRangeAt(0).cloneRange();
 
-                       return finalnodes;
-               },
-               getElement: function(node)
-               {
-                       if (!node) node = this.getCurrent();
-                       while (node)
-                       {
-                               if (node.nodeType == 1)
-                               {
-                                       if ($(node).hasClass('redactor_editor')) return false;
-                                       return node;
-                               }
+                                                       if (range.getClientRects)
+                                                       {
+                                                               range.collapse(true);
+                                                               rect = range.getClientRects()[0];
 
-                               node = node.parentNode;
-                       }
+                                                               try
+                                                               {
+                                                                       x = rect.left;
+                                                                       y = rect.top;
+                                                               }
+                                                               catch(e) {}
 
-                       return false;
-               },
-               getRangeSelectedNodes: function(range)
-               {
-                       range = range || this.getRange();
-                       var node = range.startContainer;
-                       var endNode = range.endContainer;
+                                                       }
 
-                       if (node == endNode) return [node];
+                                                       // Fallback
+                                                       if (x === 0 && y === 0)
+                                                       {
+                                                               var span = document.createElement("span");
+                                                               if (span.getClientRects)
+                                                               {
+                                                                       span.appendChild(document.createTextNode("\u200b"));
+                                                                       range.insertNode(span);
 
-                       var rangeNodes = [];
-                       while (node && node != endNode)
-                       {
-                               rangeNodes.push(node = this.nextNode(node));
-                       }
+                                                                       rect = span.getClientRects()[0];
 
-                       node = range.startContainer;
-                       while (node && node != range.commonAncestorContainer)
-                       {
-                               rangeNodes.unshift(node);
-                               node = node.parentNode;
-                       }
+                                                                       if (typeof rect != 'undefined')
+                                                                       {
+                                                                               x = rect.left;
+                                                                               y = rect.top;
+                                                                       }
 
-                       return rangeNodes;
-               },
-               nextNode: function(node)
-               {
-                       if (node.hasChildNodes()) return node.firstChild;
-                       else
-                       {
-                               while (node && !node.nextSibling)
-                               {
-                                       node = node.parentNode;
-                               }
+                                                                       var parent = span.parentNode;
+                                                                       parent.removeChild(span);
+                                                                       parent.normalize();
+                                                               }
+                                                       }
+                                               }
+                                               catch(e) {}
 
-                               if (!node) return null;
-                               return node.nextSibling;
-                       }
-               },
+                                       }
 
-               // GET SELECTION HTML OR TEXT
-               getSelectionText: function()
-               {
-                       return this.getSelection().toString();
+                                       return { x: x, y: y };
+
+                               }
+                       };
                },
-               getSelectionHtml: function()
+               selection: function()
                {
-                       var html = '';
+                       return {
+                               get: function()
+                               {
+                                       this.sel = document.getSelection();
 
-                       var sel = this.getSelection();
-                       if (sel.rangeCount)
-                       {
-                               var container = this.document.createElement( "div" );
-                               var len = sel.rangeCount;
-                               for (var i = 0; i < len; ++i)
+                                       if (document.getSelection && this.sel.getRangeAt && this.sel.rangeCount)
+                                       {
+                                               this.range = this.sel.getRangeAt(0);
+                                       }
+                                       else
+                                       {
+                                               this.range = document.createRange();
+                                       }
+                               },
+                               addRange: function()
                                {
-                                       container.appendChild(sel.getRangeAt(i).cloneContents());
-                               }
+                                       try {
+                                               this.sel.removeAllRanges();
+                                       } catch (e) {}
 
-                               html = container.innerHTML;
-                       }
+                                       this.sel.addRange(this.range);
+                               },
+                               getCurrent: function()
+                               {
+                                       var el = false;
+                                       this.selection.get();
 
-                       return this.syncClean(html);
-               },
+                                       if (this.sel && this.sel.rangeCount > 0)
+                                       {
+                                               el = this.sel.getRangeAt(0).startContainer;
+                                       }
 
-               // SAVE & RESTORE
-               selectionSave: function()
-               {
-                       if (!this.isFocused())
-                       {
-                               this.focusWithSaveScroll();
-                       }
+                                       return this.utils.isRedactorParent(el);
+                               },
+                               getParent: function(elem)
+                               {
+                                       elem = elem || this.selection.getCurrent();
+                                       if (elem)
+                                       {
+                                               return this.utils.isRedactorParent($(elem).parent()[0]);
+                                       }
 
-                       if (!this.opts.rangy)
-                       {
-                               this.selectionCreateMarker(this.getRange());
-                       }
-                       // rangy
-                       else
-                       {
-                               this.savedSel = rangy.saveSelection();
-                       }
-               },
-               selectionCreateMarker: function(range, remove)
-               {
-                       if (!range) return;
+                                       return false;
+                               },
+                               getBlock: function(node)
+                               {
+                                       node = node || this.selection.getCurrent();
 
-                       var node1 = $('<span id="selection-marker-1" class="redactor-selection-marker">' + this.opts.invisibleSpace + '</span>', this.document)[0];
-                       var node2 = $('<span id="selection-marker-2" class="redactor-selection-marker">' + this.opts.invisibleSpace + '</span>', this.document)[0];
+                                       while (node)
+                                       {
+                                               if (this.utils.isBlockTag(node.tagName))
+                                               {
+                                                       return ($(node).hasClass('redactor-editor')) ? false : node;
+                                               }
 
-                       if (range.collapsed === true)
-                       {
-                               this.selectionSetMarker(range, node1, true);
-                       }
-                       else
-                       {
-                               this.selectionSetMarker(range, node1, true);
-                               this.selectionSetMarker(range, node2, false);
-                       }
+                                               node = node.parentNode;
+                                       }
 
-                       this.savedSel = this.$editor.html();
+                                       return false;
+                               },
+                               getInlines: function(nodes)
+                               {
+                                       this.selection.get();
 
-                       this.selectionRestore(false, false);
-               },
-               selectionSetMarker: function(range, node, type)
-               {
-                       var boundaryRange = range.cloneRange();
+                                       if (this.range && this.range.collapsed)
+                                       {
+                                               return false;
+                                       }
 
-                       try {
-                               boundaryRange.collapse(type);
-                               boundaryRange.insertNode(node);
-                               boundaryRange.detach();
-                       }
-                       catch (e)
-                       {
-                               var html = this.opts.emptyHtml;
-                               if (this.opts.linebreaks) html = '<br>';
+                                       var inlines = [];
+                                       nodes = (typeof nodes == 'undefined') ? this.selection.getNodes() : nodes;
+                                       var inlineTags = this.opts.inlineTags;
+                                       inlineTags.push('span');
+                                       $.each(nodes, $.proxy(function(i,node)
+                                       {
+                                               if ($.inArray(node.tagName.toLowerCase(), inlineTags) != -1)
+                                               {
+                                                       inlines.push(node);
+                                               }
 
-                               this.$editor.prepend(html);
-                               this.focus();
-                       }
-               },
-               selectionRestore: function(replace, remove)
-               {
-                       if (!this.opts.rangy)
-                       {
-                               if (replace === true && this.savedSel)
+                                       }, this));
+
+                                       return (inlines.length === 0) ? false : inlines;
+                               },
+                               getBlocks: function(nodes)
                                {
-                                       this.$editor.html(this.savedSel);
-                               }
+                                       this.selection.get();
+
+                                       if (this.range && this.range.collapsed)
+                                       {
+                                               return [this.selection.getBlock()];
+                                       }
 
-                               var node1 = this.$editor.find('span#selection-marker-1');
-                               var node2 = this.$editor.find('span#selection-marker-2');
+                                       var blocks = [];
+                                       nodes = (typeof nodes == 'undefined') ? this.selection.getNodes() : nodes;
+                                       $.each(nodes, $.proxy(function(i,node)
+                                       {
+                                               if (this.utils.isBlock(node))
+                                               {
+                                                       this.selection.lastBlock = node;
+                                                       blocks.push(node);
+                                               }
+
+                                       }, this));
 
-                               if (this.browser('mozilla'))
+                                       return (blocks.length === 0) ? [this.selection.getBlock()] : blocks;
+                               },
+                               getLastBlock: function()
                                {
-                                       this.$editor.focus();
-                               }
-                               else if (!this.isFocused())
+                                       return this.selection.lastBlock;
+                               },
+                               getNodes: function()
                                {
-                                       this.focusWithSaveScroll();
-                               }
+                                       this.selection.get();
 
-                               if (node1.length != 0 && node2.length != 0)
-                               {
+                                       var startNode = this.selection.getNodesMarker(1);
+                                       var endNode = this.selection.getNodesMarker(2);
 
-                                       this.selectionSet(node1[0], 0, node2[0], 0);
-                               }
-                               else if (node1.length != 0)
-                               {
-                                       this.selectionSet(node1[0], 0, null, 0);
-                               }
+                                       this.selection.setNodesMarker(this.range, startNode, true);
 
-                               if (remove !== false)
-                               {
-                                       this.selectionRemoveMarkers();
-                                       this.savedSel = false;
-                               }
-                       }
-                       // rangy
-                       else
-                       {
-                               rangy.restoreSelection(this.savedSel);
-                       }
-               },
-               selectionRemoveMarkers: function(type)
-               {
-                       if (!this.opts.rangy)
-                       {
-                               $.each(this.$editor.find('span.redactor-selection-marker'), function()
-                               {
-                                       var html = $.trim($(this).html().replace(/[^\u0000-\u1C7F]/g, ''));
-                                       if (html == '')
+                                       if (this.range.collapsed === false)
                                        {
-                                               $(this).remove();
+                                               this.selection.setNodesMarker(this.range, endNode, false);
                                        }
                                        else
                                        {
-                                               $(this).removeAttr('class').removeAttr('id');
+                                               endNode = startNode;
                                        }
-                               });
-                       }
-                       // rangy
-                       else
-                       {
-                               rangy.removeMarkers(this.savedSel);
-                       }
-               },
 
-               // TABLE
-               tableShow: function()
-               {
-                       this.selectionSave();
+                                       var nodes = [];
+                                       var counter = 0;
 
-                       this.modalInit(this.opts.curLang.table, this.opts.modal_table, 300, $.proxy(function()
-                       {
-                               $('#redactor_insert_table_btn').click($.proxy(this.tableInsert, this));
+                                       var self = this;
+                                       this.$editor.find('*').each(function()
+                                       {
+                                               if (this == startNode)
+                                               {
+                                                       var parent = $(this).parent();
+                                                       if (parent.length !== 0 && parent[0].tagName != 'BODY' && self.utils.isRedactorParent(parent[0]))
+                                                       {
+                                                               nodes.push(parent[0]);
+                                                       }
 
-                               setTimeout(function()
-                               {
-                                       $('#redactor_table_rows').focus();
+                                                       nodes.push(this);
+                                                       counter = 1;
+                                               }
+                                               else
+                                               {
+                                                       if (counter > 0)
+                                                       {
+                                                               nodes.push(this);
+                                                               counter = counter + 1;
+                                                       }
+                                               }
 
-                               }, 200);
+                                               if (this == endNode)
+                                               {
+                                                       return false;
+                                               }
 
-                       }, this));
-               },
-               tableInsert: function()
-               {
-                       this.bufferSet(false);
+                                       });
 
-                       var rows = $('#redactor_table_rows').val(),
-                               columns = $('#redactor_table_columns').val(),
-                               $table_box = $('<div></div>'),
-                               tableId = Math.floor(Math.random() * 99999),
-                               $table = $('<table id="table' + tableId + '"><tbody></tbody></table>'),
-                               i, $row, z, $column;
+                                       var finalNodes = [];
+                                       var len = nodes.length;
+                                       for (var i = 0; i < len; i++)
+                                       {
+                                               if (nodes[i].id != 'nodes-marker-1' && nodes[i].id != 'nodes-marker-2')
+                                               {
+                                                       finalNodes.push(nodes[i]);
+                                               }
+                                       }
 
-                       for (i = 0; i < rows; i++)
-                       {
-                               $row = $('<tr></tr>');
+                                       this.selection.removeNodesMarkers();
+
+                                       return finalNodes;
 
-                               for (z = 0; z < columns; z++)
+                               },
+                               getNodesMarker: function(num)
+                               {
+                                       return $('<span id="nodes-marker-' + num + '" class="redactor-nodes-marker" data-verified="redactor">' + this.opts.invisibleSpace + '</span>')[0];
+                               },
+                               setNodesMarker: function(range, node, type)
                                {
-                                       $column = $('<td>' + this.opts.invisibleSpace + '</td>');
+                                       range = range.cloneRange();
 
-                                       // set the focus to the first td
-                                       if (i === 0 && z === 0)
-                                       {
-                                               $column.append('<span id="selection-marker-1">' + this.opts.invisibleSpace + '</span>');
+                                       try {
+                                               range.collapse(type);
+                                               range.insertNode(node);
                                        }
+                                       catch (e) {}
+                               },
+                               removeNodesMarkers: function()
+                               {
+                                       $(document).find('span.redactor-nodes-marker').remove();
+                                       this.$editor.find('span.redactor-nodes-marker').remove();
+                               },
+                               fromPoint: function(startX, endX, startY, endY)
+                               {
+                                       this.caret.setToPoint(startX, endX, startY, endY);
+                               },
+                               wrap: function(tag)
+                               {
+                                       this.selection.get();
 
-                                       $($row).append($column);
-                               }
+                                       if (this.range.collapsed) return false;
 
-                               $table.append($row);
-                       }
+                                       var wrapper = document.createElement(tag);
+                                       wrapper.appendChild(this.range.extractContents());
+                                       this.range.insertNode(wrapper);
+
+                                       return wrapper;
+                               },
+                               selectElement: function(node)
+                               {
+                                       this.caret.set(node, 0, node, 1);
+                               },
+                               selectAll: function()
+                               {
+                                       this.selection.get();
+                                       this.range.selectNodeContents(this.$editor[0]);
+                                       this.selection.addRange();
+                               },
+                               remove: function()
+                               {
+                                       this.selection.get();
+                                       this.sel.removeAllRanges();
+                               },
+                               save: function()
+                               {
+                                       this.selection.createMarkers();
+                               },
+                               createMarkers: function()
+                               {
+                                       this.selection.get();
 
-                       $table_box.append($table);
-                       var html = $table_box.html();
+                                       var node1 = this.selection.getMarker(1);
 
-                       if (this.opts.linebreaks === false && this.browser('mozilla'))
-                       {
-                               html += '<p>' + this.opts.invisibleSpace + '</p>';
-                       }
+                                       this.selection.setMarker(this.range, node1, true);
 
-                       this.modalClose();
-                       this.selectionRestore();
+                                       if (this.range.collapsed === false)
+                                       {
+                                               var node2 = this.selection.getMarker(2);
+                                               this.selection.setMarker(this.range, node2, false);
+                                       }
 
-                       var current = this.getBlock() || this.getCurrent();
+                                       this.savedSel = this.$editor.html();
+                               },
+                               getMarker: function(num)
+                               {
+                                       if (typeof num == 'undefined') num = 1;
 
-                       if (current && current.tagName != 'BODY')
-                       {
-                               if (current.tagName == 'LI')
+                                       return $('<span id="selection-marker-' + num + '" class="redactor-selection-marker"  data-verified="redactor">' + this.opts.invisibleSpace + '</span>')[0];
+                               },
+                               getMarkerAsHtml: function(num)
                                {
-                                       var current = $(current).closest('ul, ol');
-                               }
+                                       return this.utils.getOuterHtml(this.selection.getMarker(num));
+                               },
+                               setMarker: function(range, node, type)
+                               {
+                                       range = range.cloneRange();
+
+                                       try {
+                                               range.collapse(type);
+                                               range.insertNode(node);
+                                       }
+                                       catch (e)
+                                       {
+                                               this.focus.setStart();
+                                       }
+                               },
+                               restore: function()
+                               {
+                                       var node1 = this.$editor.find('span#selection-marker-1');
+                                       var node2 = this.$editor.find('span#selection-marker-2');
+
+                                       if (node1.length !== 0 && node2.length !== 0)
+                                       {
+                                               this.caret.set(node1, 0, node2, 0);
+                                       }
+                                       else if (node1.length !== 0)
+                                       {
+                                               this.caret.set(node1, 0, node1, 0);
+                                       }
+                                       else
+                                       {
+                                               this.$editor.focus();
+                                       }
 
-                               $(current).after(html)
-                       }
-                       else
-                       {
+                                       this.selection.removeMarkers();
+                                       this.savedSel = false;
 
-                               this.insertHtmlAdvanced(html, false);
-                       }
+                               },
+                               removeMarkers: function()
+                               {
+                                       this.$editor.find('span.redactor-selection-marker').remove();
+                               },
+                               getText: function()
+                               {
+                                       this.selection.get();
 
-                       this.selectionRestore();
+                                       return this.sel.toString();
+                               },
+                               getHtml: function()
+                               {
+                                       var html = '';
 
-                       var table = this.$editor.find('#table' + tableId);
-                       this.buttonActiveObserver();
+                                       this.selection.get();
+                                       if (this.sel.rangeCount)
+                                       {
+                                               var container = document.createElement('div');
+                                               var len = this.sel.rangeCount;
+                                               for (var i = 0; i < len; ++i)
+                                               {
+                                                       container.appendChild(this.sel.getRangeAt(i).cloneContents());
+                                               }
 
-                       table.find('span#selection-marker-1, inline#selection-marker-1').remove();
-                       table.removeAttr('id');
+                                               html = container.innerHTML;
+                                       }
 
-                       this.sync();
+                                       return this.clean.onSync(html);
+                               }
+                       };
                },
-               tableDeleteTable: function()
+               observe: function()
                {
-                       var $table = $(this.getParent()).closest('table');
-                       if (!this.isParentRedactor($table)) return false;
-                       if ($table.size() == 0) return false;
+                       return {
+                               load: function()
+                               {
+                                       this.observe.images();
+                                       this.observe.links();
+                               },
+                               buttons: function(e, btnName)
+                               {
+                                       var current = this.selection.getCurrent();
+                                       var parent = this.selection.getParent();
 
-                       this.bufferSet();
+                                       this.button.setInactiveAll(btnName);
 
-                       $table.remove();
-                       this.sync();
-               },
-               tableDeleteRow: function()
-               {
-                       var parent = this.getParent();
-                       var $table = $(parent).closest('table');
+                                       if (e === false && btnName !== 'html')
+                                       {
+                                               if ($.inArray(btnName, this.opts.activeButtons) != -1) this.button.toggleActive(btnName);
+                                               return;
+                                       }
 
+                                       var linkButtonName = (this.utils.isCurrentOrParent('A')) ? this.lang.get('link_edit') : this.lang.get('link_insert');
+                                       $('body').find('a.redactor-dropdown-link').text(linkButtonName);
 
-                       if (!this.isParentRedactor($table)) return false;
-                       if ($table.size() == 0) return false;
+                                       $.each(this.opts.activeButtonsStates, $.proxy(function(key, value)
+                                       {
+                                               if ($(parent).closest(key).length !== 0 || $(current).closest(key).length !== 0)
+                                               {
+                                                       this.button.setActive(value);
+                                               }
 
-                       this.bufferSet();
+                                       }, this));
 
-                       var $current_tr = $(parent).closest('tr');
-                       var $focus_tr = $current_tr.prev().length ? $current_tr.prev() : $current_tr.next();
-                       if ($focus_tr.length)
-                       {
-                               var $focus_td = $focus_tr.children('td' ).first();
-                               if ($focus_td.length)
+                                       var $parent = $(parent).closest(this.opts.alignmentTags.toString().toLowerCase());
+                                       if ($parent.length)
+                                       {
+                                               var align = ($parent.css('text-align') === '') ? 'left' : $parent.css('text-align');
+                                               this.button.setActive('align' + align);
+                                       }
+                               },
+                               addButton: function(tag, btnName)
                                {
-                                       $focus_td.prepend('<span id="selection-marker-1">' + this.opts.invisibleSpace + '</span>');
-                               }
-                       }
-
-                       $current_tr.remove();
-                       this.selectionRestore();
-                       $table.find('span#selection-marker-1').remove();
-                       this.sync();
-               },
-               tableDeleteColumn: function()
-               {
-                       var parent = this.getParent();
-                       var $table = $(parent).closest('table');
+                                       this.opts.activeButtons.push(btnName);
+                                       this.opts.activeButtonsStates[tag] = btnName;
+                               },
+                               images: function()
+                               {
+                                       this.$editor.find('img').each($.proxy(function(i, img)
+                                       {
+                                               var $img = $(img);
 
-                       if (!this.isParentRedactor($table)) return false;
-                       if ($table.size() == 0) return false;
+                                               // IE fix (when we clicked on an image and then press backspace IE does goes to image's url)
+                                               $img.closest('a').on('click', function(e) { e.preventDefault(); });
 
-                       this.bufferSet();
+                                               if (this.utils.browser('msie')) $img.attr('unselectable', 'on');
 
-                       var $current_td = $(parent).closest('td');
-                       if (!($current_td.is('td')))
-                       {
-                               $current_td = $current_td.closest('td');
-                       }
+                                               this.image.setEditable($img);
 
-                       var index = $current_td.get(0).cellIndex;
+                                       }, this));
 
-                       // Set the focus correctly
-                       $table.find('tr').each($.proxy(function(i, elem)
-                       {
-                               var focusIndex = index - 1 < 0 ? index + 1 : index - 1;
-                               if (i === 0)
-                               {
-                                       $(elem).find('td').eq(focusIndex).prepend('<span id="selection-marker-1">' + this.opts.invisibleSpace + '</span>');
-                               }
+                                       $(document).on('click.redactor-image-delete', $.proxy(function(e)
+                                       {
+                                               this.observe.image = false;
+                                               if (e.target.tagName == 'IMG' && this.utils.isRedactorParent(e.target))
+                                               {
+                                                       this.observe.image = (this.observe.image && this.observe.image == e.target) ? false : e.target;
+                                               }
 
-                               $(elem).find('td').eq(index).remove();
+                                       }, this));
 
-                       }, this));
+                               },
+                               links: function()
+                               {
+                                       if (!this.opts.linkTooltip) return;
 
-                       this.selectionRestore();
-                       $table.find('span#selection-marker-1').remove();
-                       this.sync();
-               },
-               tableAddHead: function()
-               {
-                       var $table = $(this.getParent()).closest('table');
-                       if (!this.isParentRedactor($table)) return false;
-                       if ($table.size() == 0) return false;
+                                       this.$editor.find('a').on('touchstart click', $.proxy(this.observe.showTooltip, this));
+                                       this.$editor.on('touchstart click.redactor', $.proxy(this.observe.closeTooltip, this));
+                                       $(document).on('touchstart click.redactor', $.proxy(this.observe.closeTooltip, this));
+                               },
+                               getTooltipPosition: function($link)
+                               {
+                                       return $link.offset();
+                               },
+                               showTooltip: function(e)
+                               {
+                                       var $link = $(e.target);
+                                       if ($link.size() === 0 || $link[0].tagName !== 'A') return;
 
-                       this.bufferSet();
+                                       var pos = this.observe.getTooltipPosition($link);
 
-                       if ($table.find('thead').size() !== 0) this.tableDeleteHead();
-                       else
-                       {
-                               var tr = $table.find('tr').first().clone();
-                               tr.find('td').html(this.opts.invisibleSpace);
-                               $thead = $('<thead></thead>');
-                               $thead.append(tr);
-                               $table.prepend($thead);
+                                       var tooltip = $('<span class="redactor-link-tooltip"></span>');
 
-                               this.sync();
-                       }
-               },
-               tableDeleteHead: function()
-               {
-                       var $table = $(this.getParent()).closest('table');
-                       if (!this.isParentRedactor($table)) return false;
-                       var $thead = $table.find('thead');
+                                       var href = $link.attr('href');
+                                       if (href === undefined)
+                                       {
+                                               href = '';
+                                       }
 
-                       if ($thead.size() == 0) return false;
+                                       if (href.length > 24) href = href.substring(0, 24) + '...';
 
-                       this.bufferSet();
+                                       var aLink = $('<a href="' + $link.attr('href') + '" target="_blank" />').html(href).addClass('redactor-link-tooltip-action');
+                                       var aEdit = $('<a href="#" />').html(this.lang.get('edit')).on('click', $.proxy(this.link.show, this)).addClass('redactor-link-tooltip-action');
+                                       var aUnlink = $('<a href="#" />').html(this.lang.get('unlink')).on('click', $.proxy(this.link.unlink, this)).addClass('redactor-link-tooltip-action');
 
-                       $thead.remove();
-                       this.sync();
-               },
-               tableAddRowAbove: function()
-               {
-                       this.tableAddRow('before');
-               },
-               tableAddRowBelow: function()
-               {
-                       this.tableAddRow('after');
-               },
-               tableAddColumnLeft: function()
-               {
-                       this.tableAddColumn('before');
-               },
-               tableAddColumnRight: function()
-               {
-                       this.tableAddColumn('after');
-               },
-               tableAddRow: function(type)
-               {
-                       var $table = $(this.getParent()).closest('table');
-                       if (!this.isParentRedactor($table)) return false;
-                       if ($table.size() == 0) return false;
+                                       tooltip.append(aLink).append(' | ').append(aEdit).append(' | ').append(aUnlink);
+                                       tooltip.css({
+                                               top: (pos.top + 20) + 'px',
+                                               left: pos.left + 'px'
+                                       });
 
-                       this.bufferSet();
+                                       $('.redactor-link-tooltip').remove();
+                                       $('body').append(tooltip);
+                               },
+                               closeTooltip: function(e)
+                               {
+                                       e = e.originalEvent || e;
 
-                       var $current_tr = $(this.getParent()).closest('tr');
-                       var new_tr = $current_tr.clone();
-                       new_tr.find('td').html(this.opts.invisibleSpace);
+                                       if (e.target.tagName == 'A' && !$(e.target).hasClass('redactor-link-tooltip-action') && this.utils.isRedactorParent(e.target))
+                                       {
+                                               return;
+                                       }
 
-                       if (type === 'after') $current_tr.after(new_tr);
-                       else $current_tr.before(new_tr);
+                                       $('.redactor-link-tooltip').remove();
+                               }
 
-                       this.sync();
+                       };
                },
-               tableAddColumn: function (type)
+               link: function()
                {
-                       var parent = this.getParent();
-                       var $table = $(parent).closest('table');
+                       return {
+                               show: function(e)
+                               {
+                                       if (typeof e != 'undefined' && e.preventDefault) e.preventDefault();
 
-                       if (!this.isParentRedactor($table)) return false;
-                       if ($table.size() == 0) return false;
+                                       this.modal.load('link', this.lang.get('link_insert'), 600);
 
-                       this.bufferSet();
+                                       this.modal.createCancelButton();
+                                       this.link.buttonInsert = this.modal.createActionButton(this.lang.get('insert'));
 
-                       var index = 0;
+                                       this.selection.get();
 
-                       var current = this.getCurrent();
-                       var $current_tr = $(current).closest('tr');
-                       var $current_td =  $(current).closest('td');
+                                       this.link.getData();
+                                       this.link.cleanUrl();
 
-                       $current_tr.find('td').each($.proxy(function(i, elem)
-                       {
-                               if ($(elem)[0] === $current_td[0]) index = i;
+                                       if (this.link.target == '_blank') $('#redactor-link-blank').prop('checked', true);
 
-                       }, this));
+                                       this.link.$inputUrl = $('#redactor-link-url');
+                                       this.link.$inputText = $('#redactor-link-url-text');
 
-                       $table.find('tr').each($.proxy(function(i, elem)
-                       {
-                               var $current = $(elem).find('td').eq(index);
+                                       this.link.$inputText.val(this.link.text);
+                                       this.link.$inputUrl.val(this.link.url);
 
-                               var td = $current.clone();
-                               td.html(this.opts.invisibleSpace);
+                                       this.link.buttonInsert.on('click', $.proxy(this.link.insert, this));
 
-                               type === 'after' ? $current.after(td) : $current.before(td);
+                                       // show modal
+                                       this.selection.save();
+                                       this.modal.show();
+                                       this.link.$inputUrl.focus();
+                               },
+                               cleanUrl: function()
+                               {
+                                       var thref = self.location.href.replace(/\/$/i, '');
+                                       this.link.url = this.link.url.replace(thref, '');
+                                       this.link.url = this.link.url.replace(/^\/#/, '#');
+                                       this.link.url = this.link.url.replace('mailto:', '');
 
-                       }, this));
+                                       // remove host from href
+                                       if (!this.opts.linkProtocol)
+                                       {
+                                               var re = new RegExp('^(http|ftp|https)://' + self.location.host, 'i');
+                                               this.link.url = this.link.url.replace(re, '');
+                                       }
 
-                       this.sync();
-               },
+                               },
+                               getData: function()
+                               {
+                                       this.link.$node = false;
 
-               // VIDEO
-               videoShow: function()
-               {
-                       this.selectionSave();
+                                       var $el = $(this.selection.getCurrent()).closest('a');
+                                       if ($el.size() !== 0 && $el[0].tagName === 'A')
+                                       {
+                                               this.link.$node = $el;
 
-                       this.modalInit(this.opts.curLang.video, this.opts.modal_video, 600, $.proxy(function()
-                       {
-                               $('#redactor_insert_video_btn').click($.proxy(this.videoInsert, this));
+                                               this.link.url = $el.attr('href');
+                                               this.link.text = $el.text();
+                                               this.link.target = $el.attr('target');
+                                       }
+                                       else
+                                       {
+                                               this.link.text = this.sel.toString();
+                                               this.link.url = '';
+                                               this.link.target = '';
+                                       }
 
-                               setTimeout(function()
+                               },
+                               insert: function()
                                {
-                                       $('#redactor_insert_video_area').focus();
+                                       var target = '';
+                                       var link = this.link.$inputUrl.val();
+                                       var text = this.link.$inputText.val();
 
-                               }, 200);
+                                       if ($.trim(link) === '')
+                                       {
+                                               this.link.$inputUrl.addClass('redactor-input-error').on('keyup', function()
+                                               {
+                                                       $(this).removeClass('redactor-input-error');
+                                                       $(this).off('keyup');
 
-                       }, this));
-               },
-               videoInsert: function ()
-               {
-                       var data = $('#redactor_insert_video_area').val();
-                       data = this.cleanStripTags(data);
+                                               });
+                                               return;
+                                       }
 
-                       // parse if it is link on youtube & vimeo
-                       var iframeStart = '<iframe width="500" height="281" src="',
-                               iframeEnd = '" frameborder="0" allowfullscreen></iframe>';
+                                       // mailto
+                                       if (link.search('@') != -1 && /(http|ftp|https):\/\//i.test(link) === false)
+                                       {
+                                               link = 'mailto:' + link;
+                                       }
+                                       // url, not anchor
+                                       else if (link.search('#') !== 0)
+                                       {
+                                               if ($('#redactor-link-blank').prop('checked'))
+                                               {
+                                                       target = '_blank';
+                                               }
 
-                       if (data.match(reUrlYoutube))
-                       {
-                               data = data.replace(reUrlYoutube, iframeStart + '//www.youtube.com/embed/$1' + iframeEnd);
-                       }
-                       else if (data.match(reUrlVimeo))
-                       {
-                               data = data.replace(reUrlVimeo, iframeStart + '//player.vimeo.com/video/$2' + iframeEnd);
-                       }
+                                               // test url (add protocol)
+                                               var pattern = '((xn--)?[a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,}';
+                                               var re = new RegExp('^(http|ftp|https)://' + pattern, 'i');
+                                               var re2 = new RegExp('^' + pattern, 'i');
 
-                       this.selectionRestore();
+                                               if (link.search(re) == -1 && link.search(re2) === 0 && this.opts.linkProtocol)
+                                               {
+                                                       link = this.opts.linkProtocol + '://' + link;
+                                               }
+                                       }
 
-                       var current = this.getBlock() || this.getCurrent();
+                                       this.link.set(text, link, target);
+                                       this.modal.close();
+                               },
+                               set: function(text, link, target)
+                               {
+                                       text = $.trim(text.replace(/<|>/g, ''));
 
-                       if (current) $(current).after(data)
-                       else this.insertHtmlAdvanced(data, false);
+                                       this.selection.restore();
 
-                       this.sync();
-                       this.modalClose();
-               },
+                                       if (text === '') return;
 
+                                       if (this.link.$node)
+                                       {
+                                               this.buffer.set();
 
-               // LINK
-               linkShow: function()
-               {
-                       this.selectionSave();
+                                               this.link.$node.text(text).attr('href', link);
+                                               if (target !== '')
+                                               {
+                                                       this.link.$node.attr('target', target);
+                                               }
+                                               else
+                                               {
+                                                       this.link.$node.removeAttr('target');
+                                               }
 
-                       var callback = $.proxy(function()
-                       {
-                               // Predefined links
-                               if (this.opts.predefinedLinks !== false)
-                               {
-                                       this.predefinedLinksStorage = {};
-                                       var that = this;
-                                       $.getJSON(this.opts.predefinedLinks, function(data)
+                                               this.code.sync();
+                                       }
+                                       else
                                        {
-                                               var $select = $('#redactor-predefined-links');
-                                               $select .html('');
-                                               $.each(data, function(key, val)
-                                               {
-                                                       that.predefinedLinksStorage[key] = val;
-                                                       $select.append($('<option>').val(key).html(val.name));
-                                               });
+                                               document.execCommand('createLink', false, link);
 
-                                               $select.on('change', function()
+                                               var $a = $(this.selection.getCurrent()).closest('a');
+                                               if (target !== '')
                                                {
-                                                       var key = $(this).val();
-                                                       var name = '', url = '';
-                                                       if (key != 0)
-                                                       {
-                                                               name = that.predefinedLinksStorage[key].name;
-                                                               url = that.predefinedLinksStorage[key].url;
-                                                       }
-
-                                                       $('#redactor_link_url').val(url);
-                                                       $('#redactor_link_url_text').val(name);
+                                                       $a.attr('target', target);
+                                               }
 
-                                               });
+                                               $a.removeAttr('style');
 
-                                               $select.show();
-                                       });
-                               }
+                                               this.code.sync();
+                                               this.core.setCallback('insertedLink', $a);
 
-                               this.insert_link_node = false;
+                                       }
 
-                               var sel = this.getSelection();
-                               var url = '', text = '', target = '';
+                                       // link tooltip
+                                       setTimeout($.proxy(function()
+                                       {
+                                               this.observe.links();
 
-                               var elem = this.getParent();
-                               var par = $(elem).parent().get(0);
-                               if (par && par.tagName === 'A')
+                                       }, this), 5);
+                               },
+                               unlink: function(e)
                                {
-                                       elem = par;
-                               }
+                                       if (typeof e != 'undefined' && e.preventDefault) e.preventDefault();
 
-                               if (elem && elem.tagName === 'A')
-                               {
-                                       url = elem.href;
-                                       text = $(elem).text();
-                                       target = elem.target;
+                                       var nodes = this.selection.getNodes();
+                                       if (!nodes) return;
 
-                                       this.insert_link_node = elem;
-                               }
-                               else text = sel.toString();
+                                       this.buffer.set();
+
+                                       var len = nodes.length;
+                                       for (var i = 0; i < len; i++)
+                                       {
+                                               if (nodes[i].tagName == 'A')
+                                               {
+                                                       var $node = $(nodes[i]);
+                                                       $node.replaceWith($node.contents());
+                                               }
+                                       }
 
-                               $('#redactor_link_url_text').val(text);
+                                       // remove tooltip
+                                       $('.redactor-link-tooltip').remove();
 
-                               var thref = self.location.href.replace(/\/$/i, '');
-                               url = url.replace(thref, '');
-                               url = url.replace(/^\/#/, '#');
-                               url = url.replace('mailto:', '');
+                                       this.code.sync();
 
-                               // remove host from href
-                               if (this.opts.linkProtocol === false)
-                               {
-                                       var re = new RegExp('^(http|ftp|https)://' + self.location.host, 'i');
-                                       url = url.replace(re, '');
                                }
+                       };
+               },
+               image: function()
+               {
+                       return {
+                               show: function()
+                               {
+                                       this.modal.load('image', this.lang.get('image'), 700);
+                                       this.upload.init('#redactor-modal-image-droparea', this.opts.imageUpload, this.image.insert);
 
-                               // set url
-                               $('#redactor_link_url').val(url);
+                                       this.selection.save();
+                                       this.modal.show();
 
-                               if (target === '_blank')
+                               },
+                               showEdit: function($image)
                                {
-                                       $('#redactor_link_blank').prop('checked', true);
-                               }
+                                       var $link = $image.closest('a');
 
-                               this.linkInsertPressed = false;
-                               $('#redactor_insert_link_btn').on('click', $.proxy(this.linkProcess, this));
+                                       this.modal.load('imageEdit', this.lang.get('edit'), 705);
 
+                                       this.modal.createCancelButton();
+                                       this.image.buttonDelete = this.modal.createDeleteButton(this.lang.get('_delete'));
+                                       this.image.buttonSave = this.modal.createActionButton(this.lang.get('save'));
 
-                               setTimeout(function()
-                               {
-                                       $('#redactor_link_url').focus();
+                                       this.image.buttonDelete.on('click', $.proxy(function()
+                                       {
+                                               this.image.remove($image);
 
-                               }, 200);
+                                       }, this));
 
-                       }, this);
+                                       this.image.buttonSave.on('click', $.proxy(function()
+                                       {
+                                               this.image.update($image);
 
-                       this.modalInit(this.opts.curLang.link, this.opts.modal_link, 460, callback);
+                                       }, this));
 
-               },
-               linkProcess: function()
-               {
-                       if (this.linkInsertPressed)
-                       {
-                               return;
-                       }
 
-                       this.linkInsertPressed = true;
-                       var target = '', targetBlank = '';
+                                       $('#redactor-image-title').val($image.attr('alt'));
 
-                       var link = $('#redactor_link_url').val();
-                       var text = $('#redactor_link_url_text').val();
+                                       if (!this.opts.imageLink) $('.redactor-image-link-option').hide();
+                                       else
+                                       {
+                                               var $redactorImageLink = $('#redactor-image-link');
 
-                       // mailto
-                       if (link.search('@') != -1 && /(http|ftp|https):\/\//i.test(link) === false)
-                       {
-                               link = 'mailto:' + link;
-                       }
-                       // url, not anchor
-                       else if (link.search('#') != 0)
-                       {
-                               if ($('#redactor_link_blank').prop('checked'))
-                               {
-                                       target = ' target="_blank"';
-                                       targetBlank = '_blank';
-                               }
+                                               $redactorImageLink.attr('href', $image.attr('src'));
+                                               if ($link.size() !== 0)
+                                               {
+                                                       $redactorImageLink.val($link.attr('href'));
+                                                       if ($link.attr('target') == '_blank') $('#redactor-image-link-blank').prop('checked', true);
+                                               }
+                                       }
+
+                                       if (!this.opts.imagePosition) $('.redactor-image-position-option').hide();
+                                       else
+                                       {
+                                               var floatValue = ($image.css('display') == 'block' && $image.css('float') == 'none') ? 'center' : $image.css('float');
+                                               $('#redactor-image-align').val(floatValue);
+                                       }
 
-                               // test url (add protocol)
-                               var pattern = '((xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}';
-                               var re = new RegExp('^(http|ftp|https)://' + pattern, 'i');
-                               var re2 = new RegExp('^' + pattern, 'i');
+                                       this.modal.show();
 
-                               if (link.search(re) == -1 && link.search(re2) == 0 && this.opts.linkProtocol)
+                               },
+                               setFloating: function($image)
                                {
-                                       link = this.opts.linkProtocol + link;
-                               }
-                       }
+                                       var floating = $('#redactor-image-align').val();
 
-                       text = text.replace(/<|>/g, '');
-                       var extra = '&nbsp;';
-                       if (this.browser('mozilla'))
-                       {
-                               extra = '&nbsp;';
-                       }
+                                       var imageFloat = '';
+                                       var imageDisplay = '';
+                                       var imageMargin = '';
 
-                       this.linkInsert('<a href="' + link + '"' + target + '>' + text + '</a>' + extra, $.trim(text), link, targetBlank);
-
-               },
-               linkInsert: function (a, text, link, target)
-               {
-                       this.selectionRestore();
+                                       switch (floating)
+                                       {
+                                               case 'left':
+                                                       imageFloat = 'left';
+                                                       imageMargin = '0 ' + this.opts.imageFloatMargin + ' ' + this.opts.imageFloatMargin + ' 0';
+                                               break;
+                                               case 'right':
+                                                       imageFloat = 'right';
+                                                       imageMargin = '0 0 ' + this.opts.imageFloatMargin + ' ' + this.opts.imageFloatMargin;
+                                               break;
+                                               case 'center':
+                                                       imageDisplay = 'block';
+                                                       imageMargin = 'auto';
+                                               break;
+                                       }
 
-                       if (text !== '')
-                       {
-                               if (this.insert_link_node)
+                                       $image.css({ 'float': imageFloat, display: imageDisplay, margin: imageMargin });
+                                       $image.attr('rel', $image.attr('style'));
+                               },
+                               update: function($image)
                                {
-                                       this.bufferSet();
+                                       this.image.hideResize();
+                                       this.buffer.set();
 
-                                       $(this.insert_link_node).text(text).attr('href', link);
+                                       var $link = $image.closest('a');
 
-                                       if (target !== '')
+                                       $image.attr('alt', $('#redactor-image-title').val());
+
+                                       this.image.setFloating($image);
+
+                                       // as link
+                                       var link = $.trim($('#redactor-image-link').val());
+                                       if (link !== '')
                                        {
-                                               $(this.insert_link_node).attr('target', target);
+                                               var target = ($('#redactor-image-link-blank').prop('checked')) ? true : false;
+
+                                               if ($link.size() === 0)
+                                               {
+                                                       var a = $('<a href="' + link + '">' + this.utils.getOuterHtml($image) + '</a>');
+                                                       if (target) a.attr('target', '_blank');
+
+                                                       $image.replaceWith(a);
+                                               }
+                                               else
+                                               {
+                                                       $link.attr('href', link);
+                                                       if (target)
+                                                       {
+                                                               $link.attr('target', '_blank');
+                                                       }
+                                                       else
+                                                       {
+                                                               $link.removeAttr('target');
+                                                       }
+                                               }
                                        }
-                                       else
+                                       else if ($link.size() !== 0)
                                        {
-                                               $(this.insert_link_node).removeAttr('target');
+                                               $link.replaceWith(this.utils.getOuterHtml($image));
+
                                        }
-                               }
-                               else
+
+                                       this.modal.close();
+                                       this.observe.images();
+                                       this.code.sync();
+
+
+                               },
+                               setEditable: function($image)
                                {
-                                       var $a = $(a).addClass('redactor-added-link');
-                                       this.exec('inserthtml', this.outerHtml($a), false);
+                                       if (!this.opts.imageEditable) return;
 
-                                       var link = this.$editor.find('a.redactor-added-link');
+                                       $image.on('dragstart', $.proxy(this.image.onDrag, this));
 
-                                       link.removeAttr('style').removeClass('redactor-added-link').each(function()
+                                       $image.on('mousedown', $.proxy(this.image.hideResize, this));
+                                       $image.on('click touchstart', $.proxy(function(e)
                                        {
-                                               if (this.className == '') $(this).removeAttr('class');
-                                       });
+                                               this.observe.image = $image;
 
-                               }
+                                               if (this.$editor.find('#redactor-image-box').size() !== 0) return false;
 
-                               this.sync();
-                       }
+                                               // resize
+                                               if (!this.opts.imageResizable) return;
 
-                       // link tooltip
-                       setTimeout($.proxy(function()
-                       {
-                               if (this.opts.observeLinks) this.observeLinks();
+                                               this.image.resizer = this.image.loadEditableControls($image);
+                                               this.image.resizer.on('mousedown.redactor touchstart.redactor', $.proxy(function(e)
+                                               {
+                                                       e.preventDefault();
 
-                       }, this), 5);
+                                                   this.image.resizeHandle = {
+                                                       x : e.pageX,
+                                                       y : e.pageY,
+                                                       el : $image,
+                                                       ratio: $image.width() / $image.height(),
+                                                       h: $image.height()
+                                                   };
 
-                       this.modalClose();
-               },
+                                                   e = e.originalEvent || e;
 
-               // FILE
-               fileShow: function ()
-               {
+                                                   if (e.targetTouches)
+                                                   {
+                                                        this.image.resizeHandle.x = e.targetTouches[0].pageX;
+                                                        this.image.resizeHandle.y = e.targetTouches[0].pageY;
+                                                   }
 
-                       this.selectionSave();
+                                                       this.image.startResize();
 
-                       var callback = $.proxy(function()
-                       {
-                               var sel = this.getSelection();
+                                               }, this));
 
-                               var text = '';
-                               if (this.oldIE()) text = sel.text;
-                               else text = sel.toString();
 
-                               $('#redactor_filename').val(text);
+                                               $(document).on('click.redactor-image-resize-hide', $.proxy(this.image.hideResize, this));
+                                               this.$editor.on('click.redactor-image-resize-hide', $.proxy(this.image.hideResize, this));
 
-                               // dragupload
-                               if (!this.isMobile() && !this.isIPad())
+
+                                       }, this));
+                               },
+                               startResize: function()
                                {
-                                       this.draguploadInit('#redactor_file', {
-                                               url: this.opts.fileUpload,
-                                               uploadFields: this.opts.uploadFields,
-                                               success: $.proxy(this.fileCallback, this),
-                                               error: $.proxy( function(obj, json)
-                                               {
-                                                       this.callback('fileUploadError', json);
+                                       $(document).on('mousemove.redactor-image-resize touchmove.redactor-image-resize', $.proxy(this.image.moveResize, this));
+                                       $(document).on('mouseup.redactor-image-resize touchend.redactor-image-resize', $.proxy(this.image.stopResize, this));
+                               },
+                               moveResize: function(e)
+                               {
+                                       e.preventDefault();
 
-                                               }, this),
-                                               uploadParam: this.opts.fileUploadParam
-                                       });
-                               }
+                                       e = e.originalEvent || e;
 
-                               this.uploadInit('redactor_file', {
-                                       auto: true,
-                                       url: this.opts.fileUpload,
-                                       success: $.proxy(this.fileCallback, this),
-                                       error: $.proxy(function(obj, json)
-                                       {
-                                               this.callback('fileUploadError', json);
+                                       var height =  this.image.resizeHandle.h;
 
-                                       }, this)
-                               });
+                           if (e.targetTouches) height += (e.targetTouches[0].pageY -  this.image.resizeHandle.y);
+                           else height += (e.pageY -  this.image.resizeHandle.y);
 
-                       }, this);
 
-                       this.modalInit(this.opts.curLang.file, this.opts.modal_file, 500, callback);
-               },
-               fileCallback: function(json)
-               {
+                                       var width = Math.round(height *  this.image.resizeHandle.ratio);
 
-                       this.selectionRestore();
+                                       if (height < 50 || width < 100) return;
 
-                       if (json !== false)
-                       {
+                           this.image.resizeHandle.el.height(height);
+                           this.image.resizeHandle.el.width(width);
 
-                               var text = $('#redactor_filename').val();
-                               if (text === '') text = json.filename;
+                           this.code.sync();
+                               },
+                               stopResize: function()
+                               {
+                                       this.handle = false;
+                                       $(document).off('.redactor-image-resize');
+                                       this.image.hideResize();
+                               },
+                               onDrag: function(e)
+                               {
+                                       if (this.$editor.find('#redactor-image-box').size() !== 0)
+                                       {
+                                               e.preventDefault();
+                                               return false;
+                                       }
 
-                               var link = '<a href="' + json.filelink + '" id="filelink-marker">' + text + '</a>';
+                                       this.$editor.on('drop.redactor-image-inside-drop', $.proxy(function()
+                                       {
+                                               setTimeout($.proxy(this.image.onDrop, this), 1);
 
-                               // chrome fix
-                               if (this.browser('webkit') && !!this.window.chrome)
+                                       }, this));
+                               },
+                               onDrop: function()
                                {
-                                       link = link + '&nbsp;';
-                               }
+                                       this.image.fixImageSourceAfterDrop();
+                                       this.observe.images();
+                                       this.$editor.off('drop.redactor-image-inside-drop');
+                                       this.clean.clearUnverified();
+                                       this.code.sync();
+                               },
+                               fixImageSourceAfterDrop: function()
+                               {
+                                       this.$editor.find('img[data-save-url]').each(function()
+                                       {
+                                               var $el = $(this);
+                                               $el.attr('src', $el.attr('data-save-url'));
+                                               $el.removeAttr('data-save-url');
+                                       });
+                               },
+                               hideResize: function(e)
+                               {
+                                       if (e && $(e.target).closest('#redactor-image-box').length !== 0) return;
+                                       if (e && e.target.tagName == 'IMG')
+                                       {
+                                               var $image = $(e.target);
+                                               $image.attr('data-save-url', $image.attr('src'));
+                                       }
 
-                               this.execCommand('inserthtml', link, false);
+                                       var imageBox = this.$editor.find('#redactor-image-box');
+                                       if (imageBox.size() === 0) return;
 
-                               var linkmarker = $(this.$editor.find('a#filelink-marker'));
-                               if (linkmarker.size() != 0) linkmarker.removeAttr('id');
-                               else linkmarker = false;
+                                       this.image.editter.remove();
+                                       this.image.resizer.remove();
 
-                               this.sync();
+                                       imageBox.find('img').css({
+                                               marginTop: imageBox[0].style.marginTop,
+                                               marginBottom: imageBox[0].style.marginBottom,
+                                               marginLeft: imageBox[0].style.marginLeft,
+                                               marginRight: imageBox[0].style.marginRight
+                                       });
 
-                               // file upload callback
-                               this.callback('fileUpload', linkmarker, json);
-                       }
+                                       imageBox.css('margin', '');
+                                       imageBox.find('img').css('opacity', '');
+                                       imageBox.replaceWith(function()
+                                       {
+                                               return $(this).contents();
+                                       });
 
-                       this.modalClose();
-               },
+                                       $(document).off('click.redactor-image-resize-hide');
+                                       this.$editor.off('click.redactor-image-resize-hide');
 
-               // IMAGE
-               imageShow: function()
-               {
+                                       this.code.sync();
 
-                       this.selectionSave();
+                               },
+                               loadEditableControls: function($image)
+                               {
+                                       var imageBox = $('<span id="redactor-image-box" data-redactor="verified">');
+                                       imageBox.css({
+                                               position: 'relative',
+                                               display: 'inline-block',
+                                               lineHeight: 0,
+                                               outline: '1px dashed rgba(0, 0, 0, .6)',
+                                               'float': $image.css('float')
+                                       });
+                                       imageBox.attr('contenteditable', false);
 
-                       var callback = $.proxy(function()
-                       {
-                               // json
-                               if (this.opts.imageGetJson)
-                               {
+                                       if ($image[0].style.margin != 'auto')
+                                       {
+                                               imageBox.css({
+                                                       marginTop: $image[0].style.marginTop,
+                                                       marginBottom: $image[0].style.marginBottom,
+                                                       marginLeft: $image[0].style.marginLeft,
+                                                       marginRight: $image[0].style.marginRight
+                                               });
 
-                                       $.getJSON(this.opts.imageGetJson, $.proxy(function(data)
+                                               $image.css('margin', '');
+                                       }
+                                       else
                                        {
-                                               var folders = {}, count = 0;
+                                               imageBox.css({ 'display': 'block', 'margin': 'auto' });
+                                       }
 
-                                               // folders
-                                               $.each(data, $.proxy(function(key, val)
-                                               {
-                                                       if (typeof val.folder !== 'undefined')
-                                                       {
-                                                               count++;
-                                                               folders[val.folder] = count;
-                                                       }
+                                       $image.css('opacity', '.5').after(imageBox);
 
-                                               }, this));
+                                       // editter
+                                       this.image.editter = $('<span id="redactor-image-editter" data-redactor="verified">' + this.lang.get('edit') + '</span>');
+                                       this.image.editter.css({
+                                               position: 'absolute',
+                                               zIndex: 5,
+                                               top: '50%',
+                                               left: '50%',
+                                               marginTop: '-11px',
+                                               marginLeft: '-18px',
+                                               lineHeight: 1,
+                                               backgroundColor: '#000',
+                                               color: '#fff',
+                                               fontSize: '11px',
+                                               padding: '7px 10px',
+                                               cursor: 'pointer'
+                                       });
+                                       this.image.editter.attr('contenteditable', false);
+                                       this.image.editter.on('click', $.proxy(function()
+                                       {
+                                               this.image.showEdit($image);
+                                       }, this));
 
-                                               var folderclass = false;
-                                               $.each(data, $.proxy(function(key, val)
-                                               {
-                                                       // title
-                                                       var thumbtitle = '';
-                                                       if (typeof val.title !== 'undefined') thumbtitle = val.title;
+                                       imageBox.append(this.image.editter);
 
-                                                       var folderkey = 0;
-                                                       if (!$.isEmptyObject(folders) && typeof val.folder !== 'undefined')
-                                                       {
-                                                               folderkey = folders[val.folder];
-                                                               if (folderclass === false) folderclass = '.redactorfolder' + folderkey;
-                                                       }
+                                       // position correction
+                                       var editerWidth = this.image.editter.innerWidth();
+                                       this.image.editter.css('margin-left', '-' + editerWidth/2 + 'px');
 
-                                                       var img = $('<img src="' + val.thumb + '" class="redactorfolder redactorfolder' + folderkey + '" rel="' + val.image + '" title="' + thumbtitle + '" />');
-                                                       $('#redactor_image_box').append(img);
-                                                       $(img).click($.proxy(this.imageThumbClick, this));
 
-                                               }, this));
+                                       // resizer
+                                       if (this.opts.imageResizable && !this.utils.isMobile())
+                                       {
+                                               var imageResizer = $('<span id="redactor-image-resizer" data-redactor="verified"></span>');
+                                               imageResizer.css({
+                                                       position: 'absolute',
+                                                       zIndex: 2,
+                                                       lineHeight: 1,
+                                                       cursor: 'nw-resize',
+                                                       bottom: '-4px',
+                                                       right: '-5px',
+                                                       border: '1px solid #fff',
+                                                       backgroundColor: '#000',
+                                                       width: '8px',
+                                                       height: '8px'
+                                               });
 
-                                               // folders
-                                               if (!$.isEmptyObject(folders))
+                                               if (!this.utils.isDesktop())
                                                {
-                                                       $('.redactorfolder').hide();
-                                                       $(folderclass).show();
-
-                                                       var onchangeFunc = function(e)
-                                                       {
-                                                               $('.redactorfolder').hide();
-                                                               $('.redactorfolder' + $(e.target).val()).show();
-                                                       };
-
-                                                       var select = $('<select id="redactor_image_box_select">');
-                                                       $.each( folders, function(k, v)
-                                                       {
-                                                               select.append( $('<option value="' + v + '">' + k + '</option>'));
-                                                       });
-
-                                                       $('#redactor_image_box').before(select);
-                                                       select.change(onchangeFunc);
+                                                       imageResizer.css({ width: '15px', height: '15px' });
                                                }
-                                       }, this));
 
-                               }
-                               else
-                               {
-                                       $('#redactor-tab-control-2').remove();
-                               }
+                                               imageResizer.attr('contenteditable', false);
+                                               imageBox.append(imageResizer);
+                                               imageBox.append($image);
 
-                               if (this.opts.imageUpload || this.opts.s3)
+                                               return imageResizer;
+                                       }
+                                       else
+                                       {
+                                               imageBox.append($image);
+                                               return false;
+                                       }
+                               },
+                               remove: function(image)
                                {
-                                       // dragupload
-                                       if (!this.isMobile()  && !this.isIPad() && this.opts.s3 === false)
+                                       var $image = $(image);
+                                       var $link = $image.closest('a');
+                                       var $figure = $image.closest('figure');
+                                       var $parent = $image.parent();
+                                       if ($('#redactor-image-box').size() !== 0)
                                        {
-                                               if ($('#redactor_file' ).length)
-                                               {
-                                                       this.draguploadInit('#redactor_file', {
-                                                               url: this.opts.imageUpload,
-                                                               uploadFields: this.opts.uploadFields,
-                                                               success: $.proxy(this.imageCallback, this),
-                                                               error: $.proxy(function(obj, json)
-                                                               {
-                                                                       this.callback('imageUploadError', json);
-
-                                                               }, this),
-                                                               uploadParam: this.opts.imageUploadParam
-                                                       });
-                                               }
+                                               $parent = $('#redactor-image-box').parent();
                                        }
 
-                                       if (this.opts.s3 === false)
+                                       var $next;
+                                       if ($figure.size() !== 0)
                                        {
-                                               // ajax upload
-                                               this.uploadInit('redactor_file', {
-                                                       auto: true,
-                                                       url: this.opts.imageUpload,
-                                                       success: $.proxy(this.imageCallback, this),
-                                                       error: $.proxy(function(obj, json)
-                                                       {
-                                                               this.callback('imageUploadError', json);
-
-                                                       }, this)
-                                               });
+                                               $next = $figure.next();
+                                               $figure.remove();
+                                       }
+                                       else if ($link.size() !== 0)
+                                       {
+                                               $parent = $link.parent();
+                                               $link.remove();
                                        }
-                                       // s3 upload
                                        else
                                        {
-                                               $('#redactor_file').on('change.redactor', $.proxy(this.s3handleFileSelect, this));
+                                               $image.remove();
                                        }
 
-                               }
-                               else
-                               {
-                                       $('.redactor_tab').hide();
-                                       if (!this.opts.imageGetJson)
+                                       $('#redactor-image-box').remove();
+
+                                       if ($figure.size() !== 0)
                                        {
-                                               $('#redactor_tabs').remove();
-                                               $('#redactor_tab3').show();
+                                               this.caret.setStart($next);
                                        }
                                        else
                                        {
-                                               $('#redactor-tab-control-1').remove();
-                                               $('#redactor-tab-control-2').addClass('redactor_tabs_act');
-                                               $('#redactor_tab2').show();
+                                               this.caret.setStart($parent);
                                        }
-                               }
-
-                               if (!this.opts.imageTabLink && (this.opts.imageUpload || this.opts.imageGetJson))
-                               {
-                                       $('#redactor-tab-control-3').hide();
-                               }
 
-                               $('#redactor_upload_btn').click($.proxy(this.imageCallbackLink, this));
+                                       // delete callback
+                                       this.core.setCallback('imageDelete', $image[0].src, $image);
 
-                               if (!this.opts.imageUpload && !this.opts.imageGetJson)
+                                       this.modal.close();
+                                       this.code.sync();
+                               },
+                               insert: function(json, direct, e)
                                {
-                                       setTimeout(function()
+                                       // error callback
+                                       if (typeof json.error != 'undefined')
                                        {
-                                               $('#redactor_file_link').focus();
-
-                                       }, 200);
-                               }
-
-                       }, this);
-
-                       this.modalInit(this.opts.curLang.image, this.opts.modal_image, 610, callback);
-
-               },
-               imageEdit: function(image)
-               {
-                       var $el = image;
-                       var parent = $el.parent().parent();
-
-                       var callback = $.proxy(function()
-                       {
-                               $('#redactor_file_alt').val($el.attr('alt'));
-                               $('#redactor_image_edit_src').attr('href', $el.attr('src'));
-
-                               if ($el.css('display') == 'block' && $el.css('float') == 'none')
-                               {
-                                       $('#redactor_form_image_align').val('center');
-                               }
-                               else
-                               {
-                                       $('#redactor_form_image_align').val($el.css('float'));
-                               }
-
-                               if ($(parent).get(0).tagName === 'A')
-                               {
-                                       $('#redactor_file_link').val($(parent).attr('href'));
+                                               this.modal.close();
+                                               this.selection.restore();
+                                               this.core.setCallback('imageUploadError', json);
+                                               return;
+                                       }
 
-                                       if ($(parent).attr('target') == '_blank')
+                                       var $img;
+                                       if (typeof json == 'string')
                                        {
-                                               $('#redactor_link_blank').prop('checked', true);
+                                               $img = $(json).attr('data-redactor-inserted-image', 'true');
+                                       }
+                                       else
+                                       {
+                                               $img = $('<img>');
+                                               $img.attr('src', json.filelink).attr('data-redactor-inserted-image', 'true');
                                        }
-                               }
-
-                               $('#redactor_image_delete_btn').click($.proxy(function()
-                               {
-                                       this.imageRemove($el);
 
-                               }, this));
 
-                               $('#redactorSaveBtn').click($.proxy(function()
-                               {
-                                       this.imageSave($el);
+                                       var node = $img;
+                                       var isP = this.utils.isCurrentOrParent('P');
+                                       if (isP)
+                                       {
+                                               // will replace
+                                               node = $('<blockquote />').append($img);
+                                       }
 
-                               }, this));
+                                       if (direct)
+                                       {
+                                               this.selection.removeMarkers();
+                                               var marker = this.selection.getMarker();
+                                               this.insert.nodeToCaretPositionFromPoint(e, marker);
+                                       }
+                                       else
+                                       {
+                                               this.modal.close();
+                                       }
 
-                       }, this);
+                                       this.selection.restore();
+                                       this.buffer.set();
 
-                       this.modalInit(this.opts.curLang.edit, this.opts.modal_image_edit, 380, callback);
 
-               },
-               imageRemove: function(el)
-               {
-                       var parentLink = $(el).parent().parent();
-                       var parent = $(el).parent();
-                       var parentEl = false;
+                                       this.insert.html(this.utils.getOuterHtml(node));
 
-                       if (parentLink.length && parentLink[0].tagName === 'A')
-                       {
-                               parentEl = true;
-                               $(parentLink).remove();
-                       }
-                       else if (parent.length && parent[0].tagName === 'A')
-                       {
-                               parentEl = true;
-                               $(parent).remove();
-                       }
-                       else
-                       {
-                               $(el).remove();
-                       }
+                                       var $image = this.$editor.find('img[data-redactor-inserted-image=true]').removeAttr('data-redactor-inserted-image');
 
-                       if (parent.length && parent[0].tagName === 'P')
-                       {
-                               this.focusWithSaveScroll();
+                                       if (isP)
+                                       {
+                                               $image.parent().contents().unwrap().wrap('<p />');
+                                       }
+                                       else if (this.opts.linebreaks)
+                                       {
+                                               $image.before('<br>').after('<br>');
+                                       }
 
-                               if (parentEl === false) this.selectionStart(parent);
-                       }
+                                       if (typeof json == 'string') return;
 
-                       // delete callback
-                       this.callback('imageDelete', el);
+                                       this.core.setCallback('imageUpload', $image, json);
 
-                       this.modalClose();
-                       this.sync();
+                               }
+                       };
                },
-               imageSave: function(el)
+               file: function()
                {
-                       this.imageResizeHide(false);
-
-                       var $el = $(el);
-                       var parent = $el.parent();
-
-                       $el.attr('alt', $('#redactor_file_alt').val());
+                       return {
+                               show: function()
+                               {
+                                       this.modal.load('file', this.lang.get('file'), 700);
+                                       this.upload.init('#redactor-modal-file-upload', this.opts.fileUpload, this.file.insert);
 
-                       var floating = $('#redactor_form_image_align').val();
-                       var margin = '';
+                                       this.selection.save();
 
-                       if (floating === 'left')
-                       {
-                               margin = '0 ' + this.opts.imageFloatMargin + ' ' + this.opts.imageFloatMargin + ' 0';
-                               $el.css({ 'float': 'left', 'margin': margin });
-                       }
-                       else if (floating === 'right')
-                       {
-                               margin = '0 0 ' + this.opts.imageFloatMargin + ' ' + this.opts.imageFloatMargin + '';
-                               $el.css({ 'float': 'right', 'margin': margin });
-                       }
-                       else if (floating === 'center')
-                       {
-                               $el.css({ 'float': '', 'display': 'block', 'margin': 'auto' });
-                       }
-                       else
-                       {
-                               $el.css({ 'float': '', 'display': '', 'margin': '' });
-                       }
+                                       this.selection.get();
+                                       var text = this.sel.toString();
 
-                       // as link
-                       var link = $.trim($('#redactor_file_link').val());
-                       if (link !== '')
-                       {
-                               var target = false;
-                               if ($('#redactor_link_blank').prop('checked'))
-                               {
-                                       target = true;
-                               }
+                                       $('#redactor-filename').val(text);
 
-                               if (parent.get(0).tagName !== 'A')
+                                       this.modal.show();
+                               },
+                               insert: function(json, direct, e)
                                {
-                                       var a = $('<a href="' + link + '">' + this.outerHtml(el) + '</a>');
-
-                                       if (target)
+                                       // error callback
+                                       if (typeof json.error != 'undefined')
                                        {
-                                               a.attr('target', '_blank');
+                                               this.modal.close();
+                                               this.selection.restore();
+                                               this.core.setCallback('fileUploadError', json);
+                                               return;
                                        }
 
-                                       $el.replaceWith(a);
-                               }
-                               else
-                               {
-                                       parent.attr('href', link);
-                                       if (target)
+                                       var link;
+                                       if (typeof json == 'string')
                                        {
-                                               parent.attr('target', '_blank');
+                                               link = json;
                                        }
                                        else
                                        {
-                                               parent.removeAttr('target');
-                                       }
-                               }
-                       }
-                       else
-                       {
-                               if (parent.get(0).tagName === 'A')
-                               {
-                                       parent.replaceWith(this.outerHtml(el));
-                               }
-                       }
+                                               var text = $('#redactor-filename').val();
+                                               if (typeof text == 'undefined' || text === '') text = json.filename;
 
-                       this.modalClose();
-                       this.observeImages();
-                       this.sync();
-
-               },
-               imageResizeHide: function(e)
-               {
-                       if (e !== false && $(e.target).parent().size() != 0 && $(e.target).parent()[0].id === 'redactor-image-box')
-                       {
-                               return false;
-                       }
+                                               link = '<a href="' + json.filelink + '" id="filelink-marker">' + text + '</a>';
+                                       }
 
-                       var imageBox = this.$editor.find('#redactor-image-box');
-                       if (imageBox.size() == 0)
-                       {
-                               return false;
-                       }
+                                       if (direct)
+                                       {
+                                               this.selection.removeMarkers();
+                                               var marker = this.selection.getMarker();
+                                               this.insert.nodeToCaretPositionFromPoint(e, marker);
+                                       }
+                                       else
+                                       {
+                                               this.modal.close();
+                                       }
 
-                       this.$editor.find('#redactor-image-editter, #redactor-image-resizer').remove();
+                                       this.selection.restore();
+                                       this.buffer.set();
 
-                       imageBox.find('img').css({
-                               marginTop: imageBox[0].style.marginTop,
-                               marginBottom: imageBox[0].style.marginBottom,
-                               marginLeft: imageBox[0].style.marginLeft,
-                               marginRight: imageBox[0].style.marginRight
-                       });
 
-                       imageBox.css('margin', '');
+                                       this.insert.html(link);
 
+                                       if (typeof json == 'string') return;
 
-                       imageBox.find('img').css('opacity', '');
-                       imageBox.replaceWith(function()
-                       {
-                               return $(this).contents();
-                       });
+                                       var linkmarker = $(this.$editor.find('a#filelink-marker'));
+                                       if (linkmarker.size() !== 0) linkmarker.removeAttr('id');
+                                       else linkmarker = false;
 
-                       $(document).off('click.redactor-image-resize-hide');
-                       this.$editor.off('click.redactor-image-resize-hide');
-                       this.$editor.off('keydown.redactor-image-delete');
 
-                       this.sync()
+                                       this.core.setCallback('fileUpload', linkmarker, json);
 
+                               }
+                       };
                },
-               imageResize: function(image)
+               modal: function()
                {
-                       var $image = $(image);
-
-                       $image.on('mousedown', $.proxy(function()
-                       {
-                               this.imageResizeHide(false);
-                       }, this));
+                       return {
+                               callbacks: {},
+                               loadTemplates: function()
+                               {
+                                       this.opts.modal = {
+                                               imageEdit: String()
+                                               + '<section id="redactor-modal-image-edit">'
+                                                       + '<label>' + this.lang.get('title') + '</label>'
+                                                       + '<input type="text" id="redactor-image-title" />'
+                                                       + '<label class="redactor-image-link-option">' + this.lang.get('link') + '</label>'
+                                                       + '<input type="text" id="redactor-image-link" class="redactor-image-link-option" />'
+                                                       + '<label class="redactor-image-link-option"><input type="checkbox" id="redactor-image-link-blank"> ' + this.lang.get('link_new_tab') + '</label>'
+                                                       + '<label class="redactor-image-position-option">' + this.lang.get('image_position') + '</label>'
+                                                       + '<select class="redactor-image-position-option" id="redactor-image-align">'
+                                                               + '<option value="none">' + this.lang.get('none') + '</option>'
+                                                               + '<option value="left">' + this.lang.get('left') + '</option>'
+                                                               + '<option value="center">' + this.lang.get('center') + '</option>'
+                                                               + '<option value="right">' + this.lang.get('right') + '</option>'
+                                                       + '</select>'
+                                               + '</section>',
+
+                                               image: String()
+                                               + '<section id="redactor-modal-image-insert">'
+                                                       + '<div id="redactor-modal-image-droparea"></div>'
+                                               + '</section>',
+
+                                               file: String()
+                                               + '<section id="redactor-modal-file-insert">'
+                                                       + '<div id="redactor-modal-file-upload-box">'
+                                                               + '<label>' + this.lang.get('filename') + '</label>'
+                                                               + '<input type="text" id="redactor-filename" /><br><br>'
+                                                               + '<div id="redactor-modal-file-upload"></div>'
+                                                       + '</div>'
+                                               + '</section>',
+
+                                               link: String()
+                                               + '<section id="redactor-modal-link-insert">'
+                                                       + '<label>URL</label>'
+                                                       + '<input type="url" id="redactor-link-url" />'
+                                                       + '<label>' + this.lang.get('text') + '</label>'
+                                                       + '<input type="text" id="redactor-link-url-text" />'
+                                                       + '<label><input type="checkbox" id="redactor-link-blank"> ' + this.lang.get('link_new_tab') + '</label>'
+                                               + '</section>'
+                                       };
+
+
+                                       $.extend(this.opts, this.opts.modal);
 
-                       $image.on('dragstart', $.proxy(function()
-                       {
-                               this.$editor.on('drop.redactor-image-inside-drop', $.proxy(function()
+                               },
+                               addCallback: function(name, callback)
                                {
-                                       setTimeout($.proxy(function()
-                                       {
-                                               this.observeImages();
-                                               this.$editor.off('drop.redactor-image-inside-drop');
-                                               this.sync();
-
-                                       }, this), 1);
-
-                               },this));
-                       }, this));
-
-                       $image.on('click', $.proxy(function(e)
-                       {
-                               if (this.$editor.find('#redactor-image-box').size() != 0)
+                                       this.modal.callbacks[name] = callback;
+                               },
+                               createTabber: function($modal)
                                {
-                                       return false;
-                               }
-
-                               var clicked = false,
-                               start_x,
-                               start_y,
-                               ratio = $image.width() / $image.height(),
-                               min_w = 20,
-                               min_h = 10;
+                                       this.modal.$tabber = $('<div>').attr('id', 'redactor-modal-tabber');
 
-                               var imageResizer = this.imageResizeControls($image);
-
-                               // resize
-                               var isResizing = false;
-                               if (imageResizer !== false)
+                                       $modal.prepend(this.modal.$tabber);
+                               },
+                               addTab: function(id, name, active)
                                {
-                                       imageResizer.on('mousedown', function(e)
+                                       var $tab = $('<a href="#" rel="tab' + id + '">').text(name);
+                                       if (active)
                                        {
-                                               isResizing = true;
-                                               e.preventDefault();
-
-                                               ratio = $image.width() / $image.height();
-
-                                               start_x = Math.round(e.pageX - $image.eq(0).offset().left);
-                                               start_y = Math.round(e.pageY - $image.eq(0).offset().top);
-
-                                       });
+                                               $tab.addClass('active');
+                                       }
 
-                                       $(this.document.body).on('mousemove', $.proxy(function(e)
+                                       var self = this;
+                                       $tab.on('click', function(e)
                                        {
-                                               if (isResizing)
-                                               {
-                                                       var mouse_x = Math.round(e.pageX - $image.eq(0).offset().left) - start_x;
-                                                       var mouse_y = Math.round(e.pageY - $image.eq(0).offset().top) - start_y;
-
-                                                       var div_h = $image.height();
+                                               e.preventDefault();
+                                               $('.redactor-tab').hide();
+                                               $('.redactor-' + $(this).attr('rel')).show();
 
-                                                       var new_h = parseInt(div_h, 10) + mouse_y;
-                                                       var new_w = Math.round(new_h * ratio);
+                                               self.modal.$tabber.find('a').removeClass('active');
+                                               $(this).addClass('active');
 
-                                                       if (new_w > min_w)
-                                                       {
-                                                               $image.width(new_w);
+                                       });
 
-                                                               if (new_w < 100)
-                                                               {
-                                                                       this.imageEditter.css({
-                                                                               marginTop: '-7px',
-                                                                               marginLeft: '-13px',
-                                                                               fontSize: '9px',
-                                                                               padding: '3px 5px'
-                                                                       });
-                                                               }
-                                                               else
-                                                               {
-                                                                       this.imageEditter.css({
-                                                                               marginTop: '-11px',
-                                                                               marginLeft: '-18px',
-                                                                               fontSize: '11px',
-                                                                               padding: '7px 10px'
-                                                                       });
-                                                               }
-                                                       }
+                                       this.modal.$tabber.append($tab);
+                               },
+                               addTemplate: function(name, template)
+                               {
+                                       this.opts.modal[name] = template;
+                               },
+                               getTemplate: function(name)
+                               {
+                                       return this.opts.modal[name];
+                               },
+                               getModal: function()
+                               {
+                                       return this.$modalBody.find('section');
+                               },
+                               load: function(templateName, title, width)
+                               {
+                                       this.modal.templateName = templateName;
+                                       this.modal.width = width;
 
-                                                       start_x = Math.round(e.pageX - $image.eq(0).offset().left);
-                                                       start_y = Math.round(e.pageY - $image.eq(0).offset().top);
+                                       this.modal.build();
+                                       this.modal.enableEvents();
+                                       this.modal.setTitle(title);
+                                       this.modal.setDraggable();
+                                       this.modal.setContent();
 
-                                                       this.sync()
-                                               }
-                                       }, this)).on('mouseup', function()
+                                       // callbacks
+                                       if (typeof this.modal.callbacks[templateName] != 'undefined')
                                        {
-                                               isResizing = false;
-                                       });
-                               }
-
+                                               this.modal.callbacks[templateName].call(this);
+                                       }
 
-                               this.$editor.on('keydown.redactor-image-delete', $.proxy(function(e)
+                               },
+                               show: function()
                                {
-                                       var key = e.which;
+                                       this.modal.bodyOveflow = $(document.body).css('overflow');
+                                       $(document.body).css('overflow', 'hidden');
 
-                                       if (this.keyCode.BACKSPACE == key || this.keyCode.DELETE == key)
+                                       if (this.utils.isMobile())
                                        {
-                                               this.bufferSet(false);
-                                               this.imageResizeHide(false);
-                                               this.imageRemove($image);
+                                               this.modal.showOnMobile();
+                                       }
+                                       else
+                                       {
+                                               this.modal.showOnDesktop();
                                        }
 
-                               }, this));
-
-                               $(document).on('click.redactor-image-resize-hide', $.proxy(this.imageResizeHide, this));
-                               this.$editor.on('click.redactor-image-resize-hide', $.proxy(this.imageResizeHide, this));
-
-
-                       }, this));
-               },
-               imageResizeControls: function($image)
-               {
-                       var imageBox = $('<span id="redactor-image-box" data-redactor="verified">');
-                       imageBox.css({
-                               position: 'relative',
-                               display: 'inline-block',
-                               lineHeight: 0,
-                               outline: '1px dashed rgba(0, 0, 0, .6)',
-                               'float': $image.css('float')
-                       });
-                       imageBox.attr('contenteditable', false);
-
-                       if ($image[0].style.margin != 'auto')
-                       {
-                               imageBox.css({
-                                       marginTop: $image[0].style.marginTop,
-                                       marginBottom: $image[0].style.marginBottom,
-                                       marginLeft: $image[0].style.marginLeft,
-                                       marginRight: $image[0].style.marginRight
-                               });
-
-                               $image.css('margin', '');
-                       }
-                       else
-                       {
-                               imageBox.css({ 'display': 'block', 'margin': 'auto' });
-                       }
-
-                       $image.css('opacity', .5).after(imageBox);
-
-                       // editter
-                       this.imageEditter = $('<span id="redactor-image-editter" data-redactor="verified">' + this.opts.curLang.edit + '</span>');
-                       this.imageEditter.css({
-                               position: 'absolute',
-                               zIndex: 5,
-                               top: '50%',
-                               left: '50%',
-                               marginTop: '-11px',
-                               marginLeft: '-18px',
-                               lineHeight: 1,
-                               backgroundColor: '#000',
-                               color: '#fff',
-                               fontSize: '11px',
-                               padding: '7px 10px',
-                               cursor: 'pointer'
-                       });
-                       this.imageEditter.attr('contenteditable', false);
-                       this.imageEditter.on('click', $.proxy(function()
-                       {
-                               this.imageEdit($image);
-                       }, this));
-                       imageBox.append(this.imageEditter);
-
-                       // resizer
-                       if (this.opts.imageResizable)
-                       {
-                               var imageResizer = $('<span id="redactor-image-resizer" data-redactor="verified"></span>');
-                               imageResizer.css({
-                                       position: 'absolute',
-                                       zIndex: 2,
-                                       lineHeight: 1,
-                                       cursor: 'nw-resize',
-                                       bottom: '-4px',
-                                       right: '-5px',
-                                       border: '1px solid #fff',
-                                       backgroundColor: '#000',
-                                       width: '8px',
-                                       height: '8px'
-                               });
-                               imageResizer.attr('contenteditable', false);
-                               imageBox.append(imageResizer);
-
-                               imageBox.append($image);
-
-                               return imageResizer;
-                       }
-                       else
-                       {
-                               imageBox.append($image);
-
-                               return false;
-                       }
-               },
-               imageThumbClick: function(e)
-               {
-                       var img = '<img id="image-marker" src="' + $(e.target).attr('rel') + '" alt="' + $(e.target).attr('title') + '" />';
-
-                       var parent = this.getParent();
-                       if (this.opts.paragraphy && $(parent).closest('li').size() == 0) img = '<p>' + img + '</p>';
-
-                       this.imageInsert(img, true);
-               },
-               imageCallbackLink: function()
-               {
-                       var val = $('#redactor_file_link').val();
-
-                       if (val !== '')
-                       {
-                               var data = '<img id="image-marker" src="' + val + '" />';
-                               if (this.opts.linebreaks === false) data = '<p>' + data + '</p>';
-
-                               this.imageInsert(data, true);
-
-                       }
-                       else this.modalClose();
-               },
-               imageCallback: function(data)
-               {
-                       this.imageInsert(data);
-               },
-               imageInsert: function(json, link)
-               {
-                       this.selectionRestore();
-
-                       if (json !== false)
-                       {
-                               var html = '';
-                               if (link !== true)
-                               {
-                                       html = '<img id="image-marker" src="' + json.filelink + '" />';
+                                       this.$modalOverlay.show();
+                                       this.$modalBox.show();
+
+                                       this.modal.setButtonsWidth();
+
+                                       this.utils.saveScroll();
 
-                                       var parent = this.getParent();
-                                       if (this.opts.paragraphy && $(parent).closest('li').size() == 0)
+                                       // resize
+                                       if (!this.utils.isMobile())
                                        {
-                                               html = '<p>' + html + '</p>';
+                                               setTimeout($.proxy(this.modal.showOnDesktop, this), 0);
+                                               $(window).on('resize.redactor-modal', $.proxy(this.modal.resize, this));
                                        }
-                               }
-                               else
-                               {
-                                       html = json;
-                               }
 
-                               this.execCommand('inserthtml', html, false);
+                                       // modal shown callback
+                                       this.core.setCallback('modalOpened', this.modal.templateName, this.$modal);
 
-                               var image = $(this.$editor.find('img#image-marker'));
+                                       // fix bootstrap modal focus
+                                       $(document).off('focusin.modal');
 
-                               if (image.length) image.removeAttr('id');
-                               else image = false;
+                                       // enter
+                                       this.$modal.find('input[type=text]').on('keypress.redactor-modal', $.proxy(this.modal.setEnter, this));
 
-                               this.sync();
-
-                               // upload image callback
-                               link !== true && this.callback('imageUpload', image, json);
-                       }
+                               },
+                               showOnDesktop: function()
+                               {
+                                       var height = this.$modal.outerHeight();
+                                       var windowHeight = $(window).height();
+                                       var windowWidth = $(window).width();
 
-                       this.modalClose();
-                       this.observeImages();
-               },
+                                       if (this.modal.width > windowWidth)
+                                       {
+                                               this.$modal.css({
+                                                       width: '96%',
+                                                       marginTop: (windowHeight/2 - height/2) + 'px'
+                                               });
+                                               return;
+                                       }
 
-               // PROGRESS BAR
-               buildProgressBar: function()
-               {
-                       if ($('#redactor-progress').size() != 0) return;
+                                       if (height > windowHeight)
+                                       {
+                                               this.$modal.css({
+                                                       width: this.modal.width + 'px',
+                                                       marginTop: '20px'
+                                               });
+                                       }
+                                       else
+                                       {
+                                               this.$modal.css({
+                                                       width: this.modal.width + 'px',
+                                                       marginTop: (windowHeight/2 - height/2) + 'px'
+                                               });
+                                       }
+                               },
+                               showOnMobile: function()
+                               {
+                                       this.$modal.css({
+                                               width: '96%',
+                                               marginTop: '2%'
+                                       });
 
-                       this.$progressBar = $('<div id="redactor-progress"><span></span></div>');
-                       $(document.body).append(this.$progressBar);
-               },
-               showProgressBar: function()
-               {
-                       this.buildProgressBar();
-                       $('#redactor-progress').fadeIn();
-               },
-               hideProgressBar: function()
-               {
-                       $('#redactor-progress').fadeOut(1500);
-               },
+                               },
+                               resize: function()
+                               {
+                                       if (this.utils.isMobile())
+                                       {
+                                               this.modal.showOnMobile();
+                                       }
+                                       else
+                                       {
+                                               this.modal.showOnDesktop();
+                                       }
+                               },
+                               setTitle: function(title)
+                               {
+                                       this.$modalHeader.html(title);
+                               },
+                               setContent: function()
+                               {
+                                       this.$modalBody.html(this.modal.getTemplate(this.modal.templateName));
+                               },
+                               setDraggable: function()
+                               {
+                                       if (typeof $.fn.draggable === 'undefined') return;
 
-               // MODAL
-               modalTemplatesInit: function()
-               {
-                       $.extend( this.opts,
-                       {
-                               modal_file: String()
-                               + '<section id="redactor-modal-file-insert">'
-                                       + '<form id="redactorUploadFileForm" method="post" action="" enctype="multipart/form-data">'
-                                               + '<label>' + this.opts.curLang.filename + '</label>'
-                                               + '<input type="text" id="redactor_filename" class="redactor_input" />'
-                                               + '<div style="margin-top: 7px;">'
-                                                       + '<input type="file" id="redactor_file" name="' + this.opts.fileUploadParam + '" />'
-                                               + '</div>'
-                                       + '</form>'
-                               + '</section>',
-
-                               modal_image_edit: String()
-                               + '<section id="redactor-modal-image-edit">'
-                                       + '<label>' + this.opts.curLang.title + '</label>'
-                                       + '<input type="text" id="redactor_file_alt" class="redactor_input" />'
-                                       + '<label>' + this.opts.curLang.link + '</label>'
-                                       + '<input type="text" id="redactor_file_link" class="redactor_input" />'
-                                       + '<label><input type="checkbox" id="redactor_link_blank"> ' + this.opts.curLang.link_new_tab + '</label>'
-                                       + '<label>' + this.opts.curLang.image_position + '</label>'
-                                       + '<select id="redactor_form_image_align">'
-                                               + '<option value="none">' + this.opts.curLang.none + '</option>'
-                                               + '<option value="left">' + this.opts.curLang.left + '</option>'
-                                               + '<option value="center">' + this.opts.curLang.center + '</option>'
-                                               + '<option value="right">' + this.opts.curLang.right + '</option>'
-                                       + '</select>'
-                               + '</section>'
-                               + '<footer>'
-                                       + '<button id="redactor_image_delete_btn" class="redactor_modal_btn redactor_modal_delete_btn">' + this.opts.curLang._delete + '</button>'
-                                       + '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</button>'
-                                       + '<button id="redactorSaveBtn" class="redactor_modal_btn redactor_modal_action_btn">' + this.opts.curLang.save + '</button>'
-                               + '</footer>',
-
-                               modal_image: String()
-                               + '<section id="redactor-modal-image-insert">'
-                                       + '<div id="redactor_tabs">'
-                                               + '<a href="#" id="redactor-tab-control-1" class="redactor_tabs_act">' + this.opts.curLang.upload + '</a>'
-                                               + '<a href="#" id="redactor-tab-control-2">' + this.opts.curLang.choose + '</a>'
-                                               + '<a href="#" id="redactor-tab-control-3">' + this.opts.curLang.link + '</a>'
-                                       + '</div>'
-                                       + '<form id="redactorInsertImageForm" method="post" action="" enctype="multipart/form-data">'
-                                               + '<div id="redactor_tab1" class="redactor_tab">'
-                                                       + '<input type="file" id="redactor_file" name="' + this.opts.imageUploadParam + '" />'
-                                               + '</div>'
-                                               + '<div id="redactor_tab2" class="redactor_tab" style="display: none;">'
-                                                       + '<div id="redactor_image_box"></div>'
-                                               + '</div>'
-                                       + '</form>'
-                                       + '<div id="redactor_tab3" class="redactor_tab" style="display: none;">'
-                                               + '<label>' + this.opts.curLang.image_web_link + '</label>'
-                                               + '<input type="text" name="redactor_file_link" id="redactor_file_link" class="redactor_input"  /><br><br>'
-                                       + '</div>'
-                               + '</section>'
-                               + '<footer>'
-                                       + '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</button>'
-                                       + '<button class="redactor_modal_btn redactor_modal_action_btn" id="redactor_upload_btn">' + this.opts.curLang.insert + '</button>'
-                               + '</footer>',
-
-                               modal_link: String()
-                               + '<section id="redactor-modal-link-insert">'
-                                       + '<select id="redactor-predefined-links" style="width: 99.5%; display: none;"></select>'
-                                       + '<label>URL</label>'
-                                       + '<input type="text" class="redactor_input" id="redactor_link_url" />'
-                                       + '<label>' + this.opts.curLang.text + '</label>'
-                                       + '<input type="text" class="redactor_input" id="redactor_link_url_text" />'
-                                       + '<label><input type="checkbox" id="redactor_link_blank"> ' + this.opts.curLang.link_new_tab + '</label>'
-                               + '</section>'
-                               + '<footer>'
-                                       + '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</button>'
-                                       + '<button id="redactor_insert_link_btn" class="redactor_modal_btn redactor_modal_action_btn">' + this.opts.curLang.insert + '</button>'
-                               + '</footer>',
-
-                               modal_table: String()
-                               + '<section id="redactor-modal-table-insert">'
-                                       + '<label>' + this.opts.curLang.rows + '</label>'
-                                       + '<input type="text" size="5" value="2" id="redactor_table_rows" />'
-                                       + '<label>' + this.opts.curLang.columns + '</label>'
-                                       + '<input type="text" size="5" value="3" id="redactor_table_columns" />'
-                               + '</section>'
-                               + '<footer>'
-                                       + '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</button>'
-                                       + '<button id="redactor_insert_table_btn" class="redactor_modal_btn redactor_modal_action_btn">' + this.opts.curLang.insert + '</button>'
-                               + '</footer>',
-
-                               modal_video: String()
-                               + '<section id="redactor-modal-video-insert">'
-                                       + '<form id="redactorInsertVideoForm">'
-                                               + '<label>' + this.opts.curLang.video_html_code + '</label>'
-                                               + '<textarea id="redactor_insert_video_area" style="width: 99%; height: 160px;"></textarea>'
-                                       + '</form>'
-                               + '</section>'
-                               + '<footer>'
-                                       + '<button class="redactor_modal_btn redactor_btn_modal_close">' + this.opts.curLang.cancel + '</button>'
-                                       + '<button id="redactor_insert_video_btn" class="redactor_modal_btn redactor_modal_action_btn">' + this.opts.curLang.insert + '</button>'
-                               + '</footer>'
+                                       this.$modal.draggable({ handle: this.$modalHeader });
+                                       this.$modalHeader.css('cursor', 'move');
+                               },
+                               setEnter: function(e)
+                               {
+                                       if (e.which != 13) return;
 
-                       });
-               },
-               modalInit: function(title, content, width, callback)
-               {
-                       this.modalSetOverlay();
+                                       e.preventDefault();
+                                       this.$modal.find('button.redactor-modal-action-btn').click();
+                               },
+                               createCancelButton: function()
+                               {
+                                       var button = $('<button>').addClass('redactor-modal-btn redactor-modal-close-btn').html(this.lang.get('cancel'));
+                                       button.on('click', $.proxy(this.modal.close, this));
 
-                       this.$redactorModalWidth = width;
-                       this.$redactorModal = $('#redactor_modal');
+                                       this.$modalFooter.append(button);
+                               },
+                               createDeleteButton: function(label)
+                               {
+                                       return this.modal.createButton(label, 'delete');
+                               },
+                               createActionButton: function(label)
+                               {
+                                       return this.modal.createButton(label, 'action');
+                               },
+                               createButton: function(label, className)
+                               {
+                                       var button = $('<button>').addClass('redactor-modal-btn').addClass('redactor-modal-' + className + '-btn').html(label);
+                                       this.$modalFooter.append(button);
 
-                       if (!this.$redactorModal.length)
-                       {
-                               this.$redactorModal = $('<div id="redactor_modal" style="display: none;" />');
-                               this.$redactorModal.append($('<div id="redactor_modal_close">&times;</div>'));
-                               this.$redactorModal.append($('<header id="redactor_modal_header" />'));
-                               this.$redactorModal.append($('<div id="redactor_modal_inner" />'));
-                               this.$redactorModal.appendTo(document.body);
-                       }
+                                       return button;
+                               },
+                               setButtonsWidth: function()
+                               {
+                                       var buttons = this.$modalFooter.find('button');
+                                       var buttonsSize = buttons.size();
+                                       if (buttonsSize === 0) return;
 
-                       $('#redactor_modal_close').on('click', $.proxy(this.modalClose, this));
-                       $(document).on('keyup', $.proxy(this.modalCloseHandler, this));
-                       this.$editor.on('keyup', $.proxy(this.modalCloseHandler, this));
+                                       buttons.css('width', (100/buttonsSize) + '%');
+                               },
+                               build: function()
+                               {
+                                       this.modal.buildOverlay();
+
+                                       this.$modalBox = $('<div id="redactor-modal-box" />').hide();
+                                       this.$modal = $('<div id="redactor-modal" />');
+                                       this.$modalHeader = $('<header />');
+                                       this.$modalClose = $('<span id="redactor-modal-close" />').html('&times;');
+                                       this.$modalBody = $('<div id="redactor-modal-body" />');
+                                       this.$modalFooter = $('<footer />');
+
+                                       this.$modal.append(this.$modalHeader);
+                                       this.$modal.append(this.$modalClose);
+                                       this.$modal.append(this.$modalBody);
+                                       this.$modal.append(this.$modalFooter);
+                                       this.$modalBox.append(this.$modal);
+                                       this.$modalBox.appendTo(document.body);
+                               },
+                               buildOverlay: function()
+                               {
+                                       this.$modalOverlay = $('<div id="redactor-modal-overlay">').hide();
+                                       $('body').prepend(this.$modalOverlay);
+                               },
+                               enableEvents: function()
+                               {
+                                       this.$modalClose.on('click.redactor-modal', $.proxy(this.modal.close, this));
+                                       $(document).on('keyup.redactor-modal', $.proxy(this.modal.closeHandler, this));
+                                       this.$editor.on('keyup.redactor-modal', $.proxy(this.modal.closeHandler, this));
+                                       this.$modalBox.on('click.redactor-modal', $.proxy(this.modal.close, this));
+                               },
+                               disableEvents: function()
+                               {
+                                       this.$modalClose.off('click.redactor-modal');
+                                       $(document).off('keyup.redactor-modal');
+                                       this.$editor.off('keyup.redactor-modal');
+                                       this.$modalBox.off('click.redactor-modal');
+                                       $(window).off('resize.redactor-modal');
+                               },
+                               closeHandler: function(e)
+                               {
+                                       if (e.which != this.keyCode.ESC) return;
 
-                       this.modalSetContent(content);
-                       this.modalSetTitle(title);
-                       this.modalSetDraggable();
-                       this.modalLoadTabs();
-                       this.modalOnCloseButton();
-                       this.modalSetButtonsWidth();
+                                       this.modal.close(false);
+                               },
+                               close: function(e)
+                               {
+                                       if (e)
+                                       {
+                                               if (!$(e.target).hasClass('redactor-modal-close-btn') && e.target != this.$modalClose[0] && e.target != this.$modalBox[0])
+                                               {
+                                                       return;
+                                               }
 
-                       this.saveModalScroll = this.document.body.scrollTop;
-                       if (this.opts.autoresize === false)
-                       {
-                               this.saveModalScroll = this.$editor.scrollTop();
-                       }
+                                               e.preventDefault();
+                                       }
 
-                       if (this.isMobile() === false) this.modalShowOnDesktop();
-                       else this.modalShowOnMobile();
+                                       if (!this.$modalBox) return;
 
-                       // modal actions callback
-                       if (typeof callback === 'function')
-                       {
-                               callback();
-                       }
+                                       this.modal.disableEvents();
 
-                       // modal shown callback
-                       setTimeout($.proxy(function()
-                       {
-                               this.callback('modalOpened', this.$redactorModal);
+                                       this.$modalOverlay.remove();
 
-                       }, this), 11);
+                                       this.$modalBox.fadeOut('fast', $.proxy(function()
+                                       {
+                                               this.$modalBox.remove();
 
-                       // fix bootstrap modal focus
-                       $(document).off('focusin.modal');
+                                               setTimeout($.proxy(this.utils.restoreScroll, this), 0);
 
-                       // enter
-                       this.$redactorModal.find('input[type=text]').on('keypress', $.proxy(function(e)
-                       {
-                               if (e.which === 13)
-                               {
-                                       this.$redactorModal.find('.redactor_modal_action_btn').click();
-                                       e.preventDefault();
-                               }
-                       }, this));
+                                               if (e !== undefined) this.selection.restore();
 
-                       return this.$redactorModal;
+                                               $(document.body).css('overflow', this.modal.bodyOveflow);
+                                               this.core.setCallback('modalClosed', this.modal.templateName);
 
-               },
-               modalShowOnDesktop: function()
-               {
-                       this.$redactorModal.css({
-                               position: 'fixed',
-                               top: '-2000px',
-                               left: '50%',
-                               width: this.$redactorModalWidth + 'px',
-                               marginLeft: '-' + (this.$redactorModalWidth / 2) + 'px'
-                       }).show();
-
-                       this.modalSaveBodyOveflow = $(document.body).css('overflow');
-                       $(document.body).css('overflow', 'hidden');
-
-                       setTimeout($.proxy(function()
-                       {
-                               var height = this.$redactorModal.outerHeight();
-                               this.$redactorModal.css({
-                                       top: '50%',
-                                       height: 'auto',
-                                       minHeight: 'auto',
-                                       marginTop: '-' + (height + 10) / 2 + 'px'
-                               });
-                       }, this), 15);
-               },
-               modalShowOnMobile: function()
-               {
-                       this.$redactorModal.css({
-                               position: 'fixed',
-                               width: '100%',
-                               height: '100%',
-                               top: '0',
-                               left: '0',
-                               margin: '0',
-                               minHeight: '300px'
-                       }).show();
-               },
-               modalSetContent: function(content)
-               {
-                       this.modalcontent = false;
-                       if (content.indexOf('#') == 0)
-                       {
-                               this.modalcontent = $(content);
-                               $('#redactor_modal_inner').empty().append(this.modalcontent.html());
-                               this.modalcontent.html('');
+                                       }, this));
 
-                       }
-                       else
-                       {
-                               $('#redactor_modal_inner').empty().append(content);
-                       }
-               },
-               modalSetTitle: function(title)
-               {
-                       this.$redactorModal.find('#redactor_modal_header').html(title);
-               },
-               modalSetButtonsWidth: function()
-               {
-                       var buttons = this.$redactorModal.find('footer button').not('.redactor_modal_btn_hidden');
-                       var buttonsSize = buttons.size();
-                       if (buttonsSize > 0)
-                       {
-                               $(buttons).css('width', (this.$redactorModalWidth/buttonsSize) + 'px')
-                       }
-               },
-               modalOnCloseButton: function()
-               {
-                       this.$redactorModal.find('.redactor_btn_modal_close').on('click', $.proxy(this.modalClose, this));
+                               }
+                       };
                },
-               modalSetOverlay: function()
+               progress: function()
                {
-                       if (this.opts.modalOverlay)
-                       {
-                               this.$redactorModalOverlay = $('#redactor_modal_overlay');
-                               if (!this.$redactorModalOverlay.length)
+                       return {
+                               show: function()
                                {
-                                       this.$redactorModalOverlay = $('<div id="redactor_modal_overlay" style="display: none;"></div>');
-                                       $('body').prepend(this.$redactorModalOverlay);
+                                       $(document.body).append($('<div id="redactor-progress"><span></span></div>'));
+                                       $('#redactor-progress').fadeIn();
+                               },
+                               hide: function()
+                               {
+                                       $('#redactor-progress').fadeOut(1500, function()
+                                       {
+                                               $(this).remove();
+                                       });
                                }
 
-                               this.$redactorModalOverlay.show().on('click', $.proxy(this.modalClose, this));
-                       }
-               },
-               modalSetDraggable: function()
-               {
-                       if (typeof $.fn.draggable !== 'undefined')
-                       {
-                               this.$redactorModal.draggable({ handle: '#redactor_modal_header' });
-                               this.$redactorModal.find('#redactor_modal_header').css('cursor', 'move');
-                       }
+                       };
                },
-               modalLoadTabs: function()
+               upload: function()
                {
-                       var $redactor_tabs = $('#redactor_tabs');
-                       if (!$redactor_tabs.length) return false;
-
-                       var that = this;
-                       $redactor_tabs.find('a').each(function(i, s)
-                       {
-                               i++;
-                               $(s).on('click', function(e)
+                       return {
+                               init: function(id, url, callback)
                                {
-                                       e.preventDefault();
+                                       this.upload.direct = false;
+                                       this.upload.callback = callback;
+                                       this.upload.url = url;
+                                       this.upload.$el = $(id);
+                                       this.upload.$droparea = $('<div id="redactor-droparea" />');
+
+                                       this.upload.$placeholdler = $('<div id="redactor-droparea-placeholder" />').text('Drop file here or ');
+                                       this.upload.$input = $('<input type="file" name="file" />');
 
-                                       $redactor_tabs.find('a').removeClass('redactor_tabs_act');
-                                       $(this).addClass('redactor_tabs_act');
-                                       $('.redactor_tab').hide();
-                                       $('#redactor_tab' + i ).show();
-                                       $('#redactor_tab_selected').val(i);
+                                       this.upload.$placeholdler.append(this.upload.$input);
+                                       this.upload.$droparea.append(this.upload.$placeholdler);
+                                       this.upload.$el.append(this.upload.$droparea);
 
-                                       if (that.isMobile() === false)
+                                       this.upload.$droparea.off('redactor.upload');
+                                       this.upload.$input.off('redactor.upload');
+
+                                       this.upload.$droparea.on('dragover.redactor.upload', $.proxy(this.upload.onDrag, this));
+                                       this.upload.$droparea.on('dragleave.redactor.upload', $.proxy(this.upload.onDragLeave, this));
+
+                                       // change
+                                       this.upload.$input.on('change.redactor.upload', $.proxy(function(e)
                                        {
-                                               var height = that.$redactorModal.outerHeight();
-                                               that.$redactorModal.css('margin-top', '-' + (height + 10) / 2 + 'px');
-                                       }
-                               });
-                       });
+                                               e = e.originalEvent || e;
+                                               this.upload.traverseFile(this.upload.$input[0].files[0], e);
+                                       }, this));
 
-               },
-               modalCloseHandler: function(e)
-               {
-                       if (e.keyCode === this.keyCode.ESC)
-                       {
-                               this.modalClose();
-                               return false;
-                       }
-               },
-               modalClose: function()
-               {
-                       $('#redactor_modal_close').off('click', this.modalClose);
-                       $('#redactor_modal').fadeOut('fast', $.proxy(function()
-                       {
-                               var redactorModalInner = $('#redactor_modal_inner');
+                                       // drop
+                                       this.upload.$droparea.on('drop.redactor.upload', $.proxy(function(e)
+                                       {
+                                               e.preventDefault();
 
-                               if (this.modalcontent !== false)
-                               {
-                                       this.modalcontent.html(redactorModalInner.html());
-                                       this.modalcontent = false;
-                               }
+                                               this.upload.$droparea.removeClass('drag-hover').addClass('drag-drop');
+                                               this.upload.onDrop(e);
 
-                               redactorModalInner.html('');
+                                       }, this));
+                               },
+                               directUpload: function(file, e)
+                               {
+                                       this.upload.direct = true;
+                                       this.upload.traverseFile(file, e);
+                               },
+                               onDrop: function(e)
+                               {
+                                       e = e.originalEvent || e;
+                                       var files = e.dataTransfer.files;
 
-                               if (this.opts.modalOverlay)
+                                       this.upload.traverseFile(files[0], e);
+                               },
+                               traverseFile: function(file, e)
                                {
-                                       $('#redactor_modal_overlay').hide().off('click', this.modalClose);
-                               }
+                                       if (this.opts.s3)
+                                       {
+                                               this.upload.setConfig(file);
+                                               this.upload.s3uploadFile(file);
+                                               return;
+                                       }
+
+                                       var formData = !!window.FormData ? new FormData() : null;
+                                       if (window.FormData)
+                                       {
+                                               this.upload.setConfig(file);
 
-                               $(document).off('keyup', this.modalCloseHandler);
-                               this.$editor.off('keyup', this.modalCloseHandler);
+                                               var name = (this.upload.type == 'image') ? this.opts.imageUploadParam : this.opts.fileUploadParam;
+                                               formData.append(name, file);
+                                       }
 
-                               this.selectionRestore();
+                                       this.progress.show();
+                                       this.upload.sendData(formData, e);
+                               },
+                               setConfig: function(file)
+                               {
+                                       this.upload.getType(file);
 
-                               // restore scroll
-                               if (this.opts.autoresize && this.saveModalScroll)
+                                       if (this.upload.direct)
+                                       {
+                                               this.upload.url = (this.upload.type == 'image') ? this.opts.imageUpload : this.opts.fileUpload;
+                                               this.upload.callback = (this.upload.type == 'image') ? this.image.insert : this.file.insert;
+                                       }
+                               },
+                               getType: function(file)
                                {
-                                       $(this.document.body).scrollTop(this.saveModalScroll);
-                               }
-                               else if (this.opts.autoresize === false && this.saveModalScroll)
+                                       this.upload.type = 'image';
+                                       if (this.opts.imageTypes.indexOf(file.type) == -1)
+                                       {
+                                               this.upload.type = 'file';
+                                       }
+                               },
+                               getHiddenFields: function(obj, fd)
                                {
-                                       this.$editor.scrollTop(this.saveModalScroll);
-                               }
+                                       if (obj === false || typeof obj !== 'object') return fd;
 
-                               this.callback('modalClosed');
+                                       $.each(obj, $.proxy(function(k, v)
+                                       {
+                                               if (v !== null && v.toString().indexOf('#') === 0) v = $(v).val();
+                                               fd.append(k, v);
 
-                       }, this));
+                                       }, this));
 
+                                       return fd;
 
-                       if (this.isMobile() === false)
-                       {
-                               $(document.body).css('overflow', this.modalSaveBodyOveflow ? this.modalSaveBodyOveflow : 'visible');
-                       }
+                               },
+                               sendData: function(formData, e)
+                               {
+                                       // append hidden fields
+                                       if (this.upload.type == 'image')
+                                       {
+                                               formData = this.upload.getHiddenFields(this.opts.uploadImageFields, formData);
+                                               formData = this.upload.getHiddenFields(this.upload.imageFields, formData);
+                                       }
+                                       else
+                                       {
+                                               formData = this.upload.getHiddenFields(this.opts.uploadFileFields, formData);
+                                               formData = this.upload.getHiddenFields(this.upload.fileFields, formData);
+                                       }
 
-                       return false;
-               },
-               modalSetTab: function(num)
-               {
-                       $('.redactor_tab').hide();
-                       $('#redactor_tabs').find('a').removeClass('redactor_tabs_act').eq(num - 1).addClass('redactor_tabs_act');
-                       $('#redactor_tab' + num).show();
-               },
+                                       var xhr = new XMLHttpRequest();
+                                       xhr.open('POST', this.upload.url);
 
-               // S3
-               s3handleFileSelect: function(e)
-               {
-                       var files = e.target.files;
+                                       // complete
+                                       xhr.onreadystatechange = $.proxy(function()
+                                       {
+                                           if (xhr.readyState == 4)
+                                           {
+                                               var data = xhr.responseText;
 
-                       for (var i = 0, f; f = files[i]; i++)
-                       {
-                               this.s3uploadFile(f);
-                       }
-               },
-               s3uploadFile: function(file)
-               {
-                       this.s3executeOnSignedUrl(file, $.proxy(function(signedURL)
-                       {
-                               this.s3uploadToS3(file, signedURL);
-                       }, this));
-               },
-               s3executeOnSignedUrl: function(file, callback)
-               {
-                       var xhr = new XMLHttpRequest();
+                                                       data = data.replace(/^\[/, '');
+                                                       data = data.replace(/\]$/, '');
 
-                       var mark = '?';
-                       if (this.opts.s3.search(/\?/) != '-1') mark = '&';
+                                                       var json = (typeof data === 'string' ? $.parseJSON(data) : data);
 
-                       xhr.open('GET', this.opts.s3 + mark + 'name=' + file.name + '&type=' + file.type, true);
+                                                       this.progress.hide();
 
-                       // Hack to pass bytes through unprocessed.
-                       if (xhr.overrideMimeType) xhr.overrideMimeType('text/plain; charset=x-user-defined');
+                                                       if (!this.upload.direct)
+                                                       {
+                                                               this.upload.$droparea.removeClass('drag-drop');
+                                                       }
 
-                       var that = this;
-                       xhr.onreadystatechange = function(e)
-                       {
-                               if (this.readyState == 4 && this.status == 200)
-                               {
-                                       that.showProgressBar();
-                                       callback(decodeURIComponent(this.responseText));
-                               }
-                               else if(this.readyState == 4 && this.status != 200)
-                               {
-                                       //setProgress(0, 'Could not contact signing script. Status = ' + this.status);
-                               }
-                       };
+                                                       this.upload.callback(json, this.upload.direct, e);
+                                           }
+                                       }, this);
 
-                       xhr.send();
-               },
-               s3createCORSRequest: function(method, url)
-               {
-                       var xhr = new XMLHttpRequest();
-                       if ("withCredentials" in xhr)
-                       {
-                               xhr.open(method, url, true);
-                       }
-                       else if (typeof XDomainRequest != "undefined")
-                       {
-                               xhr = new XDomainRequest();
-                               xhr.open(method, url);
-                       }
-                       else
-                       {
-                               xhr = null;
-                       }
 
-                       return xhr;
-               },
-               s3uploadToS3: function(file, url)
-               {
-                       var xhr = this.s3createCORSRequest('PUT', url);
-                       if (!xhr)
-                       {
-                               //setProgress(0, 'CORS not supported');
-                       }
-                       else
-                       {
-                               xhr.onload = $.proxy(function()
-                               {
-                                       if (xhr.status == 200)
+                                       /*
+                                       xhr.upload.onprogress = $.proxy(function(e)
                                        {
-                                               //setProgress(100, 'Upload completed.');
-
-                                               this.hideProgressBar();
-
-                                               var s3image = url.split('?');
-
-                                               if (!s3image[0])
+                                               if (e.lengthComputable)
                                                {
-                                                        // url parsing is fail
-                                                        return false;
+                                                       var complete = (e.loaded / e.total * 100 | 0);
+                                                       //progress.value = progress.innerHTML = complete;
                                                }
 
-                                               this.selectionRestore();
+                                       }, this);
+                                       */
+
 
-                                               var html = '';
-                                               html = '<img id="image-marker" src="' + s3image[0] + '" />';
-                                               if (this.opts.paragraphy) html = '<p>' + html + '</p>';
+                                       xhr.send(formData);
+                               },
+                               onDrag: function(e)
+                               {
+                                       e.preventDefault();
+                                       this.upload.$droparea.addClass('drag-hover');
+                               },
+                               onDragLeave: function(e)
+                               {
+                                       e.preventDefault();
+                                       this.upload.$droparea.removeClass('drag-hover');
+                               },
+                               clearImageFields: function()
+                               {
+                                       this.upload.imageFields = {};
+                               },
+                               addImageFields: function(name, value)
+                               {
+                                       this.upload.imageFields[name] = value;
+                               },
+                               removeImageFields: function(name)
+                               {
+                                       delete this.upload.imageFields[name];
+                               },
+                               clearFileFields: function()
+                               {
+                                       this.upload.fileFields = {};
+                               },
+                               addFileFields: function(name, value)
+                               {
+                                       this.upload.fileFields[name] = value;
+                               },
+                               removeFileFields: function(name)
+                               {
+                                       delete this.upload.fileFields[name];
+                               },
 
-                                               this.execCommand('inserthtml', html, false);
 
-                                               var image = $(this.$editor.find('img#image-marker'));
+                               // S3
+                               s3uploadFile: function(file)
+                               {
+                                       this.upload.s3executeOnSignedUrl(file, $.proxy(function(signedURL)
+                                       {
+                                               this.upload.s3uploadToS3(file, signedURL);
+                                       }, this));
+                               },
+                               s3executeOnSignedUrl: function(file, callback)
+                               {
+                                       var xhr = new XMLHttpRequest();
 
-                                               if (image.length) image.removeAttr('id');
-                                               else image = false;
+                                       var mark = '?';
+                                       if (this.opts.s3.search(/\?/) != '-1') mark = '&';
 
-                                               this.sync();
+                                       xhr.open('GET', this.opts.s3 + mark + 'name=' + file.name + '&type=' + file.type, true);
 
-                                               // upload image callback
-                                               this.callback('imageUpload', image, false);
+                                       // Hack to pass bytes through unprocessed.
+                                       if (xhr.overrideMimeType) xhr.overrideMimeType('text/plain; charset=x-user-defined');
 
-                                               this.modalClose();
-                                               this.observeImages();
+                                       var that = this;
+                                       xhr.onreadystatechange = function(e)
+                                       {
+                                               if (this.readyState == 4 && this.status == 200)
+                                               {
+                                                       that.progress.show();
+                                                       callback(decodeURIComponent(this.responseText));
+                                               }
+                                               else if (this.readyState == 4 && this.status != 200)
+                                               {
+                                                       //setProgress(0, 'Could not contact signing script. Status = ' + this.status);
+                                               }
+                                       };
 
+                                       xhr.send();
+                               },
+                               s3createCORSRequest: function(method, url)
+                               {
+                                       var xhr = new XMLHttpRequest();
+                                       if ("withCredentials" in xhr)
+                                       {
+                                               xhr.open(method, url, true);
+                                       }
+                                       else if (typeof XDomainRequest != "undefined")
+                                       {
+                                               xhr = new XDomainRequest();
+                                               xhr.open(method, url);
                                        }
                                        else
                                        {
-                                               //setProgress(0, 'Upload error: ' + xhr.status);
+                                               xhr = null;
                                        }
-                               }, this);
 
-                               xhr.onerror = function()
-                               {
-                                       //setProgress(0, 'XHR error.');
-                               };
-
-                               xhr.upload.onprogress = function(e)
+                                       return xhr;
+                               },
+                               s3uploadToS3: function(file, url)
                                {
-                                       /*
-                                       if (e.lengthComputable)
+                                       var xhr = this.upload.s3createCORSRequest('PUT', url);
+                                       if (!xhr)
                                        {
-                                               var percentLoaded = Math.round((e.loaded / e.total) * 100);
-                                               setProgress(percentLoaded, percentLoaded == 100 ? 'Finalizing.' : 'Uploading.');
+                                               //setProgress(0, 'CORS not supported');
                                        }
-                                       */
-                               };
-
-                               xhr.setRequestHeader('Content-Type', file.type);
-                               xhr.setRequestHeader('x-amz-acl', 'public-read');
-
-                               xhr.send(file);
-                       }
-               },
-
-               // UPLOAD
-               uploadInit: function(el, options)
-               {
-                       this.uploadOptions = {
-                               url: false,
-                               success: false,
-                               error: false,
-                               start: false,
-                               trigger: false,
-                               auto: false,
-                               input: false
-                       };
+                                       else
+                                       {
+                                               xhr.onload = $.proxy(function()
+                                               {
+                                                       if (xhr.status == 200)
+                                                       {
+                                                               //setProgress(100, 'Upload completed.');
 
-                       $.extend(this.uploadOptions, options);
+                                                               this.progress.hide();
 
-                       var $el = $('#' + el);
+                                                               var s3file = url.split('?');
 
-                       // Test input or form
-                       if ($el.length && $el[0].tagName === 'INPUT')
-                       {
-                               this.uploadOptions.input = $el;
-                               this.el = $($el[0].form);
-                       }
-                       else this.el = $el;
+                                                               if (!s3file[0])
+                                                               {
+                                                                        // url parsing is fail
+                                                                        return false;
+                                                               }
 
-                       this.element_action = this.el.attr('action');
 
-                       // Auto or trigger
-                       if (this.uploadOptions.auto)
-                       {
-                               $(this.uploadOptions.input).change($.proxy(function(e)
-                               {
-                                       this.el.submit(function(e)
-                                       {
-                                               return false;
-                                       });
+                                                               if (!this.upload.direct)
+                                                               {
+                                                                       this.upload.$droparea.removeClass('drag-drop');
+                                                               }
 
-                                       this.uploadSubmit(e);
+                                                               var json = { filelink: s3file[0] };
+                                                               if (this.upload.type == 'file')
+                                                               {
+                                                                       var arr = s3file[0].split('/');
+                                                                       json.filename = arr[arr.length-1];
+                                                               }
 
-                               }, this));
+                                                               this.upload.callback(json, this.upload.direct, false);
 
-                       }
-                       else if (this.uploadOptions.trigger)
-                       {
-                               $('#' + this.uploadOptions.trigger).click($.proxy(this.uploadSubmit, this));
-                       }
-               },
-               uploadSubmit: function(e)
-               {
-                       this.showProgressBar();
-                       this.uploadForm(this.element, this.uploadFrame());
-               },
-               uploadFrame: function()
-               {
-                       this.id = 'f' + Math.floor(Math.random() * 99999);
 
-                       var d = this.document.createElement('div');
-                       var iframe = '<iframe style="display:none" id="' + this.id + '" name="' + this.id + '"></iframe>';
+                                                       }
+                                                       else
+                                                       {
+                                                               //setProgress(0, 'Upload error: ' + xhr.status);
+                                                       }
+                                               }, this);
 
-                       d.innerHTML = iframe;
-                       $(d).appendTo("body");
+                                               xhr.onerror = function()
+                                               {
+                                                       //setProgress(0, 'XHR error.');
+                                               };
 
-                       // Start
-                       if (this.uploadOptions.start) this.uploadOptions.start();
+                                               xhr.upload.onprogress = function(e)
+                                               {
+                                                       /*
+                                                       if (e.lengthComputable)
+                                                       {
+                                                               var percentLoaded = Math.round((e.loaded / e.total) * 100);
+                                                               setProgress(percentLoaded, percentLoaded == 100 ? 'Finalizing.' : 'Uploading.');
+                                                       }
+                                                       */
+                                               };
 
-                       $( '#' + this.id ).load($.proxy(this.uploadLoaded, this));
+                                               xhr.setRequestHeader('Content-Type', file.type);
+                                               xhr.setRequestHeader('x-amz-acl', 'public-read');
 
-                       return this.id;
+                                               xhr.send(file);
+                                       }
+                               }
+                       };
                },
-               uploadForm: function(f, name)
+               utils: function()
                {
-                       if (this.uploadOptions.input)
-                       {
-                               var formId = 'redactorUploadForm' + this.id,
-                                       fileId = 'redactorUploadFile' + this.id;
-
-                               this.form = $('<form  action="' + this.uploadOptions.url + '" method="POST" target="' + name + '" name="' + formId + '" id="' + formId + '" enctype="multipart/form-data" />');
-
-                               // append hidden fields
-                               if (this.opts.uploadFields !== false && typeof this.opts.uploadFields === 'object')
+                       return {
+                               isMobile: function()
+                               {
+                                       return /(iPhone|iPod|BlackBerry|Android)/.test(navigator.userAgent);
+                               },
+                               isDesktop: function()
+                               {
+                                       return !/(iPhone|iPod|iPad|BlackBerry|Android)/.test(navigator.userAgent);
+                               },
+                               isString: function(obj)
+                               {
+                                       return Object.prototype.toString.call(obj) == '[object String]';
+                               },
+                               isEmpty: function(html, removeEmptyTags)
                                {
-                                       $.each(this.opts.uploadFields, $.proxy(function(k, v)
+                                       html = html.replace(/[\u200B-\u200D\uFEFF]/g, '');
+                                       html = html.replace(/&nbsp;/gi, '');
+                                       html = html.replace(/<\/?br\s?\/?>/g, '');
+                                       html = html.replace(/\s/g, '');
+                                       html = html.replace(/^<p>[^\W\w\D\d]*?<\/p>$/i, '');
+
+                                       // remove empty tags
+                                       if (removeEmptyTags !== false)
                                        {
-                                               if (v != null && v.toString().indexOf('#') === 0) v = $(v).val();
+                                               html = html.replace(/<[^\/>][^>]*><\/[^>]+>/gi, '');
+                                               html = html.replace(/<[^\/>][^>]*><\/[^>]+>/gi, '');
+                                       }
 
-                                               var hidden = $('<input/>', {
-                                                       'type': "hidden",
-                                                       'name': k,
-                                                       'value': v
-                                               });
+                                       html = $.trim(html);
 
-                                               $(this.form).append(hidden);
+                                       return html === '';
+                               },
+                               normalize: function(str)
+                               {
+                                       if (typeof(str) === 'undefined') return 0;
+                                       return parseInt(str.replace('px',''), 10);
+                               },
+                               hexToRgb: function(hex)
+                               {
+                                       if (typeof hex == 'undefined') return;
+                                       if (hex.search(/^#/) == -1) return hex;
 
-                                       }, this));
-                               }
+                                       var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
+                                       hex = hex.replace(shorthandRegex, function(m, r, g, b)
+                                       {
+                                               return r + r + g + g + b + b;
+                                       });
 
-                               var oldElement = this.uploadOptions.input;
-                               var newElement = $(oldElement).clone();
+                                       var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
+                                       return 'rgb(' + parseInt(result[1], 16) + ', ' + parseInt(result[2], 16) + ', ' + parseInt(result[3], 16) + ')';
+                               },
+                               getOuterHtml: function(el)
+                               {
+                                       return $('<div>').append($(el).eq(0).clone()).html();
+                               },
+                               getAlignmentElement: function(el)
+                               {
+                                       if ($.inArray(el.tagName, this.opts.alignmentTags) !== -1)
+                                       {
+                                               return $(el);
+                                       }
+                                       else
+                                       {
+                                               return $(el).closest(this.opts.alignmentTags.toString().toLowerCase(), this.$editor[0]);
+                                       }
+                               },
+                               removeEmptyAttr: function(el, attr)
+                               {
+                                       var $el = $(el);
+                                       if (typeof $el.attr(attr) == 'undefined')
+                                       {
+                                               return true;
+                                       }
 
-                               $(oldElement).attr('id', fileId).before(newElement).appendTo(this.form);
+                                       if ($el.attr(attr) === '')
+                                       {
+                                               $el.removeAttr(attr);
+                                               return true;
+                                       }
 
-                               $(this.form).css('position', 'absolute')
-                                               .css('top', '-2000px')
-                                               .css('left', '-2000px')
-                                               .appendTo('body');
+                                       return false;
+                               },
+                               removeEmpty: function(i, s)
+                               {
+                                       var $s = $(s);
+                                       var text = $.trim($s.text());
+                                       if ($s.children('hr, br, img').length !== 0) return;
+                                       if (this.utils.isEmpty(text, false))
+                                       {
+                                               $s.remove();
+                                       }
+                               },
 
-                               this.form.submit();
+                               // save and restore scroll
+                               saveScroll: function()
+                               {
+                                       if (this.utils.isSelectAll()) return;
 
-                       }
-                       else
-                       {
-                               f.attr('target', name)
-                                       .attr('method', 'POST')
-                                       .attr('enctype', 'multipart/form-data')
-                                       .attr('action', this.uploadOptions.url);
+                                       this.saveEditorScroll = this.$editor.scrollTop();
+                                       this.saveBodyScroll = $(window).scrollTop();
 
-                               this.element.submit();
-                       }
-               },
-               uploadLoaded: function()
-               {
-                       var i = $( '#' + this.id)[0], d;
+                                       if (this.opts.scrollTarget) this.saveTargetScroll = $(this.opts.scrollTarget).scrollTop();
+                               },
+                               restoreScroll: function()
+                               {
+                                       if (!this.saveScroll && !this.saveBodyScroll) return;
 
-                       if (i.contentDocument) d = i.contentDocument;
-                       else if (i.contentWindow) d = i.contentWindow.document;
-                       else d = window.frames[this.id].document;
+                                       $(window).scrollTop(this.saveBodyScroll);
+                                       this.$editor.scrollTop(this.saveEditorScroll);
 
-                       // Success
-                       if (this.uploadOptions.success)
-                       {
-                               this.hideProgressBar();
+                                       if (this.opts.scrollTarget) $(this.opts.scrollTarget).scrollTop(this.saveTargetScroll);
+                               },
 
-                               if (typeof d !== 'undefined')
+                               // get invisible space element
+                               createSpaceElement: function()
                                {
-                                       // Remove bizarre <pre> tag wrappers around our json data:
-                                       var rawString = d.body.innerHTML;
-                                       var jsonString = rawString.match(/\{(.|\n)*\}/)[0];
-
-                                       jsonString = jsonString.replace(/^\[/, '');
-                                       jsonString = jsonString.replace(/\]$/, '');
+                                       var space = document.createElement('span');
+                                       space.className = 'redactor-invisible-space';
+                                       space.innerHTML = this.opts.invisibleSpace;
 
-                                       var json = $.parseJSON(jsonString);
+                                       return space;
+                               },
 
-                                       if (typeof json.error == 'undefined') this.uploadOptions.success(json);
-                                       else
-                                       {
-                                               this.uploadOptions.error(this, json);
-                                               this.modalClose();
-                                       }
-                               }
-                               else
+                               // replace
+                               removeInlineTags: function(node)
                                {
-                                       this.modalClose();
-                                       alert('Upload failed!');
-                               }
-                       }
+                                       var tags = this.opts.inlineTags;
+                                       tags.push('span');
 
-                       this.el.attr('action', this.element_action);
-                       this.el.attr('target', '');
-               },
+                                       if (node.tagName == 'PRE') tags.push('a');
 
-               // DRAGUPLOAD
-               draguploadInit: function (el, options)
-               {
-                       this.draguploadOptions = $.extend({
-                               url: false,
-                               success: false,
-                               error: false,
-                               preview: false,
-                               uploadFields: false,
-                               text: this.opts.curLang.drop_file_here,
-                               atext: this.opts.curLang.or_choose,
-                               uploadParam: false
-                       }, options);
-
-                       if (window.FormData === undefined) return false;
-
-                       this.droparea = $('<div class="redactor_droparea"></div>');
-                       this.dropareabox = $('<div class="redactor_dropareabox">' + this.draguploadOptions.text + '</div>');
-                       this.dropalternative = $('<div class="redactor_dropalternative">' + this.draguploadOptions.atext + '</div>');
-
-                       this.droparea.append(this.dropareabox);
-
-                       $(el).before(this.droparea);
-                       $(el).before(this.dropalternative);
-
-                       // drag over
-                       this.dropareabox.on('dragover', $.proxy(function()
-                       {
-                               return this.draguploadOndrag();
+                                       $(node).find(tags.join(',')).not('span.redactor-selection-marker').contents().unwrap();
+                               },
+                               replaceWithContents: function(node, removeInlineTags)
+                               {
+                                       var self = this;
+                                       $(node).replaceWith(function()
+                                       {
+                                               if (removeInlineTags === true) self.utils.removeInlineTags(this);
 
-                       }, this));
+                                               return $(this).contents();
+                                       });
+                               },
+                               replaceToTag: function(node, tag, removeInlineTags)
+                               {
+                                       var replacement;
+                                       var self = this;
+                                       $(node).replaceWith(function()
+                                       {
+                                               replacement = $('<' + tag + ' />').append($(this).contents());
 
-                       // drag leave
-                       this.dropareabox.on('dragleave', $.proxy(function()
-                       {
-                               return this.draguploadOndragleave();
+                                               for (var i = 0; i < this.attributes.length; i++)
+                                               {
+                                                       replacement.attr(this.attributes[i].name, this.attributes[i].value);
+                                               }
 
-                       }, this));
+                                               if (removeInlineTags === true) self.utils.removeInlineTags(replacement);
 
-                       // drop
-                       this.dropareabox.get(0).ondrop = $.proxy(function(e)
-                       {
-                               e.preventDefault();
+                                               return replacement;
+                                       });
 
-                               this.dropareabox.removeClass('hover').addClass('drop');
-                               this.showProgressBar();
-                               this.dragUploadAjax(this.draguploadOptions.url, e.dataTransfer.files[0], false, e, this.draguploadOptions.uploadParam);
+                                       return replacement;
+                               },
 
-                       }, this );
-               },
-               dragUploadAjax: function(url, file, directupload, e, uploadParam)
-               {
-                       if (!directupload)
-                       {
-                               var xhr = $.ajaxSettings.xhr();
-                               if (xhr.upload)
+                               // start and end of element
+                               isStartOfElement: function()
                                {
-                                       xhr.upload.addEventListener('progress', $.proxy(this.uploadProgress, this), false);
-                               }
+                                       var block = this.selection.getBlock();
+                                       if (!block) return false;
 
-                               $.ajaxSetup({
-                                 xhr: function () { return xhr; }
-                               });
-                       }
+                                       var offset = this.caret.getOffset(block);
 
-                       // drop callback
-                       this.callback('drop', e);
+                                       return (offset === 0) ? true : false;
+                               },
+                               isEndOfElement: function()
+                               {
+                                       var block = this.selection.getBlock();
+                                       if (!block) return false;
 
-                       var fd = new FormData();
+                                       var offset = this.caret.getOffset(block);
+                                       var text = $.trim($(block).text()).replace(/\n\r\n/g, '');
+                                       var len = text.length;
 
-                       // append file data
-                       if (uploadParam !== false)
-                       {
-                               fd.append(uploadParam, file);
-                       }
-                       else
-                       {
-                               fd.append('file', file);
-                       }
+                                       return (offset == len) ? true : false;
+                               },
 
-                       // append hidden fields
-                       if (this.opts.uploadFields !== false && typeof this.opts.uploadFields === 'object')
-                       {
-                               $.each(this.opts.uploadFields, $.proxy(function(k, v)
+                               // test blocks
+                               isBlock: function(block)
                                {
-                                       if (v != null && v.toString().indexOf('#') === 0) v = $(v).val();
-                                       fd.append(k, v);
+                                       block = block[0] || block;
 
-                               }, this));
-                       }
+                                       return block && this.utils.isBlockTag(block.tagName);
+                               },
+                               isBlockTag: function(tag)
+                               {
+                                       if (typeof tag == 'undefined') return false;
+
+                                       return this.reIsBlock.test(tag);
+                               },
 
-                       $.ajax({
-                               url: url,
-                               dataType: 'html',
-                               data: fd,
-                               cache: false,
-                               contentType: false,
-                               processData: false,
-                               type: 'POST',
-                               success: $.proxy(function(data)
+                               // tag detection
+                               isTag: function(current, tag)
                                {
-                                       data = data.replace(/^\[/, '');
-                                       data = data.replace(/\]$/, '');
+                                       var element = $(current).closest(tag);
+                                       if (element.size() == 1)
+                                       {
+                                               return element[0];
+                                       }
 
-                                       var json = (typeof data === 'string' ? $.parseJSON(data) : data);
+                                       return false;
+                               },
 
-                                       this.hideProgressBar();
+                               // select all
+                               isSelectAll: function()
+                               {
+                                       return this.selectAll;
+                               },
+                               enableSelectAll: function()
+                               {
+                                       this.selectAll = true;
+                               },
+                               disableSelectAll: function()
+                               {
+                                       this.selectAll = false;
+                               },
 
-                                       if (directupload)
+                               // parents detection
+                               isRedactorParent: function(el)
+                               {
+                                       if (!el)
                                        {
-                                           var $img = $('<img>');
-                                               $img.attr('src', json.filelink).attr('id', 'drag-image-marker');
-
-                                               this.insertNodeToCaretPositionFromPoint(e, $img[0]);
+                                               return false;
+                                       }
 
-                                               var image = $(this.$editor.find('img#drag-image-marker'));
-                                               if (image.length) image.removeAttr('id');
-                                               else image = false;
+                                       if ($(el).parents('.redactor-editor').length === 0 || $(el).hasClass('redactor-editor'))
+                                       {
+                                               return false;
+                                       }
 
-                                               this.sync();
-                                               this.observeImages();
+                                       return el;
+                               },
+                               isCurrentOrParent: function(tagName)
+                               {
+                                       var parent = this.selection.getParent();
+                                       var current = this.selection.getCurrent();
 
-                                               // upload callback
-                                               if (image) this.callback('imageUpload', image, json);
+                                       if ($.isArray(tagName))
+                                       {
+                                               var matched = 0;
+                                               $.each(tagName, $.proxy(function(i, s)
+                                               {
+                                                       if (this.utils.isCurrentOrParentOne(current, parent, s))
+                                                       {
+                                                               matched++;
+                                                       }
+                                               }, this));
 
-                                               // error callback
-                                               if (typeof json.error !== 'undefined') this.callback('imageUploadError', json);
+                                               return (matched === 0) ? false : true;
                                        }
                                        else
                                        {
-                                               if (typeof json.error == 'undefined')
-                                               {
-                                                       this.draguploadOptions.success(json);
-                                               }
-                                               else
-                                               {
-                                                       this.draguploadOptions.error(this, json);
-                                                       this.draguploadOptions.success(false);
-                                               }
+                                               return this.utils.isCurrentOrParentOne(current, parent, tagName);
                                        }
+                               },
+                               isCurrentOrParentOne: function(current, parent, tagName)
+                               {
+                                       return parent && parent.tagName === tagName ? parent : current && current.tagName === tagName ? current : false;
+                               },
 
-                               }, this)
-                       });
-               },
-               draguploadOndrag: function()
-               {
-                       this.dropareabox.addClass('hover');
-                       return false;
-               },
-               draguploadOndragleave: function()
-               {
-                       this.dropareabox.removeClass('hover');
-                       return false;
-               },
-               uploadProgress: function(e, text)
-               {
-                       var percent = e.loaded ? parseInt(e.loaded / e.total * 100, 10) : e;
-                       this.dropareabox.text('Loading ' + percent + '% ' + (text || ''));
-               },
-
-               // UTILS
-               isMobile: function()
-               {
-                       return /(iPhone|iPod|BlackBerry|Android)/.test(navigator.userAgent);
-               },
-               isIPad: function()
-               {
-                       return /iPad/.test(navigator.userAgent);
-               },
-               normalize: function(str)
-               {
-                       if (typeof(str) === 'undefined') return 0;
-                       return parseInt(str.replace('px',''), 10);
-               },
-               outerHtml: function(el)
-               {
-                       return $('<div>').append($(el).eq(0).clone()).html();
-               },
-               stripHtml: function(html)
-               {
-                       var tmp = document.createElement("DIV");
-                       tmp.innerHTML = html;
-                       return tmp.textContent || tmp.innerText || "";
-               },
-               isString: function(obj)
-               {
-                       return Object.prototype.toString.call(obj) == '[object String]';
-               },
-               isEmpty: function(html)
-               {
-                       html = html.replace(/&#x200b;|<br>|<br\/>|&nbsp;/gi, '');
-                       html = html.replace(/\s/g, '');
-                       html = html.replace(/^<p>[^\W\w\D\d]*?<\/p>$/i, '');
 
-                       return html == '';
-               },
-               getInternetExplorerVersion: function()
-               {
-                       var rv = false;
-                       if (navigator.appName == 'Microsoft Internet Explorer')
-                       {
-                               var ua = navigator.userAgent;
-                               var re  = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
-                               if (re.exec(ua) != null)
+                               // browsers detection
+                               isOldIe: function()
+                               {
+                                       return (this.utils.browser('msie') && parseInt(this.utils.browser('version'), 10) < 9) ? true : false;
+                               },
+                               isLessIe10: function()
                                {
-                                       rv = parseFloat(RegExp.$1);
+                                       return (this.utils.browser('msie') && parseInt(this.utils.browser('version'), 10) < 10) ? true : false;
+                               },
+                               isIe11: function()
+                               {
+                                       return !!navigator.userAgent.match(/Trident\/7\./);
+                               },
+                               browser: function(browser)
+                               {
+                                       var ua = navigator.userAgent.toLowerCase();
+                                       var match = /(opr)[\/]([\w.]+)/.exec( ua ) ||
+                           /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
+                           /(webkit)[ \/]([\w.]+).*(safari)[ \/]([\w.]+)/.exec(ua) ||
+                           /(webkit)[ \/]([\w.]+)/.exec( ua ) ||
+                           /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
+                           /(msie) ([\w.]+)/.exec( ua ) ||
+                           ua.indexOf("trident") >= 0 && /(rv)(?::| )([\w.]+)/.exec( ua ) ||
+                           ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ||
+                           [];
+
+                                       if (browser == 'version') return match[2];
+                                       if (browser == 'webkit') return (match[1] == 'chrome' || match[1] == 'webkit');
+                                       if (match[1] == 'rv') return browser == 'msie';
+                                       if (match[1] == 'opr') return browser == 'webkit';
+
+                                       return browser == match[1];
                                }
-                       }
-
-                       return rv;
-               },
-               isIe11: function()
-               {
-                       return !!navigator.userAgent.match(/Trident\/7\./);
-               },
-               browser: function(browser)
-               {
-                       var ua = navigator.userAgent.toLowerCase();
-                       var match = /(opr)[\/]([\w.]+)/.exec( ua ) ||
-            /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
-            /(webkit)[ \/]([\w.]+).*(safari)[ \/]([\w.]+)/.exec(ua) ||
-            /(webkit)[ \/]([\w.]+)/.exec( ua ) ||
-            /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
-            /(msie) ([\w.]+)/.exec( ua ) ||
-            ua.indexOf("trident") >= 0 && /(rv)(?::| )([\w.]+)/.exec( ua ) ||
-            ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ||
-            [];
-
-                       if (browser == 'version') return match[2];
-                       if (browser == 'webkit') return (match[1] == 'chrome' || match[1] == 'webkit');
-                       if (match[1] == 'rv') return browser == 'msie';
-                       if (match[1] == 'opr') return browser == 'webkit';
-
-                       return browser == match[1];
-
-               },
-               oldIE: function()
-               {
-                       if (this.browser('msie') && parseInt(this.browser('version'), 10) < 9) return true;
-                       return false;
-               },
-               getFragmentHtml: function (fragment)
-               {
-                       var cloned = fragment.cloneNode(true);
-                       var div = this.document.createElement('div');
-
-                       div.appendChild(cloned);
-                       return div.innerHTML;
-               },
-               extractContent: function()
-               {
-                       var node = this.$editor[0];
-                       var frag = this.document.createDocumentFragment();
-                       var child;
-
-                       while ((child = node.firstChild))
-                       {
-                               frag.appendChild(child);
-                       }
-
-                       return frag;
-               },
-               isParentRedactor: function(el)
-               {
-                       if (!el) return false;
-                       if (this.opts.iframe) return el;
-
-                       if ($(el).parents('div.redactor_editor').length == 0 || $(el).hasClass('redactor_editor')) return false;
-                       else return el;
-               },
-               currentOrParentIs: function(tagName)
-               {
-                       var parent = this.getParent(), current = this.getCurrent();
-                       return parent && parent.tagName === tagName ? parent : current && current.tagName === tagName ? current : false;
-               },
-               isEndOfElement: function()
-               {
-                       var current = this.getBlock();
-                       var offset = this.getCaretOffset(current);
-
-                       var text = $.trim($(current).text()).replace(/\n\r\n/g, '');
-
-                       var len = text.length;
-
-                       if (offset == len) return true;
-                       else return false;
-               },
-               isFocused: function()
-               {
-                       var el, sel = this.getSelection();
-
-                       if (sel && sel.rangeCount && sel.rangeCount > 0) el = sel.getRangeAt(0).startContainer;
-                       if (!el) return false;
-                       if (this.opts.iframe)
-                       {
-                               if (this.getCaretOffsetRange().equals()) return !this.$editor.is(el);
-                               else return true;
-                       }
-
-                       return $(el).closest('div.redactor_editor').length != 0;
-               },
-               removeEmptyAttr: function (el, attr)
-               {
-                       if ($(el).attr(attr) == '') $(el).removeAttr(attr);
-               },
-               removeFromArrayByValue: function(array, value)
-               {
-                       var index = null;
-
-                       while ((index = array.indexOf(value)) !== -1)
-                       {
-                               array.splice(index, 1);
-                       }
-
-                       return array;
+                       };
                }
-
        };
 
        // constructor
        Redactor.prototype.init.prototype = Redactor.prototype;
 
        // LINKIFY
-       $.Redactor.fn.formatLinkify = function(protocol, convertLinks, convertImageLinks, convertVideoLinks, linkSize)
+       $.Redactor.fn.formatLinkify = function(protocol, convertLinks, convertUrlLinks, convertImageLinks, convertVideoLinks, linkSize)
        {
-               var url = /(((https?|ftps?):\/\/)|www[.][^\s])(.+?\..+?)([.),]?)(\s|\.\s+|\)|$)/gi,
-                       rProtocol = /(https?|ftp):\/\//i,
-                       urlImage = /(https?:\/\/.*\.(?:png|jpg|jpeg|gif))/gi;
+               var urlCheck = '((?:http[s]?:\\/\\/(?:www\\.)?|www\\.){1}(?:[0-9A-Za-z\\-%_]+\\.)+[a-zA-Z]{2,}(?::[0-9]+)?(?:(?:/[0-9A-Za-z\\-\\.%_]*)+)?(?:\\?(?:[0-9A-Za-z\\-\\.%_]+(?:=[0-9A-Za-z\\-\\.%_\\+]*)?)?(?:&(?:[0-9A-Za-z\\-\\.%_]+(?:=[0-9A-Za-z\\-\\.%_\\+]*)?)?)*)?(?:#[0-9A-Za-z\\-\\.%_\\+=\\?&;]*)?)';
+               var regex = new RegExp(urlCheck, 'gi');
+               var rProtocol = /(https?|ftp):\/\//i;
+               var urlImage = /(https?:\/\/.*\.(?:png|jpg|jpeg|gif))/gi;
 
-               var childNodes = (this.$editor ? this.$editor.get(0) : this).childNodes, i = childNodes.length;
+               var childNodes = (this.$editor ? this.$editor[0] : this).childNodes, i = childNodes.length;
                while (i--)
                {
                        var n = childNodes[i];
                                }
 
                                // link
-                               if (convertLinks && html && html.match(url))
+                               if (html.search(/\$/g) != -1) html = html.replace(/\$/g, '&#36;');
+
+                               var matches = html.match(regex);
+                               if (convertUrlLinks && html && matches)
                                {
-                                       var matches = html.match(url);
 
-                                       for (var i in matches)
+                                       var len = matches.length;
+                                       for (var z = 0; z < len; z++)
                                        {
-                                               var href = matches[i];
+                                               // remove dot in the end
+                                               if (matches[z].match(/\.$/) !== null) matches[z] = matches[z].replace(/\.$/, '');
+
+                                               var href = matches[z];
                                                var text = href;
 
+
                                                var space = '';
                                                if (href.match(/\s$/) !== null) space = ' ';
 
-                                               var addProtocol = protocol;
+                                               var addProtocol = protocol + '://';
                                                if (href.match(rProtocol) !== null) addProtocol = '';
 
                                                if (text.length > linkSize) text = text.substring(0, linkSize) + '...';
+                                               text = text.replace(/&#36;/g, '$').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
 
-                                               text = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
-
-                                               /*
-                                                       To handle URLs which may have $ characters in them, need to escape $ -> $$ to prevent $1 from getting treated as a backreference.
-                                                       See http://gotofritz.net/blog/code-snippets/escaping-in-replace-strings-in-javascript/
-                                               */
-                                               var escapedBackReferences = text.replace('$', '$$$');
-
-                                               html = html.replace(href, '<a href=\"' + addProtocol + $.trim(href) + '\">' + $.trim(escapedBackReferences) + '</a>' + space);
+                                               html = html.replace(href, '<a href=\"' + addProtocol + $.trim(href) + '\">' + $.trim(text) + '</a>' + space);
                                        }
 
                                        $(n).after(html).remove();
                        }
                        else if (n.nodeType === 1 && !/^(a|button|textarea)$/i.test(n.tagName))
                        {
-                               $.Redactor.fn.formatLinkify.call(n, protocol, convertLinks, convertImageLinks, convertVideoLinks, linkSize);
+                               $.Redactor.fn.formatLinkify.call(n, protocol, convertLinks, convertUrlLinks, convertImageLinks, convertVideoLinks, linkSize);
                        }
                }
        };
index b22cb851f2739bc65ac87ef2692026211489e0d0..80561d5aa4244beea6ea8b5214b88c8f743bdf9c 100644 (file)
@@ -1,6 +1,6 @@
 /*
-       Redactor v9.2.6
-       Updated: Jul 19, 2014
+       Redactor v10.0
+       Updated: September 24, 2014
 
        http://imperavi.com/redactor/
 
@@ -9,4 +9,5 @@
 
        Usage: $('#content').redactor();
 */
-eval(function(p,a,c,k,e,d){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k[c]||e(c)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('(B($){q 7F=0;"lV lX";q bI=B(O){c[0]=O.lW;c[1]=O.kj;c.O=O;F c};bI.5o.iz=B(){F c[0]===c[1]};q 8N=/5n?:\\/\\/(?:[0-9A-Z-]+\\.)?(?:lT\\.be\\/|cO\\.7w\\S*[^\\w\\-\\s])([\\w\\-]{11})(?=[^\\w\\-]|$)(?![?=&+%\\w.-]*(?:[\'"][^<>]*>|<\\/a>))[?=&+%\\w.-]*/ig;q 8Q=/5n?:\\/\\/(aA\\.)?cI.7w\\/(\\d+)($|\\/)/;$.fn.U=B(3R){q 1p=[];q hg=i3.5o.jR.5q(hw,1);if(1E 3R==="8I"){c.1u(B(){q 7l=$.1a(c,"U");if(1E 7l!=="1J"&&$.7z(7l[3R])){q aO=7l[3R].bO(7l,hg);if(aO!==1J&&aO!==7l){1p.3d(aO)}}I{F $.3U(\'lP lS 5l "\'+3R+\'" 3z 3J\')}})}I{c.1u(B(){if(!$.1a(c,"U")){$.1a(c,"U",3J(c,3R))}})}if(1p.1m===0){F c}I{if(1p.1m===1){F 1p[0]}I{F 1p}}};B 3J(el,3R){F 2c 3J.5o.77(el,3R)}$.3J=3J;$.3J.mf="9.2.6";$.3J.C={41:E,1Q:E,4n:E,1f:E,1y:"en",63:"mh",4U:E,7X:E,fO:E,g1:N,gw:N,gQ:N,j7:E,a1:N,iO:N,bV:N,dS:E,kt:E,5N:N,2h:E,7c:E,4Y:N,8R:E,dO:E,6j:{"3x+m, 4w+m":"c.26(\'mb\', E)","3x+b, 4w+b":"c.26(\'3m\', E)","3x+i, 4w+i":"c.26(\'3q\', E)","3x+h, 4w+h":"c.26(\'fm\', E)","3x+l, 4w+l":"c.26(\'fy\', E)","3x+k, 4w+k":"c.9N()","3x+7V+7":"c.26(\'8q\', E)","3x+7V+8":"c.26(\'8k\', E)"},jL:E,7C:E,ah:60,cn:E,8a:"aI://",ky:E,7A:50,ec:E,8i:"8Y",6L:E,fR:N,kC:N,3Y:E,an:"25",gF:N,84:E,e5:"25",9p:N,bT:E,dM:["T/gY","T/hc","T/ha"],5Q:E,3H:E,4T:N,5m:N,fb:N,aW:E,i2:N,30:E,gJ:["6x","3m","3q","5H","6f","6a","7m","6m"],1A:N,6B:E,7T:X,bb:0,as:E,7O:E,h9:E,gK:N,4a:["o","6x","3m","3q","5H","6f","6a","7m","6m","T","3y","25","1n","1r","9R","|","7Q"],cU:[],aY:["5H","3q","3m","4K","6f","6a","cf","bn","bs","dB","1n"],cv:{b:"3m",3V:"3m",i:"3q",em:"3q",56:"5H",5F:"5H",2r:"6f",ol:"6a",u:"4K",3j:"1n",1g:"1n",1n:"1n"},gM:["p","2f","2q","h1","h2","h3","h4","h5","h6"],1N:E,5Y:N,9a:N,7d:N,6l:E,75:E,fz:E,8x:E,5G:E,8y:["o","9U","1r","2v","4w","3n","1o","lq"],6r:"3V",6p:"em",9q:20,3E:[],8A:[],64:E,58:"<p>&#9F;</p>",2i:"&#9F;",cu:/^(P|H[1-6]|3h|9e|9f|9Z|9Y|a4|a0)$/i,5t:["P","it","iC","iF","iw","i9","hM","i6","i7","gf","8K","6h","3F","fJ","cA","9e","9f","9Z","9Y","a4","a0"],bL:["fU","2v","9U","hr","i?2Z","1r","4w","le","1o","3n","1n","8c","3T","di"],bJ:["li","dt","dt","h[1-6]","4b","3n"],jM:["2f","12","dl","fV","2t","nx","gc","ol","p","2q","3s","1g","dN","3j","2r"],bg:["P","it","iC","iF","iw","i9","hM","i6","i7","gf","8K","3h","3F","fJ","cA","6i","9e","9f","9Z","9Y","a4","a0","6h"],jG:{en:{o:"kZ",3y:"6b jn",T:"6b kG",1n:"fc",1r:"l2",cz:"6b 1r",fg:"jV 1r",6F:"mO",6x:"mL",jK:"mW Y",ki:"mV",2k:"kr",kp:"7W 1",kn:"7W 2",k3:"7W 3",k1:"7W 4",kb:"7W 5",3m:"mG",3q:"mt",mC:"mE gg",my:"no gg",6f:"nb g7",6a:"lr g7",7m:"ll",6m:"lN",6H:"mg",4y:"6b",7N:"mj",iv:"7L",cd:"6b fc",c5:"7Y f0 m6",c4:"7Y f0 lm",bX:"7Y eW kJ",bW:"7Y eW kz",c2:"7L eW",c0:"7L f0",dW:"7L fc",aF:"nD",aJ:"n8",bU:"7Y kY",bY:"7L kY",1e:"n0",hP:"n7",3r:"ni",1s:"kJ",4Z:"kz",6O:"j4",ip:"kG mr l2",Y:"mU",eH:"mN",mJ:"gN",hF:"jn mn kr",25:"6b mw",8H:"jE",mB:"mz",eo:"mZ",jB:"np eo",jA:"nr 25 nj",bp:"j1 Y iW iZ 1s",bv:"j4 Y",bo:"j1 Y iW iZ 4Z",bu:"ny Y",7Q:"6b n6 n4",5H:"n9",na:"nc",eB:"lz 1r in 2c 52",4K:"lv",9R:"lE",ed:"m9 (m8)",8p:"jV"}}};3J.fn=$.3J.5o={2O:{a2:8,eC:46,dH:40,b9:13,dY:27,iG:9,lR:17,lU:91,mi:37,fw:91},77:B(el,3R){c.8j=E;c.$2j=c.$1v=$(el);c.7F=7F++;q C=$.4W(N,{},$.3J.C);c.C=$.4W({},C,c.$2j.1a(),3R);c.2y=N;c.lY=[];c.a9=c.$1v.1f("22");c.lL=c.$1v.1f("2p");if(c.C.4n){c.C.1Q=N}if(c.C.1N){c.C.5Y=E}if(c.C.5Y){c.C.1N=E}if(c.C.as){c.C.6B=N}c.X=X;c.42=42;c.5S=E;c.kT=2c 2J("^<(/?"+c.C.bL.5V("|/?")+"|"+c.C.bJ.5V("|")+")[ >]");c.kN=2c 2J("^<(br|/?"+c.C.bL.5V("|/?")+"|/"+c.C.bJ.5V("|/")+")[ >]");c.d7=2c 2J("^</?("+c.C.jM.5V("|")+")[ >]");c.ax=2c 2J("^("+c.C.bg.5V("|")+")$","i");if(c.C.1N===E){if(c.C.5G!==E){q bQ=["3V","em","56"];q jC=["b","i","5F"];if($.4O("p",c.C.5G)==="-1"){c.C.5G.3d("p")}3z(i in bQ){if($.4O(bQ[i],c.C.5G)!="-1"){c.C.5G.3d(jC[i])}}}if(c.C.8y!==E){q 3X=$.4O("p",c.C.8y);if(3X!=="-1"){c.C.8y.ap(3X,3X)}}}if(c.1C("3t")||c.1C("94")){c.C.4a=c.iB(c.C.4a,"7Q")}c.C.1F=c.C.jG[c.C.1y];$.4W(c.C.6j,c.C.jL);c.hj();c.g0()},fN:B(1y){F{o:{1e:1y.o,1H:"d0"},6x:{1e:1y.6x,1H:"2M",1R:{p:{1e:1y.jK,1H:"5I"},2f:{1e:1y.ki,1H:"d9",2S:"m3"},2q:{1e:1y.2k,1H:"5I",2S:"nd"},h1:{1e:1y.kp,1H:"5I",2S:"nf"},h2:{1e:1y.kn,1H:"5I",2S:"n2"},h3:{1e:1y.k3,1H:"5I",2S:"mm"},h4:{1e:1y.k1,1H:"5I",2S:"n3"},h5:{1e:1y.kb,1H:"5I",2S:"nz"}}},3m:{1e:1y.3m,2o:"3m"},3q:{1e:1y.3q,2o:"3q"},5H:{1e:1y.5H,2o:"fC"},4K:{1e:1y.4K,2o:"4K"},6f:{1e:"&nB; "+1y.6f,2o:"8k"},6a:{1e:"1. "+1y.6a,2o:"8q"},7m:{1e:"< "+1y.7m,1H:"cy"},6m:{1e:"> "+1y.6m,1H:"cx"},T:{1e:1y.T,1H:"l4"},3y:{1e:1y.3y,1H:"iI"},25:{1e:1y.25,1H:"jg"},1n:{1e:1y.1n,1H:"2M",1R:{cd:{1e:1y.cd,1H:"ko"},nm:{2m:"aN"},c5:{1e:1y.c5,1H:"j3"},c4:{1e:1y.c4,1H:"iV"},bX:{1e:1y.bX,1H:"iM"},bW:{1e:1y.bW,1H:"iL"},nn:{2m:"aN"},bU:{1e:1y.bU,1H:"j2"},bY:{1e:1y.bY,1H:"ey"},nq:{2m:"aN"},c2:{1e:1y.c2,1H:"iY"},c0:{1e:1y.c0,1H:"k8"},dW:{1e:1y.dW,1H:"ka"}}},1r:{1e:1y.1r,1H:"2M",1R:{1r:{1e:1y.cz,1H:"9N"},6F:{1e:1y.6F,2o:"6F"}}},9R:{1e:1y.9R,1H:"2M",1R:{cf:{1e:1y.bp,1H:"dI"},bn:{1e:1y.bv,1H:"dy"},bs:{1e:1y.bo,1H:"dD"},dB:{1e:1y.bu,1H:"dA"}}},cf:{1e:1y.bp,1H:"dI"},bn:{1e:1y.bv,1H:"dy"},bs:{1e:1y.bo,1H:"dD"},mv:{1e:1y.bu,1H:"dA"},7Q:{2o:"fx",1e:1y.7Q}}},1c:B(1G,67,1a){q 1c=c.C[1G+"ms"];if($.7z(1c)){if(67===E){F 1c.5q(c,1a)}I{F 1c.5q(c,67,1a)}}I{F 1a}},mF:B(){hd(c.ah);$(42).3b(".U");c.$1v.3b("U-5s");c.$2j.3b(".U").mT("U");q o=c.2R();if(c.C.64){c.$2C.2I(c.$1v);c.$2C.1w();c.$1v.1p(o).2M()}I{q $1z=c.$K;if(c.C.1Q){$1z=c.$2j}c.$2C.2I($1z);c.$2C.1w();$1z.3c("4d").3c("fM").2B("3K").o(o).2M()}if(c.C.7O){$(c.C.7O).o("")}if(c.C.30){$("#hb"+c.7F).1w()}},mS:B(){F $.4W({},c)},mX:B(){F c.$K},mQ:B(){F c.$2C},mI:B(){F(c.C.1Q)?c.$2Z:E},mK:B(){F(c.$1A)?c.$1A:E},2R:B(){F c.$1v.1p()},ku:B(){c.$K.2B("3K").2B("69");q o=c.3Z(c.$2Z.1Y().4c());c.$K.1i({3K:N,69:c.C.63});F o},6V:B(o,9g,du){o=o.43();o=o.G(/\\$/g,"&#36;");if(c.C.4n){c.kE(o)}I{c.l3(o,9g)}if(o==""){du=E}if(du!==E){c.hs()}},l3:B(o,9g){if(9g!==E){o=c.de(o);o=c.8d(o);o=c.dR(o);o=c.9S(o,N);if(c.C.1N===E){o=c.dK(o)}I{o=o.G(/<p(.*?)>([\\w\\W]*?)<\\/p>/gi,"$2<br>")}}o=o.G(/&9h;#36;/g,"$");o=c.dJ(o);c.$K.o(o);c.8u();c.92();c.1j()},kE:B(o){q 3C=c.cV();c.$2Z[0].3p="mD:mY";o=c.dR(o);o=c.9S(o);o=c.6Z(o);3C.b7();3C.gW(o);3C.h8();if(c.C.4n){c.$K=c.$2Z.1Y().1b("2v").1i({3K:N,69:c.C.63})}c.8u();c.92();c.1j()},d1:B(o){c.7y=o.1U(/^<\\!kH[^>]*>/i);if(c.7y&&c.7y.1m==1){o=o.G(/^<\\!kH[^>]*>/i,"")}o=c.de(o,N);o=c.dK(o);o=c.dJ(o);c.$K.o(o);c.8u();c.92();c.1j()},kw:B(){if(c.7y&&c.7y.1m==1){q 1v=c.7y[0]+"\\n"+c.$1v.1p();c.$1v.1p(1v)}},92:B(){q b4=c.$K.1b("V");q 76="3W";$.1u(b4,B(){q aD=c.jb;q 5y=2c 2J("<"+c.Q,"gi");q 5K=aD.G(5y,"<"+76);5y=2c 2J("</"+c.Q,"gi");5K=5K.G(5y,"</"+76);$(c).2e(5K)})},ad:B(o){o=o.G(/<V(.*?)>/,"<3W$1>");F o.G(/<\\/V>/,"</3W>")},8u:B(){c.$K.1b(".nk").1i("3K",E)},1j:B(e){q o="";c.g5();if(c.C.4n){o=c.ku()}I{o=c.$K.o()}o=c.eE(o);o=c.dq(o);q 1v=c.6Z(c.$1v.1p(),E);q K=c.6Z(o,E);if(1v==K){F E}o=o.G(/<\\/li><(2r|ol)>([\\w\\W]*?)<\\/(2r|ol)>/gi,"<$1>$2</$1></li>");if($.2b(o)==="<br>"){o=""}if(c.C.kt){q ks=["br","hr","1B","1r","2T","4w"];$.1u(ks,B(i,s){o=o.G(2c 2J("<"+s+"(.*?[^/$]?)>","gi"),"<"+s+"$1 />")})}o=c.1c("nt",E,o);c.$1v.1p(o);c.kw();c.1c("nu",E,o);if(c.2y===E){if(1E e!="1J"){kA(e.5X){5u 37:5f;5u 38:5f;5u 39:5f;5u 40:5f;nC:c.1c("68",E,o)}}I{c.1c("68",E,o)}}},eE:B(o){if(!c.C.4n){o=c.8d(o)}o=$.2b(o);o=c.hq(o);o=o.G(/&#9F;/gi,"");o=o.G(/&#nA;/gi,"");o=o.G(/<\\/a>&3u;/gi,"</a> ");o=o.G(/\\7D/g,"");if(o=="<p></p>"||o=="<p> </p>"||o=="<p>&3u;</p>"){o=""}if(c.C.ky){o=o.G(/<a(.*?)4m="kx"(.*?)>/gi,"<a$1$2>");o=o.G(/<a(.*?)>/gi,\'<a$1 4m="kx">\')}o=o.G("<!--?4I","<?4I");o=o.G("?-->","?>");o=o.G(/<(.*?)1x="kI"(.*?) 3K="E"(.*?)>/gi,\'<$nv="kI"$2$3>\');o=o.G(/ 1a-81=""/gi,"");o=o.G(/<br\\s?\\/?>\\n?<\\/(P|H[1-6]|3h|9e|9f|9Z|9Y|a4|a0)>/gi,"</$1>");o=o.G(/<V(.*?)id="U-T-2C"(.*?)>([\\w\\W]*?)<1B(.*?)><\\/V>/gi,"$3<1B$4>");o=o.G(/<V(.*?)id="U-T-ej"(.*?)>(.*?)<\\/V>/gi,"");o=o.G(/<V(.*?)id="U-T-es"(.*?)>(.*?)<\\/V>/gi,"");o=o.G(/<(2r|ol)>\\s*\\t*\\n*<\\/(2r|ol)>/gi,"");if(c.C.bV){o=o.G(/<2A(.*?)>([\\w\\W]*?)<\\/2A>/gi,"$2")}o=o.G(/<V(.*?)>([\\w\\W]*?)<\\/V>/gi,"$2");o=o.G(/<3W>([\\w\\W]*?)<\\/3W>/gi,"$1");o=o.G(/<3W>/gi,"<V>");o=o.G(/<3W /gi,"<V ");o=o.G(/<\\/3W>/gi,"</V>");if(c.C.a1){o=o.G(/<V>([\\w\\W]*?)<\\/V>/gi,"$1")}o=o.G(/<V(.*?)1x="55"(.*?)>([\\w\\W]*?)<\\/V>/gi,"");o=o.G(/<1B(.*?)3K="E"(.*?)>/gi,"<1B$1$2>");o=o.G(/&/gi,"&");o=o.G(/\\ls/gi,"&lF;");o=o.G(/\\lf/gi,"&lh;");o=o.G(/\\lk/gi,"&ma;");o=o.G(/\\lO/gi,"&lM;");o=o.G(/\\lZ/gi,"&m0;");o=c.fL(o);F o},g0:B(){c.3L="";c.$2C=$(\'<12 1x="lQ" />\');if(c.$1v[0].Q==="m2"){c.C.64=N}if(c.C.g1===E&&c.5a()){c.gb()}I{c.iH();if(c.C.1Q){c.C.4Y=E;c.5j()}I{if(c.C.64){c.fZ()}I{c.fY()}}if(!c.C.1Q){c.cJ();c.cH()}}},gb:B(){if(!c.C.64){c.$K=c.$1v;c.$K.2U();c.$1v=c.bd(c.$K);c.$1v.1p(c.3L)}c.$2C.at(c.$1v).1h(c.$1v)},iH:B(){if(c.C.64){c.3L=$.2b(c.$1v.1p())}I{c.3L=$.2b(c.$1v.o())}},fZ:B(){c.$K=$("<12 />");c.$2C.at(c.$1v).1h(c.$K).1h(c.$1v);c.fQ(c.$K);c.dw()},fY:B(){c.$K=c.$1v;c.$1v=c.bd(c.$K);c.$2C.at(c.$K).1h(c.$K).1h(c.$1v);c.dw()},bd:B($1v){F $("<5s />").1i("2m",$1v.1i("id")).1f("22",c.a9)},fQ:B(el){$.1u(c.$1v.2R(0).2S.4o(/\\s+/),B(i,s){el.2z("lo"+s)})},dw:B(){c.$K.2z("4d").1i({3K:N,69:c.C.63});c.$1v.1i("69",c.C.63).2U();c.6V(c.3L,N,E)},cJ:B(){q $1v=c.$K;if(c.C.1Q){$1v=c.$2Z}if(c.C.7c){$1v.1i("7c",c.C.7c)}if(c.C.8R){$1v.1f("fP-22",c.C.8R+"px")}I{if(c.1C("31")&&c.C.1N){c.$K.1f("fP-22","lH")}}if(c.1C("31")&&c.C.1N){c.$K.1f("8X-ft","8Y")}if(c.C.dO){c.C.4Y=E;c.a9=c.C.dO}if(c.C.fO){c.$K.2z("fM")}if(c.C.7X){c.$K.2z("U-K-7X")}if(!c.C.4Y){$1v.1f("22",c.a9)}},cH:B(){c.2y=E;if(c.C.1A){c.C.1A=c.fN(c.C.1F);c.gI()}c.i5();c.iu();c.d2();if(c.C.7C){c.7C()}2V($.M(c.7H,c),4);if(c.1C("31")){eG{c.X.26("ne",E,E);c.X.26("ng",E,E)}fe(e){}}if(c.C.2h){2V($.M(c.2h,c),3O)}if(!c.C.5N){2V($.M(B(){c.C.5N=N;c.d0(E)},c),5k)}c.1c("77")},d2:B(){c.7P=0;if(c.C.fR&&(c.C.3Y!==E||c.C.5Q!==E)){c.$K.on("6w.U",$.M(c.fT,c))}c.$K.on("23.U",$.M(B(){c.5R=E},c));c.$K.on("2T.U",$.M(c.1j,c));c.$K.on("9v.U",$.M(c.gd,c));c.$K.on("5T.U",$.M(c.go,c));c.$K.on("5d.U",$.M(c.hS,c));if($.7z(c.C.fS)){c.$1v.on("5T.U-5s",$.M(c.C.fS,c))}if($.7z(c.C.fX)){c.$K.on("2h.U",$.M(c.C.fX,c))}q a3;$(X).9J(B(e){a3=$(e.1O)});c.$K.on("cN.U",$.M(B(e){if(!$(a3).3v("cW")&&$(a3).8B(".cW").1V()==0){c.5R=E;if($.7z(c.C.n1)){c.1c("cN",e)}}},c))},fT:B(e){e=e.gE||e;if(42.bw===1J||!e.aX){F N}q 1m=e.aX.7I.1m;if(1m==0){F N}e.2u();q 25=e.aX.7I[0];if(c.C.dM!==E&&c.C.dM.44(25.1G)==-1){F N}c.21();c.8C();if(c.C.5Q===E){c.bF(c.C.3Y,25,N,e,c.C.an)}I{c.e1(25)}},gd:B(e){q dL=E;if(c.1C("4s")&&6n.7k.44("n5")===-1){q 2Q=c.1C("9n").4o(".");if(2Q[0]<nh){dL=N}}if(dL){F N}if(c.1C("94")){F N}if(c.C.9p&&c.gz(e)){F N}if(c.C.gw){c.8j=N;c.28();if(!c.5R){if(c.C.4Y===N&&c.c7!==N){c.$K.22(c.$K.22());c.9t=c.X.2v.3e}I{c.9t=c.$K.3e()}}q 4k=c.cE();2V($.M(B(){q gv=c.cE();c.$K.1h(4k);c.1W();q o=c.fF(gv);c.j8(o);if(c.C.4Y===N&&c.c7!==N){c.$K.1f("22","4l")}},c),1)}},gz:B(e){q 67=e.gE||e;c.gk=E;if(1E(67.dT)==="1J"){F E}if(67.dT.gh){q 25=67.dT.gh[0].mA();if(25!==2G){c.21();c.gk=N;q dC=2c mo();dC.jd=$.M(c.jz,c);dC.mq(25);F N}}F E},go:B(e){if(c.8j){F E}q 1k=e.5X;q 3x=e.9P||e.80;q L=c.2E();q 1t=c.3P();q 1l=c.2N();q 2q=E;c.1c("5T",e);if(c.1C("31")&&"dz"in 42.29()){if((3x)&&(e.2O===37||e.2O===39)){q 1I=c.29();q dE=(e.80?"9I":"mu");if(e.2O===37){1I.dz("4W","1s",dE);if(!e.53){1I.ih()}}if(e.2O===39){1I.dz("4W","4Z",dE);if(!e.53){1I.mR()}}e.2u()}}c.6t(E);if((L&&$(L).2R(0).Q==="6i")||(1t&&$(1t).2R(0).Q==="6i")){2q=N;if(1k===c.2O.dH){c.6U(1l)}}if(1k===c.2O.dH){if(L&&$(L)[0].Q==="3F"){c.6U(L)}if(1t&&$(1t)[0].Q==="3F"){c.6U(1t)}if(L&&$(L)[0].Q==="P"&&$(L).L()[0].Q=="3F"){c.6U(L,$(L).L()[0])}if(1t&&$(1t)[0].Q==="P"&&L&&$(L)[0].Q=="3F"){c.6U(1t,L)}}c.6j(e,1k);if(3x&&1k===90&&!e.53&&!e.fk){e.2u();if(c.C.3E.1m){c.k7()}I{c.X.26("mP",E,E)}F}I{if(3x&&1k===90&&e.53&&!e.fk){e.2u();if(c.C.8A.1m!=0){c.k6()}I{c.X.26("mM",E,E)}F}}if(1k==32){c.21()}if(3x&&1k===65){c.21();c.5R=N}I{if(1k!=c.2O.fw&&!3x){c.5R=E}}if(1k==c.2O.b9&&!e.53&&!e.9P&&!e.80){q O=c.3o();if(O&&O.4F===E){1q=c.29();if(1q.4V){O.aC()}}if(c.1C("3t")&&(L.4D==1&&(L.Q=="6h"||L.Q=="mH"))){e.2u();c.21();c.3B(X.4C("br"));c.1c("7x",e);F E}if(1l&&(1l.Q=="3F"||$(1l).L()[0].Q=="3F")){if(c.ck()){if(c.7P==1){q 2j;q 2W;if(1l.Q=="3F"){2W="br";2j=1l}I{2W="p";2j=$(1l).L()[0]}e.2u();c.bt(2j);c.7P=0;if(2W=="p"){$(1l).L().1b("p").2W().1w()}I{q 2L=$.2b($(1l).o());$(1l).o(2L.G(/<br\\s?\\/?>$/i,""))}F}I{c.7P++}}I{c.7P++}}if(2q===N){F c.hZ(e,1t)}I{if(!c.C.1N){if(1l&&1l.Q=="3h"){q 5P=c.2N();if(5P!==E||5P.Q==="3h"){q 9o=$.2b($(1l).Y());q fA=$.2b($(5P).Y());if(9o==""&&fA==""&&$(5P).4f("li").1V()==0&&$(5P).8B("li").1V()==0){c.21();q $3a=$(5P).2g("ol, 2r");$(5P).1w();q J=$("<p>"+c.C.2i+"</p>");$3a.2I(J);c.4g(J);c.1j();c.1c("7x",e);F E}}}if(1l&&c.C.cu.4e(1l.Q)){c.21();2V($.M(B(){q 5e=c.2N();if(5e.Q==="8K"&&!$(5e).3v("4d")){q J=$("<p>"+c.C.2i+"</p>");$(5e).2e(J);c.4g(J)}},c),1)}I{if(1l===E){c.21();q J=$("<p>"+c.C.2i+"</p>");c.3B(J[0]);c.4g(J);c.1c("7x",e);F E}}}if(c.C.1N){if(1l&&c.C.cu.4e(1l.Q)){c.21();2V($.M(B(){q 5e=c.2N();if((5e.Q==="8K"||5e.Q==="P")&&!$(5e).3v("4d")){c.jr(5e)}},c),1)}I{F c.cl(e)}}if(1l.Q=="3F"||1l.Q=="cA"){F c.cl(e)}}c.1c("7x",e)}I{if(1k===c.2O.b9&&(e.9P||e.53)){c.21();e.2u();c.al()}}if((1k===c.2O.iG||e.80&&1k===hi)&&c.C.6j){F c.hY(e,2q,1k)}if(1k===c.2O.a2){c.i4(e,1t,L)}},hZ:B(e,1t){e.2u();c.21();q o=$(1t).L().Y();c.3B(X.82("\\n"));if(o.4G(/\\s$/)==-1){c.3B(X.82("\\n"))}c.1j();c.1c("7x",e);F E},hY:B(e,2q,1k){if(!c.C.i2){F N}if(c.9B(c.2R())&&c.C.aW===E){F N}e.2u();if(2q===N&&!e.53){c.21();c.3B(X.82("\\t"));c.1j();F E}I{if(c.C.aW!==E){c.21();c.3B(X.82(i3(c.C.aW+1).5V("\\mp")));c.1j();F E}I{if(!e.53){c.cx()}I{c.cy()}}}F E},i4:B(e,1t,L){if(L&&1t&&L.4L.Q=="6h"&&L.Q=="fB"&&1t.Q=="3h"&&$(L).4c("li").1V()==1){q Y=$(1t).Y().G(/[\\7D-\\gs\\gB]/g,"");if(Y==""){q J=L.4L;$(L).1w();c.4g(J);c.1j();F E}}if(1E 1t.Q!=="1J"&&/^(H[1-6])$/i.4e(1t.Q)){q J;if(c.C.1N===E){J=$("<p>"+c.C.2i+"</p>")}I{J=$("<br>"+c.C.2i)}$(1t).2e(J);c.4g(J);c.1j()}if(1E 1t.aH!=="1J"&&1t.aH!==2G){if(1t.1w&&1t.4D===3&&1t.aH.1U(/[^\\7D]/g)==2G){$(1t).4M().1w();c.1j()}}},cl:B(e){c.21();e.2u();c.al();c.1c("7x",e);F},hS:B(e){if(c.8j){F E}q 1k=e.5X;q L=c.2E();q 1t=c.3P();if(!c.C.1N&&1t.4D==3&&(L==E||L.Q=="ee")){q J=$("<p>").1h($(1t).6q());$(1t).2e(J);q 4f=$(J).4f();if(1E(4f[0])!=="1J"&&4f[0].Q=="bC"){4f.1w()}c.7G(J)}if((c.C.7d||c.C.6l||c.C.75)&&1k===c.2O.b9){c.ix()}if(1k===c.2O.eC||1k===c.2O.a2){F c.kP(e)}c.1c("5d",e);c.1j(e)},ix:B(){c.e7(c.C.8a,c.C.7d,c.C.6l,c.C.75,c.C.7A);2V($.M(B(){if(c.C.6l){c.4T()}if(c.C.5m){c.5m()}},c),5)},iu:B(){if(!c.C.cn){F}$.1u(c.C.cn,$.M(B(i,s){if(cq[s]){$.4W(c,cq[s]);if($.7z(cq[s].77)){c.77()}}},c))},5j:B(){c.io();if(c.C.64){c.co(c.$1v)}I{c.$cp=c.$1v.2U();c.$1v=c.bd(c.$cp);c.co(c.$cp)}},co:B(el){c.$1v.1i("69",c.C.63).2U();c.$2C.at(el).1h(c.$2Z).1h(c.$1v)},io:B(){c.$2Z=$(\'<1Q 1o="2p: 3O%;" cS="0" />\').b8("jZ",$.M(B(){if(c.C.4n){c.cV();if(c.3L===""){c.3L=c.C.2i}c.$2Z.1Y()[0].gW(c.3L);c.$2Z.1Y()[0].h8();q gV=gS($.M(B(){if(c.$2Z.1Y().1b("2v").o()){hd(gV);c.cZ()}},c),0)}I{c.cZ()}},c))},bG:B(){F c.$2Z[0].bK.X},cV:B(){q 3C=c.bG();if(3C.aE){3C.ly(3C.aE)}F 3C},cK:B(1f){1f=1f||c.C.1f;if(c.kF(1f)){c.$2Z.1Y().1b("9U").1h(\'<1r 4m="lw" 1S="\'+1f+\'" />\')}if($.lu(1f)){$.1u(1f,$.M(B(i,1K){c.cK(1K)},c))}},cZ:B(){c.$K=c.$2Z.1Y().1b("2v").1i({3K:N,69:c.C.63});if(c.$K[0]){c.X=c.$K[0].lK;c.42=c.X.me||42}c.cK();if(c.C.4n){c.d1(c.$1v.1p())}I{c.6V(c.3L,N,E)}c.cJ();c.cH()},hj:B(){if(c.C.4U!==E){c.cR=c.C.4U;c.C.4U=N}I{if(1E c.$2j.1i("4U")=="1J"||c.$2j.1i("4U")==""){c.C.4U=E}I{c.cR=c.$2j.1i("4U");c.C.4U=N}}},gl:B(o){if(c.C.4U===E){F E}if(c.9B(o)){c.C.2h=E;c.cQ();c.cM();F c.cP()}I{c.cM()}F E},cQ:B(){c.$K.on("2h.55",$.M(c.hm,c))},cM:B(){c.$K.on("cN.55",$.M(c.hh,c))},cP:B(){q ph=$(\'<V 1x="55">\').1a("U","9s").1i("3K",E).Y(c.cR);if(c.C.1N===E){F $("<p>").1h(ph)}I{F ph}},hh:B(){q o=c.2R();if(c.9B(o)){c.cQ();c.$K.o(c.cP())}},hm:B(){c.$K.1b("V.55").1w();q o="";if(c.C.1N===E){o=c.C.58}c.$K.3b("2h.55");c.$K.o(o);if(c.C.1N===E){c.4g(c.$K.4c()[0])}I{c.2h()}c.1j()},hs:B(){c.$K.1b("V.55").1w();c.$K.3b("2h.55")},hq:B(o){F o.G(/<V 1x="55"(.*?)>(.*?)<\\/V>/i,"")},6j:B(e,1k){if(!c.C.6j){if((e.9P||e.80)&&(1k===66||1k===73)){e.2u()}F E}$.1u(c.C.6j,$.M(B(4p,hn){q 54=4p.4o(",");3z(q i in 54){if(1E 54[i]==="8I"){c.ho(e,$.2b(54[i]),$.M(B(){m1(hn)},c))}}},c))},ho:B(e,54,hA){q hu={8:"mk",9:"52",10:"F",13:"F",16:"7V",17:"3x",18:"8n",19:"md",20:"mc",27:"m5",32:"6c",33:"m4",34:"m7",35:"3l",36:"ml",37:"1s",38:"lC",39:"4Z",40:"ln",45:"4y",46:"56",59:";",61:"=",96:"0",97:"1",98:"2",99:"3",3O:"4",lp:"5",lj:"6",lg:"7",lJ:"8",lD:"9",lG:"*",lI:"+",lB:"-",lA:".",lx:"/",mx:"f1",rm:"f2",rk:"f3",rp:"f4",rr:"f5",rc:"f6",qs:"f7",qv:"f8",qy:"f9",qe:"ql",qk:"qi",qj:"qV",qY:"qZ",qQ:"h7",qP:"-",qD:";",qE:"=",qu:",",qL:"-",qK:".",qM:"/",qN:"`",hi:"[",qO:"\\\\",qJ:"]",qI:"\'"};q cL={"`":"~","1":"!","2":"@","3":"#","4":"$","5":"%","6":"^","7":"&","8":"*","9":"(","0":")","-":"qF","=":"+",";":": ","\'":\'"\',",":"<",".":">","/":"?","\\\\":"|"};54=54.3N().4o(" ");q aw=hu[e.2O],7S=51.qG(e.5X).3N(),7q="",7u={};$.1u(["8n","3x","4w","7V"],B(2K,az){if(e[az+"qH"]&&aw!==az){7q+=az+"+"}});if(aw){7u[7q+aw]=N}if(7S){7u[7q+7S]=N;7u[7q+cL[7S]]=N;if(7q==="7V+"){7u[cL[7S]]=N}}3z(q i=0,l=54.1m;i<l;i++){if(7u[54[i]]){e.2u();F hA.bO(c,hw)}}},2h:B(){if(!c.1C("94")){c.42.2V($.M(c.aK,c,N),1)}I{c.$K.2h()}},4i:B(){if(c.1C("3t")){q 1T=c.X.aE.3e}c.$K.2h();if(c.1C("3t")){c.X.aE.3e=1T}},cb:B(){if(!c.1C("31")){c.aK()}I{if(c.C.1N===E){q 2W=c.$K.4c().2W();c.$K.2h();c.7G(2W)}I{c.aK()}}},aK:B(5h,2j){c.$K.2h();if(1E 2j=="1J"){2j=c.$K[0]}q O=c.3o();O.aM(2j);O.5h(5h||E);q 1q=c.29();1q.4X();1q.5g(O)},d0:B(aG){if(c.C.5N){c.hz(aG)}I{c.hy()}},hy:B(){q o=c.$1v.2U().1p();if(1E c.6E!=="1J"){q 6E=c.6E.G(/\\n/g,"");q 78=o.G(/\\n/g,"");78=c.6Z(78,E);c.6E=c.6Z(6E,E)!==78}if(c.6E){if(c.C.4n&&o===""){c.d1(o)}I{c.6V(o);if(c.C.4n){c.d2()}}c.1c("68",E,o)}if(c.C.1Q){c.$2Z.2M()}I{c.$K.2M()}if(c.C.4n){c.$K.1i("3K",N)}c.$1v.3b("5T.U-5s-gP");c.$K.2h();c.1W();c.7H();c.hN();c.ct("o");c.C.5N=N},hz:B(aG){if(aG!==E){c.28()}q 22=2G;if(c.C.1Q){22=c.$2Z.22();if(c.C.4n){c.$K.2B("3K")}c.$2Z.2U()}I{22=c.$K.iE();c.$K.2U()}q o=c.$1v.1p();if(o!==""&&c.C.gQ){c.$1v.1p(c.l5(o))}c.6E=o;c.$1v.22(22).2M().2h();c.$1v.on("5T.U-5s-gP",c.gR);c.gG();c.7b("o");c.C.5N=E},gR:B(e){if(e.2O===9){q $el=$(c);q 2y=$el.2R(0).4g;$el.1p($el.1p().aQ(0,2y)+"\\t"+$el.1p().aQ($el.2R(0).7G));$el.2R(0).4g=$el.2R(0).7G=2y+1;F E}},7C:B(){q cY=E;c.ah=gS($.M(B(){q o=c.2R();if(cY!==o){q 2m=c.$1v.1i("2m");$.iU({1K:c.C.7C,1G:"7R",1a:"2m="+2m+"&"+2m+"="+r0(r1(o)),4h:$.M(B(1a){q 1M=$.8J(1a);if(1E 1M.3U=="1J"){c.1c("7C",E,1M)}I{c.1c("r2",E,1M)}cY=o},c)})}},c),c.C.ah*qX)},gI:B(){if(c.5a()&&c.C.cU.1m>0){$.1u(c.C.cU,$.M(B(i,s){q 2K=c.C.4a.44(s);c.C.4a.ap(2K,1)},c))}if(c.C.30){c.C.4a=c.C.gJ}I{if(!c.C.gK){q 2K=c.C.4a.44("o");c.C.4a.ap(2K,1)}}if(c.C.1A){$.1u(c.C.1A.6x.1R,$.M(B(i,s){if($.4O(i,c.C.gM)=="-1"){e9 c.C.1A.6x.1R[i]}},c))}if(c.C.4a.1m===0){F E}c.il();c.$1A=$("<2r>").2z("cW").1i("id","qW"+c.7F);if(c.C.7X){c.$1A.2z("U-1A-7X")}if(c.C.h9&&c.5a()){c.$1A.2z("U-1A-8V")}if(c.C.30){c.$30=$(\'<12 1x="ik">\').1i("id","hb"+c.7F).2U();c.$30.1h(c.$1A);$("2v").1h(c.$30)}I{if(c.C.7O){c.$1A.2z("U-1A-qS");$(c.C.7O).o(c.$1A)}I{c.$2C.6d(c.$1A)}}$.1u(c.C.4a,$.M(B(i,2w){if(c.C.1A[2w]){q 2d=c.C.1A[2w];if(c.C.84===E&&2w==="25"){F N}c.$1A.1h($("<li>").1h(c.7f(2w,2d)))}},c));c.$1A.1b("a").1i("7c","-1");if(c.C.6B){c.cX();$(c.C.7T).on("h7.U",$.M(c.cX,c))}if(c.C.aY){c.$K.on("a8.U 5d.U",$.M(c.8r,c))}},cX:B(){q 3e=$(c.C.7T).3e();q 7M=0;q 1s=0;q 3l=0;if(c.C.7T===X){7M=c.$2C.2Y().1T}I{7M=1}3l=7M+c.$2C.22()+40;if(3e>7M){q 2p="3O%";if(c.C.as){1s=c.$2C.2Y().1s;2p=c.$2C.ic();c.$1A.2z("im")}c.6B=N;if(c.C.7T===X){c.$1A.1f({3k:"ak",2p:2p,9u:h0,1T:c.C.bb+"px",1s:1s})}I{c.$1A.1f({3k:"8T",2p:2p,9u:h0,1T:(c.C.bb+3e)+"px",1s:0})}if(3e<3l){c.$1A.1f("hH","gq")}I{c.$1A.1f("hH","8E")}}I{c.6B=E;c.$1A.1f({3k:"gC",2p:"4l",1T:0,1s:1s});if(c.C.as){c.$1A.3c("im")}}},il:B(){if(!c.C.30){F}c.$K.on("a8.U 5d.U",c,$.M(B(e){q Y=c.aB();if(e.1G==="a8"&&Y!=""){c.cF(e)}if(e.1G==="5d"&&e.53&&Y!=""){q $cG=$(c.8g(c.29().qU)),2Y=$cG.2Y();2Y.22=$cG.22();c.cF(2Y,N)}},c))},cF:B(e,ij){if(!c.C.30){F}c.28();q 1s,1T;$(".ik").2U();if(ij){1s=e.1s;1T=e.1T+e.22+14;if(c.C.1Q){1T+=c.$2C.3k().1T-$(c.X).3e();1s+=c.$2C.3k().1s}}I{q 2p=c.$30.ic();1s=e.jl;if($(c.X).2p()<(1s+2p)){1s-=2p}1T=e.jm+14;if(c.C.1Q){1T+=c.$2C.3k().1T;1s+=c.$2C.3k().1s}I{1T+=$(c.X).3e()}}c.$30.1f({1s:1s+"px",1T:1T+"px"}).2M();c.ie()},ie:B(){if(!c.C.30){F}q 6W=$.M(B(3C){$(3C).on("9J.U",$.M(B(e){if($(e.1O).2g(c.$1A).1m===0){c.$30.6N(3O);c.bA();$(3C).3b(e)}},c)).on("5T.U",$.M(B(e){if(e.5X===c.2O.dY){c.29().ih()}c.$30.6N(3O);$(3C).3b(e)},c))},c);6W(X);if(c.C.1Q){6W(c.X)}},bi:B(){if(!c.C.30){F}q 6W=$.M(B(3C){$(3C).on("l1.U",$.M(B(e){if($(e.1O).2g(c.$1A).1m===0){c.$30.6N(3O);$(3C).3b(e)}},c))},c);6W(X);if(c.C.1Q){6W(c.X)}},hJ:B($1R,ir){$.1u(ir,$.M(B(2w,2d){if(!2d.2S){2d.2S=""}q $6Y;if(2d.2m==="aN"){$6Y=$(\'<a 1x="qC">\')}I{$6Y=$(\'<a 1S="#" 1x="\'+2d.2S+" qB"+2w+\'">\'+2d.1e+"</a>");$6Y.on("23",$.M(B(e){if(c.C.30){c.1W()}if(e.2u){e.2u()}if(c.1C("3t")){e.hU=E}if(2d.1c){2d.1c.5q(c,2w,$6Y,2d,e)}if(2d.2o){c.26(2d.2o,2w)}if(2d.1H){c[2d.1H](2w)}c.8r();if(c.C.30){c.$30.6N(3O)}},c))}$1R.1h($6Y)},c))},cj:B(e,1k){if(!c.C.5N){e.2u();F E}q $1D=c.4q(1k);q $1R=$1D.1a("1R").8P(X.2v);if($1D.3v("7a")){c.bj()}I{c.bj();c.1c("cj",{1R:$1R,1k:1k,1D:$1D});c.7b(1k);$1D.2z("7a");q 7U=$1D.2Y();q cr=$1R.2p();if((7U.1s+cr)>$(X).2p()){7U.1s-=cr}q 1s=7U.1s+"px";q cm=$1D.iE();q 3k="8T";q 1T=(cm+c.C.bb)+"px";if(c.C.6B&&c.6B){3k="ak"}I{1T=7U.1T+cm+"px"}$1R.1f({3k:3k,1s:1s,1T:1T}).2M();c.1c("qm",{1R:$1R,1k:1k,1D:$1D})}q bh=$.M(B(e){c.ci(e,$1R)},c);$(X).b8("23",bh);c.$K.b8("23",bh);c.$K.b8("qh",bh);e.qg();c.4i()},bj:B(){c.$1A.1b("a.7a").3c("8z").3c("7a");$(".hO").2U();c.1c("ci")},ci:B(e,$1R){if(!$(e.1O).3v("7a")){$1R.3c("7a");c.bj()}},7f:B(2w,2d,hT){q $1D=$(\'<a 1S="ab:;" 1e="\'+2d.1e+\'" 7c="-1" 1x="re-aR re-\'+2w+\'"></a>\');if(1E hT!="1J"){$1D.2z("U-2a-T")}$1D.on("23",$.M(B(e){if(e.2u){e.2u()}if(c.1C("3t")){e.hU=E}if($1D.3v("cB")){F E}if(c.7h()===E&&!2d.2o){c.4i()}if(2d.2o){c.4i();c.26(2d.2o,2w);c.bi()}I{if(2d.1H&&2d.1H!=="2M"){c[2d.1H](2w);c.bi()}I{if(2d.1c){2d.1c.5q(c,2w,$1D,2d,e);c.bi()}I{if(2d.1R){c.cj(e,2w)}}}}c.8r(E,2w)},c));if(2d.1R){q $1R=$(\'<12 1x="hO qb\'+2w+\'" 1o="3f: 3r;">\');$1D.1a("1R",$1R);c.hJ($1R,2d.1R)}F $1D},4q:B(1k){if(!c.C.1A){F E}F $(c.$1A.1b("a.re-"+1k))},qa:B(cs,Q){c.C.aY.3d(cs);c.C.cv[Q]=cs},fo:B(1k){q 2a=c.4q(1k);if(2a.3v("8z")){c.ct(1k)}I{c.7b(1k)}},7b:B(1k){q 2a=c.4q(1k);2a.2z("8z")},ct:B(1k){q 2a=c.4q(1k);2a.3c("8z")},fl:B(2w){c.$1A.1b("a.re-aR").ay(".re-"+2w).3c("8z")},hN:B(){c.$1A.1b("a.re-aR").ay("a.re-o").3c("cB")},gG:B(){c.$1A.1b("a.re-aR").ay("a.re-o").2z("cB")},qc:B(1k,aP){c.4q(1k).2z("re-"+aP)},qd:B(1k,aP){c.4q(1k).3c("re-"+aP)},qf:B(1k,2m){q 1D=c.4q(1k);1D.3c("U-2a-T");1D.2z("fa-U-2a");1D.o(\'<i 1x="fa \'+2m+\'"></i>\')},qn:B(1k,1e,1c,1R){if(!c.C.1A){F}q 2a=c.7f(1k,{1e:1e,1c:1c,1R:1R},N);c.$1A.1h($("<li>").1h(2a));F 2a},qo:B(1k,1e,1c,1R){if(!c.C.1A){F}q 2a=c.7f(1k,{1e:1e,1c:1c,1R:1R},N);c.$1A.6d($("<li>").1h(2a))},qx:B(hX,1k,1e,1c,1R){if(!c.C.1A){F}q 2a=c.7f(1k,{1e:1e,1c:1c,1R:1R},N);q $2a=c.4q(hX);if($2a.1V()!==0){$2a.L().2I($("<li>").1h(2a))}I{c.$1A.1h($("<li>").1h(2a))}F 2a},qw:B(i1,1k,1e,1c,1R){if(!c.C.1A){F}q 2a=c.7f(1k,{1e:1e,1c:1c,1R:1R},N);q $2a=c.4q(i1);if($2a.1V()!==0){$2a.L().3S($("<li>").1h(2a))}I{c.$1A.1h($("<li>").1h(2a))}F 2a},qz:B(1k){q $2a=c.4q(1k);$2a.1w()},8r:B(e,2w){q L=c.2E();c.fl(2w);if(e===E&&2w!=="o"){if($.4O(2w,c.C.aY)!=-1){c.fo(2w)}F}if(L&&L.Q==="A"){c.$1A.1b("a.fG").Y(c.C.1F.fg)}I{c.$1A.1b("a.fG").Y(c.C.1F.cz)}$.1u(c.C.cv,$.M(B(1k,2s){if($(L).2g(1k,c.$K.2R()[0]).1m!=0){c.7b(2s)}},c));q $L=$(L).2g(c.C.5t.43().3N(),c.$K[0]);if($L.1m){q 57=$L.1f("Y-57");if(57==""){57="1s"}c.7b("57"+57)}},aL:B(o){q 1q=c.29();if(1q.48&&1q.4V){q O=c.3o();O.aC();q el=c.X.4C("12");el.3Q=o;q 4k=c.X.cD(),J,5v;3w((J=el.8G)){5v=4k.7g(J)}q r4=4k.8G;O.3B(4k);if(5v){O=O.ag();O.ae(5v);O.5h(N)}1q.4X();1q.5g(O)}},2o:B(1P,2F,1j){if(1P==="d8"&&c.1C("3t")){2F="<"+2F+">"}if(1P==="5c"&&c.1C("3t")){if(!c.8L()){c.4i();c.X.1I.5i().do(2F)}I{c.aL(2F)}}I{c.X.26(1P,E,2F)}if(1j!==E){c.1j()}c.1c("26",1P,2F)},26:B(1P,2F,1j){if(!c.C.5N){c.$1v.2h();F E}if(1P==="3m"||1P==="3q"||1P==="4K"||1P==="fC"){c.21()}if(1P==="fm"||1P==="fy"){q L=c.2E();if(L.Q==="qq"||L.Q==="qp"){c.bk(L)}}if(1P==="5c"){c.9i(2F,1j);c.1c("26",1P,2F);F}if(c.74("6i")&&!c.C.fz){F E}if(1P==="8k"||1P==="8q"){F c.fv(1P,2F)}if(1P==="6F"){F c.fu(1P,2F)}c.2o(1P,2F,1j);if(1P==="fx"){c.$K.1b("hr").2B("id")}},fu:B(1P,2F){c.21();q 1r=c.74("A");if(1r){$(1r).2e($(1r).Y());c.1j();c.1c("26",1P,2F);F}},fv:B(1P,2F){c.21();q L=c.2E();q $3a=$(L).2g("ol, 2r");if(!c.4j($3a)&&$3a.1V()!=0){$3a=E}q 1w=E;if($3a&&$3a.1m){1w=N;q 5x=$3a[0].Q;if((1P==="8k"&&5x==="qr")||(1P==="8q"&&5x==="fB")){1w=E}}c.28();if(1w){q 1X=c.7r();q 4t=c.3I(1X);if(1E 1X[0]!="1J"&&1X.1m>1&&1X[0].4D==3){4t.jS(c.2N())}q 1a="",5B="";$.1u(4t,$.M(B(i,s){if(s.Q=="3h"){q $s=$(s);q 7e=$s.6q();7e.1b("2r","ol").1w();if(c.C.1N===E){1a+=c.3Z($("<p>").1h(7e.1Y()))}I{q fH=7e.o().G(/<br\\s?\\/?>$/i,"");1a+=fH+"<br>"}if(i==0){$s.2z("U-5B").6z();5B=c.3Z($s)}I{$s.1w()}}},c));o=c.$K.o().G(5B,"</"+5x+">"+1a+"<"+5x+">");c.$K.o(o);c.$K.1b(5x+":6z").1w()}I{q fh=$(c.2E()).2g("1g");if(c.1C("3t")&&!c.8L()&&c.C.1N){q 3G=c.bx("12");q 9w=$(3G).o();q 7i=$("<2r>");if(1P=="8q"){7i=$("<ol>")}q 8o=$("<li>");if($.2b(9w)==""){8o.1h(9w+\'<V id="1I-1L-1">\'+c.C.2i+"</V>");7i.1h(8o);c.$K.1b("#1I-1L-1").2e(7i)}I{8o.1h(9w);7i.1h(8o);$(3G).2e(7i)}}I{c.X.26(1P)}q L=c.2E();q $3a=$(L).2g("ol, 2r");if(c.C.1N===E){q 9o=$.2b($3a.Y());if(9o==""){$3a.4c("li").1b("br").1w();$3a.4c("li").1h(\'<V id="1I-1L-1">\'+c.C.2i+"</V>")}}if(fh.1V()!=0){$3a.kQ("<1g>")}if($3a.1m){q $7n=$3a.L();if(c.4j($7n)&&$7n[0].Q!="3h"&&c.7v($7n[0])){$7n.2e($7n.1Y())}}if(c.1C("31")){c.$K.2h()}}c.1W();c.$K.1b("#1I-1L-1").2B("id");c.1j();c.1c("26",1P,2F);F},cx:B(){c.d4("6m")},cy:B(){c.d4("7m")},d4:B(1P){c.21();if(1P==="6m"){q 1l=c.2N();c.28();if(1l&&1l.Q=="3h"){q L=c.2E();q $3a=$(L).2g("ol, 2r");q 5x=$3a[0].Q;q 4t=c.3I();$.1u(4t,B(i,s){if(s.Q=="3h"){q $4M=$(s).4M();if($4M.1V()!=0&&$4M[0].Q=="3h"){q $dF=$4M.4c("2r, ol");if($dF.1V()==0){$4M.1h($("<"+5x+">").1h(s))}I{$dF.1h(s)}}}})}I{if(1l===E&&c.C.1N===N){c.2o("6o","2f");q 7j=c.2N();q 1l=$(\'<12 1a-81="">\').o($(7j).o());$(7j).2e(1l);q 1s=c.9M($(1l).1f("2H-1s"))+c.C.9q;$(1l).1f("2H-1s",1s+"px")}I{q 79=c.3I();$.1u(79,$.M(B(i,1z){q $el=E;if(1z.Q==="6h"){F}if($.4O(1z.Q,c.C.5t)!==-1){$el=$(1z)}I{$el=$(1z).2g(c.C.5t.43().3N(),c.$K[0])}q 1s=c.9M($el.1f("2H-1s"))+c.C.9q;$el.1f("2H-1s",1s+"px")},c))}}c.1W()}I{c.28();q 1l=c.2N();if(1l&&1l.Q=="3h"){q 4t=c.3I();q 2K=0;c.dG(1l,2K,4t)}I{q 79=c.3I();$.1u(79,$.M(B(i,1z){q $el=E;if($.4O(1z.Q,c.C.5t)!==-1){$el=$(1z)}I{$el=$(1z).2g(c.C.5t.43().3N(),c.$K[0])}q 1s=c.9M($el.1f("2H-1s"))-c.C.9q;if(1s<=0){if(c.C.1N===N&&1E($el.1a("81"))!=="1J"){$el.2e($el.o()+"<br>")}I{$el.1f("2H-1s","");c.4H($el,"1o")}}I{$el.1f("2H-1s",1s+"px")}},c))}c.1W()}c.1j()},dG:B(li,2K,4t){if(li&&li.Q=="3h"){q $L=$(li).L().L();if($L.1V()!=0&&$L[0].Q=="3h"){$L.2I(li)}I{if(1E 4t[2K]!="1J"){li=4t[2K];2K++;c.dG(li,2K,4t)}I{c.26("8k")}}}},dI:B(){c.8l("","rg")},dD:B(){c.8l("4Z","rA")},dy:B(){c.8l("6O","rz")},dA:B(){c.8l("dB","rB")},8l:B(1G,1P){c.21();if(c.cw()){c.X.26(1P,E,E);F N}c.28();q 1l=c.2N();if(!1l&&c.C.1N){c.2o("d8","12");q 7j=c.2N();q 1l=$(\'<12 1a-81="">\').o($(7j).o());$(7j).2e(1l);$(1l).1f("Y-57",1G);c.4H(1l,"1o");if(1G==""&&1E($(1l).1a("81"))!=="1J"){$(1l).2e($(1l).o())}}I{q 79=c.3I();$.1u(79,$.M(B(i,1z){q $el=E;if($.4O(1z.Q,c.C.5t)!==-1){$el=$(1z)}I{$el=$(1z).2g(c.C.5t.43().3N(),c.$K[0])}if($el){$el.1f("Y-57",1G);c.4H($el,"1o")}},c))}c.1W();c.1j()},dJ:B(o){q ph=c.gl(o);if(ph!==E){F ph}if(c.C.1N===E){if(o===""){o=c.C.58}I{if(o.4G(/^<hr\\s?\\/?>$/gi)!==-1){o="<hr>"+c.C.58}}}F o},dK:B(o){if(c.C.9a&&!c.C.rD){o=o.G(/<12(.*?)>([\\w\\W]*?)<\\/12>/gi,"<p$1>$2</p>")}if(c.C.5Y){o=c.9z(o)}F o},dR:B(o){if(c.C.dS){o=o.G(/\\{\\{(.*?)\\}\\}/gi,"<!-- 9r ge $1 -->");o=o.G(/\\{(.*?)\\}/gi,"<!-- 9r $1 -->")}o=o.G(/<3n(.*?)>([\\w\\W]*?)<\\/3n>/gi,\'<1e 1G="Y/ab" 1o="3f: 3r;" 1x="U-3n-1d"$1>$2</1e>\');o=o.G(/<1o(.*?)>([\\w\\W]*?)<\\/1o>/gi,\'<2l$1 1o="3f: 3r;" 4m="U-1o-1d">$2</2l>\');o=o.G(/<2t(.*?)>([\\w\\W]*?)<\\/2t>/gi,\'<2l$1 4m="U-2t-1d">$2</2l>\');if(c.C.8x){o=o.G(/<\\?4I([\\w\\W]*?)\\?>/gi,\'<2l 1o="3f: 3r;" 4m="U-4I-1d">$1</2l>\')}I{o=o.G(/<\\?4I([\\w\\W]*?)\\?>/gi,"")}F o},fL:B(o){if(c.C.dS){o=o.G(/<!-- 9r ge (.*?) -->/gi,"{{$1}}");o=o.G(/<!-- 9r (.*?) -->/gi,"{$1}")}o=o.G(/<1e 1G="Y\\/ab" 1o="3f: 3r;" 1x="U-3n-1d"(.*?)>([\\w\\W]*?)<\\/1e>/gi,\'<3n$1 1G="Y/ab">$2<\\/3n>\');o=o.G(/<2l(.*?) 1o="3f: 3r;" 4m="U-1o-1d">([\\w\\W]*?)<\\/2l>/gi,"<1o$1>$2</1o>");o=o.G(/<2l(.*?)4m="U-2t-1d"(.*?)>([\\w\\W]*?)<\\/2l>/gi,"<2t$1$2>$3</2t>");if(c.C.8x){o=o.G(/<2l 1o="3f: 3r;" 4m="U-4I-1d">([\\w\\W]*?)<\\/2l>/gi,"<?4I\\r\\n$1\\r\\n?>")}F o},6Z:B(o,3E){if(3E!==E){q 3E=[];q 2x=o.1U(/<(2q|1o|3n|1e)(.*?)>([\\w\\W]*?)<\\/(2q|1o|3n|1e)>/gi);if(2x===2G){2x=[]}if(c.C.8x){q 6X=o.1U(/<\\?4I([\\w\\W]*?)\\?>/gi);if(6X){2x=$.dP(2x,6X)}}if(2x){$.1u(2x,B(i,s){o=o.G(s,"gr"+i);3E.3d(s)})}}o=o.G(/\\n/g," ");o=o.G(/[\\t]*/g,"");o=o.G(/\\n\\s*\\n/g,"\\n");o=o.G(/^[\\s\\n]*/g," ");o=o.G(/[\\s\\n]*$/g," ");o=o.G(/>\\s{2,}</g,"> <");o=c.gj(o,3E);o=o.G(/\\n\\n/g,"\\n");F o},gj:B(o,3E){if(3E===E){F o}$.1u(3E,B(i,s){o=o.G("gr"+i,s)});F o},dq:B(o){o=o.G(/[\\7D-\\gs\\gB]/g,"");q dU=["<b>\\\\s*</b>","<b>&3u;</b>","<em>\\\\s*</em>"];q 70=["<2q></2q>","<2f>\\\\s*</2f>","<dd></dd>","<dt></dt>","<2r></2r>","<ol></ol>","<li></li>","<1n></1n>","<3j></3j>","<V>\\\\s*<V>","<V>&3u;<V>","<p>\\\\s*</p>","<p></p>","<p>&3u;</p>","<p>\\\\s*<br>\\\\s*</p>","<12>\\\\s*</12>","<12>\\\\s*<br>\\\\s*</12>"];if(c.C.a1){70=70.rd(dU)}I{70=dU}q 4P=70.1m;3z(q i=0;i<4P;++i){o=o.G(2c 2J(70[i],"gi"),"")}F o},9z:B(o){o=$.2b(o);if(c.C.1N===N){F o}if(o===""||o==="<p></p>"){F c.C.58}o=o+"\\n";if(c.C.a1===E){F o}q dx=[];q 2x=o.1U(/<(1n|12|2q|3M)(.*?)>([\\w\\W]*?)<\\/(1n|12|2q|3M)>/gi);if(!2x){2x=[]}q dQ=o.1U(/<!--([\\w\\W]*?)-->/gi);if(dQ){2x=$.dP(2x,dQ)}if(c.C.8x){q 6X=o.1U(/<2l(.*?)4m="U-4I-1d">([\\w\\W]*?)<\\/2l>/gi);if(6X){2x=$.dP(2x,6X)}}if(2x){$.1u(2x,B(i,s){dx[i]=s;o=o.G(s,"{G"+i+"}\\n")})}o=o.G(/<br \\/>\\s*<br \\/>/gi,"\\n\\n");o=o.G(/<br><br>/gi,"\\n\\n");B R(4p,gu,r){F o.G(2c 2J(4p,gu),r)}q 3D="(rh|o|2v|9U|1e|4w|1o|3n|1r|1Q|1n|3T|di|ra|r6|r5|8c|3j|1g|dN|12|dl|dd|dt|2r|ol|li|2q|3s|4b|2t|gc|fU|2f|dm|r7|1o|p|h[1-6]|hr|fV|nE|2l|jk|jj|r8|aS|49|r9|ri|rj|rs|rt|ru)";o=R("(<"+3D+"[^>]*>)","gi","\\n$1");o=R("(</"+3D+">)","gi","$1\\n\\n");o=R("\\r\\n","g","\\n");o=R("\\r","g","\\n");o=R("/\\n\\n+/","g","\\n\\n");q 4A=o.4o(2c 2J("\\ns*\\n","g"),-1);o="";3z(q i in 4A){if(4A.rq(i)){if(4A[i].4G("{G")==-1){4A[i]=4A[i].G(/<p>\\n\\t?<\\/p>/gi,"");4A[i]=4A[i].G(/<p><\\/p>/gi,"");if(4A[i]!=""){o+="<p>"+4A[i].G(/^\\n+|\\n+$/g,"")+"</p>"}}I{o+=4A[i]}}}o=R("<p><p>","gi","<p>");o=R("</p></p>","gi","</p>");o=R("<p>s?</p>","gi","");o=R("<p>([^<]+)</(12|dm|2t)>","gi","<p>$1</p></$2>");o=R("<p>(</?"+3D+"[^>]*>)</p>","gi","$1");o=R("<p>(<li.+?)</p>","gi","$1");o=R("<p>s?(</?"+3D+"[^>]*>)","gi","$1");o=R("(</?"+3D+"[^>]*>)s?</p>","gi","$1");o=R("(</?"+3D+"[^>]*>)s?<br />","gi","$1");o=R("<br />(s*</?(?:p|li|12|dl|dd|dt|dN|2q|1g|2r|ol)[^>]*>)","gi","$1");o=R("\\n</p>","gi","</p>");o=R("<li><p>","gi","<li>");o=R("</p></li>","gi","</li>");o=R("</li><p>","gi","</li>");o=R("<p>\\t?\\n?<p>","gi","<p>");o=R("</dt><p>","gi","</dt>");o=R("</dd><p>","gi","</dd>");o=R("<br></p></2f>","gi","</2f>");o=R("<p>\\t*</p>","gi","");$.1u(dx,B(i,s){o=o.G("{G"+i+"}",s)});F $.2b(o)},9S:B(o,6V){q 6r="3V";if(c.C.6r==="b"){6r="b"}q 6p="em";if(c.C.6p==="i"){6p="i"}o=o.G(/<V 1o="2A-1o: 3q;">([\\w\\W]*?)<\\/V>/gi,"<"+6p+">$1</"+6p+">");o=o.G(/<V 1o="2A-7o: 3m;">([\\w\\W]*?)<\\/V>/gi,"<"+6r+">$1</"+6r+">");if(c.C.6r==="3V"){o=o.G(/<b>([\\w\\W]*?)<\\/b>/gi,"<3V>$1</3V>")}I{o=o.G(/<3V>([\\w\\W]*?)<\\/3V>/gi,"<b>$1</b>")}if(c.C.6p==="em"){o=o.G(/<i>([\\w\\W]*?)<\\/i>/gi,"<em>$1</em>")}I{o=o.G(/<em>([\\w\\W]*?)<\\/em>/gi,"<i>$1</i>")}o=o.G(/<V 1o="Y-bZ: 4K;">([\\w\\W]*?)<\\/V>/gi,"<u>$1</u>");if(6V!==N){o=o.G(/<5F>([\\w\\W]*?)<\\/5F>/gi,"<56>$1</56>")}I{o=o.G(/<56>([\\w\\W]*?)<\\/56>/gi,"<5F>$1</5F>")}F o},8d:B(o){if(o==""||1E o=="1J"){F o}q 9T=E;if(c.C.5G!==E){9T=N}q 2Q=9T===N?c.C.5G:c.C.8y;q g8=/<\\/?([a-z][a-eL-9]*)\\b[^>]*>/gi;o=o.G(g8,B($0,$1){if(9T===N){F $.4O($1.3N(),2Q)>"-1"?$0:""}I{F $.4O($1.3N(),2Q)>"-1"?"":$0}});o=c.9S(o);F o},de:B(o,ga){q 2q=o.1U(/<(2q|2k)(.*?)>([\\w\\W]*?)<\\/(2q|2k)>/gi);if(2q!==2G){$.1u(2q,$.M(B(i,s){q 2Q=s.1U(/<(2q|2k)(.*?)>([\\w\\W]*?)<\\/(2q|2k)>/i);2Q[3]=2Q[3].G(/&3u;/g," ");if(ga!==E){2Q[3]=c.ce(2Q[3])}2Q[3]=2Q[3].G(/\\$/g,"&#36;");o=o.G(s,"<"+2Q[1]+2Q[2]+">"+2Q[3]+"</"+2Q[1]+">")},c))}F o},ce:B(4p){4p=51(4p).G(/&9h;/g,"&").G(/&lt;/g,"<").G(/&gt;/g,">").G(/&g6;/g,\'"\');F 4p.G(/&/g,"&9h;").G(/</g,"&lt;").G(/>/g,"&gt;").G(/"/g,"&g6;")},g5:B(){q $1z=c.$K.1b("li, 1B, a, b, 3V, q8, ov, i, em, u, ou, 5F, 56, V, ot");$1z.g2(\'[1o*="8t-6s: g3;"][1o*="9I-22"]\').1f("8t-6s","").1f("9I-22","");$1z.g2(\'[1o*="8t-6s: g3;"]\').1f("8t-6s","");$1z.1f("9I-22","");$.1u($1z,$.M(B(i,s){c.4H(s,"1o")},c));q $cg=c.$K.1b("b, 3V, i, em, u, 5F, 56");$cg.1f("2A-1V","");$.1u($cg,$.M(B(i,s){c.4H(s,"1o")},c));c.$K.1b(\'12[1o="Y-57: -4s-4l;"]\').1Y().l7();c.$K.1b("2r, ol, li").2B("1o")},l5:B(2k){q i=0,9C=2k.1m,3i=0,2y=2G,3l=2G,1d="",1Z="",4N="";c.8v=0;3z(;i<9C;i++){3i=i;if(-1==2k.4R(i).44("<")){1Z+=2k.4R(i);F c.dg(1Z)}3w(3i<9C&&2k.5C(3i)!="<"){3i++}if(i!=3i){4N=2k.4R(i,3i-i);if(!4N.1U(/^\\s{2,}$/g)){if("\\n"==1Z.5C(1Z.1m-1)){1Z+=c.72()}I{if("\\n"==4N.5C(0)){1Z+="\\n"+c.72();4N=4N.G(/^\\s+/,"")}}1Z+=4N}if(4N.1U(/\\n/)){1Z+="\\n"+c.72()}}2y=3i;3w(3i<9C&&">"!=2k.5C(3i)){3i++}1d=2k.4R(2y,3i-2y);i=3i;q t;if("!--"==1d.4R(1,3)){if(!1d.1U(/--$/)){3w("-->"!=2k.4R(3i,3)){3i++}3i+=2;1d=2k.4R(2y,3i-2y);i=3i}if("\\n"!=1Z.5C(1Z.1m-1)){1Z+="\\n"}1Z+=c.72();1Z+=1d+">\\n"}I{if("!"==1d[1]){1Z=c.9L(1d+">",1Z)}I{if("?"==1d[1]){1Z+=1d+">\\n"}I{if(t=1d.1U(/^<(3n|1o|2q)/i)){t[1]=t[1].3N();1d=c.dc(1d);1Z=c.9L(1d,1Z);3l=51(2k.4R(i+1)).3N().44("</"+t[1]);if(3l){4N=2k.4R(i+1,3l);i+=3l;1Z+=4N}}I{1d=c.dc(1d);1Z=c.9L(1d,1Z)}}}}}F c.dg(1Z)},72:B(){q s="";3z(q j=0;j<c.8v;j++){s+="\\t"}F s},dg:B(2k){2k=2k.G(/\\n\\s*\\n/g,"\\n");2k=2k.G(/^[\\s\\n]*/,"");2k=2k.G(/[\\s\\n]*$/,"");2k=2k.G(/<3n(.*?)>\\n<\\/3n>/gi,"<3n$1><\\/3n>");c.8v=0;F 2k},dc:B(1d){q 8w="";1d=1d.G(/\\n/g," ");1d=1d.G(/\\s{2,}/g," ");1d=1d.G(/^\\s+|\\s+$/g," ");q db="";if(1d.1U(/\\/$/)){db="/";1d=1d.G(/\\/+$/,"")}q m;3w(m=/\\s*([^= ]+)(?:=(([\'"\']).*?\\3|[^ ]+))?/.2o(1d)){if(m[2]){8w+=m[1].3N()+"="+m[2]}I{if(m[1]){8w+=m[1].3N()}}8w+=" ";1d=1d.4R(m[0].1m)}F 8w.G(/\\s*$/,"")+db+">"},9L:B(1d,1Z){q nl=1d.1U(c.d7);if(1d.1U(c.kT)||nl){1Z=1Z.G(/\\s*$/,"");1Z+="\\n"}if(nl&&"/"==1d.5C(1)){c.8v--}if("\\n"==1Z.5C(1Z.1m-1)){1Z+=c.72()}if(nl&&"/"!=1d.5C(1)){c.8v++}1Z+=1d;if(1d.1U(c.kN)||1d.1U(c.d7)){1Z=1Z.G(/ *$/,"");1Z+="\\n"}F 1Z},kP:B(e){q o=$.2b(c.$K.o());if(c.C.1N){if(o==""){e.2u();c.$K.o("");c.2h()}}I{o=o.G(/<br\\s?\\/?>/i,"");q 78=o.G(/<p>\\s?<\\/p>/gi,"");if(o===""||78===""){e.2u();q J=$(c.C.58).2R(0);c.$K.o(J);c.2h()}}c.1j()},5I:B(1d){if(c.1C("31")&&c.7h()){c.$K.2h()}c.21();q 1X=c.3I();c.28();$.1u(1X,$.M(B(i,J){if(J.Q!=="3h"){q L=$(J).L();if(1d==="p"){if((J.Q==="P"&&L.1V()!=0&&L[0].Q==="3F")||J.Q==="3F"){c.d9();F}I{if(c.C.1N){if(J&&J.Q.4G(/H[1-6]/)==0){$(J).2e(J.3Q+"<br>")}I{F}}I{c.6o(1d,J)}}}I{c.6o(1d,J)}}},c));c.1W();c.1j()},6o:B(1d,1l){if(1l===E){1l=c.2N()}if(1l===E&&c.C.1N===N){c.26("d8",1d);F N}q 1Y="";if(1d!=="2q"){1Y=$(1l).1Y()}I{1Y=$(1l).o();if($.2b(1Y)===""){1Y=\'<V id="1I-1L-1"></V>\'}}if(1l.Q==="6i"){1d="p"}if(c.C.1N===N&&1d==="p"){$(1l).2e($("<12>").1h(1Y).o()+"<br>")}I{q L=c.2E();q J=$("<"+1d+">").1h(1Y);$(1l).2e(J);if(L&&L.Q=="6h"){$(J).kQ("<1g>")}}},kf:B(kK,kO,7N){if(7N!==E){c.28()}q 8S=$("<"+kO+"/>");$(kK).2e(B(){F 8S.1h($(c).1Y())});if(7N!==E){c.1W()}F 8S},d9:B(){if(c.1C("31")&&c.7h()){c.$K.2h()}c.21();if(c.C.1N===E){c.28();q 3D=c.3I();q 2f=E;q kM=3D.1m;if(3D){q 1a="";q 5B="";q G=E;q da=N;$.1u(3D,B(i,s){if(s.Q!=="P"){da=E}});$.1u(3D,$.M(B(i,s){if(s.Q==="3F"){c.6o("p",s,E)}I{if(s.Q==="P"){2f=$(s).L();if(2f[0].Q=="3F"){q 6G=$(2f).4c("p").1V();if(6G==1){$(2f).2e(s)}I{if(6G==kM){G="2f";1a+=c.3Z(s)}I{G="o";1a+=c.3Z(s);if(i==0){$(s).2z("U-5B").6z();5B=c.3Z(s)}I{$(s).1w()}}}}I{if(da===E||3D.1m==1){c.6o("2f",s,E)}I{G="kS";1a+=c.3Z(s)}}}I{if(s.Q!=="3h"){c.6o("2f",s,E)}}}},c));if(G){if(G=="kS"){$(3D[0]).2e("<2f>"+1a+"</2f>");$(3D).1w()}I{if(G=="2f"){$(2f).2e(1a)}I{if(G=="o"){q o=c.$K.o().G(5B,"</2f>"+1a+"<2f>");c.$K.o(o);c.$K.1b("2f").1u(B(){if($.2b($(c).o())==""){$(c).1w()}})}}}}}c.1W()}I{q 1l=c.2N();if(1l.Q==="3F"){c.28();q o=$.2b($(1l).o());q 1I=$.2b(c.jP());o=o.G(/<V(.*?)id="1I-1L(.*?)<\\/V>/gi,"");if(o==1I){$(1l).2e($(1l).o()+"<br>")}I{c.l6("2L");q 2L=c.$K.1b("2L");2L.6z();q kW=c.$K.o().G("<2L></2L>",\'</2f><V id="1I-1L-1">\'+c.C.2i+"</V>"+1I+"<2f>");c.$K.o(kW);2L.1w();c.$K.1b("2f").1u(B(){if($.2b($(c).o())==""){$(c).1w()}})}c.1W();c.$K.1b("V#1I-1L-1").1i("id",E)}I{q 3G=c.bx("2f");q o=$(3G).o();q kV=["2r","ol","1n","3j","8c","3T","di","dl"];$.1u(kV,B(i,s){o=o.G(2c 2J("<"+s+"(.*?)>","gi"),"");o=o.G(2c 2J("</"+s+">","gi"),"")});q 7p=c.C.bg;$.1u(7p,B(i,s){o=o.G(2c 2J("<"+s+"(.*?)>","gi"),"");o=o.G(2c 2J("</"+s+">","gi"),"<br>")});$(3G).o(o);c.by(3G);q 4f=$(3G).4f();if(4f.1V()!=0&&4f[0].Q==="bC"){4f.1w()}}}c.1j()},oi:B(1i,2s){q 1X=c.3I();$(1X).2B(1i);c.1j()},oh:B(1i,2s){q 1X=c.3I();$(1X).1i(1i,2s);c.1j()},ok:B(5A){q 1X=c.3I();$(1X).1f(5A,"");c.4H(1X,"1o");c.1j()},om:B(5A,2s){q 1X=c.3I();$(1X).1f(5A,2s);c.1j()},oq:B(2S){q 1X=c.3I();$(1X).3c(2S);c.4H(1X,"1x");c.1j()},op:B(2S){q 1X=c.3I();$(1X).2z(2S);c.1j()},oo:B(2S){c.28();c.dr(B(J){$(J).3c(2S);c.4H(J,"1x")});c.1W();c.1j()},q9:B(2S){q 1t=c.3P();if(!$(1t).3v(2S)){c.93("2z",2S)}},oA:B(5A){c.28();c.dr(B(J){$(J).1f(5A,"");c.4H(J,"1o")});c.1W();c.1j()},oB:B(5A,2s){c.93("1f",5A,2s)},oN:B(1i){c.28();q O=c.3o(),J=c.8g(),1X=c.7r();if(O.4F||O.6D===O.8h&&J){1X=$(J)}$(1X).2B(1i);c.ld();c.1W();c.1j()},oM:B(1i,2s){c.93("1i",1i,2s)},93:B(1G,1i,2s){c.21();c.28();q O=c.3o();q el=c.8g();if((O.4F||O.6D===O.8h)&&el&&!c.7v(el)){$(el)[1G](1i,2s)}I{q 1P,dj=2s;kA(1i){5u"2A-1V":1P="8s";dj=4;5f;5u"2A-oP":1P="oQ";5f;5u"6s":1P="oS";5f;5u"8t-6s":1P="oL";5f}c.X.26(1P,E,dj);q aU=c.$K.1b("2A");$.1u(aU,$.M(B(i,s){c.kD(1G,s,1i,2s)},c))}c.1W();c.1j()},kD:B(1G,s,1i,2s){q L=$(s).L(),el;q 9x=c.aB();q 9k=$(L).Y();q 8Z=9x==9k;if(8Z&&L&&L[0].Q==="ds"&&L[0].oK.1m!=0){el=L;$(s).2e($(s).o())}I{el=$("<3W>").1h($(s).1Y());$(s).2e(el)}$(el)[1G](1i,2s);F el},dr:B(1c){q O=c.3o(),J=c.8g(),1X=c.7r(),4F;if(O.4F||O.6D===O.8h&&J){1X=$(J);4F=N}$.1u(1X,$.M(B(i,J){if(!4F&&J.Q!=="ds"){q 9x=c.aB();q 9k=$(J).L().Y();q 8Z=9x==9k;if(8Z&&J.4L.Q==="ds"&&!$(J.4L).3v("4d")){J=J.4L}I{F}}1c.5q(c,J)},c))},ld:B(){q $b4=c.$K.1b("3W");$.1u($b4,$.M(B(i,V){q $V=$(V);if($V.1i("1x")===1J&&$V.1i("1o")===1J){$V.1Y().l7()}},c))},l6:B(1d){c.28();c.X.26("8s",E,4);q aU=c.$K.1b("2A");q 2W;$.1u(aU,B(i,s){q el=$("<"+1d+"/>").1h($(s).1Y());$(s).2e(el);2W=el});c.1W();c.1j()},oE:B(1d){c.28();q dv=1d.oD();q 1X=c.7r();q L=$(c.2E()).L();$.1u(1X,B(i,s){if(s.Q===dv){c.bk(s)}});if(L&&L[0].Q===dv){c.bk(L)}c.1W();c.1j()},bk:B(el){$(el).2e($(el).1Y())},9i:B(o,1j){q 1t=c.3P();q L=1t.4L;c.4i();c.21();q $o=$("<12>").1h($.dn(o));o=$o.o();o=c.dq(o);$o=$("<12>").1h($.dn(o));q dp=c.2N();if($o.1Y().1m==1){q bf=$o.1Y()[0].Q;if(bf!="P"&&bf==dp.Q||bf=="6i"){$o=$("<12>").1h(o)}}if(c.C.1N){o=o.G(/<p(.*?)>([\\w\\W]*?)<\\/p>/gi,"$2<br>")}if(!c.C.1N&&$o.1Y().1m==1&&$o.1Y()[0].4D==3&&(c.cc().1m>2||(!1t||1t.Q=="ee"&&!L||L.Q=="kZ"))){o="<p>"+o+"</p>"}o=c.ad(o);if($o.1Y().1m>1&&dp||$o.1Y().is("p, :aS, 2r, ol, li, 12, 1n, 1g, 2f, 2q, dm, 2l, aS, 49, jj, jk")){if(c.1C("3t")){if(!c.8L()){c.X.1I.5i().do(o)}I{c.aL(o)}}I{c.X.26("5c",E,o)}}I{c.9O(o,E)}if(c.5R){c.42.2V($.M(B(){if(!c.C.1N){c.7G(c.$K.1Y().2W())}I{c.cb()}},c),1)}c.7H();c.8u();if(1j!==E){c.1j()}},9O:B(o,1j){o=c.ad(o);q 1q=c.29();if(1q.48&&1q.4V){q O=1q.48(0);O.aC();q el=X.4C("12");el.3Q=o;q 4k=X.cD(),J,5v;3w((J=el.8G)){5v=4k.7g(J)}O.3B(4k);if(5v){O=O.ag();O.ae(5v);O.5h(N);1q.4X();1q.5g(O)}}if(1j!==E){c.1j()}},oC:B(o){o=c.ad(o);q J=$(o);q 6c=X.4C("V");6c.3Q="\\7D";q O=c.3o();O.3B(6c);O.3B(J[0]);O.5h(E);q 1q=c.29();1q.4X();1q.5g(O);c.1j()},oF:B(o){q $o=$($.dn(o));if($o.1m){o=$o.Y()}c.4i();if(c.1C("3t")){if(!c.8L()){c.X.1I.5i().do(o)}I{c.aL(o)}}I{c.X.26("5c",E,o)}c.1j()},3B:B(J){J=J[0]||J;if(J.Q=="jo"){q 76="3W";q aD=J.jb;q 5y=2c 2J("<"+J.Q,"i");q 5K=aD.G(5y,"<"+76);5y=2c 2J("</"+J.Q,"i");5K=5K.G(5y,"</"+76);J=$(5K)[0]}q 1q=c.29();if(1q.48&&1q.4V){O=1q.48(0);O.aC();O.3B(J);O.oG(J);O.ae(J);1q.4X();1q.5g(O)}F J},js:B(e,J){q O;q x=e.jl,y=e.jm;if(c.X.ju){q 3X=c.X.ju(x,y);O=c.3o();O.86(3X.oJ,3X.2Y);O.5h(N);O.3B(J)}I{if(c.X.jv){O=c.X.jv(x,y);O.3B(J)}I{if(1E X.2v.jw!="1J"){O=c.X.2v.jw();O.jx(x,y);q bq=O.oI();bq.jx(x,y);O.oH("og",bq);O.3s()}}}},6U:B(2j,L){if(1E(L)!="1J"){2j=L}if(c.ck()){if(c.C.1N){q 1Y=$("<12>").1h($.2b(c.$K.o())).1Y();q 2W=1Y.2W()[0];if(2W.Q=="jo"&&2W.3Q==""){2W=1Y.4M()[0]}if(c.3Z(2W)!=c.3Z(2j)){F E}}I{if(c.$K.1Y().2W()[0]!==2j){F E}}c.bt(2j)}},bt:B(2j){c.21();if(c.C.1N===E){q J=$(c.C.58);$(2j).2I(J);c.4g(J)}I{q J=$(\'<V id="1I-1L-1">\'+c.C.2i+"</V>",c.X)[0];$(2j).2I(J);$(J).2I(c.C.2i);c.1W();c.$K.1b("V#1I-1L-1").2B("id")}},al:B(jp){c.28();q br="<br>";if(jp==N){br="<br><br>"}if(c.1C("31")){q V=$("<V>").o(c.C.2i);c.$K.1b("#1I-1L-1").3S(br).3S(V).3S(c.C.2i);c.jF(V[0]);V.1w();c.8b()}I{q L=c.2E();if(L&&L.Q==="A"){q 2Y=c.ch(L);q Y=$.2b($(L).Y()).G(/\\n\\r\\n/g,"");q 4P=Y.1m;if(2Y==4P){c.8b();q J=$(\'<V id="1I-1L-1">\'+c.C.2i+"</V>",c.X)[0];$(L).2I(J);$(J).3S(br+(c.1C("4s")?c.C.2i:""));c.1W();F N}}c.$K.1b("#1I-1L-1").3S(br+(c.1C("4s")?c.C.2i:""));c.1W()}},of:B(){c.al(N)},jr:B(2j){q J=$("<br>"+c.C.2i);$(2j).2e(J);c.4g(J)},j8:B(o){o=c.1c("nR",E,o);if(c.1C("3t")){q 2L=$.2b(o);if(2L.4G(/^<a(.*?)>(.*?)<\\/a>$/i)==0){o=o.G(/^<a(.*?)>(.*?)<\\/a>$/i,"$2")}}if(c.C.j7){q 2L=c.X.4C("12");o=o.G(/<br>|<\\/H[1-6]>|<\\/p>|<\\/12>/gi,"\\n");2L.3Q=o;o=2L.9Q||2L.dh;o=$.2b(o);o=o.G("\\n","<br>");o=c.9z(o);c.8W(o);F E}q 95=E;if(c.74("6h")){95=N;q 7p=c.C.bg;7p.3d("3j");7p.3d("1n");$.1u(7p,B(i,s){o=o.G(2c 2J("<"+s+"(.*?)>","gi"),"");o=o.G(2c 2J("</"+s+">","gi"),"<br>")})}if(c.74("6i")){o=c.j6(o);c.8W(o);F N}o=o.G(/<1B(.*?)v:nQ=(.*?)>/gi,"");o=o.G(/<p(.*?)1x="nP"([\\w\\W]*?)<\\/p>/gi,"<2r><li$2</li>");o=o.G(/<p(.*?)1x="nS"([\\w\\W]*?)<\\/p>/gi,"<li$2</li>");o=o.G(/<p(.*?)1x="nT"([\\w\\W]*?)<\\/p>/gi,"<li$2</li></2r>");o=o.G(/<p(.*?)1x="nV"([\\w\\W]*?)<\\/p>/gi,"<2r><li$2</li></2r>");o=o.G(/·/g,"");o=o.G(/<!--[\\s\\S]*?-->|<\\?(?:4I)?[\\s\\S]*?\\?>/gi,"");if(c.C.iO===N){o=o.G(/(&3u;){2,}/gi,"&3u;");o=o.G(/&3u;/gi," ")}o=o.G(/<b\\nU="iN-1v-1L(.*?)">([\\w\\W]*?)<\\/b>/gi,"$2");o=o.G(/<b(.*?)id="nO-iN-nN(.*?)">([\\w\\W]*?)<\\/b>/gi,"$3");o=o.G(/<V[^>]*(2A-1o: 3q; 2A-7o: 3m|2A-7o: 3m; 2A-1o: 3q)[^>]*>/gi,\'<V 1o="2A-7o: 3m;"><V 1o="2A-1o: 3q;">\');o=o.G(/<V[^>]*2A-1o: 3q[^>]*>/gi,\'<V 1o="2A-1o: 3q;">\');o=o.G(/<V[^>]*2A-7o: 3m[^>]*>/gi,\'<V 1o="2A-7o: 3m;">\');o=o.G(/<V[^>]*Y-bZ: 4K[^>]*>/gi,\'<V 1o="Y-bZ: 4K;">\');o=o.G(/<1g>\\nF*<\\/1g>/gi,"[1g]");o=o.G(/<1g>&3u;<\\/1g>/gi,"[1g]");o=o.G(/<1g><br><\\/1g>/gi,"[1g]");o=o.G(/<1g(.*?)9W="(.*?)"(.*?)>([\\w\\W]*?)<\\/1g>/gi,\'[1g 9W="$2"]$4[/1g]\');o=o.G(/<1g(.*?)9X="(.*?)"(.*?)>([\\w\\W]*?)<\\/1g>/gi,\'[1g 9X="$2"]$4[/1g]\');o=o.G(/<a(.*?)1S="(.*?)"(.*?)>([\\w\\W]*?)<\\/a>/gi,\'[a 1S="$2"]$4[/a]\');o=o.G(/<1Q(.*?)>([\\w\\W]*?)<\\/1Q>/gi,"[1Q$1]$2[/1Q]");o=o.G(/<3y(.*?)>([\\w\\W]*?)<\\/3y>/gi,"[3y$1]$2[/3y]");o=o.G(/<5r(.*?)>([\\w\\W]*?)<\\/5r>/gi,"[5r$1]$2[/5r]");o=o.G(/<4S(.*?)>([\\w\\W]*?)<\\/4S>/gi,"[4S$1]$2[/4S]");o=o.G(/<3M(.*?)>([\\w\\W]*?)<\\/3M>/gi,"[3M$1]$2[/3M]");o=o.G(/<2F(.*?)>/gi,"[2F$1]");o=o.G(/<1B(.*?)>/gi,"[1B$1]");o=o.G(/ 1x="(.*?)"/gi,"");o=o.G(/<(\\w+)([\\w\\W]*?)>/gi,"<$1>");if(c.C.1N){o=o.G(/<3V><\\/3V>/gi,"");o=o.G(/<u><\\/u>/gi,"");if(c.C.bV){o=o.G(/<2A(.*?)>([\\w\\W]*?)<\\/2A>/gi,"$2")}o=o.G(/<[^\\/>][^>]*>(\\s*|\\t*|\\n*|&3u;|<br>)<\\/[^>]+>/gi,"<br>")}I{o=o.G(/<[^\\/>][^>]*>(\\s*|\\t*|\\n*|&3u;|<br>)<\\/[^>]+>/gi,"")}o=o.G(/<12>\\s*?\\t*?\\n*?(<2r>|<ol>|<p>)/gi,"$1");o=o.G(/\\[1g 9W="(.*?)"\\]([\\w\\W]*?)\\[\\/1g\\]/gi,\'<1g 9W="$1">$2</1g>\');o=o.G(/\\[1g 9X="(.*?)"\\]([\\w\\W]*?)\\[\\/1g\\]/gi,\'<1g 9X="$1">$2</1g>\');o=o.G(/\\[1g\\]/gi,"<1g>&3u;</1g>");o=o.G(/\\[a 1S="(.*?)"\\]([\\w\\W]*?)\\[\\/a\\]/gi,\'<a 1S="$1">$2</a>\');o=o.G(/\\[1Q(.*?)\\]([\\w\\W]*?)\\[\\/1Q\\]/gi,"<1Q$1>$2</1Q>");o=o.G(/\\[3y(.*?)\\]([\\w\\W]*?)\\[\\/3y\\]/gi,"<3y$1>$2</3y>");o=o.G(/\\[5r(.*?)\\]([\\w\\W]*?)\\[\\/5r\\]/gi,"<5r$1>$2</5r>");o=o.G(/\\[4S(.*?)\\]([\\w\\W]*?)\\[\\/4S\\]/gi,"<4S$1>$2</4S>");o=o.G(/\\[3M(.*?)\\]([\\w\\W]*?)\\[\\/3M\\]/gi,"<3M$1>$2</3M>");o=o.G(/\\[2F(.*?)\\]/gi,"<2F$1>");o=o.G(/\\[1B(.*?)\\]/gi,"<1B$1>");if(c.C.9a){o=o.G(/<12(.*?)>([\\w\\W]*?)<\\/12>/gi,"<p>$2</p>");o=o.G(/<\\/12><p>/gi,"<p>");o=o.G(/<\\/p><\\/12>/gi,"</p>");o=o.G(/<p><\\/p>/gi,"<br />")}I{o=o.G(/<12><\\/12>/gi,"<br />")}o=c.8d(o);if(c.74("3h")){o=o.G(/<p>([\\w\\W]*?)<\\/p>/gi,"$1<br>")}I{if(95===E){o=c.9z(o)}}o=o.G(/<V(.*?)>([\\w\\W]*?)<\\/V>/gi,"$2");o=o.G(/<1B>/gi,"");o=o.G(/<[^\\/>][^>][^1B|2F|1v|1g][^<]*>(\\s*|\\t*|\\n*| |<br>)<\\/[^>]+>/gi,"");o=o.G(/\\n{3,}/gi,"\\n");o=o.G(/<p><p>/gi,"<p>");o=o.G(/<\\/p><\\/p>/gi,"</p>");o=o.G(/<li>(\\s*|\\t*|\\n*)<p>/gi,"<li>");o=o.G(/<\\/p>(\\s*|\\t*|\\n*)<\\/li>/gi,"</li>");if(c.C.1N===N){o=o.G(/<p(.*?)>([\\w\\W]*?)<\\/p>/gi,"$2<br>")}o=o.G(/<[^\\/>][^>][^1B|2F|1v|1g][^<]*>(\\s*|\\t*|\\n*| |<br>)<\\/[^>]+>/gi,"");o=o.G(/<1B 3p="4s-nJ-1K\\:\\/\\/(.*?)"(.*?)>/gi,"");o=o.G(/<1g(.*?)>(\\s*|\\t*|\\n*)<p>([\\w\\W]*?)<\\/p>(\\s*|\\t*|\\n*)<\\/1g>/gi,"<1g$1>$3</1g>");if(c.C.9a){o=o.G(/<12(.*?)>([\\w\\W]*?)<\\/12>/gi,"$2");o=o.G(/<12(.*?)>([\\w\\W]*?)<\\/12>/gi,"$2")}c.c6=E;if(c.1C("31")){if(c.C.9p){q 2x=o.1U(/<1B 3p="1a:T(.*?)"(.*?)>/gi);if(2x!==2G){c.c6=2x;3z(k in 2x){q 1B=2x[k].G("<1B",\'<1B 1a-31-9v-T="\'+k+\'" \');o=o.G(2x[k],1B)}}}3w(/<br>$/gi.4e(o)){o=o.G(/<br>$/gi,"")}}o=o.G(/<p>•([\\w\\W]*?)<\\/p>/gi,"<li>$1</li>");if(c.1C("3t")){3w(/<2A>([\\w\\W]*?)<\\/2A>/gi.4e(o)){o=o.G(/<2A>([\\w\\W]*?)<\\/2A>/gi,"$1")}}if(95===E){o=o.G(/<1g(.*?)>([\\w\\W]*?)<p(.*?)>([\\w\\W]*?)<\\/1g>/gi,"<1g$1>$2$4</1g>");o=o.G(/<1g(.*?)>([\\w\\W]*?)<\\/p>([\\w\\W]*?)<\\/1g>/gi,"<1g$1>$2$3</1g>");o=o.G(/<1g(.*?)>([\\w\\W]*?)<p(.*?)>([\\w\\W]*?)<\\/1g>/gi,"<1g$1>$2$4</1g>");o=o.G(/<1g(.*?)>([\\w\\W]*?)<\\/p>([\\w\\W]*?)<\\/1g>/gi,"<1g$1>$2$3</1g>")}o=o.G(/\\n/g," ");o=o.G(/<p>\\n?<li>/gi,"<li>");c.8W(o)},j6:B(s){s=s.G(/<br>|<\\/H[1-6]>|<\\/p>|<\\/12>/gi,"\\n");q 2L=c.X.4C("12");2L.3Q=s;F c.ce(2L.9Q||2L.dh)},8W:B(o){o=c.1c("nM",E,o);if(c.5R){c.$K.o(o);c.bA();c.cb();c.1j()}I{c.9i(o)}c.5R=E;2V($.M(B(){c.8j=E;if(c.1C("31")){c.$K.1b("p:6z").1w()}if(c.c6!==E){c.j0()}},c),3O);if(c.C.4Y&&c.c7!==N){$(c.X.2v).3e(c.9t)}I{c.$K.3e(c.9t)}},c9:B(4v){if(c.C.3H!==E&&1E c.C.3H==="3M"){$.1u(c.C.3H,$.M(B(k,v){if(v!=2G&&v.43().44("#")===0){v=$(v).1p()}4v[k]=v},c))}F 4v},j0:B(){q jy=c.$K.1b("1B[1a-31-9v-T]");$.1u(jy,$.M(B(i,s){q $s=$(s);q 2Q=s.3p.4o(",");q 4v={c3:2Q[0].4o(";")[0].4o(":")[1],1a:2Q[1]};4v=c.c9(4v);$.7R(c.C.bT,4v,$.M(B(1a){q 1M=(1E 1a==="8I"?$.8J(1a):1a);$s.1i("3p",1M.6e);$s.2B("1a-31-9v-T");c.1j();c.1c("3Y",$s,1M)},c))},c))},jz:B(e){q 9l=e.1O.9l;q 2Q=9l.4o(",");q 4v={c3:2Q[0].4o(";")[0].4o(":")[1],1a:2Q[1]};if(c.C.9p){4v=c.c9(4v);$.7R(c.C.bT,4v,$.M(B(1a){q 1M=(1E 1a==="8I"?$.8J(1a):1a);q o=\'<1B 3p="\'+1M.6e+\'" id="kc-T-1L" />\';c.26("5c",o,E);q T=$(c.$K.1b("1B#kc-T-1L"));if(T.1m){T.2B("id")}I{T=E}c.1j();if(T){c.1c("3Y",T,1M)}},c))}I{c.9i(\'<1B 3p="\'+9l+\'" />\')}},21:B(28){if(28!==E){c.28()}c.C.3E.3d(c.$K.o());if(28!==E){c.8b("3E")}},k7:B(){if(c.C.3E.1m===0){c.4i();F}c.28();c.C.8A.3d(c.$K.o());c.1W(E,N);c.$K.o(c.C.3E.k2());c.1W();2V($.M(c.7H,c),3O)},k6:B(){if(c.C.8A.1m===0){c.4i();F E}c.28();c.C.3E.3d(c.$K.o());c.1W(E,N);c.$K.o(c.C.8A.k2());c.1W(N);2V($.M(c.7H,c),4)},7H:B(){c.4T();if(c.C.5m){c.5m()}},5m:B(){c.$K.1b("a").on("23",$.M(c.k4,c));c.$K.on("23.U",$.M(B(e){c.7E(e)},c));$(X).on("23.U",$.M(B(e){c.7E(e)},c))},4T:B(){if(c.C.4T===E){F E}c.$K.1b("1B").1u($.M(B(i,1z){if(c.1C("3t")){$(1z).1i("oa","on")}q L=$(1z).L();if(!L.3v("bS")&&!L.3v("bD")){c.kU(1z)}},c));c.$K.1b(".bD, .bS").on("23",$.M(c.o9,c))},k4:B(e){q $1r=$(e.1O);q L=$(e.1O).L();if(L.3v("bS")||L.3v("bD")){F}if($1r.1V()==0||$1r[0].Q!=="A"){F}q 3X=$1r.2Y();if(c.C.1Q){q bE=c.$2Z.2Y();3X.1T=bE.1T+(3X.1T-$(c.X).3e());3X.1s+=bE.1s}q 4E=$(\'<V 1x="U-1r-4E"></V>\');q 1S=$1r.1i("1S");if(1S===1J){1S=""}if(1S.1m>24){1S=1S.aQ(0,24)+"..."}q k5=$(\'<a 1S="\'+$1r.1i("1S")+\'" 1O="6y">\'+1S+"</a>").on("23",$.M(B(e){c.7E(E)},c));q kd=$(\'<a 1S="#">\'+c.C.1F.8p+"</a>").on("23",$.M(B(e){e.2u();c.9N();c.7E(E)},c));q ke=$(\'<a 1S="#">\'+c.C.1F.6F+"</a>").on("23",$.M(B(e){e.2u();c.26("6F");c.7E(E)},c));4E.1h(k5);4E.1h(" | ");4E.1h(kd);4E.1h(" | ");4E.1h(ke);4E.1f({1T:(3X.1T+20)+"px",1s:3X.1s+"px"});$(".U-1r-4E").1w();$("2v").1h(4E)},7E:B(e){if(e!==E&&e.1O.Q=="A"){F E}$(".U-1r-4E").1w()},29:B(){if(!c.C.41){F c.X.29()}I{if(!c.C.1Q){F 41.29()}I{F 41.29(c.$2Z[0])}}},3o:B(){if(!c.C.41){if(c.X.29){q 1q=c.29();if(1q.48&&1q.4V){F 1q.48(0)}}F c.X.5i()}I{if(!c.C.1Q){F 41.5i()}I{F 41.5i(c.bG())}}},by:B(J){c.jH(J)},4g:B(J){c.8e(J[0]||J,0,2G,0)},7G:B(J){c.8e(J[0]||J,1,2G,1)},8e:B(4Q,bB,87,b5){if(87==2G){87=4Q}if(b5==2G){b5=bB}q 1q=c.29();if(!1q){F}if(4Q.Q=="P"&&4Q.3Q==""){4Q.3Q=c.C.2i}if(4Q.Q=="bC"&&c.C.1N===E){q 5U=$(c.C.58)[0];$(4Q).2e(5U);4Q=5U;87=4Q}q O=c.3o();O.86(4Q,bB);O.85(87,b5);eG{1q.4X()}fe(e){}1q.5g(O)},bx:B(1d){1d=1d.3N();q 1l=c.2N();if(1l){q 3G=c.kf(1l,1d);c.1j();F 3G}q 1q=c.29();q O=1q.48(0);q 3G=X.4C(1d);3G.7g(O.oe());O.3B(3G);c.by(3G);F 3G},od:B(){q O=c.3o();O.aM(c.$K[0]);q 1q=c.29();1q.4X();1q.5g(O)},bA:B(){c.29().4X()},ch:B(2j){q bH=0;q O=c.3o();q ba=O.ag();ba.aM(2j);ba.85(O.8h,O.kj);bH=$.2b(ba.43()).1m;F bH},hR:B(){F 2c bI(c.29().48(0))},jH:B(el,2y,3l){if(1E 3l==="1J"){3l=2y}el=el[0]||el;q O=c.3o();O.aM(el);q 4B=c.bN(el);q aq=E;q 7J=0,7K;if(4B.1m==1&&2y){O.86(4B[0],2y);O.85(4B[0],3l)}I{3z(q i=0,88;88=4B[i++];){7K=7J+88.1m;if(!aq&&2y>=7J&&(2y<7K||(2y==7K&&i<4B.1m))){O.86(88,2y-7J);aq=N}if(aq&&3l<=7K){O.85(88,3l-7J);5f}7J=7K}}q 1q=c.29();1q.4X();1q.5g(O)},jF:B(J){c.$K.2h();J=J[0]||J;q O=c.X.5i();q 2y=1;q 3l=-1;O.86(J,2y);O.85(J,3l+2);q 1I=c.42.29();q bR=c.X.5i();q av=c.X.82("\\7D");$(J).2I(av);bR.ae(av);1I.4X();1I.5g(bR);$(av).1w()},bN:B(J){q 4B=[];if(J.4D==3){4B.3d(J)}I{q 4c=J.8F;3z(q i=0,4P=4c.1m;i<4P;++i){4B.3d.bO(4B,c.bN(4c[i]))}}F 4B},3P:B(){q el=E;q 1q=c.29();if(1q&&1q.4V>0){el=1q.48(0).6D}F c.4j(el)},2E:B(1z){1z=1z||c.3P();if(1z){F c.4j($(1z).L()[0])}I{F E}},2N:B(J){if(1E J==="1J"){J=c.3P()}3w(J){if(c.7v(J)){if($(J).3v("4d")){F E}F J}J=J.4L}F E},3I:B(1X){q 89=[];if(1E 1X=="1J"){q O=c.3o();if(O&&O.4F===N){F[c.2N()]}q 1X=c.7r(O)}$.1u(1X,$.M(B(i,J){if(c.C.1Q===E&&$(J).8B("12.4d").1V()==0){F E}if(c.7v(J)){89.3d(J)}},c));if(89.1m===0){89=[c.2N()]}F 89},o5:B(J){if(J.4D!=1){F E}F!c.ax.4e(J.jY)},7v:B(J){F J.4D==1&&c.ax.4e(J.jY)},bM:B(1d){F c.ax.4e(1d)},7r:B(O,1d){if(1E O=="1J"||O==E){q O=c.3o()}if(O&&O.4F===N){if(1E 1d==="1J"&&c.bM(1d)){q 1l=c.2N();if(1l.Q==1d){F[1l]}I{F[]}}I{F[c.3P()]}}q 1X=[],4x=[];q 1q=c.X.29();if(!1q.o4){1X=c.cc(1q.48(0))}$.1u(1X,$.M(B(i,J){if(c.C.1Q===E&&$(J).8B("12.4d").1V()==0){F E}if(1E 1d==="1J"){if($.2b(J.9Q)!=""){4x.3d(J)}}I{if(J.Q==1d){4x.3d(J)}}},c));if(4x.1m==0){if(1E 1d==="1J"&&c.bM(1d)){q 1l=c.2N();if(1l.Q==1d){F 4x.3d(1l)}I{F[]}}I{4x.3d(c.3P())}}q 2W=4x[4x.1m-1];if(c.7v(2W)){4x=4x.jR(0,-1)}F 4x},8g:B(J){if(!J){J=c.3P()}3w(J){if(J.4D==1){if($(J).3v("4d")){F E}F J}J=J.4L}F E},cc:B(O){O=O||c.3o();q J=O.6D;q eK=O.8h;if(J==eK){F[J]}q 9m=[];3w(J&&J!=eK){9m.3d(J=c.jQ(J))}J=O.6D;3w(J&&J!=O.o3){9m.jS(J);J=J.4L}F 9m},jQ:B(J){if(J.oT()){F J.8G}I{3w(J&&!J.jO){J=J.4L}if(!J){F 2G}F J.jO}},aB:B(){F c.29().43()},jP:B(){q o="";q 1q=c.29();if(1q.4V){q eF=c.X.4C("12");q 4P=1q.4V;3z(q i=0;i<4P;++i){eF.7g(1q.48(i).oU())}o=eF.3Q}F c.eE(o)},28:B(){if(!c.7h()){c.4i()}if(!c.C.41){c.jN(c.3o())}I{c.5S=41.pL()}},jN:B(O,1w){if(!O){F}q 5O=$(\'<V id="1I-1L-1" 1x="U-1I-1L">\'+c.C.2i+"</V>",c.X)[0];q 8f=$(\'<V id="1I-1L-2" 1x="U-1I-1L">\'+c.C.2i+"</V>",c.X)[0];if(O.4F===N){c.ai(O,5O,N)}I{c.ai(O,5O,N);c.ai(O,8f,E)}c.5S=c.$K.o();c.1W(E,E)},ai:B(O,J,1G){q ar=O.ag();eG{ar.5h(1G);ar.3B(J);ar.pJ()}fe(e){q o=c.C.58;if(c.C.1N){o="<br>"}c.$K.6d(o);c.2h()}},1W:B(G,1w){if(!c.C.41){if(G===N&&c.5S){c.$K.o(c.5S)}q 5O=c.$K.1b("V#1I-1L-1");q 8f=c.$K.1b("V#1I-1L-2");if(c.1C("31")){c.$K.2h()}I{if(!c.7h()){c.4i()}}if(5O.1m!=0&&8f.1m!=0){c.8e(5O[0],0,8f[0],0)}I{if(5O.1m!=0){c.8e(5O[0],0,2G,0)}}if(1w!==E){c.8b();c.5S=E}}I{41.pN(c.5S)}},8b:B(1G){if(!c.C.41){$.1u(c.$K.1b("V.U-1I-1L"),B(){q o=$.2b($(c).o().G(/[^\\pP-\\pO]/g,""));if(o==""){$(c).1w()}I{$(c).2B("1x").2B("id")}})}I{41.pI(c.5S)}},ko:B(){c.28();c.62(c.C.1F.1n,c.C.hf,pH,$.M(B(){$("#hv").23($.M(c.km,c));2V(B(){$("#eX").2h()},5k)},c))},km:B(){c.21(E);q aF=$("#eX").1p(),aJ=$("#hx").1p(),$e2=$("<12></12>"),ez=4u.kg(4u.kh()*k0),$1n=$(\'<1n id="1n\'+ez+\'"><8c></8c></1n>\'),i,$aZ,z,$b1;3z(i=0;i<aF;i++){$aZ=$("<3j></3j>");3z(z=0;z<aJ;z++){$b1=$("<1g>"+c.C.2i+"</1g>");if(i===0&&z===0){$b1.1h(\'<V id="1I-1L-1">\'+c.C.2i+"</V>")}$($aZ).1h($b1)}$1n.1h($aZ)}$e2.1h($1n);q o=$e2.o();if(c.C.1N===E&&c.1C("31")){o+="<p>"+c.C.2i+"</p>"}c.3g();c.1W();q 1t=c.2N()||c.3P();if(1t&&1t.Q!="ee"){if(1t.Q=="3h"){q 1t=$(1t).2g("2r, ol")}$(1t).2I(o)}I{c.9O(o,E)}c.1W();q 1n=c.$K.1b("#1n"+ez);c.8r();1n.1b("V#1I-1L-1, 3W#1I-1L-1").1w();1n.2B("id");c.1j()},ka:B(){q $1n=$(c.2E()).2g("1n");if(!c.4j($1n)){F E}if($1n.1V()==0){F E}c.21();$1n.1w();c.1j()},k8:B(){q L=c.2E();q $1n=$(L).2g("1n");if(!c.4j($1n)){F E}if($1n.1V()==0){F E}c.21();q $4z=$(L).2g("3j");q $eg=$4z.4M().1m?$4z.4M():$4z.4f();if($eg.1m){q $ek=$eg.4c("1g").j5();if($ek.1m){$ek.6d(\'<V id="1I-1L-1">\'+c.C.2i+"</V>")}}$4z.1w();c.1W();$1n.1b("V#1I-1L-1").1w();c.1j()},iY:B(){q L=c.2E();q $1n=$(L).2g("1n");if(!c.4j($1n)){F E}if($1n.1V()==0){F E}c.21();q $5Z=$(L).2g("1g");if(!($5Z.is("1g"))){$5Z=$5Z.2g("1g")}q 2K=$5Z.2R(0).pC;$1n.1b("3j").1u($.M(B(i,1z){q iX=2K-1<0?2K+1:2K-1;if(i===0){$(1z).1b("1g").eq(iX).6d(\'<V id="1I-1L-1">\'+c.C.2i+"</V>")}$(1z).1b("1g").eq(2K).1w()},c));c.1W();$1n.1b("V#1I-1L-1").1w();c.1j()},j2:B(){q $1n=$(c.2E()).2g("1n");if(!c.4j($1n)){F E}if($1n.1V()==0){F E}c.21();if($1n.1b("3T").1V()!==0){c.ey()}I{q 3j=$1n.1b("3j").j5().6q();3j.1b("1g").o(c.C.2i);$3T=$("<3T></3T>");$3T.1h(3j);$1n.6d($3T);c.1j()}},ey:B(){q $1n=$(c.2E()).2g("1n");if(!c.4j($1n)){F E}q $3T=$1n.1b("3T");if($3T.1V()==0){F E}c.21();$3T.1w();c.1j()},j3:B(){c.ew("3S")},iV:B(){c.ew("2I")},iM:B(){c.e3("3S")},iL:B(){c.e3("2I")},ew:B(1G){q $1n=$(c.2E()).2g("1n");if(!c.4j($1n)){F E}if($1n.1V()==0){F E}c.21();q $4z=$(c.2E()).2g("3j");q 9V=$4z.6q();9V.1b("1g").o(c.C.2i);if(1G==="2I"){$4z.2I(9V)}I{$4z.3S(9V)}c.1j()},e3:B(1G){q L=c.2E();q $1n=$(L).2g("1n");if(!c.4j($1n)){F E}if($1n.1V()==0){F E}c.21();q 2K=0;q 1t=c.3P();q $4z=$(1t).2g("3j");q $5Z=$(1t).2g("1g");$4z.1b("1g").1u($.M(B(i,1z){if($(1z)[0]===$5Z[0]){2K=i}},c));$1n.1b("3j").1u($.M(B(i,1z){q $1t=$(1z).1b("1g").eq(2K);q 1g=$1t.6q();1g.o(c.C.2i);1G==="2I"?$1t.2I(1g):$1t.3S(1g)},c));c.1j()},iI:B(){c.28();c.62(c.C.1F.3y,c.C.hB,pR,$.M(B(){$("#hk").23($.M(c.iJ,c));2V(B(){$("#eD").2h()},5k)},c))},iJ:B(){q 1a=$("#eD").1p();1a=c.8d(1a);q 5j=\'<1Q 2p="cT" 22="hG" 3p="\',7B=\'" cS="0" hE></1Q>\';if(1a.1U(8N)){1a=1a.G(8N,5j+"//aA.cO.7w/4S/$1"+7B)}I{if(1a.1U(8Q)){1a=1a.G(8Q,5j+"//hp.cI.7w/3y/$2"+7B)}}c.1W();q 1t=c.2N()||c.3P();if(1t){$(1t).2I(1a)}I{c.9O(1a,E)}c.1j();c.3g()},9N:B(){c.28();q 1c=$.M(B(){if(c.C.ec!==E){c.9H={};q 4J=c;$.l9(c.C.ec,B(1a){q $3s=$("#U-gU-gL");$3s.o("");$.1u(1a,B(1k,1p){4J.9H[1k]=1p;$3s.1h($("<4b>").1p(1k).o(1p.2m))});$3s.on("68",B(){q 1k=$(c).1p();q 2m="",1K="";if(1k!=0){2m=4J.9H[1k].2m;1K=4J.9H[1k].1K}$("#7Z").1p(1K);$("#aj").1p(2m)});$3s.2M()})}c.6K=E;q 1q=c.29();q 1K="",Y="",1O="";q 1z=c.2E();q 5U=$(1z).L().2R(0);if(5U&&5U.Q==="A"){1z=5U}if(1z&&1z.Q==="A"){1K=1z.1S;Y=$(1z).Y();1O=1z.1O;c.6K=1z}I{Y=1q.43()}$("#aj").1p(Y);q iT=iS.iR.1S.G(/\\/$/i,"");1K=1K.G(iT,"");1K=1K.G(/^\\/#/,"#");1K=1K.G("eH:","");if(c.C.8a===E){q re=2c 2J("^(aI|am|5n)://"+iS.iR.q2,"i");1K=1K.G(re,"")}$("#7Z").1p(1K);if(1O==="6y"){$("#6S").9D("9K",N)}c.eJ=E;$("#he").on("23",$.M(c.iQ,c));2V(B(){$("#7Z").2h()},5k)},c);c.62(c.C.1F.1r,c.C.gX,q1,1c)},iQ:B(){if(c.eJ){F}c.eJ=N;q 1O="",eZ="";q 1r=$("#7Z").1p();q Y=$("#aj").1p();if(1r.4G("@")!=-1&&/(aI|am|5n):\\/\\//i.4e(1r)===E){1r="eH:"+1r}I{if(1r.4G("#")!=0){if($("#6S").9D("9K")){1O=\' 1O="6y"\';eZ="6y"}q eO="((q4--)?[a-eL-9]+(-[a-eL-9]+)*.)+[a-z]{2,}";q re=2c 2J("^(aI|am|5n)://"+eO,"i");q jt=2c 2J("^"+eO,"i");if(1r.4G(re)==-1&&1r.4G(jt)==0&&c.C.8a){1r=c.C.8a+1r}}}Y=Y.G(/<|>/g,"");q eY="&3u;";if(c.1C("31")){eY="&3u;"}c.j9(\'<a 1S="\'+1r+\'"\'+1O+">"+Y+"</a>"+eY,$.2b(Y),1r,eZ)},j9:B(a,Y,1r,1O){c.1W();if(Y!==""){if(c.6K){c.21();$(c.6K).Y(Y).1i("1S",1r);if(1O!==""){$(c.6K).1i("1O",1O)}I{$(c.6K).2B("1O")}}I{q $a=$(a).2z("U-dZ-1r");c.2o("5c",c.3Z($a),E);q 1r=c.$K.1b("a.U-dZ-1r");1r.2B("1o").3c("U-dZ-1r").1u(B(){if(c.2S==""){$(c).2B("1x")}})}c.1j()}2V($.M(B(){if(c.C.5m){c.5m()}},c),5);c.3g()},jg:B(){c.28();q 1c=$.M(B(){q 1q=c.29();q Y="";if(c.cw()){Y=1q.Y}I{Y=1q.43()}$("#e6").1p(Y);if(!c.5a()&&!c.d6()){c.bP("#5p",{1K:c.C.84,3H:c.C.3H,4h:$.M(c.ex,c),3U:$.M(B(71,1M){c.1c("kq",1M)},c),6k:c.C.e5})}c.et("5p",{4l:N,1K:c.C.84,4h:$.M(c.ex,c),3U:$.M(B(71,1M){c.1c("kq",1M)},c)})},c);c.62(c.C.1F.25,c.C.hW,cT,1c)},ex:B(1M){c.1W();if(1M!==E){q Y=$("#e6").1p();if(Y===""){Y=1M.ed}q 1r=\'<a 1S="\'+1M.6e+\'" id="6e-1L">\'+Y+"</a>";if(c.1C("4s")&&!!c.42.d5){1r=1r+"&3u;"}c.26("5c",1r,E);q 83=$(c.$K.1b("a#6e-1L"));if(83.1V()!=0){83.2B("id")}I{83=E}c.1j();c.1c("84",83,1M)}c.3g()},l4:B(){c.28();q 1c=$.M(B(){if(c.C.6L){$.l9(c.C.6L,$.M(B(1a){q 6R={},6G=0;$.1u(1a,$.M(B(1k,1p){if(1E 1p.aV!=="1J"){6G++;6R[1p.aV]=6G}},c));q 9j=E;$.1u(1a,$.M(B(1k,1p){q eh="";if(1E 1p.1e!=="1J"){eh=1p.1e}q b0=0;if(!$.l8(6R)&&1E 1p.aV!=="1J"){b0=6R[1p.aV];if(9j===E){9j=".6T"+b0}}q 1B=$(\'<1B 3p="\'+1p.q6+\'" 1x="6T 6T\'+b0+\'" 4m="\'+1p.T+\'" 1e="\'+eh+\'" />\');$("#eA").1h(1B);$(1B).23($.M(c.fI,c))},c));if(!$.l8(6R)){$(".6T").2U();$(9j).2M();q l0=B(e){$(".6T").2U();$(".6T"+$(e.1O).1p()).2M()};q 3s=$(\'<3s id="q0">\');$.1u(6R,B(k,v){3s.1h($(\'<4b 2s="\'+v+\'">\'+k+"</4b>"))});$("#eA").3S(3s);3s.68(l0)}},c))}I{$("#U-52-6v-2").1w()}if(c.C.3Y||c.C.5Q){if(!c.5a()&&!c.d6()&&c.C.5Q===E){if($("#5p").1m){c.bP("#5p",{1K:c.C.3Y,3H:c.C.3H,4h:$.M(c.eN,c),3U:$.M(B(71,1M){c.1c("dV",1M)},c),6k:c.C.an})}}if(c.C.5Q===E){c.et("5p",{4l:N,1K:c.C.3Y,4h:$.M(c.eN,c),3U:$.M(B(71,1M){c.1c("dV",1M)},c)})}I{$("#5p").on("68.U",$.M(c.gD,c))}}I{$(".5D").2U();if(!c.C.6L){$("#5E").1w();$("#iq").2M()}I{$("#U-52-6v-1").1w();$("#U-52-6v-2").2z("6I");$("#ib").2M()}}if(!c.C.kC&&(c.C.3Y||c.C.6L)){$("#U-52-6v-3").2U()}$("#gZ").23($.M(c.fq,c));if(!c.C.3Y&&!c.C.6L){2V(B(){$("#6C").2h()},5k)}},c);c.62(c.C.1F.T,c.C.iD,pT,1c)},gn:B(T){q $el=T;q L=$el.L().L();q 1c=$.M(B(){$("#e0").1p($el.1i("8n"));$("#pS").1i("1S",$el.1i("3p"));if($el.1f("3f")=="1l"&&$el.1f("5J")=="3r"){$("#bl").1p("6O")}I{$("#bl").1p($el.1f("5J"))}if($(L).2R(0).Q==="A"){$("#6C").1p($(L).1i("1S"));if($(L).1i("1O")=="6y"){$("#6S").9D("9K",N)}}$("#iy").23($.M(B(){c.dX($el)},c));$("#iA").23($.M(B(){c.kL($el)},c))},c);c.62(c.C.1F.8p,c.C.hL,pV,1c)},dX:B(el){q 9b=$(el).L().L();q L=$(el).L();q a7=E;if(9b.1m&&9b[0].Q==="A"){a7=N;$(9b).1w()}I{if(L.1m&&L[0].Q==="A"){a7=N;$(L).1w()}I{$(el).1w()}}if(L.1m&&L[0].Q==="P"){c.4i();if(a7===E){c.4g(L)}}c.1c("pX",el);c.3g();c.1j()},kL:B(el){c.6t(E);q $el=$(el);q L=$el.L();$el.1i("8n",$("#e0").1p());q 9E=$("#bl").1p();q 2H="";if(9E==="1s"){2H="0 "+c.C.8i+" "+c.C.8i+" 0";$el.1f({"5J":"1s",2H:2H})}I{if(9E==="4Z"){2H="0 0 "+c.C.8i+" "+c.C.8i+"";$el.1f({"5J":"4Z",2H:2H})}I{if(9E==="6O"){$el.1f({"5J":"",3f:"1l",2H:"4l"})}I{$el.1f({"5J":"",3f:"",2H:""})}}}q 1r=$.2b($("#6C").1p());if(1r!==""){q 1O=E;if($("#6S").9D("9K")){1O=N}if(L.2R(0).Q!=="A"){q a=$(\'<a 1S="\'+1r+\'">\'+c.3Z(el)+"</a>");if(1O){a.1i("1O","6y")}$el.2e(a)}I{L.1i("1S",1r);if(1O){L.1i("1O","6y")}I{L.2B("1O")}}}I{if(L.2R(0).Q==="A"){L.2e(c.3Z(el))}}c.3g();c.4T();c.1j()},6t:B(e){if(e!==E&&$(e.1O).L().1V()!=0&&$(e.1O).L()[0].id==="U-T-2C"){F E}q 2X=c.$K.1b("#U-T-2C");if(2X.1V()==0){F E}c.$K.1b("#U-T-es, #U-T-ej").1w();2X.1b("1B").1f({5M:2X[0].1o.5M,9c:2X[0].1o.9c,5L:2X[0].1o.5L,9d:2X[0].1o.9d});2X.1f("2H","");2X.1b("1B").1f("gp","");2X.2e(B(){F $(c).1Y()});$(X).3b("23.U-T-8m-2U");c.$K.3b("23.U-T-8m-2U");c.$K.3b("5T.U-T-e9");c.1j()},kU:B(T){q $T=$(T);$T.on("9J",$.M(B(){c.6t(E)},c));$T.on("p6",$.M(B(){c.$K.on("6w.U-T-kv-6w",$.M(B(){2V($.M(B(){c.4T();c.$K.3b("6w.U-T-kv-6w");c.1j()},c),1)},c))},c));$T.on("23",$.M(B(e){if(c.$K.1b("#U-T-2C").1V()!=0){F E}q p5=E,a6,a5,eM=$T.2p()/$T.22(),g9=20,p8=10;q 5z=c.gA($T);q aa=E;if(5z!==E){5z.on("9J",B(e){aa=N;e.2u();eM=$T.2p()/$T.22();a6=4u.6A(e.eS-$T.eq(0).2Y().1s);a5=4u.6A(e.ff-$T.eq(0).2Y().1T)});$(c.X.2v).on("l1",$.M(B(e){if(aa){q p9=4u.6A(e.eS-$T.eq(0).2Y().1s)-a6;q hV=4u.6A(e.ff-$T.eq(0).2Y().1T)-a5;q iP=$T.22();q g4=9y(iP,10)+hV;q 9G=4u.6A(g4*eM);if(9G>g9){$T.2p(9G);if(9G<3O){c.6u.1f({5M:"-aT",5L:"-pb",8s:"pa",8X:"p4 fp"})}I{c.6u.1f({5M:"-8U",5L:"-gm",8s:"8U",8X:"aT 8Y"})}}a6=4u.6A(e.eS-$T.eq(0).2Y().1s);a5=4u.6A(e.ff-$T.eq(0).2Y().1T);c.1j()}},c)).on("a8",B(){aa=E})}c.$K.on("5T.U-T-e9",$.M(B(e){q 1k=e.5X;if(c.2O.a2==1k||c.2O.eC==1k){c.21(E);c.6t(E);c.dX($T)}},c));$(X).on("23.U-T-8m-2U",$.M(c.6t,c));c.$K.on("23.U-T-8m-2U",$.M(c.6t,c))},c))},gA:B($T){q 2X=$(\'<V id="U-T-2C" 1a-U="9s">\');2X.1f({3k:"gC",3f:"3W-1l",ep:0,oX:"fi oW oV(0, 0, 0, .6)","5J":$T.1f("5J")});2X.1i("3K",E);if($T[0].1o.2H!="4l"){2X.1f({5M:$T[0].1o.5M,9c:$T[0].1o.9c,5L:$T[0].1o.5L,9d:$T[0].1o.9d});$T.1f("2H","")}I{2X.1f({3f:"1l",2H:"4l"})}$T.1f("gp",0.5).2I(2X);c.6u=$(\'<V id="U-T-es" 1a-U="9s">\'+c.C.1F.8p+"</V>");c.6u.1f({3k:"8T",9u:5,1T:"50%",1s:"50%",5M:"-8U",5L:"-gm",ep:1,fK:"#fE",6s:"#fj",8s:"8U",8X:"aT 8Y",er:"oZ"});c.6u.1i("3K",E);c.6u.on("23",$.M(B(){c.gn($T)},c));2X.1h(c.6u);if(c.C.gF){q 5z=$(\'<V id="U-T-ej" 1a-U="9s"></V>\');5z.1f({3k:"8T",9u:2,ep:1,er:"nw-8m",ft:"-p1",4Z:"-fp",p0:"fi pc #fj",fK:"#fE",2p:"fD",22:"fD"});5z.1i("3K",E);2X.1h(5z);2X.1h($T);F 5z}I{2X.1h($T);F E}},fI:B(e){q 1B=\'<1B id="T-1L" 3p="\'+$(e.1O).1i("4m")+\'" 8n="\'+$(e.1O).1i("1e")+\'" />\';q L=c.2E();if(c.C.5Y&&$(L).2g("li").1V()==0){1B="<p>"+1B+"</p>"}c.b2(1B,N)},fq:B(){q 1p=$("#6C").1p();if(1p!==""){q 1a=\'<1B id="T-1L" 3p="\'+1p+\'" />\';if(c.C.1N===E){1a="<p>"+1a+"</p>"}c.b2(1a,N)}I{c.3g()}},eN:B(1a){c.b2(1a)},b2:B(1M,1r){c.1W();if(1M!==E){q o="";if(1r!==N){o=\'<1B id="T-1L" 3p="\'+1M.6e+\'" />\';q L=c.2E();if(c.C.5Y&&$(L).2g("li").1V()==0){o="<p>"+o+"</p>"}}I{o=1M}c.26("5c",o,E);q T=$(c.$K.1b("1B#T-1L"));if(T.1m){T.2B("id")}I{T=E}c.1j();1r!==N&&c.1c("3Y",T,1M)}c.3g();c.4T()},i8:B(){if($("#U-8O").1V()!=0){F}c.$i0=$(\'<12 id="U-8O"><V></V></12>\');$(X.2v).1h(c.$i0)},8C:B(){c.i8();$("#U-8O").pr()},b6:B(){$("#U-8O").6N(pq)},i5:B(){$.4W(c.C,{hW:51()+\'<2l id="U-5W-25-4y"><2t id="pp" 5l="7R" 7t="" b3="af/2t-1a"><2D>\'+c.C.1F.ed+\'</2D><2T 1G="Y" id="e6" 1x="6Q" /><12 1o="2H-1T: aT;"><2T 1G="25" id="5p" 2m="\'+c.C.e5+\'" /></12></2t></2l>\',hL:51()+\'<2l id="U-5W-T-8p"><2D>\'+c.C.1F.1e+\'</2D><2T 1G="Y" id="e0" 1x="6Q" /><2D>\'+c.C.1F.1r+\'</2D><2T 1G="Y" id="6C" 1x="6Q" /><2D><2T 1G="gT" id="6S"> \'+c.C.1F.eB+"</2D><2D>"+c.C.1F.hP+\'</2D><3s id="bl"><4b 2s="3r">\'+c.C.1F.3r+\'</4b><4b 2s="1s">\'+c.C.1F.1s+\'</4b><4b 2s="6O">\'+c.C.1F.6O+\'</4b><4b 2s="4Z">\'+c.C.1F.4Z+\'</4b></3s></2l><49><1D id="iy" 1x="4r pt">\'+c.C.1F.iv+\'</1D><1D 1x="4r 6M">\'+c.C.1F.6H+\'</1D><1D id="iA" 1x="4r 6P">\'+c.C.1F.7N+"</1D></49>",iD:51()+\'<2l id="U-5W-T-4y"><12 id="5E"><a 1S="#" id="U-52-6v-1" 1x="6I">\'+c.C.1F.8H+\'</a><a 1S="#" id="U-52-6v-2">\'+c.C.1F.eo+\'</a><a 1S="#" id="U-52-6v-3">\'+c.C.1F.1r+\'</a></12><2t id="pu" 5l="7R" 7t="" b3="af/2t-1a"><12 id="pn" 1x="5D"><2T 1G="25" id="5p" 2m="\'+c.C.an+\'" /></12><12 id="ib" 1x="5D" 1o="3f: 3r;"><12 id="eA"></12></12></2t><12 id="iq" 1x="5D" 1o="3f: 3r;"><2D>\'+c.C.1F.ip+\'</2D><2T 1G="Y" 2m="6C" id="6C" 1x="6Q"  /><br><br></12></2l><49><1D 1x="4r 6M">\'+c.C.1F.6H+\'</1D><1D 1x="4r 6P" id="gZ">\'+c.C.1F.4y+"</1D></49>",gX:51()+\'<2l id="U-5W-1r-4y"><3s id="U-gU-gL" 1o="2p: 99.5%; 3f: 3r;"></3s><2D>gN</2D><2T 1G="Y" 1x="6Q" id="7Z" /><2D>\'+c.C.1F.Y+\'</2D><2T 1G="Y" 1x="6Q" id="aj" /><2D><2T 1G="gT" id="6S"> \'+c.C.1F.eB+\'</2D></2l><49><1D 1x="4r 6M">\'+c.C.1F.6H+\'</1D><1D id="he" 1x="4r 6P">\'+c.C.1F.4y+"</1D></49>",hf:51()+\'<2l id="U-5W-1n-4y"><2D>\'+c.C.1F.aF+\'</2D><2T 1G="Y" 1V="5" 2s="2" id="eX" /><2D>\'+c.C.1F.aJ+\'</2D><2T 1G="Y" 1V="5" 2s="3" id="hx" /></2l><49><1D 1x="4r 6M">\'+c.C.1F.6H+\'</1D><1D id="hv" 1x="4r 6P">\'+c.C.1F.4y+"</1D></49>",hB:51()+\'<2l id="U-5W-3y-4y"><2t id="pf"><2D>\'+c.C.1F.hF+\'</2D><5s id="eD" 1o="2p: 99%; 22: pe;"></5s></2t></2l><49><1D 1x="4r 6M">\'+c.C.1F.6H+\'</1D><1D id="hk" 1x="4r 6P">\'+c.C.1F.4y+"</1D></49>"})},62:B(1e,3L,2p,1c){c.gH();c.$au=2p;c.$2P=$("#eR");if(!c.$2P.1m){c.$2P=$(\'<12 id="eR" 1o="3f: 3r;" />\');c.$2P.1h($(\'<12 id="eP">&pj;</12>\'));c.$2P.1h($(\'<aS id="bc" />\'));c.$2P.1h($(\'<12 id="ac" />\'));c.$2P.8P(X.2v)}$("#eP").on("23",$.M(c.3g,c));$(X).on("5d",$.M(c.8M,c));c.$K.on("5d",$.M(c.8M,c));c.ht(3L);c.hD(1e);c.hI();c.ia();c.gO();c.hC();c.6J=c.X.2v.3e;if(c.C.4Y===E){c.6J=c.$K.3e()}if(c.5a()===E){c.fW()}I{c.hl()}if(1E 1c==="B"){1c()}2V($.M(B(){c.1c("pm",c.$2P)},c),11);$(X).3b("pl.5W");c.$2P.1b("2T[1G=Y]").on("pk",$.M(B(e){if(e.5X===13){c.$2P.1b(".6P").23();e.2u()}},c));F c.$2P},fW:B(){c.$2P.1f({3k:"ak",1T:"-df",1s:"50%",2p:c.$au+"px",5L:"-"+(c.$au/2)+"px"}).2M();c.ei=$(X.2v).1f("8V");$(X.2v).1f("8V","8E");2V($.M(B(){q 22=c.$2P.hQ();c.$2P.1f({1T:"50%",22:"4l",8R:"4l",5M:"-"+(22+10)/2+"px"})},c),15)},hl:B(){c.$2P.1f({3k:"ak",2p:"3O%",22:"3O%",1T:"0",1s:"0",2H:"0",8R:"pi"}).2M()},ht:B(3L){c.6g=E;if(3L.44("#")==0){c.6g=$(3L);$("#ac").6z().1h(c.6g.o());c.6g.o("")}I{$("#ac").6z().1h(3L)}},hD:B(1e){c.$2P.1b("#bc").o(1e)},hC:B(){q 4a=c.$2P.1b("49 1D").ay(".pg");q ea=4a.1V();if(ea>0){$(4a).1f("2p",(c.$au/ea)+"px")}},gO:B(){c.$2P.1b(".6M").on("23",$.M(c.3g,c))},gH:B(){if(c.C.fb){c.$8D=$("#ev");if(!c.$8D.1m){c.$8D=$(\'<12 id="ev" 1o="3f: 3r;"></12>\');$("2v").6d(c.$8D)}c.$8D.2M().on("23",$.M(c.3g,c))}},hI:B(){if(1E $.fn.ii!=="1J"){c.$2P.ii({po:"#bc"});c.$2P.1b("#bc").1f("er","pv")}},ia:B(){q $5E=$("#5E");if(!$5E.1m){F E}q 4J=c;$5E.1b("a").1u(B(i,s){i++;$(s).on("23",B(e){e.2u();$5E.1b("a").3c("6I");$(c).2z("6I");$(".5D").2U();$("#5D"+i).2M();$("#ps").1p(i);if(4J.5a()===E){q 22=4J.$2P.hQ();4J.$2P.1f("2H-1T","-"+(22+10)/2+"px")}})})},8M:B(e){if(e.2O===c.2O.dY){c.3g();F E}},3g:B(){$("#eP").3b("23",c.3g);$("#eR").6N("pd",$.M(B(){q eU=$("#ac");if(c.6g!==E){c.6g.o(eU.o());c.6g=E}eU.o("");if(c.C.fb){$("#ev").2U().3b("23",c.3g)}$(X).3b("5d",c.8M);c.$K.3b("5d",c.8M);c.1W();if(c.C.4Y&&c.6J){$(c.X.2v).3e(c.6J)}I{if(c.C.4Y===E&&c.6J){c.$K.3e(c.6J)}}c.1c("p2")},c));if(c.5a()===E){$(X.2v).1f("8V",c.ei?c.ei:"gq")}F E},oY:B(eu){$(".5D").2U();$("#5E").1b("a").3c("6I").eq(eu-1).2z("6I");$("#5D"+eu).2M()},gD:B(e){q 7I=e.1O.7I;3z(q i=0,f;f=7I[i];i++){c.e1(f)}},e1:B(25){c.gy(25,$.M(B(gx){c.ji(25,gx)},c))},gy:B(25,1c){q 2n=2c la();q eV="?";if(c.C.5Q.4G(/\\?/)!="-1"){eV="&"}2n.b7("p3",c.C.5Q+eV+"2m="+25.2m+"&1G="+25.1G,N);if(2n.kR){2n.kR("Y/p7; pw=x-py-pY")}q 4J=c;2n.pW=B(e){if(c.lc==4&&c.e8==5k){4J.8C();1c(pU(c.pZ))}I{if(c.lc==4&&c.e8!=5k){}}};2n.k9()},ja:B(5l,1K){q 2n=2c la();if("q7"in 2n){2n.b7(5l,1K,N)}I{if(1E lb!="1J"){2n=2c lb();2n.b7(5l,1K)}I{2n=2G}}F 2n},ji:B(25,1K){q 2n=c.ja("q5",1K);if(!2n){}I{2n.jd=$.M(B(){if(2n.e8==5k){c.b6();q eb=1K.4o("?");if(!eb[0]){F E}c.1W();q o="";o=\'<1B id="T-1L" 3p="\'+eb[0]+\'" />\';if(c.C.5Y){o="<p>"+o+"</p>"}c.26("5c",o,E);q T=$(c.$K.1b("1B#T-1L"));if(T.1m){T.2B("id")}I{T=E}c.1j();c.1c("3Y",T,E);c.3g();c.4T()}I{}},c);2n.q3=B(){};2n.8H.pQ=B(e){};2n.iK("pE-pF",25.1G);2n.iK("x-pG-pD","pz-pA");2n.k9(25)}},et:B(el,3R){c.3A={1K:E,4h:E,3U:E,2y:E,ef:E,4l:E,2T:E};$.4W(c.3A,3R);q $el=$("#"+el);if($el.1m&&$el[0].Q==="pB"){c.3A.2T=$el;c.el=$($el[0].2t)}I{c.el=$el}c.jD=c.el.1i("7t");if(c.3A.4l){$(c.3A.2T).68($.M(B(e){c.el.ca(B(e){F E});c.e4(e)},c))}I{if(c.3A.ef){$("#"+c.3A.ef).23($.M(c.e4,c))}}},e4:B(e){c.8C();c.jI(c.2j,c.kl())},kl:B(){c.id="f"+4u.kg(4u.kh()*k0);q d=c.X.4C("12");q 1Q=\'<1Q 1o="3f:3r" id="\'+c.id+\'" 2m="\'+c.id+\'"></1Q>\';d.3Q=1Q;$(d).8P("2v");if(c.3A.2y){c.3A.2y()}$("#"+c.id).jZ($.M(c.jT,c));F c.id},jI:B(f,2m){if(c.3A.2T){q eI="pM"+c.id,jX="pK"+c.id;c.2t=$(\'<2t  7t="\'+c.3A.1K+\'" 5l="c1" 1O="\'+2m+\'" 2m="\'+eI+\'" id="\'+eI+\'" b3="af/2t-1a" />\');if(c.C.3H!==E&&1E c.C.3H==="3M"){$.1u(c.C.3H,$.M(B(k,v){if(v!=2G&&v.43().44("#")===0){v=$(v).1p()}q 8E=$("<2T/>",{1G:"8E",2m:k,2s:v});$(c.2t).1h(8E)},c))}q eT=c.3A.2T;q 8S=$(eT).6q();$(eT).1i("id",jX).3S(8S).8P(c.2t);$(c.2t).1f("3k","8T").1f("1T","-df").1f("1s","-df").8P("2v");c.2t.ca()}I{f.1i("1O",2m).1i("5l","c1").1i("b3","af/2t-1a").1i("7t",c.3A.1K);c.2j.ca()}},jT:B(){q i=$("#"+c.id)[0],d;if(i.jU){d=i.jU}I{if(i.bK){d=i.bK.X}I{d=42.o2[c.id].X}}if(c.3A.4h){c.b6();if(1E d!=="1J"){q jW=d.2v.3Q;q 7s=jW.1U(/\\{(.|\\n)*\\}/)[0];7s=7s.G(/^\\[/,"");7s=7s.G(/\\]$/,"");q 1M=$.8J(7s);if(1E 1M.3U=="1J"){c.3A.4h(1M)}I{c.3A.3U(c,1M);c.3g()}}I{c.3g();o1("jE nY!")}}c.el.1i("7t",c.jD);c.el.1i("1O","")},bP:B(el,3R){c.5w=$.4W({1K:E,4h:E,3U:E,nZ:E,3H:E,Y:c.C.1F.jA,jJ:c.C.1F.jB,6k:E},3R);if(42.bw===1J){F E}c.bz=$(\'<12 1x="o0"></12>\');c.5b=$(\'<12 1x="o6">\'+c.5w.Y+"</12>");c.kk=$(\'<12 1x="o7">\'+c.5w.jJ+"</12>");c.bz.1h(c.5b);$(el).3S(c.bz);$(el).3S(c.kk);c.5b.on("oc",$.M(B(){F c.je()},c));c.5b.on("ob",$.M(B(){F c.jf()},c));c.5b.2R(0).o8=$.M(B(e){e.2u();c.5b.3c("dk").2z("6w");c.8C();c.bF(c.5w.1K,e.aX.7I[0],E,e,c.5w.6k)},c)},bF:B(1K,25,bm,e,6k){if(!bm){q 2n=$.nX.2n();if(2n.8H){2n.8H.nK("8O",$.M(c.jh,c),E)}$.nL({2n:B(){F 2n}})}c.1c("6w",e);q fd=2c bw();if(6k!==E){fd.1h(6k,25)}I{fd.1h("25",25)}if(c.C.3H!==E&&1E c.C.3H==="3M"){$.1u(c.C.3H,$.M(B(k,v){if(v!=2G&&v.43().44("#")===0){v=$(v).1p()}fd.1h(k,v)},c))}$.iU({1K:1K,nI:"o",1a:fd,nG:E,c3:E,nH:E,1G:"c1",4h:$.M(B(1a){1a=1a.G(/^\\[/,"");1a=1a.G(/\\]$/,"");q 1M=(1E 1a==="8I"?$.8J(1a):1a);c.b6();if(bm){q $1B=$("<1B>");$1B.1i("3p",1M.6e).1i("id","jc-T-1L");c.js(e,$1B[0]);q T=$(c.$K.1b("1B#jc-T-1L"));if(T.1m){T.2B("id")}I{T=E}c.1j();c.4T();if(T){c.1c("3Y",T,1M)}if(1E 1M.3U!=="1J"){c.1c("dV",1M)}}I{if(1E 1M.3U=="1J"){c.5w.4h(1M)}I{c.5w.3U(c,1M);c.5w.4h(E)}}},c)})},je:B(){c.5b.2z("dk");F E},jf:B(){c.5b.3c("dk");F E},jh:B(e,Y){q kX=e.kB?9y(e.kB/e.oR*3O,10):e;c.5b.Y("oO "+kX+"% "+(Y||""))},5a:B(){F/(oj|or|os|oy)/.4e(6n.7k)},d6:B(){F/oz/.4e(6n.7k)},9M:B(4p){if(1E(4p)==="1J"){F 0}F 9y(4p.G("px",""),10)},3Z:B(el){F $("<12>").1h($(el).eq(0).6q()).o()},ox:B(o){q 2L=X.4C("8K");2L.3Q=o;F 2L.9Q||2L.dh||""},kF:B(71){F ow.5o.43.5q(71)=="[3M 51]"},9B:B(o){o=o.G(/&#9F;|<br>|<br\\/>|&3u;/gi,"");o=o.G(/\\s/g,"");o=o.G(/^<p>[^\\W\\w\\D\\d]*?<\\/p>$/i,"");F o==""},ro:B(){q rv=E;if(6n.rn=="rl rb rx"){q 47=6n.7k;q re=2c 2J("rf ([0-9]{1,}[.0-9]{0,})");if(re.2o(47)!=2G){rv=rw(2J.$1)}}F rv},8L:B(){F!!6n.7k.1U(/ry\\/7\\./)},1C:B(1C){q 47=6n.7k.3N();q 1U=/(fs)[\\/]([\\w.]+)/.2o(47)||/(d5)[ \\/]([\\w.]+)/.2o(47)||/(4s)[ \\/]([\\w.]+).*(rC)[ \\/]([\\w.]+)/.2o(47)||/(4s)[ \\/]([\\w.]+)/.2o(47)||/(94)(?:.*9n|)[ \\/]([\\w.]+)/.2o(47)||/(3t) ([\\w.]+)/.2o(47)||47.44("r3")>=0&&/(rv)(?::| )([\\w.]+)/.2o(47)||47.44("qt")<0&&/(31)(?:.*? rv:([\\w.]+)|)/.2o(47)||[];if(1C=="9n"){F 1U[2]}if(1C=="4s"){F(1U[1]=="d5"||1U[1]=="4s")}if(1U[1]=="rv"){F 1C=="3t"}if(1U[1]=="fs"){F 1C=="4s"}F 1C==1U[1]},cw:B(){if(c.1C("3t")&&9y(c.1C("9n"),10)<9){F N}F E},fF:B(fr){q 7e=fr.qA(N);q 12=c.X.4C("12");12.7g(7e);F 12.3Q},cE:B(){q J=c.$K[0];q 4k=c.X.cD();q cC;3w((cC=J.8G)){4k.7g(cC)}F 4k},4j:B(el){if(!el){F E}if(c.C.1Q){F el}if($(el).8B("12.4d").1m==0||$(el).3v("4d")){F E}I{F el}},74:B(Q){q L=c.2E(),1t=c.3P();F L&&L.Q===Q?L:1t&&1t.Q===Q?1t:E},ck:B(){q 1t=c.2N();q 2Y=c.ch(1t);q Y=$.2b($(1t).Y()).G(/\\n\\r\\n/g,"");q 4P=Y.1m;if(2Y==4P){F N}I{F E}},7h:B(){q el,1q=c.29();if(1q&&1q.4V&&1q.4V>0){el=1q.48(0).6D}if(!el){F E}if(c.C.1Q){if(c.hR().iz()){F!c.$K.is(el)}I{F N}}F $(el).2g("12.4d").1m!=0},4H:B(el,1i){if($(el).1i(1i)==""){$(el).2B(1i)}},iB:B(ao,2s){q 2K=2G;3w((2K=ao.44(2s))!==-1){ao.ap(2K,1)}F ao}};3J.5o.77.5o=3J.5o;$.3J.fn.e7=B(eQ,7d,6l,75,7A){q 1K=/(((5n?|qT?):\\/\\/)|aA[.][^\\s])(.+?\\..+?)([.),]?)(\\s|\\.\\s+|\\)|$)/gi,hK=/(5n?|am):\\/\\//i,d3=/(5n?:\\/\\/.*\\.(?:gY|qR|hc|ha))/gi;q 8F=(c.$K?c.$K.2R(0):c).8F,i=8F.1m;3w(i--){q n=8F[i];if(n.4D===3){q o=n.aH;if(75&&o){q 5j=\'<1Q 2p="cT" 22="hG" 3p="\',7B=\'" cS="0" hE></1Q>\';if(o.1U(8N)){o=o.G(8N,5j+"//aA.cO.7w/4S/$1"+7B);$(n).2I(o).1w()}I{if(o.1U(8Q)){o=o.G(8Q,5j+"//hp.cI.7w/3y/$2"+7B);$(n).2I(o).1w()}}}if(6l&&o&&o.1U(d3)){o=o.G(d3,\'<1B 3p="$1">\');$(n).2I(o).1w()}if(7d&&o&&o.1U(1K)){q 2x=o.1U(1K);3z(q i in 2x){q 1S=2x[i];q Y=1S;q 6c="";if(1S.1U(/\\s$/)!==2G){6c=" "}q c8=eQ;if(1S.1U(hK)!==2G){c8=""}if(Y.1m>7A){Y=Y.aQ(0,7A)+"..."}Y=Y.G(/&/g,"&9h;").G(/</g,"&lt;").G(/>/g,"&gt;");q jq=Y.G("$","$$$");o=o.G(1S,\'<a 1S="\'+c8+$.2b(1S)+\'">\'+$.2b(jq)+"</a>"+6c)}$(n).2I(o).1w()}}I{if(n.4D===1&&!/^(a|1D|5s)$/i.4e(n.Q)){$.3J.fn.e7.5q(n,eQ,7d,6l,75,7A)}}}}})(nW);',62,1714,'||||||||||||this||||||||||||html||var|||||||||||function|opts||false|return|replace||else|node|editor|parent|proxy|true|range||tagName|||image|redactor|span||document|text||||div||||||||data|find|callback|tag|title|css|td|append|attr|sync|key|block|length|table|style|val|sel|link|left|current|each|source|remove|class|lang|elem|toolbar|img|browser|button|typeof|curLang|type|func|selection|undefined|url|marker|json|linebreaks|target|cmd|iframe|dropdown|href|top|match|size|selectionRestore|nodes|contents|out||bufferSet|height|click||file|execCommand||selectionSave|getSelection|btn|trim|new|btnObject|replaceWith|blockquote|closest|focus|invisibleSpace|element|code|section|name|xhr|exec|width|pre|ul|value|form|preventDefault|body|btnName|matches|start|addClass|font|removeAttr|box|label|getParent|param|null|margin|after|RegExp|index|tmp|show|getBlock|keyCode|redactorModal|arr|get|className|input|hide|setTimeout|last|imageBox|offset|frame|air|mozilla|||||||||list|off|removeClass|push|scrollTop|display|modalClose|LI|point|tr|position|end|bold|script|getRange|src|italic|none|select|msie|nbsp|hasClass|while|ctrl|video|for|uploadOptions|insertNode|doc|blocks|buffer|BLOCKQUOTE|wrapper|uploadFields|getBlocks|Redactor|contenteditable|content|object|toLowerCase|100|getCurrent|innerHTML|options|before|thead|error|strong|inline|pos|imageUpload|outerHtml||rangy|window|toString|indexOf|||ua|getRangeAt|footer|buttons|option|children|redactor_editor|test|next|selectionStart|success|focusWithSaveScroll|isParentRedactor|frag|auto|rel|fullpage|split|str|buttonGet|redactor_modal_btn|webkit|elems|Math|postData|meta|finalnodes|insert|current_tr|htmls|textNodes|createElement|nodeType|tooltip|collapsed|search|removeEmptyAttr|php|that|underline|parentNode|prev|cont|inArray|len|orgn|substr|embed|observeImages|placeholder|rangeCount|extend|removeAllRanges|autoresize|right||String|tab|shiftKey|keys|redactor_placeholder|del|align|emptyHtml||isMobile|dropareabox|inserthtml|keyup|blockElem|break|addRange|collapse|createRange|iframeStart|200|method|observeLinks|https|prototype|redactor_file|call|audio|textarea|alignmentTags|case|lastNode|draguploadOptions|listTag|regex|imageResizer|rule|replaced|charAt|redactor_tab|redactor_tabs|strike|allowedTags|deleted|formatBlocks|float|newTag|marginLeft|marginTop|visual|node1|listCurrent|s3|selectall|savedSel|keydown|par|join|modal|which|paragraphy|current_td|||modalInit|direction|textareamode|||event|change|dir|orderedlist|Insert|space|prepend|filelink|unorderedlist|modalcontent|TD|PRE|shortcuts|uploadParam|convertImageLinks|indent|navigator|formatBlock|italicTag|clone|boldTag|color|imageResizeHide|imageEditter|control|drop|formatting|_blank|empty|round|toolbarFixed|redactor_file_link|startContainer|modified|unlink|count|cancel|redactor_tabs_act|saveModalScroll|insert_link_node|imageGetJson|redactor_btn_modal_close|fadeOut|center|redactor_modal_action_btn|redactor_input|folders|redactor_link_blank|redactorfolder|insertAfterLastElement|set|hideHandler|phpMatches|item|cleanRemoveSpaces|etags|obj|cleanGetTabs||currentOrParentIs|convertVideoLinks|replacementTag|init|thtml|elements|dropact|buttonActive|tabindex|convertLinks|cloned|buttonBuild|appendChild|isFocused|tmpList|newblock|userAgent|instance|outdent|listParent|weight|blocksElems|modif|getNodes|jsonString|action|possible|nodeTestBlocks|com|enter|fullpageDoctype|isFunction|linkSize|iframeEnd|autosave|u200B|linkObserverTooltipClose|uuid|selectionEnd|observeStart|files|charCount|endCharCount|Delete|boxTop|save|toolbarExternal|dblEnter|horizontalrule|post|character|toolbarFixedTarget|keyPosition|shift|Header|typewriter|Add|redactor_link_url|metaKey|tagblock|createTextNode|linkmarker|fileUpload|setEnd|setStart|focn|textNode|newnodes|linkProtocol|selectionRemoveMarkers|tbody|cleanStripTags|selectionSet|node2|getElement|endContainer|imageFloatMargin|rtePaste|insertunorderedlist|alignmentSet|resize|alt|tmpLi|edit|insertorderedlist|buttonActiveObserver|fontSize|background|setNonEditable|cleanlevel|tagout|phpTags|deniedTags|redactor_act|rebuffer|parents|showProgressBar|redactorModalOverlay|hidden|childNodes|firstChild|upload|string|parseJSON|DIV|isIe11|modalCloseHandler|reUrlYoutube|progress|appendTo|reUrlVimeo|minHeight|newElement|absolute|11px|overflow|pasteInsert|padding|10px|selected|||setSpansVerified|inlineMethods|opera|tablePaste|||||convertDivs|parentLink|marginBottom|marginRight|ADDRESS|SECTION|strip|amp|insertHtml|folderclass|parentHtml|result|rangeNodes|version|listText|clipboardUpload|indentValue|template|verified|saveScroll|zIndex|paste|wrapperHtml|selectionHtml|parseInt|cleanParagraphy||isEmpty|codeLength|prop|floating|x200b|new_w|predefinedLinksStorage|line|mousedown|checked|placeTag|normalize|linkShow|insertHtmlAdvanced|ctrlKey|textContent|alignment|cleanConvertInlineTags|allowed|head|new_tr|colspan|rowspan|FOOTER|HEADER|ARTICLE|removeEmptyTags|BACKSPACE|clickedElement|ASIDE|start_y|start_x|parentEl|mouseup|sourceHeight|isResizing|javascript|redactor_modal_inner|setSpansVerifiedHtml|setStartAfter|multipart|cloneRange|autosaveInterval|selectionSetMarker|redactor_link_url_text|fixed|insertLineBreak|ftp|imageUploadParam|array|splice|foundStart|boundaryRange|toolbarFixedBox|insertAfter|redactorModalWidth|emptyElement|special|rTestBlock|not|specialKey|www|getSelectionText|deleteContents|outer|documentElement|rows|direct|nodeValue|http|columns|focusSet|execPasteFrag|selectNodeContents|separator|methodVal|classname|substring|icon|header|7px|fonts|folder|tabSpaces|dataTransfer|activeButtons|row|folderkey|column|imageInsert|enctype|spans|foco|hideProgressBar|open|one|ENTER|preCaretRange|toolbarFixedTopOffset|redactor_modal_header|buildCodearea||htmlTagName|blockLevelElements|hdlHideDropDown|airBindMousemoveHide|dropdownHideAll|inlineRemoveFormatReplace|redactor_form_image_align|directupload|aligncenter|align_right|align_left|endRange||alignright|insertingAfterLastElement|align_justify|align_center|FormData|selectionWrap|selectionElement|droparea|selectionRemove|orgo|BR|fotorama|posFrame|dragUploadAjax|iframeDoc|caretOffset|Range|contOwnLine|contentWindow|ownLine|tagTestBlock|getTextNodesIn|apply|draguploadInit|arrSearch|cursorRange|royalSlider|clipboardUploadUrl|add_head|cleanFontTag|insert_column_right|insert_column_left|delete_head|decoration|delete_row|POST|delete_column|contentType|insert_row_below|insert_row_above|pasteClipboardMozilla|fullscreen|addProtocol|pasteClipboardAppendFields|submit|focusEnd|getRangeSelectedNodes|insert_table|cleanEncodeEntities|alignleft|elem2|getCaretOffset|dropdownHide|dropdownShow|isEndOfElement|buildEventKeydownInsertLineBreak|btnHeight|plugins|iframeAppend|sourceOld|RedactorPlugins|dropdownWidth|buttonName|buttonInactive|rBlockTest|activeButtonsStates|oldIE|indentingIndent|indentingOutdent|link_insert|FIGCAPTION|redactor_button_disabled|child|createDocumentFragment|extractContent|airShow|focusElem|buildAfter|vimeo|buildOptions|iframeAddCss|hotkeysShiftNums|placeholderOnBlur|blur|youtube|placeholderGet|placeholderOnFocus|placeholderText|frameborder|500|buttonsHideOnMobile|iframePage|redactor_toolbar|toolbarObserveScroll|savedHtml|iframeLoad|toggle|setFullpageOnInit|buildBindKeyboard|urlImage|indentingStart|chrome|isIPad|cleannewLevel|formatblock|formatQuote|paragraphsOnly|suffix|cleanTag||cleanSavePreCode|2000px|cleanFinish|innerText|tfoot|arg|hover||address|parseHTML|pasteHTML|currBlock|cleanRemoveEmptyTags|inlineEachNodes|INLINE||placeholderRemove|utag|buildEnable|safes|alignmentCenter|modify|alignmentJustify|justify|reader|alignmentRight|lineOrWord|childList|insideOutdent|DOWN|alignmentLeft|cleanEmpty|cleanConverters|oldsafari|dnbImageTypes|th|maxHeight|merge|commentsMatches|cleanConvertProtected|templateVars|clipboardData|etagsInline|imageUploadError|delete_table|imageRemove|ESC|added|redactor_file_alt|s3uploadFile|table_box|tableAddColumn|uploadSubmit|fileUploadParam|redactor_filename|formatLinkify|status|delete|buttonsSize|s3image|predefinedLinks|filename|BODY|trigger|focus_tr|thumbtitle|modalSaveBodyOveflow|resizer|focus_td||||choose|lineHeight||cursor|editter|uploadInit|num|redactor_modal_overlay|tableAddRow|fileCallback|tableDeleteHead|tableId|redactor_image_box|link_new_tab|DELETE|redactor_insert_video_area|syncClean|container|try|mailto|formId|linkInsertPressed|endNode|z0|ratio|imageCallback|pattern|redactor_modal_close|protocol|redactor_modal|pageX|oldElement|redactorModalInner|mark|Column|redactor_table_rows|extra|targetBlank|Row|||||||||||modalOverlay|Table||catch|pageY|link_edit|firstParent|1px|fff|altKey|buttonInactiveAll|superscript||buttonActiveToggle|5px|imageCallbackLink|fragment|opr|bottom|execUnlink|execLists|LEFT_WIN|inserthorizontalrule|subscript|formattingPre|listCurrentText|UL|strikethrough|8px|000|getFragmentHtml|redactor_dropdown_link|clonedHtml|imageThumbClick|OUTPUT|backgroundColor|cleanReConvertProtected|redactor_editor_wym|toolbarInit|wym|min|buildAddClasses|dragUpload|textareaKeydownCallback|buildEventDrop|area|fieldset|modalShowOnDesktop|focusCallback|buildFromElement|buildFromTextarea|buildStart|mobile|filter|transparent|new_h|cleanUnverified|quot|List|tags|min_w|encode|buildMobile|map|buildEventPaste|double|DT|Color|items||cleanReplacer|clipboardFilePaste|placeholderStart|18px|imageEdit|buildEventKeydown|opacity|visible|buffer_|u200D||mod|pastedFrag|cleanup|signedURL|s3executeOnSignedUrl|buildEventClipboardUpload|imageResizeControls|uFEFF|relative|s3handleFileSelect|originalEvent|imageResizable|buttonInactiveVisual|modalSetOverlay|toolbarBuild|airButtons|buttonSource|links|formattingTags|URL|modalOnCloseButton|indenting|tidyHtml|textareaIndenting|setInterval|checkbox|predefined|timer|write|modal_link|png|redactor_upload_btn|10005|||||||scroll|close|toolbarOverflow|gif|redactor_air_|jpeg|clearInterval|redactor_insert_link_btn|modal_table|args|placeholderBlur|219|placeholderInit|redactor_insert_video_btn|modalShowOnMobile|placeholderFocus|command|shortcutsHandler|player|placeholderRemoveFromCode||placeholderRemoveFromEditor|modalSetContent|hotkeysSpecialKeys|redactor_insert_table_btn|arguments|redactor_table_columns|toggleVisual|toggleCode|origHandler|modal_video|modalSetButtonsWidth|modalSetTitle|allowfullscreen|video_html_code|281|visibility|modalSetDraggable|dropdownBuild|rProtocol|modal_image_edit|H6|buttonActiveVisual|redactor_dropdown|image_position|outerHeight|getCaretOffsetRange|buildEventKeyup|buttonImage|returnValue|mouse_y|modal_file|afterkey|buildEventKeydownTab|buildEventKeydownPre|progressBar|beforekey|tabFocus|Array|buildEventKeydownBackspace|modalTemplatesInit|DD|DL|buildProgressBar|H5|modalLoadTabs|redactor_tab2|innerWidth||airBindHide|||collapseToStart|draggable|keyboard|redactor_air|airEnable|toolbar_fixed_box||iframeCreate|image_web_link|redactor_tab3|dropdownObject||H1|buildPlugins|_delete|H4|buildEventKeyupConverters|redactor_image_delete_btn|equals|redactorSaveBtn|removeFromArrayByValue|H2|modal_image|innerHeight|H3|TAB|buildContent|videoShow|videoInsert|setRequestHeader|tableAddColumnRight|tableAddColumnLeft|internal|cleanSpaces|div_h|linkProcess|location|self|thref|ajax|tableAddRowBelow|to|focusIndex|tableDeleteColumn|the|pasteClipboardUploadMozilla|Align|tableAddHead|tableAddRowAbove|Center|first|pastePre|pastePlainText|pasteClean|linkInsert|s3createCORSRequest|outerHTML|drag|onload|draguploadOndrag|draguploadOndragleave|fileShow|uploadProgress|s3uploadToS3|aside|article|clientX|clientY|Video|SPAN|twice|escapedBackReferences|replaceLineBreak|insertNodeToCaretPositionFromPoint|re2|caretPositionFromPoint|caretRangeFromPoint|createTextRange|moveToPoint|imgs|pasteClipboardUpload|drop_file_here|or_choose|arrAdd|element_action|Upload|setCaretAfter|langs|setCaret|uploadForm|atext|paragraph|shortcutsAdd|newLevel|selectionCreateMarker|nextSibling|getSelectionHtml|nextNode|slice|unshift|uploadLoaded|contentDocument|Edit|rawString|fileId|nodeName|load|99999|header4|pop|header3|linkObserver|aLink|bufferRedo|bufferUndo|tableDeleteRow|send|tableDeleteTable|header5|clipboard|aEdit|aUnlink|formatChangeTag|floor|random|quote|endOffset|dropalternative|uploadFrame|tableInsert|header2|tableShow|header1|fileUploadError|Code|xhtmlTags|xhtml|getCodeIframe|inside|setFullpageDoctype|nofollow|linkNofollow|Right|switch|loaded|imageTabLink|inlineSetMethods|setCodeIframe|isString|Image|doctype|noeditable|Left|fromElement|imageSave|blocksLen|cleanlineAfter|toTagName|formatEmpty|wrapAll|overrideMimeType|paragraphs|cleanlineBefore|imageResize|blocksElemsRemove|newhtml|percent|Head|HTML|onchangeFunc|mousemove|Link|setEditor|imageShow|cleanHtml|inlineFormat|unwrap|isEmptyObject|getJSON|XMLHttpRequest|XDomainRequest|readyState|inlineUnwrapSpan|noscript|u00a9|103|copy||102|u2026|Outdent|Below|down|redactor_|101|applet|Ordered|u2122||isArray|Underline|stylesheet|111|removeChild|Open|110|109|up|105|Alignment|trade|106|45px|107|104|ownerDocument|sourceWidth|mdash|Indent|u2014|No|redactor_box|CTRL|such|youtu|META|use|startOffset|strict|dropdowns|u2010|dash|eval|TEXTAREA|redactor_format_blockquote|pageup|esc|Above|pagedown|optional|Name|hellip|removeFormat|capslock|pause|defaultView|VERSION|Cancel|ltr|LEFT|Save|backspace|home|redactor_format_h3|Embed|FileReader|u00a0|readAsDataURL|Web|Callback|Italic|word|alignjustify|File|112|backcolor|Download|getAsFile|download|fontcolor|about|Font|destroy|Bold|TH|getIframe|web|getToolbar|Formatting|redo|Email|Unlink|undo|getBox|collapseToEnd|getObject|removeData|Text|Quote|Normal|getEditor|blank|Choose|Title|blurCallback|redactor_format_h2|redactor_format_h4|Rule|Chrome|Horizontal|Position|Columns|Deleted|anchor|Unordered|Anchor|redactor_format_pre|enableObjectResizing|redactor_format_h1|enableInlineTableEditing|536|None|here|noneditable||separator_drop1|separator_drop2|Back|Or|separator_drop3|Drop||syncBefore|syncAfter|1class||frameset|Justify|redactor_format_h5|8203|bull|default|Rows|legend|u200b|cache|processData|dataType|fake|addEventListener|ajaxSetup|pasteAfter|guid|docs|MsoListParagraphCxSpFirst|shapes|pasteBefore|MsoListParagraphCxSpMiddle|MsoListParagraphCxSpLast|sid|MsoListParagraph|jQuery|ajaxSettings|failed|preview|redactor_droparea|alert|frames|commonAncestorContainer|isCollapsed|isInlineNode|redactor_dropareabox|redactor_dropalternative|ondrop|editGallery|unselectable|dragleave|dragover|selectionAll|extractContents|insertDoubleLineBreak|EndToEnd|blockSetAttr|blockRemoveAttr|iPhone|blockRemoveStyle||blockSetStyle||inlineRemoveClass|blockSetClass|blockRemoveClass|iPod|BlackBerry|cite|small|sup|Object|stripHtml|Android|iPad|inlineRemoveStyle|inlineSetStyle|insertBeforeCursor|toUpperCase|inlineRemoveFormat|insertText|setEndAfter|setEndPoint|duplicate|offsetNode|attributes|backColor|inlineSetAttr|inlineRemoveAttr|Loading|family|fontName|total|foreColor|hasChildNodes|cloneContents|rgba|dashed|outline|modalSetTab|pointer|border|4px|modalClosed|GET|3px|clicked|dragstart|plain|min_h|mouse_x|9px|13px|solid|fast|160px|redactorInsertVideoForm|redactor_modal_btn_hidden||300px|times|keypress|focusin|modalOpened|redactor_tab1|handle|redactorUploadFileForm|1500|fadeIn|redactor_tab_selected|redactor_modal_delete_btn|redactorInsertImageForm|move|charset||user|public|read|INPUT|cellIndex|acl|Content|Type|amz|300|removeMarkers|detach|redactorUploadFile|saveSelection|redactorUploadForm|restoreSelection|u1C7F|u0000|onprogress|600|redactor_image_edit_src|610|decodeURIComponent|380|onreadystatechange|imageDelete|defined|responseText|redactor_image_box_select|460|host|onerror|xn|PUT|thumb|withCredentials|sub|inlineSetClass|buttonTagToActiveState|redactor_dropdown_box_|buttonChangeIcon|buttonRemoveIcon|121|buttonAwesome|stopPropagation|touchstart|f11|123|122|f10|dropdownShown|buttonAdd|buttonAddFirst|SUB|SUP|OL|118|compatible|188|119|buttonAddBefore|buttonAddAfter|120|buttonRemove|cloneNode|redactor_dropdown_|redactor_separator_drop|186|187|_|fromCharCode|Key|222|221|190|189|191|192|220|173|145|jpg|external|ftps|focusNode|f12|redactor_toolbar_|1000|144|numlock|escape|encodeURIComponent|autosaveError|trident|firstNode|colgroup|col|math|hgroup|nav|caption|Internet|117|concat||MSIE|JustifyLeft|comment|figure|figcaption|114|Microsoft|113|appName|getInternetExplorerVersion|115|hasOwnProperty|116|details|menu|summary||parseFloat|Explorer|Trident|JustifyCenter|JustifyRight|JustifyFull|safari|gallery'.split('|'),0,{}))
\ No newline at end of file
+
+eval(function(p,a,c,k,e,d){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k[c]||e(c)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('(B($){\'mk ml\';if(!i5.5f.9l){i5.5f.9l=B(i6){C fn=c;E B(){E fn.8A(i6)}}}C 7Z=0;C cT=/6h?:\\/\\/(?:[0-9A-Z-]+\\.)?(?:me\\.be\\/|dF\\.9Y\\S*[^\\w\\-\\s])([\\w\\-]{11})(?=[^\\w\\-]|$)(?![?=&+%\\w.\\-]*(?:[\'"][^<>]*>|<\\/a>))[?=&+%\\w.-]*/ig;C cx=/6h?:\\/\\/(ae\\.)?dz.9Y\\/(\\d+)($|\\/)/;$.fn.G=B(3C){C 2q=[];C hq=8D.5f.mu.69(d4,1);if(1B 3C===\'5p\'){c.1z(B(){C 67=$.1d(c,\'G\');C 1s;if(3C.3z(/\\./)!=\'-1\'){1s=3C.3K(\'.\');if(1B 67[1s[0]]!=\'1H\'){1s=67[1s[0]][1s[1]]}}L{1s=67[3C]}if(1B 67!==\'1H\'&&$.4U(1s)){C 9k=1s.8A(67,hq);if(9k!==1H&&9k!==67){2q.3f(9k)}}L{$.6U(\'lb lp as "\'+3C+\'" 2A 3A\')}})}L{c.1z(B(){$.1d(c,\'G\',{});$.1d(c,\'G\',3A(c,3C))})}if(2q.1p===0)E c;L if(2q.1p===1)E 2q[0];L E 2q};B 3A(el,3C){E 1I 3A.5f.3s(el,3C)}$.3A=3A;$.3A.lm=\'10.0\';$.3A.bW=[\'1R\',\'2j\',\'1C\',\'1q\',\'1r\',\'1x\',\'14\',\'1o\',\'1y\',\'2o\',\'2p\',\'28\',\'2P\',\'3x\',\'1W\',\'3d\',\'3r\',\'4v\',\'1w\',\'2m\',\'5D\',\'4D\',\'2S\',\'O\',\'1T\',\'1Q\',\'22\',\'K\',\'2l\',\'T\',\'J\',\'1L\',\'V\',\'54\',\'1f\',\'N\'];$.3A.F={1C:\'en\',b1:\'ll\',7Y:M,28:M,fX:M,2P:M,6t:1l,84:M,9i:M,9g:M,1M:M,7i:1l,2o:1l,fb:M,aE:1l,e6:1l,dE:1l,ds:M,3x:M,cB:M,7R:60,cA:M,h9:1l,7u:\'7J\',dy:M,6z:50,j9:1l,hQ:1l,g8:1l,8H:\'ip\',cr:1l,5A:M,iT:\'1L\',l9:M,hz:1l,e7:M,8o:M,iS:\'1L\',gb:1l,a5:M,82:1l,6E:1l,6P:1l,6v:1l,6s:4,9t:M,eU:1l,7S:M,1q:1l,jf:1l,9q:1a,9r:M,i8:M,ih:M,3D:[\'q\',\'2E\',\'3Y\',\'3X\',\'5M\',\'5r\',\'4Z\',\'7p\',\'3d\',\'J\',\'1L\',\'T\',\'3r\',\'9m\'],aY:[],aQ:[],2E:[\'p\',\'1N\',\'2V\',\'h1\',\'h2\',\'h3\',\'h4\',\'h5\',\'h6\'],cm:M,2p:1l,4y:[\'q\',\'eF\',\'T\',\'3l\',\'4V\',\'5j\',\'1t\',\'kU\'],4J:M,62:M,4H:[[\'8u\',\'41\']],5d:[[\'3w-77:\\\\s?3Y\',"52"],[\'3w-1t:\\\\s?3X\',"em"],[\'1i-9f:\\\\s?51\',"u"],[\'1i-9f:\\\\s?4D-kV\',\'41\']],5l:M,1Z:M,5c:M,5Z:[\'1b\'],3S:[\'p\'],9D:[\'5M\',\'3X\',\'3Y\',\'51\',\'5r\',\'4Z\',\'kW\',\'l1\',\'l2\',\'8e\'],cM:{b:\'3Y\',52:\'3Y\',i:\'3X\',em:\'3X\',41:\'5M\',8u:\'5M\',2H:\'5r\',ol:\'4Z\',u:\'51\'},5D:{\'3O+6b+m, 4V+6b+m\':{1s:\'1T.b6\'},\'3O+b, 4V+b\':{1s:\'1T.2W\',5C:[\'3Y\']},\'3O+i, 4V+i\':{1s:\'1T.2W\',5C:[\'3X\']},\'3O+h, 4V+h\':{1s:\'1T.2W\',5C:[\'jb\']},\'3O+l, 4V+l\':{1s:\'1T.2W\',5C:[\'jg\']},\'3O+k, 4V+k\':{1s:\'T.2z\'},\'3O+6b+7\':{1s:\'2S.2I\',5C:[\'4Z\']},\'3O+6b+8\':{1s:\'2S.2I\',5C:[\'5r\']}},jp:M,1W:[],8F:[],8s:\'<p>&#aB;</p>\',5y:\'&#aB;\',jh:[\'J/eO\',\'J/fq\',\'J/fU\'],95:20,bT:[\'a\',\'1D\',\'b\',\'52\',\'7g\',\'7f\',\'i\',\'em\',\'u\',\'87\',\'8u\',\'41\',\'88\',\'2H\',\'ol\',\'li\'],4P:[\'52\',\'b\',\'u\',\'em\',\'i\',\'14\',\'41\',\'lP\',\'8Y\',\'8Z\',\'7f\',\'7g\',\'6c\',\'C\',\'88\',\'87\'],7Q:[\'P\',\'du\',\'dw\',\'dq\',\'di\',\'dn\',\'dH\',\'lQ\',\'lW\',\'lU\',\'8L\',\'8y\',\'9S\',\'lT\',\'dr\',\'lR\',\'lS\',\'lI\',\'ls\',\'lz\',\'lD\'],8g:[\'aj\',\'8i\',\'8f\',\'4Y\'],gP:{en:{q:\'oa\',63:\'6n eI\',J:\'6n eD\',4o:\'b9\',T:\'bF\',9V:\'6n T\',hH:\'iC T\',5T:\'o6\',2E:\'o8\',gO:\'oe 1i\',c8:\'ok\',14:\'fB\',gu:\'83 1\',gs:\'83 2\',gr:\'83 3\',gv:\'83 4\',gz:\'83 5\',3Y:\'of\',3X:\'oE\',oX:\'oM dk\',oK:\'oJ dk\',5r:\'oG dK\',4Z:\'oH dK\',7p:\'oN\',3d:\'oO\',gm:\'oU\',1Q:\'6n\',2N:\'oS\',hk:\'7X\',oP:\'6n b9\',og:\'81 bm nn\',o5:\'81 bm nk\',np:\'81 bd dY\',nq:\'81 bd dX\',nv:\'7X bd\',nr:\'7X bm\',n6:\'7X b9\',n7:\'n4\',n1:\'n2\',n3:\'81 fF\',n8:\'7X fF\',1E:\'nb\',ha:\'nz\',5R:\'nU\',1S:\'dY\',4l:\'dX\',55:\'g2\',o3:\'eD nE bF\',1i:\'nN\',cH:\'kf\',jN:\'gZ\',jC:\'eI jA fB or kr/kt bF\',1L:\'6n k5\',1f:\'k7\',kd:\'jW\',iZ:\'jU\',jT:\'kD iZ\',jP:\'gL 1L gp\',gy:\'g1 1i ht iN 1S\',hS:\'g2 1i\',hR:\'g1 1i ht iN 4l\',j1:\'jv 1i\',9m:\'6n jx jz\',5M:\'ju\',jK:\'jJ\',cN:\'jL T in 1I 5e\',51:\'jM\',3r:\'jB\',68:\'jI (jF)\',7C:\'iC\'}}};3A.fn=$.3A.5f={3e:{7m:8,8M:46,fQ:40,6e:13,bC:32,ba:27,aG:9,fO:17,fN:91,fI:16,fJ:18,js:37,eY:91},3s:B(el,3C){c.$2B=$(el);c.7Z=7Z++;c.7a=M;c.$5J=M;c.jo(3C);c.iR();c.2E={};$.jE(c.F.8g,c.F.7Q);c.dW=1I 1U(\'^(\'+c.F.8g.2K(\'|\')+\')$\',\'i\');c.1y.eh();c.1C.2D();$.7e(c.F.5D,c.F.jp);c.1R.2n(\'3b\');c.3b=1l;c.2j.hh()},jo:B(3C){c.F=$.7e({},$.7e(1l,{},$.3A.F),c.$2B.1d(),3C)},bH:B(4k){E fp.jG(4k).iD(B(jm){E 1B 4k[jm]==\'B\'})},iR:B(){C 1X=$.3A.bW.1p;2A(C i=0;i<1X;i++){c.iL($.3A.bW[i])}},iL:B(5Y){if(1B c[5Y]==\'1H\')E;c[5Y]=c[5Y]();C 5L=c.bH(c[5Y]);C 1X=5L.1p;2A(C z=0;z<1X;z++){c[5Y][5L[z]]=c[5Y][5L[z]].9l(c)}},1R:B(){E{jD:B(){E $.7e({},c)},jt:B(){E c.$U},jy:B(){E c.$2t},jw:B(){E c.$2B},jO:B(){E c.$2M},kS:B(){E(c.$1q)?c.$1q:M},8k:B(1g){c.1R.hg=1g},6m:B(){E c.1R.hg},2n:B(1c,e,1d){C 2g=c.F[1c+\'kw\'];if($.4U(2g)){E(1B 1d==\'1H\')?2g.69(c,e):2g.69(c,e,1d)}L{E(1B 1d==\'1H\')?e:1d}},hc:B(){c.1R.2n(\'hc\');c.$2B.3G(\'.G\').i4(\'G\');c.$U.3G(\'.G\');c.$U.2R(\'G-U G-1M G-2P\');c.$U.1Z(\'72\');C q=c.14.12();if(c.2j.9j()){c.$2t.3u(c.$2B);c.$2t.1v();c.$2B.2q(q).2z()}L{c.$2t.3u(c.$U);c.$2t.1v();c.$2B.q(q).2z()}if(c.$5J)c.$5J.1v();if(c.$4u)c.$4u.1v();if(c.$6H)c.$6H.1v();$(\'.G-1q-3c\').1v();dT(c.7R)}}},2j:B(){E{hh:B(){c.2j.gW();c.2j.h8();c.2j.hI();c.2j.hp();c.2j.hM()},9j:B(){E(c.$2B[0].1n===\'ky\')},gW:B(){c.$2t=$(\'<1m 1J="G-2t" />\')},hL:B(){c.$2M=$(\'<2M />\').1e(\'1g\',c.2j.gT())},gT:B(){C 1g=c.$2B.1e(\'id\');if(1B(1g)==\'1H\'){1g=\'aN-\'+c.7Z}E 1g},h8:B(){C 1s=(c.2j.9j())?\'2q\':\'q\';c.aN=$.2Z(c.$2B[1s]())},hp:B(){c.$U.1e({\'72\':1l,\'hv\':c.F.b1})},hI:B(){C 1s=(c.2j.9j())?\'hG\':\'hK\';c.2j[1s]()},hG:B(){c.$U=$(\'<1m />\');c.$2M=c.$2B;c.$2t.hO(c.$2B).1u(c.$U).1u(c.$2B);c.$U.2s(\'G-U\');c.$2B.3a()},hK:B(){c.$U=c.$2B;c.2j.hL();c.$2t.hO(c.$U).1u(c.$U).1u(c.$2M);c.$U.2s(\'G-U\');c.$2M.3a()},hM:B(){c.14.1K(c.aN);c.2j.hD();c.2j.hE();if(!c.F.6t){3q($.Y(c.14.cL,c),a1)}},hE:B(){c.2j.gI();c.2j.hx();c.2j.fY();if(c.F.1q){c.F.1q=c.1q.3s();c.1q.2j()}c.V.hl();c.2j.7Y();3q($.Y(c.2l.2D,c),4);c.1R.2n(\'3s\')},hD:B(){$(c.$2M).1e(\'hv\',c.F.b1);if(c.F.1M)c.$U.2s(\'G-1M\');if(c.F.84)c.$U.1e(\'84\',c.F.84);if(c.F.9i)c.$U.1F(\'9i\',c.F.9i);if(c.F.9g)c.$U.1F(\'9g\',c.F.9g)},hx:B(){c.$U.on(\'4T.G\',$.Y(B(e){e=e.6p||e;if(3R.5U===1H||!e.av)E 1l;C 1p=e.av.5E.1p;if(1p===0)E 1l;L{e.2k();if(c.F.hz||c.F.gb){C 5E=e.av.5E;c.1f.iX(5E[0],e)}}3q($.Y(c.1o.4a,c),1);c.1R.2n(\'4T\',e)},c));c.$U.on(\'2f.G\',$.Y(B(e){C 1c=\'2f\';if((c.1R.6m()==\'2f\'||c.1R.6m()==\'4w\')){1c=M}c.1R.8k(1c);c.N.7H();c.1R.2n(\'2f\',e)},c));c.$U.on(\'4v.G\',$.Y(c.4v.3s,c));c.$U.on(\'1w.G\',$.Y(c.1w.3s,c));c.$U.on(\'2m.G\',$.Y(c.2m.3s,c));if($.4U(c.F.gk)){c.$2M.on(\'1w.G-2M\',$.Y(c.F.gk,c))}if($.4U(c.F.gh)){c.$2M.on(\'2m.G-2M\',$.Y(c.F.gh,c))}if($.4U(c.F.g9)){c.$U.on(\'28.G\',$.Y(c.F.g9,c))}C b3;$(1a).on(\'bV\',B(e){b3=$(e.1Y)});c.$U.on(\'fZ.G\',$.Y(B(e){if(c.7a)E;C $el=$(b3);if(!$el.3h(\'G-1q, G-1x\')&&!$el.is(\'#G-V\')&&$el.eE(\'.G-1q, .G-1x, #G-V\').2a()===0){c.N.7H();if($.4U(c.F.kv))c.1R.2n(\'fZ\',e)}},c))},fY:B(){c.3x.9b();c.2P.9b();if(c.F.28)3q($.Y(c.28.2O,c),7h);if(c.F.fX)3q($.Y(c.28.4c,c),7h)},7Y:B(){if(!c.F.7Y)E;if(!9h)E;$.1z(c.F.7Y,$.Y(B(i,s){if(9h[s]){if(!$.4U(9h[s]))E;c[s]=9h[s]();C 5L=c.bH(c[s]);C 1X=5L.1p;2A(C z=0;z<1X;z++){c[s][5L[z]]=c[s][5L[z]].9l(c)}if($.4U(c[s].3s))c[s].3s()}},c))},gI:B(){if(!c.N.23(\'7c\'))E;4G{1a.3V(\'ko\',M,M);1a.3V(\'kn\',M,M)}4F(e){}}}},1C:B(){E{2D:B(){c.F.b7=c.F.gP[c.F.1C]},12:B(1g){E(1B c.F.b7[1g]!=\'1H\')?c.F.b7[1g]:\'\'}}},1q:B(){E{3s:B(){E{q:{1E:c.1C.12(\'q\'),1s:\'14.2I\'},2E:{1E:c.1C.12(\'2E\'),1x:{p:{1E:c.1C.12(\'gO\'),1s:\'O.2W\'},1N:{1E:c.1C.12(\'c8\'),1s:\'O.2W\'},2V:{1E:c.1C.12(\'14\'),1s:\'O.2W\'},h1:{1E:c.1C.12(\'gu\'),1s:\'O.2W\'},h2:{1E:c.1C.12(\'gs\'),1s:\'O.2W\'},h3:{1E:c.1C.12(\'gr\'),1s:\'O.2W\'},h4:{1E:c.1C.12(\'gv\'),1s:\'O.2W\'},h5:{1E:c.1C.12(\'gz\'),1s:\'O.2W\'}}},3Y:{1E:c.1C.12(\'3Y\'),1s:\'1T.2W\'},3X:{1E:c.1C.12(\'3X\'),1s:\'1T.2W\'},5M:{1E:c.1C.12(\'5M\'),1s:\'1T.2W\'},51:{1E:c.1C.12(\'51\'),1s:\'1T.2W\'},5r:{1E:\'&kq; \'+c.1C.12(\'5r\'),1s:\'2S.2I\'},4Z:{1E:\'1. \'+c.1C.12(\'4Z\'),1s:\'2S.2I\'},7p:{1E:\'< \'+c.1C.12(\'7p\'),1s:\'3d.9M\'},3d:{1E:\'> \'+c.1C.12(\'3d\'),1s:\'3d.9N\'},J:{1E:c.1C.12(\'J\'),1s:\'J.2z\'},1L:{1E:c.1C.12(\'1L\'),1s:\'1L.2z\'},T:{1E:c.1C.12(\'T\'),1x:{T:{1E:c.1C.12(\'9V\'),1s:\'T.2z\'},5T:{1E:c.1C.12(\'5T\'),1s:\'T.5T\'}}},3r:{1E:c.1C.12(\'3r\'),1x:{1S:{1E:c.1C.12(\'gy\'),1s:\'3r.1S\'},55:{1E:c.1C.12(\'hS\'),1s:\'3r.55\'},4l:{1E:c.1C.12(\'hR\'),1s:\'3r.4l\'},8e:{1E:c.1C.12(\'j1\'),1s:\'3r.8e\'}}},9m:{1E:c.1C.12(\'9m\'),1s:\'4D.1Q\'}}},2j:B(){c.1q.ib();c.1q.i3();c.1q.i7();if(c.F.3D.1p===0)E;c.$1q=c.1q.j7();c.1q.iJ();c.1q.1u();c.1q.iW();c.1q.iQ();c.1q.jr();c.1q.je();if(c.F.9D){c.$U.on(\'jl.G 2m.G 28.G\',$.Y(c.2l.3D,c))}},j7:B(){E $(\'<2H>\').2s(\'G-1q\').1e(\'id\',\'G-1q-\'+c.7Z)},iW:B(){$.1z(c.F.1q.2E.1x,$.Y(B(i,s){if($.3L(i,c.F.2E)==-1)8j c.F.1q.2E.1x[i]},c))},iQ:B(){$.1z(c.F.3D,$.Y(B(i,2h){if(!c.F.1q[2h])E;if(c.F.8o===M&&2h===\'1L\')E 1l;if(c.F.5A===M&&2h===\'J\')E 1l;C 3j=c.F.1q[2h];c.$1q.1u($(\'<li>\').1u(c.1r.2j(2h,3j)))},c))},1u:B(){if(c.F.9r){c.$1q.2s(\'G-1q-kF\');$(c.F.9r).q(c.$1q)}L{c.$2t.4N(c.$1q)}},je:B(){if(c.N.5x())E;if(c.F.9r)E;if(!c.F.jf)E;c.1q.bt();$(c.F.9q).on(\'7q.G\',$.Y(c.1q.bt,c))},jr:B(){c.$1q.1h(\'a\').1e(\'84\',\'-1\')},iJ:B(){if(c.N.5x()&&c.F.i8){c.$1q.2s(\'G-1q-6N\')}},i7:B(){if(c.F.ih)E;C 6j=c.F.3D.4z(\'q\');c.F.3D.93(6j,1)},ib:B(){if(c.F.aY.1p===0)E;$.1z(c.F.aY,$.Y(B(i,s){C 6j=c.F.3D.4z(s);c.F.3D.93(6j,1)},c))},i3:B(){if(!c.N.5x()&&c.F.aQ.1p===0)E;$.1z(c.F.aQ,$.Y(B(i,s){C 6j=c.F.3D.4z(s);c.F.3D.93(6j,1)},c))},bt:B(){C 3U=$(c.F.9q).3U();C 5W=1;if(c.F.9q===1a){5W=c.$2t.4t().2T}if(3U>5W){c.1q.hY(3U,5W)}L{c.1q.hZ()}},hY:B(3U,5W){C 2T=3U-5W;C 1S=0;C 5z=5W+c.$2t.2Q()+30;C 2v=c.$2t.aA();c.$1q.2s(\'1q-7o-2t\');c.$1q.1F({4b:\'7W\',2v:2v,2T:2T+\'3I\',1S:1S});c.1q.iB();c.$1q.1F(\'ij\',(3U<5z)?\'ik\':\'aT\')},hZ:B(){c.$1q.1F({4b:\'i0\',2v:\'8U\',2T:0,1S:0,ij:\'ik\'});c.1q.iH();c.$1q.2R(\'1q-7o-2t\')},iB:B(){C 2Y=c;$(\'.G-1x\').1z(B(){$(c).1F({4b:\'7o\',2T:2Y.$1q.6S()})})},iH:B(){C 2Y=c;$(\'.G-1x\').1z(B(){C 2T=(2Y.$1q.6S()+2Y.$1q.4t().2T)+\'3I\';$(c).1F({4b:\'7W\',2T:2T})})}}},1r:B(){E{2j:B(2h,3j){C $1r=$(\'<a 2d="#" 1J="3i-85 3i-\'+2h+\'" 3N="\'+2h+\'" />\');if(3j.1s||3j.47||3j.1x){$1r.on(\'5N 2f\',$.Y(B(e){if($1r.3h(\'G-1r-8b\'))E M;C 1c=\'1s\';C 2g=3j.1s;if(3j.47){1c=\'47\';2g=3j.47}L if(3j.1x){1c=\'1x\';2g=M}c.1r.9n(e,2h,1c,2g)},c))}if(3j.1x){C $1x=$(\'<1m 1J="G-1x G-1x-2t-\'+2h+\'" 1t="6L: 5R;">\');$1r.1d(\'1x\',$1x);c.1x.2j(2h,$1x,3j.1x)}if(c.N.bi()){c.1r.fV($1r,2h,3j.1E)}E $1r},fV:B($1r,1g,1E){C $3c=$(\'<1b>\').2s(\'G-1q-3c G-1q-3c-\'+1g).3a().q(1E);$3c.ac(\'3l\');$1r.on(\'dp\',B(){if($(c).3h(\'G-1r-8b\'))E;C 2X=$1r.4t();C 2Q=$1r.6S();C 2v=$1r.aA();$3c.2z();$3c.1F({2T:(2X.2T+2Q)+\'3I\',1S:(2X.1S+2v/2-$3c.aA()/2)+\'3I\'})});$1r.on(\'dm\',B(){$3c.3a()})},9n:B(e,2h,1c,2g){e.2k();if(c.N.23(\'2F\'))e.kh=M;if(1c==\'47\'){c.1T.2W(2g)}L if(1c==\'1x\'){c.1x.2z(e,2h)}L{C 1s;if($.4U(2g)){2g.69(c,2h);c.2l.3D(e,2h)}L if(2g.3z(/\\./)!=\'-1\'){1s=2g.3K(\'.\');if(1B c[1s[0]]!=\'1H\'){c[1s[0]][1s[1]](2h);c.2l.3D(e,2h)}}L{c[2g](2h);c.2l.3D(e,2h)}}},12:B(1j){E c.$1q.1h(\'a.3i-\'+1j)},7t:B(1j){c.1r.12(1j).2s(\'G-8a\')},dg:B(1j){c.1r.12(1j).2R(\'G-8a\')},hF:B(1j){if(1B 1j==\'1H\'){c.$1q.1h(\'a.3i-85\').2R(\'G-8a\')}L{c.$1q.1h(\'a.3i-85\').5S(\'.3i-\'+1j).2R(\'G-8a\')}},do:B(){c.$1q.1h(\'a.3i-85\').5S(\'a.3i-q\').2R(\'G-1r-8b\')},cY:B(){c.$1q.1h(\'a.3i-85\').5S(\'a.3i-q\').2s(\'G-1r-8b\')},ki:B(1j,9p){c.1r.12(1j).2s(\'3i-\'+9p)},ke:B(1j,9p){c.1r.12(1j).2R(\'3i-\'+9p)},k8:B(1j,1g){C $1r=c.1r.12(1j);$1r.2R(\'G-25-J\').2s(\'fa-G-25\');$1r.q(\'<i 1J="fa \'+1g+\'"></i>\')},cO:B($25,2g){C 1c=(2g==\'1x\')?\'1x\':\'1s\';C 1j=$25.1e(\'3N\');$25.on(\'5N 2f\',$.Y(B(e){if($25.3h(\'G-1r-8b\'))E M;c.1r.9n(e,1j,1c,2g)},c))},k9:B($25,1x){C 1j=$25.1e(\'3N\');c.1r.cO($25,\'1x\');C $1x=$(\'<1m 1J="G-1x G-1x-2t-\'+1j+\'" 1t="6L: 5R;">\');$25.1d(\'1x\',$1x);if(1x){c.1x.2j(1j,$1x,1x)}E $1x},cU:B(1j,1E){if(!c.F.1q)E;C 25=c.1r.2j(1j,{1E:1E});25.2s(\'G-25-J\');c.$1q.1u($(\'<li>\').1u(25));E 25},ka:B(1j,1E){if(!c.F.1q)E;C 25=c.1r.2j(1j,{1E:1E});c.$1q.4N($(\'<li>\').1u(25));E 25},kb:B(iw,1j,1E){if(!c.F.1q)E;C 25=c.1r.2j(1j,{1E:1E});C $25=c.1r.12(iw);if($25.2a()!==0)$25.1k().3u($(\'<li>\').1u(25));L c.$1q.1u($(\'<li>\').1u(25));E 25},kc:B(da,1j,1E){if(!c.F.1q)E;C 25=c.1r.2j(1j,{1E:1E});C $25=c.1r.12(da);if($25.2a()!==0)$25.1k().cJ($(\'<li>\').1u(25));L c.$1q.1u($(\'<li>\').1u(25));E 25},1v:B(1j){c.1r.12(1j).1v()}}},1x:B(){E{2j:B(1g,$1x,cn){if(1g==\'2E\'&&c.F.cm){$.1z(c.F.cm,$.Y(B(i,s){C 1g=s.Q;if(1B s.1J!=\'1H\'){1g=1g+\'-\'+s.1J}s.1c=(c.N.6y(s.Q))?\'O\':\'1T\';C 1s=(s.1c==\'1T\')?\'1T.2E\':\'O.2E\';if(c.F.1M&&s.1c==\'O\'&&s.Q==\'p\')E;c.2E[1g]={Q:s.Q,1t:s.1t,\'1J\':s.1J,1e:s.1e,1d:s.1d};cn[1g]={1s:1s,1E:s.1E}},c))}$.1z(cn,$.Y(B(2h,3j){C $3g=$(\'<a 2d="#" 1J="G-1x-\'+2h+\'">\'+3j.1E+\'</a>\');if(1g==\'2E\')$3g.2s(\'G-2E-\'+2h);$3g.on(\'2f\',$.Y(B(e){C 1c=\'1s\';C 2g=3j.1s;if(3j.47){1c=\'47\';2g=3j.47}L if(3j.1x){1c=\'1x\';2g=3j.1x}c.1r.9n(e,2h,1c,2g)},c));$1x.1u($3g)},c))},2z:B(e,1j){if(!c.F.6t){e.2k();E M}C $1r=c.1r.12(1j);C $1x=$1r.1d(\'1x\').ac(1a.3l);if($1r.3h(\'6X\')){c.1x.9o()}L{c.1x.9o();c.1R.2n(\'kk\',{1x:$1x,1j:1j,1r:$1r});c.1r.7t(1j);$1r.2s(\'6X\');C 8c=$1r.4t();C c7=$1x.2v();if((8c.1S+c7)>$(1a).2v()){8c.1S-=c7}C 1S=8c.1S+\'3I\';if(c.$1q.3h(\'1q-7o-2t\')){$1x.1F({4b:\'7o\',1S:1S,2T:c.$1q.6S()}).2z()}L{C 2T=($1r.6S()+8c.2T)+\'3I\';$1x.1F({4b:\'7W\',1S:1S,2T:2T}).2z()}c.1R.2n(\'kg\',{1x:$1x,1j:1j,1r:$1r})}$(1a).dj(\'2f\',$.Y(c.1x.3a,c));c.$U.dj(\'2f\',$.Y(c.1x.3a,c));$1x.on(\'dp\',B(){$(\'q\').1F(\'6N\',\'aT\')});$1x.on(\'dm\',B(){$(\'q\').1F(\'6N\',\'\')});e.fk()},9o:B(){c.$1q.1h(\'a.6X\').2R(\'G-8a\').2R(\'6X\');$(\'.G-1x\').3a();c.1R.2n(\'k6\')},3a:B(e){C $1x=$(e.1Y);if(!$1x.3h(\'6X\')){$1x.2R(\'6X\');c.1x.9o()}}}},14:B(){E{1K:B(q){q=$.2Z(q.4E());q=c.1o.dJ(q);c.$U.q(q);c.14.1P();3q($.Y(c.1W.cU,c),15);if(c.3b===M)c.2l.2D()},12:B(){C 14=c.$2M.2q();14=c.2p.12(14);E 14},1P:B(){3q($.Y(c.14.dC,c),10)},dC:B(){C q=c.$U.q();if(c.14.cP&&c.14.cP==q){E}c.14.cP=q;q=c.1R.2n(\'jV\',q);q=c.1o.aR(q);c.$2M.2q(q);c.1R.2n(\'1P\',q);if(c.3b===M){c.1R.2n(\'j3\',q)}c.3b=M;c.3x.dU()},2I:B(){if(c.F.6t){c.14.cL()}L{c.14.db()}},cL:B(){c.14.cz=c.22.g0();C 7q=$(3R).3U();C 2Q=c.$U.6S();c.$U.3a();C q=c.$2M.2q();c.dc=c.1o.co(q);q=c.2p.12(q);c.$2M.2q(q).2Q(2Q).2z().28();c.$2M.on(\'1w.G-2M-de\',c.14.d1);$(3R).3U(7q);c.F.6t=M;c.1r.cY();c.1r.7t(\'q\');c.1R.2n(\'92\',q)},db:B(){if(c.F.6t)E;C q=c.$2M.3a().2q();if(c.dc!==c.1o.co(q)){c.14.1K(q)}c.$U.2z();if(!c.N.4m(q)){c.2P.1v()}c.$U.28();c.22.aI(c.14.cz.x,c.14.cz.y);if(c.28.9w()){c.28.2O()}c.$2M.3G(\'1w.G-2M-de\');c.1r.do();c.1r.dg(\'q\');c.2l.2D();c.F.6t=1l},d1:B(e){if(e.3e!==9)E 1l;C $el=c.$2M;C 3b=$el.12(0).dD;$el.2q($el.2q().ah(0,3b)+"\\t"+$el.2q().ah($el.12(0).dB));$el.12(0).dD=$el.12(0).dB=3b+1;E M}}},1o:B(){E{dJ:B(q){q=c.1o.fD(q);q=q.I(/\\$/g,\'&#36;\');q=q.I(/”/g,\'"\');q=q.I(/‘/g,\'\\\'\');q=q.I(/’/g,\'\\\'\');if(c.F.7i)q=c.1o.7i(q);if(c.F.1M)q=c.1o.8X(q);q=c.1o.cd(q);q=q.I(/<3w(.*?)1t="(.*?)"(.*?)>([\\w\\W]*?)<\\/3w>/gi,\'<1b 1t="$2">$4</1b>\');q=q.I(/<3w(.*?[^<])>/gi,\'\');q=q.I(/<\\/3w>/gi,\'\');q=c.1y.2D(q);if(c.F.2o)q=c.2o.2D(q);q=c.1o.8t(q);q=c.1o.c9(q);E q},aR:B(q){q=q.I(/[\\7E-\\aw\\a3]/g,\'\');q=q.I(/&#aB;/gi,\'\');q=q.I(/&5i;/gi,\' \');if(q.3z(/^<p>(||\\s||&5i;)<\\/p>$/i)!=-1){E\'\'}q=c.1o.ej(q);C dI={\'\\jR\':\'&jS;\',\'\\jX\':\'&jY;\',\'\\k3\':\'&k4;\',\'\\k2\':\'&k1;\',\'\\jZ\':\'&k0;\'};$.1z(dI,B(i,s){q=q.I(1I 1U(i,\'g\'),s)});q=q.I(1I 1U(\'<br\\\\s?/?></li>\',\'gi\'),\'</li>\');q=q.I(1I 1U(\'</li><br\\\\s?/?>\',\'gi\'),\'</li>\');q=q.I(1I 1U(\'<1m(.*?) 1d-8d="G"(.*?[^>])>\',\'gi\'),\'<1m$1$2>\');q=q.I(1I 1U(\'<(.*?) 1d-3v="G"(.*?[^>])>\',\'gi\'),\'<$1$2>\');q=q.I(1I 1U(\'<1b(.*?) 3N="(.*?)"(.*?[^>])>\',\'gi\'),\'<1b$1$3>\');q=q.I(1I 1U(\'<1D(.*?) 3N="(.*?)"(.*?[^>])>\',\'gi\'),\'<1D$1$3>\');q=q.I(1I 1U(\'<1b 1J="G-ap-3o">(.*?)</1b>\',\'gi\'),\'$1\');q=q.I(/ 1d-2N-2c="(.*?[^>])"/gi,\'\');q=q.I(/<1b(.*?)id="G-J-2t"(.*?[^>])>([\\w\\W]*?)<1D(.*?)><\\/1b>/gi,\'$3<1D$4>\');q=q.I(/<1b(.*?)id="G-J-86"(.*?[^>])>(.*?)<\\/1b>/gi,\'\');q=q.I(/<1b(.*?)id="G-J-4I"(.*?[^>])>(.*?)<\\/1b>/gi,\'\');q=c.1y.2D(q);if(c.F.dy){q=q.I(/<a(.*?)3N="dx"(.*?[^>])>/gi,\'<a$1$2>\');q=q.I(/<a(.*?[^>])>/gi,\'<a$1 3N="dx">\')}q=q.I(/<(.*?) 1d-G-Q="(.*?)"(.*?[^>])>/gi,\'<$1$3>\');q=q.I(/<(.*?) 1d-G-1J="(.*?)"(.*?[^>])>/gi,\'<$1$3>\');q=q.I(/<(.*?) 1d-G-1t="(.*?)"(.*?[^>])>/gi,\'<$1$3>\');q=q.I(1I 1U(\'<(.*?) 1d-3v="G"(.*?[^>])>\',\'gi\'),\'<$1$2>\');q=q.I(1I 1U(\'<(.*?) 1d-3v="G">\',\'gi\'),\'<$1>\');E q},8v:B(q,7L){q=$.2Z(q);q=q.I(/\\$/g,\'&#36;\');q=q.I(/”/g,\'"\');q=q.I(/“/g,\'"\');q=q.I(/‘/g,\'\\\'\');q=q.I(/’/g,\'\\\'\');q=q.I(/<1b 1J="dv-kl-3o">&5i;<\\/1b>/gi,\' \');q=q.I(/<1b 1J="dv-5e-1b"[^>]*>\\t<\\/1b>/gi,\'\\t\');q=q.I(/<1b[^>]*>(\\s|&5i;)<\\/1b>/gi,\' \');if(c.F.ds){E c.1o.9H(q)}if(!c.N.6M()&&1B 7L==\'1H\'){if(c.N.4Q([\'dr\',\'A\'])){E c.1o.9H(q,M)}if(c.N.4Q(\'aj\')){E c.1o.dR(q)}if(c.N.4Q([\'9S\',\'du\',\'dw\',\'dq\',\'di\',\'dn\',\'dH\'])){q=c.1o.dP(q);if(!c.N.23(\'2F\')){C O=c.K.43();if(O&&O.1n==\'P\'){q=q.I(/<1D(.*?)>/gi,\'<p><1D$1></p>\')}}E q}if(c.N.4Q([\'8y\'])){q=c.1o.9e(q,\'3J\');if(c.F.1M)q=c.1o.8X(q);q=c.1o.eJ(q);E q}if(c.N.4Q([\'4Y\'])){E c.1o.9e(q,\'li\')}}q=c.1o.fL(q,7L);if(!c.1o.8w){if(c.F.1M)q=c.1o.8X(q);if(c.F.7i)q=c.1o.7i(q);q=c.1o.cd(q)}q=c.1o.fM(q);q=c.1o.dL(q);q=c.1o.dM(q);q=c.1o.9e(q,\'cZ\');if(!c.1o.8w&&c.F.2o){q=c.2o.2D(q)}q=c.1o.e0(q);q=c.1o.fr(q);q=c.1o.dN(q);q=c.1o.c9(q);E q},dL:B(q){q=q.I(/<!--[\\s\\S]*?-->/gi,\'\');q=q.I(/<1t[^>]*>[\\s\\S]*?<\\/1t>/gi,\'\');q=q.I(/<1D(.*?)v:km=(.*?)>/gi,\'\');q=q.I(/3F="1L\\:\\/\\/(.*?)"/,\'3F=""\');q=q.I(/<p(.*?)1J="kI"([\\w\\W]*?)<\\/p>/gi,\'<2H><li$2</li>\');q=q.I(/<p(.*?)1J="kJ"([\\w\\W]*?)<\\/p>/gi,\'<li$2</li>\');q=q.I(/<p(.*?)1J="kH"([\\w\\W]*?)<\\/p>/gi,\'<li$2</li></2H>\');q=q.I(/<p(.*?)1J="kG"([\\w\\W]*?)<\\/p>/gi,\'<2H><li$2</li></2H>\');q=q.I(/·/g,\'\');q=q.I(/<p 1J="kE(.*?)"/gi,\'<p\');q=q.I(/ 1J=\\"(dh[^\\"]*)\\"/gi,"");q=q.I(/ 1J=(dh\\w+)/gi,"");q=q.I(/<o:p(.*?)>([\\w\\W]*?)<\\/o:p>/gi,\'$2\');if(c.F.dE){q=q.I(/(\\s|&5i;)+/g,\' \')}q=q.I(/\\n/g,\' \');q=q.I(/<p>\\n?<li>/gi,\'<li>\');E q},dM:B(q){q=q.I(/<b\\kK="d0-92-2J(.*?)">([\\w\\W]*?)<\\/b>/gi,"$2");q=q.I(/<b(.*?)id="kL-d0-kQ(.*?)">([\\w\\W]*?)<\\/b>/gi,"$3");q=q.I(/<1b[^>]*(3w-1t: 3X; 3w-77: 3Y|3w-77: 3Y; 3w-1t: 3X)[^>]*>/gi,\'<1b 1t="3w-77: 3Y;"><1b 1t="3w-1t: 3X;">\');q=q.I(/<1b[^>]*3w-1t: 3X[^>]*>/gi,\'<1b 1t="3w-1t: 3X;">\');q=q.I(/<1b[^>]*3w-77: 3Y[^>]*>/gi,\'<1b 1t="3w-77: 3Y;">\');q=q.I(/<1b[^>]*1i-9f: 51[^>]*>/gi,\'<1b 1t="1i-9f: 51;">\');q=q.I(/<1D>/gi,\'\');q=q.I(/\\n{3,}/gi,\'\\n\');q=q.I(/<3w(.*?)>([\\w\\W]*?)<\\/3w>/gi,\'$2\');q=q.I(/<p><p>/gi,\'<p>\');q=q.I(/<\\/p><\\/p>/gi,\'</p>\');q=q.I(/<li>(\\s*|\\t*|\\n*)<p>/gi,\'<li>\');q=q.I(/<\\/p>(\\s*|\\t*|\\n*)<\\/li>/gi,\'</li>\');q=q.I(/<\\/p>\\s<p/gi,\'<\\/p><p\');q=q.I(/<1D 3F="6Q-kM-2c\\:\\/\\/(.*?)"(.*?)>/gi,\'\');q=q.I(/<p>•([\\w\\W]*?)<\\/p>/gi,\'<li>$1</li>\');if(c.N.23(\'7c\')){q=q.I(/<br\\s?\\/?>$/gi,\'\')}E q},9e:B(q,1c){C 2i=[\'1b\',\'a\',\'2V\',\'1N\',\'87\',\'em\',\'52\',\'14\',\'8Z\',\'6c\',\'bh\',\'88\',\'C\',\'8Y\',\'b5\',\'7f\',\'7g\',\'b\',\'i\',\'u\',\'41\',\'ol\',\'2H\',\'li\',\'dl\',\'dt\',\'dd\',\'p\',\'br\',\'63\',\'7V\',\'6W\',\'7K\',\'4k\',\'1D\',\'4o\',\'3J\',\'7n\',\'5Q\',\'aO\',\'aL\',\'aP\',\'h1\',\'h2\',\'h3\',\'h4\',\'h5\',\'h6\'];C bu=M;C bK=[[\'a\',\'*\'],[\'1D\',[\'3F\',\'7b\']],[\'1b\',[\'1J\',\'3N\',\'1d-3v\']],[\'63\',\'*\'],[\'7V\',\'*\'],[\'6W\',\'*\'],[\'4k\',\'*\'],[\'7K\',\'*\'],[\'92\',\'*\']];if(1c==\'cZ\'){bu=[\'p\',\'1b\',\'h1\',\'h2\',\'h3\',\'h4\',\'h5\',\'h6\'];bK=[[\'4o\',\'1J\'],[\'3J\',[\'kC\',\'ks\']],[\'a\',\'*\'],[\'1D\',[\'3F\',\'7b\',\'1d-G-8r-J\']],[\'1b\',[\'1J\',\'3N\',\'1d-3v\']],[\'63\',\'*\'],[\'7V\',\'*\'],[\'6W\',\'*\'],[\'4k\',\'*\'],[\'7K\',\'*\'],[\'92\',\'*\']]}L if(1c==\'3J\'){2i=[\'2H\',\'ol\',\'li\',\'1b\',\'a\',\'87\',\'em\',\'52\',\'14\',\'8Z\',\'6c\',\'88\',\'C\',\'8Y\',\'b5\',\'7f\',\'7g\',\'b\',\'i\',\'u\',\'41\',\'ol\',\'2H\',\'li\',\'dl\',\'dt\',\'dd\',\'br\',\'63\',\'7V\',\'6W\',\'7K\',\'4k\',\'1D\',\'h1\',\'h2\',\'h3\',\'h4\',\'h5\',\'h6\']}L if(1c==\'li\'){2i=[\'2H\',\'ol\',\'li\',\'1b\',\'a\',\'87\',\'em\',\'52\',\'14\',\'8Z\',\'6c\',\'88\',\'C\',\'8Y\',\'b5\',\'7f\',\'7g\',\'b\',\'i\',\'u\',\'41\',\'br\',\'63\',\'7V\',\'6W\',\'7K\',\'4k\',\'1D\']}C 3C={4y:M,4J:2i,62:1l,ku:1l,1Z:M,5c:bK,3S:bu};E c.1y.2D(q,3C)},dN:B(q){q=q.I(/<(p|h[1-6])>(|\\s|\\n|\\t|<br\\s?\\/?>)<\\/(p|h[1-6])>/gi,\'\');if(!c.F.1M)q=q.I(/<br>$/i,\'\');E q},fr:B(q){q=q.I(/<1b>(.*?)<\\/1b>/gi,\'$1\');q=q.I(/<1b[^>]*>\\s|&5i;<\\/1b>/gi,\' \');E q},fM:B(q){if(!c.N.23(\'2F\'))E q;C 57=$.2Z(q);if(57.3z(/^<a(.*?)>(.*?)<\\/a>$/i)===0){q=q.I(/^<a(.*?)>(.*?)<\\/a>$/i,"$2")}E q},fL:B(q,7L){c.1o.8w=M;if(!c.N.6M()&&1B 7L==\'1H\'){C 1O=c.F.8g.2K(\'|\').I(\'P|\',\'\').I(\'8L|\',\'\');C fP=q.2e(1I 1U(\'</(\'+1O+\')>\',\'gi\'));C 8W=q.2e(/<\\/(p|1m)>/gi);if(!fP&&(8W===3n||(8W&&8W.1p<=1))){C fz=q.2e(/<br\\s?\\/?>/gi);C fA=q.2e(/<1D(.*?[^>])>/gi);if(!fz&&!fA){c.1o.8w=1l;q=q.I(/<\\/?(p|1m)(.*?)>/gi,\'\')}}}E q},kA:B(3p,5h){5h=(((5h||\'\')+\'\').2L().2e(/<[a-z][a-8T-9]*>/g)||[]).2K(\'\');C 2i=/<\\/?([a-z][a-8T-9]*)\\b[^>]*>/gi;E 3p.I(2i,B($0,$1){E 5h.4z(\'<\'+$1.2L()+\'>\')>-1?$0:\'\'})},fD:B(q){C 2V=q.2e(/<(2V|14)(.*?)>([\\w\\W]*?)<\\/(2V|14)>/gi);if(2V!==3n){$.1z(2V,$.Y(B(i,s){C 2x=s.2e(/<(2V|14)(.*?)>([\\w\\W]*?)<\\/(2V|14)>/i);2x[3]=2x[3].I(/<br\\s?\\/?>/g,\'\\n\');2x[3]=2x[3].I(/&5i;/g,\' \');if(c.F.6s){2x[3]=2x[3].I(/\\t/g,8D(c.F.6s+1).2K(\' \'))}2x[3]=c.1o.cy(2x[3]);2x[3]=2x[3].I(/\\$/g,\'&#36;\');q=q.I(s,\'<\'+2x[1]+2x[2]+\'>\'+2x[3]+\'</\'+2x[1]+\'>\')},c))}E q},cD:B(q){q=q.I(/<br\\s?\\/?>|<\\/H[1-6]>|<\\/p>|<\\/1m>|<\\/li>|<\\/3J>/gi,\'\\n\');C 57=1a.3m(\'1m\');57.3P=q;q=57.kB||57.kz;E $.2Z(q)},9H:B(q,2o){q=c.1o.cD(q);q=q.I(/\\n/g,\'<br />\');if(c.F.2o&&1B 2o==\'1H\'){q=c.1o.2o(q)}E q},dR:B(q){q=q.I(/<1D(.*?) 1t="(.*?)"(.*?[^>])>/gi,\'<1D$1$3>\');q=q.I(/<1D(.*?)>/gi,\'&lt;1D$1&gt;\');q=c.1o.cD(q);if(c.F.6s){q=q.I(/\\t/g,8D(c.F.6s+1).2K(\' \'))}q=c.1o.cy(q);E q},dP:B(q){q=q.I(/<1D(.*?)>/gi,\'[1D$1]\');q=q.I(/<(.*?)>/gi,\'\');q=q.I(/\\[1D(.*?)\\]/gi,\'<1D$1>\');E q},kx:B(q){q=q.I(/<a(.*?)2d="(.*?)"(.*?)>([\\w\\W]*?)<\\/a>/gi,\'[a 2d="$2"]$4[/a]\');q=q.I(/<1D(.*?)>/gi,\'[1D$1]\');q=q.I(/<(.*?)>/gi,\'\');q=q.I(/\\[a 2d="(.*?)"\\]([\\w\\W]*?)\\[\\/a\\]/gi,\'<a 2d="$1">$2</a>\');q=q.I(/\\[1D(.*?)\\]/gi,\'<1D$1>\');E q},cy:B(5k){5k=5o(5k).I(/&bD;/g,\'&\').I(/&lt;/g,\'<\').I(/&gt;/g,\'>\').I(/&dV;/g,\'"\');E 5k.I(/&/g,\'&bD;\').I(/</g,\'&lt;\').I(/>/g,\'&gt;\').I(/"/g,\'&dV;\')},e0:B(q){if(c.N.23(\'2F\'))E q;C 1m=1a.3m(\'1m\');1m.3P=q;c.1o.bQ($(1m));q=1m.3P;$(1m).1v();E q},4a:B(){if(c.N.23(\'2F\'))E;c.1o.bQ(c.$U);C c1=c.$U.1h(\'h1, h2, h3, h4, h5, h6\');c1.1h(\'1b\').1Z(\'1t\');c1.1h(c.F.bT.2K(\', \')).1Z(\'1t\');c.14.1P()},bQ:B($U){$U.1h(c.F.bT.2K(\', \')).1Z(\'1t\');$U.1h(\'1b\').5S(\'[1d-3v="G"]\').1Z(\'1t\');$U.1h(\'1b[1d-3v="G"], 1D[1d-3v="G"]\').1z(B(i,s){C $s=$(s);$s.1e(\'1t\',$s.1e(\'3N\'))})},8t:B(q){if(c.N.23(\'2F\'))E q;q=q.I(1I 1U(\'<1D(.*?[^>])>\',\'gi\'),\'<1D$1 1d-3v="G">\');q=q.I(1I 1U(\'<1b(.*?)>\',\'gi\'),\'<1b$1 1d-3v="G">\');C 4s=q.2e(1I 1U(\'<(1b|1D)(.*?)1t="(.*?)"(.*?[^>])>\',\'gi\'));if(4s){C 1X=4s.1p;2A(C i=0;i<1X;i++){C eA=4s[i].I(/1t="(.*?)"/i,\'1t="$1" 3N="$1"\');q=q.I(1I 1U(4s[i],\'gi\'),eA)}}E q},c9:B(q){C $1m=$(\'<1m />\').q(q);C 2i=c.F.4P;2i.3f(\'1b\');$1m.1h(2i.2K(\',\')).1z(B(){C $el=$(c);C Q=c.1n.2L();$el.1e(\'1d-G-Q\',Q);if(Q==\'1b\'){if($el.1e(\'1t\'))$el.1e(\'1d-G-1t\',$el.1e(\'1t\'));L if($el.1e(\'1J\'))$el.1e(\'1d-G-1J\',$el.1e(\'1J\'))}});q=$1m.q();$1m.1v();E q},9W:B(){c.$U.1h(\'li\').1z(B(i,s){C $2w=$(s).2w();if($2w.1p!==0&&($2w[0].1n==\'8i\'||$2w[0].1n==\'8f\')){$(s).1u($2w)}})},co:B(q){q=q.I(/\\n/g,\'\');q=q.I(/[\\t]*/g,\'\');q=q.I(/\\n\\s*\\n/g,"\\n");q=q.I(/^[\\s\\n]*/g,\' \');q=q.I(/[\\s\\n]*$/g,\' \');q=q.I(/>\\s{2,}</g,\'> <\');q=q.I(/\\n\\n/g,"\\n");q=q.I(/[\\7E-\\aw\\a3]/g,\'\');E q},7i:B(q){if(c.F.1M){q=q.I(/<1m><br\\s?\\/?><\\/1m>/gi,\'<br />\');q=q.I(/<1m(.*?)>([\\w\\W]*?)<\\/1m>/gi,\'$2<br />\')}L{q=q.I(/<1m(.*?)>([\\w\\W]*?)<\\/1m>/gi,\'<p$1>$2</p>\')}E q},eJ:B(q){q=q.I(/<1m\\s(.*?)>/gi,\'<p>\');q=q.I(/<1m><br\\s?\\/?><\\/1m>/gi,\'<br /><br />\');q=q.I(/<1m>([\\w\\W]*?)<\\/1m>/gi,\'$1<br /><br />\');E q},8X:B(q){q=q.I(/<p\\s(.*?)>/gi,\'<p>\');q=q.I(/<p><br\\s?\\/?><\\/p>/gi,\'<br />\');q=q.I(/<p>([\\w\\W]*?)<\\/p>/gi,\'$1<br /><br />\');q=q.I(/(<br\\s?\\/?>){1,}\\n?<\\/1N>/gi,\'</1N>\');E q},cd:B(q){E q.I(/<5u(.*?)>([\\w\\W]*?)<\\/5u>/gi,\'<3Q$1 3N="G-5u-Q">$2</3Q>\')},ej:B(q){E q.I(/<3Q(.*?) 3N="G-5u-Q"(.*?)>([\\w\\W]*?)<\\/3Q>/gi,\'<5u$1$2>$3</5u>\')}}},1y:B(){E{eh:B(){if(c.F.4J)c.F.4y=M;if(c.F.5c)c.F.1Z=M;if(c.F.1M)E;C 2i=[\'p\',\'3Q\'];if(c.F.4J)c.1y.ei(2i);if(c.F.4y)c.1y.er(2i)},ei:B(2i){C 1X=2i.1p;2A(C i=0;i<1X;i++){if($.3L(2i[i],c.F.4J)==-1){c.F.4J.3f(2i[i])}}},er:B(2i){C 1X=2i.1p;2A(C i=0;i<1X;i++){C 2X=$.3L(2i[i],c.F.4y);if(2X!=-1){c.F.4y.93(2X,1)}}},2D:B(q,3C){c.1y.29={4y:c.F.4y,4J:c.F.4J,62:c.F.62,4H:c.F.4H,5d:c.F.5d,5l:c.F.5l,1Z:c.F.1Z,5c:c.F.5c,5Z:c.F.5Z,3S:c.F.3S};$.7e(c.1y.29,3C);q=c.1y.62(q);q=c.1y.4H(q);c.1y.$1m=$(\'<1m />\').1u(q);c.1y.5d();c.1y.et();c.1y.1Z();c.1y.3S();c.1y.ew();c.1y.5l();c.1y.5Z();q=c.1y.$1m.q();c.1y.$1m.1v();E q},62:B(q){if(!c.1y.29.62)E q;E q.I(/<!--[\\s\\S]*?-->/gi,\'\')},4H:B(q){if(!c.1y.29.4H)E q;C 1X=c.1y.29.4H.1p;2A(C i=0;i<1X;i++){C 3i=1I 1U(\'<\'+c.1y.29.4H[i][0]+\'(.*?[^>])>\',\'gi\');q=q.I(3i,\'<\'+c.1y.29.4H[i][1]+\'$1>\');3i=1I 1U(\'</\'+c.1y.29.4H[i][0]+\'>\',\'gi\');q=q.I(3i,\'</\'+c.1y.29.4H[i][1]+\'>\')}E q},5d:B(){if(!c.1y.29.5d)E;C 1X=c.1y.29.5d.1p;c.1y.$1m.1h(\'1b\').1z($.Y(B(n,s){C $el=$(s);C 1t=$el.1e(\'1t\');2A(C i=0;i<1X;i++){if(1t&&1t.2e(1I 1U(\'^\'+c.1y.29.5d[i][0],\'i\'))){C 1n=c.1y.29.5d[i][1];$el.2C(B(){C Q=1a.3m(1n);E $(Q).1u($(c).26())})}}},c))},et:B(){if(!c.1y.29.4y&&c.1y.29.4J){c.1y.$1m.1h(\'*\').5S(c.1y.29.4J.2K(\',\')).26().44()}if(c.1y.29.4y){c.1y.$1m.1h(c.1y.29.4y.2K(\',\')).26().44()}},1Z:B(){C 1X;if(!c.1y.29.1Z&&c.1y.29.5c){C cg=[],ch=[];1X=c.1y.29.5c.1p;2A(C i=0;i<1X;i++){cg.3f(c.1y.29.5c[i][0]);ch.3f(c.1y.29.5c[i][1])}c.1y.$1m.1h(\'*\').1z($.Y(B(n,s){C $el=$(s);C 2X=$.3L($el[0].1n.2L(),cg);C 5a=c.1y.eu(2X,ch,$el);if(5a){$.1z(5a,B(z,f){$el.1Z(f)})}},c))}if(c.1y.29.1Z){1X=c.1y.29.1Z.1p;2A(C i=0;i<1X;i++){C 7I=c.1y.29.1Z[i][1];if($.aa(7I))7I=7I.2K(\' \');c.1y.$1m.1h(c.1y.29.1Z[i][0]).1Z(7I)}}},eu:B(2X,5h,$el){C 5a=[];if(2X==-1){$.1z($el[0].4n,B(i,3g){5a.3f(3g.1g)})}L if(5h[2X]==\'*\'){5a=[]}L{$.1z($el[0].4n,B(i,3g){if($.aa(5h[2X])){if($.3L(3g.1g,5h[2X])==-1){5a.3f(3g.1g)}}L if(5h[2X]!=3g.1g){5a.3f(3g.1g)}})}E 5a},ex:B(el,7d){7d=1I 1U(7d,"g");E el.1z(B(){C 2Y=$(c);C 1X=c.4n.1p-1;2A(C i=1X;i>=0;i--){C 3g=c.4n[i];if(3g&&3g.nL&&3g.1g.3z(7d)>=0){2Y.1Z(3g.1g)}}})},3S:B(){if(!c.1y.29.3S)E;c.1y.$1m.1h(c.1y.29.3S.2K(\',\')).1z(B(){C $el=$(c);C 1i=$el.1i();1i=1i.I(/[\\7E-\\aw\\a3]/g,\'\');1i=1i.I(/&5i;/gi,\'\');1i=1i.I(/\\s/g,\'\');if(1i===\'\'&&$el.49().1p===0){$el.1v()}})},ew:B(){c.1y.$1m.1h(\'li p\').26().44()},5l:B(){if(!c.1y.29.5l)E;C 2i=c.1y.29.5l;if($.aa(c.1y.29.5l))2i=c.1y.29.5l.2K(\',\');c.1y.ex(c.1y.$1m.1h(2i),\'^(1d-)\')},5Z:B(){if(!c.1y.29.5Z)E;c.1y.$1m.1h(c.1y.29.5Z.2K(\',\')).1z(B(){if(c.4n.1p===0){$(c).26().44()}})}}},2o:B(){E{2D:B(q){if(c.F.1M)E q;if(q===\'\'||q===\'<p></p>\')E c.F.8s;c.2o.1O=[\'4o\',\'1m\',\'2V\',\'5u\',\'2H\',\'ol\',\'h1\',\'h2\',\'h3\',\'h4\',\'h5\',\'h6\',\'dl\',\'1N\',\'6T\',\'bh\',\'3Q\',\'a8\',\'bI\',\'gA\',\'gB\',\'4k\',\'1t\',\'5j\',\'cw\',\'6a\',\'3p\',\'2M\',\'1r\',\'3H\',\'eC\',\'eG\',\'nH\',\'hr\',\'eB\',\'nG\',\'nB\',\'nA\',\'6o\',\'nC\',\'nD\',\'nF\',\'p\'];q=q+"\\n";c.2o.94=[];c.2o.z=0;q=q.I(/(<br\\s?\\/?>){1,}\\n?<\\/1N>/gi,\'</1N>\');q=c.2o.ek(q);q=c.2o.eo(q);q=c.2o.eK(q);q=c.2o.eH(q);q=c.2o.eL(q);q=c.2o.ey(q);q=q.I(1I 1U(\'<br\\\\s?/?>\\n?<(\'+c.2o.1O.2K(\'|\')+\')(.*?[^>])>\',\'gi\'),\'<p><br /></p>\\n<$1$2>\');E $.2Z(q)},ek:B(q){C $1m=$(\'<1m />\').1u(q);$1m.1h(\'1N p\').2C(B(){E $(c).1u(\'<br />\').26()});q=$1m.q();$1m.1h(c.2o.1O.2K(\', \')).1z($.Y(B(i,s){c.2o.z++;c.2o.94[c.2o.z]=s.ep;q=q.I(s.ep,\'\\n{I\'+c.2o.z+\'}\')},c));E q},eo:B(q){C cs=q.2e(/<!--([\\w\\W]*?)-->/gi);if(!cs)E q;$.1z(cs,$.Y(B(i,s){c.2o.z++;c.2o.94[c.2o.z]=s;q=q.I(s,\'\\n{I\'+c.2o.z+\'}\')},c));E q},ey:B(q){$.1z(c.2o.94,B(i,s){q=q.I(\'{I\'+i+\'}\',s)});E q},eH:B(q){C 4r=q.3K(1I 1U(\'\\n\',\'g\'),-1);q=\'\';if(4r){C 1X=4r.1p;2A(C i=0;i<1X;i++){if(!4r.nO(i))E;if(4r[i].3z(\'{I\')==-1){4r[i]=4r[i].I(/<p>\\n\\t?<\\/p>/gi,\'\');4r[i]=4r[i].I(/<p><\\/p>/gi,\'\');if(4r[i]!==\'\'){q+=\'<p>\'+4r[i].I(/^\\n+|\\n+$/g,"")+"</p>"}}L q+=4r[i]}}E q},eK:B(q){q=q.I(/<br \\/>\\s*<br \\/>/gi,"\\n\\n");q=q.I(/<br\\s?\\/?>\\n?<br\\s?\\/?>/gi,"\\n<br /><br />");q=q.I(1I 1U("\\r\\n",\'g\'),"\\n");q=q.I(1I 1U("\\r",\'g\'),"\\n");q=q.I(1I 1U("/\\n\\n+/"),\'g\',"\\n\\n");E q},eL:B(q){q=q.I(1I 1U(\'</1N></p>\',\'gi\'),\'</1N>\');q=q.I(1I 1U(\'<p></1N>\',\'gi\'),\'</1N>\');q=q.I(1I 1U(\'<p><1N>\',\'gi\'),\'<1N>\');q=q.I(1I 1U(\'<1N></p>\',\'gi\'),\'<1N>\');q=q.I(1I 1U(\'<p><p \',\'gi\'),\'<p \');q=q.I(1I 1U(\'<p><p>\',\'gi\'),\'<p>\');q=q.I(1I 1U(\'</p></p>\',\'gi\'),\'</p>\');q=q.I(1I 1U(\'<p>\\\\s?</p>\',\'gi\'),\'\');q=q.I(1I 1U("\\n</p>",\'gi\'),\'</p>\');q=q.I(1I 1U(\'<p>\\t?\\t?\\n?<p>\',\'gi\'),\'<p>\');q=q.I(1I 1U(\'<p>\\t*</p>\',\'gi\'),\'\');E q}}},2p:B(){E{12:B(14){if(!c.F.2p)E 14;C bU=[\'eG\',\'3l\',\'eF\',\'hr\',\'i?nZ\',\'T\',\'4V\',\'nY\',\'1t\',\'5j\',\'4o\',\'aO\',\'aP\',\'aL\'];C bS=[\'li\',\'dt\',\'dt\',\'h[1-6]\',\'3H\',\'5j\'];C 7T=[\'1N\',\'1m\',\'dl\',\'eB\',\'5u\',\'o1\',\'eC\',\'ol\',\'p\',\'2V\',\'6a\',\'3J\',\'7n\',\'5Q\',\'2H\'];c.2p.ef=1I 1U(\'^<(/?\'+bU.2K(\'|/?\')+\'|\'+bS.2K(\'|\')+\')[ >]\');c.2p.dZ=1I 1U(\'^<(br|/?\'+bU.2K(\'|/?\')+\'|/\'+bS.2K(\'|/\')+\')[ >]\');c.2p.7T=1I 1U(\'^</?(\'+7T.2K(\'|\')+\')[ >]\');C i=0,9d=14.1p,3B=0,3b=3n,5z=3n,Q=\'\',2b=\'\',4M=\'\';c.2p.7M=0;2A(;i<9d;i++){3B=i;if(-1==14.4R(i).4z(\'<\')){2b+=14.4R(i);E c.2p.bP(2b)}56(3B<9d&&14.5K(3B)!=\'<\'){3B++}if(i!=3B){4M=14.4R(i,3B-i);if(!4M.2e(/^\\s{2,}$/g)){if(\'\\n\'==2b.5K(2b.1p-1))2b+=c.2p.6K();L if(\'\\n\'==4M.5K(0)){2b+=\'\\n\'+c.2p.6K();4M=4M.I(/^\\s+/,\'\')}2b+=4M}if(4M.2e(/\\n/))2b+=\'\\n\'+c.2p.6K()}3b=3B;56(3B<9d&&\'>\'!=14.5K(3B)){3B++}Q=14.4R(3b,3B-3b);i=3B;C t;if(\'!--\'==Q.4R(1,3)){if(!Q.2e(/--$/)){56(\'-->\'!=14.4R(3B,3)){3B++}3B+=2;Q=14.4R(3b,3B-3b);i=3B}if(\'\\n\'!=2b.5K(2b.1p-1))2b+=\'\\n\';2b+=c.2p.6K();2b+=Q+\'>\\n\'}L if(\'!\'==Q[1]){2b=c.2p.9c(Q+\'>\',2b)}L if(\'?\'==Q[1]){2b+=Q+\'>\\n\'}L if(t=Q.2e(/^<(5j|1t|2V)/i)){t[1]=t[1].2L();Q=c.2p.bX(Q);2b=c.2p.9c(Q,2b);5z=5o(14.4R(i+1)).2L().4z(\'</\'+t[1]);if(5z){4M=14.4R(i+1,5z);i+=5z;2b+=4M}}L{Q=c.2p.bX(Q);2b=c.2p.9c(Q,2b)}}E c.2p.bP(2b)},6K:B(){C s=\'\';2A(C j=0;j<c.2p.7M;j++){s+=\'\\t\'}E s},bP:B(14){14=14.I(/\\n\\s*\\n/g,\'\\n\');14=14.I(/^[\\s\\n]*/,\'\');14=14.I(/[\\s\\n]*$/,\'\');14=14.I(/<5j(.*?)>\\n<\\/5j>/gi,\'<5j$1></5j>\');c.2p.7M=0;E 14},bX:B(Q){C 7G=\'\';Q=Q.I(/\\n/g,\' \');Q=Q.I(/\\s{2,}/g,\' \');Q=Q.I(/^\\s+|\\s+$/g,\' \');C c6=\'\';if(Q.2e(/\\/$/)){c6=\'/\';Q=Q.I(/\\/+$/,\'\')}C m;56(m=/\\s*([^= ]+)(?:=(([\'"\']).*?\\3|[^ ]+))?/.4O(Q)){if(m[2])7G+=m[1].2L()+\'=\'+m[2];L if(m[1])7G+=m[1].2L();7G+=\' \';Q=Q.4R(m[0].1p)}E 7G.I(/\\s*$/,\'\')+c6+\'>\'},9c:B(Q,2b){C nl=Q.2e(c.2p.7T);if(Q.2e(c.2p.ef)||nl){2b=2b.I(/\\s*$/,\'\');2b+=\'\\n\'}if(nl&&\'/\'==Q.5K(1))c.2p.7M--;if(\'\\n\'==2b.5K(2b.1p-1))2b+=c.2p.6K();if(nl&&\'/\'!=Q.5K(1))c.2p.7M++;2b+=Q;if(Q.2e(c.2p.dZ)||Q.2e(c.2p.7T)){2b=2b.I(/ *$/,\'\');2b+=\'\\n\'}E 2b}}},28:B(){E{2O:B(){c.$U.28();C 3M=c.$U.49().3M();if(3M.2a()===0)E;if(3M[0].1p===0||3M[0].1n==\'6d\'||3M[0].an==3){E}if(3M[0].1n==\'8i\'||3M[0].1n==\'8f\'){3M=3M.1h(\'li\').3M()}if(c.F.1M&&!c.N.6y(3M[0].1n)){c.K.12();c.X.2O(c.$U[0],0);c.X.4c(c.$U[0],0);c.K.4S();E}c.22.2O(3M)},4c:B(){if(c.N.23(\'7c\')||c.N.23(\'2F\')){C 3t=c.$U.49().3t();c.22.4c(3t)}L{c.K.12();4G{c.X.aX(c.$U[0]);c.X.4f(M);c.K.4S()}4F(e){}}},9w:B(){C 7U=1a.5P().7U;if(7U===3n)E M;if(c.F.1M&&$(7U.9v).3h(\'G-1M\'))E 1l;L if(!c.N.4K(7U.9v))E M;E c.$U.is(\':28\')}}},2P:B(){E{9b:B(){if(!c.2P.is())E;c.$U.1e(\'2P\',c.$2B.1e(\'2P\'));c.2P.2I();c.$U.on(\'2m.G-2P\',$.Y(c.2P.2I,c))},2I:B(){C 1s=\'2R\';if(c.N.4m(c.$U.q(),M))1s=\'2s\';c.$U[1s](\'G-2P\')},1v:B(){c.$U.2R(\'G-2P\')},is:B(){if(c.F.2P){E c.$2B.1e(\'2P\',c.F.2P)}L{E!(1B c.$2B.1e(\'2P\')==\'1H\'||c.$2B.1e(\'2P\')===\'\')}}}},3x:B(){E{9b:B(){if(!c.F.3x)E;c.3x.q=M;c.3x.1g=(c.F.cB)?c.F.cB:c.$2M.1e(\'1g\');if(!c.F.cA){c.7R=nX($.Y(c.3x.2D,c),c.F.7R*nW)}},dU:B(){if(!c.F.cA)E;c.3x.2D()},2D:B(){C q=c.14.12();if(c.3x.q===q)E;if(c.N.4m(q))E;$.nR({2c:c.F.3x,1c:\'fw\',1d:\'1g=\'+c.3x.1g+\'&\'+c.3x.1g+\'=\'+nQ(nS(q)),cI:$.Y(B(1d){c.3x.cI(1d,q)},c)})},cI:B(1d,q){C 2r;4G{2r=$.aK(1d)}4F(e){2r=1d}C dO=(1B 2r.6U==\'1H\')?\'3x\':\'nT\';c.1R.2n(dO,c.3x.1g,2r);c.3x.q=q},nV:B(){dT(c.7R)}}},1W:B(){E{1K:B(1c){if(1B 1c==\'1H\'||1c==\'9s\'){c.1W.e1()}L{c.1W.e2()}},e1:B(){c.K.2N();c.F.1W.3f(c.$U.q());c.K.2U()},e2:B(){c.K.2N();c.F.8F.3f(c.$U.q());c.K.2U()},ea:B(){c.$U.q(c.F.1W.eb())},ec:B(){c.$U.q(c.F.8F.eb())},cU:B(){c.F.1W.3f(c.$U.q())},9s:B(){if(c.F.1W.1p===0)E;c.1W.1K(\'bB\');c.1W.ea();c.K.2U();3q($.Y(c.2l.2D,c),50)},bB:B(){if(c.F.8F.1p===0)E;c.1W.1K(\'9s\');c.1W.ec();c.K.2U();3q($.Y(c.2l.2D,c),50)}}},3d:B(){E{9N:B(){if(!c.N.23(\'2F\'))c.$U.28();c.1W.1K();c.K.2N();C O=c.K.43();if(O&&O.1n==\'4Y\'){c.3d.ed()}L if(O===M&&c.F.1M){c.3d.e9()}L{c.3d.ee()}c.K.2U();c.14.1P()},ed:B(){1a.3V(\'3d\');c.3d.9U();c.1o.9W();c.1o.4a()},ee:B(){$.1z(c.K.3Z(),$.Y(B(i,4C){if(4C.1n===\'8y\'||4C.1n===\'cQ\')E;C $el=c.N.aq(4C);C 1S=c.N.a0($el.1F(\'4j-1S\'))+c.F.95;$el.1F(\'4j-1S\',1S+\'3I\')},c))},e9:B(){C 4d=c.K.5q(\'1m\');$(4d).1e(\'1d-8d\',\'G\');$(4d).1F(\'4j-1S\',c.F.95+\'3I\')},9M:B(){c.1W.1K();c.K.2N();C O=c.K.43();if(O&&O.1n==\'4Y\'){c.3d.e8()}L{c.3d.e4()}c.K.2U();c.14.1P()},e8:B(){1a.3V(\'7p\');C 1A=c.K.3E();C $3g=$(1A).2G(\'li\');C $1k=$3g.1k();if($3g.2a()!==0&&$1k.2a()!==0&&$1k[0].1n==\'4Y\'){$1k.3u($3g)}c.3d.9U();if(!c.F.1M&&$3g.2a()===0){1a.3V(\'9Q\',M,\'p\');c.$U.1h(\'2H, ol, 1N, p\').1z($.Y(c.N.3S,c))}c.1o.4a()},e4:B(){$.1z(c.K.3Z(),$.Y(B(i,4C){C $el=c.N.aq(4C);C 1S=c.N.a0($el.1F(\'4j-1S\'))-c.F.95;if(1S<=0){if(c.F.1M&&1B($el.1d(\'8d\'))!==\'1H\'){$el.2C($el.q()+\'<br />\')}L{$el.1F(\'4j-1S\',\'\');c.N.5X($el,\'1t\')}}L{$el.1F(\'4j-1S\',1S+\'3I\')}},c))},9U:B(){C O=c.K.43();if(c.X.5g&&O&&O.1n==\'4Y\'&&c.N.4m($(O).1i())){C $O=$(O);$O.1h(\'1b\').5S(\'.G-K-2J\').26().44();$O.1u(\'<br>\')}}}},3r:B(){E{1S:B(){c.3r.1K(\'\')},4l:B(){c.3r.1K(\'4l\')},55:B(){c.3r.1K(\'55\')},8e:B(){c.3r.1K(\'8e\')},1K:B(1c){if(!c.N.23(\'2F\'))c.$U.28();c.1W.1K();c.K.2N();c.3r.1O=c.K.3Z();if(c.F.1M&&c.3r.1O[0]===M){c.3r.e3(1c)}L{c.3r.e5(1c)}c.K.2U();c.14.1P()},e3:B(1c){C 4d=c.K.5q(\'1m\');$(4d).1e(\'1d-8d\',\'G\');$(4d).1F(\'1i-4L\',1c)},e5:B(1c){$.1z(c.3r.1O,$.Y(B(i,el){C $el=c.N.aq(el);if(!$el)E;if(1c===\'\'&&1B($el.1d(\'8d\'))!==\'1H\'){$el.2C($el.q())}L{$el.1F(\'1i-4L\',1c);c.N.5X($el,\'1t\')}},c))}}},4v:B(){E{3s:B(e){if(!c.F.e6)E;if(c.F.e7&&c.4v.fE(e))E;c.7a=1l;c.1W.1K();c.K.2N();c.N.7O();c.4v.eN();$(3R).on(\'7q.G-eM\',$.Y(B(){$(3R).3U(c.at)},c));3q($.Y(B(){C q=c.$5J.q();c.$5J.1v();c.K.2U();c.N.ak();c.4v.1Q(q);$(3R).3G(\'7q.G-eM\')},c),1)},eN:B(){c.$5J=$(\'<1m>\').q(\' \').1e(\'72\',\'1l\').1F({4b:\'7o\',2v:0,2T:0,1S:\'-ny\'});c.$5J.ac(1a.3l);c.$5J.28()},1Q:B(q){q=c.1R.2n(\'na\',q);q=(c.N.6M())?c.1o.8v(q,M):c.1o.8v(q);q=c.1R.2n(\'4v\',q);if(c.N.6M()){c.1Q.1K(q,M)}L{c.1Q.q(q,M)}c.N.7H();c.7a=M;if(c.N.23(\'7c\'))3q($.Y(c.4v.ft,c),7h);3q($.Y(c.1o.4a,c),10)},fE:B(e){e=e.6p||e;if(1B(e.9a)===\'1H\')E;if(!e.9a.5n||!e.9a.5n.1p)E;C 1L=e.9a.5n[0].nc();if(1L===3n)E M;C aU=1I nd();aU.cX=$.Y(c.4v.fy,c);aU.nf(1L);E 1l},ft:B(){C fv=c.$U.1h(\'1D\');$.1z(fv,$.Y(B(i,s){if(s.3F.3z(/^1d\\:J/i)==-1)E;C $s=$(s);C 2x=s.3F.3K(",");C aV={\'fx\':1,\'fG\':2x[0].3K(";")[0].3K(":")[1],\'1d\':2x[1]};if(c.F.a7&&1B c.F.a7===\'4k\'){$.1z(c.F.a7,$.Y(B(k,v){if(v!==3n&&v.4E().4z(\'#\')===0)v=$(v).2q();aV[k]=v},c))}$.fw(c.F.5A,aV,$.Y(B(1d){C 2r=(1B 1d===\'5p\'?$.aK(1d):1d);$s.1e(\'3F\',2r.76);c.14.1P();c.1R.2n(\'5A\',$s,2r);c.2l.89()},c))},c))},fy:B(e){C 6u=e.1Y.6u;C 2x=6u.3K(",");C 3y=!!3R.5U?1I 5U():3n;if(!3R.5U)E;c.1W.1K();c.1f.4i=1l;c.1f.1c=\'J\';c.1f.2c=c.F.5A;c.1f.2g=c.J.1Q;3y.1u(\'fx\',1);3y.1u(\'fG\',2x[0].3K(";")[0].3K(":")[1]);3y.1u(\'1d\',2x[1]);c.1f.aJ(3y,e)}}},1w:B(){E{3s:B(e){if(c.7a)E;C 1j=e.8q;C 4w=(1j>=37&&1j<=40);c.1w.3O=e.8K||e.6f;c.1w.1A=c.K.3E();c.1w.1k=c.K.5w();c.1w.O=c.K.43();c.1w.2V=c.N.9Z(c.1w.1A,\'2V\');c.1w.1N=c.N.9Z(c.1w.1A,\'1N\');c.1w.6T=c.N.9Z(c.1w.1A,\'6T\');c.5D.3s(e,1j);c.1w.fS(4w,1j);c.1w.fs(e,1j);c.1w.fK(4w);c.1w.eW(e,1j);C fH=c.1R.2n(\'1w\',e);if(fH===M){e.2k();E M}if(c.F.aE&&1j===c.3e.fQ){c.1w.eZ()}if(!c.F.aE&&1j===c.3e.6e){e.2k();if(!c.X.5g)c.X.5G();E}if(1j==c.3e.6e&&!e.6r&&!e.8K&&!e.6f){C fR=c.1R.2n(\'ne\',e);if(fR===M){e.2k();E M}if(c.1w.1N&&c.1w.fm(e)===1l){E M}C 1A;if(c.1w.2V){E c.1w.fo(e)}L if(c.1w.1N||c.1w.6T){1A=c.K.3E();C $2w=$(1A).2w();if($2w.2a()!==0&&$2w[0].1n==\'6d\'){E c.1w.8G(e)}L if(c.N.7N()&&(1A&&1A!=\'6V\')){E c.1w.9J(e)}L{E c.1w.8G(e)}}L if(c.F.1M&&!c.1w.O){1A=c.K.3E();if(1A!==0&&$(1A).3h(\'G-ap-3o\')){$(1A).1v();E c.1w.9J(e)}L{E c.1w.8G(e)}}L if(c.F.1M&&c.1w.O){3q($.Y(c.1w.eT,c),1)}L if(!c.F.1M&&c.1w.O){3q($.Y(c.1w.eS,c),1)}L if(!c.F.1M&&!c.1w.O){E c.1w.fc(e)}}if(1j===c.3e.6e&&(e.8K||e.6r)){E c.1w.f0(e)}if(1j===c.3e.aG||e.6f&&1j===cv||e.6f&&1j===c3){E c.1w.eV(e,1j)}if(1j===c.3e.7m||1j===c.3e.8M){C 1V=c.K.6D();if(1V){C 1X=1V.1p;C 3t;2A(C i=0;i<1X;i++){C 49=$(1V[i]).49(\'1D\');if(49.2a()!==0){C 2Y=c;$.1z(49,B(z,s){C $s=$(s);if($s.1F(\'6C\')!=\'5R\')E;2Y.1R.2n(\'c2\',s.3F,$s);3t=s})}L if(1V[i].1n==\'cc\'){if(3t!=1V[i]){c.1R.2n(\'c2\',1V[i].3F,$(1V[i]));3t=1V[i]}}}}}if(1j===c.3e.7m){c.1w.ff();c.1w.fe(e)}c.14.1P()},fS:B(4w,1j){if(!4w&&(c.1R.6m()==\'2f\'||c.1R.6m()==\'4w\')){c.1R.8k(M);if(c.1w.fT(1j)){c.1W.1K()}}},fT:B(1j){C k=c.3e;C 4B=[k.7m,k.8M,k.6e,k.bC,k.ba,k.aG,k.fO,k.fN,k.fJ,k.fI];E($.3L(1j,4B)==-1)?1l:M},fK:B(4w){if(!4w)E;if((c.1R.6m()==\'2f\'||c.1R.6m()==\'4w\')){c.1R.8k(M);E}c.1R.8k(\'4w\')},fs:B(e,1j){if(c.1w.3O&&1j===90&&!e.6r&&!e.eX&&c.F.1W.1p){e.2k();c.1W.9s();E}L if(c.1w.3O&&1j===90&&e.6r&&!e.eX&&c.F.8F.1p!==0){e.2k();c.1W.bB();E}L if(!c.1w.3O){if(1j==c.3e.7m||1j==c.3e.8M||(1j==c.3e.6e&&!e.8K&&!e.6r)||1j==c.3e.bC){c.1W.1K()}}},eW:B(e,1j){if(c.1w.3O&&1j===65){c.N.eg()}L if(1j!=c.3e.eY&&!c.1w.3O){c.N.7H()}},eZ:B(){C 2i=[c.1w.1N,c.1w.2V,c.1w.6T];2A(C i=0;i<2i.1p;i++){if(2i[i]){c.1w.fl(2i[i]);E M}}},f0:B(e){c.1W.1K();if(c.1w.1N&&c.N.7N()){E c.1w.9J(e)}E c.1w.8G(e)},eV:B(e,1j){if(!c.F.eU)E 1l;if(c.N.4m(c.14.12())&&c.F.9t===M)E 1l;e.2k();C R;if(c.1w.2V&&!e.6r){R=(c.F.6s)?1a.7x(8D(c.F.6s+1).2K(\'\\eQ\')):1a.7x(\'\\t\');c.1Q.R(R);c.14.1P()}L if(c.F.9t!==M){R=1a.7x(8D(c.F.9t+1).2K(\'\\eQ\'));c.1Q.R(R);c.14.1P()}L{if(e.6f&&1j===c3)c.3d.9M();L if(e.6f&&1j===cv)c.3d.9N();L if(!e.6r)c.3d.9N();L c.3d.9M()}E M},eT:B(){C 4e=c.K.43();C 9L=4e.3P.I(/<br\\s?\\/?>/gi,\'\');if((4e.1n===\'8L\'||4e.1n===\'P\')&&9L===\'\'&&!$(4e).3h(\'G-U\')){C br=1a.3m(\'br\');$(4e).2C(br);c.22.bq(br);c.14.1P();E M}},eS:B(){C 4e=c.K.43();C 9L=4e.3P.I(/<br\\s?\\/?>/gi,\'\');if(4e.1n===\'8L\'&&9L===\'\'&&!$(4e).3h(\'G-U\')){C p=1a.3m(\'p\');p.3P=c.F.5y;$(4e).2C(p);c.22.2O(p);c.14.1P();E M}L if(c.F.fb&&4e.1n==\'P\'){$(4e).1Z(\'1J\').1Z(\'1t\')}},fc:B(e){e.2k();c.K.12();C p=1a.3m(\'p\');p.3P=c.F.5y;c.X.5G();c.X.3T(p);c.22.2O(p);c.14.1P();E M},fm:B(e){if(!c.N.7N())E;C 57=$.2Z($(c.1w.O).q());if(57.3z(/(<br\\s?\\/?>){2}$/i)!=-1){e.2k();if(c.F.1M){C br=1a.3m(\'br\');$(c.1w.1N).3u(br);c.22.bq(br);$(c.1w.O).q(57.I(/<br\\s?\\/?>$/i,\'\'))}L{C R=$(c.F.8s);$(c.1w.1N).3u(R);c.22.2O(R)}E 1l}E},fl:B(2B){if(!c.N.7N())E;c.1W.1K();if(c.F.1M){C 26=$(\'<1m>\').1u($.2Z(c.$U.q())).26();C 3t=26.3t()[0];if(3t.1n==\'6V\'&&3t.3P===\'\'){3t=26.6i()[0]}if(c.N.5O(3t)!=c.N.5O(2B))E;C br=1a.3m(\'br\');$(2B).3u(br);c.22.5I(br)}L{if(c.$U.26().3t()[0]!==2B)E;C R=$(c.F.8s);$(2B).3u(R);c.22.2O(R)}},fo:B(e){e.2k();C R=1a.7x(\'\\n\');c.K.12();c.X.5G();c.X.3T(R);c.22.5I(R);c.14.1P();E M},8G:B(e){E c.1w.bf(e)},9J:B(e){E c.1w.bf(e,1l)},bf:B(e,fj){e.fk();c.K.12();C bl=1a.3m(\'br\');c.X.5G();c.X.3T(bl);if(fj===1l){C bo=1a.3m(\'br\');c.X.3T(bo);c.22.5I(bo)}L{c.22.5I(bl)}c.14.1P();E M},ff:B(){C $1A=$(c.1w.1A);if($1A.1i().3z(/^\\7E$/g)===0){$1A.1v()}},fe:B(e){C $1A=$(c.1w.1A);C $1k=$(c.1w.1k);C 3J=$1A.2G(\'3J\');if(3J.2a()!==0&&$1A.2G(\'li\')&&$1k.49(\'li\').2a()===1){if(!c.N.4m($1A.1i()))E;e.2k();$1A.1v();$1k.1v();c.22.2O(3J)}}}},2m:B(){E{3s:B(e){if(c.7a)E;C 1j=e.8q;c.2m.1A=c.K.3E();c.2m.1k=c.K.5w();C $1k=c.N.4K($(c.2m.1k).1k());C fi=c.1R.2n(\'2m\',e);if(fi===M){e.2k();E M}if(!c.F.1M&&c.2m.1A.an==3&&c.2m.1A.1p<=1&&(c.2m.1k===M||c.2m.1k.1n==\'b0\')){c.2m.bk()}if($(c.2m.1k).3h(\'G-ap-3o\')&&($1k===M||$1k[0].1n==\'b0\')){$(c.2m.1k).26().44();c.2m.bk()}if(c.F.82&&(c.F.6E||c.F.6P||c.F.6v)&&1j===c.3e.6e){c.aD(c.F.7u,c.F.82,c.F.6E,c.F.6P,c.F.6v,c.F.6z);c.2l.2D();c.14.1P()}if(1j===c.3e.8M||1j===c.3e.7m){c.1o.4a();if(c.2l.J){e.2k();c.J.5V();c.1W.1K();c.J.1v(c.2l.J);c.2l.J=M;E M}c.$U.1h(\'p\').1z($.Y(c.N.3S,c));if(c.2m.1A&&c.2m.1A.1n==\'8L\'&&c.N.4m(c.2m.1A.3P)){if(c.F.1M){$(c.2m.1A).3u(c.K.9E());c.K.2U();$(c.2m.1A).1v()}}E c.2m.d3(e)}},bk:B(){C $1A=$(c.2m.1A);C R=$(\'<p>\').1u($1A.fC());$1A.2C(R);C 2w=$(R).2w();if(1B(2w[0])!==\'1H\'&&2w[0].1n==\'6d\'){2w.1v()}c.22.4c(R)},d3:B(e){C q=$.2Z(c.$U.q());if(!c.N.4m(q))E;e.2k();if(c.F.1M){if(!c.N.23(\'2F\'))c.$U.q(\'<br />\');c.28.2O()}L{q=\'<p><br /></p>\';c.$U.q(q);c.28.2O()}c.14.1P();E M}}},5D:B(){E{3s:B(e,1j){if(!c.F.5D){if((e.8K||e.6f)&&(1j===66||1j===73))e.2k();E M}$.1z(c.F.5D,$.Y(B(5k,47){C 4B=5k.3K(\',\');C 1X=4B.1p;2A(C i=0;i<1X;i++){if(1B 4B[i]===\'5p\'){c.5D.d2(e,$.2Z(4B[i]),$.Y(B(){C 1s;if(47.1s.3z(/\\./)!=\'-1\'){1s=47.1s.3K(\'.\');if(1B c[1s[0]]!=\'1H\'){c[1s[0]][1s[1]].8A(c,47.5C)}}L{c[47.1s].8A(c,47.5C)}},c))}}},c))},d2:B(e,4B,d9){C dA={8:"nx",9:"5e",10:"E",13:"E",16:"6b",17:"3O",18:"7b",19:"nj",20:"ni",27:"nm",32:"3o",33:"o4",34:"oQ",35:"5z",36:"oR",37:"1S",38:"oT",39:"4l",40:"oI",45:"1Q",46:"41",59:";",61:"=",96:"0",97:"1",98:"2",99:"3",7h:"4",oW:"5",oV:"6",oZ:"7",p3:"8",p4:"9",p5:"*",p6:"+",p0:"-",p1:".",p2:"/",oY:"f1",oL:"f2",oF:"f3",oh:"f4",oi:"f5",oj:"f6",od:"f7",o7:"f8",o9:"f9",oc:"ob",om:"oo",oz:"oy",oA:"oB",oD:"7q",ox:"-",ow:";",oq:"=",op:",",os:"-",ot:".",ov:"/",ou:"`",c3:"[",no:"\\\\",cv:"]",mZ:"\'"};C cp={"`":"~","1":"!","2":"@","3":"#","4":"$","5":"%","6":"^","7":"&","8":"*","9":"(","0":")","-":"5F","=":"+",";":": ","\'":"\\"",",":"<",".":">","/":"?","\\\\":"|"};4B=4B.2L().3K(" ");C 9K=dA[e.3e],8I=5o.n0(e.8q).2L(),70="",6Z={};$.1z(["7b","3O","4V","6b"],B(6j,8V){if(e[8V+\'lG\']&&9K!==8V){70+=8V+\'+\'}});if(9K)6Z[70+9K]=1l;if(8I){6Z[70+8I]=1l;6Z[70+cp[8I]]=1l;if(70==="6b+"){6Z[cp[8I]]=1l}}2A(C i=0,1X=4B.1p;i<1X;i++){if(6Z[4B[i]]){e.2k();E d9.8A(c,d4)}}}}},4D:B(){E{1Q:B(){c.1W.1K();C 1O=c.K.3Z();if(1O[0]!==M&&c.4D.d6(1O)){if(!c.N.23(\'2F\'))c.$U.28();E}if(c.N.23(\'2F\')){c.4D.9O()}L{c.4D.iu()}},d6:B(1O){C 8m=[\'li\',\'3J\',\'7n\',\'1N\',\'6T\',\'2V\',\'dl\',\'dt\',\'dd\'];C 3M=1O[0].1n.2L();C 3t=c.K.ga();3t=(1B 3t==\'1H\')?3M:3t.1n.2L();C ci=$.3L(3M,8m)!=-1;C it=$.3L(3t,8m)!=-1;if((ci&&it)||ci){E 1l}},9O:B(){c.N.7O();c.1W.1K();c.1Q.R(1a.3m(\'hr\'));c.N.ak();c.14.1P()},iu:B(){c.1W.1K();C ce=\'<p id="G-1Q-4D"><br /></p>\';if(c.F.1M)ce=\'<br id="G-1Q-4D">\';1a.3V(\'iY\',M,\'<hr>\'+ce);c.4D.iv();c.14.1P()},iv:B(){C R=c.$U.1h(\'#G-1Q-4D\');C 2w=$(R).2w()[0];if(2w){c.22.5I(R);R.1v()}L{R.1Z(\'id\')}}}},2S:B(){E{2I:B(4h){if(!c.N.23(\'2F\'))c.$U.28();c.1W.1K();c.K.2N();C 1k=c.K.5w();C $2S=$(1k).2G(\'ol, 2H\');if(!c.N.4K($2S)&&$2S.2a()!==0){$2S=M}C cb,cj;C 1v=M;if($2S&&$2S.1p){1v=1l;C ca=$2S[0].1n;cb=(4h===\'4Z\'&&ca===\'8i\');cj=(4h===\'5r\'&&ca===\'8f\')}if(cb){c.N.4p($2S,\'ol\')}L if(cj){c.N.4p($2S,\'2H\')}L{if(1v){c.2S.1v(4h)}L{c.2S.1Q(4h)}}c.K.2U();c.14.1P()},1Q:B(4h){if(c.N.23(\'2F\')&&c.F.1M){c.2S.9O(4h)}L{1a.3V(\'1Q\'+4h)}C 1k=c.K.5w();C $2S=$(1k).2G(\'ol, 2H\');if(!c.F.1M&&c.N.4m($2S.1h(\'li\').1i())){C $49=$2S.49(\'li\');$49.1h(\'br\').1v();$49.1u(c.K.9E())}if($2S.1p){C $6R=$2S.1k();if(c.N.4K($6R)&&$6R[0].1n!=\'4Y\'&&c.N.6w($6R[0])){$6R.2C($6R.26())}}if(!c.N.23(\'2F\')){c.$U.28()}c.1o.4a()},9O:B(4h){C 4d=c.K.5q(\'1m\');C 9P=$(4d).q();C 6Y=(4h==\'4Z\')?$(\'<ol>\'):$(\'<2H>\');C 8x=$(\'<li>\');if($.2Z(9P)===\'\'){8x.1u(c.K.9E());6Y.1u(8x);c.$U.1h(\'#K-2J-1\').2C(6Y)}L{C 5n=9P.3K(/<br\\s?\\/?>/gi);if(5n){2A(C i=0;i<5n.1p;i++){if($.2Z(5n[i])!==\'\'){6Y.1u($(\'<li>\').q(5n[i]))}}}L{8x.1u(9P);6Y.1u(8x)}$(4d).2C(6Y)}},1v:B(4h){1a.3V(\'1Q\'+4h);C $1A=$(c.K.3E());c.3d.9U();if(!c.F.1M&&$1A.2G(\'li, 7n, 3J\').2a()===0){1a.3V(\'9Q\',M,\'p\');c.$U.1h(\'2H, ol, 1N, p\').1z($.Y(c.N.3S,c))}C $4o=$(c.K.3E()).2G(\'4o\');C $6i=$4o.6i();if(!c.F.1M&&$4o.2a()!==0&&$6i.2a()!==0&&$6i[0].1n==\'6d\'){$6i.1v()}c.1o.4a()}}},O:B(){E{2E:B(1g){C 1c,1G;if(1B c.2E[1g].1d!=\'1H\')1c=\'1d\';L if(1B c.2E[1g].1e!=\'1H\')1c=\'1e\';L if(1B c.2E[1g].1J!=\'1H\')1c=\'1J\';if(1c)1G=c.2E[1g][1c];c.O.2W(c.2E[1g].Q,1c,1G)},2W:B(Q,1c,1G){if(Q==\'c8\')Q=\'1N\';C ix=[\'p\',\'2V\',\'1N\',\'h1\',\'h2\',\'h3\',\'h4\',\'h5\',\'h6\'];if($.3L(Q,ix)==-1)E;c.O.8h=(Q==\'2V\'||Q.3z(/h[1-6]/i)!=-1);if(!c.N.23(\'2F\'))c.$U.28();c.O.1O=c.K.3Z();c.O.7j=c.O.1O.1p;c.O.1c=1c;c.O.1G=1G;c.1W.1K();c.K.2N();c.O.1K(Q);c.K.2U();c.14.1P()},1K:B(Q){c.K.12();c.O.5t=c.X.iI.1n;if(c.X.5g){c.O.iy(Q)}L{c.O.iF(Q)}},iy:B(Q){C O=c.O.1O[0];if(O===M)E;if(O.1n==\'4Y\'){if(Q!=\'1N\')E;c.O.9R();E}C 9T=(c.O.5t==\'8y\'||c.O.5t==\'cQ\');if(9T){if(!c.F.1M&&Q==\'p\'){1a.3V(\'9Q\',M,\'<\'+Q+\'>\');O=c.K.43();c.O.2I($(O))}}L if(O.1n.2L()!=Q){if(c.F.1M&&Q==\'p\'){$(O).4N(\'<br>\').1u(\'<br>\');c.N.5B(O)}L{C $21=c.N.4p(O,Q);c.O.2I($21);if(Q!=\'p\'&&Q!=\'1N\')$21.1h(\'1D\').1v();if(c.O.8h)c.N.4A($21);if(Q==\'p\'||c.O.cF)$21.1h(\'p\').26().44()}}L if(Q==\'1N\'&&O.1n.2L()==Q){if(c.F.1M){$(O).4N(\'<br>\').1u(\'<br>\');c.N.5B(O)}L{C $el=c.N.4p(O,\'p\');c.O.2I($el)}}L if(O.1n.2L()==Q){c.O.2I($(O))}},iF:B(Q){C O=c.O.1O[0];C 9T=(c.O.5t==\'8y\'||c.O.5t==\'cQ\');if(O!==M&&c.O.7j===1){if(O.1n.2L()==Q&&Q==\'1N\'){if(c.F.1M){$(O).4N(\'<br>\').1u(\'<br>\');c.N.5B(O)}L{C $el=c.N.4p(O,\'p\');c.O.2I($el)}}L if(O.1n==\'4Y\'){if(Q!=\'1N\')E;c.O.9R()}L if(c.O.5t==\'9S\'){c.O.i1(Q)}L if(c.F.1M&&((9T)||(c.X.iI!=O))){c.O.cS(Q)}L{if(c.F.1M&&Q==\'p\'){$(O).4N(\'<br>\').1u(\'<br>\');c.N.5B(O)}L{C $21=c.N.4p(O,Q);c.O.2I($21);if(c.O.8h)c.N.4A($21);if(Q==\'p\'||c.O.cF)$21.1h(\'p\').26().44()}}}L{if(c.F.1M||Q!=\'p\'){if(Q==\'1N\'){C cK=0;2A(C i=0;i<c.O.7j;i++){if(c.O.1O[i].1n==\'9S\')cK++}if(cK==c.O.7j){$.1z(c.O.1O,$.Y(B(i,s){if(c.F.1M){$(s).4N(\'<br>\').1u(\'<br>\');c.N.5B(s)}L{c.N.4p(s,\'p\')}},c));E}}c.O.cS(Q)}L{C 8l=0;C 4X=M;if(c.O.1c==\'1J\'){4X=\'2I\';8l=$(c.O.1O).iD(\'.\'+c.O.1G).2a();if(c.O.7j==8l)4X=\'2I\';L if(c.O.7j>8l)4X=\'1K\';L if(8l===0)4X=\'1K\'}C 8m=[\'2H\',\'ol\',\'li\',\'3J\',\'7n\',\'dl\',\'dt\',\'dd\'];$.1z(c.O.1O,$.Y(B(i,s){if($.3L(s.1n.2L(),8m)!=-1)E;C $21=c.N.4p(s,Q);if(4X){if(4X==\'2I\')c.O.2I($21);L if(4X==\'1v\')c.O.1v($21);L if(4X==\'1K\')c.O.lJ($21)}L c.O.2I($21);if(Q!=\'p\'&&Q!=\'1N\')$21.1h(\'1D\').1v();if(c.O.8h)c.N.4A($21);if(Q==\'p\'||c.O.cF)$21.1h(\'p\').26().44()},c))}}},2I:B($el){if(c.O.1c==\'1J\'){$el.9I(c.O.1G);E}L if(c.O.1c==\'1e\'||c.O.1c==\'1d\'){if($el.1e(c.O.1G.1g)==c.O.1G.1G){$el.1Z(c.O.1G.1g)}L{$el.1e(c.O.1G.1g,c.O.1G.1G)}E}L{$el.1Z(\'1t 1J\');E}},1v:B($el){$el.2R(c.O.1G)},9R:B(){C O=$(c.O.1O[0]).2G(\'2H, ol\');$(O).1h(\'2H, ol\').26().44();$(O).1h(\'li\').1u($(\'<br>\')).26().44();C $el=c.N.4p(O,\'1N\');c.O.2I($el)},i1:B(Q){1a.3V(\'7p\');1a.3V(\'9Q\',M,Q);c.1o.4a();c.$U.1h(\'p:hV\').1v();C 21=c.K.43();if(Q!=\'p\'){$(21).1h(\'1D\').1v()}if(!c.F.1M){c.O.2I($(21))}c.$U.1h(\'2H, ol, 5Q, 1N, p\').1z($.Y(c.N.3S,c));if(c.F.1M&&Q==\'p\'){c.N.5B(21)}},cS:B(Q){if(c.O.5t==\'8i\'||c.O.5t==\'8f\'){if(Q==\'1N\'){c.O.9R()}L{E}}C 21=c.K.5q(Q);if(21===M)E;C $21=$(21);c.O.hW($21);C $aM=$21.1h(c.F.8g.2K(\',\')+\', 3J, 4o, aP, aO, aL, 7n, 5Q\');if((c.F.1M&&Q==\'p\')||Q==\'2V\'||Q==\'1N\'){$aM.1u(\'<br />\')}$aM.26().44();if(Q!=\'p\'&&Q!=\'1N\')$21.1h(\'1D\').1v();$.1z(c.O.1O,$.Y(c.N.3S,c));$21.1u(c.K.6q(2));if(!c.F.1M){c.O.2I($21)}c.$U.1h(\'2H, ol, 5Q, 1N, p\').1z($.Y(c.N.3S,c));$21.1h(\'1N:hV\').1v();if(c.O.8h){c.N.4A($21)}if(c.F.1M&&Q==\'p\'){c.N.5B($21)}},hW:B($21){if($21.2G(\'4o\').2a()===0)E;if($21.2G(\'5Q\').2a()===0)$21.5q(\'<5Q>\');if($21.2G(\'3J\').2a()===0)$21.5q(\'<3J>\')},i4:B(1g,1G){C 1O=c.K.3Z();$(1O).1Z(\'1d-\'+1g);c.14.1P()},l3:B(1g,1G){C 1O=c.K.3Z();$(1O).1e(\'1d-\'+1g,1G);c.14.1P()},l5:B(1g,1G){C 1O=c.K.3Z();$.1z(1O,B(){if($(c).1e(\'1d-\'+1g)){$(c).1Z(\'1d-\'+1g)}L{$(c).1e(\'1d-\'+1g,1G)}})},1Z:B(1e,1G){C 1O=c.K.3Z();$(1O).1Z(1e);c.14.1P()},l6:B(1e,1G){C 1O=c.K.3Z();$(1O).1e(1e,1G);c.14.1P()},l8:B(1e,1G){C 1O=c.K.3Z();$.1z(1O,B(){if($(c).1e(1g)){$(c).1Z(1g)}L{$(c).1e(1g,1G)}})},2R:B(4q){C 1O=c.K.3Z();$(1O).2R(4q);c.N.5X(1O,\'1J\');c.14.1P()},l7:B(4q){C 1O=c.K.3Z();$(1O).2s(4q);c.14.1P()},9I:B(4q){C 1O=c.K.3Z();$(1O).9I(4q);c.14.1P()}}},1T:B(){E{2E:B(1g){C 1c,1G;if(1B c.2E[1g].1t!=\'1H\')1c=\'1t\';L if(1B c.2E[1g].1J!=\'1H\')1c=\'1J\';if(1c)1G=c.2E[1g][1c];c.1T.2W(c.2E[1g].Q,1c,1G)},2W:B(Q,1c,1G){if(c.N.4Q(\'aj\'))E;C 2i=[\'b\',\'3Y\',\'i\',\'3X\',\'51\',\'jj\',\'5M\',\'jb\',\'jg\'];C jk=[\'52\',\'52\',\'em\',\'em\',\'u\',\'41\',\'41\',\'7f\',\'7g\'];2A(C i=0;i<2i.1p;i++){if(Q==2i[i])Q=jk[i]}c.1T.1c=1c||M;c.1T.1G=1G||M;c.1W.1K();c.$U.28();c.K.12();if(c.X.5g){c.1T.jc(Q)}L{c.1T.jd(Q)}},jc:B(Q){C 1A=c.K.3E();C $1k=$(1A).2G(Q+\'[1d-G-Q=\'+Q+\']\');if($1k.2a()!==0){c.22.5I($1k[0]);if(c.N.4m($1k.1i()))$1k.1v();c.14.1P();E}C R=$(\'<\'+Q+\'>\').1e(\'1d-3v\',\'G\').1e(\'1d-G-Q\',Q);R.q(c.F.5y);R=c.1T.bb(R);c.1Q.R(R);c.22.4c(R);c.14.1P();E},jd:B(Q){c.1T.j8(Q);c.K.2N();1a.3V(\'jj\');c.$U.1h(\'8u\').1z($.Y(B(i,s){C $el=$(s);c.1T.ji($el,Q);C $1b;if(c.1T.1c){$1b=$(\'<1b>\').1e(\'1d-G-Q\',Q).1e(\'1d-3v\',\'G\');$1b=c.1T.bb($1b)}L{$1b=$(\'<\'+Q+\'>\').1e(\'1d-G-Q\',Q).1e(\'1d-3v\',\'G\')}$el.2C($1b.q($el.26()));if(Q==\'1b\'){C $1k=$1b.1k();if($1k&&$1k[0].1n==\'6V\'&&c.1T.1c==\'1t\'){C 2x=c.1T.1G.3K(\';\');2A(C z=0;z<2x.1p;z++){if(2x[z]===\'\')E;C 1t=2x[z].3K(\':\');$1k.1F(1t[0],\'\');if(c.N.5X($1k,\'1t\')){$1k.2C($1k.26())}}}}},c));if(Q!=\'41\'){C 2Y=c;c.$U.1h(\'1T\').1z(B(i,s){2Y.N.4p(s,\'41\')})}c.K.2U();c.14.1P()},ji:B($el,Q){$el.49(Q).1z(B(){C $bG=$(c);if(!$bG.3h(\'G-K-2J\')){$bG.26().44()}})},j8:B(Q){c.K.2N();C 1h=\'\';if(c.1T.1c==\'1J\')1h=\'[1d-G-1J=\'+c.1T.1G+\']\';L if(c.1T.1c==\'1t\'){1h=\'[1d-G-1t="\'+c.1T.1G+\'"]\'}if(Q!=\'41\'){C 2Y=c;c.$U.1h(\'41\').1z(B(i,s){2Y.N.4p(s,\'1T\')})}c.$U.1h(\'[1d-G-Q="\'+Q+\'"]\'+1h).1z(B(){if(1h===\'\'&&Q==\'1b\'&&c.1n.2L()==Q)E;C $el=$(c);$el.2C($(\'<8u />\').q($el.26()))});c.K.2U()},bb:B(R){gH(c.1T.1c){8z\'1J\':if(R.3h(c.1T.1G)){R.2R(c.1T.1G);R.1Z(\'1d-G-1J\')}L{R.2s(c.1T.1G);R.1e(\'1d-G-1J\',c.1T.1G)}8C;8z\'1t\':R[0].1t.kX=c.1T.1G;R.1e(\'1d-G-1t\',c.1T.1G);8C}E R},kY:B(){c.1W.1K();C 1A=c.K.3E();C 1V=c.K.by();c.K.2N();if(1A&&1A.1n===\'6V\'){C $s=$(1A);$s.1Z(\'1t\');if($s[0].4n.1p===0){$s.2C($s.26())}}$.1z(1V,$.Y(B(i,s){C $s=$(s);if($.3L(s.1n.2L(),c.F.4P)!=-1&&!$s.3h(\'G-K-2J\')){$s.1Z(\'1t\');if($s[0].4n.1p===0){$s.2C($s.26())}}},c));c.K.2U();c.14.1P()},l0:B(1g){c.1W.1K();C 1k=c.K.5w();C 1V=c.K.by();c.K.2N();if(1k&&1k.1n===\'6V\'){C $s=$(1k);$s.1F(1g,\'\');c.N.5X($s,\'1t\');if($s[0].4n.1p===0){$s.2C($s.26())}}$.1z(1V,$.Y(B(i,s){C $s=$(s);if($.3L(s.1n.2L(),c.F.4P)!=-1&&!$s.3h(\'G-K-2J\')){$s.1F(1g,\'\');c.N.5X($s,\'1t\');if($s[0].4n.1p===0){$s.2C($s.26())}}},c));c.K.2U();c.14.1P()},b6:B(){c.1W.1K();C 1A=c.K.3E();c.K.2N();1a.3V(\'b6\');if(1A&&1A.1n===\'6V\'){$(1A).2C($(1A).26())}$.1z(c.K.6D(),$.Y(B(i,s){C $s=$(s);if($.3L(s.1n.2L(),c.F.4P)!=-1&&!$s.3h(\'G-K-2J\')){$s.2C($s.26())}},c));c.K.2U();c.14.1P()},9I:B(4q){c.1T.2W(\'1b\',\'1J\',4q)},kZ:B(1G){c.1T.2W(\'1b\',\'1t\',1G)}}},1Q:B(){E{1K:B(q,1o){c.2P.1v();q=c.1o.8t(q);if(1B 1o==\'1H\'){q=c.1o.8v(q,M)}c.$U.q(q);c.K.1v();c.28.4c();c.1o.9W();c.14.1P();c.2l.2D();if(1B 1o==\'1H\'){3q($.Y(c.1o.4a,c),10)}},1i:B(1i){c.2P.1v();1i=1i.4E();1i=$.2Z(1i);1i=c.1o.9H(1i,M);c.$U.28();if(c.N.23(\'2F\')){c.1Q.bg(1i)}L{c.K.12();c.X.5G();C el=1a.3m("1m");el.3P=1i;C 53=1a.b4(),R,4W;56((R=el.b8)){4W=53.6B(R)}c.X.3T(53);if(4W){C X=c.X.7A();X.g6(4W);X.4f(1l);c.2u.9F();c.2u.4S(X)}}c.14.1P();c.1o.4a()},q:B(q,1o){c.2P.1v();if(1B 1o==\'1H\')1o=1l;c.$U.28();q=c.1o.8t(q);if(1o){q=c.1o.8v(q)}if(c.N.23(\'2F\')){c.1Q.bg(q)}L{if(c.1o.8w)c.1Q.gq(q);L 1a.3V(\'iY\',3n,q);c.1Q.j0()}c.1o.9W();if(!c.F.1M){c.$U.1h(\'p\').1z($.Y(c.N.3S,c))}c.14.1P();c.2l.2D();if(1o){c.1o.4a()}},j0:B(){if(!c.N.23(\'7c\'))E;C $2w=$(c.K.43()).2w();if($2w.1p>0&&$2w[0].1n==\'P\'&&$2w.q()===\'\'){$2w.1v()}},bg:B(q){if(c.N.ez()){C 1k=c.N.4Q(\'P\');C $q=$(\'<1m>\').1u(q);C gw=$q.26().is(\'p, :a8, dl, 2H, ol, 1m, 4o, 3J, 1N, 2V, bh, 3Q, a8, bI, gA, gB\');if(1k&&gw)c.1Q.gN(1k,q);L c.1Q.gK(q);E}1a.K.7B().lo(q)},gq:B(q){q=c.1o.8t(q);c.K.12();c.X.5G();C el=1a.3m(\'1m\');el.3P=q;C 53=1a.b4(),R,4W;56((R=el.b8)){4W=53.6B(R)}c.X.3T(53);c.X.4f(1l);c.22.5I(4W)},R:B(R){R=R[0]||R;c.K.12();c.X.5G();c.X.3T(R);c.X.4f(M);c.K.4S();E R},lj:B(R,x,y){R=R[0]||R;c.K.12();C X;if(1a.6x){C 2X=1a.6x(x,y);c.X.2O(2X.bJ,2X.4t);c.X.4f(1l);c.X.3T(R)}L if(1a.7k){X=1a.7k(x,y);X.3T(R)}L if(1B 1a.3l.6O!="1H"){X=1a.3l.6O();X.7v(x,y);C 6I=X.gC();6I.7v(x,y);X.gD("gM",6I);X.6a()}},cG:B(e,R){R=R[0]||R;C X;C x=e.lh,y=e.lc;if(1a.6x){C 2X=1a.6x(x,y);C 2u=1a.5P();X=2u.71(0);X.2O(2X.bJ,2X.4t);X.4f(1l);X.3T(R)}L if(1a.7k){X=1a.7k(x,y);X.3T(R)}L if(1B 1a.3l.6O!="1H"){X=1a.3l.6O();X.7v(x,y);C 6I=X.gC();6I.7v(x,y);X.gD("gM",6I);X.6a()}},gN:B(1k,q){C R=1a.3m(\'1b\');R.4q=\'G-ie-4v\';c.1Q.R(R);C 9y=$(1k).q();9y=\'<p>\'+9y.I(/<1b 1J="G-ie-4v"><\\/1b>/gi,\'</p>\'+q+\'<p>\')+\'</p>\';$(1k).2C(9y)},gK:B(q){c.K.12();c.X.5G();C el=1a.3m("1m");el.3P=q;C 53=1a.b4(),R,4W;56((R=el.b8)){4W=53.6B(R)}c.X.3T(53)}}},22:B(){E{2O:B(R){if(!c.N.6w(R)){C 3o=c.N.cu();$(R).4N(3o);c.22.4c(3o)}L{c.22.1K(R,0,R,0)}},4c:B(R){c.22.1K(R,1,R,1)},1K:B(4g,go,6G,g4){if(!c.N.23(\'2F\'))c.$U.28();4g=4g[0]||4g;6G=6G[0]||6G;if(c.N.6y(4g.1n)&&4g.3P===\'\'){4g.3P=c.F.5y}if(4g.1n==\'6d\'&&c.F.1M===M){C bc=$(c.F.8s)[0];$(4g).2C(bc);4g=bc;6G=4g}c.K.12();4G{c.X.2O(4g,go);c.X.4c(6G,g4)}4F(e){}c.K.4S()},5I:B(R){C Q=$(R)[0].1n;if(Q!=\'6d\'&&!c.N.6w(R)){C 3o=c.N.cu();$(R).3u(3o);c.22.4c(3o)}L{if(Q!=\'6d\'&&c.N.23(\'2F\')){c.22.2O($(R).2w())}L{c.22.bE(R,\'3u\')}}},bq:B(R){if(c.N.6w(R)){c.22.4c($(R).6i())}L{c.22.bE(R,\'cJ\')}},bE:B(R,1c){if(!c.N.23(\'2F\'))c.$U.28();R=R[0]||R;c.K.12();if(1c==\'3u\'){4G{c.X.g6(R);c.X.lX(R)}4F(e){}}L{4G{c.X.lY(R);c.X.mE(R)}4F(e){}}c.X.4f(M);c.K.4S()},c0:B(R){R=R[0]||R;c.K.12();C 9x=c.X.7A();9x.aX(R);9x.4c(c.X.mF,c.X.mG);E $.2Z(9x.4E()).1p},aI:B(6l,6k){if(6l===0&&6k===0){c.28.2O();E}C 3b,X=3n;if(1a.6x){3b=1a.6x(6l,6k);X=1a.7B();X.2O(3b.bJ,3b.4t);X.4f(1l)}L if(1a.7k){3b=1a.7k(6l,6k);X=1a.7B();if(3b!==3n){X.2O(3b.gg,3b.mI);X.4f(1l)}L{X=3n}}L if(1a.3l.6O){X=1a.3l.6O();X.7v(6l,6k);X.6a()}if(X!==3n){C 2u=1a.5P();2u.9F();2u.4S(X)}if(!c.28.9w()){c.28.2O()}E},g0:B(){if(!c.28.9w()){c.$U.28()}C 2u=1a.K,X,5s;C x=0,y=0;if(2u){if(2u.1c!="mC"){X=2u.7B();X.4f(1l);x=X.mB;y=X.mw}}L if(1a.5P){2u=1a.5P();4G{X=2u.71(0).7A();if(X.9u){X.4f(1l);5s=X.9u()[0];4G{x=5s.1S;y=5s.2T}4F(e){}}if(x===0&&y===0){C 1b=1a.3m("1b");if(1b.9u){1b.6B(1a.7x("\\mv"));X.3T(1b);5s=1b.9u()[0];if(1B 5s!=\'1H\'){x=5s.1S;y=5s.2T}C 1k=1b.9v;1k.mx(1b);1k.a0()}}}4F(e){}}E{x:x,y:y}}}},K:B(){E{12:B(){c.2u=1a.5P();if(1a.5P&&c.2u.71&&c.2u.9C){c.X=c.2u.71(0)}L{c.X=1a.7B()}},4S:B(){4G{c.2u.9F()}4F(e){}c.2u.4S(c.X)},3E:B(){C el=M;c.K.12();if(c.2u&&c.2u.9C>0){el=c.2u.71(0).gg}E c.N.4K(el)},5w:B(4C){4C=4C||c.K.3E();if(4C){E c.N.4K($(4C).1k()[0])}E M},43:B(R){R=R||c.K.3E();56(R){if(c.N.6y(R.1n)){E($(R).3h(\'G-U\'))?M:R}R=R.9v}E M},by:B(1V){c.K.12();if(c.X&&c.X.5g){E M}C 9z=[];1V=(1B 1V==\'1H\')?c.K.6D():1V;C 4P=c.F.4P;4P.3f(\'1b\');$.1z(1V,$.Y(B(i,R){if($.3L(R.1n.2L(),4P)!=-1){9z.3f(R)}},c));E(9z.1p===0)?M:9z},3Z:B(1V){c.K.12();if(c.X&&c.X.5g){E[c.K.43()]}C 1O=[];1V=(1B 1V==\'1H\')?c.K.6D():1V;$.1z(1V,$.Y(B(i,R){if(c.N.6w(R)){c.K.gc=R;1O.3f(R)}},c));E(1O.1p===0)?[c.K.43()]:1O},ga:B(){E c.K.gc},6D:B(){c.K.12();C 9B=c.K.aH(1);C 9G=c.K.aH(2);c.K.aC(c.X,9B,1l);if(c.X.5g===M){c.K.aC(c.X,9G,M)}L{9G=9B}C 1V=[];C 7r=0;C 2Y=c;c.$U.1h(\'*\').1z(B(){if(c==9B){C 1k=$(c).1k();if(1k.1p!==0&&1k[0].1n!=\'b0\'&&2Y.N.4K(1k[0])){1V.3f(1k[0])}1V.3f(c);7r=1}L{if(7r>0){1V.3f(c);7r=7r+1}}if(c==9G){E M}});C aF=[];C 1X=1V.1p;2A(C i=0;i<1X;i++){if(1V[i].id!=\'1V-2J-1\'&&1V[i].id!=\'1V-2J-2\'){aF.3f(1V[i])}}c.K.gR();E aF},aH:B(5v){E $(\'<1b id="1V-2J-\'+5v+\'" 1J="G-1V-2J" 1d-3v="G">\'+c.F.5y+\'</1b>\')[0]},aC:B(X,R,1c){X=X.7A();4G{X.4f(1c);X.3T(R)}4F(e){}},gR:B(){$(1a).1h(\'1b.G-1V-2J\').1v();c.$U.1h(\'1b.G-1V-2J\').1v()},mA:B(6l,hA,6k,hC){c.22.aI(6l,hA,6k,hC)},5q:B(Q){c.K.12();if(c.X.5g)E M;C 4d=1a.3m(Q);4d.6B(c.X.mz());c.X.3T(4d);E 4d},mJ:B(R){c.22.1K(R,0,R,1)},af:B(){c.K.12();c.X.aX(c.$U[0]);c.K.4S()},1v:B(){c.K.12();c.2u.9F()},2N:B(){c.K.hs()},hs:B(){c.K.12();C 5m=c.K.6q(1);c.K.aZ(c.X,5m,1l);if(c.X.5g===M){C 7z=c.K.6q(2);c.K.aZ(c.X,7z,M)}c.hN=c.$U.q()},6q:B(5v){if(1B 5v==\'1H\')5v=1;E $(\'<1b id="K-2J-\'+5v+\'" 1J="G-K-2J"  1d-3v="G">\'+c.F.5y+\'</1b>\')[0]},9E:B(5v){E c.N.5O(c.K.6q(5v))},aZ:B(X,R,1c){X=X.7A();4G{X.4f(1c);X.3T(R)}4F(e){c.28.2O()}},2U:B(){C 5m=c.$U.1h(\'1b#K-2J-1\');C 7z=c.$U.1h(\'1b#K-2J-2\');if(5m.1p!==0&&7z.1p!==0){c.22.1K(5m,0,7z,0)}L if(5m.1p!==0){c.22.1K(5m,0,5m,0)}L{c.$U.28()}c.K.ab();c.hN=M},ab:B(){c.$U.1h(\'1b.G-K-2J\').1v()},mX:B(){c.K.12();E c.2u.4E()},mS:B(){C q=\'\';c.K.12();if(c.2u.9C){C aS=1a.3m(\'1m\');C 1X=c.2u.9C;2A(C i=0;i<1X;++i){aS.6B(c.2u.71(i).mL())}q=aS.3P}E c.1o.aR(q)}}},2l:B(){E{2D:B(){c.2l.89();c.2l.cW()},3D:B(e,2h){C 1A=c.K.3E();C 1k=c.K.5w();c.1r.hF(2h);if(e===M&&2h!==\'q\'){if($.3L(2h,c.F.9D)!=-1)c.1r.mN(2h);E}C hJ=(c.N.4Q(\'A\'))?c.1C.12(\'hH\'):c.1C.12(\'9V\');$(\'3l\').1h(\'a.G-1x-T\').1i(hJ);$.1z(c.F.cM,$.Y(B(1j,1G){if($(1k).2G(1j).1p!==0||$(1A).2G(1j).1p!==0){c.1r.7t(1G)}},c));C $1k=$(1k).2G(c.F.7Q.4E().2L());if($1k.1p){C 4L=($1k.1F(\'1i-4L\')===\'\')?\'1S\':$1k.1F(\'1i-4L\');c.1r.7t(\'4L\'+4L)}},mQ:B(Q,2h){c.F.9D.3f(2h);c.F.cM[Q]=2h},89:B(){c.$U.1h(\'1D\').1z($.Y(B(i,1D){C $1D=$(1D);$1D.2G(\'a\').on(\'2f\',B(e){e.2k()});if(c.N.23(\'2F\'))$1D.1e(\'mP\',\'on\');c.J.iM($1D)},c));$(1a).on(\'2f.G-J-8j\',$.Y(B(e){c.2l.J=M;if(e.1Y.1n==\'cc\'&&c.N.4K(e.1Y)){c.2l.J=(c.2l.J&&c.2l.J==e.1Y)?M:e.1Y}},c))},cW:B(){if(!c.F.h9)E;c.$U.1h(\'a\').on(\'5N 2f\',$.Y(c.2l.gY,c));c.$U.on(\'5N 2f.G\',$.Y(c.2l.cV,c));$(1a).on(\'5N 2f.G\',$.Y(c.2l.cV,c))},gX:B($T){E $T.4t()},gY:B(e){C $T=$(e.1Y);if($T.2a()===0||$T[0].1n!==\'A\')E;C 2X=c.2l.gX($T);C 3c=$(\'<1b 1J="G-T-3c"></1b>\');C 2d=$T.1e(\'2d\');if(2d===1H){2d=\'\'}if(2d.1p>24)2d=2d.ah(0,24)+\'...\';C gS=$(\'<a 2d="\'+$T.1e(\'2d\')+\'" 1Y="6A" />\').q(2d).2s(\'G-T-3c-7l\');C gU=$(\'<a 2d="#" />\').q(c.1C.12(\'7C\')).on(\'2f\',$.Y(c.T.2z,c)).2s(\'G-T-3c-7l\');C gV=$(\'<a 2d="#" />\').q(c.1C.12(\'5T\')).on(\'2f\',$.Y(c.T.5T,c)).2s(\'G-T-3c-7l\');3c.1u(gS).1u(\' | \').1u(gU).1u(\' | \').1u(gV);3c.1F({2T:(2X.2T+20)+\'3I\',1S:2X.1S+\'3I\'});$(\'.G-T-3c\').1v();$(\'3l\').1u(3c)},cV:B(e){e=e.6p||e;if(e.1Y.1n==\'A\'&&!$(e.1Y).3h(\'G-T-3c-7l\')&&c.N.4K(e.1Y)){E}$(\'.G-T-3c\').1v()}}},T:B(){E{2z:B(e){if(1B e!=\'1H\'&&e.2k)e.2k();c.V.2D(\'T\',c.1C.12(\'9V\'),mt);c.V.bA();c.T.hj=c.V.bx(c.1C.12(\'1Q\'));c.K.12();c.T.hd();c.T.hm();if(c.T.1Y==\'6A\')$(\'#G-T-74\').8P(\'8R\',1l);c.T.$8B=$(\'#G-T-2c\');c.T.$cE=$(\'#G-T-2c-1i\');c.T.$cE.2q(c.T.1i);c.T.$8B.2q(c.T.2c);c.T.hj.on(\'2f\',$.Y(c.T.1Q,c));c.K.2N();c.V.2z();c.T.$8B.28()},hm:B(){C hn=2Y.hi.2d.I(/\\/$/i,\'\');c.T.2c=c.T.2c.I(hn,\'\');c.T.2c=c.T.2c.I(/^\\/#/,\'#\');c.T.2c=c.T.2c.I(\'cH:\',\'\');if(!c.F.7u){C 3i=1I 1U(\'^(7J|ay|6h)://\'+2Y.hi.m8,\'i\');c.T.2c=c.T.2c.I(3i,\'\')}},hd:B(){c.T.$R=M;C $el=$(c.K.3E()).2G(\'a\');if($el.2a()!==0&&$el[0].1n===\'A\'){c.T.$R=$el;c.T.2c=$el.1e(\'2d\');c.T.1i=$el.1i();c.T.1Y=$el.1e(\'1Y\')}L{c.T.1i=c.2u.4E();c.T.2c=\'\';c.T.1Y=\'\'}},1Q:B(){C 1Y=\'\';C T=c.T.$8B.2q();C 1i=c.T.$cE.2q();if($.2Z(T)===\'\'){c.T.$8B.2s(\'G-3p-6U\').on(\'2m\',B(){$(c).2R(\'G-3p-6U\');$(c).3G(\'2m\')});E}if(T.3z(\'@\')!=-1&&/(7J|ay|6h):\\/\\//i.80(T)===M){T=\'cH:\'+T}L if(T.3z(\'#\')!==0){if($(\'#G-T-74\').8P(\'8R\')){1Y=\'6A\'}C cC=\'((m7--)?[a-8T-9]+(-[a-8T-9]+)*\\\\.)+[a-z]{2,}\';C 3i=1I 1U(\'^(7J|ay|6h)://\'+cC,\'i\');C he=1I 1U(\'^\'+cC,\'i\');if(T.3z(3i)==-1&&T.3z(he)===0&&c.F.7u){T=c.F.7u+\'://\'+T}}c.T.1K(1i,T,1Y);c.V.42()},1K:B(1i,T,1Y){1i=$.2Z(1i.I(/<|>/g,\'\'));c.K.2U();if(1i===\'\')E;if(c.T.$R){c.1W.1K();c.T.$R.1i(1i).1e(\'2d\',T);if(1Y!==\'\'){c.T.$R.1e(\'1Y\',1Y)}L{c.T.$R.1Z(\'1Y\')}c.14.1P()}L{1a.3V(\'ma\',M,T);C $a=$(c.K.3E()).2G(\'a\');if(1Y!==\'\'){$a.1e(\'1Y\',1Y)}$a.1Z(\'1t\');c.14.1P();c.1R.2n(\'mc\',$a)}3q($.Y(B(){c.2l.cW()},c),5)},5T:B(e){if(1B e!=\'1H\'&&e.2k)e.2k();C 1V=c.K.6D();if(!1V)E;c.1W.1K();C 1X=1V.1p;2A(C i=0;i<1X;i++){if(1V[i].1n==\'A\'){C $R=$(1V[i]);$R.2C($R.26())}}$(\'.G-T-3c\').1v();c.14.1P()}}},J:B(){E{2z:B(){c.V.2D(\'J\',c.1C.12(\'J\'),hf);c.1f.3s(\'#G-V-J-3W\',c.F.5A,c.J.1Q);c.K.2N();c.V.2z()},hX:B($J){C $T=$J.2G(\'a\');c.V.2D(\'hb\',c.1C.12(\'7C\'),m6);c.V.bA();c.J.h7=c.V.gl(c.1C.12(\'hk\'));c.J.ho=c.V.bx(c.1C.12(\'2N\'));c.J.h7.on(\'2f\',$.Y(B(){c.J.1v($J)},c));c.J.ho.on(\'2f\',$.Y(B(){c.J.j4($J)},c));$(\'#G-J-1E\').2q($J.1e(\'7b\'));if(!c.F.hQ)$(\'.G-J-T-3H\').3a();L{C $bZ=$(\'#G-J-T\');$bZ.1e(\'2d\',$J.1e(\'3F\'));if($T.2a()!==0){$bZ.2q($T.1e(\'2d\'));if($T.1e(\'1Y\')==\'6A\')$(\'#G-J-T-74\').8P(\'8R\',1l)}}if(!c.F.g8)$(\'.G-J-4b-3H\').3a();L{C g3=($J.1F(\'6L\')==\'O\'&&$J.1F(\'6C\')==\'5R\')?\'55\':$J.1F(\'6C\');$(\'#G-J-4L\').2q(g3)}c.V.2z()},iP:B($J){C gE=$(\'#G-J-4L\').2q();C 8O=\'\';C c5=\'\';C 8E=\'\';gH(gE){8z\'1S\':8O=\'1S\';8E=\'0 \'+c.F.8H+\' \'+c.F.8H+\' 0\';8C;8z\'4l\':8O=\'4l\';8E=\'0 0 \'+c.F.8H+\' \'+c.F.8H;8C;8z\'55\':c5=\'O\';8E=\'8U\';8C}$J.1F({\'6C\':8O,6L:c5,4j:8E});$J.1e(\'3N\',$J.1e(\'1t\'))},j4:B($J){c.J.5V();c.1W.1K();C $T=$J.2G(\'a\');$J.1e(\'7b\',$(\'#G-J-1E\').2q());c.J.iP($J);C T=$.2Z($(\'#G-J-T\').2q());if(T!==\'\'){C 1Y=($(\'#G-J-T-74\').8P(\'8R\'))?1l:M;if($T.2a()===0){C a=$(\'<a 2d="\'+T+\'">\'+c.N.5O($J)+\'</a>\');if(1Y)a.1e(\'1Y\',\'6A\');$J.2C(a)}L{$T.1e(\'2d\',T);if(1Y){$T.1e(\'1Y\',\'6A\')}L{$T.1Z(\'1Y\')}}}L if($T.2a()!==0){$T.2C(c.N.5O($J))}c.V.42();c.2l.89();c.14.1P()},iM:B($J){if(!c.F.j9)E;$J.on(\'m1\',$.Y(c.J.ai,c));$J.on(\'bV\',$.Y(c.J.5V,c));$J.on(\'2f 5N\',$.Y(B(e){c.2l.J=$J;if(c.$U.1h(\'#G-J-2t\').2a()!==0)E M;if(!c.F.cr)E;c.J.86=c.J.i2($J);c.J.86.on(\'bV.G 5N.G\',$.Y(B(e){e.2k();c.J.58={x:e.iV,y:e.8N,el:$J,i9:$J.2v()/$J.2Q(),h:$J.2Q()};e=e.6p||e;if(e.7F){c.J.58.x=e.7F[0].iV;c.J.58.y=e.7F[0].8N}c.J.iU()},c));$(1a).on(\'2f.G-J-48-3a\',$.Y(c.J.5V,c));c.$U.on(\'2f.G-J-48-3a\',$.Y(c.J.5V,c))},c))},iU:B(){$(1a).on(\'m4.G-J-48 m3.G-J-48\',$.Y(c.J.jn,c));$(1a).on(\'jl.G-J-48 md.G-J-48\',$.Y(c.J.iK,c))},jn:B(e){e.2k();e=e.6p||e;C 2Q=c.J.58.h;if(e.7F)2Q+=(e.7F[0].8N-c.J.58.y);L 2Q+=(e.8N-c.J.58.y);C 2v=mn.mp(2Q*c.J.58.i9);if(2Q<50||2v<7h)E;c.J.58.el.2Q(2Q);c.J.58.el.2v(2v);c.14.1P()},iK:B(){c.gd=M;$(1a).3G(\'.G-J-48\');c.J.5V()},ai:B(e){if(c.$U.1h(\'#G-J-2t\').2a()!==0){e.2k();E M}c.$U.on(\'4T.G-J-ii-4T\',$.Y(B(){3q($.Y(c.J.ax,c),1)},c))},ax:B(){c.J.hU();c.2l.89();c.$U.3G(\'4T.G-J-ii-4T\');c.1o.4a();c.14.1P()},hU:B(){c.$U.1h(\'1D[1d-2N-2c]\').1z(B(){C $el=$(c);$el.1e(\'3F\',$el.1e(\'1d-2N-2c\'));$el.1Z(\'1d-2N-2c\')})},5V:B(e){if(e&&$(e.1Y).2G(\'#G-J-2t\').1p!==0)E;if(e&&e.1Y.1n==\'cc\'){C $J=$(e.1Y);$J.1e(\'1d-2N-2c\',$J.1e(\'3F\'))}C 3k=c.$U.1h(\'#G-J-2t\');if(3k.2a()===0)E;c.J.4I.1v();c.J.86.1v();3k.1h(\'1D\').1F({5b:3k[0].1t.5b,8S:3k[0].1t.8S,8J:3k[0].1t.8J,8Q:3k[0].1t.8Q});3k.1F(\'4j\',\'\');3k.1h(\'1D\').1F(\'ir\',\'\');3k.2C(B(){E $(c).26()});$(1a).3G(\'2f.G-J-48-3a\');c.$U.3G(\'2f.G-J-48-3a\');c.14.1P()},i2:B($J){C 3k=$(\'<1b id="G-J-2t" 1d-G="3v">\');3k.1F({4b:\'i0\',6L:\'1T-O\',cq:0,ms:\'iO mr mm(0, 0, 0, .6)\',\'6C\':$J.1F(\'6C\')});3k.1e(\'72\',M);if($J[0].1t.4j!=\'8U\'){3k.1F({5b:$J[0].1t.5b,8S:$J[0].1t.8S,8J:$J[0].1t.8J,8Q:$J[0].1t.8Q});$J.1F(\'4j\',\'\')}L{3k.1F({\'6L\':\'O\',\'4j\':\'8U\'})}$J.1F(\'ir\',\'.5\').3u(3k);c.J.4I=$(\'<1b id="G-J-4I" 1d-G="3v">\'+c.1C.12(\'7C\')+\'</1b>\');c.J.4I.1F({4b:\'7W\',ia:5,2T:\'50%\',1S:\'50%\',5b:\'-io\',8J:\'-mj\',cq:1,j6:\'#j2\',mi:\'#j5\',mh:\'io\',mf:\'mg ip\',bL:\'mq\'});c.J.4I.1e(\'72\',M);c.J.4I.on(\'2f\',$.Y(B(){c.J.hX($J)},c));3k.1u(c.J.4I);C ic=c.J.4I.aA();c.J.4I.1F(\'4j-1S\',\'-\'+ic/2+\'3I\');if(c.F.cr&&!c.N.5x()){C 75=$(\'<1b id="G-J-86" 1d-G="3v"></1b>\');75.1F({4b:\'7W\',ia:2,cq:1,bL:\'nw-48\',mo:\'-m2\',4l:\'-lZ\',m0:\'iO m5 #j5\',j6:\'#j2\',2v:\'gx\',2Q:\'gx\'});if(!c.N.bi()){75.1F({2v:\'gF\',2Q:\'gF\'})}75.1e(\'72\',M);3k.1u(75);3k.1u($J);E 75}L{3k.1u($J);E M}},1v:B(J){C $J=$(J);C $T=$J.2G(\'a\');C $6o=$J.2G(\'6o\');C $1k=$J.1k();if($(\'#G-J-2t\').2a()!==0){$1k=$(\'#G-J-2t\').1k()}C $2w;if($6o.2a()!==0){$2w=$6o.2w();$6o.1v()}L if($T.2a()!==0){$1k=$T.1k();$T.1v()}L{$J.1v()}$(\'#G-J-2t\').1v();if($6o.2a()!==0){c.22.2O($2w)}L{c.22.2O($1k)}c.1R.2n(\'c2\',$J[0].3F,$J);c.V.42();c.14.1P()},1Q:B(2r,4i,e){if(1B 2r.6U!=\'1H\'){c.V.42();c.K.2U();c.1R.2n(\'mb\',2r);E}C $1D;if(1B 2r==\'5p\'){$1D=$(2r).1e(\'1d-G-8r-J\',\'1l\')}L{$1D=$(\'<1D>\');$1D.1e(\'3F\',2r.76).1e(\'1d-G-8r-J\',\'1l\')}C R=$1D;C cR=c.N.4Q(\'P\');if(cR){R=$(\'<1N />\').1u($1D)}if(4i){c.K.ab();C 2J=c.K.6q();c.1Q.cG(e,2J)}L{c.V.42()}c.K.2U();c.1W.1K();c.1Q.q(c.N.5O(R));C $J=c.$U.1h(\'1D[1d-G-8r-J=1l]\').1Z(\'1d-G-8r-J\');if(cR){$J.1k().26().44().5q(\'<p />\')}L if(c.F.1M){$J.cJ(\'<br>\').3u(\'<br>\')}if(1B 2r==\'5p\')E;c.1R.2n(\'5A\',$J,2r)}}},1L:B(){E{2z:B(){c.V.2D(\'1L\',c.1C.12(\'1L\'),hf);c.1f.3s(\'#G-V-1L-1f\',c.F.8o,c.1L.1Q);c.K.2N();c.K.12();C 1i=c.2u.4E();$(\'#G-68\').2q(1i);c.V.2z()},1Q:B(2r,4i,e){if(1B 2r.6U!=\'1H\'){c.V.42();c.K.2U();c.1R.2n(\'m9\',2r);E}C T;if(1B 2r==\'5p\'){T=2r}L{C 1i=$(\'#G-68\').2q();if(1B 1i==\'1H\'||1i===\'\')1i=2r.68;T=\'<a 2d="\'+2r.76+\'" id="76-2J">\'+1i+\'</a>\'}if(4i){c.K.ab();C 2J=c.K.6q();c.1Q.cG(e,2J)}L{c.V.42()}c.K.2U();c.1W.1K();c.1Q.q(T);if(1B 2r==\'5p\')E;C 7D=$(c.$U.1h(\'a#76-2J\'));if(7D.2a()!==0)7D.1Z(\'id\');L 7D=M;c.1R.2n(\'8o\',7D,2r)}}},V:B(){E{a4:{},hl:B(){c.F.V={hb:5o()+\'<3Q id="G-V-J-7C">\'+\'<31>\'+c.1C.12(\'1E\')+\'</31>\'+\'<3p 1c="1i" id="G-J-1E" />\'+\'<31 1J="G-J-T-3H">\'+c.1C.12(\'T\')+\'</31>\'+\'<3p 1c="1i" id="G-J-T" 1J="G-J-T-3H" />\'+\'<31 1J="G-J-T-3H"><3p 1c="h0" id="G-J-T-74"> \'+c.1C.12(\'cN\')+\'</31>\'+\'<31 1J="G-J-4b-3H">\'+c.1C.12(\'ha\')+\'</31>\'+\'<6a 1J="G-J-4b-3H" id="G-J-4L">\'+\'<3H 1G="5R">\'+c.1C.12(\'5R\')+\'</3H>\'+\'<3H 1G="1S">\'+c.1C.12(\'1S\')+\'</3H>\'+\'<3H 1G="55">\'+c.1C.12(\'55\')+\'</3H>\'+\'<3H 1G="4l">\'+c.1C.12(\'4l\')+\'</3H>\'+\'</6a>\'+\'</3Q>\',J:5o()+\'<3Q id="G-V-J-1Q">\'+\'<1m id="G-V-J-3W"></1m>\'+\'</3Q>\',1L:5o()+\'<3Q id="G-V-1L-1Q">\'+\'<1m id="G-V-1L-1f-2t">\'+\'<31>\'+c.1C.12(\'68\')+\'</31>\'+\'<3p 1c="1i" id="G-68" /><br><br>\'+\'<1m id="G-V-1L-1f"></1m>\'+\'</1m>\'+\'</3Q>\',T:5o()+\'<3Q id="G-V-T-1Q">\'+\'<31>gZ</31>\'+\'<3p 1c="2c" id="G-T-2c" />\'+\'<31>\'+c.1C.12(\'1i\')+\'</31>\'+\'<3p 1c="1i" id="G-T-2c-1i" />\'+\'<31><3p 1c="h0" id="G-T-74"> \'+c.1C.12(\'cN\')+\'</31>\'+\'</3Q>\'};$.7e(c.F,c.F.V)},cO:B(1g,2g){c.V.a4[1g]=2g},mO:B($V){c.V.$7w=$(\'<1m>\').1e(\'id\',\'G-V-7w\');$V.4N(c.V.$7w)},mM:B(id,1g,7y){C $5e=$(\'<a 2d="#" 3N="5e\'+id+\'">\').1i(1g);if(7y){$5e.2s(\'7y\')}C 2Y=c;$5e.on(\'2f\',B(e){e.2k();$(\'.G-5e\').3a();$(\'.G-\'+$(c).1e(\'3N\')).2z();2Y.V.$7w.1h(\'a\').2R(\'7y\');$(c).2s(\'7y\')});c.V.$7w.1u($5e)},mR:B(1g,hP){c.F.V[1g]=hP},hy:B(1g){E c.F.V[1g]},mY:B(){E c.$a9.1h(\'3Q\')},2D:B(5H,1E,2v){c.V.5H=5H;c.V.2v=2v;c.V.2j();c.V.g5();c.V.hw(1E);c.V.gQ();c.V.hB();if(1B c.V.a4[5H]!=\'1H\'){c.V.a4[5H].69(c)}},2z:B(){c.V.gG=$(1a.3l).1F(\'6N\');$(1a.3l).1F(\'6N\',\'aT\');if(c.N.5x()){c.V.aW()}L{c.V.ad()}c.$6H.2z();c.$4u.2z();c.V.gj();c.N.7O();if(!c.N.5x()){3q($.Y(c.V.ad,c),0);$(3R).on(\'48.G-V\',$.Y(c.V.48,c))}c.1R.2n(\'mW\',c.V.5H,c.$V);$(1a).3G(\'mV.V\');c.$V.1h(\'3p[1c=1i]\').on(\'mT.G-V\',$.Y(c.V.gf,c))},ad:B(){C 2Q=c.$V.mU();C ag=$(3R).2Q();C hu=$(3R).2v();if(c.V.2v>hu){c.$V.1F({2v:\'96%\',5b:(ag/2-2Q/2)+\'3I\'});E}if(2Q>ag){c.$V.1F({2v:c.V.2v+\'3I\',5b:\'mK\'})}L{c.$V.1F({2v:c.V.2v+\'3I\',5b:(ag/2-2Q/2)+\'3I\'})}},aW:B(){c.$V.1F({2v:\'96%\',5b:\'2%\'})},48:B(){if(c.N.5x()){c.V.aW()}L{c.V.ad()}},hw:B(1E){c.$7s.q(1E)},hB:B(){c.$a9.q(c.V.hy(c.V.5H))},gQ:B(){if(1B $.fn.ge===\'1H\')E;c.$V.ge({gd:c.$7s});c.$7s.1F(\'bL\',\'my\')},gf:B(e){if(e.8q!=13)E;e.2k();c.$V.1h(\'1r.G-V-7l-25\').2f()},bA:B(){C 1r=$(\'<1r>\').2s(\'G-V-25 G-V-42-25\').q(c.1C.12(\'gm\'));1r.on(\'2f\',$.Y(c.V.42,c));c.$7P.1u(1r)},gl:B(31){E c.V.bw(31,\'8j\')},bx:B(31){E c.V.bw(31,\'7l\')},bw:B(31,4q){C 1r=$(\'<1r>\').2s(\'G-V-25\').2s(\'G-V-\'+4q+\'-25\').q(31);c.$7P.1u(1r);E 1r},gj:B(){C 3D=c.$7P.1h(\'1r\');C bv=3D.2a();if(bv===0)E;3D.1F(\'2v\',(7h/bv)+\'%\')},2j:B(){c.V.fW();c.$4u=$(\'<1m id="G-V-2t" />\').3a();c.$V=$(\'<1m id="G-V" />\');c.$7s=$(\'<a8 />\');c.$8p=$(\'<1b id="G-V-42" />\').q(\'&mH;\');c.$a9=$(\'<1m id="G-V-3l" />\');c.$7P=$(\'<bI />\');c.$V.1u(c.$7s);c.$V.1u(c.$8p);c.$V.1u(c.$a9);c.$V.1u(c.$7P);c.$4u.1u(c.$V);c.$4u.ac(1a.3l)},fW:B(){c.$6H=$(\'<1m id="G-V-mD">\').3a();$(\'3l\').4N(c.$6H)},g5:B(){c.$8p.on(\'2f.G-V\',$.Y(c.V.42,c));$(1a).on(\'2m.G-V\',$.Y(c.V.bp,c));c.$U.on(\'2m.G-V\',$.Y(c.V.bp,c));c.$4u.on(\'2f.G-V\',$.Y(c.V.42,c))},gn:B(){c.$8p.3G(\'2f.G-V\');$(1a).3G(\'2m.G-V\');c.$U.3G(\'2m.G-V\');c.$4u.3G(\'2f.G-V\');$(3R).3G(\'48.G-V\')},bp:B(e){if(e.8q!=c.3e.ba)E;c.V.42(M)},42:B(e){if(e){if(!$(e.1Y).3h(\'G-V-42-25\')&&e.1Y!=c.$8p[0]&&e.1Y!=c.$4u[0]){E}e.2k()}if(!c.$4u)E;c.V.gn();c.$6H.1v();c.$4u.gJ(\'lf\',$.Y(B(){c.$4u.1v();3q($.Y(c.N.ak,c),0);if(e!==1H)c.K.2U();$(1a.3l).1F(\'6N\',c.V.gG);c.1R.2n(\'lg\',c.V.5H)},c))}}},54:B(){E{2z:B(){$(1a.3l).1u($(\'<1m id="G-54"><1b></1b></1m>\'));$(\'#G-54\').le()},3a:B(){$(\'#G-54\').gJ(ld,B(){$(c).1v()})}}},1f:B(){E{3s:B(id,2c,2g){c.1f.4i=M;c.1f.2g=2g;c.1f.2c=2c;c.1f.$el=$(id);c.1f.$3W=$(\'<1m id="G-3W" />\');c.1f.$bj=$(\'<1m id="G-3W-2P" />\').1i(\'gL 1L gp or \');c.1f.$3p=$(\'<3p 1c="1L" 1g="1L" />\');c.1f.$bj.1u(c.1f.$3p);c.1f.$3W.1u(c.1f.$bj);c.1f.$el.1u(c.1f.$3W);c.1f.$3W.3G(\'G.1f\');c.1f.$3p.3G(\'G.1f\');c.1f.$3W.on(\'ln.G.1f\',$.Y(c.1f.ai,c));c.1f.$3W.on(\'lk.G.1f\',$.Y(c.1f.hT,c));c.1f.$3p.on(\'j3.G.1f\',$.Y(B(e){e=e.6p||e;c.1f.am(c.1f.$3p[0].5E[0],e)},c));c.1f.$3W.on(\'4T.G.1f\',$.Y(B(e){e.2k();c.1f.$3W.2R(\'78-bM\').2s(\'78-4T\');c.1f.ax(e)},c))},iX:B(1L,e){c.1f.4i=1l;c.1f.am(1L,e)},ax:B(e){e=e.6p||e;C 5E=e.av.5E;c.1f.am(5E[0],e)},am:B(1L,e){if(c.F.a5){c.1f.bs(1L);c.1f.iA(1L);E}C 3y=!!3R.5U?1I 5U():3n;if(3R.5U){c.1f.bs(1L);C 1g=(c.1f.1c==\'J\')?c.F.iT:c.F.iS;3y.1u(1g,1L)}c.54.2z();c.1f.aJ(3y,e)},bs:B(1L){c.1f.ja(1L);if(c.1f.4i){c.1f.2c=(c.1f.1c==\'J\')?c.F.5A:c.F.8o;c.1f.2g=(c.1f.1c==\'J\')?c.J.1Q:c.1L.1Q}},ja:B(1L){c.1f.1c=\'J\';if(c.F.jh.4z(1L.1c)==-1){c.1f.1c=\'1L\'}},8n:B(79,fd){if(79===M||1B 79!==\'4k\')E fd;$.1z(79,$.Y(B(k,v){if(v!==3n&&v.4E().4z(\'#\')===0)v=$(v).2q();fd.1u(k,v)},c));E fd},aJ:B(3y,e){if(c.1f.1c==\'J\'){3y=c.1f.8n(c.F.a7,3y);3y=c.1f.8n(c.1f.ao,3y)}L{3y=c.1f.8n(c.F.l4,3y);3y=c.1f.8n(c.1f.a2,3y)}C 2y=1I ck();2y.a6(\'lq\',c.1f.2c);2y.im=$.Y(B(){if(2y.ct==4){C 1d=2y.iq;1d=1d.I(/^\\[/,\'\');1d=1d.I(/\\]$/,\'\');C 2r=(1B 1d===\'5p\'?$.aK(1d):1d);c.54.3a();if(!c.1f.4i){c.1f.$3W.2R(\'78-4T\')}c.1f.2g(2r,c.1f.4i,e)}},c);2y.bn(3y)},ai:B(e){e.2k();c.1f.$3W.2s(\'78-bM\')},hT:B(e){e.2k();c.1f.$3W.2R(\'78-bM\')},lr:B(){c.1f.ao={}},lN:B(1g,1G){c.1f.ao[1g]=1G},lO:B(1g){8j c.1f.ao[1g]},lM:B(){c.1f.a2={}},lL:B(1g,1G){c.1f.a2[1g]=1G},lK:B(1g){8j c.1f.a2[1g]},iA:B(1L){c.1f.iE(1L,$.Y(B(iz){c.1f.d8(1L,iz)},c))},iE:B(1L,2g){C 2y=1I ck();C 6c=\'?\';if(c.F.a5.3z(/\\?/)!=\'-1\')6c=\'&\';2y.a6(\'lV\',c.F.a5+6c+\'1g=\'+1L.1g+\'&1c=\'+1L.1c,1l);if(2y.iG)2y.iG(\'1i/lH; lx=x-ly-lw\');C il=c;2y.im=B(e){if(c.ct==4&&c.cl==a1){il.54.2z();2g(lv(c.iq))}L if(c.ct==4&&c.cl!=a1){}};2y.bn()},df:B(as,2c){C 2y=1I ck();if("lu"in 2y){2y.a6(as,2c,1l)}L if(1B g7!="1H"){2y=1I g7();2y.a6(as,2c)}L{2y=3n}E 2y},d8:B(1L,2c){C 2y=c.1f.df(\'lA\',2c);if(!2y){}L{2y.cX=$.Y(B(){if(2y.cl==a1){c.54.3a();C 9X=2c.3K(\'?\');if(!9X[0]){E M}if(!c.1f.4i){c.1f.$3W.2R(\'78-4T\')}C 2r={76:9X[0]};if(c.1f.1c==\'1L\'){C 2x=9X[0].3K(\'/\');2r.68=2x[2x.1p-1]}c.1f.2g(2r,c.1f.4i,M)}L{}},c);2y.lF=B(){};2y.1f.lE=B(e){};2y.dG(\'lB-oC\',1L.1c);2y.dG(\'x-nu-nt\',\'kT-ns\');2y.bn(1L)}}}},N:B(){E{5x:B(){E/(d7|d5|fh|fg)/.80(au.al)},bi:B(){E!/(d7|d5|nh|fh|fg)/.80(au.al)},ng:B(79){E fp.5f.4E.69(79)==\'[4k 5o]\'},4m:B(q,eR){q=q.I(/[\\7E-\\aw\\a3]/g,\'\');q=q.I(/&5i;/gi,\'\');q=q.I(/<\\/?br\\s?\\/?>/g,\'\');q=q.I(/\\s/g,\'\');q=q.I(/^<p>[^\\W\\w\\D\\d]*?<\\/p>$/i,\'\');if(eR!==M){q=q.I(/<[^\\/>][^>]*><\\/[^>]+>/gi,\'\');q=q.I(/<[^\\/>][^>]*><\\/[^>]+>/gi,\'\')}q=$.2Z(q);E q===\'\'},a0:B(5k){if(1B(5k)===\'1H\')E 0;E 6J(5k.I(\'3I\',\'\'),10)},n5:B(64){if(1B 64==\'1H\')E;if(64.3z(/^#/)==-1)E 64;C eP=/^#?([a-f\\d])([a-f\\d])([a-f\\d])$/i;64=64.I(eP,B(m,r,g,b){E r+r+g+g+b+b});C 6u=/^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.4O(64);E\'n9(\'+6J(6u[1],16)+\', \'+6J(6u[2],16)+\', \'+6J(6u[3],16)+\')\'},5O:B(el){E $(\'<1m>\').1u($(el).eq(0).fC()).q()},aq:B(el){if($.3L(el.1n,c.F.7Q)!==-1){E $(el)}L{E $(el).2G(c.F.7Q.4E().2L(),c.$U[0])}},5X:B(el,1e){C $el=$(el);if(1B $el.1e(1e)==\'1H\'){E 1l}if($el.1e(1e)===\'\'){$el.1Z(1e);E 1l}E M},3S:B(i,s){C $s=$(s);C 1i=$.2Z($s.1i());if($s.49(\'hr, br, 1D\').1p!==0)E;if(c.N.4m(1i,M)){$s.1v()}},7O:B(){if(c.N.6M())E;c.dS=c.$U.3U();c.at=$(3R).3U();if(c.F.7S)c.dQ=$(c.F.7S).3U()},ak:B(){if(!c.7O&&!c.at)E;$(3R).3U(c.at);c.$U.3U(c.dS);if(c.F.7S)$(c.F.7S).3U(c.dQ)},cu:B(){C 3o=1a.3m(\'1b\');3o.4q=\'G-ap-3o\';3o.3P=c.F.5y;E 3o},4A:B(R){C 2i=c.F.4P;2i.3f(\'1b\');if(R.1n==\'aj\')2i.3f(\'a\');$(R).1h(2i.2K(\',\')).5S(\'1b.G-K-2J\').26().44()},5B:B(R,4A){C 2Y=c;$(R).2C(B(){if(4A===1l)2Y.N.4A(c);E $(c).26()})},4p:B(R,Q,4A){C 6F;C 2Y=c;$(R).2C(B(){6F=$(\'<\'+Q+\' />\').1u($(c).26());2A(C i=0;i<c.4n.1p;i++){6F.1e(c.4n[i].1g,c.4n[i].1G)}if(4A===1l)2Y.N.4A(6F);E 6F});E 6F},o2:B(){C O=c.K.43();if(!O)E M;C 4t=c.22.c0(O);E(4t===0)?1l:M},7N:B(){C O=c.K.43();if(!O)E M;C 4t=c.22.c0(O);C 1i=$.2Z($(O).1i()).I(/\\n\\r\\n/g,\'\');C 1X=1i.1p;E(4t==1X)?1l:M},6w:B(O){O=O[0]||O;E O&&c.N.6y(O.1n)},6y:B(Q){if(1B Q==\'1H\')E M;E c.dW.80(Q)},9Z:B(1A,Q){C 2B=$(1A).2G(Q);if(2B.2a()==1){E 2B[0]}E M},6M:B(){E c.af},eg:B(){c.af=1l},7H:B(){c.af=M},4K:B(el){if(!el){E M}if($(el).eE(\'.G-U\').1p===0||$(el).3h(\'G-U\')){E M}E el},4Q:B(1n){C 1k=c.K.5w();C 1A=c.K.3E();if($.aa(1n)){C bN=0;$.1z(1n,$.Y(B(i,s){if(c.N.bR(1A,1k,s)){bN++}},c));E(bN===0)?M:1l}L{E c.N.bR(1A,1k,1n)}},bR:B(1A,1k,1n){E 1k&&1k.1n===1n?1k:1A&&1A.1n===1n?1A:M},o0:B(){E(c.N.23(\'2F\')&&6J(c.N.23(\'ar\'),10)<9)?1l:M},nP:B(){E(c.N.23(\'2F\')&&6J(c.N.23(\'ar\'),10)<10)?1l:M},ez:B(){E!!au.al.2e(/nM\\/7\\./)},23:B(23){C 4x=au.al.2L();C 2e=/(es)[\\/]([\\w.]+)/.4O(4x)||/(ev)[ \\/]([\\w.]+)/.4O(4x)||/(6Q)[ \\/]([\\w.]+).*(nK)[ \\/]([\\w.]+)/.4O(4x)||/(6Q)[ \\/]([\\w.]+)/.4O(4x)||/(nI)(?:.*ar|)[ \\/]([\\w.]+)/.4O(4x)||/(2F) ([\\w.]+)/.4O(4x)||4x.4z("nJ")>=0&&/(cf)(?::| )([\\w.]+)/.4O(4x)||4x.4z("lC")<0&&/(7c)(?:.*? cf:([\\w.]+)|)/.4O(4x)||[];if(23==\'ar\')E 2e[2];if(23==\'6Q\')E(2e[1]==\'ev\'||2e[1]==\'6Q\');if(2e[1]==\'cf\')E 23==\'2F\';if(2e[1]==\'es\')E 23==\'6Q\';E 23==2e[1]}}}};3A.5f.3s.5f=3A.5f;$.3A.fn.aD=B(bz,82,6E,6P,6v,6z){C fu=\'((?:7J[s]?:\\\\/\\\\/(?:ae\\\\.)?|ae\\\\.){1}(?:[0-9A-6g-z\\\\-%5F]+\\\\.)+[a-jH-Z]{2,}(?::[0-9]+)?(?:(?:/[0-9A-6g-z\\\\-\\\\.%5F]*)+)?(?:\\\\?(?:[0-9A-6g-z\\\\-\\\\.%5F]+(?:=[0-9A-6g-z\\\\-\\\\.%5F\\\\+]*)?)?(?:&(?:[0-9A-6g-z\\\\-\\\\.%5F]+(?:=[0-9A-6g-z\\\\-\\\\.%5F\\\\+]*)?)?)*)?(?:#[0-9A-6g-z\\\\-\\\\.%5F\\\\+=\\\\?&;]*)?)\';C 7d=1I 1U(fu,\'gi\');C jq=/(6h?|ay):\\/\\//i;C bY=/(6h?:\\/\\/.*\\.(?:eO|kp|fq|fU))/gi;C az=(c.$U?c.$U[0]:c).az,i=az.1p;56(i--){C n=az[i];if(n.an===3){C q=n.kN;if(6v&&q){C c4=\'<cw 2v="kO" 2Q="kP" 3F="\',bO=\'" kR="0" jQ></cw>\';if(q.2e(cT)){q=q.I(cT,c4+\'//ae.dF.9Y/6W/$1\'+bO);$(n).3u(q).1v()}L if(q.2e(cx)){q=q.I(cx,c4+\'//kj.dz.9Y/63/$2\'+bO);$(n).3u(q).1v()}}if(6P&&q&&q.2e(bY)){q=q.I(bY,\'<1D 3F="$1">\');$(n).3u(q).1v()}if(q.3z(/\\$/g)!=-1)q=q.I(/\\$/g,\'&#36;\');C 4s=q.2e(7d);if(6E&&q&&4s){C 1X=4s.1p;2A(C z=0;z<1X;z++){if(4s[z].2e(/\\.$/)!==3n)4s[z]=4s[z].I(/\\.$/,\'\');C 2d=4s[z];C 1i=2d;C 3o=\'\';if(2d.2e(/\\s$/)!==3n)3o=\' \';C b2=bz+\'://\';if(2d.2e(jq)!==3n)b2=\'\';if(1i.1p>6z)1i=1i.ah(0,6z)+\'...\';1i=1i.I(/&#36;/g,\'$\').I(/&/g,\'&bD;\').I(/</g,\'&lt;\').I(/>/g,\'&gt;\');q=q.I(2d,\'<a 2d=\\"\'+b2+$.2Z(2d)+\'\\">\'+$.2Z(1i)+\'</a>\'+3o)}$(n).3u(q).1v()}}L if(n.an===1&&!/^(a|1r|2M)$/i.80(n.1n)){$.3A.fn.aD.69(n,bz,82,6E,6P,6v,6z)}}}})(la);',62,1557,'||||||||||||this||||||||||||||html|||||||||||function|var||return|opts|redactor||replace|image|selection|else|false|utils|block||tag|node||link|editor|modal||range|proxy||||get||code||||||document|span|type|data|attr|upload|name|find|text|key|parent|true|div|tagName|clean|length|toolbar|button|func|style|append|remove|keydown|dropdown|tidy|each|current|typeof|lang|img|title|css|value|undefined|new|class|set|file|linebreaks|blockquote|blocks|sync|insert|core|left|inline|RegExp|nodes|buffer|len|target|removeAttr||formatted|caret|browser||btn|contents||focus|settings|size|out|url|href|match|click|callback|btnName|tags|build|preventDefault|observe|keyup|setCallback|paragraphize|tabifier|val|json|addClass|box|sel|width|next|arr|xhr|show|for|element|replaceWith|load|formatting|msie|closest|ul|toggle|marker|join|toLowerCase|textarea|save|setStart|placeholder|height|removeClass|list|top|restore|pre|format|pos|self|trim||label|||||||||hide|start|tooltip|indent|keyCode|push|item|hasClass|re|btnObject|imageBox|body|createElement|null|space|input|setTimeout|alignment|init|last|after|verified|font|autosave|formData|search|Redactor|point|options|buttons|getCurrent|src|off|option|px|td|split|inArray|first|rel|ctrl|innerHTML|section|window|removeEmpty|insertNode|scrollTop|execCommand|droparea|italic|bold|getBlocks||del|close|getBlock|unwrap|||command|resize|children|clearUnverified|position|setEnd|wrapper|blockElem|collapse|orgn|cmd|direct|margin|object|right|isEmpty|attributes|table|replaceToTag|className|htmls|matches|offset|modalBox|paste|arrow|ua|deniedTags|indexOf|removeInlineTags|keys|elem|line|toString|catch|try|replaceTags|editter|allowedTags|isRedactorParent|align|cont|prepend|exec|inlineTags|isCurrentOrParent|substr|addRange|drop|isFunction|meta|lastNode|toggleType|LI|orderedlist||underline|strong|frag|progress|center|while|tmp|resizeHandle||attributesRemove|marginTop|allowedAttr|replaceStyles|tab|prototype|collapsed|allowed|nbsp|script|str|removeDataAttr|node1|items|String|string|wrap|unorderedlist|rect|containerTag|form|num|getParent|isMobile|invisibleSpace|end|imageUpload|replaceWithContents|params|shortcuts|files|_|deleteContents|templateName|setAfter|pasteBox|charAt|methods|deleted|touchstart|getOuterHtml|getSelection|tr|none|not|unlink|FormData|hideResize|boxTop|removeEmptyAttr|module|removeWithoutAttr|||removeComments|video|hex|||instance|filename|call|select|shift|mark|BR|ENTER|metaKey|Za|https|prev|index|startY|startX|getEvent|Insert|figure|originalEvent|getMarker|shiftKey|preSpaces|visual|result|convertVideoLinks|isBlock|caretPositionFromPoint|isBlockTag|linkSize|_blank|appendChild|float|getNodes|convertUrlLinks|replacement|focn|modalOverlay|endRange|parseInt|getTabs|display|isSelectAll|overflow|createTextRange|convertImageLinks|webkit|listParent|innerHeight|figcaption|error|SPAN|embed|dropact|tmpList|possible|modif|getRangeAt|contenteditable||blank|imageResizer|filelink|weight|drag|obj|rtePaste|alt|mozilla|regex|extend|sup|sub|100|replaceDivs|blocksSize|caretRangeFromPoint|action|BACKSPACE|th|fixed|outdent|scroll|counter|modalHeader|setActive|linkProtocol|moveToPoint|tabber|createTextNode|active|node2|cloneRange|createRange|edit|linkmarker|u200B|targetTouches|tagout|disableSelectAll|attrs|http|param|setMode|cleanlevel|isEndOfElement|saveScroll|modalFooter|alignmentTags|autosaveInterval|scrollTarget|newLevel|focusNode|audio|absolute|Delete|plugins|uuid|test|Add|convertLinks|Header|tabindex|icon|resizer|small|cite|images|act|disabled|keyPosition|tagblock|justify|OL|blockLevelElements|isRemoveInline|UL|delete|addEvent|classSize|exceptTags|getHiddenFields|fileUpload|modalClose|which|inserted|emptyHtml|setVerified|strike|onPaste|singleLine|tmpLi|TD|case|apply|inputUrl|break|Array|imageMargin|rebuffer|insertBreakLine|imageFloatMargin|character|marginLeft|ctrlKey|DIV|DELETE|pageY|imageFloat|prop|marginRight|checked|marginBottom|z0|auto|specialKey|matchContainers|replaceParagraphsToBr|samp|kbd|||source|splice|safes|indentValue|||||clipboardData|enable|placeTag|codeLength|onPasteTidy|decoration|maxHeight|RedactorPlugins|minHeight|isTextarea|methodVal|bind|horizontalrule|onClick|hideAll|classname|toolbarFixedTarget|toolbarExternal|undo|tabAsSpaces|getClientRects|parentNode|isFocused|cloned|parHtml|inlines||startNode|rangeCount|activeButtons|getMarkerAsHtml|removeAllRanges|endNode|getPlainText|toggleClass|insertDblBreakLine|special|blockHtml|decrease|increase|insertInIe|wrapperHtml|formatblock|formatListToBlockquote|BLOCKQUOTE|isContainerTable|fixEmptyIndent|link_insert|normalizeLists|s3file|com|isTag|normalize|200|fileFields|uFEFF|callbacks|s3|open|uploadImageFields|header|modalBody|isArray|removeMarkers|appendTo|showOnDesktop|www|selectAll|windowHeight|substring|onDrag|PRE|restoreScroll|userAgent|traverseFile|nodeType|imageFields|invisible|getAlignmentElement|version|method|saveBodyScroll|navigator|dataTransfer|u200D|onDrop|ftp|childNodes|innerWidth|x200b|setNodesMarker|formatLinkify|enterKey|finalNodes|TAB|getNodesMarker|setToPoint|sendData|parseJSON|tfoot|elements|content|tbody|thead|buttonsHideOnMobile|onSync|container|hidden|reader|postData|showOnMobile|selectNodeContents|buttonsHide|setMarker|BODY|direction|addProtocol|clickedElement|createDocumentFragment|dfn|removeFormat|curLang|firstChild|Table|ESC|setFormat|par|Column||insertBreakLineProcessing|htmlIe|address|isDesktop|placeholdler|replaceToParagraph|br1|Row|send|br2|closeHandler|setBefore||setConfig|observeScroll|tagsEmpty|buttonsSize|createButton|createActionButton|getInlines|protocol|createCancelButton|redo|SPACE|amp|setAfterOrBefore|Link|child|getModuleMethods|footer|offsetNode|attrAllowed|cursor|hover|matched|iframeEnd|finish|clearUnverifiedRemove|isCurrentOrParentOne|contOwnLine|verifiedTags|ownLine|mousedown|modules|cleanTag|urlImage|redactorImageLink|getOffset|headers|imageDelete|219|iframeStart|imageDisplay|suffix|dropdownWidth|quote|convertInline|listTag|isUnorderedCmdOrdered|IMG|saveFormTags|extra|rv|allowedAttrTags|allowedAttrData|firstFound|isOrderedCmdUnordered|XMLHttpRequest|status|formattingAdd|dropdownObject|removeSpaces|hotkeysShiftNums|lineHeight|imageResizable|commentsMatches|readyState|createSpaceElement|221|iframe|reUrlVimeo|encodeEntities|coords|autosaveOnChange|autosaveName|pattern|getTextFromHtml|inputText|headTag|nodeToCaretPositionFromPoint|mailto|success|before|count|showCode|activeButtonsStates|link_new_tab|addCallback|syncCode|TH|isP|formatWrap|reUrlYoutube|add|closeTooltip|links|onload|setInactiveInCode|all|internal|textareaIndenting|handler|formatEmpty|arguments|iPod|isExceptLastOrFirst|iPhone|s3uploadToS3|origHandler|beforekey|showVisual|modified||indenting|s3createCORSRequest|setInactive|mso|H4|one|Color||mouseout|H5|setActiveInVisual|mouseover|H3|FIGCAPTION|pastePlainText||H1|Apple|H2|nofollow|linkNofollow|vimeo|hotkeysSpecialKeys|selectionEnd|startSync|selectionStart|cleanSpaces|youtube|setRequestHeader|H6|chars|onSet|List|onPasteWord|onPasteExtra|onPasteRemoveEmpty|callbackName|getOnlyImages|saveTargetScroll|getPreCode|saveEditorScroll|clearInterval|onChange|quot|reIsBlock|Right|Left|lineAfter|removeDirtyStyles|setUndo|setRedo|setText|decreaseBlocks|setBlocks|cleanOnPaste|clipboardImageUpload|decreaseLists|increaseText|getUndo|pop|getRedo|increaseLists|increaseBlocks|lineBefore|enableSelectAll|setupAllowed|addToAllowed|restoreFormTags|getSafes||||getSafesComments|outerHTML||removeFromDenied|opr|removeTags|removeAttrGetRemoves|chrome|removeParagraphsInLists|removeAttrs|restoreSafes|isIe11|newTag|fieldset|map|Image|parents|head|area|replaceBreaksToParagraphs|Video|replaceDivsToBr|replaceBreaksToNewLines|clear|freeze|createPasteBox|png|shorthandRegex|u00a0|removeEmptyTags|replaceDivToParagraph|replaceDivToBreakLine|tabFocus|onTab|setupSelectAll|altKey|LEFT_WIN|onArrowDown|onShiftEnter|||||||||||cleanStyleOnEnter|insertParagraph||removeEmptyListInTable|removeInvisibleSpace|Android|BlackBerry|keyupStop|dbl|stopPropagation|insertAfterLastElement|exitFromBlockquote||insertNewLine|Object|jpeg|onPasteRemoveSpans|setupBuffer|clipboardUploadInMozilla|urlCheck|imgs|post|clipboard|insertFromClipboard|matchBR|matchIMG|Code|clone|savePreCode|detectEventClipboardUpload|Head|contentType|keydownStop|SHIFT|ALT|addArrowsEvent|isSingleLine|onPasteIeFixLinks|META|CTRL|matchBlocks|DOWN|stop|checkEvents|checkKeyEvents|gif|createTooltip|buildOverlay|focusEnd|setHelpers|blur|getCoords|Align|Center|floatValue|foco|enableEvents|setStartAfter|XDomainRequest|imagePosition|focusCallback|getLastBlock|dragFileUpload|lastBlock|handle|draggable|setEnter|startContainer|codeKeyupCallback||setButtonsWidth|codeKeydownCallback|createDeleteButton|cancel|disableEvents|orgo|here|execHtml|header3|header2||header1|header4|blocksMatch|8px|align_left|header5|aside|article|duplicate|setEndPoint|floating|15px|bodyOveflow|switch|disableMozillaEditing|fadeOut|ie11PasteFrag|Drop|EndToEnd|ie11FixInserting|paragraph|langs|setDraggable|removeNodesMarkers|aLink|getTextareaName|aEdit|aUnlink|createContainerBox|getTooltipPosition|showTooltip|URL|checkbox|||||||buttonDelete|loadContent|linkTooltip|image_position|imageEdit|destroy|getData|re2|700|event|run|location|buttonInsert|_delete|loadTemplates|cleanUrl|thref|buttonSave|enableEditor|args||createMarkers|to|windowWidth|dir|setTitle|setEvents|getTemplate|dragImageUpload|endX|setContent|endY|setOptions|callEditor|setInactiveAll|fromTextarea|link_edit|loadEditor|linkButtonName|fromElement|createTextarea|setCodeAndCall|savedSel|insertAfter|template|imageLink|align_right|align_center|onDragLeave|fixImageSourceAfterDrop|empty|formatTableWrapping|showEdit|observeScrollEnable|observeScrollDisable|relative|formatBlockquote|loadEditableControls|hideButtonsOnMobile|removeData|Function|scope|isButtonSourceNeeded|toolbarOverflow|ratio|zIndex|hideButtons|editerWidth|||||buttonSource|inside|visibility|visible|that|onreadystatechange||11px|10px|responseText|opacity||lastFound|insertInOthersBrowsers|setFocus|afterkey|formatTags|setCollapsed|signedURL|s3uploadFile|setDropdownsFixed|Edit|filter|s3executeOnSignedUrl|setMultiple|overrideMimeType|unsetDropdownsFixed|commonAncestorContainer|setOverflow|stopResize|bindModuleMethods|setEditable|the|1px|setFloating|loadButtons|loadModules|fileUploadParam|imageUploadParam|startResize|pageX|setFormattingTags|directUpload|insertHTML|choose|htmlFixMozilla|align_justify|000|change|update|fff|backgroundColor|createContainer|formatConvert|imageEditable|getType|superscript|formatCollapsed|formatMultiple|setFixed|toolbarFixed|subscript|imageTypes|formatRemoveSameChildren|strikethrough|replaced|mouseup|property|moveResize|loadOptions|shortcutsAdd|rProtocol|setTabindex|LEFT|getEditor|Deleted|Justify|getElement|Horizontal|getBox|Rule|Embed|Alignment|video_html_code|getObject|merge|optional|getOwnPropertyNames|zA|Name|Anchor|anchor|Open|Underline|web|getTextarea|drop_file_here|allowfullscreen|u2122|trade|or_choose|Choose|syncBefore|Download|u00a9|copy|u2010|dash|mdash|u2014|u2026|hellip|File|dropdownHide|Upload|setAwesome|addDropdown|addFirst|addAfter|addBefore|download|removeIcon|Email|dropdownShown|returnValue|changeIcon|player|dropdownShow|converted|shapes|enableInlineTableEditing|enableObjectResizing|jpg|bull|Youtube|rowspan|Vimeo|removePhp|blurCallback|Callback|getOnlyLinksAndImages|TEXTAREA|innerText|stripTags|textContent|colspan|Or|Mso|external|MsoListParagraph|MsoListParagraphCxSpLast|MsoListParagraphCxSpFirst|MsoListParagraphCxSpMiddle|sid|docs|fake|nodeValue|500|281|guid|frameborder|getToolbar|public|applet|through|alignleft|cssText|removeStyle|toggleStyle|removeStyleRule|aligncenter|alignright|setData|uploadFileFields|toggleData|setAttr|setClass|toggleAttr|uploadImageField|jQuery|No|clientY|1500|fadeIn|fast|modalClosed|clientX||nodeToPoint|dragleave|ltr|VERSION|dragover|pasteHTML|such|POST|clearImageFields|FOOTER||withCredentials|decodeURIComponent|defined|charset|user|ASIDE|PUT|Content|compatible|ARTICLE|onprogress|onerror|Key|plain|HEADER|set2|removeFileFields|addFileFields|clearFileFields|addImageFields|removeImageFields|ins|DL|ADDRESS|SECTION|OUTPUT|DD|GET|DT|setEndAfter|setStartBefore|5px|border|dragstart|4px|touchmove|mousemove|solid|705|xn|host|fileUploadError|createLink|imageUploadError|insertedLink|touchend|youtu|padding|7px|fontSize|color|18px|use|strict|rgba|Math|bottom|round|pointer|dashed|outline|600|slice|u200b|boundingTop|removeChild|move|extractContents|fromPoint|boundingLeft|Control|overlay|setEndBefore|endContainer|endOffset|times|startOffset|selectElement|20px|cloneContents|addTab|toggleActive|createTabber|unselectable|addButton|addTemplate|getHtml|keypress|outerHeight|focusin|modalOpened|getText|getModal|222|fromCharCode|columns|Columns|add_head|Rows|hexToRgb|delete_table|rows|delete_head|rgb|pasteBefore|Title|getAsFile|FileReader|enter|readAsDataURL|isString|iPad|capslock|pause|Below||esc|Above|220|insert_column_left|insert_column_right|delete_row|read|acl|amz|delete_column||backspace|9999px|Position|nav|hgroup|details|menu|Web|summary|legend|math|opera|trident|safari|specified|Trident|Text|hasOwnProperty|isLessIe10|escape|ajax|encodeURIComponent|autosaveError|None|disable|1000|setInterval|noscript|frame|isOldIe|frameset|isStartOfElement|image_web_link|pageup|insert_row_below|Unlink|119|Formatting|120|HTML|f10|121|118|Normal|Bold|insert_row_above|115|116|117|Quote||122||f11|188|187||189|190|192|191|186|173|f12|123|144|numlock|Type|145|Italic|114|Unordered|Ordered|down|Back|backcolor|113|Font|Outdent|Indent|insert_table|pagedown|home|Save|up|Cancel|102|101|fontcolor|112|103|109|110|111|104|105|106|107'.split('|'),0,{}))
\ No newline at end of file
index 1720ddcca25f7727699f5d8eff670ae510370e5a..f47e0446e0960a68cdd11465784ab40e01eb51b2 100644 (file)
 .re-aligncenter:before { content: "\e61e"; }
 .re-gallery:before { content: "\e61f"; }
 
-.redactor_box {
+.redactor-box {
        border: 1px solid @wcfContainerBorderColor;
        font-size: 0;
        line-height: @wcfBaseLineHeight;
+       overflow: hidden;
        
        & + .messageTabMenu {
                padding: 0;
@@ -64,7 +65,7 @@
        }
 }
 
-.redactor_editor {
+.redactor-editor {
        font-size: 1rem;
        max-height: 500px;
        padding: 10px;
@@ -76,6 +77,7 @@
                border-width: 0;
                box-shadow: none;
                font-size: 1rem;
+               line-height: @wcfBaseLineHeight;
                outline: none;
                padding: 10px;
                
        }
 }
 
-.redactor_toolbar {
+.redactor-toolbar {
        background: repeating-linear-gradient(0deg, @wcfContainerBackgroundColor, @wcfContainerAccentBackgroundColor 29px, @wcfContainerBorderColor 30px, @wcfContainerBorderColor 30px);
        border-bottom: 1px solid @wcfContainerBorderColor;
        margin-top: -1px;
                        
                        .textShadow(@wcfButtonBackgroundColor);
                        
-                       &.redactor_button_disabled,
-                       &.redactor_button_disabled:before,
-                       &.redactor_button_disabled > i:before {
+                       &.redactor-button-disabled,
+                       &.redactor-button-disabled:before,
+                       &.redactor-button-disabled > i:before {
                                color: @wcfButtonBackgroundColor;
                                cursor: default;
                        }
                                }
                        }
                        
-                       &:not(.redactor_button_disabled):hover,
-                       &.redactor_act,
+                       &:not(.redactor-button-disabled):hover,
+                       &.redactor-act,
                        &.dropact {
                                background-color: @wcfContainerHoverBackgroundColor;
                                
        }
 }
 
-.redactor_dropdown_box_fontcolor {
+.redactor-dropdown-box-fontcolor {
        width: 200px;
        
        > li.redactorColorPallet {