]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-includes/js/tinymce/plugins/paste/plugin.js
WordPress 4.0
[autoinstalls/wordpress.git] / wp-includes / js / tinymce / plugins / paste / plugin.js
1 /**
2  * Compiled inline version. (Library mode)
3  */
4
5 /*jshint smarttabs:true, undef:true, latedef:true, curly:true, bitwise:true, camelcase:true */
6 /*globals $code */
7
8 (function(exports, undefined) {
9         "use strict";
10
11         var modules = {};
12
13         function require(ids, callback) {
14                 var module, defs = [];
15
16                 for (var i = 0; i < ids.length; ++i) {
17                         module = modules[ids[i]] || resolve(ids[i]);
18                         if (!module) {
19                                 throw 'module definition dependecy not found: ' + ids[i];
20                         }
21
22                         defs.push(module);
23                 }
24
25                 callback.apply(null, defs);
26         }
27
28         function define(id, dependencies, definition) {
29                 if (typeof id !== 'string') {
30                         throw 'invalid module definition, module id must be defined and be a string';
31                 }
32
33                 if (dependencies === undefined) {
34                         throw 'invalid module definition, dependencies must be specified';
35                 }
36
37                 if (definition === undefined) {
38                         throw 'invalid module definition, definition function must be specified';
39                 }
40
41                 require(dependencies, function() {
42                         modules[id] = definition.apply(null, arguments);
43                 });
44         }
45
46         function defined(id) {
47                 return !!modules[id];
48         }
49
50         function resolve(id) {
51                 var target = exports;
52                 var fragments = id.split(/[.\/]/);
53
54                 for (var fi = 0; fi < fragments.length; ++fi) {
55                         if (!target[fragments[fi]]) {
56                                 return;
57                         }
58
59                         target = target[fragments[fi]];
60                 }
61
62                 return target;
63         }
64
65         function expose(ids) {
66                 for (var i = 0; i < ids.length; i++) {
67                         var target = exports;
68                         var id = ids[i];
69                         var fragments = id.split(/[.\/]/);
70
71                         for (var fi = 0; fi < fragments.length - 1; ++fi) {
72                                 if (target[fragments[fi]] === undefined) {
73                                         target[fragments[fi]] = {};
74                                 }
75
76                                 target = target[fragments[fi]];
77                         }
78
79                         target[fragments[fragments.length - 1]] = modules[id];
80                 }
81         }
82
83 // Included from: js/tinymce/plugins/paste/classes/Utils.js
84
85 /**
86  * Utils.js
87  *
88  * Copyright, Moxiecode Systems AB
89  * Released under LGPL License.
90  *
91  * License: http://www.tinymce.com/license
92  * Contributing: http://www.tinymce.com/contributing
93  */
94
95 /**
96  * This class contails various utility functions for the paste plugin.
97  *
98  * @class tinymce.pasteplugin.Clipboard
99  * @private
100  */
101 define("tinymce/pasteplugin/Utils", [
102         "tinymce/util/Tools",
103         "tinymce/html/DomParser",
104         "tinymce/html/Schema"
105 ], function(Tools, DomParser, Schema) {
106         function filter(content, items) {
107                 Tools.each(items, function(v) {
108                         if (v.constructor == RegExp) {
109                                 content = content.replace(v, '');
110                         } else {
111                                 content = content.replace(v[0], v[1]);
112                         }
113                 });
114
115                 return content;
116         }
117
118         /**
119          * Gets the innerText of the specified element. It will handle edge cases
120          * and works better than textContent on Gecko.
121          *
122          * @param {String} html HTML string to get text from.
123          * @return {String} String of text with line feeds.
124          */
125         function innerText(html) {
126                 var schema = new Schema(), domParser = new DomParser({}, schema), text = '';
127                 var shortEndedElements = schema.getShortEndedElements();
128                 var ignoreElements = Tools.makeMap('script noscript style textarea video audio iframe object', ' ');
129                 var blockElements = schema.getBlockElements();
130
131                 function walk(node) {
132                         var name = node.name, currentNode = node;
133
134                         if (name === 'br') {
135                                 text += '\n';
136                                 return;
137                         }
138
139                         // img/input/hr
140                         if (shortEndedElements[name]) {
141                                 text += ' ';
142                         }
143
144                         // Ingore script, video contents
145                         if (ignoreElements[name]) {
146                                 text += ' ';
147                                 return;
148                         }
149
150                         if (node.type == 3) {
151                                 text += node.value;
152                         }
153
154                         // Walk all children
155                         if (!node.shortEnded) {
156                                 if ((node = node.firstChild)) {
157                                         do {
158                                                 walk(node);
159                                         } while ((node = node.next));
160                                 }
161                         }
162
163                         // Add \n or \n\n for blocks or P
164                         if (blockElements[name] && currentNode.next) {
165                                 text += '\n';
166
167                                 if (name == 'p') {
168                                         text += '\n';
169                                 }
170                         }
171                 }
172
173                 html = filter(html, [
174                         /<!\[[^\]]+\]>/g // Conditional comments
175                 ]);
176
177                 walk(domParser.parse(html));
178
179                 return text;
180         }
181
182         /**
183          * Trims the specified HTML by removing all WebKit fragments, all elements wrapping the body trailing BR elements etc.
184          *
185          * @param {String} html Html string to trim contents on.
186          * @return {String} Html contents that got trimmed.
187          */
188         function trimHtml(html) {
189                 function trimSpaces(all, s1, s2) {
190                         // WebKit &nbsp; meant to preserve multiple spaces but instead inserted around all inline tags,
191                         // including the spans with inline styles created on paste
192                         if (!s1 && !s2) {
193                                 return ' ';
194                         }
195
196                         return '\u00a0';
197                 }
198
199                 html = filter(html, [
200                         /^[\s\S]*<body[^>]*>\s*|\s*<\/body[^>]*>[\s\S]*$/g, // Remove anything but the contents within the BODY element
201                         /<!--StartFragment-->|<!--EndFragment-->/g, // Inner fragments (tables from excel on mac)
202                         [/( ?)<span class="Apple-converted-space">\u00a0<\/span>( ?)/g, trimSpaces],
203                         /<br>$/i // Trailing BR elements
204                 ]);
205
206                 return html;
207         }
208
209         return {
210                 filter: filter,
211                 innerText: innerText,
212                 trimHtml: trimHtml
213         };
214 });
215
216 // Included from: js/tinymce/plugins/paste/classes/Clipboard.js
217
218 /**
219  * Clipboard.js
220  *
221  * Copyright, Moxiecode Systems AB
222  * Released under LGPL License.
223  *
224  * License: http://www.tinymce.com/license
225  * Contributing: http://www.tinymce.com/contributing
226  */
227
228 /**
229  * This class contains logic for getting HTML contents out of the clipboard.
230  *
231  * We need to make a lot of ugly hacks to get the contents out of the clipboard since
232  * the W3C Clipboard API is broken in all browsers that have it: Gecko/WebKit/Blink.
233  * We might rewrite this the way those API:s stabilize. Browsers doesn't handle pasting
234  * from applications like Word the same way as it does when pasting into a contentEditable area
235  * so we need to do lots of extra work to try to get to this clipboard data.
236  *
237  * Current implementation steps:
238  *  1. On keydown with paste keys Ctrl+V or Shift+Insert create
239  *     a paste bin element and move focus to that element.
240  *  2. Wait for the browser to fire a "paste" event and get the contents out of the paste bin.
241  *  3. Check if the paste was successful if true, process the HTML.
242  *  (4). If the paste was unsuccessful use IE execCommand, Clipboard API, document.dataTransfer old WebKit API etc.
243  *
244  * @class tinymce.pasteplugin.Clipboard
245  * @private
246  */
247 define("tinymce/pasteplugin/Clipboard", [
248         "tinymce/Env",
249         "tinymce/util/VK",
250         "tinymce/pasteplugin/Utils"
251 ], function(Env, VK, Utils) {
252         return function(editor) {
253                 var self = this, pasteBinElm, lastRng, keyboardPasteTimeStamp = 0, draggingInternally = false;
254                 var pasteBinDefaultContent = '%MCEPASTEBIN%', keyboardPastePlainTextState;
255
256                 /**
257                  * Pastes the specified HTML. This means that the HTML is filtered and then
258                  * inserted at the current selection in the editor. It will also fire paste events
259                  * for custom user filtering.
260                  *
261                  * @param {String} html HTML code to paste into the current selection.
262                  */
263                 function pasteHtml(html) {
264                         var args, dom = editor.dom;
265
266                         args = editor.fire('BeforePastePreProcess', {content: html}); // Internal event used by Quirks
267                         args = editor.fire('PastePreProcess', args);
268                         html = args.content;
269
270                         if (!args.isDefaultPrevented()) {
271                                 // User has bound PastePostProcess events then we need to pass it through a DOM node
272                                 // This is not ideal but we don't want to let the browser mess up the HTML for example
273                                 // some browsers add &nbsp; to P tags etc
274                                 if (editor.hasEventListeners('PastePostProcess') && !args.isDefaultPrevented()) {
275                                         // We need to attach the element to the DOM so Sizzle selectors work on the contents
276                                         var tempBody = dom.add(editor.getBody(), 'div', {style: 'display:none'}, html);
277                                         args = editor.fire('PastePostProcess', {node: tempBody});
278                                         dom.remove(tempBody);
279                                         html = args.node.innerHTML;
280                                 }
281
282                                 if (!args.isDefaultPrevented()) {
283                                         editor.insertContent(html, {merge: editor.settings.paste_merge_formats !== false});
284                                 }
285                         }
286                 }
287
288                 /**
289                  * Pastes the specified text. This means that the plain text is processed
290                  * and converted into BR and P elements. It will fire paste events for custom filtering.
291                  *
292                  * @param {String} text Text to paste as the current selection location.
293                  */
294                 function pasteText(text) {
295                         text = editor.dom.encode(text).replace(/\r\n/g, '\n');
296
297                         var startBlock = editor.dom.getParent(editor.selection.getStart(), editor.dom.isBlock);
298
299                         // Create start block html for example <p attr="value">
300                         var forcedRootBlockName = editor.settings.forced_root_block;
301                         var forcedRootBlockStartHtml;
302                         if (forcedRootBlockName) {
303                                 forcedRootBlockStartHtml = editor.dom.createHTML(forcedRootBlockName, editor.settings.forced_root_block_attrs);
304                                 forcedRootBlockStartHtml = forcedRootBlockStartHtml.substr(0, forcedRootBlockStartHtml.length - 3) + '>';
305                         }
306
307                         if ((startBlock && /^(PRE|DIV)$/.test(startBlock.nodeName)) || !forcedRootBlockName) {
308                                 text = Utils.filter(text, [
309                                         [/\n/g, "<br>"]
310                                 ]);
311                         } else {
312                                 text = Utils.filter(text, [
313                                         [/\n\n/g, "</p>" + forcedRootBlockStartHtml],
314                                         [/^(.*<\/p>)(<p>)$/, forcedRootBlockStartHtml + '$1'],
315                                         [/\n/g, "<br />"]
316                                 ]);
317
318                                 if (text.indexOf('<p>') != -1) {
319                                         text = forcedRootBlockStartHtml + text;
320                                 }
321                         }
322
323                         pasteHtml(text);
324                 }
325
326                 /**
327                  * Creates a paste bin element as close as possible to the current caret location and places the focus inside that element
328                  * so that when the real paste event occurs the contents gets inserted into this element
329                  * instead of the current editor selection element.
330                  */
331                 function createPasteBin() {
332                         var dom = editor.dom, body = editor.getBody();
333                         var viewport = editor.dom.getViewPort(editor.getWin()), scrollTop = viewport.y, top = 20;
334                         var scrollContainer;
335
336                         lastRng = editor.selection.getRng();
337
338                         if (editor.inline) {
339                                 scrollContainer = editor.selection.getScrollContainer();
340
341                                 // Can't always rely on scrollTop returning a useful value.
342                                 // It returns 0 if the browser doesn't support scrollTop for the element or is non-scrollable
343                                 if (scrollContainer && scrollContainer.scrollTop > 0) {
344                                         scrollTop = scrollContainer.scrollTop;
345                                 }
346                         }
347
348                         // Calculate top cordinate this is needed to avoid scrolling to top of document
349                         // We want the paste bin to be as close to the caret as possible to avoid scrolling
350                         if (lastRng.getClientRects) {
351                                 var rects = lastRng.getClientRects();
352
353                                 if (rects.length) {
354                                         // Client rects gets us closes to the actual
355                                         // caret location in for example a wrapped paragraph block
356                                         top = scrollTop + (rects[0].top - dom.getPos(body).y);
357                                 } else {
358                                         top = scrollTop;
359
360                                         // Check if we can find a closer location by checking the range element
361                                         var container = lastRng.startContainer;
362                                         if (container) {
363                                                 if (container.nodeType == 3 && container.parentNode != body) {
364                                                         container = container.parentNode;
365                                                 }
366
367                                                 if (container.nodeType == 1) {
368                                                         top = dom.getPos(container, scrollContainer || body).y;
369                                                 }
370                                         }
371                                 }
372                         }
373
374                         // Create a pastebin
375                         pasteBinElm = dom.add(editor.getBody(), 'div', {
376                                 id: "mcepastebin",
377                                 contentEditable: true,
378                                 "data-mce-bogus": "all",
379                                 style: 'position: absolute; top: ' + top + 'px;' +
380                                         'width: 10px; height: 10px; overflow: hidden; opacity: 0'
381                         }, pasteBinDefaultContent);
382
383                         // Move paste bin out of sight since the controlSelection rect gets displayed otherwise on IE and Gecko
384                         if (Env.ie || Env.gecko) {
385                                 dom.setStyle(pasteBinElm, 'left', dom.getStyle(body, 'direction', true) == 'rtl' ? 0xFFFF : -0xFFFF);
386                         }
387
388                         // Prevent focus events from bubbeling fixed FocusManager issues
389                         dom.bind(pasteBinElm, 'beforedeactivate focusin focusout', function(e) {
390                                 e.stopPropagation();
391                         });
392
393                         pasteBinElm.focus();
394                         editor.selection.select(pasteBinElm, true);
395                 }
396
397                 /**
398                  * Removes the paste bin if it exists.
399                  */
400                 function removePasteBin() {
401                         if (pasteBinElm) {
402                                 var pasteBinClone;
403
404                                 // WebKit/Blink might clone the div so
405                                 // lets make sure we remove all clones
406                                 // TODO: Man o man is this ugly. WebKit is the new IE! Remove this if they ever fix it!
407                                 while ((pasteBinClone = editor.dom.get('mcepastebin'))) {
408                                         editor.dom.remove(pasteBinClone);
409                                         editor.dom.unbind(pasteBinClone);
410                                 }
411
412                                 if (lastRng) {
413                                         editor.selection.setRng(lastRng);
414                                 }
415                         }
416
417                         pasteBinElm = lastRng = null;
418                 }
419
420                 /**
421                  * Returns the contents of the paste bin as a HTML string.
422                  *
423                  * @return {String} Get the contents of the paste bin.
424                  */
425                 function getPasteBinHtml() {
426                         var html = '', pasteBinClones, i, clone, cloneHtml;
427
428                         // Since WebKit/Chrome might clone the paste bin when pasting
429                         // for example: <img style="float: right"> we need to check if any of them contains some useful html.
430                         // TODO: Man o man is this ugly. WebKit is the new IE! Remove this if they ever fix it!
431                         pasteBinClones = editor.dom.select('div[id=mcepastebin]');
432                         for (i = 0; i < pasteBinClones.length; i++) {
433                                 clone = pasteBinClones[i];
434
435                                 // Pasting plain text produces pastebins in pastebinds makes sence right!?
436                                 if (clone.firstChild && clone.firstChild.id == 'mcepastebin') {
437                                         clone = clone.firstChild;
438                                 }
439
440                                 cloneHtml = clone.innerHTML;
441                                 if (html != pasteBinDefaultContent) {
442                                         html += cloneHtml;
443                                 }
444                         }
445
446                         return html;
447                 }
448
449                 /**
450                  * Gets various content types out of a datatransfer object.
451                  *
452                  * @param {DataTransfer} dataTransfer Event fired on paste.
453                  * @return {Object} Object with mime types and data for those mime types.
454                  */
455                 function getDataTransferItems(dataTransfer) {
456                         var data = {};
457
458                         if (dataTransfer) {
459                                 // Use old WebKit/IE API
460                                 if (dataTransfer.getData) {
461                                         var legacyText = dataTransfer.getData('Text');
462                                         if (legacyText && legacyText.length > 0) {
463                                                 data['text/plain'] = legacyText;
464                                         }
465                                 }
466
467                                 if (dataTransfer.types) {
468                                         for (var i = 0; i < dataTransfer.types.length; i++) {
469                                                 var contentType = dataTransfer.types[i];
470                                                 data[contentType] = dataTransfer.getData(contentType);
471                                         }
472                                 }
473                         }
474
475                         return data;
476                 }
477
478                 /**
479                  * Gets various content types out of the Clipboard API. It will also get the
480                  * plain text using older IE and WebKit API:s.
481                  *
482                  * @param {ClipboardEvent} clipboardEvent Event fired on paste.
483                  * @return {Object} Object with mime types and data for those mime types.
484                  */
485                 function getClipboardContent(clipboardEvent) {
486                         return getDataTransferItems(clipboardEvent.clipboardData || editor.getDoc().dataTransfer);
487                 }
488
489                 /**
490                  * Checks if the clipboard contains image data if it does it will take that data
491                  * and convert it into a data url image and paste that image at the caret location.
492                  *
493                  * @param  {ClipboardEvent} e Paste/drop event object.
494                  * @param  {DOMRange} rng Optional rng object to move selection to.
495                  * @return {Boolean} true/false if the image data was found or not.
496                  */
497                 function pasteImageData(e, rng) {
498                         var dataTransfer = e.clipboardData || e.dataTransfer;
499
500                         function processItems(items) {
501                                 var i, item, reader;
502
503                                 function pasteImage() {
504                                         if (rng) {
505                                                 editor.selection.setRng(rng);
506                                                 rng = null;
507                                         }
508
509                                         pasteHtml('<img src="' + reader.result + '">');
510                                 }
511
512                                 if (items) {
513                                         for (i = 0; i < items.length; i++) {
514                                                 item = items[i];
515
516                                                 if (/^image\/(jpeg|png|gif)$/.test(item.type)) {
517                                                         reader = new FileReader();
518                                                         reader.onload = pasteImage;
519                                                         reader.readAsDataURL(item.getAsFile ? item.getAsFile() : item);
520
521                                                         e.preventDefault();
522                                                         return true;
523                                                 }
524                                         }
525                                 }
526                         }
527
528                         if (editor.settings.paste_data_images && dataTransfer) {
529                                 return processItems(dataTransfer.items) || processItems(dataTransfer.files);
530                         }
531                 }
532
533                 /**
534                  * Chrome on Andoid doesn't support proper clipboard access so we have no choice but to allow the browser default behavior.
535                  *
536                  * @param {Event} e Paste event object to check if it contains any data.
537                  * @return {Boolean} true/false if the clipboard is empty or not.
538                  */
539                 function isBrokenAndoidClipboardEvent(e) {
540                         var clipboardData = e.clipboardData;
541
542                         return navigator.userAgent.indexOf('Android') != -1 && clipboardData && clipboardData.items && clipboardData.items.length === 0;
543                 }
544
545                 function getCaretRangeFromEvent(e) {
546                         var doc = editor.getDoc(), rng, point;
547
548                         if (doc.caretPositionFromPoint) {
549                                 point = doc.caretPositionFromPoint(e.clientX, e.clientY);
550                                 rng = doc.createRange();
551                                 rng.setStart(point.offsetNode, point.offset);
552                                 rng.collapse(true);
553                         } else if (doc.caretRangeFromPoint) {
554                                 rng = doc.caretRangeFromPoint(e.clientX, e.clientY);
555                         } else if (doc.body.createTextRange) {
556                                 rng = doc.body.createTextRange();
557
558                                 try {
559                                         rng.moveToPoint(e.clientX, e.clientY);
560                                         rng.collapse(true);
561                                 } catch (ex) {
562                                         // Append to top or bottom depending on drop location
563                                         rng.collapse(e.clientY < doc.body.clientHeight);
564                                 }
565                         }
566
567                         return rng;
568                 }
569
570                 function hasContentType(clipboardContent, mimeType) {
571                         return mimeType in clipboardContent && clipboardContent[mimeType].length > 0;
572                 }
573
574                 function isKeyboardPasteEvent(e) {
575                         return (VK.metaKeyPressed(e) && e.keyCode == 86) || (e.shiftKey && e.keyCode == 45);
576                 }
577
578                 function registerEventHandlers() {
579                         editor.on('keydown', function(e) {
580                                 function removePasteBinOnKeyUp(e) {
581                                         // Ctrl+V or Shift+Insert
582                                         if (isKeyboardPasteEvent(e) && !e.isDefaultPrevented()) {
583                                                 removePasteBin();
584                                         }
585                                 }
586
587                                 // Ctrl+V or Shift+Insert
588                                 if (isKeyboardPasteEvent(e) && !e.isDefaultPrevented()) {
589                                         keyboardPastePlainTextState = e.shiftKey && e.keyCode == 86;
590
591                                         // Edge case on Safari on Mac where it doesn't handle Cmd+Shift+V correctly
592                                         // it fires the keydown but no paste or keyup so we are left with a paste bin
593                                         if (keyboardPastePlainTextState && Env.webkit && navigator.userAgent.indexOf('Version/') != -1) {
594                                                 return;
595                                         }
596
597                                         // Prevent undoManager keydown handler from making an undo level with the pastebin in it
598                                         e.stopImmediatePropagation();
599
600                                         keyboardPasteTimeStamp = new Date().getTime();
601
602                                         // IE doesn't support Ctrl+Shift+V and it doesn't even produce a paste event
603                                         // so lets fake a paste event and let IE use the execCommand/dataTransfer methods
604                                         if (Env.ie && keyboardPastePlainTextState) {
605                                                 e.preventDefault();
606                                                 editor.fire('paste', {ieFake: true});
607                                                 return;
608                                         }
609
610                                         removePasteBin();
611                                         createPasteBin();
612
613                                         // Remove pastebin if we get a keyup and no paste event
614                                         // For example pasting a file in IE 11 will not produce a paste event
615                                         editor.once('keyup', removePasteBinOnKeyUp);
616                                         editor.once('paste', function() {
617                                                 editor.off('keyup', removePasteBinOnKeyUp);
618                                         });
619                                 }
620                         });
621
622                         editor.on('paste', function(e) {
623                                 // Getting content from the Clipboard can take some time
624                                 var clipboardTimer = new Date().getTime();
625                                 var clipboardContent = getClipboardContent(e);
626                                 var clipboardDelay = new Date().getTime() - clipboardTimer;
627
628                                 var isKeyBoardPaste = (new Date().getTime() - keyboardPasteTimeStamp - clipboardDelay) < 1000;
629                                 var plainTextMode = self.pasteFormat == "text" || keyboardPastePlainTextState;
630
631                                 keyboardPastePlainTextState = false;
632
633                                 if (e.isDefaultPrevented() || isBrokenAndoidClipboardEvent(e)) {
634                                         removePasteBin();
635                                         return;
636                                 }
637
638                                 if (pasteImageData(e)) {
639                                         removePasteBin();
640                                         return;
641                                 }
642
643                                 // Not a keyboard paste prevent default paste and try to grab the clipboard contents using different APIs
644                                 if (!isKeyBoardPaste) {
645                                         e.preventDefault();
646                                 }
647
648                                 // Try IE only method if paste isn't a keyboard paste
649                                 if (Env.ie && (!isKeyBoardPaste || e.ieFake)) {
650                                         createPasteBin();
651
652                                         editor.dom.bind(pasteBinElm, 'paste', function(e) {
653                                                 e.stopPropagation();
654                                         });
655
656                                         editor.getDoc().execCommand('Paste', false, null);
657                                         clipboardContent["text/html"] = getPasteBinHtml();
658                                 }
659
660                                 setTimeout(function() {
661                                         var content;
662
663                                         // Grab HTML from Clipboard API or paste bin as a fallback
664                                         if (hasContentType(clipboardContent, 'text/html')) {
665                                                 content = clipboardContent['text/html'];
666                                         } else {
667                                                 content = getPasteBinHtml();
668
669                                                 // If paste bin is empty try using plain text mode
670                                                 // since that is better than nothing right
671                                                 if (content == pasteBinDefaultContent) {
672                                                         plainTextMode = true;
673                                                 }
674                                         }
675
676                                         content = Utils.trimHtml(content);
677
678                                         // WebKit has a nice bug where it clones the paste bin if you paste from for example notepad
679                                         // so we need to force plain text mode in this case
680                                         if (pasteBinElm && pasteBinElm.firstChild && pasteBinElm.firstChild.id === 'mcepastebin') {
681                                                 plainTextMode = true;
682                                         }
683
684                                         removePasteBin();
685
686                                         // If we got nothing from clipboard API and pastebin then we could try the last resort: plain/text
687                                         if (!content.length) {
688                                                 plainTextMode = true;
689                                         }
690
691                                         // Grab plain text from Clipboard API or convert existing HTML to plain text
692                                         if (plainTextMode) {
693                                                 // Use plain text contents from Clipboard API unless the HTML contains paragraphs then
694                                                 // we should convert the HTML to plain text since works better when pasting HTML/Word contents as plain text
695                                                 if (hasContentType(clipboardContent, 'text/plain') && content.indexOf('</p>') == -1) {
696                                                         content = clipboardContent['text/plain'];
697                                                 } else {
698                                                         content = Utils.innerText(content);
699                                                 }
700                                         }
701
702                                         // If the content is the paste bin default HTML then it was
703                                         // impossible to get the cliboard data out.
704                                         if (content == pasteBinDefaultContent) {
705                                                 if (!isKeyBoardPaste) {
706                                                         editor.windowManager.alert('Please use Ctrl+V/Cmd+V keyboard shortcuts to paste contents.');
707                                                 }
708
709                                                 return;
710                                         }
711
712                                         if (plainTextMode) {
713                                                 pasteText(content);
714                                         } else {
715                                                 pasteHtml(content);
716                                         }
717                                 }, 0);
718                         });
719
720                         editor.on('dragstart dragend', function(e) {
721                                 draggingInternally = e.type == 'dragstart';
722                         });
723
724                         editor.on('drop', function(e) {
725                                 var rng = getCaretRangeFromEvent(e);
726
727                                 if (e.isDefaultPrevented() || draggingInternally) {
728                                         return;
729                                 }
730
731                                 if (pasteImageData(e, rng)) {
732                                         return;
733                                 }
734
735                                 if (rng && editor.settings.paste_filter_drop !== false) {
736                                         var dropContent = getDataTransferItems(e.dataTransfer);
737                                         var content = dropContent['mce-internal'] || dropContent['text/html'] || dropContent['text/plain'];
738
739                                         if (content) {
740                                                 e.preventDefault();
741
742                                                 editor.undoManager.transact(function() {
743                                                         if (dropContent['mce-internal']) {
744                                                                 editor.execCommand('Delete');
745                                                         }
746
747                                                         editor.selection.setRng(rng);
748
749                                                         content = Utils.trimHtml(content);
750
751                                                         if (!dropContent['text/html']) {
752                                                                 pasteText(content);
753                                                         } else {
754                                                                 pasteHtml(content);
755                                                         }
756                                                 });
757                                         }
758                                 }
759                         });
760
761                         editor.on('dragover dragend', function(e) {
762                                 var i, dataTransfer = e.dataTransfer;
763
764                                 if (editor.settings.paste_data_images && dataTransfer) {
765                                         for (i = 0; i < dataTransfer.types.length; i++) {
766                                                 // Prevent default if we have files dragged into the editor since the pasteImageData handles that
767                                                 if (dataTransfer.types[i] == "Files") {
768                                                         e.preventDefault();
769                                                         return false;
770                                                 }
771                                         }
772                                 }
773                         });
774                 }
775
776                 self.pasteHtml = pasteHtml;
777                 self.pasteText = pasteText;
778
779                 editor.on('preInit', function() {
780                         registerEventHandlers();
781
782                         // Remove all data images from paste for example from Gecko
783                         // except internal images like video elements
784                         editor.parser.addNodeFilter('img', function(nodes) {
785                                 if (!editor.settings.paste_data_images) {
786                                         var i = nodes.length;
787
788                                         while (i--) {
789                                                 var src = nodes[i].attributes.map.src;
790
791                                                 // Some browsers automatically produce data uris on paste
792                                                 // Safari on Mac produces webkit-fake-url see: https://bugs.webkit.org/show_bug.cgi?id=49141
793                                                 if (src && /^(data:image|webkit\-fake\-url)/.test(src)) {
794                                                         if (!nodes[i].attr('data-mce-object') && src !== Env.transparentSrc) {
795                                                                 nodes[i].remove();
796                                                         }
797                                                 }
798                                         }
799                                 }
800                         });
801                 });
802         };
803 });
804
805 // Included from: js/tinymce/plugins/paste/classes/WordFilter.js
806
807 /**
808  * WordFilter.js
809  *
810  * Copyright, Moxiecode Systems AB
811  * Released under LGPL License.
812  *
813  * License: http://www.tinymce.com/license
814  * Contributing: http://www.tinymce.com/contributing
815  */
816
817 /**
818  * This class parses word HTML into proper TinyMCE markup.
819  *
820  * @class tinymce.pasteplugin.Quirks
821  * @private
822  */
823 define("tinymce/pasteplugin/WordFilter", [
824         "tinymce/util/Tools",
825         "tinymce/html/DomParser",
826         "tinymce/html/Schema",
827         "tinymce/html/Serializer",
828         "tinymce/html/Node",
829         "tinymce/pasteplugin/Utils"
830 ], function(Tools, DomParser, Schema, Serializer, Node, Utils) {
831         /**
832          * Checks if the specified content is from any of the following sources: MS Word/Office 365/Google docs.
833          */
834         function isWordContent(content) {
835                 return (
836                         (/<font face="Times New Roman"|class="?Mso|style="[^"]*\bmso-|style='[^'']*\bmso-|w:WordDocument/i).test(content) ||
837                         (/class="OutlineElement/).test(content) ||
838                         (/id="?docs\-internal\-guid\-/.test(content))
839                 );
840         }
841
842         /**
843          * Checks if the specified text starts with "1. " or "a. " etc.
844          */
845         function isNumericList(text) {
846                 var found, patterns;
847
848                 patterns = [
849                         /^[IVXLMCD]{1,2}\.[ \u00a0]/,  // Roman upper case
850                         /^[ivxlmcd]{1,2}\.[ \u00a0]/,  // Roman lower case
851                         /^[a-z]{1,2}[\.\)][ \u00a0]/,  // Alphabetical a-z
852                         /^[A-Z]{1,2}[\.\)][ \u00a0]/,  // Alphabetical A-Z
853                         /^[0-9]+\.[ \u00a0]/,          // Numeric lists
854                         /^[\u3007\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d]+\.[ \u00a0]/, // Japanese
855                         /^[\u58f1\u5f10\u53c2\u56db\u4f0d\u516d\u4e03\u516b\u4e5d\u62fe]+\.[ \u00a0]/  // Chinese
856                 ];
857
858                 text = text.replace(/^[\u00a0 ]+/, '');
859
860                 Tools.each(patterns, function(pattern) {
861                         if (pattern.test(text)) {
862                                 found = true;
863                                 return false;
864                         }
865                 });
866
867                 return found;
868         }
869
870         function isBulletList(text) {
871                 return /^[\s\u00a0]*[\u2022\u00b7\u00a7\u00d8\u25CF]\s*/.test(text);
872         }
873
874         function WordFilter(editor) {
875                 var settings = editor.settings;
876
877                 editor.on('BeforePastePreProcess', function(e) {
878                         var content = e.content, retainStyleProperties, validStyles;
879
880                         retainStyleProperties = settings.paste_retain_style_properties;
881                         if (retainStyleProperties) {
882                                 validStyles = Tools.makeMap(retainStyleProperties.split(/[, ]/));
883                         }
884
885                         /**
886                          * Converts fake bullet and numbered lists to real semantic OL/UL.
887                          *
888                          * @param {tinymce.html.Node} node Root node to convert children of.
889                          */
890                         function convertFakeListsToProperLists(node) {
891                                 var currentListNode, prevListNode, lastLevel = 1;
892
893                                 function getText(node) {
894                                         var txt = '';
895
896                                         if (node.type === 3) {
897                                                 return node.value;
898                                         }
899
900                                         if ((node = node.firstChild)) {
901                                                 do {
902                                                         txt += getText(node);
903                                                 } while ((node = node.next));
904                                         }
905
906                                         return txt;
907                                 }
908
909                                 function trimListStart(node, regExp) {
910                                         if (node.type === 3) {
911                                                 if (regExp.test(node.value)) {
912                                                         node.value = node.value.replace(regExp, '');
913                                                         return false;
914                                                 }
915                                         }
916
917                                         if ((node = node.firstChild)) {
918                                                 do {
919                                                         if (!trimListStart(node, regExp)) {
920                                                                 return false;
921                                                         }
922                                                 } while ((node = node.next));
923                                         }
924
925                                         return true;
926                                 }
927
928                                 function removeIgnoredNodes(node) {
929                                         if (node._listIgnore) {
930                                                 node.remove();
931                                                 return;
932                                         }
933
934                                         if ((node = node.firstChild)) {
935                                                 do {
936                                                         removeIgnoredNodes(node);
937                                                 } while ((node = node.next));
938                                         }
939                                 }
940
941                                 function convertParagraphToLi(paragraphNode, listName, start) {
942                                         var level = paragraphNode._listLevel || lastLevel;
943
944                                         // Handle list nesting
945                                         if (level != lastLevel) {
946                                                 if (level < lastLevel) {
947                                                         // Move to parent list
948                                                         if (currentListNode) {
949                                                                 currentListNode = currentListNode.parent.parent;
950                                                         }
951                                                 } else {
952                                                         // Create new list
953                                                         prevListNode = currentListNode;
954                                                         currentListNode = null;
955                                                 }
956                                         }
957
958                                         if (!currentListNode || currentListNode.name != listName) {
959                                                 prevListNode = prevListNode || currentListNode;
960                                                 currentListNode = new Node(listName, 1);
961
962                                                 if (start > 1) {
963                                                         currentListNode.attr('start', '' + start);
964                                                 }
965
966                                                 paragraphNode.wrap(currentListNode);
967                                         } else {
968                                                 currentListNode.append(paragraphNode);
969                                         }
970
971                                         paragraphNode.name = 'li';
972
973                                         // Append list to previous list if it exists
974                                         if (level > lastLevel && prevListNode) {
975                                                 prevListNode.lastChild.append(currentListNode);
976                                         }
977
978                                         lastLevel = level;
979
980                                         // Remove start of list item "1. " or "&middot; " etc
981                                         removeIgnoredNodes(paragraphNode);
982                                         trimListStart(paragraphNode, /^\u00a0+/);
983                                         trimListStart(paragraphNode, /^\s*([\u2022\u00b7\u00a7\u00d8\u25CF]|\w+\.)/);
984                                         trimListStart(paragraphNode, /^\u00a0+/);
985                                 }
986
987                                 var paragraphs = node.getAll('p');
988
989                                 for (var i = 0; i < paragraphs.length; i++) {
990                                         node = paragraphs[i];
991
992                                         if (node.name == 'p' && node.firstChild) {
993                                                 // Find first text node in paragraph
994                                                 var nodeText = getText(node);
995
996                                                 // Detect unordered lists look for bullets
997                                                 if (isBulletList(nodeText)) {
998                                                         convertParagraphToLi(node, 'ul');
999                                                         continue;
1000                                                 }
1001
1002                                                 // Detect ordered lists 1., a. or ixv.
1003                                                 if (isNumericList(nodeText)) {
1004                                                         // Parse OL start number
1005                                                         var matches = /([0-9])\./.exec(nodeText);
1006                                                         var start = 1;
1007                                                         if (matches) {
1008                                                                 start = parseInt(matches[1], 10);
1009                                                         }
1010
1011                                                         convertParagraphToLi(node, 'ol', start);
1012                                                         continue;
1013                                                 }
1014
1015                                                 // Convert paragraphs marked as lists but doesn't look like anything
1016                                                 if (node._listLevel) {
1017                                                         convertParagraphToLi(node, 'ul', 1);
1018                                                         continue;
1019                                                 }
1020
1021                                                 currentListNode = null;
1022                                         }
1023                                 }
1024                         }
1025
1026                         function filterStyles(node, styleValue) {
1027                                 var outputStyles = {}, matches, styles = editor.dom.parseStyle(styleValue);
1028
1029                                 Tools.each(styles, function(value, name) {
1030                                         // Convert various MS styles to W3C styles
1031                                         switch (name) {
1032                                                 case 'mso-list':
1033                                                         // Parse out list indent level for lists
1034                                                         matches = /\w+ \w+([0-9]+)/i.exec(styleValue);
1035                                                         if (matches) {
1036                                                                 node._listLevel = parseInt(matches[1], 10);
1037                                                         }
1038
1039                                                         // Remove these nodes <span style="mso-list:Ignore">o</span>
1040                                                         // Since the span gets removed we mark the text node and the span
1041                                                         if (/Ignore/i.test(value) && node.firstChild) {
1042                                                                 node._listIgnore = true;
1043                                                                 node.firstChild._listIgnore = true;
1044                                                         }
1045
1046                                                         break;
1047
1048                                                 case "horiz-align":
1049                                                         name = "text-align";
1050                                                         break;
1051
1052                                                 case "vert-align":
1053                                                         name = "vertical-align";
1054                                                         break;
1055
1056                                                 case "font-color":
1057                                                 case "mso-foreground":
1058                                                         name = "color";
1059                                                         break;
1060
1061                                                 case "mso-background":
1062                                                 case "mso-highlight":
1063                                                         name = "background";
1064                                                         break;
1065
1066                                                 case "font-weight":
1067                                                 case "font-style":
1068                                                         if (value != "normal") {
1069                                                                 outputStyles[name] = value;
1070                                                         }
1071                                                         return;
1072
1073                                                 case "mso-element":
1074                                                         // Remove track changes code
1075                                                         if (/^(comment|comment-list)$/i.test(value)) {
1076                                                                 node.remove();
1077                                                                 return;
1078                                                         }
1079
1080                                                         break;
1081                                         }
1082
1083                                         if (name.indexOf('mso-comment') === 0) {
1084                                                 node.remove();
1085                                                 return;
1086                                         }
1087
1088                                         // Never allow mso- prefixed names
1089                                         if (name.indexOf('mso-') === 0) {
1090                                                 return;
1091                                         }
1092
1093                                         // Output only valid styles
1094                                         if (retainStyleProperties == "all" || (validStyles && validStyles[name])) {
1095                                                 outputStyles[name] = value;
1096                                         }
1097                                 });
1098
1099                                 // Convert bold style to "b" element
1100                                 if (/(bold)/i.test(outputStyles["font-weight"])) {
1101                                         delete outputStyles["font-weight"];
1102                                         node.wrap(new Node("b", 1));
1103                                 }
1104
1105                                 // Convert italic style to "i" element
1106                                 if (/(italic)/i.test(outputStyles["font-style"])) {
1107                                         delete outputStyles["font-style"];
1108                                         node.wrap(new Node("i", 1));
1109                                 }
1110
1111                                 // Serialize the styles and see if there is something left to keep
1112                                 outputStyles = editor.dom.serializeStyle(outputStyles, node.name);
1113                                 if (outputStyles) {
1114                                         return outputStyles;
1115                                 }
1116
1117                                 return null;
1118                         }
1119
1120                         if (settings.paste_enable_default_filters === false) {
1121                                 return;
1122                         }
1123
1124                         // Detect is the contents is Word junk HTML
1125                         if (isWordContent(e.content)) {
1126                                 e.wordContent = true; // Mark it for other processors
1127
1128                                 // Remove basic Word junk
1129                                 content = Utils.filter(content, [
1130                                         // Word comments like conditional comments etc
1131                                         /<!--[\s\S]+?-->/gi,
1132
1133                                         // Remove comments, scripts (e.g., msoShowComment), XML tag, VML content,
1134                                         // MS Office namespaced tags, and a few other tags
1135                                         /<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi,
1136
1137                                         // Convert <s> into <strike> for line-though
1138                                         [/<(\/?)s>/gi, "<$1strike>"],
1139
1140                                         // Replace nsbp entites to char since it's easier to handle
1141                                         [/&nbsp;/gi, "\u00a0"],
1142
1143                                         // Convert <span style="mso-spacerun:yes">___</span> to string of alternating
1144                                         // breaking/non-breaking spaces of same length
1145                                         [/<span\s+style\s*=\s*"\s*mso-spacerun\s*:\s*yes\s*;?\s*"\s*>([\s\u00a0]*)<\/span>/gi,
1146                                                 function(str, spaces) {
1147                                                         return (spaces.length > 0) ?
1148                                                                 spaces.replace(/./, " ").slice(Math.floor(spaces.length / 2)).split("").join("\u00a0") : "";
1149                                                 }
1150                                         ]
1151                                 ]);
1152
1153                                 var validElements = settings.paste_word_valid_elements;
1154                                 if (!validElements) {
1155                                         validElements = '-strong/b,-em/i,-span,-p,-ol,-ul,-li,-h1,-h2,-h3,-h4,-h5,-h6,-p/div,' +
1156                                                 '-table[width],-tr,-td[colspan|rowspan|width],-th,-thead,-tfoot,-tbody,-a[href|name],sub,sup,strike,br,del';
1157                                 }
1158
1159                                 // Setup strict schema
1160                                 var schema = new Schema({
1161                                         valid_elements: validElements,
1162                                         valid_children: '-li[p]'
1163                                 });
1164
1165                                 // Add style/class attribute to all element rules since the user might have removed them from
1166                                 // paste_word_valid_elements config option and we need to check them for properties
1167                                 Tools.each(schema.elements, function(rule) {
1168                                         if (!rule.attributes["class"]) {
1169                                                 rule.attributes["class"] = {};
1170                                                 rule.attributesOrder.push("class");
1171                                         }
1172
1173                                         if (!rule.attributes.style) {
1174                                                 rule.attributes.style = {};
1175                                                 rule.attributesOrder.push("style");
1176                                         }
1177                                 });
1178
1179                                 // Parse HTML into DOM structure
1180                                 var domParser = new DomParser({}, schema);
1181
1182                                 // Filter styles to remove "mso" specific styles and convert some of them
1183                                 domParser.addAttributeFilter('style', function(nodes) {
1184                                         var i = nodes.length, node;
1185
1186                                         while (i--) {
1187                                                 node = nodes[i];
1188                                                 node.attr('style', filterStyles(node, node.attr('style')));
1189
1190                                                 // Remove pointess spans
1191                                                 if (node.name == 'span' && node.parent && !node.attributes.length) {
1192                                                         node.unwrap();
1193                                                 }
1194                                         }
1195                                 });
1196
1197                                 // Check the class attribute for comments or del items and remove those
1198                                 domParser.addAttributeFilter('class', function(nodes) {
1199                                         var i = nodes.length, node, className;
1200
1201                                         while (i--) {
1202                                                 node = nodes[i];
1203
1204                                                 className = node.attr('class');
1205                                                 if (/^(MsoCommentReference|MsoCommentText|msoDel|MsoCaption)$/i.test(className)) {
1206                                                         node.remove();
1207                                                 }
1208
1209                                                 node.attr('class', null);
1210                                         }
1211                                 });
1212
1213                                 // Remove all del elements since we don't want the track changes code in the editor
1214                                 domParser.addNodeFilter('del', function(nodes) {
1215                                         var i = nodes.length;
1216
1217                                         while (i--) {
1218                                                 nodes[i].remove();
1219                                         }
1220                                 });
1221
1222                                 // Keep some of the links and anchors
1223                                 domParser.addNodeFilter('a', function(nodes) {
1224                                         var i = nodes.length, node, href, name;
1225
1226                                         while (i--) {
1227                                                 node = nodes[i];
1228                                                 href = node.attr('href');
1229                                                 name = node.attr('name');
1230
1231                                                 if (href && href.indexOf('#_msocom_') != -1) {
1232                                                         node.remove();
1233                                                         continue;
1234                                                 }
1235
1236                                                 if (href && href.indexOf('file://') === 0) {
1237                                                         href = href.split('#')[1];
1238                                                         if (href) {
1239                                                                 href = '#' + href;
1240                                                         }
1241                                                 }
1242
1243                                                 if (!href && !name) {
1244                                                         node.unwrap();
1245                                                 } else {
1246                                                         // Remove all named anchors that aren't specific to TOC, Footnotes or Endnotes
1247                                                         if (name && !/^_?(?:toc|edn|ftn)/i.test(name)) {
1248                                                                 node.unwrap();
1249                                                                 continue;
1250                                                         }
1251
1252                                                         node.attr({
1253                                                                 href: href,
1254                                                                 name: name
1255                                                         });
1256                                                 }
1257                                         }
1258                                 });
1259
1260                                 // Parse into DOM structure
1261                                 var rootNode = domParser.parse(content);
1262
1263                                 // Process DOM
1264                                 convertFakeListsToProperLists(rootNode);
1265
1266                                 // Serialize DOM back to HTML
1267                                 e.content = new Serializer({}, schema).serialize(rootNode);
1268                         }
1269                 });
1270         }
1271
1272         WordFilter.isWordContent = isWordContent;
1273
1274         return WordFilter;
1275 });
1276
1277 // Included from: js/tinymce/plugins/paste/classes/Quirks.js
1278
1279 /**
1280  * Quirks.js
1281  *
1282  * Copyright, Moxiecode Systems AB
1283  * Released under LGPL License.
1284  *
1285  * License: http://www.tinymce.com/license
1286  * Contributing: http://www.tinymce.com/contributing
1287  */
1288
1289 /**
1290  * This class contains various fixes for browsers. These issues can not be feature
1291  * detected since we have no direct control over the clipboard. However we might be able
1292  * to remove some of these fixes once the browsers gets updated/fixed.
1293  *
1294  * @class tinymce.pasteplugin.Quirks
1295  * @private
1296  */
1297 define("tinymce/pasteplugin/Quirks", [
1298         "tinymce/Env",
1299         "tinymce/util/Tools",
1300         "tinymce/pasteplugin/WordFilter",
1301         "tinymce/pasteplugin/Utils"
1302 ], function(Env, Tools, WordFilter, Utils) {
1303         "use strict";
1304
1305         return function(editor) {
1306                 function addPreProcessFilter(filterFunc) {
1307                         editor.on('BeforePastePreProcess', function(e) {
1308                                 e.content = filterFunc(e.content);
1309                         });
1310                 }
1311
1312                 /**
1313                  * Removes BR elements after block elements. IE9 has a nasty bug where it puts a BR element after each
1314                  * block element when pasting from word. This removes those elements.
1315                  *
1316                  * This:
1317                  *  <p>a</p><br><p>b</p>
1318                  *
1319                  * Becomes:
1320                  *  <p>a</p><p>b</p>
1321                  */
1322                 function removeExplorerBrElementsAfterBlocks(html) {
1323                         // Only filter word specific content
1324                         if (!WordFilter.isWordContent(html)) {
1325                                 return html;
1326                         }
1327
1328                         // Produce block regexp based on the block elements in schema
1329                         var blockElements = [];
1330
1331                         Tools.each(editor.schema.getBlockElements(), function(block, blockName) {
1332                                 blockElements.push(blockName);
1333                         });
1334
1335                         var explorerBlocksRegExp = new RegExp(
1336                                 '(?:<br>&nbsp;[\\s\\r\\n]+|<br>)*(<\\/?(' + blockElements.join('|') + ')[^>]*>)(?:<br>&nbsp;[\\s\\r\\n]+|<br>)*',
1337                                 'g'
1338                         );
1339
1340                         // Remove BR:s from: <BLOCK>X</BLOCK><BR>
1341                         html = Utils.filter(html, [
1342                                 [explorerBlocksRegExp, '$1']
1343                         ]);
1344
1345                         // IE9 also adds an extra BR element for each soft-linefeed and it also adds a BR for each word wrap break
1346                         html = Utils.filter(html, [
1347                                 [/<br><br>/g, '<BR><BR>'], // Replace multiple BR elements with uppercase BR to keep them intact
1348                                 [/<br>/g, ' '],            // Replace single br elements with space since they are word wrap BR:s
1349                                 [/<BR><BR>/g, '<br>']      // Replace back the double brs but into a single BR
1350                         ]);
1351
1352                         return html;
1353                 }
1354
1355                 /**
1356                  * WebKit has a nasty bug where the all computed styles gets added to style attributes when copy/pasting contents.
1357                  * This fix solves that by simply removing the whole style attribute.
1358                  *
1359                  * The paste_webkit_styles option can be set to specify what to keep:
1360                  *  paste_webkit_styles: "none" // Keep no styles
1361                  *  paste_webkit_styles: "all", // Keep all of them
1362                  *  paste_webkit_styles: "font-weight color" // Keep specific ones
1363                  *
1364                  * @param {String} content Content that needs to be processed.
1365                  * @return {String} Processed contents.
1366                  */
1367                 function removeWebKitStyles(content) {
1368                         // Passthrough all styles from Word and let the WordFilter handle that junk
1369                         if (WordFilter.isWordContent(content)) {
1370                                 return content;
1371                         }
1372
1373                         // Filter away styles that isn't matching the target node
1374                         var webKitStyles = editor.settings.paste_webkit_styles;
1375
1376                         if (editor.settings.paste_remove_styles_if_webkit === false || webKitStyles == "all") {
1377                                 return content;
1378                         }
1379
1380                         if (webKitStyles) {
1381                                 webKitStyles = webKitStyles.split(/[, ]/);
1382                         }
1383
1384                         // Keep specific styles that doesn't match the current node computed style
1385                         if (webKitStyles) {
1386                                 var dom = editor.dom, node = editor.selection.getNode();
1387
1388                                 content = content.replace(/(<[^>]+) style="([^"]*)"([^>]*>)/gi, function(all, before, value, after) {
1389                                         var inputStyles = dom.parseStyle(value, 'span'), outputStyles = {};
1390
1391                                         if (webKitStyles === "none") {
1392                                                 return before + after;
1393                                         }
1394
1395                                         for (var i = 0; i < webKitStyles.length; i++) {
1396                                                 var inputValue = inputStyles[webKitStyles[i]], currentValue = dom.getStyle(node, webKitStyles[i], true);
1397
1398                                                 if (/color/.test(webKitStyles[i])) {
1399                                                         inputValue = dom.toHex(inputValue);
1400                                                         currentValue = dom.toHex(currentValue);
1401                                                 }
1402
1403                                                 if (currentValue != inputValue) {
1404                                                         outputStyles[webKitStyles[i]] = inputValue;
1405                                                 }
1406                                         }
1407
1408                                         outputStyles = dom.serializeStyle(outputStyles, 'span');
1409                                         if (outputStyles) {
1410                                                 return before + ' style="' + outputStyles + '"' + after;
1411                                         }
1412
1413                                         return before + after;
1414                                 });
1415                         } else {
1416                                 // Remove all external styles
1417                                 content = content.replace(/(<[^>]+) style="([^"]*)"([^>]*>)/gi, '$1$3');
1418                         }
1419
1420                         // Keep internal styles
1421                         content = content.replace(/(<[^>]+) data-mce-style="([^"]+)"([^>]*>)/gi, function(all, before, value, after) {
1422                                 return before + ' style="' + value + '"' + after;
1423                         });
1424
1425                         return content;
1426                 }
1427
1428                 // Sniff browsers and apply fixes since we can't feature detect
1429                 if (Env.webkit) {
1430                         addPreProcessFilter(removeWebKitStyles);
1431                 }
1432
1433                 if (Env.ie) {
1434                         addPreProcessFilter(removeExplorerBrElementsAfterBlocks);
1435                 }
1436         };
1437 });
1438
1439 // Included from: js/tinymce/plugins/paste/classes/Plugin.js
1440
1441 /**
1442  * Plugin.js
1443  *
1444  * Copyright, Moxiecode Systems AB
1445  * Released under LGPL License.
1446  *
1447  * License: http://www.tinymce.com/license
1448  * Contributing: http://www.tinymce.com/contributing
1449  */
1450
1451 /**
1452  * This class contains the tinymce plugin logic for the paste plugin.
1453  *
1454  * @class tinymce.pasteplugin.Plugin
1455  * @private
1456  */
1457 define("tinymce/pasteplugin/Plugin", [
1458         "tinymce/PluginManager",
1459         "tinymce/pasteplugin/Clipboard",
1460         "tinymce/pasteplugin/WordFilter",
1461         "tinymce/pasteplugin/Quirks"
1462 ], function(PluginManager, Clipboard, WordFilter, Quirks) {
1463         var userIsInformed;
1464
1465         PluginManager.add('paste', function(editor) {
1466                 var self = this, clipboard, settings = editor.settings;
1467
1468                 function togglePlainTextPaste() {
1469                         if (clipboard.pasteFormat == "text") {
1470                                 this.active(false);
1471                                 clipboard.pasteFormat = "html";
1472                         } else {
1473                                 clipboard.pasteFormat = "text";
1474                                 this.active(true);
1475
1476                                 if (!userIsInformed) {
1477                                         editor.windowManager.alert(
1478                                                 'Paste is now in plain text mode. Contents will now ' +
1479                                                 'be pasted as plain text until you toggle this option off.'
1480                                         );
1481
1482                                         userIsInformed = true;
1483                                 }
1484                         }
1485                 }
1486
1487                 self.clipboard = clipboard = new Clipboard(editor);
1488                 self.quirks = new Quirks(editor);
1489                 self.wordFilter = new WordFilter(editor);
1490
1491                 if (editor.settings.paste_as_text) {
1492                         self.clipboard.pasteFormat = "text";
1493                 }
1494
1495                 if (settings.paste_preprocess) {
1496                         editor.on('PastePreProcess', function(e) {
1497                                 settings.paste_preprocess.call(self, self, e);
1498                         });
1499                 }
1500
1501                 if (settings.paste_postprocess) {
1502                         editor.on('PastePostProcess', function(e) {
1503                                 settings.paste_postprocess.call(self, self, e);
1504                         });
1505                 }
1506
1507                 editor.addCommand('mceInsertClipboardContent', function(ui, value) {
1508                         if (value.content) {
1509                                 self.clipboard.pasteHtml(value.content);
1510                         }
1511
1512                         if (value.text) {
1513                                 self.clipboard.pasteText(value.text);
1514                         }
1515                 });
1516
1517                 // Block all drag/drop events
1518                 if (editor.paste_block_drop) {
1519                         editor.on('dragend dragover draggesture dragdrop drop drag', function(e) {
1520                                 e.preventDefault();
1521                                 e.stopPropagation();
1522                         });
1523                 }
1524
1525                 // Prevent users from dropping data images on Gecko
1526                 if (!editor.settings.paste_data_images) {
1527                         editor.on('drop', function(e) {
1528                                 var dataTransfer = e.dataTransfer;
1529
1530                                 if (dataTransfer && dataTransfer.files && dataTransfer.files.length > 0) {
1531                                         e.preventDefault();
1532                                 }
1533                         });
1534                 }
1535
1536                 editor.addButton('pastetext', {
1537                         icon: 'pastetext',
1538                         tooltip: 'Paste as text',
1539                         onclick: togglePlainTextPaste,
1540                         active: self.clipboard.pasteFormat == "text"
1541                 });
1542
1543                 editor.addMenuItem('pastetext', {
1544                         text: 'Paste as text',
1545                         selectable: true,
1546                         active: clipboard.pasteFormat,
1547                         onclick: togglePlainTextPaste
1548                 });
1549         });
1550 });
1551
1552 expose(["tinymce/pasteplugin/Utils","tinymce/pasteplugin/WordFilter"]);
1553 })(this);