", "gi"); - content = tinyMCE.regexpReplace(content, "\r\r", "
", "gi"); - content = tinyMCE.regexpReplace(content, "\n\n", "
", "gi"); - - // Has paragraphs - if ((pos = content.indexOf('
')) != -1) { - tinyMCE.execCommand("Delete"); - - var node = tinyMCE.selectedInstance.getFocusElement(); - - // Get list of elements to break - var breakElms = new Array(); - - do { - if (node.nodeType == 1) { - // Don't break tables and break at body - if (node.nodeName == "TD" || node.nodeName == "BODY") - break; - - breakElms[breakElms.length] = node; - } - } while(node = node.parentNode); - - var before = "", after = "
"; - before += content.substring(0, pos); - - for (var i=0; i";
- content = before + content.substring(pos+7) + after;
- }
- }
-
- if (tinyMCE.getParam("paste_create_linebreaks", true)) {
- content = tinyMCE.regexpReplace(content, "\r\n", "
", "gi");
- content = tinyMCE.regexpReplace(content, "\r", "
", "gi");
- content = tinyMCE.regexpReplace(content, "\n", "
", "gi");
+ // Restore the old range and clear the contents before pasting
+ sel.setRng(oldRng);
+ sel.setContent('');
+
+ // For some odd reason we need to detach the the mceInsertContent call from the paste event
+ // It's like IE has a reference to the parent element that you paste in and the selection gets messed up
+ // when it tries to restore the selection
+ setTimeout(function() {
+ // Process contents
+ process({content : n.innerHTML});
+ }, 0);
+
+ // Block the real paste event
+ return tinymce.dom.Event.cancel(e);
+ } else {
+ function block(e) {
+ e.preventDefault();
+ };
+
+ // Block mousedown and click to prevent selection change
+ dom.bind(ed.getDoc(), 'mousedown', block);
+ dom.bind(ed.getDoc(), 'keydown', block);
+
+ or = ed.selection.getRng();
+
+ // Move select contents inside DIV
+ n = n.firstChild;
+ rng = ed.getDoc().createRange();
+ rng.setStart(n, 0);
+ rng.setEnd(n, 2);
+ sel.setRng(rng);
+
+ // Wait a while and grab the pasted contents
+ window.setTimeout(function() {
+ var h = '', nl;
+
+ // Paste divs duplicated in paste divs seems to happen when you paste plain text so lets first look for that broken behavior in WebKit
+ if (!dom.select('div.mcePaste > div.mcePaste').length) {
+ nl = dom.select('div.mcePaste');
+
+ // WebKit will split the div into multiple ones so this will loop through then all and join them to get the whole HTML string
+ each(nl, function(n) {
+ var child = n.firstChild;
+
+ // WebKit inserts a DIV container with lots of odd styles
+ if (child && child.nodeName == 'DIV' && child.style.marginTop && child.style.backgroundColor) {
+ dom.remove(child, 1);
+ }
+
+ // Remove apply style spans
+ each(dom.select('span.Apple-style-span', n), function(n) {
+ dom.remove(n, 1);
+ });
+
+ // Remove bogus br elements
+ each(dom.select('br[data-mce-bogus]', n), function(n) {
+ dom.remove(n);
+ });
+
+ // WebKit will make a copy of the DIV for each line of plain text pasted and insert them into the DIV
+ if (n.parentNode.className != 'mcePaste')
+ h += n.innerHTML;
+ });
+ } else {
+ // Found WebKit weirdness so force the content into paragraphs this seems to happen when you paste plain text from Nodepad etc
+ // So this logic will replace double enter with paragraphs and single enter with br so it kind of looks the same
+ h = '
' + dom.encode(textContent).replace(/\r?\n\r?\n/g, '
').replace(/\r?\n/g, '
') + '
$1
'); + // IE9 adds BRs before/after block elements when contents is pasted from word or for example another browser + if (tinymce.isIE && document.documentMode >= 9) { + // IE9 adds BRs before/after block elements when contents is pasted from word or for example another browser + process([[/(?:" + middot + "$1
"); - content = content.replace(new RegExp('', 'gi'), "" + bull); // Covert to bull list - content = content.replace(/]*class="?MsoHeading"?[^>]*>(.*?)<\/p>/gi, "
$1
"); + } - content = content.replace(/<\/?font[^>]*>/gi, ""); + if (getParam(ed, "paste_convert_middot_lists")) { + process([ + [//gi, '$&__MCE_ITEM__'], // Convert supportLists to a list item marker + [/(]+(?:mso-list:|:\s*symbol)[^>]+>)/gi, '$1__MCE_ITEM__'], // Convert mso-list and symbol spans to item markers + [/(]+(?:MsoListParagraph)[^>]+>)/gi, '$1__MCE_ITEM__'] // Convert mso-list and symbol paragraphs to item markers (FF)
+ ]);
+ }
- // Strips class attributes.
- switch (tinyMCE.getParam("paste_strip_class_attributes", "all")) {
- case "all":
- content = content.replace(/<(\w[^>]*) class=([^ |>]*)([^>]*)/gi, "<$1$3");
- break;
+ process([
+ // Word comments like conditional comments etc
+ //gi,
+
+ // Remove comments, scripts (e.g., msoShowComment), XML tag, VML content, MS Office namespaced tags, and a few other tags
+ /<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi,
+
+ // Convert "],
+ [/<\/h[1-6][^>]*>/gi, " <\/p>/gi, ""); // Remove pagebreaks
- content = content.replace(/-- page break --/gi, ""); // Remove pagebreaks
+ process([
+ // Copy paste from Java like Open Office will produce this junk on FF
+ [/Version:[\d.]+\nStartHTML:\d+\nEndHTML:\d+\nStartFragment:\d+\nEndFragment:\d+/gi, '']
+ ]);
+
+ // Class attribute options are: leave all as-is ("none"), remove all ("all"), or remove only those starting with mso ("mso").
+ // Note:- paste_strip_class_attributes: "none", verify_css_classes: true is also a good variation.
+ stripClass = getParam(ed, "paste_strip_class_attributes");
+
+ if (stripClass !== "none") {
+ function removeClasses(match, g1) {
+ if (stripClass === "all")
+ return '';
+
+ var cls = grep(explode(g1.replace(/^(["'])(.*)\1$/, "$2"), " "),
+ function(v) {
+ return (/^(?!mso)/i.test(v));
+ }
+ );
- // content = content.replace(/\/? */gi, "");
- // content = content.replace(/ <\/p>/gi, '');
+ return cls.length ? ' class="' + cls.join(" ") + '"' : '';
+ };
- if (!tinyMCE.settings['force_p_newlines']) {
- content = content.replace('', '' ,'gi');
- content = content.replace(' ');
- content = content.replace(/<\/h[1-6]>/gi, ' into for line-though
+ [/<(\/?)s>/gi, "<$1strike>"],
+
+ // Replace nsbp entites to char since it's easier to handle
+ [/ /gi, "\u00a0"]
+ ]);
+
+ // Remove bad attributes, with or without quotes, ensuring that attribute text is really inside a tag.
+ // If JavaScript had a RegExp look-behind, we could have integrated this with the last process() array and got rid of the loop. But alas, it does not, so we cannot.
+ do {
+ len = h.length;
+ h = h.replace(/(<[a-z][^>]*\s)(?:id|name|language|type|on\w+|\w+:\w+)=(?:"[^"]*"|\w+)\s?/gi, "$1");
+ } while (len != h.length);
+
+ // Remove all spans if no styles is to be retained
+ if (getParam(ed, "paste_retain_style_properties").replace(/^none$/i, "").length == 0) {
+ h = h.replace(/<\/?span[^>]*>/gi, "");
+ } else {
+ // We're keeping styles, so at least clean them up.
+ // CSS Reference: http://msdn.microsoft.com/en-us/library/aa155477.aspx
+
+ process([
+ // Convert ___ to string of alternating breaking/non-breaking spaces of same length
+ [/([\s\u00a0]*)<\/span>/gi,
+ function(str, spaces) {
+ return (spaces.length > 0)? spaces.replace(/./, " ").slice(Math.floor(spaces.length/2)).split("").join("\u00a0") : "";
+ }
+ ],
+
+ // Examine all styles: delete junk, transform some, and keep the rest
+ [/(<[a-z][^>]*)\sstyle="([^"]*)"/gi,
+ function(str, tag, style) {
+ var n = [],
+ i = 0,
+ s = explode(trim(style).replace(/"/gi, "'"), ";");
+
+ // Examine each style definition within the tag's style attribute
+ each(s, function(v) {
+ var name, value,
+ parts = explode(v, ":");
+
+ function ensureUnits(v) {
+ return v + ((v !== "0") && (/\d$/.test(v)))? "px" : "";
+ }
+
+ if (parts.length == 2) {
+ name = parts[0].toLowerCase();
+ value = parts[1].toLowerCase();
+
+ // Translate certain MS Office styles into their CSS equivalents
+ switch (name) {
+ case "mso-padding-alt":
+ case "mso-padding-top-alt":
+ case "mso-padding-right-alt":
+ case "mso-padding-bottom-alt":
+ case "mso-padding-left-alt":
+ case "mso-margin-alt":
+ case "mso-margin-top-alt":
+ case "mso-margin-right-alt":
+ case "mso-margin-bottom-alt":
+ case "mso-margin-left-alt":
+ case "mso-table-layout-alt":
+ case "mso-height":
+ case "mso-width":
+ case "mso-vertical-align-alt":
+ n[i++] = name.replace(/^mso-|-alt$/g, "") + ":" + ensureUnits(value);
+ return;
+
+ case "horiz-align":
+ n[i++] = "text-align:" + value;
+ return;
+
+ case "vert-align":
+ n[i++] = "vertical-align:" + value;
+ return;
+
+ case "font-color":
+ case "mso-foreground":
+ n[i++] = "color:" + value;
+ return;
+
+ case "mso-background":
+ case "mso-highlight":
+ n[i++] = "background:" + value;
+ return;
+
+ case "mso-default-height":
+ n[i++] = "min-height:" + ensureUnits(value);
+ return;
+
+ case "mso-default-width":
+ n[i++] = "min-width:" + ensureUnits(value);
+ return;
+
+ case "mso-padding-between-alt":
+ n[i++] = "border-collapse:separate;border-spacing:" + ensureUnits(value);
+ return;
+
+ case "text-line-through":
+ if ((value == "single") || (value == "double")) {
+ n[i++] = "text-decoration:line-through";
+ }
+ return;
+
+ case "mso-zero-height":
+ if (value == "yes") {
+ n[i++] = "display:none";
+ }
+ return;
+ }
+
+ // Eliminate all MS Office style definitions that have no CSS equivalent by examining the first characters in the name
+ if (/^(mso|column|font-emph|lang|layout|line-break|list-image|nav|panose|punct|row|ruby|sep|size|src|tab-|table-border|text-(?!align|decor|indent|trans)|top-bar|version|vnd|word-break)/.test(name)) {
+ return;
+ }
+
+ // If it reached this point, it must be a valid CSS style
+ n[i++] = name + ":" + parts[1]; // Lower-case name, but keep value case
+ }
+ });
+
+ // If style attribute contained any valid styles the re-write it; otherwise delete style attribute.
+ if (i > 0) {
+ return tag + ' style="' + n.join(';') + '"';
+ } else {
+ return tag;
+ }
+ }
+ ]
+ ]);
+ }
+ }
- case "mso":
- content = content.replace(new RegExp('<(\\w[^>]*) class="?mso([^ |>]*)([^>]*)', 'gi'), "<$1$3");
- break;
+ // Replace headers with
+ if (getParam(ed, "paste_convert_headers_to_strong")) {
+ process([
+ [/
' ,'gi');
+ h = h.replace(/ class="([^"]+)"/gi, removeClasses);
+ h = h.replace(/ class=([\-\w]+)/gi, removeClasses);
}
- if (!tinyMCE.isMSIE && !tinyMCE.settings['force_p_newlines']) {
- content = content.replace(/<\/?p[^>]*>/gi, "");
+ // Remove spans option
+ if (getParam(ed, "paste_remove_spans")) {
+ h = h.replace(/<\/?span[^>]*>/gi, "");
}
- content = content.replace(/<\/?div[^>]*>/gi, "");
+ //console.log('After preprocess:' + h);
- // Convert all middlot lists to UL lists
- if (tinyMCE.getParam("paste_convert_middot_lists", true)) {
- var div = document.createElement("div");
- div.innerHTML = content;
+ o.content = h;
+ },
- // Convert all middot paragraphs to li elements
- var className = tinyMCE.getParam("paste_unindented_list_class", "unIndentedList");
+ /**
+ * Various post process items.
+ */
+ _postProcess : function(pl, o) {
+ var t = this, ed = t.editor, dom = ed.dom, styleProps;
- while (TinyMCE_PastePlugin._convertMiddots(div, "--list--")) ; // bull
- while (TinyMCE_PastePlugin._convertMiddots(div, middot, className)) ; // Middot
- while (TinyMCE_PastePlugin._convertMiddots(div, bull)) ; // bull
+ if (ed.settings.paste_enable_default_filters == false) {
+ return;
+ }
+
+ if (o.wordContent) {
+ // Remove named anchors or TOC links
+ each(dom.select('a', o.node), function(a) {
+ if (!a.href || a.href.indexOf('#_Toc') != -1)
+ dom.remove(a, 1);
+ });
+
+ if (getParam(ed, "paste_convert_middot_lists")) {
+ t._convertLists(pl, o);
+ }
- content = div.innerHTML;
+ // Process styles
+ styleProps = getParam(ed, "paste_retain_style_properties"); // retained properties
+
+ // Process only if a string was specified and not equal to "all" or "*"
+ if ((tinymce.is(styleProps, "string")) && (styleProps !== "all") && (styleProps !== "*")) {
+ styleProps = tinymce.explode(styleProps.replace(/^none$/i, ""));
+
+ // Retains some style properties
+ each(dom.select('*', o.node), function(el) {
+ var newStyle = {}, npc = 0, i, sp, sv;
+
+ // Store a subset of the existing styles
+ if (styleProps) {
+ for (i = 0; i < styleProps.length; i++) {
+ sp = styleProps[i];
+ sv = dom.getStyle(el, sp);
+
+ if (sv) {
+ newStyle[sp] = sv;
+ npc++;
+ }
+ }
+ }
+
+ // Remove all of the existing styles
+ dom.setAttrib(el, 'style', '');
+
+ if (styleProps && npc > 0)
+ dom.setStyles(el, newStyle); // Add back the stored subset of styles
+ else // Remove empty span tags that do not have class attributes
+ if (el.nodeName == 'SPAN' && !el.className)
+ dom.remove(el, true);
+ });
+ }
}
- // Replace all headers with strong and fix some other issues
- if (tinyMCE.getParam("paste_convert_headers_to_strong", false)) {
- content = content.replace(/
]*>|<\/tr>/gi, "\n"], // Single linebreak for
tags and table rows
+ [/<\/t[dh]>\s*
"]
+ ]);
+ } else {
+ process([
+ [/\n\n/g, "
"], + [/^(.*<\/p>)(
)$/, '
$1'],
+ [/\n/g, "
"]
+ ]);
+ }
- with (div.style) {
- visibility = 'hidden';
- overflow = 'hidden';
- position = 'absolute';
- width = 1;
- height = 1;
+ ed.execCommand('mceInsertContent', false, content);
+ }
+ },
+
+ /**
+ * This method will open the old style paste dialogs. Some users might want the old behavior but still use the new cleanup engine.
+ */
+ _legacySupport : function() {
+ var t = this, ed = t.editor;
+
+ // Register command(s) for backwards compatibility
+ ed.addCommand("mcePasteWord", function() {
+ ed.windowManager.open({
+ file: t.url + "/pasteword.htm",
+ width: parseInt(getParam(ed, "paste_dialog_width")),
+ height: parseInt(getParam(ed, "paste_dialog_height")),
+ inline: 1
+ });
+ });
+
+ if (getParam(ed, "paste_text_use_dialog")) {
+ ed.addCommand("mcePasteText", function() {
+ ed.windowManager.open({
+ file : t.url + "/pastetext.htm",
+ width: parseInt(getParam(ed, "paste_dialog_width")),
+ height: parseInt(getParam(ed, "paste_dialog_height")),
+ inline : 1
+ });
+ });
}
- document.body.appendChild(div);
+ // Register button for backwards compatibility
+ ed.addButton("pasteword", {title : "paste.paste_word_desc", cmd : "mcePasteWord"});
}
+ });
- div.innerHTML = '';
- var rng = document.body.createTextRange();
- rng.moveToElementText(div);
- rng.execCommand('Paste');
- var html = div.innerHTML;
- div.innerHTML = '';
- return html;
- }
-};
-
-tinyMCE.addPlugin("paste", TinyMCE_PastePlugin);
+ // Register plugin
+ tinymce.PluginManager.add("paste", tinymce.plugins.PastePlugin);
+})();