\r
(function() {\r
var each = tinymce.each,\r
- entities = null,\r
defs = {\r
paste_auto_cleanup_on_paste : true,\r
+ paste_enable_default_filters : true,\r
paste_block_drop : false,\r
paste_retain_style_properties : "none",\r
paste_strip_class_attributes : "mso",\r
paste_dialog_height : "400",\r
paste_text_use_dialog : false,\r
paste_text_sticky : false,\r
+ paste_text_sticky_default : false,\r
paste_text_notifyalways : false,\r
paste_text_linebreaktype : "p",\r
paste_text_replacements : [\r
ed.execCallback('paste_postprocess', pl, o);\r
});\r
\r
+ ed.onKeyDown.addToTop(function(ed, e) {\r
+ // Block ctrl+v from adding an undo level since the default logic in tinymce.Editor will add that\r
+ if (((tinymce.isMac ? e.metaKey : e.ctrlKey) && e.keyCode == 86) || (e.shiftKey && e.keyCode == 45))\r
+ return false; // Stop other listeners\r
+ });\r
+\r
// Initialize plain text flag\r
- ed.pasteAsPlainText = false;\r
+ ed.pasteAsPlainText = getParam(ed, 'paste_text_sticky_default');\r
\r
// This function executes the process handlers and inserts the contents\r
// force_rich overrides plain text mode set by user, important for pasting with execCommand\r
function process(o, force_rich) {\r
- var dom = ed.dom;\r
+ var dom = ed.dom, rng;\r
\r
// Execute pre process handlers\r
t.onPreProcess.dispatch(t, o);\r
// Create DOM structure\r
o.node = dom.create('div', 0, o.content);\r
\r
+ // If pasting inside the same element and the contents is only one block\r
+ // remove the block and keep the text since Firefox will copy parts of pre and h1-h6 as a pre element\r
+ if (tinymce.isGecko) {\r
+ rng = ed.selection.getRng(true);\r
+ if (rng.startContainer == rng.endContainer && rng.startContainer.nodeType == 3) {\r
+ // Is only one block node and it doesn't contain word stuff\r
+ if (o.node.childNodes.length === 1 && /^(p|h[1-6]|pre)$/i.test(o.node.firstChild.nodeName) && o.content.indexOf('__MCE_ITEM__') === -1)\r
+ dom.remove(o.node.firstChild, true);\r
+ }\r
+ }\r
+\r
// Execute post process handlers\r
t.onPostProcess.dispatch(t, o);\r
\r
// Serialize content\r
- o.content = ed.serializer.serialize(o.node, {getInner : 1});\r
+ o.content = ed.serializer.serialize(o.node, {getInner : 1, forced_root_block : ''});\r
\r
// Plain text option active?\r
if ((!force_rich) && (ed.pasteAsPlainText)) {\r
ed.pasteAsPlainText = false;\r
ed.controlManager.setActive("pastetext", false);\r
}\r
- } else if (/<(p|h[1-6]|ul|ol)/.test(o.content)) {\r
- // Handle insertion of contents containing block elements separately\r
- t._insertBlockContent(ed, dom, o.content);\r
} else {\r
t._insert(o.content);\r
}\r
\r
if ((ed.pasteAsPlainText) && (!cookie.get("tinymcePasteText"))) {\r
if (getParam(ed, "paste_text_sticky")) {\r
- ed.windowManager.alert("Paste is now in plain text mode. Click again to toggle back to regular paste mode. After you paste something you will be returned to regular paste mode.");\r
+ ed.windowManager.alert(ed.translate('paste.plaintext_mode_sticky'));\r
} else {\r
- ed.windowManager.alert("Paste is now in plain text mode. Click again to toggle back to regular paste mode.");\r
+ ed.windowManager.alert(ed.translate('paste.plaintext_mode_sticky'));\r
}\r
\r
if (!getParam(ed, "paste_text_notifyalways")) {\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
+ var n, or, rng, oldRng, sel = ed.selection, dom = ed.dom, body = ed.getBody(), posY, textContent;\r
+\r
+ // Check if browser supports direct plaintext access\r
+ if (e.clipboardData || dom.doc.dataTransfer) {\r
+ textContent = (e.clipboardData || dom.doc.dataTransfer).getData('Text');\r
+\r
+ if (ed.pasteAsPlainText) {\r
+ e.preventDefault();\r
+ process({content : textContent.replace(/\r?\n/g, '<br />')});\r
+ return;\r
+ }\r
+ }\r
\r
if (dom.get('_mcePaste'))\r
return;\r
\r
// Create container to paste into\r
- n = dom.add(body, 'div', {id : '_mcePaste', 'class' : 'mcePaste'}, '\uFEFF');\r
+ n = dom.add(body, 'div', {id : '_mcePaste', 'class' : 'mcePaste', 'data-mce-bogus' : '1'}, '\uFEFF\uFEFF');\r
\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
+ posY = body.scrollTop + dom.getViewPort(ed.getWin()).y;\r
\r
// Styles needs to be applied after the element is added to the document since WebKit will otherwise remove all styles\r
+ // If also needs to be in view on IE or the paste would fail\r
dom.setStyles(n, {\r
position : 'absolute',\r
- left : -10000,\r
- top : posY,\r
+ left : tinymce.isGecko ? -40 : 0, // Need to move it out of site on Gecko since it will othewise display a ghost resize rect for the div\r
+ top : posY - 25,\r
width : 1,\r
height : 1,\r
overflow : 'hidden'\r
});\r
\r
if (tinymce.isIE) {\r
+ // Store away the old range\r
+ oldRng = sel.getRng();\r
+\r
// Select the container\r
rng = dom.doc.body.createTextRange();\r
rng.moveToElementText(n);\r
\r
// Check if the contents was changed, if it wasn't then clipboard extraction failed probably due\r
// to IE security settings so we pass the junk though better than nothing right\r
- if (n.innerHTML === '\uFEFF') {\r
+ if (n.innerHTML === '\uFEFF\uFEFF') {\r
ed.execCommand('mcePasteWord');\r
e.preventDefault();\r
return;\r
}\r
\r
- // Process contents\r
- process({content : n.innerHTML});\r
+ // Restore the old range and clear the contents before pasting\r
+ sel.setRng(oldRng);\r
+ sel.setContent('');\r
+\r
+ // For some odd reason we need to detach the the mceInsertContent call from the paste event\r
+ // It's like IE has a reference to the parent element that you paste in and the selection gets messed up\r
+ // when it tries to restore the selection\r
+ setTimeout(function() {\r
+ // Process contents\r
+ process({content : n.innerHTML});\r
+ }, 0);\r
\r
// Block the real paste event\r
return tinymce.dom.Event.cancel(e);\r
\r
or = ed.selection.getRng();\r
\r
- // Move caret into hidden div\r
+ // Move select contents inside DIV\r
n = n.firstChild;\r
rng = ed.getDoc().createRange();\r
rng.setStart(n, 0);\r
- rng.setEnd(n, 1);\r
+ rng.setEnd(n, 2);\r
sel.setRng(rng);\r
\r
// Wait a while and grab the pasted contents\r
window.setTimeout(function() {\r
- var h = '', nl = dom.select('div.mcePaste');\r
+ var h = '', nl;\r
\r
- // WebKit will split the div into multiple ones so this will loop through then all and join them to get the whole HTML string\r
- each(nl, function(n) {\r
- // WebKit duplicates the divs so we need to remove them\r
- each(dom.select('div.mcePaste', n), function(n) {\r
- dom.remove(n, 1);\r
- });\r
+ // Paste divs duplicated in paste divs seems to happen when you paste plain text so lets first look for that broken behavior in WebKit\r
+ if (!dom.select('div.mcePaste > div.mcePaste').length) {\r
+ nl = dom.select('div.mcePaste');\r
\r
- // Contents in WebKit is sometimes wrapped in a apple style span so we need to grab it from that one\r
- h += (dom.select('> span.Apple-style-span div', n)[0] || dom.select('> span.Apple-style-span', n)[0] || n).innerHTML;\r
- });\r
+ // WebKit will split the div into multiple ones so this will loop through then all and join them to get the whole HTML string\r
+ each(nl, function(n) {\r
+ var child = n.firstChild;\r
+\r
+ // WebKit inserts a DIV container with lots of odd styles\r
+ if (child && child.nodeName == 'DIV' && child.style.marginTop && child.style.backgroundColor) {\r
+ dom.remove(child, 1);\r
+ }\r
+\r
+ // Remove apply style spans\r
+ each(dom.select('span.Apple-style-span', n), function(n) {\r
+ dom.remove(n, 1);\r
+ });\r
+\r
+ // Remove bogus br elements\r
+ each(dom.select('br[data-mce-bogus]', n), function(n) {\r
+ dom.remove(n);\r
+ });\r
+\r
+ // WebKit will make a copy of the DIV for each line of plain text pasted and insert them into the DIV\r
+ if (n.parentNode.className != 'mcePaste')\r
+ h += n.innerHTML;\r
+ });\r
+ } else {\r
+ // Found WebKit weirdness so force the content into plain text mode\r
+ h = '<pre>' + dom.encode(textContent).replace(/\r?\n/g, '<br />') + '</pre>';\r
+ }\r
\r
// Remove the nodes\r
- each(nl, function(n) {\r
+ each(dom.select('div.mcePaste'), function(n) {\r
dom.remove(n);\r
});\r
\r
if (getParam(ed, "paste_auto_cleanup_on_paste")) {\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
+ ed.onKeyDown.addToTop(function(ed, e) {\r
if (((tinymce.isMac ? e.metaKey : e.ctrlKey) && e.keyCode == 86) || (e.shiftKey && e.keyCode == 45))\r
grabContent(e);\r
});\r
}\r
}\r
\r
- // Block all drag/drop events\r
- if (getParam(ed, "paste_block_drop")) {\r
- ed.onInit.add(function() {\r
+ ed.onInit.add(function() {\r
+ ed.controlManager.setActive("pastetext", ed.pasteAsPlainText);\r
+\r
+ // Block all drag/drop events\r
+ if (getParam(ed, "paste_block_drop")) {\r
ed.dom.bind(ed.getBody(), ['dragend', 'dragover', 'draggesture', 'dragdrop', 'drop', 'drag'], function(e) {\r
e.preventDefault();\r
e.stopPropagation();\r
\r
return false;\r
});\r
- });\r
- }\r
+ }\r
+ });\r
\r
// Add legacy support\r
t._legacySupport();\r
},\r
\r
_preProcess : function(pl, o) {\r
- //console.log('Before preprocess:' + o.content);\r
-\r
var ed = this.editor,\r
h = o.content,\r
grep = tinymce.grep,\r
trim = tinymce.trim,\r
len, stripClass;\r
\r
+ //console.log('Before preprocess:' + o.content);\r
+\r
function process(items) {\r
each(items, function(v) {\r
// Remove or replace\r
h = h.replace(v[0], v[1]);\r
});\r
}\r
+ \r
+ if (ed.settings.paste_enable_default_filters == false) {\r
+ return;\r
+ }\r
+\r
+ // IE9 adds BRs before/after block elements when contents is pasted from word or for example another browser\r
+ if (tinymce.isIE && document.documentMode >= 9) {\r
+ // IE9 adds BRs before/after block elements when contents is pasted from word or for example another browser\r
+ process([[/(?:<br> [\s\r\n]+|<br>)*(<\/?(h[1-6r]|p|div|address|pre|form|table|tbody|thead|tfoot|th|tr|td|li|ol|ul|caption|blockquote|center|dl|dt|dd|dir|fieldset)[^>]*>)(?:<br> [\s\r\n]+|<br>)*/g, '$1']]);\r
+\r
+ // IE9 also adds an extra BR element for each soft-linefeed and it also adds a BR for each word wrap break\r
+ process([\r
+ [/<br><br>/g, '<BR><BR>'], // Replace multiple BR elements with uppercase BR to keep them intact\r
+ [/<br>/g, ' '], // Replace single br elements with space since they are word wrap BR:s\r
+ [/<BR><BR>/g, '<br>'], // Replace back the double brs but into a single BR\r
+ ]);\r
+ }\r
\r
// Detect Word content and process it more aggressive\r
if (/class="?Mso|style="[^"]*\bmso-|w:WordDocument/i.test(h) || o.wordContent) {\r
if (getParam(ed, "paste_convert_middot_lists")) {\r
process([\r
[/<!--\[if !supportLists\]-->/gi, '$&__MCE_ITEM__'], // Convert supportLists to a list item marker\r
- [/(<span[^>]+(?:mso-list:|:\s*symbol)[^>]+>)/gi, '$1__MCE_ITEM__'] // Convert mso-list and symbol spans to item markers\r
+ [/(<span[^>]+(?:mso-list:|:\s*symbol)[^>]+>)/gi, '$1__MCE_ITEM__'], // Convert mso-list and symbol spans to item markers\r
+ [/(<p[^>]+(?:MsoListParagraph)[^>]+>)/gi, '$1__MCE_ITEM__'] // Convert mso-list and symbol paragraphs to item markers (FF)\r
]);\r
}\r
\r
]);\r
}\r
\r
+ process([\r
+ // Copy paste from Java like Open Office will produce this junk on FF\r
+ [/Version:[\d.]+\nStartHTML:\d+\nEndHTML:\d+\nStartFragment:\d+\nEndFragment:\d+/gi, '']\r
+ ]);\r
+\r
// Class attribute options are: leave all as-is ("none"), remove all ("all"), or remove only those starting with mso ("mso").\r
// Note:- paste_strip_class_attributes: "none", verify_css_classes: true is also a good variation.\r
stripClass = getParam(ed, "paste_strip_class_attributes");\r
};\r
\r
h = h.replace(/ class="([^"]+)"/gi, removeClasses);\r
- h = h.replace(/ class=(\w+)/gi, removeClasses);\r
+ h = h.replace(/ class=([\-\w]+)/gi, removeClasses);\r
}\r
\r
// Remove spans option\r
_postProcess : function(pl, o) {\r
var t = this, ed = t.editor, dom = ed.dom, styleProps;\r
\r
+ if (ed.settings.paste_enable_default_filters == false) {\r
+ return;\r
+ }\r
+ \r
if (o.wordContent) {\r
// Remove named anchors or TOC links\r
each(dom.select('a', o.node), function(a) {\r
if (getParam(ed, "paste_remove_styles") || (getParam(ed, "paste_remove_styles_if_webkit") && tinymce.isWebKit)) {\r
each(dom.select('*[style]', o.node), function(el) {\r
el.removeAttribute('style');\r
- el.removeAttribute('_mce_style');\r
+ el.removeAttribute('data-mce-style');\r
});\r
} else {\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
+ el.removeAttribute('data-mce-style');\r
});\r
}\r
}\r
val = p.innerHTML.replace(/<\/?\w+[^>]*>/gi, '').replace(/ /g, '\u00a0');\r
\r
// Detect unordered lists look for bullets\r
- if (/^(__MCE_ITEM__)+[\u2022\u00b7\u00a7\u00d8o]\s*\u00a0*/.test(val))\r
+ if (/^(__MCE_ITEM__)+[\u2022\u00b7\u00a7\u00d8o\u25CF]\s*\u00a0*/.test(val))\r
type = 'ul';\r
\r
// Detect ordered lists 1., a. or ixv.\r
- if (/^__MCE_ITEM__\s*\w+\.\s*\u00a0{2,}/.test(val))\r
+ if (/^__MCE_ITEM__\s*\w+\.\s*\u00a0+/.test(val))\r
type = 'ol';\r
\r
// Check if node value matches the list pattern: o \r
var html = span.innerHTML.replace(/<\/?\w+[^>]*>/gi, '');\r
\r
// Remove span with the middot or the number\r
- if (type == 'ul' && /^[\u2022\u00b7\u00a7\u00d8o]/.test(html))\r
+ if (type == 'ul' && /^__MCE_ITEM__[\u2022\u00b7\u00a7\u00d8o\u25CF]/.test(html))\r
dom.remove(span);\r
- else if (/^[\s\S]*\w+\.( |\u00a0)*\s*/.test(html))\r
+ else if (/^__MCE_ITEM__[\s\S]*\w+\.( |\u00a0)*\s*/.test(html))\r
dom.remove(span);\r
});\r
\r
\r
// Remove middot/list items\r
if (type == 'ul')\r
- html = p.innerHTML.replace(/__MCE_ITEM__/g, '').replace(/^[\u2022\u00b7\u00a7\u00d8o]\s*( |\u00a0)+\s*/, '');\r
+ html = p.innerHTML.replace(/__MCE_ITEM__/g, '').replace(/^[\u2022\u00b7\u00a7\u00d8o\u25CF]\s*( |\u00a0)+\s*/, '');\r
else\r
html = p.innerHTML.replace(/__MCE_ITEM__/g, '').replace(/^\s*\w+\.( |\u00a0)+\s*/, '');\r
\r
o.node.innerHTML = html.replace(/__MCE_ITEM__/g, '');\r
},\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, markerId = 'mce_marker';\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
- // Insert a marker for the caret position\r
- this._insert('<span id="' + markerId + '"> </span>', 1);\r
- marker = dom.get(markerId);\r
- parentBlock = dom.getParent(marker, 'p,h1,h2,h3,h4,h5,h6,ul,ol,th,td');\r
-\r
- // If it's a parent block but not a table cell\r
- if (parentBlock && !/TD|TH/.test(parentBlock.nodeName)) {\r
- // Split parent block\r
- marker = dom.split(parentBlock, marker);\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
- // Remove marker if it's left\r
- while (elm = dom.get(markerId))\r
- dom.remove(elm);\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
/**\r
* Inserts the specified contents at the caret position.\r
*/\r
_insert : function(h, skip_undo) {\r
- var ed = this.editor;\r
+ var ed = this.editor, r = ed.selection.getRng();\r
\r
- // First delete the contents seems to work better on WebKit\r
- if (!ed.selection.isCollapsed())\r
+ // First delete the contents seems to work better on WebKit when the selection spans multiple list items or multiple table cells.\r
+ if (!ed.selection.isCollapsed() && r.startContainer != r.endContainer)\r
ed.getDoc().execCommand('Delete', false, null);\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
+ ed.execCommand('mceInsertContent', false, h, {skip_undo : skip_undo});\r
},\r
\r
/**\r
};\r
\r
if ((typeof(h) === "string") && (h.length > 0)) {\r
- if (!entities)\r
- entities = ("34,quot,38,amp,39,apos,60,lt,62,gt," + ed.serializer.settings.entities).split(",");\r
-\r
// If HTML content with line-breaking tags, then remove all cr/lf chars because only tags will break a line\r
if (/<(?:p|br|h[1-6]|ul|ol|dl|table|t[rdh]|div|blockquote|fieldset|pre|address|center)[^>]*>/i.test(h)) {\r
process([\r
[/<\/t[dh]>\s*<t[dh][^>]*>/gi, "\t"], // Table cells get tabs betweem them\r
/<[a-z!\/?][^>]*>/gi, // Delete all remaining tags\r
[/ /gi, " "], // Convert non-break spaces to regular spaces (remember, *plain text*)\r
- [\r
- // HTML entity\r
- /&(#\d+|[a-z0-9]{1,10});/gi,\r
-\r
- // Replace with actual character\r
- function(e, s) {\r
- if (s.charAt(0) === "#") {\r
- return String.fromCharCode(s.slice(1));\r
- }\r
- else {\r
- return ((e = inArray(entities, s)) > 0)? String.fromCharCode(entities[e-1]) : " ";\r
- }\r
- }\r
- ],\r
[/(?:(?!\n)\s)*(\n+)(?:(?!\n)\s)*/gi, "$1"], // Cool little RegExp deletes whitespace around linebreak chars.\r
[/\n{3,}/g, "\n\n"], // Max. 2 consecutive linebreaks\r
/^\s+|\s+$/g // Trim the front & back\r
]);\r
\r
- h = dom.encode(h);\r
+ h = dom.decode(tinymce.html.Entities.encodeRaw(h));\r
\r
// Delete any highlighted text before pasting\r
if (!sel.isCollapsed()) {\r