]> git.donarmstrong.com Git - roundcube.git/blobdiff - program/js/tiny_mce/plugins/paste/editor_plugin_src.js
Imported Upstream version 0.3
[roundcube.git] / program / js / tiny_mce / plugins / paste / editor_plugin_src.js
index f271758f9d09d30a6a73cb2940289106fce754c1..14d86a957ab77014b99a643abdde46cf8ebeeb5f 100644 (file)
 /**\r
- * $Id: editor_plugin_src.js 862 2008-06-02 20:09:06Z spocke $\r
+ * $Id: editor_plugin_src.js 1104 2009-04-22 12:16:47Z spocke $\r
  *\r
  * @author Moxiecode\r
  * @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved.\r
  */\r
 \r
 (function() {\r
-       var Event = tinymce.dom.Event;\r
+       var each = tinymce.each;\r
 \r
        tinymce.create('tinymce.plugins.PastePlugin', {\r
                init : function(ed, url) {\r
-                       var t = this;\r
-\r
-                       t.editor = ed; \r
-\r
-                       // Register commands\r
-                       ed.addCommand('mcePasteText', function(ui, v) {\r
-                               if (ui) {\r
-                                       if ((ed.getParam('paste_use_dialog', true)) || (!tinymce.isIE)) {\r
-                                               ed.windowManager.open({\r
-                                                       file : url + '/pastetext.htm',\r
-                                                       width : 450,\r
-                                                       height : 400,\r
-                                                       inline : 1\r
-                                               }, {\r
-                                                       plugin_url : url\r
-                                               });\r
-                                       } else\r
-                                               t._insertText(clipboardData.getData("Text"), true);\r
-                               } else\r
-                                       t._insertText(v.html, v.linebreaks);\r
-                       });\r
+                       var t = this, cb;\r
 \r
-                       ed.addCommand('mcePasteWord', function(ui, v) {\r
-                               if (ui) {\r
-                                       if ((ed.getParam('paste_use_dialog', true)) || (!tinymce.isIE)) {\r
-                                               ed.windowManager.open({\r
-                                                       file : url + '/pasteword.htm',\r
-                                                       width : 450,\r
-                                                       height : 400,\r
-                                                       inline : 1\r
-                                               }, {\r
-                                                       plugin_url : url\r
-                                               });\r
-                                       } else\r
-                                               t._insertText(t._clipboardHTML());\r
-                               } else\r
-                                       t._insertWordContent(v);\r
-                       });\r
+                       t.editor = ed;\r
+                       t.url = url;\r
 \r
-                       ed.addCommand('mceSelectAll', function() {\r
-                               ed.execCommand('selectall'); \r
-                       });\r
+                       // Setup plugin events\r
+                       t.onPreProcess = new tinymce.util.Dispatcher(t);\r
+                       t.onPostProcess = new tinymce.util.Dispatcher(t);\r
 \r
-                       // Register buttons\r
-                       ed.addButton('pastetext', {title : 'paste.paste_text_desc', cmd : 'mcePasteText', ui : true});\r
-                       ed.addButton('pasteword', {title : 'paste.paste_word_desc', cmd : 'mcePasteWord', ui : true});\r
-                       ed.addButton('selectall', {title : 'paste.selectall_desc', cmd : 'mceSelectAll'});\r
+                       // Register default handlers\r
+                       t.onPreProcess.add(t._preProcess);\r
+                       t.onPostProcess.add(t._postProcess);\r
 \r
-                       if (ed.getParam("paste_auto_cleanup_on_paste", false)) {\r
-                               ed.onPaste.add(function(ed, e) {\r
-                                       return t._handlePasteEvent(e)\r
-                               });\r
-                       }\r
-\r
-                       if (!tinymce.isIE && ed.getParam("paste_auto_cleanup_on_paste", false)) {\r
-                               // Force paste dialog if non IE browser\r
-                               ed.onKeyDown.add(function(ed, e) {\r
-                                       if (e.ctrlKey && e.keyCode == 86) {\r
-                                               window.setTimeout(function() {\r
-                                                       ed.execCommand("mcePasteText", true);\r
-                                               }, 1);\r
+                       // Register optional preprocess handler\r
+                       t.onPreProcess.add(function(pl, o) {\r
+                               ed.execCallback('paste_preprocess', pl, o);\r
+                       });\r
 \r
-                                               Event.cancel(e);\r
-                                       }\r
-                               });\r
-                       }\r
-               },\r
+                       // Register optional postprocess\r
+                       t.onPostProcess.add(function(pl, o) {\r
+                               ed.execCallback('paste_postprocess', pl, o);\r
+                       });\r
 \r
-               getInfo : function() {\r
-                       return {\r
-                               longname : 'Paste text/word',\r
-                               author : 'Moxiecode Systems AB',\r
-                               authorurl : 'http://tinymce.moxiecode.com',\r
-                               infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/paste',\r
-                               version : tinymce.majorVersion + "." + tinymce.minorVersion\r
-                       };\r
-               },\r
+                       // This function executes the process handlers and inserts the contents\r
+                       function process(h) {\r
+                               var dom = ed.dom, o = {content : h};\r
 \r
-               // Private methods\r
+                               // Execute pre process handlers\r
+                               t.onPreProcess.dispatch(t, o);\r
 \r
-               _handlePasteEvent : function(e) {\r
-                       var html = this._clipboardHTML(), ed = this.editor, sel = ed.selection, r;\r
+                               // Create DOM structure\r
+                               o.node = dom.create('div', 0, o.content);\r
 \r
-                       // Removes italic, strong etc, the if was needed due to bug #1437114\r
-                       if (ed && (r = sel.getRng()) && r.text.length > 0)\r
-                               ed.execCommand('delete');\r
+                               // Execute post process handlers\r
+                               t.onPostProcess.dispatch(t, o);\r
 \r
-                       if (html && html.length > 0)\r
-                               ed.execCommand('mcePasteWord', false, html);\r
+                               // Serialize content\r
+                               o.content = ed.serializer.serialize(o.node, {getInner : 1});\r
 \r
-                       return Event.cancel(e);\r
-               },\r
+                               // Insert cleaned content. We need to handle insertion of contents containing block elements separatly\r
+                               if (/<(p|h[1-6]|ul|ol)/.test(o.content))\r
+                                       t._insertBlockContent(ed, dom, o.content);\r
+                               else\r
+                                       t._insert(o.content);\r
+                       };\r
 \r
-               _insertText : function(content, bLinebreaks) {\r
-                       content = this.editor.dom.encode(content);\r
-\r
-                       if (content && content.length > 0) {\r
-                               if (bLinebreaks) { \r
-                                       // Special paragraph treatment \r
-                                       if (this.editor.getParam("paste_create_paragraphs", true)) {\r
-                                               var rl = this.editor.getParam("paste_replace_list", '\u2122,<sup>TM</sup>,\u2026,...,\u201c|\u201d,",\u2019,\',\u2013|\u2014|\u2015|\u2212,-').split(',');\r
-                                               for (var i=0; i<rl.length; i+=2)\r
-                                                       content = content.replace(new RegExp(rl[i], 'gi'), rl[i+1]);\r
-\r
-                                               content = content.replace(/\r\n\r\n/g, '</p><p>');\r
-                                               content = content.replace(/\r\r/g, '</p><p>');\r
-                                               content = content.replace(/\n\n/g, '</p><p>');\r
-\r
-                                               // Has paragraphs \r
-                                               if ((pos = content.indexOf('</p><p>')) != -1) { \r
-                                                       this.editor.execCommand("Delete"); \r
-\r
-                                                       var node = this.editor.selection.getNode(); \r
-\r
-                                                       // Get list of elements to break \r
-                                                       var breakElms = [];\r
-\r
-                                                       do { \r
-                                                               if (node.nodeType == 1) { \r
-                                                                       // Don't break tables and break at body \r
-                                                                       if (node.nodeName == "TD" || node.nodeName == "BODY") \r
-                                                                               break; \r
-                       \r
-                                                                       breakElms[breakElms.length] = node; \r
-                                                               } \r
-                                                       } while(node = node.parentNode); \r
-\r
-                                                       var before = "", after = "</p>"; \r
-                                                       before += content.substring(0, pos); \r
-\r
-                                                       for (var i=0; i<breakElms.length; i++) { \r
-                                                               before += "</" + breakElms[i].nodeName + ">"; \r
-                                                               after += "<" + breakElms[(breakElms.length-1)-i].nodeName + ">"; \r
-                                                       } \r
-\r
-                                                       before += "<p>"; \r
-                                                       content = before + content.substring(pos+7) + after; \r
-                                               } \r
-                                       } \r
-\r
-                                       if (this.editor.getParam("paste_create_linebreaks", true)) {\r
-                                               content = content.replace(/\r\n/g, '<br />');\r
-                                               content = content.replace(/\r/g, '<br />');\r
-                                               content = content.replace(/\n/g, '<br />');\r
-                                       }\r
-                               } \r
+                       // Add command for external usage\r
+                       ed.addCommand('mceInsertClipboardContent', function(u, v) {\r
+                               process(v);\r
+                       });\r
 \r
-                               this.editor.execCommand("mceInsertRawHTML", false, content); \r
-                       }\r
-               },\r
+                       // This function grabs the contents from the clipboard by adding a\r
+                       // hidden div and placing the caret inside it and after the browser paste\r
+                       // is done it grabs that contents and processes that\r
+                       function grabContent(e) {\r
+                               var n, or, rng, sel = ed.selection, dom = ed.dom, body = ed.getBody(), posY;\r
 \r
-               _insertWordContent : function(content) { \r
-                       var t = this, ed = t.editor;\r
+                               if (dom.get('_mcePaste'))\r
+                                       return;\r
 \r
-                       if (content && content.length > 0) {\r
-                               // Cleanup Word content\r
-                               var bull = String.fromCharCode(8226);\r
-                               var middot = String.fromCharCode(183);\r
+                               // Create container to paste into\r
+                               n = dom.add(body, 'div', {id : '_mcePaste'}, '&nbsp;');\r
 \r
-                               if (ed.getParam('paste_insert_word_content_callback'))\r
-                                       content = ed.execCallback('paste_insert_word_content_callback', 'before', content);\r
+                               // If contentEditable mode we need to find out the position of the closest element\r
+                               if (body != ed.getDoc().body)\r
+                                       posY = dom.getPos(ed.selection.getStart(), body).y;\r
+                               else\r
+                                       posY = body.scrollTop;\r
+\r
+                               // Styles needs to be applied after the element is added to the document since WebKit will otherwise remove all styles\r
+                               dom.setStyles(n, {\r
+                                       position : 'absolute',\r
+                                       left : -10000,\r
+                                       top : posY,\r
+                                       width : 1,\r
+                                       height : 1,\r
+                                       overflow : 'hidden'\r
+                               });\r
 \r
-                               var rl = ed.getParam("paste_replace_list", '\u2122,<sup>TM</sup>,\u2026,...,\u201c|\u201d,",\u2019,\',\u2013|\u2014|\u2015|\u2212,-').split(',');\r
-                               for (var i=0; i<rl.length; i+=2)\r
-                                       content = content.replace(new RegExp(rl[i], 'gi'), rl[i+1]);\r
+                               if (tinymce.isIE) {\r
+                                       // Select the container\r
+                                       rng = dom.doc.body.createTextRange();\r
+                                       rng.moveToElementText(n);\r
+                                       rng.execCommand('Paste');\r
 \r
-                               if (this.editor.getParam("paste_convert_headers_to_strong", false)) {\r
-                                       content = content.replace(new RegExp('<p class=MsoHeading.*?>(.*?)<\/p>', 'gi'), '<p><b>$1</b></p>');\r
-                               }\r
+                                       // Remove container\r
+                                       dom.remove(n);\r
 \r
-                               content = content.replace(new RegExp('tab-stops: list [0-9]+.0pt">', 'gi'), '">' + "--list--");\r
-                               content = content.replace(new RegExp(bull + "(.*?)<BR>", "gi"), "<p>" + middot + "$1</p>");\r
-                               content = content.replace(new RegExp('<SPAN style="mso-list: Ignore">', 'gi'), "<span>" + bull); // Covert to bull list\r
-                               content = content.replace(/<o:p><\/o:p>/gi, "");\r
-                               content = content.replace(new RegExp('<br style="page-break-before: always;.*>', 'gi'), '-- page break --'); // Replace pagebreaks\r
-                               content = content.replace(new RegExp('<(!--)([^>]*)(--)>', 'g'), "");  // Word comments\r
+                                       // Process contents\r
+                                       process(n.innerHTML);\r
 \r
-                               if (this.editor.getParam("paste_remove_spans", true))\r
-                                       content = content.replace(/<\/?span[^>]*>/gi, "");\r
+                                       return tinymce.dom.Event.cancel(e);\r
+                               } else {\r
+                                       or = ed.selection.getRng();\r
 \r
-                               if (this.editor.getParam("paste_remove_styles", true))\r
-                                       content = content.replace(new RegExp('<(\\w[^>]*) style="([^"]*)"([^>]*)', 'gi'), "<$1$3");\r
+                                       // Move caret into hidden div\r
+                                       n = n.firstChild;\r
+                                       rng = ed.getDoc().createRange();\r
+                                       rng.setStart(n, 0);\r
+                                       rng.setEnd(n, 1);\r
+                                       sel.setRng(rng);\r
 \r
-                               content = content.replace(/<\/?font[^>]*>/gi, "");\r
+                                       // Wait a while and grab the pasted contents\r
+                                       window.setTimeout(function() {\r
+                                               var n = dom.get('_mcePaste'), h;\r
 \r
-                               // Strips class attributes.\r
-                               switch (this.editor.getParam("paste_strip_class_attributes", "all")) {\r
-                                       case "all":\r
-                                               content = content.replace(/<(\w[^>]*) class=([^ |>]*)([^>]*)/gi, "<$1$3");\r
-                                               break;\r
+                                               // Webkit clones the _mcePaste div for some odd reason so this will ensure that we get the real new div not the old empty one\r
+                                               n.id = '_mceRemoved';\r
+                                               dom.remove(n);\r
+                                               n = dom.get('_mcePaste') || n;\r
 \r
-                                       case "mso":\r
-                                               content = content.replace(new RegExp('<(\\w[^>]*) class="?mso([^ |>]*)([^>]*)', 'gi'), "<$1$3");\r
-                                               break;\r
-                               }\r
+                                               // Grab the HTML contents\r
+                                               // We need to look for a apple style wrapper on webkit it also adds a div wrapper if you copy/paste the body of the editor\r
+                                               // It's amazing how strange the contentEditable mode works in WebKit\r
+                                               h = (dom.select('> span.Apple-style-span div', n)[0] || dom.select('> span.Apple-style-span', n)[0] || n).innerHTML;\r
 \r
-                               content = content.replace(new RegExp('href="?' + this._reEscape("" + document.location) + '', 'gi'), 'href="' + this.editor.documentBaseURI.getURI());\r
-                               content = content.replace(/<(\w[^>]*) lang=([^ |>]*)([^>]*)/gi, "<$1$3");\r
-                               content = content.replace(/<\\?\?xml[^>]*>/gi, "");\r
-                               content = content.replace(/<\/?\w+:[^>]*>/gi, "");\r
-                               content = content.replace(/-- page break --\s*<p>&nbsp;<\/p>/gi, ""); // Remove pagebreaks\r
-                               content = content.replace(/-- page break --/gi, ""); // Remove pagebreaks\r
+                                               // Remove hidden div and restore selection\r
+                                               dom.remove(n);\r
 \r
-               //              content = content.replace(/\/?&nbsp;*/gi, ""); &nbsp;\r
-               //              content = content.replace(/<p>&nbsp;<\/p>/gi, '');\r
+                                               // Restore the old selection\r
+                                               if (or)\r
+                                                       sel.setRng(or);\r
 \r
-                               if (!this.editor.getParam('force_p_newlines')) {\r
-                                       content = content.replace('', '' ,'gi');\r
-                                       content = content.replace('</p>', '<br /><br />' ,'gi');\r
+                                               process(h);\r
+                                       }, 0);\r
                                }\r
+                       };\r
 \r
-                               if (!tinymce.isIE && !this.editor.getParam('force_p_newlines')) {\r
-                                       content = content.replace(/<\/?p[^>]*>/gi, "");\r
+                       // Check if we should use the new auto process method                   \r
+                       if (ed.getParam('paste_auto_cleanup_on_paste', true)) {\r
+                               // Is it's Opera or older FF use key handler\r
+                               if (tinymce.isOpera || /Firefox\/2/.test(navigator.userAgent)) {\r
+                                       ed.onKeyDown.add(function(ed, e) {\r
+                                               if (((tinymce.isMac ? e.metaKey : e.ctrlKey) && e.keyCode == 86) || (e.shiftKey && e.keyCode == 45))\r
+                                                       grabContent(e);\r
+                                       });\r
+                               } else {\r
+                                       // Grab contents on paste event on Gecko and WebKit\r
+                                       ed.onPaste.addToTop(function(ed, e) {\r
+                                               return grabContent(e);\r
+                                       });\r
                                }\r
+                       }\r
 \r
-                               content = content.replace(/<\/?div[^>]*>/gi, "");\r
+                       // Add legacy support\r
+                       t._legacySupport();\r
+               },\r
 \r
-                               // Convert all middlot lists to UL lists\r
-                               if (this.editor.getParam("paste_convert_middot_lists", true)) {\r
-                                       var div = ed.dom.create("div", null, content);\r
+               getInfo : function() {\r
+                       return {\r
+                               longname : 'Paste text/word',\r
+                               author : 'Moxiecode Systems AB',\r
+                               authorurl : 'http://tinymce.moxiecode.com',\r
+                               infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/paste',\r
+                               version : tinymce.majorVersion + "." + tinymce.minorVersion\r
+                       };\r
+               },\r
 \r
-                                       // Convert all middot paragraphs to li elements\r
-                                       var className = this.editor.getParam("paste_unindented_list_class", "unIndentedList");\r
+               _preProcess : function(pl, o) {\r
+                       var h = o.content, process;\r
 \r
-                                       while (this._convertMiddots(div, "--list--")) ; // bull\r
-                                       while (this._convertMiddots(div, middot, className)) ; // Middot\r
-                                       while (this._convertMiddots(div, bull)) ; // bull\r
+                       //console.log('Before preprocess:' + o.content);\r
 \r
-                                       content = div.innerHTML;\r
-                               }\r
+                       function process(items) {\r
+                               each(items, function(v) {\r
+                                       // Remove or replace\r
+                                       if (v.constructor == RegExp)\r
+                                               h = h.replace(v, '');\r
+                                       else\r
+                                               h = h.replace(v[0], v[1]);\r
+                               });\r
+                       };\r
 \r
-                               // Replace all headers with strong and fix some other issues\r
-                               if (this.editor.getParam("paste_convert_headers_to_strong", false)) {\r
-                                       content = content.replace(/<h[1-6]>&nbsp;<\/h[1-6]>/gi, '<p>&nbsp;&nbsp;</p>');\r
-                                       content = content.replace(/<h[1-6]>/gi, '<p><b>');\r
-                                       content = content.replace(/<\/h[1-6]>/gi, '</b></p>');\r
-                                       content = content.replace(/<b>&nbsp;<\/b>/gi, '<b>&nbsp;&nbsp;</b>');\r
-                                       content = content.replace(/^(&nbsp;)*/gi, '');\r
-                               }\r
+                       // Process away some basic content\r
+                       process([\r
+                               /^\s*(&nbsp;)+/g,                                                                                       // nbsp entities at the start of contents\r
+                               /(&nbsp;|<br[^>]*>)+\s*$/g                                                                      // nbsp entities at the end of contents\r
+                       ]);\r
+\r
+                       // Detect Word content and process it more agressive\r
+                       if (/(class=\"?Mso|style=\"[^\"]*\bmso\-|w:WordDocument)/.test(h)) {\r
+                               o.wordContent = true; // Mark the pasted contents as word specific content\r
+                               //console.log('Word contents detected.');\r
+\r
+                               process([\r
+                                       /<!--[\s\S]+?-->/gi,                                                                                            // Word comments\r
+                                       /<\/?(img|font|meta|link|style|span|div|v:\w+)[^>]*>/gi,                        // Remove some tags including VML content\r
+                                       /<\\?\?xml[^>]*>/gi,                                                                                            // XML namespace declarations\r
+                                       /<\/?o:[^>]*>/gi,                                                                                                       // MS namespaced elements <o:tag>\r
+                                       / (id|name|class|language|type|on\w+|v:\w+)=\"([^\"]*)\"/gi,    // on.., class, style and language attributes with quotes\r
+                                       / (id|name|class|language|type|on\w+|v:\w+)=(\w+)/gi,                   // on.., class, style and language attributes without quotes (IE)\r
+                                       [/<(\/?)s>/gi, '<$1strike>'],                                                                           // Convert <s> into <strike> for line-though\r
+                                       /<script[^>]+>[\s\S]*?<\/script>/gi,                                                            // All scripts elements for msoShowComment for example\r
+                                       [/&nbsp;/g, '\u00a0']                                                                                           // Replace nsbp entites to char since it's easier to handle\r
+                               ]);\r
+                       }\r
 \r
-                               content = content.replace(/--list--/gi, ""); // Remove --list--\r
+                       //console.log('After preprocess:' + h);\r
 \r
-                               if (ed.getParam('paste_insert_word_content_callback'))\r
-                                       content = ed.execCallback('paste_insert_word_content_callback', 'after', content);\r
+                       o.content = h;\r
+               },\r
 \r
-                               // Insert cleaned content\r
-                               this.editor.execCommand("mceInsertContent", false, content);\r
+               /**\r
+                * Various post process items.\r
+                */\r
+               _postProcess : function(pl, o) {\r
+                       var t = this, dom = t.editor.dom;\r
+\r
+                       if (o.wordContent) {\r
+                               // Remove named anchors or TOC links\r
+                               each(dom.select('a', o.node), function(a) {\r
+                                       if (!a.href || a.href.indexOf('#_Toc') != -1)\r
+                                               dom.remove(a, 1);\r
+                               });\r
 \r
-                               if (this.editor.getParam('paste_force_cleanup_wordpaste', true)) {\r
-                                       var ed = this.editor;\r
+                               if (t.editor.getParam('paste_convert_middot_lists', true))\r
+                                       t._convertLists(pl, o);\r
 \r
-                                       window.setTimeout(function() {\r
-                                               ed.execCommand("mceCleanup");\r
-                                       }, 1); // Do normal cleanup detached from this thread\r
-                               }\r
+                               // Remove all styles\r
+                               each(dom.select('*', o.node), function(el) {\r
+                                       dom.setAttrib(el, 'style', '');\r
+                               });\r
+                       }\r
+\r
+                       if (tinymce.isWebKit) {\r
+                               // We need to compress the styles on WebKit since if you paste <img border="0" /> it will become <img border="0" style="... lots of junk ..." />\r
+                               // Removing the mce_style that contains the real value will force the Serializer engine to compress the styles\r
+                               each(dom.select('*', o.node), function(el) {\r
+                                       el.removeAttribute('mce_style');\r
+                               });\r
                        }\r
                },\r
 \r
-               _reEscape : function(s) {\r
-                       var l = "?.\\*[](){}+^$:";\r
-                       var o = "";\r
+               /**\r
+                * Converts the most common bullet and number formats in Office into a real semantic UL/LI list.\r
+                */\r
+               _convertLists : function(pl, o) {\r
+                       var dom = pl.editor.dom, listElm, li, lastMargin = -1, margin, levels = [], lastType;\r
+\r
+                       // Convert middot lists into real scemantic lists\r
+                       each(dom.select('p', o.node), function(p) {\r
+                               var sib, val = '', type, html, idx, parents;\r
+\r
+                               // Get text node value at beginning of paragraph\r
+                               for (sib = p.firstChild; sib && sib.nodeType == 3; sib = sib.nextSibling)\r
+                                       val += sib.nodeValue;\r
+\r
+                               // Detect unordered lists look for bullets\r
+                               if (/^[\u2022\u00b7\u00a7\u00d8o]\s*\u00a0\u00a0*/.test(val))\r
+                                       type = 'ul';\r
+\r
+                               // Detect ordered lists 1., a. or ixv.\r
+                               if (/^[\s\S]*\w+\.[\s\S]*\u00a0{2,}/.test(val))\r
+                                       type = 'ol';\r
+\r
+                               // Check if node value matches the list pattern: o&nbsp;&nbsp;\r
+                               if (type) {\r
+                                       margin = parseFloat(p.style.marginLeft || 0);\r
+\r
+                                       if (margin > lastMargin)\r
+                                               levels.push(margin);\r
+\r
+                                       if (!listElm || type != lastType) {\r
+                                               listElm = dom.create(type);\r
+                                               dom.insertAfter(listElm, p);\r
+                                       } else {\r
+                                               // Nested list element\r
+                                               if (margin > lastMargin) {\r
+                                                       listElm = li.appendChild(dom.create(type));\r
+                                               } else if (margin < lastMargin) {\r
+                                                       // Find parent level based on margin value\r
+                                                       idx = tinymce.inArray(levels, margin);\r
+                                                       parents = dom.getParents(listElm.parentNode, type);\r
+                                                       listElm = parents[parents.length - 1 - idx] || listElm;\r
+                                               }\r
+                                       }\r
 \r
-                       for (var i=0; i<s.length; i++) {\r
-                               var c = s.charAt(i);\r
+                                       if (type == 'ul')\r
+                                               html = p.innerHTML.replace(/^[\u2022\u00b7\u00a7\u00d8o]\s*(&nbsp;|\u00a0)+\s*/, '');\r
+                                       else\r
+                                               html = p.innerHTML.replace(/^[\s\S]*\w+\.(&nbsp;|\u00a0)+\s*/, '');\r
 \r
-                               if (l.indexOf(c) != -1)\r
-                                       o += '\\' + c;\r
-                               else\r
-                                       o += c;\r
-                       }\r
+                                       li = listElm.appendChild(dom.create('li', 0, html));\r
+                                       dom.remove(p);\r
 \r
-                       return o;\r
+                                       lastMargin = margin;\r
+                                       lastType = type;\r
+                               } else\r
+                                       listElm = lastMargin = 0; // End list element\r
+                       });\r
                },\r
 \r
-               _convertMiddots : function(div, search, class_name) {\r
-                       var ed = this.editor, mdot = String.fromCharCode(183), bull = String.fromCharCode(8226);\r
-                       var nodes, prevul, i, p, ul, li, np, cp, li;\r
-\r
-                       nodes = div.getElementsByTagName("p");\r
-                       for (i=0; i<nodes.length; i++) {\r
-                               p = nodes[i];\r
-\r
-                               // Is middot\r
-                               if (p.innerHTML.indexOf(search) == 0) {\r
-                                       ul = ed.dom.create("ul");\r
-\r
-                                       if (class_name)\r
-                                               ul.className = class_name;\r
-\r
-                                       // Add the first one\r
-                                       li = ed.dom.create("li");\r
-                                       li.innerHTML = p.innerHTML.replace(new RegExp('' + mdot + '|' + bull + '|--list--|&nbsp;', "gi"), '');\r
-                                       ul.appendChild(li);\r
-\r
-                                       // Add the rest\r
-                                       np = p.nextSibling;\r
-                                       while (np) {\r
-                                               // If the node is whitespace, then\r
-                                               // ignore it and continue on.\r
-                                               if (np.nodeType == 3 && new RegExp('^\\s$', 'm').test(np.nodeValue)) {\r
-                                                               np = np.nextSibling;\r
-                                                               continue;\r
-                                               }\r
+               /**\r
+                * This method will split the current block parent and insert the contents inside the split position.\r
+                * This logic can be improved so text nodes at the start/end remain in the start/end block elements\r
+                */\r
+               _insertBlockContent : function(ed, dom, content) {\r
+                       var parentBlock, marker, sel = ed.selection, last, elm, vp, y, elmHeight;\r
+\r
+                       function select(n) {\r
+                               var r;\r
+\r
+                               if (tinymce.isIE) {\r
+                                       r = ed.getDoc().body.createTextRange();\r
+                                       r.moveToElementText(n);\r
+                                       r.collapse(false);\r
+                                       r.select();\r
+                               } else {\r
+                                       sel.select(n, 1);\r
+                                       sel.collapse(false);\r
+                               }\r
+                       };\r
 \r
-                                               if (search == mdot) {\r
-                                                               if (np.nodeType == 1 && new RegExp('^o(\\s+|&nbsp;)').test(np.innerHTML)) {\r
-                                                                               // Second level of nesting\r
-                                                                               if (!prevul) {\r
-                                                                                               prevul = ul;\r
-                                                                                               ul = ed.dom.create("ul");\r
-                                                                                               prevul.appendChild(ul);\r
-                                                                               }\r
-                                                                               np.innerHTML = np.innerHTML.replace(/^o/, '');\r
-                                                               } else {\r
-                                                                               // Pop the stack if we're going back up to the first level\r
-                                                                               if (prevul) {\r
-                                                                                               ul = prevul;\r
-                                                                                               prevul = null;\r
-                                                                               }\r
-                                                                               // Not element or middot paragraph\r
-                                                                               if (np.nodeType != 1 || np.innerHTML.indexOf(search) != 0)\r
-                                                                                               break;\r
-                                                               }\r
-                                               } else {\r
-                                                               // Not element or middot paragraph\r
-                                                               if (np.nodeType != 1 || np.innerHTML.indexOf(search) != 0)\r
-                                                                               break;\r
-                                                       }\r
-\r
-                                               cp = np.nextSibling;\r
-                                               li = ed.dom.create("li");\r
-                                               li.innerHTML = np.innerHTML.replace(new RegExp('' + mdot + '|' + bull + '|--list--|&nbsp;', "gi"), '');\r
-                                               np.parentNode.removeChild(np);\r
-                                               ul.appendChild(li);\r
-                                               np = cp;\r
-                                       }\r
+                       // Insert a marker for the caret position\r
+                       this._insert('<span id="_marker">&nbsp;</span>', 1);\r
+                       marker = dom.get('_marker');\r
+                       parentBlock = dom.getParent(marker, 'p,h1,h2,h3,h4,h5,h6,ul,ol');\r
 \r
-                                       p.parentNode.replaceChild(ul, p);\r
+                       if (parentBlock) {\r
+                               // Split parent block\r
+                               marker = dom.split(parentBlock, marker);\r
 \r
-                                       return true;\r
-                               }\r
+                               // Insert nodes before the marker\r
+                               each(dom.create('div', 0, content).childNodes, function(n) {\r
+                                       last = marker.parentNode.insertBefore(n.cloneNode(true), marker);\r
+                               });\r
+\r
+                               // Move caret after marker\r
+                               select(last);\r
+                       } else {\r
+                               dom.setOuterHTML(marker, content);\r
+                               sel.select(ed.getBody(), 1);\r
+                               sel.collapse(0);\r
                        }\r
 \r
-                       return false;\r
+                       dom.remove('_marker'); // Remove marker if it's left\r
+\r
+                       // Get element, position and height\r
+                       elm = sel.getStart();\r
+                       vp = dom.getViewPort(ed.getWin());\r
+                       y = ed.dom.getPos(elm).y;\r
+                       elmHeight = elm.clientHeight;\r
+\r
+                       // Is element within viewport if not then scroll it into view\r
+                       if (y < vp.y || y + elmHeight > vp.y + vp.h)\r
+                               ed.getDoc().body.scrollTop = y < vp.y ? y : y - vp.h + 25;\r
                },\r
 \r
-               _clipboardHTML : function() {\r
-                       var div = document.getElementById('_TinyMCE_clipboardHTML');\r
+               /**\r
+                * Inserts the specified contents at the caret position.\r
+                */\r
+               _insert : function(h, skip_undo) {\r
+                       var ed = this.editor;\r
 \r
-                       if (!div) {\r
-                               var div = document.createElement('DIV');\r
-                               div.id = '_TinyMCE_clipboardHTML';\r
+                       // First delete the contents seems to work better on WebKit\r
+                       ed.execCommand('Delete');\r
 \r
-                               with (div.style) {\r
-                                       visibility = 'hidden';\r
-                                       overflow = 'hidden';\r
-                                       position = 'absolute';\r
-                                       width = 1;\r
-                                       height = 1;\r
-                               }\r
+                       // It's better to use the insertHTML method on Gecko since it will combine paragraphs correctly before inserting the contents\r
+                       ed.execCommand(tinymce.isGecko ? 'insertHTML' : 'mceInsertContent', false, h, {skip_undo : skip_undo});\r
+               },\r
 \r
-                               document.body.appendChild(div);\r
-                       }\r
+               /**\r
+                * This method will open the old style paste dialogs. Some users might want the old behavior but still use the new cleanup engine.\r
+                */\r
+               _legacySupport : function() {\r
+                       var t = this, ed = t.editor;\r
+\r
+                       // Register commands for backwards compatibility\r
+                       each(['mcePasteText', 'mcePasteWord'], function(cmd) {\r
+                               ed.addCommand(cmd, function() {\r
+                                       ed.windowManager.open({\r
+                                               file : t.url + (cmd == 'mcePasteText' ? '/pastetext.htm' : '/pasteword.htm'),\r
+                                               width : 450,\r
+                                               height : 400,\r
+                                               inline : 1\r
+                                       });\r
+                               });\r
+                       });\r
 \r
-                       div.innerHTML = '';\r
-                       var rng = document.body.createTextRange();\r
-                       rng.moveToElementText(div);\r
-                       rng.execCommand('Paste');\r
-                       var html = div.innerHTML;\r
-                       div.innerHTML = '';\r
-                       return html;\r
+                       // Register buttons for backwards compatibility\r
+                       ed.addButton('pastetext', {title : 'paste.paste_text_desc', cmd : 'mcePasteText'});\r
+                       ed.addButton('pasteword', {title : 'paste.paste_word_desc', cmd : 'mcePasteWord'});\r
+                       ed.addButton('selectall', {title : 'paste.selectall_desc', cmd : 'selectall'});\r
                }\r
        });\r
 \r